| author | |
| committer | |
| log | 4cd49040184a9da68e9584b71ac21f9e19723d59 |
| tree | b4e843e8e58dbed96158fb445cd5056eba0e5262 |
| parent | fa2de52f403657b87c1c7db26b6010bf1c019ad5 |
| signature | Commit is signed but in an unrecognized format. |
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.1 | 8 | ## 0.1.1 |
| 2 | 9 | ||
| 3 | - feat: `var` declarations / runtime memory | 10 | - feat: `var` declarations / runtime memory |
build.zig+19-6| ... | @@ -1,4 +1,7 @@ | ... | @@ -1,4 +1,7 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const Build = std.Build; | ||
| 3 | const Target = std.Target; | ||
| 4 | const OsVersion = Target.Query.OsVersion; | ||
| 2 | 5 | ||
| 3 | pub fn build(b: *std.Build) void { | 6 | pub 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); |
| 33 | 37 | ||
| ... | @@ -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")); |
| 68 | 72 | ||
| 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"); |
| 86 | 94 | ||
| 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")); |
| 96 | 108 | ||
| 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, |
| 40 | nodes: Node.List.Slice, | 40 | nodes: Node.List.Slice, |
| 41 | extra: []const u32, | 41 | extra: []const u32, |
| 42 | 42 | ||
| 43 | const Parse = @import("Parse.zig"); | ||
| 44 | pub 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 | |||
| 85 | pub const Node = struct { | 43 | pub 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 | } |
| 646 | 604 | ||
| 647 | pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenIndex) Location { | 605 | pub 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]; |
| 655 | 607 | 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 | } |
| 684 | 609 | ||
| 685 | pub fn tokenSlice(ast: Ast, token_index: TokenIndex) []const u8 { | 610 | pub 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 | } |
| 131 | 131 | ||
| 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 report | 140 | // 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 | } |
| 71 | 71 | ||
| 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", .{}); |
| 73 | 73 | ||
| 74 | // 2. Print every function decl | 74 | // 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 | } |
| 78 | 78 | ||
| 79 | // 3. Print the top-level code | 79 | // 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 | } |
| 82 | 86 | ||
| 83 | fn deinit(link: *JsLink) void { | 87 | fn 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 { |
| 107 | 107 | ||
| 108 | _ = try c.add(.root, undefined); | 108 | _ = try c.add(.root, undefined); |
| 109 | 109 | ||
| 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 | } | ||
| 117 | 118 | ||
| 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 | } |
| 121 | 123 | ||
| 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 = .{}, |
| 10 | extra: ArrayListUnmanaged(u32) = .{}, | 10 | extra: ArrayListUnmanaged(u32) = .{}, |
| 11 | scratch: ArrayListUnmanaged(NodeIndex) = .{}, | 11 | scratch: ArrayListUnmanaged(NodeIndex) = .{}, |
| 12 | 12 | ||
| 13 | ast_error: ?AstError = null, | 13 | ast_error: ?AstError.WithTrace = null, |
| 14 | |||
| 15 | pub 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 | } | ||
| 14 | 54 | ||
| 15 | pub fn init(gpa: Allocator, source: [:0]const u8, tokens: Token.List.Slice) Parse { | 55 | fn 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 | } |
| 23 | 63 | ||
| 24 | /// This transfers the parse result into an Ast | 64 | /// This transfers the parse result into an Ast |
| 25 | pub fn finalize(p: *Parse, token_slice: Token.List.Slice) !Ast { | 65 | fn 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 | } |
| 41 | 81 | ||
| 42 | /// This discards all parser work | 82 | /// This discards all parser work |
| 43 | pub fn deinit(p: *Parse) void { | 83 | fn 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 | }; |
| 54 | 94 | ||
| 95 | pub 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. | ||
| 56 | pub const AstError = union(enum) { | 116 | pub 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, | ||
| 65 | 132 | ||
| 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 | } |
| 69 | 136 | ||
| 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 | }, |
| 89 | 156 | .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 | }; |
| 95 | 180 | ||
| 96 | pub fn parseRoot(p: *Parse) Error!void { | 181 | pub 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 { |
| 141 | 226 | ||
| 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 | } |
| 146 | 231 | ||
| 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(); |
| 181 | 266 | ||
| 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 | else | 269 | else |
| 185 | null; | 270 | null; |
| 186 | 271 | ||
| ... | @@ -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 | } |
| 228 | 313 | ||
| ... | @@ -239,13 +324,13 @@ fn parseParamDecl(p: *Parse) Error!NodeIndex { | ... | @@ -239,13 +324,13 @@ fn parseParamDecl(p: *Parse) Error!NodeIndex { |
| 239 | 324 | ||
| 240 | _ = try p.expect(.colon); | 325 | _ = try p.expect(.colon); |
| 241 | 326 | ||
| 242 | const @"type" = try p.parseExpression(); | 327 | const ty = try p.parseExpression(false); |
| 243 | 328 | ||
| 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 { |
| 274 | 359 | ||
| 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 } { |
| 301 | 386 | ||
| 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 | } |
| 306 | 391 | ||
| 307 | const first_tag = p.currentTag(); | 392 | const first_tag = p.currentTag(); |
| 308 | 393 | ||
| 309 | try p.scratch.append(p.gpa, try p.parseExpression()); | 394 | try p.scratch.append(p.gpa, try p.parseExpression(true)); |
| 310 | 395 | ||
| 311 | switch (first_tag) { | 396 | switch (first_tag) { |
| 312 | // some control flow and blocks are allowed to not have a | 397 | // 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); |
| 353 | 438 | ||
| 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 | else | 441 | else |
| 357 | .none; | 442 | .none; |
| 358 | 443 | ||
| 359 | _ = try p.expect(.equal); | 444 | _ = try p.expect(.equal); |
| 360 | 445 | ||
| 361 | const data_init = try p.parseExpression(); | 446 | const data_init = try p.parseExpression(true); |
| 362 | 447 | ||
| 363 | _ = try p.expect(.semi); | 448 | _ = try p.expect(.semi); |
| 364 | 449 | ||
| ... | @@ -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 construction | 637 | 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 | }; |
| 555 | 645 | ||
| 556 | fn parseExpression(p: *Parse) Error!NodeIndex { | 646 | fn 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(); |
| 559 | 649 | ||
| ... | @@ -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(); |
| 570 | 660 | ||
| 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 | } |
| 574 | 665 | ||
| 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 | }, |
| 618 | 709 | ||
| 619 | else => return p.fail(.expected_expr), | 710 | else => return p.failUnexpectedToken(.expression), |
| 620 | }; | 711 | }; |
| 621 | 712 | ||
| 622 | // parse field, calls, and index expressions | 713 | // 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); |
| 661 | 752 | ||
| 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 | } |
| 678 | 769 | ||
| 679 | fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) Error!NodeIndex { | 770 | fn 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(); |
| 682 | 773 | ||
| ... | @@ -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(); |
| 686 | 777 | ||
| 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; |
| 694 | 787 | ||
| 695 | rhs = try p.parseBinarySuffix(rhs, next_op); | 788 | rhs = try p.parseBinarySuffix(rhs, next_op, allow_assign_operators); |
| 696 | } | 789 | } |
| 697 | 790 | ||
| 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); |
| 713 | 806 | ||
| 714 | const expr = if (p.isExpressionStart()) | 807 | const expr = if (p.isExpressionStart()) |
| 715 | try p.parseExpression() | 808 | try p.parseExpression(false) |
| 716 | else | 809 | else |
| 717 | null; | 810 | null; |
| 718 | 811 | ||
| ... | @@ -737,7 +830,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex { | ... | @@ -737,7 +830,7 @@ fn parseBreak(p: *Parse) Error!NodeIndex { |
| 737 | null; | 830 | null; |
| 738 | 831 | ||
| 739 | const expr = if (p.isExpressionStart()) | 832 | const expr = if (p.isExpressionStart()) |
| 740 | try p.parseExpression() | 833 | try p.parseExpression(false) |
| 741 | else | 834 | else |
| 742 | null; | 835 | null; |
| 743 | 836 | ||
| ... | @@ -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); |
| 777 | 870 | ||
| 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)); |
| 780 | 873 | ||
| 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 | } |
| 793 | 886 | ||
| ... | @@ -802,7 +895,7 @@ fn parseIf(p: *Parse) Error!NodeIndex { | ... | @@ -802,7 +895,7 @@ fn parseIf(p: *Parse) Error!NodeIndex { |
| 802 | 895 | ||
| 803 | const main_token = p.assertToken(.kw_if); | 896 | const main_token = p.assertToken(.kw_if); |
| 804 | 897 | ||
| 805 | const cond = try p.parseExpression(); | 898 | const cond = try p.parseExpression(false); |
| 806 | 899 | ||
| 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(), |
| 840 | 933 | ||
| 841 | else => return p.fail(.expected_else_value), | 934 | else => return p.failUnexpectedToken(.else_clause), |
| 842 | }; | 935 | }; |
| 843 | } | 936 | } |
| 844 | 937 | ||
| ... | @@ -848,7 +941,7 @@ fn parseGroup(p: *Parse) Error!NodeIndex { | ... | @@ -848,7 +941,7 @@ fn parseGroup(p: *Parse) Error!NodeIndex { |
| 848 | 941 | ||
| 849 | const main_token = p.assertToken(.start_paren); | 942 | const main_token = p.assertToken(.start_paren); |
| 850 | 943 | ||
| 851 | const expr = try p.parseExpression(); | 944 | const expr = try p.parseExpression(false); |
| 852 | 945 | ||
| 853 | _ = try p.expect(.end_paren); | 946 | _ = try p.expect(.end_paren); |
| 854 | 947 | ||
| ... | @@ -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 | } |
| 886 | 979 | ||
| 887 | fn fail(p: *Parse, ast_error: AstError) Error { | 980 | fn 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 | } |
| 892 | 987 | ||
| 988 | fn failUnexpectedToken(p: *Parse, kind: ExpectedContext) Error { | ||
| 989 | return p.fail(.{ .unexpected_context = .{ | ||
| 990 | .got = p.fullToken(p.pos), | ||
| 991 | .expected = kind, | ||
| 992 | } }); | ||
| 993 | } | ||
| 994 | |||
| 893 | fn blankNode(p: *Parse, comptime tag: Token.Tag) !NodeIndex { | 995 | fn 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 { |
| 917 | 1019 | ||
| 918 | fn expect(p: *Parse, tag: Token.Tag) !TokenIndex { | 1020 | fn 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 | } |
| 924 | 1029 | ||
| 925 | fn expectWithoutAdvance(p: *Parse, tag: Token.Tag) !void { | 1030 | fn 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 | } |
| 930 | 1038 |
src/compiler/Report.zig+137-19| ... | @@ -9,27 +9,89 @@ errors: []Item = &.{}, | ... | @@ -9,27 +9,89 @@ errors: []Item = &.{}, |
| 9 | warnings: []Item = &.{}, | 9 | warnings: []Item = &.{}, |
| 10 | string_bytes: []u8 = &.{}, | 10 | string_bytes: []u8 = &.{}, |
| 11 | 11 | ||
| 12 | pub var debug_show_report_trace: if (builtin.mode == .Debug) bool else void = | ||
| 13 | if (builtin.mode == .Debug) false; | ||
| 14 | |||
| 12 | pub const Item = struct { | 15 | pub const Item = struct { |
| 13 | /// What is this diagnostic | 16 | /// What is this diagnostic |
| 14 | data: union { @"error": Err, warn: Warn }, | 17 | data: union { @"error": Err, warn: Warn }, |
| 15 | /// What file was it in | 18 | /// What file was it in |
| 16 | url: String, | 19 | url: String, |
| 17 | 20 | ||
| 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 | }; |
| 21 | 26 | ||
| 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. | ||
| 30 | pub 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 | |||
| 70 | pub const DebugStoredTrace = if (builtin.mode == .Debug) | ||
| 71 | StoredTrace | ||
| 72 | else | ||
| 73 | struct { | ||
| 74 | pub fn capture() @This() { | ||
| 75 | return .{}; | ||
| 76 | } | ||
| 77 | |||
| 78 | pub fn from(_: ?*std.builtin.StackTrace) @This() { | ||
| 79 | return .{}; | ||
| 80 | } | ||
| 81 | }; | ||
| 82 | |||
| 22 | pub const Err = union(enum) { | 83 | pub const Err = union(enum) { |
| 23 | pub const FieldEnum = std.meta.FieldEnum(Err); | 84 | pub const FieldEnum = std.meta.FieldEnum(Err); |
| 24 | 85 | ||
| 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); |
| 29 | 90 | ||
| 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); |
| 35 | 97 | ||
| ... | @@ -42,7 +104,7 @@ pub const Err = union(enum) { | ... | @@ -42,7 +104,7 @@ pub const Err = union(enum) { |
| 42 | node: NodeIndex, | 104 | node: NodeIndex, |
| 43 | 105 | ||
| 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, |
| 63 | 125 | ||
| 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, |
| 71 | 136 | ||
| 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, |
| 84 | 149 | ||
| 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, | ||
| 135 | 201 | ||
| 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) catch | 297 | .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 | } |
| 247 | 324 | ||
| 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 | }; |
| 262 | 340 | ||
| 263 | const utf8_source = (try file.getSource(comp.gpa)).code; | 341 | const utf8_source = ctx.utf8_source; |
| 264 | 342 | ||
| 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 | }, |
| 285 | 372 | ||
| 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 | } |
| 348 | 450 | ||
| 349 | pub fn dumpToStdErr(report: Report, comp: *Comp) !void { | 451 | pub 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); |
| 354 | 454 | ||
| 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 writer | 456 | // 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 | } |
| 381 | 487 | ||
| ... | @@ -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 | } |
| 388 | 495 | ||
| ... | @@ -426,8 +533,12 @@ pub const Builder = struct { | ... | @@ -426,8 +533,12 @@ pub const Builder = struct { |
| 426 | }; | 533 | }; |
| 427 | 534 | ||
| 428 | pub const Highlight = union(enum) { | 535 | pub const Highlight = union(enum) { |
| 536 | // No source view | ||
| 537 | alone, | ||
| 429 | // Highlight one token | 538 | // 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 tokens | 542 | // Highlight two tokens |
| 432 | two_tokens: TokenIndex, | 543 | two_tokens: TokenIndex, |
| 433 | // Highlight a Node | 544 | // 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, | ||
| 451 | 563 | ||
| 452 | /// This arena is is a stack fallback with 16kb, then | 564 | /// This arena is is a stack fallback with 16kb, then |
| 453 | /// falling back to an arena allocator. This should | 565 | /// 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 | }; |
| 478 | 594 | ||
| 479 | const Report = @This(); | 595 | const Report = @This(); |
| ... | @@ -482,6 +598,8 @@ const ArrayListUnmanaged = std.ArrayListUnmanaged; | ... | @@ -482,6 +598,8 @@ const ArrayListUnmanaged = std.ArrayListUnmanaged; |
| 482 | const Allocator = std.mem.Allocator; | 598 | const Allocator = std.mem.Allocator; |
| 483 | const assert = std.debug.assert; | 599 | const assert = std.debug.assert; |
| 484 | 600 | ||
| 601 | const builtin = @import("builtin"); | ||
| 602 | |||
| 485 | const Tokenizer = @import("Tokenizer.zig"); | 603 | const Tokenizer = @import("Tokenizer.zig"); |
| 486 | const ByteOffset = Tokenizer.ByteOffset; | 604 | const ByteOffset = Tokenizer.ByteOffset; |
| 487 | 605 |
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 { |
| 130 | 130 | ||
| 131 | const return_type = try s.resolveRefComptime(fn_decl.return_type); | 131 | const return_type = try s.resolveRefComptime(fn_decl.return_type); |
| 132 | 132 | ||
| 133 | const root = (try s.analyzeBlock( | 133 | const root_block = fn_decl.body.toIndex() orelse |
| 134 | fn_decl.body.toIndex() orelse | 134 | @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 | }; | ||
| 138 | 142 | ||
| 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); |
| 389 | 401 | ||
| ... | @@ -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 | } |
| 198 | 205 |
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 checks | 146 | /// Used so that parser does not need to do out of bounds checks |
| 147 | eof, | 147 | eof, |
| 148 | 148 | ||
| 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 length | 152 | .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, |
| 162 | 165 | ||
| 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, | ||
| 216 | 216 | ||
| 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 | }; |
| 226 | 247 | ||
| 227 | /// Instead of storing the length of a token, we can recompute this | 248 | /// 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 | } |
| 244 | 265 | ||
| 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 | } |
| 248 | 270 | ||
| 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 | } |
| 923 | 945 | ||
| 946 | pub 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 | |||
| 924 | const Tokenizer = @This(); | 988 | const Tokenizer = @This(); |
| 925 | const std = @import("std"); | 989 | const std = @import("std"); |
| 926 | const unicode = std.unicode; | 990 | const unicode = std.unicode; |
| ... | @@ -928,3 +992,4 @@ const assert = std.debug.assert; | ... | @@ -928,3 +992,4 @@ const assert = std.debug.assert; |
| 928 | const mem = std.mem; | 992 | const mem = std.mem; |
| 929 | const unicode_props = @import("unicode_props.zig"); | 993 | const unicode_props = @import("unicode_props.zig"); |
| 930 | const T = @import("tracer"); | 994 | const T = @import("tracer"); |
| 995 | const 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 | }; |
| 605 | 605 | ||
| 606 | lookup.mutated = true; | 606 | lookup.used = true; |
| 607 | 607 | ||
| 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); |
| 1062 | 1062 | ||
| 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 | }); |
| 1103 | 1104 | ||
| 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 | }; |
| 1111 | 1134 | ||
| 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 { |
| 21 | 21 | ||
| 22 | try unicode_props.init(); | 22 | try unicode_props.init(); |
| 23 | 23 | ||
| 24 | std.debug.print("\r\x1b[K", .{}); | 24 | if (builtin.mode == .Debug) { |
| 25 | std.debug.print("\r\x1b[K", .{}); | ||
| 26 | } | ||
| 25 | 27 | ||
| 26 | const use_gpa = builtin.mode == .Debug; | 28 | const use_gpa = builtin.mode == .Debug; |
| 27 | 29 | ||
| ... | @@ -54,14 +56,17 @@ pub fn main() !void { | ... | @@ -54,14 +56,17 @@ pub fn main() !void { |
| 54 | 56 | ||
| 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 | } |
| 66 | 71 | ||
| 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 { |
| 77 | 82 | ||
| 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 | }; |
| 89 | 104 |