diff --git a/src/Ast.zig b/src/Ast.zig index f711479..0fbac5e 100644 --- a/src/Ast.zig +++ b/src/Ast.zig @@ -218,6 +218,7 @@ pub const Error = struct { panic, unexpected_token, unknown_identifier, + redefined_identifier, assignment_to_const, expected_newline, expected_expression, diff --git a/src/Ast/Render.zig b/src/Ast/Render.zig index 6e337a2..6acdaff 100644 --- a/src/Ast/Render.zig +++ b/src/Ast/Render.zig @@ -162,6 +162,7 @@ fn renderError(r: *Render, writer: *std.Io.Writer, err: Ast.Error) !void { switch (err.tag) { .panic => try renderErrorf(r, writer, err, "parser panicked"), .unknown_identifier => try renderErrorf(r, writer, err, "unknown identifier"), + .redefined_identifier => try renderErrorf(r, writer, err, "redefined identifier"), .assignment_to_const => try renderErrorf(r, writer, err, "assignment to constant value"), .unexpected_token => try renderErrorf(r, writer, err, "unexpected token"), .expected_newline => try renderErrorf(r, writer, err, "expected newline"), diff --git a/src/AstGen.zig b/src/AstGen.zig index 3231576..9a1311d 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -7,244 +7,28 @@ const assert = std.debug.assert; const AstGen = @This(); gpa: std.mem.Allocator, +arena: std.mem.Allocator, tree: *const Ast, -story: *Story, +string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.default_max_load_percentage) = .empty, // TODO: Output string_bytes. // NOTE: String bytes have a 4GiB limit. string_bytes: std.ArrayListUnmanaged(u8) = .empty, -string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.default_max_load_percentage) = .empty, +instructions: std.ArrayListUnmanaged(u8) = .empty, label_stack: std.ArrayListUnmanaged(Label) = .empty, -jump_stack: std.ArrayListUnmanaged(Jump) = .empty, +fixup_stack: std.ArrayListUnmanaged(Fixup) = .empty, +constants: std.ArrayListUnmanaged(CodeUnit.Constant) = .empty, +knots: std.ArrayListUnmanaged(CodeUnit.Knot) = .empty, errors: std.ArrayListUnmanaged(Ast.Error) = .empty, -default_knot_name: [:0]const u8 = "@main@", pub const CheckError = error{ OutOfMemory, - CompilerBug, SemanticError, TooManyConstants, InvalidCharacter, NotImplemented, } || anyerror; -const Scope = struct { - parent: ?*Scope, - global: *AstGen, - chunk: *Chunk, - symbol_table: std.StringHashMapUnmanaged(Symbol), - jump_stack_top: usize, - label_stack_top: usize, - exit_label: usize, - - pub fn deinit(scope: *Scope) void { - const gpa = scope.global.gpa; - scope.symbol_table.deinit(gpa); - } - - pub fn fail( - scope: *Scope, - tag: Ast.Error.Tag, - node: *const Ast.Node, - ) error{ SemanticError, OutOfMemory } { - const gpa = scope.global.gpa; - const err: Ast.Error = .{ - .tag = tag, - .loc = .{ - .start = node.loc.start, - .end = node.loc.end, - }, - }; - - try scope.global.errors.append(gpa, err); - return error.SemanticError; - } - - pub fn lookupIdentifier(self: *Scope, node: *const Ast.Node) ?Symbol { - const token_bytes = self.global.getLexemeFromNode(node); - var current_scope: ?*Scope = self; - while (current_scope) |scope| : (current_scope = scope.parent) { - const symbol_table = &scope.symbol_table; - const result = symbol_table.get(token_bytes); - if (result) |symbol| return symbol; - } - return null; - } - - pub fn insertIdentifier( - scope: *Scope, - node: *const Ast.Node, - symbol: Symbol, - ) error{OutOfMemory}!void { - const gpa = scope.global.gpa; - const symbol_table = &scope.symbol_table; - const token_bytes = scope.global.getLexemeFromNode(node); - return symbol_table.put(gpa, token_bytes, symbol); - } - - pub fn makeSubBlock(parent_scope: *Scope) Scope { - const global = parent_scope.global; - const current_chunk = parent_scope.chunk; - - return .{ - .parent = parent_scope, - .global = global, - .chunk = current_chunk, - .symbol_table = .empty, - .jump_stack_top = global.jump_stack.items.len, - .label_stack_top = global.label_stack.items.len, - .exit_label = parent_scope.exit_label, - }; - } - - pub fn makeConstant(scope: *Scope, object: *Story.Object) error{OutOfMemory}!usize { - const gpa = scope.global.gpa; - const chunk = scope.chunk; - const const_id = chunk.const_pool.items.len; - - try chunk.const_pool.append(gpa, object); - return const_id; - } - - pub const IndexSlice = struct { - index: u32, - len: u32, - }; - - pub fn makeString(scope: *Scope, bytes: []const u8) error{OutOfMemory}!IndexSlice { - const global = scope.global; - const gpa = scope.global.gpa; - const str_index: u32 = @intCast(global.string_bytes.items.len); - const string_bytes = &global.string_bytes; - try global.string_bytes.appendSlice(gpa, bytes); - - const key: []const u8 = global.string_bytes.items[str_index..]; - const gop = try global.string_table.getOrPutContextAdapted(gpa, key, StringIndexAdapter{ - .bytes = string_bytes, - }, StringIndexContext{ - .bytes = string_bytes, - }); - if (gop.found_existing) { - string_bytes.shrinkRetainingCapacity(str_index); - return .{ .index = gop.key_ptr.*, .len = @intCast(key.len) }; - } else { - gop.key_ptr.* = str_index; - try string_bytes.append(gpa, 0); - return .{ .index = str_index, .len = @intCast(key.len) }; - } - } - - pub fn makeGlobal( - scope: *Scope, - node: *const Ast.Node, - symbol: Symbol, - ) error{OutOfMemory}!void { - const gpa = scope.global.gpa; - const global_data = scope.global; - const token_bytes = global_data.getLexemeFromNode(node); - try global_data.story.globals.put(gpa, token_bytes, null); - return scope.insertIdentifier(node, symbol); - } - - pub fn emitByte(scope: *Scope, byte: u8) error{OutOfMemory}!void { - return scope.chunk.bytes.append(scope.global.gpa, byte); - } - - pub fn emitSimpleInst(scope: *Scope, op: Story.Opcode) error{OutOfMemory}!void { - return scope.emitByte(@intFromEnum(op)); - } - - pub fn emitConstInst(scope: *Scope, op: Story.Opcode, arg: usize) !void { - if (arg >= 256) return error.TooManyConstants; - - try scope.emitSimpleInst(op); - try scope.emitByte(@intCast(arg)); - } - - pub fn emitJumpInst(scope: *Scope, op: Story.Opcode) error{OutOfMemory}!usize { - const bytes = &scope.chunk.bytes; - try scope.emitSimpleInst(op); - try scope.emitByte(0xff); - try scope.emitByte(0xff); - return bytes.items.len - 2; - } - - // Create a new code label. - pub fn makeLabel(scope: *Scope) error{OutOfMemory}!usize { - const gpa = scope.global.gpa; - const dummy_address = 0xffffffff; - const label_stack = &scope.global.label_stack; - const label_index = label_stack.items.len; - const label_data: Label = .{ .code_offset = dummy_address }; - - try label_stack.append(gpa, label_data); - return label_index; - } - - // Sets the code offset pointed to by a label. - pub fn setLabel(scope: *Scope, label_index: usize) void { - const chunk = scope.chunk; - const code_offset = chunk.bytes.items.len; - const label_stack = &scope.global.label_stack; - assert(label_index <= label_stack.items.len); - - const label_data = &label_stack.items[label_index]; - label_data.code_offset = code_offset; - } - - pub fn makeJump(scope: *Scope, jump: Jump) !usize { - const gpa = scope.global.gpa; - const jump_stack = &scope.global.jump_stack; - const jump_index = jump_stack.items.len; - try jump_stack.append(gpa, jump); - return jump_index; - } - - pub fn setExit(scope: *Scope, label_index: usize) void { - scope.exit_label = label_index; - } - - pub fn resolveLabels(scope: *Scope, start_index: usize, end_index: usize) !void { - assert(start_index <= end_index); - const jump_stack = &scope.global.jump_stack; - const label_stack = &scope.global.label_stack; - const chunk_bytes = &scope.chunk.bytes; - const jump_list = jump_stack.items[start_index..end_index]; - - for (jump_list) |jump| { - const label = label_stack.items[jump.label_index]; - const jump_offset: usize = switch (jump.mode) { - .relative => label.code_offset - jump.code_offset - 2, - .absolute => label.code_offset, - }; - if (jump_offset >= std.math.maxInt(u16)) { - std.debug.print("Too much code to jump over! {d}\n", .{jump_offset}); - return error.CompilerBug; - } - - assert(chunk_bytes.capacity >= jump.code_offset + 2); - chunk_bytes.items[jump.code_offset] = @intCast((jump_offset >> 8) & 0xff); - chunk_bytes.items[jump.code_offset + 1] = @intCast(jump_offset & 0xff); - } - } -}; - -pub const Symbol = union(enum) { - global_variable: struct { - is_constant: bool, - constant_slot: usize, - node: *const Ast.Node, - }, - local_variable: struct { - stack_slot: usize, - node: *const Ast.Node, - }, - parameter: struct { - constant_slot: usize, - stack_slot: usize, - }, -}; - -pub const Jump = struct { +pub const Fixup = struct { mode: enum { relative, absolute, @@ -257,184 +41,405 @@ pub const Label = struct { code_offset: usize, }; -// Code chunk to emitting bytes and constants to during code generation. -pub const Chunk = struct { - name: [:0]const u8, - arity: usize, - locals_count: usize, - const_pool: std.ArrayListUnmanaged(*Story.Object), - bytes: std.ArrayListUnmanaged(u8), +pub const IndexSlice = struct { + index: u32, + len: u32, +}; - pub fn finalize(chunk: *Chunk, scope: *Scope) !*Story.Object.ContentPath { - const gpa = scope.global.gpa; - const story = scope.global.story; - const jump_stack_top = scope.global.jump_stack.items.len; +const Scope = struct { + parent: ?*Scope, + astgen: *AstGen, + decls: std.AutoHashMapUnmanaged(IndexSlice, Decl), - try scope.resolveLabels(scope.jump_stack_top, jump_stack_top); - - const const_pool = try chunk.const_pool.toOwnedSlice(gpa); - const bytes = try chunk.bytes.toOwnedSlice(gpa); - const knot_name = try Story.Object.String.create(story, chunk.name); - const content_path = try Story.Object.ContentPath.create( - story, - knot_name, - chunk.arity, - chunk.locals_count, - const_pool, - bytes, - ); - return content_path; + pub fn deinit(self: *Scope) void { + const gpa = self.astgen.gpa; + self.decls.deinit(gpa); } - pub fn deinit(chunk: *Chunk, gpa: std.mem.Allocator) void { - chunk.const_pool.deinit(gpa); - chunk.bytes.deinit(gpa); - chunk.* = undefined; + pub fn insert(self: *Scope, ref: IndexSlice, symbol: Decl) !void { + const gpa = self.astgen.arena; + return self.decls.put(gpa, ref, symbol); + } + + pub fn lookup(self: *Scope, ref: IndexSlice) ?Decl { + var current_scope: ?*Scope = self; + while (current_scope) |scope| : (current_scope = scope.parent) { + const result = scope.decls.get(ref); + if (result) |symbol| return symbol; + } + return null; + } +}; + +const CodeGen = struct { + astgen: *AstGen, + exit_label: usize, + chunk_name_ref: IndexSlice, + chunk_stack_size: u32, + chunk_arity: u32, + constants_top: usize, + instructions_top: usize, + fixup_stack_top: usize, + label_stack_top: usize, + + pub fn deinit(_: *CodeGen) void {} + + pub fn finalize(self: *CodeGen) !void { + const gpa = self.astgen.gpa; + const knot: CodeUnit.Knot = .{ + .name_ref = self.chunk_name_ref, + .arity = self.chunk_arity, + .stack_size = self.chunk_stack_size, + .instructions = .{ + .index = @intCast(self.instructions_top), + .len = @intCast(self.astgen.instructions.items.len - self.instructions_top), + }, + .constants = .{ + .index = @intCast(self.constants_top), + .len = @intCast(self.astgen.constants.items.len - self.constants_top), + }, + }; + return self.astgen.knots.append(gpa, knot); + } + + fn fail( + self: *CodeGen, + tag: Ast.Error.Tag, + node: *const Ast.Node, + ) error{ SemanticError, OutOfMemory } { + return self.astgen.fail(tag, node); + } + + pub fn makeSub(self: *CodeGen) CodeGen { + return .{ + .astgen = self.astgen, + .constants_top = self.astgen.constants.items.len, + .instructions_top = self.astgen.instructions.items.len, + .fixup_stack_top = self.astgen.fixup_stack.items.len, + .label_stack_top = self.astgen.label_stack.items.len, + .exit_label = self.exit_label, + .chunk_name_ref = self.chunk_name_ref, + .chunk_stack_size = self.chunk_stack_size, + .chunk_arity = self.chunk_arity, + }; + } + + pub fn makeConstant( + self: *CodeGen, + constant: CodeUnit.Constant, + ) error{OutOfMemory}!usize { + const gpa = self.astgen.gpa; + const constant_index = self.astgen.constants.items.len; + + try self.astgen.constants.append(gpa, constant); + return constant_index; + } + + pub fn makeGlobal(self: *CodeGen, decl: Decl) error{OutOfMemory}!void { + const gpa = self.astgen.gpa; + const global_data = self.astgen; + try global_data.globals.append(gpa, .{ + .variable = .{ .name = decl.string_index }, + }); + } + + pub fn makeStackSlot(self: *CodeGen) !u32 { + const stack_index = self.chunk_stack_size; + self.chunk_stack_size += 1; + return stack_index; + } + + pub fn emitByte(self: *CodeGen, byte: u8) error{OutOfMemory}!void { + const gpa = self.astgen.gpa; + return self.astgen.instructions.append(gpa, byte); + } + + pub fn emitSimpleInst(self: *CodeGen, op: Story.Opcode) error{OutOfMemory}!void { + return self.emitByte(@intFromEnum(op)); + } + + pub fn emitConstInst(self: *CodeGen, op: Story.Opcode, arg: usize) !void { + if (arg >= std.math.maxInt(u8)) return error.TooManyConstants; + + try self.emitSimpleInst(op); + try self.emitByte(@intCast(arg)); + } + + pub fn emitJumpInst(self: *CodeGen, op: Story.Opcode) error{OutOfMemory}!usize { + try self.emitSimpleInst(op); + try self.emitByte(0xff); + try self.emitByte(0xff); + return self.astgen.instructions.items.len - 2; + } + + // Create a new code label. + pub fn makeLabel(self: *CodeGen) error{OutOfMemory}!usize { + const gpa = self.astgen.gpa; + const dummy_address = 0xffffffff; + const label_stack = &self.astgen.label_stack; + const label_index = label_stack.items.len; + const label_data: Label = .{ .code_offset = dummy_address }; + + try label_stack.append(gpa, label_data); + return label_index; + } + + // Sets the code offset pointed to by a label. + pub fn setLabel(self: *CodeGen, label_index: usize) void { + const code_offset = self.astgen.instructions.items.len; + const label_stack = &self.astgen.label_stack; + assert(label_index <= label_stack.items.len); + + const label_data = &label_stack.items[label_index]; + label_data.code_offset = code_offset; + } + + pub fn makeFixup(scope: *CodeGen, fixup: Fixup) !usize { + const gpa = scope.astgen.gpa; + const fixup_stack = &scope.astgen.fixup_stack; + const fixup_index = fixup_stack.items.len; + try fixup_stack.append(gpa, fixup); + return fixup_index; + } + + pub fn setExit(scope: *CodeGen, label_index: usize) void { + scope.exit_label = label_index; + } + + pub fn resolveLabels(scope: *CodeGen, start_index: usize, end_index: usize) !void { + assert(start_index <= end_index); + const fixup_stack = &scope.astgen.fixup_stack; + const label_stack = &scope.astgen.label_stack; + const chunk_bytes = &scope.chunk.bytes; + const fixup_list = fixup_stack.items[start_index..end_index]; + + for (fixup_list) |fixup| { + const label = label_stack.items[fixup.label_index]; + const fixup_offset: usize = switch (fixup.mode) { + .relative => label.code_offset - fixup.code_offset - 2, + .absolute => label.code_offset, + }; + if (fixup_offset >= std.math.maxInt(u16)) { + std.debug.print("Too much code to fixup over! {d}\n", .{fixup_offset}); + return error.CompilerBug; + } + + assert(chunk_bytes.capacity >= fixup.code_offset + 2); + chunk_bytes.items[fixup.code_offset] = @intCast((fixup_offset >> 8) & 0xff); + chunk_bytes.items[fixup.code_offset + 1] = @intCast(fixup_offset & 0xff); + } } }; pub fn deinit(astgen: *AstGen) void { const gpa = astgen.gpa; - astgen.string_bytes.deinit(gpa); astgen.string_table.deinit(gpa); + astgen.string_bytes.deinit(gpa); astgen.label_stack.deinit(gpa); - astgen.jump_stack.deinit(gpa); + astgen.fixup_stack.deinit(gpa); + astgen.instructions.deinit(gpa); + astgen.constants.deinit(gpa); + astgen.knots.deinit(gpa); astgen.errors.deinit(gpa); } -fn getLexemeFromNode(astgen: *const AstGen, node: *const Ast.Node) []const u8 { +fn fail( + self: *AstGen, + tag: Ast.Error.Tag, + source_node: *const Ast.Node, +) error{ SemanticError, OutOfMemory } { + const gpa = self.gpa; + const err: Ast.Error = .{ + .tag = tag, + .loc = .{ + .start = source_node.loc.start, + .end = source_node.loc.end, + }, + }; + + try self.errors.append(gpa, err); + return error.SemanticError; +} + +fn sliceFromNode(astgen: *const AstGen, node: *const Ast.Node) []const u8 { assert(node.loc.start <= node.loc.end); const source_bytes = astgen.tree.source; return source_bytes[node.loc.start..node.loc.end]; } -fn checkUnaryOp(scope: *Scope, node: *const Ast.Node, op: Story.Opcode) CheckError!void { - try checkExpr(scope, node.data.bin.lhs); - try scope.emitSimpleInst(op); +fn stringFromBytes(astgen: *AstGen, bytes: []const u8) error{OutOfMemory}!IndexSlice { + const gpa = astgen.gpa; + const str_index: u32 = @intCast(astgen.string_bytes.items.len); + const string_bytes = &astgen.string_bytes; + try string_bytes.appendSlice(gpa, bytes); + + const key: []const u8 = string_bytes.items[str_index..]; + const gop = try astgen.string_table.getOrPutContextAdapted(gpa, key, StringIndexAdapter{ + .bytes = string_bytes, + }, StringIndexContext{ + .bytes = string_bytes, + }); + if (gop.found_existing) { + string_bytes.shrinkRetainingCapacity(str_index); + return .{ .index = gop.key_ptr.*, .len = @intCast(key.len) }; + } else { + gop.key_ptr.* = str_index; + try string_bytes.append(gpa, 0); + return .{ .index = str_index, .len = @intCast(key.len) }; + } } -fn checkBinaryOp(scope: *Scope, node: *const Ast.Node, op: Story.Opcode) CheckError!void { +fn stringFromNode(astgen: *AstGen, node: *const Ast.Node) !IndexSlice { + const name_bytes = sliceFromNode(astgen, node); + assert(name_bytes.len > 0); + return astgen.stringFromBytes(name_bytes); +} + +fn createScope(astgen: *AstGen, parent_scope: ?*Scope) !*Scope { + const child_scope = try astgen.arena.create(Scope); + child_scope.* = .{ + .parent = parent_scope, + .astgen = astgen, + .decls = .empty, + }; + return child_scope; +} + +fn checkUnaryOp( + gen: *CodeGen, + scope: *Scope, + node: *const Ast.Node, + op: Story.Opcode, +) CheckError!void { + try checkExpr(gen, scope, node.data.bin.lhs); + try gen.emitSimpleInst(op); +} + +fn checkBinaryOp( + gen: *CodeGen, + scope: *Scope, + node: *const Ast.Node, + op: Story.Opcode, +) CheckError!void { const data = node.data.bin; assert(data.lhs != null and data.rhs != null); - try checkExpr(scope, data.lhs); - try checkExpr(scope, data.rhs); - try scope.emitSimpleInst(op); + try checkExpr(gen, scope, data.lhs); + try checkExpr(gen, scope, data.rhs); + try gen.emitSimpleInst(op); } -fn checkLogicalOp(scope: *Scope, node: *const Ast.Node, op: Story.Opcode) CheckError!void { +fn checkLogicalOp( + gen: *CodeGen, + scope: *Scope, + node: *const Ast.Node, + op: Story.Opcode, +) CheckError!void { const data = node.data.bin; assert(data.lhs != null and data.rhs != null); + try checkExpr(gen, scope, data.lhs); - try checkExpr(scope, data.lhs); - - const else_label = try scope.makeLabel(); - const jump_offset = try scope.emitJumpInst(op); - _ = try scope.makeJump(.{ + const else_label = try gen.makeLabel(); + const fixup_offset = try gen.emitJumpInst(op); + _ = try gen.makeFixup(.{ .mode = .relative, .label_index = else_label, - .code_offset = jump_offset, + .code_offset = fixup_offset, }); - try scope.emitSimpleInst(.pop); - const rhs_label = try scope.makeLabel(); - scope.setLabel(rhs_label); + try gen.emitSimpleInst(.pop); + const rhs_label = try gen.makeLabel(); + gen.setLabel(rhs_label); - try checkExpr(scope, data.rhs); - scope.setLabel(else_label); + try checkExpr(gen, scope, data.rhs); + gen.setLabel(else_label); } -fn checkTrueLiteral(scope: *Scope, _: *const Ast.Node) CheckError!void { - try scope.emitSimpleInst(.true); +fn checkTrueLiteral(gen: *CodeGen, _: *const Ast.Node) CheckError!void { + try gen.emitSimpleInst(.true); } -fn checkFalseLiteral(scope: *Scope, _: *const Ast.Node) CheckError!void { - try scope.emitSimpleInst(.false); +fn checkFalseLiteral(gen: *CodeGen, _: *const Ast.Node) CheckError!void { + try gen.emitSimpleInst(.false); } -fn checkNumberLiteral(scope: *Scope, node: *const Ast.Node) CheckError!void { - const lexeme = getLexemeFromNode(scope.global, node); +fn checkNumberLiteral(gen: *CodeGen, node: *const Ast.Node) CheckError!void { + const lexeme = sliceFromNode(gen.astgen, node); const number_value = try std.fmt.parseUnsigned(i64, lexeme, 10); - const number_object = try Story.Object.Number.create( - scope.global.story, - .{ .integer = number_value }, - ); - const constant_id = try scope.makeConstant(@ptrCast(number_object)); + const constant_id = try gen.makeConstant(.{ .number = number_value }); - try scope.emitConstInst(.load_const, constant_id); + try gen.emitConstInst(.load_const, constant_id); } -fn checkStringLiteral(scope: *Scope, node: *const Ast.Node) CheckError!void { - const string_bytes = getLexemeFromNode(scope.global, node); - _ = try scope.makeString(string_bytes); +fn checkStringLiteral(gen: *CodeGen, node: *const Ast.Node) CheckError!void { + const string_ref = try gen.astgen.stringFromNode(node); + const constant_id = try gen.makeConstant(.{ .string = string_ref }); - const string_object = try Story.Object.String.create(scope.global.story, string_bytes); - const constant_id = try scope.makeConstant(@ptrCast(string_object)); - - try scope.emitConstInst(.load_const, constant_id); + try gen.emitConstInst(.load_const, constant_id); } -fn checkStringExpr(scope: *Scope, node: *const Ast.Node) CheckError!void { +fn checkStringExpr(gen: *CodeGen, node: *const Ast.Node) CheckError!void { assert(node.data.bin.lhs != null); const expr_node = node.data.bin.lhs orelse return; - return checkStringLiteral(scope, expr_node); + return checkStringLiteral(gen, expr_node); } -fn checkIdentifier(scope: *Scope, node: *const Ast.Node) !void { - if (scope.lookupIdentifier(node)) |symbol| { - switch (symbol) { - .global_variable => |data| { - return scope.emitConstInst(.load_global, data.constant_slot); - }, - .local_variable => |data| { - return scope.emitConstInst(.load, data.stack_slot); - }, - .parameter => |data| { - return scope.emitConstInst(.load, data.stack_slot); - }, - } - } - return scope.fail(.unknown_identifier, node); +fn checkIdentifier(gen: *CodeGen, scope: *Scope, node: *const Ast.Node) CheckError!void { + const name_ref = try gen.astgen.stringFromNode(node); + + if (scope.lookup(name_ref)) |symbol| switch (symbol) { + .global => |data| { + return gen.emitConstInst(.load_global, data.constant_slot); + }, + .local => |data| { + return gen.emitConstInst(.load, data.stack_slot); + }, + .parameter => |data| { + return gen.emitConstInst(.load, data.stack_slot); + }, + else => unreachable, + }; + return gen.fail(.unknown_identifier, node); } -fn checkExpr(scope: *Scope, expr: ?*const Ast.Node) CheckError!void { +fn checkExpr(gen: *CodeGen, scope: *Scope, expr: ?*const Ast.Node) CheckError!void { const expr_node = expr orelse return; switch (expr_node.tag) { - .true_literal => try checkTrueLiteral(scope, expr_node), - .false_literal => try checkFalseLiteral(scope, expr_node), - .number_literal => try checkNumberLiteral(scope, expr_node), - .string_literal => try checkStringLiteral(scope, expr_node), - .string_expr => try checkStringExpr(scope, expr_node), - .identifier => try checkIdentifier(scope, expr_node), - .add_expr => try checkBinaryOp(scope, expr_node, .add), - .subtract_expr => try checkBinaryOp(scope, expr_node, .sub), - .multiply_expr => try checkBinaryOp(scope, expr_node, .mul), - .divide_expr => try checkBinaryOp(scope, expr_node, .div), - .mod_expr => try checkBinaryOp(scope, expr_node, .mod), - .negate_expr => try checkUnaryOp(scope, expr_node, .neg), - .logical_and_expr => try checkLogicalOp(scope, expr_node, .jmp_f), - .logical_or_expr => try checkLogicalOp(scope, expr_node, .jmp_t), - .logical_not_expr => try checkUnaryOp(scope, expr_node, .not), - .logical_equality_expr => try checkBinaryOp(scope, expr_node, .cmp_eq), + .true_literal => try checkTrueLiteral(gen, expr_node), + .false_literal => try checkFalseLiteral(gen, expr_node), + .number_literal => try checkNumberLiteral(gen, expr_node), + .string_literal => try checkStringLiteral(gen, expr_node), + .string_expr => try checkStringExpr(gen, expr_node), + .identifier => try checkIdentifier(gen, scope, expr_node), + .add_expr => try checkBinaryOp(gen, scope, expr_node, .add), + .subtract_expr => try checkBinaryOp(gen, scope, expr_node, .sub), + .multiply_expr => try checkBinaryOp(gen, scope, expr_node, .mul), + .divide_expr => try checkBinaryOp(gen, scope, expr_node, .div), + .mod_expr => try checkBinaryOp(gen, scope, expr_node, .mod), + .negate_expr => try checkUnaryOp(gen, scope, expr_node, .neg), + .logical_and_expr => try checkLogicalOp(gen, scope, expr_node, .jmp_f), + .logical_or_expr => try checkLogicalOp(gen, scope, expr_node, .jmp_t), + .logical_not_expr => try checkUnaryOp(gen, scope, expr_node, .not), + .logical_equality_expr => try checkBinaryOp(gen, scope, expr_node, .cmp_eq), .logical_inequality_expr => { - try scope.emitSimpleInst(.not); - try checkBinaryOp(scope, expr_node, .cmp_eq); + try gen.emitSimpleInst(.not); + try checkBinaryOp(gen, scope, expr_node, .cmp_eq); }, - .logical_greater_expr => try checkBinaryOp(scope, expr_node, .cmp_gt), - .logical_greater_or_equal_expr => try checkBinaryOp(scope, expr_node, .cmp_gte), - .logical_lesser_expr => try checkBinaryOp(scope, expr_node, .cmp_lt), - .logical_lesser_or_equal_expr => try checkBinaryOp(scope, expr_node, .cmp_lte), + .logical_greater_expr => try checkBinaryOp(gen, scope, expr_node, .cmp_gt), + .logical_greater_or_equal_expr => try checkBinaryOp(gen, scope, expr_node, .cmp_gte), + .logical_lesser_expr => try checkBinaryOp(gen, scope, expr_node, .cmp_lt), + .logical_lesser_or_equal_expr => try checkBinaryOp(gen, scope, expr_node, .cmp_lte), else => return error.NotImplemented, } } -fn checkExprStmt(scope: *Scope, stmt: *const Ast.Node) CheckError!void { +fn checkExprStmt(gen: *CodeGen, scope: *Scope, stmt: *const Ast.Node) CheckError!void { const expr_node = stmt.data.bin.lhs orelse return; - try checkExpr(scope, expr_node); - try scope.emitSimpleInst(.pop); + try checkExpr(gen, scope, expr_node); + try gen.emitSimpleInst(.pop); } -fn validateSwitchProngs(scope: *Scope, stmt: *const Ast.Node) CheckError!void { +fn validateSwitchProngs(gen: *CodeGen, stmt: *const Ast.Node) CheckError!void { var stmt_has_block: bool = false; var stmt_has_else: bool = false; const case_list = stmt.data.switch_stmt.cases; @@ -444,15 +449,15 @@ fn validateSwitchProngs(scope: *Scope, stmt: *const Ast.Node) CheckError!void { .block_stmt => stmt_has_block = true, .switch_case, .if_branch => { if (stmt_has_block) { - //return scope.fail(.expected_else, case_stmt); + //return gen.fail(.expected_else, case_stmt); } }, .else_branch => { if (case_stmt != last_prong) { - return scope.fail(.invalid_else_stmt, case_stmt); + return gen.fail(.invalid_else_stmt, case_stmt); } if (stmt_has_else) { - return scope.fail(.unexpected_else_stmt, case_stmt); + return gen.fail(.unexpected_else_stmt, case_stmt); } stmt_has_else = true; }, @@ -461,14 +466,11 @@ fn validateSwitchProngs(scope: *Scope, stmt: *const Ast.Node) CheckError!void { } } -fn checkIfStmt(parent_scope: *Scope, stmt: *const Ast.Node) CheckError!void { - var child_scope = parent_scope.makeSubBlock(); - defer child_scope.deinit(); - +fn checkIfStmt(gen: *CodeGen, parent_scope: *Scope, stmt: *const Ast.Node) CheckError!void { const case_list = stmt.data.switch_stmt.cases; const eval_expr = stmt.data.switch_stmt.condition_expr; if (eval_expr) |expr_node| { - try validateSwitchProngs(&child_scope, stmt); + try validateSwitchProngs(gen, stmt); const first_prong = case_list[0]; const last_prong = case_list[case_list.len - 1]; @@ -478,79 +480,81 @@ fn checkIfStmt(parent_scope: *Scope, stmt: *const Ast.Node) CheckError!void { else last_prong; - try checkExpr(&child_scope, expr_node); + var child_scope = try gen.astgen.createScope(parent_scope); + defer child_scope.deinit(); + try checkExpr(gen, child_scope, expr_node); - const else_label = try child_scope.makeLabel(); - const end_label = try child_scope.makeLabel(); - const then_br = try child_scope.emitJumpInst(.jmp_f); - _ = try child_scope.makeJump(.{ + const else_label = try gen.makeLabel(); + const end_label = try gen.makeLabel(); + const then_br = try gen.emitJumpInst(.jmp_f); + _ = try gen.makeFixup(.{ .mode = .relative, .label_index = else_label, .code_offset = then_br, }); - try child_scope.emitSimpleInst(.pop); - try checkBlockStmt(&child_scope, then_stmt); + try gen.emitSimpleInst(.pop); + try checkBlockStmt(gen, child_scope, then_stmt); - const else_br = try child_scope.emitJumpInst(.jmp); - _ = try child_scope.makeJump(.{ + const else_br = try gen.emitJumpInst(.jmp); + _ = try gen.makeFixup(.{ .mode = .relative, .label_index = end_label, .code_offset = else_br, }); - child_scope.setLabel(else_label); - try child_scope.emitSimpleInst(.pop); + gen.setLabel(else_label); + try gen.emitSimpleInst(.pop); if (else_stmt) |else_node| { const block_stmt = else_node.data.bin.rhs; - try checkBlockStmt(&child_scope, block_stmt); + try checkBlockStmt(gen, child_scope, block_stmt); } - child_scope.setLabel(end_label); + gen.setLabel(end_label); } else { - return child_scope.fail(.expected_expression, stmt); + return gen.fail(.expected_expression, stmt); } } fn checkMultiIfStmt( + gen: *CodeGen, parent_scope: *Scope, - stmt: *const Ast.Node, + stmt_node: *const Ast.Node, ) CheckError!void { - const gpa = parent_scope.global.gpa; - var child_scope = parent_scope.makeSubBlock(); - defer child_scope.deinit(); - - const exit_label = try child_scope.makeLabel(); - child_scope.setExit(exit_label); - - try validateSwitchProngs(&child_scope, stmt); - const case_list = stmt.data.switch_stmt.cases; - + const gpa = gen.astgen.gpa; + try validateSwitchProngs(gen, stmt_node); + const case_list = stmt_node.data.switch_stmt.cases; // NOTE: We're going to create an array of label indexes here, since we // may create additional labels while traversing nested expressions. var label_list: std.ArrayList(usize) = .empty; defer label_list.deinit(gpa); try label_list.ensureUnusedCapacity(gpa, case_list.len); + var child_scope = try gen.astgen.createScope(parent_scope); + defer child_scope.deinit(); + + const exit_label = try gen.makeLabel(); + gen.setExit(exit_label); + for (case_list) |case_stmt| { - const label_index = try child_scope.makeLabel(); + const label_index = try gen.makeLabel(); switch (case_stmt.tag) { .if_branch => { const lhs = case_stmt.data.bin.lhs orelse unreachable; - try checkExpr(&child_scope, lhs); + try checkExpr(gen, child_scope, lhs); - const jump_offset = try child_scope.emitJumpInst(.jmp_t); - _ = try child_scope.makeJump(.{ + const fixup_offset = try gen.emitJumpInst(.jmp_t); + _ = try gen.makeFixup(.{ .mode = .relative, .label_index = label_index, - .code_offset = jump_offset, + .code_offset = fixup_offset, }); - try child_scope.emitSimpleInst(.pop); + try gen.emitSimpleInst(.pop); }, .else_branch => { - const jump_offset = try child_scope.emitJumpInst(.jmp); - _ = try child_scope.makeJump(.{ + const fixup_offset = try gen.emitJumpInst(.jmp); + _ = try gen.makeFixup(.{ .mode = .relative, .label_index = label_index, - .code_offset = jump_offset, + .code_offset = fixup_offset, }); }, else => unreachable, @@ -560,38 +564,41 @@ fn checkMultiIfStmt( const body_stmt = case_stmt.data.bin.rhs; switch (case_stmt.tag) { .if_branch => { - child_scope.setLabel(label_index); - try child_scope.emitSimpleInst(.pop); + gen.setLabel(label_index); + try gen.emitSimpleInst(.pop); }, .else_branch => { - child_scope.setLabel(label_index); + gen.setLabel(label_index); }, else => unreachable, } - try checkBlockStmt(&child_scope, body_stmt); + try checkBlockStmt(gen, child_scope, body_stmt); - const jump_inst = try child_scope.emitJumpInst(.jmp); - _ = try child_scope.makeJump(.{ + const fixup_inst = try gen.emitJumpInst(.jmp); + _ = try gen.makeFixup(.{ .mode = .relative, - .label_index = child_scope.exit_label, - .code_offset = jump_inst, + .label_index = gen.exit_label, + .code_offset = fixup_inst, }); } - child_scope.setLabel(child_scope.exit_label); + gen.setLabel(gen.exit_label); } -fn checkSwitchStmt(parent_scope: *Scope, switch_stmt: *const Ast.Node) CheckError!void { - const gpa = parent_scope.global.gpa; - const current_chunk = parent_scope.chunk; - var child_scope = parent_scope.makeSubBlock(); +fn checkSwitchStmt( + gen: *CodeGen, + parent_scope: *Scope, + stmt_node: *const Ast.Node, +) CheckError!void { + const gpa = gen.astgen.gpa; + var child_scope = try gen.astgen.createScope(parent_scope); defer child_scope.deinit(); - const label_index = try child_scope.makeLabel(); - child_scope.setExit(label_index); + const label_index = try gen.makeLabel(); + gen.setExit(label_index); - const eval_expr = switch_stmt.data.switch_stmt.condition_expr; - const case_list = switch_stmt.data.switch_stmt.cases; + const eval_expr = stmt_node.data.switch_stmt.condition_expr; + const case_list = stmt_node.data.switch_stmt.cases; // NOTE: We're going to create an array of label indexes here, since we // may create additional labels while traversing nested expressions. @@ -599,15 +606,13 @@ fn checkSwitchStmt(parent_scope: *Scope, switch_stmt: *const Ast.Node) CheckErro defer label_list.deinit(gpa); try label_list.ensureUnusedCapacity(gpa, case_list.len); - const stack_slot = current_chunk.arity + current_chunk.locals_count; - current_chunk.locals_count += 1; - - try checkExpr(&child_scope, eval_expr); - try child_scope.emitConstInst(.store, stack_slot); - try child_scope.emitSimpleInst(.pop); + const stack_slot = try gen.makeStackSlot(); + try checkExpr(gen, child_scope, eval_expr); + try gen.emitConstInst(.store, stack_slot); + try gen.emitSimpleInst(.pop); for (case_list) |case_stmt| { - const case_label_index = try child_scope.makeLabel(); + const case_label_index = try gen.makeLabel(); label_list.appendAssumeCapacity(case_label_index); switch (case_stmt.tag) { @@ -615,117 +620,123 @@ fn checkSwitchStmt(parent_scope: *Scope, switch_stmt: *const Ast.Node) CheckErro const case_eval_expr = case_stmt.data.bin.lhs orelse unreachable; switch (case_eval_expr.tag) { .number_literal, .true_literal, .false_literal => {}, - else => { - return child_scope.fail(.invalid_switch_case, case_stmt); - }, + else => return gen.fail(.invalid_switch_case, case_stmt), } - try child_scope.emitConstInst(.load, stack_slot); - try checkExpr(&child_scope, case_eval_expr); - try child_scope.emitSimpleInst(.cmp_eq); + try gen.emitConstInst(.load, stack_slot); + try checkExpr(gen, child_scope, case_eval_expr); + try gen.emitSimpleInst(.cmp_eq); - const jump_offset = try child_scope.emitJumpInst(.jmp_t); - _ = try child_scope.makeJump(.{ + const fixup_offset = try gen.emitJumpInst(.jmp_t); + _ = try gen.makeFixup(.{ .mode = .relative, .label_index = case_label_index, - .code_offset = jump_offset, + .code_offset = fixup_offset, }); - try child_scope.emitSimpleInst(.pop); + try gen.emitSimpleInst(.pop); }, .else_branch => { - const jump_offset = try child_scope.emitJumpInst(.jmp); - _ = try child_scope.makeJump(.{ + const fixup_offset = try gen.emitJumpInst(.jmp); + _ = try gen.makeFixup(.{ .mode = .relative, .label_index = case_label_index, - .code_offset = jump_offset, + .code_offset = fixup_offset, }); }, else => unreachable, } } for (case_list, label_list.items) |case_stmt, case_label_index| { - child_scope.setLabel(case_label_index); + gen.setLabel(case_label_index); switch (case_stmt.tag) { .switch_case => { - try child_scope.emitSimpleInst(.pop); + try gen.emitSimpleInst(.pop); }, .else_branch => {}, else => unreachable, } const block_stmt = case_stmt.data.bin.rhs; - try checkBlockStmt(&child_scope, block_stmt); - const jump_offset = try child_scope.emitJumpInst(.jmp); - _ = try child_scope.makeJump(.{ + try checkBlockStmt(gen, child_scope, block_stmt); + const fixup_offset = try gen.emitJumpInst(.jmp); + _ = try gen.makeFixup(.{ .mode = .relative, - .label_index = child_scope.exit_label, - .code_offset = jump_offset, + .label_index = gen.exit_label, + .code_offset = fixup_offset, }); } - child_scope.setLabel(child_scope.exit_label); + gen.setLabel(gen.exit_label); } -fn checkInlineLogicExpr(scope: *Scope, expr: *const Ast.Node) CheckError!void { - assert(expr.data.bin.lhs != null); - return checkExpr(scope, expr.data.bin.lhs); +fn checkInlineLogicExpr( + gen: *CodeGen, + scope: *Scope, + expr_node: *const Ast.Node, +) CheckError!void { + const main_node = expr_node.data.bin.lhs; + assert(main_node != null); + return checkExpr(gen, scope, main_node); } -fn checkContentExpr(scope: *Scope, expr: *const Ast.Node) CheckError!void { - const node_list = expr.data.list.items orelse return; +fn checkContentExpr( + gen: *CodeGen, + scope: *Scope, + expr_node: *const Ast.Node, +) CheckError!void { + const node_list = expr_node.data.list.items orelse return; for (node_list) |child_node| { switch (child_node.tag) { .string_literal => { - try checkStringLiteral(scope, child_node); - try scope.emitSimpleInst(.stream_push); + try checkStringLiteral(gen, child_node); + try gen.emitSimpleInst(.stream_push); }, .inline_logic_expr => { - try checkInlineLogicExpr(scope, child_node); - try scope.emitSimpleInst(.stream_push); + try checkInlineLogicExpr(gen, scope, child_node); + try gen.emitSimpleInst(.stream_push); }, - .if_stmt => try checkIfStmt(scope, child_node), - .multi_if_stmt => try checkMultiIfStmt(scope, child_node), - .switch_stmt => try checkSwitchStmt(scope, child_node), + .if_stmt => try checkIfStmt(gen, scope, child_node), + .multi_if_stmt => try checkMultiIfStmt(gen, scope, child_node), + .switch_stmt => try checkSwitchStmt(gen, scope, child_node), else => return error.NotImplemented, } } } -fn checkContentStmt(scope: *Scope, stmt: *const Ast.Node) CheckError!void { - const expr_node = stmt.data.bin.lhs orelse return; - try checkContentExpr(scope, expr_node); - try scope.emitSimpleInst(.stream_flush); +fn checkContentStmt(gen: *CodeGen, scope: *Scope, stmt_node: *const Ast.Node) CheckError!void { + const expr_node = stmt_node.data.bin.lhs orelse return; + try checkContentExpr(gen, scope, expr_node); + try gen.emitSimpleInst(.stream_flush); } -fn checkAssignStmt(scope: *Scope, stmt: *const Ast.Node) CheckError!void { - const lhs = stmt.data.bin.lhs orelse return error.CompilerBug; - const rhs = stmt.data.bin.rhs orelse return error.CompilerBug; +fn checkAssignStmt(gen: *CodeGen, scope: *Scope, stmt_node: *const Ast.Node) CheckError!void { + const identifier_node = stmt_node.data.bin.lhs orelse unreachable; + const expr_node = stmt_node.data.bin.rhs orelse unreachable; + const name_ref = try gen.astgen.stringFromNode(identifier_node); - if (scope.lookupIdentifier(lhs)) |symbol| { - switch (symbol) { - .global_variable => |data| { - if (data.is_constant) { - return scope.fail(.assignment_to_const, lhs); - } - try checkExpr(scope, rhs); - try scope.emitConstInst(.store_global, data.constant_slot); - try scope.emitSimpleInst(.pop); - return; - }, - .local_variable => |data| { - try checkExpr(scope, rhs); - try scope.emitConstInst(.store, data.stack_slot); - try scope.emitSimpleInst(.pop); - return; - }, - else => unreachable, - } - } - return scope.fail(.unknown_identifier, lhs); + if (scope.lookup(name_ref)) |symbol| switch (symbol) { + .global => |data| { + if (data.is_constant) { + return gen.fail(.assignment_to_const, identifier_node); + } + try checkExpr(gen, scope, expr_node); + try gen.emitConstInst(.store_global, data.constant_slot); + try gen.emitSimpleInst(.pop); + return; + }, + .local => |data| { + try checkExpr(gen, scope, expr_node); + try gen.emitConstInst(.store, data.stack_slot); + try gen.emitSimpleInst(.pop); + return; + }, + else => unreachable, + }; + return gen.fail(.unknown_identifier, identifier_node); } -fn checkChoiceStmt(scope: *Scope, stmt_node: *const Ast.Node) CheckError!void { +fn checkChoiceStmt(gen: *CodeGen, scope: *Scope, stmt_node: *const Ast.Node) CheckError!void { const Choice = struct { label_index: usize, start_expression: ?*const Ast.Node, @@ -737,33 +748,32 @@ fn checkChoiceStmt(scope: *Scope, stmt_node: *const Ast.Node) CheckError!void { const branch_list = stmt_node.data.list.items orelse unreachable; assert(branch_list.len != 0); - const gpa = scope.global.gpa; + const gpa = gen.astgen.gpa; var choice_list: std.ArrayListUnmanaged(Choice) = .empty; defer choice_list.deinit(gpa); try choice_list.ensureUnusedCapacity(gpa, branch_list.len); for (branch_list) |branch_stmt| { assert(branch_stmt.tag == .choice_star_stmt or branch_stmt.tag == .choice_plus_stmt); - const branch_data = branch_stmt.data.bin; const branch_expr = branch_data.lhs orelse unreachable; const branch_expr_data = branch_expr.data.choice_expr; - const label_index = try scope.makeLabel(); + const label_index = try gen.makeLabel(); if (branch_expr_data.start_expr) |node| { - try checkStringLiteral(scope, node); - try scope.emitSimpleInst(.stream_push); + try checkStringLiteral(gen, node); + try gen.emitSimpleInst(.stream_push); } if (branch_expr_data.option_expr) |node| { - try checkStringLiteral(scope, node); - try scope.emitSimpleInst(.stream_push); + try checkStringLiteral(gen, node); + try gen.emitSimpleInst(.stream_push); } - const jump_offset = try scope.emitJumpInst(.br_push); - _ = try scope.makeJump(.{ + const fixup_offset = try gen.emitJumpInst(.br_push); + _ = try gen.makeFixup(.{ .mode = .absolute, .label_index = label_index, - .code_offset = jump_offset, + .code_offset = fixup_offset, }); choice_list.appendAssumeCapacity(.{ @@ -775,182 +785,364 @@ fn checkChoiceStmt(scope: *Scope, stmt_node: *const Ast.Node) CheckError!void { }); } - try scope.emitSimpleInst(.br_table); - try scope.emitSimpleInst(.br_select_index); - try scope.emitSimpleInst(.br_dispatch); + try gen.emitSimpleInst(.br_table); + try gen.emitSimpleInst(.br_select_index); + try gen.emitSimpleInst(.br_dispatch); for (choice_list.items) |choice| { - scope.setLabel(choice.label_index); + gen.setLabel(choice.label_index); if (choice.start_expression) |expr_node| { - try checkStringLiteral(scope, expr_node); - try scope.emitSimpleInst(.stream_push); + try checkStringLiteral(gen, expr_node); + try gen.emitSimpleInst(.stream_push); } if (choice.inner_expression) |expr_node| { - try checkStringLiteral(scope, expr_node); - try scope.emitSimpleInst(.stream_push); + try checkStringLiteral(gen, expr_node); + try gen.emitSimpleInst(.stream_push); } - try scope.emitSimpleInst(.stream_flush); + try gen.emitSimpleInst(.stream_flush); if (choice.block_stmt) |block| { - try checkBlockStmt(scope, block); + try checkBlockStmt(gen, scope, block); } else { - try scope.emitSimpleInst(.exit); + try gen.emitSimpleInst(.exit); } } } -fn checkVarDecl(scope: *Scope, decl_node: *const Ast.Node) !void { - const identifier_node = decl_node.data.bin.lhs orelse return error.CompilerBug; - const expr_node = decl_node.data.bin.rhs orelse return error.CompilerBug; - - try checkExpr(scope, expr_node); - switch (decl_node.tag) { +fn checkVarDecl(gen: *CodeGen, scope: *Scope, decl_node: *const Ast.Node) !void { + const identifier_node = decl_node.data.bin.lhs orelse unreachable; + const expr_node = decl_node.data.bin.rhs orelse unreachable; + const name_ref = try gen.astgen.stringFromNode(identifier_node); + const decl_symbol: Decl = blk: switch (decl_node.tag) { .temp_decl => { - const stack_slot = scope.chunk.arity + scope.chunk.locals_count; - const symbol: Symbol = .{ - .local_variable = .{ - .node = decl_node, + const stack_slot = try gen.makeStackSlot(); + break :blk .{ + .local = .{ + .decl_node = decl_node, .stack_slot = stack_slot, }, }; - scope.chunk.locals_count += 1; - - try scope.insertIdentifier(identifier_node, symbol); - try scope.emitConstInst(.store, stack_slot); }, .var_decl, .const_decl => |tag| { - const string_bytes = getLexemeFromNode(scope.global, identifier_node); - _ = try scope.makeString(string_bytes); - const string_object = try Story.Object.String.create(scope.global.story, string_bytes); - const constant_slot = try scope.makeConstant(@ptrCast(string_object)); - const symbol: Symbol = .{ - .global_variable = .{ + const constant_slot = try gen.makeConstant(.{ + .string = name_ref, + }); + break :blk .{ + .global = .{ + .decl_node = decl_node, .is_constant = tag == .const_decl, - .node = decl_node, .constant_slot = constant_slot, }, }; + }, + else => unreachable, + }; - try scope.makeGlobal(identifier_node, symbol); - try scope.emitConstInst(.store_global, constant_slot); + try scope.insert(name_ref, decl_symbol); + try checkExpr(gen, scope, expr_node); + + switch (decl_symbol) { + .local => |data| { + try gen.emitConstInst(.store, data.stack_slot); + }, + .global => |data| { + try gen.emitConstInst(.store_global, data.constant_slot); }, else => unreachable, } - - try scope.emitSimpleInst(.pop); + try gen.emitSimpleInst(.pop); } -fn checkStmt(scope: *Scope, stmt: *const Ast.Node) CheckError!void { - switch (stmt.tag) { - .var_decl => try checkVarDecl(scope, stmt), - .const_decl => try checkVarDecl(scope, stmt), - .temp_decl => try checkVarDecl(scope, stmt), - .assign_stmt => try checkAssignStmt(scope, stmt), - .content_stmt => try checkContentStmt(scope, stmt), - .choice_stmt => try checkChoiceStmt(scope, stmt), - .expr_stmt => try checkExprStmt(scope, stmt), +fn checkStmt(gen: *CodeGen, scope: *Scope, stmt_node: *const Ast.Node) CheckError!void { + switch (stmt_node.tag) { + .var_decl => try checkVarDecl(gen, scope, stmt_node), + .const_decl => try checkVarDecl(gen, scope, stmt_node), + .temp_decl => try checkVarDecl(gen, scope, stmt_node), + .assign_stmt => try checkAssignStmt(gen, scope, stmt_node), + .content_stmt => try checkContentStmt(gen, scope, stmt_node), + .choice_stmt => try checkChoiceStmt(gen, scope, stmt_node), + .expr_stmt => try checkExprStmt(gen, scope, stmt_node), else => return error.NotImplemented, } } -fn checkBlockStmt(parent_scope: *Scope, block_stmt: ?*const Ast.Node) CheckError!void { +fn checkBlockStmt( + gen: *CodeGen, + parent_scope: *Scope, + block_stmt: ?*const Ast.Node, +) CheckError!void { if (block_stmt) |stmt| { - var block_scope = parent_scope.makeSubBlock(); - defer block_scope.deinit(); - + const block_scope = try gen.astgen.createScope(parent_scope); const children = stmt.data.list.items orelse return; - for (children) |child_stmt| try checkStmt(&block_scope, child_stmt); + for (children) |child_stmt| try checkStmt(gen, block_scope, child_stmt); } else { - const jump_offset = try parent_scope.emitJumpInst(.jmp); - _ = try parent_scope.makeJump(.{ + const fixup_offset = try gen.emitJumpInst(.jmp); + _ = try gen.makeFixup(.{ .mode = .relative, - .label_index = parent_scope.exit_label, - .code_offset = jump_offset, + .label_index = gen.exit_label, + .code_offset = fixup_offset, }); } } -fn checkAnonymousKnot(parent_scope: *Scope, body: *const Ast.Node) CheckError!void { - const exit_label = try parent_scope.makeLabel(); - parent_scope.exit_label = exit_label; +fn checkDefaultBlock( + gen: *CodeGen, + parent_scope: *Scope, + body_node: *const Ast.Node, +) CheckError!void { + const name_ref = try gen.astgen.stringFromBytes("$__main__$"); + const exit_label = try gen.makeLabel(); + defer gen.setExit(exit_label); - try checkBlockStmt(parent_scope, body); - parent_scope.setLabel(exit_label); - try parent_scope.emitSimpleInst(.exit); + gen.chunk_name_ref = name_ref; + + const block_scope = try gen.astgen.createScope(parent_scope); + try checkBlockStmt(gen, block_scope, body_node); + gen.setLabel(exit_label); + try gen.emitSimpleInst(.exit); + try gen.finalize(); } -fn checkFile(astgen: *AstGen, file: *const Ast.Node) CheckError!void { - const gpa = astgen.gpa; - var main_chunk: Chunk = .{ - .name = astgen.default_knot_name, - .arity = 0, - .locals_count = 0, - .bytes = .empty, - .const_pool = .empty, - }; - defer main_chunk.deinit(gpa); +fn checkStitchDecl(_: *CodeGen, _: *Scope, _: *const Ast.Node) CheckError!void {} - var file_scope: Scope = .{ - .parent = null, - .global = astgen, - .chunk = &main_chunk, - .symbol_table = .empty, - .jump_stack_top = astgen.jump_stack.items.len, - .label_stack_top = astgen.label_stack.items.len, - .exit_label = 0, - }; - defer file_scope.deinit(); +fn checkFunctionDecl(_: *CodeGen, _: *Scope, _: *const Ast.Node) CheckError!void {} - _ = try file_scope.makeString(astgen.default_knot_name); - const children = file.data.list.items orelse return; - if (children.len == 0) return; +fn checkKnotDecl(gen: *CodeGen, scope: *Scope, decl_node: *const Ast.Node) CheckError!void { + const prototype_node = decl_node.data.knot_decl.prototype; + const nested_decls_list = decl_node.data.knot_decl.children orelse return; + const identifier_node = prototype_node.data.bin.lhs orelse unreachable; + const ident_ref = try gen.astgen.stringFromNode(identifier_node); + const knot_symbol = scope.lookup(ident_ref) orelse unreachable; + const knot_scope = knot_symbol.knot.decl_scope; - // TODO: intern paths + var block_gen = gen.makeSub(); + defer block_gen.deinit(); - const first_child = children[0]; + var start_index: usize = 0; + const first_child = nested_decls_list[0]; if (first_child.tag == .block_stmt) { - const chunk = file_scope.chunk; - try checkAnonymousKnot(&file_scope, first_child); - const content_path = try chunk.finalize(&file_scope); - try astgen.story.paths.append(gpa, @ptrCast(content_path)); + try checkBlockStmt(&block_gen, knot_scope, first_child); + if (nested_decls_list.len > 1) start_index += 1 else return; + } + for (nested_decls_list[start_index..]) |nested_decl_node| { + switch (decl_node.tag) { + .stitch_decl => try checkStitchDecl(gen, knot_scope, nested_decl_node), + .function_decl => try checkFunctionDecl(gen, knot_scope, nested_decl_node), + else => unreachable, + } } } -fn dumpStringsWithHex(astgen: *const AstGen) void { - var start: usize = 0; - const bytes = astgen.string_bytes.items; +fn checkFile(astgen: *AstGen, scope: *Scope, file_node: *const Ast.Node) CheckError!void { + const nested_decls_list = file_node.data.list.items orelse return; + if (nested_decls_list.len == 0) return; - while (start < bytes.len) { - const end = std.mem.indexOfScalarPos(u8, bytes, start, 0) orelse break; - const s = bytes[start..end]; + var gen: CodeGen = .{ + .astgen = astgen, + .exit_label = 0, + .chunk_name_ref = .{ .index = 0, .len = 0 }, + .chunk_stack_size = 0, + .chunk_arity = 0, + .instructions_top = astgen.instructions.items.len, + .constants_top = astgen.constants.items.len, + .fixup_stack_top = astgen.fixup_stack.items.len, + .label_stack_top = astgen.label_stack.items.len, + }; + defer gen.deinit(); - std.debug.print("[{d:04}] ", .{start}); - for (s) |b| std.debug.print("{x:02} ", .{b}); - std.debug.print("00: {s}\n", .{s}); - start = end + 1; + var start_index: usize = 0; + const first_child = nested_decls_list[0]; + if (first_child.tag == .block_stmt) { + try checkDefaultBlock(&gen, scope, first_child); + if (nested_decls_list.len > 1) start_index += 1 else return; } + for (nested_decls_list[start_index..]) |child_node| { + switch (child_node.tag) { + .knot_decl => try checkKnotDecl(&gen, scope, child_node), + .stitch_decl => try checkStitchDecl(&gen, scope, child_node), + .function_decl => try checkFunctionDecl(&gen, scope, child_node), + else => unreachable, + } + } +} + +fn insertDecl(astgen: *AstGen, parent_scope: *Scope, proto_node: *const Ast.Node) CheckError!void { + const identifier_node = proto_node.data.bin.lhs orelse unreachable; + const name_ref = try astgen.stringFromNode(identifier_node); + + if (parent_scope.lookup(name_ref)) |_| { + return astgen.fail(.redefined_identifier, proto_node); + } + + const decl_scope = try astgen.createScope(parent_scope); + const arity = if (proto_node.data.bin.rhs) |param_list_node| + param_list_node.data.list.items.?.len + else + 0; + + try parent_scope.insert(name_ref, .{ + .knot = .{ + .decl_node = proto_node, + .decl_scope = decl_scope, + .arity = arity, + }, + }); + if (proto_node.data.bin.rhs) |param_list_node| { + const param_list = param_list_node.data.list.items orelse unreachable; + for (param_list, 0..) |param_node, stack_index| { + const param_name_ref = try astgen.stringFromNode(param_node); + if (decl_scope.lookup(param_name_ref)) |_| { + return astgen.fail(.redefined_identifier, param_node); + } + + try decl_scope.insert(param_name_ref, .{ + .parameter = .{ + .decl_node = param_node, + .stack_slot = stack_index, + }, + }); + } + } +} + +pub const Decl = union(enum) { + knot: struct { + decl_node: *const Ast.Node, + arity: usize, + decl_scope: *Scope, + }, + global: struct { + decl_node: *const Ast.Node, + is_constant: bool, + constant_slot: usize, + }, + local: struct { + decl_node: *const Ast.Node, + stack_slot: usize, + }, + parameter: struct { + decl_node: *const Ast.Node, + stack_slot: usize, + }, +}; + +fn collectRootDecls( + astgen: *AstGen, + parent_scope: *Scope, + root_node: *const Ast.Node, +) CheckError!void { + const root_decl_list = root_node.data.list.items orelse return; + for (root_decl_list) |root_decl_node| switch (root_decl_node.tag) { + .knot_decl => { + const knot_proto_node = root_decl_node.data.bin.lhs orelse unreachable; + try insertDecl(astgen, parent_scope, knot_proto_node); + + const knot_scope = try astgen.createScope(parent_scope); + const knot_nest_node = root_decl_node.data.bin.rhs orelse unreachable; + const nested_decl_list = knot_nest_node.data.list.items; + if (nested_decl_list) |decl_list| { + for (decl_list) |decl_node| switch (decl_node.tag) { + .stitch_decl, .function_decl => { + const proto_node = decl_node.data.bin.lhs orelse unreachable; + try insertDecl(astgen, knot_scope, proto_node); + }, + .block_stmt => {}, + else => unreachable, + }; + } + }, + .stitch_decl, .function_decl => try insertDecl(astgen, parent_scope, root_decl_node), + .block_stmt => {}, + else => unreachable, + }; } /// Perform code generation via tree-walk. -pub fn generate(gpa: std.mem.Allocator, tree: *const Ast) !Story { - const root_node = tree.root orelse return error.CompilerBug; - var story: Story = .{ - .allocator = gpa, - .is_exited = false, - .can_advance = false, - }; - errdefer story.deinit(); +/// +/// Currently, two passes over the AST are performed; one to resolve forward +/// references and another to generate code. +pub fn generate(gpa: std.mem.Allocator, tree: *const Ast) !CodeUnit { + var arena_allocator: std.heap.ArenaAllocator = .init(gpa); + defer arena_allocator.deinit(); var astgen: AstGen = .{ .gpa = gpa, + .arena = arena_allocator.allocator(), .tree = tree, - .story = &story, }; defer astgen.deinit(); + // First entry is reserved for the empty string sentinel. try astgen.string_bytes.append(gpa, 0); - try checkFile(&astgen, root_node); - dumpStringsWithHex(&astgen); - return story; + + const file_scope = try astgen.createScope(null); + + const root_node = tree.root orelse unreachable; + try collectRootDecls(&astgen, file_scope, root_node); + try checkFile(&astgen, file_scope, root_node); + + return .{ + .string_bytes = try astgen.string_bytes.toOwnedSlice(gpa), + .constants = try astgen.constants.toOwnedSlice(gpa), + .instructions = try astgen.instructions.toOwnedSlice(gpa), + .knots = try astgen.knots.toOwnedSlice(gpa), + .errors = try astgen.errors.toOwnedSlice(gpa), + }; } + +pub const CodeUnit = struct { + string_bytes: []const u8, + // TODO: Constants COULD be interned as well. + constants: []const Constant, + instructions: []const u8, + knots: []const Knot, + errors: []const Ast.Error, + + pub const Knot = struct { + name_ref: IndexSlice, + constants: IndexSlice, + instructions: IndexSlice, + arity: usize, + stack_size: usize, + }; + + pub const Constant = union(enum) { + number: i64, + string: IndexSlice, + }; + + pub fn deinit(unit: CodeUnit, gpa: std.mem.Allocator) void { + gpa.free(unit.instructions); + gpa.free(unit.constants); + gpa.free(unit.knots); + gpa.free(unit.errors); + gpa.free(unit.string_bytes); + } + + pub fn dumpStringsWithHex(cu: *const CodeUnit) void { + const bytes = cu.string_bytes; + var start: usize = 0; + while (start < bytes.len) { + const end = std.mem.indexOfScalarPos(u8, bytes, start, 0) orelse break; + const s = bytes[start..end]; + + std.debug.print("[{d:04}] ", .{start}); + for (s) |b| std.debug.print("{x:02} ", .{b}); + std.debug.print("00: {s}\n", .{s}); + start = end + 1; + } + } + + pub fn resolveString(self: *const CodeUnit, ref: IndexSlice) []const u8 { + return self.string_bytes[ref.index..][0..ref.len]; + } + + pub fn resolveConstants(self: *const CodeUnit, ref: IndexSlice) []const Constant { + return self.constants[ref.index..][0..ref.len]; + } + + pub fn resolveInstructions(self: *const CodeUnit, ref: IndexSlice) []const u8 { + return self.instructions[ref.index..][0..ref.len]; + } +}; diff --git a/src/Story.zig b/src/Story.zig index 74cf674..4ed0fe7 100644 --- a/src/Story.zig +++ b/src/Story.zig @@ -497,11 +497,48 @@ pub fn loadFromString( return error.Invalid; } - var story = try AstGen.generate(gpa, &ast); + const comp_unit = try AstGen.generate(gpa, &ast); + defer comp_unit.deinit(gpa); + comp_unit.dumpStringsWithHex(); + + var story: Story = .{ + .allocator = gpa, + .can_advance = false, + .dump_writer = options.dump_writer, + }; errdefer story.deinit(); - try story.divert("@main@"); - story.dump_writer = options.dump_writer; + for (comp_unit.knots) |compiled_chunk| { + const chunk_name = comp_unit.resolveString(compiled_chunk.name_ref); + var constant_pool: std.ArrayList(*Object) = .empty; + try constant_pool.ensureUnusedCapacity(gpa, compiled_chunk.constants.len); + defer constant_pool.deinit(gpa); + + for (comp_unit.resolveConstants(compiled_chunk.constants)) |constant| { + switch (constant) { + .number => |value| { + const object: *Object.Number = try .create(&story, .{ .integer = value }); + constant_pool.appendAssumeCapacity(&object.base); + }, + .string => |ref| { + const bytes = comp_unit.resolveString(ref); + const object: *Object.String = try .create(&story, bytes); + constant_pool.appendAssumeCapacity(&object.base); + }, + } + } + + const runtime_chunk: *Object.ContentPath = try .create(&story, .{ + .name = try .create(&story, chunk_name), + .arity = @intCast(compiled_chunk.arity), + .locals_count = @intCast(compiled_chunk.stack_size - compiled_chunk.arity), + .const_pool = try constant_pool.toOwnedSlice(gpa), + .bytes = try gpa.dupe(u8, comp_unit.resolveInstructions(compiled_chunk.instructions)), + }); + try story.paths.append(gpa, &runtime_chunk.base); + } + + try story.divert("$__main__$"); story.can_advance = true; return story; } diff --git a/src/Story/object.zig b/src/Story/object.zig index b7c787e..0112979 100644 --- a/src/Story/object.zig +++ b/src/Story/object.zig @@ -262,17 +262,23 @@ pub const Object = struct { base: Object, name: *Object.String, arity: usize, + // TODO: Rename this to stack size. locals_count: usize, + // TODO: Rename this to constant_pool. const_pool: []*Object, bytes: []const u8, - pub fn create( - story: *Story, + pub const CreateOptions = struct { name: *Object.String, arity: usize, locals_count: usize, const_pool: []*Object, bytes: []const u8, + }; + + pub fn create( + story: *Story, + options: CreateOptions, ) error{OutOfMemory}!*ContentPath { const gpa = story.allocator; const alloc_len = @sizeOf(ContentPath); @@ -285,11 +291,11 @@ pub const Object = struct { .is_marked = false, .node = .{}, }, - .name = name, - .arity = arity, - .locals_count = locals_count, - .const_pool = const_pool, - .bytes = bytes, + .name = options.name, + .arity = options.arity, + .locals_count = options.locals_count, + .const_pool = options.const_pool, + .bytes = options.bytes, }; story.gc_objects.prepend(&object.base.node); diff --git a/testing/regression/syntax/multi-if-no-else.ink b/testing/regression/syntax/multi-if-no-else.ink new file mode 100644 index 0000000..dc50735 --- /dev/null +++ b/testing/regression/syntax/multi-if-no-else.ink @@ -0,0 +1,42 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: |--VarDecl +// CHECK-NEXT: |  |--Identifier `x` +// CHECK-NEXT: |  `--NumberLiteral `1` +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--MultiIfStmt +// CHECK-NEXT: |--IfBranch +// CHECK-NEXT: |  |--LogicalEqualityExpr +// CHECK-NEXT: |  |  |--Identifier `x` +// CHECK-NEXT: |  |  `--NumberLiteral `1` +// CHECK-NEXT: |  `--BlockStmt +// CHECK-NEXT: |   `--ContentStmt +// CHECK-NEXT: |   `--Content +// CHECK-NEXT: |   `--StringLiteral `One` +// CHECK-NEXT: |--IfBranch +// CHECK-NEXT: |  |--LogicalEqualityExpr +// CHECK-NEXT: |  |  |--Identifier `x` +// CHECK-NEXT: |  |  `--NumberLiteral `2` +// CHECK-NEXT: |  `--BlockStmt +// CHECK-NEXT: |   `--ContentStmt +// CHECK-NEXT: |   `--Content +// CHECK-NEXT: |   `--StringLiteral `Two` +// CHECK-NEXT: `--IfBranch +// CHECK-NEXT: |--LogicalEqualityExpr +// CHECK-NEXT: |  |--Identifier `x` +// CHECK-NEXT: |  `--NumberLiteral `3` +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--StringLiteral `Three` + +VAR x = 1 + +{ +- x == 1: One +- x == 2: Two +- x == 3: Three +} diff --git a/testing/regression/syntax/multi-if-with-else.ink b/testing/regression/syntax/multi-if-with-else.ink new file mode 100644 index 0000000..bf670f7 --- /dev/null +++ b/testing/regression/syntax/multi-if-with-else.ink @@ -0,0 +1,47 @@ +// RUN: %ink-compiler --stdin --compile-only --dump-ast < %s | FileCheck %s + +// CHECK: File "" +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: |--TempDecl +// CHECK-NEXT: |  |--Identifier `foo` +// CHECK-NEXT: |  `--NumberLiteral `3` +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--MultiIfStmt +// CHECK-NEXT: |--IfBranch +// CHECK-NEXT: |  |--LogicalEqualityExpr +// CHECK-NEXT: |  |  |--Identifier `foo` +// CHECK-NEXT: |  |  `--NumberLiteral `1` +// CHECK-NEXT: |  `--BlockStmt +// CHECK-NEXT: |   `--ContentStmt +// CHECK-NEXT: |   `--Content +// CHECK-NEXT: |   `--StringLiteral `One!` +// CHECK-NEXT: |--IfBranch +// CHECK-NEXT: |  |--LogicalEqualityExpr +// CHECK-NEXT: |  |  |--Identifier `foo` +// CHECK-NEXT: |  |  `--NumberLiteral `2` +// CHECK-NEXT: |  `--BlockStmt +// CHECK-NEXT: |   `--ContentStmt +// CHECK-NEXT: |   `--Content +// CHECK-NEXT: |   `--StringLiteral `Two!` +// CHECK-NEXT: |--IfBranch +// CHECK-NEXT: |  |--LogicalEqualityExpr +// CHECK-NEXT: |  |  |--Identifier `foo` +// CHECK-NEXT: |  |  `--NumberLiteral `3` +// CHECK-NEXT: |  `--BlockStmt +// CHECK-NEXT: |   `--ContentStmt +// CHECK-NEXT: |   `--Content +// CHECK-NEXT: |   `--StringLiteral `Three!` +// CHECK-NEXT: `--ElseBranch +// CHECK-NEXT: `--BlockStmt +// CHECK-NEXT: `--ContentStmt +// CHECK-NEXT: `--Content +// CHECK-NEXT: `--StringLiteral `Something else!` + +~ temp foo = 3 +{ + - foo == 1: One! + - foo == 2: Two! + - foo == 3: Three! + - else: Something else! +}