import { test, expect } from "bun:test" import { VM } from "#vm" import { OpCode } from "#opcode" import { toValue } from "#value" test("PUSH_TRY and POP_TRY - no exception thrown", async () => { // Try block that completes successfully const vm = new VM({ instructions: [ { op: OpCode.PUSH_TRY, operand: 5 }, // catch at instruction 5 { op: OpCode.PUSH, operand: 0 }, // push 42 { op: OpCode.PUSH, operand: 1 }, // push 10 { op: OpCode.ADD }, { op: OpCode.POP_TRY }, // pop handler (no exception) { op: OpCode.HALT }, // Catch block (not executed) { op: OpCode.PUSH, operand: 2 }, // push 999 { op: OpCode.HALT } ], constants: [ toValue(42), toValue(10), toValue(999) ] }) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 52 }) }) test("THROW - catch exception with error value", async () => { // Try block that throws an exception const vm = new VM({ instructions: [ { op: OpCode.PUSH_TRY, operand: 5 }, // catch at instruction 5 { op: OpCode.PUSH, operand: 0 }, // push error message { op: OpCode.THROW }, // throw exception { op: OpCode.PUSH, operand: 1 }, // not executed { op: OpCode.HALT }, // Catch block (instruction 5) { op: OpCode.HALT } // error value is on stack ], constants: [ toValue('error occurred'), toValue(999) ] }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'error occurred' }) }) test("THROW - uncaught exception throws JS error", async () => { const vm = new VM({ instructions: [ { op: OpCode.PUSH, operand: 0 }, { op: OpCode.THROW } ], constants: [ toValue('something went wrong') ] }) await expect(vm.run()).rejects.toThrow('Uncaught exception: something went wrong') }) test("THROW - exception with nested try blocks", async () => { // Nested try blocks, inner one catches const vm = new VM({ instructions: [ { op: OpCode.PUSH_TRY, operand: 10 }, // outer catch at 10 { op: OpCode.PUSH_TRY, operand: 6 }, // inner catch at 6 { op: OpCode.PUSH, operand: 0 }, // push 'inner error' { op: OpCode.THROW }, // throw { op: OpCode.PUSH, operand: 1 }, // not executed { op: OpCode.HALT }, // Inner catch block (instruction 6) { op: OpCode.STORE, operand: 'err' }, { op: OpCode.POP_TRY }, // pop outer handler { op: OpCode.LOAD, operand: 'err' }, { op: OpCode.HALT }, // Outer catch block (instruction 10, not executed) { op: OpCode.PUSH, operand: 2 }, { op: OpCode.HALT } ], constants: [ toValue('inner error'), toValue(999), toValue('outer error') ] }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'inner error' }) }) test("THROW - exception skips outer handler", async () => { // Nested try blocks, inner doesn't catch, outer does const vm = new VM({ instructions: [ { op: OpCode.PUSH_TRY, operand: 8 }, // outer catch at 8 { op: OpCode.PUSH_TRY, operand: 6 }, // inner catch at 6 { op: OpCode.PUSH, operand: 0 }, // push error { op: OpCode.THROW }, // throw { op: OpCode.HALT }, // Inner catch block (instruction 6) - re-throws { op: OpCode.THROW }, // re-throw the error // Outer catch block (instruction 8) { op: OpCode.HALT } ], constants: [ toValue('error message') ] }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'error message' }) }) test("THROW - exception unwinds call stack", async () => { // Function that throws, caller has try/catch const vm = new VM({ instructions: [ // 0: Main code with try block { op: OpCode.PUSH_TRY, operand: 6 }, { op: OpCode.MAKE_FUNCTION, operand: 0 }, { op: OpCode.CALL, operand: 0 }, { op: OpCode.POP_TRY }, { op: OpCode.HALT }, // 5: Not executed { op: OpCode.PUSH, operand: 2 }, // 6: Catch block { op: OpCode.HALT }, // error value on stack // 7: Function body (throws) { op: OpCode.PUSH, operand: 1 }, { op: OpCode.THROW } ], constants: [ { type: 'function_def', params: [], defaults: {}, body: 7, variadic: false, kwargs: false }, toValue('function error'), toValue(999) ] }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'function error' }) }) test("POP_TRY - error when no handler to pop", async () => { const vm = new VM({ instructions: [ { op: OpCode.POP_TRY } ], constants: [] }) await expect(vm.run()).rejects.toThrow('POP_TRY: no exception handler to pop') }) test("PUSH_FINALLY - finally executes after successful try", async () => { // Try block completes normally, compiler jumps to finally const vm = new VM({ instructions: [ { op: OpCode.PUSH_TRY, operand: 8 }, // catch at 8 { op: OpCode.PUSH_FINALLY, operand: 9 }, // finally at 9 { op: OpCode.PUSH, operand: 0 }, // push 10 { op: OpCode.STORE, operand: 'x' }, { op: OpCode.POP_TRY }, { op: OpCode.JUMP, operand: 3 }, // compiler jumps to finally (5->6, +3 = 9) // Not executed { op: OpCode.HALT }, { op: OpCode.HALT }, // Catch block (instruction 8) - not executed { op: OpCode.HALT }, // Finally block (instruction 9) { op: OpCode.PUSH, operand: 1 }, // push 100 { op: OpCode.LOAD, operand: 'x' }, // load 10 { op: OpCode.ADD }, // 110 { op: OpCode.HALT } ], constants: [ toValue(10), toValue(100) ] }) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 110 }) }) test("PUSH_FINALLY - finally executes after exception", async () => { // Try block throws, THROW jumps to finally (skipping catch) const vm = new VM({ instructions: [ { op: OpCode.PUSH_TRY, operand: 5 }, // catch at 5 { op: OpCode.PUSH_FINALLY, operand: 7 }, // finally at 7 { op: OpCode.PUSH, operand: 0 }, // push error { op: OpCode.THROW }, // throw (jumps to finally, not catch!) { op: OpCode.HALT }, // not executed // Catch block (instruction 5) - skipped because finally is present { op: OpCode.STORE, operand: 'err' }, { op: OpCode.HALT }, // Finally block (instruction 7) - error is still on stack { op: OpCode.POP }, // discard error { op: OpCode.PUSH, operand: 1 }, // push "finally ran" { op: OpCode.HALT } ], constants: [ toValue('error'), toValue('finally ran') ] }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'finally ran' }) }) test("PUSH_FINALLY - finally without catch", async () => { // Try-finally without catch (compiler generates jump to finally) const vm = new VM({ instructions: [ { op: OpCode.PUSH_TRY, operand: 7 }, // catch at 7 (dummy) { op: OpCode.PUSH_FINALLY, operand: 7 }, // finally at 7 { op: OpCode.PUSH, operand: 0 }, // push 42 { op: OpCode.STORE, operand: 'x' }, { op: OpCode.POP_TRY }, { op: OpCode.JUMP, operand: 1 }, // compiler jumps to finally { op: OpCode.HALT }, // skipped // Finally block (instruction 7) { op: OpCode.LOAD, operand: 'x' }, // load 42 { op: OpCode.PUSH, operand: 1 }, // push 10 { op: OpCode.ADD }, // 52 { op: OpCode.HALT } ], constants: [ toValue(42), toValue(10) ] }) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 52 }) }) test("PUSH_FINALLY - nested try-finally blocks", async () => { // Nested try-finally blocks with compiler-generated jumps const vm = new VM({ instructions: [ { op: OpCode.PUSH_TRY, operand: 11 }, // outer catch at 11 { op: OpCode.PUSH_FINALLY, operand: 14 }, // outer finally at 14 { op: OpCode.PUSH_TRY, operand: 9 }, // inner catch at 9 { op: OpCode.PUSH_FINALLY, operand: 10 }, // inner finally at 10 { op: OpCode.PUSH, operand: 0 }, // push 1 { op: OpCode.POP_TRY }, // inner pop (instruction 5) { op: OpCode.JUMP, operand: 3 }, // jump to inner finally (6->7, +3 = 10) { op: OpCode.HALT }, // skipped { op: OpCode.HALT }, // skipped // Inner catch (instruction 9) - not executed { op: OpCode.HALT }, // Inner finally (instruction 10) { op: OpCode.PUSH, operand: 1 }, // push 10 { op: OpCode.POP_TRY }, // outer pop (instruction 11) { op: OpCode.JUMP, operand: 1 }, // jump to outer finally (12->13, +1 = 14) // Outer catch (instruction 13) - not executed { op: OpCode.HALT }, // Outer finally (instruction 14) { op: OpCode.ADD }, // 1 + 10 = 11 { op: OpCode.HALT } ], constants: [ toValue(1), toValue(10) ] }) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 11 }) }) test("PUSH_FINALLY - error when no handler", async () => { const vm = new VM({ instructions: [ { op: OpCode.PUSH_FINALLY, operand: 5 } ], constants: [] }) await expect(vm.run()).rejects.toThrow('PUSH_FINALLY: no exception handler to modify') })