diff --git a/src/scripts/basic.lua b/src/scripts/basic.lua index 5b2cdbc..cd3b588 100644 --- a/src/scripts/basic.lua +++ b/src/scripts/basic.lua @@ -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") - else - io.stderr:write("Hello nil\n") +-------------------------------------------------------- +-------------------------------------------------------- +function print(...) + local args = { ... } + for i, arg in ipairs(args) do + io.stderr:write("\x1b[32m[" .. i .. "]\x1b[0m " .. tostring(arg) .. "\n") end end -function add(a, b) - return a + b +-------------------------------------------------------- +-------------------------------------------------------- +function addUser(username, password) + app:addUser({ username = username, password = password }) end +-------------------------------------------------------- +-------------------------------------------------------- function getCoords() return 1.5, 2.5 end +-------------------------------------------------------- +-------------------------------------------------------- config = { width = 800, height = 600, diff --git a/src/scripts/types.lua b/src/scripts/types.lua new file mode 100644 index 0000000..e531719 --- /dev/null +++ b/src/scripts/types.lua @@ -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 diff --git a/src/zla.zig b/src/zla.zig index a6c4dc5..153063c 100644 --- a/src/zla.zig +++ b/src/zla.zig @@ -1,6 +1,8 @@ //! ---------------------------------------------------- //! ---------------------------------------------------- +// TODO: Add proper getTable etc + const std = @import("std"); const lua = @import("luajit"); const Self = @This(); @@ -12,6 +14,10 @@ const Self = @This(); state: *lua.lua_State, err: ?[*:0]const u8 = null, +// +// LIFETIME +// + /// ---------------------------------------------------- /// ---------------------------------------------------- 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) { 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 { - if (lua.luaL_dofile(self.state, path)) { +pub fn execFromFile( + self: *Self, + comptime ReturnType: type, + path: []const u8, +) !ReturnType { + if (lua.luaL_dofile(self.state, path.ptr)) { return self.luaError(); } 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 { - lua.lua_getglobal(self.state, name); +pub fn getGlobal( + self: *Self, + name: []const u8, +) void { + lua.lua_getglobal(self.state, name.ptr); } /// ---------------------------------------------------- /// ---------------------------------------------------- -pub fn setGlobal(self: *Self, name: [*:0]const u8) void { - lua.lua_setglobal(self.state, name); +pub fn setGlobal( + 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 { - 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)) { +pub fn push( + self: *Self, + value: anytype, +) void { + switch (@typeInfo(@TypeOf(value))) { .void, .null => lua.lua_pushnil(self.state), .optional => { 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| { 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) { bool => lua.lua_toboolean(self.state, idx) != 0, 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) { []const u8 => { 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); + + // --- # --- if (info == .@"struct" and info.@"struct".is_tuple) { const n: c_int = @intCast(info.@"struct".fields.len); var result: ReturnType = undefined; @@ -187,6 +236,8 @@ fn readReturn(self: *Self, comptime ReturnType: type) ReturnType { self.pop(n); return result; } + + // --- # --- const result = self.to(ReturnType, -1); self.pop(1); 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)) { .@"struct" => |s| if (s.is_tuple) @intCast(s.fields.len) else @as(c_int, 1), else => 1, }; + + // --- # --- if (@as(i32, lua.lua_pcall(self.state, @intCast(nargs), nresults, 0)) != 0) { return self.luaError(); } + + // --- # --- if (ReturnType == void) return {}; 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( self: *Self, comptime ReturnType: type, - name: [*:0]const u8, + name: []const u8, args: anytype, ) !ReturnType { self.getGlobal(name); @@ -221,32 +297,17 @@ pub fn call( 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 { +pub fn register( + self: *Self, + name: []const u8, + comptime func: anytype, +) void { const fn_info = @typeInfo(@TypeOf(func)).@"fn"; 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_setglobal(self.state, name); + lua.lua_setglobal(self.state, name.ptr); } /// ---------------------------------------------------- /// ---------------------------------------------------- pub fn registerWithCtx( self: *Self, - name: [*:0]const u8, + name: []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| { @@ -305,6 +370,7 @@ pub fn registerWithCtx( break :args a; }; + // --- # --- const ret = @call(.auto, func, args); if (Return == void) return 0; zl.push(ret); @@ -312,19 +378,119 @@ pub fn registerWithCtx( } }; + // --- # --- lua.lua_pushlightuserdata(self.state, @ptrCast(@constCast(ctx))); 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| { 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 // @@ -353,72 +519,95 @@ fn luaError(self: *Self) 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, +const User = struct { + username: []const u8, + password: []const u8, }; -fn increment(ctx: *Ctx) i64 { - ctx.counter += 1; - return ctx.counter; -} +const App = struct { + users: std.ArrayList(User), + 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" { - // --- ZLA --- - var zl: Self = try .init(); - defer zl.deinit(); + // --- DEBUG --- + std.debug.print("\x1b[34m<--- EXEC --->\x1b[0m\n", .{}); - // --- REGISTER FUNCTIONS TO LUA --- - zl.register("spawnEntity", spawnEntity); + // --- ZLA --- + var zla: Self = try .init(); + defer zla.deinit(); // --- EXEC FILE --- - // try zl.exec(void, @embedFile("scripts/basic.lua")); - try zl.execFromFile(void, "src/scripts/basic.lua"); + try zla.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); + try zla.call(void, "print", .{ + "Hello World", + "Hello Zig", + }); } -// TODO: Nicer tables / generic +test "userdata" { + // --- DEBUG --- + std.debug.print("\x1b[34m<--- USERDATA --->\x1b[0m\n", .{}); -// 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.?}); -// } + // --- ZLA --- + var zla: Self = try .init(); + defer zla.deinit(); + + // --- APP --- + var app: App = .init(std.testing.allocator); + defer app.deinit(); + + // --- REGISTER METHODS & PUSH USERDATA --- + zla.registerMethods(App, &.{ + .{ .name = "addUser", .func = App.addUser }, + }); + zla.pushUserdata(&app); + zla.setGlobal("app"); + + // --- EXEC FILE --- + try zla.execFromFile( + void, + "src/scripts/basic.lua", + ); + + // --- CALL --- + try zla.call(void, "addUser", .{ "abux", "25569" }); + 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, + }); + } +}