const std = @import("std"); const Story = @import("../Story.zig"); const Value = Story.Value; const Object = Story.Object; const Opcode = Story.Opcode; const assert = std.debug.assert; const Dumper = @This(); story: *const Story, indent_level: usize = 0, fn dumpSimpleInst(_: *Dumper, w: *std.Io.Writer, offset: usize, op: Opcode) !usize { try w.print("{s}\n", .{@tagName(op)}); return offset + 1; } fn dumpByteInst( self: *Dumper, w: *std.Io.Writer, knot: *const Object.Knot, offset: usize, op: Opcode, ) !usize { const code = knot.code; const constants_pool = &self.story.constants_pool; assert(code.bytecode.len > offset + 1); const arg = code.bytecode[offset + 1]; if (op == .load_const) { try w.print("{s} {d}", .{ @tagName(op), arg }); try w.writeAll(" ("); if (code.constants.len > arg) { const constant_index = code.constants[arg]; if (constants_pool.items.len > constant_index) { const global_constant = &constants_pool.items[constant_index]; try self.dumpValue(w, global_constant); } else { try w.writeAll("invalid!"); } } else { try w.writeAll("invalid!"); } try w.writeAll(")\n"); } else { try w.print("{s} {x}\n", .{ @tagName(op), arg }); } return offset + 2; } fn dumpGlobalInst( self: *Dumper, w: *std.Io.Writer, knot: *const Object.Knot, offset: usize, op: Opcode, ) !usize { const code = knot.code; const constants_pool = &self.story.constants_pool; assert(code.bytecode.len > offset + 1); const arg = code.bytecode[offset + 1]; assert(code.constants.len > arg); const constant_index = code.constants[arg]; const global_constant = constants_pool.items[constant_index]; switch (global_constant) { .object => |object| switch (object.tag) { .string => { const global_name: *Object.String = @ptrCast(object); const name_bytes = global_name.bytes[0..global_name.length]; try w.print("{s} {x} '{s}'\n", .{ @tagName(op), arg, name_bytes }); }, else => unreachable, }, else => unreachable, } return offset + 2; } const Jump = enum { relative, absolute, }; fn dumpJumpInst( _: *Dumper, w: *std.Io.Writer, knot: *const Object.Knot, offset: usize, op: Opcode, mode: Jump, ) !usize { const code = knot.code; assert(code.bytecode.len > offset + 2); var jump: u16 = @as(u16, code.bytecode[offset + 1]) << 8; jump |= code.bytecode[offset + 2]; switch (mode) { .relative => try w.print("{s} 0x{x:0>4} (0x{x:0>4} -> 0x{x:0>4})\n", .{ @tagName(op), jump, offset, offset + 3 + jump, }), .absolute => try w.print("{s} 0x{x:0>4}\n", .{ @tagName(op), jump, }), } return offset + 3; } pub fn dumpInst( self: *Dumper, w: *std.Io.Writer, knot: *const Object.Knot, offset: usize, should_prefix: bool, ) !usize { const name_object = knot.name; const name_bytes = name_object.bytes[0..name_object.length]; const op: Opcode = @enumFromInt(knot.code.bytecode[offset]); try w.splatByteAll(' ', self.indent_level); if (should_prefix) { try w.print("<{s}>:0x{x:0>4} | ", .{ name_bytes, offset }); } else { try w.print("0x{x:0>4} | ", .{offset}); } switch (op) { .exit => return self.dumpSimpleInst(w, offset, op), .done => return self.dumpSimpleInst(w, offset, op), .ret => return self.dumpSimpleInst(w, offset, op), .pop => return self.dumpSimpleInst(w, offset, op), .true => return self.dumpSimpleInst(w, offset, op), .false => return self.dumpSimpleInst(w, offset, op), .add => return self.dumpSimpleInst(w, offset, op), .sub => return self.dumpSimpleInst(w, offset, op), .mul => return self.dumpSimpleInst(w, offset, op), .div => return self.dumpSimpleInst(w, offset, op), .mod => return self.dumpSimpleInst(w, offset, op), .neg => return self.dumpSimpleInst(w, offset, op), .not => return self.dumpSimpleInst(w, offset, op), .cmp_eq => return self.dumpSimpleInst(w, offset, op), .cmp_lt => return self.dumpSimpleInst(w, offset, op), .cmp_lte => return self.dumpSimpleInst(w, offset, op), .cmp_gt => return self.dumpSimpleInst(w, offset, op), .cmp_gte => return self.dumpSimpleInst(w, offset, op), .load_const => return self.dumpByteInst(w, knot, offset, op), .load => return self.dumpByteInst(w, knot, offset, op), .store => return self.dumpByteInst(w, knot, offset, op), .load_global => return self.dumpGlobalInst(w, knot, offset, op), .store_global => return self.dumpGlobalInst(w, knot, offset, op), .call => return self.dumpByteInst(w, knot, offset, op), .divert => return self.dumpByteInst(w, knot, offset, op), .jmp => return self.dumpJumpInst(w, knot, offset, op, .relative), .jmp_t => return self.dumpJumpInst(w, knot, offset, op, .relative), .jmp_f => return self.dumpJumpInst(w, knot, offset, op, .relative), .load_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_flush => return self.dumpSimpleInst(w, offset, op), .stream_line => return self.dumpSimpleInst(w, offset, op), .stream_glue => return self.dumpSimpleInst(w, offset, op), .br_push => return self.dumpJumpInst(w, knot, offset, op, .absolute), .br_table => return self.dumpSimpleInst(w, offset, op), .br_dispatch => return self.dumpSimpleInst(w, offset, op), .br_select_index => return self.dumpSimpleInst(w, offset, op), else => |code| { try w.print("Unknown opcode 0x{x:0>4}\n", .{@intFromEnum(code)}); return offset + 1; }, } } fn dumpKnotConstants(w: *std.Io.Writer, knot: *const Object.Knot) !void { try w.writeAll("["); for (knot.code.constants) |index| { try w.print("{d},", .{index}); } try w.writeAll("]"); try w.flush(); } fn dumpKnotChildren(w: *std.Io.Writer, knot: *const Object.Knot) !void { try w.writeAll("["); var stitch_iter = knot.members.iterator(); while (stitch_iter.next()) |entry| { try w.print("Stitch: \"{s}\",", .{entry.key_ptr.*}); } try w.writeAll("]"); try w.flush(); } fn dumpKnotBytecode(self: *Dumper, w: *std.Io.Writer, knot: *const Object.Knot) !void { var index: usize = 0; while (index < knot.code.bytecode.len) { index = try self.dumpInst(w, knot, index, false); try w.flush(); } return w.flush(); } pub fn dumpKnot(self: *Dumper, w: *std.Io.Writer, knot: *const Object.Knot) !void { const name_object = knot.name; const name_bytes = name_object.bytes[0..name_object.length]; try w.splatByteAll(' ', self.indent_level); try w.print("Name: \"{s}\"\n", .{name_bytes}); self.indent_level += 1; try w.splatByteAll(' ', self.indent_level); try w.print("Arguments: {d}\n", .{knot.code.args_count}); try w.splatByteAll(' ', self.indent_level); try w.print("Locals: {d}\n", .{knot.code.locals_count}); try w.splatByteAll(' ', self.indent_level); try w.print("Stack Size: {d}\n", .{knot.code.stack_size}); try w.splatByteAll(' ', self.indent_level); try w.writeAll("Constants: "); try dumpKnotConstants(w, knot); try w.writeAll("\n"); try w.splatByteAll(' ', self.indent_level); try w.writeAll("Children: "); try dumpKnotChildren(w, knot); try w.writeAll("\n"); try w.splatByteAll(' ', self.indent_level); try w.writeAll("Bytecode: \n"); self.indent_level += 2; try self.dumpKnotBytecode(w, knot); try w.writeAll("\n"); self.indent_level -= 2; var stitch_iter = knot.members.iterator(); var count: usize = 0; while (stitch_iter.next()) |entry| : (count += 1) { const key = entry.key_ptr.*; const stitch: *Object.Knot = @ptrCast(entry.value_ptr.*); try w.splatByteAll(' ', self.indent_level); try w.print("Stitch #{d}: \"{s}\"\n", .{ count, key }); self.indent_level += 2; try w.splatByteAll(' ', self.indent_level); try w.print("Arguments: {d}\n", .{stitch.code.args_count}); try w.splatByteAll(' ', self.indent_level); try w.print("Locals: {d}\n", .{stitch.code.locals_count}); try w.splatByteAll(' ', self.indent_level); try w.print("Stack Size: {d}\n", .{stitch.code.stack_size}); try w.splatByteAll(' ', self.indent_level); try w.writeAll("Constants: "); try dumpKnotConstants(w, stitch); try w.writeAll("\n"); try w.splatByteAll(' ', self.indent_level); try w.writeAll("Children: "); try dumpKnotChildren(w, stitch); try w.writeAll("\n"); try w.splatByteAll(' ', self.indent_level); try w.writeAll("Bytecode: \n"); self.indent_level += 2; try self.dumpKnotBytecode(w, stitch); try w.writeAll("\n"); self.indent_level -= 4; try w.flush(); } self.indent_level = 2; return w.flush(); } pub fn dumpValue(_: Dumper, w: *std.Io.Writer, value: *const Value) !void { switch (value.*) { .bool => |_| try w.print( "<{s} value={f}>", .{ value.tagBytes(), value }, ), .int => |_| try w.print( "<{s} value={f}, address={*}>", .{ value.tagBytes(), value, value }, ), .float => |_| try w.print( "<{s} value={f}, address={*}>", .{ value.tagBytes(), value, value }, ), .object => |object| switch (object.tag) { .string => { const typed_object: *const Object.String = @ptrCast(object); const string_bytes = typed_object.bytes[0..typed_object.length]; try w.print("<{s} value=\"{s}\", address={*}>", .{ object.tag.tagBytes(), string_bytes, object, }); }, else => try w.print("<{s} address={*}>", .{ object.tag.tagBytes(), object }), }, } } pub fn dump(story: *Story, writer: *std.Io.Writer) !void { var story_dumper: Dumper = .{ .story = story }; try writer.writeAll("=== Constants ===\n"); var i: usize = 0; while (i < story.constants_pool.items.len) : (i += 1) { const global_constant = &story.constants_pool.items[i]; try story_dumper.dumpValue(writer, global_constant); try writer.writeAll("\n"); } try writer.writeAll("\n"); try writer.writeAll("=== Globals ===\n"); var global_iter = story.globals.iterator(); while (global_iter.next()) |entry| { try writer.print("{s} => ...\n", .{entry.key_ptr.*}); } try writer.writeAll("\n"); try writer.writeAll("=== Knots ===\n"); try writer.flush(); var knots_iter = story.globals.iterator(); while (knots_iter.next()) |entry| { if (entry.value_ptr.*) |global| { switch (global) { .object => |object| switch (object.tag) { .knot => { try writer.writeAll("*"); story_dumper.indent_level += 2; try story_dumper.dumpKnot(writer, @ptrCast(object)); story_dumper.indent_level -= 2; }, else => {}, }, else => {}, } } } } pub fn trace(story: *Story, writer: *std.Io.Writer, frame: *Story.CallFrame) !void { var dumper: Dumper = .{ .story = story }; const stack = &story.stack; const stack_top = story.stack.items.len; try writer.print("\tStack => stack_pointer={d}, objects=[", .{frame.sp}); if (stack_top > 0) { // FIXME: There has to be a better way to do this. if (stack_top > 1) { var i: usize = frame.sp; while (i < stack.items.len - 1) : (i += 1) { if (stack.items[i]) |*value| { try dumper.dumpValue(writer, value); } else { try writer.writeAll("null"); } try writer.writeAll(", "); } } if (stack.items[stack.items.len - 1]) |*object| { try dumper.dumpValue(writer, object); } else { try writer.writeAll("null"); } } try writer.writeAll("]\n"); _ = try dumper.dumpInst(writer, frame.callee, frame.ip, true); return writer.flush(); }