diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..3c74c5fc58489f3128134c2b03b80ff86f6f2b54 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,8 @@ +## 0.1.1-alpha + +- introduce `Report` and show errors during Transform. +- add flag --dump-ir for enabling ir dumps + +## 0.1.0-alpha + +initial demo of compiler pipeline in zig. diff --git a/demo.txt b/demo.txt new file mode 100644 index 0000000000000000000000000000000000000000..39750d3339d522e2ab214079e016390dd62e5d8f --- /dev/null +++ b/demo.txt @@ -0,0 +1 @@ +no "waay diff --git a/src/compiler/Ast.zig b/src/compiler/Ast.zig index d0f5f446a8bbc5404504c677ee7b59279a5e383e..cd8b54a59709fc63bca77f2281e9524be219cbe0 100644 --- a/src/compiler/Ast.zig +++ b/src/compiler/Ast.zig @@ -34,96 +34,54 @@ //! The `Ast` owns token and node data, but not the source code. /// Externally owned source code. -utf8_source: []const u8, +utf8_source: [:0]const u8, tokens: Token.List.Slice, nodes: Node.List.Slice, extra: []const u32, -pub fn parse(gpa: std.mem.Allocator, source: []const u8) !Ast { - const Parse = @import("Parse.zig"); - - var todo: ByteOffset = 0; - var tokens = try Tokenizer.tokenize(source, gpa, &todo); +const Parse = @import("Parse.zig"); +pub fn parse( + gpa: std.mem.Allocator, + source: [:0]const u8, + ast_error: *Parse.AstError, +) Parse.Error!Ast { + var err_byte: ByteOffset = 0; + var tokens = Tokenizer.tokenize( + source, + gpa, + &err_byte, + ) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + ast_error.* = .{ .tokenization = .{ + .err = err, + .byte = err_byte, + } }; + return error.ParseFail; + }, + }; errdefer tokens.deinit(gpa); var parser = Parse.init(gpa, source, tokens); errdefer parser.deinit(); - try parser.nodes.ensureUnusedCapacity(gpa, @divFloor(tokens.len, 4)); + try parser.nodes.ensureUnusedCapacity( + gpa, + @max(1, @divFloor(tokens.len, 4)), + ); - try parser.parseRoot(); + parser.parseRoot() catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ParseFail => { + ast_error.* = parser.ast_error.?; + return error.ParseFail; + }, + }; return try parser.finalize(tokens); } -pub const Location = struct { - line: usize, - column: usize, - line_start: usize, - line_end: usize, -}; - -pub const Span = struct { - start: u32, - end: u32, - main: u32, -}; - -// This can be removed when the following issue is implemented: -// See: https://github.com/ziglang/zig/issues/104 -// See: https://github.com/ziglang/zig/issues/3806 -pub const OptionalNodeIndex = enum(u32) { - none = 0, - _, - - pub fn init(idx: ?NodeIndex) OptionalNodeIndex { - assert(idx != 0); - return @enumFromInt(idx orelse return .none); - } - - pub fn unwrap(idx: OptionalNodeIndex) ?NodeIndex { - return switch (idx) { - .none => null, - _ => @intFromEnum(idx), - }; - } -}; - -pub const OptionalTokenIndex = enum(u32) { - none = 0, - _, - - pub fn init(idx: ?TokenIndex) OptionalTokenIndex { - assert(idx != 0); - return @enumFromInt(idx orelse return .none); - } - - pub fn unwrap(idx: OptionalTokenIndex) ?TokenIndex { - return switch (idx) { - .none => null, - _ => @intFromEnum(idx), - }; - } -}; - -pub const OptionalExtraIndex = enum(u32) { - none = std.math.maxInt(u32), - _, - - pub fn init(idx: ?ExtraIndex) OptionalExtraIndex { - assert(idx != std.math.maxInt(u32)); - return @enumFromInt(idx orelse return .none); - } - - pub fn unwrap(idx: OptionalExtraIndex) ?ExtraIndex { - return switch (idx) { - .none => null, - _ => @intFromEnum(idx), - }; - } -}; - pub const Node = struct { tag: Tag, main_token: TokenIndex, @@ -462,6 +420,76 @@ pub const Node = struct { }; }; +pub const Error = error{ OutOfMemory, ParseFail }; + +pub const Location = struct { + // TODO: add an ordinal number type, replace with Ordinal(usize) + line: usize, + column: usize, + line_start: usize, + line_end: usize, +}; + +pub const Span = struct { + start: u32, + end: u32, + main: u32, +}; + +// This can be removed when the following issue is implemented: +// See: https://github.com/ziglang/zig/issues/104 +// See: https://github.com/ziglang/zig/issues/3806 +pub const OptionalNodeIndex = enum(u32) { + none = 0, + _, + + pub fn init(idx: ?NodeIndex) OptionalNodeIndex { + assert(idx != 0); + return @enumFromInt(idx orelse return .none); + } + + pub fn unwrap(idx: OptionalNodeIndex) ?NodeIndex { + return switch (idx) { + .none => null, + _ => @intFromEnum(idx), + }; + } +}; + +pub const OptionalTokenIndex = enum(u32) { + none = 0, + _, + + pub fn init(idx: ?TokenIndex) OptionalTokenIndex { + assert(idx != 0); + return @enumFromInt(idx orelse return .none); + } + + pub fn unwrap(idx: OptionalTokenIndex) ?TokenIndex { + return switch (idx) { + .none => null, + _ => @intFromEnum(idx), + }; + } +}; + +pub const OptionalExtraIndex = enum(u32) { + none = std.math.maxInt(u32), + _, + + pub fn init(idx: ?ExtraIndex) OptionalExtraIndex { + assert(idx != std.math.maxInt(u32)); + return @enumFromInt(idx orelse return .none); + } + + pub fn unwrap(idx: OptionalExtraIndex) ?ExtraIndex { + return switch (idx) { + .none => null, + _ => @intFromEnum(idx), + }; + } +}; + pub const SubList = struct { /// Either NodeIndex or TokenIndex, depending on where this came from. start: u32, @@ -598,6 +626,19 @@ pub fn fullParamDecl(ast: Ast, node: NodeIndex) full.ParamDecl { }; } +pub fn idToken(ast: Ast, node: NodeIndex) TokenIndex { + return switch (ast.nodeTag(node)) { + .param_decl, + .id, + .label, + => ast.nodeToken(node), + .fn_decl, + .var_decl, + => ast.nodeToken(node) + 1, + else => unreachable, // does not have an id + }; +} + pub fn nodeStatements(ast: *const Ast, node: NodeIndex) []const NodeIndex { const data = ast.nodes.items(.data)[node].list; return data.slice(ast); @@ -605,15 +646,15 @@ pub fn nodeStatements(ast: *const Ast, node: NodeIndex) []const NodeIndex { pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenIndex) Location { var loc = Location{ - .line = 0, - .column = 0, + .line = 1, + .column = 1, .line_start = start_offset, - .line_end = self.source.len, + .line_end = self.utf8_source.len, }; - const token_start = self.tokens.items(.start)[token_index]; + const token_start = self.tokens.items(.offset)[token_index]; // Scan to by line until we go past the token start - while (std.mem.indexOfScalarPos(u8, self.source, loc.line_start, '\n')) |i| { + while (std.mem.indexOfScalarPos(u8, self.utf8_source, loc.line_start, '\n')) |i| { if (i >= token_start) { break; // Went past } @@ -622,17 +663,17 @@ pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenInde } const offset = loc.line_start; - for (self.source[offset..], 0..) |c, i| { + for (self.utf8_source[offset..], 0..) |c, i| { if (i + offset == token_start) { loc.line_end = i + offset; - while (loc.line_end < self.source.len and self.source[loc.line_end] != '\n') { + while (loc.line_end < self.utf8_source.len and self.utf8_source[loc.line_end] != '\n') { loc.line_end += 1; } return loc; } if (c == '\n') { loc.line += 1; - loc.column = 0; + loc.column = 1; loc.line_start = i + 1; } else { loc.column += 1; @@ -651,6 +692,11 @@ pub fn basicNodeSlice(ast: Ast, node: NodeIndex) []const u8 { return ast.tokenSlice(token); } +pub fn fnDeclIdToken(tree: Ast, node: NodeIndex) TokenIndex { + assert(tree.nodeTag(node) == .fn_decl); + return tree.nodeToken(node) + 1; +} + pub fn extraData(tree: Ast, index: usize, comptime T: type) T { const fields = std.meta.fields(T); var result: T = undefined; @@ -699,3 +745,5 @@ const Tokenizer = @import("Tokenizer.zig"); const Token = Tokenizer.Token; const TokenList = Tokenizer.Token.List; const ByteOffset = Tokenizer.ByteOffset; + +const Report = @import("Report.zig"); diff --git a/src/compiler/Comp.zig b/src/compiler/Comp.zig index 7b79942603d4183c3c9f7fe1a463be0ecd89ba98..2d44f1c7be87c13506cec703d2f31b6ebdf3eeab 100644 --- a/src/compiler/Comp.zig +++ b/src/compiler/Comp.zig @@ -10,6 +10,8 @@ files: std.StringArrayHashMapUnmanaged(*File) = .{}, /// # of sema instructions in this compilation cycle analyzed_instructions: usize = 0, +options: Options = .{}, + // TODO: consider computing this dynamicly per-link current_compile_decls: std.ArrayListUnmanaged(Decl.Index) = .{}, @@ -74,7 +76,16 @@ pub const File = struct { path_digest: ?Cache.BinDigest, - pub fn getSource(file: *File, gpa: Allocator) !Source { + const GetSourceError = + std.fs.File.OpenError || + std.fs.File.ReadError || + std.fs.File.StatError || + Allocator.Error || error{ + FileTooBig, + UnexpectedEndOfFile, + }; + + pub fn getSource(file: *File, gpa: Allocator) GetSourceError!Source { if (file.source) |source| { return source; } @@ -111,17 +122,42 @@ pub const File = struct { return loaded_source; } - pub fn getAst(file: *File, gpa: Allocator) !Ast { + const GetAstError = GetSourceError || Parse.Error; + + pub fn getAst(file: *File, comp: *Comp) GetAstError!Ast { if (file.ast) |ast| { return ast; } - const ast = try Ast.parse(gpa, (try file.getSource(gpa)).code); + var ast_err: Parse.AstError = undefined; + const ast = Ast.parse( + comp.gpa, + (try file.getSource(comp.gpa)).code, + &ast_err, + ) catch |err| switch (err) { + error.OutOfMemory => return err, + error.ParseFail => { + var report = try ast_err.toReport(comp, file); + // TODO: let this merge with other report + report.dumpToStdErr(comp) catch {}; + report.deinit(comp.gpa); + return err; + }, + }; + + if (comp.options.dump_ir) { + ast.dumpToWriter(std.io.getStdErr().writer()) catch { + // Simplify the error set. + }; + } + file.ast = ast; return ast; } - pub fn getTeg(file: *File, gpa: Allocator) !Teg { + const GetTegError = GetAstError || Transform.Error; + + pub fn getTeg(file: *File, comp: *Comp, fail_report: *Report) GetTegError!Teg { if (file.teg) |teg| { return teg; } @@ -136,7 +172,13 @@ pub const File = struct { }; file.path_digest = bin_digest; - const teg = try Transform.transform(try file.getAst(gpa), gpa); + const teg = try Transform.transform( + try file.getAst(comp), + comp.gpa, + file, + fail_report, + ); + file.teg = teg; return teg; } @@ -290,8 +332,6 @@ pub const Namespace = struct { pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !Namespace.Index { const teg = file.teg.?; - log.info("Index {} @ {d}", .{ std.fmt.fmtSliceHexLower(file.path_digest.?[0..8]), @intFromEnum(module_index) }); - const ns = try comp.intern_pool.createNamespace(comp.gpa, .{ .parent = .none, .file_scope = file, @@ -322,7 +362,13 @@ pub fn indexTegModule(comp: *Comp, file: *File, module_index: Teg.Inst.Index) !N } pub fn semaEntryPoint(comp: *Comp, file: *File) !void { - const teg = try file.getTeg(comp.gpa); + var report: Report = undefined; + const teg = try file.getTeg(comp, &report); + + // TODO, move this elsewhere. we should print all errors and warnings at once + if (teg.report.warnings.len > 0) { + try teg.report.dumpToStdErr(comp); + } const ns = try comp.indexTegModule(file, .main_module_inst); 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 { const t = tracer.trace(@src()); defer t.end(); - std.debug.print("Start Codegen\n", .{}); - defer std.debug.print("End Codegen\n", .{}); - var arena_state = std.heap.ArenaAllocator.init(c.gpa); defer arena_state.deinit(); const arena = arena_state.allocator(); @@ -371,6 +414,10 @@ pub fn link(c: *Comp, writer: anytype) !void { try JsLink.linkJsCode(c, c.current_compile_decls.items, writer); } +const Options = struct { + dump_ir: bool = false, +}; + const Comp = @This(); const std = @import("std"); @@ -391,5 +438,7 @@ const Transform = @import("Transform.zig"); const JsNodeGen = @import("JsNodeGen.zig"); const JsTree = @import("JsTree.zig"); const JsLink = @import("JsLink.zig"); +const Report = @import("Report.zig"); +const Parse = @import("Parse.zig"); const tracer = @import("tracer"); diff --git a/src/compiler/JsLink.zig b/src/compiler/JsLink.zig index 2597285951141d69a620e0425bbfdacfa194ebf9..31fddb953304cf48ddf5e5c94fdcf825add9a6dc 100644 --- a/src/compiler/JsLink.zig +++ b/src/compiler/JsLink.zig @@ -19,7 +19,6 @@ pub fn linkJsCode( decls: []const Decl.Index, writer: anytype, ) !void { - log.info("Linking {d} decls", .{decls.len}); // Arena allocator is used for the main flow var link: JsLink = .{ .comp = comp }; @@ -68,7 +67,7 @@ pub fn linkJsCode( try link.decls.putNoClobber(comp.gpa, decl_index, @enumFromInt(gop.index)); } - try writer.print("// Compiler Demo. Do not use in production\n", .{}); + try writer.print("// Language Compiler v0.0.1-alpha2\n", .{}); // 2. Print every function decl for (decls[1..]) |decl_index| { @@ -106,7 +105,6 @@ fn reserveExternSymbol(link: *JsLink, extern_symbol: String.WithLen) !void { fn appendLinkString(link: *JsLink, string: []const u8) !LinkString { const start = link.string_bytes.items.len; - log.info("Append '{s}' -> .{{ .start = {d}, .len = {d} }}", .{ string, start, string.len }); try link.string_bytes.appendSlice(link.comp.gpa, string); return .{ .start = @intCast(start), .len = @intCast(string.len) }; } @@ -170,7 +168,6 @@ fn printBlock(link: *JsLink, tree: *const JsTree, list: JsTree.IndexList, writer } fn printExpr(link: *JsLink, tree: *const JsTree, node: Node.Index, writer: anytype) @TypeOf(writer).Error!void { - log.info("print node {d}", .{@intFromEnum(node)}); switch (tree.nodeTag(node)) { else => |tag| std.debug.panic("TODO: printExpr .{s}", .{@tagName(tag)}), .root => unreachable, diff --git a/src/compiler/JsNodeGen.zig b/src/compiler/JsNodeGen.zig index c20514022a9fe04e844626d3ecfb3dbdb743bfd0..4bfd52d61676ed7b2759e07bd2b91196428b869c 100644 --- a/src/compiler/JsNodeGen.zig +++ b/src/compiler/JsNodeGen.zig @@ -117,7 +117,6 @@ pub fn build(c: *JsNodeGen) !void { } fn codeGenInst(c: *JsNodeGen, inst: Air.Inst.Index) Error!void { - std.debug.print("JsNodeGen: #{d}\n", .{@intFromEnum(inst)}); switch (c.air.instTag(inst)) { .arg => unreachable, // codegen must skip over these instructions. @@ -400,7 +399,6 @@ fn codeGenBranch(c: *JsNodeGen, inst: Air.Inst.Ref, temporary: ?Node.Index) !?Br this_label, }); - std.debug.print("requires_label {}\n", .{requires_label}); const block: Air.IndexList = c.air.instData(.block, i); for (block.slice(c.air)) |item| { try c.codeGenInst(item); diff --git a/src/compiler/Parse.zig b/src/compiler/Parse.zig index 6dc6147ebdfb1c0fc4218b36d46beef745d789c9..e7ff73382438728b9af4bbc487431fd3ff746a64 100644 --- a/src/compiler/Parse.zig +++ b/src/compiler/Parse.zig @@ -1,7 +1,7 @@ //! Parser takes a Token list and creates a Node list. It does not own the source code or //! token data, however transferring into an `Ast` does move token list ownership. gpa: Allocator, -source: []const u8, +source: [:0]const u8, token_tags: []const Token.Tag, token_starts: []const ByteOffset, @@ -12,7 +12,7 @@ scratch: ArrayListUnmanaged(NodeIndex) = .{}, ast_error: ?AstError = null, -pub fn init(gpa: Allocator, source: []const u8, tokens: Token.List.Slice) Parse { +pub fn init(gpa: Allocator, source: [:0]const u8, tokens: Token.List.Slice) Parse { return Parse{ .gpa = gpa, .source = source, @@ -36,8 +36,6 @@ pub fn finalize(p: *Parse, token_slice: Token.List.Slice) !Ast { .extra = extra, }; - ast.dumpToWriter(std.io.getStdErr().writer()) catch {}; - return ast; } @@ -50,18 +48,52 @@ pub fn deinit(p: *Parse) void { } pub const Error = error{ - SyntaxError, + OutOfMemory, + ParseFail, }; /// When error.SyntaxError is returned, `p.err` is set to this as well as `p.pos` pub const AstError = union(enum) { + tokenization: struct { + err: Tokenizer.TokenizeError, + byte: u32, + }, expected_token: Token.Tag, expected_statement, expected_expr, expected_else_value, + + pub fn toReport(parse_err: AstError, comp: *Comp, file: *Comp.File) !Report { + return (Report.Err{ .ast = parse_err }).toReport(comp, file); + } + + pub fn build(err: AstError, ctx: *Report.MsgContext) !void { + switch (err) { + .tokenization => |data| { + // TODO: consider making tokenizer emit AstError more directly, + // so that it can just annotate start and ends + try ctx.msg(switch (data.err) { + error.UnterminatedString => .{ .codepoint_eol = data.byte }, + else => .{ .codepoint = data.byte }, + }, "{s}", .{switch (data.err) { + error.OutOfMemory => unreachable, + error.InvalidSyntax => "unexpected token", + error.TrailingNumericSeparator => "trailing numberic separator", + error.UnterminatedString => "unterminated string literal", + error.InvalidEscapeSequence => "invalid escape sequence", + error.InvalidEscapeCodepoint => "invalid escape codepoint", + error.InvalidUtf8 => "file is not valid utf8", + error.GitConflict => "git merge conflict encountered", + }}); + }, + + else => unreachable, + // + } + } }; -pub fn parseRoot(p: *Parse) !void { +pub fn parseRoot(p: *Parse) Error!void { const trace = t.trace(@src()); defer trace.end(); @@ -82,7 +114,7 @@ pub fn parseRoot(p: *Parse) !void { }; } -fn parseModuleMembers(p: *Parse) !SubList { +fn parseModuleMembers(p: *Parse) Error!SubList { const trace = t.trace(@src()); defer trace.end(); @@ -103,7 +135,7 @@ fn parseModuleMembers(p: *Parse) !SubList { .kw_enum, .kw_trait, .kw_impl, - => return error.Todo, + => |tag| std.debug.panic("TODO: parse {s}", .{@tagName(tag)}), .eof => break, @@ -120,7 +152,7 @@ fn parseModuleMembers(p: *Parse) !SubList { return p.scratchToSubList(scratch_top); } -fn parseModuleDecl(p: *Parse) !NodeIndex { +fn parseModuleDecl(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -135,7 +167,7 @@ fn parseModuleDecl(p: *Parse) !NodeIndex { }; } -fn parseFnDecl(p: *Parse) !NodeIndex { +fn parseFnDecl(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -170,7 +202,7 @@ fn parseFnDecl(p: *Parse) !NodeIndex { }); } -fn parseParamList(p: *Parse) !SubList { +fn parseParamList(p: *Parse) Error!SubList { const trace = t.trace(@src()); defer trace.end(); @@ -199,7 +231,7 @@ fn parseParamList(p: *Parse) !SubList { return p.scratchToSubList(scratch_top); } -fn parseParamDecl(p: *Parse) !NodeIndex { +fn parseParamDecl(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -218,7 +250,7 @@ fn parseParamDecl(p: *Parse) !NodeIndex { }); } -fn parseBlock(p: *Parse) !NodeIndex { +fn parseBlock(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -236,7 +268,7 @@ fn parseBlock(p: *Parse) !NodeIndex { }); } -fn parseLabelExpression(p: *Parse) !NodeIndex { +fn parseLabelExpression(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -252,7 +284,7 @@ fn parseLabelExpression(p: *Parse) !NodeIndex { }); } -fn parseBlockContents(p: *Parse) !struct { Node.Tag, SubList } { +fn parseBlockContents(p: *Parse) Error!struct { Node.Tag, SubList } { const trace = t.trace(@src()); defer trace.end(); @@ -308,7 +340,7 @@ fn parseBlockContents(p: *Parse) !struct { Node.Tag, SubList } { } /// VarDecl <- (kw_pub)? (kw_var | kw_let) id (colon expr)? equal expr semi -fn parseVarDecl(p: *Parse) !NodeIndex { +fn parseVarDecl(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -521,7 +553,7 @@ const Operator = enum(u8) { } }; -fn parseExpression(p: *Parse) anyerror!NodeIndex { +fn parseExpression(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -543,7 +575,7 @@ fn parseExpression(p: *Parse) anyerror!NodeIndex { return expr; } -fn parseExpressionPart(p: *Parse) !NodeIndex { +fn parseExpressionPart(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -644,7 +676,7 @@ fn parseExpressionPart(p: *Parse) !NodeIndex { return expr; } -fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) !NodeIndex { +fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -673,7 +705,7 @@ fn parseBinarySuffix(p: *Parse, lhs: NodeIndex, op: Operator) !NodeIndex { }); } -fn parseReturn(p: *Parse) !NodeIndex { +fn parseReturn(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -693,7 +725,7 @@ fn parseReturn(p: *Parse) !NodeIndex { }); } -fn parseBreak(p: *Parse) !NodeIndex { +fn parseBreak(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -719,7 +751,7 @@ fn parseBreak(p: *Parse) !NodeIndex { }); } -fn parseBuiltinCall(p: *Parse) !NodeIndex { +fn parseBuiltinCall(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -736,7 +768,7 @@ fn parseBuiltinCall(p: *Parse) !NodeIndex { }); } -fn parseCallArgs(p: *Parse) anyerror!SubList { +fn parseCallArgs(p: *Parse) Error!SubList { const trace = t.trace(@src()); defer trace.end(); @@ -764,7 +796,7 @@ fn parseCallArgs(p: *Parse) anyerror!SubList { return p.scratchToSubList(scratch_top); } -fn parseIf(p: *Parse) !NodeIndex { +fn parseIf(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -795,7 +827,7 @@ fn parseIf(p: *Parse) !NodeIndex { }); } -fn parseElseValue(p: *Parse) anyerror!NodeIndex { +fn parseElseValue(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -810,7 +842,7 @@ fn parseElseValue(p: *Parse) anyerror!NodeIndex { }; } -fn parseGroup(p: *Parse) !NodeIndex { +fn parseGroup(p: *Parse) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); @@ -855,7 +887,7 @@ fn addExtra(p: *Parse, comptime T: type, extra: T) !ExtraIndex { fn fail(p: *Parse, ast_error: AstError) Error { p.ast_error = ast_error; std.debug.print("got {s}", .{@tagName(p.currentTag())}); - return Error.SyntaxError; + return Error.ParseFail; } fn blankNode(p: *Parse, comptime tag: Token.Tag) !NodeIndex { @@ -959,3 +991,6 @@ const optionalNodeIndex = OptionalNodeIndex.init; const optionalExtraIndex = OptionalExtraIndex.init; const t = @import("tracer"); + +const Report = @import("Report.zig"); +const Comp = @import("Comp.zig"); diff --git a/src/compiler/Report.zig b/src/compiler/Report.zig index 53f1509e0027bdb5e6590dfb36fb9858b5a7e6a4..93a6d9e7cef45287821ae974af8a5d32db3781df 100644 --- a/src/compiler/Report.zig +++ b/src/compiler/Report.zig @@ -1,38 +1,489 @@ +//! A Report is a list of errors and warnings for a part of a compilation. +//! +//! `Err/Warn` structs are unions of every possible message, the logic +//! for printing them is all done here. +//! +//! Across the compiler, `Report.Builder`s' `.err` and `.warn` methods +//! can add messages by ID. errors: []Item = &.{}, -warns: []Item = &.{}, +warnings: []Item = &.{}, string_bytes: []u8 = &.{}, pub const Item = struct { - offset: ByteOffset, - message: String, + /// What is this diagnostic + data: union { @"error": Err, warn: Warn }, + /// What file was it in + url: String, + + /// Type is never stored, inferred from which list it is in. + pub const Type = enum { @"error", warn }; +}; + +pub const Err = union(enum) { + pub const FieldEnum = std.meta.FieldEnum(Err); + + /// Constructs a `Report` with just a single error. + pub fn toReport(err: Err, comp: *Comp, file: *File) !Report { + const string_bytes = try comp.gpa.dupe(u8, file.file_path); + errdefer comp.gpa.free(string_bytes); + + const errors = try comp.gpa.dupe(Item, &.{.{ + .data = .{ .@"error" = err }, + .url = .{ .start = 0, .len = @intCast(string_bytes.len) }, + }}); + errdefer comp.gpa.free(errors); + + return .{ .errors = errors, .string_bytes = string_bytes }; + } + + ast: @import("Parse.zig").AstError, + + unknown_identifier: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgNode(data.node, "unknown identifier '{s}'", .{ + ctx.ast.basicNodeSlice(data.node), + }); + } + }, + invalid_builtin: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgToken( + ctx.ast.nodeToken(data.node), + "unknown built-in function '{s}'", + .{ctx.ast.basicNodeSlice(data.node)}, + ); + } + }, + invalid_discard: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgNode(data.node, "local cannot be named '_'", .{}); + } + }, + duplicate_function: struct { + node: NodeIndex, + prev: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of '{s}'", .{ + ctx.ast.fullFnDecl(data.node).id, + }); + try ctx.msgToken(ctx.ast.fnDeclIdToken(data.prev), "also declared here", .{}); + // TODO: be able to store a full list of other declarations + // and add them here as "and here" and so on + } + }, + duplicate_param: struct { + node: NodeIndex, + prev: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgToken(ctx.ast.fnDeclIdToken(data.node), "re-declaration of '{s}'", .{ + ctx.ast.fullParamDecl(data.node).id, + }); + try ctx.msgToken(ctx.ast.nodeToken(data.prev), "also declared here", .{}); + // TODO: be able to store a full list of other declarations + // and add them here as "and here" and so on + } + }, + invalid_return: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgNode( + data.node, + "return cannot be used here", + .{}, + ); + } + }, + unresolved_label: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msg( + .{ .two_tokens = ctx.ast.nodeToken(data.node) + 1 }, + "unknown block label '{s}'", + .{ctx.ast.tokenSlice(ctx.ast.nodeToken(data.node) + 2)}, + ); + } + }, + shadow_builtin_variable: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgToken( + ctx.ast.nodeToken(data.node) + 1, + "declaration shadows built-in constant '{s}'", + .{ctx.ast.fullVarDecl(data.node).id}, + ); + } + }, +}; + +pub const Warn = union(enum) { + pub const FieldEnum = std.meta.FieldEnum(Warn); + + unused: struct { + node: NodeIndex, + kind: UnusedKind, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgToken(ctx.ast.idToken(data.node), "unused {s} '{s}'", .{ + @tagName(data.kind), + switch (ctx.ast.nodeTag(data.node)) { + .param_decl => ctx.ast.fullParamDecl(data.node).id, + .fn_decl => ctx.ast.fullFnDecl(data.node).id, + .var_decl => ctx.ast.fullVarDecl(data.node).id, + else => unreachable, // not a valid symbol node + }, + }); + } + }, +}; + +pub const UnusedKind = enum { + param, + constant, + variable, + function, }; pub const String = struct { start: u32, len: u32, - pub fn message(item: Item, report: Report) []const u8 { - return report.string_bytes[item.message_start..][0..item.message_len]; + pub fn slice(str: String, report: Report) []const u8 { + return report.string_bytes[str.start..][0..str.len]; } }; -pub fn hasErrors(report: Report) bool { - return report.errors.len > 0; +const Prefix = enum { + @"error", + warn, + note, + + fn color(prefix: Prefix) std.io.tty.Color { + return switch (prefix) { + .@"error" => .red, + .warn => .yellow, + .note => .blue, + }; + } +}; + +pub fn deinit(report: Report, gpa: Allocator) void { + gpa.free(report.errors); + gpa.free(report.warnings); + gpa.free(report.string_bytes); +} + +pub fn dumpToWriter(report: Report, comp: *Comp, writer: anytype) !void { + const tty = util.detectTTYFromWriter(writer); + try writer.print("{d} errors, {d} warnings\n", .{ report.errors.len, report.warnings.len }); + for (report.errors) |item| { + try report.formatItem(item, .@"error", comp, writer, tty); + } + for (report.warnings) |item| { + try report.formatItem(item, .warn, comp, writer, tty); + } +} + +pub fn formatItem( + report: Report, + item: Item, + item_type: Item.Type, + comp: *Comp, + writer: anytype, + tty: std.io.tty.Config, +) !void { + var arena = std.heap.ArenaAllocator.init(comp.gpa); + defer arena.deinit(); + var sfa = std.heap.stackFallback(16384, arena.allocator()); + const alloc = sfa.get(); + + const file = try comp.getFile(item.url.slice(report)); + + const need_load_ast = !(item_type == .@"error" and item.data.@"error" == .ast); + const ast_data = if (need_load_ast) try file.getAst(comp) else undefined; + const ast = if (need_load_ast) &ast_data else null; + + var ctx: MsgContext = .{ + .arena = alloc, + .comp = comp, + .file = file, + .ast = &ast_data, + .components = ArrayListUnmanaged(Component).initCapacity(alloc, 16) catch + unreachable, // stack-fallback allocator has enough space. + }; + + switch (item_type) { + inline else => |tag| switch (@field(item.data, @tagName(tag))) { + inline else => |payload| try payload.build(&ctx), + }, + } + + assert(ctx.components.items.len > 0); + + for (ctx.components.items, 1..) |component, i| { + const prefix: Prefix = if (i == 1) + switch (item_type) { + inline else => |tag| @field(Prefix, @tagName(tag)), + } + else + .note; + + const is_last = i == ctx.components.items.len; + + if (i != 1) { + try tty.setColor(writer, .dim); + try writer.writeAll(" │ "); + try tty.setColor(writer, .reset); + } + + try tty.setColor(writer, prefix.color()); + try writer.writeAll(@tagName(prefix)); + try tty.setColor(writer, .reset); + try tty.setColor(writer, .dim); + try writer.writeAll(": "); + try tty.setColor(writer, .reset); + try writer.writeAll(component.message_utf8); + try writer.writeAll("\n"); + + const SliceLoc = struct { + loc: Location, + byte: ByteOffset, + slice: []const u8, + }; + + const utf8_source = (try file.getSource(comp.gpa)).code; + + const at: SliceLoc = switch (component.highlight) { + inline .node, + .token, + .two_tokens, + => |payload, tag| loc: { + const token_index: TokenIndex = switch (tag) { + .node => ast.?.nodeToken(payload), + .token, .two_tokens => payload, + else => comptime unreachable, + }; + + const location = ast.?.tokenLocation(0, token_index); + const token = ast.?.tokens.get(token_index); + + break :loc .{ + .loc = location, + .slice = token.slice(ast.?.utf8_source), + .byte = token.offset, + }; + }, + + inline .codepoint, + .codepoint_eol, + => |b, tag| loc: { + const len = switch (tag) { + .codepoint => std.unicode.utf8ByteSequenceLength(utf8_source[b]) catch 1, + .codepoint_eol => if (std.mem.indexOfScalar(u8, utf8_source[b..], '\n')) |n| + n + else + (utf8_source.len - b), + else => comptime unreachable, + }; + break :loc .{ + .byte = b, + .slice = utf8_source[b..][0..len], + .loc = .{ + .column = 0, + .line = 0, + .line_start = 0, + .line_end = utf8_source.len, + }, + }; + }, + }; + + const line_before = utf8_source[at.loc.line_start..at.byte]; + const trimmed_before = std.mem.trimLeft(u8, line_before, " \t\r"); + const line_after = utf8_source[at.byte + at.slice.len .. at.loc.line_end + 1]; + const width = std.fmt.count("{d}", .{at.loc.line}); + + try tty.setColor(writer, .dim); + try writer.writeByteNTimes(' ', width); + try writer.print("{s}{s}:{d}:{d}\n", .{ + if (i == 1) "┬ " else "├ ", + file.file_path, + at.loc.line, + at.loc.column, + }); + try writer.print("{d}", .{at.loc.line}); + try writer.print("{s} ", .{"│"}); + try tty.setColor(writer, .reset); + try writer.writeAll(trimmed_before); + try tty.setColor(writer, prefix.color()); + try tty.setColor(writer, .bold); + try writer.writeAll(at.slice); + try tty.setColor(writer, .reset); + try writer.writeAll(line_after); + try tty.setColor(writer, .dim); + try writer.writeAll(if (is_last) " ╰" else " ├"); + try writer.writeBytesNTimes( + "─", + width + + (at.byte - at.loc.line_start) - + (line_before.len - trimmed_before.len), + ); + try tty.setColor(writer, .reset); + try tty.setColor(writer, prefix.color()); + try tty.setColor(writer, .bold); + try writer.writeBytesNTimes("━", at.slice.len); + try tty.setColor(writer, .reset); + try writer.writeAll("\n"); + } } -pub fn hasWarns(report: Report) bool { - return report.warns.len > 0; +pub fn dumpToStdErr(report: Report, comp: *Comp) !void { + const stderr = std.io.getStdErr().writer(); + + // Disabled because of coloring issues. + // var buf = std.io.bufferedWriter(stderr); + // const writer = buf.writer(); + try dumpToWriter(report, comp, stderr); + // try buf.flush(); } -pub const Wip = struct { - errors: ArrayListUnmanaged(Item), - warns: ArrayListUnmanaged(Item), - string_bytes: ArrayListUnmanaged(u8), +pub const Builder = struct { + gpa: Allocator, + current_file: *Comp.File, + errors: ArrayListUnmanaged(Item) = .{}, + warnings: ArrayListUnmanaged(Item) = .{}, + string_bytes: ArrayListUnmanaged(u8) = .{}, + current_file_str: ?String = null, + + pub fn err(b: *Builder, comptime kind: Err.FieldEnum, data: std.meta.FieldType(Err, kind)) !void { + try b.errors.append(b.gpa, .{ + .data = .{ .@"error" = @unionInit(Err, @tagName(kind), data) }, + .url = try b.currentFileString(), + }); + } + + pub fn warn(b: *Builder, comptime kind: Warn.FieldEnum, data: std.meta.FieldType(Warn, kind)) !void { + try b.warnings.append(b.gpa, .{ + .data = .{ .warn = @unionInit(Warn, @tagName(kind), data) }, + .url = try b.currentFileString(), + }); + } + + pub fn addString(b: *Builder, str: []const u8) !String { + const top = b.string_bytes.items.len; + try b.string_bytes.appendSlice(b.gpa, str); + return .{ .start = @intCast(top), .len = @intCast(str.len) }; + } + + pub fn currentFileString(b: *Builder) !String { + return b.current_file_str orelse { + b.current_file_str = try b.addString(b.current_file.file_path); + return b.current_file_str.?; + }; + } + + pub fn deinit(b: *Builder) void { + b.errors.deinit(b.gpa); + b.warnings.deinit(b.gpa); + } + + pub fn finalize(b: *Builder) !Report { + defer b.* = undefined; + errdefer b.deinit(); + + const errors = try b.errors.toOwnedSlice(b.gpa); + errdefer b.gpa.free(errors); + + const warnings = try b.warnings.toOwnedSlice(b.gpa); + errdefer b.gpa.free(warnings); + + const string_bytes = try b.string_bytes.toOwnedSlice(b.gpa); + errdefer b.gpa.free(string_bytes); + + return .{ + .errors = errors, + .warnings = warnings, + .string_bytes = string_bytes, + }; + } +}; + +pub const Highlight = union(enum) { + // Highlight one token + token: TokenIndex, + // Highlight two tokens + two_tokens: TokenIndex, + // Highlight a Node + node: NodeIndex, + // Highlight a codepoint + codepoint: ByteOffset, + // Highlight from the start of a codepoint to '\n' + codepoint_eol: ByteOffset, +}; + +const Component = struct { + message_utf8: []const u8, + highlight: Highlight, +}; + +/// This serves as a small API to make creating messages easier. +pub const MsgContext = struct { + file: *File, + comp: *Comp, + ast: *const Ast, + + /// This arena is is a stack fallback with 16kb, then + /// falling back to an arena allocator. This should + /// make the cost of arbitrarily creating `Component` + /// nodes extremely lightweight. + arena: Allocator, + + /// First component is "error" or "warn" + /// Trailing components are "note" + components: ArrayListUnmanaged(Component), + + pub fn msg(m: *MsgContext, highlight: Highlight, comptime fmt: []const u8, args: anytype) !void { + const text = try std.fmt.allocPrint(m.arena, fmt, args); + try m.components.append(m.arena, .{ + .message_utf8 = text, + .highlight = highlight, + }); + } + + pub fn msgNode(m: *MsgContext, node: NodeIndex, comptime fmt: []const u8, args: anytype) !void { + return m.msg(.{ .node = node }, fmt, args); + } + + pub fn msgToken(m: *MsgContext, token: TokenIndex, comptime fmt: []const u8, args: anytype) !void { + return m.msg(.{ .token = token }, fmt, args); + } }; const Report = @This(); const std = @import("std"); const ArrayListUnmanaged = std.ArrayListUnmanaged; +const Allocator = std.mem.Allocator; const assert = std.debug.assert; -pub const Tokenizer = @import("Tokenizer.zig"); -pub const ByteOffset = Tokenizer.ByteOffset; + +const Tokenizer = @import("Tokenizer.zig"); +const ByteOffset = Tokenizer.ByteOffset; + +const Ast = @import("Ast.zig"); +const TokenIndex = Ast.TokenIndex; +const NodeIndex = Ast.NodeIndex; +const Location = Ast.Location; + +const Comp = @import("Comp.zig"); +const File = Comp.File; + +const util = @import("util.zig"); diff --git a/src/compiler/Sema.zig b/src/compiler/Sema.zig index 5dd1f1e735811f7f247eaf748a5a0cb8c6875b69..d68a7409ace4beed313cecf98aed549c4017519a 100644 --- a/src/compiler/Sema.zig +++ b/src/compiler/Sema.zig @@ -37,7 +37,6 @@ const InstMap = struct { pub fn init(allocator: Allocator, code: *const Teg, inst_start: Teg.Inst.Index) !InstMap { const decl = code.declData(inst_start); - log.info("pre-allocating {d} map-indicies for InstMap", .{decl.instruction_count}); const insts = try allocator.alloc(Air.Inst.Ref.Optional, decl.instruction_count); @memset(insts, .none); return .{ @@ -72,7 +71,6 @@ pub fn init(s: *Sema) !void { try s.comp.current_compile_decls.append(s.gpa, s.decl_index); - log.info("init sema on index {}", .{s.decl_index}); s.inst_map = try InstMap.init( s.arena, &s.code, @@ -193,8 +191,6 @@ fn analyzeCode(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { const t = tracer.traceFmt(@src(), "%{d}, {s}", .{ @intFromEnum(in), @tagName(s.code.instTag(in)) }); defer t.end(); - log.info("runtime %{d} = .{s}", .{ @intFromEnum(in), @tagName(s.code.instTag(in)) }); - const out = try s.analyzeCodeInner(in); if (s.code.instTag(in) != .block) try s.inst_map.add(in, out); @@ -226,8 +222,6 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { const string = rhs.asString(s.ip); const base_type = s.typeOfRef(base); - log.info("process field with type {any} . {s}", .{ base_type, string.toSlice(s.ip) }); - switch (base_type) { .opaque_type => { return s.addInstRef(.field, .{ @@ -277,18 +271,12 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { } const left_type = s.typeOfRef(lhs); + _ = left_type; // autofix const right_type = s.typeOfRef(rhs); + _ = right_type; // autofix // TODO: validate type for operation - log.info("runtime {s} operation on {} and {}", .{ - @tagName(tag), - left_type.toKey(s.ip), - right_type.toKey(s.ip), - }); - - // todo: type check - return s.addInstRef(tag.airTag(), .{ .lhs = lhs, .rhs = rhs, @@ -300,14 +288,10 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { const operand = try s.resolveRefRuntime(un.operand); const operand_type = s.typeOfRef(operand); + _ = operand_type; // autofix // TODO: validate type for operation - log.info("runtime {s} operation on {}", .{ - @tagName(tag), - operand_type.toKey(s.ip), - }); - return s.addInstRef(tag.airTag(), .{ .operand = operand, }); @@ -425,7 +409,6 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { fn markInst(s: *Sema, inst: Air.Inst.Index) void { const flags: *Air.Flags = &s.air_instructions.items(.flags)[@intFromEnum(inst)]; flags.uses = flags.uses.increment(); - log.info("markInst {}", .{inst}); } fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) anyerror!Air.Inst.Ref { @@ -485,15 +468,13 @@ fn resolveRefComptime(s: *Sema, ref: Teg.Inst.Ref) !Value { } } - const tag = s.code.instTag(in); - log.info("comptime %{d} = .{s}", .{ @intFromEnum(in), @tagName(tag) }); - switch (tag) { + switch (s.code.instTag(in)) { .string => { const string: Teg.SlicedString = s.code.instPayload(.string, in); return s.makeValue(.{ .string = string.slice(s.code.string_bytes) }); }, - else => std.debug.panic("TODO: comptime analyze of .{s}", .{@tagName(tag)}), + else => |tag| std.debug.panic("TODO: comptime analyze of .{s}", .{@tagName(tag)}), } } @@ -509,12 +490,13 @@ fn resolveDecl(s: *Sema, decl_index: Decl.Index) !Value { var arena = std.heap.ArenaAllocator.init(s.gpa); defer arena.deinit(); + var todo: Report = undefined; var sema: Sema = .{ .comp = s.comp, .gpa = s.gpa, .ip = s.ip, .arena = arena.allocator(), - .code = try s.ip.namespacePtr(decl.src_namespace).file_scope.getTeg(s.gpa), + .code = try s.ip.namespacePtr(decl.src_namespace).file_scope.getTeg(s.comp, &todo), .decl = decl, .decl_index = decl_index, .namespace_index = decl.src_namespace, @@ -529,10 +511,9 @@ fn resolveDecl(s: *Sema, decl_index: Decl.Index) !Value { return value; }, - .complete => { + else => { return decl.val; }, - else => |tag| std.debug.panic("TODO: load_decl when decl is in state .{s}", .{@tagName(tag)}), } } @@ -573,7 +554,6 @@ const IncompleteBlock = struct { }; fn addBlock(s: *Sema) !IncompleteBlock { - log.info("start block at @{d}", .{s.air_instructions.len}); try s.air_instructions.append(s.gpa, .{ .tag = .block, .data = undefined, @@ -583,7 +563,6 @@ fn addBlock(s: *Sema) !IncompleteBlock { } fn addInstRef(s: *Sema, comptime tag: Air.Inst.Tag, data: tag.Data()) !Air.Inst.Ref { - log.info("add air .{s}, {}", .{ @tagName(tag), data }); try s.air_instructions.append(s.gpa, .{ .tag = tag, .data = Air.EncodedData.encode(tag, data), @@ -627,11 +606,9 @@ fn mapTeg(s: *Sema, inst: Teg.Inst.Ref) Air.Inst.Ref { } fn validateType(s: *Sema, checked_type: Value, wanted_type: InternPool.Index) !void { + _ = s; // autofix if (checked_type.toIntern() != wanted_type) { - log.info("failed type check {}, got {}", .{ Value.fromInterned(wanted_type).fmt(s.ip), checked_type.fmt(s.ip) }); return error.TypeCheckFailed; - } else { - log.info("passed type check {}", .{checked_type.fmt(s.ip)}); } } @@ -690,4 +667,6 @@ const InternPool = @import("InternPool.zig"); const NullTerminatedString = InternPool.NullTerminatedString; const dod = @import("dod.zig"); +const Report = @import("Report.zig"); + const tracer = @import("tracer"); diff --git a/src/compiler/Teg.zig b/src/compiler/Teg.zig index 3f38ec22e6b4c167a68c892d39cfd9cadd16e1bd..b2444b4221296b0deb11a2417e02447a0eef8f38 100644 --- a/src/compiler/Teg.zig +++ b/src/compiler/Teg.zig @@ -3,6 +3,7 @@ insts: Inst.List.Slice, string_bytes: []const u8, extra: ExtraStore = .{}, +report: Report, pub const ExtraStore = dod.ExtraStore(.teg, .@"const"); pub const EncodedData = dod.EncodedData(Inst.Tag, u64); @@ -343,6 +344,7 @@ pub fn deinit(teg: *Teg, gpa: std.mem.Allocator) void { gpa.free(teg.string_bytes); teg.extra.deinit(gpa); teg.insts.deinit(gpa); + teg.report.deinit(gpa); teg.* = undefined; } @@ -433,3 +435,5 @@ const Float = dod.Float; const InternPool = @import("InternPool.zig"); const Air = @import("Air.zig"); + +const Report = @import("Report.zig"); diff --git a/src/compiler/Tokenizer.zig b/src/compiler/Tokenizer.zig index 546c06e330ed999ccd554849c9515479905f0214..b80108b8b71b73507d62d2351cb9b90825eb144f 100644 --- a/src/compiler/Tokenizer.zig +++ b/src/compiler/Tokenizer.zig @@ -5,18 +5,14 @@ //! Retriving payloads like string/numeric values, identifier names, etc is //! done separately, either in Parse or Transform, when the respective data //! is actually needed. Tokenization does guarante that such data is valid. - -// TODO: make `code` a [:0]const u8. this doesnt disallow null bytes, but -// it requires the source to end with one. This can make byteAt(1) return u8 -// instead of an optional. I am told this improves performance. -code: []const u8, +code: [:0]const u8, pos: ByteOffset = 0, /// Tokenize source code into a list of tokens. /// When an error occurs, the position of the error is returned in `error_pos`. /// Caller owns returned slice. pub fn tokenize( - source_code: []const u8, + source_code: [:0]const u8, gpa: mem.Allocator, error_pos: *ByteOffset, ) TokenizeError!Token.List.Slice { @@ -233,7 +229,7 @@ pub const Token = struct { /// /// If you are computing the length of something that is knowable /// without the source code, use token.tag.staticLen() instead. - pub fn len(token: Token, utf8_source: []const u8) u32 { + pub fn len(token: Token, utf8_source: [:0]const u8) u32 { return token.tag.staticLen() orelse { var tokenizer = Tokenizer{ .code = utf8_source, .pos = token.offset }; assert( @@ -246,7 +242,7 @@ pub const Token = struct { }; } - pub fn slice(token: Token, utf8_source: []const u8) []const u8 { + pub fn slice(token: Token, utf8_source: [:0]const u8) []const u8 { return utf8_source[token.offset..][0..token.len(utf8_source)]; } @@ -294,7 +290,7 @@ pub fn next(t: *Tokenizer) !?Token { const trace = T.trace(@src()); defer trace.end(); - switch (t.byte() orelse return null) { + switch (t.byte()) { ' ', '\t', '\n', '\r' => unreachable, // call skipWhitespace 'a'...'z', 'A'...'Z', '_', '$' => { @@ -303,7 +299,7 @@ pub fn next(t: *Tokenizer) !?Token { return try t.tokenizeIdentifier(start); }, - '@' => switch (t.byteAt(1) orelse 0) { + '@' => switch (t.nextByte()) { '"' => { const start = t.pos; t.pos += 1; @@ -319,12 +315,12 @@ pub fn next(t: *Tokenizer) !?Token { '0'...'9' => return try t.tokenizeNumber(), - '.' => switch (t.byteAt(1) orelse 0) { + '.' => switch (t.nextByte()) { '0'...'9' => return try t.tokenizeNumber(), else => return t.simpleToken(.dot), }, - '/' => switch (t.byteAt(1) orelse 0) { + '/' => switch (t.nextByte()) { '=' => return t.simpleToken(.slash_equal), '/' => { if ((t.byteAt(2) orelse 0 == '/') and @@ -349,7 +345,7 @@ pub fn next(t: *Tokenizer) !?Token { else => return t.simpleToken(.slash), }, - '\\' => switch (t.byteAt(1) orelse 0) { + '\\' => switch (t.nextByte()) { '\\' => { const start = t.pos; 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 { '"' => return try t.tokenizeString(), - '&' => switch (t.byteAt(1) orelse 0) { + '&' => switch (t.nextByte()) { '&' => return t.simpleToken(.and_and), '=' => return t.simpleToken(.and_equal), else => return t.simpleToken(.@"and"), }, - '|' => switch (t.byteAt(1) orelse 0) { + '|' => switch (t.nextByte()) { '|' => return t.simpleToken(.pipe_pipe), '=' => return t.simpleToken(.pipe_equal), else => return t.simpleToken(.pipe), }, - '^' => switch (t.byteAt(1) orelse 0) { + '^' => switch (t.nextByte()) { '^' => return t.simpleToken(.caret), '=' => return t.simpleToken(.caret_equal), else => return t.simpleToken(.caret), }, - '!' => switch (t.byteAt(1) orelse 0) { + '!' => switch (t.nextByte()) { '=' => return t.simpleToken(.equal_equal), else => return t.simpleToken(.bang_equal), }, - '=' => switch (t.byteAt(1) orelse 0) { + '=' => switch (t.nextByte()) { '=' => return t.simpleToken(.equal_equal), else => return t.simpleToken(.equal), }, - '>' => switch (t.byteAt(1) orelse 0) { + '>' => switch (t.nextByte()) { '=' => return t.simpleToken(.gt_equal), '>' => switch (t.byteAt(2) orelse 0) { '>' => return t.simpleToken(.gt_gt_gt), @@ -391,7 +387,7 @@ pub fn next(t: *Tokenizer) !?Token { }, else => return t.simpleToken(.gt), }, - '<' => switch (t.byteAt(1) orelse 0) { + '<' => switch (t.nextByte()) { '=' => return t.simpleToken(.lt_equal), '<' => switch (t.byteAt(2) orelse 0) { '=' => return t.simpleToken(.lt_lt_equal), @@ -399,14 +395,12 @@ pub fn next(t: *Tokenizer) !?Token { // Git merge conflicts are <<<<<<< const start = t.pos; t.pos += 2; - brk: { - var remain: u3 = 4; - while (t.byte() orelse break :brk == '<') { - t.pos += 1; - remain -= 1; - if (remain == 0) { - return error.GitConflict; - } + var remain: u3 = 4; + while (t.byte() == '<') { + t.pos += 1; + remain -= 1; + if (remain == 0) { + return error.GitConflict; } } t.pos = start; @@ -415,25 +409,25 @@ pub fn next(t: *Tokenizer) !?Token { }, else => return t.simpleToken(.lt), }, - '+' => switch (t.byteAt(1) orelse 0) { + '+' => switch (t.nextByte()) { '=' => return t.simpleToken(.plus_equal), else => return t.simpleToken(.plus), }, - '-' => switch (t.byteAt(1) orelse 0) { + '-' => switch (t.nextByte()) { '=' => return t.simpleToken(.minus_equal), '>' => return t.simpleToken(.minus_lt), else => return t.simpleToken(.minus), }, - '*' => switch (t.byteAt(1) orelse 0) { + '*' => switch (t.nextByte()) { '*' => return t.simpleToken(.star_star), '=' => return t.simpleToken(.star_equal), else => return t.simpleToken(.star), }, - '%' => switch (t.byteAt(1) orelse 0) { + '%' => switch (t.nextByte()) { '=' => return t.simpleToken(.percent_equal), else => return t.simpleToken(.percent), }, - '~' => switch (t.byteAt(1) orelse 0) { + '~' => switch (t.nextByte()) { '=' => return t.simpleToken(.tilde_equal), else => return t.simpleToken(.tilde), }, @@ -462,19 +456,40 @@ pub fn next(t: *Tokenizer) !?Token { }, else => return error.InvalidSyntax, + + 0 => { + if (t.pos == t.code.len) return null; + return error.InvalidSyntax; + }, } } } -fn byte(t: Tokenizer) ?u8 { - if (t.pos >= t.code.len) return null; +/// Source code is null terminated, so accessing this is safe +fn byte(t: Tokenizer) u8 { return t.code[t.pos]; } +/// Source code is null terminated. +fn nextByte(t: Tokenizer) u8 { + assert(t.byte() < 0x80); + return t.code[t.pos + 1]; +} + /// Remember that using this only makes sense if you know for certain that all /// previous bytes are ASCII, otherwise you may hit UB -fn byteAt(t: Tokenizer, offset: u32) ?u8 { - if (t.pos + offset >= t.code.len) return null; +fn byteAt(t: Tokenizer, comptime offset: u32) ?u8 { + if (offset == 1) @compileError("use nextByte()"); + + // assert that the previous bytes are ASCII + // current byte is allowed to be a continuation byte + for (t.code[t.pos .. t.pos + offset]) |b| { + assert(b < 0x80); + } + return t.code[t.pos + offset]; +} + +fn byteAtRt(t: Tokenizer, offset: u32) ?u8 { // assert that the previous bytes are ASCII // current byte is allowed to be a continuation byte for (t.code[t.pos .. t.pos + offset]) |b| { @@ -484,7 +499,7 @@ fn byteAt(t: Tokenizer, offset: u32) ?u8 { } fn codePoint(t: Tokenizer) !struct { u3, u21 } { - const b = t.byte() orelse unreachable; // must check the first byte manually + const b = t.byte(); // must check the first byte manually assert(b >= 0x80); // must be a continuation byte const byte_len = unicode.utf8ByteSequenceLength(b) catch @@ -498,7 +513,7 @@ fn codePoint(t: Tokenizer) !struct { u3, u21 } { } fn isAtIdentifierStart(t: *Tokenizer) !bool { - switch (t.byte() orelse return false) { + switch (t.byte()) { 'a'...'z', 'A'...'Z', '_', '$' => return true, else => return false, 0x80...0xFF => { @@ -513,7 +528,7 @@ fn tokenizeIdentifier(t: *Tokenizer, start_byte: u32) !Token { defer trace.end(); while (true) { - switch (t.byte() orelse break) { + switch (t.byte()) { 'a'...'z', 'A'...'Z', '0'...'9', '_', '$' => { t.pos += 1; }, @@ -555,8 +570,8 @@ fn tokenizeNumber(t: *Tokenizer) !?Token { defer trace.end(); // Caller ensures the following invariant: - switch (t.byte() orelse unreachable) { - '.' => switch (t.byteAt(1) orelse unreachable) { + switch (t.byte()) { + '.' => switch (t.nextByte()) { '0'...'9' => {}, else => unreachable, }, @@ -566,7 +581,7 @@ fn tokenizeNumber(t: *Tokenizer) !?Token { // Parse Hex, Binary, Octal numbers if (t.byte() == '0') { - switch (t.byteAt(1) orelse 0) { + switch (t.nextByte()) { 'x', 'X' => { t.pos += 2; try t.consumeIntegerRadix(.hex); @@ -594,8 +609,8 @@ fn tokenizeNumber(t: *Tokenizer) !?Token { try t.consumeInteger(); // decimal point notation - switch (t.byte() orelse 0) { - '.' => switch (t.byteAt(1) orelse 0) { + switch (t.byte()) { + '.' => switch (t.nextByte()) { '0'...'9' => { t.pos += 1; tag = .number_float; @@ -611,15 +626,15 @@ fn tokenizeNumber(t: *Tokenizer) !?Token { } } - switch (t.byte() orelse 0) { + switch (t.byte()) { 'e', 'E' => { tag = .number_float; t.pos += 1; - switch (t.byte() orelse 0) { + switch (t.byte()) { '+', '-' => { t.pos += 1; - switch (t.byte() orelse 0) { + switch (t.byte()) { '0'...'9' => {}, else => return t.maybeUTF8Error(error.InvalidSyntax), } @@ -631,8 +646,8 @@ fn tokenizeNumber(t: *Tokenizer) !?Token { try t.consumeInteger(); // decimal point notation - switch (t.byte() orelse 0) { - '.' => switch (t.byteAt(1) orelse 0) { + switch (t.byte()) { + '.' => switch (t.nextByte()) { '0'...'9' => { t.pos += 1; tag = .number_float; @@ -662,7 +677,7 @@ fn maybeConsumeInteger(t: *Tokenizer) !?void { const trace = T.trace(@src()); defer trace.end(); - switch (t.byte() orelse return null) { + switch (t.byte()) { '0'...'9' => { t.pos += 1; }, @@ -676,7 +691,7 @@ fn consumeInteger(t: *Tokenizer) !void { const trace = T.trace(@src()); defer trace.end(); - switch (t.byte() orelse unreachable) { + switch (t.byte()) { '0'...'9' => { t.pos += 1; }, @@ -684,13 +699,13 @@ fn consumeInteger(t: *Tokenizer) !void { } while (true) { - switch (t.byte() orelse return) { + switch (t.byte()) { '0'...'9' => { t.pos += 1; }, '_' => { t.pos += 1; - switch (t.byte() orelse 0) { + switch (t.byte()) { '0'...'9' => { t.pos += 1; }, @@ -715,7 +730,7 @@ fn consumeIntegerRadix(t: *Tokenizer, comptime radix: Radix) !void { }; first: { - const b = t.byte() orelse return; + const b = t.byte(); inline for (digits) |digit| { if (b == digit) { break :first; @@ -728,7 +743,7 @@ fn consumeIntegerRadix(t: *Tokenizer, comptime radix: Radix) !void { while (true) { brk: { - const b = t.byte() orelse return; + const b = t.byte(); inline for (digits) |digit| { if (b == digit) { break :brk; @@ -741,7 +756,7 @@ fn consumeIntegerRadix(t: *Tokenizer, comptime radix: Radix) !void { } fn tokenizeString(t: *Tokenizer) !Token { - assert(t.byte().? == '"'); + assert(t.byte() == '"'); const start = t.pos; try t.consumeString(); return .{ .tag = .string, .offset = start }; @@ -751,32 +766,30 @@ fn consumeString(t: *Tokenizer) !void { const trace = T.trace(@src()); defer trace.end(); - assert(t.byte().? == '"'); + const start = t.pos; + assert(t.byte() == '"'); t.pos += 1; while (true) { - const b = t.byte() orelse return error.UnterminatedString; - switch (b) { + switch (t.byte()) { '"' => { t.pos += 1; break; }, '\\' => { - switch (t.byteAt(1) orelse { - // we want error.InvalidEscapeSequence to be at the '\' char, - // but we want error.UnterminatedString to be at the end of the source code - t.pos += 1; - return error.UnterminatedString; - }) { - '\n' => return error.UnterminatedString, + switch (t.nextByte()) { + '\n' => { + t.pos = start; + return error.UnterminatedString; + }, '\\' => t.pos += 2, '"' => t.pos += 2, 'n' => t.pos += 2, 'r' => t.pos += 2, 't' => t.pos += 2, '0' => switch (t.byteAt(2) orelse { - t.pos += 2; + t.pos = start; return error.UnterminatedString; }) { // '\0xFF' hex escape @@ -788,7 +801,7 @@ fn consumeString(t: *Tokenizer) !void { const esc_start = t.pos; t.consumeStringUnicodeEscape() catch |err| switch (err) { error.InvalidEscapeSequence => { - if (t.byte().? == '\n') + if (t.byte() == '\n') return error.UnterminatedString; t.pos = esc_start; return err; @@ -797,12 +810,16 @@ fn consumeString(t: *Tokenizer) !void { t.pos = esc_start; return err; }, - error.UnterminatedString => return err, + // error.UnterminatedString => return err, }; }, else => return t.maybeUTF8ErrorAt(error.InvalidEscapeSequence, 1), } }, + '\n' => { + t.pos = start; + return error.UnterminatedString; + }, else => { t.pos += 1; }, @@ -818,16 +835,16 @@ fn consumeStringUnicodeEscape(t: *Tokenizer) !void { const trace = T.trace(@src()); defer trace.end(); - assert(t.byte().? == '\\'); - assert(t.byteAt(1).? == 'u'); + assert(t.byte() == '\\'); + assert(t.nextByte() == 'u'); t.pos += 2; - if ((t.byte() orelse return error.UnterminatedString) != '{') + if (t.byte() != '{') return error.InvalidEscapeSequence; t.pos += 1; - if ((t.byte() orelse return error.UnterminatedString) == '0') { - if ((t.byteAt(1) orelse return error.UnterminatedString) == 'x') { + if (t.byte() == '0') { + if (t.nextByte() == 'x') { t.pos += 2; } } @@ -837,7 +854,7 @@ fn consumeStringUnicodeEscape(t: *Tokenizer) !void { const slice = t.code[start..t.pos]; - if ((t.byte() orelse return error.UnterminatedString) != '}') + if (t.byte() != '}') return error.InvalidEscapeSequence; 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(.{ .{ .name = @errorName(real_err) }, }, }) { - switch (t.byte() orelse return real_err) { + switch (t.byte()) { 0x80...0xFF => { _ = try t.codePoint(); return real_err; @@ -881,7 +898,7 @@ fn maybeUTF8ErrorAt(t: *Tokenizer, comptime real_err: anytype, at: u32) @Type(.{ .{ .name = @errorName(real_err) }, }, }) { - switch (t.byteAt(at) orelse return real_err) { + switch (t.byteAtRt(at) orelse return real_err) { 0x80...0xFF => { _ = try t.codePoint(); return real_err; @@ -895,7 +912,7 @@ pub fn skipWhitespace(t: *Tokenizer) void { defer trace.end(); while (true) { - switch (t.byte() orelse break) { + switch (t.byte()) { ' ', '\t', '\n', '\r' => { t.pos += 1; }, diff --git a/src/compiler/Transform.zig b/src/compiler/Transform.zig index d2534f57bfed4f540e9d98878f8feb1220ca3932..3ec6fc99384a4dc37a9e2bd629bd6c05ffa0ef1a 100644 --- a/src/compiler/Transform.zig +++ b/src/compiler/Transform.zig @@ -14,29 +14,51 @@ scratch: std.ArrayListUnmanaged(u32) = .{}, call_args: std.ArrayListUnmanaged(Inst.Ref) = .{}, scopes: std.ArrayListUnmanaged(Scope) = .{}, -pub fn transform(ast: Ast, gpa: Allocator) !Teg { +report: Report.Builder, + +pub const Error = error{ + OutOfMemory, + TransformFail, +}; + +pub fn transform( + ast: Ast, + gpa: Allocator, + file: *Comp.File, + fail_report: *Report, +) Error!Teg { const trace = tracer.trace(@src()); defer trace.end(); var t: Transform = .{ .ast = ast, .gpa = gpa, + .report = .{ .gpa = gpa, .current_file = file }, }; errdefer t.deinit(); try t.string_bytes.append(t.gpa, 0); - const module = try t.transformModule(0); + const module = t.transformModule(0) catch |err| switch (err) { + error.OutOfMemory => return err, + }; + + const report = try t.report.finalize(); + if (report.errors.len > 0) { + fail_report.* = report; + return error.TransformFail; + } + assert(@intFromEnum(module) == 0); try t.insts.setCapacity(t.gpa, t.insts.len); defer t.deinit(); - return Teg{ .insts = t.insts.toOwnedSlice(), .string_bytes = try t.string_bytes.toOwnedSlice(t.gpa), .extra = try t.extra.toOwnedStore(t.gpa), + .report = report, }; } @@ -45,6 +67,9 @@ pub fn deinit(t: *Transform) void { t.call_args.deinit(t.gpa); assert(t.scopes.items.len == 0); t.scopes.deinit(t.gpa); + t.string_bytes.deinit(t.gpa); + t.insts.deinit(t.gpa); + t.extra.deinit(t.gpa); t.* = undefined; } @@ -65,6 +90,25 @@ const Scope = struct { }; }; + fn addUnusedWarnings(s: Scope, t: *Transform) !void { + for (s.symbols.values()) |value| { + if (!value.used) { + try t.report.warn(.unused, .{ + .node = value.node, + .kind = switch (value.data) { + .declaration => .function, + .param => .param, + .mutable => .variable, + .immutable => switch (t.ast.nodeTag(value.node)) { + .param_decl => .param, + else => .constant, + }, + }, + }); + } + } + } + fn deinit(s: *Scope, gpa: Allocator) void { s.symbols.deinit(gpa); s.* = undefined; @@ -171,7 +215,7 @@ const BuiltInIds = std.ComptimeStringMap(Inst.Ref, .{ // todo: nan/infinity? }); -fn transformModule(t: *Transform, module_node: NodeIndex) !Inst.Index { +fn transformModule(t: *Transform, module_node: NodeIndex) Allocator.Error!Inst.Index { const trace = tracer.trace(@src()); defer trace.end(); @@ -259,10 +303,12 @@ fn transformModule(t: *Transform, module_node: NodeIndex) !Inst.Index { .extra_index = try t.extra.append(t.gpa, IndexList, declarations), }); + try t.scope().addUnusedWarnings(t); + return module; } -fn previsitModuleStatement(t: *Transform, node: NodeIndex) !void { +fn previsitModuleStatement(t: *Transform, node: NodeIndex) Allocator.Error!void { const trace = tracer.trace(@src()); defer trace.end(); @@ -272,7 +318,11 @@ fn previsitModuleStatement(t: *Transform, node: NodeIndex) !void { const fn_decl = t.ast.fullFnDecl(node); const gop = try t.scope().symbols.getOrPut(t.gpa, fn_decl.id); if (gop.found_existing) { - return error.DuplicateFunction; + try t.report.err(.duplicate_function, .{ + .node = node, + .prev = gop.value_ptr.node, + }); + return; } gop.value_ptr.* = .{ .node = node, @@ -299,7 +349,7 @@ fn transformModuleStatement(t: *Transform, decl: BlockContext, node: NodeIndex) } } -fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) !Inst.Index { +fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) Allocator.Error!Inst.Index { const trace = tracer.trace(@src()); defer trace.end(); @@ -350,6 +400,8 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) ! .extra_index = decl_extra, }); + try t.scope().addUnusedWarnings(t); + return func; } @@ -366,7 +418,7 @@ fn transformExpressionToBlock( decl: BlockContext, node: NodeIndex, comptime is_function: bool, -) anyerror!Inst.Ref { +) Allocator.Error!Inst.Ref { const trace = tracer.trace(@src()); defer trace.end(); @@ -461,7 +513,7 @@ fn transformExpressionToBlock( return ret_block.toRef(); } -fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) !Inst.Ref { +fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) Allocator.Error!Inst.Ref { const trace = tracer.trace(@src()); defer trace.end(); @@ -472,7 +524,11 @@ fn transformParamDecl(t: *Transform, decl: BlockContext, param: NodeIndex) !Inst // can use this param's value, if known at compile time. const sym = try t.scope().symbols.getOrPut(t.gpa, param_decl.id); if (sym.found_existing) { - return error.DuplicateParam; + try t.report.err(.duplicate_param, .{ + .node = param, + .prev = sym.value_ptr.node, + }); + return .void_value; } sym.value_ptr.* = .{ @@ -501,7 +557,8 @@ const Builtin = enum { defer trace.end(); if (args.len != 1) { - return error.InvalidBuiltin; + // try t.report.err(.invalid_builtin_args, .{ .id = "js" }); + return .void_value; } const source = try t.transformExpression(decl, args[0]) orelse return null; @@ -518,7 +575,7 @@ const Builtin = enum { /// This is the primary transformation function: converting an AST expression into instructions. /// `null` represents a `return` or `break`, aka this branch of code is dead. -fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyerror!?Inst.Ref { +fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Allocator.Error!?Inst.Ref { const node_tag = t.ast.nodes.items(.tag)[node]; const trace = tracer.traceFmt(@src(), "{s}", .{@tagName(node_tag)}); @@ -537,17 +594,13 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyer return ref; } - if (mem.eql(u8, id, "_")) { - return error.InvalidDiscard; - } - if (mem.startsWith(u8, id, "@")) { @panic("TODO: string identifiers"); } const lookup = t.lookupId(id) orelse { - std.debug.print("lookupId failed for {s}\n", .{id}); - return error.UnknownIdentifier; + try t.report.err(.unknown_identifier, .{ .node = node }); + return .void_value; }; return switch (lookup.data) { @@ -706,7 +759,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyer .label => { const expr = t.ast.nodeData(node).label.expr; - const label = t.ast.tokenSlice(t.ast.nodes.items(.main_token)[node]); + const label = t.ast.tokenSlice(t.ast.nodeToken(node)); var target: BlockContext.BreakTarget = .{ .parent = decl.break_target, @@ -778,7 +831,8 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyer .builtin_call, => { const builtin = Builtin.map.get(t.ast.basicNodeSlice(node)[1..]) orelse { - return error.InvalidBuiltin; + try t.report.err(.invalid_builtin, .{ .node = node }); + return .void_value; }; const base = t.ast.nodeData(node).list; @@ -847,7 +901,7 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyer const string_ref = try t.addInstRef(.{ .tag = .string, .data = EncodedData.encode(.string, .{ - .src_node = decl.nodeOffset(node), // TODO: this is incorrect, there is no node + .src_node = decl.nodeOffset(node), .extra_index = try t.extra.append(t.gpa, SlicedString, prop_name_str), }), }); @@ -915,7 +969,8 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyer const ret_node = t.ast.nodeData(node).@"return"; const ret_block_ptr = decl.return_target orelse { - return error.InvalidReturn; + // return error.InvalidReturn; + @panic("TODO"); }; const ret_block = if (ret_block_ptr.*) |block| @@ -957,7 +1012,8 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) anyer ); const block = try decl.findAndMarkBreak(t, label) orelse { - return error.UnknownBreakTarget; + try t.report.err(.unresolved_label, .{ .node = node }); + return null; }; const operand: Inst.Ref = if (brk_node.value.unwrap()) |expr| @@ -1001,6 +1057,17 @@ fn transformBlockStatement( .var_decl => { const var_decl = t.ast.fullVarDecl(node); + + if (mem.eql(u8, var_decl.id, "_")) { + try t.report.err(.invalid_discard, .{ .node = node }); + return .void_value; + } + + if (BuiltInIds.has(var_decl.id)) { + try t.report.err(.shadow_builtin_variable, .{ .node = node }); + return .void_value; + } + const type_ref = if (var_decl.type) |data_type| try t.transformExpression(decl, data_type) else @@ -1040,7 +1107,7 @@ fn transformBlockStatement( }, }); - return mem_ref; + return .void_value; }, .fn_decl, @@ -1145,10 +1212,7 @@ fn getInstData(t: *Transform, index: Inst.Index) *EncodedData { } fn addScratch(t: *Transform, comptime T: type, value: T) !void { - try t.scratch.append(t.gpa, switch (T) { - Inst.Ref, Inst.Index => @intFromEnum(value), - else => @compileError("addScratch not implemented for " ++ @typeName(T)), - }); + try t.scratch.append(t.gpa, @intFromEnum(value)); } fn scratchToRefList(t: *Transform, scratch_top: usize) !RefList { @@ -1273,4 +1337,7 @@ const optionalExtraIndex = OptionalExtraIndex.init; const InternPool = @import("InternPool.zig"); +const Report = @import("Report.zig"); +const Comp = @import("Comp.zig"); + const tracer = @import("tracer"); diff --git a/src/compiler/Value.zig b/src/compiler/Value.zig index 86c9dfc091c7cf2ae7322a8f253d0b25af593167..4882152d0b2e1bdca7c543a656bb52cd7aa16f68 100644 --- a/src/compiler/Value.zig +++ b/src/compiler/Value.zig @@ -32,7 +32,6 @@ pub fn asString(val: Value, ip: *InternPool) InternPool.String.WithLen { } pub fn @"type"(val: Value, ip: *InternPool) Value { - std.debug.print("typeOfValue {any}\n", .{val.toKey(ip)}); const v: InternPool.Index = switch (val.toKey(ip)) { .simple_type, .type_function => .type_type, diff --git a/src/compiler/main.zig b/src/compiler/main.zig index 289424463fdfa691861b11e24e54f2354bc36ee5..1b0925e7c6e567df4bc86738d56756a3e09f4c0b 100644 --- a/src/compiler/main.zig +++ b/src/compiler/main.zig @@ -3,11 +3,15 @@ const Tokenizer = @import("Tokenizer.zig"); const Parse = @import("Parse.zig"); const Transform = @import("Transform.zig"); const Comp = @import("Comp.zig"); - +const unicode_props = @import("unicode_props.zig"); const T = @import("tracer"); pub const tracer_impl = @field(T, "none"); +pub const Report = @import("Report.zig"); + pub fn main() !void { + try unicode_props.init(); + std.debug.print("\r\x1b[K", .{}); try T.init(); @@ -45,7 +49,14 @@ pub fn main() !void { defer alloc.free(exe_name); const file_path = maybe_file_path orelse { - std.debug.print("Usage: {s} \n", .{exe_name}); + std.debug.print("Usage: {s} [flags] \n", .{exe_name}); + std.debug.print( + // | 60 chars wrap point| + \\ + \\Flags: + \\ --dump-ir Dump intermediate representations. + \\ + , .{}); std.process.exit(1); }; @@ -54,7 +65,16 @@ pub fn main() !void { defer comp.deinit(); const main_file = try comp.getFile(file_path); - const teg = try main_file.getTeg(comp.gpa); + var fail_report: Report = undefined; + const teg = main_file.getTeg(&comp, &fail_report) catch |err| switch (err) { + error.TransformFail => { + std.debug.print("Transform failed:\n", .{}); + try fail_report.dumpToStdErr(&comp); + std.process.exit(1); + }, + else => return err, + }; + { const stderr = std.io.getStdErr().writer(); try teg.dumpToWriter(stderr); diff --git a/src/compiler/util.zig b/src/compiler/util.zig index 2896fe9d2c8b08afd599693f52eb05f50597e4b4..a7210196e0d788228f85ee454e1392dd4dc882fd 100644 --- a/src/compiler/util.zig +++ b/src/compiler/util.zig @@ -7,9 +7,9 @@ pub fn detectTTYFromWriter(writer: anytype) std.io.tty.Config { return std.io.tty.detectConfig(writer.context); } if (@TypeOf(writer) == std.io.AnyWriter) { - const writeFn = comptime (std.fs.File{ .handle = undefined }) + const fileWriteFn = comptime (std.fs.File{ .handle = undefined }) .writer().any().writeFn; - if (writer.writeFn == writeFn) { + if (writer.writeFn == fileWriteFn) { const file: *const std.fs.File = @alignCast(@ptrCast(writer.context)); return std.io.tty.detectConfig(file.*); }