diff options
| author | Patrick | 2026-05-17 17:44:09 +0000 |
|---|---|---|
| committer | Patrick | 2026-05-17 17:44:09 +0000 |
| commit | fbe506286ace10bb7d9fbd8114a34ae651b7e770 (patch) | |
| tree | 6ccbf9028077eb10337ccf10d4862534d940c084 /src | |
| parent | 5a9132af42bdcae15a7ceabb171aac10a5ee9a3a (diff) | |
| download | reactive-zig-main.tar.gz reactive-zig-main.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/refl.zig | 245 |
1 files changed, 198 insertions, 47 deletions
diff --git a/src/refl.zig b/src/refl.zig index 5cfcc52..1a34aad 100644 --- a/src/refl.zig +++ b/src/refl.zig @@ -1,9 +1,19 @@ const std = @import("std"); -fn Args(params: []const std.builtin.Type.Fn.Param) type { - var argTypes: [params.len]type = undefined; +const List = std.ArrayList; - for (params, 0..) |p, i| { +fn Args(comptime T: type, params: []const std.builtin.Type.Fn.Param) type { + const method = params.len > 0 and switch (@typeInfo(params[0].type.?)) { + .pointer => |p| p.child == T, + else => params[0].type == T, + }; + + const argCount = if (method) params.len - 1 else params.len; + + var argTypes: [argCount]type = undefined; + + for (0..argCount) |i| { + const p = if (method) params[i + 1] else params[i]; argTypes[i] = p.type.?; } @@ -11,14 +21,65 @@ fn Args(params: []const std.builtin.Type.Fn.Param) type { } fn Val(comptime T: type) type { - const ti = @typeInfo(T).@"struct"; + const ti: std.builtin.Type.Struct = switch (@typeInfo(T)) { + .@"struct" => |s| s, + .pointer => |p| @typeInfo(p.child).@"struct", + else => @compileError("only struct types allowed"), + }; - const fieldCount = ti.fields.len + ti.decls.len + 1; + const fieldCount = ti.fields.len + ti.decls.len + 1; // + 1 for value var names: [fieldCount][]const u8 = undefined; var types: [fieldCount]type = undefined; var attrs: [fieldCount]std.builtin.Type.StructField.Attributes = undefined; + // value + { + names[fieldCount - 1] = "value"; + const NT = struct { + value: T = undefined, + + pub fn init(self: *@This(), t: T) void { + self.value = t; + + inline for (ti.fields) |field| { + const parent: *Val(T) = @alignCast(@fieldParentPtr("value", self)); + @field(parent, field.name).value.init(&@field(self.value, field.name)); + } + inline for (ti.decls) |decl| { + const parent: *Val(T) = @alignCast(@fieldParentPtr("value", self)); + @field(parent, decl.name).init(); + } + } + + pub fn deinit(self: *@This(), alloc: std.mem.Allocator) void { + inline for (ti.fields) |field| { + const parent: *Val(T) = @alignCast(@fieldParentPtr("value", self)); + @field(parent, field.name).value.deinit(alloc); + } + inline for (ti.decls) |decl| { + const parent: *Val(T) = @alignCast(@fieldParentPtr("value", self)); + @field(parent, decl.name).deinit(alloc); + } + } + + pub fn get(self: *@This()) if (@typeInfo(T) == .pointer) T else *T { + if (@typeInfo(T) == .pointer) { + return self.value; + } else { + return &self.value; + } + } + }; + types[fieldCount - 1] = NT; + attrs[fieldCount - 1] = .{ + .@"comptime" = false, + .@"align" = @alignOf(NT), + .default_value_ptr = &NT{}, + }; + } + + // fields inline for (ti.fields, 0..) |field, idx| { if (field.is_comptime) @compileError("implement skipping comptime fields"); @@ -27,69 +88,122 @@ fn Val(comptime T: type) type { switch (fi) { .@"struct" => { names[idx] = field.name; - const NT = Val(field.type); + const NT = Val(*field.type); types[idx] = NT; - attrs[idx] = .{ - .default_value_ptr = &NT{}, - }; + attrs[idx] = .{}; }, else => { names[idx] = field.name; const NT = struct { + const _NT = @This(); + + value: struct { + value: *field.type = undefined, + + pub fn init(self: *@This(), value: *field.type) void { + self.value = value; + var parent: *_NT = @fieldParentPtr("value", self); + parent.on_get = .empty; + parent.on_set = .empty; + } + + pub fn deinit(self: *@This(), alloc: std.mem.Allocator) void { + var parent: *_NT = @fieldParentPtr("value", self); + parent.on_get.clearAndFree(alloc); + parent.on_set.clearAndFree(alloc); + } + }, + on_get: List(*const fn (field.type) void) = .empty, + on_set: List(*const fn (field.type) void) = .empty, + pub fn get(self: *@This()) field.type { - std.debug.print("getting {s}\n", .{field.name}); - const parent = self.valuePtr(); - return @field(parent.value, field.name); + for (self.on_get.items) |cb| cb(self.value.value.*); + return self.value.value.*; } pub fn set(self: *@This(), value: field.type) void { - std.debug.print("setting {s}\n", .{field.name}); - const parent = self.valuePtr(); - @field(parent.value, field.name) = value; - } - fn valuePtr(self: *@This()) *align(@alignOf(@This())) Val(T) { - const parent: *align(@alignOf(@This())) Val(T) = @fieldParentPtr(field.name, self); - return parent; + for (self.on_set.items) |cb| cb(self.value.value.*); + self.value.value.* = value; } }; types[idx] = NT; - attrs[idx] = .{ - .default_value_ptr = &NT{}, - }; + attrs[idx] = .{}; }, } } - inline for (ti.decls, 0..) |decl, idx| { - names[ti.fields.len + idx] = decl.name; - const fi = @typeInfo(@TypeOf(@field(T, decl.name))).@"fn"; + + // decls + inline for (ti.decls, ti.fields.len..) |decl, idx| { + names[idx] = decl.name; + const T2 = if (@typeInfo(T) == .pointer) @typeInfo(T).pointer.child else T; + const F = @TypeOf(@field(T2, decl.name)); + const fi = @typeInfo(F).@"fn"; + + const method = fi.params.len > 0 and switch (@typeInfo(fi.params[0].type.?)) { + .pointer => |p| p.child == T, + else => fi.params[0].type == T, + }; + const modifying = method and switch (@typeInfo(fi.params[0].type.?)) { + .pointer => |p| !p.is_const, + else => false, + }; const NT = struct { - pub fn call(self: @This(), args: Args(fi.params)) (fi.return_type orelse void) { - _ = self; - std.debug.print("calling {s}\n", .{decl.name}); - @call(.auto, @field(T, decl.name), args); - std.debug.print("done calling {s}\n", .{decl.name}); + on_call: List(*const F) = .empty, + on_modify: List(*const F) = .empty, + + pub fn init(self: *@This()) void { + self.on_call = .empty; + self.on_modify = .empty; + } + + pub fn deinit(self: *@This(), alloc: std.mem.Allocator) void { + self.on_call.clearAndFree(alloc); + self.on_modify.clearAndFree(alloc); + } + + pub fn call(self: *@This(), args: Args(T, fi.params)) (fi.return_type orelse void) { + const allArgs = blk: { + if (method) { + const parent: *Val(T) = @alignCast(@fieldParentPtr(decl.name, self)); + const tiv = @typeInfo(@TypeOf(parent.value.value)); + const tip = @typeInfo(fi.params[0].type.?); + const arg0 = + if (tiv == .pointer and tip != .pointer) parent.value.value.* else if (tiv != .pointer and tip == .pointer) &parent.value.value else parent.value.value; + break :blk .{arg0} ++ args; + } else { + break :blk args; + } + }; + + @call(.auto, @field(T2, decl.name), allArgs); + + for (self.on_call.items) |cb| @call(.auto, cb, allArgs); + if (modifying) + for (self.on_modify.items) |cb| @call(.auto, cb, allArgs); } }; - types[ti.fields.len + idx] = NT; - attrs[ti.fields.len + idx] = .{ .default_value_ptr = &NT{} }; + types[idx] = NT; + attrs[idx] = .{ .default_value_ptr = &NT{} }; } - names[fieldCount - 1] = "value"; - types[fieldCount - 1] = T; - attrs[fieldCount - 1] = .{ - .@"comptime" = false, - .@"align" = @alignOf(T), - .default_value_ptr = &T{}, - }; return @Struct(.auto, null, &names, &types, &attrs); } -pub fn main() !void { - var a = Val(struct { +pub fn main(init: std.process.Init) !void { + const A = struct { i: i32 = 123, j: i32 = 456, s: struct { i: i32 = 789, + t: struct { + j: i32 = 341, + pub fn yo() void { + std.debug.print("t.yo!\n", .{}); + } + } = .{}, + pub fn jo() void { + std.debug.print("s.jo!\n", .{}); + } } = .{}, pub fn b() void { std.debug.print("B!\n", .{}); @@ -97,18 +211,55 @@ pub fn main() !void { pub fn c(self: @This()) void { std.debug.print("C! ({})\n", .{self}); } - pub fn d(self: @This(), i: i32) void { + pub fn d(self: *@This(), i: i32) void { self.i += i; std.debug.print("D! ({})\n", .{self}); } - }){}; - std.debug.print("[{}] {}\n{}\n", .{ @TypeOf(a), a, a.value }); - // a.b.call(.{}); - // a.c.call(.{a}); - // a.c.call(.{a, 5}); + }; + + var a: Val(A) = undefined; + a.value.init(.{}); + defer a.value.deinit(init.gpa); + + const callbacks = struct { + fn set1(i: i32) void { + std.debug.print("{} was set\n", .{i}); + } + fn get1(i: i32) void { + std.debug.print("{} was get\n", .{i}); + } + fn f0() void { + std.debug.print("called!\n", .{}); + } + fn f1(self: A) void { + std.debug.print("called ({})!\n", .{self}); + } + fn f2(self: *A, i: i32) void { + std.debug.print("called ({}, {})!\n", .{ self, i }); + } + }; + + try a.j.on_get.append(init.gpa, callbacks.get1); + try a.j.on_set.append(init.gpa, callbacks.set1); + try a.b.on_call.append(init.gpa, callbacks.f0); + try a.c.on_call.append(init.gpa, callbacks.f1); + try a.d.on_call.append(init.gpa, callbacks.f2); + try a.s.jo.on_call.append(init.gpa, callbacks.f0); + try a.s.t.yo.on_call.append(init.gpa, callbacks.f0); + + std.debug.print("{}\n{}\n", .{ a, a.value }); + a.b.call(.{}); + std.debug.print("{}\n", .{a.value.get()}); + std.debug.print("{}\n", .{a.s.value.get()}); + a.c.call(.{}); + a.d.call(.{5}); + a.s.jo.call(.{}); + a.s.t.yo.call(.{}); std.debug.print("{}\n", .{a.i.get()}); std.debug.print("{}\n", .{a.j.get()}); a.j.set(9); std.debug.print("{}\n", .{a.j.get()}); + std.debug.print("{}\n", .{a.j.get()}); + std.debug.print("{}\n", .{a.j.get()}); std.debug.print("{}\n", .{a.s.i.get()}); } |
