diff --git a/src/lib.zig b/src/lib.zig index 8556ba7..82609ae 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -1651,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); @@ -3026,6 +3027,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,42 +3082,41 @@ 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); } }, } }, + .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); }, + .Enum => { + _ = lua.pushString(@tagName(value)); + }, .Optional, .Null => { if (value == null) { lua.pushNil(); @@ -3103,8 +3146,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) { @@ -3130,32 +3171,37 @@ pub const Lua = struct { }, } }, - .Pointer => |param_info| { - switch (param_info.size) { + .Pointer => |info| { + if (comptime isTypeString(info)) { + 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) { - @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.sentinel == null) { - return string[0..end]; - } else { - return string[0..end :0]; - } + return try lua.toSlice(info.child, index); }, else => { - return try lua.toUserdata(param_info.child, index); + return try lua.toUserdata(info.child, index); }, } }, .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); }, @@ -3173,6 +3219,26 @@ 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; + } + + const size = lua.rawLen(index); + var result = try lua.allocator().alloc(ChildType, size); + + for (1..size + 1) |i| { + _ = try lua.pushAny(i); + _ = lua.getTable(index); + result[i - 1] = try lua.toAny(ChildType, -1); + } + + return result; + } + /// 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 e11e09e..33f5817 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" { @@ -2495,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 lua.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(); @@ -2528,6 +2551,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" { @@ -2546,6 +2575,16 @@ test "pushAny struct" { try testing.expect(value.bar == (MyType{}).bar); } +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 { return a + b; } @@ -2566,14 +2605,25 @@ test "autoPushFunction" { lua.autoPushFunction(bar); lua.setGlobal("bar"); - try lua.doString("result = foo(1, 2)"); - - const program = + try lua.doString( + \\result = foo(1, 2) + ); + try lua.doString( \\local status, result = pcall(bar, 1, 2) - ; - lua.doString(program) catch |err| { - std.debug.print("{!}\n\n", .{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" { @@ -2604,3 +2654,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); +}