const std = @import("std"); const Io = std.Io; const fern = @import("fern"); const options = @import("options"); const zlua = @import("zlua"); pub const Lua = zlua.Lua; const maivi = @import("../root.zig"); const helpers = maivi.helpers; pub const read_data = @import("./data.zig").read; const embed = @import("embed.zig"); const lua_display = @import("lua_display.zig"); const lua_md4c = if (options.md4c) @import("lua_md4c.zig"); const lua_print = @import("lua_print.zig").lua_print; const lua_read = @import("lua_read.zig").lua_read; const lua_str = @import("lua_str.zig"); pub const pcall = @import("pcall.zig").pcall; pub var lua: *Lua = undefined; pub fn init() !void { fern.trace().ctx("lua.init").log(); lua = try lua_init(); } pub fn thread() *Lua { return lua.newThread(); } pub fn lua_init() !*Lua { const l = try Lua.init(maivi.allocator); errdefer l.deinit(); l.openBase(); l.openOS(); l.openDebug(); l.openString(); l.openTable(); l.openMath(); l.openBit32(); // package ================================================================ l.openPackage(); _ = l.getGlobal("package"); { const path = try std.fmt.allocPrint(maivi.allocator, "{s}/?.lua;{s}/?/init.lua", .{ maivi.config.luaroot, maivi.config.luaroot }); defer maivi.allocator.free(path); _ = l.pushString(path); l.setField(-2, "path"); } if (options.runtime) { const path = try std.fmt.allocPrint(maivi.allocator, "{s}/?.lua;{s}/?/init.lua", .{ maivi.config.runtime, maivi.config.runtime }); defer maivi.allocator.free(path); _ = l.getField(-1, "path"); _ = l.pushString(";"); _ = l.pushString(path); _ = l.concat(3); l.setField(-2, "path"); } l.pop(1); // pops "package" // global fn ============================================================== l.register("print", zlua.wrap(lua_print)); l.register("read", zlua.wrap(lua_read)); // maivi ====================================================================== if (options.runtime) try do_require(l, "maivi") else try do_buffer(l, embed.maivi_module, "@runtime/maivi.lua"); l.setGlobal("maivi"); _ = l.getGlobal("maivi"); _ = try lua_str.lua_openstr(l); l.setField(-2, "str"); _ = try lua_display.lua_opendisplay(l); l.setField(-2, "display"); try do_buffer(l, embed.inspect_module, "@inspect_lua"); l.setField(-2, "inspect"); _ = try maivi.Router.lua_openrouter(l); l.setField(-2, "router"); l.pop(1); // pops "maivi" // h ========================================================================== if (options.runtime) try do_require(l, "h") else try do_buffer(l, embed.h_module, "@runtime/h.lua"); l.setGlobal("h"); // this ======================================================================= if (options.runtime) try do_require(l, "this") else try do_buffer(l, embed.this_module, "@runtime/this.lua"); _ = l.pushString(maivi.config.base_url); l.setField(-2, "base_url"); _ = l.pushString(maivi.rev orelse "dev"); l.setField(-2, "rev"); _ = l.pushBoolean(maivi.dev); l.setField(-2, "dev"); l.setGlobal("this"); // iter ======================================================================= if (options.runtime) try do_require(l, "iter") else try do_buffer(l, embed.iter_module, "@runtime/iter.lua"); l.setGlobal("Iter"); // preload ================================================================ if (options.md4c) { _ = l.getGlobal("package"); _ = l.getField(-1, "loaded"); defer l.pop(2); // pops loaded, package _ = try lua_md4c.lua_openmd4c(l); l.setField(-2, "md4c"); } if (l.getTop() != 0) return error.UnexpectedStackSize; return l; } pub fn deinit() void { lua.deinit(); } /// * Pops from Stack: `0` /// * Pushes to Stack: `1` fn do_buffer(l: *Lua, buf: [:0]const u8, chunkname: [:0]const u8) !void { try l.loadBuffer(buf, chunkname); try pcall(l, .{ .results = 1 }); } /// * Pops from Stack: `0` /// * Pushes to Stack: `1` fn do_require(l: *Lua, modname: []const u8) !void { _ = l.getGlobal("require"); _ = l.pushString(modname); try pcall(l, .{ .args = 1, .results = 1 }); } pub fn setup(io: Io) !void { fern.info().ctx("lua.setup").msg("loading plugins").log(); // load plugins blk: { const dir = try Io.Dir.openDirAbsolute(io, maivi.config.root, .{}); defer dir.close(io); const plugindir = dir.openDir(io, "plugins", .{ .iterate = true }) catch |err| switch (err) { error.FileNotFound => break :blk, else => return err, }; defer plugindir.close(io); var it = plugindir.iterate(); while (try it.next(io)) |entry| { if (entry.kind != .file) continue; if (!std.mem.endsWith(u8, entry.name, ".lua")) continue; const contents = try helpers.read_file(io, maivi.allocator, plugindir, entry.name); defer maivi.allocator.free(contents); const filename = try maivi.allocator.dupeZ(u8, entry.name); defer maivi.allocator.free(filename); try lua.loadBuffer(contents, filename); try pcall(lua, .{}); } } fern.info().ctx("lua.setup").msg("running lua init module").log(); try do_require(lua, "init"); lua.pop(1); } pub fn toRef(l: *Lua, i: ?i32) i32 { if (i) |idx| l.pushValue(idx); return l.ref(zlua.registry_index); } pub fn setMethodTable(l: *Lua, comptime T: type) !void { const type_info = @typeInfo(T); comptime { if (type_info != .@"struct") @compileError("non-struct type passed as method table"); const fields = type_info.@"struct".fields; if (fields.len > 0) @compileError("method table cannot contain fields"); } l.createTable(0, 1); { const decls = type_info.@"struct".decls; l.createTable(0, decls.len); inline for (decls) |field| { try l.pushAny(field.name); l.pushFunction(zlua.wrap(@field(T, field.name))); l.setTable(-3); } l.setField(-2, "__index"); } l.setMetatable(-2); } pub fn pushCtx(l: *Lua, comptime T: type, ctx: *T) !void { { const stack_size_on_entry = l.getTop(); // ctx const useref = @hasField(T, "ref") and @FieldType(T, "ref") == ?i32; if (useref and ctx.ref != null) { fern.debug().msg("reusing ctx ref").str("name", @typeName(T)).log(); _ = l.getIndexRaw(zlua.registry_index, ctx.ref.?); } else try l.pushAny(ctx.*); if (useref and ctx.ref == null) { ctx.ref = maivi.lua.toRef(l, -1); } if (l.getTop() != stack_size_on_entry + 1) return error.UnexpectedStackSize; } l.pushLightUserdata(@ptrCast(ctx)); l.setField(-2, "ptr"); } pub const Event = enum { handle, read_data, pub fn str(self: @This()) []const u8 { return switch (self) { else => @tagName(self), }; } }; pub fn trigger(l: *Lua, comptime T: type, ctx: *T, event: Event, props: anytype) !void { // trigger _ = l.getGlobal("maivi"); defer l.pop(1); _ = l.getField(-1, "trigger"); // -1 => f, -2 => maivi errdefer l.pop(2); try pushCtx(l, T, ctx); // -1 => ctx, -2 => f, -3 => maivi const hook_str = event.str(); _ = l.pushString(hook_str); try l.pushAny(props); try pcall(l, .{ .args = 3 }); }