feat: code generation for diverts, wip
This commit is contained in:
parent
ee26be6254
commit
20292bcc6a
5 changed files with 699 additions and 149 deletions
513
src/AstGen.zig
513
src/AstGen.zig
|
|
@ -15,6 +15,7 @@ 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,
|
||||
scratch: std.ArrayListUnmanaged(u32) = .empty,
|
||||
errors: std.ArrayListUnmanaged(Ast.Error) = .empty,
|
||||
|
||||
pub const InnerError = error{
|
||||
|
|
@ -32,38 +33,46 @@ pub fn deinit(astgen: *AstGen) void {
|
|||
astgen.global_ref_table.deinit(gpa);
|
||||
astgen.instructions.deinit(gpa);
|
||||
astgen.extra.deinit(gpa);
|
||||
astgen.scratch.deinit(gpa);
|
||||
astgen.errors.deinit(gpa);
|
||||
}
|
||||
|
||||
const Scope = struct {
|
||||
parent: ?*Scope,
|
||||
astgen: *AstGen,
|
||||
namespace_prefix: Ir.NullTerminatedString,
|
||||
decls: std.AutoHashMapUnmanaged(Ir.NullTerminatedString, Decl),
|
||||
|
||||
pub const Decl = struct {
|
||||
const Decl = struct {
|
||||
decl_node: *const Ast.Node,
|
||||
inst_index: Ir.Inst.Index,
|
||||
};
|
||||
|
||||
pub fn deinit(self: *Scope) void {
|
||||
fn deinit(self: *Scope) void {
|
||||
const gpa = self.astgen.gpa;
|
||||
self.decls.deinit(gpa);
|
||||
}
|
||||
|
||||
pub fn makeChild(parent_scope: *Scope) Scope {
|
||||
fn makeChild(parent_scope: *Scope) Scope {
|
||||
return .{
|
||||
.parent = parent_scope,
|
||||
.astgen = parent_scope.astgen,
|
||||
.namespace_prefix = parent_scope.namespace_prefix,
|
||||
.decls = .empty,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn insert(self: *Scope, ref: Ir.NullTerminatedString, decl: Decl) !void {
|
||||
fn setNamespacePrefix(scope: *Scope, relative: Ir.NullTerminatedString) !void {
|
||||
const astgen = scope.astgen;
|
||||
scope.namespace_prefix = try astgen.qualifiedString(scope.namespace_prefix, relative);
|
||||
}
|
||||
|
||||
fn insert(self: *Scope, ref: Ir.NullTerminatedString, decl: Decl) !void {
|
||||
const gpa = self.astgen.gpa;
|
||||
return self.decls.put(gpa, ref, decl);
|
||||
}
|
||||
|
||||
pub fn lookup(self: *Scope, ref: Ir.NullTerminatedString) ?Decl {
|
||||
fn lookup(self: *Scope, ref: Ir.NullTerminatedString) ?Decl {
|
||||
var current_scope: ?*Scope = self;
|
||||
while (current_scope) |scope| : (current_scope = scope.parent) {
|
||||
const result = scope.decls.get(ref);
|
||||
|
|
@ -149,6 +158,12 @@ const GenIr = struct {
|
|||
} });
|
||||
}
|
||||
|
||||
fn addStr(gi: *GenIr, tag: Ir.Inst.Tag, str: Ir.NullTerminatedString) !Ir.Inst.Ref {
|
||||
return add(gi, .{ .tag = tag, .data = .{
|
||||
.string = .{ .start = str },
|
||||
} });
|
||||
}
|
||||
|
||||
fn addUnaryNode(gi: *GenIr, tag: Ir.Inst.Tag, arg: Ir.Inst.Ref) !Ir.Inst.Ref {
|
||||
return add(gi, .{ .tag = tag, .data = .{
|
||||
.un = .{ .lhs = arg },
|
||||
|
|
@ -174,20 +189,21 @@ const GenIr = struct {
|
|||
} });
|
||||
}
|
||||
|
||||
fn makePayloadNode(gi: *GenIr, tag: Ir.Inst.Tag) !Ir.Inst.Index {
|
||||
fn addPayloadNode(gi: *GenIr, tag: Ir.Inst.Tag, extra: anytype) !Ir.Inst.Ref {
|
||||
const gpa = gi.astgen.gpa;
|
||||
const inst_index: Ir.Inst.Index = @enumFromInt(gi.astgen.instructions.items.len);
|
||||
try gi.astgen.instructions.append(gpa, .{
|
||||
.tag = tag,
|
||||
.data = .{
|
||||
.payload = .{ .payload_index = undefined },
|
||||
},
|
||||
});
|
||||
return inst_index;
|
||||
}
|
||||
try gi.instructions.ensureUnusedCapacity(gpa, 1);
|
||||
try gi.astgen.instructions.ensureUnusedCapacity(gpa, 1);
|
||||
|
||||
fn makeDeclaration(gi: *GenIr) !Ir.Inst.Index {
|
||||
return makePayloadNode(gi, .declaration);
|
||||
const payload_index = try gi.astgen.addExtra(extra);
|
||||
const new_index: Ir.Inst.Index = @enumFromInt(gi.astgen.instructions.items.len);
|
||||
gi.astgen.instructions.appendAssumeCapacity(.{
|
||||
.tag = tag,
|
||||
.data = .{ .payload = .{
|
||||
.payload_index = payload_index,
|
||||
} },
|
||||
});
|
||||
gi.instructions.appendAssumeCapacity(new_index);
|
||||
return new_index.toRef();
|
||||
}
|
||||
|
||||
fn makeBlockInst(gi: *GenIr, tag: Ir.Inst.Tag) !Ir.Inst.Index {
|
||||
|
|
@ -208,14 +224,20 @@ const GenIr = struct {
|
|||
const extra_len = @typeInfo(Ir.Inst.Knot).@"struct".fields.len + body.len;
|
||||
try self.astgen.extra.ensureUnusedCapacity(gpa, extra_len);
|
||||
|
||||
const knot_node = try makePayloadNode(self, .decl_knot);
|
||||
const inst_data = &self.astgen.instructions.items[@intFromEnum(knot_node)].data;
|
||||
const new_index: Ir.Inst.Index = @enumFromInt(self.astgen.instructions.items.len);
|
||||
try self.astgen.instructions.append(gpa, .{
|
||||
.tag = .decl_knot,
|
||||
.data = .{
|
||||
.payload = .{ .payload_index = undefined },
|
||||
},
|
||||
});
|
||||
const inst_data = &self.astgen.instructions.items[@intFromEnum(new_index)].data;
|
||||
inst_data.payload.payload_index = self.astgen.addExtraAssumeCapacity(
|
||||
Ir.Inst.Knot{ .body_len = @intCast(body.len) },
|
||||
);
|
||||
|
||||
self.astgen.appendBlockBody(body);
|
||||
return knot_node;
|
||||
return new_index;
|
||||
}
|
||||
|
||||
fn addVar(self: *GenIr) !Ir.Inst.Index {
|
||||
|
|
@ -304,6 +326,51 @@ const GenIr = struct {
|
|||
}
|
||||
};
|
||||
|
||||
/// Splat an IR data struct into the `extra` array.
|
||||
fn addExtra(astgen: *AstGen, extra: anytype) !u32 {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
try astgen.extra.ensureUnusedCapacity(astgen.gpa, fields.len);
|
||||
return addExtraAssumeCapacity(astgen, extra);
|
||||
}
|
||||
|
||||
/// Splat an IR data struct into the `extra` array.
|
||||
fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
const extra_index: u32 = @intCast(astgen.extra.items.len);
|
||||
astgen.extra.items.len += fields.len;
|
||||
setExtra(astgen, extra_index, extra);
|
||||
return extra_index;
|
||||
}
|
||||
|
||||
fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
var i = index;
|
||||
inline for (fields) |field| {
|
||||
astgen.extra.items[i] = switch (field.type) {
|
||||
u32 => @field(extra, field.name),
|
||||
Ir.Inst.Index => @intFromEnum(@field(extra, field.name)),
|
||||
Ir.Inst.Ref => @intFromEnum(@field(extra, field.name)),
|
||||
Ir.NullTerminatedString => @intFromEnum(@field(extra, field.name)),
|
||||
else => @compileError("bad field type"),
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn appendBlockBody(astgen: *AstGen, body: []const Ir.Inst.Index) void {
|
||||
return appendBlockBodyArrayList(astgen, &astgen.extra, body);
|
||||
}
|
||||
|
||||
fn appendBlockBodyArrayList(
|
||||
_: *AstGen,
|
||||
list: *std.ArrayListUnmanaged(u32),
|
||||
body: []const Ir.Inst.Index,
|
||||
) void {
|
||||
for (body) |inst_index| {
|
||||
list.appendAssumeCapacity(@intFromEnum(inst_index));
|
||||
}
|
||||
}
|
||||
|
||||
fn setDeclaration(
|
||||
decl_index: Ir.Inst.Index,
|
||||
args: struct {
|
||||
|
|
@ -311,11 +378,11 @@ fn setDeclaration(
|
|||
tag: Ir.Global.Tag,
|
||||
ref: Ir.Inst.Index,
|
||||
decl_node: *const Ast.Node,
|
||||
body_gi: *GenIr,
|
||||
body_block: *GenIr,
|
||||
is_constant: bool = true,
|
||||
},
|
||||
) !void {
|
||||
const astgen = args.body_gi.astgen;
|
||||
const astgen = args.body_block.astgen;
|
||||
const gpa = astgen.gpa;
|
||||
const extra_len = @typeInfo(Ir.Inst.Declaration).@"struct".fields.len;
|
||||
const global_index = astgen.globals.items.len;
|
||||
|
|
@ -324,22 +391,22 @@ fn setDeclaration(
|
|||
try astgen.globals.ensureUnusedCapacity(gpa, 1);
|
||||
try astgen.global_ref_table.ensureUnusedCapacity(gpa, 1);
|
||||
|
||||
if (astgen.global_ref_table.get(args.name)) |_| {
|
||||
return astgen.fail(.redefined_identifier, args.decl_node);
|
||||
}
|
||||
|
||||
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 },
|
||||
);
|
||||
|
||||
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_block.unstack();
|
||||
}
|
||||
|
||||
fn setCondBrPayload(
|
||||
|
|
@ -372,29 +439,6 @@ fn setCondBrPayload(
|
|||
astgen.appendBlockBody(else_body);
|
||||
}
|
||||
|
||||
fn addExtraAssumeCapacity(astgen: *AstGen, extra: anytype) u32 {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
const extra_index: u32 = @intCast(astgen.extra.items.len);
|
||||
astgen.extra.items.len += fields.len;
|
||||
setExtra(astgen, extra_index, extra);
|
||||
return extra_index;
|
||||
}
|
||||
|
||||
fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
var i = index;
|
||||
inline for (fields) |field| {
|
||||
astgen.extra.items[i] = switch (field.type) {
|
||||
u32 => @field(extra, field.name),
|
||||
Ir.Inst.Index => @intFromEnum(@field(extra, field.name)),
|
||||
Ir.Inst.Ref => @intFromEnum(@field(extra, field.name)),
|
||||
Ir.NullTerminatedString => @intFromEnum(@field(extra, field.name)),
|
||||
else => @compileError("bad field type"),
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
fn fail(
|
||||
self: *AstGen,
|
||||
tag: Ast.Error.Tag,
|
||||
|
|
@ -413,12 +457,6 @@ fn fail(
|
|||
return error.SemanticError;
|
||||
}
|
||||
|
||||
fn appendBlockBody(self: *AstGen, body: []const Ir.Inst.Index) void {
|
||||
for (body) |inst_index| {
|
||||
self.extra.appendAssumeCapacity(@intFromEnum(inst_index));
|
||||
}
|
||||
}
|
||||
|
||||
fn sliceFromNode(astgen: *const AstGen, node: *const Ast.Node) []const u8 {
|
||||
assert(node.loc.start <= node.loc.end);
|
||||
const source_bytes = astgen.tree.source;
|
||||
|
|
@ -427,8 +465,8 @@ fn sliceFromNode(astgen: *const AstGen, node: *const Ast.Node) []const u8 {
|
|||
|
||||
fn stringFromBytes(astgen: *AstGen, bytes: []const u8) error{OutOfMemory}!Ir.NullTerminatedString {
|
||||
const gpa = astgen.gpa;
|
||||
const str_index: u32 = @intCast(astgen.string_bytes.items.len);
|
||||
const string_bytes = &astgen.string_bytes;
|
||||
const str_index: u32 = @intCast(string_bytes.items.len);
|
||||
try string_bytes.appendSlice(gpa, bytes);
|
||||
|
||||
const key: []const u8 = string_bytes.items[str_index..];
|
||||
|
|
@ -453,6 +491,46 @@ fn stringFromNode(astgen: *AstGen, node: *const Ast.Node) !Ir.NullTerminatedStri
|
|||
return astgen.stringFromBytes(name_bytes);
|
||||
}
|
||||
|
||||
fn nullTerminatedString(astgen: *AstGen, str: Ir.NullTerminatedString) [:0]const u8 {
|
||||
const slice = astgen.string_bytes.items[@intFromEnum(str)..];
|
||||
return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0];
|
||||
}
|
||||
|
||||
fn qualifiedString(
|
||||
astgen: *AstGen,
|
||||
prefix: Ir.NullTerminatedString,
|
||||
relative: Ir.NullTerminatedString,
|
||||
) !Ir.NullTerminatedString {
|
||||
const gpa = astgen.gpa;
|
||||
const string_bytes = &astgen.string_bytes;
|
||||
const string_table = &astgen.string_table;
|
||||
const str_index: u32 = @intCast(string_bytes.items.len);
|
||||
|
||||
switch (prefix) {
|
||||
.empty => return relative,
|
||||
else => |prev| {
|
||||
try string_bytes.appendSlice(gpa, nullTerminatedString(astgen, prev));
|
||||
try string_bytes.append(gpa, '.');
|
||||
try string_bytes.appendSlice(gpa, nullTerminatedString(astgen, relative));
|
||||
|
||||
const key: []const u8 = string_bytes.items[str_index..];
|
||||
const gop = try string_table.getOrPutContextAdapted(gpa, key, StringIndexAdapter{
|
||||
.bytes = string_bytes,
|
||||
}, StringIndexContext{
|
||||
.bytes = string_bytes,
|
||||
});
|
||||
if (gop.found_existing) {
|
||||
string_bytes.shrinkRetainingCapacity(str_index);
|
||||
return @enumFromInt(gop.key_ptr.*);
|
||||
} else {
|
||||
gop.key_ptr.* = str_index;
|
||||
try string_bytes.append(gpa, 0);
|
||||
return @enumFromInt(str_index);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn unaryOp(
|
||||
gi: *GenIr,
|
||||
scope: *Scope,
|
||||
|
|
@ -565,7 +643,7 @@ fn expr(gi: *GenIr, scope: *Scope, optional_expr: ?*const Ast.Node) InnerError!I
|
|||
.choice_option_expr => unreachable,
|
||||
.choice_inner_expr => unreachable,
|
||||
.divert_expr => unreachable,
|
||||
.selector_expr => unreachable,
|
||||
.selector_expr => return fieldAccess(gi, scope, expr_node),
|
||||
.assign_stmt => unreachable,
|
||||
.block_stmt => unreachable,
|
||||
.content_stmt => unreachable,
|
||||
|
|
@ -825,7 +903,10 @@ fn contentExpr(block: *GenIr, scope: *Scope, expr_node: *const Ast.Node) InnerEr
|
|||
const result = try stringLiteral(block, child_node);
|
||||
_ = try block.addUnaryNode(.content_push, result);
|
||||
},
|
||||
.inline_logic_expr => _ = try inlineLogicExpr(block, scope, child_node),
|
||||
.inline_logic_expr => {
|
||||
const result = try inlineLogicExpr(block, scope, child_node);
|
||||
_ = try block.addUnaryNode(.content_push, result);
|
||||
},
|
||||
.if_stmt => _ = try ifStmt(block, scope, child_node),
|
||||
.multi_if_stmt => _ = try multiIfStmt(block, scope, child_node),
|
||||
.switch_stmt => _ = try switchStmt(block, scope, child_node),
|
||||
|
|
@ -929,6 +1010,169 @@ fn choiceStmt(
|
|||
astgen.extra.appendSliceAssumeCapacity(case_indexes.items[0..]);
|
||||
}
|
||||
|
||||
const Callee = union(enum) {
|
||||
field: struct {
|
||||
obj_ptr: Ir.Inst.Ref,
|
||||
/// Offset into `string_bytes`.
|
||||
field_name_start: Ir.NullTerminatedString,
|
||||
},
|
||||
direct: Ir.Inst.Ref,
|
||||
};
|
||||
|
||||
fn fieldAccess(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
assert(node.tag == .selector_expr);
|
||||
const data = node.data.bin;
|
||||
|
||||
assert(data.rhs.?.tag == .identifier);
|
||||
const field_str = try gi.astgen.stringFromNode(data.rhs.?);
|
||||
const lhs = try expr(gi, scope, data.lhs.?);
|
||||
|
||||
return gi.addPayloadNode(.field_ptr, Ir.Inst.Field{
|
||||
.lhs = lhs,
|
||||
.field_name_start = field_str,
|
||||
});
|
||||
}
|
||||
|
||||
/// calleeExpr generates the function part of a call expression (f in f(x)), but
|
||||
/// *not* the callee argument for the call. Its purpose is to distinguish
|
||||
/// between standard calls and method call syntax `a.b()`. Thus, if the lhs
|
||||
/// is a field access, we return using the `field` union field;
|
||||
/// otherwise, we use the `direct` union field.
|
||||
fn calleeExpr(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Callee {
|
||||
switch (node.tag) {
|
||||
.selector_expr => {
|
||||
const data = node.data.bin;
|
||||
const call_target = data.rhs.?;
|
||||
assert(call_target.tag == .identifier);
|
||||
|
||||
const field_str = try gi.astgen.stringFromNode(call_target);
|
||||
const lhs = try expr(gi, scope, data.lhs.?);
|
||||
return .{
|
||||
.field = .{ .obj_ptr = lhs, .field_name_start = field_str },
|
||||
};
|
||||
},
|
||||
.identifier => {
|
||||
return .{ .direct = try expr(gi, scope, node) };
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn callExpr(
|
||||
gi: *GenIr,
|
||||
scope: *Scope,
|
||||
node: *const Ast.Node,
|
||||
comptime call: enum { divert, call },
|
||||
) !Ir.Inst.Ref {
|
||||
const astgen = gi.astgen;
|
||||
const gpa = astgen.gpa;
|
||||
const data = node.data.bin;
|
||||
const callee_node = data.lhs.?;
|
||||
const args_node = data.rhs;
|
||||
const callee = try calleeExpr(gi, scope, callee_node);
|
||||
|
||||
const scratch_top = astgen.scratch.items.len;
|
||||
defer astgen.scratch.shrinkRetainingCapacity(scratch_top);
|
||||
const args_count = if (args_node) |n| n.data.list.items.?.len else 0;
|
||||
try astgen.scratch.resize(gpa, scratch_top + args_count);
|
||||
var scratch_index = scratch_top;
|
||||
|
||||
if (args_node) |n| {
|
||||
const args_list = n.data.list.items.?;
|
||||
for (args_list) |arg| {
|
||||
var arg_block = gi.makeSubBlock();
|
||||
defer arg_block.unstack();
|
||||
|
||||
_ = try expr(&arg_block, scope, arg);
|
||||
|
||||
const body = arg_block.instructionsSlice();
|
||||
try astgen.scratch.ensureUnusedCapacity(gpa, body.len);
|
||||
appendBlockBodyArrayList(astgen, &astgen.scratch, body);
|
||||
|
||||
astgen.scratch.items[scratch_index] = @intCast(astgen.scratch.items.len - scratch_top);
|
||||
scratch_index += 1;
|
||||
}
|
||||
}
|
||||
switch (callee) {
|
||||
.direct => |callee_obj| {
|
||||
const payload_index = try addExtra(astgen, Ir.Inst.Call{
|
||||
.callee = callee_obj,
|
||||
.args_len = @intCast(args_count),
|
||||
});
|
||||
if (args_count != 0) {
|
||||
try astgen.extra.appendSlice(gpa, astgen.scratch.items[scratch_top..]);
|
||||
}
|
||||
return gi.add(.{
|
||||
.tag = if (call == .divert) .divert else .call,
|
||||
.data = .{ .payload = .{
|
||||
.payload_index = payload_index,
|
||||
} },
|
||||
});
|
||||
},
|
||||
.field => |callee_field| {
|
||||
const payload_index = try addExtra(astgen, Ir.Inst.FieldCall{
|
||||
.obj_ptr = callee_field.obj_ptr,
|
||||
.field_name_start = callee_field.field_name_start,
|
||||
.args_len = @intCast(args_count),
|
||||
});
|
||||
if (args_count != 0) {
|
||||
try astgen.extra.appendSlice(gpa, astgen.scratch.items[scratch_top..]);
|
||||
}
|
||||
return gi.add(.{
|
||||
.tag = if (call == .divert) .field_divert else .field_call,
|
||||
.data = .{ .payload = .{
|
||||
.payload_index = payload_index,
|
||||
} },
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn divertExpr(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void {
|
||||
// FIXME: The AST should always have an args list for these nodes.
|
||||
const lhs = node.data.bin.lhs.?;
|
||||
switch (lhs.tag) {
|
||||
.identifier => {
|
||||
const callee = try calleeExpr(gi, scope, lhs);
|
||||
switch (callee) {
|
||||
.direct => |callee_obj| {
|
||||
const payload_index = try addExtra(gi.astgen, Ir.Inst.Call{
|
||||
.callee = callee_obj,
|
||||
.args_len = @intCast(0),
|
||||
});
|
||||
_ = try gi.add(.{
|
||||
.tag = .divert,
|
||||
.data = .{ .payload = .{
|
||||
.payload_index = payload_index,
|
||||
} },
|
||||
});
|
||||
},
|
||||
.field => |callee_field| {
|
||||
const payload_index = try addExtra(gi.astgen, Ir.Inst.FieldCall{
|
||||
.obj_ptr = callee_field.obj_ptr,
|
||||
.field_name_start = callee_field.field_name_start,
|
||||
.args_len = @intCast(0),
|
||||
});
|
||||
_ = try gi.add(.{
|
||||
.tag = .field_divert,
|
||||
.data = .{ .payload = .{
|
||||
.payload_index = payload_index,
|
||||
} },
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
else => {
|
||||
_ = try callExpr(gi, scope, lhs, .divert);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn divertStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void {
|
||||
const data = node.data.bin;
|
||||
return divertExpr(gi, scope, data.lhs.?);
|
||||
}
|
||||
|
||||
fn tempDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
|
||||
const identifier_node = decl_node.data.bin.lhs.?;
|
||||
const expr_node = decl_node.data.bin.rhs.?;
|
||||
|
|
@ -950,23 +1194,26 @@ fn tempDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
|
|||
|
||||
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();
|
||||
try gi.instructions.append(gpa, decl_inst);
|
||||
const decl_inst = try gi.add(.{
|
||||
.tag = .declaration,
|
||||
.data = .{ .payload = .{
|
||||
.payload_index = undefined,
|
||||
} },
|
||||
});
|
||||
|
||||
var decl_block = gi.makeSubBlock();
|
||||
defer decl_block.unstack();
|
||||
|
||||
_ = try expr(&decl_block, scope, expr_node);
|
||||
const var_inst = try decl_block.addVar();
|
||||
try setDeclaration(decl_inst, .{
|
||||
try setDeclaration(decl_inst.toIndex().?, .{
|
||||
.tag = .variable,
|
||||
.name = try astgen.stringFromNode(identifier_node),
|
||||
.ref = var_inst,
|
||||
.decl_node = decl_node,
|
||||
.body_gi = &decl_block,
|
||||
.body_block = &decl_block,
|
||||
.is_constant = decl_node.tag == .const_decl,
|
||||
});
|
||||
}
|
||||
|
|
@ -984,9 +1231,11 @@ fn blockInner(gi: *GenIr, parent_scope: *Scope, stmt_list: []*Ast.Node) !void {
|
|||
.content_stmt => try contentStmt(gi, &child_scope, inner_node),
|
||||
.choice_stmt => try choiceStmt(gi, &child_scope, inner_node),
|
||||
.expr_stmt => try exprStmt(gi, &child_scope, inner_node),
|
||||
.divert_stmt => try divertStmt(gi, &child_scope, inner_node),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
_ = try gi.addUnaryNode(.implicit_ret, .none);
|
||||
}
|
||||
|
||||
fn blockStmt(block: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void {
|
||||
|
|
@ -995,74 +1244,143 @@ fn blockStmt(block: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerErro
|
|||
try blockInner(block, scope, block_stmts);
|
||||
}
|
||||
|
||||
const main_knot_name: [:0]const u8 = "$__main__$";
|
||||
|
||||
fn defaultBlock(
|
||||
gi: *GenIr,
|
||||
scope: *Scope,
|
||||
body_node: *const Ast.Node,
|
||||
) InnerError!void {
|
||||
const astgen = gi.astgen;
|
||||
const gpa = astgen.gpa;
|
||||
const decl_inst = try gi.makeDeclaration();
|
||||
try gi.instructions.append(gpa, decl_inst);
|
||||
const decl_inst = try gi.addAsIndex(.{
|
||||
.tag = .declaration,
|
||||
.data = .{ .payload = .{
|
||||
.payload_index = undefined,
|
||||
} },
|
||||
});
|
||||
|
||||
var decl_scope = gi.makeSubBlock();
|
||||
defer decl_scope.unstack();
|
||||
|
||||
// TODO: Make sure that this value is concrete to omit check.
|
||||
const block_stmts = body_node.data.list.items orelse unreachable;
|
||||
const block_stmts = body_node.data.list.items.?;
|
||||
try blockInner(&decl_scope, scope, block_stmts);
|
||||
|
||||
const knot_inst = try decl_scope.addKnot();
|
||||
try setDeclaration(decl_inst, .{
|
||||
.tag = .knot,
|
||||
.decl_node = body_node,
|
||||
.name = try astgen.stringFromBytes("$__main__$"),
|
||||
.name = try astgen.stringFromBytes(Story.default_knot_name),
|
||||
.ref = knot_inst,
|
||||
.body_gi = &decl_scope,
|
||||
.decl_node = body_node,
|
||||
.body_block = &decl_scope,
|
||||
});
|
||||
}
|
||||
|
||||
fn stitchDecl(_: *GenIr, _: *Scope, _: *const Ast.Node) InnerError!void {}
|
||||
fn stitchDeclInner(
|
||||
gi: *GenIr,
|
||||
scope: *Scope,
|
||||
decl_node: *const Ast.Node,
|
||||
prototype_node: *const Ast.Node,
|
||||
body_node: ?*const Ast.Node,
|
||||
) InnerError!void {
|
||||
const astgen = gi.astgen;
|
||||
const prototype_data = prototype_node.data.bin;
|
||||
const identifier_node = prototype_data.lhs.?;
|
||||
const decl_inst = try gi.addAsIndex(.{
|
||||
.tag = .declaration,
|
||||
.data = .{ .payload = .{
|
||||
.payload_index = undefined,
|
||||
} },
|
||||
});
|
||||
|
||||
var decl_block = gi.makeSubBlock();
|
||||
defer decl_block.unstack();
|
||||
|
||||
if (prototype_data.rhs) |args_node| {
|
||||
const args_list = args_node.data.list.items.?;
|
||||
for (args_list) |arg| {
|
||||
assert(arg.tag == .parameter_decl);
|
||||
const arg_str = try astgen.stringFromNode(arg);
|
||||
const arg_inst = try decl_block.addStr(.param, arg_str);
|
||||
|
||||
// TODO: Maybe make decl accept a ref?
|
||||
try scope.insert(arg_str, .{
|
||||
.decl_node = arg,
|
||||
.inst_index = arg_inst.toIndex().?,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (body_node) |body| {
|
||||
try blockStmt(&decl_block, scope, body);
|
||||
} else {
|
||||
_ = try decl_block.addUnaryNode(.implicit_ret, .none);
|
||||
}
|
||||
|
||||
const knot_inst = try decl_block.addKnot();
|
||||
const name_str = try astgen.stringFromNode(identifier_node);
|
||||
try setDeclaration(decl_inst, .{
|
||||
.tag = .knot,
|
||||
.name = try astgen.qualifiedString(scope.namespace_prefix, name_str),
|
||||
.ref = knot_inst,
|
||||
.decl_node = decl_node,
|
||||
.body_block = &decl_block,
|
||||
});
|
||||
}
|
||||
|
||||
fn stitchDecl(gi: *GenIr, parent_scope: *Scope, decl_node: *const Ast.Node) InnerError!void {
|
||||
const knot_data = decl_node.data.bin;
|
||||
const prototype_node = knot_data.lhs.?;
|
||||
const body_node = knot_data.rhs.?;
|
||||
var decl_scope = parent_scope.makeChild();
|
||||
defer decl_scope.deinit();
|
||||
|
||||
return stitchDeclInner(gi, &decl_scope, decl_node, prototype_node, body_node);
|
||||
}
|
||||
|
||||
fn functionDecl(_: *GenIr, _: *Scope, _: *const Ast.Node) InnerError!void {}
|
||||
|
||||
fn knotDecl(gen: *GenIr, scope: *Scope, decl_node: *const Ast.Node) InnerError!void {
|
||||
const prototype_node = decl_node.data.knot_decl.prototype;
|
||||
const nested_decls_list = decl_node.data.knot_decl.children orelse return;
|
||||
const identifier_node = prototype_node.data.bin.lhs orelse unreachable;
|
||||
const ident_ref = try gen.astgen.stringFromNode(identifier_node);
|
||||
const knot_symbol = scope.lookup(ident_ref) orelse unreachable;
|
||||
const knot_scope = knot_symbol.knot.decl_scope;
|
||||
fn knotDecl(gi: *GenIr, parent_scope: *Scope, decl_node: *const Ast.Node) InnerError!void {
|
||||
const knot_data = decl_node.data.knot_decl;
|
||||
const prototype_node = knot_data.prototype;
|
||||
const identifier_node = prototype_node.data.bin.lhs.?;
|
||||
const nested_decls_list = knot_data.children.?;
|
||||
|
||||
var block_gen = gen.makeSubBlock();
|
||||
defer block_gen.deinit();
|
||||
var decl_scope = parent_scope.makeChild();
|
||||
defer decl_scope.deinit();
|
||||
|
||||
var start_index: usize = 0;
|
||||
const first_child = nested_decls_list[0];
|
||||
if (first_child.tag == .block_stmt) {
|
||||
try blockStmt(&block_gen, knot_scope, first_child);
|
||||
if (nested_decls_list.len > 1) start_index += 1 else return;
|
||||
try stitchDeclInner(gi, &decl_scope, decl_node, prototype_node, first_child);
|
||||
start_index += 1;
|
||||
} else {
|
||||
try stitchDeclInner(gi, &decl_scope, decl_node, prototype_node, null);
|
||||
}
|
||||
|
||||
const name_str = try gi.astgen.stringFromNode(identifier_node);
|
||||
try decl_scope.setNamespacePrefix(name_str);
|
||||
|
||||
for (nested_decls_list[start_index..]) |nested_decl_node| {
|
||||
switch (decl_node.tag) {
|
||||
.stitch_decl => try stitchDecl(gen, knot_scope, nested_decl_node),
|
||||
.function_decl => try functionDecl(gen, knot_scope, nested_decl_node),
|
||||
switch (nested_decl_node.tag) {
|
||||
.stitch_decl => try stitchDecl(gi, &decl_scope, nested_decl_node),
|
||||
.function_decl => try functionDecl(gi, &decl_scope, nested_decl_node),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
fn file(gi: *GenIr, scope: *Scope, file_node: *const Ast.Node) InnerError!void {
|
||||
const file_inst = try gi.addAsIndex(.{
|
||||
.tag = .file,
|
||||
.data = .{
|
||||
.payload = .{
|
||||
.payload_index = undefined,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
var start_index: usize = 0;
|
||||
var file_scope = root_gi.makeSubBlock();
|
||||
var file_scope = 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;
|
||||
|
|
@ -1077,9 +1395,9 @@ fn file(root_gi: *GenIr, scope: *Scope, file_node: *const Ast.Node) InnerError!v
|
|||
}
|
||||
for (nested_decls_list[start_index..]) |child_node| {
|
||||
switch (child_node.tag) {
|
||||
//.knot_decl => try knotDecl(gi, scope, child_node),
|
||||
//.stitch_decl => try stitchDecl(gi, scope, child_node),
|
||||
//.function_decl => try functionDecl(gi, scope, child_node),
|
||||
.knot_decl => try knotDecl(gi, scope, child_node),
|
||||
.stitch_decl => try stitchDecl(gi, scope, child_node),
|
||||
.function_decl => try functionDecl(gi, scope, child_node),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
|
@ -1103,6 +1421,7 @@ pub fn generate(gpa: std.mem.Allocator, tree: *const Ast) !Ir {
|
|||
var file_scope: Scope = .{
|
||||
.parent = null,
|
||||
.decls = .empty,
|
||||
.namespace_prefix = .empty,
|
||||
.astgen = &astgen,
|
||||
};
|
||||
var gen: GenIr = .{
|
||||
|
|
|
|||
121
src/Ir.zig
121
src/Ir.zig
|
|
@ -77,6 +77,12 @@ pub const Inst = struct {
|
|||
content_flush,
|
||||
choice_br,
|
||||
implicit_ret,
|
||||
call,
|
||||
divert,
|
||||
field_ptr,
|
||||
field_call,
|
||||
field_divert,
|
||||
param,
|
||||
};
|
||||
|
||||
pub const Data = union {
|
||||
|
|
@ -116,6 +122,11 @@ pub const Inst = struct {
|
|||
body_len: u32,
|
||||
};
|
||||
|
||||
pub const Field = struct {
|
||||
lhs: Ref,
|
||||
field_name_start: NullTerminatedString,
|
||||
};
|
||||
|
||||
pub const Block = struct {
|
||||
body_len: u32,
|
||||
};
|
||||
|
|
@ -151,6 +162,17 @@ pub const Inst = struct {
|
|||
body_len: u32,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Call = struct {
|
||||
args_len: u32,
|
||||
callee: Ref,
|
||||
};
|
||||
|
||||
pub const FieldCall = struct {
|
||||
args_len: u32,
|
||||
obj_ptr: Ref,
|
||||
field_name_start: NullTerminatedString,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Global = struct {
|
||||
|
|
@ -186,6 +208,7 @@ pub fn deinit(ir: *Ir, gpa: std.mem.Allocator) void {
|
|||
const Render = struct {
|
||||
gpa: std.mem.Allocator,
|
||||
prefix: Prefix,
|
||||
code: Ir,
|
||||
writer: *std.Io.Writer,
|
||||
|
||||
pub const Error = error{
|
||||
|
|
@ -259,6 +282,7 @@ const Render = struct {
|
|||
|
||||
fn renderBodyInner(r: *Render, ir: Ir, body_list: []const Inst.Index) Error!void {
|
||||
const io_w = r.writer;
|
||||
try r.prefix.writeIndent(io_w);
|
||||
try io_w.writeAll("{\n");
|
||||
{
|
||||
const old_len = try r.prefix.pushChildPrefix(r.gpa);
|
||||
|
|
@ -413,6 +437,88 @@ const Render = struct {
|
|||
return io_w.writeAll(")");
|
||||
}
|
||||
|
||||
fn writeTag(_: *Render, w: *std.Io.Writer, inst: Ir.Inst) Error!void {
|
||||
return w.print("{s}", .{@tagName(inst.tag)});
|
||||
}
|
||||
|
||||
fn writeIndent(self: *Render, w: *std.Io.Writer) Error!void {
|
||||
return self.prefix.writeIndent(w);
|
||||
}
|
||||
|
||||
fn writeString(self: *Render, w: *std.Io.Writer, str: Ir.NullTerminatedString) Error!void {
|
||||
return w.print("\"{s}\"", .{self.code.nullTerminatedString(str)});
|
||||
}
|
||||
|
||||
fn writeFieldPtr(self: *Render, w: *std.Io.Writer, inst: Ir.Inst) Error!void {
|
||||
const data = inst.data.payload;
|
||||
const extra = self.code.extraData(Inst.Field, data.payload_index).data;
|
||||
|
||||
try self.writeTag(w, inst);
|
||||
try w.writeAll("(");
|
||||
try self.renderInstRef(extra.lhs);
|
||||
try w.writeAll(", ");
|
||||
try self.writeString(w, extra.field_name_start);
|
||||
return w.writeAll(")");
|
||||
}
|
||||
|
||||
fn writeCall(
|
||||
self: *Render,
|
||||
w: *std.Io.Writer,
|
||||
inst: Ir.Inst,
|
||||
comptime kind: enum {
|
||||
field,
|
||||
direct,
|
||||
},
|
||||
) Error!void {
|
||||
const ExtraType = switch (kind) {
|
||||
.direct => Ir.Inst.Call,
|
||||
.field => Ir.Inst.FieldCall,
|
||||
};
|
||||
const data = inst.data.payload;
|
||||
const extra = self.code.extraData(ExtraType, data.payload_index);
|
||||
const args_len = extra.data.args_len;
|
||||
const body = self.code.extra[extra.end..];
|
||||
|
||||
try self.writeTag(w, inst);
|
||||
try w.writeAll("(");
|
||||
|
||||
switch (kind) {
|
||||
.direct => try self.renderInstRef(extra.data.callee),
|
||||
.field => {
|
||||
try self.renderInstRef(extra.data.obj_ptr);
|
||||
try w.writeAll(", ");
|
||||
try self.writeString(w, extra.data.field_name_start);
|
||||
},
|
||||
}
|
||||
try w.writeAll(", ");
|
||||
try w.writeAll("[\n");
|
||||
|
||||
var arg_start: u32 = args_len;
|
||||
var i: u32 = 0;
|
||||
while (i < args_len) : (i += 1) {
|
||||
const old_len = try self.prefix.pushChildPrefix(self.gpa);
|
||||
defer self.prefix.restore(old_len);
|
||||
|
||||
const arg_end = self.code.extra[extra.end + i];
|
||||
defer arg_start = arg_end;
|
||||
const arg_body = body[arg_start..arg_end];
|
||||
try self.renderBodyInner(self.code, @ptrCast(arg_body));
|
||||
try w.writeAll(",\n");
|
||||
}
|
||||
|
||||
try self.writeIndent(w);
|
||||
try w.writeAll("]");
|
||||
return w.writeAll(")");
|
||||
}
|
||||
|
||||
fn writeParam(self: *Render, w: *std.Io.Writer, inst: Ir.Inst) Error!void {
|
||||
const data = inst.data.string.start;
|
||||
try self.writeTag(w, inst);
|
||||
try w.writeAll("(");
|
||||
try self.writeString(w, data);
|
||||
return w.writeAll(")");
|
||||
}
|
||||
|
||||
fn renderInst(r: *Render, ir: Ir, index: Inst.Index) Error!void {
|
||||
const io_w = r.writer;
|
||||
const inst_index: u32 = @intFromEnum(index);
|
||||
|
|
@ -461,14 +567,25 @@ const Render = struct {
|
|||
.content_flush => try r.renderUnary(inst),
|
||||
.choice_br => try r.renderChoiceBr(ir, inst),
|
||||
.implicit_ret => try r.renderUnary(inst),
|
||||
.call => try r.writeCall(r.writer, inst, .direct),
|
||||
.divert => try r.writeCall(r.writer, inst, .direct),
|
||||
.field_call => try r.writeCall(r.writer, inst, .field),
|
||||
.field_divert => try r.writeCall(r.writer, inst, .field),
|
||||
.field_ptr => try r.writeFieldPtr(r.writer, inst),
|
||||
.param => try r.writeParam(r.writer, inst),
|
||||
}
|
||||
try io_w.writeAll("\n");
|
||||
}
|
||||
};
|
||||
|
||||
pub fn render(ir: Ir, gpa: std.mem.Allocator, writer: *std.Io.Writer) !void {
|
||||
std.debug.assert(ir.instructions.len > 0);
|
||||
var r = Render{ .gpa = gpa, .writer = writer, .prefix = .{} };
|
||||
assert(ir.instructions.len > 0);
|
||||
var r = Render{
|
||||
.gpa = gpa,
|
||||
.writer = writer,
|
||||
.prefix = .{},
|
||||
.code = ir,
|
||||
};
|
||||
defer r.prefix.deinit(gpa);
|
||||
|
||||
try r.renderInst(ir, @enumFromInt(0));
|
||||
|
|
|
|||
98
src/Sema.zig
98
src/Sema.zig
|
|
@ -19,9 +19,9 @@ const InnerError = error{
|
|||
};
|
||||
|
||||
const Ref = union(enum) {
|
||||
none,
|
||||
bool_true,
|
||||
bool_false,
|
||||
none,
|
||||
index: u32,
|
||||
constant: u32,
|
||||
global: u32,
|
||||
|
|
@ -64,11 +64,11 @@ fn addGlobal(sema: *Sema, name: Ir.NullTerminatedString) !Ref {
|
|||
fn getGlobal(sema: *Sema, name: Ir.NullTerminatedString) !Ref {
|
||||
const interned = try sema.getConstant(.{ .string = name });
|
||||
for (sema.ir.globals) |global| {
|
||||
if (name == global.name) {
|
||||
if (global.name == name) {
|
||||
return .{ .global = interned.constant };
|
||||
}
|
||||
}
|
||||
return sema.fail("unknown global variable");
|
||||
return fail(sema, "unknown global variable");
|
||||
}
|
||||
|
||||
fn irInteger(sema: *Sema, inst: Ir.Inst.Index) InnerError!Ref {
|
||||
|
|
@ -352,9 +352,7 @@ fn irDeclKnot(
|
|||
.sema = sema,
|
||||
.knot = &knot,
|
||||
};
|
||||
defer chunk.fixups.deinit(gpa);
|
||||
defer chunk.labels.deinit(gpa);
|
||||
defer chunk.inst_map.deinit(gpa);
|
||||
defer chunk.deinit(gpa);
|
||||
|
||||
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
||||
try blockBodyInner(sema, &chunk, body);
|
||||
|
|
@ -374,6 +372,63 @@ fn irDeclaration(sema: *Sema, parent_chunk: ?*Chunk, inst: Ir.Inst.Index) !void
|
|||
}
|
||||
}
|
||||
|
||||
fn irCall(_: *Sema, _: *Chunk, _: Ir.Inst.Index) !Ref {
|
||||
return .none;
|
||||
}
|
||||
|
||||
fn irDivert(
|
||||
sema: *Sema,
|
||||
chunk: *Chunk,
|
||||
inst: Ir.Inst.Index,
|
||||
comptime kind: enum { direct, field },
|
||||
) !void {
|
||||
const ExtraType = switch (kind) {
|
||||
.direct => Ir.Inst.Call,
|
||||
.field => Ir.Inst.FieldCall,
|
||||
};
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||
const extra = sema.ir.extraData(ExtraType, data.payload_index);
|
||||
const args_len = extra.data.args_len;
|
||||
const body = sema.ir.extra[extra.end..];
|
||||
|
||||
const callee = switch (kind) {
|
||||
.direct => chunk.resolveInst(extra.data.callee),
|
||||
.field => chunk.resolveInst(extra.data.obj_ptr),
|
||||
};
|
||||
_ = try chunk.doLoad(callee);
|
||||
|
||||
var arg_start: u32 = args_len;
|
||||
var i: u32 = 0;
|
||||
while (i < args_len) : (i += 1) {
|
||||
const arg_end = sema.ir.extra[extra.end + i];
|
||||
defer arg_start = arg_end;
|
||||
const arg_body = body[arg_start..arg_end];
|
||||
try blockBodyInner(sema, chunk, @ptrCast(arg_body));
|
||||
// FIXME: hack
|
||||
{
|
||||
const last_inst: Ir.Inst.Index = @enumFromInt(arg_body[arg_body.len - 1]);
|
||||
const val = chunk.resolveInst(last_inst.toRef());
|
||||
_ = try chunk.doLoad(val);
|
||||
}
|
||||
}
|
||||
_ = try chunk.addConstOp(.divert, @intCast(args_len));
|
||||
}
|
||||
|
||||
fn irFieldPtr(_: *Sema, _: *Chunk, _: Ir.Inst.Index) !Ref {
|
||||
return .none;
|
||||
}
|
||||
|
||||
// TODO: Check for duplicate parameters.
|
||||
fn irParam(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) !Ref {
|
||||
//const data = sema.ir.instructions[@intFromEnum(inst)].data.string;
|
||||
const local_index = chunk.knot.stack_size;
|
||||
// TODO: Add constraints on how many temporaries we can have.
|
||||
// max(u8) or max(u16) are most likey appropriate.
|
||||
chunk.knot.arity += 1;
|
||||
chunk.knot.stack_size += 1;
|
||||
return .{ .local = local_index };
|
||||
}
|
||||
|
||||
fn blockBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) InnerError!void {
|
||||
const gpa = sema.gpa;
|
||||
|
||||
|
|
@ -433,6 +488,18 @@ fn blockBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) Inner
|
|||
continue;
|
||||
},
|
||||
.implicit_ret => try irImplicitRet(sema, chunk, inst),
|
||||
.call => try irCall(sema, chunk, inst),
|
||||
.divert => {
|
||||
try irDivert(sema, chunk, inst, .direct);
|
||||
continue;
|
||||
},
|
||||
.field_call => try irCall(sema, chunk, inst),
|
||||
.field_divert => {
|
||||
try irDivert(sema, chunk, inst, .field);
|
||||
continue;
|
||||
},
|
||||
.field_ptr => try irFieldPtr(sema, chunk, inst),
|
||||
.param => try irParam(sema, chunk, inst),
|
||||
};
|
||||
try chunk.inst_map.put(gpa, inst, ref);
|
||||
}
|
||||
|
|
@ -473,6 +540,12 @@ const Chunk = struct {
|
|||
code_offset: u32,
|
||||
};
|
||||
|
||||
fn deinit(chunk: *Chunk, gpa: std.mem.Allocator) void {
|
||||
chunk.fixups.deinit(gpa);
|
||||
chunk.labels.deinit(gpa);
|
||||
chunk.inst_map.deinit(gpa);
|
||||
}
|
||||
|
||||
fn addByteOp(chunk: *Chunk, op: Story.Opcode) error{OutOfMemory}!Ref {
|
||||
const gpa = chunk.sema.gpa;
|
||||
const bytecode = &chunk.knot.bytecode;
|
||||
|
|
@ -627,13 +700,13 @@ pub const CompiledStory = struct {
|
|||
pub fn buildRuntime(
|
||||
self: *CompiledStory,
|
||||
gpa: std.mem.Allocator,
|
||||
ir: Ir,
|
||||
ir: *Ir,
|
||||
story: *Story,
|
||||
) !void {
|
||||
const globals_len = self.globals.len + self.knots.len;
|
||||
const constants_pool = &story.constants_pool;
|
||||
try constants_pool.ensureUnusedCapacity(gpa, self.constants.len);
|
||||
try story.paths.ensureUnusedCapacity(gpa, self.knots.len);
|
||||
try story.globals.ensureUnusedCapacity(gpa, @intCast(self.globals.len));
|
||||
try story.globals.ensureUnusedCapacity(gpa, @intCast(globals_len));
|
||||
|
||||
for (self.constants) |constant| {
|
||||
switch (constant) {
|
||||
|
|
@ -656,15 +729,18 @@ pub const CompiledStory = struct {
|
|||
story.globals.putAssumeCapacity(name_bytes, null);
|
||||
}
|
||||
for (self.knots) |*knot| {
|
||||
const knot_name = ir.nullTerminatedString(knot.name);
|
||||
const runtime_chunk: *Object.ContentPath = try .create(story, .{
|
||||
.name = try .create(story, ir.nullTerminatedString(knot.name)),
|
||||
.name = try .create(story, knot_name),
|
||||
.arity = @intCast(knot.arity),
|
||||
.locals_count = @intCast(knot.stack_size - knot.arity),
|
||||
.const_pool = try knot.constants.toOwnedSlice(gpa),
|
||||
.bytes = try knot.bytecode.toOwnedSlice(gpa),
|
||||
});
|
||||
story.paths.appendAssumeCapacity(&runtime_chunk.base);
|
||||
story.globals.putAssumeCapacity(knot_name, &runtime_chunk.base);
|
||||
}
|
||||
story.string_bytes = ir.string_bytes;
|
||||
ir.string_bytes = &.{};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
111
src/Story.zig
111
src/Story.zig
|
|
@ -17,11 +17,14 @@ choice_index: usize = 0,
|
|||
current_choices: std.ArrayListUnmanaged(Choice) = .empty,
|
||||
constants_pool: std.ArrayListUnmanaged(*Object) = .empty,
|
||||
globals: std.StringHashMapUnmanaged(?*Object) = .empty,
|
||||
paths: std.ArrayListUnmanaged(*Object) = .empty,
|
||||
stack: std.ArrayListUnmanaged(?*Object) = .empty,
|
||||
call_stack: std.ArrayListUnmanaged(CallFrame) = .empty,
|
||||
stack_max: usize = 128,
|
||||
gc_objects: std.SinglyLinkedList = .{},
|
||||
// FIXME: This was a hack to keep string bytes alive.
|
||||
string_bytes: []const u8 = &.{},
|
||||
|
||||
pub const default_knot_name: [:0]const u8 = "$__main__$";
|
||||
|
||||
pub const CallFrame = struct {
|
||||
ip: usize,
|
||||
|
|
@ -104,9 +107,9 @@ pub fn deinit(story: *Story) void {
|
|||
story.current_choices.deinit(gpa);
|
||||
story.constants_pool.deinit(gpa);
|
||||
story.globals.deinit(gpa);
|
||||
story.paths.deinit(gpa);
|
||||
story.stack.deinit(gpa);
|
||||
story.call_stack.deinit(gpa);
|
||||
gpa.free(story.string_bytes);
|
||||
}
|
||||
|
||||
pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
||||
|
|
@ -129,8 +132,14 @@ pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
|||
try writer.writeAll("\n");
|
||||
try writer.writeAll("=== Knots ===\n");
|
||||
|
||||
for (story.paths.items) |path_object| {
|
||||
try story_dumper.dump(@ptrCast(path_object));
|
||||
var knots_iter = story.globals.iterator();
|
||||
while (knots_iter.next()) |entry| {
|
||||
if (entry.value_ptr.*) |global| {
|
||||
switch (global.tag) {
|
||||
.content_path => try story_dumper.dump(@ptrCast(global)),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,16 +150,18 @@ pub fn trace(story: *Story, writer: *std.Io.Writer, frame: *CallFrame) !void {
|
|||
const stack = &story.stack;
|
||||
const stack_top = story.stack.items.len;
|
||||
if (stack_top > 0) {
|
||||
const last_slot = stack.items[stack.items.len - 1];
|
||||
for (stack.items[frame.sp .. stack.items.len - 1]) |slot| {
|
||||
if (slot) |object| {
|
||||
try story_dumper.dumpObject(object);
|
||||
} else {
|
||||
try writer.writeAll("null");
|
||||
// FIXME: There has to be a better way to do this.
|
||||
if (stack_top > 1) {
|
||||
for (stack.items[frame.sp .. stack.items.len - 1]) |slot| {
|
||||
if (slot) |object| {
|
||||
try story_dumper.dumpObject(object);
|
||||
} else {
|
||||
try writer.writeAll("null");
|
||||
}
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
if (last_slot) |object| {
|
||||
if (stack.items[stack.items.len - 1]) |object| {
|
||||
try story_dumper.dumpObject(object);
|
||||
} else {
|
||||
try writer.writeAll("null");
|
||||
|
|
@ -198,7 +209,7 @@ fn getConstant(story: *Story, frame: *CallFrame, offset: u8) !*Object {
|
|||
}
|
||||
|
||||
fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?*Object {
|
||||
const stack_top = vm.stack.capacity;
|
||||
const stack_top = vm.stack.items.len;
|
||||
const stack_offset = frame.sp + offset;
|
||||
assert(stack_top > stack_offset);
|
||||
|
||||
|
|
@ -206,19 +217,21 @@ fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?*Object {
|
|||
}
|
||||
|
||||
fn setLocal(vm: *Story, frame: *CallFrame, offset: u8, value: *Object) void {
|
||||
const stack_top = vm.stack.capacity;
|
||||
const stack_top = vm.stack.items.len;
|
||||
const stack_offset = frame.sp + offset;
|
||||
assert(stack_top > stack_offset);
|
||||
|
||||
vm.stack.items[stack_offset] = value;
|
||||
}
|
||||
|
||||
// TODO: This should probably check the constants table first.
|
||||
fn getGlobal(vm: *Story, key: *const Object.String) !*Object {
|
||||
const key_bytes = key.bytes[0..key.length];
|
||||
const val = vm.globals.get(key_bytes) orelse return error.InvalidVariable;
|
||||
return val orelse unreachable;
|
||||
}
|
||||
|
||||
// TODO: This should probably check the constants table first.
|
||||
fn setGlobal(vm: *Story, key: *const Object.String, value: *Object) !void {
|
||||
const key_bytes = key.bytes[0..key.length];
|
||||
return vm.globals.putAssumeCapacity(key_bytes, value);
|
||||
|
|
@ -231,12 +244,12 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
}
|
||||
if (vm.isCallStackEmpty()) return .empty;
|
||||
|
||||
const frame = vm.currentFrame();
|
||||
const code = std.mem.bytesAsSlice(Opcode, frame.callee.bytes);
|
||||
var stream_writer = std.Io.Writer.Allocating.init(gpa);
|
||||
defer stream_writer.deinit();
|
||||
|
||||
while (true) {
|
||||
const frame = vm.currentFrame();
|
||||
const code = std.mem.bytesAsSlice(Opcode, frame.callee.bytes);
|
||||
if (vm.dump_writer) |w| {
|
||||
vm.trace(w, frame) catch {};
|
||||
}
|
||||
|
|
@ -447,6 +460,16 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
|
||||
frame.ip = branch_dispatch.dest_offset;
|
||||
},
|
||||
.divert => {
|
||||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
frame.ip += 2;
|
||||
|
||||
if (peekStack(vm, arg_offset)) |knot| {
|
||||
try divertToKnot(vm, @ptrCast(knot));
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
},
|
||||
else => return error.InvalidInstruction,
|
||||
}
|
||||
}
|
||||
|
|
@ -462,27 +485,37 @@ pub fn advance(story: *Story, gpa: std.mem.Allocator) ![]const u8 {
|
|||
return content.toOwnedSlice(gpa);
|
||||
}
|
||||
|
||||
fn divert(vm: *Story, path_name: []const u8) !void {
|
||||
const gpa = vm.allocator;
|
||||
const path_object: ?*Object.ContentPath = blk: {
|
||||
for (vm.paths.items) |object| {
|
||||
const current_path: *Object.ContentPath = @ptrCast(object);
|
||||
const current_name = current_path.name;
|
||||
// TODO(Brett): We probably should create a method for doing this.
|
||||
const name_bytes = current_name.bytes[0..current_name.length];
|
||||
if (std.mem.eql(u8, name_bytes, path_name)) break :blk current_path;
|
||||
pub fn getKnot(vm: *Story, name: []const u8) ?*Object.ContentPath {
|
||||
const knot: ?*Object.ContentPath = blk: {
|
||||
if (vm.globals.get(name)) |object| {
|
||||
break :blk @ptrCast(object);
|
||||
}
|
||||
break :blk null;
|
||||
};
|
||||
if (path_object) |path| {
|
||||
// TODO(Brett): Add arguments?
|
||||
const stack_needed = path.arity + path.locals_count;
|
||||
const stack_ptr = vm.stack.items.len;
|
||||
try vm.stack.ensureUnusedCapacity(gpa, stack_needed);
|
||||
try vm.call_stack.ensureUnusedCapacity(gpa, 1);
|
||||
return knot;
|
||||
}
|
||||
|
||||
vm.stack.appendNTimesAssumeCapacity(null, stack_needed);
|
||||
vm.call_stack.appendAssumeCapacity(.{ .ip = 0, .sp = stack_ptr, .callee = path });
|
||||
// TODO(Brett): Add arguments?
|
||||
fn divertToKnot(vm: *Story, knot: *Object.ContentPath) !void {
|
||||
const gpa = vm.allocator;
|
||||
const stack_ptr = vm.stack.items.len - knot.arity;
|
||||
const stack_needed = knot.locals_count;
|
||||
|
||||
try vm.stack.ensureUnusedCapacity(gpa, stack_needed);
|
||||
try vm.call_stack.ensureUnusedCapacity(gpa, 1);
|
||||
|
||||
vm.call_stack.appendAssumeCapacity(.{
|
||||
.callee = knot,
|
||||
.ip = 0,
|
||||
.sp = stack_ptr,
|
||||
});
|
||||
vm.stack.appendNTimesAssumeCapacity(null, stack_needed);
|
||||
vm.can_advance = true;
|
||||
}
|
||||
|
||||
fn divert(vm: *Story, knot_name: []const u8) !void {
|
||||
return if (getKnot(vm, knot_name)) |knot| {
|
||||
return divertToKnot(vm, knot);
|
||||
} else return error.InvalidPath;
|
||||
}
|
||||
|
||||
|
|
@ -535,7 +568,6 @@ pub fn loadFromString(
|
|||
try options.stderr_writer.flush();
|
||||
return error.CompilationFailed;
|
||||
}
|
||||
|
||||
if (options.dump_ir) {
|
||||
if (options.dump_writer) |w| {
|
||||
try w.writeAll("=== Semantic IR ===\n");
|
||||
|
|
@ -552,8 +584,13 @@ pub fn loadFromString(
|
|||
.can_advance = false,
|
||||
.dump_writer = options.dump_writer,
|
||||
};
|
||||
try compiled.buildRuntime(gpa, sem_ir, &story);
|
||||
try story.divert("$__main__$");
|
||||
story.can_advance = true;
|
||||
errdefer story.deinit();
|
||||
|
||||
try compiled.buildRuntime(gpa, &sem_ir, &story);
|
||||
|
||||
if (story.getKnot(Story.default_knot_name)) |knot| {
|
||||
try story.divertToKnot(knot);
|
||||
story.can_advance = true;
|
||||
}
|
||||
return story;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,8 +110,8 @@ pub fn dumpInst(
|
|||
.store => return d.dumpByteInst(path, offset, op),
|
||||
.load_global => return d.dumpGlobalInst(path, offset, op),
|
||||
.store_global => return d.dumpGlobalInst(path, offset, op),
|
||||
.call => return d.dumpGlobalInst(path, offset, op),
|
||||
.divert => return d.dumpGlobalInst(path, offset, op),
|
||||
.call => return d.dumpByteInst(path, offset, op),
|
||||
.divert => return d.dumpByteInst(path, offset, op),
|
||||
.jmp => return d.dumpJumpInst(path, offset, op, .relative),
|
||||
.jmp_t => return d.dumpJumpInst(path, offset, op, .relative),
|
||||
.jmp_f => return d.dumpJumpInst(path, offset, op, .relative),
|
||||
|
|
@ -143,6 +143,7 @@ pub fn dump(d: Dumper, path: *const Object.ContentPath) !void {
|
|||
var index: usize = 0;
|
||||
while (index < path.bytes.len) {
|
||||
index = try d.dumpInst(path, index, false);
|
||||
try d.writer.flush();
|
||||
}
|
||||
return d.writer.flush();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue