ReefVM/tests/repl.test.ts
2025-10-25 09:10:43 -07:00

113 lines
2.9 KiB
TypeScript

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
})