Skip to content

Commit

Permalink
Add support for tagged unions, vectors and arrays in pushAny and toAny (
Browse files Browse the repository at this point in the history
#66)

* Add support for tagged unions in pushAny and toAny
* Add new test cases for tagged unions
* Add support for void in pushAny and toAny
* New tests
* Make pushAny arrays and slices one-based
* Add support for vectors and arrays in pushAny and toAny
* Replace getIndex with more compatible code in toAny
* Fix tests

---------

Co-authored-by: Robert Burnett <[email protected]>
  • Loading branch information
T1nk3r1 and VisenDev authored Mar 19, 2024
1 parent 67fc4a9 commit df0c1aa
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 11 deletions.
84 changes: 76 additions & 8 deletions src/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3055,7 +3055,7 @@ pub const Lua = struct {

/// Pushes any valid zig value onto the stack,
/// Works with ints, floats, booleans, structs,
/// optionals, and strings
/// tagged unions, optionals, and strings
pub fn pushAny(lua: *Lua, value: anytype) !void {
switch (@typeInfo(@TypeOf(value))) {
.Int, .ComptimeInt => {
Expand All @@ -3079,7 +3079,7 @@ pub const Lua = struct {
.C, .Many, .Slice => {
lua.createTable(0, 0);
for (value, 0..) |index_value, i| {
try lua.pushAny(i);
try lua.pushAny(i + 1);
try lua.pushAny(index_value);
lua.setTable(-3);
}
Expand All @@ -3089,11 +3089,14 @@ pub const Lua = struct {
.Array => {
lua.createTable(0, 0);
for (value, 0..) |index_value, i| {
try lua.pushAny(i);
try lua.pushAny(i + 1);
try lua.pushAny(index_value);
lua.setTable(-3);
}
},
.Vector => |info| {
try lua.pushAny(@as([info.len]info.child, value));
},
.Bool => {
lua.pushBoolean(value);
},
Expand All @@ -3115,10 +3118,25 @@ pub const Lua = struct {
lua.setTable(-3);
}
},
.Union => |info| {
if (info.tag_type == null) @compileError("Parameter type is not a tagged union");
lua.createTable(0, 0);
errdefer lua.pop(1);
try lua.pushAnyString(@tagName(value));

inline for (info.fields) |field| {
if (std.mem.eql(u8, field.name, @tagName(value))) {
try lua.pushAny(@field(value, field.name));
}
}
lua.setTable(-3);
},
.Fn => {
lua.autoPushFunction(value);
},
.Void => {},
.Void => {
lua.createTable(0, 0);
},
else => {
@compileLog(value);
@compileError("Invalid type given");
Expand Down Expand Up @@ -3189,6 +3207,28 @@ pub const Lua = struct {
},
}
},
.Array, .Vector => {
const child = std.meta.Child(T);
const arr_len = switch (@typeInfo(T)) {
inline else => |i| i.len,
};
var result: T = undefined;
lua.pushValue(index);
defer lua.pop(1);

for (0..arr_len) |i| {
if (lua.getMetaField(-1, "__index")) |_| {
lua.pushValue(-2);
lua.pushInteger(@intCast(i + 1));
lua.call(2, 1);
} else |_| {
_ = lua.rawGetIndex(-1, @intCast(i + 1));
}
defer lua.pop(1);
result[i] = try lua.toAny(child, -1);
}
return result;
},
.Pointer => |info| {
if (comptime isTypeString(info)) {
const string: [*:0]const u8 = try lua.toString(index);
Expand Down Expand Up @@ -3226,13 +3266,43 @@ pub const Lua = struct {
.Struct => {
return try lua.toStruct(T, a, allow_alloc, index);
},
.Union => |u| {
if (u.tag_type == null) @compileError("Parameter type is not a tagged union");
if (!lua.isTable(index)) return error.ValueIsNotATable;

lua.pushValue(index);
defer lua.pop(1);
lua.pushNil();
if (lua.next(-2)) {
defer lua.pop(2);
const key = try lua.toAny([]const u8, -2);
inline for (u.fields) |field| {
if (std.mem.eql(u8, key, field.name)) {
return @unionInit(T, field.name, try lua.toAny(field.type, -1));
}
}
return error.InvalidTagName;
}
return error.TableIsEmpty;
},
.Optional => {
if (lua.isNil(index)) {
return null;
} else {
return try lua.toAnyInternal(@typeInfo(T).Optional.child, a, allow_alloc, index);
}
},
.Void => {
if (!lua.isTable(index)) return error.ValueIsNotATable;
lua.pushValue(index);
defer lua.pop(1);
lua.pushNil();
if (lua.next(-2)) {
lua.pop(2);
return error.VoidTableIsNotEmpty;
}
return void{};
},
else => {
@compileError("Invalid parameter type");
},
Expand All @@ -3253,8 +3323,8 @@ pub const Lua = struct {
for (1..size + 1) |i| {
_ = try lua.pushAny(i);
_ = lua.getTable(index);
defer lua.pop(1);
result[i - 1] = try lua.toAnyInternal(ChildType, a, true, -1);
lua.pop(1);
}

return result;
Expand All @@ -3270,7 +3340,6 @@ pub const Lua = struct {
if (!lua.isTable(index)) {
return error.ValueNotATable;
}
std.debug.assert(lua.typeOf(index) == .table);

var result: T = undefined;

Expand All @@ -3279,19 +3348,18 @@ pub const Lua = struct {
_ = lua.pushString(field_name);

const lua_field_type = lua.getTable(index);
defer lua.pop(1);
if (lua_field_type == .nil) {
if (field.default_value) |default_value| {
@field(result, field.name) = @as(*const field.type, @ptrCast(@alignCast(default_value))).*;
} else {
lua.pop(1);
return error.LuaTableMissingValue;
}
} else {
const stack_size_before_call = lua.getTop();
@field(result, field.name) = try lua.toAnyInternal(field.type, a, allow_alloc, -1);
std.debug.assert(stack_size_before_call == lua.getTop());
}
lua.pop(1); //pop the value off the stack
}

return result;
Expand Down
113 changes: 110 additions & 3 deletions src/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2437,6 +2437,13 @@ test "toAny" {
_ = lua.pushString("hello");
const my_enum = try lua.toAny(MyEnumType, -1);
try testing.expect(my_enum == MyEnumType.hello);

//void
try lua.doString("value = {}\nvalue_err = {a = 5}");
_ = try lua.getGlobal("value");
try testing.expectEqual(void{}, try lua.toAny(void, -1));
_ = try lua.getGlobal("value_err");
try testing.expectError(error.VoidTableIsNotEmpty, lua.toAny(void, -1));
}

test "toAny struct" {
Expand Down Expand Up @@ -2472,7 +2479,7 @@ test "toAny struct recursive" {
try lua.doString(
\\value = {
\\ ["foo"] = 10,
\\ ["bar"] = true,
\\ ["bar"] = false,
\\ ["bizz"] = "hi",
\\ ["meep"] = {
\\ ["a"] = nil
Expand All @@ -2482,7 +2489,43 @@ test "toAny struct recursive" {

_ = try lua.getGlobal("value");
const my_struct = try lua.toAny(MyType, -1);
_ = my_struct;
try testing.expectEqualDeep(MyType{}, my_struct);
}

test "toAny tagged union" {
var lua = try Lua.init(&testing.allocator);
defer lua.deinit();

const MyType = union(enum) {
a: i32,
b: bool,
c: []const u8,
d: struct { t0: f64, t1: f64 },
};

try lua.doString(
\\value0 = {
\\ ["c"] = "Hello, world!",
\\}
\\value1 = {
\\ ["d"] = {t0 = 5.0, t1 = -3.0},
\\}
\\value2 = {
\\ ["a"] = 1000,
\\}
);

_ = try lua.getGlobal("value0");
const my_struct0 = try lua.toAny(MyType, -1);
try testing.expectEqualDeep(MyType{ .c = "Hello, world!" }, my_struct0);

_ = try lua.getGlobal("value1");
const my_struct1 = try lua.toAny(MyType, -1);
try testing.expectEqualDeep(MyType{ .d = .{ .t0 = 5.0, .t1 = -3.0 } }, my_struct1);

_ = try lua.getGlobal("value2");
const my_struct2 = try lua.toAny(MyType, -1);
try testing.expectEqualDeep(MyType{ .a = 1000 }, my_struct2);
}

test "toAny slice" {
Expand All @@ -2502,6 +2545,34 @@ test "toAny slice" {
);
}

test "toAny array" {
var lua = try Lua.init(&testing.allocator);
defer lua.deinit();

const arr: [5]?u32 = .{ 1, 2, null, 4, 5 };
const program =
\\array= {1, 2, nil, 4, 5}
;
try lua.doString(program);
_ = try lua.getGlobal("array");
const array = try lua.toAny([5]?u32, -1);
try testing.expectEqual(arr, array);
}

test "toAny vector" {
var lua = try Lua.init(&testing.allocator);
defer lua.deinit();

const vec = @Vector(4, bool){ true, false, false, true };
const program =
\\vector= {true, false, false, true}
;
try lua.doString(program);
_ = try lua.getGlobal("vector");
const vector = try lua.toAny(@Vector(4, bool), -1);
try testing.expectEqual(vec, vector);
}

test "pushAny" {
var lua = try Lua.init(&testing.allocator);
defer lua.deinit();
Expand Down Expand Up @@ -2541,6 +2612,10 @@ test "pushAny" {
try lua.pushAny(MyEnumType.goodbye);
const my_enum = try lua.toAny(MyEnumType, -1);
try testing.expect(my_enum == MyEnumType.goodbye);

//void
try lua.pushAny(void{});
try testing.expectEqual(void{}, try lua.toAny(void, -1));
}

test "pushAny struct" {
Expand All @@ -2559,14 +2634,46 @@ test "pushAny struct" {
try testing.expect(value.bar == (MyType{}).bar);
}

test "pushAny slice/array" {
test "pushAny tagged union" {
var lua = try Lua.init(&testing.allocator);
defer lua.deinit();

const MyType = union(enum) {
a: i32,
b: bool,
c: []const u8,
d: struct { t0: f64, t1: f64 },
};

const t0 = MyType{ .d = .{ .t0 = 5.0, .t1 = -3.0 } };
try lua.pushAny(t0);
const value0 = try lua.toAny(MyType, -1);
try testing.expectEqualDeep(t0, value0);

const t1 = MyType{ .c = "Hello, world!" };
try lua.pushAny(t1);
const value1 = try lua.toAny(MyType, -1);
try testing.expectEqualDeep(t1, value1);
}

test "pushAny toAny slice/array/vector" {
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..];
const my_vector: @Vector(5, u32) = .{ 1, 2, 3, 4, 5 };
try lua.pushAny(my_slice);
try lua.pushAny(my_array);
try lua.pushAny(my_vector);
const vector = try lua.toAny(@TypeOf(my_vector), -1);
const array = try lua.toAny(@TypeOf(my_array), -2);
const slice = try lua.toAnyAlloc(@TypeOf(my_slice), -3);
defer slice.deinit();

try testing.expectEqual(my_array, array);
try testing.expectEqualDeep(my_slice, slice.value);
try testing.expectEqual(my_vector, vector);
}

fn foo(a: i32, b: i32) i32 {
Expand Down

0 comments on commit df0c1aa

Please sign in to comment.