1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 23:24:09 +00:00

BoundFunction POLYMORPHIC!!!

This commit is contained in:
renerocksai 2025-03-27 01:12:40 +01:00
parent 6dd3c93a0f
commit d4a2079542

View file

@ -1,125 +1,22 @@
const std = @import("std"); const std = @import("std");
/// Bind a function with specific signature to a method of any instance of a given type /// Helper function that returns a function type with ArgType prepended to the
pub fn OldBind(func: anytype, instance: anytype) OldBound(@typeInfo(@TypeOf(instance)).pointer.child, func) { /// function's args.
const Instance = @typeInfo(@TypeOf(instance)).pointer.child; /// Example:
return OldBound(Instance, func).init(@constCast(instance)); /// Func = fn(usize) void
} /// ArgType = *Instance
/// --------------------------
pub fn OldBound(Instance: type, func: anytype) type { /// Result = fn(*Instance, usize) void
// Verify Func is a function type fn PrependFnArg(Func: type, ArgType: type) 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,
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"}));
}
/// Creates a function type with instance pointer prepended to args
fn PrependFnArg(Func: type, Instance: type) type {
const InstancePtr = *Instance;
// Get the function type
const fn_info = @typeInfo(Func); const fn_info = @typeInfo(Func);
if (fn_info != .@"fn") { if (fn_info != .@"fn") @compileError("First argument must be a function type");
@compileError("Second argument must be a function");
}
// Create new parameter list with instance pointer prepended
comptime var new_params: [fn_info.@"fn".params.len + 1]std.builtin.Type.Fn.Param = undefined; comptime var new_params: [fn_info.@"fn".params.len + 1]std.builtin.Type.Fn.Param = undefined;
new_params[0] = .{ new_params[0] = .{ .is_generic = false, .is_noalias = false, .type = ArgType };
.is_generic = false,
.is_noalias = false,
.type = InstancePtr,
};
// Copy original parameters
for (fn_info.@"fn".params, 0..) |param, i| { for (fn_info.@"fn".params, 0..) |param, i| {
new_params[i + 1] = param; new_params[i + 1] = param;
} }
// Return the new function type
return @Type(.{ return @Type(.{
.@"fn" = .{ .@"fn" = .{
.calling_convention = fn_info.@"fn".calling_convention, .calling_convention = fn_info.@"fn".calling_convention,
@ -130,55 +27,287 @@ fn PrependFnArg(Func: type, Instance: type) type {
}, },
}); });
} }
/// 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 Bind(Instance: type, Func: type) type {
// TODO: construct DFunc on-the-fly // External Generic Interface (CallbackInterface)
pub fn CallbackInterface(comptime Func: type) type {
// Verify Func is a function type
const func_info = @typeInfo(Func); const func_info = @typeInfo(Func);
if (func_info != .@"fn") { if (func_info != .@"fn") @compileError("CallbackInterface expects a function type");
@compileError("Bound expexts a function type as second parameter"); if (func_info.@"fn".is_generic) @compileError("CallbackInterface does not support generic functions");
} if (func_info.@"fn".is_var_args) @compileError("CallbackInterface does not support var_args functions");
const ArgsTupleType = std.meta.ArgsTuple(Func);
const ReturnType = func_info.@"fn".return_type.?;
const FnPtrType = *const fn (ctx: ?*const anyopaque, args: ArgsTupleType) ReturnType;
const InstanceMethod = PrependFnArg(Func, Instance);
return struct { return struct {
instance: *Instance, ctx: ?*const anyopaque,
foo: *const InstanceMethod, callFn: FnPtrType,
pub const Interface = @This();
const BoundFunction = @This(); pub fn call(self: Interface, args: ArgsTupleType) ReturnType {
if (self.ctx == null) @panic("Called uninitialized CallbackInterface");
pub fn call(self: BoundFunction, args: anytype) func_info.@"fn".return_type.? { if (ReturnType == void) {
return @call(.auto, self.foo, .{self.instance} ++ args); self.callFn(self.ctx, args);
} } else {
return self.callFn(self.ctx, args);
// convenience init }
pub fn init(instance_: *Instance, foo_: *const InstanceMethod) BoundFunction {
return .{ .instance = instance_, .foo = foo_ };
} }
}; };
} }
test Bind { pub fn Bind(Instance: type, Func: type) type {
const func_info = @typeInfo(Func);
if (func_info != .@"fn") @compileError("Bind expects a function type as second parameter");
if (func_info.@"fn".is_generic) @compileError("Binding generic functions is not supported");
if (func_info.@"fn".is_var_args) @compileError("Binding var_args functions is not currently supported");
const ReturnType = func_info.@"fn".return_type.?;
const OriginalParams = func_info.@"fn".params; // Needed for comptime loops
const ArgsTupleType = std.meta.ArgsTuple(Func);
const InstanceMethod = PrependFnArg(Func, *Instance);
const InterfaceType = CallbackInterface(Func);
return struct {
instance: *Instance,
method: *const InstanceMethod,
pub const BoundFunction = @This();
// Trampoline function using runtime tuple construction
fn callDetached(ctx: ?*const anyopaque, args: ArgsTupleType) ReturnType {
if (ctx == null) @panic("callDetached called with null context");
const self: *const BoundFunction = @ptrCast(@alignCast(ctx.?));
// 1. Define the tuple type needed for the call: .{*Instance, OriginalArgs...}
const CallArgsTupleType = comptime T: {
var tuple_fields: [OriginalParams.len + 1]std.builtin.Type.StructField = undefined;
// Field 0: *Instance type
tuple_fields[0] = .{
.name = "0",
.type = @TypeOf(self.instance),
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
};
// Fields 1..N: Original argument types (use ArgsTupleType fields)
for (std.meta.fields(ArgsTupleType), 0..) |field, i| {
tuple_fields[i + 1] = .{
.name = std.fmt.comptimePrint("{d}", .{i + 1}),
.type = field.type,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
};
}
break :T @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &tuple_fields,
.decls = &.{},
.is_tuple = true,
} });
};
// 2. Create and populate the tuple at runtime
var call_args_tuple: CallArgsTupleType = undefined;
@field(call_args_tuple, "0") = self.instance; // Set the instance pointer
// Copy original args from 'args' tuple to 'call_args_tuple'
comptime var i = 0;
inline while (i < OriginalParams.len) : (i += 1) {
const src_field_name = comptime std.fmt.comptimePrint("{}", .{i});
const dest_field_name = comptime std.fmt.comptimePrint("{}", .{i + 1});
@field(call_args_tuple, dest_field_name) = @field(args, src_field_name);
}
// 3. Perform the call using the populated tuple
if (ReturnType == void) {
@call(.auto, self.method, call_args_tuple);
} else {
return @call(.auto, self.method, call_args_tuple);
}
}
pub fn interface(self: *const BoundFunction) InterfaceType {
return .{ .ctx = @ptrCast(self), .callFn = &callDetached };
}
// Direct call convenience method using runtime tuple construction
pub fn call(self: *const BoundFunction, args: anytype) ReturnType {
// 1. Verify 'args' is the correct ArgsTupleType or compatible tuple literal
// (This check could be more robust if needed)
if (@TypeOf(args) != ArgsTupleType) {
// Attempt reasonable check for tuple literal compatibility
if (@typeInfo(@TypeOf(args)) != .@"struct" or !@typeInfo(@TypeOf(args)).@"struct".is_tuple) {
@compileError(std.fmt.comptimePrint(
"Direct .call expects arguments as a tuple literal compatible with {}, found type {}",
.{ ArgsTupleType, @TypeOf(args) },
));
}
// Further check field count/types if necessary
if (std.meta.fields(@TypeOf(args)).len != OriginalParams.len) {
@compileError(std.fmt.comptimePrint(
"Direct .call tuple literal has wrong number of arguments (expected {}, got {}) for {}",
.{ OriginalParams.len, std.meta.fields(@TypeOf(args)).len, ArgsTupleType },
));
}
// Could add type checks per field here too
}
// 2. Define the tuple type needed for the call: .{*Instance, OriginalArgs...}
const CallArgsTupleType = comptime T: {
var tuple_fields: [OriginalParams.len + 1]std.builtin.Type.StructField = undefined;
tuple_fields[0] = .{
.name = "0",
.type = @TypeOf(self.instance),
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
};
for (std.meta.fields(ArgsTupleType), 0..) |field, i| {
tuple_fields[i + 1] = .{
.name = std.fmt.comptimePrint("{d}", .{i + 1}),
.type = field.type,
.default_value_ptr = null,
.is_comptime = false,
.alignment = 0,
};
}
break :T @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &tuple_fields,
.decls = &.{},
.is_tuple = true,
} });
};
// 3. Create and populate the tuple at runtime
var call_args_tuple: CallArgsTupleType = undefined;
@field(call_args_tuple, "0") = self.instance;
comptime var i = 0;
inline while (i < OriginalParams.len) : (i += 1) {
const field_name = comptime std.fmt.comptimePrint("{}", .{i});
// Check if field exists in args (useful for struct literals, less for tuples)
// For tuple literals, direct access should work if type check passed.
// if (@hasField(@TypeOf(args), field_name)) { ... }
const dest_field_name = comptime std.fmt.comptimePrint("{}", .{i + 1});
@field(call_args_tuple, dest_field_name) = @field(args, field_name);
}
// 4. Perform the call using the populated tuple
if (ReturnType == void) {
@call(.auto, self.method, call_args_tuple);
} else {
return @call(.auto, self.method, call_args_tuple);
}
}
pub fn init(instance_: *Instance, method_: *const InstanceMethod) BoundFunction {
return .{ .instance = instance_, .method = method_ };
}
};
}
const testing = std.testing;
test "Bind Direct Call" {
const Person = struct { const Person = struct {
name: []const u8, name: []const u8,
_buf: [1024]u8 = undefined, _buf: [1024]u8 = undefined,
pub fn speak(self: *@This(), msg: []const u8) ![]const u8 {
pub fn speak(self: *@This(), message: []const u8) ![]const u8 { return std.fmt.bufPrint(&self._buf, "{s}: {s}", .{ self.name, msg });
return std.fmt.bufPrint(self._buf[0..], "{s} says: >>{s}!<<\n", .{ self.name, message });
} }
}; };
const FuncSig = fn ([]const u8) anyerror![]const u8;
var p = Person{ .name = "Alice" };
const bound = Bind(Person, FuncSig).init(&p, &Person.speak);
const res = try bound.call(.{"Hi"}); // Pass tuple literal
try testing.expectEqualStrings("Alice: Hi", res);
}
test "BindInterface Call (External)" {
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, "{s} says: >>{s}!<<\n", .{ self.name, message });
}
};
const CallBack = fn ([]const u8) anyerror![]const u8; const CallBack = fn ([]const u8) anyerror![]const u8;
var alice: Person = .{ .name = "Alice" };
const BoundSpeak = Bind(Person, CallBack);
const bound_speak = BoundSpeak.init(&alice, &Person.speak);
var alice_interface = bound_speak.interface();
const greeting = try alice_interface.call(.{"Hello"}); // Pass tuple literal
try testing.expectEqualStrings("Alice says: >>Hello!<<\n", greeting);
}
test "BindInterface Polymorphism (External)" {
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, "{s} says: >>{s}!<<\n", .{ self.name, message });
}
};
const Dog = struct {
name: []const u8,
_buf: [1024]u8 = undefined,
pub fn bark(self: *@This(), message: []const u8) ![]const u8 {
return std.fmt.bufPrint(&self._buf, "{s} barks: >>{s}!<<\n", .{ self.name, message });
}
};
const CallBack = fn ([]const u8) anyerror![]const u8;
const CbInterface = CallbackInterface(CallBack);
var alice: Person = .{ .name = "Alice" }; var alice: Person = .{ .name = "Alice" };
const bound_alice = Bind(Person, CallBack).init(&alice, &Person.speak);
const alice_interface = bound_alice.interface();
const bound_greet = Bind(Person, CallBack).init(&alice, &Person.speak); var bob: Dog = .{ .name = "Bob" };
const bound_bob = Bind(Dog, CallBack).init(&bob, &Dog.bark);
const bob_interface = bound_bob.interface();
const greeting = try bound_greet.call(.{"Hello"}); const interfaces = [_]CbInterface{ alice_interface, bob_interface };
var results: [2][]const u8 = undefined;
for (interfaces, 0..) |iface, i| {
results[i] = try iface.call(.{"Test"});
} // Pass tuple literal
try std.testing.expectEqualStrings("Alice says: >>Hello!<<\n", greeting); try testing.expectEqualStrings("Alice says: >>Test!<<\n", results[0]);
try testing.expectEqualStrings("Bob barks: >>Test!<<\n", results[1]);
}
test "Void Return Type (External Interface)" {
var counter: u32 = 0;
const Counter = struct {
count: *u32,
pub fn increment(self: *@This(), amount: u32) void {
self.count.* += amount;
}
};
const Decrementer = struct {
count: *u32,
pub fn decrement(self: *@This(), amount: u32) void {
self.count.* -= amount;
}
};
const IncrementFn = fn (u32) void;
const IncInterface = CallbackInterface(IncrementFn);
var my_counter = Counter{ .count = &counter };
const bound_inc = Bind(Counter, IncrementFn).init(&my_counter, &Counter.increment);
bound_inc.call(.{5});
try testing.expectEqual(@as(u32, 5), counter);
var my_dec = Decrementer{ .count = &counter };
const bound_dec = Bind(Decrementer, IncrementFn).init(&my_dec, &Decrementer.decrement);
const iface1 = bound_inc.interface();
const iface2 = bound_dec.interface();
const void_ifaces = [_]IncInterface{ iface1, iface2 };
void_ifaces[0].call(.{3}); // counter = 5 + 3 = 8
try testing.expectEqual(@as(u32, 8), counter);
void_ifaces[1].call(.{2}); // counter = 8 - 2 = 6
try testing.expectEqual(@as(u32, 6), counter);
} }