diff --git a/src/vm.ts b/src/vm.ts index 9abc315..ab06280 100644 --- a/src/vm.ts +++ b/src/vm.ts @@ -60,6 +60,14 @@ export class VM { this.scope.set(name, { type: 'native', fn, value: '' }) } + pushScope() { + this.scope = new Scope(this.scope) + } + + popScope() { + this.scope = this.scope.parent! + } + async run(): Promise { this.pc = 0 this.stopped = false diff --git a/tests/vm.test.ts b/tests/vm.test.ts new file mode 100644 index 0000000..f7b8817 --- /dev/null +++ b/tests/vm.test.ts @@ -0,0 +1,42 @@ +import { test, expect, describe } from "bun:test" +import { VM } from "#vm" +import { toBytecode } from "#bytecode" +import { toValue } from "#value" + +describe("VM scope methods", () => { + test("pushScope creates isolated child scope", async () => { + const bytecode = toBytecode([["HALT"]]) + const vm = new VM(bytecode) + + vm.set("x", 42) + + vm.pushScope() + + const xValue = vm.scope.get("x") + expect(xValue).toEqual({ type: "number", value: 42 }) + + vm.set("y", 100) + + expect(vm.scope.get("x")).toEqual({ type: "number", value: 42 }) + expect(vm.scope.get("y")).toEqual({ type: "number", value: 100 }) + }) + + test("popScope returns to parent scope and child variables are not accessible", async () => { + const bytecode = toBytecode([["HALT"]]) + const vm = new VM(bytecode) + + vm.set("x", 42) + + vm.pushScope() + vm.set("y", 100) + + expect(vm.scope.get("x")).toEqual({ type: "number", value: 42 }) + expect(vm.scope.get("y")).toEqual({ type: "number", value: 100 }) + + vm.popScope() + + expect(vm.scope.get("x")).toEqual({ type: "number", value: 42 }) + + expect(vm.scope.get("y")).toBeUndefined() + }) +})