From dc8e81accf37afa40e91a013940947ee428923dc Mon Sep 17 00:00:00 2001 From: bgk- Date: Wed, 28 Feb 2024 22:01:14 -0800 Subject: [PATCH] Add starting bough path --- build.zig.zon | 2 +- docs/syntax.md | 32 +++++----- examples/hello.topi | 2 - examples/story.topi | 3 +- src/bytecode.zig | 26 ++++++++ src/cli.zig | 110 +++++++++++++++++++++------------- src/compiler.test.zig | 16 +++++ src/compiler.zig | 34 +++++++++++ src/export.zig | 15 +++-- src/lexer.zig | 2 +- src/structures/jump-tree.zig | 5 +- src/structures/visit-tree.zig | 4 +- src/values.zig | 14 ++--- src/vm.test.zig | 35 +++-------- src/vm.zig | 31 +++++++++- 15 files changed, 217 insertions(+), 114 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index 9d35e18..eaedd2d 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = "topiary", - .version = "0.8.1", + .version = "0.9.0", .paths = .{""}, .dependencies = .{ }, diff --git a/docs/syntax.md b/docs/syntax.md index dee1c06..6452ec4 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -17,17 +17,24 @@ Tags can are also optional and can be omitted or multiple can be separated each ### Jumps -Boughs can be started with jumps, denoted with `=> [Name]`. - -So with that we can write a very simple program. +Boughs can be started with jumps, denoted with `=> [Name]`. The first jump must be passed in to the `start` function, +or if using the CLI the second parameter of the run command. ```topi +# topi run file.topi START + === START { :John: "Hello Jane!" #greet :Jane: "Great to see you, John" } +``` + +#### Automatic Jump Starts + +As a convenience, if you don't pass in a jump to the CLI the first bough in the file will be used. -=> START +```topi +# topi run file.topi // START will run in the above example ``` ### Nesting @@ -35,12 +42,13 @@ So with that we can write a very simple program. Boughs can be nested and jumped to with `.` like so: ```topi +# topi run file.topi START.INNER + === START { === INNER { :Speaker: "Start here" } } -=> START.INNER ``` ### Forks @@ -63,8 +71,6 @@ Fork bodies can be either a jump or a block surrounded by braces. === END { :John: "This is hard" } - -=> START ``` Forks can also be named to revisit, useful if wanting to loop choices. @@ -82,7 +88,6 @@ Forks can also be named to revisit, useful if wanting to loop choices. === END { :John: "The hard way it is" } -=> START ``` Choices can also be unique with `~*`, this means once they are visited, @@ -103,7 +108,6 @@ they won't be added to the choice list again. === END { :John: "The hard way it is" } -=> START ``` Choices can also be named to get the visit count in the story, @@ -126,7 +130,6 @@ to be used in your runner code. print(START.DIFFICULTY.EASY) // everytime EASY was chosen print(START.DIFFICULTY.HARD) // everytime HARD was chosen } -=> START ``` ### Visits @@ -180,7 +183,6 @@ Visit paths can be found within scopes and don't need the full path written out. } } } -=> START ``` ### Flow and Jump Back Ups @@ -225,7 +227,6 @@ However named forks with backups should be used with caution. } :Jane: "Good choice." } -=> START ``` @@ -406,7 +407,6 @@ const value = 42 greeting = "Hello" :Jane: "{greeting}, John. The password is {value}." // Hello, John. The password is 42. } -=> START ``` ### Functions @@ -529,6 +529,8 @@ When making nested jumps be aware that preceeding code will be execuded to ensur Consider the following ```topi +# topi run file.topi +// Expected output :Speaker: "End test 2" === START { :Speaker: "Start conversation" var test = "test" @@ -538,11 +540,9 @@ Consider the following :Speaker: "End {test}" } } - -=> START.INNER // Expected output :Speaker: "End test 2" ``` -In this situation we need to creating the `test` variable before it's set to `test 2`. +In this situation we need to create the `test` variable before it's set to `test 2`. To ensure that happens when you jump to `START.INNER` first Topiary will jump to `START` execute all code (while skipping dialogues and forks), then when it encounters another jump or ends, it'll then jump to `INNER`. diff --git a/examples/hello.topi b/examples/hello.topi index 24d28e5..3b7e96c 100644 --- a/examples/hello.topi +++ b/examples/hello.topi @@ -14,5 +14,3 @@ :: "They walk away..." } -=> START - diff --git a/examples/story.topi b/examples/story.topi index 4880de0..d51bf20 100644 --- a/examples/story.topi +++ b/examples/story.topi @@ -10,7 +10,7 @@ var party = Set{"Rogue", "Druid", "Fighter", "Archer"} var gold = 0 var spiderDefeated = false -class Character { +class Character = { injured = false, } var rogue = new Character{} @@ -18,7 +18,6 @@ var druid = new Character{} var fighter = new Character{} var archer = new Character{} -=> TAVERN === TAVERN { :Innkeeper: "Welcome to the Rusty Tankard, travelers. A dire quest awaits those who are brave." :Innkeeper: "A terrible curse plagues our town, and we need your help. Are you four up for the challenge?" diff --git a/src/bytecode.zig b/src/bytecode.zig index f4419c9..4ffead2 100644 --- a/src/bytecode.zig +++ b/src/bytecode.zig @@ -10,6 +10,9 @@ pub const Bytecode = struct { uuids: []UUID.ID, locals_count: usize, token_lines: []u32, + boughs: []BoughJump, + + pub const BoughJump = struct { name: []const u8, ip: OpCode.Size(.jump) }; pub const GlobalSymbol = struct { name: []const u8, @@ -29,6 +32,8 @@ pub const Bytecode = struct { allocator.free(self.uuids); for (self.global_symbols) |s| allocator.free(s.name); allocator.free(self.global_symbols); + for (self.boughs) |b| allocator.free(b.name); + allocator.free(self.boughs); } pub fn serialize(self: *Bytecode, writer: anytype) !void { @@ -41,6 +46,13 @@ pub const Bytecode = struct { try writer.writeByte(if (sym.is_extern) 1 else 0); } + try writer.writeInt(u64, @as(u64, @intCast(self.boughs.len)), .little); + for (self.boughs) |bough| { + try writer.writeInt(u16, @as(u16, @intCast(bough.name.len)), .little); + try writer.writeAll(bough.name); + try writer.writeInt(OpCode.Size(.jump), bough.ip, .little); + } + try writer.writeInt(u64, @as(u64, @intCast(self.instructions.len)), .little); try writer.writeAll(self.instructions); try writer.writeInt(u64, @as(u64, @intCast(self.token_lines.len)), .little); @@ -68,6 +80,19 @@ pub const Bytecode = struct { }; } + const bough_count = try reader.readInt(u64, .little); + var boughs = try allocator.alloc(BoughJump, bough_count); + count = 0; + while (count < bough_count) : (count += 1) { + const length = try reader.readInt(u16, .little); + const buf = try allocator.alloc(u8, length); + try reader.readNoEof(buf); + boughs[count] = BoughJump{ + .name = buf, + .ip = try reader.readInt(OpCode.Size(.jump), .little), + }; + } + const instruction_count = try reader.readInt(u64, .little); const instructions = try allocator.alloc(u8, instruction_count); try reader.readNoEof(instructions); @@ -91,6 +116,7 @@ pub const Bytecode = struct { return .{ .instructions = instructions, .token_lines = token_lines, + .boughs = boughs, .constants = constants, .global_symbols = global_symbols, .uuids = uuids, diff --git a/src/cli.zig b/src/cli.zig index c5e9ba0..f0214f9 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -18,12 +18,21 @@ fn usage(comptime msg: []const u8) !void { if (!std.mem.eql(u8, msg, "")) { try out.print(msg, .{}); try out.print("\n", .{}); - } else { - try out.print("topi - command line topiary processor\n", .{}); - try out.print("Usage:\n", .{}); - try out.print("\n", .{}); } - try out.print(" topi [--auto|-a ] [--compile|-c ]\n", .{}); + try out.print("topi - command line topiary processor\n", .{}); + try out.print("Usage:\n", .{}); + try out.print(" topi [-v | --version] [-h | --help] [flags]\n", .{}); + try out.print("\n", .{}); + try out.print("Commands:\n", .{}); + try out.print(" topi run [start_bough] [--verbose]\n", .{}); + try out.print(" topi auto [-verbose]\n", .{}); + try out.print(" topi compile [output_file] [--verbose] [--dry|-d]\n", .{}); + try out.print("\n", .{}); + try out.print("Flags:\n", .{}); + try out.print("\n", .{}); + try out.print(" --version, -v: Output current version\n", .{}); + try out.print(" --verbose: Output debug logs\n", .{}); + try out.print(" --dry, -d: Compile only\n", .{}); } pub fn main() !void { @@ -32,51 +41,64 @@ pub fn main() !void { var args = try std.process.argsWithAllocator(arena.allocator()); _ = args.skip(); - const file_path = args.next(); - if (file_path == null) { - try usage("No file argument provided.\n"); - return; - } - if (std.mem.eql(u8, file_path.?, "-h") or std.mem.eql(u8, file_path.?, "--help")) { - try usage(""); - return; + const maybe_cmd = args.next(); + if (maybe_cmd == null) return usage(""); + const cmd = maybe_cmd.?; + + const is_version = std.mem.eql(u8, cmd, "-v") or std.mem.eql(u8, cmd, "--version"); + if (is_version) return usage(""); + const is_help = std.mem.eql(u8, cmd, "-h") or std.mem.eql(u8, cmd, "--help"); + if (is_help) return usage(""); + + const is_run = std.mem.eql(u8, cmd, "run"); + const is_auto = std.mem.eql(u8, cmd, "auto"); + const is_compile = std.mem.eql(u8, cmd, "compile"); + if (!is_run and !is_auto and !is_compile) return usage("Unknown command"); + + const maybe_file_path = args.next(); + if (maybe_file_path == null) { + return usage("No file argument provided.\n"); } - var is_compile = false; + const file_path = maybe_file_path.?; + var out_path: ?[]const u8 = null; - var is_auto = false; + var bough_path: ?[]const u8 = null; var auto_count: usize = 0; var is_dry = false; var is_verbose = false; - while (args.next()) |flag| { - if (std.mem.eql(u8, flag, "-c") or std.mem.eql(u8, flag, "--compile")) { - const maybe_out_file = args.next(); - if (maybe_out_file == null) { - try usage("Compile requires an output file.\n"); - return; - } - is_compile = true; - out_path = maybe_out_file.?; - } - if (std.mem.eql(u8, flag, "-a") or std.mem.eql(u8, flag, "--auto")) { - const maybe_auto_count = args.next(); - if (maybe_auto_count == null) { - try usage("Auto requires a play count.\n"); - return; + + if (is_run) { + bough_path = args.next(); + if (bough_path) |b| { + if (std.mem.eql(u8, b, "--verbose")) { + bough_path = null; + is_verbose = true; } - is_auto = true; - auto_count = try std.fmt.parseInt(u64, maybe_auto_count.?, 10); } - if (std.mem.eql(u8, flag, "-d") or std.mem.eql(u8, flag, "--dry")) { - is_dry = true; - } - if (std.mem.eql(u8, flag, "-v") or std.mem.eql(u8, flag, "--verbose")) { + } + if (is_compile) { + out_path = args.next(); + if (out_path == null) return usage("Compile requires an output path or --dry flag."); + is_dry = std.mem.eql(u8, out_path.?, "--dry") or std.mem.eql(u8, out_path.?, "-d"); + } + + if (is_auto) { + const maybe_count = args.next(); + if (maybe_count == null) return usage("Auto requires a play count.\n"); + auto_count = std.fmt.parseInt(u64, maybe_count.?, 10) catch { + return usage("Invalid auto count specified"); + }; + } + const flag = args.next(); + if (flag) |f| { + if (std.mem.eql(u8, f, "--verbose")) { is_verbose = true; } } const allocator = arena.allocator(); - const full_path = std.fs.cwd().realpathAlloc(allocator, file_path.?) catch |err| { - try std.io.getStdErr().writer().print("Could not find file at {s}", .{file_path.?}); + const full_path = std.fs.cwd().realpathAlloc(allocator, file_path) catch |err| { + try std.io.getStdErr().writer().print("Could not find file at {s}", .{file_path}); if (is_verbose) return err; return; }; @@ -117,7 +139,7 @@ pub fn main() !void { var auto_runner = AutoTestRunner.init(); var vm = try Vm.init(vm_alloc, bytecode, &auto_runner.runner); vm.interpret() catch { - vm.err.print(std.debug); + vm.err.print(std.io.getStdErr().writer()); continue; }; defer vm.deinit(); @@ -144,9 +166,13 @@ pub fn main() !void { return; }; - vm.interpret() catch { - vm.err.print(std.debug); - }; + try vm.start(bough_path); + while (vm.can_continue) { + vm.run() catch { + vm.err.print(std.io.getStdErr().writer()); + break; + }; + } } fn getFilePath(allocator: std.mem.Allocator) !?[]const u8 { diff --git a/src/compiler.test.zig b/src/compiler.test.zig index d4f3c62..94b4914 100644 --- a/src/compiler.test.zig +++ b/src/compiler.test.zig @@ -1695,6 +1695,22 @@ test "Classes" { } } +test "Global Jump Error" { + const input = + \\ === START {} + \\ => START + ; + + var mod = Module.create(allocator); + defer mod.deinit(); + defer mod.entry.source_loaded = false; + const err = compileSource(input, &mod); + const errWriter = std.io.getStdIn().writer(); + try mod.writeErrors(errWriter); + + try testing.expect(Compiler.Error.CompilerError == err); +} + test "Serialize" { const input = \\ var str = "string value" diff --git a/src/compiler.zig b/src/compiler.zig index 3da793e..111437f 100644 --- a/src/compiler.zig +++ b/src/compiler.zig @@ -139,8 +139,34 @@ pub const Compiler = struct { .is_extern = s.is_extern, }; } + var boughs = std.ArrayList(Bytecode.BoughJump).init(self.allocator); + defer boughs.deinit(); + var stack = std.ArrayList(*const JumpTree.Node).init(self.allocator); + defer stack.deinit(); + // reverse iterate so we keep the order when popping off stack + var i: usize = self.jump_tree.root.children.items.len; + while (i > 0) { + i -= 1; + try stack.append(self.jump_tree.root.children.items[i]); + } + while (stack.items.len > 0) { + var node = stack.pop(); + var path = std.ArrayList(u8).init(self.allocator); + defer path.deinit(); + try node.writePath(path.writer()); + try boughs.append(.{ + .name = try path.toOwnedSlice(), + .ip = node.dest_ip, + }); + var child_i: usize = node.children.items.len; + while (child_i > 0) { + child_i -= 1; + try stack.append(node.children.items[child_i]); + } + } return .{ .instructions = try self.chunk.instructions.toOwnedSlice(), + .boughs = try boughs.toOwnedSlice(), .token_lines = try self.chunk.token_lines.toOwnedSlice(), .constants = try self.constants.toOwnedSlice(), .global_symbols = global_symbols, @@ -175,6 +201,9 @@ pub const Compiler = struct { } try self.replaceDiverts(); + // Add one final fin at the end of file to grab the initial jump_request + try self.chunk.token_lines.append(self.chunk.token_lines.items[self.chunk.instructions.items.len - 1]); + try self.chunk.instructions.append(@intFromEnum(OpCode.fin)); } fn enterChunk(self: *Compiler) !void { @@ -561,6 +590,11 @@ pub const Compiler = struct { try self.writeId(d.id, token); }, .divert => |d| { + if (self.scope == self.root_scope) { + const path = try std.mem.join(self.allocator, ".", d.path); + defer self.allocator.free(path); + return self.fail("Cannot execute jump \"{s}\" in global scope", token, .{path}); + } var backup_pos: usize = 0; if (d.is_backup) { try self.writeOp(.backup, token); diff --git a/src/export.zig b/src/export.zig index 235cca5..643ed64 100644 --- a/src/export.zig +++ b/src/export.zig @@ -136,7 +136,7 @@ export fn compile(path_ptr: [*c]const u8, path_length: usize, out_ptr: [*c]u8, m return fbs.pos; } -export fn start(vm_ptr: usize) void { +export fn start(vm_ptr: usize, path_ptr: [*]const u8, path_len: usize) void { log("Starting VM", .{}, .info); var vm: *Vm = @ptrFromInt(vm_ptr); for (vm.bytecode.global_symbols) |sym| { @@ -144,7 +144,11 @@ export fn start(vm_ptr: usize) void { log("Export \"{s}\" has not been set", .{sym.name}, .warn); } } - vm.start() catch |err| log("Could not start vm: {any}", .{err}, .err); + const path = if (path_len > 0) path_ptr[0..path_len] else if (vm.bytecode.boughs.len > 0) vm.bytecode.boughs[0].name else { + log("Topi file does not have a start bough", .{}, .err); + return; + }; + vm.start(path) catch |err| log("Could not start vm: {any}", .{err}, .err); } export fn run(vm_ptr: usize) void { @@ -494,7 +498,6 @@ test "Create and Destroy Vm" { \\ :: "They walk away..." \\ } \\ - \\ => START ; debug_log = TestRunner.log; @@ -512,9 +515,9 @@ test "Create and Destroy Vm" { try file.seekTo(0); const buf = try std.testing.allocator.alloc(u8, 4096); defer std.testing.allocator.free(buf); - const dir_path = try std.fs.cwd().realpathAlloc(std.testing.allocator,"."); + const dir_path = try std.fs.cwd().realpathAlloc(std.testing.allocator, "."); defer std.testing.allocator.free(dir_path); - const path = try std.fs.path.resolve(std.testing.allocator, &.{dir_path,"tmp.topi"}); + const path = try std.fs.path.resolve(std.testing.allocator, &.{ dir_path, "tmp.topi" }); defer std.testing.allocator.free(path); try std.testing.expect(compile(path.ptr, path.len, buf.ptr, buf.len) > 0); @@ -532,7 +535,7 @@ test "Create and Destroy Vm" { val_name.len, @intFromPtr(&testSubscriber), ); - start(vm_ptr); + start(vm_ptr, "", 0); while (canContinue(vm_ptr)) { run(vm_ptr); if (vm.err.msg) |msg| { diff --git a/src/lexer.zig b/src/lexer.zig index ef3f97c..edc53b9 100644 --- a/src/lexer.zig +++ b/src/lexer.zig @@ -157,7 +157,7 @@ pub const Lexer = struct { } fn skipWhitespace(self: *Lexer) void { - while (isWhitespace(self.char)) { + while (isWhitespace(self.char) or self.char == ';') { if (isEndOfLine(self.char)) { self.line += 1; self.column = 1; diff --git a/src/structures/jump-tree.zig b/src/structures/jump-tree.zig index 1625eaf..5a66c77 100644 --- a/src/structures/jump-tree.zig +++ b/src/structures/jump-tree.zig @@ -58,9 +58,10 @@ pub const JumpTree = struct { node = n.parent; } std.mem.reverse(*const Node, list.items); - for (list.items) |n| { + for (list.items, 0..) |n, i| { + if (i == 0) continue; writer.writeAll(n.name) catch break; - writer.writeAll(".") catch break; + if (i != list.items.len - 1) writer.writeAll(".") catch break; } } diff --git a/src/structures/visit-tree.zig b/src/structures/visit-tree.zig index d797bca..16e350c 100644 --- a/src/structures/visit-tree.zig +++ b/src/structures/visit-tree.zig @@ -55,9 +55,9 @@ pub const VisitTree = struct { node = n.parent; } std.mem.reverse(*const Node, list.items); - for (list.items) |n| { + for (list.items, 0..) |n, i| { writer.writeAll(n.name) catch break; - writer.writeByte('.') catch break; + if (i != list.items.len - 1) writer.writeByte('.') catch break; } } diff --git a/src/values.zig b/src/values.zig index c71a208..0c722ac 100644 --- a/src/values.zig +++ b/src/values.zig @@ -217,7 +217,6 @@ pub const Value = union(Type) { bool => if (value) True else False, @TypeOf(null) => Nil, f32 => .{ .number = value }, - u32 => .{ .visit = value }, else => unreachable, }; } @@ -258,7 +257,8 @@ pub const Value = union(Type) { }, .enum_value => |e| { try writer.writeByte(e.index); - // try writer.writeInt(OpCode.SizeOf(.constant)); + try writer.writeByte(@intCast(e.base.name.len)); + try writer.writeAll(e.base.name); }, .obj => |o| { try writer.writeByte(@intFromEnum(@as(Obj.DataType, o.data))); @@ -294,14 +294,8 @@ pub const Value = union(Type) { try writer.writeInt(u16, @as(u16, @intCast(f.lines.len)), .little); for (f.lines) |l| try writer.writeInt(u32, l, .little); }, - .@"enum" => |e| { - try writer.writeByte(@intCast(e.name.len)); - try writer.writeAll(e.name); - try writer.writeByte(@intCast(e.values.len)); - for (e.values) |val| { - try writer.writeByte(@intCast(val.len)); - try writer.writeAll(val); - } + .instance => |i| { + _ = i; }, else => {}, } diff --git a/src/vm.test.zig b/src/vm.test.zig index a42cb0b..ee1d8fd 100644 --- a/src/vm.test.zig +++ b/src/vm.test.zig @@ -571,7 +571,7 @@ test "Builtin Functions" { defer vm.deinit(); defer vm.bytecode.free(testing.allocator); vm.interpret() catch |err| { - vm.err.print(std.debug); + vm.err.print(std.io.getStdErr().writer()); return err; }; const value = vm.stack.previous(); @@ -734,7 +734,7 @@ test "Loops" { defer vm.deinit(); defer vm.bytecode.free(testing.allocator); vm.interpret() catch |err| { - vm.err.print(std.debug); + vm.err.print(std.io.getStdErr().writer()); return err; }; const value = vm.stack.previous(); @@ -836,7 +836,6 @@ test "Boughs" { \\ :speaker: "Text goes here" #tag1 #tag2 \\ :speaker: "More text here" \\ } - \\ => START }, .{ .input = \\ === START { @@ -845,7 +844,6 @@ test "Boughs" { \\ :speaker_one: "{before} and then more text here" \\ :speaker_two: "Text goes here {after}" \\ } - \\ => START }, .{ .input = \\ const repeat = |str, count| { @@ -861,14 +859,12 @@ test "Boughs" { \\ :speaker_one: "Hello, {repeat("Yo ", 5)}!" \\ :speaker_two: "Uh.. hello?" \\ } - \\ => START }, .{ .input = \\ === START { \\ if true { :speaker: "This is true" } \\ else { :speaker: "This is false" } \\ } - \\ => START }, .{ .input = \\ === START { @@ -877,7 +873,6 @@ test "Boughs" { \\ if false :speaker: "False text doesn't appear" \\ :speaker: "Final text here" \\ } - \\ => START }, .{ .input = \\ === START { @@ -889,7 +884,6 @@ test "Boughs" { \\ => INNER \\ :speaker: "Final goes here" // should not be printed \\ } - \\ => START }, .{ .input = \\ === START { @@ -904,7 +898,6 @@ test "Boughs" { \\ => OUTER.INNER \\ :speaker: "Text doesn't appear here" // should not be printed \\ } - \\ => START }, }; @@ -932,9 +925,8 @@ test "Bough Loops" { \\ str = "{i}" \\ :Speaker: "Testing {str}" \\ } - \\ } - \\ => START^ \\ i + \\ } , .value = 5, }, @@ -947,7 +939,7 @@ test "Bough Loops" { defer vm.deinit(); defer vm.bytecode.free(testing.allocator); vm.interpret() catch |err| { - vm.err.print(std.debug); + vm.err.print(std.io.getStdErr().writer()); return err; }; const value = vm.stack.previous(); @@ -970,7 +962,6 @@ test "Forks" { \\ } \\ } \\ } - \\ => START , }, .{ @@ -989,7 +980,6 @@ test "Forks" { \\ } \\ } \\ } - \\ => START \\ === DONE { \\ :speaker: "Done" \\ } @@ -1024,12 +1014,11 @@ test "Visits" { \\ :speaker: "You chose two" \\ } \\ } - \\ } - \\ => START^ \\ print("START: {START}") \\ print("START.NAMED: {START.NAMED}") \\ print("START.NAMED.ONE: {START.NAMED.ONE}") \\ print("START.NAMED.TWO: {START.NAMED.TWO}") + \\ } , }, .{ @@ -1044,11 +1033,10 @@ test "Visits" { \\ :speaker: "You chose two" \\ } \\ } - \\ } - \\ => START^ \\ print("START: {START}") \\ print("START._0.ONE: {START._0.ONE}") \\ print("START._0.TWO: {START._0.TWO}") + \\ } , }, .{ @@ -1066,12 +1054,11 @@ test "Visits" { \\ } \\ } \\ } - \\ } - \\ => START.INNER^ \\ print(START) \\ print(START.INNER) \\ print(START.INNER._0.ONE) \\ print(START.INNER._0.TWO) + \\ } , }, .{ @@ -1091,7 +1078,6 @@ test "Visits" { \\ } \\ } \\ } - \\ => START , }, }; @@ -1112,7 +1098,6 @@ test "Jump Backups" { const test_cases = .{ .{ .input = - \\ => START \\ === START { \\ :speaker: "Question" \\ => MIDDLE^ @@ -1137,7 +1122,6 @@ test "Jump Backups" { \\ } \\ :speaker: "Continue here after fork" \\ } - \\ => START , }, .{ @@ -1164,7 +1148,6 @@ test "Jump Backups" { \\ :speaker: "Not done yet" \\ } else :speaker: "Done" \\ } - \\ => START , }, }; @@ -1197,12 +1180,10 @@ test "Jump Code" { \\ else :: "Done" \\ } \\ } - \\ => START.INNER , }, .{ .input = - \\ => START.INNER \\ === START { \\ var firstValue = 0 \\ :: "First: {firstValue}" @@ -1228,7 +1209,7 @@ test "Jump Code" { defer vm.deinit(); defer vm.bytecode.free(testing.allocator); vm.interpret() catch |err| { - vm.err.print(std.debug); + vm.err.print(std.io.getStdErr().writer()); return err; }; } diff --git a/src/vm.zig b/src/vm.zig index a4183b1..054275a 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -43,7 +43,7 @@ pub const RuntimeErr = struct { msg: ?[]const u8 = null, pub fn print(self: @This(), writer: anytype) void { if (self.msg) |m| { - writer.print("Error at line {}: {s}\n", .{ self.line, m }); + writer.print("Error at line {}: {s}\n", .{ self.line, m }) catch {}; } } }; @@ -81,6 +81,7 @@ pub const Vm = struct { pub const Error = error{ RuntimeError, + BoughNotFound, InvalidChoice, Uninitialized, } || Compiler.Error; @@ -246,7 +247,8 @@ pub const Vm = struct { } pub fn interpret(self: *Vm) !void { - try self.start(); + const path = if (self.bytecode.boughs.len == 0) null else self.bytecode.boughs[0].name; + try self.start(path); while (self.can_continue) { try self.run(); } @@ -257,13 +259,36 @@ pub const Vm = struct { // } } - pub fn start(self: *Vm) !void { + pub fn start(self: *Vm, bough_path: ?[]const u8) !void { self.can_continue = true; self.stack.resize(self.bytecode.locals_count); while (self.frames.count > 1) { _ = self.frames.pop(); } self.currentFrame().ip = 0; + if (bough_path) |path| { + var split_it = std.mem.split(u8, path, "."); + var path_parts = std.ArrayList([]const u8).init(self.allocator); + defer path_parts.deinit(); + while (split_it.next()) |split| { + try path_parts.append(split); + const current_path = try std.mem.join(self.allocator, ".", path_parts.items); + defer self.allocator.free(current_path); + var found = false; + for (self.bytecode.boughs) |b| { + if (std.mem.eql(u8, current_path, b.name)) { + try self.jump_requests.append(b.ip); + found = true; + break; + } + } + if (!found) { + self.err.msg = "Could not find starting path"; + self.err.line = 0; + return Error.BoughNotFound; + } + } + } } fn fail(self: *Vm, comptime msg: []const u8, args: anytype) !void {