Backlog/lib/lua/src/lua.zig

379 lines
12 KiB
Zig

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);
}
};