1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 15:14:08 +00:00

WIP: progress on Bound function

This commit is contained in:
Rene Schallner 2025-03-22 20:31:10 +01:00 committed by renerocksai
parent 9d3434c21c
commit 3353b6a525

View file

@ -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);
}