feat: add fileIter function

also fixes a missing null check bug
This commit is contained in:
LordMZTE 2024-06-18 15:46:57 +02:00
parent f7abe9ee0a
commit 624485b360
Signed by untrusted user: LordMZTE
GPG key ID: B64802DC33A64FF6
6 changed files with 165 additions and 23 deletions

View file

@ -1,6 +1,6 @@
.{
.name = "confgen",
.version = "0.4.0",
.version = "0.4.1",
.dependencies = .{
.zig_args = .{

View file

@ -46,7 +46,17 @@ pub fn luaCheckString(l: *c.lua_State, idx: c_int) []const u8 {
return c.luaL_checklstring(l, idx, &len)[0..len];
}
pub fn luaToString(l: *c.lua_State, idx: c_int) []const u8 {
pub fn luaToString(l: *c.lua_State, idx: c_int) ?[]const u8 {
var len: usize = 0;
return c.lua_tolstring(l, idx, &len)[0..len];
return (@as(?[*]const u8, c.lua_tolstring(l, idx, &len)) orelse return null)[0..len];
}
pub fn luaConvertString(l: *c.lua_State, idx: c_int) []const u8 {
c.lua_pushvalue(l, idx);
c.lua_getglobal(l, "tostring");
c.lua_insert(l, -2);
c.lua_call(l, 1, 1);
const s = luaToString(l, -1) orelse unreachable;
c.lua_pop(l, 1);
return s;
}

View file

@ -57,7 +57,7 @@ pub fn luaToJSON(l: *c.lua_State, stream: anytype) !void {
// Need to duplicate the key in order to call luaToString.
// Direct call may break lua_next
c.lua_pushvalue(l, -2);
try stream.objectField(ffi.luaToString(l, -1));
try stream.objectField(ffi.luaConvertString(l, -1));
c.lua_pop(l, 1);
}
try luaToJSON(l, stream);

View file

@ -12,7 +12,12 @@ pub const CgState = struct {
rootpath: []const u8,
files: std.StringHashMap(CgFile),
/// Number of currently alive iterators over the files map. This is in place to cause an error
/// when the user attempts concurrent modification.
nfile_iters: usize = 0,
pub fn deinit(self: *CgState) void {
std.debug.assert(self.nfile_iters == 0);
var iter = self.files.iterator();
while (iter.next()) |kv| {
self.files.allocator.free(kv.key_ptr.*);
@ -88,6 +93,9 @@ pub fn initLuaState(cgstate: *CgState) !*c.lua_State {
c.lua_pushcfunction(l, ffi.luaFunc(lToJSON));
c.lua_setfield(l, -2, "toJSON");
c.lua_pushcfunction(l, ffi.luaFunc(lFileIter));
c.lua_setfield(l, -2, "fileIter");
// add cg table to globals
c.lua_setglobal(l, "cg");
@ -100,6 +108,7 @@ pub fn initLuaState(cgstate: *CgState) !*c.lua_State {
c.lua_setfield(l, c.LUA_REGISTRYINDEX, on_done_callbacks_key);
LTemplate.initMetatable(l);
LFileIter.initMetatable(l);
TemplateCode.initMetatable(l);
return l;
@ -107,23 +116,23 @@ pub fn initLuaState(cgstate: *CgState) !*c.lua_State {
pub fn loadCGFile(l: *c.lua_State, cgfile: [*:0]const u8) !void {
if (c.luaL_loadfile(l, cgfile) != 0) {
std.log.err("loading confgen file: {s}", .{ffi.luaToString(l, -1)});
std.log.err("loading confgen file: {?s}", .{ffi.luaToString(l, -1)});
return error.RootfileExec;
}
if (c.lua_pcall(l, 0, 0, 0) != 0) {
std.log.err("running confgen file: {s}", .{ffi.luaToString(l, -1)});
std.log.err("running confgen file: {?s}", .{ffi.luaToString(l, -1)});
return error.RootfileExec;
}
}
pub fn evalUserCode(l: *c.lua_State, code: []const u8) !void {
if (c.luaL_loadbuffer(l, code.ptr, code.len, "<eval>") != 0) {
std.log.err("loading user code: {s}", .{ffi.luaToString(l, -1)});
std.log.err("loading user code: {?s}", .{ffi.luaToString(l, -1)});
return error.Explained;
}
if (c.lua_pcall(l, 0, 0, 0) != 0) {
std.log.err("evaluating user code: {s}", .{ffi.luaToString(l, -1)});
std.log.err("evaluating user code: {?s}", .{ffi.luaToString(l, -1)});
return error.Explained;
}
}
@ -148,7 +157,7 @@ pub fn generate(l: *c.lua_State, code: TemplateCode) !GeneratedFile {
errdefer code.deinit();
if (c.luaL_loadbuffer(l, code.content.ptr, code.content.len, code.name) != 0) {
std.log.err("failed to load template: {s}", .{ffi.luaToString(l, -1)});
std.log.err("failed to load template: {?s}", .{ffi.luaToString(l, -1)});
return error.LoadTemplate;
}
@ -175,7 +184,7 @@ pub fn generate(l: *c.lua_State, code: TemplateCode) !GeneratedFile {
_ = c.lua_setfenv(l, -2);
if (c.lua_pcall(l, 0, 0, 0) != 0) {
std.log.err("failed to run template: {s}", .{ffi.luaToString(l, -1)});
std.log.err("failed to run template: {?s}", .{ffi.luaToString(l, -1)});
return error.RunTemplate;
}
@ -197,7 +206,7 @@ pub fn callOnDoneCallbacks(l: *c.lua_State, errors: bool) void {
c.lua_pushboolean(l, @intFromBool(errors));
if (c.lua_pcall(l, 1, 0, 0) != 0) {
const err_s = ffi.luaToString(l, -1);
std.log.err("running onDone callback: {s}", .{err_s});
std.log.err("running onDone callback: {?s}", .{err_s});
c.lua_pop(l, 1);
}
}
@ -213,7 +222,7 @@ fn lPrint(l: *c.lua_State) !c_int {
try writer.writeAll("\x1b[1;34mL:\x1b[0m ");
for (0..@intCast(nargs)) |i| {
const s = ffi.luaToString(l, @intCast(i + 1));
const s = ffi.luaConvertString(l, @intCast(i + 1));
try writer.writeAll(s);
if (i + 1 != nargs)
try writer.writeByte('\t');
@ -231,6 +240,8 @@ fn lAddString(l: *c.lua_State) !c_int {
const state = getState(l);
if (state.nfile_iters != 0) return error.IteratorsAlive; // TODO: Nicer error
const outpath_d = try state.files.allocator.dupe(u8, outpath);
errdefer state.files.allocator.free(outpath_d);
@ -253,6 +264,8 @@ fn lAddPath(l: *c.lua_State) !c_int {
const state = getState(l);
if (state.nfile_iters != 0) return error.IteratorsAlive; // TODO: Nicer error
var dir = try std.fs.cwd().openDir(path, .{ .iterate = true });
defer dir.close();
@ -289,6 +302,8 @@ fn lAddPath(l: *c.lua_State) !c_int {
fn lAddFile(l: *c.lua_State) !c_int {
const state = getState(l);
if (state.nfile_iters != 0) return error.IteratorsAlive; // TODO: Nicer error
const argc = c.lua_gettop(l);
const inpath = ffi.luaCheckString(l, 1);
@ -346,7 +361,7 @@ fn lDoTemplate(l: *c.lua_State) !c_int {
c.lua_getfield(l, 2, "name");
if (!c.lua_isnil(l, -1)) {
source_name = ffi.luaToString(l, -1);
source_name = ffi.luaToString(l, -1) orelse return error.InvalidArgument;
}
{
@ -363,7 +378,7 @@ fn lDoTemplate(l: *c.lua_State) !c_int {
tmpl_code.name.ptr,
) != 0) {
// TODO: turn this into a lua error
std.log.err("loading template: {s}", .{ffi.luaToString(l, -1)});
std.log.err("loading template: {?s}", .{ffi.luaToString(l, -1)});
return error.LoadTemplate;
}
@ -388,7 +403,7 @@ fn lDoTemplate(l: *c.lua_State) !c_int {
if (c.lua_pcall(l, 0, 0, 0) != 0) {
// TODO: turn this into a lua error
std.log.err("failed to run template: {s}", .{ffi.luaToString(l, -1)});
std.log.err("failed to run template: {?s}", .{ffi.luaToString(l, -1)});
return error.RunTemplate;
}
@ -433,7 +448,7 @@ fn lDoTemplateFile(l: *c.lua_State) !c_int {
tmpl_code.name.ptr,
) != 0) {
// TODO: turn this into a lua error
std.log.err("loading template: {s}", .{ffi.luaToString(l, -1)});
std.log.err("loading template: {?s}", .{ffi.luaToString(l, -1)});
return error.LoadTemplate;
}
@ -457,7 +472,7 @@ fn lDoTemplateFile(l: *c.lua_State) !c_int {
if (c.lua_pcall(l, 0, 0, 0) != 0) {
// TODO: turn this into a lua error
std.log.err("failed to run template: {s}", .{ffi.luaToString(l, -1)});
std.log.err("failed to run template: {?s}", .{ffi.luaToString(l, -1)});
return error.RunTemplate;
}
@ -481,7 +496,7 @@ fn lOnDone(l: *c.lua_State) !c_int {
return 0;
}
pub fn lToJSON(l: *c.lua_State) !c_int {
fn lToJSON(l: *c.lua_State) !c_int {
c.luaL_checkany(l, 1);
const pretty = if (c.lua_gettop(l) >= 2) c.lua_toboolean(l, 2) != 0 else false;
@ -507,6 +522,83 @@ pub fn lToJSON(l: *c.lua_State) !c_int {
return 1;
}
fn lFileIter(l: *c.lua_State) !c_int {
const state = getState(l);
state.nfile_iters += 1;
_ = (LFileIter{ .iter = state.files.iterator() }).push(l);
return 1;
}
pub const LFileIter = struct {
pub const lua_registry_key = "confgen_file_iter";
iter: std.StringHashMap(CgFile).Iterator,
pub fn push(self: LFileIter, l: *c.lua_State) *LFileIter {
const self_ptr = ffi.luaPushUdata(l, LFileIter);
self_ptr.* = self;
return self_ptr;
}
fn lGC(l: *c.lua_State) !c_int {
const state = getState(l);
state.nfile_iters -= 1;
return 0;
}
fn lCall(l: *c.lua_State) !c_int {
const self = ffi.luaGetUdata(LFileIter, l, 1);
if (self.iter.next()) |kv| {
c.lua_createtable(l, 0, 3);
c.lua_pushlstring(l, kv.key_ptr.ptr, kv.key_ptr.len);
c.lua_setfield(l, -2, "path");
c.lua_pushboolean(l, @intFromBool(kv.value_ptr.copy));
c.lua_setfield(l, -2, "copy");
{ // content
c.lua_createtable(l, 0, 1);
switch (kv.value_ptr.content) {
.path => |p| {
c.lua_pushlstring(l, p.ptr, p.len);
c.lua_setfield(l, -2, "path");
},
.string => |s| {
c.lua_pushlstring(l, s.ptr, s.len);
c.lua_setfield(l, -2, "string");
},
}
c.lua_setfield(l, -2, "content");
}
return 1;
} else {
c.lua_pushnil(l);
return 1;
}
}
fn initMetatable(l: *c.lua_State) void {
_ = c.luaL_newmetatable(l, lua_registry_key);
c.lua_pushcfunction(l, ffi.luaFunc(lGC));
c.lua_setfield(l, -2, "__gc");
c.lua_pushcfunction(l, ffi.luaFunc(lCall));
c.lua_setfield(l, -2, "__call");
c.lua_pop(l, 1);
}
};
pub const LTemplate = struct {
pub const lua_registry_key = "confgen_template";
@ -568,11 +660,11 @@ pub const LTemplate = struct {
// call post processor
if (c.lua_pcall(l, 1, 1, 0) != 0) {
// TODO: return this instead of logging
std.log.err("running post processor: {s}", .{ffi.luaToString(l, -1)});
std.log.err("running post processor: {?s}", .{ffi.luaToString(l, -1)});
return error.PostProcessor;
}
const out = ffi.luaToString(l, -1);
const out = ffi.luaConvertString(l, -1);
return try self.output.allocator.dupe(u8, out);
}
@ -604,8 +696,11 @@ pub const LTemplate = struct {
}
fn lPushValue(l: *c.lua_State) !c_int {
c.luaL_checkany(l, 2);
if (c.lua_isnil(l, 2)) return 0; // do nothing if passed nil
const self = ffi.luaGetUdata(LTemplate, l, 1);
try self.output.appendSlice(ffi.luaCheckString(l, 2));
try self.output.appendSlice(ffi.luaConvertString(l, 2));
return 0;
}
@ -632,7 +727,7 @@ pub const LTemplate = struct {
const mode = mode: {
if (c.lua_isstring(l, 2) != 0) {
const s = ffi.luaToString(l, 2);
const s = ffi.luaToString(l, 2) orelse unreachable;
if (s.len != 3) break :mode null;
break :mode std.fmt.parseInt(u24, s, 8) catch null;
} else if (c.lua_isnumber(l, 2) != 0) {
@ -684,5 +779,7 @@ pub const LTemplate = struct {
c.lua_pushvalue(l, -1);
c.lua_setfield(l, -2, "__index");
c.lua_pop(l, 1);
}
};

View file

@ -40,6 +40,8 @@ pub const TemplateCode = struct {
c.lua_pushcfunction(l, ffi.luaFunc(lGC));
c.lua_setfield(l, -2, "__gc");
c.lua_pop(l, 1);
}
};

View file

@ -146,6 +146,37 @@ will be serialized as
Example:
.B local json = cg.toJSON({ x = \(dqy\(dq }) -- '{\(dqx\(dq: \(dqy\(dq}'
.TP
.B cg.fileIter()
Obtain an
.I iterator function
over the files that have been added to confgen. Upon each invocation, it will return a table with
the following fields:
.IP \(bu
The
.I path
field will contain the path of the output file relative to the
.IR destination\ directory .
.IP \(bu
The
.I copy
field will be set to
.I true
if the file is not a template and will be simply copied to the destination and
.I false
if the file is a template and will have generation done first.
.IP \(bu
The
.I content
field will be a table, either containing the file's source code in its
.I string
field, or the path to the source file in its
.I path
field.
.TP
.B tmpl
The global value
@ -170,7 +201,9 @@ Example:
.B tmpl:pushValue(value)
Push a Lua value to the output buffer. The values will be converted to strings the same as Lua's
.I tostring
would. This function is rarely called manually and is instead what
would, except that
.I nil
is ignored and nothing is appended. This function is rarely called manually and is instead what
.I Confgen
generates for
.IR Lua\ expression\ blocks .