From 4cd49040184a9da68e9584b71ac21f9e19723d59 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 22 May 2024 01:23:06 -0700 Subject: [PATCH] some bugfixes --- CHANGELOG.md | 7 ++ build.zig | 25 +++-- src/compiler/Ast.zig | 77 +-------------- src/compiler/Comp.zig | 8 +- src/compiler/JsLink.zig | 8 +- src/compiler/JsNodeGen.zig | 20 ++-- src/compiler/Parse.zig | 192 +++++++++++++++++++++++++++++-------- src/compiler/Report.zig | 156 ++++++++++++++++++++++++++---- src/compiler/Sema.zig | 24 +++-- src/compiler/Teg.Dump.zig | 9 +- src/compiler/Tokenizer.zig | 181 +++++++++++++++++++++++----------- src/compiler/Transform.zig | 29 +++++- src/compiler/main.zig | 41 +++++--- 13 files changed, 537 insertions(+), 240 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4389e56ab2b4d379f6cbd79c595173967eae089..607b137ce9acc3d27db45f0d4eb15b8ea24a5e05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 0.1.2 + +- feat: parsing of assignments within expressions is disallowed now +- feat: debug builds have `--report-trace` to print stack traces +- fix: unused warnings now properly work +- fix: empty files and functions no longer crash + ## 0.1.1 - feat: `var` declarations / runtime memory diff --git a/build.zig b/build.zig index 40a4cad33305d73b4236a37d9dd0f3270b737d05..5bc5f92e3880af4528727e2f504297444d4d227a 100644 --- a/build.zig +++ b/build.zig @@ -1,4 +1,7 @@ const std = @import("std"); +const Build = std.Build; +const Target = std.Target; +const OsVersion = Target.Query.OsVersion; pub fn build(b: *std.Build) void { b.reference_trace = 20; @@ -28,6 +31,7 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/compiler/main.zig"), .target = target, .optimize = optimize, + .link_libc = true, }); b.installArtifact(exe); @@ -67,13 +71,17 @@ pub fn build(b: *std.Build) void { // wasm_api.root_module.addImport("zg:PropsData", zg.module("PropsData")); const release = b.step("release", "Package releases"); + const linux_version: OsVersion = .{ .semver = .{ .major = 1, .minor = 0, .patch = 0 } }; + const mac_version: OsVersion = .{ .semver = .{ .major = 11, .minor = 0, .patch = 0 } }; + const win_version: OsVersion = .{ .windows = .win7 }; + inline for (.{ - b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl }), - b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl }), - b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .macos }), - b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .macos }), - b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows }), - b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .windows }), + b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version }), + b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .musl, .os_version_min = linux_version }), + b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .macos, .os_version_min = mac_version }), + b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .macos, .os_version_min = mac_version }), + b.resolveTargetQuery(.{ .cpu_arch = .x86_64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu }), + b.resolveTargetQuery(.{ .cpu_arch = .aarch64, .os_tag = .windows, .os_version_min = win_version, .abi = .gnu }), }) |cross_target| { const name = std.fmt.allocPrint( b.allocator, @@ -84,19 +92,24 @@ pub fn build(b: *std.Build) void { }, ) catch @panic("OOM"); + // std.log.info("{s}\n", .{cross_target.result.zigTriple(b.allocator) catch @panic("OOM")}); + const cross_exe = b.addExecutable(.{ .name = "compiler", .root_source_file = b.path("src/compiler/main.zig"), .target = cross_target, .optimize = .ReleaseSafe, .strip = true, + .link_libc = true, }); + cross_exe.root_module.addImport("build_options", opts_mod); cross_exe.root_module.addImport("zg:PropsData", zg.module("PropsData")); cross_exe.root_module.addImport("tracer", tracer.module("tracer")); release.dependOn(&b.addInstallFile( cross_exe.getEmittedBin(), b.pathJoin(&.{ + "release", name, std.mem.concat(b.allocator, u8, &.{ "compiler", diff --git a/src/compiler/Ast.zig b/src/compiler/Ast.zig index cd8b54a59709fc63bca77f2281e9524be219cbe0..f1eb29fcf62758e5e113d3a4e1573cf1b7d18c57 100644 --- a/src/compiler/Ast.zig +++ b/src/compiler/Ast.zig @@ -40,48 +40,6 @@ tokens: Token.List.Slice, nodes: Node.List.Slice, extra: []const u32, -const Parse = @import("Parse.zig"); -pub fn parse( - gpa: std.mem.Allocator, - source: [:0]const u8, - ast_error: *Parse.AstError, -) Parse.Error!Ast { - var err_byte: ByteOffset = 0; - var tokens = Tokenizer.tokenize( - source, - gpa, - &err_byte, - ) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - ast_error.* = .{ .tokenization = .{ - .err = err, - .byte = err_byte, - } }; - return error.ParseFail; - }, - }; - errdefer tokens.deinit(gpa); - - var parser = Parse.init(gpa, source, tokens); - errdefer parser.deinit(); - - try parser.nodes.ensureUnusedCapacity( - gpa, - @max(1, @divFloor(tokens.len, 4)), - ); - - parser.parseRoot() catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ParseFail => { - ast_error.* = parser.ast_error.?; - return error.ParseFail; - }, - }; - - return try parser.finalize(tokens); -} - pub const Node = struct { tag: Tag, main_token: TokenIndex, @@ -645,41 +603,8 @@ pub fn nodeStatements(ast: *const Ast, node: NodeIndex) []const NodeIndex { } pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenIndex) Location { - var loc = Location{ - .line = 1, - .column = 1, - .line_start = start_offset, - .line_end = self.utf8_source.len, - }; const token_start = self.tokens.items(.offset)[token_index]; - - // Scan to by line until we go past the token start - while (std.mem.indexOfScalarPos(u8, self.utf8_source, loc.line_start, '\n')) |i| { - if (i >= token_start) { - break; // Went past - } - loc.line += 1; - loc.line_start = i + 1; - } - - const offset = loc.line_start; - for (self.utf8_source[offset..], 0..) |c, i| { - if (i + offset == token_start) { - loc.line_end = i + offset; - while (loc.line_end < self.utf8_source.len and self.utf8_source[loc.line_end] != '\n') { - loc.line_end += 1; - } - return loc; - } - if (c == '\n') { - loc.line += 1; - loc.column = 1; - loc.line_start = i + 1; - } else { - loc.column += 1; - } - } - return loc; + return Tokenizer.tokenLocation(self.utf8_source, start_offset, token_start); } pub fn tokenSlice(ast: Ast, token_index: TokenIndex) []const u8 { diff --git a/src/compiler/Comp.zig b/src/compiler/Comp.zig index 2d44f1c7be87c13506cec703d2f31b6ebdf3eeab..69546f10eb12e928d89aea708d4df15de2e21765 100644 --- a/src/compiler/Comp.zig +++ b/src/compiler/Comp.zig @@ -129,16 +129,16 @@ pub const File = struct { return ast; } - var ast_err: Parse.AstError = undefined; - const ast = Ast.parse( + var err_data: Parse.AstError.WithTrace = undefined; + const ast = Parse.parse( comp.gpa, (try file.getSource(comp.gpa)).code, - &ast_err, + &err_data, ) catch |err| switch (err) { error.OutOfMemory => return err, error.ParseFail => { - var report = try ast_err.toReport(comp, file); // TODO: let this merge with other report + var report = try err_data.toReport(comp, file); report.dumpToStdErr(comp) catch {}; report.deinit(comp.gpa); return err; diff --git a/src/compiler/JsLink.zig b/src/compiler/JsLink.zig index 116a5c00c0026be6e22d6745da8362d1867e1d90..4ba12b29c4c85af7185b78f3eba8f7313c70c97e 100644 --- a/src/compiler/JsLink.zig +++ b/src/compiler/JsLink.zig @@ -69,7 +69,7 @@ pub fn linkJsCode( try link.decls.putNoClobber(comp.gpa, decl_index, @enumFromInt(gop.index)); } - try writer.print("// Language Compiler v0.1.1. Alpha quality software.\n", .{}); + try writer.print("// Language Compiler v0.1.2. Alpha quality software.\n", .{}); // 2. Print every function decl for (decls[1..]) |decl_index| { @@ -77,7 +77,11 @@ pub fn linkJsCode( } // 3. Print the top-level code - try link.printDeclContents(&root_decl.codegen.?, writer); + if (root_decl.codegen.?.nodes.len > 1) { + try link.printDeclContents(&root_decl.codegen.?, writer); + } else { + try writer.print("// (program has no observable effects, thus an empty file)\n", .{}); + } } fn deinit(link: *JsLink) void { diff --git a/src/compiler/JsNodeGen.zig b/src/compiler/JsNodeGen.zig index 2e57ba65862a96b28a570bd49a4e119303c5c7e0..4ddaf23628c137251b9c2be82e418e024326fe26 100644 --- a/src/compiler/JsNodeGen.zig +++ b/src/compiler/JsNodeGen.zig @@ -107,16 +107,18 @@ pub fn build(c: *JsNodeGen) !void { _ = try c.add(.root, undefined); - for (statements[0..c.param_count], 1..) |stmt, expected_id| { - assert(@intFromEnum(stmt) == expected_id); - assert(c.air.instTag(stmt) == .arg); - c.air_node_map[@intFromEnum(stmt)] = .{ - .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }), - }; - } + if (statements.len > 0) { + for (statements[0..c.param_count], 1..) |stmt, expected_id| { + assert(@intFromEnum(stmt) == expected_id); + assert(c.air.instTag(stmt) == .arg); + c.air_node_map[@intFromEnum(stmt)] = .{ + .node = try c.add(.local, .{ .id = @intFromEnum(stmt) - 1 }), + }; + } - for (statements[c.param_count..]) |stmt| { - try c.codeGenInst(stmt); + for (statements[c.param_count..]) |stmt| { + try c.codeGenInst(stmt); + } } const items = c.scratch.items[scratch_top..]; diff --git a/src/compiler/Parse.zig b/src/compiler/Parse.zig index e7ff73382438728b9af4bbc487431fd3ff746a64..ba02a1b6584f7d4b9028af3d3d07f12b9e656646 100644 --- a/src/compiler/Parse.zig +++ b/src/compiler/Parse.zig @@ -10,9 +10,49 @@ nodes: Node.List = .{}, extra: ArrayListUnmanaged(u32) = .{}, scratch: ArrayListUnmanaged(NodeIndex) = .{}, -ast_error: ?AstError = null, +ast_error: ?AstError.WithTrace = null, -pub fn init(gpa: Allocator, source: [:0]const u8, tokens: Token.List.Slice) Parse { +pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8, ast_error: *AstError.WithTrace) Parse.Error!Ast { + var err_byte: ByteOffset = 0; + var tokens = Tokenizer.tokenize( + source, + gpa, + &err_byte, + ) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + ast_error.* = .{ + .err = .{ .tokenization = .{ + .err = err, + .byte = err_byte, + } }, + .trace = Report.DebugStoredTrace.from(@errorReturnTrace()), + }; + return error.ParseFail; + }, + }; + errdefer tokens.deinit(gpa); + + var parser = Parse.init(gpa, source, tokens); + errdefer parser.deinit(); + + try parser.nodes.ensureUnusedCapacity( + gpa, + @max(1, @divFloor(tokens.len, 4)), + ); + + parser.parseRoot() catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ParseFail => { + ast_error.* = parser.ast_error.?; + return error.ParseFail; + }, + }; + + return try parser.finalize(tokens); +} + +fn init(gpa: Allocator, source: [:0]const u8, tokens: Token.List.Slice) Parse { return Parse{ .gpa = gpa, .source = source, @@ -22,7 +62,7 @@ pub fn init(gpa: Allocator, source: [:0]const u8, tokens: Token.List.Slice) Pars } /// This transfers the parse result into an Ast -pub fn finalize(p: *Parse, token_slice: Token.List.Slice) !Ast { +fn finalize(p: *Parse, token_slice: Token.List.Slice) !Ast { const extra = try p.extra.toOwnedSlice(p.gpa); defer p.* = undefined; 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 { } /// This discards all parser work -pub fn deinit(p: *Parse) void { +fn deinit(p: *Parse) void { defer p.* = undefined; p.nodes.deinit(p.gpa); p.extra.deinit(p.gpa); @@ -52,19 +92,46 @@ pub const Error = error{ ParseFail, }; +pub const ExpectedContext = enum { + param, + argument, + statement, + expression, + else_clause, + + pub fn description(tag: ExpectedContext) []const u8 { + return switch (tag) { + .param => "a parameter", + .argument => "an argument", + .statement => "a statement", + .expression => "an expression", + .else_clause => "an else clause", + }; + } +}; + /// When error.SyntaxError is returned, `p.err` is set to this as well as `p.pos` +/// When printing these, token list and Ast is not actually available, so full tokens +/// are stored in the errors. pub const AstError = union(enum) { + /// Failure during tokenization tokenization: struct { err: Tokenizer.TokenizeError, - byte: u32, + byte: ByteOffset, + }, + /// Unexpected data, expected a token. + unexpected_token: struct { + expected: Token.Tag, + got: Token, + }, + /// Unexpected data, expected + unexpected_context: struct { + expected: ExpectedContext, + got: Token, }, - expected_token: Token.Tag, - expected_statement, - expected_expr, - expected_else_value, - pub fn toReport(parse_err: AstError, comp: *Comp, file: *Comp.File) !Report { - return (Report.Err{ .ast = parse_err }).toReport(comp, file); + pub fn toReport(err: AstError, comp: *Comp, file: *Comp.File, trace: Report.DebugStoredTrace) !Report { + return (Report.Err{ .ast = err }).toReport(comp, file, trace); } pub fn build(err: AstError, ctx: *Report.MsgContext) !void { @@ -86,11 +153,29 @@ pub const AstError = union(enum) { error.GitConflict => "git merge conflict encountered", }}); }, - - else => unreachable, - // + .unexpected_token => |data| { + try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{ + data.expected.description(), + data.got.tag.description(), + }); + }, + .unexpected_context => |data| { + try ctx.msg(.{ .token_stored = data.got }, "expected {s}, but found {s}", .{ + data.expected.description(), + data.got.tag.description(), + }); + }, } } + + pub const WithTrace = struct { + err: AstError, + trace: Report.DebugStoredTrace, + + pub fn toReport(err: WithTrace, comp: *Comp, file: *Comp.File) !Report { + return err.err.toReport(comp, file, err.trace); + } + }; }; pub fn parseRoot(p: *Parse) Error!void { @@ -141,10 +226,10 @@ fn parseModuleMembers(p: *Parse) Error!SubList { else => { if (!p.isExpressionStart()) { - return p.fail(.expected_statement); + return p.failUnexpectedToken(.statement); } - try p.scratch.append(p.gpa, try p.parseExpression()); + try p.scratch.append(p.gpa, try p.parseExpression(true)); _ = try p.expect(.semi); }, }; @@ -180,7 +265,7 @@ fn parseFnDecl(p: *Parse) Error!NodeIndex { const params = try p.parseParamList(); const return_type = if (p.eatToken(.minus_lt)) |_| - try p.parseExpression() + try p.parseExpression(false) else null; @@ -222,7 +307,7 @@ fn parseParamList(p: *Parse) Error!SubList { _ = p.nextToken(); }, .end_paren => break, - else => return p.fail(.{ .expected_token = .comma }), + else => return p.failUnexpectedToken(.param), } } @@ -239,13 +324,13 @@ fn parseParamDecl(p: *Parse) Error!NodeIndex { _ = try p.expect(.colon); - const @"type" = try p.parseExpression(); + const ty = try p.parseExpression(false); return try p.addNode(.{ .tag = .param_decl, .main_token = main_token, .data = .{ .param_decl = .{ - .type = @"type", + .type = ty, } }, }); } @@ -274,7 +359,7 @@ fn parseLabelExpression(p: *Parse) Error!NodeIndex { const main_token = p.assertToken(.id); _ = try p.expect(.colon); - const expr = try p.parseExpression(); + const expr = try p.parseExpression(false); return try p.addNode(.{ .tag = .label, .main_token = main_token, @@ -301,12 +386,12 @@ fn parseBlockContents(p: *Parse) Error!struct { Node.Tag, SubList } { else => { if (!p.isExpressionStart()) { - return p.fail(.expected_statement); + return p.failUnexpectedToken(.statement); } const first_tag = p.currentTag(); - try p.scratch.append(p.gpa, try p.parseExpression()); + try p.scratch.append(p.gpa, try p.parseExpression(true)); switch (first_tag) { // some control flow and blocks are allowed to not have a @@ -352,13 +437,13 @@ fn parseVarDecl(p: *Parse) Error!NodeIndex { _ = try p.expect(.id); const data_type: OptionalNodeIndex = if (p.eatToken(.colon)) |_| - optionalNodeIndex(try p.parseExpression()) + optionalNodeIndex(try p.parseExpression(false)) else .none; _ = try p.expect(.equal); - const data_init = try p.parseExpression(); + const data_init = try p.parseExpression(true); _ = try p.expect(.semi); @@ -412,6 +497,7 @@ const Operator = enum(u8) { le = @intFromEnum(Token.Tag.lt_equal), gt = @intFromEnum(Token.Tag.gt), ge = @intFromEnum(Token.Tag.gt_equal), + // Following operators are assignment operators assign = @intFromEnum(Token.Tag.equal), add_assign = @intFromEnum(Token.Tag.plus_equal), sub_assign = @intFromEnum(Token.Tag.minus_equal), @@ -551,9 +637,13 @@ const Operator = enum(u8) { assert(tag != .root); // invalid operator or lut construction return tag; } + + pub fn isAssign(op: Operator) bool { + return @intFromEnum(op) >= @intFromEnum(Operator.assign); + } }; -fn parseExpression(p: *Parse) Error!NodeIndex { +fn parseExpression(p: *Parse, allow_assign_operators: bool) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -569,7 +659,8 @@ fn parseExpression(p: *Parse) Error!NodeIndex { var expr = try p.parseExpressionPart(); while (Operator.fromToken(p.currentTag())) |op| { - expr = try p.parseBinarySuffix(expr, op); + if (!allow_assign_operators and op.isAssign()) break; + expr = try p.parseBinarySuffix(expr, op, allow_assign_operators); } return expr; @@ -616,7 +707,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex { }); }, - else => return p.fail(.expected_expr), + else => return p.failUnexpectedToken(.expression), }; // parse field, calls, and index expressions @@ -657,7 +748,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex { }, .start_bracket => { const main_token = p.nextToken(); - const index = try p.parseExpression(); + const index = try p.parseExpression(false); expr = try p.addNode(.{ .tag = .index, @@ -676,7 +767,7 @@ fn parseExpressionPart(p: *Parse) Error!NodeIndex { return expr; } -fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) Error!NodeIndex { +fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator, allow_assign_operators: bool) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -685,6 +776,8 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) Error!NodeIndex { var rhs = try p.parseExpressionPart(); while (Operator.fromToken(p.currentTag())) |next_op| { + if (!allow_assign_operators and next_op.isAssign()) break; + const new_level = next_op.level(); const use = switch (op.direction()) { .ltr => @intFromEnum(new_level) < @intFromEnum(level), @@ -692,7 +785,7 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) Error!NodeIndex { }; if (!use) break; - rhs = try p.parseBinarySuffix(rhs, next_op); + rhs = try p.parseBinarySuffix(rhs, next_op, allow_assign_operators); } return try p.addNode(.{ @@ -712,7 +805,7 @@ fn parseReturn(p: *Parse) Error!NodeIndex { const main_token = p.assertToken(.kw_return); const expr = if (p.isExpressionStart()) - try p.parseExpression() + try p.parseExpression(false) else null; @@ -737,7 +830,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex { null; const expr = if (p.isExpressionStart()) - try p.parseExpression() + try p.parseExpression(false) else null; @@ -776,7 +869,7 @@ fn parseCallArgs(p: *Parse) Error!SubList { defer p.scratch.shrinkRetainingCapacity(scratch_top); while (p.isExpressionStart()) { - try p.scratch.append(p.gpa, try p.parseExpression()); + try p.scratch.append(p.gpa, try p.parseExpression(false)); switch (p.currentTag()) { .end_paren => { @@ -787,7 +880,7 @@ fn parseCallArgs(p: *Parse) Error!SubList { if (p.currentTag() == .end_paren) break; }, - else => return p.fail(.{ .expected_token = .end_paren }), + else => return p.failUnexpectedToken(.argument), } } @@ -802,7 +895,7 @@ fn parseIf(p: *Parse) Error!NodeIndex { const main_token = p.assertToken(.kw_if); - const cond = try p.parseExpression(); + const cond = try p.parseExpression(false); _ = try p.expectWithoutAdvance(.start_brace); const yes = try p.parseBlock(); @@ -838,7 +931,7 @@ fn parseElseValue(p: *Parse) Error!NodeIndex { .kw_if => p.parseIf(), .id => p.parseLabelExpression(), - else => return p.fail(.expected_else_value), + else => return p.failUnexpectedToken(.else_clause), }; } @@ -848,7 +941,7 @@ fn parseGroup(p: *Parse) Error!NodeIndex { const main_token = p.assertToken(.start_paren); - const expr = try p.parseExpression(); + const expr = try p.parseExpression(false); _ = try p.expect(.end_paren); @@ -885,11 +978,20 @@ fn addExtra(p: *Parse, comptime T: type, extra: T) !ExtraIndex { } fn fail(p: *Parse, ast_error: AstError) Error { - p.ast_error = ast_error; - std.debug.print("got {s}", .{@tagName(p.currentTag())}); + p.ast_error = .{ + .err = ast_error, + .trace = Report.DebugStoredTrace.capture(@returnAddress()), + }; return Error.ParseFail; } +fn failUnexpectedToken(p: *Parse, kind: ExpectedContext) Error { + return p.fail(.{ .unexpected_context = .{ + .got = p.fullToken(p.pos), + .expected = kind, + } }); +} + fn blankNode(p: *Parse, comptime tag: Token.Tag) !NodeIndex { const main_token = p.assertToken(tag); return try p.addNode(.{ @@ -917,14 +1019,20 @@ fn eatToken(p: *Parse, tag: Token.Tag) ?TokenIndex { fn expect(p: *Parse, tag: Token.Tag) !TokenIndex { if (p.currentTag() != tag) { - return p.fail(.{ .expected_token = tag }); + return p.fail(.{ .unexpected_token = .{ + .got = p.fullToken(p.pos), + .expected = tag, + } }); } return p.nextToken(); } fn expectWithoutAdvance(p: *Parse, tag: Token.Tag) !void { if (p.currentTag() != tag) { - return p.fail(.{ .expected_token = tag }); + return p.fail(.{ .unexpected_token = .{ + .got = p.fullToken(p.pos), + .expected = tag, + } }); } } diff --git a/src/compiler/Report.zig b/src/compiler/Report.zig index b6a01561c3b55313a84f3a8a603b553210f7eba5..7b595efe01e95cd9f076ab258e1a476ada81f4b3 100644 --- a/src/compiler/Report.zig +++ b/src/compiler/Report.zig @@ -9,27 +9,89 @@ errors: []Item = &.{}, warnings: []Item = &.{}, string_bytes: []u8 = &.{}, +pub var debug_show_report_trace: if (builtin.mode == .Debug) bool else void = + if (builtin.mode == .Debug) false; + pub const Item = struct { /// What is this diagnostic data: union { @"error": Err, warn: Warn }, /// What file was it in url: String, + debug_stack_trace: DebugStoredTrace, + /// Type is never stored, inferred from which list it is in. pub const Type = enum { @"error", warn }; }; +/// A variant of std.builtin.StackTrace that stores it's data within itself +/// instead of being a pointer. This allows capturing the stack trace and +/// saving it for later printing. +pub const StoredTrace = struct { + data: [32]usize, + index: usize, + + pub const empty = .{ + .data = .{0} ** 32, + .index = 0, + }; + + fn trace(stored: *StoredTrace) std.builtin.StackTrace { + return .{ + .index = stored.index, + .instruction_addresses = &stored.data, + }; + } + + pub fn capture(begin: ?usize) StoredTrace { + var stored: StoredTrace = undefined; + var frame = stored.trace(); + std.debug.captureStackTrace(begin orelse @returnAddress(), &frame); + stored.index = frame.index; + return stored; + } + + pub fn from(stack_trace: ?*std.builtin.StackTrace) StoredTrace { + if (stack_trace) |stack| { + var data: [32]usize = undefined; + @memset(&data, 0); + const items = @min(stack.instruction_addresses.len, 32); + @memcpy(data[0..items], stack.instruction_addresses[0..items]); + return .{ + .data = data, + .index = @min(items, stack.index), + }; + } else { + return empty; + } + } +}; + +pub const DebugStoredTrace = if (builtin.mode == .Debug) + StoredTrace +else + struct { + pub fn capture() @This() { + return .{}; + } + + pub fn from(_: ?*std.builtin.StackTrace) @This() { + return .{}; + } + }; + pub const Err = union(enum) { pub const FieldEnum = std.meta.FieldEnum(Err); /// Constructs a `Report` with just a single error. - pub fn toReport(err: Err, comp: *Comp, file: *File) !Report { + pub fn toReport(err: Err, comp: *Comp, file: *File, trace: DebugStoredTrace) !Report { const string_bytes = try comp.gpa.dupe(u8, file.file_path); errdefer comp.gpa.free(string_bytes); const errors = try comp.gpa.dupe(Item, &.{.{ .data = .{ .@"error" = err }, .url = .{ .start = 0, .len = @intCast(string_bytes.len) }, + .debug_stack_trace = trace, }}); errdefer comp.gpa.free(errors); @@ -42,7 +104,7 @@ pub const Err = union(enum) { node: NodeIndex, fn build(data: @This(), ctx: *MsgContext) !void { - try ctx.msgNode(data.node, "unknown identifier '{s}'", .{ + try ctx.msgNode(data.node, "unknown identifier \"{s}\"", .{ ctx.ast.basicNodeSlice(data.node), }); } @@ -53,7 +115,7 @@ pub const Err = union(enum) { fn build(data: @This(), ctx: *MsgContext) !void { try ctx.msgToken( ctx.ast.nodeToken(data.node), - "unknown built-in function '{s}'", + "unknown built-in function \"{s}\"", .{ctx.ast.basicNodeSlice(data.node)}, ); } @@ -62,7 +124,10 @@ pub const Err = union(enum) { node: NodeIndex, fn build(data: @This(), ctx: *MsgContext) !void { - try ctx.msgNode(data.node, "local cannot be named '_'", .{}); + try ctx.msgToken(ctx.ast.idToken(data.node), "local cannot be named \"_\"", .{}); + try ctx.msgAlone("to discard this value, remove '{s}'", .{ + ctx.ast.tokenSlice(ctx.ast.nodeToken(data.node)), + }); } }, duplicate_function: struct { @@ -70,7 +135,7 @@ pub const Err = union(enum) { prev: NodeIndex, fn build(data: @This(), ctx: *MsgContext) !void { - try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of '{s}'", .{ + try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of \"{s}\"", .{ ctx.ast.fullFnDecl(data.node).id, }); try ctx.msgToken(ctx.ast.fnDeclIdToken(data.prev), "also declared here", .{}); @@ -83,7 +148,7 @@ pub const Err = union(enum) { prev: NodeIndex, fn build(data: @This(), ctx: *MsgContext) !void { - try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of '{s}'", .{ + try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of \"{s}\"", .{ ctx.ast.fullParamDecl(data.node).id, }); try ctx.msgToken(ctx.ast.nodeToken(data.prev), "also declared here", .{}); @@ -108,7 +173,7 @@ pub const Err = union(enum) { fn build(data: @This(), ctx: *MsgContext) !void { try ctx.msg( .{ .two_tokens = ctx.ast.nodeToken(data.node) + 1 }, - "unknown block label '{s}'", + "unknown block label \"{s}\"", .{ctx.ast.tokenSlice(ctx.ast.nodeToken(data.node) + 2)}, ); } @@ -119,7 +184,7 @@ pub const Err = union(enum) { fn build(data: @This(), ctx: *MsgContext) !void { try ctx.msgToken( ctx.ast.nodeToken(data.node) + 1, - "declaration shadows built-in constant '{s}'", + "declaration shadows reserved built-in \"{s}\"", .{ctx.ast.fullVarDecl(data.node).id}, ); } @@ -132,17 +197,28 @@ pub const Warn = union(enum) { unused: struct { node: NodeIndex, kind: UnusedKind, + // shadowed: ?NodeIndex = null, fn build(data: @This(), ctx: *MsgContext) !void { - try ctx.msgToken(ctx.ast.idToken(data.node), "unused {s} '{s}'", .{ + const id = switch (ctx.ast.nodeTag(data.node)) { + .param_decl => ctx.ast.fullParamDecl(data.node).id, + .fn_decl => ctx.ast.fullFnDecl(data.node).id, + .var_decl => ctx.ast.fullVarDecl(data.node).id, + else => unreachable, // not a valid symbol node + }; + + try ctx.msgToken(ctx.ast.idToken(data.node), "unused {s} \"{s}\"", .{ @tagName(data.kind), - switch (ctx.ast.nodeTag(data.node)) { - .param_decl => ctx.ast.fullParamDecl(data.node).id, - .fn_decl => ctx.ast.fullFnDecl(data.node).id, - .var_decl => ctx.ast.fullVarDecl(data.node).id, - else => unreachable, // not a valid symbol node - }, + id, }); + + // if (data.shadowed) |node| { + // try ctx.msgToken(ctx.ast.idToken(node), "{s} was re-declared here", .{ + // @tagName(data.kind), + // }); + // } + + try ctx.msgAlone("if this was intentional, discard it with \"_ = {s};\"", .{id}); } }, }; @@ -217,6 +293,7 @@ pub fn formatItem( .comp = comp, .file = file, .ast = &ast_data, + .utf8_source = (try file.getSource(comp.gpa)).code, .components = ArrayListUnmanaged(Component).initCapacity(alloc, 16) catch unreachable, // stack-fallback allocator has enough space. }; @@ -246,6 +323,7 @@ pub fn formatItem( } try tty.setColor(writer, prefix.color()); + try tty.setColor(writer, .bold); try writer.writeAll(@tagName(prefix)); try tty.setColor(writer, .reset); try tty.setColor(writer, .dim); @@ -260,9 +338,18 @@ pub fn formatItem( slice: []const u8, }; - const utf8_source = (try file.getSource(comp.gpa)).code; + const utf8_source = ctx.utf8_source; const at: SliceLoc = switch (component.highlight) { + .alone => { + if (is_last) { + try tty.setColor(writer, .dim); + try writer.print(" {s}", .{"┴"}); + try writer.writeAll("\n"); + try tty.setColor(writer, .reset); + } + continue; + }, inline .node, .token, .two_tokens, @@ -283,6 +370,16 @@ pub fn formatItem( }; }, + .token_stored => |token| loc: { + const location = Tokenizer.tokenLocation(ctx.utf8_source, 0, token.offset); + + break :loc .{ + .loc = location, + .slice = token.slice(utf8_source), + .byte = token.offset, + }; + }, + inline .codepoint, .codepoint_eol, => |b, tag| loc: { @@ -344,17 +441,25 @@ pub fn formatItem( try tty.setColor(writer, .reset); try writer.writeAll("\n"); } + + if (builtin.mode == .Debug and debug_show_report_trace) { + var trace = item.debug_stack_trace; + std.debug.dumpStackTrace(trace.trace()); + } } pub fn dumpToStdErr(report: Report, comp: *Comp) !void { - const builtin = @import("builtin"); - const stderr = std.io.getStdErr(); const tty = std.io.tty.detectConfig(stderr); - if (builtin.os.tag == .windows and tty == .windows_api) { + if (false or // Windows API uses it's own API calls, so a buffered writer // cannot be used :/ + (builtin.os.tag == .windows and tty == .windows_api) or + // Debug stack trace printer writes directly to standard err, + // so we need to be unbuffered for that. + (builtin.mode == .Debug and debug_show_report_trace)) + { try dumpToWriter(report, comp, stderr.writer(), tty); } else { var buf = std.io.bufferedWriter(stderr.writer()); @@ -376,6 +481,7 @@ pub const Builder = struct { try b.errors.append(b.gpa, .{ .data = .{ .@"error" = @unionInit(Err, @tagName(kind), data) }, .url = try b.currentFileString(), + .debug_stack_trace = if (builtin.mode == .Debug) StoredTrace.capture(@returnAddress()) else .{}, }); } @@ -383,6 +489,7 @@ pub const Builder = struct { try b.warnings.append(b.gpa, .{ .data = .{ .warn = @unionInit(Warn, @tagName(kind), data) }, .url = try b.currentFileString(), + .debug_stack_trace = if (builtin.mode == .Debug) StoredTrace.capture(@returnAddress()) else .{}, }); } @@ -426,8 +533,12 @@ pub const Builder = struct { }; pub const Highlight = union(enum) { + // No source view + alone, // Highlight one token token: TokenIndex, + // Highlight one token without referring to the Ast + token_stored: Tokenizer.Token, // Highlight two tokens two_tokens: TokenIndex, // Highlight a Node @@ -448,6 +559,7 @@ pub const MsgContext = struct { file: *File, comp: *Comp, ast: *const Ast, + utf8_source: [:0]const u8, /// This arena is is a stack fallback with 16kb, then /// falling back to an arena allocator. This should @@ -474,6 +586,10 @@ pub const MsgContext = struct { pub fn msgToken(m: *MsgContext, token: TokenIndex, comptime fmt: []const u8, args: anytype) !void { return m.msg(.{ .token = token }, fmt, args); } + + pub fn msgAlone(m: *MsgContext, comptime fmt: []const u8, args: anytype) !void { + return m.msg(.alone, fmt, args); + } }; const Report = @This(); @@ -482,6 +598,8 @@ const ArrayListUnmanaged = std.ArrayListUnmanaged; const Allocator = std.mem.Allocator; const assert = std.debug.assert; +const builtin = @import("builtin"); + const Tokenizer = @import("Tokenizer.zig"); const ByteOffset = Tokenizer.ByteOffset; diff --git a/src/compiler/Sema.zig b/src/compiler/Sema.zig index 937555b7d476d35755b8b262b603d3dc958d0ba7..113cc4adc395ca6e7c3b1cbe3ec4ea4399760397 100644 --- a/src/compiler/Sema.zig +++ b/src/compiler/Sema.zig @@ -130,11 +130,15 @@ pub fn analyzeDeclaration(s: *Sema) anyerror!Value { const return_type = try s.resolveRefComptime(fn_decl.return_type); - const root = (try s.analyzeBlock( - fn_decl.body.toIndex() orelse - @panic("TODO: please change this to Index. Ref is not allowed here anymore"), - )).decode().inst; - assert(@intFromEnum(root) == 0); + const root_block = fn_decl.body.toIndex() orelse + @panic("TODO: please change this to Index. Ref is not allowed here anymore"); + + _ = if (s.code.instPayload(.block, root_block).len > 0) + try s.analyzeBlock(root_block) + else empty_block: { + var root = try s.addBlock(); + break :empty_block (try root.finish(s, s.air_instructions.len - 1)); + }; const func_prototype = try s.ip.get(s.gpa, .{ .type_function = .{ @@ -384,6 +388,14 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { } return InternPool.Index.void_value.toAir(); }, + .discard => { + const operand = try s.resolveRefRuntimeExtra(s.code.instData(.discard, in).operand, false); + const t = Value.fromInterned(s.typeOfRef(operand)); + if (t.ip_index == .void_value) { + std.debug.print("oh no, we have a useless _ = void;\n", .{}); + } + return InternPool.Index.void_value.toAir(); + }, .@"if" => { const data: Teg.Inst.If = s.code.instData(.@"if", in).extra_index.get(&s.code); @@ -627,7 +639,6 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Air.Inst.Ref { .float = result, })).toAir(); }, - inline .lt, .gt, => |tag| { @@ -643,7 +654,6 @@ fn comptimeBin(s: *Sema, op: BinaryOp, lhs: Value, rhs: Value) !Air.Inst.Ref { if (result) .bool_true else .bool_false, ).toAir(); }, - .eq, => { const l = lhs.toFloat(s.ip) orelse @panic("TODO: typecheck / non-numeric eq"); diff --git a/src/compiler/Teg.Dump.zig b/src/compiler/Teg.Dump.zig index b9360febed1a4c8e88050d2ef091230ac658199a..011fbace39b14b6c2c928ca8006144d97342f9a1 100644 --- a/src/compiler/Teg.Dump.zig +++ b/src/compiler/Teg.Dump.zig @@ -120,6 +120,14 @@ const State = struct { try s.dumpRefMarker(writer, data.rhs); try writer.writeAll(")"); }, + .as => { + const data = teg.instData(.as, inst).extra_index.get(teg); + try writer.writeAll("("); + try s.dumpRefMarker(writer, data.type); + try writer.writeAll(", "); + try s.dumpRefMarker(writer, data.operand); + try writer.writeAll(")"); + }, .@"break" => { const data: Teg.Inst.Break = teg.instData(.@"break", inst).extra_index.get(teg); try writer.writeAll("("); @@ -192,7 +200,6 @@ const State = struct { try s.newline(writer); try writer.writeAll("}"); }, - else => std.debug.panic("TODO format {s}", .{@tagName(tag)}), } } diff --git a/src/compiler/Tokenizer.zig b/src/compiler/Tokenizer.zig index b80108b8b71b73507d62d2351cb9b90825eb144f..770d1f980c5a1a8edf73fa557b215ebe7a823f43 100644 --- a/src/compiler/Tokenizer.zig +++ b/src/compiler/Tokenizer.zig @@ -146,10 +146,13 @@ pub const Token = struct { /// Used so that parser does not need to do out of bounds checks eof, - /// Most tokens have a static length, such as "struct" or "+=" - pub fn staticLen(tag: Tag) ?u32 { + /// Most tokens have statically known lexeme, such as "struct" or "+=" + pub fn lexeme(tag: Tag) ?[]const u8 { return switch (tag) { - // do not have a static length + .invalid => unreachable, + + // do not have a static lexeme + .eof, .id, .builtin_id, .number_int, @@ -160,68 +163,86 @@ pub const Token = struct { .module_doc_comment, => null, - .@"and" => 1, - .and_and => 2, - .bang => 1, - .bang_equal => 2, - .equal_lt => 2, - .caret => 1, - .end_brace => 1, - .end_bracket => 1, - .end_paren => 1, - .colon => 1, - .comma => 1, - .dot => 1, - .equal => 1, - .equal_equal => 2, - .gt => 1, - .gt_equal => 2, - .gt_gt => 2, - .gt_gt_gt => 3, - .lt => 1, - .lt_equal => 2, - .lt_lt => 2, - .minus => 1, - .start_brace => 1, - .start_bracket => 1, - .start_paren => 1, - .pipe => 1, - .pipe_pipe => 2, - .percent => 1, - .plus => 1, - .minus_lt => 2, - .semi => 1, - .slash => 1, - .star => 1, - .star_star => 2, - .tilde => 1, - .slash_equal => 2, - .percent_equal => 2, - .tilde_equal => 2, - .caret_equal => 2, - .and_equal => 2, - .pipe_equal => 2, - .lt_lt_equal => 3, - .gt_gt_equal => 3, - .gt_gt_gt_equal => 4, - .plus_equal => 2, - .minus_equal => 2, - .star_equal => 2, - .star_star_equal => 3, - .pipe_pipe_equal => 3, - .and_and_equal => 3, - - .invalid => 0, - .eof => 0, + .@"and" => "&", + .and_and => "&&", + .bang => "!", + .bang_equal => "!=", + .equal_lt => "=<", + .caret => "^", + .end_brace => "}", + .end_bracket => "]", + .end_paren => ")", + .colon => ":", + .comma => ",", + .dot => ".", + .equal => "=", + .equal_equal => "==", + .gt => ">", + .gt_equal => ">=", + .gt_gt => ">>", + .gt_gt_gt => ">>>", + .lt => "<", + .lt_equal => "<=", + .lt_lt => "<<", + .minus => "-", + .start_brace => "{", + .start_bracket => "[", + .start_paren => "(", + .pipe => "|", + .pipe_pipe => "||", + .percent => "%", + .plus => "+", + .minus_lt => "<", + .semi => ";", + .slash => "/", + .star => "*", + .star_star => "**", + .tilde => "~", + .slash_equal => "/=", + .percent_equal => "%=", + .tilde_equal => "~=", + .caret_equal => "^=", + .and_equal => "&=", + .pipe_equal => "|=", + .lt_lt_equal => "<<=", + .gt_gt_equal => ">>=", + .gt_gt_gt_equal => ">>>=", + .plus_equal => "+=", + .minus_equal => "-=", + .star_equal => "*=", + .star_star_equal => "**=", + .pipe_pipe_equal => "||=", + .and_and_equal => "&&=", inline else => |keyword| comptime len: { if (!mem.startsWith(u8, @tagName(keyword), "kw_")) { @compileError("Token tag is not a keyword: " ++ @tagName(keyword)); } - break :len @tagName(keyword).len - 3; + break :len @tagName(keyword)[3..]; }, }; } + + pub fn description(tag: Tag) []const u8 { + return switch (tag) { + .invalid => unreachable, + .eof => "end of file", + .id => "an identifier", + .builtin_id => "a builtin identifier", + .number_int => "a numeric literal", + .number_float => "a numeric literal", + .string => "a string literal", + .multiline_string => "a multiline string literal", + .doc_comment => "a documentation comment", + .module_doc_comment => "a module documentation comment", + inline else => |t| comptime "\"" ++ t.lexeme().? ++ "\"", + }; + } + + /// Most tokens have a static length, such as "struct" or "+=" + pub fn staticLen(tag: Tag) ?u32 { + return if (tag.lexeme()) |str| @intCast(str.len) else null; + } }; /// Instead of storing the length of a token, we can recompute this @@ -243,7 +264,8 @@ pub const Token = struct { } pub fn slice(token: Token, utf8_source: [:0]const u8) []const u8 { - return utf8_source[token.offset..][0..token.len(utf8_source)]; + return token.tag.lexeme() orelse + utf8_source[token.offset..][0..token.len(utf8_source)]; } const keywords = std.ComptimeStringMap(Tag, keywords: { @@ -921,6 +943,48 @@ pub fn skipWhitespace(t: *Tokenizer) void { } } +pub fn tokenLocation( + utf8_source: []const u8, + start_offset: ByteOffset, + token_start: ByteOffset, +) Location { + var loc: Location = .{ + .line = 1, + .column = 1, + .line_start = start_offset, + .line_end = utf8_source.len, + }; + + // Scan to by line until we go past the token start + while (std.mem.indexOfScalarPos(u8, utf8_source, loc.line_start, '\n')) |i| { + if (i >= token_start) { + break; // Went past + } + loc.line += 1; + loc.line_start = i + 1; + } + + const offset = loc.line_start; + for (utf8_source[offset..], 0..) |c, i| { + if (i + offset == token_start) { + loc.line_end = i + offset; + while (loc.line_end < utf8_source.len and utf8_source[loc.line_end] != '\n') { + loc.line_end += 1; + } + return loc; + } + if (c == '\n') { + loc.line += 1; + loc.column = 1; + loc.line_start = i + 1; + } else { + loc.column += 1; + } + } + + return loc; +} + const Tokenizer = @This(); const std = @import("std"); const unicode = std.unicode; @@ -928,3 +992,4 @@ const assert = std.debug.assert; const mem = std.mem; const unicode_props = @import("unicode_props.zig"); const T = @import("tracer"); +const Location = @import("Ast.zig").Location; diff --git a/src/compiler/Transform.zig b/src/compiler/Transform.zig index ab3f324cfe5d79e590c1f6154d88fc0585699da0..2e07e7d236e213e574bcfdd907c7c1725c654d47 100644 --- a/src/compiler/Transform.zig +++ b/src/compiler/Transform.zig @@ -603,7 +603,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc return .void_value; }; - lookup.mutated = true; + lookup.used = true; return switch (lookup.data) { .declaration => try t.addInstRef(.{ @@ -1061,6 +1061,7 @@ fn transformBlockStatement( const var_decl = t.ast.fullVarDecl(node); if (mem.eql(u8, var_decl.id, "_")) { + // TODO: relax this restriction into just creating a discard node try t.report.err(.invalid_discard, .{ .node = node }); return .void_value; } @@ -1101,13 +1102,35 @@ fn transformBlockStatement( }), }); - try t.scope().symbols.put(t.gpa, var_decl.id, .{ + const gop = try t.scope().symbols.getOrPut(t.gpa, var_decl.id); + + // if re-declaring, apply unused warnings now + if (gop.found_existing) { + const value = gop.value_ptr.*; + if (!value.used) { + try t.report.warn(.unused, .{ + .node = value.node, + .kind = switch (value.data) { + .declaration => .function, + .param => .param, + .mutable => .variable, + .immutable => switch (t.ast.nodeTag(value.node)) { + .param_decl => .param, + else => .constant, + }, + }, + // .shadowed = node, + }); + } + } + + gop.value_ptr.* = .{ .node = node, .data = switch (var_decl.is_const) { true => .{ .immutable = mem_ref }, false => .{ .mutable = mem_ref }, }, - }); + }; return .void_value; }, diff --git a/src/compiler/main.zig b/src/compiler/main.zig index ee011e37c09e3455562948887dea8351d98a3e40..318962c10147234617ff51b0f05674a04cfea948 100644 --- a/src/compiler/main.zig +++ b/src/compiler/main.zig @@ -21,7 +21,9 @@ pub fn main() !void { try unicode_props.init(); - std.debug.print("\r\x1b[K", .{}); + if (builtin.mode == .Debug) { + std.debug.print("\r\x1b[K", .{}); + } const use_gpa = builtin.mode == .Debug; @@ -54,14 +56,17 @@ pub fn main() !void { while (args.next()) |arg| { if (std.mem.startsWith(u8, arg, "-")) { + if (builtin.mode == .Debug and std.mem.eql(u8, arg, "--report-trace")) { + Report.debug_show_report_trace = true; + continue; + } if (std.mem.eql(u8, arg, "--dump-ir")) { dump_ir = true; - } else { - std.debug.print("error: unknown flag '{s}'\n", .{arg}); - maybe_file_path = null; - break; + continue; } - continue; + std.debug.print("error: unknown flag '{s}'\n", .{arg}); + maybe_file_path = null; + break; } if (maybe_file_path == null) { @@ -77,13 +82,23 @@ pub fn main() !void { const file_path = maybe_file_path orelse { std.debug.print("Usage: {s} [flags] \n", .{exe_name}); - std.debug.print( - // | 60 chars wrap point| - \\ - \\Flags: - \\ --dump-ir Dump intermediate representations. - \\ - , .{}); + { + std.debug.print( + // | 60 chars wrap point| + \\ + \\Flags: + \\ --dump-ir Dump intermediate representations. + \\ + , .{}); + } + if (builtin.mode == .Debug) { + std.debug.print( + \\ + \\Debug Mode Flags: + \\ --report-trace Print stack traces per error. Very noisy + \\ + , .{}); + } std.process.exit(1); }; -- 2.54.0