feat: runtime content processing, supporting glue
This commit is contained in:
parent
d325cdf965
commit
f5eac49729
12 changed files with 387 additions and 286 deletions
|
|
@ -285,6 +285,13 @@ const GenIr = struct {
|
||||||
return last_inst.isNoReturn();
|
return last_inst.isNoReturn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn endsWithContent(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.tag == .content_push;
|
||||||
|
}
|
||||||
|
|
||||||
fn makeSubBlock(self: *GenIr) GenIr {
|
fn makeSubBlock(self: *GenIr) GenIr {
|
||||||
return .{
|
return .{
|
||||||
.astgen = self.astgen,
|
.astgen = self.astgen,
|
||||||
|
|
@ -807,7 +814,7 @@ fn inlineIfStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.
|
||||||
if (data.rhs) |rhs| {
|
if (data.rhs) |rhs| {
|
||||||
// TODO: Revisit this. This isn't quite correct.
|
// TODO: Revisit this. This isn't quite correct.
|
||||||
switch (rhs.tag) {
|
switch (rhs.tag) {
|
||||||
.content => _ = try contentExpr(&then_block, scope, rhs),
|
.content => _ = try content(&then_block, scope, rhs),
|
||||||
inline else => |tag| @panic("Unexpected node type: " ++ @tagName(tag)),
|
inline else => |tag| @panic("Unexpected node type: " ++ @tagName(tag)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1051,8 +1058,11 @@ fn switchStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.In
|
||||||
return switch_br.toRef();
|
return switch_br.toRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contentExpr(block: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
fn content(block: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||||
const data = node.data.content;
|
const data = node.data.content;
|
||||||
|
if (data.leading_glue) {
|
||||||
|
_ = try block.addUnaryNode(.content_glue, .none);
|
||||||
|
}
|
||||||
for (data.items) |child_node| {
|
for (data.items) |child_node| {
|
||||||
switch (child_node.tag) {
|
switch (child_node.tag) {
|
||||||
.string_literal => {
|
.string_literal => {
|
||||||
|
|
@ -1070,13 +1080,17 @@ fn contentExpr(block: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!I
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (data.trailing_glue) {
|
||||||
|
_ = try block.addUnaryNode(.content_glue, .none);
|
||||||
|
} else if (block.endsWithContent()) {
|
||||||
|
_ = try block.addUnaryNode(.content_line, .none);
|
||||||
|
}
|
||||||
return .none;
|
return .none;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn contentStmt(gen: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
fn contentStmt(block: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void {
|
||||||
const expr_node = node.data.bin.lhs.?;
|
const data = node.data.bin;
|
||||||
const expr_ref = try contentExpr(gen, scope, expr_node);
|
_ = try content(block, scope, data.lhs.?);
|
||||||
return gen.addUnaryNode(.content_flush, expr_ref);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assignStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void {
|
fn assignStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void {
|
||||||
|
|
@ -1133,6 +1147,9 @@ fn choiceStmt(
|
||||||
if (branch_data.rhs) |branch_body| {
|
if (branch_data.rhs) |branch_body| {
|
||||||
_ = try blockStmt(&sub_block, scope, branch_body);
|
_ = try blockStmt(&sub_block, scope, branch_body);
|
||||||
}
|
}
|
||||||
|
if (!sub_block.endsWithNoReturn()) {
|
||||||
|
_ = try sub_block.addUnaryNode(.implicit_ret, .none);
|
||||||
|
}
|
||||||
|
|
||||||
const body = sub_block.instructionsSlice();
|
const body = sub_block.instructionsSlice();
|
||||||
const case_extra_len = @typeInfo(Ir.Inst.SwitchBr.Case).@"struct".fields.len + body.len;
|
const case_extra_len = @typeInfo(Ir.Inst.SwitchBr.Case).@"struct".fields.len + body.len;
|
||||||
|
|
|
||||||
|
|
@ -202,8 +202,10 @@ pub const Inst = struct {
|
||||||
condbr,
|
condbr,
|
||||||
@"break",
|
@"break",
|
||||||
switch_br,
|
switch_br,
|
||||||
|
/// Uses the `un` union field.
|
||||||
content_push,
|
content_push,
|
||||||
content_flush,
|
content_line,
|
||||||
|
content_glue,
|
||||||
choice_br,
|
choice_br,
|
||||||
// Uses the `un` union field.
|
// Uses the `un` union field.
|
||||||
ret,
|
ret,
|
||||||
|
|
@ -373,7 +375,8 @@ pub const Inst = struct {
|
||||||
.condbr,
|
.condbr,
|
||||||
.switch_br,
|
.switch_br,
|
||||||
.content_push,
|
.content_push,
|
||||||
.content_flush,
|
.content_line,
|
||||||
|
.content_glue,
|
||||||
.choice_br,
|
.choice_br,
|
||||||
.call,
|
.call,
|
||||||
.divert,
|
.divert,
|
||||||
|
|
|
||||||
|
|
@ -923,13 +923,12 @@ fn parseLbraceExpr(p: *Parse) Error!?*Ast.Node {
|
||||||
.end = rbrace_token.loc.end,
|
.end = rbrace_token.loc.end,
|
||||||
}, lhs, null);
|
}, lhs, null);
|
||||||
}
|
}
|
||||||
_ = try p.expectToken(.colon, true);
|
|
||||||
|
|
||||||
|
_ = try p.expectToken(.colon, false);
|
||||||
if (p.checkToken(.newline)) {
|
if (p.checkToken(.newline)) {
|
||||||
_ = p.nextToken();
|
_ = p.nextToken();
|
||||||
return parseConditional(p, lbrace_token, lhs);
|
return parseConditional(p, lbrace_token, lhs);
|
||||||
} else {
|
} else {
|
||||||
_ = p.nextToken();
|
|
||||||
return parseInlineIf(p, lbrace_token, lhs);
|
return parseInlineIf(p, lbrace_token, lhs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -977,19 +976,22 @@ fn parseContent(p: *Parse, options: ContentOptions) Error!?*Ast.Node {
|
||||||
leading_glue = true;
|
leading_glue = true;
|
||||||
_ = p.nextToken();
|
_ = p.nextToken();
|
||||||
}
|
}
|
||||||
while (true) {
|
loop: while (true) {
|
||||||
const node: ?*Ast.Node = switch (p.token.tag) {
|
const node: ?*Ast.Node = switch (p.token.tag) {
|
||||||
.eof, .newline, .left_arrow, .right_brace => break,
|
.eof, .newline, .left_arrow, .right_brace => break,
|
||||||
.left_brace => try parseLbraceExpr(p),
|
.left_brace => try parseLbraceExpr(p),
|
||||||
.right_arrow => try parseDivertStmt(p),
|
.right_arrow => try parseDivertStmt(p),
|
||||||
.glue => blk: {
|
.glue => blk: {
|
||||||
const next_token = p.nextToken();
|
while (true) {
|
||||||
switch (next_token.tag) {
|
_ = p.nextToken();
|
||||||
.eof, .newline, .right_brace => {
|
switch (p.token.tag) {
|
||||||
trailing_glue = true;
|
.whitespace => continue,
|
||||||
break;
|
.eof, .newline, .right_brace => {
|
||||||
},
|
trailing_glue = true;
|
||||||
else => break :blk null,
|
break :loop;
|
||||||
|
},
|
||||||
|
else => break :blk null,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
else => |tag| blk: {
|
else => |tag| blk: {
|
||||||
|
|
|
||||||
17
src/Sema.zig
17
src/Sema.zig
|
|
@ -697,10 +697,6 @@ fn irContentPush(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError
|
||||||
try builder.addByteOp(.stream_push);
|
try builder.addByteOp(.stream_push);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn irContentFlush(_: *Sema, builder: *Builder, _: Ir.Inst.Index) InnerError!void {
|
|
||||||
try builder.addByteOp(.stream_flush);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn irChoiceBr(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void {
|
fn irChoiceBr(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!void {
|
||||||
const gpa = sema.gpa;
|
const gpa = sema.gpa;
|
||||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||||
|
|
@ -716,6 +712,8 @@ fn irChoiceBr(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!vo
|
||||||
const case_label = try builder.addLabel();
|
const case_label = try builder.addLabel();
|
||||||
branch_labels.appendAssumeCapacity(case_label);
|
branch_labels.appendAssumeCapacity(case_label);
|
||||||
|
|
||||||
|
try builder.addByteOp(.stream_mark);
|
||||||
|
|
||||||
switch (case_extra.data.operand_1) {
|
switch (case_extra.data.operand_1) {
|
||||||
.none => {},
|
.none => {},
|
||||||
else => |content| {
|
else => |content| {
|
||||||
|
|
@ -762,8 +760,7 @@ fn irChoiceBr(sema: *Sema, builder: *Builder, inst: Ir.Inst.Index) InnerError!vo
|
||||||
try builder.addByteOp(.stream_push);
|
try builder.addByteOp(.stream_push);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
try builder.addByteOp(.stream_line);
|
||||||
try builder.addByteOp(.stream_flush);
|
|
||||||
_ = try analyzeBodyInner(sema, builder, body_slice, false);
|
_ = try analyzeBodyInner(sema, builder, body_slice, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1018,8 +1015,12 @@ fn analyzeBodyInner(
|
||||||
try irContentPush(sema, builder, inst);
|
try irContentPush(sema, builder, inst);
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
.content_flush => {
|
.content_line => {
|
||||||
try irContentFlush(sema, builder, inst);
|
try builder.addByteOp(.stream_line);
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
.content_glue => {
|
||||||
|
try builder.addByteOp(.stream_glue);
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
.choice_br => {
|
.choice_br => {
|
||||||
|
|
|
||||||
521
src/Story.zig
521
src/Story.zig
|
|
@ -9,27 +9,102 @@ const Dumper = @import("Story/Dumper.zig");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Story = @This();
|
const Story = @This();
|
||||||
|
|
||||||
allocator: std.mem.Allocator,
|
gpa: std.mem.Allocator,
|
||||||
dump_writer: ?*std.Io.Writer = null,
|
arena: std.heap.ArenaAllocator,
|
||||||
|
|
||||||
|
// Flags
|
||||||
is_exited: bool = false,
|
is_exited: bool = false,
|
||||||
can_advance: bool = false,
|
can_advance: bool = false,
|
||||||
choice_index: usize = 0,
|
|
||||||
stack_top: usize = 0,
|
stack_top: usize = 0,
|
||||||
call_stack_top: usize = 0,
|
call_stack_top: usize = 0,
|
||||||
|
output_marker: usize = 0,
|
||||||
|
choice_selected: ?Choice = null,
|
||||||
|
output_buffer: std.ArrayListUnmanaged(OutputCommand) = .empty,
|
||||||
|
output_scratch: std.ArrayListUnmanaged(u8) = .empty,
|
||||||
current_choices: std.ArrayListUnmanaged(Choice) = .empty,
|
current_choices: std.ArrayListUnmanaged(Choice) = .empty,
|
||||||
code_chunks: std.ArrayListUnmanaged(*Object.Code) = .empty,
|
|
||||||
globals: std.StringHashMapUnmanaged(Value) = .empty,
|
globals: std.StringHashMapUnmanaged(Value) = .empty,
|
||||||
stack: []Value = &.{},
|
stack: []Value = &.{},
|
||||||
call_stack: []CallFrame = &.{},
|
call_stack: []CallFrame = &.{},
|
||||||
/// Global constants pool.
|
code_chunks: std.ArrayListUnmanaged(*Object.Code) = .empty,
|
||||||
constants_pool: []const Value = &.{},
|
|
||||||
/// Linked list of all tracked runtime objects.
|
/// Linked list of all tracked runtime objects.
|
||||||
gc_objects: std.SinglyLinkedList = .{},
|
gc_objects: std.SinglyLinkedList = .{},
|
||||||
|
/// Global constants pool.
|
||||||
|
constants_pool: []const Value = &.{},
|
||||||
// FIXME: This was a hack to keep string bytes alive.
|
// FIXME: This was a hack to keep string bytes alive.
|
||||||
string_bytes: []const u8 = &.{},
|
string_bytes: []const u8 = &.{},
|
||||||
|
dump_writer: ?*std.Io.Writer = null,
|
||||||
|
|
||||||
pub const default_knot_name: [:0]const u8 = "$__main__$";
|
pub const default_knot_name: [:0]const u8 = "$__main__$";
|
||||||
|
|
||||||
|
pub const Opcode = enum(u8) {
|
||||||
|
/// Exit the VM normally.
|
||||||
|
exit,
|
||||||
|
done,
|
||||||
|
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.
|
||||||
|
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,
|
||||||
|
not,
|
||||||
|
cmp_eq,
|
||||||
|
cmp_neq,
|
||||||
|
cmp_lt,
|
||||||
|
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,
|
||||||
|
load_const,
|
||||||
|
load,
|
||||||
|
store,
|
||||||
|
load_global,
|
||||||
|
store_global,
|
||||||
|
load_attr,
|
||||||
|
store_attr,
|
||||||
|
/// Pop a value off the stack and write it to the content stream.
|
||||||
|
stream_push,
|
||||||
|
stream_line,
|
||||||
|
stream_glue,
|
||||||
|
stream_mark,
|
||||||
|
br_push,
|
||||||
|
br_table,
|
||||||
|
br_dispatch,
|
||||||
|
br_select_index,
|
||||||
|
_,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const CallFrame = struct {
|
||||||
|
callee: *Object.Knot,
|
||||||
|
caller_top: usize,
|
||||||
|
ip: usize,
|
||||||
|
sp: usize,
|
||||||
|
};
|
||||||
|
|
||||||
pub const Value = union(enum) {
|
pub const Value = union(enum) {
|
||||||
nil,
|
nil,
|
||||||
bool: bool,
|
bool: bool,
|
||||||
|
|
@ -237,82 +312,19 @@ pub const Value = union(enum) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const CallFrame = struct {
|
pub const OutputCommand = union(enum) {
|
||||||
callee: *Object.Knot,
|
line,
|
||||||
caller_top: usize,
|
glue,
|
||||||
ip: usize,
|
value: Value,
|
||||||
sp: usize,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Choice = struct {
|
pub const Choice = struct {
|
||||||
text: std.ArrayListUnmanaged(u8),
|
content: []const u8,
|
||||||
dest_offset: u16,
|
dest_offset: u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Opcode = enum(u8) {
|
|
||||||
/// Exit the VM normally.
|
|
||||||
exit,
|
|
||||||
done,
|
|
||||||
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.
|
|
||||||
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,
|
|
||||||
not,
|
|
||||||
cmp_eq,
|
|
||||||
cmp_neq,
|
|
||||||
cmp_lt,
|
|
||||||
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,
|
|
||||||
load_const,
|
|
||||||
load,
|
|
||||||
store,
|
|
||||||
load_global,
|
|
||||||
store_global,
|
|
||||||
load_attr,
|
|
||||||
store_attr,
|
|
||||||
/// 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,
|
|
||||||
_,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn deinit(story: *Story) void {
|
pub fn deinit(story: *Story) void {
|
||||||
const gpa = story.allocator;
|
const gpa = story.gpa;
|
||||||
var next = story.gc_objects.first;
|
var next = story.gc_objects.first;
|
||||||
while (next) |node| {
|
while (next) |node| {
|
||||||
const object: *Object = @alignCast(@fieldParentPtr("node", node));
|
const object: *Object = @alignCast(@fieldParentPtr("node", node));
|
||||||
|
|
@ -320,13 +332,17 @@ pub fn deinit(story: *Story) void {
|
||||||
object.destroy(story);
|
object.destroy(story);
|
||||||
}
|
}
|
||||||
|
|
||||||
story.current_choices.deinit(gpa);
|
story.arena.deinit();
|
||||||
story.globals.deinit(gpa);
|
story.globals.deinit(gpa);
|
||||||
|
story.current_choices.deinit(gpa);
|
||||||
|
story.output_scratch.deinit(gpa);
|
||||||
|
story.output_buffer.deinit(gpa);
|
||||||
|
|
||||||
gpa.free(story.string_bytes);
|
gpa.free(story.string_bytes);
|
||||||
gpa.free(story.constants_pool);
|
gpa.free(story.constants_pool);
|
||||||
gpa.free(story.stack);
|
gpa.free(story.stack);
|
||||||
gpa.free(story.call_stack);
|
gpa.free(story.call_stack);
|
||||||
|
story.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn currentFrame(vm: *Story) *CallFrame {
|
fn currentFrame(vm: *Story) *CallFrame {
|
||||||
|
|
@ -350,7 +366,7 @@ fn popStack(vm: *Story) ?Value {
|
||||||
|
|
||||||
const stack_top = vm.stack_top;
|
const stack_top = vm.stack_top;
|
||||||
vm.stack_top -= 1;
|
vm.stack_top -= 1;
|
||||||
return vm.stack[stack_top];
|
return vm.stack[stack_top - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getConstant(story: *Story, frame: *CallFrame, offset: u8) !Value {
|
fn getConstant(story: *Story, frame: *CallFrame, offset: u8) !Value {
|
||||||
|
|
@ -398,13 +414,98 @@ fn setGlobal(vm: *Story, key: Value, value: Value) !void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
pub fn getKnot(vm: *Story, name: []const u8) ?*Object.Knot {
|
||||||
const gpa = vm.allocator;
|
const knot: ?*Object.Knot = blk: {
|
||||||
if (vm.call_stack_top == 0) return .empty;
|
if (vm.globals.get(name)) |value| {
|
||||||
errdefer vm.can_advance = false;
|
// TODO: Do a check here.
|
||||||
|
break :blk @ptrCast(value.object);
|
||||||
|
}
|
||||||
|
break :blk null;
|
||||||
|
};
|
||||||
|
return knot;
|
||||||
|
}
|
||||||
|
|
||||||
var stream_writer = std.Io.Writer.Allocating.init(gpa);
|
fn call(vm: *Story, knot: *Object.Knot) !void {
|
||||||
defer stream_writer.deinit();
|
if (vm.call_stack_top >= vm.call_stack.len)
|
||||||
|
return error.CallStackOverflow;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 = sp,
|
||||||
|
.caller_top = caller_top,
|
||||||
|
};
|
||||||
|
vm.call_stack_top += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.can_advance)
|
||||||
|
vm.can_advance = true;
|
||||||
|
|
||||||
|
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],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
const StepSignal = union(enum) {
|
||||||
|
exit,
|
||||||
|
done,
|
||||||
|
choices_ready,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn step(vm: *Story) !StepSignal {
|
||||||
|
assert(vm.call_stack_top > 0);
|
||||||
|
assert(vm.can_advance == true);
|
||||||
|
|
||||||
|
const gpa = vm.gpa;
|
||||||
|
const arena = vm.arena.allocator();
|
||||||
|
|
||||||
var frame = vm.currentFrame();
|
var frame = vm.currentFrame();
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
@ -413,15 +514,8 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
||||||
Dumper.trace(vm, w, frame) catch {};
|
Dumper.trace(vm, w, frame) catch {};
|
||||||
}
|
}
|
||||||
switch (code[frame.ip]) {
|
switch (code[frame.ip]) {
|
||||||
.exit => {
|
.exit => return .exit,
|
||||||
vm.is_exited = true;
|
.done => return .done,
|
||||||
vm.can_advance = false;
|
|
||||||
return .empty;
|
|
||||||
},
|
|
||||||
.done => {
|
|
||||||
vm.can_advance = false;
|
|
||||||
return .empty;
|
|
||||||
},
|
|
||||||
.ret => {
|
.ret => {
|
||||||
const return_value = vm.stack[vm.stack_top - 1];
|
const return_value = vm.stack[vm.stack_top - 1];
|
||||||
|
|
||||||
|
|
@ -429,14 +523,16 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
||||||
const completed_frame = vm.call_stack[vm.call_stack_top];
|
const completed_frame = vm.call_stack[vm.call_stack_top];
|
||||||
|
|
||||||
vm.stack_top = completed_frame.caller_top;
|
vm.stack_top = completed_frame.caller_top;
|
||||||
|
|
||||||
vm.stack[vm.stack_top] = return_value;
|
vm.stack[vm.stack_top] = return_value;
|
||||||
vm.stack_top += 1;
|
vm.stack_top += 1;
|
||||||
|
|
||||||
if (vm.call_stack_top == 0) return error.UnexpectedReturn;
|
if (vm.call_stack_top == 0) return error.UnexpectedReturn;
|
||||||
|
|
||||||
frame = &vm.call_stack[vm.call_stack_top - 1];
|
frame = &vm.call_stack[vm.call_stack_top - 1];
|
||||||
},
|
},
|
||||||
|
.pop => {
|
||||||
|
if (vm.popStack()) |_| {} else return error.InvalidArgument;
|
||||||
|
frame.ip += 1;
|
||||||
|
},
|
||||||
.true => {
|
.true => {
|
||||||
try vm.pushStack(.{ .bool = true });
|
try vm.pushStack(.{ .bool = true });
|
||||||
frame.ip += 1;
|
frame.ip += 1;
|
||||||
|
|
@ -445,10 +541,6 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
||||||
try vm.pushStack(.{ .bool = false });
|
try vm.pushStack(.{ .bool = false });
|
||||||
frame.ip += 1;
|
frame.ip += 1;
|
||||||
},
|
},
|
||||||
.pop => {
|
|
||||||
if (vm.popStack()) |_| {} else return error.InvalidArgument;
|
|
||||||
frame.ip += 1;
|
|
||||||
},
|
|
||||||
.add => {
|
.add => {
|
||||||
const lhs = vm.peekStack(1) orelse return error.Bugged;
|
const lhs = vm.peekStack(1) orelse return error.Bugged;
|
||||||
const rhs = vm.peekStack(0) orelse return error.Bugged;
|
const rhs = vm.peekStack(0) orelse return error.Bugged;
|
||||||
|
|
@ -551,7 +643,6 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
||||||
return error.InvalidArgument;
|
return error.InvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-fetch — we're now in the callee's frame
|
|
||||||
frame = &vm.call_stack[vm.call_stack_top - 1];
|
frame = &vm.call_stack[vm.call_stack_top - 1];
|
||||||
},
|
},
|
||||||
.divert => {
|
.divert => {
|
||||||
|
|
@ -593,6 +684,27 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
||||||
}
|
}
|
||||||
frame.ip += 2;
|
frame.ip += 2;
|
||||||
},
|
},
|
||||||
|
.load_attr => {
|
||||||
|
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||||
|
|
||||||
|
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_object.members.get(knot_attr.toSlice())) |attr_object| {
|
||||||
|
try vm.pushStack(.{ .object = attr_object });
|
||||||
|
} else {
|
||||||
|
return error.InvalidArgument;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return error.InvalidArgument;
|
||||||
|
}
|
||||||
|
frame.ip += 2;
|
||||||
|
},
|
||||||
|
// TODO: store_attr
|
||||||
.load_global => {
|
.load_global => {
|
||||||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
||||||
const global_name = try vm.getConstant(frame, arg_offset);
|
const global_name = try vm.getConstant(frame, arg_offset);
|
||||||
|
|
@ -614,72 +726,48 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
||||||
frame.ip += 2;
|
frame.ip += 2;
|
||||||
},
|
},
|
||||||
.stream_push => {
|
.stream_push => {
|
||||||
// FIXME: This should be more strict.
|
// TODO: Make this more strict.
|
||||||
// Its not because theres a bug in when these instructions are
|
if (popStack(vm)) |value| {
|
||||||
// emitted.
|
try vm.output_buffer.append(gpa, .{ .value = value });
|
||||||
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;
|
|
||||||
//}
|
|
||||||
|
|
||||||
frame.ip += 1;
|
frame.ip += 1;
|
||||||
},
|
},
|
||||||
.stream_flush => {
|
.stream_line => {
|
||||||
|
try vm.output_buffer.append(gpa, .line);
|
||||||
|
frame.ip += 1;
|
||||||
|
},
|
||||||
|
.stream_glue => {
|
||||||
|
try vm.output_buffer.append(gpa, .glue);
|
||||||
|
frame.ip += 1;
|
||||||
|
},
|
||||||
|
.stream_mark => {
|
||||||
|
vm.output_marker = vm.output_buffer.items.len;
|
||||||
frame.ip += 1;
|
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 => {
|
.br_push => {
|
||||||
|
const marker = vm.output_marker;
|
||||||
const arg_offset = readAddress(code, frame.ip);
|
const arg_offset = readAddress(code, frame.ip);
|
||||||
|
const output_stream = vm.output_buffer.items[marker..];
|
||||||
|
const choice_display = try resolveOutputStream(vm, arena, output_stream);
|
||||||
|
defer vm.output_buffer.shrinkRetainingCapacity(marker);
|
||||||
|
|
||||||
try vm.current_choices.append(gpa, .{
|
try vm.current_choices.append(gpa, .{
|
||||||
.text = stream_writer.toArrayList(),
|
.content = choice_display,
|
||||||
.dest_offset = arg_offset,
|
.dest_offset = arg_offset,
|
||||||
});
|
});
|
||||||
|
|
||||||
frame.ip += 3;
|
frame.ip += 3;
|
||||||
},
|
},
|
||||||
.br_table => {
|
.br_table => {
|
||||||
frame.ip += 1;
|
frame.ip += 1;
|
||||||
},
|
},
|
||||||
.br_select_index => {
|
.br_select_index => {
|
||||||
vm.can_advance = false;
|
|
||||||
frame.ip += 1;
|
frame.ip += 1;
|
||||||
return .empty;
|
return .choices_ready;
|
||||||
},
|
},
|
||||||
.br_dispatch => {
|
.br_dispatch => {
|
||||||
const index = vm.choice_index;
|
if (vm.choice_selected) |choice| {
|
||||||
const branch_dispatch = vm.current_choices.items[index];
|
frame.ip = choice.dest_offset;
|
||||||
defer {
|
|
||||||
for (vm.current_choices.items) |*choice| {
|
|
||||||
choice.text.deinit(gpa);
|
|
||||||
}
|
|
||||||
vm.current_choices.clearRetainingCapacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.ip = branch_dispatch.dest_offset;
|
|
||||||
},
|
|
||||||
.load_attr => {
|
|
||||||
const arg_offset: u8 = @intFromEnum(code[frame.ip + 1]);
|
|
||||||
frame.ip += 2;
|
|
||||||
|
|
||||||
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_object.members.get(knot_attr.toSlice())) |attr_object| {
|
|
||||||
try vm.pushStack(.{ .object = attr_object });
|
|
||||||
} else {
|
|
||||||
return error.InvalidArgument;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return error.InvalidArgument;
|
return error.InvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
@ -689,83 +777,68 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn readAddress(code: []const Story.Opcode, offset: usize) u16 {
|
fn resolveOutputStream(
|
||||||
const arg_offset = std.mem.bytesToValue(u16, code[offset + 1 ..][0..2]);
|
story: *Story,
|
||||||
return std.mem.bigToNative(u16, arg_offset);
|
gpa: std.mem.Allocator,
|
||||||
}
|
stream: []const OutputCommand,
|
||||||
|
) ![]const u8 {
|
||||||
|
var pending_newline = false;
|
||||||
|
var result: std.ArrayListUnmanaged(u8) = .empty;
|
||||||
|
defer result.deinit(gpa);
|
||||||
|
|
||||||
pub fn advance(story: *Story, gpa: std.mem.Allocator) ![]const u8 {
|
for (stream) |cmd| {
|
||||||
var content = try story.execute();
|
switch (cmd) {
|
||||||
return content.toOwnedSlice(gpa);
|
.value => |value| {
|
||||||
}
|
if (pending_newline) {
|
||||||
|
pending_newline = false;
|
||||||
pub fn getKnot(vm: *Story, name: []const u8) ?*Object.Knot {
|
try result.append(gpa, '\n');
|
||||||
const knot: ?*Object.Knot = blk: {
|
}
|
||||||
if (vm.globals.get(name)) |value| {
|
const str = try Object.String.fromValue(story, value);
|
||||||
// TODO: Do a check here.
|
try result.appendSlice(gpa, str.toSlice());
|
||||||
break :blk @ptrCast(value.object);
|
},
|
||||||
|
.line => {
|
||||||
|
if (result.items.len > 0)
|
||||||
|
pending_newline = true;
|
||||||
|
},
|
||||||
|
.glue => pending_newline = false,
|
||||||
}
|
}
|
||||||
break :blk null;
|
|
||||||
};
|
|
||||||
return knot;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(vm: *Story, knot: *Object.Knot) !void {
|
|
||||||
if (vm.call_stack_top >= vm.call_stack.len)
|
|
||||||
return error.CallStackOverflow;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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 = sp,
|
|
||||||
.caller_top = caller_top,
|
|
||||||
};
|
|
||||||
vm.call_stack_top += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return result.toOwnedSlice(gpa);
|
||||||
|
}
|
||||||
|
|
||||||
const frame_top = sp + args_count + locals_count;
|
pub fn advance(story: *Story) !?[]const u8 {
|
||||||
if (frame_top > vm.stack.len) return error.StackOverflow;
|
const arena = story.arena.allocator();
|
||||||
for (vm.stack[sp + args_count .. frame_top]) |*slot| slot.* = .nil;
|
const output_buffer = &story.output_buffer;
|
||||||
vm.stack_top = frame_top;
|
const output_scratch = &story.output_scratch;
|
||||||
|
|
||||||
current_frame.* = .{
|
output_buffer.clearRetainingCapacity();
|
||||||
.callee = knot,
|
output_scratch.clearRetainingCapacity();
|
||||||
.ip = 0,
|
|
||||||
.sp = sp,
|
if (!story.can_advance) return null;
|
||||||
.caller_top = caller_top,
|
while (story.can_advance) {
|
||||||
};
|
const signal = try story.step();
|
||||||
|
switch (signal) {
|
||||||
|
.exit => {
|
||||||
|
story.is_exited = true;
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
.done, .choices_ready => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
story.can_advance = false;
|
||||||
|
return try resolveOutputStream(story, arena, output_buffer.items[0..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selectChoiceIndex(story: *Story, index: usize) !void {
|
||||||
|
const choices = &story.current_choices;
|
||||||
|
if (index >= choices.items.len)
|
||||||
|
return error.InvalidChoice;
|
||||||
|
|
||||||
|
story.choice_selected = choices.items[index];
|
||||||
|
story.can_advance = true;
|
||||||
|
story.output_marker = 0;
|
||||||
|
story.current_choices.clearRetainingCapacity();
|
||||||
|
_ = story.arena.reset(.retain_capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const LoadOptions = struct {
|
pub const LoadOptions = struct {
|
||||||
|
|
@ -777,12 +850,6 @@ pub const LoadOptions = struct {
|
||||||
dump_trace: bool = false,
|
dump_trace: bool = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
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 dump(story: *Story, writer: *std.Io.Writer) !void {
|
pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
||||||
return Dumper.dump(story, writer);
|
return Dumper.dump(story, writer);
|
||||||
}
|
}
|
||||||
|
|
@ -796,6 +863,7 @@ pub fn loadFromString(
|
||||||
defer arena_allocator.deinit();
|
defer arena_allocator.deinit();
|
||||||
|
|
||||||
const arena = arena_allocator.allocator();
|
const arena = arena_allocator.allocator();
|
||||||
|
|
||||||
var comp = try Module.compile(gpa, arena, .{
|
var comp = try Module.compile(gpa, arena, .{
|
||||||
.source_bytes = source_bytes,
|
.source_bytes = source_bytes,
|
||||||
.filename = "<STDIN>",
|
.filename = "<STDIN>",
|
||||||
|
|
@ -813,6 +881,7 @@ pub fn loadFromString(
|
||||||
return error.LoadFailed;
|
return error.LoadFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Make this configureable.
|
||||||
const stack_size = 128;
|
const stack_size = 128;
|
||||||
const eval_stack_ptr = try gpa.alloc(Value, stack_size);
|
const eval_stack_ptr = try gpa.alloc(Value, stack_size);
|
||||||
errdefer gpa.free(eval_stack_ptr);
|
errdefer gpa.free(eval_stack_ptr);
|
||||||
|
|
@ -821,7 +890,8 @@ pub fn loadFromString(
|
||||||
errdefer gpa.free(call_stack_ptr);
|
errdefer gpa.free(call_stack_ptr);
|
||||||
|
|
||||||
var story: Story = .{
|
var story: Story = .{
|
||||||
.allocator = gpa,
|
.gpa = gpa,
|
||||||
|
.arena = .init(gpa),
|
||||||
.can_advance = false,
|
.can_advance = false,
|
||||||
.dump_writer = if (options.dump_trace) options.dump_writer else null,
|
.dump_writer = if (options.dump_trace) options.dump_writer else null,
|
||||||
.stack = eval_stack_ptr,
|
.stack = eval_stack_ptr,
|
||||||
|
|
@ -832,7 +902,6 @@ pub fn loadFromString(
|
||||||
try comp.setupStoryRuntime(gpa, &story);
|
try comp.setupStoryRuntime(gpa, &story);
|
||||||
if (story.getKnot(Story.default_knot_name)) |knot| {
|
if (story.getKnot(Story.default_knot_name)) |knot| {
|
||||||
try story.divert(knot);
|
try story.divert(knot);
|
||||||
story.can_advance = true;
|
|
||||||
}
|
}
|
||||||
return story;
|
return story;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,9 +154,9 @@ pub fn dumpInst(
|
||||||
.load_attr => return self.dumpGlobalInst(w, knot, offset, op),
|
.load_attr => return self.dumpGlobalInst(w, knot, offset, op),
|
||||||
.store_attr => return self.dumpGlobalInst(w, knot, offset, op),
|
.store_attr => return self.dumpGlobalInst(w, knot, offset, op),
|
||||||
.stream_push => return self.dumpSimpleInst(w, offset, op),
|
.stream_push => return self.dumpSimpleInst(w, offset, op),
|
||||||
.stream_flush => return self.dumpSimpleInst(w, offset, op),
|
|
||||||
.stream_line => return self.dumpSimpleInst(w, offset, op),
|
.stream_line => return self.dumpSimpleInst(w, offset, op),
|
||||||
.stream_glue => return self.dumpSimpleInst(w, offset, op),
|
.stream_glue => return self.dumpSimpleInst(w, offset, op),
|
||||||
|
.stream_mark => return self.dumpSimpleInst(w, offset, op),
|
||||||
.br_push => return self.dumpJumpInst(w, knot, offset, op, .absolute),
|
.br_push => return self.dumpJumpInst(w, knot, offset, op, .absolute),
|
||||||
.br_table => return self.dumpSimpleInst(w, offset, op),
|
.br_table => return self.dumpSimpleInst(w, offset, op),
|
||||||
.br_dispatch => return self.dumpSimpleInst(w, offset, op),
|
.br_dispatch => return self.dumpSimpleInst(w, offset, op),
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ pub const String = struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn create(story: *Story, options: Options) error{OutOfMemory}!*Object.String {
|
pub fn create(story: *Story, options: Options) error{OutOfMemory}!*Object.String {
|
||||||
const gpa = story.allocator;
|
const gpa = story.gpa;
|
||||||
const alloc_len = @sizeOf(Type) + options.bytes.len + 1;
|
const alloc_len = @sizeOf(Type) + options.bytes.len + 1;
|
||||||
const raw = try gpa.alignedAlloc(u8, .of(Type), alloc_len);
|
const raw = try gpa.alignedAlloc(u8, .of(Type), alloc_len);
|
||||||
const object: *Type = @ptrCast(raw);
|
const object: *Type = @ptrCast(raw);
|
||||||
|
|
@ -94,7 +94,7 @@ pub const String = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(obj: *String, story: *Story) void {
|
pub fn destroy(obj: *String, story: *Story) void {
|
||||||
const gpa = story.allocator;
|
const gpa = story.gpa;
|
||||||
const alloc_len = @sizeOf(Type) + obj.length + 1;
|
const alloc_len = @sizeOf(Type) + obj.length + 1;
|
||||||
const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj);
|
const base: [*]align(@alignOf(Type)) u8 = @ptrCast(obj);
|
||||||
gpa.free(base[0..alloc_len]);
|
gpa.free(base[0..alloc_len]);
|
||||||
|
|
@ -120,7 +120,7 @@ pub const String = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn concat(story: *Story, lhs: *String, rhs: *String) !*Object.String {
|
pub fn concat(story: *Story, lhs: *String, rhs: *String) !*Object.String {
|
||||||
const gpa = story.allocator;
|
const gpa = story.gpa;
|
||||||
const length = lhs.length + rhs.length;
|
const length = lhs.length + rhs.length;
|
||||||
const bytes = try gpa.alloc(u8, length + 1);
|
const bytes = try gpa.alloc(u8, length + 1);
|
||||||
defer gpa.free(bytes);
|
defer gpa.free(bytes);
|
||||||
|
|
@ -158,7 +158,7 @@ pub const Code = struct {
|
||||||
const Type = Code;
|
const Type = Code;
|
||||||
|
|
||||||
pub fn create(story: *Story, options: Options) error{OutOfMemory}!*Object.Code {
|
pub fn create(story: *Story, options: Options) error{OutOfMemory}!*Object.Code {
|
||||||
const gpa = story.allocator;
|
const gpa = story.gpa;
|
||||||
const raw = try gpa.alignedAlloc(u8, .of(Type), @sizeOf(Type));
|
const raw = try gpa.alignedAlloc(u8, .of(Type), @sizeOf(Type));
|
||||||
const obj: *Type = @ptrCast(raw);
|
const obj: *Type = @ptrCast(raw);
|
||||||
|
|
||||||
|
|
@ -176,7 +176,7 @@ pub const Code = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(obj: *Code, story: *Story) void {
|
pub fn destroy(obj: *Code, story: *Story) void {
|
||||||
const gpa = story.allocator;
|
const gpa = story.gpa;
|
||||||
gpa.free(obj.constants);
|
gpa.free(obj.constants);
|
||||||
gpa.free(obj.bytecode);
|
gpa.free(obj.bytecode);
|
||||||
|
|
||||||
|
|
@ -201,7 +201,7 @@ pub const Knot = struct {
|
||||||
const Type = Knot;
|
const Type = Knot;
|
||||||
|
|
||||||
pub fn create(story: *Story, options: Options) error{OutOfMemory}!*Object.Knot {
|
pub fn create(story: *Story, options: Options) error{OutOfMemory}!*Object.Knot {
|
||||||
const gpa = story.allocator;
|
const gpa = story.gpa;
|
||||||
const raw = try gpa.alignedAlloc(u8, .of(Type), @sizeOf(Type));
|
const raw = try gpa.alignedAlloc(u8, .of(Type), @sizeOf(Type));
|
||||||
const obj: *Type = @ptrCast(raw);
|
const obj: *Type = @ptrCast(raw);
|
||||||
|
|
||||||
|
|
@ -219,7 +219,7 @@ pub const Knot = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(obj: *Knot, story: *Story) void {
|
pub fn destroy(obj: *Knot, story: *Story) void {
|
||||||
const gpa = story.allocator;
|
const gpa = story.gpa;
|
||||||
obj.members.deinit(gpa);
|
obj.members.deinit(gpa);
|
||||||
|
|
||||||
const alloc_len = @sizeOf(Type);
|
const alloc_len = @sizeOf(Type);
|
||||||
|
|
|
||||||
|
|
@ -183,29 +183,24 @@ fn testRunner(gpa: std.mem.Allocator, source_bytes: [:0]const u8, options: Optio
|
||||||
defer story.deinit();
|
defer story.deinit();
|
||||||
|
|
||||||
while (!story.is_exited and story.can_advance) {
|
while (!story.is_exited and story.can_advance) {
|
||||||
while (story.can_advance) {
|
while (try story.advance()) |content| {
|
||||||
const content_text = try story.advance(gpa);
|
try io_w.print("{s}\n", .{content});
|
||||||
defer gpa.free(content_text);
|
|
||||||
if (content_text.len != 0) {
|
|
||||||
try io_w.print("{s}\n", .{content_text});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (story.current_choices.items.len > 0) {
|
if (story.current_choices.items.len > 0) {
|
||||||
for (story.current_choices.items, 0..) |*choice, index| {
|
for (story.current_choices.items, 0..) |*choice, index| {
|
||||||
try io_w.print("{d}: {s}\n", .{ index + 1, choice.text.items });
|
try io_w.print("{d}: {s}\n", .{ index + 1, choice.content });
|
||||||
}
|
}
|
||||||
try io_w.print("?> ", .{});
|
try io_w.print("?> ", .{});
|
||||||
|
|
||||||
const input_line = try io_r.takeDelimiter('\n');
|
const input_line = try io_r.takeDelimiter('\n');
|
||||||
if (input_line) |bytes| {
|
if (input_line) |bytes| {
|
||||||
const parsed_choice_index = try std.fmt.parseUnsigned(usize, bytes, 10);
|
|
||||||
const choice_index = if (parsed_choice_index == 0) 0 else parsed_choice_index - 1;
|
|
||||||
// TODO: Seems like Ink proof wants to check the option text, not the actually
|
// TODO: Seems like Ink proof wants to check the option text, not the actually
|
||||||
// rendered text.
|
// rendered text.
|
||||||
//const result_text = story.current_choices.items[choice_index];
|
//const result_text = story.current_choices.items[choice_index];
|
||||||
//try io_w.print("{s}\n", .{result_text.text.items});
|
//try io_w.print("{s}\n", .{result_text.text.items});
|
||||||
|
|
||||||
try story.selectChoiceIndex(choice_index);
|
const index = try std.fmt.parseUnsigned(usize, bytes, 10);
|
||||||
|
try story.selectChoiceIndex(if (index > 0) index - 1 else index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -543,6 +543,7 @@ pub const Module = struct {
|
||||||
|
|
||||||
pub fn deinit(mod: *Module) void {
|
pub fn deinit(mod: *Module) void {
|
||||||
const gpa = mod.gpa;
|
const gpa = mod.gpa;
|
||||||
|
mod.tree.deinit(gpa);
|
||||||
mod.ir.deinit(gpa);
|
mod.ir.deinit(gpa);
|
||||||
mod.intern_pool.deinit(gpa);
|
mod.intern_pool.deinit(gpa);
|
||||||
mod.globals.deinit(gpa);
|
mod.globals.deinit(gpa);
|
||||||
|
|
|
||||||
29
src/main.zig
29
src/main.zig
|
|
@ -108,28 +108,29 @@ fn mainArgs(
|
||||||
return if (!compile_only) run(gpa, &story);
|
return if (!compile_only) run(gpa, &story);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(gpa: std.mem.Allocator, story: *Story) !void {
|
fn run(_: std.mem.Allocator, story: *Story) !void {
|
||||||
const stdin = std.fs.File.stdin();
|
const stdin = std.fs.File.stdin();
|
||||||
var stdin_reader = stdin.reader(&stdin_buffer);
|
var stdin_reader = stdin.reader(&stdin_buffer);
|
||||||
|
const stdout = std.fs.File.stdin();
|
||||||
|
var stdout_writer = stdout.writer(&stdout_buffer);
|
||||||
|
const reader = &stdin_reader.interface;
|
||||||
|
const writer = &stdout_writer.interface;
|
||||||
|
|
||||||
while (!story.is_exited and story.can_advance) {
|
while (!story.is_exited and story.can_advance) {
|
||||||
while (story.can_advance) {
|
while (try story.advance()) |content| {
|
||||||
const content_text = try story.advance(gpa);
|
try writer.print("{s}\n", .{content});
|
||||||
defer gpa.free(content_text);
|
try writer.flush();
|
||||||
std.debug.print("{s}\n", .{content_text});
|
|
||||||
}
|
}
|
||||||
if (story.current_choices.items.len > 0) {
|
if (story.current_choices.items.len > 0) {
|
||||||
for (story.current_choices.items, 0..) |*choice, index| {
|
for (story.current_choices.items, 0..) |choice, index| {
|
||||||
const choice_text = try choice.text.toOwnedSlice(gpa);
|
try writer.print("[{d}]: {s}\n", .{ index + 1, choice.content });
|
||||||
defer gpa.free(choice_text);
|
|
||||||
std.debug.print("[{d}]: {s}\n", .{ index + 1, choice_text });
|
|
||||||
}
|
}
|
||||||
std.debug.print("> ", .{});
|
try writer.writeAll("> ");
|
||||||
|
try writer.flush();
|
||||||
|
|
||||||
const input_line = try stdin_reader.interface.takeDelimiter('\n');
|
if (try reader.takeDelimiter('\n')) |bytes| {
|
||||||
if (input_line) |bytes| {
|
const index = try std.fmt.parseUnsigned(usize, bytes, 10);
|
||||||
const choice_index = try std.fmt.parseUnsigned(usize, bytes, 10);
|
try story.selectChoiceIndex(if (index > 0) index - 1 else index);
|
||||||
try story.selectChoiceIndex(if (choice_index == 0) 0 else choice_index - 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -324,7 +324,8 @@ pub const Writer = struct {
|
||||||
.float => try self.writeFloatInst(w, inst),
|
.float => try self.writeFloatInst(w, inst),
|
||||||
.str => try self.writeStringInst(w, inst),
|
.str => try self.writeStringInst(w, inst),
|
||||||
.content_push => try self.writeUnaryInst(w, inst),
|
.content_push => try self.writeUnaryInst(w, inst),
|
||||||
.content_flush => try self.writeUnaryInst(w, inst),
|
.content_line => try self.writeUnaryInst(w, inst),
|
||||||
|
.content_glue => try self.writeUnaryInst(w, inst),
|
||||||
.choice_br => try self.writeChoiceBrInst(w, inst),
|
.choice_br => try self.writeChoiceBrInst(w, inst),
|
||||||
.ret => try self.writeUnaryInst(w, inst),
|
.ret => try self.writeUnaryInst(w, inst),
|
||||||
.implicit_ret => try self.writeUnaryInst(w, inst),
|
.implicit_ret => try self.writeUnaryInst(w, inst),
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,7 @@ pub const Tokenizer = struct {
|
||||||
slash,
|
slash,
|
||||||
equal,
|
equal,
|
||||||
bang,
|
bang,
|
||||||
|
pipe,
|
||||||
less_than,
|
less_than,
|
||||||
greater_than,
|
greater_than,
|
||||||
word,
|
word,
|
||||||
|
|
@ -224,7 +225,10 @@ pub const Tokenizer = struct {
|
||||||
},
|
},
|
||||||
'|' => {
|
'|' => {
|
||||||
self.index += 1;
|
self.index += 1;
|
||||||
result.tag = .pipe;
|
switch (grammar) {
|
||||||
|
.content => result.tag = .pipe,
|
||||||
|
.expression => continue :state .pipe,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'(' => {
|
'(' => {
|
||||||
self.index += 1;
|
self.index += 1;
|
||||||
|
|
@ -280,6 +284,13 @@ pub const Tokenizer = struct {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
.pipe => switch (self.buffer[self.index]) {
|
||||||
|
'|' => {
|
||||||
|
self.index += 1;
|
||||||
|
result.tag = .pipe_pipe;
|
||||||
|
},
|
||||||
|
else => result.tag = .pipe,
|
||||||
|
},
|
||||||
.minus => switch (self.buffer[self.index]) {
|
.minus => switch (self.buffer[self.index]) {
|
||||||
'>' => {
|
'>' => {
|
||||||
self.index += 1;
|
self.index += 1;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue