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 @@...@@ -1,15 +1,16 @@
1## 0.1.61## 0.1.6
22
3- breaking: rename `@js` to `@extern`, considering future backends3- breaking: rename `@js` to `@extern`, considering future backends
4- breaking: rename `noreturn` to `never`
4- re-write most of the semantic analysis pass (#79)5- re-write most of the semantic analysis pass (#79)
5 - feat: implement function expressions6 - feat: implement function expressions
6 - feat: implement simple `comptime` evaluation7 - feat: implement simple `comptime` evaluation
7 - feat: change all semantic analysis panics into handled errors8 - feat: change all semantic analysis panics into handled errors
8 - fix: no longer analyzes contents past a block end9 - fix: no longer analyzes contents past a block end
9- feat: add `--run` to execute the file in QuickJS instead of outputting source10- feat: introduce cli subcommands
11- feat: add `run` subcommand to execute the file in QuickJS instead of outputting source
10- feat: add array index syntax via `x[1]` and `x.1`12- feat: add array index syntax via `x[1]` and `x.1`
11- feat: add `len` property to arrays13- feat: add `len` property to arrays
12- feat: rename `noreturn` to `never`
13- feat: allow instantiating `@extern` at compile time14- feat: allow instantiating `@extern` at compile time
14- feat: implement tuples15- feat: implement tuples
15- feat: implement multiline string literals16- feat: implement multiline string literals
build.zig+1
...@@ -400,6 +400,7 @@ pub fn build(b: *Build) !void {...@@ -400,6 +400,7 @@ pub fn build(b: *Build) !void {
400 b.installArtifact(main.cli);400 b.installArtifact(main.cli);
401401
402 inline for (.{402 inline for (.{
403 .{ main.cli, "run" },
403 .{ minimal.compiler_cli, "compiler" },404 .{ minimal.compiler_cli, "compiler" },
404 .{ minimal.format_cli, "format" },405 .{ minimal.format_cli, "format" },
405 .{ minimal.loader_cli, "loader" },406 .{ minimal.loader_cli, "loader" },
src/Cli.zig+53-31
...@@ -3,11 +3,18 @@...@@ -3,11 +3,18 @@
3//! allocator, however it expects incoming arguments to be UTF-8 formatted which3//! allocator, however it expects incoming arguments to be UTF-8 formatted which
4//! on Windows will require an allocator.4//! on Windows will require an allocator.
5//!5//!
6//! See `fn main` for a basic usage example.6//! See `fn main` for a basic usage example. Parsing subcommands is done in
7options: []const Option = &.{},7//! `main.zig`, utilizing a different `Cli` for each option set.
8positionals: Positionals = .none,8options: []const Option,
9positionals: Positionals,
9help: HelpConfig = .{},10help: HelpConfig = .{},
1011
12pub const empty: Cli = .{
13 .options = &.{},
14 .positionals = .none,
15 .help = .{ .option = false },
16};
17
11pub const Option = struct {18pub const Option = struct {
12 /// Should be formatted as the flag name: '--dump-ir'19 /// Should be formatted as the flag name: '--dump-ir'
13 name: []const u8,20 name: []const u8,
...@@ -262,14 +269,15 @@ pub fn parse(...@@ -262,14 +269,15 @@ pub fn parse(
262 stderr.get(),269 stderr.get(),
263 stderr.tty,270 stderr.tty,
264 true,271 true,
265 true,
266 ) catch |e| switch (e) {272 ) catch |e| switch (e) {
267 error.InvalidArguments,273 error.InvalidArguments => {
268 error.DisplayedHelpMenu,
269 => {
270 stderr.buffered_writer.flush() catch {};274 stderr.buffered_writer.flush() catch {};
271 std.process.exit(1);275 std.process.exit(1);
272 },276 },
277 error.DisplayedHelpMenu => {
278 stderr.buffered_writer.flush() catch {};
279 std.process.exit(0);
280 },
273 };281 };
274}282}
275283
...@@ -279,9 +287,9 @@ pub fn parseWithErrorWriter(...@@ -279,9 +287,9 @@ pub fn parseWithErrorWriter(
279 args: []const []const u8,287 args: []const []const u8,
280 err: std.io.AnyWriter,288 err: std.io.AnyWriter,
281 tty: std.io.tty.Config,289 tty: std.io.tty.Config,
282 comptime show_help_on_error: bool,
283 comptime exit_on_error: bool,290 comptime exit_on_error: bool,
284) ParseError!p.Parsed() {291) ParseError!p.Parsed() {
292 _ = exit_on_error; // autofix
285 var out: p.Parsed() = .{};293 var out: p.Parsed() = .{};
286 var had_errors = false;294 var had_errors = false;
287 var positionals_counted: usize = 0;295 var positionals_counted: usize = 0;
...@@ -312,7 +320,7 @@ pub fn parseWithErrorWriter(...@@ -312,7 +320,7 @@ pub fn parseWithErrorWriter(
312 positionals_counted += remain.len;320 positionals_counted += remain.len;
313321
314 if (positionals_counted > max_positionals) {322 if (positionals_counted > max_positionals) {
315 had_errors = true;323 p.markFirstError(&had_errors, program_name, err, tty);
316 const diff = @min(remain.len, positionals_counted - max_positionals);324 const diff = @min(remain.len, positionals_counted - max_positionals);
317 for (remain[remain.len - diff ..]) |pos| {325 for (remain[remain.len - diff ..]) |pos| {
318 writeError(err, tty);326 writeError(err, tty);
...@@ -333,7 +341,7 @@ pub fn parseWithErrorWriter(...@@ -333,7 +341,7 @@ pub fn parseWithErrorWriter(
333 break :try_this_def;341 break :try_this_def;
334342
335 if (kind != .boolean and is_blank) {343 if (kind != .boolean and is_blank) {
336 had_errors = true;344 p.markFirstError(&had_errors, program_name, err, tty);
337 writeError(err, tty);345 writeError(err, tty);
338 err.print(prefix ++ " requires a value\n", .{}) catch {};346 err.print(prefix ++ " requires a value\n", .{}) catch {};
339 continue :outer;347 continue :outer;
...@@ -348,7 +356,7 @@ pub fn parseWithErrorWriter(...@@ -348,7 +356,7 @@ pub fn parseWithErrorWriter(
348 } else if (mem.eql(u8, value, "true")) {356 } else if (mem.eql(u8, value, "true")) {
349 dest.* = true;357 dest.* = true;
350 } else {358 } else {
351 had_errors = true;359 p.markFirstError(&had_errors, program_name, err, tty);
352 writeError(err, tty);360 writeError(err, tty);
353 err.print(prefix ++ " only accepts true or false, got '{s}'\n", .{361 err.print(prefix ++ " only accepts true or false, got '{s}'\n", .{
354 arg[prefix.len + 1 ..],362 arg[prefix.len + 1 ..],
...@@ -358,14 +366,14 @@ pub fn parseWithErrorWriter(...@@ -358,14 +366,14 @@ pub fn parseWithErrorWriter(
358 .string => if (std.unicode.utf8ValidateSlice(value)) {366 .string => if (std.unicode.utf8ValidateSlice(value)) {
359 dest.* = value;367 dest.* = value;
360 } else {368 } else {
361 had_errors = true;369 p.markFirstError(&had_errors, program_name, err, tty);
362 writeError(err, tty);370 writeError(err, tty);
363 err.print(prefix ++ " requires valid UTF-8 input\n", .{}) catch {};371 err.print(prefix ++ " requires valid UTF-8 input\n", .{}) catch {};
364 },372 },
365373
366 .@"enum" => |E| {374 .@"enum" => |E| {
367 dest.* = std.meta.stringToEnum(E, value) orelse {375 dest.* = std.meta.stringToEnum(E, value) orelse {
368 had_errors = true;376 p.markFirstError(&had_errors, program_name, err, tty);
369 const values_list = comptime brk: {377 const values_list = comptime brk: {
370 var str: []const u8 = "";378 var str: []const u8 = "";
371 for (std.enums.values(E)) |val| {379 for (std.enums.values(E)) |val| {
...@@ -390,14 +398,14 @@ pub fn parseWithErrorWriter(...@@ -390,14 +398,14 @@ pub fn parseWithErrorWriter(
390 if (mem.startsWith(u8, arg[2..], "help") and (arg.len == "--help".len or arg["--help".len] == '=')) {398 if (mem.startsWith(u8, arg[2..], "help") and (arg.len == "--help".len or arg["--help".len] == '=')) {
391 show_help = true;399 show_help = true;
392 if (arg.len > "--help".len) {400 if (arg.len > "--help".len) {
393 had_errors = true;401 p.markFirstError(&had_errors, program_name, err, tty);
394 err.print("--help does not take a value\n", .{}) catch {};402 err.print("--help does not take a value\n", .{}) catch {};
395 }403 }
396 continue :outer;404 continue :outer;
397 }405 }
398 }406 }
399407
400 had_errors = true;408 p.markFirstError(&had_errors, program_name, err, tty);
401 writeError(err, tty);409 writeError(err, tty);
402 err.print("unrecognized argument '{s}'\n", .{arg}) catch {};410 err.print("unrecognized argument '{s}'\n", .{arg}) catch {};
403 } else if (arg.len > 1 and arg[0] == '-') {411 } else if (arg.len > 1 and arg[0] == '-') {
...@@ -421,7 +429,7 @@ pub fn parseWithErrorWriter(...@@ -421,7 +429,7 @@ pub fn parseWithErrorWriter(
421 if (positionals_counted == 0) first_positional = i;429 if (positionals_counted == 0) first_positional = i;
422 positionals_counted += 1;430 positionals_counted += 1;
423 if (positionals_counted > max_positionals) {431 if (positionals_counted > max_positionals) {
424 had_errors = true;432 p.markFirstError(&had_errors, program_name, err, tty);
425 writeError(err, tty);433 writeError(err, tty);
426 err.print("unrecognized argument '{s}'\n", .{arg}) catch {};434 err.print("unrecognized argument '{s}'\n", .{arg}) catch {};
427 } else if (p.positionals == .bounded) {435 } else if (p.positionals == .bounded) {
...@@ -435,19 +443,16 @@ pub fn parseWithErrorWriter(...@@ -435,19 +443,16 @@ pub fn parseWithErrorWriter(
435 writeNote(err, tty);443 writeNote(err, tty);
436 err.writeAll(switch (max_positionals) {444 err.writeAll(switch (max_positionals) {
437 0 => "this program does not take positional arguments\n",445 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",
439 else => |n| std.fmt.comptimePrint(447 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",
441 .{n},449 .{n},
442 ),450 ),
443 }) catch {};451 }) catch {};
444 }452 }
445 if (show_help or show_help_on_error) {453 if (show_help) {
446 p.showHelpWithWriter(program_name, err, tty);454 p.showHelpWithWriter(program_name, err, tty);
447 }455 }
448 if (exit_on_error) {
449 return error.DisplayedHelpMenu;
450 }
451 return error.InvalidArguments;456 return error.InvalidArguments;
452 }457 }
453458
...@@ -476,6 +481,18 @@ pub fn parseWithErrorWriter(...@@ -476,6 +481,18 @@ pub fn parseWithErrorWriter(
476 return out;481 return out;
477}482}
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
479pub fn showHelp(comptime p: Cli, program_name: []const u8) void {496pub fn showHelp(comptime p: Cli, program_name: []const u8) void {
480 var stderr = StdErrWriter.init();497 var stderr = StdErrWriter.init();
481 defer stderr.buffered_writer.flush() catch {};498 defer stderr.buffered_writer.flush() catch {};
...@@ -502,14 +519,18 @@ pub fn showHelpWithWriter(...@@ -502,14 +519,18 @@ pub fn showHelpWithWriter(
502 };519 };
503 };520 };
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
507 if (p.options.len > 0 or p.help.option) {527 if (p.options.len > 0 or p.help.option) {
508 w.writeAll("\n") catch return;528 w.writeAll("\n") catch return;
509 tty.setColor(w, .bold) catch return;529 tty.setColor(w, .bold) catch return;
510 w.writeAll("options:") catch return;530 w.writeAll("options") catch return;
511 tty.setColor(w, .reset) catch return;531 tty.setColor(w, .reset) catch return;
512 w.writeAll(comptime brk: {532 w.writeAll(comptime brk: {
533 var out: []const u8 = ":\n\n";
513 var width = 0;534 var width = 0;
514 var any_short_options = false;535 var any_short_options = false;
515536
...@@ -526,7 +547,6 @@ pub fn showHelpWithWriter(...@@ -526,7 +547,6 @@ pub fn showHelpWithWriter(
526 if (any_short_options) width += "-h ".len;547 if (any_short_options) width += "-h ".len;
527 const sub = if (any_short_options) 4 else 1;548 const sub = if (any_short_options) 4 else 1;
528549
529 var out: []const u8 = "\n";
530 for (all_opts) |opt| {550 for (all_opts) |opt| {
531 out = out ++ " ";551 out = out ++ " ";
532 if (any_short_options) {552 if (any_short_options) {
...@@ -605,12 +625,14 @@ pub fn fatalWithHelp(p: Cli, program_name: []const u8, comptime fmt: []const u8,...@@ -605,12 +625,14 @@ pub fn fatalWithHelp(p: Cli, program_name: []const u8, comptime fmt: []const u8,
605}625}
606626
607pub fn fatal(comptime fmt: []const u8, args: anytype) noreturn {627pub fn fatal(comptime fmt: []const u8, args: anytype) noreturn {
608 const stderr = std.io.getStdErr();628 print: {
609 const tty = std.io.tty.detectConfig(stderr);629 const stderr = std.io.getStdErr();
610 var bufw = std.io.bufferedWriter(stderr.writer());630 const tty = std.io.tty.detectConfig(stderr);
611 writeError(bufw.writer().any(), tty);631 var bufw = std.io.bufferedWriter(stderr.writer());
612 bufw.writer().print(fmt ++ "\n", args);632 writeError(bufw.writer().any(), tty);
613 bufw.flush() catch {}; // ignoring I/O errors633 bufw.writer().print(fmt ++ "\n", args) catch break :print;
634 bufw.flush() catch break :print;
635 }
614 std.process.exit(1);636 std.process.exit(1);
615}637}
616638
src/main.zig+303-46
...@@ -1,31 +1,155 @@...@@ -1,31 +1,155 @@
1//! This is the `lang` command line interface. It combines all components1//! This is the user-facing command line, combining all features into
2//! of the software development kit into one. Multiple versions of this2//! different subcommands, each with their own option set.
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.
63
7const config = @import("config");4const default_program_name = "wlang"; // fallback when argv[0] is missing
85
9const arg_config: Cli = .{6const Command = struct {
10 .options = .{7 name: [:0]const u8,
11 .{ .short = 'i', .name = "--dump-ir", .desc = "Dump intermediate representations" },8 description: []const u8,
12 .{ .name = "--dump-state", .desc = "Dump compiler state to JSON", .kind = .string },9 cli: Cli,
13 } ++ Cli.Option.optional(config.have_embedded_quickjs, &.{10 run: fn (Context) anyerror!void,
14 .{ .name = "--run", .desc = "Execute with QuickJS" },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
15 }),80 }),
16 .positionals = .single,
17};81};
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 {
20 try tracer.init();140 try tracer.init();
21 defer tracer.deinit();141 defer tracer.deinit();
22 try tracer.init_thread(null);142 try tracer.init_thread(null);
23 defer tracer.deinit_thread();143 defer tracer.deinit_thread();
24144
25 // unicode props is asserted to be initialized before core.Tokenizer is used145 // 'unicode_props' is asserted to be initialized when core.Tokenizer is used.
26 // this will result in a static page allocation, which is never cleaned up.
27 try core.unicode_props.init();146 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
29 const use_gpa = builtin.mode == .Debug;153 const use_gpa = builtin.mode == .Debug;
30 var gpa = if (use_gpa)154 var gpa = if (use_gpa)
31 std.heap.GeneralPurposeAllocator(.{155 std.heap.GeneralPurposeAllocator(.{
...@@ -38,18 +162,118 @@ pub fn main() !u8 {...@@ -38,18 +162,118 @@ pub fn main() !u8 {
38162
39 // Current working directory is required for CLI resolution.163 // Current working directory is required for CLI resolution.
40 var cwd_buf: [std.fs.max_path_bytes]u8 = undefined;164 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
43 // Command line argument parsing.170 // Command line argument parsing.
44 const program_name = "zig build compiler --";171 const program_base_name, const args = try Cli.programArgsAlloc(alloc, default_program_name);
45 _, const args = try Cli.programArgsAlloc(alloc, program_name);
46 defer Cli.programArgsFree(alloc, args);172 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
49 const file_path = try std.fs.path.resolve(alloc, &.{271 const file_path = try std.fs.path.resolve(alloc, &.{
50 cwd,272 ctx.cwd,
51 opts.positionals orelse273 opts.positionals orelse {
52 arg_config.showHelpAndExit(program_name),274 ctx.showHelp(@field(CommandId, @tagName(mode)));
275 Cli.fatal("missing entry file argument", .{});
276 },
53 });277 });
54 defer alloc.free(file_path);278 defer alloc.free(file_path);
55279
...@@ -80,13 +304,19 @@ pub fn main() !u8 {...@@ -80,13 +304,19 @@ pub fn main() !u8 {
80 std.debug.print("Transform failed:\n", .{});304 std.debug.print("Transform failed:\n", .{});
81 try fail_report.dump(.initComp(&comp, main_file), comp.out);305 try fail_report.dump(.initComp(&comp, main_file), comp.out);
82 fail_report.deinit(comp.gpa);306 fail_report.deinit(comp.gpa);
83 return 1;307 std.process.exit(1);
84 },308 },
85 error.ParseFail => {309 error.ParseFail => {
86 // Already printed.310 // 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 });
88 },319 },
89 else => return err,
90 };320 };
91321
92 if (comp.options.dump_ir) {322 if (comp.options.dump_ir) {
...@@ -98,33 +328,56 @@ pub fn main() !u8 {...@@ -98,33 +328,56 @@ pub fn main() !u8 {
98 comp.runAllJobs() catch |err| switch (err) {328 comp.runAllJobs() catch |err| switch (err) {
99 error.AnalysisFail => {329 error.AnalysisFail => {
100 // error is already printed330 // error is already printed
101 return 1;331 std.process.exit(1);
102 },332 },
103 else => return err,333 else => return err,
104 };334 };
105335
106 if (config.have_embedded_quickjs and (opts.run orelse false)) {336 switch (mode) {
107 // Execute with QuickJS337 .run => {
108 var out: std.ArrayListUnmanaged(u8) = .{};338 // Execute with QuickJS
109 defer out.deinit(alloc);339 var out: std.ArrayListUnmanaged(u8) = .{};
110 try js_backend.link(&comp, entry_point, out.writer(alloc).any());340 defer out.deinit(alloc);
111 try out.append(alloc, 0);341 try js_backend.link(&comp, entry_point, out.writer(alloc).any());
112 try runWithQuickJs(out.items[0 .. out.items.len - 1 :0]);342 try out.append(alloc, 0);
113 } else {343 try runWithQuickJs(out.items[0 .. out.items.len - 1 :0]);
114 // Output to standard out344 },
115 const output = std.io.getStdOut().writer();345 .build => {
116 var bufw = std.io.bufferedWriter(output);346 // Output to standard out
117 const writer = bufw.writer();347 const output = std.io.getStdOut().writer();
118 try js_backend.link(&comp, entry_point, writer.any());348 var bufw = std.io.bufferedWriter(output);
119 try bufw.flush();349 const writer = bufw.writer();
350 try js_backend.link(&comp, entry_point, writer.any());
351 try bufw.flush();
352 },
120 }353 }
121 }354 }
122355
123 std.process.cleanExit();356 std.process.cleanExit();
124 return 0;
125}357}
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
127fn runWithQuickJs(js: [:0]const u8) !void {379fn runWithQuickJs(js: [:0]const u8) !void {
380 comptime assert(config.have_embedded_quickjs);
128 const qjs = @import("quickjs");381 const qjs = @import("quickjs");
129382
130 const runtime = qjs.JS_NewRuntime() orelse383 const runtime = qjs.JS_NewRuntime() orelse
...@@ -159,20 +412,24 @@ fn runWithQuickJs(js: [:0]const u8) !void {...@@ -159,20 +412,24 @@ fn runWithQuickJs(js: [:0]const u8) !void {
159412
160 const slice = std.mem.span(exception_str);413 const slice = std.mem.span(exception_str);
161414
162 std.debug.print("Exception: {s}\n", .{slice});415 Cli.fatal("{s}", .{slice});
163
164 std.process.exit(1);
165 }416 }
166}417}
167418
419const config = @import("config");
420
421const std = @import("std");
422const assert = std.debug.assert;
423const Allocator = std.mem.Allocator;
424
168const core = @import("core");425const core = @import("core");
426const OutStream = core.OutStream;
169const Comp = core.Comp;427const Comp = core.Comp;
170const Report = core.Report;428const Report = core.Report;
171429
172const tracer = @import("tracer");430const tracer = @import("tracer");
173pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode));431pub const tracer_impl = @field(tracer, @tagName(@import("config").tracing_mode));
174432
175const std = @import("std");
176const builtin = @import("builtin");433const builtin = @import("builtin");
177const Cli = @import("Cli.zig");434const Cli = @import("Cli.zig");
178435