feat: code generation for conditional and switch statements
This commit is contained in:
parent
889f678dd8
commit
d6ff3a40bd
7 changed files with 476 additions and 59 deletions
124
src/Story.zig
124
src/Story.zig
|
|
@ -38,7 +38,9 @@ pub const Opcode = enum(u8) {
|
|||
ret,
|
||||
/// Pop a value off the stack, discarding it.
|
||||
pop,
|
||||
/// Push an object representing the boolean value of "true" to the stack.
|
||||
true,
|
||||
/// Push an object representing the boolean value of "false" to the stack.
|
||||
false,
|
||||
/// Pop two values off the stack and calculate their sum.
|
||||
/// The result will be pushed to the stack.
|
||||
|
|
@ -60,8 +62,13 @@ pub const Opcode = enum(u8) {
|
|||
cmp_gt,
|
||||
cmp_lte,
|
||||
cmp_gte,
|
||||
/// Jump unconditionally to the target address.
|
||||
jmp,
|
||||
/// Jump conditionally to the target address if the boolean value at the
|
||||
/// top of the stack is true.
|
||||
jmp_t,
|
||||
/// Jump conditionally to the target address if the boolean value at the
|
||||
/// top of the stack is false.
|
||||
jmp_f,
|
||||
call,
|
||||
divert,
|
||||
|
|
@ -118,14 +125,14 @@ pub fn trace(story: *Story, writer: *std.Io.Writer, frame: *CallFrame) !void {
|
|||
if (slot) |object| {
|
||||
try story_dumper.dumpObject(object);
|
||||
} else {
|
||||
try writer.writeAll("NULL");
|
||||
try writer.writeAll("null");
|
||||
}
|
||||
try writer.writeAll(", ");
|
||||
}
|
||||
if (last_slot) |object| {
|
||||
try story_dumper.dumpObject(object);
|
||||
} else {
|
||||
try writer.writeAll("NULL");
|
||||
try writer.writeAll("null");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +205,7 @@ fn setGlobal(vm: *Story, key: *const Object.String, value: *Object) !void {
|
|||
|
||||
fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
||||
const gpa = vm.allocator;
|
||||
defer {
|
||||
errdefer {
|
||||
vm.can_advance = false;
|
||||
}
|
||||
if (vm.isCallStackEmpty()) return .empty;
|
||||
|
|
@ -215,11 +222,27 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
switch (code[frame.ip]) {
|
||||
.exit => {
|
||||
vm.is_exited = true;
|
||||
vm.can_advance = false;
|
||||
return .empty;
|
||||
},
|
||||
.true => {
|
||||
const true_object = try Object.Number.create(vm, .{
|
||||
.boolean = true,
|
||||
});
|
||||
try vm.pushStack(@ptrCast(true_object));
|
||||
frame.ip += 1;
|
||||
},
|
||||
.false => {
|
||||
const false_object = try Object.Number.create(vm, .{
|
||||
.boolean = false,
|
||||
});
|
||||
try vm.pushStack(@ptrCast(false_object));
|
||||
frame.ip += 1;
|
||||
},
|
||||
.pop => {
|
||||
const object_top = vm.popStack();
|
||||
if (object_top == null) return error.InvalidArgument;
|
||||
if (vm.popStack()) |_| {} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
frame.ip += 1;
|
||||
},
|
||||
.add => {
|
||||
|
|
@ -243,12 +266,74 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
frame.ip += 1;
|
||||
},
|
||||
.neg => {
|
||||
const arg_object = vm.peekStack(0);
|
||||
if (arg_object) |arg| {
|
||||
if (vm.peekStack(0)) |arg| {
|
||||
_ = Object.Number.negate(@ptrCast(arg));
|
||||
} 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));
|
||||
} else {
|
||||
return error.StackOverflow;
|
||||
}
|
||||
frame.ip += 1;
|
||||
},
|
||||
.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));
|
||||
_ = vm.popStack();
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(@ptrCast(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));
|
||||
_ = vm.popStack();
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(@ptrCast(value));
|
||||
|
||||
frame.ip += 1;
|
||||
},
|
||||
.jmp => {
|
||||
const arg_offset = readAddress(code, frame.ip);
|
||||
frame.ip += 3 + arg_offset;
|
||||
},
|
||||
.jmp_t => {
|
||||
const arg_offset = readAddress(code, frame.ip);
|
||||
frame.ip += 3;
|
||||
|
||||
if (vm.peekStack(0)) |condition| {
|
||||
if (!condition.isFalsey()) {
|
||||
frame.ip += arg_offset;
|
||||
}
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
},
|
||||
.jmp_f => {
|
||||
const arg_offset = readAddress(code, frame.ip);
|
||||
frame.ip += 3;
|
||||
|
||||
if (vm.peekStack(0)) |condition| {
|
||||
if (condition.isFalsey()) {
|
||||
frame.ip += arg_offset;
|
||||
}
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
},
|
||||
.load_const => {
|
||||
const index: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
const value = try vm.getConstant(frame, index);
|
||||
|
|
@ -262,10 +347,11 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
frame.ip += 2;
|
||||
},
|
||||
.store => {
|
||||
const value = vm.peekStack(0);
|
||||
if (value) |arg| {
|
||||
if (vm.peekStack(0)) |arg| {
|
||||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
vm.setLocal(frame, arg_offset, arg);
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
frame.ip += 2;
|
||||
},
|
||||
|
|
@ -283,11 +369,12 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
const global_name = try vm.getConstant(frame, arg_offset);
|
||||
assert(global_name.tag == .string);
|
||||
|
||||
const value = vm.peekStack(0);
|
||||
if (value) |arg| {
|
||||
if (vm.peekStack(0)) |arg| {
|
||||
try vm.setGlobal(@ptrCast(global_name), arg);
|
||||
_ = vm.popStack();
|
||||
try vm.pushStack(arg);
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
frame.ip += 2;
|
||||
},
|
||||
|
|
@ -297,20 +384,26 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
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);
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
|
||||
frame.ip += 1;
|
||||
},
|
||||
.stream_flush => {
|
||||
frame.ip += 1;
|
||||
|
||||
// FIXME: This is a bit of a hack, but we have to deal with this right now.
|
||||
const buffered = stream_writer.writer.buffered();
|
||||
if (buffered.len == 0) continue;
|
||||
return stream_writer.toArrayList();
|
||||
},
|
||||
.br_push => {
|
||||
const arg_offset = std.mem.bytesToValue(u16, code[frame.ip + 1 ..][0..2]);
|
||||
const arg_offset = readAddress(code, frame.ip);
|
||||
|
||||
try vm.current_choices.append(gpa, .{
|
||||
.text = stream_writer.toArrayList(),
|
||||
.dest_offset = std.mem.bigToNative(u16, arg_offset),
|
||||
.dest_offset = arg_offset,
|
||||
});
|
||||
frame.ip += 3;
|
||||
},
|
||||
|
|
@ -335,6 +428,11 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
}
|
||||
}
|
||||
|
||||
fn readAddress(code: []const Story.Opcode, offset: usize) u16 {
|
||||
const arg_offset = std.mem.bytesToValue(u16, code[offset + 1 ..][0..2]);
|
||||
return std.mem.bigToNative(u16, arg_offset);
|
||||
}
|
||||
|
||||
pub fn advance(story: *Story, gpa: std.mem.Allocator) ![]const u8 {
|
||||
var content = try story.execute();
|
||||
return content.toOwnedSlice(gpa);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue