feat: cheaper runtime value type, float and int arithmetic rules
This commit is contained in:
parent
9b5cd4038f
commit
92e8bcd866
13 changed files with 630 additions and 448 deletions
378
src/Story.zig
378
src/Story.zig
|
|
@ -15,10 +15,10 @@ is_exited: bool = false,
|
|||
can_advance: bool = false,
|
||||
choice_index: usize = 0,
|
||||
current_choices: std.ArrayListUnmanaged(Choice) = .empty,
|
||||
code_chunks: std.ArrayListUnmanaged(*Object) = .empty,
|
||||
constants_pool: std.ArrayListUnmanaged(*Object) = .empty,
|
||||
globals: std.StringHashMapUnmanaged(?*Object) = .empty,
|
||||
stack: std.ArrayListUnmanaged(?*Object) = .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,
|
||||
gc_objects: std.SinglyLinkedList = .{},
|
||||
|
|
@ -27,6 +27,158 @@ string_bytes: []const u8 = &.{},
|
|||
|
||||
pub const default_knot_name: [:0]const u8 = "$__main__$";
|
||||
|
||||
pub const Value = union(enum) {
|
||||
bool: bool,
|
||||
int: i64,
|
||||
float: f64,
|
||||
object: *Object,
|
||||
|
||||
pub fn tagBytes(v: Value) []const u8 {
|
||||
return switch (v) {
|
||||
.bool => "Bool",
|
||||
.int => "Int",
|
||||
.float => "Float",
|
||||
.object => "Object",
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isNumeric(v: Value) bool {
|
||||
return v == .int or v == .float;
|
||||
}
|
||||
|
||||
pub fn isTruthy(v: Value) bool {
|
||||
return switch (v) {
|
||||
//.nil => false,
|
||||
.bool => |b| b,
|
||||
.int => |i| i != 0,
|
||||
.float => |f| f != 0.0,
|
||||
.object => true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toFloat(v: Value) f64 {
|
||||
return switch (v) {
|
||||
.int => |i| @floatFromInt(i),
|
||||
.float => |f| f,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn add(lhs: Value, rhs: Value, story: *Story) !Value {
|
||||
return arith(lhs, rhs, .add, story);
|
||||
}
|
||||
|
||||
pub fn arith(lhs: Value, rhs: Value, op: Opcode, story: *Story) !Value {
|
||||
if (lhs.isNumeric() and rhs.isNumeric())
|
||||
return numericArith(lhs, rhs, op);
|
||||
if (op == .add) {
|
||||
if (lhs == .object and lhs.object.tag == .string)
|
||||
return concat(lhs, rhs, story);
|
||||
if (rhs == .object and rhs.object.tag == .string)
|
||||
return concat(lhs, rhs, story);
|
||||
}
|
||||
return error.TypeError;
|
||||
}
|
||||
|
||||
pub fn numericArith(lhs: Value, rhs: Value, op: Story.Opcode) !Value {
|
||||
if (!lhs.isNumeric() or !rhs.isNumeric()) return error.TypeError;
|
||||
|
||||
if (lhs == .int and rhs == .int) {
|
||||
switch (op) {
|
||||
.add => return .{ .int = lhs.int +% rhs.int },
|
||||
.sub => return .{ .int = lhs.int -% rhs.int },
|
||||
.mul => return .{ .int = lhs.int *% rhs.int },
|
||||
.div => {
|
||||
if (rhs.int == 0)
|
||||
return error.DivisionByZero;
|
||||
return .{ .int = @divTrunc(lhs.int, rhs.int) };
|
||||
},
|
||||
.mod => if (rhs.int == 0)
|
||||
return error.DivisionByZero
|
||||
else
|
||||
return .{ .int = @mod(lhs.int, rhs.int) },
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
const l = lhs.toFloat();
|
||||
const r = rhs.toFloat();
|
||||
switch (op) {
|
||||
.add => return .{ .float = l + r },
|
||||
.sub => return .{ .float = l - r },
|
||||
.mul => return .{ .float = l * r },
|
||||
.div => if (r == 0.0)
|
||||
return error.DivisionByZero
|
||||
else
|
||||
return .{ .float = l / r },
|
||||
.mod => if (r == 0.0)
|
||||
return error.DivisionByZero
|
||||
else
|
||||
return .{ .float = @mod(l, r) },
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eql(lhs: Value, rhs: Value) bool {
|
||||
return switch (lhs) {
|
||||
.bool => |l| rhs == .bool and l == rhs.bool,
|
||||
.int => |l| switch (rhs) {
|
||||
.int => |r| l == r,
|
||||
.float => |r| @as(f64, @floatFromInt(l)) == r,
|
||||
else => false,
|
||||
},
|
||||
.float => |l| switch (rhs) {
|
||||
.int => |r| l == @as(f64, @floatFromInt(r)),
|
||||
.float => |r| l == r,
|
||||
else => false,
|
||||
},
|
||||
.object => |l| rhs == .object and l == rhs.object,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn compare(lhs: Value, rhs: Value, op: Story.Opcode) !Value {
|
||||
return switch (op) {
|
||||
.cmp_eq => .{ .bool = lhs.eql(rhs) },
|
||||
.cmp_neq => .{ .bool = !lhs.eql(rhs) },
|
||||
.cmp_lt, .cmp_gt, .cmp_lte, .cmp_gte => blk: {
|
||||
if (!lhs.isNumeric() or !rhs.isNumeric()) return error.TypeError;
|
||||
const l = lhs.toFloat();
|
||||
const r = rhs.toFloat();
|
||||
break :blk .{
|
||||
.bool = switch (op) {
|
||||
.cmp_lt => l < r,
|
||||
.cmp_gt => l > r,
|
||||
.cmp_lte => l <= r,
|
||||
.cmp_gte => l >= r,
|
||||
else => unreachable,
|
||||
},
|
||||
};
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn logicalNot(lhs: Value) Value {
|
||||
return .{ .bool = !lhs.isTruthy() };
|
||||
}
|
||||
|
||||
pub fn concat(lhs: Value, rhs: Value, story: *Story) !Value {
|
||||
const lhs_object = try Object.String.fromValue(story, lhs);
|
||||
const rhs_object = try Object.String.fromValue(story, rhs);
|
||||
const str_object = try Object.String.concat(story, lhs_object, rhs_object);
|
||||
return .{ .object = &str_object.base };
|
||||
}
|
||||
|
||||
pub fn negate(lhs: Value) !Value {
|
||||
switch (lhs) {
|
||||
.bool => return error.TypeError,
|
||||
.int => |int| return .{ .int = -int },
|
||||
.float => |float| return .{ .float = -float },
|
||||
.object => return error.TypeError,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const CallFrame = struct {
|
||||
ip: usize,
|
||||
sp: usize,
|
||||
|
|
@ -64,6 +216,7 @@ pub const Opcode = enum(u8) {
|
|||
neg,
|
||||
not,
|
||||
cmp_eq,
|
||||
cmp_neq,
|
||||
cmp_lt,
|
||||
cmp_gt,
|
||||
cmp_lte,
|
||||
|
|
@ -115,72 +268,6 @@ pub fn deinit(story: *Story) void {
|
|||
gpa.free(story.string_bytes);
|
||||
}
|
||||
|
||||
pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
||||
var story_dumper: Dumper = .{ .story = story };
|
||||
|
||||
try writer.writeAll("=== Constants ===\n");
|
||||
for (story.constants_pool.items) |global_constant| {
|
||||
try story_dumper.dumpObject(writer, global_constant);
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
try writer.writeAll("\n");
|
||||
try writer.writeAll("=== Globals ===\n");
|
||||
|
||||
var global_iter = story.globals.iterator();
|
||||
while (global_iter.next()) |entry| {
|
||||
try writer.print("{s} => ...\n", .{entry.key_ptr.*});
|
||||
}
|
||||
|
||||
try writer.writeAll("\n");
|
||||
try writer.writeAll("=== Knots ===\n");
|
||||
try writer.flush();
|
||||
|
||||
var knots_iter = story.globals.iterator();
|
||||
while (knots_iter.next()) |entry| {
|
||||
if (entry.value_ptr.*) |global| {
|
||||
switch (global.tag) {
|
||||
.knot => {
|
||||
try writer.writeAll("*");
|
||||
story_dumper.indent_level += 2;
|
||||
try story_dumper.dumpKnot(writer, @ptrCast(global));
|
||||
story_dumper.indent_level -= 2;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trace(story: *Story, writer: *std.Io.Writer, frame: *CallFrame) !void {
|
||||
try writer.print("\tStack => stack_pointer={d}, objects=[", .{frame.sp});
|
||||
var story_dumper: Dumper = .{ .story = story };
|
||||
const stack = &story.stack;
|
||||
const stack_top = story.stack.items.len;
|
||||
if (stack_top > 0) {
|
||||
// FIXME: There has to be a better way to do this.
|
||||
if (stack_top > 1) {
|
||||
for (stack.items[frame.sp .. stack.items.len - 1]) |slot| {
|
||||
if (slot) |object| {
|
||||
try story_dumper.dumpObject(writer, object);
|
||||
} else {
|
||||
try writer.writeAll("null");
|
||||
}
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
}
|
||||
if (stack.items[stack.items.len - 1]) |object| {
|
||||
try story_dumper.dumpObject(writer, object);
|
||||
} else {
|
||||
try writer.writeAll("null");
|
||||
}
|
||||
}
|
||||
|
||||
try writer.writeAll("]\n");
|
||||
_ = try story_dumper.dumpInst(writer, frame.callee, frame.ip, true);
|
||||
return writer.flush();
|
||||
}
|
||||
|
||||
fn isCallStackEmpty(vm: *const Story) bool {
|
||||
return vm.call_stack.items.len == 0;
|
||||
}
|
||||
|
|
@ -189,34 +276,34 @@ fn currentFrame(vm: *Story) *CallFrame {
|
|||
return &vm.call_stack.items[vm.call_stack.items.len - 1];
|
||||
}
|
||||
|
||||
fn peekStack(vm: *Story, offset: usize) ?*Object {
|
||||
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];
|
||||
}
|
||||
|
||||
fn pushStack(vm: *Story, object: *Object) !void {
|
||||
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, object);
|
||||
return vm.stack.append(gpa, value);
|
||||
}
|
||||
|
||||
fn popStack(vm: *Story) ?*Object {
|
||||
fn popStack(vm: *Story) ?Value {
|
||||
return vm.stack.pop() orelse unreachable;
|
||||
}
|
||||
|
||||
fn getConstant(story: *Story, frame: *CallFrame, offset: u8) !*Object {
|
||||
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];
|
||||
}
|
||||
|
||||
fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?*Object {
|
||||
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);
|
||||
|
|
@ -224,7 +311,7 @@ fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?*Object {
|
|||
return vm.stack.items[stack_offset];
|
||||
}
|
||||
|
||||
fn setLocal(vm: *Story, frame: *CallFrame, offset: u8, value: *Object) void {
|
||||
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);
|
||||
|
|
@ -233,16 +320,35 @@ fn setLocal(vm: *Story, frame: *CallFrame, offset: u8, value: *Object) void {
|
|||
}
|
||||
|
||||
// TODO: This should probably check the constants table first.
|
||||
fn getGlobal(vm: *Story, key: *const Object.String) !*Object {
|
||||
const key_bytes = key.bytes[0..key.length];
|
||||
const val = vm.globals.get(key_bytes) orelse return error.InvalidVariable;
|
||||
return val orelse unreachable;
|
||||
fn getGlobal(vm: *Story, key: Value) !Value {
|
||||
switch (key) {
|
||||
.object => |object| switch (object.tag) {
|
||||
.string => {
|
||||
const str_object: *Object.String = @ptrCast(object);
|
||||
if (vm.globals.get(str_object.toSlice())) |value| {
|
||||
// FIXME: Support for nil
|
||||
return value.?;
|
||||
}
|
||||
return error.InvalidVariable;
|
||||
},
|
||||
else => return error.TypeError,
|
||||
},
|
||||
else => return error.TypeError,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This should probably check the constants table first.
|
||||
fn setGlobal(vm: *Story, key: *const Object.String, value: *Object) !void {
|
||||
const key_bytes = key.bytes[0..key.length];
|
||||
return vm.globals.putAssumeCapacity(key_bytes, value);
|
||||
fn setGlobal(vm: *Story, key: Value, value: Value) !void {
|
||||
switch (key) {
|
||||
.object => |object| switch (object.tag) {
|
||||
.string => {
|
||||
const str_object: *Object.String = @ptrCast(object);
|
||||
return vm.globals.putAssumeCapacity(str_object.toSlice(), value);
|
||||
},
|
||||
else => return error.TypeError,
|
||||
},
|
||||
else => return error.TypeError,
|
||||
}
|
||||
}
|
||||
|
||||
fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
||||
|
|
@ -259,7 +365,7 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
const frame = vm.currentFrame();
|
||||
const code = std.mem.bytesAsSlice(Opcode, frame.callee.code.bytecode);
|
||||
if (vm.dump_writer) |w| {
|
||||
vm.trace(w, frame) catch {};
|
||||
Dumper.trace(vm, w, frame) catch {};
|
||||
}
|
||||
switch (code[frame.ip]) {
|
||||
.exit => {
|
||||
|
|
@ -268,31 +374,23 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
return .empty;
|
||||
},
|
||||
.true => {
|
||||
// TODO: Intern this value.
|
||||
const true_object = try Object.Number.create(vm, .{
|
||||
.boolean = true,
|
||||
});
|
||||
try vm.pushStack(@ptrCast(true_object));
|
||||
const value: Value = .{ .bool = true };
|
||||
try vm.pushStack(value);
|
||||
frame.ip += 1;
|
||||
},
|
||||
.false => {
|
||||
// TODO: Intern this value.
|
||||
const false_object = try Object.Number.create(vm, .{
|
||||
.boolean = false,
|
||||
});
|
||||
try vm.pushStack(@ptrCast(false_object));
|
||||
const value: Value = .{ .bool = false };
|
||||
try vm.pushStack(value);
|
||||
frame.ip += 1;
|
||||
},
|
||||
.pop => {
|
||||
if (vm.popStack()) |_| {} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
if (vm.popStack()) |_| {} else return error.InvalidArgument;
|
||||
frame.ip += 1;
|
||||
},
|
||||
.add => {
|
||||
const lhs = vm.peekStack(1) orelse return error.Bugged;
|
||||
const rhs = vm.peekStack(0) orelse return error.Bugged;
|
||||
const value = try Object.add(vm, lhs, rhs);
|
||||
const value = try Value.add(lhs, rhs, vm);
|
||||
|
||||
_ = vm.popStack();
|
||||
_ = vm.popStack();
|
||||
|
|
@ -302,29 +400,26 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
.sub, .mul, .div, .mod => |op| {
|
||||
const lhs = vm.peekStack(1) orelse return error.Bugged;
|
||||
const rhs = vm.peekStack(0) orelse return error.Bugged;
|
||||
const value = try Object.Number.performArithmetic(vm, op, @ptrCast(lhs), @ptrCast(rhs));
|
||||
const value = try Value.arith(lhs, rhs, op, vm);
|
||||
|
||||
_ = vm.popStack();
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(@ptrCast(value));
|
||||
try vm.pushStack(value);
|
||||
frame.ip += 1;
|
||||
},
|
||||
.neg => {
|
||||
if (vm.peekStack(0)) |arg| {
|
||||
_ = Object.Number.negate(@ptrCast(arg));
|
||||
if (vm.popStack()) |arg| {
|
||||
const value = try Value.negate(arg);
|
||||
try vm.pushStack(value);
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
frame.ip += 1;
|
||||
},
|
||||
.not => {
|
||||
if (vm.peekStack(0)) |arg| {
|
||||
const value = try Object.Number.create(vm, .{
|
||||
.boolean = arg.isFalsey(),
|
||||
});
|
||||
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(@ptrCast(value));
|
||||
if (vm.popStack()) |arg| {
|
||||
const value = Value.logicalNot(arg);
|
||||
try vm.pushStack(value);
|
||||
} else {
|
||||
return error.StackOverflow;
|
||||
}
|
||||
|
|
@ -333,20 +428,20 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
.cmp_eq => {
|
||||
const lhs = vm.peekStack(1) orelse return error.Bugged;
|
||||
const rhs = vm.peekStack(0) orelse return error.Bugged;
|
||||
const value = try Object.cmpEql(vm, @ptrCast(lhs), @ptrCast(rhs));
|
||||
const value = try Value.compare(lhs, rhs, .cmp_eq);
|
||||
_ = vm.popStack();
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(@ptrCast(value));
|
||||
try vm.pushStack(value);
|
||||
|
||||
frame.ip += 1;
|
||||
},
|
||||
.cmp_lt, .cmp_gt, .cmp_lte, .cmp_gte => |op| {
|
||||
const lhs = vm.peekStack(1) orelse return error.Bugged;
|
||||
const rhs = vm.peekStack(0) orelse return error.Bugged;
|
||||
const value = try Object.Number.performLogic(vm, op, @ptrCast(lhs), @ptrCast(rhs));
|
||||
const value = try Value.compare(lhs, rhs, op);
|
||||
_ = vm.popStack();
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(@ptrCast(value));
|
||||
try vm.pushStack(value);
|
||||
|
||||
frame.ip += 1;
|
||||
},
|
||||
|
|
@ -359,7 +454,7 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
frame.ip += 3;
|
||||
|
||||
if (vm.peekStack(0)) |condition| {
|
||||
if (!condition.isFalsey()) {
|
||||
if (condition.isTruthy()) {
|
||||
frame.ip += arg_offset;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -371,7 +466,7 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
frame.ip += 3;
|
||||
|
||||
if (vm.peekStack(0)) |condition| {
|
||||
if (condition.isFalsey()) {
|
||||
if (!condition.isTruthy()) {
|
||||
frame.ip += arg_offset;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -402,19 +497,16 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
.load_global => {
|
||||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
const global_name = try vm.getConstant(frame, arg_offset);
|
||||
assert(global_name.tag == .string);
|
||||
|
||||
const global_value = try vm.getGlobal(@ptrCast(global_name));
|
||||
const global_value = try vm.getGlobal(global_name);
|
||||
try vm.pushStack(global_value);
|
||||
frame.ip += 2;
|
||||
},
|
||||
.store_global => {
|
||||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
const global_name = try vm.getConstant(frame, arg_offset);
|
||||
assert(global_name.tag == .string);
|
||||
|
||||
if (vm.peekStack(0)) |arg| {
|
||||
try vm.setGlobal(@ptrCast(global_name), arg);
|
||||
try vm.setGlobal(global_name, arg);
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(arg);
|
||||
} else {
|
||||
|
|
@ -426,10 +518,9 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
// FIXME: This should be more strict.
|
||||
// Its not because theres a bug in when these instructions are
|
||||
// emitted.
|
||||
if (vm.peekStack(0)) |object| {
|
||||
const string_object = try Object.String.fromObject(vm, object);
|
||||
const string_bytes = string_object.bytes[0..string_object.length];
|
||||
try stream_writer.writer.writeAll(string_bytes);
|
||||
if (vm.peekStack(0)) |arg| {
|
||||
const str_object = try Object.String.fromValue(vm, arg);
|
||||
try stream_writer.writer.writeAll(str_object.toSlice());
|
||||
_ = vm.popStack();
|
||||
} //else {
|
||||
//return error.InvalidArgument;
|
||||
|
|
@ -474,8 +565,8 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
frame.ip += 2;
|
||||
|
||||
if (peekStack(vm, arg_offset)) |knot| {
|
||||
try divertToKnot(vm, @ptrCast(knot));
|
||||
if (peekStack(vm, arg_offset)) |value| {
|
||||
try divertToValue(vm, value);
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
|
|
@ -484,17 +575,15 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
frame.ip += 2;
|
||||
|
||||
if (peekStack(vm, 0)) |obj| {
|
||||
assert(obj.tag == .knot);
|
||||
const knot_obj: *Object.Knot = @ptrCast(obj);
|
||||
const arg_obj = try vm.getConstant(frame, arg_offset);
|
||||
assert(arg_obj.tag == .string);
|
||||
const knot_attr: *Object.String = @ptrCast(arg_obj);
|
||||
if (peekStack(vm, 0)) |value| {
|
||||
const knot_object: *Object.Knot = @ptrCast(value.object);
|
||||
const arg_value = try vm.getConstant(frame, arg_offset);
|
||||
const knot_attr: *Object.String = @ptrCast(arg_value.object);
|
||||
|
||||
_ = popStack(vm);
|
||||
|
||||
if (knot_obj.members.get(knot_attr.toSlice())) |attr_obj| {
|
||||
try vm.pushStack(attr_obj);
|
||||
if (knot_object.members.get(knot_attr.toSlice())) |attr_object| {
|
||||
try vm.pushStack(.{ .object = attr_object });
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
|
|
@ -519,8 +608,9 @@ pub fn advance(story: *Story, gpa: std.mem.Allocator) ![]const u8 {
|
|||
|
||||
pub fn getKnot(vm: *Story, name: []const u8) ?*Object.Knot {
|
||||
const knot: ?*Object.Knot = blk: {
|
||||
if (vm.globals.get(name)) |object| {
|
||||
break :blk @ptrCast(object);
|
||||
if (vm.globals.get(name)) |value| {
|
||||
// TODO: Do a check here.
|
||||
break :blk @ptrCast(value.?.object);
|
||||
}
|
||||
break :blk null;
|
||||
};
|
||||
|
|
@ -545,6 +635,16 @@ fn divertToKnot(vm: *Story, knot: *Object.Knot) !void {
|
|||
vm.can_advance = true;
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
fn divert(vm: *Story, knot_name: []const u8) !void {
|
||||
return if (getKnot(vm, knot_name)) |knot| {
|
||||
return divertToKnot(vm, knot);
|
||||
|
|
@ -566,6 +666,10 @@ pub fn selectChoiceIndex(story: *Story, index: usize) !void {
|
|||
story.can_advance = true;
|
||||
}
|
||||
|
||||
pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
||||
return Dumper.dump(story, writer);
|
||||
}
|
||||
|
||||
pub fn loadFromString(
|
||||
gpa: std.mem.Allocator,
|
||||
source_bytes: [:0]const u8,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue