Skip to content

Commit

Permalink
feat: add userdata slice functions
Browse files Browse the repository at this point in the history
The C API for full userdata allows both single and many-item
allocations. This expands the Zig interface to allow for slices of full
userdata to be allocated. Also exposes the name of the userdata
metatables as strings (typename is not flexible enough)
  • Loading branch information
natecraddock committed Apr 4, 2023
1 parent 00abb7d commit 334821b
Show file tree
Hide file tree
Showing 8 changed files with 334 additions and 52 deletions.
44 changes: 39 additions & 5 deletions src/ziglua-5.1/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ pub const Lua = struct {
}

/// This function allocates a new userdata of the given type.
/// Returns a pointer to the Lua-owned data
/// See https://www.lua.org/manual/5.1/manual.html#lua_newuserdata
pub fn newUserdata(lua: *Lua, comptime T: type) *T {
// safe to .? because this function throws a Lua error on out of memory
Expand All @@ -635,6 +636,15 @@ pub const Lua = struct {
return opaqueCast(T, ptr);
}

/// This function creates and pushes a slice of full userdata onto the stack.
/// Returns a slice to the Lua-owned data.
/// See https://www.lua.org/manual/5.1/manual.html#lua_newuserdata
pub fn newUserdataSlice(lua: *Lua, comptime T: type, size: usize) []T {
// safe to .? because this function throws a Lua error on out of memory
const ptr = c.lua_newuserdata(lua.state, @sizeOf(T) * size).?;
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
}

/// Pops a key from the stack, and pushes a key-value pair from the table at the given index.
/// See https://www.lua.org/manual/5.1/manual.html#lua_next
pub fn next(lua: *Lua, index: i32) bool {
Expand Down Expand Up @@ -933,14 +943,27 @@ pub const Lua = struct {
return error.Fail;
}

/// Returns a pointer of the given type to the userdata at the given index.
/// Works for both full and light userdata. Otherwise returns an error.
/// Returns a Lua-owned userdata pointer of the given type at the given index.
/// Works for both light and full userdata.
/// Returns an error if the value is not a userdata.
/// See https://www.lua.org/manual/5.1/manual.html#lua_touserdata
pub fn toUserdata(lua: *Lua, comptime T: type, index: i32) !*T {
if (c.lua_touserdata(lua.state, index)) |ptr| return opaqueCast(T, ptr);
return error.Fail;
}

/// Returns a Lua-owned userdata slice of the given type at the given index.
/// Returns an error if the value is not a userdata.
/// See https://www.lua.org/manual/5.1/manual.html#lua_touserdata
pub fn toUserdataSlice(lua: *Lua, comptime T: type, index: i32) ![]T {
if (c.lua_touserdata(lua.state, index)) |ptr| {
const size = lua.objectLen(index) / @sizeOf(T);
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
}
return error.Fail;
}


/// Returns the `LuaType` of the value at the given index
/// Note that this is equivalent to lua_type but because type is a Zig primitive it is renamed to `typeOf`
/// See https://www.lua.org/manual/5.1/manual.html#lua_type
Expand Down Expand Up @@ -1197,14 +1220,25 @@ pub const Lua = struct {
c.luaL_checktype(lua.state, arg, @enumToInt(t));
}

/// Checks whether the function argument `arg` is a userdata of the type `type_name`
/// Checks whether the function argument `arg` is a userdata of the type `name`
/// Returns the userdata's memory-block address
/// See https://www.lua.org/manual/5.1/manual.html#luaL_checkudata
pub fn checkUserdata(lua: *Lua, comptime T: type, arg: i32) *T {
pub fn checkUserdata(lua: *Lua, comptime T: type, arg: i32, name: [:0]const u8) *T {
// the returned pointer will not be null
return opaqueCast(T, c.luaL_checkudata(lua.state, arg, @typeName(T)).?);
return opaqueCast(T, c.luaL_checkudata(lua.state, arg, name.ptr).?);
}

/// Checks whether the function argument `arg` is a userdata of the type `name`
/// Returns a Lua-owned userdata slice
/// See https://www.lua.org/manual/5.1/manual.html#luaL_checkudata
pub fn checkUserdataSlice(lua: *Lua, comptime T: type, arg: i32, name: [:0]const u8) []T {
// the returned pointer will not be null
const ptr = c.luaL_checkudata(lua.state, arg, name.ptr).?;
const size = lua.objectLen(arg) / @sizeOf(T);
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
}


/// Loads and runs the given file
/// See https://www.lua.org/manual/5.1/manual.html#luaL_dofile
pub fn doFile(lua: *Lua, file_name: [:0]const u8) !void {
Expand Down
39 changes: 36 additions & 3 deletions src/ziglua-5.1/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1159,11 +1159,11 @@ test "userdata" {
defer lua.deinit();

const Type = struct { a: i32, b: f32 };
try lua.newMetatable(@typeName(Type));
try lua.newMetatable("Type");

const checkUdata = ziglua.wrap(struct {
fn inner(l: *Lua) i32 {
const ptr = l.checkUserdata(Type, 1);
const ptr = l.checkUserdata(Type, 1, "Type");
if (ptr.a != 1234) {
l.pushBytes("error!");
l.raiseError();
Expand All @@ -1180,7 +1180,7 @@ test "userdata" {

{
var t = lua.newUserdata(Type);
lua.getField(ziglua.registry_index, @typeName(Type));
lua.getField(ziglua.registry_index, "Type");
lua.setMetatable(-2);
t.a = 1234;
t.b = 3.14;
Expand All @@ -1191,6 +1191,39 @@ test "userdata" {
}
}

test "userdata slices" {
var lua = try Lua.init(testing.allocator);
defer lua.deinit();

try lua.newMetatable("FixedArray");

// create an array of 10
const slice = lua.newUserdataSlice(Integer, 10);
lua.getField(ziglua.registry_index, "FixedArray");
lua.setMetatable(-2);

for (slice, 1..) |*item, index| {
item.* = @intCast(Integer, index);
}

const udataFn = struct {
fn inner(l: *Lua) i32 {
_ = l.checkUserdataSlice(Integer, 1, "FixedArray");
const arr = l.toUserdataSlice(Integer, 1) catch unreachable;
for (arr, 1..) |item, index| {
if (item != index) l.raiseErrorStr("something broke!", .{});
}

return 0;
}
}.inner;

lua.pushFunction(ziglua.wrap(udataFn));
lua.pushValue(2);

try lua.protectedCall(1, 0, 0);
}

test "refs" {
// tests for functions that aren't tested or will not be tested in ziglua
// but ensures that the signatures are at least type checked
Expand Down
55 changes: 48 additions & 7 deletions src/ziglua-5.2/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ pub const Lua = struct {
}

/// This function allocates a new userdata of the given type
/// Returns a pointer to the Lua-owned data
/// See https://www.lua.org/manual/5.2/manual.html#lua_newuserdata
pub fn newUserdata(lua: *Lua, comptime T: type) *T {
// safe to .? because this function throws a Lua error on out of memory
Expand All @@ -719,6 +720,15 @@ pub const Lua = struct {
return opaqueCast(T, ptr);
}

/// This function creates and pushes a slice of full userdata onto the stack.
/// Returns a slice to the Lua-owned data.
/// See https://www.lua.org/manual/5.2/manual.html#lua_newuserdata
pub fn newUserdataSlice(lua: *Lua, comptime T: type, size: usize) []T {
// safe to .? because this function throws a Lua error on out of memory
const ptr = c.lua_newuserdata(lua.state, @sizeOf(T) * size).?;
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
}

/// Pops a key from the stack, and pushes a key-value pair from the table at the given index
/// See https://www.lua.org/manual/5.2/manual.html#lua_next
pub fn next(lua: *Lua, index: i32) bool {
Expand Down Expand Up @@ -1084,14 +1094,26 @@ pub const Lua = struct {
return result;
}

/// Returns a pointer of the given type to the userdata at the given index.
/// Works for both full and light userdata. Otherwise returns an error.
/// Returns a Lua-owned userdata pointer of the given type at the given index.
/// Works for both light and full userdata.
/// Returns an error if the value is not a userdata.
/// See https://www.lua.org/manual/5.2/manual.html#lua_touserdata
pub fn toUserdata(lua: *Lua, comptime T: type, index: i32) !*T {
if (c.lua_touserdata(lua.state, index)) |ptr| return opaqueCast(T, ptr);
return error.Fail;
}

/// Returns a Lua-owned userdata slice of the given type at the given index.
/// Returns an error if the value is not a userdata.
/// See https://www.lua.org/manual/5.2/manual.html#lua_touserdata
pub fn toUserdataSlice(lua: *Lua, comptime T: type, index: i32) ![]T {
if (c.lua_touserdata(lua.state, index)) |ptr| {
const size = lua.rawLen(index) / @sizeOf(T);
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
}
return error.Fail;
}

/// Returns the `LuaType` of the value at the given index
/// Note that this is equivalent to lua_type but because type is a Zig primitive it is renamed to `typeOf`
/// See https://www.lua.org/manual/5.2/manual.html#lua_type
Expand Down Expand Up @@ -1389,12 +1411,22 @@ pub const Lua = struct {
c.luaL_checktype(lua.state, arg, @enumToInt(t));
}

/// Checks whether the function argument `arg` is a userdata of the type `type_name`
/// Checks whether the function argument `arg` is a userdata of the type `name`
/// Returns the userdata's memory-block address
/// See https://www.lua.org/manual/5.2/manual.html#luaL_checkudata
pub fn checkUserdata(lua: *Lua, comptime T: type, arg: i32) *T {
pub fn checkUserdata(lua: *Lua, comptime T: type, arg: i32, name: [:0]const u8) *T {
// the returned pointer will not be null
return opaqueCast(T, c.luaL_checkudata(lua.state, arg, @typeName(T)).?);
return opaqueCast(T, c.luaL_checkudata(lua.state, arg, name.ptr).?);
}

/// Checks whether the function argument `arg` is a userdata of the type `name`
/// Returns a Lua-owned userdata slice
/// See https://www.lua.org/manual/5.2/manual.html#luaL_checkudata
pub fn checkUserdataSlice(lua: *Lua, comptime T: type, arg: i32, name: [:0]const u8) []T {
// the returned pointer will not be null
const ptr = c.luaL_checkudata(lua.state, arg, name.ptr).?;
const size = lua.rawLen(arg) / @sizeOf(T);
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
}

/// Checks whether the function argument arg is a number and returns this number cast to an unsigned
Expand Down Expand Up @@ -1652,12 +1684,21 @@ pub const Lua = struct {

/// This function works like `Lua.checkUserdata()` except it returns a Zig error instead of raising a Lua error on fail
/// See https://www.lua.org/manual/5.2/manual.html#luaL_testudata
pub fn testUserdata(lua: *Lua, comptime T: type, arg: i32) !*T {
if (c.luaL_testudata(lua.state, arg, @typeName(T))) |ptr| {
pub fn testUserdata(lua: *Lua, comptime T: type, arg: i32, name: [:0]const u8) !*T {
if (c.luaL_testudata(lua.state, arg, name.ptr)) |ptr| {
return opaqueCast(T, ptr);
} else return error.Fail;
}

/// This function works like `Lua.checkUserdataSlice()` except it returns a Zig error instead of raising a Lua error on fail
/// See https://www.lua.org/manual/5.2/manual.html#luaL_checkudata
pub fn testUserdataSlice(lua: *Lua, comptime T: type, arg: i32, name: [:0]const u8) ![]T {
if (c.luaL_testudata(lua.state, arg, name.ptr)) |ptr| {
const size = lua.rawLen(arg) / @sizeOf(T);
return @ptrCast([*]T, @alignCast(@alignOf([*]T), ptr))[0..size];
} else return error.Fail;
}

/// Converts any Lua value at the given index into a string in a reasonable format
/// See https://www.lua.org/manual/5.2/manual.html#luaL_tolstring
pub fn toBytesFmt(lua: *Lua, index: i32) [:0]const u8 {
Expand Down
42 changes: 37 additions & 5 deletions src/ziglua-5.2/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1339,11 +1339,11 @@ test "userdata" {
defer lua.deinit();

const Type = struct { a: i32, b: f32 };
try lua.newMetatable(@typeName(Type));
try lua.newMetatable("Type");

const checkUdata = ziglua.wrap(struct {
fn inner(l: *Lua) i32 {
const ptr = l.checkUserdata(Type, 1);
const ptr = l.checkUserdata(Type, 1, "Type");
if (ptr.a != 1234) {
_ = l.pushBytes("error!");
l.raiseError();
Expand All @@ -1360,7 +1360,7 @@ test "userdata" {

{
var t = lua.newUserdata(Type);
lua.setMetatableRegistry(@typeName(Type));
lua.setMetatableRegistry("Type");
t.a = 1234;
t.b = 3.14;

Expand All @@ -1371,7 +1371,7 @@ test "userdata" {

const testUdata = ziglua.wrap(struct {
fn inner(l: *Lua) i32 {
const ptr = l.testUserdata(Type, 1) catch {
const ptr = l.testUserdata(Type, 1, "Type") catch {
_ = l.pushBytes("error!");
l.raiseError();
};
Expand All @@ -1391,7 +1391,7 @@ test "userdata" {

{
var t = lua.newUserdata(Type);
lua.setMetatableRegistry(@typeName(Type));
lua.setMetatableRegistry("Type");
t.a = 1234;
t.b = 3.14;

Expand All @@ -1401,6 +1401,38 @@ test "userdata" {
}
}

test "userdata slices" {
var lua = try Lua.init(testing.allocator);
defer lua.deinit();

try lua.newMetatable("FixedArray");

// create an array of 10
const slice = lua.newUserdataSlice(Integer, 10);
lua.setMetatableRegistry("FixedArray");
for (slice, 1..) |*item, index| {
item.* = @intCast(Integer, index);
}

const udataFn = struct {
fn inner(l: *Lua) i32 {
_ = l.checkUserdataSlice(Integer, 1, "FixedArray");
_ = l.testUserdataSlice(Integer, 1, "FixedArray") catch unreachable;
const arr = l.toUserdataSlice(Integer, 1) catch unreachable;
for (arr, 1..) |item, index| {
if (item != index) l.raiseErrorStr("something broke!", .{});
}

return 0;
}
}.inner;

lua.pushFunction(ziglua.wrap(udataFn));
lua.pushValue(2);

try lua.protectedCall(1, 0, 0);
}

test "refs" {
// tests for functions that aren't tested or will not be tested in ziglua
// but ensures that the signatures are at least type checked
Expand Down
Loading

0 comments on commit 334821b

Please sign in to comment.