refactor: story objects and story dumper

This commit is contained in:
Brett Broadhurst 2026-03-02 12:52:20 -07:00
parent 849908f251
commit 3ab279de0f
Failed to generate hash of commit
3 changed files with 196 additions and 209 deletions

View file

@ -1,9 +1,10 @@
//! Virtual machine state for story execution. //! Virtual machine state for story execution.
const std = @import("std"); const std = @import("std");
const tokenizer = @import("./tokenizer.zig"); const tokenizer = @import("tokenizer.zig");
const Ast = @import("./Ast.zig"); const Ast = @import("Ast.zig");
const AstGen = @import("./AstGen.zig"); const AstGen = @import("AstGen.zig");
pub const Object = @import("./object.zig").Object; pub const Object = @import("Story/object.zig").Object;
const Dumper = @import("Story/Dumper.zig");
const assert = std.debug.assert; const assert = std.debug.assert;
const Story = @This(); const Story = @This();
@ -61,206 +62,6 @@ pub const Opcode = enum(u8) {
_, _,
}; };
fn getObjectType(object: *const Object) []const u8 {
switch (object.tag) {
.number => return "Number",
.string => return "String",
.content_path => return "ContentPath",
}
}
fn printObject(writer: *std.Io.Writer, object: *const Object) !void {
const type_string = getObjectType(object);
switch (object.tag) {
.number => {
const typed_object: *const Object.Number = @ptrCast(object);
switch (typed_object.data) {
.boolean => |value| {
try writer.print("<type={s} value={s}, address={*}>", .{
type_string,
if (value) "true" else "false",
object,
});
},
.floating => |value| {
try writer.print("<type={s} value={d}, address={*}>", .{
type_string,
value,
object,
});
},
.integer => |value| {
try writer.print("<type={s} value={d}, address={*}>", .{
type_string,
value,
object,
});
},
}
},
.string => {
const typed_object: *const Object.String = @ptrCast(object);
const string_bytes = typed_object.bytes[0..typed_object.length];
try writer.print("<type={s} value=\"{s}\", address={*}>", .{
type_string,
string_bytes,
object,
});
},
.content_path => {
try writer.print("<type={s} address={*}>", .{ type_string, object });
},
}
}
pub const Dumper = struct {
story: *const Story,
fn dumpSimpleInst(
_: *const Dumper,
writer: *std.Io.Writer,
offset: usize,
op: Opcode,
) !usize {
try writer.print("{s}\n", .{@tagName(op)});
return offset + 1;
}
fn dumpByteInst(
_: *const Dumper,
writer: *std.Io.Writer,
context: *const Object.ContentPath,
offset: usize,
op: Opcode,
) !usize {
const arg = context.bytes[offset + 1];
if (op == .load_const) {
try writer.print("{s} {d} (", .{ @tagName(op), arg });
try printObject(writer, context.const_pool[arg]);
try writer.print(")\n", .{});
} else {
try writer.print("{s} {x}\n", .{ @tagName(op), arg });
}
return offset + 2;
}
fn dumpGlobalInst(
_: *const Dumper,
writer: *std.Io.Writer,
context: *const Object.ContentPath,
offset: usize,
op: Opcode,
) !usize {
const arg = context.bytes[offset + 1];
const global_name: *Object.String = @ptrCast(context.const_pool[arg]);
const name_bytes = global_name.bytes[0..global_name.length];
try writer.print("{s} {x} '{s}'\n", .{ @tagName(op), arg, name_bytes });
return offset + 2;
}
fn dumpJumpInst(
_: *const Dumper,
writer: *std.Io.Writer,
context: *const Object.ContentPath,
offset: usize,
op: Opcode,
) !usize {
var jump: u16 = @as(u16, context.bytes[offset + 1]) << 8;
jump |= context.bytes[offset + 2];
try writer.print("{s} 0x{x:0>4} (0x{x:0>4} -> 0x{x:0>4})\n", .{
@tagName(op),
jump,
offset,
offset + 3 + jump,
});
return offset + 3;
}
pub fn dumpInst(
self: *const Dumper,
writer: *std.Io.Writer,
path: *const Object.ContentPath,
offset: usize,
should_prefix: bool,
) !usize {
const name_object = path.name;
const name_bytes = name_object.bytes[0..name_object.length];
const op: Opcode = @enumFromInt(path.bytes[offset]);
if (should_prefix) {
try writer.print("<{s}>:0x{x:0>4} | ", .{ name_bytes, offset });
} else {
try 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 self.dumpSimpleInst(writer, offset, op),
.load_const,
.load,
.store,
=> return self.dumpByteInst(writer, path, offset, op),
.load_global,
.store_global,
.call,
.divert,
=> return self.dumpGlobalInst(writer, path, offset, op),
.jmp,
.jmp_t,
.jmp_f,
=> return self.dumpJumpInst(writer, path, offset, op),
else => |code| {
try writer.print("Unknown opcode 0x{x:0>4}\n", .{@intFromEnum(code)});
return offset + 1;
},
}
}
pub fn dump(
self: *const Dumper,
writer: *std.Io.Writer,
path: *const Object.ContentPath,
) !void {
const name_object = path.name;
const name_bytes = name_object.bytes[0..name_object.length];
try writer.print("=== {s}(args: {d}, locals: {d}) ===\n", .{
name_bytes,
path.arity,
path.locals_count,
});
var index: usize = 0;
while (index < path.bytes.len) {
index = try self.dumpInst(writer, path, index, false);
}
return writer.flush();
}
};
pub fn deinit(story: *Story) void { pub fn deinit(story: *Story) void {
const gpa = story.allocator; const gpa = story.allocator;
var next = story.gc_objects.first; var next = story.gc_objects.first;
@ -279,28 +80,28 @@ pub fn deinit(story: *Story) void {
pub fn trace(story: *Story, writer: *std.Io.Writer, frame: *CallFrame) !void { pub fn trace(story: *Story, writer: *std.Io.Writer, frame: *CallFrame) !void {
try writer.print("\tStack => stack_pointer={d}, objects=[", .{frame.sp}); try writer.print("\tStack => stack_pointer={d}, objects=[", .{frame.sp});
const dumper = Dumper{ .story = story, .writer = writer };
const stack = &story.stack; const stack = &story.stack;
const stack_top = story.stack.items.len; const stack_top = story.stack.items.len;
if (stack_top > 0) { if (stack_top > 0) {
const last_slot = stack.items[stack.items.len - 1]; const last_slot = stack.items[stack.items.len - 1];
for (stack.items[frame.sp .. stack.items.len - 1]) |slot| { for (stack.items[frame.sp .. stack.items.len - 1]) |slot| {
if (slot) |object| { if (slot) |object| {
try printObject(writer, object); try dumper.printObject(object);
} else { } else {
try writer.writeAll("NULL"); try writer.writeAll("NULL");
} }
try writer.writeAll(", "); try writer.writeAll(", ");
} }
if (last_slot) |object| { if (last_slot) |object| {
try printObject(writer, object); try dumper.printObject(object);
} else { } else {
try writer.writeAll("NULL"); try writer.writeAll("NULL");
} }
} }
try writer.writeAll("]\n"); try writer.writeAll("]\n");
const dumper = Dumper{ .story = story }; _ = try dumper.dumpInst(frame.callee, frame.ip, true);
_ = try dumper.dumpInst(writer, frame.callee, frame.ip, true);
return writer.flush(); return writer.flush();
} }

186
src/Story/Dumper.zig Normal file
View file

@ -0,0 +1,186 @@
const std = @import("std");
const Story = @import("../Story.zig");
const Object = Story.Object;
const Opcode = Story.Opcode;
const Dumper = @This();
story: *const Story,
writer: *std.Io.Writer,
fn dumpSimpleInst(d: Dumper, offset: usize, op: Opcode) !usize {
try d.writer.print("{s}\n", .{@tagName(op)});
return offset + 1;
}
fn dumpByteInst(d: Dumper, context: *const Object.ContentPath, offset: usize, op: Opcode) !usize {
const arg = context.bytes[offset + 1];
if (op == .load_const) {
try d.writer.print("{s} {d} (", .{ @tagName(op), arg });
try printObject(d.writer, context.const_pool[arg]);
try d.writer.print(")\n", .{});
} else {
try d.writer.print("{s} {x}\n", .{ @tagName(op), arg });
}
return offset + 2;
}
fn dumpGlobalInst(
d: Dumper,
context: *const Object.ContentPath,
offset: usize,
op: Opcode,
) !usize {
const arg = context.bytes[offset + 1];
const global_name: *Object.String = @ptrCast(context.const_pool[arg]);
const name_bytes = global_name.bytes[0..global_name.length];
try d.writer.print("{s} {x} '{s}'\n", .{ @tagName(op), arg, name_bytes });
return offset + 2;
}
fn dumpJumpInst(
d: Dumper,
context: *const Object.ContentPath,
offset: usize,
op: Opcode,
) !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,
});
return offset + 3;
}
pub fn dumpInst(
d: Dumper,
path: *const Object.ContentPath,
offset: usize,
should_prefix: bool,
) !usize {
const name_object = path.name;
const name_bytes = name_object.bytes[0..name_object.length];
const op: Opcode = @enumFromInt(path.bytes[offset]);
if (should_prefix) {
try d.writer.print("<{s}>:0x{x:0>4} | ", .{ name_bytes, offset });
} else {
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),
else => |code| {
try d.writer.print("Unknown opcode 0x{x:0>4}\n", .{@intFromEnum(code)});
return offset + 1;
},
}
}
pub fn dump(d: Dumper, path: *const Object.ContentPath) !void {
const name_object = path.name;
const name_bytes = name_object.bytes[0..name_object.length];
try d.writer.print("=== {s}(args: {d}, locals: {d}) ===\n", .{
name_bytes,
path.arity,
path.locals_count,
});
var index: usize = 0;
while (index < path.bytes.len) {
index = try d.dumpInst(path, index, false);
}
return d.writer.flush();
}
fn getObjectType(object: *const Object) []const u8 {
switch (object.tag) {
.number => return "Number",
.string => return "String",
.content_path => return "ContentPath",
}
}
pub fn printObject(writer: *std.Io.Writer, object: *const Object) !void {
const type_string = getObjectType(object);
switch (object.tag) {
.number => {
const typed_object: *const Object.Number = @ptrCast(object);
switch (typed_object.data) {
.boolean => |value| {
try writer.print("<type={s} value={s}, address={*}>", .{
type_string,
if (value) "true" else "false",
object,
});
},
.floating => |value| {
try writer.print("<type={s} value={d}, address={*}>", .{
type_string,
value,
object,
});
},
.integer => |value| {
try writer.print("<type={s} value={d}, address={*}>", .{
type_string,
value,
object,
});
},
}
},
.string => {
const typed_object: *const Object.String = @ptrCast(object);
const string_bytes = typed_object.bytes[0..typed_object.length];
try writer.print("<type={s} value=\"{s}\", address={*}>", .{
type_string,
string_bytes,
object,
});
},
.content_path => {
try writer.print("<type={s} address={*}>", .{ type_string, object });
},
}
}

View file

@ -1,5 +1,5 @@
const std = @import("std"); const std = @import("std");
const Story = @import("./Story.zig"); const Story = @import("../Story.zig");
/// Runtime Object, whose memory is managed through the virtual machine's /// Runtime Object, whose memory is managed through the virtual machine's
/// garbage collector. /// garbage collector.