Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for tagged unions, vectors and arrays in pushAny and toAny #66

Merged
merged 20 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading