From 8470eb9a1ead3cb8f4e93308c1bf394ca680c46d Mon Sep 17 00:00:00 2001 From: Janne Hellsten Date: Sun, 14 Sep 2025 17:02:00 +0300 Subject: [PATCH 1/2] Add fnRegsFromStruct for comptime-generation of Lua function registrations --- src/lib.zig | 35 +++++++++++++++++++++++++++++++++++ src/tests.zig | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/lib.zig b/src/lib.zig index 0c87bfc..27f226f 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -5408,3 +5408,38 @@ pub fn exportFn(comptime name: []const u8, comptime func: anytype) CFn { } }.luaopen; } + +/// Generates a list of Lua function registrations (`FnReg`) from all +/// pub declarations in a struct type `T`. +/// +/// Example: +/// +/// ```zig +/// const MyLib = struct { +/// fn someHelper(...) { ... } // non-pub funcs skipped +/// pub fn foo(l: *Lua) void { ... } +/// pub fn bar(l: *Lua) void { ... } +/// +/// }; +/// +/// const funcs = fnRegsFromStruct(MyLib); +/// lua.newLib(funcs); +/// lua.setGlobal("mylib"); // mylib.foo, mylib.bar now visible +/// ``` +pub inline fn fnRegsFromStruct(comptime T: type) []const FnReg { + comptime { + const decls = switch (@typeInfo(T)) { + .@"struct" => |info| info.decls, + else => @compileError("Expected struct, found '" ++ @typeName(T) ++ "'"), + }; + var funcs: [decls.len]FnReg = undefined; + for (decls, 0..) |d, i| { + funcs[i] = .{ + .name = d.name, + .func = wrap(@field(T, d.name)), + }; + } + const final = funcs; + return &final; + } +} diff --git a/src/tests.zig b/src/tests.zig index 1062b43..4c567e5 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -3093,3 +3093,36 @@ test "checkNumeric and toNumeric" { _ = std.mem.indexOf(u8, string, error_msg) orelse return error.BadErrorMessage; } } + +test "function registration with fnRegsFromStruct" { + const lua: *Lua = try .init(testing.allocator); + defer lua.deinit(); + + const MyLib = struct { + pub fn add(l: *Lua) i32 { + const a = l.toInteger(1) catch 0; + const b = l.toInteger(2) catch 0; + l.pushInteger(a + b); + return 1; + } + pub fn neg(l: *Lua) i32 { + const a = l.toInteger(1) catch 0; + l.pushInteger(-a); + return 1; + } + }; + + // Construct function registration table at comptime from + // public decls on MyLib. + + const funcs = zlua.fnRegsFromStruct(MyLib); + if (zlua.lang == .lua51 or zlua.lang == .luau or zlua.lang == .luajit) { + lua.newTable(); + lua.registerFns("fnregs", funcs); + } else { + lua.newLib(zlua.fnRegsFromStruct(MyLib)); + lua.setGlobal("fnregs"); + } + try lua.doString("res = fnregs.add(100, fnregs.neg(25))"); + try expectEqual(75, lua.get(i32, "res")); +} From 08bf149bb19aee52e4c143a943bb2684cf804168 Mon Sep 17 00:00:00 2001 From: Robbie Lyman Date: Sun, 14 Sep 2025 17:38:59 -0400 Subject: [PATCH 2/2] fix: allow all types with user-defined decls --- src/lib.zig | 29 +++++++++++++++-------------- src/tests.zig | 6 +++--- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 27f226f..bdc314c 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -5422,24 +5422,25 @@ pub fn exportFn(comptime name: []const u8, comptime func: anytype) CFn { /// /// }; /// -/// const funcs = fnRegsFromStruct(MyLib); +/// const funcs = fnRegsFromType(MyLib); /// lua.newLib(funcs); /// lua.setGlobal("mylib"); // mylib.foo, mylib.bar now visible /// ``` -pub inline fn fnRegsFromStruct(comptime T: type) []const FnReg { - comptime { - const decls = switch (@typeInfo(T)) { - .@"struct" => |info| info.decls, - else => @compileError("Expected struct, found '" ++ @typeName(T) ++ "'"), - }; - var funcs: [decls.len]FnReg = undefined; - for (decls, 0..) |d, i| { - funcs[i] = .{ +pub fn fnRegsFromType(comptime T: type) []const FnReg { + const decls = switch (@typeInfo(T)) { + inline .@"struct", .@"enum", .@"union", .@"opaque" => |info| info.decls, + else => @compileError("Type " ++ @typeName(T) ++ "does not allow declarations"), + }; + comptime var funcs: []const FnReg = &.{}; + inline for (decls) |d| { + if (@typeInfo(@TypeOf(@field(T, d.name))) == .@"fn") { + const reg: []const FnReg = &.{.{ .name = d.name, - .func = wrap(@field(T, d.name)), - }; + .func = comptime wrap(@field(T, d.name)), + }}; + funcs = funcs ++ reg; } - const final = funcs; - return &final; } + const final = funcs; + return final; } diff --git a/src/tests.zig b/src/tests.zig index 4c567e5..0158c3d 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -3094,7 +3094,7 @@ test "checkNumeric and toNumeric" { } } -test "function registration with fnRegsFromStruct" { +test "function registration with fnRegsFromType" { const lua: *Lua = try .init(testing.allocator); defer lua.deinit(); @@ -3115,12 +3115,12 @@ test "function registration with fnRegsFromStruct" { // Construct function registration table at comptime from // public decls on MyLib. - const funcs = zlua.fnRegsFromStruct(MyLib); if (zlua.lang == .lua51 or zlua.lang == .luau or zlua.lang == .luajit) { + const funcs = comptime zlua.fnRegsFromType(MyLib); lua.newTable(); lua.registerFns("fnregs", funcs); } else { - lua.newLib(zlua.fnRegsFromStruct(MyLib)); + lua.newLib(comptime zlua.fnRegsFromType(MyLib)); lua.setGlobal("fnregs"); } try lua.doString("res = fnregs.add(100, fnregs.neg(25))");