2025-03-06 00:45:36 -07:00
|
|
|
pub fn Parser(comptime spec: type, comptime root: bool) type {
|
2025-02-21 23:15:59 -07:00
|
|
|
return struct {
|
parser: shove an arena allocator in there
Stay a while and listen to my story.
Due to the design of the parser execution flow, the only reasonable way
to avoid leaking memory in the parser is to use an arena allocator
because the parser itself doesn't have direct access to everything it
allocates, and everything it allocates needs to live for the duration
of whatever callbacks are called.
Now, you might say, if the items it allocates are stored for the
lifetime of whatever callbacks, then that means that the items it
allocates stay allocated for effectively the entire life of the
program. In which case there's really not much point in freeing them
at all, as it's just extra work on exit that the OS should normally
clean up. And you'd be right, except for two details: if the user uses
the current GeneralPurposeAllocator, it will complain about leaks when
deinitialized, which simply isn't groovy. The other detail is that
technically the user can run any code they want after the parser
execution finishes, so forcing the user to leak memory by having an
incomplete API is rude.
The other option would be, as before, forcing the user to supply their
own arena allocator if they don't want to leak, but that's kind of a
rude thing to do and goes against the "all allocators look the same"
design of the standard library, which is what makes it so easy to use
and create allocators with advanced functionality. That seems like an
ugly thing to do, so, instead, each parser gets to eat the memory cost
of storing a pointer to its arena allocator (and the heap cost of the
arena allocator itself).
In theory, subcommands could borrow the arena allocator of their parent
command to save a bit of heap space, but that would make a variety of
creation and cleanup-related tasks less isomorphic between the parents
and the subcommands. I like the current design where commands and
subcommands are the same thing, and I'm not in a rush to disturb that.
I don't think the overhead cost of the arena allocator itself, which
can be measured in double digit bytes, is a particularly steep price
to pay.
2023-07-20 23:15:37 -07:00
|
|
|
arena: *std.heap.ArenaAllocator,
|
2025-02-21 23:15:59 -07:00
|
|
|
context: ContextType(spec),
|
|
|
|
globals: GlobalParams,
|
|
|
|
locals: LocalParams,
|
2025-03-06 00:45:36 -07:00
|
|
|
subcommands: Subcommands(spec, root),
|
2025-02-21 23:15:59 -07:00
|
|
|
|
|
|
|
pub fn init(alloc: std.mem.Allocator, context: ContextType(spec)) !Self {
|
|
|
|
const arena = try alloc.create(std.heap.ArenaAllocator);
|
|
|
|
arena.* = std.heap.ArenaAllocator.init(alloc);
|
|
|
|
|
|
|
|
const globals: GlobalParams, const locals: LocalParams = comptime blk: {
|
|
|
|
var params: struct { global: GlobalParams, local: LocalParams } = .{
|
|
|
|
.global = .{ .short = &.{}, .long = &.{} },
|
|
|
|
.local = .{ .short = &.{}, .long = &.{}, .args = &.{} },
|
|
|
|
};
|
|
|
|
|
|
|
|
for (@typeInfo(@TypeOf(spec.parameters)).@"struct".decls) |dinf| {
|
|
|
|
const decl = @field(@TypeOf(spec.parameters), dinf.name);
|
|
|
|
switch (@TypeOf(decl).param_type) {
|
2025-03-06 00:45:36 -07:00
|
|
|
.bool_group => {
|
2025-02-21 23:15:59 -07:00
|
|
|
for (.{ "truthy", "falsy" }, .{ true, false }) |bias, value| {
|
|
|
|
for (.{ "short", "long" }) |style| {
|
|
|
|
if (@field(@field(decl, bias), style)) |unw| {
|
|
|
|
@field(@field(params, @tagName(decl.scope)), style) = @field(@field(params, @tagName(decl.scope)), style) ++ &.{
|
|
|
|
.{
|
|
|
|
.param = unw,
|
2025-03-06 00:45:36 -07:00
|
|
|
.eager = decl.eager,
|
|
|
|
.takes_value = false,
|
2025-02-21 23:15:59 -07:00
|
|
|
.mutator = implicitSetter(spec, dinf.name, value),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2025-03-06 00:45:36 -07:00
|
|
|
.constant => {
|
|
|
|
for (.{ "short", "long" }) |style| {
|
|
|
|
if (@field(decl, style)) |unw| {
|
|
|
|
@field(@field(params, @tagName(decl.scope)), style) = @field(@field(params, @tagName(decl.scope)), style) ++ &.{.{
|
|
|
|
.param = unw,
|
|
|
|
.eager = decl.eager,
|
|
|
|
.takes_value = false,
|
|
|
|
.mutator = implicitSetter(spec, dinf.name, dinf.value),
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
.counter => {},
|
2025-02-21 23:15:59 -07:00
|
|
|
.option => {
|
|
|
|
for (.{ "short", "long" }) |style| {
|
|
|
|
if (@field(decl, style)) |unw| {
|
|
|
|
@field(@field(params, @tagName(decl.scope)), style) = @field(@field(params, @tagName(decl.scope)), style) ++ &.{.{
|
|
|
|
.param = unw,
|
2025-03-06 00:45:36 -07:00
|
|
|
.eager = decl.eager,
|
|
|
|
.takes_value = true,
|
2025-02-21 23:15:59 -07:00
|
|
|
.mutator = defaultMutator(spec, dinf.name),
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2025-03-06 00:45:36 -07:00
|
|
|
.argument => {
|
|
|
|
params.local.args = params.local.args ++ &.{
|
|
|
|
defaultMutator(spec, dinf.name),
|
|
|
|
};
|
|
|
|
},
|
|
|
|
.group => {},
|
2025-02-21 23:15:59 -07:00
|
|
|
}
|
|
|
|
break :blk .{ params.global, params.local };
|
2024-04-06 11:38:55 -07:00
|
|
|
}
|
2025-02-21 23:15:59 -07:00
|
|
|
};
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
return .{
|
|
|
|
.arena = arena,
|
|
|
|
.context = context,
|
|
|
|
.globals = globals,
|
|
|
|
.locals = locals,
|
|
|
|
};
|
2024-04-07 11:23:25 -07:00
|
|
|
}
|
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn deinit(self: Self) void {
|
|
|
|
const pa = self.arena.child_allocator;
|
parser: shove an arena allocator in there
Stay a while and listen to my story.
Due to the design of the parser execution flow, the only reasonable way
to avoid leaking memory in the parser is to use an arena allocator
because the parser itself doesn't have direct access to everything it
allocates, and everything it allocates needs to live for the duration
of whatever callbacks are called.
Now, you might say, if the items it allocates are stored for the
lifetime of whatever callbacks, then that means that the items it
allocates stay allocated for effectively the entire life of the
program. In which case there's really not much point in freeing them
at all, as it's just extra work on exit that the OS should normally
clean up. And you'd be right, except for two details: if the user uses
the current GeneralPurposeAllocator, it will complain about leaks when
deinitialized, which simply isn't groovy. The other detail is that
technically the user can run any code they want after the parser
execution finishes, so forcing the user to leak memory by having an
incomplete API is rude.
The other option would be, as before, forcing the user to supply their
own arena allocator if they don't want to leak, but that's kind of a
rude thing to do and goes against the "all allocators look the same"
design of the standard library, which is what makes it so easy to use
and create allocators with advanced functionality. That seems like an
ugly thing to do, so, instead, each parser gets to eat the memory cost
of storing a pointer to its arena allocator (and the heap cost of the
arena allocator itself).
In theory, subcommands could borrow the arena allocator of their parent
command to save a bit of heap space, but that would make a variety of
creation and cleanup-related tasks less isomorphic between the parents
and the subcommands. I like the current design where commands and
subcommands are the same thing, and I'm not in a rush to disturb that.
I don't think the overhead cost of the arena allocator itself, which
can be measured in double digit bytes, is a particularly steep price
to pay.
2023-07-20 23:15:37 -07:00
|
|
|
self.arena.deinit();
|
2025-02-21 23:15:59 -07:00
|
|
|
pa.destroy(self.arena);
|
parser: shove an arena allocator in there
Stay a while and listen to my story.
Due to the design of the parser execution flow, the only reasonable way
to avoid leaking memory in the parser is to use an arena allocator
because the parser itself doesn't have direct access to everything it
allocates, and everything it allocates needs to live for the duration
of whatever callbacks are called.
Now, you might say, if the items it allocates are stored for the
lifetime of whatever callbacks, then that means that the items it
allocates stay allocated for effectively the entire life of the
program. In which case there's really not much point in freeing them
at all, as it's just extra work on exit that the OS should normally
clean up. And you'd be right, except for two details: if the user uses
the current GeneralPurposeAllocator, it will complain about leaks when
deinitialized, which simply isn't groovy. The other detail is that
technically the user can run any code they want after the parser
execution finishes, so forcing the user to leak memory by having an
incomplete API is rude.
The other option would be, as before, forcing the user to supply their
own arena allocator if they don't want to leak, but that's kind of a
rude thing to do and goes against the "all allocators look the same"
design of the standard library, which is what makes it so easy to use
and create allocators with advanced functionality. That seems like an
ugly thing to do, so, instead, each parser gets to eat the memory cost
of storing a pointer to its arena allocator (and the heap cost of the
arena allocator itself).
In theory, subcommands could borrow the arena allocator of their parent
command to save a bit of heap space, but that would make a variety of
creation and cleanup-related tasks less isomorphic between the parents
and the subcommands. I like the current design where commands and
subcommands are the same thing, and I'm not in a rush to disturb that.
I don't think the overhead cost of the arena allocator itself, which
can be measured in double digit bytes, is a particularly steep price
to pay.
2023-07-20 23:15:37 -07:00
|
|
|
}
|
|
|
|
|
2025-03-06 00:45:36 -07:00
|
|
|
pub fn parse(self: Self, args: []const [:0]const u8, env: std.process.EnvMap) noclip.Status(void) {
|
|
|
|
const alloc = self.arena.allocator();
|
|
|
|
var argt = ArgTraveler.fromSlice(alloc, args) catch return .fail("out of memory");
|
|
|
|
// pre-parse globals. globals can only be named, which simplifies things
|
|
|
|
var result = defaultInit(Result(spec));
|
|
|
|
while (argt.current) |node| : (argt.next()) {
|
|
|
|
const arg = node.data;
|
|
|
|
if (arg.len > 2 and arg[0] == '-' and arg[1] == '-') {
|
|
|
|
if (self.globals.long.get(arg[2..])) |pctx| {
|
|
|
|
argt.drop();
|
|
|
|
const value: [:0]const u8 = if (pctx.takes_value)
|
|
|
|
argt.popNext()
|
|
|
|
else
|
|
|
|
"";
|
|
|
|
switch (pctx.mutator(alloc, self.context, &result, value)) {}
|
|
|
|
}
|
|
|
|
} else if (arg.len > 1 and arg[0] == '-') {
|
|
|
|
const view = std.unicode.Utf8View.init(arg[1..]) catch return .fail("thats not valid utf8");
|
|
|
|
var iter = view.iterator();
|
|
|
|
while (iter.nextCodepointSlice()) |seq| {
|
|
|
|
if (self.globals.short.get(seq)) {
|
|
|
|
// we have to drop this byte sequence within the fused short params. ugly..................... hrngrk
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// var parse_mode: enum { mixed, ordered } = .mixed;
|
|
|
|
|
|
|
|
// for (args) |arg| {
|
|
|
|
// if (arg.len > 2 and arg[0] == '-' and arg[1] == '-') {}
|
|
|
|
// // if (arg.len > 0 and arg[0] == '-')
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
const Self = @This();
|
2025-03-06 00:45:36 -07:00
|
|
|
|
|
|
|
const NamedParameter = struct {
|
|
|
|
eager: bool,
|
|
|
|
takes_value: bool,
|
|
|
|
mutator: Mutator(spec),
|
|
|
|
};
|
|
|
|
|
|
|
|
const PMap = std.StaticStringMap(NamedParameter);
|
|
|
|
const ArgList = std.SinglyLinkedList([:0]const u8);
|
|
|
|
|
|
|
|
pub const ArgTraveler = struct {
|
|
|
|
first: ?*Node = null,
|
|
|
|
current: ?*Node = null,
|
|
|
|
prev: ?*Node = null,
|
|
|
|
mem: []const Node,
|
|
|
|
|
|
|
|
pub fn fromSlice(alloc: std.mem.Allocator, slice: []const [:0]const u8) error{OutOfMemory}!ArgTraveler {
|
|
|
|
if (slice.len == 0) return .{ .mem = &.{} };
|
|
|
|
|
|
|
|
const nmem = try alloc.alloc(Node, slice.len);
|
|
|
|
nmem[0] = slice[0];
|
|
|
|
for (slice[1..], nmem[1..], nmem[0 .. nmem.len - 1]) |arg, *current, *prev| {
|
|
|
|
current.* = .{ .data = arg };
|
|
|
|
prev.next = current;
|
|
|
|
}
|
|
|
|
|
|
|
|
return .{ .first = &nmem[0], .current = &nmem[0], .mem = nmem };
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn reset(self: *ArgTraveler) void {
|
|
|
|
self.current = self.first;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn next(self: *ArgTraveler) void {
|
|
|
|
self.prev = self.current;
|
|
|
|
if (self.current) |current| {
|
|
|
|
self.current = current.next;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn drop(self: *ArgTraveler) void {
|
|
|
|
if (self.current == null) return;
|
|
|
|
|
|
|
|
if (self.current == self.first)
|
|
|
|
self.first = self.current.?.next
|
|
|
|
else if (self.prev) |prev|
|
|
|
|
prev.next = self.current.?.next;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn popNext(self: *ArgTraveler) ?*Node {
|
|
|
|
self.next();
|
|
|
|
defer self.drop();
|
|
|
|
return self.current;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub const Node = struct {
|
|
|
|
data: [:0]const u8,
|
|
|
|
next: ?*Node = null,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
// const PMap = std.StringHashMap(NamedParameter);
|
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
const GlobalParams = struct {
|
2025-03-06 00:45:36 -07:00
|
|
|
short: PMap,
|
|
|
|
long: PMap,
|
2025-02-21 23:15:59 -07:00
|
|
|
};
|
|
|
|
const LocalParams = struct {
|
2025-03-06 00:45:36 -07:00
|
|
|
short: PMap,
|
|
|
|
long: PMap,
|
2025-02-21 23:15:59 -07:00
|
|
|
args: []const Mutator(spec),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
2023-08-22 20:55:33 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn Result(comptime spec: type) type {
|
|
|
|
comptime {
|
|
|
|
var out: std.builtin.Type = .{
|
|
|
|
.@"struct" = .{
|
|
|
|
.layout = .auto,
|
|
|
|
.fields = &.{},
|
|
|
|
.decls = &.{},
|
|
|
|
.is_tuple = false,
|
|
|
|
},
|
|
|
|
};
|
2023-08-04 00:18:38 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
for (@typeInfo(@TypeOf(spec.parameters)).@"struct".decls) |df| {
|
2025-03-06 00:45:36 -07:00
|
|
|
const param = @field(spec.parameters, df.name);
|
|
|
|
if (@TypeOf(param).Result == void) continue;
|
|
|
|
|
|
|
|
const FType = ResultFieldType(param);
|
2025-02-21 23:15:59 -07:00
|
|
|
out.@"struct".fields = out.@"struct".fields ++ &.{.{
|
2025-03-06 00:45:36 -07:00
|
|
|
.name = df.name ++ "",
|
|
|
|
.type = FType,
|
|
|
|
.default_value = resultFieldDefault(param),
|
2025-02-21 23:15:59 -07:00
|
|
|
.is_comptime = false,
|
2025-03-06 00:45:36 -07:00
|
|
|
.alignment = @alignOf(FType),
|
2025-02-21 23:15:59 -07:00
|
|
|
}};
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
return @Type(out);
|
|
|
|
}
|
|
|
|
}
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2025-03-06 00:45:36 -07:00
|
|
|
pub fn defaultInit(comptime T: type) T {
|
|
|
|
var result: T = undefined;
|
|
|
|
|
|
|
|
for (@typeInfo(T).Struct.fields) |field| {
|
|
|
|
if (field.default_value) |def| {
|
|
|
|
@field(result, field.name) = @as(*const field.type, @ptrCast(@alignCast(def))).*;
|
|
|
|
} else switch (@typeInfo(field.type)) {
|
|
|
|
.@"struct" => @field(result, field.name) = defaultInit(field.type),
|
|
|
|
else => {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn ResultFieldType(comptime param: anytype) type {
|
|
|
|
if (param.mode() == .accumulate) {
|
|
|
|
return param.Type();
|
|
|
|
}
|
|
|
|
if (@typeInfo(param.Type()) == .optional) {
|
|
|
|
return if (param.default != null or param.required)
|
|
|
|
param.Type()
|
|
|
|
else
|
|
|
|
?param.Type();
|
|
|
|
} else @compileError("you stepped in it now");
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn resultFieldDefault(comptime param: anytype) ?*anyopaque {
|
|
|
|
if (param.mode() == .accumulate) {
|
|
|
|
return ¶m.default;
|
|
|
|
}
|
|
|
|
if (@typeInfo(param.Type()) == .optional) {
|
|
|
|
return if (param.default) |def|
|
|
|
|
&@as(param.Type(), def)
|
|
|
|
else
|
|
|
|
null;
|
|
|
|
} else @compileError("doom");
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn Subcommands(comptime spec: type, comptime root: bool) type {
|
|
|
|
comptime {
|
|
|
|
if (!@hasDecl(spec, "subcommands")) return void;
|
|
|
|
const decls = @typeInfo(@TypeOf(spec.subcommands)).@"struct".decls;
|
|
|
|
if (decls.len == 0) return void;
|
|
|
|
|
|
|
|
var out: std.builtin.Type = .{
|
|
|
|
.@"struct" = .{
|
|
|
|
.layout = .auto,
|
|
|
|
.fields = &.{},
|
|
|
|
.decls = &.{},
|
|
|
|
.is_tuple = false,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
for (decls) |dinf| {
|
|
|
|
const decl = @field(@TypeOf(spec.subcommands), dinf.name);
|
|
|
|
const FType = Parser(decl, false);
|
|
|
|
out.@"struct".fields = out.@"struct".fields ++ &.{.{
|
|
|
|
.name = dinf.name + "",
|
|
|
|
.type = FType,
|
|
|
|
.default_value = null,
|
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = @alignOf(FType),
|
|
|
|
}};
|
|
|
|
}
|
|
|
|
if (root) {
|
|
|
|
// help: switch (spec.options.create_help_command) {
|
|
|
|
switch (spec.options.create_help_command) {
|
|
|
|
// .if_subcommands => if (out.@"struct".fields.len > 0) continue :help .always,
|
|
|
|
.if_subcommands,
|
|
|
|
.always,
|
|
|
|
=> {
|
|
|
|
const FType = Parser(HelpCommand(spec), false);
|
|
|
|
out.@"struct".fields = out.@"struct".fields ++ &.{.{
|
|
|
|
.name = "help",
|
|
|
|
.type = FType,
|
|
|
|
.default_value = null,
|
|
|
|
.is_comptime = false,
|
|
|
|
.alignment = @alignOf(FType),
|
|
|
|
}};
|
|
|
|
},
|
|
|
|
.never => {},
|
|
|
|
}
|
|
|
|
if (spec.options.create_completion_helper) {}
|
|
|
|
}
|
|
|
|
|
|
|
|
return @Type(out);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn HelpCommand(comptime rootspec: type) type {
|
|
|
|
return struct {
|
|
|
|
pub const description =
|
|
|
|
\\Get detailed help for a subcommand
|
|
|
|
;
|
|
|
|
pub const options: noclip.CommandOptions = .{};
|
|
|
|
pub const parameters = struct {
|
|
|
|
command_path: noclip.Argument(noclip.Aggregate(noclip.String)) = .{
|
|
|
|
.description =
|
|
|
|
\\The name of the subcommand to print help for. Nested subcommands
|
|
|
|
\\can be requested as well.
|
|
|
|
,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
pub fn run(args: Result(@This())) void {
|
|
|
|
HelpGenerator(rootspec).lookupHelp(args.command_path);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn HelpGenerator(comptime rootspec: type) type {
|
|
|
|
return struct {
|
|
|
|
pub fn lookupHelp(command_path: []const noclip.String) void {
|
|
|
|
_ = rootspec;
|
|
|
|
_ = command_path;
|
|
|
|
std.debug.print("This is a stub\n", .{});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn FieldType(comptime T: type, comptime field: []const u8) type {
|
|
|
|
// return @FieldType(T, field);
|
|
|
|
return switch (@typeInfo(T)) {
|
|
|
|
.Enum => |ti| ti.tag_type,
|
|
|
|
inline .Union, .Struct => |tf| l: for (tf.fields) |tfield| {
|
|
|
|
if (std.mem.eql(u8, tfield.name, field)) break :l tfield.type;
|
|
|
|
} else unreachable,
|
|
|
|
else => unreachable,
|
|
|
|
};
|
|
|
|
}
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn ResultFT(comptime spec: type, comptime field: []const u8) type {
|
|
|
|
return FieldType(Result(spec), field);
|
|
|
|
}
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn ContextType(comptime spec: type) type {
|
|
|
|
return spec.options.context_type;
|
|
|
|
}
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn Mutator(comptime spec: type) type {
|
|
|
|
return *const fn (std.mem.Allocator, ContextType(spec), *Result(spec), []const u8) noclip.Status(void);
|
|
|
|
}
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn TrivialConverter(comptime T: type) type {
|
|
|
|
return *const fn () noclip.Status(T);
|
|
|
|
}
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn SimpleConverter(comptime T: type, comptime alloc: bool) type {
|
|
|
|
return if (alloc)
|
|
|
|
*const fn (std.mem.Allocator, []const u8) noclip.Status(T)
|
|
|
|
else
|
|
|
|
*const fn ([]const u8) noclip.Status(T);
|
|
|
|
}
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn Converter(comptime spec: type, comptime FType: type) type {
|
|
|
|
const Type = enum {
|
|
|
|
trivial,
|
|
|
|
implicit,
|
|
|
|
simple,
|
|
|
|
context,
|
|
|
|
result,
|
|
|
|
full,
|
|
|
|
|
|
|
|
alloc_simple,
|
|
|
|
alloc_context,
|
|
|
|
alloc_result,
|
|
|
|
alloc_full,
|
|
|
|
};
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
return union(Type) {
|
|
|
|
trivial: TrivialConverter(FType),
|
|
|
|
simple: SimpleConverter(FType, false),
|
|
|
|
context: *const fn (ContextType(spec), []const u8) noclip.Status(FType),
|
|
|
|
result: *const fn (*const Result(spec), []const u8) noclip.Status(FType),
|
|
|
|
full: *const fn (ContextType(spec), *const Result(spec), []const u8) noclip.Status(FType),
|
|
|
|
|
|
|
|
alloc_simple: SimpleConverter(FType, true),
|
|
|
|
alloc_context: *const fn (std.mem.Allocator, ContextType(spec), []const u8) noclip.Status(FType),
|
|
|
|
alloc_result: *const fn (std.mem.Allocator, *const Result(spec), []const u8) noclip.Status(FType),
|
|
|
|
alloc_full: *const fn (std.mem.Allocator, ContextType(spec), *const Result(spec), []const u8) noclip.Status(FType),
|
|
|
|
|
|
|
|
pub fn wrap(function: anytype) @This() {
|
|
|
|
const t: Type = comptime blk: {
|
|
|
|
const FuncType: type = switch (@typeInfo(@TypeOf(function))) {
|
|
|
|
.pointer => |ptr| ptr.child,
|
|
|
|
.@"fn" => @TypeOf(function),
|
|
|
|
else => unreachable,
|
|
|
|
};
|
|
|
|
|
|
|
|
for (std.meta.fields(Type)) |tf| {
|
|
|
|
if (@typeInfo(FieldType(@This(), tf.name)).pointer.child == FuncType)
|
|
|
|
break :blk @field(Type, tf.name);
|
|
|
|
} else unreachable;
|
2024-04-07 11:23:25 -07:00
|
|
|
};
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
return @unionInit(@This(), @tagName(t), function);
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn invoke(
|
|
|
|
self: @This(),
|
|
|
|
alloc: std.mem.Allocator,
|
|
|
|
context: ContextType(spec),
|
|
|
|
res: *const Result(spec),
|
|
|
|
rawvalue: []const u8,
|
|
|
|
) noclip.Status(FType) {
|
|
|
|
return switch (self) {
|
|
|
|
.trivial => |call| call(),
|
|
|
|
.simple => |call| call(rawvalue),
|
|
|
|
.context => |call| call(context, rawvalue),
|
|
|
|
.result => |call| call(res, rawvalue),
|
|
|
|
.full => |call| call(context, res, rawvalue),
|
|
|
|
|
|
|
|
.alloc_simple => |call| call(alloc, rawvalue),
|
|
|
|
.alloc_context => |call| call(alloc, context, rawvalue),
|
|
|
|
.alloc_result => |call| call(alloc, res, rawvalue),
|
|
|
|
.alloc_full => |call| call(alloc, context, res, rawvalue),
|
|
|
|
};
|
2023-03-31 15:37:52 -07:00
|
|
|
}
|
2025-02-21 23:15:59 -07:00
|
|
|
};
|
|
|
|
}
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn defaultConverter(comptime spec: type, comptime FType: type) Converter(spec, FType) {
|
|
|
|
if (FType == noclip.String) {
|
|
|
|
return convertString;
|
|
|
|
}
|
|
|
|
return switch (@typeInfo(FType)) {
|
|
|
|
.int => Converter(spec, FType).wrap(convertInt(FType, 0)),
|
|
|
|
.@"enum" => Converter(spec, FType).wrap(convertEnum(FType)),
|
|
|
|
};
|
|
|
|
}
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
fn defaultMutator(
|
|
|
|
comptime spec: type,
|
|
|
|
comptime field: []const u8,
|
|
|
|
) Mutator(spec) {
|
|
|
|
const converter = defaultConverter(spec, ResultFT(spec, field));
|
|
|
|
return struct {
|
|
|
|
fn mut(alloc: std.mem.Allocator, ctx: ContextType(spec), res: *Result(spec), rawvalue: []const u8) noclip.Status(void) {
|
|
|
|
switch (converter.invoke(alloc, ctx, res, rawvalue)) {
|
|
|
|
.success => |val| @field(res, field) = val,
|
|
|
|
.failure => |val| return .{ .failure = val },
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
2025-02-21 23:15:59 -07:00
|
|
|
return .success;
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
2025-02-21 23:15:59 -07:00
|
|
|
}.mut;
|
|
|
|
}
|
2023-04-01 13:04:02 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn convertInt(comptime T: type, base: u8) SimpleConverter(T, true) {
|
|
|
|
return struct {
|
|
|
|
fn conv(alloc: std.mem.Allocator, input: []const u8) noclip.Status(T) {
|
|
|
|
return if (std.fmt.parseInt(FieldType, input, base)) |res|
|
2025-03-06 00:45:36 -07:00
|
|
|
.succeed(res)
|
2023-04-02 15:11:50 -07:00
|
|
|
else |_|
|
2025-03-06 00:45:36 -07:00
|
|
|
.fail(
|
|
|
|
std.fmt.allocPrint(
|
2025-02-21 23:15:59 -07:00
|
|
|
alloc,
|
|
|
|
"could not parse {s} as an integer",
|
|
|
|
.{input},
|
|
|
|
) catch "out of memory",
|
2025-03-06 00:45:36 -07:00
|
|
|
);
|
2023-04-01 13:04:02 -07:00
|
|
|
}
|
2025-02-21 23:15:59 -07:00
|
|
|
}.conv;
|
2023-03-30 17:00:49 -07:00
|
|
|
}
|
2023-08-22 20:55:33 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn convertEnum(comptime T: type) SimpleConverter(T, true) {
|
2023-08-22 20:55:33 -07:00
|
|
|
return struct {
|
2025-02-21 23:15:59 -07:00
|
|
|
fn conv(alloc: std.mem.Allocator, input: []const u8) noclip.Status(T) {
|
|
|
|
return if (std.meta.stringToEnum(T, input)) |val|
|
2025-03-06 00:45:36 -07:00
|
|
|
.succeed(val)
|
2025-02-21 23:15:59 -07:00
|
|
|
else
|
2025-03-06 00:45:36 -07:00
|
|
|
.fail(
|
|
|
|
std.fmt.allocPrint(
|
2025-02-21 23:15:59 -07:00
|
|
|
alloc,
|
|
|
|
"`{s}` is not a member of {s}",
|
2025-03-06 00:45:36 -07:00
|
|
|
.{ input, @typeName(T) },
|
|
|
|
) catch "out of memory",
|
|
|
|
);
|
2023-08-22 20:55:33 -07:00
|
|
|
}
|
2025-02-21 23:15:59 -07:00
|
|
|
}.conv;
|
|
|
|
}
|
2023-08-22 20:55:33 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
pub fn convertString(alloc: std.mem.Allocator, input: []const u8) noclip.Status(noclip.String) {
|
|
|
|
return if (alloc.dupe(input)) |copy|
|
2025-03-06 00:45:36 -07:00
|
|
|
.succeed(.{ .bytes = copy })
|
2025-02-21 23:15:59 -07:00
|
|
|
else |_|
|
2025-03-06 00:45:36 -07:00
|
|
|
.fail("out of memory");
|
2025-02-21 23:15:59 -07:00
|
|
|
}
|
2023-08-22 20:55:33 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
fn incrementor(
|
|
|
|
comptime spec: type,
|
|
|
|
comptime field: []const u8,
|
|
|
|
comptime step: ResultFT(spec, field),
|
|
|
|
) Mutator(spec) {
|
|
|
|
return struct {
|
|
|
|
fn mut(_: std.mem.Allocator, _: ContextType(spec), res: *Result(spec), _: []const u8) noclip.Status(void) {
|
2025-03-06 00:45:36 -07:00
|
|
|
@field(res, field) +|= step;
|
2025-02-21 23:15:59 -07:00
|
|
|
return .success;
|
2023-08-22 20:55:33 -07:00
|
|
|
}
|
2025-02-21 23:15:59 -07:00
|
|
|
}.mut;
|
|
|
|
}
|
2023-08-22 20:55:33 -07:00
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
fn implicitSetter(
|
|
|
|
comptime spec: type,
|
|
|
|
comptime field: []const u8,
|
|
|
|
comptime value: ResultFT(spec, field),
|
|
|
|
) Mutator(spec) {
|
|
|
|
return struct {
|
|
|
|
fn mut(_: std.mem.Allocator, _: ContextType(spec), res: *Result(spec), _: []const u8) noclip.Status(void) {
|
|
|
|
@field(res, field) = value;
|
|
|
|
return .success;
|
2023-08-22 20:55:33 -07:00
|
|
|
}
|
2025-02-21 23:15:59 -07:00
|
|
|
}.mut;
|
2023-08-22 20:55:33 -07:00
|
|
|
}
|
|
|
|
|
2025-02-21 23:15:59 -07:00
|
|
|
fn setter(
|
|
|
|
comptime spec: type,
|
|
|
|
comptime field: []const u8,
|
|
|
|
comptime converter_fn: anytype,
|
|
|
|
) Mutator(spec) {
|
|
|
|
const converter = Converter(spec, ResultFT(spec, field)).wrap(converter_fn);
|
|
|
|
return struct {
|
|
|
|
fn mut(alloc: std.mem.Allocator, ctx: ContextType(spec), res: *Result(spec), rawvalue: []const u8) noclip.Status(void) {
|
|
|
|
switch (converter.invoke(alloc, ctx, res, rawvalue)) {
|
|
|
|
.success => |val| @field(res, field) = val,
|
|
|
|
.failure => |val| return .{ .failure = val },
|
2023-09-10 14:50:44 -07:00
|
|
|
}
|
2025-02-21 23:15:59 -07:00
|
|
|
return .success;
|
|
|
|
}
|
|
|
|
}.mut;
|
2023-08-22 20:55:33 -07:00
|
|
|
}
|
2025-02-21 23:15:59 -07:00
|
|
|
|
|
|
|
const std = @import("std");
|
|
|
|
const noclip = @import("./noclip.zig");
|