Skip to content

Instantly share code, notes, and snippets.

@daurnimator
Last active May 9, 2021 05:12
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save daurnimator/60abaa45467d07dc4d2672541e04cb17 to your computer and use it in GitHub Desktop.
Save daurnimator/60abaa45467d07dc4d2672541e04cb17 to your computer and use it in GitHub Desktop.
Lua binding creator in zig
const std = @import("std");
const assert = std.debug.assert;
const lua_ABI = @cImport({
@cInclude("lua.h");
@cInclude("lauxlib.h");
@cInclude("lualib.h");
});
pub const lua = struct {
pub usingnamespace lua_ABI;
// implement macros
pub fn lua_pop(L: ?*lua.lua_State, n: c_int) void {
lua_settop(L, -(n) - 1);
}
pub fn lua_replace(L: ?*lua.lua_State, idx: c_int) void {
lua_copy(L, -1, idx);
lua_pop(L, 1);
}
pub fn lua_call(L: ?*lua.lua_State, n: c_int, r: c_int) void {
lua_callk(L, n, r, 0, null);
}
};
const lua_int_type = @typeInfo(lua.lua_Integer).Int;
const lua_float_type = @typeInfo(lua.lua_Number).Float;
pub fn push(L: ?*lua.lua_State, value: var) void {
switch (@typeId(@typeOf(value))) {
.Void => lua.lua_pushnil(L),
.Bool => lua.lua_pushboolean(L, if (value) u1(1) else u1(0)),
.Int => {
const int_type = @typeInfo(@typeOf(value)).Int;
assert(lua_int_type.is_signed);
if (int_type.bits > lua_int_type.bits or (!int_type.is_signed and int_type.bits >= lua_int_type.bits)) {
@compileError("unable to coerce from type: " ++ @typeName(@typeOf(value)));
}
lua.lua_pushinteger(L, value);
},
.Float => {
const float_type = @typeInfo(@typeOf(value)).Float;
if (float_type.bits > lua_float_type.bits) {
@compileError("unable to coerce from type: " ++ @typeName(@typeOf(value)));
}
lua.lua_pushnumber(L, value);
},
.Fn => {
// TODO: check if already the correct signature?
lua.lua_pushcclosure(L, wrap(value), 0);
},
.Pointer => |PT| {
if (PT.size == .Slice and PT.child == u8) {
_ = lua.lua_pushlstring(L, value.ptr, value.len);
} else {
@compileError("unable to coerce from type: " ++ @typeName(@typeOf(value)));
}
},
else => @compileError("unable to coerce from type: " ++ @typeName(@typeOf(value))),
}
}
pub fn pushlib(L: ?*lua.lua_State, comptime value: type) void {
const Composite = switch(@typeInfo(value)) {
.Struct => |t| t,
.Union => |t| t,
.Enum => |t| t,
else => @compileError("unable to push type " ++ @typeName(value)),
};
{
comptime var nrec = 0;
inline for (Composite.decls) |d| {
if (d.is_pub) {
nrec += 1;
}
}
lua.lua_createtable(L, 0, nrec);
}
inline for (Composite.decls) |d| {
if (d.is_pub) {
switch (d.data) {
.Var, .Fn => {
_ = lua.lua_pushlstring(L, d.name.ptr, d.name.len);
lua.lua_pushcclosure(L, wrap(@field(value, d.name)), 0);
lua.lua_rawset(L, -3);
},
else => @compileError("NYI: Type"),
}
}
}
}
pub fn check(L: ?*lua.lua_State, idx: c_int, comptime T: type) T {
switch (@typeInfo(T)) {
.Void => {
lua.luaL_checktype(L, idx, lua.LUA_TNIL);
return void;
},
.Bool => {
lua.luaL_checktype(L, idx, lua.LUA_TBOOLEAN);
return lua.lua_toboolean(L, idx) != 0;
},
.Int => return @intCast(T, lua.luaL_checkinteger(L, idx)),
.Float => return @floatCast(T, lua.luaL_checknumber(L, idx)),
.Array => |AT| {
switch(lua.lua_type(L, idx)) {
lua.LUA_TTABLE => {
var A: T = undefined;
for (A) |*p, i| {
_ = lua.lua_geti(L, idx, @intCast(lua.lua_Integer, i + 1));
p.* = check(L, -1, AT.child);
lua.lua_pop(L, 1);
}
return A;
},
// TODO: lua.LUA_TUSERDATA
else => {
_ = lua.luaL_argerror(L, idx, c"expected table");
unreachable;
},
}
},
.Pointer => |PT| {
if (T == *c_void) {
return lua.lua_topointer(L, idx);
}
const t = lua.lua_type(L, idx);
if (T == []const u8) {
if (t == lua.LUA_TSTRING) {
var len: usize = undefined;
const ptr = lua.lua_tolstring(L, idx, &len);
return ptr[0..len];
} else if (t != lua.LUA_TUSERDATA) {
_ = lua.luaL_argerror(L, idx, c"expected string or userdata");
unreachable;
}
} else {
if (t != lua.LUA_TUSERDATA) {
_ = lua.luaL_argerror(L, idx, c"expected userdata");
unreachable;
}
}
if (lua.lua_getmetatable(L, idx) == 0) {
_ = lua.luaL_argerror(L, idx, c"unexpected userdata metatable");
unreachable;
}
// TODO: check if metatable is valid for Pointer type
@panic("unable to coerce to type: " ++ @typeName(T));
// lua.lua_pop(L, 1);
// const ptr = lua.lua_touserdata(L, idx);
},
else => @compileError("unable to coerce to type: " ++ @typeName(T)),
}
}
/// Wraps an arbitrary function in a Lua C-API using version
pub fn wrap(comptime func: var) lua.lua_CFunction {
const Fn = @typeInfo(@typeOf(func)).Fn;
// See https://github.com/ziglang/zig/issues/229
return struct {
// See https://github.com/ziglang/zig/issues/2930
fn call(L: ?*lua.lua_State) (if (Fn.return_type) |rt| rt else noreturn) {
if (Fn.args.len == 0) return @inlineCall(func);
const a1 = check(L, 1, Fn.args[0].arg_type.?);
if (Fn.args.len == 1) return @inlineCall(func, a1);
const a2 = check(L, 2, Fn.args[1].arg_type.?);
if (Fn.args.len == 2) return @inlineCall(func, a1, a2);
const a3 = check(L, 3, Fn.args[2].arg_type.?);
if (Fn.args.len == 3) return @inlineCall(func, a1, a2, a3);
const a4 = check(L, 4, Fn.args[3].arg_type.?);
if (Fn.args.len == 4) return @inlineCall(func, a1, a2, a3, a4);
const a5 = check(L, 5, Fn.args[4].arg_type.?);
if (Fn.args.len == 5) return @inlineCall(func, a1, a2, a3, a4, a5);
const a6 = check(L, 6, Fn.args[5].arg_type.?);
if (Fn.args.len == 6) return @inlineCall(func, a1, a2, a3, a4, a5, a6);
const a7 = check(L, 7, Fn.args[6].arg_type.?);
if (Fn.args.len == 7) return @inlineCall(func, a1, a2, a3, a4, a5, a6, a7);
const a8 = check(L, 8, Fn.args[7].arg_type.?);
if (Fn.args.len == 8) return @inlineCall(func, a1, a2, a3, a4, a5, a6, a7, a8);
const a9 = check(L, 9, Fn.args[8].arg_type.?);
if (Fn.args.len == 9) return @inlineCall(func, a1, a2, a3, a4, a5, a6, a7, a8, a9);
@compileError("NYI: >9 argument functions");
}
extern fn thunk(L: ?*lua.lua_State) c_int {
if (Fn.return_type) |return_type| {
const result: return_type = @inlineCall(call, L);
if (return_type == void) {
return 0;
} else {
push(L, result);
return 1;
}
} else {
// is noreturn
@inlineCall(call, L);
}
}
}.thunk;
}
test "autolua" {
_ = @import("./autolua_test.zig");
}
const std = @import("std");
const assert = std.debug.assert;
const testing = std.testing;
const autolua = @import("./autolua.zig");
const lua = autolua.lua;
test "returning an array" {
const L = lua.luaL_newstate();
lua.luaL_openlibs(L);
testing.expectEqual(c_int(lua.LUA_OK), lua.luaL_loadstring(L,
c\\return {1,2,3}
));
testing.expectEqual(c_int(lua.LUA_OK), lua.lua_pcallk(L, 0, 1, 0, 0, null));
testing.expectEqual([_]u32{ 1, 2, 3 }, autolua.check(L, 1, [3]u32));
}
test "returning a string" {
const L = lua.luaL_newstate();
lua.luaL_openlibs(L);
testing.expectEqual(c_int(lua.LUA_OK), lua.luaL_loadstring(L,
c\\return "hello world"
));
testing.expectEqual(c_int(lua.LUA_OK), lua.lua_pcallk(L, 0, 1, 0, 0, null));
testing.expectEqualSlices(u8, "hello world"[0..], autolua.check(L, 1, []const u8));
}
fn func_void() void {}
fn func_bool() bool {
return false;
}
fn func_i8() i8 {
return 42;
}
fn func_i64() i64 {
return 1 << 62;
}
fn func_u64() u64 {
return 1 << 63;
}
fn func_f16() f16 {
return 1 << 10;
}
fn func_f64() f64 {
return 1 << 64;
}
fn func_f128() f128 {
return 1 << 63;
}
fn func_addu32(x: u32, y: u32) u32 {
return x + y;
}
fn func_error() !void {
return error.SomeError;
}
fn func_unreachable() void {
unreachable;
}
extern fn bar(L: ?*lua.lua_State) c_int {
return 0;
}
const lib = [_]lua.luaL_Reg{
lua.luaL_Reg{ .name = c"func_void", .func = autolua.wrap(func_void) },
lua.luaL_Reg{ .name = c"func_bool", .func = autolua.wrap(func_bool) },
lua.luaL_Reg{ .name = c"func_i8", .func = autolua.wrap(func_i8) },
lua.luaL_Reg{ .name = c"func_i64", .func = autolua.wrap(func_i64) },
// lua.luaL_Reg{ .name = c"func_u64", .func = autolua.wrap(func_u64) },
lua.luaL_Reg{ .name = c"func_f16", .func = autolua.wrap(func_f16) },
lua.luaL_Reg{ .name = c"func_f64", .func = autolua.wrap(func_f64) },
// lua.luaL_Reg{ .name = c"func_f128", .func = autolua.wrap(func_f128) },
lua.luaL_Reg{ .name = c"func_addu32", .func = autolua.wrap(func_addu32) },
// lua.luaL_Reg{ .name = c"func_error", .func = autolua.wrap(func_error) },
lua.luaL_Reg{ .name = c"bar", .func = bar },
lua.luaL_Reg{ .name = 0, .func = null },
};
export fn luaopen_mylib(L: ?*lua.lua_State) c_int {
lua.lua_createtable(L, 0, lib.len - 1);
lua.luaL_setfuncs(L, &lib[0], 0);
return 1;
}
test "wrapping void returning function works" {
const L = lua.luaL_newstate();
lua.lua_pushcclosure(L, autolua.wrap(func_void), 0);
testing.expectEqual(c_int(lua.LUA_OK), lua.lua_pcallk(L, 0, lua.LUA_MULTRET, 0, 0, null));
testing.expectEqual(c_int(0), lua.lua_gettop(L));
}
test "wrapping boolean returning function works" {
const L = lua.luaL_newstate();
lua.lua_pushcclosure(L, autolua.wrap(func_bool), 0);
testing.expectEqual(c_int(lua.LUA_OK), lua.lua_pcallk(L, 0, lua.LUA_MULTRET, 0, 0, null));
testing.expectEqual(false, autolua.check(L, 1, bool));
}
test "wrapping integer returning function works" {
const L = lua.luaL_newstate();
lua.lua_pushcclosure(L, autolua.wrap(func_i8), 0);
testing.expectEqual(c_int(lua.LUA_OK), lua.lua_pcallk(L, 0, lua.LUA_MULTRET, 0, 0, null));
testing.expectEqual(i8(42), autolua.check(L, 1, i8));
}
test "wrapping float returning function works" {
const L = lua.luaL_newstate();
lua.lua_pushcclosure(L, autolua.wrap(func_f16), 0);
testing.expectEqual(c_int(lua.LUA_OK), lua.lua_pcallk(L, 0, lua.LUA_MULTRET, 0, 0, null));
testing.expectEqual(f16(1 << 10), autolua.check(L, 1, f16));
}
test "wrapping function that takes arguments works" {
const L = lua.luaL_newstate();
lua.lua_pushcclosure(L, autolua.wrap(func_addu32), 0);
lua.lua_pushinteger(L, 5);
lua.lua_pushinteger(L, 1000);
testing.expectEqual(c_int(lua.LUA_OK), lua.lua_pcallk(L, 2, lua.LUA_MULTRET, 0, 0, null));
testing.expectEqual(u32(5 + 1000), autolua.check(L, 1, u32));
}
// test "wrapping function that throws error" {
// const L = lua.luaL_newstate();
// lua.lua_pushcclosure(L, autolua.wrap(func_error), 0);
// testing.expectEqual(c_int(lua.LUA_ERRRUN), lua.lua_pcallk(L, 0, lua.LUA_MULTRET, 0, 0, null));
// testing.expect(error.SomeError == autolua.check(L, 1, anyerror));
// }
// test "wrapping unreachable function" {
// const L = lua.luaL_newstate();
// lua.lua_pushcclosure(L, autolua.wrap(func_unreachable), 0);
// testing.expectEqual(c_int(lua.LUA_OK), lua.lua_pcallk(L, 0, lua.LUA_MULTRET, 0, 0, null));
// }
test "library works" {
const L = lua.luaL_newstate();
lua.luaL_openlibs(L);
lua.luaL_requiref(L, c"mylib", luaopen_mylib, 0);
lua.lua_settop(L, 0);
testing.expectEqual(c_int(lua.LUA_OK), lua.luaL_loadstring(L,
c\\local mylib = require "mylib"
c\\assert(mylib.func_void() == nil)
c\\assert(mylib.func_bool() == false)
c\\assert(mylib.func_i8() == 42)
c\\assert(mylib.func_f16() == 1<<10)
c\\assert(mylib.func_addu32(5432, 1234) == 6666)
));
testing.expectEqual(c_int(lua.LUA_OK), lua.lua_pcallk(L, 0, 0, 0, 0, null));
}
test "wrap struct works" {
const L = lua.luaL_newstate();
lua.luaL_openlibs(L);
testing.expectEqual(c_int(lua.LUA_OK), lua.luaL_loadstring(L,
c\\local lib = ...
c\\assert(lib.get_void() == nil)
c\\assert(lib.get_bool() == false)
c\\assert(lib.get_i8() == 42)
c\\assert(lib.get_f16() == 1<<10)
c\\assert(lib.add_u32(5432, 1234) == 6666)
));
autolua.pushlib(L, struct {
pub fn get_void() void {
return func_void();
}
pub fn get_bool() bool {
return func_bool();
}
pub fn get_i8() i8 {
return func_i8();
}
pub fn get_i64() i64 {
return func_i64();
}
pub fn get_f16() f16 {
return func_f16();
}
pub fn get_f64() f64 {
return func_f64();
}
pub fn add_u32(x: u32, y: u32) u32 {
return func_addu32(x, y);
}
});
testing.expectEqual(c_int(lua.LUA_OK), lua.lua_pcallk(L, 1, 0, 0, 0, null));
}
@daurnimator
Copy link
Author

daurnimator commented Jul 22, 2019

Zig issue to track arbitrary function calls: ziglang/zig#2930

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment