Skip to content

Commit

Permalink
Add support for enums and slices in pushAny and toAny
Browse files Browse the repository at this point in the history
  • Loading branch information
VisenDev authored Feb 21, 2024
1 parent 0f0f489 commit 2ced050
Show file tree
Hide file tree
Showing 2 changed files with 188 additions and 56 deletions.
166 changes: 116 additions & 50 deletions src/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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();
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
},
Expand All @@ -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);
Expand Down
78 changes: 72 additions & 6 deletions src/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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" {
Expand All @@ -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;
}
Expand All @@ -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" {
Expand Down Expand Up @@ -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);
}

0 comments on commit 2ced050

Please sign in to comment.