import { test, expect, describe } from "bun:test" import { run, VM } from "#index" import { toBytecode } from "#bytecode" describe("functions parameter", () => { test("pass functions to run()", async () => { const bytecode = toBytecode(` LOAD add PUSH 5 PUSH 3 PUSH 2 PUSH 0 CALL HALT `) const result = await run(bytecode, { add: (a: number, b: number) => a + b }) expect(result).toEqual({ type: 'number', value: 8 }) }) test("pass functions to VM constructor", async () => { const bytecode = toBytecode(` LOAD multiply PUSH 10 PUSH 2 PUSH 2 PUSH 0 CALL HALT `) const vm = new VM(bytecode, { multiply: (a: number, b: number) => a * b }) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 20 }) }) test("pass multiple functions", async () => { const bytecode = toBytecode(` LOAD add PUSH 10 PUSH 5 PUSH 2 PUSH 0 CALL STORE sum LOAD multiply LOAD sum PUSH 3 PUSH 2 PUSH 0 CALL HALT `) const result = await run(bytecode, { add: (a: number, b: number) => a + b, multiply: (a: number, b: number) => a * b }) expect(result).toEqual({ type: 'number', value: 45 }) }) test("auto-wraps native functions", async () => { const bytecode = toBytecode(` LOAD concat PUSH "hello" PUSH "world" PUSH 2 PUSH 0 CALL HALT `) const result = await run(bytecode, { concat: (a: string, b: string) => a + " " + b }) expect(result).toEqual({ type: 'string', value: 'hello world' }) }) test("works with async functions", async () => { const bytecode = toBytecode(` LOAD delay PUSH 100 PUSH 1 PUSH 0 CALL HALT `) const result = await run(bytecode, { delay: async (n: number) => { await new Promise(resolve => setTimeout(resolve, 1)) return n * 2 } }) expect(result).toEqual({ type: 'number', value: 200 }) }) test("can combine with manual registerFunction", async () => { const bytecode = toBytecode(` LOAD add PUSH 5 PUSH 3 PUSH 2 PUSH 0 CALL STORE sum LOAD subtract LOAD sum PUSH 2 PUSH 2 PUSH 0 CALL HALT `) const vm = new VM(bytecode, { add: (a: number, b: number) => a + b }) // Register another function manually vm.registerFunction('subtract', (a: number, b: number) => a - b) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 6 }) }) test("no functions parameter (undefined)", async () => { const bytecode = toBytecode(` PUSH 42 HALT `) const result = await run(bytecode) expect(result).toEqual({ type: 'number', value: 42 }) }) test("empty functions object", async () => { const bytecode = toBytecode(` PUSH 99 HALT `) const result = await run(bytecode, {}) expect(result).toEqual({ type: 'number', value: 99 }) }) test("function throws error", async () => { const bytecode = toBytecode(` LOAD divide PUSH 0 PUSH 1 PUSH 0 CALL HALT `) try { await run(bytecode, { divide: (n: number) => { if (n === 0) throw new Error("Cannot divide by zero") return 100 / n } }) expect(true).toBe(false) // Should not reach here } catch (e: any) { expect(e.message).toContain("Cannot divide by zero") } }) test("complex workflow with multiple function calls", async () => { const bytecode = toBytecode(` LOAD add PUSH 5 PUSH 3 PUSH 2 PUSH 0 CALL STORE result LOAD multiply LOAD result PUSH 2 PUSH 2 PUSH 0 CALL STORE final LOAD format LOAD final PUSH 1 PUSH 0 CALL HALT `) const result = await run(bytecode, { add: (a: number, b: number) => a + b, multiply: (a: number, b: number) => a * b, format: (n: number) => `Result: ${n}` }) expect(result).toEqual({ type: 'string', value: 'Result: 16' }) }) test("function overriding - later registration wins", async () => { const bytecode = toBytecode(` LOAD getValue PUSH 5 PUSH 1 PUSH 0 CALL HALT `) const vm = new VM(bytecode, { getValue: () => 100 }) // Override with manual registration vm.registerFunction('getValue', () => 200) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 200 }) }) })