authorchloe caruso <git@paperclover.net> 2024-05-22 01:23:06-07:00
committerchloe caruso <git@paperclover.net> 2024-05-22 01:23:06-07:00
log2ff59a3d2386e34541dcf0b1404ed591f6bc1051
treeb4e843e8e58dbed96158fb445cd5056eba0e5262
parentfe8d15c9fae6526e6cad4fbaaa52e42d3b9ebd08

some bugfixes


13 files changed, 537 insertions(+), 240 deletions(-)

CHANGELOG.md+7
...@@ -1,3 +1,10 @@...@@ -1,3 +1,10 @@
1## 0.1.2
2
3- feat: parsing of assignments within expressions is disallowed now
4- feat: debug builds have `--report-trace` to print stack traces
5- fix: unused warnings now properly work
6- fix: empty files and functions no longer crash
7
1## 0.1.18## 0.1.1
29
3- feat: `var` declarations / runtime memory10- feat: `var` declarations / runtime memory
build.zig+19-6
...@@ -1,4 +1,7 @@...@@ -1,4 +1,7 @@
1const std = @import("std");1const std = @import("std");
2const Build = std.Build;
3const Target = std.Target;
4const OsVersion = Target.Query.OsVersion;
25
3pub fn build(b: *std.Build) void {6pub fn build(b: *std.Build) void {
4 b.reference_trace = 20;7 b.reference_trace = 20;
...@@ -28,6 +31,7 @@ pub fn build(b: *std.Build) void {...@@ -28,6 +31,7 @@ pub fn build(b: *std.Build) void {
28 .root_source_file = b.path("src/compiler/main.zig"),31 .root_source_file = b.path("src/compiler/main.zig"),
29 .target = target,32 .target = target,
30 .optimize = optimize,33 .optimize = optimize,
34 .link_libc = true,
31 });35 });
32 b.installArtifact(exe);36 b.installArtifact(exe);
3337
...@@ -67,13 +71,17 @@ pub fn build(b: *std.Build) void {...@@ -67,13 +71,17 @@ pub fn build(b: *std.Build) void {
67 // wasm_api.root_module.addImport("zg:PropsData", zg.module("PropsData"));71 // wasm_api.root_module.addImport("zg:PropsData", zg.module("PropsData"));
6872
69 const release = b.step("release", "Package releases");73 const release = b.step("release", "Package releases");
74 const linux_version: OsVersion = .{ .semver = .{ .major = 1, .minor = 0, .patch = 0 } };
75 const mac_version: OsVersion = .{ .semver = .{ .major = 11, .minor = 0, .patch = 0 } };
76 const win_version: OsVersion = .{ .windows = .win7 };
77
70 inline for (.{78 inline for (.{
71 b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl }),79 b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version }),
72 b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl }),80 b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version }),
73 b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .macos }),81 b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .macos, .os_version_min = mac_version }),
74 b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .macos }),82 b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .macos, .os_version_min = mac_version }),
75 b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows }),83 b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu }),
76 b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .windows }),84 b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu }),
77 }) |cross_target| {85 }) |cross_target| {
78 const name = std.fmt.allocPrint(86 const name = std.fmt.allocPrint(
79 b.allocator,87 b.allocator,
...@@ -84,19 +92,24 @@ pub fn build(b: *std.Build) void {...@@ -84,19 +92,24 @@ pub fn build(b: *std.Build) void {
84 },92 },
85 ) catch @panic("OOM");93 ) catch @panic("OOM");
8694
95 // std.log.info("{s}\n", .{cross_target.result.zigTriple(b.allocator) catch @panic("OOM")});
96
87 const cross_exe = b.addExecutable(.{97 const cross_exe = b.addExecutable(.{
88 .name = "compiler",98 .name = "compiler",
89 .root_source_file = b.path("src/compiler/main.zig"),99 .root_source_file = b.path("src/compiler/main.zig"),
90 .target = cross_target,100 .target = cross_target,
91 .optimize = .ReleaseSafe,101 .optimize = .ReleaseSafe,
92 .strip = true,102 .strip = true,
103 .link_libc = true,
93 });104 });
105 cross_exe.root_module.addImport("build_options", opts_mod);
94 cross_exe.root_module.addImport("zg:PropsData", zg.module("PropsData"));106 cross_exe.root_module.addImport("zg:PropsData", zg.module("PropsData"));
95 cross_exe.root_module.addImport("tracer", tracer.module("tracer"));107 cross_exe.root_module.addImport("tracer", tracer.module("tracer"));
96108
97 release.dependOn(&b.addInstallFile(109 release.dependOn(&b.addInstallFile(
98 cross_exe.getEmittedBin(),110 cross_exe.getEmittedBin(),
99 b.pathJoin(&.{111 b.pathJoin(&.{
112 "release",
100 name,113 name,
101 std.mem.concat(b.allocator, u8, &.{114 std.mem.concat(b.allocator, u8, &.{
102 "compiler",115 "compiler",
src/compiler/Ast.zig+1-76
...@@ -40,48 +40,6 @@ tokens: Token.List.Slice,...@@ -40,48 +40,6 @@ tokens: Token.List.Slice,
40nodes: Node.List.Slice,40nodes: Node.List.Slice,
41extra: []const u32,41extra: []const u32,
4242
43const Parse = @import("Parse.zig");
44pub fn parse(
45 gpa: std.mem.Allocator,
46 source: [:0]const u8,
47 ast_error: *Parse.AstError,
48) Parse.Error!Ast {
49 var err_byte: ByteOffset = 0;
50 var tokens = Tokenizer.tokenize(
51 source,
52 gpa,
53 &err_byte,
54 ) catch |err| switch (err) {
55 error.OutOfMemory => return error.OutOfMemory,
56 else => {
57 ast_error.* = .{ .tokenization = .{
58 .err = err,
59 .byte = err_byte,
60 } };
61 return error.ParseFail;
62 },
63 };
64 errdefer tokens.deinit(gpa);
65
66 var parser = Parse.init(gpa, source, tokens);
67 errdefer parser.deinit();
68
69 try parser.nodes.ensureUnusedCapacity(
70 gpa,
71 @max(1, @divFloor(tokens.len, 4)),
72 );
73
74 parser.parseRoot() catch |err| switch (err) {
75 error.OutOfMemory => return error.OutOfMemory,
76 error.ParseFail => {
77 ast_error.* = parser.ast_error.?;
78 return error.ParseFail;
79 },
80 };
81
82 return try parser.finalize(tokens);
83}
84
85pub const Node = struct {43pub const Node = struct {
86 tag: Tag,44 tag: Tag,
87 main_token: TokenIndex,45 main_token: TokenIndex,
...@@ -645,41 +603,8 @@ pub fn nodeStatements(ast: *const Ast, node: NodeIndex) []const NodeIndex {...@@ -645,41 +603,8 @@ pub fn nodeStatements(ast: *const Ast, node: NodeIndex) []const NodeIndex {
645}603}
646604
647pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenIndex) Location {605pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenIndex) Location {
648 var loc = Location{
649 .line = 1,
650 .column = 1,
651 .line_start = start_offset,
652 .line_end = self.utf8_source.len,
653 };
654 const token_start = self.tokens.items(.offset)[token_index];606 const token_start = self.tokens.items(.offset)[token_index];
655607 return Tokenizer.tokenLocation(self.utf8_source, start_offset, token_start);
656 // Scan to by line until we go past the token start
657 while (std.mem.indexOfScalarPos(u8, self.utf8_source, loc.line_start, '\n')) |i| {
658 if (i >= token_start) {
659 break; // Went past
660 }
661 loc.line += 1;
662 loc.line_start = i + 1;
663 }
664
665 const offset = loc.line_start;
666 for (self.utf8_source[offset..], 0..) |c, i| {
667 if (i + offset == token_start) {
668 loc.line_end = i + offset;
669 while (loc.line_end < self.utf8_source.len and self.utf8_source[loc.line_end] != '\n') {
670 loc.line_end += 1;
671 }
672 return loc;
673 }
674 if (c == '\n') {
675 loc.line += 1;
676 loc.column = 1;
677 loc.line_start = i + 1;
678 } else {
679 loc.column += 1;
680 }
681 }
682 return loc;
683}608}
684609
685pub fn tokenSlice(ast: Ast, token_index: TokenIndex) []const u8 {610pub fn tokenSlice(ast: Ast, token_index: TokenIndex) []const u8 {
src/compiler/Comp.zig+4-4
...@@ -129,16 +129,16 @@ pub const File = struct {...@@ -129,16 +129,16 @@ pub const File = struct {
129 return ast;129 return ast;
130 }130 }
131131
132 var ast_err: Parse.AstError = undefined;132 var err_data: Parse.AstError.WithTrace = undefined;
133 const ast = Ast.parse(133 const ast = Parse.parse(
134 comp.gpa,134 comp.gpa,
135 (try file.getSource(comp.gpa)).code,135 (try file.getSource(comp.gpa)).code,
136 &ast_err,136 &err_data,
137 ) catch |err| switch (err) {137 ) catch |err| switch (err) {
138 error.OutOfMemory => return err,138 error.OutOfMemory => return err,
139 error.ParseFail => {139 error.ParseFail => {
140 var report = try ast_err.toReport(comp, file);
141 // TODO: let this merge with other report140 // TODO: let this merge with other report
141 var report = try err_data.toReport(comp, file);
142 report.dumpToStdErr(comp) catch {};142 report.dumpToStdErr(comp) catch {};
143 report.deinit(comp.gpa);143 report.deinit(comp.gpa);
144 return err;144 return err;
src/compiler/JsLink.zig+6-2
...@@ -69,7 +69,7 @@ pub fn linkJsCode(...@@ -69,7 +69,7 @@ pub fn linkJsCode(
69 try link.decls.putNoClobber(comp.gpa, decl_index, @enumFromInt(gop.index));69 try link.decls.putNoClobber(comp.gpa, decl_index, @enumFromInt(gop.index));
70 }70 }
7171
72 try writer.print("// Language Compiler v0.1.1. Alpha quality software.\n", .{});72 try writer.print("// Language Compiler v0.1.2. Alpha quality software.\n", .{});
7373
74 // 2. Print every function decl74 // 2. Print every function decl
75 for (decls[1..]) |decl_index| {75 for (decls[1..]) |decl_index| {
...@@ -77,7 +77,11 @@ pub fn linkJsCode(...@@ -77,7 +77,11 @@ pub fn linkJsCode(
77 }77 }
7878
79 // 3. Print the top-level code79 // 3. Print the top-level code
80 try link.printDeclContents(&root_decl.codegen.?, writer);80 if (root_decl.codegen.?.nodes.len > 1) {
81 try link.printDeclContents(&root_decl.codegen.?, writer);
82 } else {
83 try writer.print("// (program has no observable effects, thus an empty file)\n", .{});
84 }
81}85}
8286
83fn deinit(link: *JsLink) void {87fn deinit(link: *JsLink) void {
src/compiler/JsNodeGen.zig+11-9
...@@ -107,16 +107,18 @@ pub fn build(c: *JsNodeGen) !void {...@@ -107,16 +107,18 @@ pub fn build(c: *JsNodeGen) !void {
107107
108 _ = try c.add(.root, undefined);108 _ = try c.add(.root, undefined);
109109
110 for (statements[0..c.param_count], 1..) |stmt, expected_id| {110 if (statements.len > 0) {
111 assert(@intFromEnum(stmt) == expected_id);111 for (statements[0..c.param_count], 1..) |stmt, expected_id| {
112 assert(c.air.instTag(stmt) == .arg);112 assert(@intFromEnum(stmt) == expected_id);
113 c.air_node_map[@intFromEnum(stmt)] = .{113 assert(c.air.instTag(stmt) == .arg);
114 .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }),114 c.air_node_map[@intFromEnum(stmt)] = .{
115 };115 .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }),
116 }116 };
117 }
117118
118 for (statements[c.param_count..]) |stmt| {119 for (statements[c.param_count..]) |stmt| {
119 try c.codeGenInst(stmt);120 try c.codeGenInst(stmt);
121 }
120 }122 }
121123
122 const items = c.scratch.items[scratch_top..];124 const items = c.scratch.items[scratch_top..];
src/compiler/Parse.zig+150-42
...@@ -10,9 +10,49 @@ nodes: Node.List = .{},...@@ -10,9 +10,49 @@ nodes: Node.List = .{},
10extra: ArrayListUnmanaged(u32) = .{},10extra: ArrayListUnmanaged(u32) = .{},
11scratch: ArrayListUnmanaged(NodeIndex) = .{},11scratch: ArrayListUnmanaged(NodeIndex) = .{},
1212
13ast_error: ?AstError = null,13ast_error: ?AstError.WithTrace = null,
14
15pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8, ast_error: *AstError.WithTrace) Parse.Error!Ast {
16 var err_byte: ByteOffset = 0;
17 var tokens = Tokenizer.tokenize(
18 source,
19 gpa,
20 &err_byte,
21 ) catch |err| switch (err) {
22 error.OutOfMemory => return error.OutOfMemory,
23 else => {
24 ast_error.* = .{
25 .err = .{ .tokenization = .{
26 .err = err,
27 .byte = err_byte,
28 } },
29 .trace = Report.DebugStoredTrace.from(@errorReturnTrace()),
30 };
31 return error.ParseFail;
32 },
33 };
34 errdefer tokens.deinit(gpa);
35
36 var parser = Parse.init(gpa, source, tokens);
37 errdefer parser.deinit();
38
39 try parser.nodes.ensureUnusedCapacity(
40 gpa,
41 @max(1, @divFloor(tokens.len, 4)),
42 );
43
44 parser.parseRoot() catch |err| switch (err) {
45 error.OutOfMemory => return error.OutOfMemory,
46 error.ParseFail => {
47 ast_error.* = parser.ast_error.?;
48 return error.ParseFail;
49 },
50 };
51
52 return try parser.finalize(tokens);
53}
1454
15pub fn init(gpa: Allocator, source: [:0]const u8, tokens: Token.List.Slice) Parse {55fn init(gpa: Allocator, source: [:0]const u8, tokens: Token.List.Slice) Parse {
16 return Parse{56 return Parse{
17 .gpa = gpa,57 .gpa = gpa,
18 .source = source,58 .source = source,
...@@ -22,7 +62,7 @@ pub fn init(gpa: Allocator, source: [:0]const u8, tokens: Token.List.Slice) Pars...@@ -22,7 +62,7 @@ pub fn init(gpa: Allocator, source: [:0]const u8, tokens: Token.List.Slice) Pars
22}62}
2363
24/// This transfers the parse result into an Ast64/// This transfers the parse result into an Ast
25pub fn finalize(p: *Parse, token_slice: Token.List.Slice) !Ast {65fn finalize(p: *Parse, token_slice: Token.List.Slice) !Ast {
26 const extra = try p.extra.toOwnedSlice(p.gpa);66 const extra = try p.extra.toOwnedSlice(p.gpa);
27 defer p.* = undefined;67 defer p.* = undefined;
28 assert(token_slice.items(.tag).ptr == p.token_tags.ptr);68 assert(token_slice.items(.tag).ptr == p.token_tags.ptr);
...@@ -40,7 +80,7 @@ pub fn finalize(p: *Parse, token_slice: Token.List.Slice) !Ast {...@@ -40,7 +80,7 @@ pub fn finalize(p: *Parse, token_slice: Token.List.Slice) !Ast {
40}80}
4181
42/// This discards all parser work82/// This discards all parser work
43pub fn deinit(p: *Parse) void {83fn deinit(p: *Parse) void {
44 defer p.* = undefined;84 defer p.* = undefined;
45 p.nodes.deinit(p.gpa);85 p.nodes.deinit(p.gpa);
46 p.extra.deinit(p.gpa);86 p.extra.deinit(p.gpa);
...@@ -52,19 +92,46 @@ pub const Error = error{...@@ -52,19 +92,46 @@ pub const Error = error{
52 ParseFail,92 ParseFail,
53};93};
5494
95pub const ExpectedContext = enum {
96 param,
97 argument,
98 statement,
99 expression,
100 else_clause,
101
102 pub fn description(tag: ExpectedContext) []const u8 {
103 return switch (tag) {
104 .param => "a parameter",
105 .argument => "an argument",
106 .statement => "a statement",
107 .expression => "an expression",
108 .else_clause => "an else clause",
109 };
110 }
111};
112
55/// When error.SyntaxError is returned, `p.err` is set to this as well as `p.pos`113/// When error.SyntaxError is returned, `p.err` is set to this as well as `p.pos`
114/// When printing these, token list and Ast is not actually available, so full tokens
115/// are stored in the errors.
56pub const AstError = union(enum) {116pub const AstError = union(enum) {
117 /// Failure during tokenization
57 tokenization: struct {118 tokenization: struct {
58 err: Tokenizer.TokenizeError,119 err: Tokenizer.TokenizeError,
59 byte: u32,120 byte: ByteOffset,
121 },
122 /// Unexpected data, expected a token.
123 unexpected_token: struct {
124 expected: Token.Tag,
125 got: Token,
126 },
127 /// Unexpected data, expected <ExpectedContext>
128 unexpected_context: struct {
129 expected: ExpectedContext,
130 got: Token,
60 },131 },
61 expected_token: Token.Tag,
62 expected_statement,
63 expected_expr,
64 expected_else_value,
65132
66 pub fn toReport(parse_err: AstError, comp: *Comp, file: *Comp.File) !Report {133 pub fn toReport(err: AstError, comp: *Comp, file: *Comp.File, trace: Report.DebugStoredTrace) !Report {
67 return (Report.Err{ .ast = parse_err }).toReport(comp, file);134 return (Report.Err{ .ast = err }).toReport(comp, file, trace);
68 }135 }
69136
70 pub fn build(err: AstError, ctx: *Report.MsgContext) !void {137 pub fn build(err: AstError, ctx: *Report.MsgContext) !void {
...@@ -86,11 +153,29 @@ pub const AstError = union(enum) {...@@ -86,11 +153,29 @@ pub const AstError = union(enum) {
86 error.GitConflict => "git merge conflict encountered",153 error.GitConflict => "git merge conflict encountered",
87 }});154 }});
88 },155 },
89156 .unexpected_token => |data| {
90 else => unreachable,157 try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{
91 //158 data.expected.description(),
159 data.got.tag.description(),
160 });
161 },
162 .unexpected_context => |data| {
163 try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{
164 data.expected.description(),
165 data.got.tag.description(),
166 });
167 },
92 }168 }
93 }169 }
170
171 pub const WithTrace = struct {
172 err: AstError,
173 trace: Report.DebugStoredTrace,
174
175 pub fn toReport(err: WithTrace, comp: *Comp, file: *Comp.File) !Report {
176 return err.err.toReport(comp, file, err.trace);
177 }
178 };
94};179};
95180
96pub fn parseRoot(p: *Parse) Error!void {181pub fn parseRoot(p: *Parse) Error!void {
...@@ -141,10 +226,10 @@ fn parseModuleMembers(p: *Parse) Error!SubList {...@@ -141,10 +226,10 @@ fn parseModuleMembers(p: *Parse) Error!SubList {
141226
142 else => {227 else => {
143 if (!p.isExpressionStart()) {228 if (!p.isExpressionStart()) {
144 return p.fail(.expected_statement);229 return p.failUnexpectedToken(.statement);
145 }230 }
146231
147 try p.scratch.append(p.gpa, try p.parseExpression());232 try p.scratch.append(p.gpa, try p.parseExpression(true));
148 _ = try p.expect(.semi);233 _ = try p.expect(.semi);
149 },234 },
150 };235 };
...@@ -180,7 +265,7 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {...@@ -180,7 +265,7 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex {
180 const params = try p.parseParamList();265 const params = try p.parseParamList();
181266
182 const return_type = if (p.eatToken(.minus_lt)) |_|267 const return_type = if (p.eatToken(.minus_lt)) |_|
183 try p.parseExpression()268 try p.parseExpression(false)
184 else269 else
185 null;270 null;
186271
...@@ -222,7 +307,7 @@ fn parseParamList(p: *Parse) Error!SubList {...@@ -222,7 +307,7 @@ fn parseParamList(p: *Parse) Error!SubList {
222 _ = p.nextToken();307 _ = p.nextToken();
223 },308 },
224 .end_paren => break,309 .end_paren => break,
225 else => return p.fail(.{ .expected_token = .comma }),310 else => return p.failUnexpectedToken(.param),
226 }311 }
227 }312 }
228313
...@@ -239,13 +324,13 @@ fn parseParamDecl(p: *Parse) Error!NodeIndex {...@@ -239,13 +324,13 @@ fn parseParamDecl(p: *Parse) Error!NodeIndex {
239324
240 _ = try p.expect(.colon);325 _ = try p.expect(.colon);
241326
242 const @"type" = try p.parseExpression();327 const ty = try p.parseExpression(false);
243328
244 return try p.addNode(.{329 return try p.addNode(.{
245 .tag = .param_decl,330 .tag = .param_decl,
246 .main_token = main_token,331 .main_token = main_token,
247 .data = .{ .param_decl = .{332 .data = .{ .param_decl = .{
248 .type = @"type",333 .type = ty,
249 } },334 } },
250 });335 });
251}336}
...@@ -274,7 +359,7 @@ fn parseLabelExpression(p: *Parse) Error!NodeIndex {...@@ -274,7 +359,7 @@ fn parseLabelExpression(p: *Parse) Error!NodeIndex {
274359
275 const main_token = p.assertToken(.id);360 const main_token = p.assertToken(.id);
276 _ = try p.expect(.colon);361 _ = try p.expect(.colon);
277 const expr = try p.parseExpression();362 const expr = try p.parseExpression(false);
278 return try p.addNode(.{363 return try p.addNode(.{
279 .tag = .label,364 .tag = .label,
280 .main_token = main_token,365 .main_token = main_token,
...@@ -301,12 +386,12 @@ fn parseBlockContents(p: *Parse) Error!struct { Node.Tag, SubList } {...@@ -301,12 +386,12 @@ fn parseBlockContents(p: *Parse) Error!struct { Node.Tag, SubList } {
301386
302 else => {387 else => {
303 if (!p.isExpressionStart()) {388 if (!p.isExpressionStart()) {
304 return p.fail(.expected_statement);389 return p.failUnexpectedToken(.statement);
305 }390 }
306391
307 const first_tag = p.currentTag();392 const first_tag = p.currentTag();
308393
309 try p.scratch.append(p.gpa, try p.parseExpression());394 try p.scratch.append(p.gpa, try p.parseExpression(true));
310395
311 switch (first_tag) {396 switch (first_tag) {
312 // some control flow and blocks are allowed to not have a397 // some control flow and blocks are allowed to not have a
...@@ -352,13 +437,13 @@ fn parseVarDecl(p: *Parse) Error!NodeIndex {...@@ -352,13 +437,13 @@ fn parseVarDecl(p: *Parse) Error!NodeIndex {
352 _ = try p.expect(.id);437 _ = try p.expect(.id);
353438
354 const data_type: OptionalNodeIndex = if (p.eatToken(.colon)) |_|439 const data_type: OptionalNodeIndex = if (p.eatToken(.colon)) |_|
355 optionalNodeIndex(try p.parseExpression())440 optionalNodeIndex(try p.parseExpression(false))
356 else441 else
357 .none;442 .none;
358443
359 _ = try p.expect(.equal);444 _ = try p.expect(.equal);
360445
361 const data_init = try p.parseExpression();446 const data_init = try p.parseExpression(true);
362447
363 _ = try p.expect(.semi);448 _ = try p.expect(.semi);
364449
...@@ -412,6 +497,7 @@ const Operator = enum(u8) {...@@ -412,6 +497,7 @@ const Operator = enum(u8) {
412 le = @intFromEnum(Token.Tag.lt_equal),497 le = @intFromEnum(Token.Tag.lt_equal),
413 gt = @intFromEnum(Token.Tag.gt),498 gt = @intFromEnum(Token.Tag.gt),
414 ge = @intFromEnum(Token.Tag.gt_equal),499 ge = @intFromEnum(Token.Tag.gt_equal),
500 // Following operators are assignment operators
415 assign = @intFromEnum(Token.Tag.equal),501 assign = @intFromEnum(Token.Tag.equal),
416 add_assign = @intFromEnum(Token.Tag.plus_equal),502 add_assign = @intFromEnum(Token.Tag.plus_equal),
417 sub_assign = @intFromEnum(Token.Tag.minus_equal),503 sub_assign = @intFromEnum(Token.Tag.minus_equal),
...@@ -551,9 +637,13 @@ const Operator = enum(u8) {...@@ -551,9 +637,13 @@ const Operator = enum(u8) {
551 assert(tag != .root); // invalid operator or lut construction637 assert(tag != .root); // invalid operator or lut construction
552 return tag;638 return tag;
553 }639 }
640
641 pub fn isAssign(op: Operator) bool {
642 return @intFromEnum(op) >= @intFromEnum(Operator.assign);
643 }
554};644};
555645
556fn parseExpression(p: *Parse) Error!NodeIndex {646fn parseExpression(p: *Parse, allow_assign_operators: bool) Error!NodeIndex {
557 const trace = t.trace(@src());647 const trace = t.trace(@src());
558 defer trace.end();648 defer trace.end();
559649
...@@ -569,7 +659,8 @@ fn parseExpression(p: *Parse) Error!NodeIndex {...@@ -569,7 +659,8 @@ fn parseExpression(p: *Parse) Error!NodeIndex {
569 var expr = try p.parseExpressionPart();659 var expr = try p.parseExpressionPart();
570660
571 while (Operator.fromToken(p.currentTag())) |op| {661 while (Operator.fromToken(p.currentTag())) |op| {
572 expr = try p.parseBinarySuffix(expr, op);662 if (!allow_assign_operators and op.isAssign()) break;
663 expr = try p.parseBinarySuffix(expr, op, allow_assign_operators);
573 }664 }
574665
575 return expr;666 return expr;
...@@ -616,7 +707,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {...@@ -616,7 +707,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
616 });707 });
617 },708 },
618709
619 else => return p.fail(.expected_expr),710 else => return p.failUnexpectedToken(.expression),
620 };711 };
621712
622 // parse field, calls, and index expressions713 // parse field, calls, and index expressions
...@@ -657,7 +748,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {...@@ -657,7 +748,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
657 },748 },
658 .start_bracket => {749 .start_bracket => {
659 const main_token = p.nextToken();750 const main_token = p.nextToken();
660 const index = try p.parseExpression();751 const index = try p.parseExpression(false);
661752
662 expr = try p.addNode(.{753 expr = try p.addNode(.{
663 .tag = .index,754 .tag = .index,
...@@ -676,7 +767,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {...@@ -676,7 +767,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex {
676 return expr;767 return expr;
677}768}
678769
679fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) Error!NodeIndex {770fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, allow_assign_operators: bool) Error!NodeIndex {
680 const trace = t.trace(@src());771 const trace = t.trace(@src());
681 defer trace.end();772 defer trace.end();
682773
...@@ -685,6 +776,8 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) Error!NodeIndex {...@@ -685,6 +776,8 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) Error!NodeIndex {
685 var rhs = try p.parseExpressionPart();776 var rhs = try p.parseExpressionPart();
686777
687 while (Operator.fromToken(p.currentTag())) |next_op| {778 while (Operator.fromToken(p.currentTag())) |next_op| {
779 if (!allow_assign_operators and next_op.isAssign()) break;
780
688 const new_level = next_op.level();781 const new_level = next_op.level();
689 const use = switch (op.direction()) {782 const use = switch (op.direction()) {
690 .ltr => @intFromEnum(new_level) < @intFromEnum(level),783 .ltr => @intFromEnum(new_level) < @intFromEnum(level),
...@@ -692,7 +785,7 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) Error!NodeIndex {...@@ -692,7 +785,7 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) Error!NodeIndex {
692 };785 };
693 if (!use) break;786 if (!use) break;
694787
695 rhs = try p.parseBinarySuffix(rhs, next_op);788 rhs = try p.parseBinarySuffix(rhs, next_op, allow_assign_operators);
696 }789 }
697790
698 return try p.addNode(.{791 return try p.addNode(.{
...@@ -712,7 +805,7 @@ fn parseReturn(p: *Parse) Error!NodeIndex {...@@ -712,7 +805,7 @@ fn parseReturn(p: *Parse) Error!NodeIndex {
712 const main_token = p.assertToken(.kw_return);805 const main_token = p.assertToken(.kw_return);
713806
714 const expr = if (p.isExpressionStart())807 const expr = if (p.isExpressionStart())
715 try p.parseExpression()808 try p.parseExpression(false)
716 else809 else
717 null;810 null;
718811
...@@ -737,7 +830,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex {...@@ -737,7 +830,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex {
737 null;830 null;
738831
739 const expr = if (p.isExpressionStart())832 const expr = if (p.isExpressionStart())
740 try p.parseExpression()833 try p.parseExpression(false)
741 else834 else
742 null;835 null;
743836
...@@ -776,7 +869,7 @@ fn parseCallArgs(p: *Parse) Error!SubList {...@@ -776,7 +869,7 @@ fn parseCallArgs(p: *Parse) Error!SubList {
776 defer p.scratch.shrinkRetainingCapacity(scratch_top);869 defer p.scratch.shrinkRetainingCapacity(scratch_top);
777870
778 while (p.isExpressionStart()) {871 while (p.isExpressionStart()) {
779 try p.scratch.append(p.gpa, try p.parseExpression());872 try p.scratch.append(p.gpa, try p.parseExpression(false));
780873
781 switch (p.currentTag()) {874 switch (p.currentTag()) {
782 .end_paren => {875 .end_paren => {
...@@ -787,7 +880,7 @@ fn parseCallArgs(p: *Parse) Error!SubList {...@@ -787,7 +880,7 @@ fn parseCallArgs(p: *Parse) Error!SubList {
787 if (p.currentTag() == .end_paren)880 if (p.currentTag() == .end_paren)
788 break;881 break;
789 },882 },
790 else => return p.fail(.{ .expected_token = .end_paren }),883 else => return p.failUnexpectedToken(.argument),
791 }884 }
792 }885 }
793886
...@@ -802,7 +895,7 @@ fn parseIf(p: *Parse) Error!NodeIndex {...@@ -802,7 +895,7 @@ fn parseIf(p: *Parse) Error!NodeIndex {
802895
803 const main_token = p.assertToken(.kw_if);896 const main_token = p.assertToken(.kw_if);
804897
805 const cond = try p.parseExpression();898 const cond = try p.parseExpression(false);
806899
807 _ = try p.expectWithoutAdvance(.start_brace);900 _ = try p.expectWithoutAdvance(.start_brace);
808 const yes = try p.parseBlock();901 const yes = try p.parseBlock();
...@@ -838,7 +931,7 @@ fn parseElseValue(p: *Parse) Error!NodeIndex {...@@ -838,7 +931,7 @@ fn parseElseValue(p: *Parse) Error!NodeIndex {
838 .kw_if => p.parseIf(),931 .kw_if => p.parseIf(),
839 .id => p.parseLabelExpression(),932 .id => p.parseLabelExpression(),
840933
841 else => return p.fail(.expected_else_value),934 else => return p.failUnexpectedToken(.else_clause),
842 };935 };
843}936}
844937
...@@ -848,7 +941,7 @@ fn parseGroup(p: *Parse) Error!NodeIndex {...@@ -848,7 +941,7 @@ fn parseGroup(p: *Parse) Error!NodeIndex {
848941
849 const main_token = p.assertToken(.start_paren);942 const main_token = p.assertToken(.start_paren);
850943
851 const expr = try p.parseExpression();944 const expr = try p.parseExpression(false);
852945
853 _ = try p.expect(.end_paren);946 _ = try p.expect(.end_paren);
854947
...@@ -885,11 +978,20 @@ fn addExtra(p: *Parse, comptime T: type, extra: T) !ExtraIndex {...@@ -885,11 +978,20 @@ fn addExtra(p: *Parse, comptime T: type, extra: T) !ExtraIndex {
885}978}
886979
887fn fail(p: *Parse, ast_error: AstError) Error {980fn fail(p: *Parse, ast_error: AstError) Error {
888 p.ast_error = ast_error;981 p.ast_error = .{
889 std.debug.print("got {s}", .{@tagName(p.currentTag())});982 .err = ast_error,
983 .trace = Report.DebugStoredTrace.capture(@returnAddress()),
984 };
890 return Error.ParseFail;985 return Error.ParseFail;
891}986}
892987
988fn failUnexpectedToken(p: *Parse, kind: ExpectedContext) Error {
989 return p.fail(.{ .unexpected_context = .{
990 .got = p.fullToken(p.pos),
991 .expected = kind,
992 } });
993}
994
893fn blankNode(p: *Parse, comptime tag: Token.Tag) !NodeIndex {995fn blankNode(p: *Parse, comptime tag: Token.Tag) !NodeIndex {
894 const main_token = p.assertToken(tag);996 const main_token = p.assertToken(tag);
895 return try p.addNode(.{997 return try p.addNode(.{
...@@ -917,14 +1019,20 @@ fn eatToken(p: *Parse, tag: Token.Tag) ?TokenIndex {...@@ -917,14 +1019,20 @@ fn eatToken(p: *Parse, tag: Token.Tag) ?TokenIndex {
9171019
918fn expect(p: *Parse, tag: Token.Tag) !TokenIndex {1020fn expect(p: *Parse, tag: Token.Tag) !TokenIndex {
919 if (p.currentTag() != tag) {1021 if (p.currentTag() != tag) {
920 return p.fail(.{ .expected_token = tag });1022 return p.fail(.{ .unexpected_token = .{
1023 .got = p.fullToken(p.pos),
1024 .expected = tag,
1025 } });
921 }1026 }
922 return p.nextToken();1027 return p.nextToken();
923}1028}
9241029
925fn expectWithoutAdvance(p: *Parse, tag: Token.Tag) !void {1030fn expectWithoutAdvance(p: *Parse, tag: Token.Tag) !void {
926 if (p.currentTag() != tag) {1031 if (p.currentTag() != tag) {
927 return p.fail(.{ .expected_token = tag });1032 return p.fail(.{ .unexpected_token = .{
1033 .got = p.fullToken(p.pos),
1034 .expected = tag,
1035 } });
928 }1036 }
929}1037}
9301038
src/compiler/Report.zig+137-19
...@@ -9,27 +9,89 @@ errors: []Item = &.{},...@@ -9,27 +9,89 @@ errors: []Item = &.{},
9warnings: []Item = &.{},9warnings: []Item = &.{},
10string_bytes: []u8 = &.{},10string_bytes: []u8 = &.{},
1111
12pub var debug_show_report_trace: if (builtin.mode == .Debug) bool else void =
13 if (builtin.mode == .Debug) false;
14
12pub const Item = struct {15pub const Item = struct {
13 /// What is this diagnostic16 /// What is this diagnostic
14 data: union { @"error": Err, warn: Warn },17 data: union { @"error": Err, warn: Warn },
15 /// What file was it in18 /// What file was it in
16 url: String,19 url: String,
1720
21 debug_stack_trace: DebugStoredTrace,
22
18 /// Type is never stored, inferred from which list it is in.23 /// Type is never stored, inferred from which list it is in.
19 pub const Type = enum { @"error", warn };24 pub const Type = enum { @"error", warn };
20};25};
2126
27/// A variant of std.builtin.StackTrace that stores it's data within itself
28/// instead of being a pointer. This allows capturing the stack trace and
29/// saving it for later printing.
30pub const StoredTrace = struct {
31 data: [32]usize,
32 index: usize,
33
34 pub const empty = .{
35 .data = .{0} ** 32,
36 .index = 0,
37 };
38
39 fn trace(stored: *StoredTrace) std.builtin.StackTrace {
40 return .{
41 .index = stored.index,
42 .instruction_addresses = &stored.data,
43 };
44 }
45
46 pub fn capture(begin: ?usize) StoredTrace {
47 var stored: StoredTrace = undefined;
48 var frame = stored.trace();
49 std.debug.captureStackTrace(begin orelse @returnAddress(), &frame);
50 stored.index = frame.index;
51 return stored;
52 }
53
54 pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace {
55 if (stack_trace) |stack| {
56 var data: [32]usize = undefined;
57 @memset(&data, 0);
58 const items = @min(stack.instruction_addresses.len, 32);
59 @memcpy(data[0..items], stack.instruction_addresses[0..items]);
60 return .{
61 .data = data,
62 .index = @min(items, stack.index),
63 };
64 } else {
65 return empty;
66 }
67 }
68};
69
70pub const DebugStoredTrace = if (builtin.mode == .Debug)
71 StoredTrace
72else
73 struct {
74 pub fn capture() @This() {
75 return .{};
76 }
77
78 pub fn from(_: ?*std.builtin.StackTrace) @This() {
79 return .{};
80 }
81 };
82
22pub const Err = union(enum) {83pub const Err = union(enum) {
23 pub const FieldEnum = std.meta.FieldEnum(Err);84 pub const FieldEnum = std.meta.FieldEnum(Err);
2485
25 /// Constructs a `Report` with just a single error.86 /// Constructs a `Report` with just a single error.
26 pub fn toReport(err: Err, comp: *Comp, file: *File) !Report {87 pub fn toReport(err: Err, comp: *Comp, file: *File, trace: DebugStoredTrace) !Report {
27 const string_bytes = try comp.gpa.dupe(u8, file.file_path);88 const string_bytes = try comp.gpa.dupe(u8, file.file_path);
28 errdefer comp.gpa.free(string_bytes);89 errdefer comp.gpa.free(string_bytes);
2990
30 const errors = try comp.gpa.dupe(Item, &.{.{91 const errors = try comp.gpa.dupe(Item, &.{.{
31 .data = .{ .@"error" = err },92 .data = .{ .@"error" = err },
32 .url = .{ .start = 0, .len = @intCast(string_bytes.len) },93 .url = .{ .start = 0, .len = @intCast(string_bytes.len) },
94 .debug_stack_trace = trace,
33 }});95 }});
34 errdefer comp.gpa.free(errors);96 errdefer comp.gpa.free(errors);
3597
...@@ -42,7 +104,7 @@ pub const Err = union(enum) {...@@ -42,7 +104,7 @@ pub const Err = union(enum) {
42 node: NodeIndex,104 node: NodeIndex,
43105
44 fn build(data: @This(), ctx: *MsgContext) !void {106 fn build(data: @This(), ctx: *MsgContext) !void {
45 try ctx.msgNode(data.node, "unknown identifier '{s}'", .{107 try ctx.msgNode(data.node, "unknown identifier \"{s}\"", .{
46 ctx.ast.basicNodeSlice(data.node),108 ctx.ast.basicNodeSlice(data.node),
47 });109 });
48 }110 }
...@@ -53,7 +115,7 @@ pub const Err = union(enum) {...@@ -53,7 +115,7 @@ pub const Err = union(enum) {
53 fn build(data: @This(), ctx: *MsgContext) !void {115 fn build(data: @This(), ctx: *MsgContext) !void {
54 try ctx.msgToken(116 try ctx.msgToken(
55 ctx.ast.nodeToken(data.node),117 ctx.ast.nodeToken(data.node),
56 "unknown built-in function '{s}'",118 "unknown built-in function \"{s}\"",
57 .{ctx.ast.basicNodeSlice(data.node)},119 .{ctx.ast.basicNodeSlice(data.node)},
58 );120 );
59 }121 }
...@@ -62,7 +124,10 @@ pub const Err = union(enum) {...@@ -62,7 +124,10 @@ pub const Err = union(enum) {
62 node: NodeIndex,124 node: NodeIndex,
63125
64 fn build(data: @This(), ctx: *MsgContext) !void {126 fn build(data: @This(), ctx: *MsgContext) !void {
65 try ctx.msgNode(data.node, "local cannot be named '_'", .{});127 try ctx.msgToken(ctx.ast.idToken(data.node), "local cannot be named \"_\"", .{});
128 try ctx.msgAlone("to discard this value, remove '{s}'", .{
129 ctx.ast.tokenSlice(ctx.ast.nodeToken(data.node)),
130 });
66 }131 }
67 },132 },
68 duplicate_function: struct {133 duplicate_function: struct {
...@@ -70,7 +135,7 @@ pub const Err = union(enum) {...@@ -70,7 +135,7 @@ pub const Err = union(enum) {
70 prev: NodeIndex,135 prev: NodeIndex,
71136
72 fn build(data: @This(), ctx: *MsgContext) !void {137 fn build(data: @This(), ctx: *MsgContext) !void {
73 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of '{s}'", .{138 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of \"{s}\"", .{
74 ctx.ast.fullFnDecl(data.node).id,139 ctx.ast.fullFnDecl(data.node).id,
75 });140 });
76 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.prev), "also declared here", .{});141 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.prev), "also declared here", .{});
...@@ -83,7 +148,7 @@ pub const Err = union(enum) {...@@ -83,7 +148,7 @@ pub const Err = union(enum) {
83 prev: NodeIndex,148 prev: NodeIndex,
84149
85 fn build(data: @This(), ctx: *MsgContext) !void {150 fn build(data: @This(), ctx: *MsgContext) !void {
86 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of '{s}'", .{151 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of \"{s}\"", .{
87 ctx.ast.fullParamDecl(data.node).id,152 ctx.ast.fullParamDecl(data.node).id,
88 });153 });
89 try ctx.msgToken(ctx.ast.nodeToken(data.prev), "also declared here", .{});154 try ctx.msgToken(ctx.ast.nodeToken(data.prev), "also declared here", .{});
...@@ -108,7 +173,7 @@ pub const Err = union(enum) {...@@ -108,7 +173,7 @@ pub const Err = union(enum) {
108 fn build(data: @This(), ctx: *MsgContext) !void {173 fn build(data: @This(), ctx: *MsgContext) !void {
109 try ctx.msg(174 try ctx.msg(
110 .{ .two_tokens = ctx.ast.nodeToken(data.node) + 1 },175 .{ .two_tokens = ctx.ast.nodeToken(data.node) + 1 },
111 "unknown block label '{s}'",176 "unknown block label \"{s}\"",
112 .{ctx.ast.tokenSlice(ctx.ast.nodeToken(data.node) + 2)},177 .{ctx.ast.tokenSlice(ctx.ast.nodeToken(data.node) + 2)},
113 );178 );
114 }179 }
...@@ -119,7 +184,7 @@ pub const Err = union(enum) {...@@ -119,7 +184,7 @@ pub const Err = union(enum) {
119 fn build(data: @This(), ctx: *MsgContext) !void {184 fn build(data: @This(), ctx: *MsgContext) !void {
120 try ctx.msgToken(185 try ctx.msgToken(
121 ctx.ast.nodeToken(data.node) + 1,186 ctx.ast.nodeToken(data.node) + 1,
122 "declaration shadows built-in constant '{s}'",187 "declaration shadows reserved built-in \"{s}\"",
123 .{ctx.ast.fullVarDecl(data.node).id},188 .{ctx.ast.fullVarDecl(data.node).id},
124 );189 );
125 }190 }
...@@ -132,17 +197,28 @@ pub const Warn = union(enum) {...@@ -132,17 +197,28 @@ pub const Warn = union(enum) {
132 unused: struct {197 unused: struct {
133 node: NodeIndex,198 node: NodeIndex,
134 kind: UnusedKind,199 kind: UnusedKind,
200 // shadowed: ?NodeIndex = null,
135201
136 fn build(data: @This(), ctx: *MsgContext) !void {202 fn build(data: @This(), ctx: *MsgContext) !void {
137 try ctx.msgToken(ctx.ast.idToken(data.node), "unused {s} '{s}'", .{203 const id = switch (ctx.ast.nodeTag(data.node)) {
204 .param_decl => ctx.ast.fullParamDecl(data.node).id,
205 .fn_decl => ctx.ast.fullFnDecl(data.node).id,
206 .var_decl => ctx.ast.fullVarDecl(data.node).id,
207 else => unreachable, // not a valid symbol node
208 };
209
210 try ctx.msgToken(ctx.ast.idToken(data.node), "unused {s} \"{s}\"", .{
138 @tagName(data.kind),211 @tagName(data.kind),
139 switch (ctx.ast.nodeTag(data.node)) {212 id,
140 .param_decl => ctx.ast.fullParamDecl(data.node).id,
141 .fn_decl => ctx.ast.fullFnDecl(data.node).id,
142 .var_decl => ctx.ast.fullVarDecl(data.node).id,
143 else => unreachable, // not a valid symbol node
144 },
145 });213 });
214
215 // if (data.shadowed) |node| {
216 // try ctx.msgToken(ctx.ast.idToken(node), "{s} was re-declared here", .{
217 // @tagName(data.kind),
218 // });
219 // }
220
221 try ctx.msgAlone("if this was intentional, discard it with \"_ = {s};\"", .{id});
146 }222 }
147 },223 },
148};224};
...@@ -217,6 +293,7 @@ pub fn formatItem(...@@ -217,6 +293,7 @@ pub fn formatItem(
217 .comp = comp,293 .comp = comp,
218 .file = file,294 .file = file,
219 .ast = &ast_data,295 .ast = &ast_data,
296 .utf8_source = (try file.getSource(comp.gpa)).code,
220 .components = ArrayListUnmanaged(Component).initCapacity(alloc, 16) catch297 .components = ArrayListUnmanaged(Component).initCapacity(alloc, 16) catch
221 unreachable, // stack-fallback allocator has enough space.298 unreachable, // stack-fallback allocator has enough space.
222 };299 };
...@@ -246,6 +323,7 @@ pub fn formatItem(...@@ -246,6 +323,7 @@ pub fn formatItem(
246 }323 }
247324
248 try tty.setColor(writer, prefix.color());325 try tty.setColor(writer, prefix.color());
326 try tty.setColor(writer, .bold);
249 try writer.writeAll(@tagName(prefix));327 try writer.writeAll(@tagName(prefix));
250 try tty.setColor(writer, .reset);328 try tty.setColor(writer, .reset);
251 try tty.setColor(writer, .dim);329 try tty.setColor(writer, .dim);
...@@ -260,9 +338,18 @@ pub fn formatItem(...@@ -260,9 +338,18 @@ pub fn formatItem(
260 slice: []const u8,338 slice: []const u8,
261 };339 };
262340
263 const utf8_source = (try file.getSource(comp.gpa)).code;341 const utf8_source = ctx.utf8_source;
264342
265 const at: SliceLoc = switch (component.highlight) {343 const at: SliceLoc = switch (component.highlight) {
344 .alone => {
345 if (is_last) {
346 try tty.setColor(writer, .dim);
347 try writer.print(" {s}", .{"┴"});
348 try writer.writeAll("\n");
349 try tty.setColor(writer, .reset);
350 }
351 continue;
352 },
266 inline .node,353 inline .node,
267 .token,354 .token,
268 .two_tokens,355 .two_tokens,
...@@ -283,6 +370,16 @@ pub fn formatItem(...@@ -283,6 +370,16 @@ pub fn formatItem(
283 };370 };
284 },371 },
285372
373 .token_stored => |token| loc: {
374 const location = Tokenizer.tokenLocation(ctx.utf8_source, 0, token.offset);
375
376 break :loc .{
377 .loc = location,
378 .slice = token.slice(utf8_source),
379 .byte = token.offset,
380 };
381 },
382
286 inline .codepoint,383 inline .codepoint,
287 .codepoint_eol,384 .codepoint_eol,
288 => |b, tag| loc: {385 => |b, tag| loc: {
...@@ -344,17 +441,25 @@ pub fn formatItem(...@@ -344,17 +441,25 @@ pub fn formatItem(
344 try tty.setColor(writer, .reset);441 try tty.setColor(writer, .reset);
345 try writer.writeAll("\n");442 try writer.writeAll("\n");
346 }443 }
444
445 if (builtin.mode == .Debug and debug_show_report_trace) {
446 var trace = item.debug_stack_trace;
447 std.debug.dumpStackTrace(trace.trace());
448 }
347}449}
348450
349pub fn dumpToStdErr(report: Report, comp: *Comp) !void {451pub fn dumpToStdErr(report: Report, comp: *Comp) !void {
350 const builtin = @import("builtin");
351
352 const stderr = std.io.getStdErr();452 const stderr = std.io.getStdErr();
353 const tty = std.io.tty.detectConfig(stderr);453 const tty = std.io.tty.detectConfig(stderr);
354454
355 if (builtin.os.tag == .windows and tty == .windows_api) {455 if (false or
356 // Windows API uses it's own API calls, so a buffered writer456 // Windows API uses it's own API calls, so a buffered writer
357 // cannot be used :/457 // cannot be used :/
458 (builtin.os.tag == .windows and tty == .windows_api) or
459 // Debug stack trace printer writes directly to standard err,
460 // so we need to be unbuffered for that.
461 (builtin.mode == .Debug and debug_show_report_trace))
462 {
358 try dumpToWriter(report, comp, stderr.writer(), tty);463 try dumpToWriter(report, comp, stderr.writer(), tty);
359 } else {464 } else {
360 var buf = std.io.bufferedWriter(stderr.writer());465 var buf = std.io.bufferedWriter(stderr.writer());
...@@ -376,6 +481,7 @@ pub const Builder = struct {...@@ -376,6 +481,7 @@ pub const Builder = struct {
376 try b.errors.append(b.gpa, .{481 try b.errors.append(b.gpa, .{
377 .data = .{ .@"error" = @unionInit(Err, @tagName(kind), data) },482 .data = .{ .@"error" = @unionInit(Err, @tagName(kind), data) },
378 .url = try b.currentFileString(),483 .url = try b.currentFileString(),
484 .debug_stack_trace = if (builtin.mode == .Debug) StoredTrace.capture(@returnAddress()) else .{},
379 });485 });
380 }486 }
381487
...@@ -383,6 +489,7 @@ pub const Builder = struct {...@@ -383,6 +489,7 @@ pub const Builder = struct {
383 try b.warnings.append(b.gpa, .{489 try b.warnings.append(b.gpa, .{
384 .data = .{ .warn = @unionInit(Warn, @tagName(kind), data) },490 .data = .{ .warn = @unionInit(Warn, @tagName(kind), data) },
385 .url = try b.currentFileString(),491 .url = try b.currentFileString(),
492 .debug_stack_trace = if (builtin.mode == .Debug) StoredTrace.capture(@returnAddress()) else .{},
386 });493 });
387 }494 }
388495
...@@ -426,8 +533,12 @@ pub const Builder = struct {...@@ -426,8 +533,12 @@ pub const Builder = struct {
426};533};
427534
428pub const Highlight = union(enum) {535pub const Highlight = union(enum) {
536 // No source view
537 alone,
429 // Highlight one token538 // Highlight one token
430 token: TokenIndex,539 token: TokenIndex,
540 // Highlight one token without referring to the Ast
541 token_stored: Tokenizer.Token,
431 // Highlight two tokens542 // Highlight two tokens
432 two_tokens: TokenIndex,543 two_tokens: TokenIndex,
433 // Highlight a Node544 // Highlight a Node
...@@ -448,6 +559,7 @@ pub const MsgContext = struct {...@@ -448,6 +559,7 @@ pub const MsgContext = struct {
448 file: *File,559 file: *File,
449 comp: *Comp,560 comp: *Comp,
450 ast: *const Ast,561 ast: *const Ast,
562 utf8_source: [:0]const u8,
451563
452 /// This arena is is a stack fallback with 16kb, then564 /// This arena is is a stack fallback with 16kb, then
453 /// falling back to an arena allocator. This should565 /// falling back to an arena allocator. This should
...@@ -474,6 +586,10 @@ pub const MsgContext = struct {...@@ -474,6 +586,10 @@ pub const MsgContext = struct {
474 pub fn msgToken(m: *MsgContext, token: TokenIndex, comptime fmt: []const u8, args: anytype) !void {586 pub fn msgToken(m: *MsgContext, token: TokenIndex, comptime fmt: []const u8, args: anytype) !void {
475 return m.msg(.{ .token = token }, fmt, args);587 return m.msg(.{ .token = token }, fmt, args);
476 }588 }
589
590 pub fn msgAlone(m: *MsgContext, comptime fmt: []const u8, args: anytype) !void {
591 return m.msg(.alone, fmt, args);
592 }
477};593};
478594
479const Report = @This();595const Report = @This();
...@@ -482,6 +598,8 @@ const ArrayListUnmanaged = std.ArrayListUnmanaged;...@@ -482,6 +598,8 @@ const ArrayListUnmanaged = std.ArrayListUnmanaged;
482const Allocator = std.mem.Allocator;598const Allocator = std.mem.Allocator;
483const assert = std.debug.assert;599const assert = std.debug.assert;
484600
601const builtin = @import("builtin");
602
485const Tokenizer = @import("Tokenizer.zig");603const Tokenizer = @import("Tokenizer.zig");
486const ByteOffset = Tokenizer.ByteOffset;604const ByteOffset = Tokenizer.ByteOffset;
487605
src/compiler/Sema.zig+17-7
...@@ -130,11 +130,15 @@ pub fn analyzeDeclaration(s: *Sema) anyerror!Value {...@@ -130,11 +130,15 @@ pub fn analyzeDeclaration(s: *Sema) anyerror!Value {
130130
131 const return_type = try s.resolveRefComptime(fn_decl.return_type);131 const return_type = try s.resolveRefComptime(fn_decl.return_type);
132132
133 const root = (try s.analyzeBlock(133 const root_block = fn_decl.body.toIndex() orelse
134 fn_decl.body.toIndex() orelse134 @panic("TODO: please change this to Index. Ref is not allowed here anymore");
135 @panic("TODO: please change this to Index. Ref is not allowed here anymore"),135
136 )).decode().inst;136 _ = if (s.code.instPayload(.block, root_block).len > 0)
137 assert(@intFromEnum(root) == 0);137 try s.analyzeBlock(root_block)
138 else empty_block: {
139 var root = try s.addBlock();
140 break :empty_block (try root.finish(s, s.air_instructions.len - 1));
141 };
138142
139 const func_prototype = try s.ip.get(s.gpa, .{143 const func_prototype = try s.ip.get(s.gpa, .{
140 .type_function = .{144 .type_function = .{
...@@ -384,6 +388,14 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref {...@@ -384,6 +388,14 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref {
384 }388 }
385 return InternPool.Index.void_value.toAir();389 return InternPool.Index.void_value.toAir();
386 },390 },
391 .discard => {
392 const operand = try s.resolveRefRuntimeExtra(s.code.instData(.discard, in).operand, false);
393 const t = Value.fromInterned(s.typeOfRef(operand));
394 if (t.ip_index == .void_value) {
395 std.debug.print("oh no, we have a useless _ = void;\n", .{});
396 }
397 return InternPool.Index.void_value.toAir();
398 },
387 .@"if" => {399 .@"if" => {
388 const data: Teg.Inst.If = s.code.instData(.@"if", in).extra_index.get(&s.code);400 const data: Teg.Inst.If = s.code.instData(.@"if", in).extra_index.get(&s.code);
389401
...@@ -627,7 +639,6 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Air.Inst.Ref {...@@ -627,7 +639,6 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Air.Inst.Ref {
627 .float = result,639 .float = result,
628 })).toAir();640 })).toAir();
629 },641 },
630
631 inline .lt,642 inline .lt,
632 .gt,643 .gt,
633 => |tag| {644 => |tag| {
...@@ -643,7 +654,6 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Air.Inst.Ref {...@@ -643,7 +654,6 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Air.Inst.Ref {
643 if (result) .bool_true else .bool_false,654 if (result) .bool_true else .bool_false,
644 ).toAir();655 ).toAir();
645 },656 },
646
647 .eq,657 .eq,
648 => {658 => {
649 const l = lhs.toFloat(s.ip) orelse @panic("TODO: typecheck / non-numeric eq");659 const l = lhs.toFloat(s.ip) orelse @panic("TODO: typecheck / non-numeric eq");
src/compiler/Teg.Dump.zig+8-1
...@@ -120,6 +120,14 @@ const State = struct {...@@ -120,6 +120,14 @@ const State = struct {
120 try s.dumpRefMarker(writer, data.rhs);120 try s.dumpRefMarker(writer, data.rhs);
121 try writer.writeAll(")");121 try writer.writeAll(")");
122 },122 },
123 .as => {
124 const data = teg.instData(.as, inst).extra_index.get(teg);
125 try writer.writeAll("(");
126 try s.dumpRefMarker(writer, data.type);
127 try writer.writeAll(", ");
128 try s.dumpRefMarker(writer, data.operand);
129 try writer.writeAll(")");
130 },
123 .@"break" => {131 .@"break" => {
124 const data: Teg.Inst.Break = teg.instData(.@"break", inst).extra_index.get(teg);132 const data: Teg.Inst.Break = teg.instData(.@"break", inst).extra_index.get(teg);
125 try writer.writeAll("(");133 try writer.writeAll("(");
...@@ -192,7 +200,6 @@ const State = struct {...@@ -192,7 +200,6 @@ const State = struct {
192 try s.newline(writer);200 try s.newline(writer);
193 try writer.writeAll("}");201 try writer.writeAll("}");
194 },202 },
195 else => std.debug.panic("TODO format {s}", .{@tagName(tag)}),
196 }203 }
197 }204 }
198205
src/compiler/Tokenizer.zig+123-58
...@@ -146,10 +146,13 @@ pub const Token = struct {...@@ -146,10 +146,13 @@ pub const Token = struct {
146 /// Used so that parser does not need to do out of bounds checks146 /// Used so that parser does not need to do out of bounds checks
147 eof,147 eof,
148148
149 /// Most tokens have a static length, such as "struct" or "+="149 /// Most tokens have statically known lexeme, such as "struct" or "+="
150 pub fn staticLen(tag: Tag) ?u32 {150 pub fn lexeme(tag: Tag) ?[]const u8 {
151 return switch (tag) {151 return switch (tag) {
152 // do not have a static length152 .invalid => unreachable,
153
154 // do not have a static lexeme
155 .eof,
153 .id,156 .id,
154 .builtin_id,157 .builtin_id,
155 .number_int,158 .number_int,
...@@ -160,68 +163,86 @@ pub const Token = struct {...@@ -160,68 +163,86 @@ pub const Token = struct {
160 .module_doc_comment,163 .module_doc_comment,
161 => null,164 => null,
162165
163 .@"and" => 1,166 .@"and" => "&",
164 .and_and => 2,167 .and_and => "&&",
165 .bang => 1,168 .bang => "!",
166 .bang_equal => 2,169 .bang_equal => "!=",
167 .equal_lt => 2,170 .equal_lt => "=<",
168 .caret => 1,171 .caret => "^",
169 .end_brace => 1,172 .end_brace => "}",
170 .end_bracket => 1,173 .end_bracket => "]",
171 .end_paren => 1,174 .end_paren => ")",
172 .colon => 1,175 .colon => ":",
173 .comma => 1,176 .comma => ",",
174 .dot => 1,177 .dot => ".",
175 .equal => 1,178 .equal => "=",
176 .equal_equal => 2,179 .equal_equal => "==",
177 .gt => 1,180 .gt => ">",
178 .gt_equal => 2,181 .gt_equal => ">=",
179 .gt_gt => 2,182 .gt_gt => ">>",
180 .gt_gt_gt => 3,183 .gt_gt_gt => ">>>",
181 .lt => 1,184 .lt => "<",
182 .lt_equal => 2,185 .lt_equal => "<=",
183 .lt_lt => 2,186 .lt_lt => "<<",
184 .minus => 1,187 .minus => "-",
185 .start_brace => 1,188 .start_brace => "{",
186 .start_bracket => 1,189 .start_bracket => "[",
187 .start_paren => 1,190 .start_paren => "(",
188 .pipe => 1,191 .pipe => "|",
189 .pipe_pipe => 2,192 .pipe_pipe => "||",
190 .percent => 1,193 .percent => "%",
191 .plus => 1,194 .plus => "+",
192 .minus_lt => 2,195 .minus_lt => "<",
193 .semi => 1,196 .semi => ";",
194 .slash => 1,197 .slash => "/",
195 .star => 1,198 .star => "*",
196 .star_star => 2,199 .star_star => "**",
197 .tilde => 1,200 .tilde => "~",
198 .slash_equal => 2,201 .slash_equal => "/=",
199 .percent_equal => 2,202 .percent_equal => "%=",
200 .tilde_equal => 2,203 .tilde_equal => "~=",
201 .caret_equal => 2,204 .caret_equal => "^=",
202 .and_equal => 2,205 .and_equal => "&=",
203 .pipe_equal => 2,206 .pipe_equal => "|=",
204 .lt_lt_equal => 3,207 .lt_lt_equal => "<<=",
205 .gt_gt_equal => 3,208 .gt_gt_equal => ">>=",
206 .gt_gt_gt_equal => 4,209 .gt_gt_gt_equal => ">>>=",
207 .plus_equal => 2,210 .plus_equal => "+=",
208 .minus_equal => 2,211 .minus_equal => "-=",
209 .star_equal => 2,212 .star_equal => "*=",
210 .star_star_equal => 3,213 .star_star_equal => "**=",
211 .pipe_pipe_equal => 3,214 .pipe_pipe_equal => "||=",
212 .and_and_equal => 3,215 .and_and_equal => "&&=",
213
214 .invalid => 0,
215 .eof => 0,
216216
217 inline else => |keyword| comptime len: {217 inline else => |keyword| comptime len: {
218 if (!mem.startsWith(u8, @tagName(keyword), "kw_")) {218 if (!mem.startsWith(u8, @tagName(keyword), "kw_")) {
219 @compileError("Token tag is not a keyword: " ++ @tagName(keyword));219 @compileError("Token tag is not a keyword: " ++ @tagName(keyword));
220 }220 }
221 break :len @tagName(keyword).len - 3;221 break :len @tagName(keyword)[3..];
222 },222 },
223 };223 };
224 }224 }
225
226 pub fn description(tag: Tag) []const u8 {
227 return switch (tag) {
228 .invalid => unreachable,
229 .eof => "end of file",
230 .id => "an identifier",
231 .builtin_id => "a builtin identifier",
232 .number_int => "a numeric literal",
233 .number_float => "a numeric literal",
234 .string => "a string literal",
235 .multiline_string => "a multiline string literal",
236 .doc_comment => "a documentation comment",
237 .module_doc_comment => "a module documentation comment",
238 inline else => |t| comptime "\"" ++ t.lexeme().? ++ "\"",
239 };
240 }
241
242 /// Most tokens have a static length, such as "struct" or "+="
243 pub fn staticLen(tag: Tag) ?u32 {
244 return if (tag.lexeme()) |str| @intCast(str.len) else null;
245 }
225 };246 };
226247
227 /// Instead of storing the length of a token, we can recompute this248 /// Instead of storing the length of a token, we can recompute this
...@@ -243,7 +264,8 @@ pub const Token = struct {...@@ -243,7 +264,8 @@ pub const Token = struct {
243 }264 }
244265
245 pub fn slice(token: Token, utf8_source: [:0]const u8) []const u8 {266 pub fn slice(token: Token, utf8_source: [:0]const u8) []const u8 {
246 return utf8_source[token.offset..][0..token.len(utf8_source)];267 return token.tag.lexeme() orelse
268 utf8_source[token.offset..][0..token.len(utf8_source)];
247 }269 }
248270
249 const keywords = std.ComptimeStringMap(Tag, keywords: {271 const keywords = std.ComptimeStringMap(Tag, keywords: {
...@@ -921,6 +943,48 @@ pub fn skipWhitespace(t: *Tokenizer) void {...@@ -921,6 +943,48 @@ pub fn skipWhitespace(t: *Tokenizer) void {
921 }943 }
922}944}
923945
946pub fn tokenLocation(
947 utf8_source: []const u8,
948 start_offset: ByteOffset,
949 token_start: ByteOffset,
950) Location {
951 var loc: Location = .{
952 .line = 1,
953 .column = 1,
954 .line_start = start_offset,
955 .line_end = utf8_source.len,
956 };
957
958 // Scan to by line until we go past the token start
959 while (std.mem.indexOfScalarPos(u8, utf8_source, loc.line_start, '\n')) |i| {
960 if (i >= token_start) {
961 break; // Went past
962 }
963 loc.line += 1;
964 loc.line_start = i + 1;
965 }
966
967 const offset = loc.line_start;
968 for (utf8_source[offset..], 0..) |c, i| {
969 if (i + offset == token_start) {
970 loc.line_end = i + offset;
971 while (loc.line_end < utf8_source.len and utf8_source[loc.line_end] != '\n') {
972 loc.line_end += 1;
973 }
974 return loc;
975 }
976 if (c == '\n') {
977 loc.line += 1;
978 loc.column = 1;
979 loc.line_start = i + 1;
980 } else {
981 loc.column += 1;
982 }
983 }
984
985 return loc;
986}
987
924const Tokenizer = @This();988const Tokenizer = @This();
925const std = @import("std");989const std = @import("std");
926const unicode = std.unicode;990const unicode = std.unicode;
...@@ -928,3 +992,4 @@ const assert = std.debug.assert;...@@ -928,3 +992,4 @@ const assert = std.debug.assert;
928const mem = std.mem;992const mem = std.mem;
929const unicode_props = @import("unicode_props.zig");993const unicode_props = @import("unicode_props.zig");
930const T = @import("tracer");994const T = @import("tracer");
995const Location = @import("Ast.zig").Location;
src/compiler/Transform.zig+26-3
...@@ -603,7 +603,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc...@@ -603,7 +603,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc
603 return .void_value;603 return .void_value;
604 };604 };
605605
606 lookup.mutated = true;606 lookup.used = true;
607607
608 return switch (lookup.data) {608 return switch (lookup.data) {
609 .declaration => try t.addInstRef(.{609 .declaration => try t.addInstRef(.{
...@@ -1061,6 +1061,7 @@ fn transformBlockStatement(...@@ -1061,6 +1061,7 @@ fn transformBlockStatement(
1061 const var_decl = t.ast.fullVarDecl(node);1061 const var_decl = t.ast.fullVarDecl(node);
10621062
1063 if (mem.eql(u8, var_decl.id, "_")) {1063 if (mem.eql(u8, var_decl.id, "_")) {
1064 // TODO: relax this restriction into just creating a discard node
1064 try t.report.err(.invalid_discard, .{ .node = node });1065 try t.report.err(.invalid_discard, .{ .node = node });
1065 return .void_value;1066 return .void_value;
1066 }1067 }
...@@ -1101,13 +1102,35 @@ fn transformBlockStatement(...@@ -1101,13 +1102,35 @@ fn transformBlockStatement(
1101 }),1102 }),
1102 });1103 });
11031104
1104 try t.scope().symbols.put(t.gpa, var_decl.id, .{1105 const gop = try t.scope().symbols.getOrPut(t.gpa, var_decl.id);
1106
1107 // if re-declaring, apply unused warnings now
1108 if (gop.found_existing) {
1109 const value = gop.value_ptr.*;
1110 if (!value.used) {
1111 try t.report.warn(.unused, .{
1112 .node = value.node,
1113 .kind = switch (value.data) {
1114 .declaration => .function,
1115 .param => .param,
1116 .mutable => .variable,
1117 .immutable => switch (t.ast.nodeTag(value.node)) {
1118 .param_decl => .param,
1119 else => .constant,
1120 },
1121 },
1122 // .shadowed = node,
1123 });
1124 }
1125 }
1126
1127 gop.value_ptr.* = .{
1105 .node = node,1128 .node = node,
1106 .data = switch (var_decl.is_const) {1129 .data = switch (var_decl.is_const) {
1107 true => .{ .immutable = mem_ref },1130 true => .{ .immutable = mem_ref },
1108 false => .{ .mutable = mem_ref },1131 false => .{ .mutable = mem_ref },
1109 },1132 },
1110 });1133 };
11111134
1112 return .void_value;1135 return .void_value;
1113 },1136 },
src/compiler/main.zig+28-13
...@@ -21,7 +21,9 @@ pub fn main() !void {...@@ -21,7 +21,9 @@ pub fn main() !void {
2121
22 try unicode_props.init();22 try unicode_props.init();
2323
24 std.debug.print("\r\x1b[K", .{});24 if (builtin.mode == .Debug) {
25 std.debug.print("\r\x1b[K", .{});
26 }
2527
26 const use_gpa = builtin.mode == .Debug;28 const use_gpa = builtin.mode == .Debug;
2729
...@@ -54,14 +56,17 @@ pub fn main() !void {...@@ -54,14 +56,17 @@ pub fn main() !void {
5456
55 while (args.next()) |arg| {57 while (args.next()) |arg| {
56 if (std.mem.startsWith(u8, arg, "-")) {58 if (std.mem.startsWith(u8, arg, "-")) {
59 if (builtin.mode == .Debug and std.mem.eql(u8, arg, "--report-trace")) {
60 Report.debug_show_report_trace = true;
61 continue;
62 }
57 if (std.mem.eql(u8, arg, "--dump-ir")) {63 if (std.mem.eql(u8, arg, "--dump-ir")) {
58 dump_ir = true;64 dump_ir = true;
59 } else {65 continue;
60 std.debug.print("error: unknown flag '{s}'\n", .{arg});
61 maybe_file_path = null;
62 break;
63 }66 }
64 continue;67 std.debug.print("error: unknown flag '{s}'\n", .{arg});
68 maybe_file_path = null;
69 break;
65 }70 }
6671
67 if (maybe_file_path == null) {72 if (maybe_file_path == null) {
...@@ -77,13 +82,23 @@ pub fn main() !void {...@@ -77,13 +82,23 @@ pub fn main() !void {
7782
78 const file_path = maybe_file_path orelse {83 const file_path = maybe_file_path orelse {
79 std.debug.print("Usage: {s} [flags] <input file>\n", .{exe_name});84 std.debug.print("Usage: {s} [flags] <input file>\n", .{exe_name});
80 std.debug.print(85 {
81 // | 60 chars wrap point|86 std.debug.print(
82 \\87 // | 60 chars wrap point|
83 \\Flags:88 \\
84 \\ --dump-ir Dump intermediate representations.89 \\Flags:
85 \\90 \\ --dump-ir Dump intermediate representations.
86 , .{});91 \\
92 , .{});
93 }
94 if (builtin.mode == .Debug) {
95 std.debug.print(
96 \\
97 \\Debug Mode Flags:
98 \\ --report-trace Print stack traces per error. Very noisy
99 \\
100 , .{});
101 }
87 std.process.exit(1);102 std.process.exit(1);
88 };103 };
89104