diff --git a/src/ona/io.zig b/src/ona/io.zig index 3ad3701..7714365 100644 --- a/src/ona/io.zig +++ b/src/ona/io.zig @@ -171,9 +171,10 @@ test "Spliterating text" { } /// -/// Opaque interface to a "writable" resource like a block device, memory buffer, or network socket. +/// Closure that captures a reference to writable resources like block devices, memory buffers, +/// network sockets, and more. /// -pub const Writer = meta.Function([]const u8, usize); +pub const Writer = meta.Function(@sizeOf(usize), []const u8, usize); /// /// Returns `true` if `this_bytes` is the same length and contains the same data as `that_bytes`, @@ -212,19 +213,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. +/// Returns a [Writer] that silently consumes all given data without failure and throws it away. /// -/// This is commonly used for testing or redirected otherwise unwanted output data that can't not be +/// This is commonly used for testing or redirected otherwise unwanted output data that has to be /// sent somewhere for whatever reason. /// -pub const null_writer = Writer.wrap(&null_context, struct { - fn write(_: *@TypeOf(null_context), buffer: []const u8) usize { - return buffer.len; - } -}.write); +pub fn nullWriter() Writer { + return Writer.capture(std.mem.zeroes(usize), struct { + fn write(_: usize, buffer: []const u8) usize { + return buffer.len; + } + }.write); +} test "Null writing" { const testing = std.testing; @@ -232,7 +233,7 @@ test "Null writing" { { const sequence = "foo"; - try testing.expectEqual(null_writer.apply(sequence), sequence.len); + try testing.expectEqual(nullWriter().apply(sequence), sequence.len); } } @@ -241,5 +242,5 @@ test "Null writing" { /// otherwise `false`. /// pub fn writeByte(writer: Writer, byte: u8) bool { - return (writer.call(.{@ptrCast([*]const u8, &byte)[0 .. 1]}) != 0); + return (writer.apply(std.mem.asBytes(&byte)) != 0); } diff --git a/src/ona/meta.zig b/src/ona/meta.zig index d555eca..22b38eb 100644 --- a/src/ona/meta.zig +++ b/src/ona/meta.zig @@ -1,3 +1,5 @@ +const std = @import("std"); + /// /// Returns the return type of the function type `Fn`. /// @@ -10,13 +12,14 @@ pub fn FnReturn(comptime Fn: type) type { } /// -/// Returns single-input closure type where `Input` is the input type and `Output` is the output -/// type. +/// Returns a single-input single-output closure type where `Input` represents the input type, +/// `Output` represents the output type, and `captures_size` represents the size of the closure +/// context. /// -pub fn Function(comptime Input: type, comptime Output: type) type { +pub fn Function(comptime captures_size: usize, comptime Input: type, comptime Output: type) type { return struct { - context: *anyopaque, - contextualApply: fn (*anyopaque, Input) Output, + applyErased: fn (*anyopaque, Input) Output, + context: [captures_size]u8, /// /// Function type. @@ -24,38 +27,41 @@ pub fn Function(comptime Input: type, comptime Output: type) type { const Self = @This(); /// - /// Applies `input` to `self`, producing a result according to the type-erased - /// implementation. + /// Applies `input` to `self`, producing a result according to the current context data. /// - pub fn apply(self: Self, input: Input) Output { - return self.contextualApply(self.context, input); + pub fn apply(self: *Self, input: Input) Output { + return self.applyErased(&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. + /// 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 wrap( - concrete_context: anytype, - comptime contextualApply: fn (@TypeOf(concrete_context), Input) Output - ) Self { - const ConcreteContext = @TypeOf(concrete_context); + pub fn capture(captures: anytype, comptime call: fn (@TypeOf(captures), Input) Output) Self { + const Captures = @TypeOf(captures); - if (@typeInfo(ConcreteContext) != .Pointer) - @compileError("`concrete_context` must be a pointer type"); + if (@sizeOf(Captures) > captures_size) + @compileError("`captures` must be smaller than or equal to " ++ + std.fmt.comptimePrint("{d}", .{captures_size}) ++ " bytes"); - return .{ - .context = concrete_context, + const captures_align = @alignOf(Captures); - .contextualApply = struct { - fn call(erased_context: *anyopaque, input: Input) Output { - return contextualApply(@ptrCast(ConcreteContext, @alignCast( - @alignOf(ConcreteContext), erased_context)), input); + var function = Self{ + .context = undefined, + + .applyErased = struct { + fn do(erased: *anyopaque, input: Input) Output { + return call(@ptrCast(*Captures, @alignCast( + captures_align, erased)).*, input); } - }.call, + }.do, }; + + @ptrCast(*Captures, @alignCast(captures_align, &function.context)).* = captures; + + return function; } }; } diff --git a/src/ona/stack.zig b/src/ona/stack.zig index 617dc7c..77a4e01 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, struct { + 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, @@ -109,8 +109,5 @@ test "Fixed stack manipulation" { stack.clear(); try testing.expectEqual(stack.count(), 0); - - const writer = stack.writer(); - - try testing.expectEqual(writer.apply(&.{0, 0, 0, 0}), 4); + try testing.expectEqual(stack.writer().apply(&.{0, 0, 0, 0}), 4); }