diff --git a/src/ona/fmt.zig b/src/ona/fmt.zig new file mode 100644 index 0000000..43a0cba --- /dev/null +++ b/src/ona/fmt.zig @@ -0,0 +1,70 @@ +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"), + } +} diff --git a/src/ona/io.zig b/src/ona/io.zig index 83a7de3..3ad3701 100644 --- a/src/ona/io.zig +++ b/src/ona/io.zig @@ -1,3 +1,4 @@ +const meta = @import("./meta.zig"); const stack = @import("./stack.zig"); const std = @import("std"); @@ -170,121 +171,9 @@ test "Spliterating text" { } /// -/// Opaque interface to a "writable" resource, such as a block device, memory buffer, or network -/// socket. +/// Opaque interface to a "writable" resource like a block device, memory buffer, or network socket. /// -pub const Writer = struct { - context: *anyopaque, - writeContext: fn (*anyopaque, []const u8) usize, - - /// - /// Radices supported by [writeInt]. - /// - pub const Radix = enum { - binary, - tinary, - quaternary, - quinary, - senary, - septenary, - octal, - nonary, - decimal, - undecimal, - duodecimal, - tridecimal, - tetradecimal, - pentadecimal, - hexadecimal, - }; - - /// - /// Wraps and returns a reference to `write_context` of type `WriteContext` and its associated - /// `writeContext` writing operation in a [Writer]. - /// - pub fn wrap( - comptime WriteContext: type, - write_context: *WriteContext, - comptime writeContext: fn (*WriteContext, []const u8) usize - ) Writer { - return .{ - .context = write_context, - - .writeContext = struct { - fn write(context: *anyopaque, buffer: []const u8) usize { - return writeContext(@ptrCast(*WriteContext, - @alignCast(@alignOf(WriteContext), context)), buffer); - } - }.write, - }; - } - - /// - /// Attempts to write `buffer` to `writer`, returning the number of bytes from `buffer` that - /// were successfully written. - /// - pub fn write(writer: Writer, buffer: []const u8) usize { - return writer.writeContext(writer.context, buffer); - } - - /// - /// Writes the singular `byte` to `writer`, returning `true` if it was successfully written, - /// otherwise `false`. - /// - pub fn writeByte(writer: Writer, byte: u8) bool { - return (writer.writeContext(writer.context, - @ptrCast([*]const u8, &byte)[0 .. 1]) != 0); - } - - /// - /// 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 encode `value` as, with `10` being - /// decimal, `16` being hexadecimal, `8` being octal`, so on and so forth. - /// - pub fn writeInt(writer: 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.write(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"), - } - } -}; +pub const Writer = meta.Function([]const u8, usize); /// /// Returns `true` if `this_bytes` is the same length and contains the same data as `that_bytes`, @@ -323,21 +212,19 @@ test "Hashing bytes" { try testing.expect(hashBytes(bytes_sequence) != hashBytes(&.{69, 42})); } +var null_context: usize = undefined; + /// /// Writer that silently throws consumed data away and never fails. /// /// This is commonly used for testing or redirected otherwise unwanted output data that can't not be /// sent somewhere for whatever reason. /// -pub const null_writer = Writer{ - .context = undefined, - - .writeContext = struct { - fn write(_: *anyopaque, buffer: []const u8) usize { - return buffer.len; - } - }.write, -}; +pub const null_writer = Writer.wrap(&null_context, struct { + fn write(_: *@TypeOf(null_context), buffer: []const u8) usize { + return buffer.len; + } +}.write); test "Null writing" { const testing = std.testing; @@ -345,9 +232,14 @@ test "Null writing" { { const sequence = "foo"; - try testing.expectEqual(null_writer.write(sequence), sequence.len); + try testing.expectEqual(null_writer.apply(sequence), sequence.len); } - - try testing.expect(null_writer.writeByte(0)); - try testing.expect(null_writer.writeInt(.decimal, 420)); +} + +/// +/// Writes the singular `byte` to `writer`, returning `true` if it was successfully written, +/// otherwise `false`. +/// +pub fn writeByte(writer: Writer, byte: u8) bool { + return (writer.call(.{@ptrCast([*]const u8, &byte)[0 .. 1]}) != 0); } diff --git a/src/ona/meta.zig b/src/ona/meta.zig index 9fa2c48..d555eca 100644 --- a/src/ona/meta.zig +++ b/src/ona/meta.zig @@ -8,3 +8,54 @@ pub fn FnReturn(comptime Fn: type) type { return type_info.Fn.return_type orelse void; } + +/// +/// Returns single-input closure type where `Input` is the input type and `Output` is the output +/// type. +/// +pub fn Function(comptime Input: type, comptime Output: type) type { + return struct { + context: *anyopaque, + contextualApply: fn (*anyopaque, Input) Output, + + /// + /// Function type. + /// + const Self = @This(); + + /// + /// Applies `input` to `self`, producing a result according to the type-erased + /// implementation. + /// + pub fn apply(self: Self, input: Input) Output { + return self.contextualApply(self.context, input); + } + + /// + /// Creates a new [Self] by wrapping `concrete_context` as a pointer to the implementation + /// and `contextualApply` as the behavior executed when [apply] is called. + /// + /// The newly created [Self] is returned. + /// + pub fn wrap( + concrete_context: anytype, + comptime contextualApply: fn (@TypeOf(concrete_context), Input) Output + ) Self { + const ConcreteContext = @TypeOf(concrete_context); + + if (@typeInfo(ConcreteContext) != .Pointer) + @compileError("`concrete_context` must be a pointer type"); + + return .{ + .context = concrete_context, + + .contextualApply = struct { + fn call(erased_context: *anyopaque, input: Input) Output { + return contextualApply(@ptrCast(ConcreteContext, @alignCast( + @alignOf(ConcreteContext), erased_context)), input); + } + }.call, + }; + } + }; +} diff --git a/src/ona/stack.zig b/src/ona/stack.zig index 9ebf9bc..617dc7c 100755 --- a/src/ona/stack.zig +++ b/src/ona/stack.zig @@ -23,7 +23,7 @@ pub fn Fixed(comptime Element: type) type { if (Element != u8) @compileError("Cannot coerce fixed stack of type " ++ @typeName(Element) ++ " into a Writer"); - return io.Writer.wrap(Self, self, struct { + return io.Writer.wrap(self, struct { fn write(stack: *Self, buffer: []const u8) usize { stack.pushAll(buffer) catch |err| switch (err) { error.OutOfMemory => return 0, @@ -112,6 +112,5 @@ test "Fixed stack manipulation" { const writer = stack.writer(); - try testing.expectEqual(writer.write(&.{0, 0, 0, 0}), 4); - try testing.expectEqual(writer.writeByte(0), false); + try testing.expectEqual(writer.apply(&.{0, 0, 0, 0}), 4); }