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 #5 PUSH 42 PUSH 10 ADD POP_TRY HALT 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 #5 PUSH "error occurred" THROW PUSH 999 HALT 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 #10 PUSH_TRY #6 PUSH "inner error" THROW PUSH 999 HALT STORE err POP_TRY LOAD err HALT 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 #8 PUSH_TRY #6 PUSH "error message" THROW HALT THROW HALT 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: 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 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 #8 PUSH_FINALLY #9 PUSH 10 STORE x POP_TRY JUMP #3 HALT HALT HALT 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 #5 PUSH_FINALLY #7 PUSH "error" THROW HALT STORE err HALT 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 #7 PUSH_FINALLY #7 PUSH 42 STORE x POP_TRY JUMP #1 HALT 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 #11 PUSH_FINALLY #14 PUSH_TRY #9 PUSH_FINALLY #10 PUSH 1 POP_TRY JUMP #3 HALT HALT HALT PUSH 10 POP_TRY JUMP #1 HALT ADD HALT ` expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 11 }) }) test("PUSH_FINALLY - error when no handler", async () => { const str = ` PUSH_FINALLY #5 ` await expect(run(toBytecode(str))).rejects.toThrow('PUSH_FINALLY: no exception handler to modify') })