feat: function calls
This commit is contained in:
parent
11d99fba38
commit
d08e753664
99 changed files with 881 additions and 148 deletions
113
src/AstGen.zig
113
src/AstGen.zig
|
|
@ -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)),
|
||||
};
|
||||
}
|
||||
_ = try gi.addUnaryNode(.implicit_ret, .none);
|
||||
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;
|
||||
|
|
|
|||
66
src/Ir.zig
66
src/Ir.zig
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
|||
140
src/Sema.zig
140
src/Sema.zig
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
232
src/Story.zig
232
src/Story.zig
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
0
src/Story/testdata/I004/input.txt
vendored
Normal file
53
src/Story/testdata/I004/story.ink
vendored
Normal file
53
src/Story/testdata/I004/story.ink
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/Story/testdata/I004/transcript.txt
vendored
Normal file
1
src/Story/testdata/I004/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
You have fifty-eight coins.
|
||||
0
src/Story/testdata/I006/input.txt
vendored
Normal file
0
src/Story/testdata/I006/input.txt
vendored
Normal file
3
src/Story/testdata/I006/story.ink
vendored
Normal file
3
src/Story/testdata/I006/story.ink
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
CONST CONST_STR = "ConstantString"
|
||||
VAR varStr = CONST_STR
|
||||
{varStr == CONST_STR:success}
|
||||
1
src/Story/testdata/I006/transcript.txt
vendored
Normal file
1
src/Story/testdata/I006/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
success
|
||||
0
src/Story/testdata/I014/input.txt
vendored
Normal file
0
src/Story/testdata/I014/input.txt
vendored
Normal file
9
src/Story/testdata/I014/story.ink
vendored
Normal file
9
src/Story/testdata/I014/story.ink
vendored
Normal 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
|
||||
1
src/Story/testdata/I014/transcript.txt
vendored
Normal file
1
src/Story/testdata/I014/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
1 2
|
||||
0
src/Story/testdata/I033/input.txt
vendored
Normal file
0
src/Story/testdata/I033/input.txt
vendored
Normal file
4
src/Story/testdata/I033/story.ink
vendored
Normal file
4
src/Story/testdata/I033/story.ink
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
hello -> world
|
||||
== world
|
||||
world
|
||||
-> END
|
||||
1
src/Story/testdata/I033/transcript.txt
vendored
Normal file
1
src/Story/testdata/I033/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
hello world
|
||||
1
src/Story/testdata/I034/input.txt
vendored
Normal file
1
src/Story/testdata/I034/input.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
1
|
||||
4
src/Story/testdata/I034/story.ink
vendored
Normal file
4
src/Story/testdata/I034/story.ink
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
* hello -> world
|
||||
== world
|
||||
world
|
||||
-> END
|
||||
2
src/Story/testdata/I034/transcript.txt
vendored
Normal file
2
src/Story/testdata/I034/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
1: hello
|
||||
?> hello world
|
||||
0
src/Story/testdata/I036/input.txt
vendored
Normal file
0
src/Story/testdata/I036/input.txt
vendored
Normal file
9
src/Story/testdata/I036/story.ink
vendored
Normal file
9
src/Story/testdata/I036/story.ink
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
A
|
||||
~temp someTemp = string()
|
||||
B
|
||||
A
|
||||
{string()}
|
||||
B
|
||||
=== function string()
|
||||
~ return "{3}"
|
||||
}
|
||||
5
src/Story/testdata/I036/transcript.txt
vendored
Normal file
5
src/Story/testdata/I036/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
A
|
||||
B
|
||||
A
|
||||
3
|
||||
B
|
||||
0
src/Story/testdata/I037/input.txt
vendored
Normal file
0
src/Story/testdata/I037/input.txt
vendored
Normal file
6
src/Story/testdata/I037/story.ink
vendored
Normal file
6
src/Story/testdata/I037/story.ink
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{isTrue():
|
||||
x
|
||||
}
|
||||
=== function isTrue()
|
||||
X
|
||||
~ return true
|
||||
2
src/Story/testdata/I037/transcript.txt
vendored
Normal file
2
src/Story/testdata/I037/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
X
|
||||
x
|
||||
0
src/Story/testdata/I044/input.txt
vendored
Normal file
0
src/Story/testdata/I044/input.txt
vendored
Normal file
7
src/Story/testdata/I044/story.ink
vendored
Normal file
7
src/Story/testdata/I044/story.ink
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
A
|
||||
{f():X}
|
||||
C
|
||||
=== function f()
|
||||
{ true:
|
||||
~ return false
|
||||
}
|
||||
2
src/Story/testdata/I044/transcript.txt
vendored
Normal file
2
src/Story/testdata/I044/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
A
|
||||
C
|
||||
0
src/Story/testdata/I045/input.txt
vendored
Normal file
0
src/Story/testdata/I045/input.txt
vendored
Normal file
6
src/Story/testdata/I045/story.ink
vendored
Normal file
6
src/Story/testdata/I045/story.ink
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
A {f():B}
|
||||
X
|
||||
=== function f() ===
|
||||
{true:
|
||||
~ return false
|
||||
}
|
||||
2
src/Story/testdata/I045/transcript.txt
vendored
Normal file
2
src/Story/testdata/I045/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
A
|
||||
X
|
||||
0
src/Story/testdata/I046/input.txt
vendored
Normal file
0
src/Story/testdata/I046/input.txt
vendored
Normal file
7
src/Story/testdata/I046/story.ink
vendored
Normal file
7
src/Story/testdata/I046/story.ink
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
A line.
|
||||
{ f():
|
||||
Another line.
|
||||
}
|
||||
== function f ==
|
||||
{false:nothing}
|
||||
~ return true
|
||||
2
src/Story/testdata/I046/transcript.txt
vendored
Normal file
2
src/Story/testdata/I046/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
A line.
|
||||
Another line.
|
||||
0
src/Story/testdata/I047/input.txt
vendored
Normal file
0
src/Story/testdata/I047/input.txt
vendored
Normal file
6
src/Story/testdata/I047/story.ink
vendored
Normal file
6
src/Story/testdata/I047/story.ink
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
I have {five()} eggs.
|
||||
== function five ==
|
||||
{false:
|
||||
Don't print this
|
||||
}
|
||||
five
|
||||
1
src/Story/testdata/I047/transcript.txt
vendored
Normal file
1
src/Story/testdata/I047/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
I have five eggs.
|
||||
0
src/Story/testdata/I048/input.txt
vendored
Normal file
0
src/Story/testdata/I048/input.txt
vendored
Normal file
2
src/Story/testdata/I048/story.ink
vendored
Normal file
2
src/Story/testdata/I048/story.ink
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Some <>
|
||||
content<> with glue.
|
||||
1
src/Story/testdata/I048/transcript.txt
vendored
Normal file
1
src/Story/testdata/I048/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
Some content with glue.
|
||||
0
src/Story/testdata/I055/input.txt
vendored
Normal file
0
src/Story/testdata/I055/input.txt
vendored
Normal file
6
src/Story/testdata/I055/story.ink
vendored
Normal file
6
src/Story/testdata/I055/story.ink
vendored
Normal 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
|
||||
1
src/Story/testdata/I055/transcript.txt
vendored
Normal file
1
src/Story/testdata/I055/transcript.txt
vendored
Normal 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
0
src/Story/testdata/I061/input.txt
vendored
Normal file
8
src/Story/testdata/I061/story.ink
vendored
Normal file
8
src/Story/testdata/I061/story.ink
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
=== intro
|
||||
= top
|
||||
{ main: -> done }
|
||||
-> END
|
||||
= main
|
||||
-> top
|
||||
= done
|
||||
-> END
|
||||
0
src/Story/testdata/I061/transcript.txt
vendored
Normal file
0
src/Story/testdata/I061/transcript.txt
vendored
Normal file
0
src/Story/testdata/I075/input.txt
vendored
Normal file
0
src/Story/testdata/I075/input.txt
vendored
Normal file
7
src/Story/testdata/I075/story.ink
vendored
Normal file
7
src/Story/testdata/I075/story.ink
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{RunAThing()}
|
||||
== function RunAThing ==
|
||||
The first line.
|
||||
The second line.
|
||||
== SomewhereElse ==
|
||||
{"somewhere else"}
|
||||
->END
|
||||
2
src/Story/testdata/I075/transcript.txt
vendored
Normal file
2
src/Story/testdata/I075/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
The first line.
|
||||
The second line.
|
||||
0
src/Story/testdata/I076/input.txt
vendored
Normal file
0
src/Story/testdata/I076/input.txt
vendored
Normal file
8
src/Story/testdata/I076/story.ink
vendored
Normal file
8
src/Story/testdata/I076/story.ink
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{ six() + two() }
|
||||
-> END
|
||||
=== function six
|
||||
~ return four() + two()
|
||||
=== function four
|
||||
~ return two() + two()
|
||||
=== function two
|
||||
~ return 2
|
||||
1
src/Story/testdata/I076/transcript.txt
vendored
Normal file
1
src/Story/testdata/I076/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
8
|
||||
1
src/Story/testdata/I082/input.txt
vendored
Normal file
1
src/Story/testdata/I082/input.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
1
|
||||
1
src/Story/testdata/I082/story.ink
vendored
Normal file
1
src/Story/testdata/I082/story.ink
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
* choice -> DONE
|
||||
2
src/Story/testdata/I082/transcript.txt
vendored
Normal file
2
src/Story/testdata/I082/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
1: choice
|
||||
?> choice
|
||||
1
src/Story/testdata/I085/input.txt
vendored
Normal file
1
src/Story/testdata/I085/input.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
1
|
||||
4
src/Story/testdata/I085/story.ink
vendored
Normal file
4
src/Story/testdata/I085/story.ink
vendored
Normal 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
|
||||
2
src/Story/testdata/I085/transcript.txt
vendored
Normal file
2
src/Story/testdata/I085/transcript.txt
vendored
Normal 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
1
src/Story/testdata/I087/input.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
1
|
||||
7
src/Story/testdata/I087/story.ink
vendored
Normal file
7
src/Story/testdata/I087/story.ink
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
-> knot
|
||||
== knot
|
||||
* option text[]. {true: Conditional bit.} -> next
|
||||
-> DONE
|
||||
== next
|
||||
Next.
|
||||
-> DONE
|
||||
2
src/Story/testdata/I087/transcript.txt
vendored
Normal file
2
src/Story/testdata/I087/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
1: option text
|
||||
?> option text. Conditional bit. Next.
|
||||
0
src/Story/testdata/I094/input.txt
vendored
Normal file
0
src/Story/testdata/I094/input.txt
vendored
Normal file
55
src/Story/testdata/I094/story.ink
vendored
Normal file
55
src/Story/testdata/I094/story.ink
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/Story/testdata/I094/transcript.txt
vendored
Normal file
6
src/Story/testdata/I094/transcript.txt
vendored
Normal 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
0
src/Story/testdata/I095/input.txt
vendored
Normal file
8
src/Story/testdata/I095/story.ink
vendored
Normal file
8
src/Story/testdata/I095/story.ink
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{true:
|
||||
a
|
||||
} <> b
|
||||
{true:
|
||||
a
|
||||
} <> { true:
|
||||
b
|
||||
}
|
||||
2
src/Story/testdata/I095/transcript.txt
vendored
Normal file
2
src/Story/testdata/I095/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
a b
|
||||
a b
|
||||
0
src/Story/testdata/I097/input.txt
vendored
Normal file
0
src/Story/testdata/I097/input.txt
vendored
Normal file
7
src/Story/testdata/I097/story.ink
vendored
Normal file
7
src/Story/testdata/I097/story.ink
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
~ func ()
|
||||
text 2
|
||||
~ temp tempVar = func ()
|
||||
text 2
|
||||
== function func ()
|
||||
text1
|
||||
~ return true
|
||||
4
src/Story/testdata/I097/transcript.txt
vendored
Normal file
4
src/Story/testdata/I097/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
text1
|
||||
text 2
|
||||
text1
|
||||
text 2
|
||||
0
src/Story/testdata/I112/input.txt
vendored
Normal file
0
src/Story/testdata/I112/input.txt
vendored
Normal file
4
src/Story/testdata/I112/story.ink
vendored
Normal file
4
src/Story/testdata/I112/story.ink
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{ 1:
|
||||
- 2: x
|
||||
- 3: y
|
||||
}
|
||||
0
src/Story/testdata/I112/transcript.txt
vendored
Normal file
0
src/Story/testdata/I112/transcript.txt
vendored
Normal file
0
src/Story/testdata/I113/input.txt
vendored
Normal file
0
src/Story/testdata/I113/input.txt
vendored
Normal file
20
src/Story/testdata/I113/story.ink
vendored
Normal file
20
src/Story/testdata/I113/story.ink
vendored
Normal 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
|
||||
}
|
||||
4
src/Story/testdata/I113/transcript.txt
vendored
Normal file
4
src/Story/testdata/I113/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
other
|
||||
other
|
||||
other
|
||||
other
|
||||
0
src/Story/testdata/I115/input.txt
vendored
Normal file
0
src/Story/testdata/I115/input.txt
vendored
Normal file
5
src/Story/testdata/I115/story.ink
vendored
Normal file
5
src/Story/testdata/I115/story.ink
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
{ 3:
|
||||
- 3:
|
||||
- 4:
|
||||
txt
|
||||
}
|
||||
0
src/Story/testdata/I115/transcript.txt
vendored
Normal file
0
src/Story/testdata/I115/transcript.txt
vendored
Normal file
0
src/Story/testdata/I116/input.txt
vendored
Normal file
0
src/Story/testdata/I116/input.txt
vendored
Normal file
4
src/Story/testdata/I116/story.ink
vendored
Normal file
4
src/Story/testdata/I116/story.ink
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
- false:
|
||||
beep
|
||||
}
|
||||
0
src/Story/testdata/I116/transcript.txt
vendored
Normal file
0
src/Story/testdata/I116/transcript.txt
vendored
Normal file
0
src/Story/testdata/I117/input.txt
vendored
Normal file
0
src/Story/testdata/I117/input.txt
vendored
Normal file
7
src/Story/testdata/I117/story.ink
vendored
Normal file
7
src/Story/testdata/I117/story.ink
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{ factorial(5) }
|
||||
== function factorial(n) ==
|
||||
{ n == 1:
|
||||
~ return 1
|
||||
- else:
|
||||
~ return (n * factorial(n-1))
|
||||
}
|
||||
1
src/Story/testdata/I117/transcript.txt
vendored
Normal file
1
src/Story/testdata/I117/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
120
|
||||
0
src/Story/testdata/I119/input.txt
vendored
Normal file
0
src/Story/testdata/I119/input.txt
vendored
Normal file
3
src/Story/testdata/I119/story.ink
vendored
Normal file
3
src/Story/testdata/I119/story.ink
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
VAR x = "Hello world 1"
|
||||
{x}
|
||||
Hello {"world"} 2.
|
||||
2
src/Story/testdata/I119/transcript.txt
vendored
Normal file
2
src/Story/testdata/I119/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Hello world 1
|
||||
Hello world 2.
|
||||
0
src/Story/testdata/I124/input.txt
vendored
Normal file
0
src/Story/testdata/I124/input.txt
vendored
Normal file
12
src/Story/testdata/I124/story.ink
vendored
Normal file
12
src/Story/testdata/I124/story.ink
vendored
Normal 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
|
||||
3
src/Story/testdata/I124/transcript.txt
vendored
Normal file
3
src/Story/testdata/I124/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
One
|
||||
Two
|
||||
Three
|
||||
1
src/Story/testdata/I127/input.txt
vendored
Normal file
1
src/Story/testdata/I127/input.txt
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
1
|
||||
10
src/Story/testdata/I127/story.ink
vendored
Normal file
10
src/Story/testdata/I127/story.ink
vendored
Normal 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
|
||||
5
src/Story/testdata/I127/transcript.txt
vendored
Normal file
5
src/Story/testdata/I127/transcript.txt
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
Hello world!
|
||||
Hello world 2!
|
||||
|
||||
1: choice
|
||||
?> choice
|
||||
|
|
@ -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 = &.{};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue