feat: function calls

This commit is contained in:
Brett Broadhurst 2026-03-29 15:52:34 -06:00
parent 11d99fba38
commit d08e753664
Failed to generate hash of commit
99 changed files with 881 additions and 148 deletions

View file

@ -280,6 +280,13 @@ const GenIr = struct {
self.instructions.items[self.instructions_top..];
}
fn endsWithNoReturn(self: *GenIr) bool {
if (self.isEmpty()) return false;
const last_inst_index = self.instructions.items[self.instructions.items.len - 1];
const last_inst = self.astgen.instructions.items[@intFromEnum(last_inst_index)];
return last_inst.isNoReturn();
}
fn makeSubBlock(self: *GenIr) GenIr {
return .{
.astgen = self.astgen,
@ -730,7 +737,7 @@ fn expr(gi: *GenIr, scope: *Scope, optional_node: ?*const Ast.Node) InnerError!I
.logical_greater_or_equal_expr => return binaryOp(gi, scope, node, .cmp_gte),
.logical_lesser_expr => return binaryOp(gi, scope, node, .cmp_lt),
.logical_lesser_or_equal_expr => return binaryOp(gi, scope, node, .cmp_lte),
.call_expr => unreachable,
.call_expr => return callExpr(gi, scope, node, .call),
.choice_expr => unreachable,
.choice_start_expr => unreachable,
.choice_option_expr => unreachable,
@ -1171,7 +1178,9 @@ fn callExpr(
const scratch_top = astgen.scratch.items.len;
defer astgen.scratch.shrinkRetainingCapacity(scratch_top);
const arguments: ?[]*Ast.Node = if (args_node) |n| n.data.list.items.? else null;
// FIXME: List nodes should not have optional slices.
// This hack is an abomination.
const arguments: ?[]*Ast.Node = if (args_node) |n| if (n.data.list.items) |items| items else null else null;
const args_count = if (arguments) |args| args.len else 0;
try astgen.scratch.resize(gpa, scratch_top + args_count);
@ -1275,10 +1284,20 @@ fn divertExpr(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void {
}
fn divertStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void {
// TODO: Revisit this.
const data = node.data.bin;
return divertExpr(gi, scope, data.lhs.?);
}
fn returnStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) !void {
// TODO: Revisit this.
const ret_arg = if (node.data.bin.lhs) |lhs| blk: {
const arg_inst = try expr(gi, scope, lhs);
break :blk arg_inst;
} else .none;
_ = try gi.addUnaryNode(.ret, ret_arg);
}
fn tempDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
const astgen = gi.astgen;
const identifier_node = decl_node.data.bin.lhs.?;
@ -1327,20 +1346,23 @@ fn blockInner(gi: *GenIr, parent_scope: *Scope, stmt_list: []*Ast.Node) !void {
var child_scope = parent_scope.makeChild();
defer child_scope.deinit();
for (stmt_list) |inner_node| {
_ = switch (inner_node.tag) {
.var_decl => try varDecl(gi, &child_scope, inner_node),
.const_decl => try varDecl(gi, &child_scope, inner_node),
.temp_decl => try tempDecl(gi, &child_scope, inner_node),
.assign_stmt => try assignStmt(gi, &child_scope, inner_node),
.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),
for (stmt_list) |node| {
_ = switch (node.tag) {
.var_decl => try varDecl(gi, &child_scope, node),
.const_decl => try varDecl(gi, &child_scope, node),
.temp_decl => try tempDecl(gi, &child_scope, node),
.assign_stmt => try assignStmt(gi, &child_scope, node),
.content_stmt => try contentStmt(gi, &child_scope, node),
.choice_stmt => try choiceStmt(gi, &child_scope, node),
.expr_stmt => try exprStmt(gi, &child_scope, node),
.divert_stmt => try divertStmt(gi, &child_scope, node),
.return_stmt => try returnStmt(gi, &child_scope, node),
inline else => |e| @panic("Unexpected node: " ++ @tagName(e)),
};
}
if (!gi.endsWithNoReturn()) {
_ = try gi.addUnaryNode(.implicit_ret, .none);
}
}
fn blockStmt(block: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void {
@ -1415,9 +1437,10 @@ fn stitchDeclInner(
}
}
if (body_node) |body| {
try blockStmt(&decl_block, scope, body);
const body_list = body.data.list.items.?;
try blockInner(&decl_block, scope, body_list);
} else {
_ = try decl_block.addUnaryNode(.implicit_ret, .none);
try blockInner(&decl_block, scope, &.{});
}
const decl_str = try astgen.strFromNode(identifier_node);
@ -1440,7 +1463,65 @@ fn stitchDecl(gi: *GenIr, parent_scope: *Scope, decl_node: *const Ast.Node) Inne
return stitchDeclInner(gi, &decl_scope, decl_node, prototype_node.?, body_node);
}
fn functionDecl(_: *GenIr, _: *Scope, _: *const Ast.Node) InnerError!void {}
fn functionDeclInner(
gi: *GenIr,
scope: *Scope,
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 = undefined },
});
var decl_block = gi.makeSubBlock();
defer decl_block.unstack();
const stitch_inst = try decl_block.makePayloadNode(.decl_function);
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.strFromNode(arg);
const arg_inst = try decl_block.addStrTok(.param, arg_str.index, arg.loc.start);
// TODO: Maybe make decl accept a ref?
try scope.insert(arg_str.index, .{
.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 decl_str = try astgen.strFromNode(identifier_node);
try setDeclStitchPayload(stitch_inst, &decl_block);
try setDeclaration(decl_inst, .{
.name = decl_str.index,
.value = stitch_inst,
.gi = gi,
.node = node,
});
}
fn functionDecl(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 functionDeclInner(gi, &decl_scope, decl_node, prototype_node.?, body_node);
}
fn knotDecl(gi: *GenIr, parent_scope: *Scope, decl_node: *const Ast.Node) InnerError!void {
const astgen = gi.astgen;

View file

@ -147,10 +147,16 @@ pub const Inst = struct {
pub const Tag = enum {
file,
/// Uses the `payload` union field.
declaration,
/// Uses the `payload` union field.
decl_var,
/// Uses the `payload` union field.
decl_knot,
/// Uses the `payload` union field.
decl_stitch,
/// Uses the `payload` union field.
decl_function,
/// Uses the `str_tok` union field.
decl_ref,
alloc,
@ -199,6 +205,9 @@ pub const Inst = struct {
content_push,
content_flush,
choice_br,
// Uses the `un` union field.
ret,
// Uses the `un` union field.
implicit_ret,
call,
divert,
@ -264,6 +273,11 @@ pub const Inst = struct {
body_len: u32,
};
pub const Function = struct {
/// Number of instructions for the function's body block.
body_len: u32,
};
pub const Field = struct {
lhs: Ref,
field_name_start: NullTerminatedString,
@ -324,4 +338,56 @@ pub const Inst = struct {
byte_offset: u32,
};
};
pub fn isNoReturn(inst: Inst) bool {
return switch (inst.tag) {
.file,
.declaration,
.decl_var,
.decl_knot,
.decl_stitch,
.decl_function,
.decl_ref,
.alloc,
.load,
.store,
.add,
.sub,
.mul,
.div,
.mod,
.neg,
.not,
.bool_and,
.bool_or,
.cmp_eq,
.cmp_neq,
.cmp_gt,
.cmp_gte,
.cmp_lt,
.cmp_lte,
.int,
.float,
.str,
.block,
.condbr,
.switch_br,
.content_push,
.content_flush,
.choice_br,
.call,
.divert,
.field_ptr,
.field_call,
.field_divert,
.param,
=> false,
.ret,
.implicit_ret,
.@"break",
.done,
.exit,
=> true,
};
}
};

View file

@ -29,6 +29,7 @@ pub const ValueInfo = union(enum) {
variable: InternPool.Index,
knot: InternPool.Index,
stitch: InternPool.Index,
function: InternPool.Index,
temp: u32,
};
@ -294,7 +295,11 @@ pub const Builder = struct {
const local_index = try self.getOrPutConstantIndex(index);
try self.addConstOp(.load_const, @intCast(local_index));
},
.variable, .knot, .stitch => |index| {
.variable,
.knot,
.stitch,
.function,
=> |index| {
const local_index = try self.getOrPutConstantIndex(index);
try self.addConstOp(.load_global, @intCast(local_index));
},
@ -536,7 +541,7 @@ fn irDeclRef(
const ident = try sema.lookupIdentifier(builder, ip_index, src_loc);
if (inline_block) {
switch (ident.tag) {
.knot, .stitch => unreachable,
.knot, .stitch, .function => unreachable,
.var_const => return sema.resolveGlobalDecl(builder, ip_index, src_loc),
.var_mut => return sema.fail(
src_loc,
@ -548,6 +553,7 @@ fn irDeclRef(
switch (ident.tag) {
.knot => return .{ .knot = ip_index },
.stitch => return .{ .stitch = ip_index },
.function => return .{ .function = ip_index },
.var_mut => return .{ .variable = ip_index },
.var_const => return .{ .variable = ip_index },
}
@ -577,8 +583,9 @@ fn irStore(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void
try builder.addConstOp(.store_global, @intCast(local_index));
},
.temp => |temp| try builder.addConstOp(.store, @intCast(temp)),
.stitch => |_| return sema.fail(src, "could not assign to stitch", .{}),
.knot => |_| return sema.fail(src, "could not assign to knot", .{}),
.stitch => |_| return sema.fail(src, "could not assign to stitch", .{}),
.function => |_| return sema.fail(src, "could not assign to function", .{}),
}
try builder.addByteOp(.pop);
@ -757,12 +764,66 @@ fn irChoiceBr(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!vo
}
}
fn irRet(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void {
const data = sema.ir.instructions[@intFromEnum(inst)].data.un;
const value = sema.resolveInst(data.lhs);
if (value != .none) try builder.ensureLoad(value);
try builder.addByteOp(.ret);
}
fn irImplicitRet(_: *Sema, builder: *Builder, _: Ir.Inst.Index) InnerError!void {
try builder.addByteOp(.exit);
}
fn irCall(_: *Sema, _: *Builder, _: Ir.Inst.Index) !ValueInfo {
return .none;
fn irCall(
sema: *Sema,
builder: *Builder,
inst: Ir.Inst.Index,
comptime kind: enum { direct, field },
) !ValueInfo {
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.extra_index);
const body = sema.ir.extra[extra.end..];
const callee_src: SrcLoc = .{ .src_offset = data.src_offset };
switch (kind) {
.direct => {
const callee = sema.resolveInst(extra.data.callee);
_ = try analyzeCallTarget(sema, builder, callee_src, callee);
},
.field => {
const callee = sema.resolveInst(extra.data.obj_ptr);
const target = try analyzeCallTarget(sema, builder, callee_src, callee);
const ip_index = try sema.module.intern_pool.getOrPutStr(
sema.gpa,
extra.data.field_name_start,
);
const e = try sema.lookupInNamespace(target.namespace.?, ip_index, callee_src);
switch (e.tag) {
.function => {
const local_index = try builder.getOrPutConstantIndex(ip_index);
_ = try builder.addConstOp(.load_attr, @intCast(local_index));
},
else => return sema.fail(callee_src, "invalid call target", .{}),
}
},
}
const args_len = extra.data.args_len;
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];
const arg_value = try analyzeBodyInner(sema, builder, @ptrCast(arg_body), false);
if (arg_value != .none) try builder.ensureLoad(arg_value);
}
try builder.addConstOp(.call, @intCast(args_len));
return .stack;
}
fn irDivert(
@ -809,13 +870,8 @@ fn irDivert(
const arg_end = sema.ir.extra[extra.end + i];
defer arg_start = arg_end;
const arg_body = body[arg_start..arg_end];
_ = try analyzeBodyInner(sema, builder, @ptrCast(arg_body), false);
// FIXME: hack
{
const last_inst: Ir.Inst.Index = @enumFromInt(arg_body[arg_body.len - 1]);
const val = sema.resolveInst(last_inst.toRef());
try builder.ensureLoad(val);
}
const arg_value = try analyzeBodyInner(sema, builder, @ptrCast(arg_body), false);
if (arg_value != .none) try builder.ensureLoad(arg_value);
}
try builder.addConstOp(.divert, @intCast(args_len));
}
@ -864,6 +920,21 @@ fn analyzeArithmeticArg(
}
}
fn analyzeCallTarget(
sema: *Sema,
builder: *Builder,
src: SrcLoc,
callee: ValueInfo,
) !Module.Namespace.Decl {
switch (callee) {
.function => |ip_index| {
try builder.ensureLoad(callee);
return sema.lookupIdentifier(builder, ip_index, src);
},
else => return sema.fail(src, "invalid call target", .{}),
}
}
fn analyzeDivertTarget(
sema: *Sema,
builder: *Builder,
@ -894,6 +965,7 @@ fn analyzeBodyInner(
.decl_var => unreachable, // never present inside block bodies
.decl_knot => unreachable, // never present inside block bodies
.decl_stitch => unreachable, // never present inside block bodies
.decl_function => unreachable, // never present inside block bodies
.alloc => try irAlloc(sema, builder, inst),
.store => {
try irStore(sema, builder, inst);
@ -919,6 +991,10 @@ fn analyzeBodyInner(
.cmp_gt => try irBinaryOp(sema, builder, inst, .cmp_gt),
.cmp_gte => try irBinaryOp(sema, builder, inst, .cmp_gte),
.decl_ref => try irDeclRef(sema, builder, inst, inline_block),
.ret => {
try irRet(sema, builder, inst);
continue;
},
.implicit_ret => {
try irImplicitRet(sema, builder, inst);
continue;
@ -948,12 +1024,12 @@ fn analyzeBodyInner(
try irSwitchBr(sema, builder, inst);
continue;
},
.call => try irCall(sema, builder, inst),
.call => try irCall(sema, builder, inst, .direct),
.field_call => try irCall(sema, builder, inst, .field),
.divert => {
try irDivert(sema, builder, inst, .direct);
continue;
},
.field_call => try irCall(sema, builder, inst),
.field_divert => {
try irDivert(sema, builder, inst, .field);
continue;
@ -968,6 +1044,7 @@ fn analyzeBodyInner(
return result;
}
// TODO: No return allowed.
pub fn analyzeStitch(
sema: *Sema,
builder: *Builder,
@ -979,6 +1056,19 @@ pub fn analyzeStitch(
_ = try analyzeBodyInner(sema, builder, body, false);
}
// TODO: No diverts allowed.
pub fn analyzeFunction(
sema: *Sema,
builder: *Builder,
inst: Ir.Inst.Index,
) !void {
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
const extra = sema.ir.extraData(Ir.Inst.Function, data.extra_index);
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
_ = try analyzeBodyInner(sema, builder, body, false);
}
// TODO: No return allowed.
pub fn analyzeKnot(
sema: *Sema,
builder: *Builder,
@ -1088,7 +1178,6 @@ fn scanTopLevelDecl(
.namespace = child_namespace,
};
}
try sema.module.queueWorkItem(.{
.tag = .stitch,
.decl_name = decl_name,
@ -1096,6 +1185,27 @@ fn scanTopLevelDecl(
.namespace = child_namespace,
});
},
.decl_function => {
const child_namespace = try sema.module.createNamespace(namespace);
const gop = try namespace.decls.getOrPut(sema.arena, decl_name);
if (gop.found_existing) {
return sema.fail(src_loc, "duplicate identifier", .{});
} else {
gop.value_ptr.* = .{
.tag = .function,
.decl_inst = extra.value,
.args_count = 0,
.namespace = child_namespace,
};
}
try sema.module.queueWorkItem(.{
.tag = .function,
.decl_name = decl_name,
.inst_index = extra.value,
.namespace = child_namespace,
});
},
else => unreachable,
}
}

View file

@ -14,13 +14,16 @@ dump_writer: ?*std.Io.Writer = null,
is_exited: bool = false,
can_advance: bool = false,
choice_index: usize = 0,
stack_top: usize = 0,
call_stack_top: usize = 0,
current_choices: std.ArrayListUnmanaged(Choice) = .empty,
code_chunks: std.ArrayListUnmanaged(*Object.Code) = .empty,
constants_pool: std.ArrayListUnmanaged(Value) = .empty,
globals: std.StringHashMapUnmanaged(?Value) = .empty,
stack: std.ArrayListUnmanaged(?Value) = .empty,
call_stack: std.ArrayListUnmanaged(CallFrame) = .empty,
stack_max: usize = 128,
stack: []Value = &.{},
call_stack: []CallFrame = &.{},
/// Global constants pool.
constants_pool: []const Value = &.{},
/// Linked list of all tracked runtime objects.
gc_objects: std.SinglyLinkedList = .{},
// FIXME: This was a hack to keep string bytes alive.
string_bytes: []const u8 = &.{},
@ -28,6 +31,7 @@ string_bytes: []const u8 = &.{},
pub const default_knot_name: [:0]const u8 = "$__main__$";
pub const Value = union(enum) {
nil,
bool: bool,
int: i64,
float: f64,
@ -35,6 +39,7 @@ pub const Value = union(enum) {
pub fn tagBytes(v: Value) []const u8 {
return switch (v) {
.nil => "Nil",
.bool => "Bool",
.int => "Int",
.float => "Float",
@ -57,7 +62,7 @@ pub const Value = union(enum) {
pub fn isTruthy(v: Value) bool {
return switch (v) {
//.nil => false,
.nil => false,
.bool => |b| b,
.int => |i| i != 0,
.float => |f| f != 0.0,
@ -133,6 +138,7 @@ pub const Value = union(enum) {
pub fn eql(lhs: Value, rhs: Value) bool {
return switch (lhs) {
.nil => rhs == .nil,
.bool => |l| rhs == .bool and l == rhs.bool,
.int => |l| switch (rhs) {
.int => |r| l == r,
@ -183,15 +189,15 @@ pub const Value = union(enum) {
pub fn negate(lhs: Value) !Value {
switch (lhs) {
.bool => return error.TypeError,
.nil, .bool, .object => return error.TypeError,
.int => |int| return .{ .int = -int },
.float => |float| return .{ .float = -float },
.object => return error.TypeError,
}
}
pub fn format(value: Value, writer: *std.Io.Writer) error{WriteFailed}!void {
switch (value) {
.nil => try writer.writeAll(value.tagBytes()),
.bool => |boolean| try writer.writeAll(if (boolean) "true" else "false"),
.int => |int| try writer.print("{d}", .{int}),
.float => |float| {
@ -222,9 +228,10 @@ pub const Value = union(enum) {
};
pub const CallFrame = struct {
callee: *Object.Knot,
caller_top: usize,
ip: usize,
sp: usize,
callee: *Object.Knot,
};
pub const Choice = struct {
@ -304,62 +311,52 @@ pub fn deinit(story: *Story) void {
}
story.current_choices.deinit(gpa);
story.constants_pool.deinit(gpa);
story.globals.deinit(gpa);
story.stack.deinit(gpa);
story.call_stack.deinit(gpa);
gpa.free(story.string_bytes);
}
fn isCallStackEmpty(vm: *const Story) bool {
return vm.call_stack.items.len == 0;
gpa.free(story.string_bytes);
gpa.free(story.constants_pool);
gpa.free(story.stack);
gpa.free(story.call_stack);
}
fn currentFrame(vm: *Story) *CallFrame {
return &vm.call_stack.items[vm.call_stack.items.len - 1];
assert(vm.call_stack_top > 0);
return &vm.call_stack[vm.call_stack_top - 1];
}
fn peekStack(vm: *Story, offset: usize) ?Value {
const stack_top = vm.stack.items.len;
if (stack_top <= offset) return null;
return vm.stack.items[stack_top - offset - 1];
if (vm.stack_top <= offset) return null;
return vm.stack[vm.stack_top - offset - 1];
}
fn pushStack(vm: *Story, value: Value) !void {
const gpa = vm.allocator;
const stack_top = vm.stack.items.len;
const max_stack_top = vm.stack_max;
if (stack_top >= max_stack_top) return error.StackOverflow;
return vm.stack.append(gpa, value);
if (vm.stack_top >= vm.stack.len) return error.StackOverflow;
vm.stack[vm.stack_top] = value;
vm.stack_top += 1;
}
fn popStack(vm: *Story) ?Value {
return vm.stack.pop() orelse unreachable;
if (vm.stack_top == 0) return null;
const stack_top = vm.stack_top;
vm.stack_top -= 1;
return vm.stack[stack_top];
}
fn getConstant(story: *Story, frame: *CallFrame, offset: u8) !Value {
if (offset >= frame.callee.code.constants.len) return error.InvalidArgument;
const constant_index = frame.callee.code.constants[offset];
return story.constants_pool.items[constant_index];
return story.constants_pool[constant_index];
}
fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?Value {
const stack_top = vm.stack.items.len;
const stack_offset = frame.sp + offset;
assert(stack_top > stack_offset);
return vm.stack.items[stack_offset];
assert(vm.stack_top > frame.sp + offset);
return vm.stack[frame.sp + offset];
}
fn setLocal(vm: *Story, frame: *CallFrame, offset: u8, value: Value) void {
const stack_top = vm.stack.items.len;
const stack_offset = frame.sp + offset;
assert(stack_top > stack_offset);
vm.stack.items[stack_offset] = value;
assert(vm.stack_top > frame.sp + offset);
vm.stack[frame.sp + offset] = value;
}
// TODO: This should probably check the constants table first.
@ -396,15 +393,14 @@ fn setGlobal(vm: *Story, key: Value, value: Value) !void {
fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
const gpa = vm.allocator;
if (vm.call_stack_top == 0) return .empty;
errdefer vm.can_advance = false;
if (vm.isCallStackEmpty()) return .empty;
var stream_writer = std.Io.Writer.Allocating.init(gpa);
defer stream_writer.deinit();
var frame = vm.currentFrame();
while (true) {
const frame = vm.currentFrame();
const code = std.mem.bytesAsSlice(Opcode, frame.callee.code.bytecode);
if (vm.dump_writer) |w| {
Dumper.trace(vm, w, frame) catch {};
@ -419,14 +415,27 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
vm.can_advance = false;
return .empty;
},
.ret => {
const return_value = vm.stack[vm.stack_top - 1];
vm.call_stack_top -= 1;
const completed_frame = vm.call_stack[vm.call_stack_top];
vm.stack_top = completed_frame.caller_top;
vm.stack[vm.stack_top] = return_value;
vm.stack_top += 1;
if (vm.call_stack_top == 0) return error.UnexpectedReturn;
frame = &vm.call_stack[vm.call_stack_top - 1];
},
.true => {
const value: Value = .{ .bool = true };
try vm.pushStack(value);
try vm.pushStack(.{ .bool = true });
frame.ip += 1;
},
.false => {
const value: Value = .{ .bool = false };
try vm.pushStack(value);
try vm.pushStack(.{ .bool = false });
frame.ip += 1;
},
.pop => {
@ -519,6 +528,43 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
return error.InvalidArgument;
}
},
.call => {
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
frame.ip += 2;
if (peekStack(vm, arg_offset)) |value| {
switch (value) {
.object => |object| switch (object.tag) {
.knot => try call(vm, @ptrCast(object)),
else => unreachable,
},
else => unreachable,
}
} else {
return error.InvalidArgument;
}
// Re-fetch we're now in the callee's frame
frame = &vm.call_stack[vm.call_stack_top - 1];
},
.divert => {
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
frame.ip += 2;
if (peekStack(vm, arg_offset)) |value| {
switch (value) {
.object => |object| switch (object.tag) {
.knot => try divert(vm, @ptrCast(object)),
else => unreachable,
},
else => unreachable,
}
} else {
return error.InvalidArgument;
}
frame = &vm.call_stack[vm.call_stack_top - 1];
},
.load_const => {
const index: u8 = @intFromEnum(code[frame.ip + 1]);
const value = try vm.getConstant(frame, index);
@ -611,16 +657,6 @@ 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)) |value| {
try divertToValue(vm, value);
} else {
return error.InvalidArgument;
}
},
.load_attr => {
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
frame.ip += 2;
@ -667,38 +703,62 @@ pub fn getKnot(vm: *Story, name: []const u8) ?*Object.Knot {
return knot;
}
// TODO(Brett): Add arguments?
fn divertToKnot(vm: *Story, knot: *Object.Knot) !void {
const gpa = vm.allocator;
const stack_ptr = vm.stack.items.len - knot.code.args_count;
const stack_needed = knot.code.stack_size;
fn call(vm: *Story, knot: *Object.Knot) !void {
if (vm.call_stack_top >= vm.call_stack.len)
return error.CallStackOverflow;
try vm.stack.ensureUnusedCapacity(gpa, stack_needed);
try vm.call_stack.ensureUnusedCapacity(gpa, 1);
const locals_count = knot.code.locals_count;
const args_count = knot.code.args_count;
const sp = vm.stack_top - args_count;
const caller_top = if (vm.call_stack_top == 0)
sp
else
sp - 1;
vm.call_stack.appendAssumeCapacity(.{
const frame_top = sp + args_count + locals_count;
if (frame_top > vm.stack.len) return error.StackOverflow;
for (vm.stack[sp + args_count .. frame_top]) |*slot| slot.* = .nil;
vm.stack_top = frame_top;
vm.call_stack[vm.call_stack_top] = .{
.callee = knot,
.ip = 0,
.sp = stack_ptr,
});
vm.stack.appendNTimesAssumeCapacity(null, stack_needed);
vm.can_advance = true;
.sp = sp,
.caller_top = caller_top,
};
vm.call_stack_top += 1;
}
fn divertToValue(vm: *Story, value: Value) !void {
switch (value) {
.object => |object| switch (object.tag) {
.knot => try divertToKnot(vm, @ptrCast(object)),
else => return error.TypeError,
},
else => return error.TypeError,
// Diverts are essentially tail calls.
fn divert(vm: *Story, knot: *Object.Knot) !void {
const args_count = knot.code.args_count;
const locals_count = knot.code.locals_count;
if (vm.call_stack_top == 0) return vm.call(knot);
const args_start = vm.stack_top - args_count;
const current_frame = &vm.call_stack[vm.call_stack_top - 1];
const sp = current_frame.sp;
const caller_top = current_frame.caller_top;
if (args_count > 0) {
std.mem.copyForwards(
Value,
vm.stack[sp .. sp + args_count],
vm.stack[args_start .. args_start + args_count],
);
}
}
fn divert(vm: *Story, knot_name: []const u8) !void {
return if (getKnot(vm, knot_name)) |knot| {
return divertToKnot(vm, knot);
} else return error.InvalidPath;
const frame_top = sp + args_count + locals_count;
if (frame_top > vm.stack.len) return error.StackOverflow;
for (vm.stack[sp + args_count .. frame_top]) |*slot| slot.* = .nil;
vm.stack_top = frame_top;
current_frame.* = .{
.callee = knot,
.ip = 0,
.sp = sp,
.caller_top = caller_top,
};
}
pub const LoadOptions = struct {
@ -746,15 +806,25 @@ pub fn loadFromString(
return error.LoadFailed;
}
const stack_size = 128;
const eval_stack_ptr = try gpa.alloc(Value, stack_size);
errdefer gpa.free(eval_stack_ptr);
const call_stack_ptr = try gpa.alloc(CallFrame, stack_size);
errdefer gpa.free(call_stack_ptr);
var story: Story = .{
.allocator = gpa,
.can_advance = false,
.dump_writer = if (options.dump_trace) options.dump_writer else null,
.stack = eval_stack_ptr,
.call_stack = call_stack_ptr,
};
errdefer story.deinit();
try comp.setupStoryRuntime(gpa, &story);
if (story.getKnot(Story.default_knot_name)) |knot| {
try story.divertToKnot(knot);
try story.divert(knot);
story.can_advance = true;
}
return story;

View file

@ -22,7 +22,6 @@ fn dumpByteInst(
op: Opcode,
) !usize {
const code = knot.code;
const constants_pool = &self.story.constants_pool;
assert(code.bytecode.len > offset + 1);
const arg = code.bytecode[offset + 1];
@ -31,9 +30,8 @@ fn dumpByteInst(
try w.writeAll(" (");
if (code.constants.len > arg) {
const constant_index = code.constants[arg];
if (constants_pool.items.len > constant_index) {
const global_constant = &constants_pool.items[constant_index];
try self.dumpValue(w, global_constant);
if (self.story.constants_pool.len > constant_index) {
try self.dumpValue(w, &self.story.constants_pool[constant_index]);
} else {
try w.writeAll("invalid!");
}
@ -55,14 +53,11 @@ fn dumpGlobalInst(
op: Opcode,
) !usize {
const code = knot.code;
const constants_pool = &self.story.constants_pool;
assert(code.bytecode.len > offset + 1);
const arg = code.bytecode[offset + 1];
assert(code.constants.len > arg);
const constant_index = code.constants[arg];
const global_constant = constants_pool.items[constant_index];
switch (global_constant) {
switch (self.story.constants_pool[constant_index]) {
.object => |object| switch (object.tag) {
.string => {
const global_name: *Object.String = @ptrCast(object);
@ -280,6 +275,10 @@ pub fn dumpKnot(self: *Dumper, w: *std.Io.Writer, knot: *const Object.Knot) !voi
pub fn dumpValue(_: Dumper, w: *std.Io.Writer, value: *const Value) !void {
switch (value.*) {
.nil => try w.print(
"<{s}>",
.{value.tagBytes()},
),
.bool => |_| try w.print(
"<{s} value={f}>",
.{ value.tagBytes(), value },
@ -313,8 +312,8 @@ pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
try writer.writeAll("=== Constants ===\n");
var i: usize = 0;
while (i < story.constants_pool.items.len) : (i += 1) {
const global_constant = &story.constants_pool.items[i];
while (i < story.constants_pool.len) : (i += 1) {
const global_constant = &story.constants_pool[i];
try story_dumper.dumpValue(writer, global_constant);
try writer.writeAll("\n");
}
@ -350,31 +349,14 @@ pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
}
}
pub fn trace(story: *Story, writer: *std.Io.Writer, frame: *Story.CallFrame) !void {
var dumper: Dumper = .{ .story = story };
const stack = &story.stack;
const stack_top = story.stack.items.len;
pub fn trace(vm: *Story, writer: *std.Io.Writer, frame: *Story.CallFrame) !void {
var dumper: Dumper = .{ .story = vm };
try writer.print("\tStack => stack_pointer={d}, objects=[", .{frame.sp});
if (stack_top > 0) {
// FIXME: There has to be a better way to do this.
if (stack_top > 1) {
var i: usize = frame.sp;
while (i < stack.items.len - 1) : (i += 1) {
if (stack.items[i]) |*value| {
try dumper.dumpValue(writer, value);
} else {
try writer.writeAll("null");
}
try writer.writeAll(", ");
}
}
if (stack.items[stack.items.len - 1]) |*object| {
try dumper.dumpValue(writer, object);
} else {
try writer.writeAll("null");
}
const window = vm.stack[frame.sp..vm.stack_top];
for (window, 0..) |*slot, i| {
if (i > 0) try writer.writeAll(", ");
try dumper.dumpValue(writer, slot);
}
try writer.writeAll("]\n");

View file

@ -95,7 +95,7 @@ pub const String = struct {
const print_buffer_len = 64;
var print_buffer: [print_buffer_len]u8 = undefined;
switch (value) {
.bool, .int, .float => {
.nil, .bool, .int, .float => {
const bytes = try std.fmt.bufPrint(&print_buffer, "{f}", .{value});
return .create(story, .{ .bytes = bytes });
},

View file

@ -18,10 +18,18 @@ test "fixture - I002 (Fogg comforts Passepartout)" {
try testRuntimeFixture("I002");
}
test "fixture - I004 (Print number as English)" {
try testRuntimeFixture("I004");
}
test "fixture - I005 (Const variable)" {
try testRuntimeFixture("I005");
}
test "fixture - I006 (Multiple constant references)" {
try testRuntimeFixture("I006");
}
test "fixture - I007 (Set non existant variable)" {
try testRuntimeFixture("I007");
}
@ -35,6 +43,10 @@ test "fixture - I011 (Temporaries at global scope)" {
// try testRuntimeFixture("I012");
//}
test "fixture - I014 (Variable swap recurse)" {
try testRuntimeFixture("I014");
}
test "fixture - I016 (Empty)" {
try testRuntimeFixture("I016");
}
@ -59,10 +71,26 @@ test "fixture - I023 (Whitespace)" {
try testRuntimeFixture("I023");
}
test "fixture - I033 (Newline consistency, the first)" {
try testRuntimeFixture("I033");
}
test "fixture - I034 (Newline consistency, the second)" {
try testRuntimeFixture("I034");
}
test "fixture - I035 (Newline consistency, the third)" {
try testRuntimeFixture("I035");
}
test "fixture - I036 (Newlines with string eval)" {
try testRuntimeFixture("I036");
}
test "fixture - I037 (Newline at start of multiline conditional)" {
try testRuntimeFixture("I037");
}
test "fixture - I042 (Weave options)" {
try testRuntimeFixture("I042");
}
@ -75,18 +103,35 @@ test "fixture - I064 (Done stops thread)" {
try testRuntimeFixture("I064");
}
// This one is named oddly...
test "fixture - I075 (Clean callstack reset on path choice)" {
try testRuntimeFixture("I075");
}
test "fixture - I078 (Choice with brackets only)" {
try testRuntimeFixture("I078");
}
test "fixture - I117 (Factorial recursive)" {
try testRuntimeFixture("I117");
}
test "fixture - I118 (Literal unary)" {
try testRuntimeFixture("I118");
}
test "fixture - I119 (Basic string literals)" {
try testRuntimeFixture("I119");
}
test "fixture - I121 (Arithmetic)" {
try testRuntimeFixture("I121");
}
test "fixture - I124 (Evaluating ink functions from game 2)" {
try testRuntimeFixture("I124");
}
test "fixture - I133 (Float printing precision)" {
try testRuntimeFixture("I133");
}

0
src/Story/testdata/I004/input.txt vendored Normal file
View file

53
src/Story/testdata/I004/story.ink vendored Normal file
View file

@ -0,0 +1,53 @@
You have {print_num(58)} coins.
=== function print_num(x)
{
- x >= 1000:
{print_num(x / 1000)} thousand { x mod 1000 > 0:{print_num(x mod 1000)}}
- x >= 100:
{print_num(x / 100)} hundred { x mod 100 > 0:and {print_num(x mod 100)}}
- x == 0:
zero
- else:
{ x >= 20:
{ x / 10:
- 2: twenty
- 3: thirty
- 4: forty
- 5: fifty
- 6: sixty
- 7: seventy
- 8: eighty
- 9: ninety
}
{ x mod 10 > 0:
<>-<>
}
}
{ x < 10 || x > 20:
{ x mod 10:
- 1: one
- 2: two
- 3: three
- 4: four
- 5: five
- 6: six
- 7: seven
- 8: eight
- 9: nine
}
- else:
{ x:
- 10: ten
- 11: eleven
- 12: twelve
- 13: thirteen
- 14: fourteen
- 15: fifteen
- 16: sixteen
- 17: seventeen
- 18: eighteen
- 19: nineteen
}
}
}

View file

@ -0,0 +1 @@
You have fifty-eight coins.

0
src/Story/testdata/I006/input.txt vendored Normal file
View file

3
src/Story/testdata/I006/story.ink vendored Normal file
View file

@ -0,0 +1,3 @@
CONST CONST_STR = "ConstantString"
VAR varStr = CONST_STR
{varStr == CONST_STR:success}

View file

@ -0,0 +1 @@
success

0
src/Story/testdata/I014/input.txt vendored Normal file
View file

9
src/Story/testdata/I014/story.ink vendored Normal file
View file

@ -0,0 +1,9 @@
~ f(1, 1)
== function f(x, y) ==
{ x == 1 and y == 1:
~ x = 2
~ f(y, x)
- else:
{x} {y}
}
~ return

View file

@ -0,0 +1 @@
1 2

0
src/Story/testdata/I033/input.txt vendored Normal file
View file

4
src/Story/testdata/I033/story.ink vendored Normal file
View file

@ -0,0 +1,4 @@
hello -> world
== world
world
-> END

View file

@ -0,0 +1 @@
hello world

1
src/Story/testdata/I034/input.txt vendored Normal file
View file

@ -0,0 +1 @@
1

4
src/Story/testdata/I034/story.ink vendored Normal file
View file

@ -0,0 +1,4 @@
* hello -> world
== world
world
-> END

View file

@ -0,0 +1,2 @@
1: hello
?> hello world

0
src/Story/testdata/I036/input.txt vendored Normal file
View file

9
src/Story/testdata/I036/story.ink vendored Normal file
View file

@ -0,0 +1,9 @@
A
~temp someTemp = string()
B
A
{string()}
B
=== function string()
~ return "{3}"
}

View file

@ -0,0 +1,5 @@
A
B
A
3
B

0
src/Story/testdata/I037/input.txt vendored Normal file
View file

6
src/Story/testdata/I037/story.ink vendored Normal file
View file

@ -0,0 +1,6 @@
{isTrue():
x
}
=== function isTrue()
X
~ return true

View file

@ -0,0 +1,2 @@
X
x

0
src/Story/testdata/I044/input.txt vendored Normal file
View file

7
src/Story/testdata/I044/story.ink vendored Normal file
View file

@ -0,0 +1,7 @@
A
{f():X}
C
=== function f()
{ true:
~ return false
}

View file

@ -0,0 +1,2 @@
A
C

0
src/Story/testdata/I045/input.txt vendored Normal file
View file

6
src/Story/testdata/I045/story.ink vendored Normal file
View file

@ -0,0 +1,6 @@
A {f():B}
X
=== function f() ===
{true:
~ return false
}

View file

@ -0,0 +1,2 @@
A
X

0
src/Story/testdata/I046/input.txt vendored Normal file
View file

7
src/Story/testdata/I046/story.ink vendored Normal file
View file

@ -0,0 +1,7 @@
A line.
{ f():
Another line.
}
== function f ==
{false:nothing}
~ return true

View file

@ -0,0 +1,2 @@
A line.
Another line.

0
src/Story/testdata/I047/input.txt vendored Normal file
View file

6
src/Story/testdata/I047/story.ink vendored Normal file
View file

@ -0,0 +1,6 @@
I have {five()} eggs.
== function five ==
{false:
Don't print this
}
five

View file

@ -0,0 +1 @@
I have five eggs.

0
src/Story/testdata/I048/input.txt vendored Normal file
View file

2
src/Story/testdata/I048/story.ink vendored Normal file
View file

@ -0,0 +1,2 @@
Some <>
content<> with glue.

View file

@ -0,0 +1 @@
Some content with glue.

0
src/Story/testdata/I055/input.txt vendored Normal file
View file

6
src/Story/testdata/I055/story.ink vendored Normal file
View file

@ -0,0 +1,6 @@
-> hurry_home
=== hurry_home ===
We hurried home to Savile Row -> as_fast_as_we_could
=== as_fast_as_we_could ===
as fast as we could.
-> DONE

View file

@ -0,0 +1 @@
We hurried home to Savile Row as fast as we could.

0
src/Story/testdata/I061/input.txt vendored Normal file
View file

8
src/Story/testdata/I061/story.ink vendored Normal file
View file

@ -0,0 +1,8 @@
=== intro
= top
{ main: -> done }
-> END
= main
-> top
= done
-> END

View file

0
src/Story/testdata/I075/input.txt vendored Normal file
View file

7
src/Story/testdata/I075/story.ink vendored Normal file
View file

@ -0,0 +1,7 @@
{RunAThing()}
== function RunAThing ==
The first line.
The second line.
== SomewhereElse ==
{"somewhere else"}
->END

View file

@ -0,0 +1,2 @@
The first line.
The second line.

0
src/Story/testdata/I076/input.txt vendored Normal file
View file

8
src/Story/testdata/I076/story.ink vendored Normal file
View file

@ -0,0 +1,8 @@
{ six() + two() }
-> END
=== function six
~ return four() + two()
=== function four
~ return two() + two()
=== function two
~ return 2

View file

@ -0,0 +1 @@
8

1
src/Story/testdata/I082/input.txt vendored Normal file
View file

@ -0,0 +1 @@
1

1
src/Story/testdata/I082/story.ink vendored Normal file
View file

@ -0,0 +1 @@
* choice -> DONE

View file

@ -0,0 +1,2 @@
1: choice
?> choice

1
src/Story/testdata/I085/input.txt vendored Normal file
View file

@ -0,0 +1 @@
1

4
src/Story/testdata/I085/story.ink vendored Normal file
View file

@ -0,0 +1,4 @@
* 'Hello {name()}[, your name is {name()}.'],' I said, knowing full well that his name was {name()}.
-> DONE
== function name ==
Joe

View file

@ -0,0 +1,2 @@
1: 'Hello Joe, your name is Joe.'
?> 'Hello Joe,' I said, knowing full well that his name was Joe.

1
src/Story/testdata/I087/input.txt vendored Normal file
View file

@ -0,0 +1 @@
1

7
src/Story/testdata/I087/story.ink vendored Normal file
View file

@ -0,0 +1,7 @@
-> knot
== knot
* option text[]. {true: Conditional bit.} -> next
-> DONE
== next
Next.
-> DONE

View file

@ -0,0 +1,2 @@
1: option text
?> option text. Conditional bit. Next.

0
src/Story/testdata/I094/input.txt vendored Normal file
View file

55
src/Story/testdata/I094/story.ink vendored Normal file
View file

@ -0,0 +1,55 @@
. {print_num(4)} .
. {print_num(15)} .
. {print_num(37)} .
. {print_num(101)} .
. {print_num(222)} .
. {print_num(1234)} .
=== function print_num(x) ===
{
- x >= 1000:
{print_num(x / 1000)} thousand { x mod 1000 > 0:{print_num(x mod 1000)}}
- x >= 100:
{print_num(x / 100)} hundred { x mod 100 > 0:and {print_num(x mod 100)}}
- x == 0:
zero
- else:
{ x >= 20:
{ x / 10:
- 2: twenty
- 3: thirty
- 4: forty
- 5: fifty
- 6: sixty
- 7: seventy
- 8: eighty
- 9: ninety
}
{ x mod 10 > 0:<>-<>}
}
{ x < 10 || x > 20:
{ x mod 10:
- 1: one
- 2: two
- 3: three
- 4: four
- 5: five
- 6: six
- 7: seven
- 8: eight
- 9: nine
}
- else:
{ x:
- 10: ten
- 11: eleven
- 12: twelve
- 13: thirteen
- 14: fourteen
- 15: fifteen
- 16: sixteen
- 17: seventeen
- 18: eighteen
- 19: nineteen
}
}
}

View file

@ -0,0 +1,6 @@
. four .
. fifteen .
. thirty-seven .
. one hundred and one .
. two hundred and twenty-two .
. one thousand two hundred and thirty-four .

0
src/Story/testdata/I095/input.txt vendored Normal file
View file

8
src/Story/testdata/I095/story.ink vendored Normal file
View file

@ -0,0 +1,8 @@
{true:
a
} <> b
{true:
a
} <> { true:
b
}

View file

@ -0,0 +1,2 @@
a b
a b

0
src/Story/testdata/I097/input.txt vendored Normal file
View file

7
src/Story/testdata/I097/story.ink vendored Normal file
View file

@ -0,0 +1,7 @@
~ func ()
text 2
~ temp tempVar = func ()
text 2
== function func ()
text1
~ return true

View file

@ -0,0 +1,4 @@
text1
text 2
text1
text 2

0
src/Story/testdata/I112/input.txt vendored Normal file
View file

4
src/Story/testdata/I112/story.ink vendored Normal file
View file

@ -0,0 +1,4 @@
{ 1:
- 2: x
- 3: y
}

View file

0
src/Story/testdata/I113/input.txt vendored Normal file
View file

20
src/Story/testdata/I113/story.ink vendored Normal file
View file

@ -0,0 +1,20 @@
VAR x = 3
{
- x == 1: one
- x == 2: two
- else: other
}
{
- x == 1: one
- x == 2: two
- other
}
{ x == 4:
- The main clause
- else: other
}
{ x == 4:
The main clause
- else:
other
}

View file

@ -0,0 +1,4 @@
other
other
other
other

0
src/Story/testdata/I115/input.txt vendored Normal file
View file

5
src/Story/testdata/I115/story.ink vendored Normal file
View file

@ -0,0 +1,5 @@
{ 3:
- 3:
- 4:
txt
}

View file

0
src/Story/testdata/I116/input.txt vendored Normal file
View file

4
src/Story/testdata/I116/story.ink vendored Normal file
View file

@ -0,0 +1,4 @@
{
- false:
beep
}

View file

0
src/Story/testdata/I117/input.txt vendored Normal file
View file

7
src/Story/testdata/I117/story.ink vendored Normal file
View file

@ -0,0 +1,7 @@
{ factorial(5) }
== function factorial(n) ==
{ n == 1:
~ return 1
- else:
~ return (n * factorial(n-1))
}

View file

@ -0,0 +1 @@
120

0
src/Story/testdata/I119/input.txt vendored Normal file
View file

3
src/Story/testdata/I119/story.ink vendored Normal file
View file

@ -0,0 +1,3 @@
VAR x = "Hello world 1"
{x}
Hello {"world"} 2.

View file

@ -0,0 +1,2 @@
Hello world 1
Hello world 2.

0
src/Story/testdata/I124/input.txt vendored Normal file
View file

12
src/Story/testdata/I124/story.ink vendored Normal file
View file

@ -0,0 +1,12 @@
One
Two
Three
== function func1 ==
This is a function
~ return 5
== function func2 ==
This is a function without a return value
~ return
== function add(x,y) ==
x = {x}, y = {y}
~ return x + y

View file

@ -0,0 +1,3 @@
One
Two
Three

1
src/Story/testdata/I127/input.txt vendored Normal file
View file

@ -0,0 +1 @@
1

10
src/Story/testdata/I127/story.ink vendored Normal file
View file

@ -0,0 +1,10 @@
VAR testVar = 5
VAR testVar2 = 10
Hello world!
~ testVar = 15
~ testVar2 = 100
Hello world 2!
* choice
~ testVar = 25
~ testVar2 = 200
-> END

View file

@ -0,0 +1,5 @@
Hello world!
Hello world 2!
1: choice
?> choice

View file

@ -164,6 +164,7 @@ pub const InternPool = struct {
}
};
// TODO: Revisit this. We might not need this at all.
pub const WorkItem = struct {
tag: Tag,
next: ?*WorkItem = null,
@ -174,6 +175,7 @@ pub const WorkItem = struct {
pub const Tag = enum {
knot,
stitch,
function,
};
};
@ -235,6 +237,7 @@ pub const Module = struct {
pub const Tag = enum {
knot,
stitch,
function,
var_mut,
var_const,
};
@ -315,6 +318,16 @@ pub const Module = struct {
.code_index = @enumFromInt(chunk_index),
});
},
.function => {
try sema.analyzeFunction(&builder, work_unit.inst_index);
try builder.finalize();
try mod.stitches.append(gpa, .{
.knot_index = null,
.name_index = work_unit.decl_name,
.code_index = @enumFromInt(chunk_index),
});
},
}
}
}
@ -412,11 +425,13 @@ pub const Module = struct {
pub fn setupStoryRuntime(mod: *Module, gpa: std.mem.Allocator, story: *Story) !void {
assert(mod.errors.items.len == 0);
const constants_len = mod.intern_pool.values.items.len;
var constants_pool: std.ArrayListUnmanaged(Value) = .empty;
try constants_pool.ensureUnusedCapacity(gpa, constants_len);
defer constants_pool.deinit(gpa);
try story.constants_pool.ensureUnusedCapacity(gpa, constants_len);
for (mod.intern_pool.values.items) |value| {
const obj = try mod.makeValueFromInterned(story, value);
story.constants_pool.appendAssumeCapacity(obj);
constants_pool.appendAssumeCapacity(obj);
}
for (mod.globals.items) |global| {
const key_bytes = mod.intern_pool.getStrBytes(mod.ir, global.key);
@ -459,8 +474,13 @@ pub const Module = struct {
const parent_knot_value = story.globals.get(parent_knot_name).?;
const parent_knot_obj: *Object.Knot = @ptrCast(parent_knot_value.?.object);
try parent_knot_obj.members.put(gpa, name_bytes, &stitch_obj.base);
} else {
const value: Value = .{ .object = &stitch_obj.base };
try story.globals.put(gpa, name_bytes, value);
}
}
story.constants_pool = try constants_pool.toOwnedSlice(gpa);
story.string_bytes = mod.ir.string_bytes;
mod.ir.string_bytes = &.{};
}

View file

@ -293,7 +293,10 @@ pub const Writer = struct {
.declaration => try self.writeDeclarationInst(w, inst),
.decl_var => try self.writeVarDeclInst(w, inst),
.decl_knot => try self.writeKnotDeclInst(w, inst),
// TODO: Revisit this.
.decl_stitch => try self.writeVarDeclInst(w, inst),
// TODO: Revisit this.
.decl_function => try self.writeVarDeclInst(w, inst),
.decl_ref => try self.writeStrTokInst(w, inst),
.condbr => try self.writeCondbrInst(w, inst),
.@"break" => try self.writeBreakInst(w, inst),
@ -323,6 +326,7 @@ pub const Writer = struct {
.content_push => try self.writeUnaryInst(w, inst),
.content_flush => try self.writeUnaryInst(w, inst),
.choice_br => try self.writeChoiceBrInst(w, inst),
.ret => try self.writeUnaryInst(w, inst),
.implicit_ret => try self.writeUnaryInst(w, inst),
.call => try self.writeCallInst(w, inst, .direct),
.divert => try self.writeCallInst(w, inst, .direct),