From 9783da93d185d23eb910c34869c5c8e560a87f00 Mon Sep 17 00:00:00 2001 From: Titanium Brain Date: Thu, 18 Jun 2026 19:20:52 +0100 Subject: [PATCH] feat(LuaParser): add custom bind and custom label parsing Adds support for custom_commands and custom_labels tables in the lua config file. Fixes memory leak. Fixes wrong use of lateConfigHandler. --- ly-core/src/root.zig | 133 +++++++++++++++++++++++++++++++++++++++++-- src/main.zig | 3 +- 2 files changed, 130 insertions(+), 6 deletions(-) diff --git a/ly-core/src/root.zig b/ly-core/src/root.zig index 644106d..f3523be 100644 --- a/ly-core/src/root.zig +++ b/ly-core/src/root.zig @@ -8,6 +8,7 @@ pub const interop = @import("interop.zig"); pub const UidRange = @import("UidRange.zig"); pub const LogFile = @import("LogFile.zig"); pub const SharedError = @import("SharedError.zig"); +pub const custom = @import("custom.zig"); pub fn Parser(comptime T: type) type { return union(enum) { @@ -120,6 +121,7 @@ pub fn LuaParser(comptime Struct: type) type { structure: Struct, errors: std.ArrayList(Error), maybe_load_error: ?anyerror, + allocator: std.mem.Allocator, pub fn init( allocator: std.mem.Allocator, @@ -142,6 +144,10 @@ pub fn LuaParser(comptime Struct: type) type { var data: Struct = .{}; switch (@typeInfo(Struct)) { .@"struct" => |struc| { + const ly_type = lua.getGlobal("ly"); + defer lua.pop(1); // pop ly table + if (ly_type == .nil) return error.MissingLyTable; + inline for (struc.fields) |field| { setField(allocator, lua, field, &data) catch {}; } @@ -151,11 +157,19 @@ pub fn LuaParser(comptime Struct: type) type { if (global_errors.items.len != 0) { maybe_load_error = error.InvalidConfig; } - return .{ .structure = data, .errors = global_errors, .maybe_load_error = maybe_load_error }; + + // Parse custom binds and labels + try parseCustom(lua); + + return .{ + .structure = data, + .errors = global_errors, + .maybe_load_error = maybe_load_error, + .allocator = allocator, + }; } + pub fn setField(allocator: std.mem.Allocator, lua: *Lua, comptime field: std.builtin.Type.StructField, data: *Struct) !void { - _ = lua.getGlobal("ly"); - defer lua.pop(1); // pop ly table const type_info = @typeInfo(field.type); const actual_type, const is_optional = blk: { if (type_info == .optional) { @@ -240,8 +254,119 @@ pub fn LuaParser(comptime Struct: type) type { } else unreachable; } + pub fn parseCustom(lua: *Lua) !void { + _ = lua.getGlobal("ly"); + defer lua.pop(1); // pop ly table + if (!lua.isTable(-1)) return error.MissingLyTable; + + _ = lua.getField(-1, "custom_commands"); + // custom_commands can be omitted or empty, so we just skip instead of erroring + binds: { + if (lua.isTable(-1)) { + const len = @as(usize, @intCast(lua.lenRaiseErr(-1))); + if (len == 0) break :binds; + + for (1..len + 1) |i| { + // push i-th table to stack + const ith_table_type = lua.getIndex(-1, @intCast(i)); + defer lua.pop(1); // i-th table in custom_commands + if (ith_table_type != .table) continue; + + // skip command if binding isn't set or not a string + const binding_type = lua.getField(-1, "binding"); + if (binding_type != .string) continue; + const binding = lua.toString(-1) catch continue; + const bindingZ = temporary_allocator.dupe(u8, binding) catch ""; + lua.pop(1); // binding value + + if (!custom.binds.contains(bindingZ)) { + custom.binds.put(temporary_allocator, bindingZ, .{}) catch {}; + } + if (custom.binds.getPtr(bindingZ)) |command| { + // binding name + const name_type = lua.getField(-1, "name"); + if (name_type != .string) continue; + const binding_name = lua.toString(-1) catch ""; + command.name = temporary_allocator.dupe(u8, binding_name) catch ""; + lua.pop(1); // name value + + // binding command + const cmd_type = lua.getField(-1, "cmd"); + if (cmd_type != .string) continue; + const binding_cmd = lua.toString(-1) catch ""; + command.cmd = temporary_allocator.dupe(u8, binding_cmd) catch ""; + lua.pop(1); // cmd value + } + } + } + } + lua.pop(1); + + _ = lua.getField(-1, "custom_labels"); + // custom_labels can be omitted, so we just skip instead of erroring + labels: { + if (lua.isTable(-1)) { + const len = @as(usize, @intCast(lua.lenRaiseErr(-1))); + if (len == 0) break :labels; + + for (1..len + 1) |i| { + // push i-th table to stack + const ith_table_type = lua.getIndex(-1, @intCast(i)); + defer lua.pop(1); // i-th table in custom_labels + if (ith_table_type != .table) continue; + + // skip command if binding isn't set or not a string + const label_type = lua.getField(-1, "label"); + if (label_type != .string) continue; + const label = lua.toString(-1) catch continue; + const labelZ = temporary_allocator.dupe(u8, label) catch ""; + lua.pop(1); // label value + + if (!custom.labels.contains(labelZ)) { + custom.labels.put(temporary_allocator, labelZ, .{ .name = labelZ }) catch {}; + } + if (custom.labels.getPtr(labelZ)) |label_ptr| { + // label command + const cmd_type = lua.getField(-1, "cmd"); + if (cmd_type != .string) continue; + const label_cmd = lua.toString(-1) catch ""; + label_ptr.cmd = temporary_allocator.dupe(u8, label_cmd) catch ""; + lua.pop(1); // cmd value + + // label refresh + const name_type = lua.getField(-1, "refresh"); + if (name_type != .number) continue; + const label_refresh: u32 = @intCast(lua.toInteger(-1) catch 0); + label_ptr.refresh = label_refresh; + lua.pop(1); // name value + } + } + } + } + lua.pop(1); + } + pub fn deinit(self: *Self) void { - _ = self; // autofix + inline for (@typeInfo(Struct).@"struct".fields) |field| { + const type_info = @typeInfo(field.type); + const actual_type = blk: { + if (type_info == .optional) { + break :blk type_info.optional.child; + } + break :blk field.type; + }; + + const value = @field(self.structure, field.name); + if (actual_type == []const u8 or actual_type == [:0]const u8) { + if (type_info == .optional) { + if (value) |inner| { + self.allocator.free(inner); + } + } else { + self.allocator.free(value); + } + } + } for (0..global_errors.items.len) |i| { const err = global_errors.items[i]; temporary_allocator.free(err.type_name); diff --git a/src/main.zig b/src/main.zig index b5ddf3e..3f18bbe 100644 --- a/src/main.zig +++ b/src/main.zig @@ -274,7 +274,6 @@ pub fn main(init: std.process.Init) !void { }; state.config = config_parser.structure(); - std.debug.print("=== {}\n", .{state.config.gameoflife_fg}); var lang_buffer: [16]u8 = undefined; const lang_file = try std.fmt.bufPrint(&lang_buffer, "{s}.ini", .{state.config.lang}); @@ -292,7 +291,7 @@ pub fn main(init: std.process.Init) !void { state.old_save_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "save.ini" }); } - if (config_parser.maybe_load_error() == null) { + if (config_parser.maybe_load_error() == null and config_parser == .ini) { migrator.lateConfigFieldHandler(&state.config); }