feat: ink variable semantics, global ref table for astgen
This commit is contained in:
parent
197a37ebe7
commit
9658c8a308
7 changed files with 298 additions and 141 deletions
119
src/AstGen.zig
119
src/AstGen.zig
|
|
@ -12,6 +12,8 @@ tree: *const Ast,
|
||||||
string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.default_max_load_percentage) = .empty,
|
string_table: std.HashMapUnmanaged(u32, void, StringIndexContext, std.hash_map.default_max_load_percentage) = .empty,
|
||||||
string_bytes: std.ArrayListUnmanaged(u8) = .empty,
|
string_bytes: std.ArrayListUnmanaged(u8) = .empty,
|
||||||
instructions: std.ArrayListUnmanaged(Ir.Inst) = .empty,
|
instructions: std.ArrayListUnmanaged(Ir.Inst) = .empty,
|
||||||
|
globals: std.ArrayListUnmanaged(Ir.Global) = .empty,
|
||||||
|
global_ref_table: std.AutoHashMapUnmanaged(Ir.NullTerminatedString, usize) = .empty,
|
||||||
extra: std.ArrayListUnmanaged(u32) = .empty,
|
extra: std.ArrayListUnmanaged(u32) = .empty,
|
||||||
errors: std.ArrayListUnmanaged(Ast.Error) = .empty,
|
errors: std.ArrayListUnmanaged(Ast.Error) = .empty,
|
||||||
|
|
||||||
|
|
@ -22,6 +24,17 @@ pub const InnerError = error{
|
||||||
Overflow,
|
Overflow,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn deinit(astgen: *AstGen) void {
|
||||||
|
const gpa = astgen.gpa;
|
||||||
|
astgen.string_table.deinit(gpa);
|
||||||
|
astgen.string_bytes.deinit(gpa);
|
||||||
|
astgen.globals.deinit(gpa);
|
||||||
|
astgen.global_ref_table.deinit(gpa);
|
||||||
|
astgen.instructions.deinit(gpa);
|
||||||
|
astgen.extra.deinit(gpa);
|
||||||
|
astgen.errors.deinit(gpa);
|
||||||
|
}
|
||||||
|
|
||||||
const Scope = struct {
|
const Scope = struct {
|
||||||
parent: ?*Scope,
|
parent: ?*Scope,
|
||||||
astgen: *AstGen,
|
astgen: *AstGen,
|
||||||
|
|
@ -238,32 +251,41 @@ const GenIr = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn deinit(astgen: *AstGen) void {
|
|
||||||
const gpa = astgen.gpa;
|
|
||||||
astgen.string_table.deinit(gpa);
|
|
||||||
astgen.string_bytes.deinit(gpa);
|
|
||||||
astgen.instructions.deinit(gpa);
|
|
||||||
astgen.errors.deinit(gpa);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setDeclaration(
|
fn setDeclaration(
|
||||||
decl_index: Ir.Inst.Index,
|
decl_index: Ir.Inst.Index,
|
||||||
args: struct {
|
args: struct {
|
||||||
name: Ir.NullTerminatedString,
|
name: Ir.NullTerminatedString,
|
||||||
|
tag: Ir.Global.Tag,
|
||||||
ref: Ir.Inst.Index,
|
ref: Ir.Inst.Index,
|
||||||
|
decl_node: *const Ast.Node,
|
||||||
body_gi: *GenIr,
|
body_gi: *GenIr,
|
||||||
|
is_constant: bool = true,
|
||||||
},
|
},
|
||||||
) !void {
|
) !void {
|
||||||
const astgen = args.body_gi.astgen;
|
const astgen = args.body_gi.astgen;
|
||||||
const gpa = astgen.gpa;
|
const gpa = astgen.gpa;
|
||||||
const extra_len = @typeInfo(Ir.Inst.Declaration).@"struct".fields.len;
|
const extra_len = @typeInfo(Ir.Inst.Declaration).@"struct".fields.len;
|
||||||
|
const global_index = astgen.globals.items.len;
|
||||||
|
|
||||||
try astgen.extra.ensureUnusedCapacity(gpa, extra_len);
|
try astgen.extra.ensureUnusedCapacity(gpa, extra_len);
|
||||||
|
try astgen.globals.ensureUnusedCapacity(gpa, 1);
|
||||||
|
try astgen.global_ref_table.ensureUnusedCapacity(gpa, 1);
|
||||||
|
|
||||||
const inst_data = &astgen.instructions.items[@intFromEnum(decl_index)].data;
|
const inst_data = &astgen.instructions.items[@intFromEnum(decl_index)].data;
|
||||||
inst_data.payload.payload_index = astgen.addExtraAssumeCapacity(
|
inst_data.payload.payload_index = astgen.addExtraAssumeCapacity(
|
||||||
Ir.Inst.Declaration{ .name = args.name, .value = args.ref },
|
Ir.Inst.Declaration{ .name = args.name, .value = args.ref },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (astgen.global_ref_table.get(args.name)) |_| {
|
||||||
|
return astgen.fail(.redefined_identifier, args.decl_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
astgen.globals.appendAssumeCapacity(.{
|
||||||
|
.tag = args.tag,
|
||||||
|
.name = args.name,
|
||||||
|
.is_constant = args.is_constant,
|
||||||
|
});
|
||||||
|
astgen.global_ref_table.putAssumeCapacity(args.name, global_index);
|
||||||
args.body_gi.unstack();
|
args.body_gi.unstack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -423,10 +445,13 @@ 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, node: *const Ast.Node) InnerError!Ir.Inst.Index {
|
fn identifier(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Index {
|
||||||
const name_str = try gen.astgen.stringFromNode(node);
|
const astgen = gi.astgen;
|
||||||
return gen.addDeclRef(name_str);
|
const str = try astgen.stringFromNode(node);
|
||||||
//return gen.fail(.unknown_identifier, node);
|
if (scope.lookup(str)) |decl| {
|
||||||
|
return gi.addUnaryNode(.load_local, decl.inst_index);
|
||||||
|
}
|
||||||
|
return gi.addDeclRef(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
|
@ -736,30 +761,18 @@ fn contentStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerErro
|
||||||
return gen.addUnaryNode(.content, expr_ref);
|
return gen.addUnaryNode(.content, expr_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assignStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void {
|
fn assignStmt(gi: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void {
|
||||||
|
const astgen = gi.astgen;
|
||||||
const identifier_node = stmt_node.data.bin.lhs orelse unreachable;
|
const identifier_node = stmt_node.data.bin.lhs orelse unreachable;
|
||||||
const expr_node = stmt_node.data.bin.rhs orelse unreachable;
|
const expr_node = stmt_node.data.bin.rhs orelse unreachable;
|
||||||
const name_ref = try gen.astgen.stringFromNode(identifier_node);
|
const name_ref = try astgen.stringFromNode(identifier_node);
|
||||||
|
|
||||||
if (scope.lookup(name_ref)) |symbol| switch (symbol) {
|
if (scope.lookup(name_ref)) |decl| {
|
||||||
.global => |data| {
|
const expr_result = try expr(gi, scope, expr_node);
|
||||||
if (data.is_constant) {
|
_ = try gi.addBinaryNode(.store_local, decl.inst_index, expr_result);
|
||||||
return gen.fail(.assignment_to_const, identifier_node);
|
return;
|
||||||
}
|
}
|
||||||
try expr(gen, scope, expr_node);
|
return gi.fail(.unknown_identifier, identifier_node);
|
||||||
try gen.emitConstInst(.store_global, data.constant_slot);
|
|
||||||
try gen.emitSimpleInst(.pop);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
.local => |data| {
|
|
||||||
try expr(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 choiceStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void {
|
fn choiceStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void {
|
||||||
|
|
@ -836,8 +849,7 @@ fn choiceStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn varDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
|
fn tempDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
|
||||||
const gpa = gi.astgen.gpa;
|
|
||||||
const identifier_node = decl_node.data.bin.lhs.?;
|
const identifier_node = decl_node.data.bin.lhs.?;
|
||||||
const expr_node = decl_node.data.bin.rhs.?;
|
const expr_node = decl_node.data.bin.rhs.?;
|
||||||
const name_ref = try gi.astgen.stringFromNode(identifier_node);
|
const name_ref = try gi.astgen.stringFromNode(identifier_node);
|
||||||
|
|
@ -846,6 +858,21 @@ fn varDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
|
||||||
return gi.fail(.redefined_identifier, decl_node);
|
return gi.fail(.redefined_identifier, decl_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const alloc_inst = try gi.add(.{ .tag = .alloc_local, .data = undefined });
|
||||||
|
const expr_result = try expr(gi, scope, expr_node);
|
||||||
|
_ = try gi.addBinaryNode(.store_local, alloc_inst, expr_result);
|
||||||
|
|
||||||
|
return scope.insert(name_ref, .{
|
||||||
|
.decl_node = decl_node,
|
||||||
|
.inst_index = alloc_inst,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn varDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
|
||||||
|
const astgen = gi.astgen;
|
||||||
|
const gpa = astgen.gpa;
|
||||||
|
const identifier_node = decl_node.data.bin.lhs.?;
|
||||||
|
const expr_node = decl_node.data.bin.rhs.?;
|
||||||
const decl_inst = try gi.makeDeclaration();
|
const decl_inst = try gi.makeDeclaration();
|
||||||
try gi.instructions.append(gpa, decl_inst);
|
try gi.instructions.append(gpa, decl_inst);
|
||||||
|
|
||||||
|
|
@ -853,15 +880,14 @@ fn varDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
|
||||||
defer decl_block.unstack();
|
defer decl_block.unstack();
|
||||||
|
|
||||||
_ = try expr(&decl_block, scope, expr_node);
|
_ = try expr(&decl_block, scope, expr_node);
|
||||||
try scope.insert(name_ref, .{
|
|
||||||
.decl_node = decl_node,
|
|
||||||
});
|
|
||||||
|
|
||||||
const var_inst = try decl_block.addVar();
|
const var_inst = try decl_block.addVar();
|
||||||
try setDeclaration(decl_inst, .{
|
try setDeclaration(decl_inst, .{
|
||||||
.name = name_ref,
|
.tag = .variable,
|
||||||
|
.name = try astgen.stringFromNode(identifier_node),
|
||||||
.ref = var_inst,
|
.ref = var_inst,
|
||||||
|
.decl_node = decl_node,
|
||||||
.body_gi = &decl_block,
|
.body_gi = &decl_block,
|
||||||
|
.is_constant = decl_node.tag == .const_decl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -873,8 +899,8 @@ fn blockInner(gi: *GenIr, parent_scope: *Scope, stmt_list: []*Ast.Node) !void {
|
||||||
_ = switch (inner_node.tag) {
|
_ = switch (inner_node.tag) {
|
||||||
.var_decl => try varDecl(gi, &child_scope, inner_node),
|
.var_decl => try varDecl(gi, &child_scope, inner_node),
|
||||||
.const_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),
|
.temp_decl => try tempDecl(gi, &child_scope, inner_node),
|
||||||
//.assign_stmt => try assignStmt(gen, scope, inner_node),
|
.assign_stmt => try assignStmt(gi, &child_scope, inner_node),
|
||||||
.content_stmt => try contentStmt(gi, &child_scope, inner_node),
|
.content_stmt => try contentStmt(gi, &child_scope, inner_node),
|
||||||
//.choice_stmt => try choiceStmt(gen, scope, inner_node),
|
//.choice_stmt => try choiceStmt(gen, scope, inner_node),
|
||||||
.expr_stmt => try exprStmt(gi, &child_scope, inner_node),
|
.expr_stmt => try exprStmt(gi, &child_scope, inner_node),
|
||||||
|
|
@ -922,6 +948,8 @@ fn defaultBlock(
|
||||||
|
|
||||||
const knot_inst = try decl_scope.addKnot();
|
const knot_inst = try decl_scope.addKnot();
|
||||||
try setDeclaration(decl_inst, .{
|
try setDeclaration(decl_inst, .{
|
||||||
|
.tag = .knot,
|
||||||
|
.decl_node = body_node,
|
||||||
.name = try astgen.stringFromBytes("$__main__$"),
|
.name = try astgen.stringFromBytes("$__main__$"),
|
||||||
.ref = knot_inst,
|
.ref = knot_inst,
|
||||||
.body_gi = &decl_scope,
|
.body_gi = &decl_scope,
|
||||||
|
|
@ -992,6 +1020,7 @@ fn file(root_gi: *GenIr, scope: *Scope, file_node: *const Ast.Node) InnerError!v
|
||||||
|
|
||||||
pub const Decl = struct {
|
pub const Decl = struct {
|
||||||
decl_node: *const Ast.Node,
|
decl_node: *const Ast.Node,
|
||||||
|
inst_index: Ir.Inst.Index,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Perform code generation via tree-walk.
|
/// Perform code generation via tree-walk.
|
||||||
|
|
@ -1021,12 +1050,16 @@ pub fn generate(gpa: std.mem.Allocator, tree: *const Ast) !Ir {
|
||||||
defer gen.unstack();
|
defer gen.unstack();
|
||||||
|
|
||||||
// TODO: Make sure this is never null.
|
// TODO: Make sure this is never null.
|
||||||
const root_node = tree.root orelse unreachable;
|
const root_node = tree.root.?;
|
||||||
try file(&gen, &file_scope, root_node);
|
file(&gen, &file_scope, root_node) catch |err| switch (err) {
|
||||||
|
error.SemanticError => {},
|
||||||
|
else => |e| return e,
|
||||||
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.string_bytes = try astgen.string_bytes.toOwnedSlice(gpa),
|
.string_bytes = try astgen.string_bytes.toOwnedSlice(gpa),
|
||||||
.instructions = try astgen.instructions.toOwnedSlice(gpa),
|
.instructions = try astgen.instructions.toOwnedSlice(gpa),
|
||||||
|
.globals = try astgen.globals.toOwnedSlice(gpa),
|
||||||
.extra = try astgen.extra.toOwnedSlice(gpa),
|
.extra = try astgen.extra.toOwnedSlice(gpa),
|
||||||
.errors = try astgen.errors.toOwnedSlice(gpa),
|
.errors = try astgen.errors.toOwnedSlice(gpa),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
85
src/Ir.zig
85
src/Ir.zig
|
|
@ -4,6 +4,7 @@ const Ir = @This();
|
||||||
|
|
||||||
string_bytes: []u8,
|
string_bytes: []u8,
|
||||||
instructions: []Inst,
|
instructions: []Inst,
|
||||||
|
globals: []Global,
|
||||||
extra: []u32,
|
extra: []u32,
|
||||||
errors: []Ast.Error,
|
errors: []Ast.Error,
|
||||||
|
|
||||||
|
|
@ -23,6 +24,9 @@ pub const Inst = struct {
|
||||||
decl_var,
|
decl_var,
|
||||||
decl_ref,
|
decl_ref,
|
||||||
block,
|
block,
|
||||||
|
alloc_local,
|
||||||
|
load_local,
|
||||||
|
store_local,
|
||||||
add,
|
add,
|
||||||
sub,
|
sub,
|
||||||
mul,
|
mul,
|
||||||
|
|
@ -78,6 +82,17 @@ pub const Inst = struct {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Global = struct {
|
||||||
|
tag: Tag,
|
||||||
|
name: Ir.NullTerminatedString,
|
||||||
|
is_constant: bool,
|
||||||
|
|
||||||
|
pub const Tag = enum {
|
||||||
|
knot,
|
||||||
|
variable,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
pub const NullTerminatedString = enum(u32) {
|
pub const NullTerminatedString = enum(u32) {
|
||||||
empty,
|
empty,
|
||||||
_,
|
_,
|
||||||
|
|
@ -91,6 +106,7 @@ pub const IndexSlice = struct {
|
||||||
pub fn deinit(ir: *Ir, gpa: std.mem.Allocator) void {
|
pub fn deinit(ir: *Ir, gpa: std.mem.Allocator) void {
|
||||||
gpa.free(ir.string_bytes);
|
gpa.free(ir.string_bytes);
|
||||||
gpa.free(ir.instructions);
|
gpa.free(ir.instructions);
|
||||||
|
gpa.free(ir.globals);
|
||||||
gpa.free(ir.extra);
|
gpa.free(ir.extra);
|
||||||
gpa.free(ir.errors);
|
gpa.free(ir.errors);
|
||||||
ir.* = undefined;
|
ir.* = undefined;
|
||||||
|
|
@ -129,12 +145,17 @@ const Render = struct {
|
||||||
WriteFailed,
|
WriteFailed,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn renderUnaryInst(r: *Render, inst: Inst) Error!void {
|
fn renderSimple(r: *Render, inst: Inst) Error!void {
|
||||||
|
const io_w = r.writer;
|
||||||
|
return io_w.print("{s}(?)", .{@tagName(inst.tag)});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn renderUnary(r: *Render, inst: Inst) Error!void {
|
||||||
const io_w = r.writer;
|
const io_w = r.writer;
|
||||||
return io_w.print("{s}(%{d})", .{ @tagName(inst.tag), inst.data.un.lhs });
|
return io_w.print("{s}(%{d})", .{ @tagName(inst.tag), inst.data.un.lhs });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderBinaryInst(r: *Render, inst: Inst) Error!void {
|
fn renderBinary(r: *Render, inst: Inst) Error!void {
|
||||||
const io_w = r.writer;
|
const io_w = r.writer;
|
||||||
return io_w.print("{s}(%{d}, %{d})", .{ @tagName(inst.tag), inst.data.bin.lhs, inst.data.bin.rhs });
|
return io_w.print("{s}(%{d}, %{d})", .{ @tagName(inst.tag), inst.data.bin.lhs, inst.data.bin.rhs });
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +173,7 @@ const Render = struct {
|
||||||
try io_w.writeAll("}");
|
try io_w.writeAll("}");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderBlockInst(r: *Render, ir: Ir, inst: Inst) Error!void {
|
fn renderBlock(r: *Render, ir: Ir, inst: Inst) Error!void {
|
||||||
const io_w = r.writer;
|
const io_w = r.writer;
|
||||||
const extra = ir.extraData(Inst.Block, inst.data.payload.payload_index);
|
const extra = ir.extraData(Inst.Block, 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);
|
||||||
|
|
@ -182,16 +203,6 @@ const Render = struct {
|
||||||
try io_w.writeAll(")");
|
try io_w.writeAll(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn renderFileInst(r: *Render, ir: Ir, inst: Inst) Error!void {
|
|
||||||
const io_w = r.writer;
|
|
||||||
const extra = ir.extraData(Inst.Block, inst.data.payload.payload_index);
|
|
||||||
const body_slice = ir.bodySlice(extra.end, extra.data.body_len);
|
|
||||||
|
|
||||||
try io_w.print("{s}(", .{@tagName(inst.tag)});
|
|
||||||
try renderBodyInner(r, ir, body_slice);
|
|
||||||
return io_w.writeAll(")");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn renderDeclaration(r: *Render, ir: Ir, inst: Inst) Error!void {
|
fn renderDeclaration(r: *Render, ir: Ir, inst: Inst) Error!void {
|
||||||
const io_w = r.writer;
|
const io_w = r.writer;
|
||||||
const extra = ir.extraData(Inst.Declaration, inst.data.payload.payload_index);
|
const extra = ir.extraData(Inst.Declaration, inst.data.payload.payload_index);
|
||||||
|
|
@ -207,6 +218,16 @@ const Render = struct {
|
||||||
return io_w.writeAll(")");
|
return io_w.writeAll(")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn renderFile(r: *Render, ir: Ir, inst: Inst) Error!void {
|
||||||
|
const io_w = r.writer;
|
||||||
|
const extra = ir.extraData(Inst.Block, inst.data.payload.payload_index);
|
||||||
|
const body_slice = ir.bodySlice(extra.end, extra.data.body_len);
|
||||||
|
|
||||||
|
try io_w.print("{s}(", .{@tagName(inst.tag)});
|
||||||
|
try renderBodyInner(r, ir, body_slice);
|
||||||
|
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);
|
||||||
|
|
@ -215,7 +236,7 @@ const Render = struct {
|
||||||
try r.prefix.writeIndent(io_w);
|
try r.prefix.writeIndent(io_w);
|
||||||
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.renderFile(ir, inst),
|
||||||
.declaration => try r.renderDeclaration(ir, inst),
|
.declaration => try r.renderDeclaration(ir, inst),
|
||||||
.decl_knot => try r.renderKnotDecl(ir, inst),
|
.decl_knot => try r.renderKnotDecl(ir, inst),
|
||||||
.decl_var => try r.renderVarDecl(ir, inst),
|
.decl_var => try r.renderVarDecl(ir, inst),
|
||||||
|
|
@ -223,13 +244,16 @@ const Render = struct {
|
||||||
const str_bytes = inst.data.string.get(ir);
|
const str_bytes = inst.data.string.get(ir);
|
||||||
try io_w.print("{s}(\"{s}\")", .{ @tagName(inst.tag), str_bytes });
|
try io_w.print("{s}(\"{s}\")", .{ @tagName(inst.tag), str_bytes });
|
||||||
},
|
},
|
||||||
.block => try r.renderBlockInst(ir, inst),
|
.alloc_local => try r.renderSimple(inst),
|
||||||
.add => try r.renderBinaryInst(inst),
|
.load_local => try r.renderUnary(inst),
|
||||||
.sub => try r.renderBinaryInst(inst),
|
.store_local => try r.renderBinary(inst),
|
||||||
.mul => try r.renderBinaryInst(inst),
|
.block => try r.renderBlock(ir, inst),
|
||||||
.div => try r.renderBinaryInst(inst),
|
.add => try r.renderBinary(inst),
|
||||||
.mod => try r.renderBinaryInst(inst),
|
.sub => try r.renderBinary(inst),
|
||||||
.neg => try r.renderUnaryInst(inst),
|
.mul => try r.renderBinary(inst),
|
||||||
|
.div => try r.renderBinary(inst),
|
||||||
|
.mod => try r.renderBinary(inst),
|
||||||
|
.neg => try r.renderUnary(inst),
|
||||||
.true_literal => {
|
.true_literal => {
|
||||||
try io_w.print("{s}", .{@tagName(inst.tag)});
|
try io_w.print("{s}", .{@tagName(inst.tag)});
|
||||||
},
|
},
|
||||||
|
|
@ -244,7 +268,7 @@ const Render = struct {
|
||||||
const str_bytes = inst.data.string.get(ir);
|
const str_bytes = inst.data.string.get(ir);
|
||||||
try io_w.print("{s}(\"{s}\")", .{ @tagName(inst.tag), str_bytes });
|
try io_w.print("{s}(\"{s}\")", .{ @tagName(inst.tag), str_bytes });
|
||||||
},
|
},
|
||||||
.content => try r.renderUnaryInst(inst),
|
.content => try r.renderUnary(inst),
|
||||||
}
|
}
|
||||||
try io_w.writeAll("\n");
|
try io_w.writeAll("\n");
|
||||||
}
|
}
|
||||||
|
|
@ -259,18 +283,23 @@ pub fn render(ir: Ir, gpa: std.mem.Allocator, writer: *std.Io.Writer) !void {
|
||||||
return writer.flush();
|
return writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dumpStringsWithHex(cu: *const Ir) void {
|
pub fn dumpInfo(ir: Ir, writer: *std.Io.Writer) !void {
|
||||||
const bytes = cu.string_bytes;
|
const bytes = ir.string_bytes;
|
||||||
|
|
||||||
var start: usize = 0;
|
var start: usize = 0;
|
||||||
while (start < bytes.len) {
|
while (start < bytes.len) {
|
||||||
const end = std.mem.indexOfScalarPos(u8, bytes, start, 0) orelse break;
|
const end = std.mem.indexOfScalarPos(u8, bytes, start, 0) orelse break;
|
||||||
const s = bytes[start..end];
|
const str = bytes[start..end];
|
||||||
|
|
||||||
std.debug.print("[{d:04}] ", .{start});
|
try writer.print("[{d:04}] ", .{start});
|
||||||
for (s) |b| std.debug.print("{x:02} ", .{b});
|
for (str) |b| try writer.print("{x:02} ", .{b});
|
||||||
std.debug.print("00: {s}\n", .{s});
|
try writer.print("00: {s}\n", .{str});
|
||||||
start = end + 1;
|
start = end + 1;
|
||||||
}
|
}
|
||||||
|
for (ir.globals) |global| {
|
||||||
|
try writer.print("{any}\n", .{global});
|
||||||
|
}
|
||||||
|
return writer.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ExtraData(comptime T: type) type {
|
fn ExtraData(comptime T: type) type {
|
||||||
|
|
|
||||||
215
src/Sema.zig
215
src/Sema.zig
|
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||||
const Ir = @import("Ir.zig");
|
const Ir = @import("Ir.zig");
|
||||||
const Story = @import("Story.zig");
|
const Story = @import("Story.zig");
|
||||||
const Object = Story.Object;
|
const Object = Story.Object;
|
||||||
|
const assert = std.debug.assert;
|
||||||
const Sema = @This();
|
const Sema = @This();
|
||||||
|
|
||||||
gpa: std.mem.Allocator,
|
gpa: std.mem.Allocator,
|
||||||
|
|
@ -23,6 +24,10 @@ fn deinit(sema: *Sema) void {
|
||||||
sema.* = undefined;
|
sema.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fail(_: *Sema, message: []const u8) InnerError {
|
||||||
|
@panic(message);
|
||||||
|
}
|
||||||
|
|
||||||
fn resolveIndex(sema: *Sema, index: Ir.Inst.Index) Ir.Inst {
|
fn resolveIndex(sema: *Sema, index: Ir.Inst.Index) Ir.Inst {
|
||||||
return sema.ir.instructions[@intFromEnum(index)];
|
return sema.ir.instructions[@intFromEnum(index)];
|
||||||
}
|
}
|
||||||
|
|
@ -36,12 +41,12 @@ fn emitByteOp(sema: *Sema, op: Story.Opcode) !void {
|
||||||
return emitByte(sema, @intFromEnum(op));
|
return emitByte(sema, @intFromEnum(op));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emitConstOp(sema: *Sema, op: Story.Opcode, arg: usize) !void {
|
fn emitConstOp(sema: *Sema, op: Story.Opcode, arg: u8) !void {
|
||||||
const gpa = sema.gpa;
|
const gpa = sema.gpa;
|
||||||
if (arg >= std.math.maxInt(u8)) return error.TooManyConstants;
|
if (arg >= std.math.maxInt(u8)) return error.TooManyConstants;
|
||||||
try sema.bytecode.ensureUnusedCapacity(gpa, 2);
|
try sema.bytecode.ensureUnusedCapacity(gpa, 2);
|
||||||
sema.bytecode.appendAssumeCapacity(@intFromEnum(op));
|
sema.bytecode.appendAssumeCapacity(@intFromEnum(op));
|
||||||
sema.bytecode.appendAssumeCapacity(@intCast(arg));
|
sema.bytecode.appendAssumeCapacity(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emitJumpOp(sema: *Sema, op: Story.Opcode) error{OutOfMemory}!usize {
|
fn emitJumpOp(sema: *Sema, op: Story.Opcode) error{OutOfMemory}!usize {
|
||||||
|
|
@ -60,6 +65,27 @@ fn makeConstant(sema: *Sema, data: CompiledStory.Constant) !usize {
|
||||||
return const_index;
|
return const_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Chunk = struct {
|
||||||
|
name: Ir.NullTerminatedString,
|
||||||
|
arity: u32,
|
||||||
|
stack_size: u32,
|
||||||
|
stack_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, u32),
|
||||||
|
};
|
||||||
|
|
||||||
|
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, @intCast(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, @intCast(str_const));
|
||||||
|
}
|
||||||
|
|
||||||
fn unaryInst(sema: *Sema, _: Ir.Inst, op: Story.Opcode) InnerError!void {
|
fn unaryInst(sema: *Sema, _: Ir.Inst, op: Story.Opcode) InnerError!void {
|
||||||
return emitByteOp(sema, op);
|
return emitByteOp(sema, op);
|
||||||
}
|
}
|
||||||
|
|
@ -72,34 +98,129 @@ fn contentInst(sema: *Sema, _: Ir.Inst) InnerError!void {
|
||||||
return emitByteOp(sema, .stream_flush);
|
return emitByteOp(sema, .stream_flush);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn blockInst(sema: *Sema, block_inst: Ir.Inst) InnerError!void {
|
fn allocLocal(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void {
|
||||||
|
const gpa = sema.gpa;
|
||||||
|
// TODO: Add constraints on how many temporaries we can have.
|
||||||
|
// max(u8) or max(u16) are most likey appropriate.
|
||||||
|
try chunk.stack_map.put(gpa, inst, chunk.stack_size);
|
||||||
|
chunk.stack_size += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn storeLocal(sema: *Sema, chunk: *Chunk, inst: Ir.Inst) InnerError!void {
|
||||||
|
const temp_inst = inst.data.bin.lhs;
|
||||||
|
const stack_offset = chunk.stack_map.get(temp_inst).?;
|
||||||
|
try emitConstOp(sema, .store, @intCast(stack_offset));
|
||||||
|
try emitByteOp(sema, .pop);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadLocal(sema: *Sema, chunk: *Chunk, inst: Ir.Inst) InnerError!void {
|
||||||
|
const temp_inst = inst.data.un.lhs;
|
||||||
|
const stack_offset = chunk.stack_map.get(temp_inst).?;
|
||||||
|
try emitConstOp(sema, .load, @intCast(stack_offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declRef(sema: *Sema, inst: Ir.Inst) InnerError!void {
|
||||||
|
const ir = sema.ir;
|
||||||
|
const str = inst.data.string.start;
|
||||||
|
for (ir.globals) |global| {
|
||||||
|
if (str == global.name) {
|
||||||
|
const constant_index = try sema.makeConstant(.{ .string = str });
|
||||||
|
return emitConstOp(sema, .load_global, @intCast(constant_index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sema.fail("unknown global variable");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declVar(
|
||||||
|
sema: *Sema,
|
||||||
|
chunk: *Chunk,
|
||||||
|
name: Ir.NullTerminatedString,
|
||||||
|
inst: Ir.Inst,
|
||||||
|
) InnerError!void {
|
||||||
|
const ir = sema.ir;
|
||||||
|
const payload_index = inst.data.payload.payload_index;
|
||||||
|
const extra = ir.extraData(Ir.Inst.Block, payload_index);
|
||||||
|
const body = ir.bodySlice(extra.end, extra.data.body_len);
|
||||||
|
for (body) |body_index| try compileInst(sema, chunk, body_index);
|
||||||
|
for (ir.globals) |global| {
|
||||||
|
if (name == global.name) {
|
||||||
|
const constant_index = try sema.makeConstant(.{ .string = name });
|
||||||
|
return emitConstOp(sema, .store_global, @intCast(constant_index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sema.fail("unknown global variable");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declKnot(sema: *Sema, name_ref: Ir.NullTerminatedString, inst: Ir.Inst) InnerError!void {
|
||||||
|
const gpa = sema.gpa;
|
||||||
|
const ir = sema.ir;
|
||||||
|
const extra = ir.extraData(Ir.Inst.Knot, inst.data.payload.payload_index);
|
||||||
|
const bytecode_start = sema.bytecode.items.len;
|
||||||
|
const const_start = sema.constants.items.len;
|
||||||
|
|
||||||
|
var chunk: Chunk = .{
|
||||||
|
.name = name_ref,
|
||||||
|
.arity = 0,
|
||||||
|
.stack_size = 0,
|
||||||
|
.stack_map = .empty,
|
||||||
|
};
|
||||||
|
defer chunk.stack_map.deinit(gpa);
|
||||||
|
|
||||||
|
const body_slice = ir.bodySlice(extra.end, extra.data.body_len);
|
||||||
|
for (body_slice) |body_inst| try compileInst(sema, &chunk, body_inst);
|
||||||
|
|
||||||
|
try emitByteOp(sema, .exit);
|
||||||
|
|
||||||
|
try sema.knots.append(gpa, .{
|
||||||
|
.name_ref = name_ref,
|
||||||
|
.arity = chunk.arity,
|
||||||
|
.stack_size = chunk.stack_size,
|
||||||
|
.bytecode = .{
|
||||||
|
.start = @intCast(bytecode_start),
|
||||||
|
.len = @intCast(sema.bytecode.items.len - bytecode_start),
|
||||||
|
},
|
||||||
|
.constants = .{
|
||||||
|
.start = @intCast(const_start),
|
||||||
|
.len = @intCast(sema.constants.items.len - const_start),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blockInst(sema: *Sema, chunk: *Chunk, block_inst: Ir.Inst) InnerError!void {
|
||||||
const ir = sema.ir;
|
const ir = sema.ir;
|
||||||
const extra = ir.extraData(Ir.Inst.Block, block_inst.data.payload.payload_index);
|
const extra = ir.extraData(Ir.Inst.Block, block_inst.data.payload.payload_index);
|
||||||
const body = ir.bodySlice(extra.end, extra.data.body_len);
|
const body = ir.bodySlice(extra.end, extra.data.body_len);
|
||||||
|
for (body) |body_index| try compileInst(sema, chunk, body_index);
|
||||||
|
}
|
||||||
|
|
||||||
for (body) |inst_index| {
|
fn declaration(sema: *Sema, parent_chunk: ?*Chunk, inst: Ir.Inst) !void {
|
||||||
const body_inst = resolveIndex(sema, inst_index);
|
const ir = sema.ir;
|
||||||
try compileInst(sema, body_inst);
|
const extra = ir.extraData(Ir.Inst.Declaration, inst.data.payload.payload_index);
|
||||||
|
const value_inst = sema.resolveIndex(extra.data.value);
|
||||||
|
const str = extra.data.name;
|
||||||
|
|
||||||
|
switch (value_inst.tag) {
|
||||||
|
.decl_var => try declVar(sema, parent_chunk.?, str, value_inst),
|
||||||
|
.decl_knot => try declKnot(sema, str, value_inst),
|
||||||
|
else => unreachable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn integerInst(sema: *Sema, inst: Ir.Inst) InnerError!void {
|
fn compileInst(sema: *Sema, chunk: *Chunk, index: Ir.Inst.Index) InnerError!void {
|
||||||
const int_const = try sema.makeConstant(.{
|
const inst = sema.resolveIndex(index);
|
||||||
.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) {
|
switch (inst.tag) {
|
||||||
.block => try blockInst(sema, inst),
|
.file => unreachable,
|
||||||
|
.declaration => try declaration(sema, chunk, inst),
|
||||||
|
.decl_var => unreachable, // handled in declaration()
|
||||||
|
.decl_knot => unreachable, // handled in declaration()
|
||||||
|
.decl_ref => try declRef(sema, inst),
|
||||||
|
.block => try blockInst(sema, chunk, inst),
|
||||||
|
.alloc_local => try allocLocal(sema, chunk, index),
|
||||||
|
.store_local => try storeLocal(sema, chunk, inst),
|
||||||
|
.load_local => try loadLocal(sema, chunk, inst),
|
||||||
|
.true_literal => unreachable, // TODO
|
||||||
|
.false_literal => unreachable, // TODO
|
||||||
.add => try binaryInst(sema, inst, .add),
|
.add => try binaryInst(sema, inst, .add),
|
||||||
.sub => try binaryInst(sema, inst, .sub),
|
.sub => try binaryInst(sema, inst, .sub),
|
||||||
.mul => try binaryInst(sema, inst, .mul),
|
.mul => try binaryInst(sema, inst, .mul),
|
||||||
|
|
@ -109,58 +230,16 @@ fn compileInst(sema: *Sema, inst: Ir.Inst) InnerError!void {
|
||||||
.content => try contentInst(sema, inst),
|
.content => try contentInst(sema, inst),
|
||||||
.string => try stringInst(sema, inst),
|
.string => try stringInst(sema, inst),
|
||||||
.integer => try integerInst(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 {
|
fn file(sema: *Sema, inst: Ir.Inst) !void {
|
||||||
const extra = sema.ir.extraData(Ir.Inst.Block, inst.data.payload.payload_index);
|
const extra = sema.ir.extraData(Ir.Inst.Block, inst.data.payload.payload_index);
|
||||||
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
||||||
for (body) |inst_index| {
|
for (body) |body_index| {
|
||||||
const body_inst = sema.resolveIndex(inst_index);
|
const body_inst = sema.resolveIndex(body_index);
|
||||||
switch (body_inst.tag) {
|
assert(body_inst.tag == .declaration);
|
||||||
.declaration => try declaration(sema, body_inst),
|
try declaration(sema, null, body_inst);
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -501,9 +501,17 @@ pub fn loadFromString(
|
||||||
var sem_ir = try AstGen.generate(gpa, &ast);
|
var sem_ir = try AstGen.generate(gpa, &ast);
|
||||||
defer sem_ir.deinit(gpa);
|
defer sem_ir.deinit(gpa);
|
||||||
|
|
||||||
|
if (sem_ir.errors.len != 0) {
|
||||||
|
for (sem_ir.errors) |err| {
|
||||||
|
try options.stderr_writer.print("{any}\n", .{err});
|
||||||
|
}
|
||||||
|
try options.stderr_writer.flush();
|
||||||
|
return error.CompilationFailed;
|
||||||
|
}
|
||||||
|
|
||||||
if (options.dump_writer) |w| {
|
if (options.dump_writer) |w| {
|
||||||
try w.writeAll("=== Semantic IR ===\n");
|
try w.writeAll("=== Semantic IR ===\n");
|
||||||
sem_ir.dumpStringsWithHex();
|
try sem_ir.dumpInfo(w);
|
||||||
try sem_ir.render(gpa, w);
|
try sem_ir.render(gpa, w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
3
testing/regression/semantics/global-shadowing-2.ink
Normal file
3
testing/regression/semantics/global-shadowing-2.ink
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
VAR foo = "global"
|
||||||
|
~ temp foo = "temp"
|
||||||
|
{foo}
|
||||||
2
testing/regression/semantics/global-shadowing-3.ink
Normal file
2
testing/regression/semantics/global-shadowing-3.ink
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
VAR foo = "Hello, world!"
|
||||||
|
VAR foo = "Hello, world!"
|
||||||
3
testing/regression/semantics/global-shadowing.ink
Normal file
3
testing/regression/semantics/global-shadowing.ink
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
~ temp foo = "temp"
|
||||||
|
{foo}
|
||||||
|
VAR foo = "global"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue