NOCLIP/demo/demo.zig

155 lines
5.5 KiB
Zig
Raw Permalink Normal View History

2022-11-20 12:54:26 -08:00
const std = @import("std");
const noclip = @import("noclip");
const CommandBuilder = noclip.CommandBuilder;
2022-11-20 12:54:26 -08:00
const Choice = enum { first, second };
const cli = cmd: {
var cmd = CommandBuilder(*u32){
.description =
\\The definitive noclip demonstration utility.
\\
\\This command demonstrates the functionality of the noclip library. cool!
\\
\\> // implementing factorial recursively is a silly thing to do
\\> pub fn fact(n: u64) u64 {
\\> if (n == 0) return 1;
\\> return n*fact(n - 1);
\\> }
\\
\\Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
\\incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
\\nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
\\Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore
\\eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident,
\\sunt in culpa qui officia deserunt mollit anim id est laborum.
,
};
cmd.addOption(.{ .OutputType = struct { u8, u8 } }, .{
.name = "test",
.short_tag = "-t",
.long_tag = "--test",
.env_var = "NOCLIP_TEST",
.description = "multi-value test option",
.nice_type_name = "int> <int",
});
cmd.addOption(.{ .OutputType = Choice }, .{
.name = "choice",
.short_tag = "-c",
.long_tag = "--choice",
.default = .second,
.env_var = "NOCLIP_CHOICE",
.description = "enum choice option",
.nice_type_name = "choice",
});
cmd.stringOption(.{
.name = "string",
.short_tag = "-s",
.long_tag = "--string",
.env_var = "NOCLIP_STRING",
.description = "A string value option",
});
cmd.addOption(.{ .OutputType = u32 }, .{
.name = "default",
.short_tag = "-d",
.long_tag = "--default",
.env_var = "NOCLIP_DEFAULT",
.default = 100,
.description = "default value integer option",
.nice_type_name = "uint",
});
cmd.addOption(.{ .OutputType = u8, .multi = true }, .{
.name = "multi",
.short_tag = "-m",
.long_tag = "--multi",
.description = "multiple specification test option",
});
cmd.addFlag(.{}, .{
.name = "flag",
.truthy = .{ .short_tag = "-f", .long_tag = "--flag" },
.falsy = .{ .short_tag = "-F", .long_tag = "--no-flag" },
.env_var = "NOCLIP_FLAG",
.description = "boolean flag",
});
cmd.addFlag(.{ .multi = true }, .{
.name = "multiflag",
.truthy = .{ .short_tag = "-M" },
.description = "multiple specification test flag ",
});
cmd.addOption(.{ .OutputType = u8 }, .{
.name = "env",
.env_var = "NOCLIP_ENVIRON",
.description = "environment variable only option",
});
break :cmd cmd;
2022-11-20 12:54:26 -08:00
};
const subcommand = cmd: {
var cmd = CommandBuilder([]const u8){
.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
\\
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.
,
};
cmd.simpleFlag(.{
.name = "flag",
.truthy = .{ .short_tag = "-f", .long_tag = "--flag" },
.falsy = .{ .long_tag = "--no-flag" },
.env_var = "NOCLIP_SUBFLAG",
});
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.",
});
break :cmd cmd;
};
2022-11-20 12:54:26 -08:00
fn subHandler(context: []const u8, result: subcommand.Output()) !void {
std.debug.print("subcommand: {s}\n", .{result.argument});
std.debug.print("context: {s}\n", .{context});
2022-11-20 12:54:26 -08:00
}
fn cliHandler(context: *u32, result: cli.Output()) !void {
std.debug.print("context: {d}\n", .{context.*});
std.debug.print("callback is working {s}\n", .{result.string orelse "null"});
std.debug.print("callback is working {any}\n", .{result.choice});
std.debug.print("callback is working {d}\n", .{result.default});
context.* += 1;
2022-11-20 12:54:26 -08: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();
const base = try noclip.commandGroup(allocator, .{ .description = "base group" });
defer base.deinitTree();
2022-11-20 12:54:26 -08:00
var context: u32 = 2;
const sc: []const u8 = "whassup";
try base.addSubcommand("main", try cli.createInterface(allocator, cliHandler, &context));
try base.addSubcommand("other", try subcommand.createInterface(allocator, subHandler, &sc));
const group = try noclip.commandGroup(allocator, .{ .description = "final level of a deeply nested subcommand" });
const subcon = try noclip.commandGroup(allocator, .{ .description = "third level of a deeply nested subcommand" });
const nested = try noclip.commandGroup(allocator, .{ .description = "second level of a deeply nested subcommand" });
const deeply = try noclip.commandGroup(allocator, .{ .description = "start of a deeply nested subcommand" });
try base.addSubcommand("deeply", deeply);
try deeply.addSubcommand("nested", nested);
try nested.addSubcommand("subcommand", subcon);
try subcon.addSubcommand("group", group);
try group.addSubcommand("run", try cli.createInterface(allocator, cliHandler, &context));
base.execute() catch |err| {
std.io.getStdErr().writeAll(base.getParseError()) catch {};
return err;
};
return 0;
2022-11-20 12:54:26 -08:00
}