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

Enums and slices support #60

Merged
merged 10 commits into from
Feb 21, 2024
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);
}
Loading