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
337
src/AstGen.zig
337
src/AstGen.zig
|
|
@ -18,68 +18,16 @@ jump_stack: std.ArrayListUnmanaged(Jump) = .empty,
|
|||
errors: std.ArrayListUnmanaged(Ast.Error) = .empty,
|
||||
default_knot_name: [:0]const u8 = "@main@",
|
||||
|
||||
pub const CheckError = anyerror;
|
||||
|
||||
pub const Symbol = union(enum) {
|
||||
global_variable: struct {
|
||||
is_constant: bool,
|
||||
constant_slot: usize,
|
||||
node: *const Ast.Node,
|
||||
},
|
||||
local_variable: struct {
|
||||
stack_slot: usize,
|
||||
node: *const Ast.Node,
|
||||
},
|
||||
parameter: struct {
|
||||
constant_slot: usize,
|
||||
stack_slot: usize,
|
||||
},
|
||||
pub const CheckError = error{
|
||||
OutOfMemory,
|
||||
CompilerBug,
|
||||
SemanticError,
|
||||
TooManyConstants,
|
||||
InvalidCharacter,
|
||||
NotImplemented,
|
||||
};
|
||||
|
||||
pub const Jump = struct {
|
||||
label_index: usize,
|
||||
code_offset: usize,
|
||||
};
|
||||
|
||||
pub const Label = struct {
|
||||
code_offset: usize,
|
||||
};
|
||||
|
||||
pub const Chunk = struct {
|
||||
name: [:0]const u8,
|
||||
arity: usize,
|
||||
locals_count: usize,
|
||||
const_pool: std.ArrayListUnmanaged(*Story.Object),
|
||||
bytes: std.ArrayListUnmanaged(u8),
|
||||
|
||||
pub fn finalize(chunk: *Chunk, scope: *Scope) !*Story.Object.ContentPath {
|
||||
const gpa = scope.global.gpa;
|
||||
const story = scope.global.story;
|
||||
|
||||
try scope.backpatch();
|
||||
|
||||
const const_pool = try chunk.const_pool.toOwnedSlice(gpa);
|
||||
const bytes = try chunk.bytes.toOwnedSlice(gpa);
|
||||
const knot_name = try Story.Object.String.create(story, chunk.name);
|
||||
const content_path = try Story.Object.ContentPath.create(
|
||||
story,
|
||||
knot_name,
|
||||
chunk.arity,
|
||||
chunk.locals_count,
|
||||
const_pool,
|
||||
bytes,
|
||||
);
|
||||
return content_path;
|
||||
}
|
||||
|
||||
pub fn deinit(chunk: *Chunk, gpa: std.mem.Allocator) void {
|
||||
chunk.const_pool.deinit(gpa);
|
||||
chunk.bytes.deinit(gpa);
|
||||
chunk.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Scope = struct {
|
||||
const Scope = struct {
|
||||
parent: ?*Scope,
|
||||
global: *AstGen,
|
||||
chunk: *Chunk,
|
||||
|
|
@ -122,7 +70,11 @@ pub const Scope = struct {
|
|||
return null;
|
||||
}
|
||||
|
||||
pub fn insertIdentifier(scope: *Scope, node: *const Ast.Node, symbol: Symbol) !void {
|
||||
pub fn insertIdentifier(
|
||||
scope: *Scope,
|
||||
node: *const Ast.Node,
|
||||
symbol: Symbol,
|
||||
) error{OutOfMemory}!void {
|
||||
const gpa = scope.global.gpa;
|
||||
const symbol_table = &scope.symbol_table;
|
||||
const token_bytes = scope.global.getLexemeFromNode(node);
|
||||
|
|
@ -144,27 +96,7 @@ pub const Scope = struct {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn makeLabel(scope: *Scope) !usize {
|
||||
const gpa = scope.global.gpa;
|
||||
const label_stack = &scope.global.label_stack;
|
||||
const label_id = label_stack.items.len;
|
||||
const label_data: Label = .{ .code_offset = 0xffffffff };
|
||||
|
||||
try label_stack.append(gpa, label_data);
|
||||
return label_id;
|
||||
}
|
||||
|
||||
pub fn setLabel(scope: *Scope, label_id: usize) void {
|
||||
const chunk = scope.chunk;
|
||||
const code_offset = chunk.bytes.items.len;
|
||||
const label_stack = &scope.global.label_stack;
|
||||
assert(label_id <= label_stack.items.len);
|
||||
|
||||
const label_data = &label_stack.items[label_id];
|
||||
label_data.code_offset = code_offset;
|
||||
}
|
||||
|
||||
pub fn makeConstant(scope: *Scope, object: *Story.Object) !usize {
|
||||
pub fn makeConstant(scope: *Scope, object: *Story.Object) error{OutOfMemory}!usize {
|
||||
const gpa = scope.global.gpa;
|
||||
const chunk = scope.chunk;
|
||||
const const_id = chunk.const_pool.items.len;
|
||||
|
|
@ -178,7 +110,7 @@ pub const Scope = struct {
|
|||
len: u32,
|
||||
};
|
||||
|
||||
pub fn makeString(scope: *Scope, bytes: []const u8) !IndexSlice {
|
||||
pub fn makeString(scope: *Scope, bytes: []const u8) error{OutOfMemory}!IndexSlice {
|
||||
const global = scope.global;
|
||||
const gpa = scope.global.gpa;
|
||||
const str_index: u32 = @intCast(global.string_bytes.items.len);
|
||||
|
|
@ -201,7 +133,11 @@ pub const Scope = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn makeGlobal(scope: *Scope, node: *const Ast.Node, symbol: Symbol) !void {
|
||||
pub fn makeGlobal(
|
||||
scope: *Scope,
|
||||
node: *const Ast.Node,
|
||||
symbol: Symbol,
|
||||
) error{OutOfMemory}!void {
|
||||
const gpa = scope.global.gpa;
|
||||
const global_data = scope.global;
|
||||
const token_bytes = global_data.getLexemeFromNode(node);
|
||||
|
|
@ -209,11 +145,11 @@ pub const Scope = struct {
|
|||
return scope.insertIdentifier(node, symbol);
|
||||
}
|
||||
|
||||
pub fn emitByte(scope: *Scope, byte: u8) !void {
|
||||
pub fn emitByte(scope: *Scope, byte: u8) error{OutOfMemory}!void {
|
||||
return scope.chunk.bytes.append(scope.global.gpa, byte);
|
||||
}
|
||||
|
||||
pub fn emitSimpleInst(scope: *Scope, op: Story.Opcode) !void {
|
||||
pub fn emitSimpleInst(scope: *Scope, op: Story.Opcode) error{OutOfMemory}!void {
|
||||
return scope.emitByte(@intFromEnum(op));
|
||||
}
|
||||
|
||||
|
|
@ -224,7 +160,7 @@ pub const Scope = struct {
|
|||
try scope.emitByte(@intCast(arg));
|
||||
}
|
||||
|
||||
pub fn emitJumpInst(scope: *Scope, op: Story.Opcode) !void {
|
||||
pub fn emitJumpInst(scope: *Scope, op: Story.Opcode) error{OutOfMemory}!usize {
|
||||
const bytes = &scope.chunk.bytes;
|
||||
try scope.emitSimpleInst(op);
|
||||
try scope.emitByte(0xff);
|
||||
|
|
@ -232,7 +168,125 @@ pub const Scope = struct {
|
|||
return bytes.items.len - 2;
|
||||
}
|
||||
|
||||
pub fn backpatch(_: *Scope) !void {}
|
||||
// Create a new code label.
|
||||
pub fn makeLabel(scope: *Scope) error{OutOfMemory}!usize {
|
||||
const gpa = scope.global.gpa;
|
||||
const dummy_address = 0xffffffff;
|
||||
const label_stack = &scope.global.label_stack;
|
||||
const label_index = label_stack.items.len;
|
||||
const label_data: Label = .{ .code_offset = dummy_address };
|
||||
|
||||
try label_stack.append(gpa, label_data);
|
||||
return label_index;
|
||||
}
|
||||
|
||||
// Sets the code offset pointed to by a label.
|
||||
pub fn setLabel(scope: *Scope, label_id: usize) void {
|
||||
const chunk = scope.chunk;
|
||||
const code_offset = chunk.bytes.items.len;
|
||||
const label_stack = &scope.global.label_stack;
|
||||
assert(label_id <= label_stack.items.len);
|
||||
|
||||
const label_data = &label_stack.items[label_id];
|
||||
label_data.code_offset = code_offset;
|
||||
}
|
||||
|
||||
pub fn makeJump(scope: *Scope, jump: Jump) !usize {
|
||||
const gpa = scope.global.gpa;
|
||||
const jump_stack = &scope.global.jump_stack;
|
||||
const jump_index = jump_stack.items.len;
|
||||
try jump_stack.append(gpa, jump);
|
||||
return jump_index;
|
||||
}
|
||||
|
||||
pub fn resolveLabels(scope: *Scope, start_index: usize, end_index: usize) !void {
|
||||
assert(start_index <= end_index);
|
||||
const jump_stack = &scope.global.jump_stack;
|
||||
const label_stack = &scope.global.label_stack;
|
||||
const chunk_bytes = &scope.chunk.bytes;
|
||||
const jump_list = jump_stack.items[start_index..end_index];
|
||||
|
||||
for (jump_list) |jump| {
|
||||
const label = label_stack.items[jump.label_index];
|
||||
const jump_offset: usize = switch (jump.mode) {
|
||||
.relative => label.code_offset - jump.code_offset - 2,
|
||||
.absolute => label.code_offset,
|
||||
};
|
||||
if (jump_offset >= std.math.maxInt(u16)) {
|
||||
std.debug.print("Too much code to jump over!\n", .{});
|
||||
return error.CompilerBug;
|
||||
}
|
||||
|
||||
assert(chunk_bytes.capacity >= jump.code_offset + 2);
|
||||
chunk_bytes.items[jump.code_offset] = @intCast((jump_offset >> 8) & 0xff);
|
||||
chunk_bytes.items[jump.code_offset + 1] = @intCast(jump_offset & 0xff);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Symbol = union(enum) {
|
||||
global_variable: struct {
|
||||
is_constant: bool,
|
||||
constant_slot: usize,
|
||||
node: *const Ast.Node,
|
||||
},
|
||||
local_variable: struct {
|
||||
stack_slot: usize,
|
||||
node: *const Ast.Node,
|
||||
},
|
||||
parameter: struct {
|
||||
constant_slot: usize,
|
||||
stack_slot: usize,
|
||||
},
|
||||
};
|
||||
|
||||
pub const Jump = struct {
|
||||
mode: enum {
|
||||
relative,
|
||||
absolute,
|
||||
},
|
||||
label_index: usize,
|
||||
code_offset: usize,
|
||||
};
|
||||
|
||||
pub const Label = struct {
|
||||
code_offset: usize,
|
||||
};
|
||||
|
||||
// Code chunk to emitting bytes and constants to during code generation.
|
||||
pub const Chunk = struct {
|
||||
name: [:0]const u8,
|
||||
arity: usize,
|
||||
locals_count: usize,
|
||||
const_pool: std.ArrayListUnmanaged(*Story.Object),
|
||||
bytes: std.ArrayListUnmanaged(u8),
|
||||
|
||||
pub fn finalize(chunk: *Chunk, scope: *Scope) !*Story.Object.ContentPath {
|
||||
const gpa = scope.global.gpa;
|
||||
const story = scope.global.story;
|
||||
const jump_stack_top = scope.global.jump_stack.items.len;
|
||||
|
||||
try scope.resolveLabels(scope.jump_stack_top, jump_stack_top);
|
||||
|
||||
const const_pool = try chunk.const_pool.toOwnedSlice(gpa);
|
||||
const bytes = try chunk.bytes.toOwnedSlice(gpa);
|
||||
const knot_name = try Story.Object.String.create(story, chunk.name);
|
||||
const content_path = try Story.Object.ContentPath.create(
|
||||
story,
|
||||
knot_name,
|
||||
chunk.arity,
|
||||
chunk.locals_count,
|
||||
const_pool,
|
||||
bytes,
|
||||
);
|
||||
return content_path;
|
||||
}
|
||||
|
||||
pub fn deinit(chunk: *Chunk, gpa: std.mem.Allocator) void {
|
||||
chunk.const_pool.deinit(gpa);
|
||||
chunk.bytes.deinit(gpa);
|
||||
chunk.* = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinit(astgen: *AstGen) void {
|
||||
|
|
@ -363,11 +417,11 @@ fn checkContentExpr(scope: *Scope, expr: *const Ast.Node) CheckError!void {
|
|||
switch (child_node.tag) {
|
||||
.string_literal => {
|
||||
try checkStringLiteral(scope, child_node);
|
||||
try scope.emitSimpleInst(.content);
|
||||
try scope.emitSimpleInst(.stream_push);
|
||||
},
|
||||
.inline_logic_expr => {
|
||||
try checkInlineLogicExpr(scope, child_node);
|
||||
try scope.emitSimpleInst(.content);
|
||||
try scope.emitSimpleInst(.stream_push);
|
||||
},
|
||||
else => return error.NotImplemented,
|
||||
}
|
||||
|
|
@ -379,13 +433,6 @@ fn checkContentStmt(scope: *Scope, stmt: *const Ast.Node) CheckError!void {
|
|||
return checkContentExpr(scope, expr_node);
|
||||
}
|
||||
|
||||
fn checkBlockStmt(parent_scope: *Scope, block_stmt: *const Ast.Node) CheckError!void {
|
||||
var block_scope = parent_scope.makeSubBlock();
|
||||
defer block_scope.deinit();
|
||||
const children = block_stmt.data.list.items orelse return;
|
||||
for (children) |child_stmt| try checkStmt(&block_scope, child_stmt);
|
||||
}
|
||||
|
||||
fn checkAssignStmt(scope: *Scope, stmt: *const Ast.Node) CheckError!void {
|
||||
const lhs = stmt.data.bin.lhs orelse return error.CompilerBug;
|
||||
const rhs = stmt.data.bin.rhs orelse return error.CompilerBug;
|
||||
|
|
@ -413,9 +460,91 @@ fn checkAssignStmt(scope: *Scope, stmt: *const Ast.Node) CheckError!void {
|
|||
return scope.fail(.unknown_identifier, lhs);
|
||||
}
|
||||
|
||||
fn checkBlockStmt(parent_scope: *Scope, block_stmt: *const Ast.Node) CheckError!void {
|
||||
var block_scope = parent_scope.makeSubBlock();
|
||||
defer block_scope.deinit();
|
||||
const children = block_stmt.data.list.items orelse return;
|
||||
for (children) |child_stmt| try checkStmt(&block_scope, child_stmt);
|
||||
}
|
||||
|
||||
fn checkChoiceStmt(scope: *Scope, stmt_node: *const Ast.Node) CheckError!void {
|
||||
const Choice = struct {
|
||||
label_index: usize,
|
||||
start_expression: ?*const Ast.Node,
|
||||
option_expression: ?*const Ast.Node,
|
||||
inner_expression: ?*const Ast.Node,
|
||||
block_stmt: ?*const Ast.Node,
|
||||
};
|
||||
|
||||
const branch_list = stmt_node.data.list.items orelse unreachable;
|
||||
assert(branch_list.len != 0);
|
||||
|
||||
const gpa = scope.global.gpa;
|
||||
var choice_list: std.ArrayListUnmanaged(Choice) = .empty;
|
||||
defer choice_list.deinit(gpa);
|
||||
try choice_list.ensureUnusedCapacity(gpa, branch_list.len);
|
||||
|
||||
for (branch_list) |branch_stmt| {
|
||||
assert(branch_stmt.tag == .choice_star_stmt or branch_stmt.tag == .choice_plus_stmt);
|
||||
|
||||
const branch_data = branch_stmt.data.bin;
|
||||
const branch_expr = branch_data.lhs orelse unreachable;
|
||||
const branch_expr_data = branch_expr.data.choice_expr;
|
||||
const label_index = try scope.makeLabel();
|
||||
|
||||
if (branch_expr_data.start_expr) |node| {
|
||||
try checkStringLiteral(scope, node);
|
||||
try scope.emitSimpleInst(.stream_push);
|
||||
}
|
||||
if (branch_expr_data.option_expr) |node| {
|
||||
try checkStringLiteral(scope, node);
|
||||
try scope.emitSimpleInst(.stream_push);
|
||||
}
|
||||
|
||||
const jump_offset = try scope.emitJumpInst(.br_push);
|
||||
_ = try scope.makeJump(.{
|
||||
.mode = .absolute,
|
||||
.label_index = label_index,
|
||||
.code_offset = jump_offset,
|
||||
});
|
||||
|
||||
choice_list.appendAssumeCapacity(.{
|
||||
.label_index = label_index,
|
||||
.start_expression = branch_expr_data.start_expr,
|
||||
.inner_expression = branch_expr_data.inner_expr,
|
||||
.option_expression = branch_expr_data.option_expr,
|
||||
.block_stmt = branch_data.rhs,
|
||||
});
|
||||
}
|
||||
|
||||
try scope.emitSimpleInst(.br_table);
|
||||
try scope.emitSimpleInst(.br_select_index);
|
||||
try scope.emitSimpleInst(.br_dispatch);
|
||||
|
||||
for (choice_list.items) |choice| {
|
||||
scope.setLabel(choice.label_index);
|
||||
|
||||
if (choice.start_expression) |expr_node| {
|
||||
try checkStringLiteral(scope, expr_node);
|
||||
try scope.emitSimpleInst(.stream_push);
|
||||
}
|
||||
if (choice.inner_expression) |expr_node| {
|
||||
try checkStringLiteral(scope, expr_node);
|
||||
try scope.emitSimpleInst(.stream_push);
|
||||
}
|
||||
|
||||
try scope.emitSimpleInst(.stream_flush);
|
||||
if (choice.block_stmt) |block| {
|
||||
try checkBlockStmt(scope, block);
|
||||
} else {
|
||||
try scope.emitSimpleInst(.exit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn checkVarDecl(scope: *Scope, decl_node: *const Ast.Node) !void {
|
||||
const identifier_node = decl_node.data.bin.lhs orelse return error.Fucked;
|
||||
const expr_node = decl_node.data.bin.rhs orelse return error.Fucked;
|
||||
const identifier_node = decl_node.data.bin.lhs orelse return error.CompilerBug;
|
||||
const expr_node = decl_node.data.bin.rhs orelse return error.CompilerBug;
|
||||
|
||||
try checkExpr(scope, expr_node);
|
||||
switch (decl_node.tag) {
|
||||
|
|
@ -461,6 +590,7 @@ fn checkStmt(scope: *Scope, stmt: *const Ast.Node) CheckError!void {
|
|||
.temp_decl => try checkVarDecl(scope, stmt),
|
||||
.assign_stmt => try checkAssignStmt(scope, stmt),
|
||||
.content_stmt => try checkContentStmt(scope, stmt),
|
||||
.choice_stmt => try checkChoiceStmt(scope, stmt),
|
||||
.expr_stmt => try checkExprStmt(scope, stmt),
|
||||
else => return error.NotImplemented,
|
||||
}
|
||||
|
|
@ -522,23 +652,18 @@ fn dumpStringsWithHex(astgen: *const AstGen) void {
|
|||
|
||||
std.debug.print("[{d:04}] ", .{start});
|
||||
for (s) |b| std.debug.print("{x:02} ", .{b});
|
||||
std.debug.print("00 {s}\n", .{s});
|
||||
std.debug.print("00: {s}\n", .{s});
|
||||
start = end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform code generation via tree-walk.
|
||||
pub fn generate(gpa: std.mem.Allocator, tree: *const Ast) !Story {
|
||||
const root_node = tree.root orelse return error.Fucked;
|
||||
const root_node = tree.root orelse return error.CompilerBug;
|
||||
var story: Story = .{
|
||||
.allocator = gpa,
|
||||
.is_exited = false,
|
||||
.can_advance = false,
|
||||
.gc_objects = .{},
|
||||
.globals = .empty,
|
||||
.paths = .empty,
|
||||
.stack = .empty,
|
||||
.call_stack = .empty,
|
||||
};
|
||||
var astgen: AstGen = .{
|
||||
.gpa = gpa,
|
||||
|
|
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -38,21 +38,33 @@ fn dumpGlobalInst(
|
|||
return offset + 2;
|
||||
}
|
||||
|
||||
const Jump = enum {
|
||||
relative,
|
||||
absolute,
|
||||
};
|
||||
|
||||
fn dumpJumpInst(
|
||||
d: Dumper,
|
||||
context: *const Object.ContentPath,
|
||||
offset: usize,
|
||||
op: Opcode,
|
||||
mode: Jump,
|
||||
) !usize {
|
||||
var jump: u16 = @as(u16, context.bytes[offset + 1]) << 8;
|
||||
jump |= context.bytes[offset + 2];
|
||||
|
||||
try d.writer.print("{s} 0x{x:0>4} (0x{x:0>4} -> 0x{x:0>4})\n", .{
|
||||
@tagName(op),
|
||||
jump,
|
||||
offset,
|
||||
offset + 3 + jump,
|
||||
});
|
||||
switch (mode) {
|
||||
.relative => try d.writer.print("{s} 0x{x:0>4} (0x{x:0>4} -> 0x{x:0>4})\n", .{
|
||||
@tagName(op),
|
||||
jump,
|
||||
offset,
|
||||
offset + 3 + jump,
|
||||
}),
|
||||
.absolute => try d.writer.print("{s} 0x{x:0>4}\n", .{
|
||||
@tagName(op),
|
||||
jump,
|
||||
}),
|
||||
}
|
||||
return offset + 3;
|
||||
}
|
||||
|
||||
|
|
@ -72,43 +84,41 @@ pub fn dumpInst(
|
|||
try d.writer.print("0x{x:0>4} | ", .{offset});
|
||||
}
|
||||
switch (op) {
|
||||
.exit,
|
||||
.ret,
|
||||
.pop,
|
||||
.true,
|
||||
.false,
|
||||
.add,
|
||||
.sub,
|
||||
.mul,
|
||||
.div,
|
||||
.mod,
|
||||
.neg,
|
||||
.not,
|
||||
.cmp_eq,
|
||||
.cmp_lt,
|
||||
.cmp_lte,
|
||||
.cmp_gt,
|
||||
.cmp_gte,
|
||||
.flush,
|
||||
.load_choice_id,
|
||||
.content,
|
||||
.choice,
|
||||
.line,
|
||||
.glue,
|
||||
=> return d.dumpSimpleInst(offset, op),
|
||||
.load_const,
|
||||
.load,
|
||||
.store,
|
||||
=> return d.dumpByteInst(path, offset, op),
|
||||
.load_global,
|
||||
.store_global,
|
||||
.call,
|
||||
.divert,
|
||||
=> return d.dumpGlobalInst(path, offset, op),
|
||||
.jmp,
|
||||
.jmp_t,
|
||||
.jmp_f,
|
||||
=> return d.dumpJumpInst(path, offset, op),
|
||||
.exit => return d.dumpSimpleInst(offset, op),
|
||||
.ret => return d.dumpSimpleInst(offset, op),
|
||||
.pop => return d.dumpSimpleInst(offset, op),
|
||||
.true => return d.dumpSimpleInst(offset, op),
|
||||
.false => return d.dumpSimpleInst(offset, op),
|
||||
.add => return d.dumpSimpleInst(offset, op),
|
||||
.sub => return d.dumpSimpleInst(offset, op),
|
||||
.mul => return d.dumpSimpleInst(offset, op),
|
||||
.div => return d.dumpSimpleInst(offset, op),
|
||||
.mod => return d.dumpSimpleInst(offset, op),
|
||||
.neg => return d.dumpSimpleInst(offset, op),
|
||||
.not => return d.dumpSimpleInst(offset, op),
|
||||
.cmp_eq => return d.dumpSimpleInst(offset, op),
|
||||
.cmp_lt => return d.dumpSimpleInst(offset, op),
|
||||
.cmp_lte => return d.dumpSimpleInst(offset, op),
|
||||
.cmp_gt => return d.dumpSimpleInst(offset, op),
|
||||
.cmp_gte => return d.dumpSimpleInst(offset, op),
|
||||
.load_const => return d.dumpByteInst(path, offset, op),
|
||||
.load => return d.dumpByteInst(path, offset, op),
|
||||
.store => return d.dumpByteInst(path, offset, op),
|
||||
.load_global => return d.dumpGlobalInst(path, offset, op),
|
||||
.store_global => return d.dumpGlobalInst(path, offset, op),
|
||||
.call => return d.dumpGlobalInst(path, offset, op),
|
||||
.divert => return d.dumpGlobalInst(path, offset, op),
|
||||
.jmp => return d.dumpJumpInst(path, offset, op, .relative),
|
||||
.jmp_t => return d.dumpJumpInst(path, offset, op, .relative),
|
||||
.jmp_f => return d.dumpJumpInst(path, offset, op, .relative),
|
||||
.stream_push => return d.dumpSimpleInst(offset, op),
|
||||
.stream_flush => return d.dumpSimpleInst(offset, op),
|
||||
.stream_line => return d.dumpSimpleInst(offset, op),
|
||||
.stream_glue => return d.dumpSimpleInst(offset, op),
|
||||
.br_push => return d.dumpJumpInst(path, offset, op, .absolute),
|
||||
.br_table => return d.dumpSimpleInst(offset, op),
|
||||
.br_dispatch => return d.dumpSimpleInst(offset, op),
|
||||
.br_select_index => return d.dumpSimpleInst(offset, op),
|
||||
else => |code| {
|
||||
try d.writer.print("Unknown opcode 0x{x:0>4}\n", .{@intFromEnum(code)});
|
||||
return offset + 1;
|
||||
|
|
|
|||
26
src/main.zig
26
src/main.zig
|
|
@ -73,6 +73,9 @@ fn mainArgs(
|
|||
};
|
||||
};
|
||||
|
||||
const stdin = std.fs.File.stdin();
|
||||
var stdin_reader = stdin.reader(&stdin_buffer);
|
||||
|
||||
const stdout = std.fs.File.stdout();
|
||||
var stdout_writer = stdout.writer(&stdout_buffer);
|
||||
|
||||
|
|
@ -89,11 +92,26 @@ fn mainArgs(
|
|||
try story.dump(&stderr_writer.interface);
|
||||
if (compile_only) return;
|
||||
|
||||
while (story.can_advance) {
|
||||
const content = try story.advance();
|
||||
defer gpa.free(content);
|
||||
while (!story.is_exited and story.can_advance) {
|
||||
while (story.can_advance) {
|
||||
const content_text = try story.advance(gpa);
|
||||
defer gpa.free(content_text);
|
||||
std.debug.print("{s}\n", .{content_text});
|
||||
}
|
||||
if (story.current_choices.items.len > 0) {
|
||||
for (story.current_choices.items, 0..) |*choice, index| {
|
||||
const choice_text = try choice.text.toOwnedSlice(gpa);
|
||||
defer gpa.free(choice_text);
|
||||
std.debug.print("[{d}]: {s}\n", .{ index + 1, choice_text });
|
||||
}
|
||||
std.debug.print("> ", .{});
|
||||
|
||||
std.debug.print("{s}\n", .{content});
|
||||
const input_line = try stdin_reader.interface.takeDelimiter('\n');
|
||||
if (input_line) |bytes| {
|
||||
const choice_index = try std.fmt.parseUnsigned(usize, bytes, 10);
|
||||
try story.selectChoiceIndex(if (choice_index == 0) 0 else choice_index - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue