Improved & Cleanedup

This commit is contained in:
abux 2026-07-27 17:41:38 +01:00
parent 961db3295b
commit c082ff719b
3 changed files with 335 additions and 124 deletions

View file

@ -1,21 +1,28 @@
---@diagnostic disable-next-line: lowercase-global ---@diagnostic disable: lowercase-global
function hello(name) --------------------------------------------------------
if name ~= nil then --------------------------------------------------------
io.stderr:write("Hello " .. name .. "\n") function print(...)
else local args = { ... }
io.stderr:write("Hello nil\n") for i, arg in ipairs(args) do
io.stderr:write("\x1b[32m[" .. i .. "]\x1b[0m " .. tostring(arg) .. "\n")
end end
end end
function add(a, b) --------------------------------------------------------
return a + b --------------------------------------------------------
function addUser(username, password)
app:addUser({ username = username, password = password })
end end
--------------------------------------------------------
--------------------------------------------------------
function getCoords() function getCoords()
return 1.5, 2.5 return 1.5, 2.5
end end
--------------------------------------------------------
--------------------------------------------------------
config = { config = {
width = 800, width = 800,
height = 600, height = 600,

15
src/scripts/types.lua Normal file
View file

@ -0,0 +1,15 @@
---@class User
---@field username string
---@field password string
---@class App
local App = {}
---@param user User
function App:addUser(user) end
-- ---@return integer
-- function App:getUserCount() end
---@type App
app = app

View file

@ -1,6 +1,8 @@
//! ---------------------------------------------------- //! ----------------------------------------------------
//! ---------------------------------------------------- //! ----------------------------------------------------
// TODO: Add proper getTable etc
const std = @import("std"); const std = @import("std");
const lua = @import("luajit"); const lua = @import("luajit");
const Self = @This(); const Self = @This();
@ -12,6 +14,10 @@ const Self = @This();
state: *lua.lua_State, state: *lua.lua_State,
err: ?[*:0]const u8 = null, err: ?[*:0]const u8 = null,
//
// LIFETIME
//
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn init() !Self { pub fn init() !Self {
@ -32,7 +38,11 @@ pub fn deinit(self: *Self) void {
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn exec(self: *Self, comptime ReturnType: type, code: []const u8) !ReturnType { pub fn exec(
self: *Self,
comptime ReturnType: type,
code: []const u8,
) !ReturnType {
if (lua.luaL_loadbuffer(self.state, code.ptr, code.len, "") != 0) { if (lua.luaL_loadbuffer(self.state, code.ptr, code.len, "") != 0) {
return self.luaError(); return self.luaError();
} }
@ -41,8 +51,12 @@ pub fn exec(self: *Self, comptime ReturnType: type, code: []const u8) !ReturnTyp
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn execFromFile(self: *Self, comptime ReturnType: type, path: [*:0]const u8) !ReturnType { pub fn execFromFile(
if (lua.luaL_dofile(self.state, path)) { self: *Self,
comptime ReturnType: type,
path: []const u8,
) !ReturnType {
if (lua.luaL_dofile(self.state, path.ptr)) {
return self.luaError(); return self.luaError();
} }
if (ReturnType == void) return {}; if (ReturnType == void) return {};
@ -55,14 +69,20 @@ pub fn execFromFile(self: *Self, comptime ReturnType: type, path: [*:0]const u8)
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn getGlobal(self: *Self, name: [*:0]const u8) void { pub fn getGlobal(
lua.lua_getglobal(self.state, name); self: *Self,
name: []const u8,
) void {
lua.lua_getglobal(self.state, name.ptr);
} }
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn setGlobal(self: *Self, name: [*:0]const u8) void { pub fn setGlobal(
lua.lua_setglobal(self.state, name); self: *Self,
name: []const u8,
) void {
lua.lua_setglobal(self.state, name.ptr);
} }
// //
@ -71,27 +91,11 @@ pub fn setGlobal(self: *Self, name: [*:0]const u8) void {
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn getTop(self: *Self) c_int { pub fn push(
return lua.lua_gettop(self.state); self: *Self,
} value: anytype,
) void {
/// ---------------------------------------------------- switch (@typeInfo(@TypeOf(value))) {
/// ----------------------------------------------------
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), .void, .null => lua.lua_pushnil(self.state),
.optional => { .optional => {
if (value) |v| { if (value) |v| {
@ -117,13 +121,16 @@ pub fn push(self: *Self, value: anytype) void {
} }
} }
}, },
else => @compileError("unsupported push type: " ++ @typeName(T)), else => @compileError("unsupported push type: " ++ @typeName(@TypeOf(value))),
} }
} }
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn add(self: *Self, values: anytype) void { pub fn pushMany(
self: *Self,
values: anytype,
) void {
inline for (values) |v| { inline for (values) |v| {
self.push(v); self.push(v);
} }
@ -135,7 +142,26 @@ pub fn add(self: *Self, values: anytype) void {
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn to(self: *Self, comptime T: type, idx: c_int) T { pub fn to(
self: *Self,
comptime T: type,
idx: i32,
) T {
// --- INFO ---
const info = @typeInfo(T);
// --- # ---
if (info == .@"struct" and !info.@"struct".is_tuple) {
var result: T = undefined;
inline for (info.@"struct".fields) |field| {
lua.lua_getfield(self.state, idx, field.name);
@field(result, field.name) = self.to(field.type, -1);
lua.lua_pop(self.state, 1);
}
return result;
}
// --- # ---
return switch (T) { return switch (T) {
bool => lua.lua_toboolean(self.state, idx) != 0, bool => lua.lua_toboolean(self.state, idx) != 0,
i8, i16, i32, i64, u8, u16, u32, u64 => @intCast(lua.lua_tointeger(self.state, idx)), i8, i16, i32, i64, u8, u16, u32, u64 => @intCast(lua.lua_tointeger(self.state, idx)),
@ -155,7 +181,24 @@ pub fn to(self: *Self, comptime T: type, idx: c_int) T {
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn toOwned(self: *Self, comptime T: type, alloc: std.mem.Allocator, idx: c_int) !T { pub fn toUserdata(
self: *Self,
comptime T: type,
idx: i32,
) T {
const ud = lua.lua_touserdata(self.state, idx);
const ptr: *T = @ptrCast(@alignCast(ud));
return ptr.*;
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn toOwned(
self: *Self,
comptime T: type,
alloc: std.mem.Allocator,
idx: i32,
) !T {
return switch (T) { return switch (T) {
[]const u8 => { []const u8 => {
var len: usize = 0; var len: usize = 0;
@ -176,8 +219,14 @@ pub fn toOwned(self: *Self, comptime T: type, alloc: std.mem.Allocator, idx: c_i
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
fn readReturn(self: *Self, comptime ReturnType: type) ReturnType { fn readReturn(
self: *Self,
comptime ReturnType: type,
) ReturnType {
// --- # ---
const info = @typeInfo(ReturnType); const info = @typeInfo(ReturnType);
// --- # ---
if (info == .@"struct" and info.@"struct".is_tuple) { if (info == .@"struct" and info.@"struct".is_tuple) {
const n: c_int = @intCast(info.@"struct".fields.len); const n: c_int = @intCast(info.@"struct".fields.len);
var result: ReturnType = undefined; var result: ReturnType = undefined;
@ -187,6 +236,8 @@ fn readReturn(self: *Self, comptime ReturnType: type) ReturnType {
self.pop(n); self.pop(n);
return result; return result;
} }
// --- # ---
const result = self.to(ReturnType, -1); const result = self.to(ReturnType, -1);
self.pop(1); self.pop(1);
return result; return result;
@ -194,24 +245,49 @@ fn readReturn(self: *Self, comptime ReturnType: type) ReturnType {
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn pcall(self: *Self, comptime ReturnType: type, nargs: u32) !ReturnType { pub fn pcall(
self: *Self,
comptime ReturnType: type,
nargs: u32,
) !ReturnType {
// --- # ---
const nresults: c_int = if (ReturnType == void) 0 else switch (@typeInfo(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), .@"struct" => |s| if (s.is_tuple) @intCast(s.fields.len) else @as(c_int, 1),
else => 1, else => 1,
}; };
// --- # ---
if (@as(i32, lua.lua_pcall(self.state, @intCast(nargs), nresults, 0)) != 0) { if (@as(i32, lua.lua_pcall(self.state, @intCast(nargs), nresults, 0)) != 0) {
return self.luaError(); return self.luaError();
} }
// --- # ---
if (ReturnType == void) return {}; if (ReturnType == void) return {};
return self.readReturn(ReturnType); return self.readReturn(ReturnType);
} }
/// ---------------------------------------------------- /// ----------------------------------------------------
/// --- ZIG ---
/// ```zig
/// try zla.call(void, "print", .{
/// "Hello World",
/// "Hello Zig",
/// });
/// ```
/// --- LUA ---
/// ```lua
/// function print(...)
/// local args = { ... }
/// for i, arg in ipairs(args) do
/// io.stderr:write("\x1b[32m[" .. i .. "]\x1b[0m " .. tostring(arg) .. "\n")
/// end
/// end
/// ```
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn call( pub fn call(
self: *Self, self: *Self,
comptime ReturnType: type, comptime ReturnType: type,
name: [*:0]const u8, name: []const u8,
args: anytype, args: anytype,
) !ReturnType { ) !ReturnType {
self.getGlobal(name); self.getGlobal(name);
@ -221,32 +297,17 @@ pub fn call(
return self.pcall(ReturnType, args.len); 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 // REGISTRATION
// //
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn register(self: *Self, name: [*:0]const u8, comptime func: anytype) void { pub fn register(
self: *Self,
name: []const u8,
comptime func: anytype,
) void {
const fn_info = @typeInfo(@TypeOf(func)).@"fn"; const fn_info = @typeInfo(@TypeOf(func)).@"fn";
const Return = fn_info.return_type orelse void; const Return = fn_info.return_type orelse void;
@ -270,29 +331,33 @@ pub fn register(self: *Self, name: [*:0]const u8, comptime func: anytype) void {
}; };
lua.lua_pushcfunction(self.state, Wrapper.luaFunc); lua.lua_pushcfunction(self.state, Wrapper.luaFunc);
lua.lua_setglobal(self.state, name); lua.lua_setglobal(self.state, name.ptr);
} }
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn registerWithCtx( pub fn registerWithCtx(
self: *Self, self: *Self,
name: [*:0]const u8, name: []const u8,
comptime func: anytype, comptime func: anytype,
ctx: anytype, ctx: anytype,
) void { ) void {
// --- # ---
const CtxType = @TypeOf(ctx); const CtxType = @TypeOf(ctx);
const fn_info = @typeInfo(@TypeOf(func)).@"fn"; const fn_info = @typeInfo(@TypeOf(func)).@"fn";
const Return = fn_info.return_type orelse void; const Return = fn_info.return_type orelse void;
// --- # ---
const Wrapper = struct { const Wrapper = struct {
fn luaFunc(state: ?*lua.lua_State) callconv(.c) c_int { fn luaFunc(state: ?*lua.lua_State) callconv(.c) c_int {
var zl = Self{ .state = state.? }; var zl = Self{ .state = state.? };
// --- # ---
const ctx_ptr: CtxType = @ptrCast(@alignCast( const ctx_ptr: CtxType = @ptrCast(@alignCast(
lua.lua_touserdata(state, lua.lua_upvalueindex(1)), lua.lua_touserdata(state, lua.lua_upvalueindex(1)),
)); ));
// --- # ---
const args: std.meta.ArgsTuple(@TypeOf(func)) = args: { const args: std.meta.ArgsTuple(@TypeOf(func)) = args: {
var a: std.meta.ArgsTuple(@TypeOf(func)) = undefined; var a: std.meta.ArgsTuple(@TypeOf(func)) = undefined;
inline for (fn_info.params, 0..) |param, i| { inline for (fn_info.params, 0..) |param, i| {
@ -305,6 +370,7 @@ pub fn registerWithCtx(
break :args a; break :args a;
}; };
// --- # ---
const ret = @call(.auto, func, args); const ret = @call(.auto, func, args);
if (Return == void) return 0; if (Return == void) return 0;
zl.push(ret); zl.push(ret);
@ -312,19 +378,119 @@ pub fn registerWithCtx(
} }
}; };
// --- # ---
lua.lua_pushlightuserdata(self.state, @ptrCast(@constCast(ctx))); lua.lua_pushlightuserdata(self.state, @ptrCast(@constCast(ctx)));
lua.lua_pushcclosure(self.state, Wrapper.luaFunc, 1); lua.lua_pushcclosure(self.state, Wrapper.luaFunc, 1);
lua.lua_setglobal(self.state, name); lua.lua_setglobal(self.state, name.ptr);
} }
/// ---------------------------------------------------- /// ----------------------------------------------------
/// ---------------------------------------------------- /// ----------------------------------------------------
pub fn registerMany(self: *Self, values: anytype) void { pub fn registerMany(
self: *Self,
values: anytype,
) void {
inline for (values) |value| { inline for (values) |value| {
self.register(value.name, value.func); self.register(value.name, value.func);
} }
} }
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn pushUserdata(
self: *Self,
ptr: anytype,
) void {
// --- # ---
const info = @typeInfo(@TypeOf(ptr));
if (info != .pointer) @compileError("pushUserdata requires a pointer");
const T = info.pointer.child;
// --- # ---
const ud = lua.lua_newuserdata(self.state, @sizeOf(@TypeOf(ptr)));
const stored: *@TypeOf(ptr) = @ptrCast(@alignCast(ud));
stored.* = ptr;
// --- # ---
_ = lua.luaL_newmetatable(self.state, @typeName(T));
_ = lua.lua_setmetatable(self.state, -2);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn registerMethods(
self: *Self,
comptime T: type,
methods: anytype,
) void {
// --- # ---
_ = lua.luaL_newmetatable(self.state, @typeName(T));
lua.lua_getfield(self.state, -1, "__index");
if (lua.lua_isnil(self.state, -1)) {
lua.lua_pop(self.state, 1);
lua.lua_newtable(self.state);
lua.lua_pushvalue(self.state, -1);
lua.lua_setfield(self.state, -3, "__index");
}
// --- # ---
inline for (methods) |method| {
// --- # ---
const func = method.func;
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| {
if (i == 0) {
a[i] = zl.toUserdata(param.type.?, 1);
} else {
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_setfield(self.state, -2, method.name);
}
lua.lua_pop(self.state, 2);
}
/// ----------------------------------------------------
/// ----------------------------------------------------
pub fn registerMethod(
self: *Self,
comptime T: type,
name: []const u8,
comptime func: anytype,
) void {
self.registerMethods(T, &.{.{
.name = name,
.func = func,
}});
}
// //
// ERRORS // ERRORS
// //
@ -353,72 +519,95 @@ fn luaError(self: *Self) error{LuaError} {
// TESTS // TESTS
// //
fn spawnEntity(name: []const u8, x: f64, y: f64) void { const User = struct {
std.debug.print("spawned '{s}' at ({d:.1}, {d:.1})\n", .{ name, x, y }); username: []const u8,
} password: []const u8,
const Ctx = struct {
counter: i64 = 0,
}; };
fn increment(ctx: *Ctx) i64 { const App = struct {
ctx.counter += 1; users: std.ArrayList(User),
return ctx.counter; alloc: std.mem.Allocator,
pub fn init(alloc: std.mem.Allocator) App {
return .{
.users = .empty,
.alloc = alloc,
};
} }
pub fn deinit(self: *App) void {
self.users.deinit(self.alloc);
}
pub fn addUser(
self: *App,
user: User,
) void {
self.users.append(
self.alloc,
user,
) catch unreachable;
}
};
test "exec" { test "exec" {
// --- ZLA --- // --- DEBUG ---
var zl: Self = try .init(); std.debug.print("\x1b[34m<--- EXEC --->\x1b[0m\n", .{});
defer zl.deinit();
// --- REGISTER FUNCTIONS TO LUA --- // --- ZLA ---
zl.register("spawnEntity", spawnEntity); var zla: Self = try .init();
defer zla.deinit();
// --- EXEC FILE --- // --- EXEC FILE ---
// try zl.exec(void, @embedFile("scripts/basic.lua")); try zla.execFromFile(
try zl.execFromFile(void, "src/scripts/basic.lua"); void,
"src/scripts/basic.lua",
);
// --- CALL --- // --- CALL ---
try zl.call(void, "hello", .{"World"}); try zla.call(void, "print", .{
try zl.call(void, "hello", .{"World"}); "Hello World",
"Hello Zig",
// --- 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 "userdata" {
// --- DEBUG ---
std.debug.print("\x1b[34m<--- USERDATA --->\x1b[0m\n", .{});
// test "tables" { // --- ZLA ---
// // --- ZLA --- var zla: Self = try .init();
// var zl = try Self.init(); defer zla.deinit();
// defer zl.deinit();
// // --- APP ---
// var ctx = Ctx{}; var app: App = .init(std.testing.allocator);
// zl.registerWithCtx("increment", increment, &ctx); defer app.deinit();
//
// // --- TABLE --- // --- REGISTER METHODS & PUSH USERDATA ---
// zl.getGlobal("config"); zla.registerMethods(App, &.{
// zl.tableGet(-1, "width"); .{ .name = "addUser", .func = App.addUser },
// const width = zl.to(i64, -1); });
// std.debug.print("width: {}\n", .{width}); zla.pushUserdata(&app);
// zl.pop(2); zla.setGlobal("app");
//
// // --- CTX --- // --- EXEC FILE ---
// const n = try zl.call(i64, "increment", .{}); try zla.execFromFile(
// std.debug.print("counter: {}\n", .{n}); void,
// "src/scripts/basic.lua",
// // --- LAST ERROR --- );
// zl.exec(void, "nonexistent()") catch {};
// const err = zl.lastError(); // --- CALL ---
// try std.testing.expect(err != null); try zla.call(void, "addUser", .{ "abux", "25569" });
// std.debug.print("caught: {s}\n", .{err.?}); try zla.call(void, "addUser", .{ "jamal", "_stole123" });
// } try zla.call(void, "addUser", .{ "calico", "qwerty" });
// --- SHOW USERS ---
std.debug.print("\x1b[32m[USERS]\x1b[0m\n", .{});
for (app.users.items, 0..) |user, i| {
std.debug.print("\x1b[32m| [{}]\x1b[0m {s}: {s}\n", .{
i,
user.username,
user.password,
});
}
}