diff --git a/src/BoundFunction.zig b/src/BoundFunction.zig index 94cc9f6..71c5cfe 100644 --- a/src/BoundFunction.zig +++ b/src/BoundFunction.zig @@ -1,36 +1,148 @@ const std = @import("std"); -// attempt 1: explicitly typed -pub fn Create(Fn: type, Instance: type) type { +/// Bind a function with specific signature to a method of any instance of a given type +pub fn OldBind(func: anytype, instance: anytype) OldBound(@typeInfo(@TypeOf(instance)).pointer.child, func) { + const Instance = @typeInfo(@TypeOf(instance)).pointer.child; + return OldBound(Instance, func).init(@constCast(instance)); +} + +pub fn OldBound(Instance: type, func: anytype) type { + // Verify Func is a function type + const func_info = @typeInfo(@TypeOf(func)); + if (func_info != .@"fn") { + @compileError("OldBound expexts a function type as second parameter"); + } + + // Verify first parameter is pointer to Instance type + const params = func_info.@"fn".params; + if (params.len == 0 or (params[0].type != *Instance and params[0].type != *const Instance)) { + @compileError("Function's first parameter must be " ++ @typeName(Instance) ++ " but got: " ++ @typeName(params[0].type.?)); + } + return struct { instance: *Instance, - function: *const Fn, + + const OldBoundFunction = @This(); + + pub fn call(self: OldBoundFunction, args: anytype) func_info.@"fn".return_type.? { + return @call(.auto, func, .{self.instance} ++ args); + } + + // convenience init + pub fn init(instance_: *Instance) OldBoundFunction { + return .{ .instance = instance_ }; + } + }; +} + +test "OldBound" { + const Person = struct { + name: []const u8, + _buf: [1024]u8 = undefined, + + // takes const instance + pub fn greet(self: *const @This(), gpa: std.mem.Allocator, greeting: []const u8) ![]const u8 { + return std.fmt.allocPrint(gpa, "{s}, {s}!\n", .{ greeting, self.name }); + } + + // takes non-const instance + pub fn farewell(self: *@This(), message: []const u8) ![]const u8 { + return std.fmt.bufPrint(self._buf[0..], "{s}, {s}!\n", .{ message, self.name }); + } + }; + + var alice: Person = .{ .name = "Alice" }; + + // creation variant a: manually instantiate + const bound_greet: OldBound(Person, Person.greet) = .{ .instance = &alice }; + + // creation variant b: call init function + const bound_farewell = OldBound(Person, Person.farewell).init(&alice); + + const ta = std.testing.allocator; + const greeting = try bound_greet.call(.{ ta, "Hello" }); + defer ta.free(greeting); + + try std.testing.expectEqualStrings("Hello, Alice!\n", greeting); + try std.testing.expectEqualStrings("Goodbye, Alice!\n", try bound_farewell.call(.{"Goodbye"})); +} + +test OldBind { + const Person = struct { + name: []const u8, + _buf: [1024]u8 = undefined, + + // takes const instance + pub fn greet(self: *const @This(), gpa: std.mem.Allocator, greeting: []const u8) ![]const u8 { + return std.fmt.allocPrint(gpa, "{s}, {s}!\n", .{ greeting, self.name }); + } + + // takes non-const instance + pub fn farewell(self: *@This(), message: []const u8) ![]const u8 { + return std.fmt.bufPrint(self._buf[0..], "{s}, {s}!\n", .{ message, self.name }); + } + }; + + var alice: Person = .{ .name = "Alice" }; + + const bound_greet = OldBind(Person.greet, &alice); + const bound_farewell = OldBind(Person.farewell, &alice); + + const ta = std.testing.allocator; + const greeting = try bound_greet.call(.{ ta, "Hello" }); + defer ta.free(greeting); + + try std.testing.expectEqualStrings("Hello, Alice!\n", greeting); + try std.testing.expectEqualStrings("Goodbye, Alice!\n", try bound_farewell.call(.{"Goodbye"})); +} + +/// Bind functions like `fn(a: X, b: Y)` to an instance of a struct. When called, the instance's `pub fn(self: *This(), a: X, b: Y)` is called. +/// +/// make callbacks stateful when they're not meant to be? +// pub fn Bound(Instance: type, Func: type, func: anytype) type { +pub fn Bound(Instance: type, Func: type, DFunc: type) type { + + // TODO: construct DFunc on-the-fly + + // Verify Func is a function type + const func_info = @typeInfo(Func); + // if (func_info != .@"fn") { + // @compileError("Bound expexts a function type as second parameter"); + // } + return struct { + instance: *Instance, + foo: *const DFunc, const BoundFunction = @This(); - pub fn init(function: *const Fn, instance: *Instance) BoundFunction { - return .{ - .instance = instance, - .function = function, - }; + pub fn call(self: BoundFunction, args: anytype) func_info.@"fn".return_type.? { + return @call(.auto, self.foo, .{self.instance} ++ args); } - pub fn call(self: *const BoundFunction, arg: anytype) void { - @call(.auto, self.function, .{ self.instance, arg }); + // convenience init + pub fn init(instance_: *Instance, foo_: *const DFunc) BoundFunction { + return .{ .instance = instance_, .foo = foo_ }; } }; } -test "BoundFunction" { - const X = struct { - field: usize = 0, - pub fn foo(self: *@This(), other: usize) void { - std.debug.print("field={d}, other={d}\n", .{ self.field, other }); +test Bound { + const Person = struct { + name: []const u8, + _buf: [1024]u8 = undefined, + + pub fn speak(self: *@This(), message: []const u8) ![]const u8 { + return std.fmt.bufPrint(self._buf[0..], "{s} says: >>{s}!<<\n", .{ self.name, message }); } }; - var x: X = .{ .field = 27 }; + const CallBack = fn ([]const u8) anyerror![]const u8; - var bound = Create(@TypeOf(X.foo), X).init(X.foo, &x); - bound.call(3); + var alice: Person = .{ .name = "Alice" }; + + const bound_greet = Bound(Person, CallBack, @TypeOf(Person.speak)).init(&alice, &Person.speak); + + const greeting = try bound_greet.call(.{"Hello"}); + + try std.testing.expectEqualStrings("Alice says: >>Hello!<<\n", greeting); }