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{
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;