From 74ce0c381324aeb3b73bb9928bcf433ee7e0e687 Mon Sep 17 00:00:00 2001 From: dave caruso Date: Sat, 25 May 2024 22:45:31 -0700 Subject: [PATCH] implement some more checks --- CHANGELOG.md | 11 +- README.md | 7 +- demo.ix | 1 - notes | 19 --- src/compiler/Air.Dump.zig | 2 +- src/compiler/Ast.zig | 85 -------------- src/compiler/Comp.zig | 4 +- src/compiler/InternPool.zig | 74 ++++++++++-- src/compiler/Parse.zig | 41 ++++++- src/compiler/Report.zig | 98 ++++++++++++---- src/compiler/Sema.zig | 225 +++++++++++++++++++----------------- src/compiler/Transform.zig | 30 +++-- src/compiler/main.zig | 115 +----------------- 13 files changed, 344 insertions(+), 368 deletions(-) delete mode 100644 demo.ix delete mode 100644 notes diff --git a/CHANGELOG.md b/CHANGELOG.md index 607b137ce9acc3d27db45f0d4eb15b8ea24a5e05..152375646f0bf0faf1ca457a13467471a7f8bd49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,14 @@ ## 0.1.2 +- feat: semantic analysis can return formatted errors to the user +- feat: implement basic type coersions +- feat: type-check call parameters +- feat: type-check function return types - feat: parsing of assignments within expressions is disallowed now -- feat: debug builds have `--report-trace` to print stack traces +- feat: limit parsing to 4096 nested expressions / modules +- feat: added error for return outside of function +- feat: debug builds have `--report-trace` to print a stack trace per error +- fix: function arguments were not validated - fix: unused warnings now properly work - fix: empty files and functions no longer crash @@ -16,4 +23,4 @@ ## 0.1.0 -initial demo of compiler pipeline in zig. +initial demo of compiler pipeline diff --git a/README.md b/README.md index 39d6ece5b9759de894be7dd1ecc16e5947beb533..0f0fd74ed9af3ab73dd4b7d085668e4fd7ffc320 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Compiler +# New Language Project This language is a mix of features from JavaScript, Rust, and Zig. Some notable features carried over from each include: @@ -8,7 +8,7 @@ This language is a mix of features from JavaScript, Rust, and Zig. Some notable ## Standard Library -Currently, there is no standard library. One will be added later. +Currently, there is no standard library. One will be introduced later. ## Hello World @@ -77,3 +77,6 @@ var number = 0; // `number` is a `float`, but not explicit var number: int = 0; // `number` is `int` var number: float = 0; // `number` is `float` ``` + +## Functions + diff --git a/demo.ix b/demo.ix deleted file mode 100644 index 64d858c99d2f2b67f7a3e88fe8d826952afd76bd..0000000000000000000000000000000000000000 --- a/demo.ix +++ /dev/null @@ -1 +0,0 @@ -let x: int = 1.5; diff --git a/notes b/notes deleted file mode 100644 index 1f212929e6c4a49a2518e629a4ccb8af102c76f3..0000000000000000000000000000000000000000 --- a/notes +++ /dev/null @@ -1,19 +0,0 @@ -0.1.2 -- teg: useless discard -- teg: dangling identifier -- teg: remove `let _ = ` error -- sema: .as instruction -- sema: binary op type resolution (float + num_literal -> float) -0.1.3 -- parse/teg/sema: implement the comptime keyword -- implement all the binary operators -- implement all the unary operators -- implement string escapes -- implement proper float printing -- js/link: add parenthesis when needed -0.1.4 -- parse: impl module, struct define, struct init, defer, and optionals -- teg: impl the above, except anonymous struct initializers -- sema: implement some built-in functions on floats - - float.intTruncate() to perform lossy int conversion - diff --git a/src/compiler/Air.Dump.zig b/src/compiler/Air.Dump.zig index 17c3fd4b34be4210cafcd410610599f4a196ec5d..51d7af7eaf89f4021e3893f0202526b803c38115 100644 --- a/src/compiler/Air.Dump.zig +++ b/src/compiler/Air.Dump.zig @@ -235,7 +235,7 @@ pub const State = struct { .func => |func| { try tty.setColor(w, .bright_black); try w.writeAll("<"); - try s.dumpValue(w, func.ty); + try s.dumpValue(w, func.ty.value()); try tty.setColor(w, .bright_black); try w.writeAll(", "); const decl_name = s.ip.declPtrConst(func.owner_decl).name.toSlice(s.ip); diff --git a/src/compiler/Ast.zig b/src/compiler/Ast.zig index dc677f430ddfd75fec86f31d6bf0bf5dc953bd49..f1eb29fcf62758e5e113d3a4e1573cf1b7d18c57 100644 --- a/src/compiler/Ast.zig +++ b/src/compiler/Ast.zig @@ -520,91 +520,6 @@ pub fn nodeToken(ast: Ast, node: NodeIndex) TokenIndex { return ast.nodes.items(.main_token)[node]; } -// pub fn operandNode(ast: Ast, node: NodeIndex, operand: OperandId) NodeIndex { -// if (operand == .main_node) return node; - -// switch (ast.nodeTag(node)) { -// .root => unreachable, -// .block_void => unreachable, -// .block_implicit_return => unreachable, -// .var_decl, -// => return switch (operand) { -// .type => ast.nodeData(node).var_decl.type.unwrap().?, -// .initializer => ast.nodeData(node).var_decl.init, -// else => unreachable, -// }, - -// .label, -// .group, -// .negate, -// .bit_not, -// .bool_not, -// .@"comptime", -// => return switch (operand) { -// .base => ast.nodeData(node).un.expr, -// else => unreachable, // Invalid unary operand -// }, - -// .add, -// .sub, -// .mul, -// .div, -// .rem, -// .pow, -// .@"and", -// .@"or", -// .bit_and, -// .bit_or, -// .bit_xor, -// .shl, -// .shr, -// .eq, -// .ne, -// .lt, -// .le, -// .gt, -// .ge, -// .assign, -// .add_assign, -// .sub_assign, -// .mul_assign, -// .div_assign, -// .rem_assign, -// .pow_assign, -// .and_assign, -// .or_assign, -// .bit_and_assign, -// .bit_or_assign, -// .bit_xor_assign, -// .shl_assign, -// .shr_assign, -// => { -// const bin = ast.nodeData(node).bin; -// return switch (operand) { -// .lhs => bin.lhs, -// .rhs => bin.rhs, -// else => unreachable, // Not a valid operand for binary node -// }; -// }, - -// .fn_decl, -// .param_decl, -// .id, -// .number_int, -// .number_float, -// .string, -// .multiline_string, -// .call, -// .builtin_call, -// .@"if", -// .field, -// .index, -// .@"return", -// .@"break", -// => |tag| std.debug.panic("TODO: operandNode .{s}", .{@tagName(tag)}), -// } -// } - pub fn fullFnDecl(ast: Ast, node: NodeIndex) full.FnDecl { const tok_tags = ast.tokens.items(.tag); diff --git a/src/compiler/Comp.zig b/src/compiler/Comp.zig index 3f8566f54f4cc6278851fc637f25af3b3260b578..9c9791273104a5940a38ae8a40328ba6785112a9 100644 --- a/src/compiler/Comp.zig +++ b/src/compiler/Comp.zig @@ -122,7 +122,7 @@ pub const File = struct { return loaded_source; } - const GetAstError = GetSourceError || Parse.Error; + pub const GetAstError = GetSourceError || Parse.Error; pub fn getAst(file: *File, comp: *Comp) GetAstError!Ast { if (file.ast) |ast| { @@ -155,7 +155,7 @@ pub const File = struct { return ast; } - const GetTegError = GetAstError || Transform.Error; + pub const GetTegError = GetAstError || Transform.Error; pub fn getTeg(file: *File, comp: *Comp, fail_report: *Report) GetTegError!Teg { if (file.teg) |teg| { diff --git a/src/compiler/InternPool.zig b/src/compiler/InternPool.zig index 58bb70294f3bc73cb0a188fb79d7f7654970a846..cac26a5554ec06c64b09c90e9d1c1c4e0c55c321 100644 --- a/src/compiler/InternPool.zig +++ b/src/compiler/InternPool.zig @@ -137,7 +137,7 @@ pub const Tag = enum(u8) { pub const FuncDecl = struct { analysis: FuncAnalysis, owner_decl: DeclIndex, - ty: Value, + ty: Type, teg_body_inst: TrackedInst.Index, }; @@ -252,8 +252,7 @@ pub const Value = enum(u32) { .int32 => .int32, .float => .float, .string => .string, - - .func => @panic("TODO"), + .func => value.toKey(ip).func.ty, }; } @@ -274,6 +273,13 @@ pub const Value = enum(u32) { }; } + pub fn toTypeUnsafe(value: Value, proof_ip: *const InternPool) Type { + if (std.debug.runtime_safety) { + return value.toType(proof_ip).?; + } + return @enumFromInt(@intFromEnum(value)); + } + pub fn asString(val: Value, ip: *const InternPool) InternPool.String.WithLen { return val.valueData(ip, .string).get(ip); } @@ -298,6 +304,12 @@ pub const Type = enum(u32) { void = @intFromEnum(Value.void), _, + // Restricted version of `InternPool.Tag` + pub const Tag = enum(u32) { + type_function = @intFromEnum(InternPool.Tag.type_function), + simple_type = @intFromEnum(InternPool.Tag.simple_type), + }; + /// An array of `Index` existing within the `extra` array. /// This type exists to provide a struct with lifetime that is /// not invalidated when items are added to the `InternPool`. @@ -314,9 +326,59 @@ pub const Type = enum(u32) { return @enumFromInt(@intFromEnum(ty)); } + pub fn tag(ty: Type, ip: *const InternPool) Type.Tag { + return @enumFromInt(@intFromEnum(ty.value().valueTag(ip))); + } + + pub fn toFunctionPrototype(ty: Type, ip: *const InternPool) ?Key.TypeFunction { + if (ty.tag(ip) != .type_function) return null; + return ip.indexToKey(ty.value()).type_function; + } + + pub fn displayName(ty: Type, ip: *const InternPool) std.fmt.Formatter(formatDisplayName) { + return .{ .data = .{ .type = ty, .ip = ip } }; + } + pub fn dump(ty: Type, ip: *const InternPool) std.fmt.Formatter(Value.formatValue) { return ty.value().dump(ip); } + + fn formatDisplayName( + ctx: struct { type: Type, ip: *const InternPool }, + comptime _: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, + ) !void { + const ty = ctx.type; + const ip = ctx.ip; + + switch (ty) { + .@"opaque" => return try writer.writeAll("opaque"), + .bool => return try writer.writeAll("bool"), + .int32 => return try writer.writeAll("int"), + .float => return try writer.writeAll("float"), + .string => return try writer.writeAll("string"), + .noreturn => return try writer.writeAll("noreturn"), + .type => return try writer.writeAll("type"), + .numeric_literal => return try writer.writeAll("numeric literal"), + .void => return try writer.writeAll("void"), + else => {}, + } + + switch (ty.tag(ip)) { + .simple_type => unreachable, // all simple types are already printed + .type_function => { + const type_function = ty.toFunctionPrototype(ip).?; + try writer.writeAll("fn("); + for (type_function.param_types, 0..) |param, i| { + if (i != 0) try writer.writeAll(", "); + try writer.print("{}", .{param.displayName(ip)}); + } + try writer.writeAll(") -> "); + try writer.print("{}", .{type_function.return_type.displayName(ip)}); + }, + } + } }; pub const static_keys = [_]Key{ @@ -386,10 +448,6 @@ pub const String = enum(u32) { pub fn tryToNullTerminatedString(string: WithLen, ip: *InternPool, gpa: Allocator) !NullTerminatedString { return string.string.tryToNullTerminatedString(string.len, ip, gpa); } - - pub fn toNullTerminatedString(string: WithLen, ip: *const InternPool) NullTerminatedString { - return string.string.tryToNullTerminatedString(string.len, ip); - } }; pub fn toSlice(string: String, len: u64, ip: *const InternPool) []const u8 { @@ -678,7 +736,7 @@ pub const Key = union(enum) { }; pub const Func = struct { - ty: Value, + ty: Type, owner_decl: DeclIndex, teg_body_inst: TrackedInst.Index, diff --git a/src/compiler/Parse.zig b/src/compiler/Parse.zig index 5a6c32d31a06ed3e609698ac40eb87d5081e4757..423adae31bfa917eb9cc976a3a43296e5b1300ab 100644 --- a/src/compiler/Parse.zig +++ b/src/compiler/Parse.zig @@ -12,6 +12,11 @@ scratch: ArrayListUnmanaged(NodeIndex) = .{}, ast_error: ?AstError.WithTrace = null, +depth: u32 = 0, + +/// Instead of implementing a non-recursive parser, the stack size is limited by this constant. +const depth_limit = 4096; + pub fn parse(gpa: std.mem.Allocator, source: [:0]const u8, ast_error: *AstError.WithTrace) Parse.Error!Ast { var err_byte: ByteOffset = 0; var tokens = Tokenizer.tokenize( @@ -129,6 +134,8 @@ pub const AstError = union(enum) { expected: ExpectedContext, got: Token, }, + /// Went past depth maximum + depth_limit: Token, pub fn toReport(err: AstError, comp: *Comp, file: *Comp.File, trace: Report.DebugStoredTrace) !Report { return (Report.Err{ .ast = err }).toReport(comp, file, trace); @@ -165,6 +172,9 @@ pub const AstError = union(enum) { data.got.tag.description(), }); }, + .depth_limit => |data| { + try ctx.msg(.{ .token_stored = data }, "syntax tree is nested too deeply (limit: {d})", .{depth_limit}); + }, } } @@ -200,6 +210,12 @@ pub fn parseRoot(p: *Parse) Error!void { } fn parseModuleMembers(p: *Parse) Error!SubList { + p.depth += 1; + defer p.depth -= 1; + if (p.depth > depth_limit) { + return p.fail(.{ .depth_limit = p.fullToken(p.pos) }); + } + const trace = t.trace(@src()); defer trace.end(); @@ -642,7 +658,24 @@ const Operator = enum(u8) { } pub fn isAssign(op: Operator) bool { - return @intFromEnum(op) >= @intFromEnum(Operator.assign); + return switch (op) { + .assign, + .add_assign, + .sub_assign, + .mul_assign, + .div_assign, + .rem_assign, + .pow_assign, + .and_assign, + .or_assign, + .bit_and_assign, + .bit_or_assign, + .bit_xor_assign, + .shl_assign, + .shr_assign, + => true, + else => false, + }; } }; @@ -650,6 +683,12 @@ fn parseExpression(p: *Parse, allow_assign_operators: bool) Error!NodeIndex { const trace = t.trace(@src()); defer trace.end(); + p.depth += 1; + defer p.depth -= 1; + if (p.depth > depth_limit) { + return p.fail(.{ .depth_limit = p.fullToken(p.pos) }); + } + assert(p.isExpressionStart()); // These unary operators are greedy, so handle them first. diff --git a/src/compiler/Report.zig b/src/compiler/Report.zig index be1fae9f3451aff8e0308420c889bbdd4d63873f..f0191aa5086807fae8458779e512965d82acfb1a 100644 --- a/src/compiler/Report.zig +++ b/src/compiler/Report.zig @@ -64,16 +64,6 @@ pub const Err = union(enum) { ); } }, - invalid_discard: struct { - node: NodeIndex, - - fn build(data: @This(), ctx: *MsgContext) !void { - try ctx.msgToken(ctx.ast.idToken(data.node), "local cannot be named \"_\"", .{}); - try ctx.msgAlone("to discard this value, remove '{s}'", .{ - ctx.ast.tokenSlice(ctx.ast.nodeToken(data.node)), - }); - } - }, duplicate_function: struct { node: NodeIndex, prev: NodeIndex, @@ -106,7 +96,7 @@ pub const Err = union(enum) { fn build(data: @This(), ctx: *MsgContext) !void { try ctx.msgNode( data.node, - "return cannot be used here", + "return cannot be used outside of a function body", .{}, ); } @@ -149,7 +139,34 @@ pub const Err = union(enum) { fn build(data: @This(), ctx: *MsgContext) !void { const node = try data.src.get(ctx); - try ctx.msgNode(node, "stinky", .{}); + try ctx.msgNode(node, "expected \"{s}\", got \"{s}\"", .{ + data.to.displayName(&ctx.comp.intern_pool), + data.from.displayName(&ctx.comp.intern_pool), + }); + } + }, + call_non_function: struct { + call: LazyNode, + ty: Type, + + fn build(data: @This(), ctx: *MsgContext) !void { + const node = try data.call.get(ctx); + try ctx.msgNode(node, "\"{s}\" is not callable", .{ + data.ty.displayName(&ctx.comp.intern_pool), + }); + } + }, + call_mismatch_args: struct { + call: LazyNode, + actual: u32, + expected: u32, + + fn build(data: @This(), ctx: *MsgContext) !void { + const node = try data.call.get(ctx); + try ctx.msgNode(node, "function expected {d} arguments, recieved {d}", .{ + data.expected, + data.actual, + }); } }, }; @@ -184,6 +201,20 @@ pub const Warn = union(enum) { try ctx.msgAlone("if this was intentional, discard it with \"_ = {s};\"", .{id}); } }, + invalid_discard: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgToken(ctx.ast.nodeToken(data.node), "\"let\" is not required for a discard", .{}); + } + }, + dangling_identifier: struct { + node: NodeIndex, + + fn build(data: @This(), ctx: *MsgContext) !void { + try ctx.msgToken(ctx.ast.nodeToken(data.node), "dangling identifier does nothing", .{}); + } + }, numeric_int_var_ambiguity: struct { node: LazyNode, @@ -243,7 +274,7 @@ pub const DebugStoredTrace = if (builtin.mode == .Debug) StoredTrace else struct { - pub fn capture() @This() { + pub fn capture(_: ?usize) @This() { return .{}; } @@ -289,7 +320,7 @@ pub fn deinit(report: Report, gpa: Allocator) void { } pub fn dumpToWriter(report: Report, comp: *Comp, writer: anytype, tty: std.io.tty.Config) !void { - try writer.print("{d} errors, {d} warnings\n", .{ report.errors.len, report.warnings.len }); + // 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); } @@ -653,14 +684,16 @@ pub const LazyNode = struct { const node_offset = teg.instNodeOffset(lazy.inst); - const base_node: NodeIndex = @intCast(node_offset - teg_decl.src_node); + const base_node: NodeIndex = @intCast(teg_decl.src_node + node_offset); if (lazy.operand == .main_node) return base_node; const data = ctx.ast.nodeData(base_node); + const node_tag = ctx.ast.nodeTag(base_node); + switch (teg.instTag(lazy.inst)) { - .as => switch (ctx.ast.nodeTag(base_node)) { + .as => switch (node_tag) { .var_decl => { switch (lazy.operand) { .rhs => return data.var_decl.init, @@ -669,6 +702,23 @@ pub const LazyNode = struct { }, else => {}, }, + .call => switch (node_tag) { + .call => switch (lazy.operand) { + .operand => return data.call.base, + else => |op| return ctx.ast.extraData(data.call.extra, Ast.Call) + .args.slice(ctx.ast)[op.getArgId()], + }, + else => {}, + }, + .@"break" => switch (node_tag) { + .@"return" => return data.@"return".expr.unwrap().?, + .@"break" => return data.@"break".value.unwrap().?, + .block_implicit_return => { + const statements = ctx.ast.nodeStatements(base_node); + return statements[statements.len - 1]; + }, + else => return base_node, + }, else => {}, } @@ -687,14 +737,22 @@ pub const Operand = enum(u32) { main_node = 0, /// Base of a call, field, or operand to a unary instruction operand = 1, + /// Return type of a function declaration or lambda node + return_type = 2, /// Left hand side of a binary instruction - lhs, + lhs = 4, /// Right hand side of a binary instruction - rhs, + rhs = 5, - /// An argument of a call node + /// One of: + /// - An argument of a .call node + /// - A parameter of a .fn_decl node pub fn argument(id: u32) Operand { - return @enumFromInt(id + 2); + return @enumFromInt(id + 5); + } + + fn getArgId(op: Operand) u32 { + return @intFromEnum(op) - 5; } }; diff --git a/src/compiler/Sema.zig b/src/compiler/Sema.zig index 70922158c23cdde5b9554f5cbe29fa8d709f8128..f23e3daf92670c25e204e3d58f630508d43ea234 100644 --- a/src/compiler/Sema.zig +++ b/src/compiler/Sema.zig @@ -23,14 +23,20 @@ report: Report.Builder = undefined, air_instructions: std.MultiArrayList(Air.Inst) = .{}, air_extra: dod.ExtraStore(.air, .@"var") = .{}, +temp_result_map: std.AutoArrayHashMapUnmanaged(Teg.Inst.Index, Type) = .{}, + pub const Error = error{ /// Analysis has failed, see error list AnalysisFail, - /// This analysis is waiting on other dependencies to finish - Interrupt, + // This analysis is waiting on other dependencies to finish + // Interrupt, + + // TODO: add handling of this + TooManyValues, + HasNulls, OutOfMemory, -}; +} || Comp.File.GetTegError; /// Instead of using a hash map for this, just pre-allocate a list long enough to hold everything. /// This is fine because all instructions are going to be adacent in a single declaration. @@ -104,6 +110,7 @@ pub fn deinit(s: *Sema) void { s.scratch.deinit(s.arena); s.air_extra.deinit(s.gpa); s.air_instructions.deinit(s.gpa); + s.temp_result_map.deinit(s.arena); } pub fn finalize(s: *Sema) !void { @@ -121,7 +128,7 @@ pub fn finalize(s: *Sema) !void { // --- analyze functions --- /// This is the entrypoint to semantic analysis: It all starts at a declaration. -pub fn analyzeDeclaration(s: *Sema) anyerror!Value { +pub fn analyzeDeclaration(s: *Sema) Error!Value { const t = tracer.trace(@src()); defer t.end(); @@ -137,27 +144,43 @@ pub fn analyzeDeclaration(s: *Sema) anyerror!Value { defer s.gpa.free(s.params); for (fn_decl.params.slice(&s.code), 0..) |param, i| { - s.params[i] = (try s.resolveRefComptime(param)).toType(s.ip).?; + s.params[i] = try s.resolveType( + s.inst_map.start.op(Report.Operand.argument(@intCast(i))), + param, + ); } - const return_type = (try s.resolveRefComptime(fn_decl.return_type)).toType(s.ip).?; + const return_type = try s.resolveType( + s.inst_map.start.op(.return_type), + fn_decl.return_type, + ); - const root_block = fn_decl.body.toIndex() orelse - @panic("TODO: please change this to Index. Ref is not allowed here anymore"); - - _ = if (s.code.instPayload(.block, root_block).len > 0) - try s.analyzeBlock(root_block) - else empty_block: { - var root = try s.addBlock(); - break :empty_block (try root.finish(s, s.air_instructions.len - 1)); - }; + const root_block = fn_decl.body; + switch (root_block.decode()) { + .value => |value| { + const coerced = try s.coerceValue( + s.inst_map.start.op(.operand), + value, + return_type, + ); + const block = try s.addBlock(); + _ = try s.addInstRef(.@"break", .{ + .block_ref = block.block_index, + .value = coerced.toAir(), + }); + }, + .index => |index| { + try s.temp_result_map.putNoClobber(s.arena, index, return_type); + _ = try s.analyzeBlock(index); + }, + } - const func_prototype = try s.ip.get(s.gpa, .{ + const func_prototype = (try s.ip.get(s.gpa, .{ .type_function = .{ .param_types = s.params, .return_type = return_type, }, - }); + })).toTypeUnsafe(s.ip); const value = try s.ip.get(s.gpa, .{ .func = .{ .ty = func_prototype, @@ -169,7 +192,7 @@ pub fn analyzeDeclaration(s: *Sema) anyerror!Value { return value; } -pub fn analyzeBlock(s: *Sema, in: Teg.Inst.Index) anyerror!Air.Inst.Ref { +pub fn analyzeBlock(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { const t = tracer.trace(@src()); defer t.end(); @@ -224,7 +247,7 @@ fn analyzeCode(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { return out; } -fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { +fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) Error!Air.Inst.Ref { switch (s.code.instTag(in)) { .string => { const string: Teg.SlicedString = s.code.instPayload(.string, in); @@ -236,16 +259,14 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { }, .js => { const operand_ref = s.code.instData(.js, in).operand; - const operand = try s.resolveRefComptime(operand_ref); - // const operand_str = try s.coerceValue(operand_ref.toIndex().?, operand, .string); + const operand = try s.resolveRefComptimeType(in.op(.operand), operand_ref, .string); return s.addInstRef(.js_global_ref, operand.asString(s.ip)); }, .field => { const field: Teg.Inst.Bin = s.code.instPayload(.field, in); const base = try s.resolveRefRuntime(field.lhs); - const rhs = try s.resolveRefComptime(field.rhs); - // try s.validateType(rhs.type(s.ip), .string_type); + const rhs = try s.resolveRefComptimeType(in.op(.rhs), field.rhs, .string); const string = rhs.asString(s.ip); const base_type = s.typeOfRef(base); @@ -331,66 +352,71 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { const brk: Teg.Inst.Break = s.code.instPayload(.@"break", in); const operand = try s.resolveRefRuntime(brk.operand); const block_ref = s.inst_map.get(brk.block).?.decode().index; // block ref is resolved by teg + + const coerced = if (s.temp_result_map.get(brk.block)) |ty| + try s.coerceRef(in.op(.operand), operand, ty) + else + operand; + return s.addInstRef(.@"break", .{ .block_ref = block_ref, - .value = operand, + .value = coerced, }); }, .call => { - @panic("TODO: re-implement .call"); - // const call: Teg.Inst.Call = s.code.instPayload(.call, in); - // const base = try s.resolveRefRuntime(call.base); - // const base_type = s.typeOfRef(base).toKey(s.ip); + const call: Teg.Inst.Call = s.code.instPayload(.call, in); + const base = try s.resolveRefRuntime(call.base); + const base_type = s.typeOfRef(base); - // const is_opaque = base_type == .simple_type and base_type.simple_type == .@"opaque"; + const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len); + defer s.arena.free(scratch); + if (base_type != .@"opaque") { + const proto = base_type.toFunctionPrototype(s.ip) orelse { + return s.fail(.call_non_function, .{ + .call = s.nodeLoc(in, .operand), + .ty = base_type, + }); + }; - // const scratch = try s.arena.alloc(Air.Inst.Ref, call.args.len); - // defer s.arena.free(scratch); - // if (!is_opaque) { - // if (base_type != .type_function) { - // std.debug.print("Attempt to call non-function {}", .{base_type}); - // return error.AnalysisFail; - // } + if (proto.param_types.len != call.args.len) { + // TODO: also attempt to coerce some of the params to reveal more errors. + return s.fail(.call_mismatch_args, .{ + .call = s.nodeLoc(in, .operand), + .actual = @intCast(call.args.len), + .expected = @intCast(proto.param_types.len), + }); + } - // const proto = base_type.type_function; - // if (proto.param_types.len != call.args.len) { - // std.debug.print("Incorrect args. Expect {d}, Got {d}", .{ - // proto.param_types.len, - // call.args.len, - // }); - // return error.AnalysisFail; - // } + // validate function args + for (proto.param_types, call.args.slice(&s.code), 0..) |param, arg, i| { + const ref = try s.resolveRefRuntime(arg); + const coerced = try s.coerceRef( + in.op(Report.Operand.argument(@intCast(i))), + ref, + param, + ); + scratch[i] = coerced; + } + } else { + // just copy the args in without checking + for (call.args.slice(&s.code), 0..) |arg, i| { + const ref = try s.resolveRefRuntime(arg); + scratch[i] = ref; + } + } - // // validate function args - // for (proto.param_types, call.args.slice(&s.code), 0..) |param, arg, i| { - // const ref = try s.resolveRefRuntime(arg); - // scratch[i] = ref; - // _ = param; - // // try s.validateType( - // // Value.fromInterned(s.typeOfRef(ref)), - // // param, - // // ); - // } - // } else { - // // just copy the args in without checking - // for (call.args.slice(&s.code), 0..) |arg, i| { - // const ref = try s.resolveRefRuntime(arg); - // scratch[i] = ref; - // } - // } + const start = s.air_extra.list.items.len; + try s.air_extra.list.appendSlice(s.gpa, @ptrCast(scratch)); - // const start = s.air_extra.list.items.len; - // try s.air_extra.list.appendSlice(s.gpa, @ptrCast(scratch)); - - // return s.addInstRef(.call, .{ - // .base = base, - // .call_data = try s.air_extra.append(s.gpa, Air.Call.Data, .{ - // .args = .{ - // .start = @intCast(start), - // .len = @intCast(scratch.len), - // }, - // }), - // }); + return s.addInstRef(.call, .{ + .base = base, + .call_data = try s.air_extra.append(s.gpa, Air.Call.Data, .{ + .args = .{ + .start = @intCast(start), + .len = @intCast(scratch.len), + }, + }), + }); }, .ensure_void => { const operand = try s.resolveRefRuntimeExtra(s.code.instData(.ensure_void, in).operand, false); @@ -488,7 +514,7 @@ fn analyzeCodeInner(s: *Sema, in: Teg.Inst.Index) !Air.Inst.Ref { }, .as => { const as: Teg.Inst.As = s.code.instPayload(.as, in); - const coerce_to = try s.resolveRefComptimeType(in.op(.lhs) ,as.type, .type); + const coerce_to = try s.resolveRefComptimeType(in.op(.lhs), as.type, .type); const operand = try s.resolveRefRuntime(as.operand); return try s.coerceRef(in.op(.rhs), operand, coerce_to.toType(s.ip).?); }, @@ -501,16 +527,14 @@ fn markInst(s: *Sema, inst: Air.Inst.Index) void { flags.uses = flags.uses.increment(); } -fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) anyerror!Air.Inst.Ref { +fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) Error!Air.Inst.Ref { const t = tracer.trace(@src()); defer t.end(); const scratch_top = s.scratch.items.len; defer s.scratch.shrinkRetainingCapacity(scratch_top); - std.debug.print("{d} {d}\n", .{ scratch_top, s.scratch.items.len }); const visited = try s.resolveRefRuntime(in); - std.debug.print("{d} {d}\n", .{ scratch_top, s.scratch.items.len }); if (scratch_top == s.scratch.items.len and visited.kind == .value) { return visited; @@ -530,7 +554,7 @@ fn analyzeRefToBlock(s: *Sema, in: Teg.Inst.Ref) anyerror!Air.Inst.Ref { return (try block.finish(s, scratch_top)).toRef(); } -fn resolveRefRuntimeExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) anyerror!Air.Inst.Ref { +fn resolveRefRuntimeExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) Error!Air.Inst.Ref { const index = ref.toIndex() orelse { return ref.toValue().?.toAir(); }; @@ -548,11 +572,11 @@ fn resolveRefRuntimeExtra(s: *Sema, ref: Teg.Inst.Ref, mark: bool) anyerror!Air. return s.analyzeCode(index); } -fn resolveRefRuntime(s: *Sema, ref: Teg.Inst.Ref) anyerror!Air.Inst.Ref { +fn resolveRefRuntime(s: *Sema, ref: Teg.Inst.Ref) Error!Air.Inst.Ref { return resolveRefRuntimeExtra(s, ref, true); } -fn resolveRefComptime(s: *Sema, ref: Teg.Inst.Ref) !Value { +fn resolveRefComptimeAny(s: *Sema, ref: Teg.Inst.Ref) !Value { const index = switch (ref.decode()) { .value => |value| return value, .index => |index| index, @@ -575,6 +599,15 @@ fn resolveRefComptime(s: *Sema, ref: Teg.Inst.Ref) !Value { } } +fn resolveRefComptimeType(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref, ty: Type) !Value { + const any = try s.resolveRefComptimeAny(ref); + return try s.coerceValue(src, any, ty); +} + +fn resolveType(s: *Sema, src: Teg.InstOp, ref: Teg.Inst.Ref) !Type { + return (try s.resolveRefComptimeType(src, ref, .type)).toTypeUnsafe(s.ip); +} + /// Coerce `value` runtime ref to `coerce_type`, and emit an error at `teg_ref`'s location fn coerceRef(s: *Sema, src: InstOp, value: Air.Inst.Ref, to: Type) !Air.Inst.Ref { const from = s.typeOfRef(value); @@ -628,30 +661,6 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value { return value; } switch (to) { - // .simple_type => |simple_type| switch (simple_type) { - // // .@"opaque", - // .int32 => { - // switch (value_type.toKey(s.ip)) { - // .simple_type => switch (simple_type) { - // // .int32 => return value, - // // .numeric_literal => { - // // const val = value.decode().value.toKey(s.ip).comptime_float; - // // }, - // else => |tag| std.debug.panic("TODO: coerceTo value_type simple_type .{s}", .{@tagName(tag)}), - // }, - // else => |tag| std.debug.panic("TODO: coerceTo value_type .{s}", .{@tagName(tag)}), - // } - // }, - // // .float, - // // .string, - // // .bool, - // // .noreturn, - // // .type, - // // .numeric_literal, - // else => |tag| std.debug.panic("TODO: coerceTo coerce_type .simple_type = .{s}", .{@tagName(tag)}), - // }, - // else => |tag| std.debug.panic("TODO: coerceTo coerce_type .{s}", .{@tagName(tag)}), - // } .@"opaque" => { // }, @@ -691,18 +700,18 @@ fn coerceValue(s: *Sema, src: InstOp, value: Value, to: Type) !Value { // }, .type => { - // + if (value == .void) return .void; }, .numeric_literal => {}, .void => { - // + if (value == .void) return .void; }, else => {}, } return s.coersionError(src, from, to); } -fn resolveDecl(s: *Sema, decl_index: Decl.Index) !Value { +fn resolveDecl(s: *Sema, decl_index: Decl.Index) Error!Value { const decl = s.ip.declPtr(decl_index); switch (decl.analysis) { .unreferenced => { @@ -945,7 +954,7 @@ fn coersionError(s: *Sema, src: InstOp, from: Type, to: Type) Error { } fn lazyNode(s: *const Sema, src: InstOp) Report.LazyNode { - return src.lazyNode( s.decl_index); + return src.lazyNode(s.decl_index); } const Sema = @This(); diff --git a/src/compiler/Transform.zig b/src/compiler/Transform.zig index 7101e891ec5186e60f6a805bd6977d68aff11a3b..3e054977f01cd3ab95fb9e589fe0f61a579e1c8d 100644 --- a/src/compiler/Transform.zig +++ b/src/compiler/Transform.zig @@ -396,7 +396,7 @@ fn transformFunction(t: *Transform, outer_decl: BlockContext, node: NodeIndex) A }); t.getInstData(func).* = EncodedData.encode(.declaration, .{ - .src_node = 0, + .src_node = outer_decl.nodeOffset(node), .extra_index = decl_extra, }); @@ -971,8 +971,8 @@ fn transformExpression(t: *Transform, decl: BlockContext, node: NodeIndex) Alloc const ret_node = t.ast.nodeData(node).@"return"; const ret_block_ptr = decl.return_target orelse { - // return error.InvalidReturn; - @panic("TODO"); + try t.report.err(.invalid_return, .{ .node = node }); + return .void; }; const ret_block = if (ret_block_ptr.*) |block| @@ -1061,9 +1061,18 @@ fn transformBlockStatement( const var_decl = t.ast.fullVarDecl(node); if (mem.eql(u8, var_decl.id, "_")) { - // TODO: relax this restriction into just creating a discard node - try t.report.err(.invalid_discard, .{ .node = node }); - return .void; + try t.report.warn(.invalid_discard, .{ .node = node }); + + const ref = try t.transformExpression(decl, var_decl.init) orelse + return null; + + return try t.addInstRef(.{ + .tag = .discard, + .data = EncodedData.encode(.discard, .{ + .src_node = decl.nodeOffset(node), + .operand = ref, + }), + }); } if (BuiltInIds.has(var_decl.id)) { @@ -1189,6 +1198,13 @@ fn transformBlockStatement( return null; if (!is_implicit_return_expression and result != .void) { + if (t.ast.nodeTag(node) == .id) { + try t.report.warn(.dangling_identifier, .{ + .node = node, + }); + return result; + } + return try t.addInstRef(.{ .tag = .ensure_void, .data = EncodedData.encode(.ensure_void, .{ @@ -1203,8 +1219,6 @@ fn transformBlockStatement( } } -// --- - fn addRootInst(t: *Transform, inst: Inst) !Inst.Index { try t.insts.append(t.gpa, inst); return @enumFromInt(t.insts.len - 1); diff --git a/src/compiler/main.zig b/src/compiler/main.zig index 318962c10147234617ff51b0f05674a04cfea948..ef8964f9cb66b4fdcee80059967ce6fcbb9b0a1d 100644 --- a/src/compiler/main.zig +++ b/src/compiler/main.zig @@ -131,7 +131,10 @@ pub fn main() !void { try teg.dumpToWriter(stderr); } - try comp.semaEntryPoint(main_file); + comp.semaEntryPoint(main_file) catch |err| switch (err) { + error.AnalysisFail => std.process.exit(1), + else => return err, + }; const output = std.io.getStdOut().writer(); var bufw = std.io.bufferedWriter(output); @@ -140,114 +143,4 @@ pub fn main() !void { try bufw.flush(); std.process.cleanExit(); - - // var error_pos: u32 = 0; - // var tokens = Tokenizer.tokenize(source, alloc.allocator(), &error_pos) catch |err| { - // var line: usize = 1; - // var byte: usize = 0; - // var line_data: []const u8 = undefined; - // while (true) { - // const b = std.mem.indexOfScalarPos(u8, source, byte, '\n') orelse { - // line_data = source[byte..]; - // break; - // }; - // if (b > error_pos) { - // line_data = source[byte..b]; - // break; - // } - // byte = b + 1; - // line += 1; - // } - // var col: usize = 1; - // var arrow_pos: usize = 0; - // while (byte < error_pos) { - // const b = source[byte]; - // std.debug.assert(b != '\n'); - // byte += std.unicode.utf8ByteSequenceLength(b) catch break; - // col += 1; - - // arrow_pos += 1; // TODO: grapheme cluster - // } - - // const stderr = std.io.getStdErr(); - // const config = std.io.tty.detectConfig(stderr); - // var buf = std.io.bufferedWriter(stderr.writer()); - // const writer = buf.writer(); - - // try config.setColor(writer, .red); - // try writer.writeAll("error"); - // try config.setColor(writer, .bright_black); - // try writer.writeAll(": "); - // try config.setColor(writer, .reset); - // switch (err) { - // error.OutOfMemory => try writer.writeAll("Out of Memory"), - // error.InvalidUtf8 => try writer.writeAll("Invalid UTF-8"), - // error.InvalidSyntax => try writer.writeAll("Syntax Error"), - // error.TrailingNumericSeparator => try writer.writeAll("Trailing Numeric Separator"), - // error.UnterminatedString => try writer.writeAll("Unterminated String"), - // error.InvalidEscapeSequence => try writer.writeAll("Invalid Escape Sequence"), - // error.InvalidEscapeCodepoint => try writer.writeAll("Invalid Escape Codepoint"), - // error.GitConflict => try writer.writeAll("Found Git Conflict"), - // } - // try writer.print(" at {d}:{d}\n ", .{ line, col }); - // try writer.writeAll(line_data); - // try writer.writeAll("\n "); - // try writer.writeByteNTimes(' ', col); - - // try config.setColor(writer, .bright_green); - // try writer.writeAll("^"); - // try config.setColor(writer, .reset); - // try writer.writeAll("\n"); - - // try buf.flush(); - // return err; - // }; - // defer tokens.deinit(alloc.allocator()); - - // // std.debug.print("tokens.len = {d}\n", .{tokens.len}); - // // std.debug.print("token list bytes = {d}\n", .{tokens.toMultiArrayList().allocatedBytes().len}); - // // std.debug.print("source bytes = {d}\n", .{source.len}); - // // for ( - // // tokens.items(.tag), - // // tokens.items(.offset), - // // ) |tag, offset| { - // // std.debug.print(".tag = {s} .offset = {d}\n", .{ - // // @tagName(tag), - // // offset, - // // }); - // // } - - // const ast = ast: { - // var p = Parse.init(alloc.allocator(), source, tokens); - // errdefer p.deinit(); - // try p.nodes.ensureUnusedCapacity(alloc.allocator(), 100); - // p.parseRoot() catch |err| { - // const tok = p.fullToken(p.pos); - - // std.debug.print("ast_error = {?}\n", .{p.ast_error}); - - // std.debug.print("tok: {s} - '{s}' - {d}\n", .{ - // @tagName(tok.tag), - // tok.slice(p.source), - // tok.offset, - // }); - - // return err; - // }; - // break :ast try p.finalize(tokens); - // }; - - // try ast.dumpToWriter(std.io.getStdOut().writer()); - - // const teg = try Transform.transform(ast, alloc.allocator()); - // std.debug.print("teg.insts.len = {d}\n", .{teg.insts.len}); - // std.debug.print("teg.string_bytes.len = {d}\n", .{teg.string_bytes.len}); - // std.debug.print("teg.extra.len = {d}\n", .{teg.extra.len}); - - // for (teg.insts.items(.tag), teg.insts.items(.data)) |tag, data| { - // std.debug.print("{s} - {}\n", .{ - // @tagName(tag), - // data, - // }); - // } } -- 2.54.0