import { test, expect } from "bun:test" import { VM, toBytecode } from "#reef" test("REPL mode - demonstrates PC reset problem", async () => { // Track how many times each line executes let line1Count = 0 let line2Count = 0 // Line 1: Set x = 5, track execution const line1 = toBytecode([ ["LOAD", "trackLine1"], ["PUSH", 0], ["PUSH", 0], ["CALL"], ["POP"], ["PUSH", 5], ["STORE", "x"] ]) const vm = new VM(line1, { trackLine1: () => { line1Count++; return null }, trackLine2: () => { line2Count++; return null } }) await vm.run() expect(vm.scope.get("x")).toEqual({ type: "number", value: 5 }) expect(line1Count).toBe(1) expect(line2Count).toBe(0) // Line 2: Track execution, load x const line2 = toBytecode([ ["LOAD", "trackLine2"], ["PUSH", 0], ["PUSH", 0], ["CALL"], ["POP"], ["LOAD", "x"] ]) // Append line2 bytecode to VM (what a REPL would do) vm.instructions.push(...line2.instructions) for (const constant of line2.constants) { if (!vm.constants.includes(constant)) { vm.constants.push(constant) } } // Current behavior: run() resets PC to 0, re-executing everything await vm.run() // PROBLEM: Line 1 ran AGAIN (count is now 2, not 1) // This is the issue for REPL - side effects run multiple times expect(line1Count).toBe(2) // Ran twice! Should still be 1 expect(line2Count).toBe(1) // This is correct // What we WANT for REPL: // - Only execute the NEW bytecode (line 2) // - line1Count should stay at 1 // - line2Count should be 1 }) test("REPL mode - continue() executes only new bytecode", async () => { // Track how many times each line executes let line1Count = 0 let line2Count = 0 // Line 1: Set x = 5, track execution const line1 = toBytecode([ ["LOAD", "trackLine1"], ["PUSH", 0], ["PUSH", 0], ["CALL"], ["POP"], ["PUSH", 5], ["STORE", "x"] ]) const vm = new VM(line1, { trackLine1: () => { line1Count++; return null }, trackLine2: () => { line2Count++; return null } }) await vm.run() expect(vm.scope.get("x")).toEqual({ type: "number", value: 5 }) expect(line1Count).toBe(1) expect(line2Count).toBe(0) // Line 2: Track execution, load x and add 10 const line2 = toBytecode([ ["LOAD", "trackLine2"], ["PUSH", 0], ["PUSH", 0], ["CALL"], ["POP"], ["LOAD", "x"], ["PUSH", 10], ["ADD"] ]) // Append line2 bytecode to VM using the helper method vm.appendBytecode(line2) // SOLUTION: Use continue() instead of run() to resume from current PC await vm.continue() const result = vm.stack[vm.stack.length - 1] // SUCCESS: Line 1 only ran once, line 2 ran once expect(line1Count).toBe(1) // Still 1! Side effect didn't re-run expect(line2Count).toBe(1) // Ran once as expected expect(result).toEqual({ type: "number", value: 15 }) // 5 + 10 })