From a28b585cd4444f99a1ce39e84bf63921713e0ad2 Mon Sep 17 00:00:00 2001 From: abux Date: Mon, 27 Jul 2026 10:07:04 +0200 Subject: [PATCH] first commit --- .gitignore | 1 + README.md | 4 + build.zig | 34 +++ build.zig.zon | 17 ++ src/root.zig | 424 ++++++++++++++++++++++++++++++++++++ src/scripts/basic.lua | 20 ++ src/vendors/luajit/luajit.h | 4 + 7 files changed, 504 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 src/root.zig create mode 100644 src/scripts/basic.lua create mode 100644 src/vendors/luajit/luajit.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1dfcbd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.zig-cache/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..324b818 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +**Dependancies** +```bash +sudo pacman -Sy lua --needed +``` diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..afc7b40 --- /dev/null +++ b/build.zig @@ -0,0 +1,34 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const mod = b.addModule("zla", .{ + .root_source_file = b.path("src/root.zig"), + + .target = target, + .optimize = optimize, + }); + + const mod_tests = b.addTest(.{ + .root_module = mod, + }); + + const run_mod_tests = b.addRunArtifact(mod_tests); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_mod_tests.step); + + // + // SYSTEM DEPENDENCIES + // + + const luajit = b.addTranslateC(.{ + .target = target, + .optimize = optimize, + .root_source_file = b.path("./src/vendors/luajit/luajit.h"), + }); + mod.addImport("luajit", luajit.createModule()); + mod.linkSystemLibrary("luajit", .{}); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..c036874 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,17 @@ +.{ + .name = .zla, + + .version = "0.0.0", + + .fingerprint = 0x55da41f18abfadcf, + + .minimum_zig_version = "0.16.0", + + .dependencies = .{}, + + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..6e71747 --- /dev/null +++ b/src/root.zig @@ -0,0 +1,424 @@ +//! ---------------------------------------------------- +//! ---------------------------------------------------- + +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.?}); +// } diff --git a/src/scripts/basic.lua b/src/scripts/basic.lua new file mode 100644 index 0000000..5e78d71 --- /dev/null +++ b/src/scripts/basic.lua @@ -0,0 +1,20 @@ +function hello(name) + if name ~= nil then + io.stderr:write("Hello " .. name .. "\n") + else + io.stderr:write("Hello nil\n") + end +end + +function add(a, b) + return a + b +end + +function getCoords() + return 1.5, 2.5 +end + +config = { + width = 800, + height = 600, +} diff --git a/src/vendors/luajit/luajit.h b/src/vendors/luajit/luajit.h new file mode 100644 index 0000000..73995c9 --- /dev/null +++ b/src/vendors/luajit/luajit.h @@ -0,0 +1,4 @@ +#include +#include +#include +#include