pub const c = @import("c.zig").c; const std = @import("std"); pub const pod = @import("pod.zig"); pub const LuaStateSettings = struct { defaultSetup: bool = true, }; pub const LuaCFunc = *const fn (?*c.lua_State) callconv(.C) i32; pub const LuaZigFunc = *const fn (LuaState) i32; pub const LibSpec = []const c.luaL_Reg; pub const luaL_Reg = c.luaL_Reg; pub fn CWrap(comptime Func: anytype) LuaCFunc { const Wrap = struct { pub fn inner(l: ?*c.lua_State) callconv(.C) i32 { //return @call(.always_inline, Func, .{.{ .l = l }}); return Func(.{ .l = l }); } }; return Wrap.inner; } pub extern fn setupMiniDump(takeFullDump: bool) callconv(.C) void; pub extern fn generateAnException() callconv(.C) void; pub extern fn takeDump() callconv(.C) void; pub fn WrapZigFunc(comptime baseFunc: anytype) LuaCFunc { return CWrap(FuncWrapper(baseFunc).wrapper); } var debugEnabled: bool = false; pub fn debugPrints(enable: bool) void { debugEnabled = enable; } pub fn FuncWrapper(comptime baseFunc: anytype) type { return struct { pub fn wrapper(state: LuaState) i32 { const Args = std.meta.ArgsTuple(@TypeOf(baseFunc)); var args: Args = undefined; inline for (std.meta.fields(Args), 0..) |field, index| { switch (field.type) { f32 => { args[index] = @as(f32, @floatCast(state.toNumber(index + 1))); }, f64 => { args[index] = state.toNumber(index + 1); }, i32 => { args[index] = @intFromFloat(state.toNumber(index + 1)); }, else => { args[index] = state.toUserdata(field.type, index + 1).?.*; }, } } state.pop(@intCast(args.len)); const rv = @call(.always_inline, baseFunc, args); switch (@TypeOf(rv)) { i32, u32, i64, u64 => { state.pushNumber(@floatFromInt(rv)); }, f32, f64 => { state.pushNumber(@floatCast(rv)); }, void => { return 0; }, bool => { state.pushBool(rv); }, []const u8 => { @panic("not implemented"); }, else => { const ud = state.newZigUserdata(@TypeOf(rv)) catch @panic("not implemented"); ud.* = rv; }, } return 1; } }; } var gErrorPrinterContext: ?*anyopaque = null; var errorPrintFunc: ?*const fn (?*anyopaque, []const u8) void = undefined; pub fn registerLuaErrorPrinter(context: ?*anyopaque, func: *const fn (?*anyopaque, []const u8) void) void { errorPrintFunc = func; gErrorPrinterContext = context; } pub const LuaState = struct { l: ?*c.lua_State = null, pub fn init(settings: LuaStateSettings) !@This() { const self: @This() = .{ .l = c.luaL_newstate(), }; if (self.l == null) { return error.OutOfMemory; } if (settings.defaultSetup) { self.stopGC(); self.openLibs(); self.useGenerationalGC(); } else { self.stopGC(); self.openLimited(); self.useGenerationalGC(); } return self; } pub fn deinit(self: @This()) void { c.lua_close(self.l); } pub fn openLimited(self: @This()) void { c.open_limitedio_libs(self.l); } pub fn openLibs(self: @This()) void { c.luaL_openlibs(self.l); } pub fn stopGC(self: @This()) void { _ = c.lua_gc(self.l, c.LUA_GCSTOP); } pub fn useGenerationalGC(self: @This()) void { _ = c.lua_gc(self.l, c.LUA_GCRESTART); _ = c.lua_gc(self.l, c.LUA_GCGEN, @as(u32, @intCast(0)), @as(u32, @intCast(0))); } pub fn loadFile(self: @This(), filename: []const u8) !void { const status = c.luaL_loadfilex(self.l, filename.ptr, null); if (status != c.LUA_OK) { const errorString = c.lua_tolstring(self.l, -1, 0); if (errorPrintFunc) |printFunc| { printFunc(gErrorPrinterContext, std.mem.span(errorString)); } else { std.debug.print("{s}\n", .{errorString}); } return error.LoadFileError; } } pub fn pcallStack(self: @This(), argc: c_int) !void { const status = c.lua_pcallk(self.l, argc, 0, 0, 0, null); if (status != c.LUA_OK) { const errorString = c.lua_tolstring(self.l, -1, 0); if (errorPrintFunc) |printFunc| { printFunc(gErrorPrinterContext, std.mem.span(errorString)); } else { std.debug.print("{s}\n", .{errorString}); } return error.LuaRuntimeError; } } pub fn pcall(self: @This()) !void { try self.pcallStack(0); } pub fn pushFunction(self: @This(), comptime func: LuaZigFunc) !void { const Wrap = struct { pub fn inner(l: ?*c.lua_State) callconv(.C) i32 { return func(.{ .l = l }); } }; _ = c.lua_pushcfunction(self.l, Wrap.inner); } pub fn pushCFunction(self: @This(), comptime func: LuaCFunc) !void { _ = c.lua_pushcfunction(self.l, func); } pub fn setGlobal(self: @This(), value: []const u8) !void { _ = c.lua_setglobal(self.l, value.ptr); } pub fn toNumber(self: @This(), index: i32) f64 { return c.lua_tonumberx(self.l, index, null); } pub fn pop(self: @This(), count: i32) void { c.lua_pop(self.l, count); } pub fn pushBool(self: @This(), value: bool) void { c.lua_pushboolean(self.l, @as(c_int, @intFromBool(value))); } pub fn pushNumber(self: @This(), number: f64) void { c.lua_pushnumber(self.l, number); } pub fn pushZigFunction(self: @This(), func: anytype) !void { try self.pushFunction(FuncWrapper(func).wrapper); } pub fn pushString(self: @This(), str: []const u8) !void { _ = c.lua_pushlstring(self.l, str.ptr, str.len); } pub fn toStringL(self: @This(), index: i32) []const u8 { const cstr = c.luaL_tolstring(self.l, index, null); const len = std.mem.len(cstr); return cstr[0..len]; } pub fn toString(self: @This(), index: i32) []const u8 { const cstr = c.lua_tolstring(self.l, index, null); const len = std.mem.len(cstr); return cstr[0..len]; } pub fn isString(self: @This(), index: i32) bool { return c.lua_isstring(self.l, index) > 0; } pub inline fn getGlobal(self: @This(), definition: []const u8) i32 { return c.lua_getglobal(self.l, definition.ptr); } pub inline fn getTop(self: @This()) i32 { return c.lua_gettop(self.l); } pub fn loadString(self: @This(), string: []const u8) !void { const status = c.luaL_loadstring(self.l, string.ptr); if (status != c.LUA_OK) { return error.LuaRuntimeError; } } pub fn toUserdata(self: @This(), comptime T: type, index: i32) ?*T { if (debugEnabled) { // std.debug.print("toUserdata: {s}", .{@typeName(T)}); } return @ptrCast(@alignCast(c.lua_touserdata(self.l, index))); } pub fn checkString(self: @This(), index: i32) []const u8 { return std.mem.span(c.luaL_checklstring(self.l, index, null)); } pub fn checkInteger(self: @This(), index: i32) i32 { return @intCast(c.luaL_checkinteger(self.l, index)); } pub fn pushInteger(self: @This(), index: i32) void { c.lua_pushinteger(self.l, index); } pub fn argCheck(self: @This(), condition: bool, index: i32, message: []const u8) !void { if (!condition) { _ = c.luaL_argerror(self.l, index, message.ptr); return error.BadArgument; } } pub fn newLib(self: @This(), lib: LibSpec) !void { c.luaL_checkversion(self.l); c.lua_createtable(self.l, 0, @as(c_int, @intCast(lib.len)) - 1); c.luaL_setfuncs(self.l, lib.ptr, 0); } pub fn setFuncs(self: @This(), lib: LibSpec, index: i32) !void { c.luaL_setfuncs(self.l, lib.ptr, index); } pub fn newUserdata(self: @This(), comptime T: type) !*T { return @ptrCast(@alignCast(c.lua_newuserdata(self.l, @as(c_int, @intCast(@sizeOf(T)))))); } pub fn newMetatable(self: @This(), name: [:0]const u8) !void { _ = c.luaL_newmetatable(self.l, name); } pub fn getMetatable(self: @This(), index: i32) !void { if (c.lua_getmetatable(self.l, index) == 0) { return error.BadMetatable; } } pub fn getMetafield(self: @This(), index: i32, name: [:0]const u8) bool { if (c.luaL_getmetafield(self.l, index, name) == c.LUA_TNIL) { return false; } return true; } pub fn getMetatableByName(self: @This(), name: [:0]const u8) !void { if (c.luaL_getmetatable(self.l, name) == 0) { return error.BadMetatable; } } pub fn setMetatable(self: @This(), index: i32) !void { _ = c.lua_setmetatable(self.l, index); } pub fn pushValue(self: @This(), index: i32) !void { _ = c.lua_pushvalue(self.l, index); } pub fn getField(self: @This(), index: i32, name: [:0]const u8) !void { _ = c.lua_getfield(self.l, index, name); if (self.isNil(1)) { return error.FieldNotFound; } } pub fn isNumber(self: @This(), index: i32) bool { const typeIndex = c.lua_type(self.l, index); return (typeIndex == c.LUA_TNUMBER); } pub fn isNil(self: @This(), index: i32) bool { const typeIndex = c.lua_type(self.l, index); return (typeIndex == c.LUA_TNIL); } pub fn isUserdata(self: @This(), index: i32) bool { const typeIndex = c.lua_type(self.l, index); return (typeIndex == c.LUA_TUSERDATA); } pub fn isTable(self: @This(), index: i32) bool { return c.lua_type(self.l, index) == c.LUA_TTABLE; } pub fn getFieldAsNumber(self: @This(), index: i32, name: [:0]const u8) ?f64 { const t = c.lua_getfield(self.l, index, name); defer self.pop(1); if (t != c.LUA_TNUMBER) { return null; } const n = self.toNumber(self.getTop()); return n; } pub fn newZigUserdata(self: @This(), comptime T: type) !*T { const rv = try self.newUserdata(T); if (@hasDecl(T, "MetatableName")) { try self.getMetatableByName(T.MetatableName); } else if (@hasDecl(T, "PodDataTable")) { try self.getMetatableByName(@ptrCast(T.PodDataTable.name)); } else { @panic("todo"); } try self.setMetatable(-2); return rv; } pub fn emitError(self: @This(), errorMessage: [:0]const u8) void { _ = c.luaL_error(self.l, errorMessage); } pub fn createLibrary(self: @This(), libName: []const u8, spec: LibSpec) !void { try self.newLib(spec); try self.setGlobal(libName); } };