mirror of
https://codeberg.org/fairyglade/ly.git
synced 2026-08-09 07:09:13 +02:00
feat(Lua): get lua parsing working
WIP, still has memory leaks and no error handling.
This commit is contained in:
parent
f457b148f9
commit
995650919e
2 changed files with 134 additions and 38 deletions
|
|
@ -9,17 +9,48 @@ pub const UidRange = @import("UidRange.zig");
|
|||
pub const LogFile = @import("LogFile.zig");
|
||||
pub const SharedError = @import("SharedError.zig");
|
||||
|
||||
pub fn Parser(comptime T: type) type {
|
||||
return union(enum) {
|
||||
ini: IniParser(T),
|
||||
lua: LuaParser(T),
|
||||
|
||||
pub fn errors(self: *const @This()) std.ArrayList(Error) {
|
||||
return switch (self.*) {
|
||||
inline else => |p| p.errors,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn maybe_load_error(self: *const @This()) ?anyerror {
|
||||
return switch (self.*) {
|
||||
inline else => |p| p.maybe_load_error,
|
||||
};
|
||||
}
|
||||
pub fn structure(self: *const @This()) T {
|
||||
return switch (self.*) {
|
||||
inline else => |p| p.structure,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *@This()) void {
|
||||
switch (self.*) {
|
||||
inline else => |*p| p.deinit(),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const Error = struct {
|
||||
type_name: []const u8,
|
||||
key: []const u8,
|
||||
value: []const u8,
|
||||
error_name: []const u8,
|
||||
};
|
||||
|
||||
pub fn IniParser(comptime Struct: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
const temporary_allocator = std.heap.page_allocator;
|
||||
|
||||
pub const Error = struct {
|
||||
type_name: []const u8,
|
||||
key: []const u8,
|
||||
value: []const u8,
|
||||
error_name: []const u8,
|
||||
};
|
||||
pub var global_errors: std.ArrayList(Error) = .empty;
|
||||
|
||||
ini_struct: ini.Ini(Struct),
|
||||
|
|
@ -82,26 +113,33 @@ pub fn IniParser(comptime Struct: type) type {
|
|||
pub fn LuaParser(comptime Struct: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
const temporary_allocator = std.heap.page_allocator;
|
||||
|
||||
pub var global_errors: std.ArrayList(Error) = .empty;
|
||||
|
||||
structure: Struct,
|
||||
errors: std.ArrayList(Error),
|
||||
maybe_load_error: ?anyerror,
|
||||
|
||||
pub fn init(
|
||||
allocator: std.mem.Allocator,
|
||||
io: std.Io,
|
||||
// io: std.Io,
|
||||
path: []const u8,
|
||||
) !Self {
|
||||
var lua: Lua = try .init(allocator);
|
||||
var lua: *Lua = try .init(allocator);
|
||||
defer lua.deinit();
|
||||
|
||||
lua.openLibs();
|
||||
|
||||
// convert to sentinel terminated slice
|
||||
const spath: [:0]const u8 = try allocator.dupeSentinel(u8, path, 0);
|
||||
defer allocator.free(spath);
|
||||
lua.doFile(spath) catch {
|
||||
const error_str = lua.toString(-1) catch unreachable;
|
||||
const lua_str = try allocator.dupeSentinel(u8, error_str, 0);
|
||||
_ = lua_str; // TODO add error logging
|
||||
};
|
||||
|
||||
var data: Struct = .{};
|
||||
switch (@typeInfo(Struct)) {
|
||||
.@"struct" => |struc| {
|
||||
|
|
@ -112,26 +150,64 @@ pub fn LuaParser(comptime Struct: type) type {
|
|||
else => @compileError("Expected a struct."),
|
||||
}
|
||||
|
||||
return data;
|
||||
return .{ .structure = data, .errors = .empty, .maybe_load_error = null };
|
||||
}
|
||||
pub fn setField(allocator: std.mem.Allocator, lua: *Lua, comptime field: std.builtin.Type.StructField, data: *Struct) !void {
|
||||
try lua.getGlobal("ly");
|
||||
_ = lua.getGlobal("ly");
|
||||
defer lua.pop(1); // pop ly table
|
||||
switch (@typeInfo(field.type)) {
|
||||
.int => {
|
||||
_ = lua.getField(-1, field.name);
|
||||
const value = try lua.toInteger(-1);
|
||||
lua.pop(1);
|
||||
@field(data, field.name) = @intCast(@as(field.type, @truncate(value)));
|
||||
},
|
||||
.float => {
|
||||
_ = lua.getField(-1, field.name);
|
||||
const value = try lua.toNumber(-1);
|
||||
lua.pop(1);
|
||||
@field(data, field.name) = @floatCast(@as(field.type, value));
|
||||
},
|
||||
else => @compileError("unimplemented!"),
|
||||
const type_info = @typeInfo(field.type);
|
||||
const actual_type, const is_optional = blk: {
|
||||
if (type_info == .optional) {
|
||||
break :blk .{ type_info.optional.child, true };
|
||||
}
|
||||
break :blk .{ field.type, false };
|
||||
};
|
||||
// push value to top of stack
|
||||
_ = lua.getField(-1, field.name);
|
||||
defer lua.pop(1);
|
||||
|
||||
// handle null, i.e. undefined fields
|
||||
if (is_optional and lua.isNil(-1)) {
|
||||
@field(data, field.name) = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// dispatch depending on type
|
||||
if (type_info == .int) {
|
||||
const value = try lua.toInteger(-1);
|
||||
@field(data, field.name) = @intCast(value);
|
||||
} else if (type_info == .float) { // all floats
|
||||
const value = try lua.toNumber(-1);
|
||||
@field(data, field.name) = @floatCast(value);
|
||||
} else if (type_info == .bool) {
|
||||
const value = lua.toBoolean(-1);
|
||||
@field(data, field.name) = value;
|
||||
} else if (field.type == []const u8 or field.type == [:0]const u8) {
|
||||
const value = try lua.toString(-1);
|
||||
if (actual_type == []const u8) {
|
||||
const duped = try allocator.dupe(u8, value);
|
||||
@field(data, field.name) = duped;
|
||||
} else {
|
||||
const duped = try allocator.dupeSentinel(u8, value, 0);
|
||||
@field(data, field.name) = duped;
|
||||
}
|
||||
} else if (type_info == .@"enum") {
|
||||
const value = try lua.toString(-1);
|
||||
const variant = std.meta.stringToEnum(actual_type, value) orelse return error.InvalidVariant;
|
||||
@field(data, field.name) = variant;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
_ = self; // autofix
|
||||
for (0..global_errors.items.len) |i| {
|
||||
const err = global_errors.items[i];
|
||||
temporary_allocator.free(err.type_name);
|
||||
temporary_allocator.free(err.key);
|
||||
temporary_allocator.free(err.value);
|
||||
}
|
||||
|
||||
global_errors.deinit(temporary_allocator);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
48
src/main.zig
48
src/main.zig
|
|
@ -19,7 +19,9 @@ const interop = ly_core.interop;
|
|||
const UidRange = ly_core.UidRange;
|
||||
const LogFile = ly_core.LogFile;
|
||||
const SharedError = ly_core.SharedError;
|
||||
const Parser = ly_core.Parser;
|
||||
const IniParser = ly_core.IniParser;
|
||||
const LuaParser = ly_core.LuaParser;
|
||||
const ini = ly_core.ini;
|
||||
const Ini = ini.Ini;
|
||||
|
||||
|
|
@ -198,22 +200,28 @@ pub fn main(init: std.process.Init) !void {
|
|||
if (res.args.config) |path| config_parent_path = path;
|
||||
if (res.args.@"use-kmscon-vt" != 0) state.use_kmscon_vt = true;
|
||||
if (res.args.@"validate-config") |path| {
|
||||
var parser = try IniParser(Config).init(
|
||||
state.allocator,
|
||||
state.io,
|
||||
path,
|
||||
migrator.configFieldHandler,
|
||||
);
|
||||
var parser: Parser(Config) = blk: {
|
||||
if (std.mem.endsWith(u8, path, ".ini")) {
|
||||
break :blk .{ .ini = try IniParser(Config).init(
|
||||
state.allocator,
|
||||
state.io,
|
||||
path,
|
||||
migrator.configFieldHandler,
|
||||
) };
|
||||
} else {
|
||||
break :blk .{ .lua = try LuaParser(Config).init(state.allocator, path) };
|
||||
}
|
||||
};
|
||||
defer parser.deinit();
|
||||
|
||||
for (parser.errors.items) |err| {
|
||||
for (parser.errors().items) |err| {
|
||||
std.log.err(
|
||||
"failed to convert value '{s}' of option '{s}' to type '{s}': {s}",
|
||||
.{ err.value, err.key, err.type_name, err.error_name },
|
||||
);
|
||||
}
|
||||
|
||||
if (parser.maybe_load_error) |err| {
|
||||
if (parser.maybe_load_error()) |err| {
|
||||
std.log.err("failed to load config file: {s}", .{@errorName(err)});
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
|
@ -229,12 +237,24 @@ pub fn main(init: std.process.Init) !void {
|
|||
state.allocator.free(state.old_save_path);
|
||||
};
|
||||
|
||||
// TODO do not hardcode config file name
|
||||
const config_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "config.ini" });
|
||||
defer state.allocator.free(config_path);
|
||||
|
||||
custom.binds = .empty;
|
||||
custom.labels = .empty;
|
||||
var config_parser = try IniParser(Config).init(state.allocator, state.io, config_path, migrator.configFieldHandler);
|
||||
var config_parser: Parser(Config) = blk: {
|
||||
if (std.mem.endsWith(u8, config_path, ".ini")) {
|
||||
break :blk .{ .ini = try IniParser(Config).init(
|
||||
state.allocator,
|
||||
state.io,
|
||||
config_path,
|
||||
migrator.configFieldHandler,
|
||||
) };
|
||||
} else {
|
||||
break :blk .{ .lua = try LuaParser(Config).init(state.allocator, config_path) };
|
||||
}
|
||||
};
|
||||
defer config_parser.deinit();
|
||||
defer if (!shutdown or !restart) {
|
||||
var iter = custom.binds.iterator();
|
||||
|
|
@ -253,7 +273,7 @@ pub fn main(init: std.process.Init) !void {
|
|||
custom.labels.deinit(temporary_allocator);
|
||||
};
|
||||
|
||||
state.config = config_parser.structure;
|
||||
state.config = config_parser.structure();
|
||||
|
||||
var lang_buffer: [16]u8 = undefined;
|
||||
const lang_file = try std.fmt.bufPrint(&lang_buffer, "{s}.ini", .{state.config.lang});
|
||||
|
|
@ -271,8 +291,8 @@ 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) {
|
||||
migrator.lateConfigFieldHandler(&state.config, state.lang);
|
||||
if (config_parser.maybe_load_error() == null) {
|
||||
migrator.lateConfigFieldHandler(&state.config);
|
||||
}
|
||||
|
||||
var maybe_uid_range_error: ?anyerror = null;
|
||||
|
|
@ -640,7 +660,7 @@ pub fn main(init: std.process.Init) !void {
|
|||
);
|
||||
}
|
||||
|
||||
if (config_parser.maybe_load_error) |load_error| {
|
||||
if (config_parser.maybe_load_error()) |load_error| {
|
||||
// We can't localize this since the config failed to load so we'd fallback to the default language anyway
|
||||
try state.info_line.addMessage(
|
||||
"unable to parse config file",
|
||||
|
|
@ -654,7 +674,7 @@ pub fn main(init: std.process.Init) !void {
|
|||
.{@errorName(load_error)},
|
||||
);
|
||||
|
||||
for (config_parser.errors.items) |err| {
|
||||
for (config_parser.errors().items) |err| {
|
||||
try state.log_file.err(
|
||||
state.io,
|
||||
"conf",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue