Skip to content

Commit ca95711

Browse files
committed
Use tuples as closure param
Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
1 parent ab9c140 commit ca95711

File tree

4 files changed

+58
-20
lines changed

4 files changed

+58
-20
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,21 @@ pub fn main() !void {
138138
// Call Lua function from Zig
139139
const result2 = try lua.globals().call("multiply", .{6, 7}, i32);
140140
assert(result2 == 42);
141+
142+
// Closures with captured values
143+
const table = lua.createTable(.{});
144+
defer table.deinit();
145+
146+
fn getGlobal(lua_ptr: *luaz.Lua, key: []const u8) !i32 {
147+
return try lua_ptr.globals().get(key, i32) orelse 0;
148+
}
149+
const lua_ptr = @constCast(&lua);
150+
try table.setClosure("getGlobal", .{lua_ptr}, getGlobal);
151+
try lua.globals().set("funcs", table);
152+
try lua.globals().set("myValue", @as(i32, 123));
153+
154+
const result3 = try lua.eval("return funcs.getGlobal('myValue')", .{}, i32);
155+
assert(result3 == 123);
141156
}
142157
```
143158

src/lua.zig

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -406,10 +406,15 @@ pub const Lua = struct {
406406
/// - `upvalues`: Values to be captured as upvalues (single value or tuple)
407407
/// - `func`: A Zig function that accepts upvalues as its first parameter
408408
///
409-
/// Example:
409+
/// Examples:
410410
/// ```zig
411-
/// fn scaledAdd(upvalues: struct { scale: f32, offset: f32 }, x: f32) f32 {
412-
/// return x * upvalues.scale + upvalues.offset;
411+
/// // Single upvalue
412+
/// fn add(increment: i32, x: i32) i32 { return x + increment; }
413+
/// try table.setClosure("add5", .{5}, add);
414+
///
415+
/// // Multiple upvalues (tuple)
416+
/// fn scaledAdd(upvals: struct { f32, f32 }, x: f32) f32 {
417+
/// return x * upvals[0] + upvals[1];
413418
/// }
414419
/// try table.setClosure("transform", .{ 2.0, 10.0 }, scaledAdd);
415420
/// ```

src/stack.zig

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -306,11 +306,21 @@ pub fn createFunc(_: Lua, value: anytype, comptime ClosureType: ?type) State.CFu
306306
const arg_start_idx = if (has_upvalues) blk: {
307307
// First parameter is upvalues - populate from upvalue indices
308308
const CT = ClosureType.?;
309-
const upvalues_fields = std.meta.fields(CT);
310-
inline for (upvalues_fields, 0..) |field, i| {
311-
const idx = State.upvalueIndex(@intCast(i + 1));
312-
@field(args[0], field.name) = toValue(l, field.type, idx) orelse
313-
l.state.typeError(idx, @typeName(field.type));
309+
const ct_info = @typeInfo(CT);
310+
311+
if (ct_info == .@"struct" and ct_info.@"struct".is_tuple) {
312+
// Multiple upvalues as tuple
313+
const upvalues_fields = std.meta.fields(CT);
314+
inline for (upvalues_fields, 0..) |field, i| {
315+
const idx = State.upvalueIndex(@intCast(i + 1));
316+
args[0][i] = toValue(l, field.type, idx) orelse
317+
l.state.typeError(idx, @typeName(field.type));
318+
}
319+
} else {
320+
// Single upvalue
321+
const idx = State.upvalueIndex(1);
322+
args[0] = toValue(l, CT, idx) orelse
323+
l.state.typeError(idx, @typeName(CT));
314324
}
315325
break :blk 1; // Start stack args from index 1, skip upvalue parameter
316326
} else blk: {

src/tests.zig

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,29 +1365,33 @@ test "table readonly" {
13651365
try expect(try table.get("after", ?i32) == null);
13661366
}
13671367

1368-
fn closureAdd5(upv: struct { n: i32 }, x: i32) i32 {
1369-
return x + upv.n;
1368+
fn closureAdd5(n: i32, x: i32) i32 {
1369+
return x + n;
13701370
}
13711371

1372-
fn closureTransform(upv: struct { a: f32, b: f32 }, x: f32) f32 {
1373-
return x * upv.a + upv.b;
1372+
fn closureTransform(upv: struct { f32, f32 }, x: f32) f32 {
1373+
return x * upv[0] + upv[1];
13741374
}
13751375

1376-
fn closureOptAdd(upv: struct { thresh: i32 }, x: i32, y: ?i32) i32 {
1377-
return if (x > upv.thresh) x + (y orelse 0) else x;
1376+
fn closureOptAdd(thresh: i32, x: i32, y: ?i32) i32 {
1377+
return if (x > thresh) x + (y orelse 0) else x;
13781378
}
13791379

1380-
fn closureMultiply(upv: struct { cfg: Lua.Table }, x: i32) !i32 {
1381-
const m = try upv.cfg.get("mult", i32) orelse 1;
1380+
fn closureMultiply(cfg: Lua.Table, x: i32) !i32 {
1381+
const m = try cfg.get("mult", i32) orelse 1;
13821382
return x * m;
13831383
}
13841384

1385-
fn closureConstant(upv: struct { val: i32 }) i32 {
1386-
return upv.val;
1385+
fn closureConstant(val: i32) i32 {
1386+
return val;
13871387
}
13881388

1389-
fn closureSumAll(upv: struct { base: i32 }, a: ?i32, b: ?i32) i32 {
1390-
return upv.base + (a orelse 0) + (b orelse 0);
1389+
fn closureSumAll(base: i32, a: ?i32, b: ?i32) i32 {
1390+
return base + (a orelse 0) + (b orelse 0);
1391+
}
1392+
1393+
fn closureSingle(increment: i32, x: i32) i32 {
1394+
return x + increment;
13911395
}
13921396

13931397
test "table setClosure" {
@@ -1418,6 +1422,9 @@ test "table setClosure" {
14181422
// Multiple optionals
14191423
try table.setClosure("sum", .{100}, closureSumAll);
14201424

1425+
// Single upvalue (not wrapped in struct)
1426+
try table.setClosure("single", .{42}, closureSingle);
1427+
14211428
try lua.globals().set("f", table);
14221429
try lua.globals().set("config", config);
14231430

@@ -1431,6 +1438,7 @@ test "table setClosure" {
14311438
try expect(try lua.eval("return f.const()", .{}, i32) == 42);
14321439
try expect(try lua.eval("return f.sum()", .{}, i32) == 100);
14331440
try expect(try lua.eval("return f.sum(1, 2)", .{}, i32) == 103);
1441+
try expect(try lua.eval("return f.single(8)", .{}, i32) == 50);
14341442

14351443
// Test config modification
14361444
_ = try lua.eval("config.mult = 7", .{}, void);

0 commit comments

Comments
 (0)