feat: code generation and execution for simple choice statements
This commit is contained in:
parent
55346fcd85
commit
889f678dd8
4 changed files with 389 additions and 179 deletions
109
src/Story.zig
109
src/Story.zig
|
|
@ -12,12 +12,14 @@ allocator: std.mem.Allocator,
|
|||
dump_writer: ?*std.Io.Writer = null,
|
||||
is_exited: bool = false,
|
||||
can_advance: bool = false,
|
||||
gc_objects: std.SinglyLinkedList = .{},
|
||||
choice_index: usize = 0,
|
||||
current_choices: std.ArrayListUnmanaged(Choice) = .empty,
|
||||
globals: std.StringHashMapUnmanaged(?*Object) = .empty,
|
||||
paths: std.ArrayListUnmanaged(*Object) = .empty,
|
||||
stack: std.ArrayListUnmanaged(?*Object) = .empty,
|
||||
call_stack: std.ArrayListUnmanaged(CallFrame) = .empty,
|
||||
stack_max: usize = 128,
|
||||
gc_objects: std.SinglyLinkedList = .{},
|
||||
|
||||
pub const CallFrame = struct {
|
||||
ip: usize,
|
||||
|
|
@ -25,15 +27,30 @@ pub const CallFrame = struct {
|
|||
callee: *Object.ContentPath,
|
||||
};
|
||||
|
||||
pub const Choice = struct {
|
||||
text: std.ArrayListUnmanaged(u8),
|
||||
dest_offset: u16,
|
||||
};
|
||||
|
||||
pub const Opcode = enum(u8) {
|
||||
/// Exit the VM normally.
|
||||
exit,
|
||||
ret,
|
||||
/// Pop a value off the stack, discarding it.
|
||||
pop,
|
||||
true,
|
||||
false,
|
||||
/// Pop two values off the stack and calculate their sum.
|
||||
/// The result will be pushed to the stack.
|
||||
add,
|
||||
/// Pop two values off the stack and calculate their difference.
|
||||
/// The result will be pushed to the stack.
|
||||
sub,
|
||||
/// Pop two values off the stack and calculate their product.
|
||||
/// The result will be pushed to the stack.
|
||||
mul,
|
||||
/// Pop two values off the stack and calculate their quotient.
|
||||
/// The result will be pushed to the stack.
|
||||
div,
|
||||
mod,
|
||||
neg,
|
||||
|
|
@ -53,12 +70,16 @@ pub const Opcode = enum(u8) {
|
|||
store,
|
||||
load_global,
|
||||
store_global,
|
||||
load_choice_id,
|
||||
content,
|
||||
line,
|
||||
glue,
|
||||
choice,
|
||||
flush,
|
||||
/// Pop a value off the stack and write it to the content stream.
|
||||
stream_push,
|
||||
stream_line,
|
||||
stream_glue,
|
||||
/// Flush the content stream to the story consumer.
|
||||
stream_flush,
|
||||
br_push,
|
||||
br_table,
|
||||
br_dispatch,
|
||||
br_select_index,
|
||||
_,
|
||||
};
|
||||
|
||||
|
|
@ -71,6 +92,7 @@ pub fn deinit(story: *Story) void {
|
|||
object.destroy(story);
|
||||
}
|
||||
|
||||
story.current_choices.deinit(gpa);
|
||||
story.globals.deinit(gpa);
|
||||
story.paths.deinit(gpa);
|
||||
story.stack.deinit(gpa);
|
||||
|
|
@ -174,14 +196,17 @@ fn setGlobal(vm: *Story, key: *const Object.String, value: *Object) !void {
|
|||
return vm.globals.putAssumeCapacity(key_bytes, value);
|
||||
}
|
||||
|
||||
fn execute(vm: *Story, writer: *std.Io.Writer) !void {
|
||||
fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
||||
const gpa = vm.allocator;
|
||||
defer {
|
||||
vm.can_advance = false;
|
||||
}
|
||||
if (vm.isCallStackEmpty()) return;
|
||||
if (vm.isCallStackEmpty()) return .empty;
|
||||
|
||||
const frame = vm.currentFrame();
|
||||
const code = std.mem.bytesAsSlice(Opcode, frame.callee.bytes);
|
||||
var stream_writer = std.Io.Writer.Allocating.init(gpa);
|
||||
defer stream_writer.deinit();
|
||||
|
||||
while (true) {
|
||||
if (vm.dump_writer) |w| {
|
||||
|
|
@ -190,7 +215,7 @@ fn execute(vm: *Story, writer: *std.Io.Writer) !void {
|
|||
switch (code[frame.ip]) {
|
||||
.exit => {
|
||||
vm.is_exited = true;
|
||||
return;
|
||||
return .empty;
|
||||
},
|
||||
.pop => {
|
||||
const object_top = vm.popStack();
|
||||
|
|
@ -224,16 +249,6 @@ fn execute(vm: *Story, writer: *std.Io.Writer) !void {
|
|||
}
|
||||
frame.ip += 1;
|
||||
},
|
||||
.content => {
|
||||
const arg_object = vm.popStack();
|
||||
if (arg_object) |object| {
|
||||
const string_object = try Object.String.fromObject(vm, object);
|
||||
const string_bytes = string_object.bytes[0..string_object.length];
|
||||
try writer.writeAll(string_bytes);
|
||||
}
|
||||
|
||||
frame.ip += 1;
|
||||
},
|
||||
.load_const => {
|
||||
const index: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||
const value = try vm.getConstant(frame, index);
|
||||
|
|
@ -276,17 +291,53 @@ fn execute(vm: *Story, writer: *std.Io.Writer) !void {
|
|||
}
|
||||
frame.ip += 2;
|
||||
},
|
||||
.stream_push => {
|
||||
const arg_object = vm.popStack();
|
||||
if (arg_object) |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);
|
||||
}
|
||||
|
||||
frame.ip += 1;
|
||||
},
|
||||
.stream_flush => {
|
||||
frame.ip += 1;
|
||||
return stream_writer.toArrayList();
|
||||
},
|
||||
.br_push => {
|
||||
const arg_offset = std.mem.bytesToValue(u16, code[frame.ip + 1 ..][0..2]);
|
||||
|
||||
try vm.current_choices.append(gpa, .{
|
||||
.text = stream_writer.toArrayList(),
|
||||
.dest_offset = std.mem.bigToNative(u16, arg_offset),
|
||||
});
|
||||
frame.ip += 3;
|
||||
},
|
||||
.br_table => {
|
||||
// No-op currently.
|
||||
frame.ip += 1;
|
||||
},
|
||||
.br_select_index => {
|
||||
vm.can_advance = false;
|
||||
frame.ip += 1;
|
||||
return .empty;
|
||||
},
|
||||
.br_dispatch => {
|
||||
const index = vm.choice_index;
|
||||
const branch_dispatch = vm.current_choices.items[index];
|
||||
defer vm.current_choices.clearRetainingCapacity();
|
||||
|
||||
frame.ip = branch_dispatch.dest_offset;
|
||||
},
|
||||
else => return error.InvalidInstruction,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(story: *Story) ![]const u8 {
|
||||
const gpa = story.allocator;
|
||||
var aw = std.Io.Writer.Allocating.init(gpa);
|
||||
|
||||
try story.execute(&aw.writer);
|
||||
return aw.toOwnedSlice();
|
||||
pub fn advance(story: *Story, gpa: std.mem.Allocator) ![]const u8 {
|
||||
var content = try story.execute();
|
||||
return content.toOwnedSlice(gpa);
|
||||
}
|
||||
|
||||
fn divert(vm: *Story, path_name: []const u8) !void {
|
||||
|
|
@ -319,6 +370,12 @@ pub const LoadOptions = struct {
|
|||
use_color: bool = true,
|
||||
};
|
||||
|
||||
pub fn selectChoiceIndex(story: *Story, index: usize) !void {
|
||||
if (index >= story.current_choices.items.len) return error.InvalidChoice;
|
||||
story.choice_index = index;
|
||||
story.can_advance = true;
|
||||
}
|
||||
|
||||
pub fn loadFromString(
|
||||
gpa: std.mem.Allocator,
|
||||
source_bytes: [:0]const u8,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue