From 3e3d13baa6b1cc25b6ce1dea9e22ef736d3440a4 Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Fri, 9 Feb 2024 19:56:59 -0600 Subject: [PATCH 01/10] added support for enums --- src/lib.zig | 12 ++++++++++++ src/tests.zig | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/lib.zig b/src/lib.zig index 8556ba7..c6bf612 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -3074,6 +3074,9 @@ pub const Lua = struct { .Bool => { lua.pushBoolean(value); }, + .Enum => { + _ = lua.pushString(@tagName(value)); + }, .Optional, .Null => { if (value == null) { lua.pushNil(); @@ -3156,6 +3159,15 @@ pub const Lua = struct { .Bool => { return lua.toBoolean(index); }, + .Enum => |info| { + const string = try lua.toAny([]const u8, index); + inline for (info.fields) |enum_member| { + if (std.mem.eql(u8, string, enum_member.name)) { + return @field(T, enum_member.name); + } + } + return error.InvalidEnumTagName; + }, .Struct => { return try lua.toStruct(T, index); }, diff --git a/src/tests.zig b/src/tests.zig index e11e09e..c505ecc 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -2447,6 +2447,12 @@ test "toAny" { lua.pushNil(); const maybe = try lua.toAny(?i32, -1); try testing.expect(maybe == null); + + //enum + const MyEnumType = enum { hello, goodbye }; + _ = lua.pushString("hello"); + const my_enum = try lua.toAny(MyEnumType, -1); + try testing.expect(my_enum == MyEnumType.hello); } test "toAny struct" { @@ -2528,6 +2534,12 @@ test "pushAny" { const my_optional: ?i32 = -1; try lua.pushAny(my_optional); try testing.expect(try lua.toAny(?i32, -1) == my_optional); + + //enum + const MyEnumType = enum { hello, goodbye }; + try lua.pushAny(MyEnumType.goodbye); + const my_enum = try lua.toAny(MyEnumType, -1); + try testing.expect(my_enum == MyEnumType.goodbye); } test "pushAny struct" { From 31ca3b97bcd612feb67936032a2474cf3102517d Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Fri, 9 Feb 2024 20:15:29 -0600 Subject: [PATCH 02/10] added support for slices --- src/lib.zig | 46 +++++++++++++++++++++++++++++++++++----------- src/tests.zig | 17 +++++++++++++++++ 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index c6bf612..72f1620 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -3136,19 +3136,20 @@ pub const Lua = struct { .Pointer => |param_info| { switch (param_info.size) { .Slice, .Many => { - if (param_info.child != u8) { - @compileError("Only u8 arrays (strings) may be parameters"); - } - if (!param_info.is_const) { - @compileError("Slice must be a const slice"); - } - const string: [*:0]const u8 = try lua.toString(index); - const end = std.mem.indexOfSentinel(u8, 0, string); + if (param_info.child == u8) { + if (!param_info.is_const) { + @compileError("Slice must be a const slice"); + } + const string: [*:0]const u8 = try lua.toString(index); + const end = std.mem.indexOfSentinel(u8, 0, string); - if (param_info.sentinel == null) { - return string[0..end]; + if (param_info.sentinel == null) { + return string[0..end]; + } else { + return string[0..end :0]; + } } else { - return string[0..end :0]; + return try lua.toSlice(param_info.child, index); } }, else => { @@ -3185,6 +3186,29 @@ pub const Lua = struct { } } + /// Converts a lua array to a zig slice, memory is owned by the caller + fn toSlice(lua: *Lua, comptime ChildType: type, raw_index: i32) ![]ChildType { + const index = lua.absIndex(raw_index); + + if (!lua.isTable(index)) { + return error.ValueNotATable; + } + + lua.len(index); + const size = try lua.toAny(usize, -1); + + const a = lua.allocator(); + var result = try std.ArrayListUnmanaged(ChildType).initCapacity(a, size); + + for (1..size + 1) |i| { + _ = try lua.pushAny(i); + _ = lua.getTable(index); + try result.append(a, try lua.toAny(ChildType, -1)); + } + + return result.items; + } + /// Converts value at given index to a zig struct if possible fn toStruct(lua: *Lua, comptime T: type, raw_index: i32) !T { const index = lua.absIndex(raw_index); diff --git a/src/tests.zig b/src/tests.zig index c505ecc..ade3186 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -2501,6 +2501,23 @@ test "toAny struct recursive" { _ = my_struct; } +test "toAny slice" { + var lua = try Lua.init(&testing.allocator); + defer lua.deinit(); + + const program = + \\list = {1, 2, 3, 4, 5} + ; + try lua.doString(program); + _ = try lua.getGlobal("list"); + const sliced = try lua.toAny([]u32, -1); + defer testing.allocator.free(sliced); + + try testing.expect( + std.mem.eql(u32, &[_]u32{ 1, 2, 3, 4, 5 }, sliced), + ); +} + test "pushAny" { var lua = try Lua.init(&testing.allocator); defer lua.deinit(); From a3db9f972022ece01846b88fdb605bdd22b72336 Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Fri, 9 Feb 2024 20:44:13 -0600 Subject: [PATCH 03/10] compile error fix for lua versions missing lua_len --- src/lib.zig | 20 +++++++++++--------- src/tests.zig | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 72f1620..e137f50 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -3194,19 +3194,21 @@ pub const Lua = struct { return error.ValueNotATable; } - lua.len(index); - const size = try lua.toAny(usize, -1); + var result = std.ArrayListUnmanaged(ChildType){}; + defer result.deinit(lua.allocator()); - const a = lua.allocator(); - var result = try std.ArrayListUnmanaged(ChildType).initCapacity(a, size); - - for (1..size + 1) |i| { + var i: usize = 1; + while (true) : (i += 1) { _ = try lua.pushAny(i); - _ = lua.getTable(index); - try result.append(a, try lua.toAny(ChildType, -1)); + const value_type = lua.getTable(index); + if (value_type == .nil) { + break; + } + try result.append(lua.allocator(), try lua.toAny(ChildType, -1)); } - return result.items; + //temporary solution to fix mismatched sized free error + return try lua.allocator().dupe(ChildType, result.items); } /// Converts value at given index to a zig struct if possible diff --git a/src/tests.zig b/src/tests.zig index ade3186..129bffe 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -2511,7 +2511,7 @@ test "toAny slice" { try lua.doString(program); _ = try lua.getGlobal("list"); const sliced = try lua.toAny([]u32, -1); - defer testing.allocator.free(sliced); + defer lua.allocator().free(sliced); try testing.expect( std.mem.eql(u32, &[_]u32{ 1, 2, 3, 4, 5 }, sliced), From 42a7b1722a1e8af72767b586e367e1850c126b3a Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Sat, 10 Feb 2024 09:55:39 -0600 Subject: [PATCH 04/10] pointer logic refactor --- src/lib.zig | 117 +++++++++++++++++++++++++++++++------------------- src/tests.zig | 9 ++++ 2 files changed, 83 insertions(+), 43 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index e137f50..6fb1067 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -3026,6 +3026,49 @@ pub const Lua = struct { _ = c.luaopen_bit32(lua.state); } + /// Returns if given typeinfo is a string type + fn isTypeString(typeinfo: std.builtin.Type.Pointer) bool { + const childinfo = @typeInfo(typeinfo.child); + if (typeinfo.child == u8 and typeinfo.size != .One) { + return true; + } else if (typeinfo.size == .One and childinfo == .Array and childinfo.Array.child == u8) { + return true; + } + return false; + } + + /// Pushes any string type + fn pushAnyString(lua: *Lua, value: anytype) !void { + const info = @typeInfo(@TypeOf(value)).Pointer; + switch (info.size) { + .One => { + const childinfo = @typeInfo(info.child).Array; + std.debug.assert(childinfo.child == u8); + std.debug.assert(childinfo.sentinel != null); + + const casted: *childinfo.child = @ptrCast(@constCast(childinfo.sentinel.?)); + if (casted.* != 0) { + @compileError("Sentinel of slice must be a null terminator"); + } + _ = lua.pushString(&(value.*)); + }, + .C, .Many, .Slice => { + std.debug.assert(info.child == u8); + if (info.sentinel) |sentinel| { + const casted: *info.child = @ptrCast(@constCast(sentinel)); + if (casted.* != 0) { + @compileError("Sentinel of slice must be a null terminator"); + } + _ = lua.pushString(value); + } else { + const null_terminated = try lua.allocator().dupeZ(u8, value); + defer lua.allocator().free(null_terminated); + _ = lua.pushString(null_terminated); + } + }, + } + } + /// Pushes any valid zig value onto the stack, /// Works with ints, floats, booleans, structs, /// optionals, and strings @@ -3038,35 +3081,23 @@ pub const Lua = struct { lua.pushNumber(@floatCast(value)); }, .Pointer => |info| { - switch (info.size) { + if (comptime isTypeString(info)) { + try lua.pushAnyString(value); + } else switch (info.size) { .One => { - if (@typeInfo(info.child) == .Array) { - if (@typeInfo(info.child).Array.child != u8) { - @compileError("only u8 arrays can be pushed"); - } - _ = lua.pushString(&(value.*)); - } else { - if (info.is_const) { - @compileLog(value); - @compileError("Pointer must not be const"); - } - lua.pushLightUserdata(@ptrCast(value)); + if (info.is_const) { + @compileLog(value); + @compileLog("Lua cannot guarantee that references will not be modified"); + @compileError("Pointer must not be const"); } + lua.pushLightUserdata(@ptrCast(value)); }, .C, .Many, .Slice => { - if (info.child != u8) { - @compileError("Only u8 slices (strings) are valid slice types"); - } - if (info.sentinel) |sentinel| { - const casted: *info.child = @ptrCast(@constCast(sentinel)); - if (casted.* != 0) { - @compileError("Sentinel of slice must be a null terminator"); - } - _ = lua.pushString(value); - } else { - const null_terminated = try lua.allocator().dupeZ(u8, value); - defer lua.allocator().free(null_terminated); - _ = lua.pushString(null_terminated); + lua.createTable(0, 0); + for (value, 0..) |index_value, i| { + try lua.pushAny(i); + try lua.pushAny(index_value); + lua.setTable(-3); } }, } @@ -3133,27 +3164,27 @@ pub const Lua = struct { }, } }, - .Pointer => |param_info| { - switch (param_info.size) { + + //TODO: audit this + .Pointer => |info| { + if (comptime isTypeString(info)) { + //if (!info.is_const) { + //@compileError("Slice must be a const slice"); + //} + const string: [*:0]const u8 = try lua.toString(index); + const end = std.mem.indexOfSentinel(u8, 0, string); + + if (info.sentinel == null) { + return string[0..end]; + } else { + return string[0..end :0]; + } + } else switch (info.size) { .Slice, .Many => { - if (param_info.child == u8) { - if (!param_info.is_const) { - @compileError("Slice must be a const slice"); - } - const string: [*:0]const u8 = try lua.toString(index); - const end = std.mem.indexOfSentinel(u8, 0, string); - - if (param_info.sentinel == null) { - return string[0..end]; - } else { - return string[0..end :0]; - } - } else { - return try lua.toSlice(param_info.child, index); - } + return try lua.toSlice(info.child, index); }, else => { - return try lua.toUserdata(param_info.child, index); + return try lua.toUserdata(info.child, index); }, } }, diff --git a/src/tests.zig b/src/tests.zig index 129bffe..2ff7137 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -2575,6 +2575,15 @@ test "pushAny struct" { try testing.expect(value.bar == (MyType{}).bar); } +test "pushAny slice" { + var lua = try Lua.init(&testing.allocator); + defer lua.deinit(); + + var my_array = [_]u32{ 1, 2, 3, 4, 5 }; + const my_slice: []u32 = my_array[0..]; + try lua.pushAny(my_slice); +} + fn foo(a: i32, b: i32) i32 { return a + b; } From 7d943f92fd10d787a06011f50e8476cd7829440e Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Sat, 10 Feb 2024 10:07:43 -0600 Subject: [PATCH 05/10] added version agnostic length function, removed memory duplication --- src/lib.zig | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 6fb1067..39003fd 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -1357,6 +1357,14 @@ pub const Lua = struct { return c.lua_objlen(lua.state, index); } + /// returns length of table in any lua version + pub fn versionAgnosticLen(lua: *Lua, index: i32) usize { + switch (lang) { + .lua51, .luau => return @intCast(lua.objectLen(index)), + else => return @intCast(lua.rawLen(index)), + } + } + fn protectedCall51(lua: *Lua, num_args: i32, num_results: i32, err_func: i32) !void { // The translate-c version of lua_pcall does not type-check so we must rewrite it // (macros don't always translate well with translate-c) @@ -3164,13 +3172,8 @@ pub const Lua = struct { }, } }, - - //TODO: audit this .Pointer => |info| { if (comptime isTypeString(info)) { - //if (!info.is_const) { - //@compileError("Slice must be a const slice"); - //} const string: [*:0]const u8 = try lua.toString(index); const end = std.mem.indexOfSentinel(u8, 0, string); @@ -3225,21 +3228,16 @@ pub const Lua = struct { return error.ValueNotATable; } - var result = std.ArrayListUnmanaged(ChildType){}; - defer result.deinit(lua.allocator()); + const size = lua.versionAgnosticLen(index); + var result = try lua.allocator().alloc(ChildType, size); - var i: usize = 1; - while (true) : (i += 1) { + for (1..size + 1) |i| { _ = try lua.pushAny(i); - const value_type = lua.getTable(index); - if (value_type == .nil) { - break; - } - try result.append(lua.allocator(), try lua.toAny(ChildType, -1)); + _ = lua.getTable(index); + result[i - 1] = try lua.toAny(ChildType, -1); } - //temporary solution to fix mismatched sized free error - return try lua.allocator().dupe(ChildType, result.items); + return result; } /// Converts value at given index to a zig struct if possible From 8fef6b1d38a1616ea54e1f0ded4eaca495a5f542 Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Sat, 10 Feb 2024 10:46:18 -0600 Subject: [PATCH 06/10] added more complex test --- src/lib.zig | 2 -- src/tests.zig | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 39003fd..6ec216c 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -3145,8 +3145,6 @@ pub const Lua = struct { /// Converts the specified index of the lua stack to the specified /// type if possible and returns it pub fn toAny(lua: *Lua, comptime T: type, index: i32) !T { - - //TODO implement enums switch (@typeInfo(T)) { .Int => { switch (comptime lang) { diff --git a/src/tests.zig b/src/tests.zig index 2ff7137..15a4f48 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -2642,3 +2642,19 @@ test "get set" { try lua.set("foo", 'a'); try testing.expect(try lua.get(u8, "foo") == 'a'); } + +test "array of strings" { + var lua = try Lua.init(&testing.allocator); + defer lua.deinit(); + + const program = + \\function strings() + \\ return {"hello", "world", "my name", "is foobar"} + \\end + ; + + try lua.doString(program); + + const strings = try lua.autoCall([]const []const u8, "strings", .{}); + lua.allocator().free(strings); +} From 711d695dee18a38eb3aa4f7d815633772038a929 Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Sun, 11 Feb 2024 18:15:55 -0600 Subject: [PATCH 07/10] add fixed length array support to pushAny --- src/lib.zig | 8 ++++++++ src/tests.zig | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/lib.zig b/src/lib.zig index 6ec216c..2b9ce83 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -3110,6 +3110,14 @@ pub const Lua = struct { }, } }, + .Array => { + lua.createTable(0, 0); + for (value, 0..) |index_value, i| { + try lua.pushAny(i); + try lua.pushAny(index_value); + lua.setTable(-3); + } + }, .Bool => { lua.pushBoolean(value); }, diff --git a/src/tests.zig b/src/tests.zig index 15a4f48..2489c25 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -2575,13 +2575,14 @@ test "pushAny struct" { try testing.expect(value.bar == (MyType{}).bar); } -test "pushAny slice" { +test "pushAny slice/array" { var lua = try Lua.init(&testing.allocator); defer lua.deinit(); var my_array = [_]u32{ 1, 2, 3, 4, 5 }; const my_slice: []u32 = my_array[0..]; try lua.pushAny(my_slice); + try lua.pushAny(my_array); } fn foo(a: i32, b: i32) i32 { From f5b7dc818dbb44868efac0958aa46bc6dfdb4c45 Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Sun, 11 Feb 2024 18:51:32 -0600 Subject: [PATCH 08/10] automatic api construction from struct test added --- src/tests.zig | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/tests.zig b/src/tests.zig index 2489c25..70dc6d1 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -2605,14 +2605,28 @@ test "autoPushFunction" { lua.autoPushFunction(bar); lua.setGlobal("bar"); - try lua.doString("result = foo(1, 2)"); - - const program = + try lua.doString( + \\result = foo(1, 2) + ); + lua.doString( \\local status, result = pcall(bar, 1, 2) - ; - lua.doString(program) catch |err| { - std.debug.print("{!}\n\n", .{err}); + ) catch |err| { + std.debug.print("auto push function error: {!}\n", .{err}); + std.debug.print("{s}\n", .{try lua.toString(-1)}); + return err; }; + + //automatic api construction + const my_api = .{ + .foo = foo, + .bar = bar, + }; + try lua.pushAny(my_api); + lua.setGlobal("api"); + + try lua.doString( + \\api.foo(1, 2) + ); } test "autoCall" { @@ -2659,3 +2673,13 @@ test "array of strings" { const strings = try lua.autoCall([]const []const u8, "strings", .{}); lua.allocator().free(strings); } +// +// +// +// +// +// +// +// +// +// From 155cb761e938cb191b87a0f37806810780b3bbfa Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Sun, 11 Feb 2024 18:52:45 -0600 Subject: [PATCH 09/10] formatting changes --- src/tests.zig | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/tests.zig b/src/tests.zig index 70dc6d1..33f5817 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -2608,19 +2608,16 @@ test "autoPushFunction" { try lua.doString( \\result = foo(1, 2) ); - lua.doString( + try lua.doString( \\local status, result = pcall(bar, 1, 2) - ) catch |err| { - std.debug.print("auto push function error: {!}\n", .{err}); - std.debug.print("{s}\n", .{try lua.toString(-1)}); - return err; - }; + ); //automatic api construction const my_api = .{ .foo = foo, .bar = bar, }; + try lua.pushAny(my_api); lua.setGlobal("api"); @@ -2673,13 +2670,3 @@ test "array of strings" { const strings = try lua.autoCall([]const []const u8, "strings", .{}); lua.allocator().free(strings); } -// -// -// -// -// -// -// -// -// -// From 68ba27afd1cb23ad2a23d6c353c586adccf27a7d Mon Sep 17 00:00:00 2001 From: Robert Burnett Date: Tue, 20 Feb 2024 21:25:10 -0600 Subject: [PATCH 10/10] updated rawLen to work on all lua versions --- src/lib.zig | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/lib.zig b/src/lib.zig index 2b9ce83..82609ae 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -1357,14 +1357,6 @@ pub const Lua = struct { return c.lua_objlen(lua.state, index); } - /// returns length of table in any lua version - pub fn versionAgnosticLen(lua: *Lua, index: i32) usize { - switch (lang) { - .lua51, .luau => return @intCast(lua.objectLen(index)), - else => return @intCast(lua.rawLen(index)), - } - } - fn protectedCall51(lua: *Lua, num_args: i32, num_results: i32, err_func: i32) !void { // The translate-c version of lua_pcall does not type-check so we must rewrite it // (macros don't always translate well with translate-c) @@ -1659,14 +1651,15 @@ pub const Lua = struct { /// For userdata it is the size of the block of memory /// For other values the call returns 0 /// See https://www.lua.org/manual/5.4/manual.html#lua_rawlen - pub fn rawLen(lua: *Lua, index: i32) switch (lang) { - .lua52, .lua53 => usize, - else => Unsigned, - } { - return c.lua_rawlen(lua.state, index); + pub fn rawLen(lua: *Lua, index: i32) usize { + switch (lang) { + .lua51, .luau => return @intCast(c.lua_objlen(lua.state, index)), + else => return @intCast(c.lua_rawlen(lua.state, index)), + } } - /// Similar to `Lua.setTable()` but does a raw assignment (without metamethods) + /// Similar to `Lua.setTable()` but does a raw asskdjfal;sdkfjals;dkfj;dk:q + /// gnment (without metamethods) /// See https://www.lua.org/manual/5.4/manual.html#lua_rawset pub fn rawSetTable(lua: *Lua, index: i32) void { c.lua_rawset(lua.state, index); @@ -3058,7 +3051,7 @@ pub const Lua = struct { if (casted.* != 0) { @compileError("Sentinel of slice must be a null terminator"); } - _ = lua.pushString(&(value.*)); + _ = lua.pushString(value); }, .C, .Many, .Slice => { std.debug.assert(info.child == u8); @@ -3234,7 +3227,7 @@ pub const Lua = struct { return error.ValueNotATable; } - const size = lua.versionAgnosticLen(index); + const size = lua.rawLen(index); var result = try lua.allocator().alloc(ChildType, size); for (1..size + 1) |i| {