2022-11-20 12:54:26 -08:00
|
|
|
const std = @import("std");
|
|
|
|
const noclip = @import("noclip");
|
|
|
|
|
2023-03-30 17:00:49 -07:00
|
|
|
const CommandBuilder = noclip.CommandBuilder;
|
2022-11-20 12:54:26 -08:00
|
|
|
|
2023-03-30 17:00:49 -07:00
|
|
|
const Choice = enum { first, second };
|
2022-11-26 20:29:23 -08:00
|
|
|
|
2023-03-30 17:00:49 -07:00
|
|
|
const cli = cmd: {
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
var cmd = CommandBuilder(*u32){
|
2023-04-04 23:22:12 -07:00
|
|
|
.description =
|
2023-04-01 13:04:02 -07:00
|
|
|
\\The definitive noclip demonstration utility
|
|
|
|
\\
|
2023-04-02 17:15:37 -07:00
|
|
|
\\This command demonstrates the functionality of the noclip library. cool!
|
2023-04-04 23:22:12 -07:00
|
|
|
,
|
|
|
|
};
|
2023-08-05 13:41:21 -07:00
|
|
|
cmd.addOption(.{ .OutputType = struct { u8, u8 } }, .{
|
2023-03-30 17:00:49 -07:00
|
|
|
.name = "test",
|
|
|
|
.short_tag = "-t",
|
|
|
|
.long_tag = "--test",
|
|
|
|
.env_var = "NOCLIP_TEST",
|
2023-04-02 15:11:50 -07:00
|
|
|
.description = "multi-value test option",
|
|
|
|
.nice_type_name = "int> <int",
|
2023-03-30 17:00:49 -07:00
|
|
|
});
|
2023-08-05 13:41:21 -07:00
|
|
|
cmd.addOption(.{ .OutputType = Choice }, .{
|
2023-03-30 17:00:49 -07:00
|
|
|
.name = "choice",
|
|
|
|
.short_tag = "-c",
|
|
|
|
.long_tag = "--choice",
|
2023-04-02 15:11:50 -07:00
|
|
|
.default = .second,
|
2023-03-30 17:00:49 -07:00
|
|
|
.env_var = "NOCLIP_CHOICE",
|
2023-04-02 15:11:50 -07:00
|
|
|
.description = "enum choice option",
|
|
|
|
.nice_type_name = "choice",
|
2023-03-30 17:00:49 -07:00
|
|
|
});
|
2023-08-27 13:53:14 -07:00
|
|
|
cmd.stringOption(.{
|
|
|
|
.name = "string",
|
|
|
|
.short_tag = "-s",
|
|
|
|
.long_tag = "--string",
|
|
|
|
.env_var = "NOCLIP_STRING",
|
|
|
|
.description = "A string value option",
|
|
|
|
});
|
2023-08-05 13:41:21 -07:00
|
|
|
cmd.addOption(.{ .OutputType = u32 }, .{
|
2023-03-30 17:00:49 -07:00
|
|
|
.name = "default",
|
|
|
|
.short_tag = "-d",
|
|
|
|
.long_tag = "--default",
|
|
|
|
.env_var = "NOCLIP_DEFAULT",
|
|
|
|
.default = 100,
|
2023-04-02 15:11:50 -07:00
|
|
|
.description = "default value integer option",
|
|
|
|
.nice_type_name = "uint",
|
sorta help text generation
This mostly works. Subcommands are utterly broken because we blindly
consume an additional argument to get the program name, which we
should not do.
This code was always kind of spaghetti, but it's getting worse. I want
to refactor it into something that doesn't make me cringe, but at the
same time, this project was intended to be a means to an end rather
than the end itself, and it kind of feels a bit silly to spend a ton
of time on it. On the other hand, relying on it for other projects
seems silly if it's a fragile mess. The goal was to get it into a
usable state and then hack on it as necessary, but it still has a ways
to go to get there, and working on it is kind of painful, in an
existential fashion.
Perhaps I will attempt to rewrite it, get halfway, and stall forever.
Thanks for reading my cool commit message blog. Bye.
2023-03-20 23:13:58 -07:00
|
|
|
});
|
2023-08-05 13:41:21 -07:00
|
|
|
cmd.addOption(.{ .OutputType = u8, .multi = true }, .{
|
2023-03-30 17:00:49 -07:00
|
|
|
.name = "multi",
|
|
|
|
.short_tag = "-m",
|
|
|
|
.long_tag = "--multi",
|
2023-04-02 15:11:50 -07:00
|
|
|
.description = "multiple specification test option",
|
2022-11-27 01:31:20 -08:00
|
|
|
});
|
2023-08-05 13:41:21 -07:00
|
|
|
cmd.addFlag(.{}, .{
|
2023-03-30 17:00:49 -07:00
|
|
|
.name = "flag",
|
|
|
|
.truthy = .{ .short_tag = "-f", .long_tag = "--flag" },
|
2023-04-02 15:11:50 -07:00
|
|
|
.falsy = .{ .short_tag = "-F", .long_tag = "--no-flag" },
|
2023-03-30 17:00:49 -07:00
|
|
|
.env_var = "NOCLIP_FLAG",
|
2023-04-02 15:11:50 -07:00
|
|
|
.description = "boolean flag",
|
sorta help text generation
This mostly works. Subcommands are utterly broken because we blindly
consume an additional argument to get the program name, which we
should not do.
This code was always kind of spaghetti, but it's getting worse. I want
to refactor it into something that doesn't make me cringe, but at the
same time, this project was intended to be a means to an end rather
than the end itself, and it kind of feels a bit silly to spend a ton
of time on it. On the other hand, relying on it for other projects
seems silly if it's a fragile mess. The goal was to get it into a
usable state and then hack on it as necessary, but it still has a ways
to go to get there, and working on it is kind of painful, in an
existential fashion.
Perhaps I will attempt to rewrite it, get halfway, and stall forever.
Thanks for reading my cool commit message blog. Bye.
2023-03-20 23:13:58 -07:00
|
|
|
});
|
2023-08-05 13:41:21 -07:00
|
|
|
cmd.addFlag(.{ .multi = true }, .{
|
2023-03-30 17:00:49 -07:00
|
|
|
.name = "multiflag",
|
|
|
|
.truthy = .{ .short_tag = "-M" },
|
2023-04-02 15:11:50 -07:00
|
|
|
.description = "multiple specification test flag ",
|
2023-03-30 17:00:49 -07:00
|
|
|
});
|
2023-08-05 13:41:21 -07:00
|
|
|
cmd.addOption(.{ .OutputType = u8 }, .{
|
2023-04-02 17:15:37 -07:00
|
|
|
.name = "env",
|
|
|
|
.env_var = "NOCLIP_ENVIRON",
|
|
|
|
.description = "environment variable only option",
|
|
|
|
});
|
2022-11-26 20:29:23 -08:00
|
|
|
|
2023-03-30 17:00:49 -07:00
|
|
|
break :cmd cmd;
|
2022-11-20 12:54:26 -08:00
|
|
|
};
|
|
|
|
|
2023-03-30 17:00:49 -07:00
|
|
|
const subcommand = cmd: {
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
var cmd = CommandBuilder([]const u8){
|
2023-04-04 23:22:12 -07:00
|
|
|
.description =
|
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
|
|
|
\\Demonstrate subcommand functionality
|
2023-04-01 13:04:02 -07:00
|
|
|
\\
|
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
|
|
|
\\This command demonstrates how subcommands work.
|
2023-04-04 23:22:12 -07:00
|
|
|
,
|
|
|
|
};
|
2023-08-05 13:41:21 -07:00
|
|
|
cmd.simpleFlag(.{
|
2023-03-30 17:00:49 -07:00
|
|
|
.name = "flag",
|
|
|
|
.truthy = .{ .short_tag = "-f", .long_tag = "--flag" },
|
|
|
|
.falsy = .{ .long_tag = "--no-flag" },
|
|
|
|
.env_var = "NOCLIP_SUBFLAG",
|
|
|
|
});
|
2023-08-27 13:53:14 -07:00
|
|
|
cmd.stringArgument(.{ .name = "argument" });
|
|
|
|
cmd.stringArgument(.{
|
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
|
|
|
.name = "arg",
|
|
|
|
.description = "This is an argument that doesn't really do anything, but it's very important.",
|
|
|
|
});
|
2023-03-30 17:00:49 -07:00
|
|
|
break :cmd cmd;
|
|
|
|
};
|
2022-11-20 12:54:26 -08:00
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
fn subHandler(context: []const u8, result: subcommand.Output()) !void {
|
2023-03-30 17:00:49 -07:00
|
|
|
std.debug.print("subcommand: {s}\n", .{result.argument});
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
std.debug.print("context: {s}\n", .{context});
|
2022-11-20 12:54:26 -08:00
|
|
|
}
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
fn cliHandler(context: *u32, result: cli.Output()) !void {
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
std.debug.print("context: {d}\n", .{context.*});
|
2023-08-27 13:53:14 -07:00
|
|
|
std.debug.print("callback is working {s}\n", .{result.string orelse "null"});
|
2023-03-30 17:00:49 -07:00
|
|
|
std.debug.print("callback is working {any}\n", .{result.choice});
|
|
|
|
std.debug.print("callback is working {d}\n", .{result.default});
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
context.* += 1;
|
2022-11-20 12:54:26 -08:00
|
|
|
}
|
|
|
|
|
2023-04-04 23:22:12 -07:00
|
|
|
pub fn main() !u8 {
|
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
|
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
|
|
defer _ = gpa.deinit();
|
|
|
|
const allocator = gpa.allocator();
|
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
var parser = try cli.createParser(cliHandler, 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
|
|
|
defer parser.deinitTree();
|
2022-11-20 12:54:26 -08:00
|
|
|
|
2023-03-30 17:00:49 -07:00
|
|
|
var context: u32 = 2;
|
parser: don't force pass userdata as a pointer
This is an interesting change. While I think generally passing in
constant userdata is not terribly useful, the previous implementation
precluded it entirely. Interface types, for example, are often passed
directly and stored as constants (they hold pointers to their mutable
state).
Since we type erase this so it can be bound to the generic interface
object, non-pointer objects must be passed by reference to avoid
binding the parser interface to a temporary stack copy of the object.
This means we have to handle these cases slightly differently. Also,
while technically being classified as pointers, slices don't really
behave like pointers, which is understandable but annoying. There's a
bit of asymmetry here, as CommandBuilder(*u32) and CommandBuilder
(u32) both require an *u32 when binding the parser interface. This is
of course because pointers do not need to be rewrapped to be type
erased. The same code path could be used for both cases, but then the
user would have to pass in a pointer to a pointer, which actually
looks a bit silly because then it potentially means having to
do &&my_var.
2023-04-08 15:13:00 -07:00
|
|
|
const sc: []const u8 = "whassup";
|
2023-03-30 17:00:49 -07:00
|
|
|
|
2023-08-05 13:41:21 -07:00
|
|
|
var subcon = try subcommand.createParser(subHandler, allocator);
|
|
|
|
try parser.addSubcommand("verb", subcon.interface(&sc));
|
2023-03-30 17:00:49 -07:00
|
|
|
|
|
|
|
const iface = parser.interface(&context);
|
2023-04-04 23:22:12 -07:00
|
|
|
iface.execute() catch return 1;
|
|
|
|
|
|
|
|
return 0;
|
2022-11-20 12:54:26 -08:00
|
|
|
}
|