feat: ir for declarations and semantic analyzer start

This commit is contained in:
Brett Broadhurst 2026-03-09 05:57:25 -06:00
parent f16162b5bb
commit 197a37ebe7
Failed to generate hash of commit
4 changed files with 453 additions and 145 deletions

View file

@ -18,21 +18,8 @@ errors: std.ArrayListUnmanaged(Ast.Error) = .empty,
pub const InnerError = error{ pub const InnerError = error{
OutOfMemory, OutOfMemory,
SemanticError, SemanticError,
TooManyConstants,
InvalidCharacter, InvalidCharacter,
} || anyerror; Overflow,
pub const Fixup = struct {
mode: enum {
relative,
absolute,
},
label_index: usize,
code_offset: usize,
};
pub const Label = struct {
code_offset: usize,
}; };
const Scope = struct { const Scope = struct {
@ -73,8 +60,20 @@ const GenIr = struct {
instructions: *std.ArrayListUnmanaged(Ir.Inst.Index), instructions: *std.ArrayListUnmanaged(Ir.Inst.Index),
instructions_top: usize, instructions_top: usize,
pub fn unstack(gi: *GenIr) void { const unstacked_top = std.math.maxInt(usize);
gi.instructions.items.len = gi.instructions_top;
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( fn fail(
@ -85,25 +84,14 @@ const GenIr = struct {
return self.astgen.fail(tag, node); 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 { pub fn makeSubBlock(self: *GenIr) GenIr {
return .{ return .{
.astgen = self.astgen, .astgen = self.astgen,
.instructions = self.instructions, .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 { pub fn add(gi: *GenIr, inst: Ir.Inst) !Ir.Inst.Index {
const gpa = gi.astgen.gpa; const gpa = gi.astgen.gpa;
try gi.instructions.ensureUnusedCapacity(gpa, 1); 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 { pub fn makePayloadNode(gi: *GenIr, tag: Ir.Inst.Tag) !Ir.Inst.Index {
const gpa = gi.astgen.gpa; const gpa = gi.astgen.gpa;
const inst_index: Ir.Inst.Index = @enumFromInt(gi.astgen.instructions.items.len); const inst_index: Ir.Inst.Index = @enumFromInt(gi.astgen.instructions.items.len);
@ -164,41 +161,70 @@ const GenIr = struct {
return inst_index; return inst_index;
} }
pub fn makeDeclNode(gi: *GenIr) !Ir.Inst.Index { pub fn makeDeclaration(gi: *GenIr) !Ir.Inst.Index {
return makePayloadNode(gi, .decl_knot); return makePayloadNode(gi, .declaration);
} }
pub fn makeBlockInst(gi: *GenIr) !Ir.Inst.Index { pub fn makeBlockInst(gi: *GenIr) !Ir.Inst.Index {
return makePayloadNode(gi, .block); return makePayloadNode(gi, .block);
} }
pub fn setDecl( pub fn addKnot(gi: *GenIr) !Ir.Inst.Index {
gi: *GenIr,
inst: Ir.Inst.Index,
name_ref: Ir.NullTerminatedString,
) !void {
const astgen = gi.astgen; const astgen = gi.astgen;
const gpa = astgen.gpa; const gpa = astgen.gpa;
const body = gi.instructionsSlice(); const body = gi.instructionsSlice();
const extra_len = @typeInfo(Ir.Inst.Knot).@"struct".fields.len + body.len; const extra_len = @typeInfo(Ir.Inst.Knot).@"struct".fields.len + body.len;
try astgen.extra.ensureUnusedCapacity(gpa, extra_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( 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| { for (body) |inst_index| {
astgen.extra.appendAssumeCapacity(@intFromEnum(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 { pub fn setBlockBody(gi: *GenIr, inst: Ir.Inst.Index) !void {
const astgen = gi.astgen; const astgen = gi.astgen;
const gpa = astgen.gpa; const gpa = astgen.gpa;
const body = gi.instructionsSlice(); 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); try astgen.extra.ensureUnusedCapacity(gpa, extra_len);
const inst_data = &astgen.instructions.items[@intFromEnum(inst)].data; const inst_data = &astgen.instructions.items[@intFromEnum(inst)].data;
@ -220,6 +246,27 @@ pub fn deinit(astgen: *AstGen) void {
astgen.errors.deinit(gpa); 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 { fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 {
const fields = std.meta.fields(@TypeOf(extra)); const fields = std.meta.fields(@TypeOf(extra));
const extra_index: u32 = @intCast(astgen.extra.items.len); 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) { if (gop.found_existing) {
string_bytes.shrinkRetainingCapacity(str_index); string_bytes.shrinkRetainingCapacity(str_index);
return @enumFromInt(str_index); return @enumFromInt(gop.key_ptr.*);
} else { } else {
gop.key_ptr.* = str_index; gop.key_ptr.* = str_index;
try string_bytes.append(gpa, 0); try string_bytes.append(gpa, 0);
@ -346,17 +393,11 @@ fn logicalOp(
} }
fn trueLiteral(gi: *GenIr) InnerError!Ir.Inst.Index { fn trueLiteral(gi: *GenIr) InnerError!Ir.Inst.Index {
return gi.add(.{ return gi.add(.{ .tag = .true_literal, .data = undefined });
.tag = .true_literal,
.data = undefined,
});
} }
fn falseLiteral(gi: *GenIr) InnerError!Ir.Inst.Index { fn falseLiteral(gi: *GenIr) InnerError!Ir.Inst.Index {
return gi.add(.{ return gi.add(.{ .tag = .false_literal, .data = undefined });
.tag = .false_literal,
.data = undefined,
});
} }
fn numberLiteral(gen: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Index { 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); return stringLiteral(gen, first_node);
} }
fn identifier(gen: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Index { fn identifier(gen: *GenIr, _: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Index {
const name_ref = try gen.astgen.stringFromNode(node); const name_str = try gen.astgen.stringFromNode(node);
if (scope.lookup(name_ref)) |_| {} return gen.addDeclRef(name_str);
return gen.fail(.unknown_identifier, node); //return gen.fail(.unknown_identifier, node);
} }
fn expr(gen: *GenIr, scope: *Scope, optional_expr: ?*const Ast.Node) InnerError!Ir.Inst.Index { 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 { fn varDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
const identifier_node = decl_node.data.bin.lhs orelse unreachable; const gpa = gi.astgen.gpa;
const expr_node = decl_node.data.bin.rhs orelse unreachable; const identifier_node = decl_node.data.bin.lhs.?;
const name_ref = try gen.astgen.stringFromNode(identifier_node); const expr_node = decl_node.data.bin.rhs.?;
const decl_symbol: Decl = blk: switch (decl_node.tag) { const name_ref = try gi.astgen.stringFromNode(identifier_node);
.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,
};
try scope.insert(name_ref, decl_symbol); if (scope.lookup(name_ref)) |_| {
try expr(gen, scope, expr_node); return gi.fail(.redefined_identifier, decl_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 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 { 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| { for (stmt_list) |inner_node| {
_ = switch (inner_node.tag) { _ = switch (inner_node.tag) {
//.var_decl => try varDecl(gen, scope, inner_node), .var_decl => try varDecl(gi, &child_scope, inner_node),
//.const_decl => try varDecl(gen, scope, inner_node), .const_decl => try varDecl(gi, &child_scope, inner_node),
//.temp_decl => try varDecl(gen, scope, inner_node), //.temp_decl => try varDecl(gen, scope, inner_node),
//.assign_stmt => try assignStmt(gen, scope, inner_node), //.assign_stmt => try assignStmt(gen, scope, inner_node),
.content_stmt => try contentStmt(gi, &child_scope, inner_node), .content_stmt => try contentStmt(gi, &child_scope, inner_node),
@ -884,7 +910,7 @@ fn defaultBlock(
) InnerError!void { ) InnerError!void {
const astgen = gi.astgen; const astgen = gi.astgen;
const gpa = astgen.gpa; const gpa = astgen.gpa;
const decl_inst = try gi.makeDeclNode(); const decl_inst = try gi.makeDeclaration();
try gi.instructions.append(gpa, decl_inst); try gi.instructions.append(gpa, decl_inst);
var decl_scope = gi.makeSubBlock(); var decl_scope = gi.makeSubBlock();
@ -894,9 +920,12 @@ fn defaultBlock(
const block_stmts = body_node.data.list.items orelse unreachable; const block_stmts = body_node.data.list.items orelse unreachable;
try blockInner(&decl_scope, scope, block_stmts); try blockInner(&decl_scope, scope, block_stmts);
std.debug.print("{any}\n", .{decl_scope}); const knot_inst = try decl_scope.addKnot();
const name_ref = try decl_scope.astgen.stringFromBytes("$__main__$"); try setDeclaration(decl_inst, .{
try decl_scope.setDecl(decl_inst, name_ref); .name = try astgen.stringFromBytes("$__main__$"),
.ref = knot_inst,
.body_gi = &decl_scope,
});
} }
fn stitchDecl(_: *GenIr, _: *Scope, _: *const Ast.Node) InnerError!void {} 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 { fn file(root_gi: *GenIr, scope: *Scope, file_node: *const Ast.Node) InnerError!void {
const astgen = root_gi.astgen; const astgen = root_gi.astgen;
const gpa = astgen.gpa; const gpa = astgen.gpa;
const file_inst = try root_gi.makePayloadNode(.file); const file_inst = try root_gi.makePayloadNode(.file);
try root_gi.instructions.append(gpa, file_inst); try root_gi.instructions.append(gpa, file_inst);
var start_index: usize = 0;
var file_scope = root_gi.makeSubBlock(); var file_scope = root_gi.makeSubBlock();
defer file_scope.unstack(); defer file_scope.unstack();
// TODO: Make sure this is non-nullable. // TODO: Make sure this is non-nullable.
const nested_decls_list = file_node.data.list.items orelse return; const nested_decls_list = file_node.data.list.items orelse return;
if (nested_decls_list.len == 0) return; if (nested_decls_list.len == 0) return;
var start_index: usize = 0;
const first_child = nested_decls_list[0]; const first_child = nested_decls_list[0];
if (first_child.tag == .block_stmt) { if (first_child.tag == .block_stmt) {
try defaultBlock(&file_scope, scope, first_child); 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(); 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); try astgen.string_bytes.append(gpa, 0);
var instructions: std.ArrayListUnmanaged(Ir.Inst.Index) = .empty; var instructions: std.ArrayListUnmanaged(Ir.Inst.Index) = .empty;

View file

@ -18,7 +18,10 @@ pub const Inst = struct {
pub const Tag = enum { pub const Tag = enum {
file, file,
declaration,
decl_knot, decl_knot,
decl_var,
decl_ref,
block, block,
add, add,
sub, sub,
@ -57,8 +60,16 @@ pub const Inst = struct {
}, },
}; };
pub const Declaration = struct {
name: NullTerminatedString,
value: Inst.Index,
};
pub const Knot = struct { pub const Knot = struct {
name_ref: NullTerminatedString, body_len: u32,
};
pub const Var = struct {
body_len: u32, body_len: u32,
}; };
@ -151,12 +162,22 @@ const Render = struct {
try io_w.writeAll(")"); 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 io_w = r.writer;
const extra = ir.extraData(Inst.Knot, inst.data.payload.payload_index); const extra = ir.extraData(Inst.Knot, inst.data.payload.payload_index);
const body_slice = ir.bodySlice(extra.end, extra.data.body_len); 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 renderBodyInner(r, ir, body_slice);
try io_w.writeAll(")"); try io_w.writeAll(")");
} }
@ -171,6 +192,21 @@ const Render = struct {
return io_w.writeAll(")"); 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 { fn renderInst(r: *Render, ir: Ir, index: Inst.Index) Error!void {
const io_w = r.writer; const io_w = r.writer;
const inst_index: u32 = @intFromEnum(index); const inst_index: u32 = @intFromEnum(index);
@ -180,7 +216,13 @@ const Render = struct {
try io_w.print("%{d} = ", .{inst_index}); try io_w.print("%{d} = ", .{inst_index});
switch (inst.tag) { switch (inst.tag) {
.file => try r.renderFileInst(ir, inst), .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), .block => try r.renderBlockInst(ir, inst),
.add => try r.renderBinaryInst(inst), .add => try r.renderBinaryInst(inst),
.sub => try r.renderBinaryInst(inst), .sub => try r.renderBinaryInst(inst),

261
src/Sema.zig Normal file
View file

@ -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),
};
}

View file

@ -3,6 +3,7 @@ const std = @import("std");
const tokenizer = @import("tokenizer.zig"); const tokenizer = @import("tokenizer.zig");
const Ast = @import("Ast.zig"); const Ast = @import("Ast.zig");
const AstGen = @import("AstGen.zig"); const AstGen = @import("AstGen.zig");
const Sema = @import("Sema.zig");
pub const Object = @import("Story/object.zig").Object; pub const Object = @import("Story/object.zig").Object;
const Dumper = @import("Story/Dumper.zig"); const Dumper = @import("Story/Dumper.zig");
const assert = std.debug.assert; const assert = std.debug.assert;
@ -481,11 +482,11 @@ pub fn loadFromString(
) !Story { ) !Story {
var arena_allocator = std.heap.ArenaAllocator.init(gpa); var arena_allocator = std.heap.ArenaAllocator.init(gpa);
defer arena_allocator.deinit(); defer arena_allocator.deinit();
const arena = arena_allocator.allocator(); const arena = arena_allocator.allocator();
const ast = try Ast.parse(gpa, arena, source_bytes, "<STDIN>", 0); const ast = try Ast.parse(gpa, arena, source_bytes, "<STDIN>", 0);
if (options.dump_writer) |w| { if (options.dump_writer) |w| {
try w.writeAll("=== AST ===\n");
try ast.render(gpa, w, .{ try ast.render(gpa, w, .{
.use_color = options.use_color, .use_color = options.use_color,
}); });
@ -497,48 +498,25 @@ pub fn loadFromString(
return error.Invalid; return error.Invalid;
} }
const comp_unit = try AstGen.generate(gpa, &ast); var sem_ir = try AstGen.generate(gpa, &ast);
defer comp_unit.deinit(gpa); defer sem_ir.deinit(gpa);
comp_unit.dumpStringsWithHex();
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 = .{ var story: Story = .{
.allocator = gpa, .allocator = gpa,
.can_advance = false, .can_advance = false,
.dump_writer = options.dump_writer, .dump_writer = options.dump_writer,
}; };
errdefer story.deinit(); try compiled.buildRuntime(gpa, sem_ir, &story);
// try story.divert("$__main__$");
for (comp_unit.knots) |compiled_chunk| { // story.can_advance = true;
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; return story;
} }