425 lines
13 KiB
Zig
425 lines
13 KiB
Zig
|
|
//! ----------------------------------------------------
|
||
|
|
//! ----------------------------------------------------
|
||
|
|
|
||
|
|
const std = @import("std");
|
||
|
|
const lua = @import("luajit");
|
||
|
|
const Self = @This();
|
||
|
|
|
||
|
|
//
|
||
|
|
// FIELDS
|
||
|
|
//
|
||
|
|
|
||
|
|
state: *lua.lua_State,
|
||
|
|
err: ?[*:0]const u8 = null,
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn init() !Self {
|
||
|
|
const state = lua.luaL_newstate() orelse return error.LuaStateFailed;
|
||
|
|
lua.luaL_openlibs(state);
|
||
|
|
return .{ .state = state };
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn deinit(self: *Self) void {
|
||
|
|
lua.lua_close(self.state);
|
||
|
|
}
|
||
|
|
|
||
|
|
//
|
||
|
|
// EXECUTION
|
||
|
|
//
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn exec(self: *Self, comptime ReturnType: type, code: []const u8) !ReturnType {
|
||
|
|
if (lua.luaL_loadbuffer(self.state, code.ptr, code.len, "") != 0) {
|
||
|
|
return self.luaError();
|
||
|
|
}
|
||
|
|
return self.pcall(ReturnType, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn execFromFile(self: *Self, comptime ReturnType: type, path: [*:0]const u8) !ReturnType {
|
||
|
|
if (lua.luaL_dofile(self.state, path) != 0) {
|
||
|
|
return self.luaError();
|
||
|
|
}
|
||
|
|
if (ReturnType == void) return {};
|
||
|
|
return self.readReturn(ReturnType);
|
||
|
|
}
|
||
|
|
|
||
|
|
//
|
||
|
|
// GLOBALS
|
||
|
|
//
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn getGlobal(self: *Self, name: [*:0]const u8) void {
|
||
|
|
lua.lua_getglobal(self.state, name);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn setGlobal(self: *Self, name: [*:0]const u8) void {
|
||
|
|
lua.lua_setglobal(self.state, name);
|
||
|
|
}
|
||
|
|
|
||
|
|
//
|
||
|
|
// STACK
|
||
|
|
//
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn getTop(self: *Self) c_int {
|
||
|
|
return lua.lua_gettop(self.state);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn pop(self: *Self, n: c_int) void {
|
||
|
|
lua.lua_settop(self.state, -n - 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn pushNil(self: *Self) void {
|
||
|
|
lua.lua_pushnil(self.state);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn push(self: *Self, value: anytype) void {
|
||
|
|
const T = @TypeOf(value);
|
||
|
|
switch (@typeInfo(T)) {
|
||
|
|
.void, .null => lua.lua_pushnil(self.state),
|
||
|
|
.optional => {
|
||
|
|
if (value) |v| {
|
||
|
|
self.push(v);
|
||
|
|
} else {
|
||
|
|
lua.lua_pushnil(self.state);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
.bool => lua.lua_pushboolean(self.state, @intFromBool(value)),
|
||
|
|
.int => lua.lua_pushinteger(self.state, @intCast(value)),
|
||
|
|
.float => lua.lua_pushnumber(self.state, @floatCast(value)),
|
||
|
|
.pointer => |p| {
|
||
|
|
if (p.child == u8) {
|
||
|
|
if (p.sentinel != null) {
|
||
|
|
lua.lua_pushstring(self.state, @ptrCast(value.ptr));
|
||
|
|
} else {
|
||
|
|
lua.lua_pushlstring(self.state, value.ptr, value.len);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
const child_info = @typeInfo(p.child);
|
||
|
|
if (child_info == .array and child_info.array.child == u8 and child_info.array.sentinel_ptr != null) {
|
||
|
|
lua.lua_pushstring(self.state, @ptrCast(value));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
},
|
||
|
|
else => @compileError("unsupported push type: " ++ @typeName(T)),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn add(self: *Self, values: anytype) void {
|
||
|
|
inline for (values) |v| {
|
||
|
|
self.push(v);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//
|
||
|
|
// READING
|
||
|
|
//
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn to(self: *Self, comptime T: type, idx: c_int) T {
|
||
|
|
return switch (T) {
|
||
|
|
bool => lua.lua_toboolean(self.state, idx) != 0,
|
||
|
|
i8, i16, i32, i64, u8, u16, u32, u64 => @intCast(lua.lua_tointeger(self.state, idx)),
|
||
|
|
f32, f64 => @floatCast(lua.lua_tonumber(self.state, idx)),
|
||
|
|
[:0]const u8 => ptr: {
|
||
|
|
const ptr = lua.lua_tolstring(self.state, idx, null) orelse break :ptr "";
|
||
|
|
break :ptr std.mem.sliceTo(ptr, 0);
|
||
|
|
},
|
||
|
|
[]const u8 => ptr: {
|
||
|
|
var len: usize = 0;
|
||
|
|
const ptr = lua.lua_tolstring(self.state, idx, &len) orelse break :ptr "";
|
||
|
|
break :ptr ptr[0..len];
|
||
|
|
},
|
||
|
|
else => @compileError("unsupported to type: " ++ @typeName(T)),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn toOwned(self: *Self, comptime T: type, alloc: std.mem.Allocator, idx: c_int) !T {
|
||
|
|
return switch (T) {
|
||
|
|
[]const u8 => {
|
||
|
|
var len: usize = 0;
|
||
|
|
const ptr = lua.lua_tolstring(self.state, idx, &len) orelse return error.InvalidValue;
|
||
|
|
return try alloc.dupe(u8, ptr[0..len]);
|
||
|
|
},
|
||
|
|
[:0]const u8 => {
|
||
|
|
const ptr = lua.lua_tolstring(self.state, idx, null) orelse return error.InvalidValue;
|
||
|
|
return try alloc.dupeZ(u8, std.mem.sliceTo(ptr, 0));
|
||
|
|
},
|
||
|
|
else => self.to(T, idx),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
//
|
||
|
|
// CALLING
|
||
|
|
//
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
fn readReturn(self: *Self, comptime ReturnType: type) ReturnType {
|
||
|
|
const info = @typeInfo(ReturnType);
|
||
|
|
if (info == .@"struct" and info.@"struct".is_tuple) {
|
||
|
|
const n: c_int = @intCast(info.@"struct".fields.len);
|
||
|
|
var result: ReturnType = undefined;
|
||
|
|
inline for (info.@"struct".fields, 0..) |field, i| {
|
||
|
|
result[i] = self.to(field.type, -n + @as(c_int, @intCast(i)));
|
||
|
|
}
|
||
|
|
self.pop(n);
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
const result = self.to(ReturnType, -1);
|
||
|
|
self.pop(1);
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn pcall(self: *Self, comptime ReturnType: type, nargs: u32) !ReturnType {
|
||
|
|
const nresults: c_int = if (ReturnType == void) 0 else switch (@typeInfo(ReturnType)) {
|
||
|
|
.@"struct" => |s| if (s.is_tuple) @intCast(s.fields.len) else @as(c_int, 1),
|
||
|
|
else => 1,
|
||
|
|
};
|
||
|
|
if (lua.lua_pcall(self.state, @intCast(nargs), nresults, 0) != 0) {
|
||
|
|
return self.luaError();
|
||
|
|
}
|
||
|
|
if (ReturnType == void) return {};
|
||
|
|
return self.readReturn(ReturnType);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn call(
|
||
|
|
self: *Self,
|
||
|
|
comptime ReturnType: type,
|
||
|
|
name: [*:0]const u8,
|
||
|
|
args: anytype,
|
||
|
|
) !ReturnType {
|
||
|
|
self.getGlobal(name);
|
||
|
|
inline for (args) |arg| {
|
||
|
|
self.push(arg);
|
||
|
|
}
|
||
|
|
return self.pcall(ReturnType, args.len);
|
||
|
|
}
|
||
|
|
|
||
|
|
//
|
||
|
|
// TABLES
|
||
|
|
//
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn tableGet(self: *Self, idx: c_int, key: anytype) void {
|
||
|
|
self.push(key);
|
||
|
|
lua.lua_gettable(self.state, if (idx < 0) idx - 1 else idx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn tableSet(self: *Self, idx: c_int, key: anytype, value: anytype) void {
|
||
|
|
self.push(key);
|
||
|
|
self.push(value);
|
||
|
|
lua.lua_settable(self.state, if (idx < 0) idx - 2 else idx);
|
||
|
|
}
|
||
|
|
|
||
|
|
//
|
||
|
|
// REGISTRATION
|
||
|
|
//
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn register(self: *Self, name: [*:0]const u8, comptime func: anytype) void {
|
||
|
|
const fn_info = @typeInfo(@TypeOf(func)).@"fn";
|
||
|
|
const Return = fn_info.return_type orelse void;
|
||
|
|
|
||
|
|
const Wrapper = struct {
|
||
|
|
fn luaFunc(state: ?*lua.lua_State) callconv(.c) c_int {
|
||
|
|
var zl = Self{ .state = state.? };
|
||
|
|
|
||
|
|
const args: std.meta.ArgsTuple(@TypeOf(func)) = args: {
|
||
|
|
var a: std.meta.ArgsTuple(@TypeOf(func)) = undefined;
|
||
|
|
inline for (fn_info.params, 0..) |param, i| {
|
||
|
|
a[i] = zl.to(param.type.?, @intCast(i + 1));
|
||
|
|
}
|
||
|
|
break :args a;
|
||
|
|
};
|
||
|
|
|
||
|
|
const ret = @call(.auto, func, args);
|
||
|
|
if (Return == void) return 0;
|
||
|
|
zl.push(ret);
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
lua.lua_pushcfunction(self.state, Wrapper.luaFunc);
|
||
|
|
lua.lua_setglobal(self.state, name);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn registerWithCtx(
|
||
|
|
self: *Self,
|
||
|
|
name: [*:0]const u8,
|
||
|
|
comptime func: anytype,
|
||
|
|
ctx: anytype,
|
||
|
|
) void {
|
||
|
|
const CtxType = @TypeOf(ctx);
|
||
|
|
const fn_info = @typeInfo(@TypeOf(func)).@"fn";
|
||
|
|
const Return = fn_info.return_type orelse void;
|
||
|
|
|
||
|
|
const Wrapper = struct {
|
||
|
|
fn luaFunc(state: ?*lua.lua_State) callconv(.c) c_int {
|
||
|
|
var zl = Self{ .state = state.? };
|
||
|
|
|
||
|
|
const ctx_ptr: CtxType = @ptrCast(@alignCast(
|
||
|
|
lua.lua_touserdata(state, lua.lua_upvalueindex(1)),
|
||
|
|
));
|
||
|
|
|
||
|
|
const args: std.meta.ArgsTuple(@TypeOf(func)) = args: {
|
||
|
|
var a: std.meta.ArgsTuple(@TypeOf(func)) = undefined;
|
||
|
|
inline for (fn_info.params, 0..) |param, i| {
|
||
|
|
if (i == 0) {
|
||
|
|
a[i] = ctx_ptr;
|
||
|
|
} else {
|
||
|
|
a[i] = zl.to(param.type.?, @intCast(i));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break :args a;
|
||
|
|
};
|
||
|
|
|
||
|
|
const ret = @call(.auto, func, args);
|
||
|
|
if (Return == void) return 0;
|
||
|
|
zl.push(ret);
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
lua.lua_pushlightuserdata(self.state, @ptrCast(@constCast(ctx)));
|
||
|
|
lua.lua_pushcclosure(self.state, Wrapper.luaFunc, 1);
|
||
|
|
lua.lua_setglobal(self.state, name);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn registerMany(self: *Self, values: anytype) void {
|
||
|
|
inline for (values) |value| {
|
||
|
|
self.register(value.name, value.func);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//
|
||
|
|
// ERRORS
|
||
|
|
//
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
pub fn lastError(self: *Self) ?[:0]const u8 {
|
||
|
|
if (self.err) |p| {
|
||
|
|
return std.mem.sliceTo(p, 0);
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
/// ----------------------------------------------------
|
||
|
|
fn luaError(self: *Self) error{LuaError} {
|
||
|
|
self.err = lua.lua_tolstring(self.state, -1, null);
|
||
|
|
if (self.err) |p| {
|
||
|
|
std.debug.print("lua error: {s}\n", .{p[0..std.mem.len(p)]});
|
||
|
|
}
|
||
|
|
lua.lua_pop(self.state, 1);
|
||
|
|
return error.LuaError;
|
||
|
|
}
|
||
|
|
|
||
|
|
//
|
||
|
|
// TESTS
|
||
|
|
//
|
||
|
|
|
||
|
|
fn spawnEntity(name: []const u8, x: f64, y: f64) void {
|
||
|
|
std.debug.print("spawned '{s}' at ({d:.1}, {d:.1})\n", .{ name, x, y });
|
||
|
|
}
|
||
|
|
|
||
|
|
const Ctx = struct {
|
||
|
|
counter: i64 = 0,
|
||
|
|
};
|
||
|
|
|
||
|
|
fn increment(ctx: *Ctx) i64 {
|
||
|
|
ctx.counter += 1;
|
||
|
|
return ctx.counter;
|
||
|
|
}
|
||
|
|
|
||
|
|
test "exec" {
|
||
|
|
// --- ZLA ---
|
||
|
|
var zl = try Self.init();
|
||
|
|
defer zl.deinit();
|
||
|
|
|
||
|
|
// --- REGISTER FUNCTIONS TO LUA ---
|
||
|
|
zl.register("spawnEntity", spawnEntity);
|
||
|
|
|
||
|
|
// --- EXEC FILE ---
|
||
|
|
try zl.exec(void, @embedFile("scripts/basic.lua"));
|
||
|
|
// try zl.execFromFile(void, "src/scripts/basic.lua");
|
||
|
|
|
||
|
|
// --- CALL ---
|
||
|
|
try zl.call(void, "hello", .{"World"});
|
||
|
|
try zl.call(void, "hello", .{"World"});
|
||
|
|
|
||
|
|
// --- CALL RETURN ---
|
||
|
|
const sum = try zl.call(i64, "add", .{ @as(i64, 3), @as(i64, 4) });
|
||
|
|
std.debug.print("Result: {}\n", .{sum});
|
||
|
|
|
||
|
|
// --- MULTI RETURN ---
|
||
|
|
const coords = try zl.call(struct { f64, f64 }, "getCoords", .{});
|
||
|
|
std.debug.print("coords: ({d:.1}, {d:.1})\n", .{ coords[0], coords[1] });
|
||
|
|
|
||
|
|
// --- EXEC WITH RETURN ---
|
||
|
|
const val = try zl.exec(i64, "return 6 * 7");
|
||
|
|
try std.testing.expectEqual(@as(i64, 42), val);
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO: Nicer tables / generic
|
||
|
|
|
||
|
|
// test "tables" {
|
||
|
|
// // --- ZLA ---
|
||
|
|
// var zl = try Self.init();
|
||
|
|
// defer zl.deinit();
|
||
|
|
//
|
||
|
|
// var ctx = Ctx{};
|
||
|
|
// zl.registerWithCtx("increment", increment, &ctx);
|
||
|
|
//
|
||
|
|
// // --- TABLE ---
|
||
|
|
// zl.getGlobal("config");
|
||
|
|
// zl.tableGet(-1, "width");
|
||
|
|
// const width = zl.to(i64, -1);
|
||
|
|
// std.debug.print("width: {}\n", .{width});
|
||
|
|
// zl.pop(2);
|
||
|
|
//
|
||
|
|
// // --- CTX ---
|
||
|
|
// const n = try zl.call(i64, "increment", .{});
|
||
|
|
// std.debug.print("counter: {}\n", .{n});
|
||
|
|
//
|
||
|
|
// // --- LAST ERROR ---
|
||
|
|
// zl.exec(void, "nonexistent()") catch {};
|
||
|
|
// const err = zl.lastError();
|
||
|
|
// try std.testing.expect(err != null);
|
||
|
|
// std.debug.print("caught: {s}\n", .{err.?});
|
||
|
|
// }
|