authorgravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-05-19 03:11:59-07:00
committergravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-05-19 03:11:59-07:00
logc1d54bf8b06f71d81e8614572483a69720da4ea6
tree10775f14eb33a1b28aecbc4558bb5214ed732b94
parent634a70376c0023f46a6aae6611ed672397eae557
signature Commit is signed but in an unrecognized format.

better error handling


15 files changed, 960 insertions(+), 287 deletions(-)

CHANGELOG.md created+8
......@@ -0,0 +1,8 @@
1## 0.1.1-alpha
2
3- introduce `Report` and show errors during Transform.
4- add flag --dump-ir for enabling ir dumps
5
6## 0.1.0-alpha
7
8initial demo of compiler pipeline in zig.
demo.txt created+1
......@@ -0,0 +1 @@
1no "waay
src/compiler/Ast.zig+131-83
......@@ -34,96 +34,54 @@
3434//! The `Ast` owns token and node data, but not the source code.
3535
3636/// Externally owned source code.
37utf8_source: []const u8,
37utf8_source: [:0]const u8,
3838
3939tokens: Token.List.Slice,
4040nodes: Node.List.Slice,
4141extra: []const u32,
4242
43pub fn parse(gpa: std.mem.Allocator, source: []const u8) !Ast {
44 const Parse = @import("Parse.zig");
45
46 var todo: ByteOffset = 0;
47 var tokens = try Tokenizer.tokenize(source, gpa, &todo);
43const Parse = @import("Parse.zig");
44pub fn parse(
45 gpa: std.mem.Allocator,
46 source: [:0]const u8,
47 ast_error: *Parse.AstError,
48) Parse.Error!Ast {
49 var err_byte: ByteOffset = 0;
50 var tokens = Tokenizer.tokenize(
51 source,
52 gpa,
53 &err_byte,
54 ) catch |err| switch (err) {
55 error.OutOfMemory => return error.OutOfMemory,
56 else => {
57 ast_error.* = .{ .tokenization = .{
58 .err = err,
59 .byte = err_byte,
60 } };
61 return error.ParseFail;
62 },
63 };
4864 errdefer tokens.deinit(gpa);
4965
5066 var parser = Parse.init(gpa, source, tokens);
5167 errdefer parser.deinit();
5268
53 try parser.nodes.ensureUnusedCapacity(gpa, @divFloor(tokens.len, 4));
69 try parser.nodes.ensureUnusedCapacity(
70 gpa,
71 @max(1, @divFloor(tokens.len, 4)),
72 );
5473
55 try parser.parseRoot();
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 };
5681
5782 return try parser.finalize(tokens);
5883}
5984
60pub const Location = struct {
61 line: usize,
62 column: usize,
63 line_start: usize,
64 line_end: usize,
65};
66
67pub const Span = struct {
68 start: u32,
69 end: u32,
70 main: u32,
71};
72
73// This can be removed when the following issue is implemented:
74// See: https://github.com/ziglang/zig/issues/104
75// See: https://github.com/ziglang/zig/issues/3806
76pub const OptionalNodeIndex = enum(u32) {
77 none = 0,
78 _,
79
80 pub fn init(idx: ?NodeIndex) OptionalNodeIndex {
81 assert(idx != 0);
82 return @enumFromInt(idx orelse return .none);
83 }
84
85 pub fn unwrap(idx: OptionalNodeIndex) ?NodeIndex {
86 return switch (idx) {
87 .none => null,
88 _ => @intFromEnum(idx),
89 };
90 }
91};
92
93pub const OptionalTokenIndex = enum(u32) {
94 none = 0,
95 _,
96
97 pub fn init(idx: ?TokenIndex) OptionalTokenIndex {
98 assert(idx != 0);
99 return @enumFromInt(idx orelse return .none);
100 }
101
102 pub fn unwrap(idx: OptionalTokenIndex) ?TokenIndex {
103 return switch (idx) {
104 .none => null,
105 _ => @intFromEnum(idx),
106 };
107 }
108};
109
110pub const OptionalExtraIndex = enum(u32) {
111 none = std.math.maxInt(u32),
112 _,
113
114 pub fn init(idx: ?ExtraIndex) OptionalExtraIndex {
115 assert(idx != std.math.maxInt(u32));
116 return @enumFromInt(idx orelse return .none);
117 }
118
119 pub fn unwrap(idx: OptionalExtraIndex) ?ExtraIndex {
120 return switch (idx) {
121 .none => null,
122 _ => @intFromEnum(idx),
123 };
124 }
125};
126
12785pub const Node = struct {
12886 tag: Tag,
12987 main_token: TokenIndex,
......@@ -462,6 +420,76 @@ pub const Node = struct {
462420 };
463421};
464422
423pub const Error = error{ OutOfMemory, ParseFail };
424
425pub const Location = struct {
426 // TODO: add an ordinal number type, replace with Ordinal(usize)
427 line: usize,
428 column: usize,
429 line_start: usize,
430 line_end: usize,
431};
432
433pub const Span = struct {
434 start: u32,
435 end: u32,
436 main: u32,
437};
438
439// This can be removed when the following issue is implemented:
440// See: https://github.com/ziglang/zig/issues/104
441// See: https://github.com/ziglang/zig/issues/3806
442pub const OptionalNodeIndex = enum(u32) {
443 none = 0,
444 _,
445
446 pub fn init(idx: ?NodeIndex) OptionalNodeIndex {
447 assert(idx != 0);
448 return @enumFromInt(idx orelse return .none);
449 }
450
451 pub fn unwrap(idx: OptionalNodeIndex) ?NodeIndex {
452 return switch (idx) {
453 .none => null,
454 _ => @intFromEnum(idx),
455 };
456 }
457};
458
459pub const OptionalTokenIndex = enum(u32) {
460 none = 0,
461 _,
462
463 pub fn init(idx: ?TokenIndex) OptionalTokenIndex {
464 assert(idx != 0);
465 return @enumFromInt(idx orelse return .none);
466 }
467
468 pub fn unwrap(idx: OptionalTokenIndex) ?TokenIndex {
469 return switch (idx) {
470 .none => null,
471 _ => @intFromEnum(idx),
472 };
473 }
474};
475
476pub const OptionalExtraIndex = enum(u32) {
477 none = std.math.maxInt(u32),
478 _,
479
480 pub fn init(idx: ?ExtraIndex) OptionalExtraIndex {
481 assert(idx != std.math.maxInt(u32));
482 return @enumFromInt(idx orelse return .none);
483 }
484
485 pub fn unwrap(idx: OptionalExtraIndex) ?ExtraIndex {
486 return switch (idx) {
487 .none => null,
488 _ => @intFromEnum(idx),
489 };
490 }
491};
492
465493pub const SubList = struct {
466494 /// Either NodeIndex or TokenIndex, depending on where this came from.
467495 start: u32,
......@@ -598,6 +626,19 @@ pub fn fullParamDecl(ast: Ast, node: NodeIndex) full.ParamDecl {
598626 };
599627}
600628
629pub fn idToken(ast: Ast, node: NodeIndex) TokenIndex {
630 return switch (ast.nodeTag(node)) {
631 .param_decl,
632 .id,
633 .label,
634 => ast.nodeToken(node),
635 .fn_decl,
636 .var_decl,
637 => ast.nodeToken(node) + 1,
638 else => unreachable, // does not have an id
639 };
640}
641
601642pub fn nodeStatements(ast: *const Ast, node: NodeIndex) []const NodeIndex {
602643 const data = ast.nodes.items(.data)[node].list;
603644 return data.slice(ast);
......@@ -605,15 +646,15 @@ pub fn nodeStatements(ast: *const Ast, node: NodeIndex) []const NodeIndex {
605646
606647pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenIndex) Location {
607648 var loc = Location{
608 .line = 0,
609 .column = 0,
649 .line = 1,
650 .column = 1,
610651 .line_start = start_offset,
611 .line_end = self.source.len,
652 .line_end = self.utf8_source.len,
612653 };
613 const token_start = self.tokens.items(.start)[token_index];
654 const token_start = self.tokens.items(.offset)[token_index];
614655
615656 // Scan to by line until we go past the token start
616 while (std.mem.indexOfScalarPos(u8, self.source, loc.line_start, '\n')) |i| {
657 while (std.mem.indexOfScalarPos(u8, self.utf8_source, loc.line_start, '\n')) |i| {
617658 if (i >= token_start) {
618659 break; // Went past
619660 }
......@@ -622,17 +663,17 @@ pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenInde
622663 }
623664
624665 const offset = loc.line_start;
625 for (self.source[offset..], 0..) |c, i| {
666 for (self.utf8_source[offset..], 0..) |c, i| {
626667 if (i + offset == token_start) {
627668 loc.line_end = i + offset;
628 while (loc.line_end < self.source.len and self.source[loc.line_end] != '\n') {
669 while (loc.line_end < self.utf8_source.len and self.utf8_source[loc.line_end] != '\n') {
629670 loc.line_end += 1;
630671 }
631672 return loc;
632673 }
633674 if (c == '\n') {
634675 loc.line += 1;
635 loc.column = 0;
676 loc.column = 1;
636677 loc.line_start = i + 1;
637678 } else {
638679 loc.column += 1;
......@@ -651,6 +692,11 @@ pub fn basicNodeSlice(ast: Ast, node: NodeIndex) []const u8 {
651692 return ast.tokenSlice(token);
652693}
653694
695pub fn fnDeclIdToken(tree: Ast, node: NodeIndex) TokenIndex {
696 assert(tree.nodeTag(node) == .fn_decl);
697 return tree.nodeToken(node) + 1;
698}
699
654700pub fn extraData(tree: Ast, index: usize, comptime T: type) T {
655701 const fields = std.meta.fields(T);
656702 var result: T = undefined;
......@@ -699,3 +745,5 @@ const Tokenizer = @import("Tokenizer.zig");
699745const Token = Tokenizer.Token;
700746const TokenList = Tokenizer.Token.List;
701747const ByteOffset = Tokenizer.ByteOffset;
748
749const Report = @import("Report.zig");
src/compiler/Comp.zig+60-11
......@@ -10,6 +10,8 @@ files: std.StringArrayHashMapUnmanaged(*File) = .{},
1010/// # of sema instructions in this compilation cycle
1111analyzed_instructions: usize = 0,
1212
13options: Options = .{},
14
1315// TODO: consider computing this dynamicly per-link
1416current_compile_decls: std.ArrayListUnmanaged(Decl.Index) = .{},
1517
......@@ -74,7 +76,16 @@ pub const File = struct {
7476
7577 path_digest: ?Cache.BinDigest,
7678
77 pub fn getSource(file: *File, gpa: Allocator) !Source {
79 const GetSourceError =
80 std.fs.File.OpenError ||
81 std.fs.File.ReadError ||
82 std.fs.File.StatError ||
83 Allocator.Error || error{
84 FileTooBig,
85 UnexpectedEndOfFile,
86 };
87
88 pub fn getSource(file: *File, gpa: Allocator) GetSourceError!Source {
7889 if (file.source) |source| {
7990 return source;
8091 }
......@@ -111,17 +122,42 @@ pub const File = struct {
111122 return loaded_source;
112123 }
113124
114 pub fn getAst(file: *File, gpa: Allocator) !Ast {
125 const GetAstError = GetSourceError || Parse.Error;
126
127 pub fn getAst(file: *File, comp: *Comp) GetAstError!Ast {
115128 if (file.ast) |ast| {
116129 return ast;
117130 }
118131
119 const ast = try Ast.parse(gpa, (try file.getSource(gpa)).code);
132 var ast_err: Parse.AstError = undefined;
133 const ast = Ast.parse(
134 comp.gpa,
135 (try file.getSource(comp.gpa)).code,
136 &ast_err,
137 ) catch |err| switch (err) {
138 error.OutOfMemory => return err,
139 error.ParseFail => {
140 var report = try ast_err.toReport(comp, file);
141 // TODO: let this merge with other report
142 report.dumpToStdErr(comp) catch {};
143 report.deinit(comp.gpa);
144 return err;
145 },
146 };
147
148 if (comp.options.dump_ir) {
149 ast.dumpToWriter(std.io.getStdErr().writer()) catch {
150 // Simplify the error set.
151 };
152 }
153
120154 file.ast = ast;
121155 return ast;
122156 }
123157
124 pub fn getTeg(file: *File, gpa: Allocator) !Teg {
158 const GetTegError = GetAstError || Transform.Error;
159
160 pub fn getTeg(file: *File, comp: *Comp, fail_report: *Report) GetTegError!Teg {
125161 if (file.teg) |teg| {
126162 return teg;
127163 }
......@@ -136,7 +172,13 @@ pub const File = struct {
136172 };
137173 file.path_digest = bin_digest;
138174
139 const teg = try Transform.transform(try file.getAst(gpa), gpa);
175 const teg = try Transform.transform(
176 try file.getAst(comp),
177 comp.gpa,
178 file,
179 fail_report,
180 );
181
140182 file.teg = teg;
141183 return teg;
142184 }
......@@ -290,8 +332,6 @@ pub const Namespace = struct {
290332pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !Namespace.Index {
291333 const teg = file.teg.?;
292334
293 log.info("Index {} @ {d}", .{ std.fmt.fmtSliceHexLower(file.path_digest.?[0..8]), @intFromEnum(module_index) });
294
295335 const ns = try comp.intern_pool.createNamespace(comp.gpa, .{
296336 .parent = .none,
297337 .file_scope = file,
......@@ -322,7 +362,13 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N
322362}
323363
324364pub fn semaEntryPoint(comp: *Comp, file: *File) !void {
325 const teg = try file.getTeg(comp.gpa);
365 var report: Report = undefined;
366 const teg = try file.getTeg(comp, &report);
367
368 // TODO, move this elsewhere. we should print all errors and warnings at once
369 if (teg.report.warnings.len > 0) {
370 try teg.report.dumpToStdErr(comp);
371 }
326372
327373 const ns = try comp.indexTegModule(file, .main_module_inst);
328374 const decl_index = comp.intern_pool.namespacePtr(ns).lookupDirect(comp, .empty) orelse
......@@ -354,9 +400,6 @@ pub fn codeGenDecl(c: *Comp, decl: *Decl, air: *const Air) !void {
354400 const t = tracer.trace(@src());
355401 defer t.end();
356402
357 std.debug.print("Start Codegen\n", .{});
358 defer std.debug.print("End Codegen\n", .{});
359
360403 var arena_state = std.heap.ArenaAllocator.init(c.gpa);
361404 defer arena_state.deinit();
362405 const arena = arena_state.allocator();
......@@ -371,6 +414,10 @@ pub fn link(c: *Comp, writer: anytype) !void {
371414 try JsLink.linkJsCode(c, c.current_compile_decls.items, writer);
372415}
373416
417const Options = struct {
418 dump_ir: bool = false,
419};
420
374421const Comp = @This();
375422
376423const std = @import("std");
......@@ -391,5 +438,7 @@ const Transform = @import("Transform.zig");
391438const JsNodeGen = @import("JsNodeGen.zig");
392439const JsTree = @import("JsTree.zig");
393440const JsLink = @import("JsLink.zig");
441const Report = @import("Report.zig");
442const Parse = @import("Parse.zig");
394443
395444const tracer = @import("tracer");
src/compiler/JsLink.zig+1-4
......@@ -19,7 +19,6 @@ pub fn linkJsCode(
1919 decls: []const Decl.Index,
2020 writer: anytype,
2121) !void {
22 log.info("Linking {d} decls", .{decls.len});
2322
2423 // Arena allocator is used for the main flow
2524 var link: JsLink = .{ .comp = comp };
......@@ -68,7 +67,7 @@ pub fn linkJsCode(
6867 try link.decls.putNoClobber(comp.gpa, decl_index, @enumFromInt(gop.index));
6968 }
7069
71 try writer.print("// Compiler Demo. Do not use in production\n", .{});
70 try writer.print("// Language Compiler v0.0.1-alpha2\n", .{});
7271
7372 // 2. Print every function decl
7473 for (decls[1..]) |decl_index| {
......@@ -106,7 +105,6 @@ fn reserveExternSymbol(link: *JsLink, extern_symbol: String.WithLen) !void {
106105
107106fn appendLinkString(link: *JsLink, string: []const u8) !LinkString {
108107 const start = link.string_bytes.items.len;
109 log.info("Append '{s}' -> .{{ .start = {d}, .len = {d} }}", .{ string, start, string.len });
110108 try link.string_bytes.appendSlice(link.comp.gpa, string);
111109 return .{ .start = @intCast(start), .len = @intCast(string.len) };
112110}
......@@ -170,7 +168,6 @@ fn printBlock(link: *JsLink, tree: *const JsTree, list: JsTree.IndexList, writer
170168}
171169
172170fn printExpr(link: *JsLink, tree: *const JsTree, node: Node.Index, writer: anytype) @TypeOf(writer).Error!void {
173 log.info("print node {d}", .{@intFromEnum(node)});
174171 switch (tree.nodeTag(node)) {
175172 else => |tag| std.debug.panic("TODO: printExpr .{s}", .{@tagName(tag)}),
176173 .root => unreachable,
src/compiler/JsNodeGen.zig-2
......@@ -117,7 +117,6 @@ pub fn build(c: *JsNodeGen) !void {
117117}
118118
119119fn codeGenInst(c: *JsNodeGen, inst: Air.Inst.Index) Error!void {
120 std.debug.print("JsNodeGen: #{d}\n", .{@intFromEnum(inst)});
121120 switch (c.air.instTag(inst)) {
122121 .arg => unreachable, // codegen must skip over these instructions.
123122
......@@ -400,7 +399,6 @@ fn codeGenBranch(c: *JsNodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Br
400399 this_label,
401400 });
402401
403 std.debug.print("requires_label {}\n", .{requires_label});
404402 const block: Air.IndexList = c.air.instData(.block, i);
405403 for (block.slice(c.air)) |item| {
406404 try c.codeGenInst(item);
src/compiler/Parse.zig+62-27
......@@ -1,7 +1,7 @@
11//! Parser takes a Token list and creates a Node list. It does not own the source code or
22//! token data, however transferring into an `Ast` does move token list ownership.
33gpa: Allocator,
4source: []const u8,
4source: [:0]const u8,
55token_tags: []const Token.Tag,
66token_starts: []const ByteOffset,
77
......@@ -12,7 +12,7 @@ scratch: ArrayListUnmanaged(NodeIndex) = .{},
1212
1313ast_error: ?AstError = null,
1414
15pub fn init(gpa: Allocator, source: []const u8, tokens: Token.List.Slice) Parse {
15pub fn init(gpa: Allocator, source: [:0]const u8, tokens: Token.List.Slice) Parse {
1616 return Parse{
1717 .gpa = gpa,
1818 .source = source,
......@@ -36,8 +36,6 @@ pub fn finalize(p: *Parse, token_slice: Token.List.Slice) !Ast {
3636 .extra = extra,
3737 };
3838
39 ast.dumpToWriter(std.io.getStdErr().writer()) catch {};
40
4139 return ast;
4240}
4341
......@@ -50,18 +48,52 @@ pub fn deinit(p: *Parse) void {
5048}
5149
5250pub const Error = error{
53 SyntaxError,
51 OutOfMemory,
52 ParseFail,
5453};
5554
5655/// When error.SyntaxError is returned, `p.err` is set to this as well as `p.pos`
5756pub const AstError = union(enum) {
57 tokenization: struct {
58 err: Tokenizer.TokenizeError,
59 byte: u32,
60 },
5861 expected_token: Token.Tag,
5962 expected_statement,
6063 expected_expr,
6164 expected_else_value,
65
66 pub fn toReport(parse_err: AstError, comp: *Comp, file: *Comp.File) !Report {
67 return (Report.Err{ .ast = parse_err }).toReport(comp, file);
68 }
69
70 pub fn build(err: AstError, ctx: *Report.MsgContext) !void {
71 switch (err) {
72 .tokenization => |data| {
73 // TODO: consider making tokenizer emit AstError more directly,
74 // so that it can just annotate start and ends
75 try ctx.msg(switch (data.err) {
76 error.UnterminatedString => .{ .codepoint_eol = data.byte },
77 else => .{ .codepoint = data.byte },
78 }, "{s}", .{switch (data.err) {
79 error.OutOfMemory => unreachable,
80 error.InvalidSyntax => "unexpected token",
81 error.TrailingNumericSeparator => "trailing numberic separator",
82 error.UnterminatedString => "unterminated string literal",
83 error.InvalidEscapeSequence => "invalid escape sequence",
84 error.InvalidEscapeCodepoint => "invalid escape codepoint",
85 error.InvalidUtf8 => "file is not valid utf8",
86 error.GitConflict => "git merge conflict encountered",
87 }});
88 },
89
90 else => unreachable,
91 //
92 }
93 }
6294};
6395
64pub fn parseRoot(p: *Parse) !void {
96pub fn parseRoot(p: *Parse) Error!void {
6597 const trace = t.trace(@src());
6698 defer trace.end();
6799
......@@ -82,7 +114,7 @@ pub fn parseRoot(p: *Parse) !void {
82114 };
83115}
84116
85fn parseModuleMembers(p: *Parse) !SubList {
117fn parseModuleMembers(p: *Parse) Error!SubList {
86118 const trace = t.trace(@src());
87119 defer trace.end();
88120
......@@ -103,7 +135,7 @@ fn parseModuleMembers(p: *Parse) !SubList {
103135 .kw_enum,
104136 .kw_trait,
105137 .kw_impl,
106 => return error.Todo,
138 => |tag| std.debug.panic("TODO: parse {s}", .{@tagName(tag)}),
107139
108140 .eof => break,
109141
......@@ -120,7 +152,7 @@ fn parseModuleMembers(p: *Parse) !SubList {
120152 return p.scratchToSubList(scratch_top);
121153}
122154
123fn parseModuleDecl(p: *Parse) !NodeIndex {
155fn parseModuleDecl(p: *Parse) Error!NodeIndex {
124156 const trace = t.trace(@src());
125157 defer trace.end();
126158
......@@ -135,7 +167,7 @@ fn parseModuleDecl(p: *Parse) !NodeIndex {
135167 };
136168}
137169
138fn parseFnDecl(p: *Parse) !NodeIndex {
170fn parseFnDecl(p: *Parse) Error!NodeIndex {
139171 const trace = t.trace(@src());
140172 defer trace.end();
141173
......@@ -170,7 +202,7 @@ fn parseFnDecl(p: *Parse) !NodeIndex {
170202 });
171203}
172204
173fn parseParamList(p: *Parse) !SubList {
205fn parseParamList(p: *Parse) Error!SubList {
174206 const trace = t.trace(@src());
175207 defer trace.end();
176208
......@@ -199,7 +231,7 @@ fn parseParamList(p: *Parse) !SubList {
199231 return p.scratchToSubList(scratch_top);
200232}
201233
202fn parseParamDecl(p: *Parse) !NodeIndex {
234fn parseParamDecl(p: *Parse) Error!NodeIndex {
203235 const trace = t.trace(@src());
204236 defer trace.end();
205237
......@@ -218,7 +250,7 @@ fn parseParamDecl(p: *Parse) !NodeIndex {
218250 });
219251}
220252
221fn parseBlock(p: *Parse) !NodeIndex {
253fn parseBlock(p: *Parse) Error!NodeIndex {
222254 const trace = t.trace(@src());
223255 defer trace.end();
224256
......@@ -236,7 +268,7 @@ fn parseBlock(p: *Parse) !NodeIndex {
236268 });
237269}
238270
239fn parseLabelExpression(p: *Parse) !NodeIndex {
271fn parseLabelExpression(p: *Parse) Error!NodeIndex {
240272 const trace = t.trace(@src());
241273 defer trace.end();
242274
......@@ -252,7 +284,7 @@ fn parseLabelExpression(p: *Parse) !NodeIndex {
252284 });
253285}
254286
255fn parseBlockContents(p: *Parse) !struct { Node.Tag, SubList } {
287fn parseBlockContents(p: *Parse) Error!struct { Node.Tag, SubList } {
256288 const trace = t.trace(@src());
257289 defer trace.end();
258290
......@@ -308,7 +340,7 @@ fn parseBlockContents(p: *Parse) !struct { Node.Tag, SubList } {
308340}
309341
310342/// VarDecl <- (kw_pub)? (kw_var | kw_let) id (colon expr)? equal expr semi
311fn parseVarDecl(p: *Parse) !NodeIndex {
343fn parseVarDecl(p: *Parse) Error!NodeIndex {
312344 const trace = t.trace(@src());
313345 defer trace.end();
314346
......@@ -521,7 +553,7 @@ const Operator = enum(u8) {
521553 }
522554};
523555
524fn parseExpression(p: *Parse) anyerror!NodeIndex {
556fn parseExpression(p: *Parse) Error!NodeIndex {
525557 const trace = t.trace(@src());
526558 defer trace.end();
527559
......@@ -543,7 +575,7 @@ fn parseExpression(p: *Parse) anyerror!NodeIndex {
543575 return expr;
544576}
545577
546fn parseExpressionPart(p: *Parse) !NodeIndex {
578fn parseExpressionPart(p: *Parse) Error!NodeIndex {
547579 const trace = t.trace(@src());
548580 defer trace.end();
549581
......@@ -644,7 +676,7 @@ fn parseExpressionPart(p: *Parse) !NodeIndex {
644676 return expr;
645677}
646678
647fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) !NodeIndex {
679fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) Error!NodeIndex {
648680 const trace = t.trace(@src());
649681 defer trace.end();
650682
......@@ -673,7 +705,7 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) !NodeIndex {
673705 });
674706}
675707
676fn parseReturn(p: *Parse) !NodeIndex {
708fn parseReturn(p: *Parse) Error!NodeIndex {
677709 const trace = t.trace(@src());
678710 defer trace.end();
679711
......@@ -693,7 +725,7 @@ fn parseReturn(p: *Parse) !NodeIndex {
693725 });
694726}
695727
696fn parseBreak(p: *Parse) !NodeIndex {
728fn parseBreak(p: *Parse) Error!NodeIndex {
697729 const trace = t.trace(@src());
698730 defer trace.end();
699731
......@@ -719,7 +751,7 @@ fn parseBreak(p: *Parse) !NodeIndex {
719751 });
720752}
721753
722fn parseBuiltinCall(p: *Parse) !NodeIndex {
754fn parseBuiltinCall(p: *Parse) Error!NodeIndex {
723755 const trace = t.trace(@src());
724756 defer trace.end();
725757
......@@ -736,7 +768,7 @@ fn parseBuiltinCall(p: *Parse) !NodeIndex {
736768 });
737769}
738770
739fn parseCallArgs(p: *Parse) anyerror!SubList {
771fn parseCallArgs(p: *Parse) Error!SubList {
740772 const trace = t.trace(@src());
741773 defer trace.end();
742774
......@@ -764,7 +796,7 @@ fn parseCallArgs(p: *Parse) anyerror!SubList {
764796 return p.scratchToSubList(scratch_top);
765797}
766798
767fn parseIf(p: *Parse) !NodeIndex {
799fn parseIf(p: *Parse) Error!NodeIndex {
768800 const trace = t.trace(@src());
769801 defer trace.end();
770802
......@@ -795,7 +827,7 @@ fn parseIf(p: *Parse) !NodeIndex {
795827 });
796828}
797829
798fn parseElseValue(p: *Parse) anyerror!NodeIndex {
830fn parseElseValue(p: *Parse) Error!NodeIndex {
799831 const trace = t.trace(@src());
800832 defer trace.end();
801833
......@@ -810,7 +842,7 @@ fn parseElseValue(p: *Parse) anyerror!NodeIndex {
810842 };
811843}
812844
813fn parseGroup(p: *Parse) !NodeIndex {
845fn parseGroup(p: *Parse) Error!NodeIndex {
814846 const trace = t.trace(@src());
815847 defer trace.end();
816848
......@@ -855,7 +887,7 @@ fn addExtra(p: *Parse, comptime T: type, extra: T) !ExtraIndex {
855887fn fail(p: *Parse, ast_error: AstError) Error {
856888 p.ast_error = ast_error;
857889 std.debug.print("got {s}", .{@tagName(p.currentTag())});
858 return Error.SyntaxError;
890 return Error.ParseFail;
859891}
860892
861893fn blankNode(p: *Parse, comptime tag: Token.Tag) !NodeIndex {
......@@ -959,3 +991,6 @@ const optionalNodeIndex = OptionalNodeIndex.init;
959991const optionalExtraIndex = OptionalExtraIndex.init;
960992
961993const t = @import("tracer");
994
995const Report = @import("Report.zig");
996const Comp = @import("Comp.zig");
src/compiler/Report.zig+466-15
......@@ -1,38 +1,489 @@
1//! A Report is a list of errors and warnings for a part of a compilation.
2//!
3//! `Err/Warn` structs are unions of every possible message, the logic
4//! for printing them is all done here.
5//!
6//! Across the compiler, `Report.Builder`s' `.err` and `.warn` methods
7//! can add messages by ID.
18errors: []Item = &.{},
2warns: []Item = &.{},
9warnings: []Item = &.{},
310string_bytes: []u8 = &.{},
411
512pub const Item = struct {
6 offset: ByteOffset,
7 message: String,
13 /// What is this diagnostic
14 data: union { @"error": Err, warn: Warn },
15 /// What file was it in
16 url: String,
17
18 /// Type is never stored, inferred from which list it is in.
19 pub const Type = enum { @"error", warn };
20};
21
22pub const Err = union(enum) {
23 pub const FieldEnum = std.meta.FieldEnum(Err);
24
25 /// Constructs a `Report` with just a single error.
26 pub fn toReport(err: Err, comp: *Comp, file: *File) !Report {
27 const string_bytes = try comp.gpa.dupe(u8, file.file_path);
28 errdefer comp.gpa.free(string_bytes);
29
30 const errors = try comp.gpa.dupe(Item, &.{.{
31 .data = .{ .@"error" = err },
32 .url = .{ .start = 0, .len = @intCast(string_bytes.len) },
33 }});
34 errdefer comp.gpa.free(errors);
35
36 return .{ .errors = errors, .string_bytes = string_bytes };
37 }
38
39 ast: @import("Parse.zig").AstError,
40
41 unknown_identifier: struct {
42 node: NodeIndex,
43
44 fn build(data: @This(), ctx: *MsgContext) !void {
45 try ctx.msgNode(data.node, "unknown identifier '{s}'", .{
46 ctx.ast.basicNodeSlice(data.node),
47 });
48 }
49 },
50 invalid_builtin: struct {
51 node: NodeIndex,
52
53 fn build(data: @This(), ctx: *MsgContext) !void {
54 try ctx.msgToken(
55 ctx.ast.nodeToken(data.node),
56 "unknown built-in function '{s}'",
57 .{ctx.ast.basicNodeSlice(data.node)},
58 );
59 }
60 },
61 invalid_discard: struct {
62 node: NodeIndex,
63
64 fn build(data: @This(), ctx: *MsgContext) !void {
65 try ctx.msgNode(data.node, "local cannot be named '_'", .{});
66 }
67 },
68 duplicate_function: struct {
69 node: NodeIndex,
70 prev: NodeIndex,
71
72 fn build(data: @This(), ctx: *MsgContext) !void {
73 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of '{s}'", .{
74 ctx.ast.fullFnDecl(data.node).id,
75 });
76 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.prev), "also declared here", .{});
77 // TODO: be able to store a full list of other declarations
78 // and add them here as "and here" and so on
79 }
80 },
81 duplicate_param: struct {
82 node: NodeIndex,
83 prev: NodeIndex,
84
85 fn build(data: @This(), ctx: *MsgContext) !void {
86 try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of '{s}'", .{
87 ctx.ast.fullParamDecl(data.node).id,
88 });
89 try ctx.msgToken(ctx.ast.nodeToken(data.prev), "also declared here", .{});
90 // TODO: be able to store a full list of other declarations
91 // and add them here as "and here" and so on
92 }
93 },
94 invalid_return: struct {
95 node: NodeIndex,
96
97 fn build(data: @This(), ctx: *MsgContext) !void {
98 try ctx.msgNode(
99 data.node,
100 "return cannot be used here",
101 .{},
102 );
103 }
104 },
105 unresolved_label: struct {
106 node: NodeIndex,
107
108 fn build(data: @This(), ctx: *MsgContext) !void {
109 try ctx.msg(
110 .{ .two_tokens = ctx.ast.nodeToken(data.node) + 1 },
111 "unknown block label '{s}'",
112 .{ctx.ast.tokenSlice(ctx.ast.nodeToken(data.node) + 2)},
113 );
114 }
115 },
116 shadow_builtin_variable: struct {
117 node: NodeIndex,
118
119 fn build(data: @This(), ctx: *MsgContext) !void {
120 try ctx.msgToken(
121 ctx.ast.nodeToken(data.node) + 1,
122 "declaration shadows built-in constant '{s}'",
123 .{ctx.ast.fullVarDecl(data.node).id},
124 );
125 }
126 },
127};
128
129pub const Warn = union(enum) {
130 pub const FieldEnum = std.meta.FieldEnum(Warn);
131
132 unused: struct {
133 node: NodeIndex,
134 kind: UnusedKind,
135
136 fn build(data: @This(), ctx: *MsgContext) !void {
137 try ctx.msgToken(ctx.ast.idToken(data.node), "unused {s} '{s}'", .{
138 @tagName(data.kind),
139 switch (ctx.ast.nodeTag(data.node)) {
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 });
146 }
147 },
148};
149
150pub const UnusedKind = enum {
151 param,
152 constant,
153 variable,
154 function,
8155};
9156
10157pub const String = struct {
11158 start: u32,
12159 len: u32,
13160
14 pub fn message(item: Item, report: Report) []const u8 {
15 return report.string_bytes[item.message_start..][0..item.message_len];
161 pub fn slice(str: String, report: Report) []const u8 {
162 return report.string_bytes[str.start..][0..str.len];
163 }
164};
165
166const Prefix = enum {
167 @"error",
168 warn,
169 note,
170
171 fn color(prefix: Prefix) std.io.tty.Color {
172 return switch (prefix) {
173 .@"error" => .red,
174 .warn => .yellow,
175 .note => .blue,
176 };
16177 }
17178};
18179
19pub fn hasErrors(report: Report) bool {
20 return report.errors.len > 0;
180pub fn deinit(report: Report, gpa: Allocator) void {
181 gpa.free(report.errors);
182 gpa.free(report.warnings);
183 gpa.free(report.string_bytes);
21184}
22185
23pub fn hasWarns(report: Report) bool {
24 return report.warns.len > 0;
186pub fn dumpToWriter(report: Report, comp: *Comp, writer: anytype) !void {
187 const tty = util.detectTTYFromWriter(writer);
188 try writer.print("{d} errors, {d} warnings\n", .{ report.errors.len, report.warnings.len });
189 for (report.errors) |item| {
190 try report.formatItem(item, .@"error", comp, writer, tty);
191 }
192 for (report.warnings) |item| {
193 try report.formatItem(item, .warn, comp, writer, tty);
194 }
25195}
26196
27pub const Wip = struct {
28 errors: ArrayListUnmanaged(Item),
29 warns: ArrayListUnmanaged(Item),
30 string_bytes: ArrayListUnmanaged(u8),
197pub fn formatItem(
198 report: Report,
199 item: Item,
200 item_type: Item.Type,
201 comp: *Comp,
202 writer: anytype,
203 tty: std.io.tty.Config,
204) !void {
205 var arena = std.heap.ArenaAllocator.init(comp.gpa);
206 defer arena.deinit();
207 var sfa = std.heap.stackFallback(16384, arena.allocator());
208 const alloc = sfa.get();
209
210 const file = try comp.getFile(item.url.slice(report));
211
212 const need_load_ast = !(item_type == .@"error" and item.data.@"error" == .ast);
213 const ast_data = if (need_load_ast) try file.getAst(comp) else undefined;
214 const ast = if (need_load_ast) &ast_data else null;
215
216 var ctx: MsgContext = .{
217 .arena = alloc,
218 .comp = comp,
219 .file = file,
220 .ast = &ast_data,
221 .components = ArrayListUnmanaged(Component).initCapacity(alloc, 16) catch
222 unreachable, // stack-fallback allocator has enough space.
223 };
224
225 switch (item_type) {
226 inline else => |tag| switch (@field(item.data, @tagName(tag))) {
227 inline else => |payload| try payload.build(&ctx),
228 },
229 }
230
231 assert(ctx.components.items.len > 0);
232
233 for (ctx.components.items, 1..) |component, i| {
234 const prefix: Prefix = if (i == 1)
235 switch (item_type) {
236 inline else => |tag| @field(Prefix, @tagName(tag)),
237 }
238 else
239 .note;
240
241 const is_last = i == ctx.components.items.len;
242
243 if (i != 1) {
244 try tty.setColor(writer, .dim);
245 try writer.writeAll(" │ ");
246 try tty.setColor(writer, .reset);
247 }
248
249 try tty.setColor(writer, prefix.color());
250 try writer.writeAll(@tagName(prefix));
251 try tty.setColor(writer, .reset);
252 try tty.setColor(writer, .dim);
253 try writer.writeAll(": ");
254 try tty.setColor(writer, .reset);
255 try writer.writeAll(component.message_utf8);
256 try writer.writeAll("\n");
257
258 const SliceLoc = struct {
259 loc: Location,
260 byte: ByteOffset,
261 slice: []const u8,
262 };
263
264 const utf8_source = (try file.getSource(comp.gpa)).code;
265
266 const at: SliceLoc = switch (component.highlight) {
267 inline .node,
268 .token,
269 .two_tokens,
270 => |payload, tag| loc: {
271 const token_index: TokenIndex = switch (tag) {
272 .node => ast.?.nodeToken(payload),
273 .token, .two_tokens => payload,
274 else => comptime unreachable,
275 };
276
277 const location = ast.?.tokenLocation(0, token_index);
278 const token = ast.?.tokens.get(token_index);
279
280 break :loc .{
281 .loc = location,
282 .slice = token.slice(ast.?.utf8_source),
283 .byte = token.offset,
284 };
285 },
286
287 inline .codepoint,
288 .codepoint_eol,
289 => |b, tag| loc: {
290 const len = switch (tag) {
291 .codepoint => std.unicode.utf8ByteSequenceLength(utf8_source[b]) catch 1,
292 .codepoint_eol => if (std.mem.indexOfScalar(u8, utf8_source[b..], '\n')) |n|
293 n
294 else
295 (utf8_source.len - b),
296 else => comptime unreachable,
297 };
298 break :loc .{
299 .byte = b,
300 .slice = utf8_source[b..][0..len],
301 .loc = .{
302 .column = 0,
303 .line = 0,
304 .line_start = 0,
305 .line_end = utf8_source.len,
306 },
307 };
308 },
309 };
310
311 const line_before = utf8_source[at.loc.line_start..at.byte];
312 const trimmed_before = std.mem.trimLeft(u8, line_before, " \t\r");
313 const line_after = utf8_source[at.byte + at.slice.len .. at.loc.line_end + 1];
314 const width = std.fmt.count("{d}", .{at.loc.line});
315
316 try tty.setColor(writer, .dim);
317 try writer.writeByteNTimes(' ', width);
318 try writer.print("{s}{s}:{d}:{d}\n", .{
319 if (i == 1) "┬ " else "├ ",
320 file.file_path,
321 at.loc.line,
322 at.loc.column,
323 });
324 try writer.print("{d}", .{at.loc.line});
325 try writer.print("{s} ", .{"│"});
326 try tty.setColor(writer, .reset);
327 try writer.writeAll(trimmed_before);
328 try tty.setColor(writer, prefix.color());
329 try tty.setColor(writer, .bold);
330 try writer.writeAll(at.slice);
331 try tty.setColor(writer, .reset);
332 try writer.writeAll(line_after);
333 try tty.setColor(writer, .dim);
334 try writer.writeAll(if (is_last) " ╰" else " ├");
335 try writer.writeBytesNTimes(
336 "─",
337 width +
338 (at.byte - at.loc.line_start) -
339 (line_before.len - trimmed_before.len),
340 );
341 try tty.setColor(writer, .reset);
342 try tty.setColor(writer, prefix.color());
343 try tty.setColor(writer, .bold);
344 try writer.writeBytesNTimes("━", at.slice.len);
345 try tty.setColor(writer, .reset);
346 try writer.writeAll("\n");
347 }
348}
349
350pub fn dumpToStdErr(report: Report, comp: *Comp) !void {
351 const stderr = std.io.getStdErr().writer();
352
353 // Disabled because of coloring issues.
354 // var buf = std.io.bufferedWriter(stderr);
355 // const writer = buf.writer();
356 try dumpToWriter(report, comp, stderr);
357 // try buf.flush();
358}
359
360pub const Builder = struct {
361 gpa: Allocator,
362 current_file: *Comp.File,
363 errors: ArrayListUnmanaged(Item) = .{},
364 warnings: ArrayListUnmanaged(Item) = .{},
365 string_bytes: ArrayListUnmanaged(u8) = .{},
366 current_file_str: ?String = null,
367
368 pub fn err(b: *Builder, comptime kind: Err.FieldEnum, data: std.meta.FieldType(Err, kind)) !void {
369 try b.errors.append(b.gpa, .{
370 .data = .{ .@"error" = @unionInit(Err, @tagName(kind), data) },
371 .url = try b.currentFileString(),
372 });
373 }
374
375 pub fn warn(b: *Builder, comptime kind: Warn.FieldEnum, data: std.meta.FieldType(Warn, kind)) !void {
376 try b.warnings.append(b.gpa, .{
377 .data = .{ .warn = @unionInit(Warn, @tagName(kind), data) },
378 .url = try b.currentFileString(),
379 });
380 }
381
382 pub fn addString(b: *Builder, str: []const u8) !String {
383 const top = b.string_bytes.items.len;
384 try b.string_bytes.appendSlice(b.gpa, str);
385 return .{ .start = @intCast(top), .len = @intCast(str.len) };
386 }
387
388 pub fn currentFileString(b: *Builder) !String {
389 return b.current_file_str orelse {
390 b.current_file_str = try b.addString(b.current_file.file_path);
391 return b.current_file_str.?;
392 };
393 }
394
395 pub fn deinit(b: *Builder) void {
396 b.errors.deinit(b.gpa);
397 b.warnings.deinit(b.gpa);
398 }
399
400 pub fn finalize(b: *Builder) !Report {
401 defer b.* = undefined;
402 errdefer b.deinit();
403
404 const errors = try b.errors.toOwnedSlice(b.gpa);
405 errdefer b.gpa.free(errors);
406
407 const warnings = try b.warnings.toOwnedSlice(b.gpa);
408 errdefer b.gpa.free(warnings);
409
410 const string_bytes = try b.string_bytes.toOwnedSlice(b.gpa);
411 errdefer b.gpa.free(string_bytes);
412
413 return .{
414 .errors = errors,
415 .warnings = warnings,
416 .string_bytes = string_bytes,
417 };
418 }
419};
420
421pub const Highlight = union(enum) {
422 // Highlight one token
423 token: TokenIndex,
424 // Highlight two tokens
425 two_tokens: TokenIndex,
426 // Highlight a Node
427 node: NodeIndex,
428 // Highlight a codepoint
429 codepoint: ByteOffset,
430 // Highlight from the start of a codepoint to '\n'
431 codepoint_eol: ByteOffset,
432};
433
434const Component = struct {
435 message_utf8: []const u8,
436 highlight: Highlight,
437};
438
439/// This serves as a small API to make creating messages easier.
440pub const MsgContext = struct {
441 file: *File,
442 comp: *Comp,
443 ast: *const Ast,
444
445 /// This arena is is a stack fallback with 16kb, then
446 /// falling back to an arena allocator. This should
447 /// make the cost of arbitrarily creating `Component`
448 /// nodes extremely lightweight.
449 arena: Allocator,
450
451 /// First component is "error" or "warn"
452 /// Trailing components are "note"
453 components: ArrayListUnmanaged(Component),
454
455 pub fn msg(m: *MsgContext, highlight: Highlight, comptime fmt: []const u8, args: anytype) !void {
456 const text = try std.fmt.allocPrint(m.arena, fmt, args);
457 try m.components.append(m.arena, .{
458 .message_utf8 = text,
459 .highlight = highlight,
460 });
461 }
462
463 pub fn msgNode(m: *MsgContext, node: NodeIndex, comptime fmt: []const u8, args: anytype) !void {
464 return m.msg(.{ .node = node }, fmt, args);
465 }
466
467 pub fn msgToken(m: *MsgContext, token: TokenIndex, comptime fmt: []const u8, args: anytype) !void {
468 return m.msg(.{ .token = token }, fmt, args);
469 }
31470};
32471
33472const Report = @This();
34473const std = @import("std");
35474const ArrayListUnmanaged = std.ArrayListUnmanaged;
475const Allocator = std.mem.Allocator;
36476const assert = std.debug.assert;
37pub const Tokenizer = @import("Tokenizer.zig");
38pub const ByteOffset = Tokenizer.ByteOffset;
477
478const Tokenizer = @import("Tokenizer.zig");
479const ByteOffset = Tokenizer.ByteOffset;
480
481const Ast = @import("Ast.zig");
482const TokenIndex = Ast.TokenIndex;
483const NodeIndex = Ast.NodeIndex;
484const Location = Ast.Location;
485
486const Comp = @import("Comp.zig");
487const File = Comp.File;
488
489const util = @import("util.zig");
src/compiler/Sema.zig+11-32
......@@ -37,7 +37,6 @@ const InstMap = struct {
3737
3838 pub fn init(allocator: Allocator, code: *const Teg, inst_start: Teg.Inst.Index) !InstMap {
3939 const decl = code.declData(inst_start);
40 log.info("pre-allocating {d} map-indicies for InstMap", .{decl.instruction_count});
4140 const insts = try allocator.alloc(Air.Inst.Ref.Optional, decl.instruction_count);
4241 @memset(insts, .none);
4342 return .{
......@@ -72,7 +71,6 @@ pub fn init(s: *Sema) !void {
7271
7372 try s.comp.current_compile_decls.append(s.gpa, s.decl_index);
7473
75 log.info("init sema on index {}", .{s.decl_index});
7674 s.inst_map = try InstMap.init(
7775 s.arena,
7876 &s.code,
......@@ -193,8 +191,6 @@ fn analyzeCode(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref {
193191 const t = tracer.traceFmt(@src(), "%{d}, {s}", .{ @intFromEnum(in), @tagName(s.code.instTag(in)) });
194192 defer t.end();
195193
196 log.info("runtime %{d} = .{s}", .{ @intFromEnum(in), @tagName(s.code.instTag(in)) });
197
198194 const out = try s.analyzeCodeInner(in);
199195 if (s.code.instTag(in) != .block)
200196 try s.inst_map.add(in, out);
......@@ -226,8 +222,6 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref {
226222 const string = rhs.asString(s.ip);
227223
228224 const base_type = s.typeOfRef(base);
229 log.info("process field with type {any} . {s}", .{ base_type, string.toSlice(s.ip) });
230
231225 switch (base_type) {
232226 .opaque_type => {
233227 return s.addInstRef(.field, .{
......@@ -277,18 +271,12 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref {
277271 }
278272
279273 const left_type = s.typeOfRef(lhs);
274 _ = left_type; // autofix
280275 const right_type = s.typeOfRef(rhs);
276 _ = right_type; // autofix
281277
282278 // TODO: validate type for operation
283279
284 log.info("runtime {s} operation on {} and {}", .{
285 @tagName(tag),
286 left_type.toKey(s.ip),
287 right_type.toKey(s.ip),
288 });
289
290 // todo: type check
291
292280 return s.addInstRef(tag.airTag(), .{
293281 .lhs = lhs,
294282 .rhs = rhs,
......@@ -300,14 +288,10 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref {
300288 const operand = try s.resolveRefRuntime(un.operand);
301289
302290 const operand_type = s.typeOfRef(operand);
291 _ = operand_type; // autofix
303292
304293 // TODO: validate type for operation
305294
306 log.info("runtime {s} operation on {}", .{
307 @tagName(tag),
308 operand_type.toKey(s.ip),
309 });
310
311295 return s.addInstRef(tag.airTag(), .{
312296 .operand = operand,
313297 });
......@@ -425,7 +409,6 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref {
425409fn markInst(s: *Sema, inst: Air.Inst.Index) void {
426410 const flags: *Air.Flags = &s.air_instructions.items(.flags)[@intFromEnum(inst)];
427411 flags.uses = flags.uses.increment();
428 log.info("markInst {}", .{inst});
429412}
430413
431414fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) anyerror!Air.Inst.Ref {
......@@ -485,15 +468,13 @@ fn resolveRefComptime(s: *Sema, ref: Teg.Inst.Ref) !Value {
485468 }
486469 }
487470
488 const tag = s.code.instTag(in);
489 log.info("comptime %{d} = .{s}", .{ @intFromEnum(in), @tagName(tag) });
490 switch (tag) {
471 switch (s.code.instTag(in)) {
491472 .string => {
492473 const string: Teg.SlicedString = s.code.instPayload(.string, in);
493474 return s.makeValue(.{ .string = string.slice(s.code.string_bytes) });
494475 },
495476
496 else => std.debug.panic("TODO: comptime analyze of .{s}", .{@tagName(tag)}),
477 else => |tag| std.debug.panic("TODO: comptime analyze of .{s}", .{@tagName(tag)}),
497478 }
498479}
499480
......@@ -509,12 +490,13 @@ fn resolveDecl(s: *Sema, decl_index: Decl.Index) !Value {
509490 var arena = std.heap.ArenaAllocator.init(s.gpa);
510491 defer arena.deinit();
511492
493 var todo: Report = undefined;
512494 var sema: Sema = .{
513495 .comp = s.comp,
514496 .gpa = s.gpa,
515497 .ip = s.ip,
516498 .arena = arena.allocator(),
517 .code = try s.ip.namespacePtr(decl.src_namespace).file_scope.getTeg(s.gpa),
499 .code = try s.ip.namespacePtr(decl.src_namespace).file_scope.getTeg(s.comp, &todo),
518500 .decl = decl,
519501 .decl_index = decl_index,
520502 .namespace_index = decl.src_namespace,
......@@ -529,10 +511,9 @@ fn resolveDecl(s: *Sema, decl_index: Decl.Index) !Value {
529511
530512 return value;
531513 },
532 .complete => {
514 else => {
533515 return decl.val;
534516 },
535 else => |tag| std.debug.panic("TODO: load_decl when decl is in state .{s}", .{@tagName(tag)}),
536517 }
537518}
538519
......@@ -573,7 +554,6 @@ const IncompleteBlock = struct {
573554};
574555
575556fn addBlock(s: *Sema) !IncompleteBlock {
576 log.info("start block at @{d}", .{s.air_instructions.len});
577557 try s.air_instructions.append(s.gpa, .{
578558 .tag = .block,
579559 .data = undefined,
......@@ -583,7 +563,6 @@ fn addBlock(s: *Sema) !IncompleteBlock {
583563}
584564
585565fn addInstRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref {
586 log.info("add air .{s}, {}", .{ @tagName(tag), data });
587566 try s.air_instructions.append(s.gpa, .{
588567 .tag = tag,
589568 .data = Air.EncodedData.encode(tag, data),
......@@ -627,11 +606,9 @@ fn mapTeg(s: *Sema, inst: Teg.Inst.Ref) Air.Inst.Ref {
627606}
628607
629608fn validateType(s: *Sema, checked_type: Value, wanted_type: InternPool.Index) !void {
609 _ = s; // autofix
630610 if (checked_type.toIntern() != wanted_type) {
631 log.info("failed type check {}, got {}", .{ Value.fromInterned(wanted_type).fmt(s.ip), checked_type.fmt(s.ip) });
632611 return error.TypeCheckFailed;
633 } else {
634 log.info("passed type check {}", .{checked_type.fmt(s.ip)});
635612 }
636613}
637614
......@@ -690,4 +667,6 @@ const InternPool = @import("InternPool.zig");
690667const NullTerminatedString = InternPool.NullTerminatedString;
691668const dod = @import("dod.zig");
692669
670const Report = @import("Report.zig");
671
693672const tracer = @import("tracer");
src/compiler/Teg.zig+4
......@@ -3,6 +3,7 @@
33insts: Inst.List.Slice,
44string_bytes: []const u8,
55extra: ExtraStore = .{},
6report: Report,
67
78pub const ExtraStore = dod.ExtraStore(.teg, .@"const");
89pub const EncodedData = dod.EncodedData(Inst.Tag, u64);
......@@ -343,6 +344,7 @@ pub fn deinit(teg: *Teg, gpa: std.mem.Allocator) void {
343344 gpa.free(teg.string_bytes);
344345 teg.extra.deinit(gpa);
345346 teg.insts.deinit(gpa);
347 teg.report.deinit(gpa);
346348 teg.* = undefined;
347349}
348350
......@@ -433,3 +435,5 @@ const Float = dod.Float;
433435const InternPool = @import("InternPool.zig");
434436
435437const Air = @import("Air.zig");
438
439const Report = @import("Report.zig");
src/compiler/Tokenizer.zig+96-79
......@@ -5,18 +5,14 @@
55//! Retriving payloads like string/numeric values, identifier names, etc is
66//! done separately, either in Parse or Transform, when the respective data
77//! is actually needed. Tokenization does guarante that such data is valid.
8
9// TODO: make `code` a [:0]const u8. this doesnt disallow null bytes, but
10// it requires the source to end with one. This can make byteAt(1) return u8
11// instead of an optional. I am told this improves performance.
12code: []const u8,
8code: [:0]const u8,
139pos: ByteOffset = 0,
1410
1511/// Tokenize source code into a list of tokens.
1612/// When an error occurs, the position of the error is returned in `error_pos`.
1713/// Caller owns returned slice.
1814pub fn tokenize(
19 source_code: []const u8,
15 source_code: [:0]const u8,
2016 gpa: mem.Allocator,
2117 error_pos: *ByteOffset,
2218) TokenizeError!Token.List.Slice {
......@@ -233,7 +229,7 @@ pub const Token = struct {
233229 ///
234230 /// If you are computing the length of something that is knowable
235231 /// without the source code, use token.tag.staticLen() instead.
236 pub fn len(token: Token, utf8_source: []const u8) u32 {
232 pub fn len(token: Token, utf8_source: [:0]const u8) u32 {
237233 return token.tag.staticLen() orelse {
238234 var tokenizer = Tokenizer{ .code = utf8_source, .pos = token.offset };
239235 assert(
......@@ -246,7 +242,7 @@ pub const Token = struct {
246242 };
247243 }
248244
249 pub fn slice(token: Token, utf8_source: []const u8) []const u8 {
245 pub fn slice(token: Token, utf8_source: [:0]const u8) []const u8 {
250246 return utf8_source[token.offset..][0..token.len(utf8_source)];
251247 }
252248
......@@ -294,7 +290,7 @@ pub fn next(t: *Tokenizer) !?Token {
294290 const trace = T.trace(@src());
295291 defer trace.end();
296292
297 switch (t.byte() orelse return null) {
293 switch (t.byte()) {
298294 ' ', '\t', '\n', '\r' => unreachable, // call skipWhitespace
299295
300296 'a'...'z', 'A'...'Z', '_', '$' => {
......@@ -303,7 +299,7 @@ pub fn next(t: *Tokenizer) !?Token {
303299 return try t.tokenizeIdentifier(start);
304300 },
305301
306 '@' => switch (t.byteAt(1) orelse 0) {
302 '@' => switch (t.nextByte()) {
307303 '"' => {
308304 const start = t.pos;
309305 t.pos += 1;
......@@ -319,12 +315,12 @@ pub fn next(t: *Tokenizer) !?Token {
319315
320316 '0'...'9' => return try t.tokenizeNumber(),
321317
322 '.' => switch (t.byteAt(1) orelse 0) {
318 '.' => switch (t.nextByte()) {
323319 '0'...'9' => return try t.tokenizeNumber(),
324320 else => return t.simpleToken(.dot),
325321 },
326322
327 '/' => switch (t.byteAt(1) orelse 0) {
323 '/' => switch (t.nextByte()) {
328324 '=' => return t.simpleToken(.slash_equal),
329325 '/' => {
330326 if ((t.byteAt(2) orelse 0 == '/') and
......@@ -349,7 +345,7 @@ pub fn next(t: *Tokenizer) !?Token {
349345 else => return t.simpleToken(.slash),
350346 },
351347
352 '\\' => switch (t.byteAt(1) orelse 0) {
348 '\\' => switch (t.nextByte()) {
353349 '\\' => {
354350 const start = t.pos;
355351 t.pos = @intCast(mem.indexOfScalarPos(u8, t.code, t.pos + 2, '\n') orelse t.code.len);
......@@ -360,30 +356,30 @@ pub fn next(t: *Tokenizer) !?Token {
360356
361357 '"' => return try t.tokenizeString(),
362358
363 '&' => switch (t.byteAt(1) orelse 0) {
359 '&' => switch (t.nextByte()) {
364360 '&' => return t.simpleToken(.and_and),
365361 '=' => return t.simpleToken(.and_equal),
366362 else => return t.simpleToken(.@"and"),
367363 },
368 '|' => switch (t.byteAt(1) orelse 0) {
364 '|' => switch (t.nextByte()) {
369365 '|' => return t.simpleToken(.pipe_pipe),
370366 '=' => return t.simpleToken(.pipe_equal),
371367 else => return t.simpleToken(.pipe),
372368 },
373 '^' => switch (t.byteAt(1) orelse 0) {
369 '^' => switch (t.nextByte()) {
374370 '^' => return t.simpleToken(.caret),
375371 '=' => return t.simpleToken(.caret_equal),
376372 else => return t.simpleToken(.caret),
377373 },
378 '!' => switch (t.byteAt(1) orelse 0) {
374 '!' => switch (t.nextByte()) {
379375 '=' => return t.simpleToken(.equal_equal),
380376 else => return t.simpleToken(.bang_equal),
381377 },
382 '=' => switch (t.byteAt(1) orelse 0) {
378 '=' => switch (t.nextByte()) {
383379 '=' => return t.simpleToken(.equal_equal),
384380 else => return t.simpleToken(.equal),
385381 },
386 '>' => switch (t.byteAt(1) orelse 0) {
382 '>' => switch (t.nextByte()) {
387383 '=' => return t.simpleToken(.gt_equal),
388384 '>' => switch (t.byteAt(2) orelse 0) {
389385 '>' => return t.simpleToken(.gt_gt_gt),
......@@ -391,7 +387,7 @@ pub fn next(t: *Tokenizer) !?Token {
391387 },
392388 else => return t.simpleToken(.gt),
393389 },
394 '<' => switch (t.byteAt(1) orelse 0) {
390 '<' => switch (t.nextByte()) {
395391 '=' => return t.simpleToken(.lt_equal),
396392 '<' => switch (t.byteAt(2) orelse 0) {
397393 '=' => return t.simpleToken(.lt_lt_equal),
......@@ -399,14 +395,12 @@ pub fn next(t: *Tokenizer) !?Token {
399395 // Git merge conflicts are <<<<<<<
400396 const start = t.pos;
401397 t.pos += 2;
402 brk: {
403 var remain: u3 = 4;
404 while (t.byte() orelse break :brk == '<') {
405 t.pos += 1;
406 remain -= 1;
407 if (remain == 0) {
408 return error.GitConflict;
409 }
398 var remain: u3 = 4;
399 while (t.byte() == '<') {
400 t.pos += 1;
401 remain -= 1;
402 if (remain == 0) {
403 return error.GitConflict;
410404 }
411405 }
412406 t.pos = start;
......@@ -415,25 +409,25 @@ pub fn next(t: *Tokenizer) !?Token {
415409 },
416410 else => return t.simpleToken(.lt),
417411 },
418 '+' => switch (t.byteAt(1) orelse 0) {
412 '+' => switch (t.nextByte()) {
419413 '=' => return t.simpleToken(.plus_equal),
420414 else => return t.simpleToken(.plus),
421415 },
422 '-' => switch (t.byteAt(1) orelse 0) {
416 '-' => switch (t.nextByte()) {
423417 '=' => return t.simpleToken(.minus_equal),
424418 '>' => return t.simpleToken(.minus_lt),
425419 else => return t.simpleToken(.minus),
426420 },
427 '*' => switch (t.byteAt(1) orelse 0) {
421 '*' => switch (t.nextByte()) {
428422 '*' => return t.simpleToken(.star_star),
429423 '=' => return t.simpleToken(.star_equal),
430424 else => return t.simpleToken(.star),
431425 },
432 '%' => switch (t.byteAt(1) orelse 0) {
426 '%' => switch (t.nextByte()) {
433427 '=' => return t.simpleToken(.percent_equal),
434428 else => return t.simpleToken(.percent),
435429 },
436 '~' => switch (t.byteAt(1) orelse 0) {
430 '~' => switch (t.nextByte()) {
437431 '=' => return t.simpleToken(.tilde_equal),
438432 else => return t.simpleToken(.tilde),
439433 },
......@@ -462,19 +456,40 @@ pub fn next(t: *Tokenizer) !?Token {
462456 },
463457
464458 else => return error.InvalidSyntax,
459
460 0 => {
461 if (t.pos == t.code.len) return null;
462 return error.InvalidSyntax;
463 },
465464 }
466465 }
467466}
468467
469fn byte(t: Tokenizer) ?u8 {
470 if (t.pos >= t.code.len) return null;
468/// Source code is null terminated, so accessing this is safe
469fn byte(t: Tokenizer) u8 {
471470 return t.code[t.pos];
472471}
473472
473/// Source code is null terminated.
474fn nextByte(t: Tokenizer) u8 {
475 assert(t.byte() < 0x80);
476 return t.code[t.pos + 1];
477}
478
474479/// Remember that using this only makes sense if you know for certain that all
475480/// previous bytes are ASCII, otherwise you may hit UB
476fn byteAt(t: Tokenizer, offset: u32) ?u8 {
477 if (t.pos + offset >= t.code.len) return null;
481fn byteAt(t: Tokenizer, comptime offset: u32) ?u8 {
482 if (offset == 1) @compileError("use nextByte()");
483
484 // assert that the previous bytes are ASCII
485 // current byte is allowed to be a continuation byte
486 for (t.code[t.pos .. t.pos + offset]) |b| {
487 assert(b < 0x80);
488 }
489 return t.code[t.pos + offset];
490}
491
492fn byteAtRt(t: Tokenizer, offset: u32) ?u8 {
478493 // assert that the previous bytes are ASCII
479494 // current byte is allowed to be a continuation byte
480495 for (t.code[t.pos .. t.pos + offset]) |b| {
......@@ -484,7 +499,7 @@ fn byteAt(t: Tokenizer, offset: u32) ?u8 {
484499}
485500
486501fn codePoint(t: Tokenizer) !struct { u3, u21 } {
487 const b = t.byte() orelse unreachable; // must check the first byte manually
502 const b = t.byte(); // must check the first byte manually
488503 assert(b >= 0x80); // must be a continuation byte
489504
490505 const byte_len = unicode.utf8ByteSequenceLength(b) catch
......@@ -498,7 +513,7 @@ fn codePoint(t: Tokenizer) !struct { u3, u21 } {
498513}
499514
500515fn isAtIdentifierStart(t: *Tokenizer) !bool {
501 switch (t.byte() orelse return false) {
516 switch (t.byte()) {
502517 'a'...'z', 'A'...'Z', '_', '$' => return true,
503518 else => return false,
504519 0x80...0xFF => {
......@@ -513,7 +528,7 @@ fn tokenizeIdentifier(t: *Tokenizer, start_byte: u32) !Token {
513528 defer trace.end();
514529
515530 while (true) {
516 switch (t.byte() orelse break) {
531 switch (t.byte()) {
517532 'a'...'z', 'A'...'Z', '0'...'9', '_', '$' => {
518533 t.pos += 1;
519534 },
......@@ -555,8 +570,8 @@ fn tokenizeNumber(t: *Tokenizer) !?Token {
555570 defer trace.end();
556571
557572 // Caller ensures the following invariant:
558 switch (t.byte() orelse unreachable) {
559 '.' => switch (t.byteAt(1) orelse unreachable) {
573 switch (t.byte()) {
574 '.' => switch (t.nextByte()) {
560575 '0'...'9' => {},
561576 else => unreachable,
562577 },
......@@ -566,7 +581,7 @@ fn tokenizeNumber(t: *Tokenizer) !?Token {
566581
567582 // Parse Hex, Binary, Octal numbers
568583 if (t.byte() == '0') {
569 switch (t.byteAt(1) orelse 0) {
584 switch (t.nextByte()) {
570585 'x', 'X' => {
571586 t.pos += 2;
572587 try t.consumeIntegerRadix(.hex);
......@@ -594,8 +609,8 @@ fn tokenizeNumber(t: *Tokenizer) !?Token {
594609 try t.consumeInteger();
595610
596611 // decimal point notation
597 switch (t.byte() orelse 0) {
598 '.' => switch (t.byteAt(1) orelse 0) {
612 switch (t.byte()) {
613 '.' => switch (t.nextByte()) {
599614 '0'...'9' => {
600615 t.pos += 1;
601616 tag = .number_float;
......@@ -611,15 +626,15 @@ fn tokenizeNumber(t: *Tokenizer) !?Token {
611626 }
612627 }
613628
614 switch (t.byte() orelse 0) {
629 switch (t.byte()) {
615630 'e', 'E' => {
616631 tag = .number_float;
617632 t.pos += 1;
618633
619 switch (t.byte() orelse 0) {
634 switch (t.byte()) {
620635 '+', '-' => {
621636 t.pos += 1;
622 switch (t.byte() orelse 0) {
637 switch (t.byte()) {
623638 '0'...'9' => {},
624639 else => return t.maybeUTF8Error(error.InvalidSyntax),
625640 }
......@@ -631,8 +646,8 @@ fn tokenizeNumber(t: *Tokenizer) !?Token {
631646 try t.consumeInteger();
632647
633648 // decimal point notation
634 switch (t.byte() orelse 0) {
635 '.' => switch (t.byteAt(1) orelse 0) {
649 switch (t.byte()) {
650 '.' => switch (t.nextByte()) {
636651 '0'...'9' => {
637652 t.pos += 1;
638653 tag = .number_float;
......@@ -662,7 +677,7 @@ fn maybeConsumeInteger(t: *Tokenizer) !?void {
662677 const trace = T.trace(@src());
663678 defer trace.end();
664679
665 switch (t.byte() orelse return null) {
680 switch (t.byte()) {
666681 '0'...'9' => {
667682 t.pos += 1;
668683 },
......@@ -676,7 +691,7 @@ fn consumeInteger(t: *Tokenizer) !void {
676691 const trace = T.trace(@src());
677692 defer trace.end();
678693
679 switch (t.byte() orelse unreachable) {
694 switch (t.byte()) {
680695 '0'...'9' => {
681696 t.pos += 1;
682697 },
......@@ -684,13 +699,13 @@ fn consumeInteger(t: *Tokenizer) !void {
684699 }
685700
686701 while (true) {
687 switch (t.byte() orelse return) {
702 switch (t.byte()) {
688703 '0'...'9' => {
689704 t.pos += 1;
690705 },
691706 '_' => {
692707 t.pos += 1;
693 switch (t.byte() orelse 0) {
708 switch (t.byte()) {
694709 '0'...'9' => {
695710 t.pos += 1;
696711 },
......@@ -715,7 +730,7 @@ fn consumeIntegerRadix(t: *Tokenizer, comptime radix: Radix) !void {
715730 };
716731
717732 first: {
718 const b = t.byte() orelse return;
733 const b = t.byte();
719734 inline for (digits) |digit| {
720735 if (b == digit) {
721736 break :first;
......@@ -728,7 +743,7 @@ fn consumeIntegerRadix(t: *Tokenizer, comptime radix: Radix) !void {
728743
729744 while (true) {
730745 brk: {
731 const b = t.byte() orelse return;
746 const b = t.byte();
732747 inline for (digits) |digit| {
733748 if (b == digit) {
734749 break :brk;
......@@ -741,7 +756,7 @@ fn consumeIntegerRadix(t: *Tokenizer, comptime radix: Radix) !void {
741756}
742757
743758fn tokenizeString(t: *Tokenizer) !Token {
744 assert(t.byte().? == '"');
759 assert(t.byte() == '"');
745760 const start = t.pos;
746761 try t.consumeString();
747762 return .{ .tag = .string, .offset = start };
......@@ -751,32 +766,30 @@ fn consumeString(t: *Tokenizer) !void {
751766 const trace = T.trace(@src());
752767 defer trace.end();
753768
754 assert(t.byte().? == '"');
769 const start = t.pos;
770 assert(t.byte() == '"');
755771
756772 t.pos += 1;
757773
758774 while (true) {
759 const b = t.byte() orelse return error.UnterminatedString;
760 switch (b) {
775 switch (t.byte()) {
761776 '"' => {
762777 t.pos += 1;
763778 break;
764779 },
765780 '\\' => {
766 switch (t.byteAt(1) orelse {
767 // we want error.InvalidEscapeSequence to be at the '\' char,
768 // but we want error.UnterminatedString to be at the end of the source code
769 t.pos += 1;
770 return error.UnterminatedString;
771 }) {
772 '\n' => return error.UnterminatedString,
781 switch (t.nextByte()) {
782 '\n' => {
783 t.pos = start;
784 return error.UnterminatedString;
785 },
773786 '\\' => t.pos += 2,
774787 '"' => t.pos += 2,
775788 'n' => t.pos += 2,
776789 'r' => t.pos += 2,
777790 't' => t.pos += 2,
778791 '0' => switch (t.byteAt(2) orelse {
779 t.pos += 2;
792 t.pos = start;
780793 return error.UnterminatedString;
781794 }) {
782795 // '\0xFF' hex escape
......@@ -788,7 +801,7 @@ fn consumeString(t: *Tokenizer) !void {
788801 const esc_start = t.pos;
789802 t.consumeStringUnicodeEscape() catch |err| switch (err) {
790803 error.InvalidEscapeSequence => {
791 if (t.byte().? == '\n')
804 if (t.byte() == '\n')
792805 return error.UnterminatedString;
793806 t.pos = esc_start;
794807 return err;
......@@ -797,12 +810,16 @@ fn consumeString(t: *Tokenizer) !void {
797810 t.pos = esc_start;
798811 return err;
799812 },
800 error.UnterminatedString => return err,
813 // error.UnterminatedString => return err,
801814 };
802815 },
803816 else => return t.maybeUTF8ErrorAt(error.InvalidEscapeSequence, 1),
804817 }
805818 },
819 '\n' => {
820 t.pos = start;
821 return error.UnterminatedString;
822 },
806823 else => {
807824 t.pos += 1;
808825 },
......@@ -818,16 +835,16 @@ fn consumeStringUnicodeEscape(t: *Tokenizer) !void {
818835 const trace = T.trace(@src());
819836 defer trace.end();
820837
821 assert(t.byte().? == '\\');
822 assert(t.byteAt(1).? == 'u');
838 assert(t.byte() == '\\');
839 assert(t.nextByte() == 'u');
823840 t.pos += 2;
824841
825 if ((t.byte() orelse return error.UnterminatedString) != '{')
842 if (t.byte() != '{')
826843 return error.InvalidEscapeSequence;
827844 t.pos += 1;
828845
829 if ((t.byte() orelse return error.UnterminatedString) == '0') {
830 if ((t.byteAt(1) orelse return error.UnterminatedString) == 'x') {
846 if (t.byte() == '0') {
847 if (t.nextByte() == 'x') {
831848 t.pos += 2;
832849 }
833850 }
......@@ -837,7 +854,7 @@ fn consumeStringUnicodeEscape(t: *Tokenizer) !void {
837854
838855 const slice = t.code[start..t.pos];
839856
840 if ((t.byte() orelse return error.UnterminatedString) != '}')
857 if (t.byte() != '}')
841858 return error.InvalidEscapeSequence;
842859
843860 const code_point = std.fmt.parseInt(u21, slice, 16) catch |err| switch (err) {
......@@ -866,7 +883,7 @@ fn maybeUTF8Error(t: *Tokenizer, comptime real_err: anytype) @Type(.{
866883 .{ .name = @errorName(real_err) },
867884 },
868885}) {
869 switch (t.byte() orelse return real_err) {
886 switch (t.byte()) {
870887 0x80...0xFF => {
871888 _ = try t.codePoint();
872889 return real_err;
......@@ -881,7 +898,7 @@ fn maybeUTF8ErrorAt(t: *Tokenizer, comptime real_err: anytype, at: u32) @Type(.{
881898 .{ .name = @errorName(real_err) },
882899 },
883900}) {
884 switch (t.byteAt(at) orelse return real_err) {
901 switch (t.byteAtRt(at) orelse return real_err) {
885902 0x80...0xFF => {
886903 _ = try t.codePoint();
887904 return real_err;
......@@ -895,7 +912,7 @@ pub fn skipWhitespace(t: *Tokenizer) void {
895912 defer trace.end();
896913
897914 while (true) {
898 switch (t.byte() orelse break) {
915 switch (t.byte()) {
899916 ' ', '\t', '\n', '\r' => {
900917 t.pos += 1;
901918 },
src/compiler/Transform.zig+95-28
......@@ -14,29 +14,51 @@ scratch: std.ArrayListUnmanaged(u32) = .{},
1414call_args: std.ArrayListUnmanaged(Inst.Ref) = .{},
1515scopes: std.ArrayListUnmanaged(Scope) = .{},
1616
17pub fn transform(ast: Ast, gpa: Allocator) !Teg {
17report: Report.Builder,
18
19pub const Error = error{
20 OutOfMemory,
21 TransformFail,
22};
23
24pub fn transform(
25 ast: Ast,
26 gpa: Allocator,
27 file: *Comp.File,
28 fail_report: *Report,
29) Error!Teg {
1830 const trace = tracer.trace(@src());
1931 defer trace.end();
2032
2133 var t: Transform = .{
2234 .ast = ast,
2335 .gpa = gpa,
36 .report = .{ .gpa = gpa, .current_file = file },
2437 };
2538 errdefer t.deinit();
2639
2740 try t.string_bytes.append(t.gpa, 0);
2841
29 const module = try t.transformModule(0);
42 const module = t.transformModule(0) catch |err| switch (err) {
43 error.OutOfMemory => return err,
44 };
45
46 const report = try t.report.finalize();
47 if (report.errors.len > 0) {
48 fail_report.* = report;
49 return error.TransformFail;
50 }
51
3052 assert(@intFromEnum(module) == 0);
3153
3254 try t.insts.setCapacity(t.gpa, t.insts.len);
3355
3456 defer t.deinit();
35
3657 return Teg{
3758 .insts = t.insts.toOwnedSlice(),
3859 .string_bytes = try t.string_bytes.toOwnedSlice(t.gpa),
3960 .extra = try t.extra.toOwnedStore(t.gpa),
61 .report = report,
4062 };
4163}
4264
......@@ -45,6 +67,9 @@ pub fn deinit(t: *Transform) void {
4567 t.call_args.deinit(t.gpa);
4668 assert(t.scopes.items.len == 0);
4769 t.scopes.deinit(t.gpa);
70 t.string_bytes.deinit(t.gpa);
71 t.insts.deinit(t.gpa);
72 t.extra.deinit(t.gpa);
4873 t.* = undefined;
4974}
5075
......@@ -65,6 +90,25 @@ const Scope = struct {
6590 };
6691 };
6792
93 fn addUnusedWarnings(s: Scope, t: *Transform) !void {
94 for (s.symbols.values()) |value| {
95 if (!value.used) {
96 try t.report.warn(.unused, .{
97 .node = value.node,
98 .kind = switch (value.data) {
99 .declaration => .function,
100 .param => .param,
101 .mutable => .variable,
102 .immutable => switch (t.ast.nodeTag(value.node)) {
103 .param_decl => .param,
104 else => .constant,
105 },
106 },
107 });
108 }
109 }
110 }
111
68112 fn deinit(s: *Scope, gpa: Allocator) void {
69113 s.symbols.deinit(gpa);
70114 s.* = undefined;
......@@ -171,7 +215,7 @@ const BuiltInIds = std.ComptimeStringMap(Inst.Ref, .{
171215 // todo: nan/infinity?
172216});
173217
174fn transformModule(t: *Transform, module_node: NodeIndex) !Inst.Index {
218fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.Index {
175219 const trace = tracer.trace(@src());
176220 defer trace.end();
177221
......@@ -259,10 +303,12 @@ fn transformModule(t: *Transform, module_node: NodeIndex) !Inst.Index {
259303 .extra_index = try t.extra.append(t.gpa, IndexList, declarations),
260304 });
261305
306 try t.scope().addUnusedWarnings(t);
307
262308 return module;
263309}
264310
265fn previsitModuleStatement(t: *Transform, node: NodeIndex) !void {
311fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void {
266312 const trace = tracer.trace(@src());
267313 defer trace.end();
268314
......@@ -272,7 +318,11 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) !void {
272318 const fn_decl = t.ast.fullFnDecl(node);
273319 const gop = try t.scope().symbols.getOrPut(t.gpa, fn_decl.id);
274320 if (gop.found_existing) {
275 return error.DuplicateFunction;
321 try t.report.err(.duplicate_function, .{
322 .node = node,
323 .prev = gop.value_ptr.node,
324 });
325 return;
276326 }
277327 gop.value_ptr.* = .{
278328 .node = node,
......@@ -299,7 +349,7 @@ fn transformModuleStatement(t: *Transform, decl: BlockContext, node: NodeIndex)
299349 }
300350}
301351
302fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) !Inst.Index {
352fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index {
303353 const trace = tracer.trace(@src());
304354 defer trace.end();
305355
......@@ -350,6 +400,8 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) !
350400 .extra_index = decl_extra,
351401 });
352402
403 try t.scope().addUnusedWarnings(t);
404
353405 return func;
354406}
355407
......@@ -366,7 +418,7 @@ fn transformExpressionToBlock(
366418 decl: BlockContext,
367419 node: NodeIndex,
368420 comptime is_function: bool,
369) anyerror!Inst.Ref {
421) Allocator.Error!Inst.Ref {
370422 const trace = tracer.trace(@src());
371423 defer trace.end();
372424
......@@ -461,7 +513,7 @@ fn transformExpressionToBlock(
461513 return ret_block.toRef();
462514}
463515
464fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) !Inst.Ref {
516fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Allocator.Error!Inst.Ref {
465517 const trace = tracer.trace(@src());
466518 defer trace.end();
467519
......@@ -472,7 +524,11 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) !Inst
472524 // can use this param's value, if known at compile time.
473525 const sym = try t.scope().symbols.getOrPut(t.gpa, param_decl.id);
474526 if (sym.found_existing) {
475 return error.DuplicateParam;
527 try t.report.err(.duplicate_param, .{
528 .node = param,
529 .prev = sym.value_ptr.node,
530 });
531 return .void_value;
476532 }
477533
478534 sym.value_ptr.* = .{
......@@ -501,7 +557,8 @@ const Builtin = enum {
501557 defer trace.end();
502558
503559 if (args.len != 1) {
504 return error.InvalidBuiltin;
560 // try t.report.err(.invalid_builtin_args, .{ .id = "js" });
561 return .void_value;
505562 }
506563
507564 const source = try t.transformExpression(decl, args[0]) orelse return null;
......@@ -518,7 +575,7 @@ const Builtin = enum {
518575
519576/// This is the primary transformation function: converting an AST expression into instructions.
520577/// `null` represents a `return` or `break`, aka this branch of code is dead.
521fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyerror!?Inst.Ref {
578fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Allocator.Error!?Inst.Ref {
522579 const node_tag = t.ast.nodes.items(.tag)[node];
523580
524581 const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)});
......@@ -537,17 +594,13 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyer
537594 return ref;
538595 }
539596
540 if (mem.eql(u8, id, "_")) {
541 return error.InvalidDiscard;
542 }
543
544597 if (mem.startsWith(u8, id, "@")) {
545598 @panic("TODO: string identifiers");
546599 }
547600
548601 const lookup = t.lookupId(id) orelse {
549 std.debug.print("lookupId failed for {s}\n", .{id});
550 return error.UnknownIdentifier;
602 try t.report.err(.unknown_identifier, .{ .node = node });
603 return .void_value;
551604 };
552605
553606 return switch (lookup.data) {
......@@ -706,7 +759,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyer
706759
707760 .label => {
708761 const expr = t.ast.nodeData(node).label.expr;
709 const label = t.ast.tokenSlice(t.ast.nodes.items(.main_token)[node]);
762 const label = t.ast.tokenSlice(t.ast.nodeToken(node));
710763
711764 var target: BlockContext.BreakTarget = .{
712765 .parent = decl.break_target,
......@@ -778,7 +831,8 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyer
778831 .builtin_call,
779832 => {
780833 const builtin = Builtin.map.get(t.ast.basicNodeSlice(node)[1..]) orelse {
781 return error.InvalidBuiltin;
834 try t.report.err(.invalid_builtin, .{ .node = node });
835 return .void_value;
782836 };
783837
784838 const base = t.ast.nodeData(node).list;
......@@ -847,7 +901,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyer
847901 const string_ref = try t.addInstRef(.{
848902 .tag = .string,
849903 .data = EncodedData.encode(.string, .{
850 .src_node = decl.nodeOffset(node), // TODO: this is incorrect, there is no node
904 .src_node = decl.nodeOffset(node),
851905 .extra_index = try t.extra.append(t.gpa, SlicedString, prop_name_str),
852906 }),
853907 });
......@@ -915,7 +969,8 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyer
915969 const ret_node = t.ast.nodeData(node).@"return";
916970
917971 const ret_block_ptr = decl.return_target orelse {
918 return error.InvalidReturn;
972 // return error.InvalidReturn;
973 @panic("TODO");
919974 };
920975
921976 const ret_block = if (ret_block_ptr.*) |block|
......@@ -957,7 +1012,8 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyer
9571012 );
9581013
9591014 const block = try decl.findAndMarkBreak(t, label) orelse {
960 return error.UnknownBreakTarget;
1015 try t.report.err(.unresolved_label, .{ .node = node });
1016 return null;
9611017 };
9621018
9631019 const operand: Inst.Ref = if (brk_node.value.unwrap()) |expr|
......@@ -1001,6 +1057,17 @@ fn transformBlockStatement(
10011057
10021058 .var_decl => {
10031059 const var_decl = t.ast.fullVarDecl(node);
1060
1061 if (mem.eql(u8, var_decl.id, "_")) {
1062 try t.report.err(.invalid_discard, .{ .node = node });
1063 return .void_value;
1064 }
1065
1066 if (BuiltInIds.has(var_decl.id)) {
1067 try t.report.err(.shadow_builtin_variable, .{ .node = node });
1068 return .void_value;
1069 }
1070
10041071 const type_ref = if (var_decl.type) |data_type|
10051072 try t.transformExpression(decl, data_type)
10061073 else
......@@ -1040,7 +1107,7 @@ fn transformBlockStatement(
10401107 },
10411108 });
10421109
1043 return mem_ref;
1110 return .void_value;
10441111 },
10451112
10461113 .fn_decl,
......@@ -1145,10 +1212,7 @@ fn getInstData(t: *Transform, index: Inst.Index) *EncodedData {
11451212}
11461213
11471214fn addScratch(t: *Transform, comptime T: type, value: T) !void {
1148 try t.scratch.append(t.gpa, switch (T) {
1149 Inst.Ref, Inst.Index => @intFromEnum(value),
1150 else => @compileError("addScratch not implemented for " ++ @typeName(T)),
1151 });
1215 try t.scratch.append(t.gpa, @intFromEnum(value));
11521216}
11531217
11541218fn scratchToRefList(t: *Transform, scratch_top: usize) !RefList {
......@@ -1273,4 +1337,7 @@ const optionalExtraIndex = OptionalExtraIndex.init;
12731337
12741338const InternPool = @import("InternPool.zig");
12751339
1340const Report = @import("Report.zig");
1341const Comp = @import("Comp.zig");
1342
12761343const tracer = @import("tracer");
src/compiler/Value.zig-1
......@@ -32,7 +32,6 @@ pub fn asString(val: Value, ip: *InternPool) InternPool.String.WithLen {
3232}
3333
3434pub fn @"type"(val: Value, ip: *InternPool) Value {
35 std.debug.print("typeOfValue {any}\n", .{val.toKey(ip)});
3635 const v: InternPool.Index = switch (val.toKey(ip)) {
3736 .simple_type, .type_function => .type_type,
3837
src/compiler/main.zig+23-3
......@@ -3,11 +3,15 @@ const Tokenizer = @import("Tokenizer.zig");
33const Parse = @import("Parse.zig");
44const Transform = @import("Transform.zig");
55const Comp = @import("Comp.zig");
6
6const unicode_props = @import("unicode_props.zig");
77const T = @import("tracer");
88pub const tracer_impl = @field(T, "none");
99
10pub const Report = @import("Report.zig");
11
1012pub fn main() !void {
13 try unicode_props.init();
14
1115 std.debug.print("\r\x1b[K", .{});
1216
1317 try T.init();
......@@ -45,7 +49,14 @@ pub fn main() !void {
4549 defer alloc.free(exe_name);
4650
4751 const file_path = maybe_file_path orelse {
48 std.debug.print("Usage: {s} <input file>\n", .{exe_name});
52 std.debug.print("Usage: {s} [flags] <input file>\n", .{exe_name});
53 std.debug.print(
54 // | 60 chars wrap point|
55 \\
56 \\Flags:
57 \\ --dump-ir Dump intermediate representations.
58 \\
59 , .{});
4960 std.process.exit(1);
5061 };
5162
......@@ -54,7 +65,16 @@ pub fn main() !void {
5465 defer comp.deinit();
5566 const main_file = try comp.getFile(file_path);
5667
57 const teg = try main_file.getTeg(comp.gpa);
68 var fail_report: Report = undefined;
69 const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) {
70 error.TransformFail => {
71 std.debug.print("Transform failed:\n", .{});
72 try fail_report.dumpToStdErr(&comp);
73 std.process.exit(1);
74 },
75 else => return err,
76 };
77
5878 {
5979 const stderr = std.io.getStdErr().writer();
6080 try teg.dumpToWriter(stderr);
src/compiler/util.zig+2-2
......@@ -7,9 +7,9 @@ pub fn detectTTYFromWriter(writer: anytype) std.io.tty.Config {
77 return std.io.tty.detectConfig(writer.context);
88 }
99 if (@TypeOf(writer) == std.io.AnyWriter) {
10 const writeFn = comptime (std.fs.File{ .handle = undefined })
10 const fileWriteFn = comptime (std.fs.File{ .handle = undefined })
1111 .writer().any().writeFn;
12 if (writer.writeFn == writeFn) {
12 if (writer.writeFn == fileWriteFn) {
1313 const file: *const std.fs.File = @alignCast(@ptrCast(writer.context));
1414 return std.io.tty.detectConfig(file.*);
1515 }