import { test, expect } from "bun:test" import { toBytecode } from "#bytecode" import { run } from "#index" test("PUSH_TRY and POP_TRY - no exception thrown", async () => { // Try block that completes successfully const str = ` PUSH_TRY .catch PUSH 42 PUSH 10 ADD POP_TRY HALT .catch: PUSH 999 HALT ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 52 }) }) test("THROW - catch exception with error value", async () => { // Try block that throws an exception const str = ` PUSH_TRY .catch PUSH "error occurred" THROW PUSH 999 HALT .catch: HALT ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'error occurred' }) }) test("THROW - uncaught exception throws JS error", async () => { const str = ` PUSH "something went wrong" THROW ` await expect(run(toBytecode(str))).rejects.toThrow('Uncaught exception: something went wrong') }) test("THROW - exception with nested try blocks", async () => { // Nested try blocks, inner one catches const str = ` PUSH_TRY .outer_catch PUSH_TRY .inner_catch PUSH "inner error" THROW PUSH 999 HALT .inner_catch: STORE err POP_TRY LOAD err HALT .outer_catch: PUSH "outer error" HALT ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'inner error' }) }) test("THROW - exception skips outer handler", async () => { // Nested try blocks, inner doesn't catch, outer does const str = ` PUSH_TRY .outer_catch PUSH_TRY .inner_catch PUSH "error message" THROW HALT .inner_catch: THROW HALT .outer_catch: HALT ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'error message' }) }) test("THROW - exception unwinds call stack", async () => { // Function that throws, caller has try/catch // Note: This test uses manual VM construction because it needs MAKE_FUNCTION const { VM } = await import("#vm") const { OpCode } = await import("#opcode") const { toValue } = await import("#value") const vm = new VM({ instructions: [ // 0: Main code with try block { op: OpCode.PUSH_TRY, operand: 8 }, { op: OpCode.MAKE_FUNCTION, operand: 0 }, { op: OpCode.PUSH, operand: 3 }, // positionalCount = 0 { op: OpCode.PUSH, operand: 3 }, // namedCount = 0 { op: OpCode.CALL }, { op: OpCode.POP_TRY }, { op: OpCode.HALT }, // 7: Not executed { op: OpCode.PUSH, operand: 2 }, // 8: Catch block { op: OpCode.HALT }, // error value on stack // 9: Function body (throws) { op: OpCode.PUSH, operand: 1 }, { op: OpCode.THROW } ], constants: [ { type: 'function_def', params: [], defaults: {}, body: 9, variadic: false, named: false }, toValue('function error'), toValue(999), toValue(0) // constant for 0 ] }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'function error' }) }) test("POP_TRY - error when no handler to pop", async () => { const str = ` POP_TRY ` await expect(run(toBytecode(str))).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 str = ` PUSH_TRY .catch PUSH_FINALLY .finally PUSH 10 STORE x POP_TRY JUMP .finally HALT .catch: HALT .finally: PUSH 100 LOAD x ADD HALT ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 110 }) }) test("PUSH_FINALLY - finally executes after exception", async () => { // Try block throws, THROW jumps to finally (skipping catch) const str = ` PUSH_TRY .catch PUSH_FINALLY .finally PUSH "error" THROW HALT .catch: STORE err HALT .finally: POP PUSH "finally ran" HALT ` expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'finally ran' }) }) test("PUSH_FINALLY - finally without catch", async () => { // Try-finally without catch (compiler generates jump to finally) const str = ` PUSH_TRY .catch PUSH_FINALLY .finally PUSH 42 STORE x POP_TRY JUMP .finally .catch: HALT .finally: LOAD x PUSH 10 ADD HALT ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 52 }) }) test("PUSH_FINALLY - nested try-finally blocks", async () => { // Nested try-finally blocks with compiler-generated jumps const str = ` PUSH_TRY .outer_catch PUSH_FINALLY .outer_finally PUSH_TRY .inner_catch PUSH_FINALLY .inner_finally PUSH 1 POP_TRY JUMP .inner_finally .inner_catch: HALT .inner_finally: PUSH 10 POP_TRY JUMP .outer_finally .outer_catch: HALT .outer_finally: ADD HALT ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 11 }) }) test("PUSH_FINALLY - error when no handler", async () => { const str = ` PUSH_FINALLY .finally .finally: ` await expect(run(toBytecode(str))).rejects.toThrow('PUSH_FINALLY: no exception handler to modify') })