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.*);
}