diff --git a/src/AstGen.zig b/src/AstGen.zig index 2171aea..1326f01 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -18,21 +18,8 @@ errors: std.ArrayListUnmanaged(Ast.Error) = .empty, pub const InnerError = error{ OutOfMemory, SemanticError, - TooManyConstants, InvalidCharacter, -} || anyerror; - -pub const Fixup = struct { - mode: enum { - relative, - absolute, - }, - label_index: usize, - code_offset: usize, -}; - -pub const Label = struct { - code_offset: usize, + Overflow, }; const Scope = struct { @@ -73,8 +60,20 @@ const GenIr = struct { instructions: *std.ArrayListUnmanaged(Ir.Inst.Index), instructions_top: usize, - pub fn unstack(gi: *GenIr) void { - gi.instructions.items.len = gi.instructions_top; + const unstacked_top = std.math.maxInt(usize); + + pub fn unstack(self: *GenIr) void { + if (self.instructions_top != unstacked_top) { + self.instructions.items.len = self.instructions_top; + self.instructions_top = unstacked_top; + } + } + + fn instructionsSlice(self: *const GenIr) []Ir.Inst.Index { + return if (self.instructions_top == unstacked_top) + &[0]Ir.Inst.Index{} + else + self.instructions.items[self.instructions_top..]; } fn fail( @@ -85,25 +84,14 @@ const GenIr = struct { return self.astgen.fail(tag, node); } - fn instructionsSlice(gi: *const GenIr) []Ir.Inst.Index { - return gi.instructions.items[gi.instructions_top..]; - } - pub fn makeSubBlock(self: *GenIr) GenIr { return .{ .astgen = self.astgen, .instructions = self.instructions, - .instructions_top = self.astgen.instructions.items.len, + .instructions_top = self.instructions.items.len, }; } - pub fn addGlobal(self: *GenIr, decl: Decl) error{OutOfMemory}!void { - const gpa = self.astgen.gpa; - try self.astgen.globals.append(gpa, .{ - .variable = .{ .name = decl.string_index }, - }); - } - pub fn add(gi: *GenIr, inst: Ir.Inst) !Ir.Inst.Index { const gpa = gi.astgen.gpa; try gi.instructions.ensureUnusedCapacity(gpa, 1); @@ -152,6 +140,15 @@ const GenIr = struct { }); } + pub fn addDeclRef(gi: *GenIr, decl_ref: Ir.NullTerminatedString) !Ir.Inst.Index { + return add(gi, .{ + .tag = .decl_ref, + .data = .{ .string = .{ + .start = decl_ref, + } }, + }); + } + pub fn makePayloadNode(gi: *GenIr, tag: Ir.Inst.Tag) !Ir.Inst.Index { const gpa = gi.astgen.gpa; const inst_index: Ir.Inst.Index = @enumFromInt(gi.astgen.instructions.items.len); @@ -164,41 +161,70 @@ const GenIr = struct { return inst_index; } - pub fn makeDeclNode(gi: *GenIr) !Ir.Inst.Index { - return makePayloadNode(gi, .decl_knot); + pub fn makeDeclaration(gi: *GenIr) !Ir.Inst.Index { + return makePayloadNode(gi, .declaration); } pub fn makeBlockInst(gi: *GenIr) !Ir.Inst.Index { return makePayloadNode(gi, .block); } - pub fn setDecl( - gi: *GenIr, - inst: Ir.Inst.Index, - name_ref: Ir.NullTerminatedString, - ) !void { + pub fn addKnot(gi: *GenIr) !Ir.Inst.Index { const astgen = gi.astgen; const gpa = astgen.gpa; const body = gi.instructionsSlice(); const extra_len = @typeInfo(Ir.Inst.Knot).@"struct".fields.len + body.len; try astgen.extra.ensureUnusedCapacity(gpa, extra_len); - const inst_data = &astgen.instructions.items[@intFromEnum(inst)].data; + const knot_node = try makePayloadNode(gi, .decl_knot); + const inst_data = &astgen.instructions.items[@intFromEnum(knot_node)].data; inst_data.payload.payload_index = astgen.addExtraAssumeCapacity( - Ir.Inst.Knot{ .name_ref = name_ref, .body_len = @intCast(body.len) }, + Ir.Inst.Knot{ .body_len = @intCast(body.len) }, ); for (body) |inst_index| { astgen.extra.appendAssumeCapacity(@intFromEnum(inst_index)); } - gi.unstack(); + return knot_node; + } + + pub fn addVar(gi: *GenIr) !Ir.Inst.Index { + const astgen = gi.astgen; + const gpa = astgen.gpa; + const new_index: Ir.Inst.Index = @enumFromInt(astgen.instructions.items.len); + + try astgen.instructions.ensureUnusedCapacity(gpa, 1); + try gi.instructions.ensureUnusedCapacity(gpa, 1); + + gi.astgen.instructions.appendAssumeCapacity(.{ + .tag = .decl_var, + .data = .{ + .payload = .{ .payload_index = undefined }, + }, + }); + + const body = gi.instructionsSlice(); + const extra_len = @typeInfo(Ir.Inst.Var).@"struct".fields.len + body.len; + try astgen.extra.ensureUnusedCapacity(gpa, extra_len); + + const inst_data = &astgen.instructions.items[@intFromEnum(new_index)].data; + inst_data.payload.payload_index = astgen.addExtraAssumeCapacity( + Ir.Inst.Var{ .body_len = @intCast(body.len) }, + ); + + for (body) |inst_index| { + astgen.extra.appendAssumeCapacity(@intFromEnum(inst_index)); + } + + gi.instructions.appendAssumeCapacity(new_index); + return new_index; } pub fn setBlockBody(gi: *GenIr, inst: Ir.Inst.Index) !void { const astgen = gi.astgen; const gpa = astgen.gpa; const body = gi.instructionsSlice(); - const extra_len = @typeInfo(Ir.Inst.Knot).@"struct".fields.len + body.len; + const extra_len = @typeInfo(Ir.Inst.Block).@"struct".fields.len + body.len; try astgen.extra.ensureUnusedCapacity(gpa, extra_len); const inst_data = &astgen.instructions.items[@intFromEnum(inst)].data; @@ -220,6 +246,27 @@ pub fn deinit(astgen: *AstGen) void { astgen.errors.deinit(gpa); } +fn setDeclaration( + decl_index: Ir.Inst.Index, + args: struct { + name: Ir.NullTerminatedString, + ref: Ir.Inst.Index, + body_gi: *GenIr, + }, +) !void { + const astgen = args.body_gi.astgen; + const gpa = astgen.gpa; + const extra_len = @typeInfo(Ir.Inst.Declaration).@"struct".fields.len; + try astgen.extra.ensureUnusedCapacity(gpa, extra_len); + + const inst_data = &astgen.instructions.items[@intFromEnum(decl_index)].data; + inst_data.payload.payload_index = astgen.addExtraAssumeCapacity( + Ir.Inst.Declaration{ .name = args.name, .value = args.ref }, + ); + + args.body_gi.unstack(); +} + fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 { const fields = std.meta.fields(@TypeOf(extra)); const extra_index: u32 = @intCast(astgen.extra.items.len); @@ -280,7 +327,7 @@ fn stringFromBytes(astgen: *AstGen, bytes: []const u8) error{OutOfMemory}!Ir.Nul }); if (gop.found_existing) { string_bytes.shrinkRetainingCapacity(str_index); - return @enumFromInt(str_index); + return @enumFromInt(gop.key_ptr.*); } else { gop.key_ptr.* = str_index; try string_bytes.append(gpa, 0); @@ -346,17 +393,11 @@ fn logicalOp( } fn trueLiteral(gi: *GenIr) InnerError!Ir.Inst.Index { - return gi.add(.{ - .tag = .true_literal, - .data = undefined, - }); + return gi.add(.{ .tag = .true_literal, .data = undefined }); } fn falseLiteral(gi: *GenIr) InnerError!Ir.Inst.Index { - return gi.add(.{ - .tag = .false_literal, - .data = undefined, - }); + return gi.add(.{ .tag = .false_literal, .data = undefined }); } fn numberLiteral(gen: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Index { @@ -382,10 +423,10 @@ fn stringExpr(gen: *GenIr, expr_node: *const Ast.Node) InnerError!Ir.Inst.Index return stringLiteral(gen, first_node); } -fn identifier(gen: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Index { - const name_ref = try gen.astgen.stringFromNode(node); - if (scope.lookup(name_ref)) |_| {} - return gen.fail(.unknown_identifier, node); +fn identifier(gen: *GenIr, _: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Index { + const name_str = try gen.astgen.stringFromNode(node); + return gen.addDeclRef(name_str); + //return gen.fail(.unknown_identifier, node); } fn expr(gen: *GenIr, scope: *Scope, optional_expr: ?*const Ast.Node) InnerError!Ir.Inst.Index { @@ -795,48 +836,33 @@ fn choiceStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError } } -fn varDecl(gen: *GenIr, 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 = try gen.makeStackSlot(); - break :blk .{ - .local = .{ - .decl_node = decl_node, - .stack_slot = stack_slot, - }, - }; - }, - .var_decl, .const_decl => |tag| { - const constant_slot = try gen.makeConstant(.{ - .string = name_ref, - }); - break :blk .{ - .global = .{ - .decl_node = decl_node, - .is_constant = tag == .const_decl, - .constant_slot = constant_slot, - }, - }; - }, - else => unreachable, - }; +fn varDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void { + const gpa = gi.astgen.gpa; + const identifier_node = decl_node.data.bin.lhs.?; + const expr_node = decl_node.data.bin.rhs.?; + const name_ref = try gi.astgen.stringFromNode(identifier_node); - try scope.insert(name_ref, decl_symbol); - try expr(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, + if (scope.lookup(name_ref)) |_| { + return gi.fail(.redefined_identifier, decl_node); } - try gen.emitSimpleInst(.pop); + + const decl_inst = try gi.makeDeclaration(); + try gi.instructions.append(gpa, decl_inst); + + var decl_block = gi.makeSubBlock(); + defer decl_block.unstack(); + + _ = try expr(&decl_block, scope, expr_node); + try scope.insert(name_ref, .{ + .decl_node = decl_node, + }); + + const var_inst = try decl_block.addVar(); + try setDeclaration(decl_inst, .{ + .name = name_ref, + .ref = var_inst, + .body_gi = &decl_block, + }); } fn blockInner(gi: *GenIr, parent_scope: *Scope, stmt_list: []*Ast.Node) !void { @@ -845,8 +871,8 @@ fn blockInner(gi: *GenIr, parent_scope: *Scope, stmt_list: []*Ast.Node) !void { for (stmt_list) |inner_node| { _ = switch (inner_node.tag) { - //.var_decl => try varDecl(gen, scope, inner_node), - //.const_decl => try varDecl(gen, scope, inner_node), + .var_decl => try varDecl(gi, &child_scope, inner_node), + .const_decl => try varDecl(gi, &child_scope, inner_node), //.temp_decl => try varDecl(gen, scope, inner_node), //.assign_stmt => try assignStmt(gen, scope, inner_node), .content_stmt => try contentStmt(gi, &child_scope, inner_node), @@ -884,7 +910,7 @@ fn defaultBlock( ) InnerError!void { const astgen = gi.astgen; const gpa = astgen.gpa; - const decl_inst = try gi.makeDeclNode(); + const decl_inst = try gi.makeDeclaration(); try gi.instructions.append(gpa, decl_inst); var decl_scope = gi.makeSubBlock(); @@ -894,9 +920,12 @@ fn defaultBlock( const block_stmts = body_node.data.list.items orelse unreachable; try blockInner(&decl_scope, scope, block_stmts); - std.debug.print("{any}\n", .{decl_scope}); - const name_ref = try decl_scope.astgen.stringFromBytes("$__main__$"); - try decl_scope.setDecl(decl_inst, name_ref); + const knot_inst = try decl_scope.addKnot(); + try setDeclaration(decl_inst, .{ + .name = try astgen.stringFromBytes("$__main__$"), + .ref = knot_inst, + .body_gi = &decl_scope, + }); } fn stitchDecl(_: *GenIr, _: *Scope, _: *const Ast.Node) InnerError!void {} @@ -932,18 +961,16 @@ fn knotDecl(gen: *GenIr, scope: *Scope, decl_node: *const Ast.Node) InnerError!v fn file(root_gi: *GenIr, scope: *Scope, file_node: *const Ast.Node) InnerError!void { const astgen = root_gi.astgen; const gpa = astgen.gpa; - const file_inst = try root_gi.makePayloadNode(.file); try root_gi.instructions.append(gpa, file_inst); + var start_index: usize = 0; var file_scope = root_gi.makeSubBlock(); defer file_scope.unstack(); // TODO: Make sure this is non-nullable. const nested_decls_list = file_node.data.list.items orelse return; if (nested_decls_list.len == 0) return; - var start_index: usize = 0; - const first_child = nested_decls_list[0]; if (first_child.tag == .block_stmt) { try defaultBlock(&file_scope, scope, first_child); @@ -975,7 +1002,7 @@ pub fn generate(gpa: std.mem.Allocator, tree: *const Ast) !Ir { }; defer astgen.deinit(); - // First entry is reserved for the empty string sentinel. + // First entry is reserved for Ir.NullTerminatedString.empty. try astgen.string_bytes.append(gpa, 0); var instructions: std.ArrayListUnmanaged(Ir.Inst.Index) = .empty; diff --git a/src/Ir.zig b/src/Ir.zig index 69cf5ff..2a41afb 100644 --- a/src/Ir.zig +++ b/src/Ir.zig @@ -18,7 +18,10 @@ pub const Inst = struct { pub const Tag = enum { file, + declaration, decl_knot, + decl_var, + decl_ref, block, add, sub, @@ -57,8 +60,16 @@ pub const Inst = struct { }, }; + pub const Declaration = struct { + name: NullTerminatedString, + value: Inst.Index, + }; + pub const Knot = struct { - name_ref: NullTerminatedString, + body_len: u32, + }; + + pub const Var = struct { body_len: u32, }; @@ -151,12 +162,22 @@ const Render = struct { try io_w.writeAll(")"); } - fn renderKnotInst(r: *Render, ir: Ir, inst: Inst) Error!void { + fn renderKnotDecl(r: *Render, ir: Ir, inst: Inst) Error!void { const io_w = r.writer; const extra = ir.extraData(Inst.Knot, inst.data.payload.payload_index); const body_slice = ir.bodySlice(extra.end, extra.data.body_len); - const knot_ident_str = ir.nullTerminatedString(extra.data.name_ref); - try io_w.print("{s}(name=\"{s}\",body=", .{ @tagName(inst.tag), knot_ident_str }); + + try io_w.print("{s}(body=", .{@tagName(inst.tag)}); + try renderBodyInner(r, ir, body_slice); + try io_w.writeAll(")"); + } + + fn renderVarDecl(r: *Render, ir: Ir, inst: Inst) Error!void { + const io_w = r.writer; + const extra = ir.extraData(Inst.Knot, inst.data.payload.payload_index); + const body_slice = ir.bodySlice(extra.end, extra.data.body_len); + + try io_w.print("{s}(body=", .{@tagName(inst.tag)}); try renderBodyInner(r, ir, body_slice); try io_w.writeAll(")"); } @@ -171,6 +192,21 @@ const Render = struct { return io_w.writeAll(")"); } + fn renderDeclaration(r: *Render, ir: Ir, inst: Inst) Error!void { + const io_w = r.writer; + const extra = ir.extraData(Inst.Declaration, inst.data.payload.payload_index); + const ident_str = ir.nullTerminatedString(extra.data.name); + + try io_w.print("{s}(name=\"{s}\", value={{\n", .{ @tagName(inst.tag), ident_str }); + { + const old_len = try r.prefix.pushChildPrefix(r.gpa); + defer r.prefix.restore(old_len); + try renderInst(r, ir, extra.data.value); + } + try r.prefix.writeIndent(io_w); + return io_w.writeAll(")"); + } + fn renderInst(r: *Render, ir: Ir, index: Inst.Index) Error!void { const io_w = r.writer; const inst_index: u32 = @intFromEnum(index); @@ -180,7 +216,13 @@ const Render = struct { try io_w.print("%{d} = ", .{inst_index}); switch (inst.tag) { .file => try r.renderFileInst(ir, inst), - .decl_knot => try r.renderKnotInst(ir, inst), + .declaration => try r.renderDeclaration(ir, inst), + .decl_knot => try r.renderKnotDecl(ir, inst), + .decl_var => try r.renderVarDecl(ir, inst), + .decl_ref => { + const str_bytes = inst.data.string.get(ir); + try io_w.print("{s}(\"{s}\")", .{ @tagName(inst.tag), str_bytes }); + }, .block => try r.renderBlockInst(ir, inst), .add => try r.renderBinaryInst(inst), .sub => try r.renderBinaryInst(inst), diff --git a/src/Sema.zig b/src/Sema.zig new file mode 100644 index 0000000..f47919b --- /dev/null +++ b/src/Sema.zig @@ -0,0 +1,261 @@ +const std = @import("std"); +const Ir = @import("Ir.zig"); +const Story = @import("Story.zig"); +const Object = Story.Object; +const Sema = @This(); + +gpa: std.mem.Allocator, +ir: *const Ir, +bytecode: std.ArrayListUnmanaged(u8) = .empty, +constants: std.ArrayListUnmanaged(CompiledStory.Constant) = .empty, +knots: std.ArrayListUnmanaged(CompiledStory.Knot) = .empty, + +const InnerError = error{ + OutOfMemory, + TooManyConstants, +}; + +fn deinit(sema: *Sema) void { + const gpa = sema.gpa; + sema.bytecode.deinit(gpa); + sema.constants.deinit(gpa); + sema.knots.deinit(gpa); + sema.* = undefined; +} + +fn resolveIndex(sema: *Sema, index: Ir.Inst.Index) Ir.Inst { + return sema.ir.instructions[@intFromEnum(index)]; +} + +fn emitByte(sema: *Sema, byte: u8) !void { + const gpa = sema.gpa; + return sema.bytecode.append(gpa, byte); +} + +fn emitByteOp(sema: *Sema, op: Story.Opcode) !void { + return emitByte(sema, @intFromEnum(op)); +} + +fn emitConstOp(sema: *Sema, op: Story.Opcode, arg: usize) !void { + const gpa = sema.gpa; + if (arg >= std.math.maxInt(u8)) return error.TooManyConstants; + try sema.bytecode.ensureUnusedCapacity(gpa, 2); + sema.bytecode.appendAssumeCapacity(@intFromEnum(op)); + sema.bytecode.appendAssumeCapacity(@intCast(arg)); +} + +fn emitJumpOp(sema: *Sema, op: Story.Opcode) error{OutOfMemory}!usize { + const gpa = sema.gpa; + try sema.bytecode.ensureUnusedCapacity(gpa, 3); + sema.bytecode.appendAssumeCapacity(@intFromEnum(op)); + sema.bytecode.appendAssumeCapacity(0xff); + sema.bytecode.appendAssumeCapacity(0xff); + return sema.bytecode.items.len - 2; +} + +fn makeConstant(sema: *Sema, data: CompiledStory.Constant) !usize { + const gpa = sema.gpa; + const const_index = sema.constants.items.len; + try sema.constants.append(gpa, data); + return const_index; +} + +fn unaryInst(sema: *Sema, _: Ir.Inst, op: Story.Opcode) InnerError!void { + return emitByteOp(sema, op); +} + +fn binaryInst(sema: *Sema, _: Ir.Inst, op: Story.Opcode) InnerError!void { + return emitByteOp(sema, op); +} + +fn contentInst(sema: *Sema, _: Ir.Inst) InnerError!void { + return emitByteOp(sema, .stream_flush); +} + +fn blockInst(sema: *Sema, block_inst: Ir.Inst) InnerError!void { + const ir = sema.ir; + const extra = ir.extraData(Ir.Inst.Block, block_inst.data.payload.payload_index); + const body = ir.bodySlice(extra.end, extra.data.body_len); + + for (body) |inst_index| { + const body_inst = resolveIndex(sema, inst_index); + try compileInst(sema, body_inst); + } +} + +fn integerInst(sema: *Sema, inst: Ir.Inst) InnerError!void { + const int_const = try sema.makeConstant(.{ + .integer = inst.data.integer.value, + }); + return emitConstOp(sema, .load_const, int_const); +} + +fn stringInst(sema: *Sema, inst: Ir.Inst) InnerError!void { + const str_const = try sema.makeConstant(.{ + .string = inst.data.string.start, + }); + return emitConstOp(sema, .load_const, str_const); +} + +fn compileInst(sema: *Sema, inst: Ir.Inst) InnerError!void { + switch (inst.tag) { + .block => try blockInst(sema, inst), + .add => try binaryInst(sema, inst, .add), + .sub => try binaryInst(sema, inst, .sub), + .mul => try binaryInst(sema, inst, .mul), + .div => try binaryInst(sema, inst, .div), + .mod => try binaryInst(sema, inst, .mod), + .neg => try unaryInst(sema, inst, .neg), + .content => try contentInst(sema, inst), + .string => try stringInst(sema, inst), + .integer => try integerInst(sema, inst), + else => unreachable, + } +} + +fn innerDecl(sema: *Sema, inst: Ir.Inst) !void { + const ir = sema.ir; + const extra = ir.extraData(Ir.Inst.Knot, inst.data.payload.payload_index); + const body_slice = ir.bodySlice(extra.end, extra.data.body_len); + for (body_slice) |inst_index| { + const body_inst = resolveIndex(sema, inst_index); + try compileInst(sema, body_inst); + } +} + +fn declaration(sema: *Sema, inst: Ir.Inst) !void { + const gpa = sema.gpa; + const ir = sema.ir; + const byte_index = sema.bytecode.items.len; + const const_index = sema.constants.items.len; + const extra = ir.extraData(Ir.Inst.Declaration, inst.data.payload.payload_index); + const value_inst = sema.resolveIndex(extra.data.value); + switch (value_inst.tag) { + .decl_var => try innerDecl(sema, value_inst), + .decl_knot => try innerDecl(sema, value_inst), + else => unreachable, + } + + const name_ref = extra.data.name; + try sema.knots.append(gpa, .{ + .name_ref = name_ref, + .arity = 0, + .stack_size = 0, + .bytecode = .{ + .start = @intCast(byte_index), + .len = @intCast(sema.bytecode.items.len - byte_index), + }, + .constants = .{ + .start = @intCast(const_index), + .len = @intCast(sema.constants.items.len - const_index), + }, + }); +} + +fn file(sema: *Sema, inst: Ir.Inst) !void { + const extra = sema.ir.extraData(Ir.Inst.Block, inst.data.payload.payload_index); + const body = sema.ir.bodySlice(extra.end, extra.data.body_len); + for (body) |inst_index| { + const body_inst = sema.resolveIndex(inst_index); + switch (body_inst.tag) { + .declaration => try declaration(sema, body_inst), + else => unreachable, + } + } +} + +pub const CompiledStory = struct { + knots: []Knot, + constants: []Constant, + bytecode: []u8, + + pub const Knot = struct { + name_ref: Ir.NullTerminatedString, + arity: u32, + stack_size: u32, + constants: struct { + start: u32, + len: u32, + }, + bytecode: struct { + start: u32, + len: u32, + }, + }; + + pub const Constant = union(enum) { + integer: u64, + string: Ir.NullTerminatedString, + }; + + pub fn deinit(self: *CompiledStory, gpa: std.mem.Allocator) void { + gpa.free(self.knots); + gpa.free(self.bytecode); + gpa.free(self.constants); + self.* = undefined; + } + + fn bytecodeSlice(self: CompiledStory, knot: *const Knot) []const u8 { + return self.bytecode[knot.bytecode.start..][0..knot.bytecode.len]; + } + + fn constantsSlice(self: CompiledStory, knot: *const Knot) []const Constant { + return self.constants[knot.constants.start..][0..knot.constants.len]; + } + + pub fn buildRuntime( + self: *CompiledStory, + gpa: std.mem.Allocator, + ir: Ir, + story: *Story, + ) !void { + for (self.knots) |compiled_knot| { + var constant_pool: std.ArrayListUnmanaged(*Object) = .empty; + try constant_pool.ensureUnusedCapacity(gpa, compiled_knot.constants.len); + defer constant_pool.deinit(gpa); + + const constants_slice = self.constantsSlice(&compiled_knot); + for (constants_slice) |constant| { + switch (constant) { + .integer => |value| { + const object: *Object.Number = try .create(story, .{ + .integer = @intCast(value), + }); + constant_pool.appendAssumeCapacity(&object.base); + }, + .string => |ref| { + const bytes = ir.nullTerminatedString(ref); + const object: *Object.String = try .create(story, bytes); + constant_pool.appendAssumeCapacity(&object.base); + }, + } + } + + const bytecode_slice = self.bytecodeSlice(&compiled_knot); + const chunk_name = ir.nullTerminatedString(compiled_knot.name_ref); + const runtime_chunk: *Object.ContentPath = try .create(story, .{ + .name = try .create(story, chunk_name), + .arity = @intCast(compiled_knot.arity), + .locals_count = @intCast(compiled_knot.stack_size - compiled_knot.arity), + .const_pool = try constant_pool.toOwnedSlice(gpa), + .bytes = try gpa.dupe(u8, bytecode_slice), + }); + try story.paths.append(gpa, &runtime_chunk.base); + } + } +}; + +pub fn compile(gpa: std.mem.Allocator, ir: *const Ir) !CompiledStory { + var sema: Sema = .{ + .gpa = gpa, + .ir = ir, + }; + defer sema.deinit(); + + try file(&sema, ir.instructions[0]); + return .{ + .bytecode = try sema.bytecode.toOwnedSlice(gpa), + .constants = try sema.constants.toOwnedSlice(gpa), + .knots = try sema.knots.toOwnedSlice(gpa), + }; +} diff --git a/src/Story.zig b/src/Story.zig index 4ed0fe7..47bb43d 100644 --- a/src/Story.zig +++ b/src/Story.zig @@ -3,6 +3,7 @@ const std = @import("std"); const tokenizer = @import("tokenizer.zig"); const Ast = @import("Ast.zig"); const AstGen = @import("AstGen.zig"); +const Sema = @import("Sema.zig"); pub const Object = @import("Story/object.zig").Object; const Dumper = @import("Story/Dumper.zig"); const assert = std.debug.assert; @@ -481,11 +482,11 @@ pub fn loadFromString( ) !Story { var arena_allocator = std.heap.ArenaAllocator.init(gpa); defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); const ast = try Ast.parse(gpa, arena, source_bytes, "", 0); if (options.dump_writer) |w| { + try w.writeAll("=== AST ===\n"); try ast.render(gpa, w, .{ .use_color = options.use_color, }); @@ -497,48 +498,25 @@ pub fn loadFromString( return error.Invalid; } - const comp_unit = try AstGen.generate(gpa, &ast); - defer comp_unit.deinit(gpa); - comp_unit.dumpStringsWithHex(); + var sem_ir = try AstGen.generate(gpa, &ast); + defer sem_ir.deinit(gpa); + + if (options.dump_writer) |w| { + try w.writeAll("=== Semantic IR ===\n"); + sem_ir.dumpStringsWithHex(); + try sem_ir.render(gpa, w); + } + + var compiled = try Sema.compile(gpa, &sem_ir); + defer compiled.deinit(gpa); var story: Story = .{ .allocator = gpa, .can_advance = false, .dump_writer = options.dump_writer, }; - errdefer story.deinit(); - - 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; + try compiled.buildRuntime(gpa, sem_ir, &story); + // try story.divert("$__main__$"); + // story.can_advance = true; return story; }