authorgravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-09-16 14:34:09-04:00
committergravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-09-16 14:34:09-04:00
log4f36df95e5a809e6859d7e927c5d4f71c2c5b861
treee950153a009bba0197753705bad5aee1a44cb30d
parent14d44af94483be96fb95178a9f9c7c7d0eb667f0
signature Commit is signed but in an unrecognized format.

feat: cli subcommands


4 files changed, 360 insertions(+), 79 deletions(-)

CHANGELOG.md+3-2
......@@ -1,15 +1,16 @@
11## 0.1.6
22
33- breaking: rename `@js` to `@extern`, considering future backends
4- breaking: rename `noreturn` to `never`
45- re-write most of the semantic analysis pass (#79)
56 - feat: implement function expressions
67 - feat: implement simple `comptime` evaluation
78 - feat: change all semantic analysis panics into handled errors
89 - fix: no longer analyzes contents past a block end
9- feat: add `--run` to execute the file in QuickJS instead of outputting source
10- feat: introduce cli subcommands
11- feat: add `run` subcommand to execute the file in QuickJS instead of outputting source
1012- feat: add array index syntax via `x[1]` and `x.1`
1113- feat: add `len` property to arrays
12- feat: rename `noreturn` to `never`
1314- feat: allow instantiating `@extern` at compile time
1415- feat: implement tuples
1516- feat: implement multiline string literals
build.zig+1
......@@ -400,6 +400,7 @@ pub fn build(b: *Build) !void {
400400 b.installArtifact(main.cli);
401401
402402 inline for (.{
403 .{ main.cli, "run" },
403404 .{ minimal.compiler_cli, "compiler" },
404405 .{ minimal.format_cli, "format" },
405406 .{ minimal.loader_cli, "loader" },
src/Cli.zig+53-31
......@@ -3,11 +3,18 @@
33//! allocator, however it expects incoming arguments to be UTF-8 formatted which
44//! on Windows will require an allocator.
55//!
6//! See `fn main` for a basic usage example.
7options: []const Option = &.{},
8positionals: Positionals = .none,
6//! See `fn main` for a basic usage example. Parsing subcommands is done in
7//! `main.zig`, utilizing a different `Cli` for each option set.
8options: []const Option,
9positionals: Positionals,
910help: HelpConfig = .{},
1011
12pub const empty: Cli = .{
13 .options = &.{},
14 .positionals = .none,
15 .help = .{ .option = false },
16};
17
1118pub const Option = struct {
1219 /// Should be formatted as the flag name: '--dump-ir'
1320 name: []const u8,
......@@ -262,14 +269,15 @@ pub fn parse(
262269 stderr.get(),
263270 stderr.tty,
264271 true,
265 true,
266272 ) catch |e| switch (e) {
267 error.InvalidArguments,
268 error.DisplayedHelpMenu,
269 => {
273 error.InvalidArguments => {
270274 stderr.buffered_writer.flush() catch {};
271275 std.process.exit(1);
272276 },
277 error.DisplayedHelpMenu => {
278 stderr.buffered_writer.flush() catch {};
279 std.process.exit(0);
280 },
273281 };
274282}
275283
......@@ -279,9 +287,9 @@ pub fn parseWithErrorWriter(
279287 args: []const []const u8,
280288 err: std.io.AnyWriter,
281289 tty: std.io.tty.Config,
282 comptime show_help_on_error: bool,
283290 comptime exit_on_error: bool,
284291) ParseError!p.Parsed() {
292 _ = exit_on_error; // autofix
285293 var out: p.Parsed() = .{};
286294 var had_errors = false;
287295 var positionals_counted: usize = 0;
......@@ -312,7 +320,7 @@ pub fn parseWithErrorWriter(
312320 positionals_counted += remain.len;
313321
314322 if (positionals_counted > max_positionals) {
315 had_errors = true;
323 p.markFirstError(&had_errors, program_name, err, tty);
316324 const diff = @min(remain.len, positionals_counted - max_positionals);
317325 for (remain[remain.len - diff ..]) |pos| {
318326 writeError(err, tty);
......@@ -333,7 +341,7 @@ pub fn parseWithErrorWriter(
333341 break :try_this_def;
334342
335343 if (kind != .boolean and is_blank) {
336 had_errors = true;
344 p.markFirstError(&had_errors, program_name, err, tty);
337345 writeError(err, tty);
338346 err.print(prefix ++ " requires a value\n", .{}) catch {};
339347 continue :outer;
......@@ -348,7 +356,7 @@ pub fn parseWithErrorWriter(
348356 } else if (mem.eql(u8, value, "true")) {
349357 dest.* = true;
350358 } else {
351 had_errors = true;
359 p.markFirstError(&had_errors, program_name, err, tty);
352360 writeError(err, tty);
353361 err.print(prefix ++ " only accepts true or false, got '{s}'\n", .{
354362 arg[prefix.len + 1 ..],
......@@ -358,14 +366,14 @@ pub fn parseWithErrorWriter(
358366 .string => if (std.unicode.utf8ValidateSlice(value)) {
359367 dest.* = value;
360368 } else {
361 had_errors = true;
369 p.markFirstError(&had_errors, program_name, err, tty);
362370 writeError(err, tty);
363371 err.print(prefix ++ " requires valid UTF-8 input\n", .{}) catch {};
364372 },
365373
366374 .@"enum" => |E| {
367375 dest.* = std.meta.stringToEnum(E, value) orelse {
368 had_errors = true;
376 p.markFirstError(&had_errors, program_name, err, tty);
369377 const values_list = comptime brk: {
370378 var str: []const u8 = "";
371379 for (std.enums.values(E)) |val| {
......@@ -390,14 +398,14 @@ pub fn parseWithErrorWriter(
390398 if (mem.startsWith(u8, arg[2..], "help") and (arg.len == "--help".len or arg["--help".len] == '=')) {
391399 show_help = true;
392400 if (arg.len > "--help".len) {
393 had_errors = true;
401 p.markFirstError(&had_errors, program_name, err, tty);
394402 err.print("--help does not take a value\n", .{}) catch {};
395403 }
396404 continue :outer;
397405 }
398406 }
399407
400 had_errors = true;
408 p.markFirstError(&had_errors, program_name, err, tty);
401409 writeError(err, tty);
402410 err.print("unrecognized argument '{s}'\n", .{arg}) catch {};
403411 } else if (arg.len > 1 and arg[0] == '-') {
......@@ -421,7 +429,7 @@ pub fn parseWithErrorWriter(
421429 if (positionals_counted == 0) first_positional = i;
422430 positionals_counted += 1;
423431 if (positionals_counted > max_positionals) {
424 had_errors = true;
432 p.markFirstError(&had_errors, program_name, err, tty);
425433 writeError(err, tty);
426434 err.print("unrecognized argument '{s}'\n", .{arg}) catch {};
427435 } else if (p.positionals == .bounded) {
......@@ -435,19 +443,16 @@ pub fn parseWithErrorWriter(
435443 writeNote(err, tty);
436444 err.writeAll(switch (max_positionals) {
437445 0 => "this program does not take positional arguments\n",
438 1 => "this program accepts one positional argument\n",
446 1 => "this program only accepts one positional argument\n",
439447 else => |n| std.fmt.comptimePrint(
440 "this program accepts up to {d} positional arguments\n",
448 "this program only accepts up to {d} positional arguments\n",
441449 .{n},
442450 ),
443451 }) catch {};
444452 }
445 if (show_help or show_help_on_error) {
453 if (show_help) {
446454 p.showHelpWithWriter(program_name, err, tty);
447455 }
448 if (exit_on_error) {
449 return error.DisplayedHelpMenu;
450 }
451456 return error.InvalidArguments;
452457 }
453458
......@@ -476,6 +481,18 @@ pub fn parseWithErrorWriter(
476481 return out;
477482}
478483
484pub fn markFirstError(
485 comptime p: Cli,
486 ptr: *bool,
487 program_name: []const u8,
488 err: std.io.AnyWriter,
489 tty: std.io.tty.Config,
490) void {
491 if (ptr.* == true) return;
492 ptr.* = true;
493 p.showHelpWithWriter(program_name, err, tty);
494}
495
479496pub fn showHelp(comptime p: Cli, program_name: []const u8) void {
480497 var stderr = StdErrWriter.init();
481498 defer stderr.buffered_writer.flush() catch {};
......@@ -502,14 +519,18 @@ pub fn showHelpWithWriter(
502519 };
503520 };
504521
505 w.print("usage: {s} {s}\n", .{ program_name, usage }) catch return;
522 tty.setColor(w, .bold) catch return;
523 w.writeAll("usage") catch return;
524 tty.setColor(w, .reset) catch return;
525 w.print(": {s} {s}\n", .{ program_name, usage }) catch return;
506526
507527 if (p.options.len > 0 or p.help.option) {
508528 w.writeAll("\n") catch return;
509529 tty.setColor(w, .bold) catch return;
510 w.writeAll("options:") catch return;
530 w.writeAll("options") catch return;
511531 tty.setColor(w, .reset) catch return;
512532 w.writeAll(comptime brk: {
533 var out: []const u8 = ":\n\n";
513534 var width = 0;
514535 var any_short_options = false;
515536
......@@ -526,7 +547,6 @@ pub fn showHelpWithWriter(
526547 if (any_short_options) width += "-h ".len;
527548 const sub = if (any_short_options) 4 else 1;
528549
529 var out: []const u8 = "\n";
530550 for (all_opts) |opt| {
531551 out = out ++ " ";
532552 if (any_short_options) {
......@@ -605,12 +625,14 @@ pub fn fatalWithHelp(p: Cli, program_name: []const u8, comptime fmt: []const u8,
605625}
606626
607627pub fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
608 const stderr = std.io.getStdErr();
609 const tty = std.io.tty.detectConfig(stderr);
610 var bufw = std.io.bufferedWriter(stderr.writer());
611 writeError(bufw.writer().any(), tty);
612 bufw.writer().print(fmt ++ "\n", args);
613 bufw.flush() catch {}; // ignoring I/O errors
628 print: {
629 const stderr = std.io.getStdErr();
630 const tty = std.io.tty.detectConfig(stderr);
631 var bufw = std.io.bufferedWriter(stderr.writer());
632 writeError(bufw.writer().any(), tty);
633 bufw.writer().print(fmt ++ "\n", args) catch break :print;
634 bufw.flush() catch break :print;
635 }
614636 std.process.exit(1);
615637}
616638
src/main.zig+303-46
......@@ -1,31 +1,155 @@
1//! This is the `lang` command line interface. It combines all components
2//! of the software development kit into one. Multiple versions of this
3//! file are built with different subsets of features, allowing you to,
4//! for example, test the `core` module's compilation features without
5//! needing to compile the formatter.
1//! This is the user-facing command line, combining all features into
2//! different subcommands, each with their own option set.
63
7const config = @import("config");
4const default_program_name = "wlang"; // fallback when argv[0] is missing
85
9const arg_config: Cli = .{
10 .options = .{
11 .{ .short = 'i', .name = "--dump-ir", .desc = "Dump intermediate representations" },
12 .{ .name = "--dump-state", .desc = "Dump compiler state to JSON", .kind = .string },
13 } ++ Cli.Option.optional(config.have_embedded_quickjs, &.{
14 .{ .name = "--run", .desc = "Execute with QuickJS" },
6const Command = struct {
7 name: [:0]const u8,
8 description: []const u8,
9 cli: Cli,
10 run: fn (Context) anyerror!void,
11 enabled: bool = true,
12
13 const ListItem = union(enum) {
14 separator,
15 command: Command,
16
17 fn cmd(command: Command) ListItem {
18 return .{ .command = command };
19 }
20 };
21};
22
23const general_compiler_options: []const Cli.Option = &.{
24 .{ .short = 'i', .name = "--dump-ir", .desc = "Dump intermediate representations" },
25 .{ .name = "--dump-state", .desc = "Dump compiler state to JSON", .kind = .string },
26};
27
28const top_level_commands: []const Command.ListItem = &.{
29 // Compilation
30 .cmd(.{
31 .name = "build",
32 .description = "Create an executable from source code",
33 .cli = .{
34 .options = general_compiler_options,
35 .positionals = .single,
36 .help = .{ .usage = "[options...] [entry file]" },
37 },
38 .run = cmdBuild,
39 }),
40 .cmd(.{
41 .name = "run",
42 .description = "Create executable and run with QuickJS",
43 .cli = .{
44 .options = general_compiler_options ++ .{
45 .{ .name = "--dump-code", .desc = "Dump compiled JavaScript to standard out" },
46 },
47 .positionals = .single,
48 .help = .{ .usage = "[options...] [entry file]" },
49 },
50 .enabled = config.have_embedded_quickjs,
51 .run = cmdRun,
52 }),
53
54 // Utilities
55 // .separator,
56 // .cmd(.{
57 // .name = "fmt",
58 // .description = "Reformat code into canonical form",
59 // .cli = .{
60 // .options = &.{},
61 // .positionals = .unlimited,
62 // .help = .{ .usage = "[options...] [files...]" },
63 // },
64 // .run = cmdFmt,
65 // }),
66
67 // Statistics
68 .separator,
69 .cmd(.{
70 .name = "version",
71 .description = "Print version number and exit",
72 .cli = .empty,
73 .run = undefined, // special cased
74 }),
75 .cmd(.{
76 .name = "help",
77 .description = "Print this help and exit",
78 .cli = .empty,
79 .run = undefined, // special cased
1580 }),
16 .positionals = .single,
1781};
1882
19pub fn main() !u8 {
83const Context = struct {
84 gpa: Allocator,
85 cwd: []const u8,
86 /// argv[0] ++ " " ++ subcommand.name
87 program_name: []const u8,
88
89 /// Type-erasure is needed so `top_level_commands` can hold a non-generic
90 /// function, as it is not possible to have it's parameter type depend on a
91 /// sibling field. Retrieve its concrete type via `.options()`.
92 raw_options: *align(8) const anyopaque,
93 /// This cannot be `SubCommand` directly as it would cause a dependency loop.
94 safety_check: if (core.runtime_safety) u8 else void,
95
96 pub fn options(ctx: Context, comptime id: CommandId) ParsedOptions(id) {
97 assert(@intFromEnum(id) == ctx.safety_check); // incorrect memory access
98 return @as(*const ParsedOptions(id), @ptrCast(ctx.raw_options)).*;
99 }
100
101 pub fn showHelp(ctx: Context, comptime id: CommandId) void {
102 getSubCommand(id).cli.showHelp(ctx.program_name);
103 }
104};
105
106const CommandId = @Type(.{ .@"enum" = .{
107 .tag_type = u8,
108 .fields = fields: {
109 var fields: [top_level_commands.len]std.builtin.Type.EnumField = undefined;
110 var i: usize = 0;
111 for (top_level_commands) |tlc| switch (tlc) {
112 .separator => continue,
113 .command => |cmd| if (cmd.enabled) {
114 fields[i] = .{
115 .name = cmd.name,
116 .value = i,
117 };
118 i += 1;
119 },
120 };
121 break :fields &(fields[0..i].*);
122 },
123 .decls = &.{},
124 .is_exhaustive = true,
125} });
126
127fn getSubCommand(subcommand: CommandId) Command {
128 return for (top_level_commands) |tlc| switch (tlc) {
129 .separator => continue,
130 .command => |cmd| if (std.mem.eql(u8, cmd.name, @tagName(subcommand)))
131 break cmd,
132 } else unreachable;
133}
134
135fn ParsedOptions(subcommand: CommandId) type {
136 return getSubCommand(subcommand).cli.Parsed();
137}
138
139pub fn main() !void {
20140 try tracer.init();
21141 defer tracer.deinit();
22142 try tracer.init_thread(null);
23143 defer tracer.deinit_thread();
24144
25 // unicode props is asserted to be initialized before core.Tokenizer is used
26 // this will result in a static page allocation, which is never cleaned up.
145 // 'unicode_props' is asserted to be initialized when core.Tokenizer is used.
27146 try core.unicode_props.init();
147 defer core.unicode_props.deinit();
28148
149 // General Purpose Allocator provides great debugging assertions, but
150 // in Zig 0.14 it is unsuitible for release builds. To get reasonable
151 // performance, an arena over the page allocator is used.
152 // TODO: investigate other strategies, such as mi-malloc
29153 const use_gpa = builtin.mode == .Debug;
30154 var gpa = if (use_gpa)
31155 std.heap.GeneralPurposeAllocator(.{
......@@ -38,18 +162,118 @@ pub fn main() !u8 {
38162
39163 // Current working directory is required for CLI resolution.
40164 var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;
41 const cwd = try std.posix.getcwd(&cwd_buf);
165 const cwd = std.posix.getcwd(&cwd_buf) catch |err| {
166 _ = @errorReturnTrace(); // TODO
167 Cli.fatal("Failed to query current working directory: {s}", .{@errorName(err)});
168 };
42169
43170 // Command line argument parsing.
44 const program_name = "zig build compiler --";
45 _, const args = try Cli.programArgsAlloc(alloc, program_name);
171 const program_base_name, const args = try Cli.programArgsAlloc(alloc, default_program_name);
46172 defer Cli.programArgsFree(alloc, args);
47 const opts = arg_config.parse(program_name, args);
173
174 var joined_program_name: [512]u8 = undefined;
175 @memcpy(joined_program_name[0..program_base_name.len], program_base_name);
176
177 const stderr = OutStream.getStderr();
178
179 if (args.len == 0) {
180 try showTopLevelHelp(stderr, program_base_name);
181 Cli.fatal("expected command argument", .{});
182 }
183
184 const command = std.meta.stringToEnum(CommandId, args[0]) orelse {
185 try showTopLevelHelp(stderr, program_base_name);
186 if (std.mem.eql(u8, args[0], "--help")) {
187 std.process.cleanExit();
188 return;
189 }
190
191 inline for (top_level_commands) |tlc| {
192 if (tlc == .command and !tlc.command.enabled) {
193 if (std.mem.eql(u8, args[0], tlc.command.name)) {
194 Cli.fatal("this build configuration does not include the {s} command", .{
195 tlc.command.name,
196 });
197 }
198 }
199 }
200
201 Cli.fatal("unknown command '{s}'", .{args[0]});
202 };
203
204 switch (command) {
205 // Help and version ignore all passed arguments
206 .help => {
207 try showTopLevelHelp(stderr, program_base_name);
208 std.process.cleanExit();
209 },
210 .version => {
211 const stdout = std.io.getStdOut();
212 try stdout.writeAll(config.version_string ++ "\n");
213 std.process.cleanExit();
214 },
215 inline else => |cmd| {
216 const def = comptime getSubCommand(cmd);
217 joined_program_name[program_base_name.len] = ' ';
218 @memcpy(
219 joined_program_name[program_base_name.len + 1 ..][0..def.name.len],
220 def.name,
221 );
222 const name = joined_program_name[0 .. program_base_name.len + def.name.len + 1];
223 const parsed = def.cli.parse(name, args[1..]);
224 try def.run(.{
225 .cwd = cwd,
226 .gpa = alloc,
227 .raw_options = &parsed,
228 .safety_check = @intFromEnum(cmd),
229 .program_name = name,
230 });
231 },
232 }
233
234 std.process.cleanExit();
235}
236
237fn showTopLevelHelp(out: OutStream, program_name: []const u8) !void {
238 try out.setColor(.bold);
239 try out.print("usage", .{});
240 try out.setColor(.reset);
241 try out.print(": {s} [command] [options...]\n\n", .{program_name});
242 try out.setColor(.bold);
243 try out.print("commands", .{});
244 try out.setColor(.reset);
245 try out.writer.writeAll(comptime rest: {
246 var name_width: usize = 0;
247 for (top_level_commands) |tlc| switch (tlc) {
248 .separator => continue,
249 .command => |cmd| if (cmd.enabled) {
250 name_width = @max(cmd.name.len, name_width);
251 },
252 };
253 name_width += 4;
254 var rest: []const u8 = ":\n\n";
255 for (top_level_commands) |tlc| switch (tlc) {
256 .separator => rest = rest ++ "\n",
257 .command => |cmd| if (cmd.enabled) {
258 rest = rest ++ " " ++
259 cmd.name ++ (" " ** (name_width - cmd.name.len)) ++
260 cmd.description ++ "\n";
261 },
262 };
263 break :rest rest ++ "\n";
264 });
265}
266
267fn cmdBuildOrRun(ctx: Context, comptime mode: enum { build, run }) !void {
268 const opts = ctx.options(@field(CommandId, @tagName(mode)));
269 const alloc = ctx.gpa;
48270
49271 const file_path = try std.fs.path.resolve(alloc, &.{
50 cwd,
51 opts.positionals orelse
52 arg_config.showHelpAndExit(program_name),
272 ctx.cwd,
273 opts.positionals orelse {
274 ctx.showHelp(@field(CommandId, @tagName(mode)));
275 Cli.fatal("missing entry file argument", .{});
276 },
53277 });
54278 defer alloc.free(file_path);
55279
......@@ -80,13 +304,19 @@ pub fn main() !u8 {
80304 std.debug.print("Transform failed:\n", .{});
81305 try fail_report.dump(.initComp(&comp, main_file), comp.out);
82306 fail_report.deinit(comp.gpa);
83 return 1;
307 std.process.exit(1);
84308 },
85309 error.ParseFail => {
86310 // Already printed.
87 return 1;
311 std.process.exit(1);
312 },
313 else => |e| {
314 _ = @errorReturnTrace();
315 Cli.fatal("failed to load {s}: {s}", .{
316 file_path,
317 @errorName(e),
318 });
88319 },
89 else => return err,
90320 };
91321
92322 if (comp.options.dump_ir) {
......@@ -98,33 +328,56 @@ pub fn main() !u8 {
98328 comp.runAllJobs() catch |err| switch (err) {
99329 error.AnalysisFail => {
100330 // error is already printed
101 return 1;
331 std.process.exit(1);
102332 },
103333 else => return err,
104334 };
105335
106 if (config.have_embedded_quickjs and (opts.run orelse false)) {
107 // Execute with QuickJS
108 var out: std.ArrayListUnmanaged(u8) = .{};
109 defer out.deinit(alloc);
110 try js_backend.link(&comp, entry_point, out.writer(alloc).any());
111 try out.append(alloc, 0);
112 try runWithQuickJs(out.items[0 .. out.items.len - 1 :0]);
113 } else {
114 // Output to standard out
115 const output = std.io.getStdOut().writer();
116 var bufw = std.io.bufferedWriter(output);
117 const writer = bufw.writer();
118 try js_backend.link(&comp, entry_point, writer.any());
119 try bufw.flush();
336 switch (mode) {
337 .run => {
338 // Execute with QuickJS
339 var out: std.ArrayListUnmanaged(u8) = .{};
340 defer out.deinit(alloc);
341 try js_backend.link(&comp, entry_point, out.writer(alloc).any());
342 try out.append(alloc, 0);
343 try runWithQuickJs(out.items[0 .. out.items.len - 1 :0]);
344 },
345 .build => {
346 // Output to standard out
347 const output = std.io.getStdOut().writer();
348 var bufw = std.io.bufferedWriter(output);
349 const writer = bufw.writer();
350 try js_backend.link(&comp, entry_point, writer.any());
351 try bufw.flush();
352 },
120353 }
121354 }
122355
123356 std.process.cleanExit();
124 return 0;
125357}
126358
359fn cmdBuild(ctx: Context) !void {
360 try cmdBuildOrRun(ctx, .build);
361}
362
363fn cmdRun(ctx: Context) !void {
364 try cmdBuildOrRun(ctx, .run);
365}
366
367// fn cmdFmt(ctx: Context) !void {
368// const opts = ctx.options(.fmt);
369// _ = opts; // autofix
370// }
371
372const arg_config: Cli = .{
373 .options = .{} ++ Cli.Option.optional(config.have_embedded_quickjs, &.{
374 .{ .name = "--run", .desc = "Execute with QuickJS" },
375 }),
376 .positionals = .single,
377};
378
127379fn runWithQuickJs(js: [:0]const u8) !void {
380 comptime assert(config.have_embedded_quickjs);
128381 const qjs = @import("quickjs");
129382
130383 const runtime = qjs.JS_NewRuntime() orelse
......@@ -159,20 +412,24 @@ fn runWithQuickJs(js: [:0]const u8) !void {
159412
160413 const slice = std.mem.span(exception_str);
161414
162 std.debug.print("Exception: {s}\n", .{slice});
163
164 std.process.exit(1);
415 Cli.fatal("{s}", .{slice});
165416 }
166417}
167418
419const config = @import("config");
420
421const std = @import("std");
422const assert = std.debug.assert;
423const Allocator = std.mem.Allocator;
424
168425const core = @import("core");
426const OutStream = core.OutStream;
169427const Comp = core.Comp;
170428const Report = core.Report;
171429
172430const tracer = @import("tracer");
173431pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode));
174432
175const std = @import("std");
176433const builtin = @import("builtin");
177434const Cli = @import("Cli.zig");
178435