Application Context Implementation #4

Closed
kayomn wants to merge 93 commits from event-loop-dev into main
8 changed files with 407 additions and 262 deletions
Showing only changes of commit ac95993a4b - Show all commits

View File

@ -1,70 +0,0 @@
const io = @import("io.zig");
///
/// Number formatting modes supported by [writeInt].
///
pub const Radix = enum {
binary,
tinary,
quaternary,
quinary,
senary,
septenary,
octal,
nonary,
decimal,
undecimal,
duodecimal,
tridecimal,
tetradecimal,
pentadecimal,
hexadecimal,
};
///
/// Writes `value` as a ASCII / UTF-8 encoded integer to `writer`, returning `true` if the full
/// sequence was successfully written, otherwise `false`.
///
/// The `radix` argument identifies which base system to format `value` as.
///
pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) bool {
const Int = @TypeOf(value);
const type_info = @typeInfo(Int);
switch (type_info) {
.Int => {
if (value == 0) return writer.writeByte('0');
// TODO: Unhardcode this as it will break with large ints.
var buffer = std.mem.zeroes([28]u8);
var buffer_count = @as(usize, 0);
var n1 = value;
if ((type_info.Int.signedness == .signed) and (value < 0)) {
// Negative value.
n1 = -value;
buffer[0] = '-';
buffer_count += 1;
}
while (n1 != 0) {
const base = @enumToInt(radix);
buffer[buffer_count] = @intCast(u8, (n1 % base) + '0');
n1 = (n1 / base);
buffer_count += 1;
}
for (buffer[0 .. (buffer_count / 2)]) |_, i|
std.mem.swap(u8, &buffer[i], &buffer[buffer_count - i - 1]);
return (writer.call(.{buffer[0 .. buffer_count]}) == buffer_count);
},
// Cast comptime int into known-size integer and try again.
.ComptimeInt => return writer.
writeInt(radix, @intCast(std.math.IntFittingRange(value, value), value)),
else => @compileError("value must be of type int"),
}
}

View File

