Simplify programming interface for creating closureless Functions

This commit is contained in:
kayomn 2022-11-02 13:11:17 +00:00
parent eb4a758251
commit 2e544393a5
3 changed files with 36 additions and 17 deletions

View File

@ -377,23 +377,19 @@ test "Data swapping" {
}
///
/// Returns a [Writer] that silently consumes all given data without failure and throws it away.
/// [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 has to be
/// sent somewhere for whatever reason.
///
pub fn nullWriter() Writer {
var dummy: usize = 0;
return Writer.capture(&dummy, struct {
fn write(_: *usize, buffer: []const u8) usize {
return buffer.len;
}
}.write);
}
pub const null_writer = Writer.from(struct {
fn write(buffer: []const u8) usize {
return buffer.len;
}
}.write);
test "Null writing" {
const sequence = "foo";
try testing.expect(nullWriter().call(sequence) == sequence.len);
try testing.expect(null_writer.call(sequence) == sequence.len);
}

View File

@ -18,6 +18,10 @@ pub fn Function(comptime In: type, comptime Out: type) type {
callErased: fn (*anyopaque, In) Out,
context: *anyopaque,
fn Invoker(comptime Context: type) type {
return if (Context == void) fn (In) Out else fn (Context, In) Out;
}
///
/// Function type.
///
@ -26,23 +30,42 @@ pub fn Function(comptime In: type, comptime Out: type) type {
///
/// Invokes `self` with `input`, producing a result according to the current context data.
///
pub fn call(self: *Self, input: In) Out {
pub fn call(self: Self, input: In) Out {
return self.callErased(self.context, input);
}
///
/// Creates a new [Self] by capturing the `context` value as the capture context and
/// Creates and returns a [Self] using the `invoke` as the behavior executed when [call] or
/// [callErased] is called.
///
/// For creating a closure-style function, see [fromClosure].
///
pub fn from(comptime invoke: fn (In) Out) Self {
return .{
.context = undefined,
.callErased = struct {
fn callErased(_: *anyopaque, input: In) Out {
return invoke(input);
}
}.callErased,
};
}
///
/// Creates and returns a [Self] by capturing the `context` value as the capture context and
/// `invoke` as the behavior executed when [call] or [callErased] is called.
///
/// The newly created [Self] is returned.
///
pub fn capture(context: anytype, comptime invoke: fn (@TypeOf(context), In) Out) Self {
pub fn fromClosure(context: anytype, comptime invoke: fn (@TypeOf(context), In) Out) Self {
const Context = @TypeOf(context);
switch (@typeInfo(Context)) {
.Pointer => |info| if (info.size == .Slice)
@compileError("`context` cannot be a slice"),
.Void => {},
else => @compileError("`context` must be a pointer"),
}
@ -51,8 +74,8 @@ pub fn Function(comptime In: type, comptime Out: type) type {
.callErased = struct {
fn callErased(erased: *anyopaque, input: In) Out {
return invoke(@ptrCast(*Context, @alignCast(
@alignOf(Context), erased)).*, input);
return if (Context == void) invoke(input) else invoke(@ptrCast(
*Context, @alignCast(@alignOf(Context), erased)).*, input);
}
}.callErased,
};

View File

@ -122,7 +122,7 @@ pub const PushError = io.Allocator.MakeError;
/// referenced by `fixed_stack` until it is full.
///
pub fn fixedWriter(fixed_stack: *Fixed(u8)) io.Writer {
return io.Writer.capture(fixed_stack, struct {
return io.Writer.fromClosure(fixed_stack, struct {
fn write(stack: *Fixed(u8), buffer: []const u8) usize {
stack.pushAll(buffer) catch |err| switch (err) {
error.OutOfMemory => return 0,