@ -1,11 +1,12 @@
const math = @import("./math.zig");
const meta = @import("./meta.zig");
const stack = @import("./stack.zig");
const std = @import("std");
const testing = @import("./testing.zig");
///
///
///
pub const Allocator = std.mem.Allocator;
pub const Allocator = @import("std").mem.Allocator;
///
/// Closure that captures a reference to readable resources like block devices, memory buffers,
@ -31,6 +32,26 @@ pub fn Spliterator(comptime Element: type) type {
return (self.source.len != 0);
}
test "Check has data" {
var empty_spliterator = Spliterator(u8){
.source = "",
.delimiter = "/",
};
try testing.expect(!empty_spliterator.hasNext());
var stateful_spliterator = Spliterator(u8){
.source = "data",
.delimiter = "/",
};
try testing.expect(stateful_spliterator.hasNext());
_ = try stateful_spliterator.next();
try testing.expect(!stateful_spliterator.hasNext());
}
///
/// Iterates on `self` and returns the next view of [Spliterator.source] that matches
/// [Spliterator.delimiter], or `null` if there is no more data to be processed.
@ -38,7 +59,11 @@ pub fn Spliterator(comptime Element: type) type {
pub fn next(self: *Self) ?[]const Element {
if (!self.hasNext()) return null;
if (std.mem.indexOfPos(Element, self.source, 0, self.delimiter)) |index| {
if (findFirstOf(Element, self.source, self.delimiter, struct {
fn testEquality(this: Element, that: Element) bool {
return this == that;
}
}.testEquality)) |index| {
defer self.source = self.source[(index + self.delimiter.len) .. self.source.len];
return self.source[0 .. index];
@ -48,12 +73,8 @@ pub fn Spliterator(comptime Element: type) type {
return self.source;
}
};
}
test "Spliterating text" {
const testing = std.testing;
test "Iterate through data" {
// Single-character delimiter.
{
var spliterator = Spliterator(u8){
@ -61,7 +82,9 @@ test "Spliterating text" {
.delimiter = ".",
};
const components = [_][]const u8{"single", "character", "separated", "hello", "world"};
const components = [_][]const u8{"single",
"character", "separated", "hello", "world"};
var index = @as(usize, 0);
while (spliterator.next()) |split| : (index += 1) {
@ -83,6 +106,8 @@ test "Spliterating text" {
try testing.expect(equals(u8, split, components[index]));
}
}
}
};
}
///
@ -102,7 +127,6 @@ pub fn begins(comptime Element: type, elements: []const Element, with: []const E
test "Check memory begins with" {
const bytes_sequence = &.{69, 42};
const testing = std.testing;
try testing.expect(begins(u8, &.{69, 42, 0, 89}, bytes_sequence));
try testing.expect(!begins(u8, &.{69, 89, 42, 0}, bytes_sequence));
@ -133,11 +157,9 @@ pub fn bytesOf(pointer: anytype) switch (@typeInfo(@TypeOf(pointer))) {
}
test "Bytes of types" {
const testing = std.testing;
var foo: u32 = 10;
testing.expectEqual(bytesOf(&foo), 0x0a);
try testing.expect(bytesOf(&foo)[0] == 0x0a);
}
///
@ -145,27 +167,38 @@ test "Bytes of types" {
/// sequences, otherwise `0` if they are identical.
///
pub fn compareBytes(this: []const u8, that: []const u8) isize {
var cursor: usize = 0;
const range = math.min(usize, this.len, that.len);
var index: usize = 0;
while (cursor != this.len) : (cursor += 1) {
const this_byte = this[cursor];
while (index < range) : (index += 1) {
const difference = (this[index] - that[index]);
if (cursor != that.len) return this_byte;
const that_byte = that[cursor];
if (this_byte != that_byte) return (this_byte - that_byte);
if (difference != 0) return difference;
}
return 0;
return (@intCast(isize, this.len) - @intCast(isize, that.len));
}
test "Compare bytes" {
const testing = std.testing;
try testing.expect(compareBytes(&.{69, 42, 0}, &.{69, 42, 0}) == 0);
try testing.expect(compareBytes(&.{69, 42, 11}, &.{69, 42}) == 1);
try testing.expect(compareBytes(&.{69, 42}, &.{69, 42, 11}) == -1);
}
try testing.expectEquals(compareBytes(&.{69, 42, 0}, &.{69, 42, 0}), 0);
try testing.expectEquals(compareBytes(&.{69, 42, 0}, &.{69, 42}), 42);
try testing.expectEquals(compareBytes(&.{69, 42}, &.{69, 42, 0}), -42);
///
/// Copies the contents of `source` into `target`
///
pub fn copy(comptime Element: type, target: []Element, source: []const Element) void {
for (source) |element, index| target[index] = element;
}
test "Copy data" {
var buffer = [_]u32{0} ** 20;
const data = [_]u32{3, 20, 8000};
copy(u32, &buffer, &data);
for (data) |datum, index| try testing.expect(buffer[index] == datum);
}
///
@ -175,21 +208,72 @@ test "Compare bytes" {
pub fn equals(comptime Element: type, this: []const Element, that: []const Element) bool {
if (this.len != that.len) return false;
var i = std.mem.zeroes(usize);
var index: usize = 0;
while (i < this.len) : (i += 1) if (this[i] != that[i]) return false;
while (index < this.len) : (index += 1) if (this[index] != that[index]) return false;
return true;
}
test "Check memory is equals" {
test "Check memory is equal" {
const bytes_sequence = &.{69, 42, 0};
const testing = std.testing;
try testing.expect(equals(u8, bytes_sequence, bytes_sequence));
try testing.expect(!equals(u8, bytes_sequence, &.{69, 42}));
}
///
/// Searches for the first instance of an `Element` equal to `needle` in `haystack`, returning its
/// index or `null` if nothing was found.
///
pub fn findFirst(comptime Element: type, haystack: []const Element,
needle: Element, comptime testEquality: fn (Element, Element) bool) ?usize {
for (haystack) |element, index| if (testEquality(element, needle)) return index;
return null;
}
test "Find first of element" {
const haystack = &.{"", "", "foo"};
const testEquality = struct {
fn testEquality(this: []const u8, that: []const u8) bool {
return equals(u8, this, that);
}
}.testEquality;
try testing.expect(findFirst([]const u8, haystack, "foo", testEquality).? == 2);
try testing.expect(findFirst([]const u8, haystack, "bar", testEquality) == null);
}
///
/// Searches for the first instance of an `Element` sequence equal to the contents of `needle` in
/// `haystack`, returning the starting index or `null` if nothing was found.
///
pub fn findFirstOf(comptime Element: type, haystack: []const Element,
needle: []const Element, comptime testEquality: fn (Element, Element) bool) ?usize {
var cursor: usize = 0;
kayomn marked this conversation as resolved Outdated

Worth mentioning that it performs linear time, making it O(n) time complexity?

Worth mentioning that it performs linear time, making it O(n) time complexity?
const end = (haystack.len - needle.len);
walk_haystack: while (cursor <= end) : (cursor += 1) {
const range = (cursor + needle.len);
var index = cursor;
while (index < range) : (index += 1)
if (testEquality(haystack[index], needle[index])) continue: walk_haystack;
return cursor;
}
return null;
}
test "Find first of sequence" {
}
///
/// Returns a deterministic hash code compiled from each byte in `bytes`.
///
@ -205,12 +289,30 @@ pub fn hashBytes(bytes: []const u8) usize {
test "Hashing bytes" {
const bytes_sequence = &.{69, 42, 0};
const testing = std.testing;
try testing.expect(hashBytes(bytes_sequence) == hashBytes(bytes_sequence));
try testing.expect(hashBytes(bytes_sequence) != hashBytes(&.{69, 42}));
}
///
/// Swaps the `Data` in `this` with `that`.
///
pub fn swap(comptime Data: type, this: *Data, that: *Data) void {
const temp = this.*;
this.* = that.*;
that.* = temp;
}
test "Data swapping" {
var a: u64 = 0;
var b: u64 = 1;
swap(u64, &a, &b);
try testing.expect(a == 1);
try testing.expect(b == 0);
}
///
/// Returns a [Writer] that silently consumes all given data without failure and throws it away.
///
@ -218,7 +320,7 @@ test "Hashing bytes" {
/// sent somewhere for whatever reason.
///
pub fn nullWriter() Writer {
return Writer.capture(std.mem.zeroes(usize), struct {
return Writer.capture(@as(usize, 0), struct {
fn write(_: usize, buffer: []const u8) usize {
return buffer.len;
}
@ -226,11 +328,7 @@ pub fn nullWriter() Writer {
}
test "Null writing" {
const testing = std.testing;
{
const sequence = "foo";
try testing.expectEqual(nullWriter().apply(sequence), sequence.len);
}
try testing.expect(nullWriter().call(sequence) == sequence.len);
}

View File

@ -5,12 +5,17 @@
pub const io = @import("./io.zig");
///
/// Metaprogramming introspection and generation utilities.
/// Math types and functions with a focus on graphics-specific linear algebra.
///
pub const math = @import("./math.zig");
///
/// Metaprogramming introspection and generation.
///
pub const meta = @import("./meta.zig");
///
/// Sequential last-in first-out data structures.
/// Sequential, last-in first-out data structures.
///
pub const stack = @import("./stack.zig");
@ -19,9 +24,22 @@ pub const stack = @import("./stack.zig");
///
pub const table = @import("./table.zig");
///
/// Unit testing suite utilities.
///
pub const testing = @import("./testing.zig");
///
/// Unicode-encoded string analysis and processing with a focus on UTF-8 encoded text.
///
pub const unicode = @import("./unicode.zig");
test {
_ = io;
_ = math;
_ = meta;
_ = stack;
_ = table;
_ = testing;
_ = unicode;
}

26
src/core/math.zig Normal file
View File

@ -0,0 +1,26 @@
pub const IntFittingRange = @import("std").math.IntFittingRange;
///
kayomn marked this conversation as resolved Outdated

Warrants TODO comment mentioning that this stdlib dependency should be removed in future.

Warrants `TODO` comment mentioning that this stdlib dependency should be removed in future.
/// Returns the maximum value of `Integer`.
///
pub fn maxInt(comptime Integer: type) Integer {
return switch (@typeInfo(Integer)) {
.Int => |info| if (info.bits == 0) 0 else
((1 << (info.bits - @boolToInt(info.signedness == .signed))) - 1),
else => @compileError("`" ++ @typeName(Integer) ++ "` must be an int"),
};
}
///
/// Returns the lowest `Number` value between `this` and `that`.
///
pub fn min(comptime Number: type, this: Number, that: Number) Number {
return switch (@typeInfo(Number)) {
.Int, .Float, .ComptimeInt, .ComptimeFloat => if (this < that) this else that,
else => @compileError("`" ++ @typeName(Number) ++
"` must be an int, float, comptime_int, or comptime_float"),
};
}

View File

@ -1,5 +1,3 @@
const std = @import("std");
///
/// Returns the return type of the function type `Fn`.
///
@ -11,70 +9,13 @@ pub fn FnReturn(comptime Fn: type) type {
return type_info.Fn.return_type orelse void;
}
///
/// Returns a double-input single-output closure type where `A` represents the first input type, `B`
/// represents the second, and `Out` represents the output type, and `captures_size` represents the
/// size of the closure context.
///
pub fn BiFunction(comptime captures_size: usize, comptime A: type,
comptime B: type, comptime Out: type) type {
return struct {
applyErased: fn (*anyopaque, A, B) Out,
context: [captures_size]u8,
///
/// Function type.
///
const Self = @This();
///
/// Applies `a` and `b` to `self`, producing a result according to the current context data.
///
pub fn apply(self: *Self, a: A, b: B) Out {
return self.applyErased(&self.context, a, b);
}
///
/// Creates a new [Self] by capturing the `captures` value as the context and `call` as the
/// as the behavior executed when [apply] or [applyErased] is called.
///
/// The newly created [Self] is returned.
///
pub fn capture(captures: anytype, comptime call: fn (@TypeOf(captures), A, B) Out) Self {
const Captures = @TypeOf(captures);
if (@sizeOf(Captures) > captures_size)
@compileError("`captures` must be smaller than or equal to " ++
std.fmt.comptimePrint("{d}", .{captures_size}) ++ " bytes");
var function = Self{
.context = undefined,
.applyErased = struct {
fn applyErased(erased: *anyopaque, a: A, b: B) Out {
return call(if (Captures == void) {} else @ptrCast(*Captures,
@alignCast(@alignOf(Captures), erased)).*, a, b);
}
}.applyErased,
};
if (captures != {}) {
@ptrCast(*Captures, @alignCast(@alignOf(Captures), &function.context)).* = captures;
}
return function;
}
};
}
///
/// Returns a single-input single-output closure type where `In` represents the input type, `Out`
/// represents the output type, and `captures_size` represents the size of the closure context.
///
pub fn Function(comptime captures_size: usize, comptime In: type, comptime Out: type) type {
return struct {
applyErased: fn (*anyopaque, In) Out,
callErased: fn (*anyopaque, In) Out,
context: [captures_size]u8,
///
@ -83,38 +24,38 @@ pub fn Function(comptime captures_size: usize, comptime In: type, comptime Out:
const Self = @This();
///
/// Applies `input` to `self`, producing a result according to the current context data.
/// Invokes `self` with `input`, producing a result according to the current context data.
///
pub fn apply(self: *Self, input: In) Out {
return self.applyErased(&self.context, input);
pub fn call(self: *Self, input: In) Out {
return self.callErased(&self.context, input);
}
///
/// Creates a new [Self] by capturing the `captures` value as the context and `call` as the
/// as the behavior executed when [apply] or [applyErased] is called.
/// Creates a new [Self] by capturing the `captures` value as the context and `invoke` as
/// the as the behavior executed when [call] or [callErased] is called.
///
/// The newly created [Self] is returned.
///
pub fn capture(captures: anytype, comptime call: fn (@TypeOf(captures), In) Out) Self {
pub fn capture(captures: anytype, comptime invoke: fn (@TypeOf(captures), In) Out) Self {
const Captures = @TypeOf(captures);
if (@sizeOf(Captures) > captures_size)
@compileError("`captures` must be smaller than or equal to " ++
std.fmt.comptimePrint("{d}", .{captures_size}) ++ " bytes");
@compileError("`captures` exceeds the size limit of the capture context");
const captures_align = @alignOf(Captures);
var function = Self{
.context = undefined,
.applyErased = struct {
fn applyErased(erased: *anyopaque, input: In) Out {
return call(if (Captures == void) {} else @ptrCast(*Captures,
.callErased = struct {
fn callErased(erased: *anyopaque, input: In) Out {
return invoke(if (Captures == void) {} else @ptrCast(*Captures,
@alignCast(@alignOf(Captures), erased)).*, input);
}
}.applyErased,
}.callErased,
};
if (Captures != void)
@ptrCast(*Captures, @alignCast(captures_align, &function.context)).* = captures;
return function;

View File

@ -1,5 +1,5 @@
const io = @import("./io.zig");
const std = @import("std");
const testing = @import("./testing.zig");
///
/// Returns a fixed-size stack type of `Element`s.
@ -15,42 +15,20 @@ pub fn Fixed(comptime Element: type) type {
const Self = @This();
///
/// Wraps `self` and returns it in a [io.Writer] value.
///
/// Note that this will raise a compilation error if [Element] is not `u8`.
///
pub fn writer(self: *Self) io.Writer {
if (Element != u8) @compileError("Cannot coerce fixed stack of type " ++
@typeName(Element) ++ " into a Writer");
return io.Writer.capture(self, struct {
fn write(stack: *Self, buffer: []const u8) usize {
stack.pushAll(buffer) catch |err| switch (err) {
error.OutOfMemory => return 0,
};
return buffer.len;
}
}.write);
}
///
/// Clears all elements from `self`.
/// Resets the number of filled items to `0`, otherwise leaving the actual memory contents
/// of the buffer untouched until it is later overwritten by following operations on it.
///
pub fn clear(self: *Self) void {
self.filled = 0;
}
///
/// Counts and returns the number of pushed elements in `self`.
/// If `self` is filled with at least `1` value, it is decremented by `1`, otherwise leaving
/// the actual memory contents of the buffer untouched until it is later overwritten by
/// following operations on it.
///
pub fn count(self: Self) usize {
return self.filled;
}
///
/// Attempts to pop the tail-end of `self`, returning the element value or `null` if the
/// stack is empty.
/// The value of the element removed from the list is returned if something existed to be
/// popped, otherwise `null` if it contained no elements.
///
pub fn pop(self: *Self) ?Element {
if (self.filled == 0) return null;
@ -74,40 +52,92 @@ pub fn Fixed(comptime Element: type) type {
/// Attempts to push all of `elements` into `self`, returning a [FixedPushError] if it
/// failed.
///
pub fn pushAll(self: *Self, elements: []const u8) PushError!void {
pub fn pushAll(self: *Self, elements: []const Element) PushError!void {
const filled = (self.filled + elements.len);
if (filled > self.buffer.len) return error.OutOfMemory;
std.mem.copy(u8, self.buffer[self.filled ..], elements);
io.copy(Element, self.buffer[self.filled ..], elements);
self.filled = filled;
}
};
}
test "Fixed stack of string literals" {
const default_value = "";
var buffer = [_][]const u8{default_value} ** 4;
var shopping_list = Fixed([]const u8){.buffer = &buffer};
// Pop empty stack.
{
try testing.expect(shopping_list.pop() == null);
try testing.expect(shopping_list.filled == 0);
try testing.expect(shopping_list.buffer.ptr == &buffer);
try testing.expect(shopping_list.buffer.len == buffer.len);
for (shopping_list.buffer) |item|
try testing.expect(io.equals(u8, item, default_value));
}
// Push single element.
{
try shopping_list.push("milk");
try testing.expect(shopping_list.filled == 1);
try testing.expect(shopping_list.buffer.ptr == &buffer);
try testing.expect(shopping_list.buffer.len == buffer.len);
try testing.expect(io.equals(u8, shopping_list.buffer[0], "milk"));
for (shopping_list.buffer[1 ..]) |item|
try testing.expect(io.equals(u8, item, default_value));
// TODO: Test stack overflow.
}
// Pop single element.
{
try testing.expect(io.equals(u8, shopping_list.pop().?, "milk"));
try testing.expect(shopping_list.filled == 0);
try testing.expect(shopping_list.buffer.ptr == &buffer);
try testing.expect(shopping_list.buffer.len == buffer.len);
try testing.expect(io.equals(u8, shopping_list.buffer[0], "milk"));
for (shopping_list.buffer[1 ..]) |item|
try testing.expect(io.equals(u8, item, default_value));
}
// TODO: Multiple elements.
// TODO: Clear elements.
}
///
/// Potential errors that may occur while trying to push one or more elements into a stack.
///
pub const PushError = std.mem.Allocator.Error;
pub const PushError = io.Allocator.Error;
test "Fixed stack manipulation" {
const testing = std.testing;
var buffer = std.mem.zeroes([4]u8);
var stack = Fixed(u8){.buffer = &buffer};
///
/// Returns an [io.Writer] wrapping `fixed_stack`.
///
/// Writing to the returned [io.Writer] will push values to the underlying [Fixed] stack instance
/// referenced by `fixed_stack` until it is full.
///
pub fn fixedWriter(fixed_stack: *Fixed(u8)) io.Writer {
return io.Writer.capture(fixed_stack, struct {
fn write(stack: *Fixed(u8), buffer: []const u8) usize {
stack.pushAll(buffer) catch |err| switch (err) {
error.OutOfMemory => return 0,
};
try testing.expectEqual(stack.count(), 0);
try testing.expectEqual(stack.pop(), null);
try stack.push(69);
try testing.expectEqual(stack.count(), 1);
try testing.expectEqual(stack.pop(), 69);
try stack.pushAll(&.{42, 10, 95, 0});
try testing.expectEqual(stack.count(), 4);
try testing.expectError(PushError.OutOfMemory, stack.push(1));
try testing.expectError(PushError.OutOfMemory, stack.pushAll(&.{1, 11, 11}));
stack.clear();
try testing.expectEqual(stack.count(), 0);
try testing.expectEqual(stack.writer().apply(&.{0, 0, 0, 0}), 4);
return buffer.len;
}
}.write);
}
test "Fixed writer" {
kayomn marked this conversation as resolved Outdated

Seeing as FixedStack does not depend on any kind of dynamic allocation directly, does it make sense to make PushError a direct alias of io.MakeError?

Would it make more sense to use BufferOverflow instead of OutOfMemory?

Seeing as `FixedStack` does not depend on any kind of dynamic allocation directly, does it make sense to make `PushError` a direct alias of `io.MakeError`? Would it make more sense to use `BufferOverflow` instead of `OutOfMemory`?
var buffer = [_]u8{0} ** 4;
var sequence_stack = Fixed(u8){.buffer = &buffer};
const sequence_data = [_]u8{8, 16, 32, 64};
try testing.expect(fixedWriter(&sequence_stack).call(&sequence_data) == sequence_data.len);
try testing.expect(io.equals(u8, sequence_stack.buffer, &sequence_data));
}

18
src/core/testing.zig Normal file
View File

@ -0,0 +1,18 @@
///
/// [TestError.UnexpectedResult] occurs when a conditional that should have been `true` was actually
/// `false`.
///
pub const TestError = error {
UnexpectedResult,
};
///
/// Returns a [TestError] if `ok` is false.
///
pub fn expect(ok: bool) TestError!void {
if (!ok) return error.UnexpectedResult;
}
// TODO: Implement tests.
pub const expectError = @import("std").testing.expectError;

84
src/core/unicode.zig Normal file
View File

@ -0,0 +1,84 @@
const io = @import("./io.zig");
const math = @import("./math.zig");
///
/// [PrintError.WriteFailure] occurs when the underlying [io.Writer] implementation failed to write
/// the entirety of a the requested print operation.
///
pub const PrintError = error {
WriteFailure,
};
///
/// Number formatting modes supported by [printInt].
///
pub const Radix = enum {
binary,
tinary,
quaternary,
quinary,
senary,
septenary,
octal,
nonary,
decimal,
undecimal,
duodecimal,
tridecimal,
tetradecimal,
pentadecimal,
hexadecimal,
};
///
/// Writes `value` as a ASCII / UTF-8 encoded integer to `writer`, returning `true` if the full
/// sequence was successfully written, otherwise `false`.
///
/// The `radix` argument identifies which base system to format `value` as.
///
pub fn printInt(writer: io.Writer, radix: Radix, value: anytype) PrintError!void {
const Int = @TypeOf(value);
switch (@typeInfo(Int)) {
.Int => |int_info| {
if (value == 0) return writer.apply("0");
const base = @enumToInt(radix);
const is_signed = (int_info.signedness == .signed);
var buffer = [_]u8{0} ** (math.ceil(math.log(math.
maxInt(Int), base)) + @boolToInt(is_signed));
var buffer_count: usize = 0;
var n1 = value;
if (is_signed and (value < 0)) {
// Negative value.
n1 = -value;
buffer[0] = '-';
buffer_count += 1;
}
while (n1 != 0) {
buffer[buffer_count] = @intCast(u8, (n1 % base) + '0');
n1 = (n1 / base);
buffer_count += 1;
}
for (buffer[0 .. (buffer_count / 2)]) |_, i|
io.swap(u8, &buffer[i], &buffer[buffer_count - i - 1]);
if (writer.call(buffer[0 .. buffer_count]) != buffer_count) return error.WriteFailure;
},
// Cast comptime int into known-size integer and try again.
.ComptimeInt => return printInt(writer, radix,
@intCast(math.IntFittingRange(value, value), value)),
else => @compileError("`value` must be of type int or comptime_int"),
}
}
test "Print 64-bit signed integer" {
// TODO: implement.
}