import { test, expect } from "bun:test" import { VM } from "#vm" import { OpCode } from "#opcode" import { toValue } from "#value" test("TAIL_CALL - basic tail recursive countdown", async () => { // Tail recursive function that counts down to 0 // function countdown(n) { // if (n === 0) return "done" // return countdown(n - 1) // tail call // } const vm = new VM({ instructions: [ // 0: Setup - create function and call it { op: OpCode.MAKE_FUNCTION, operand: 0 }, { op: OpCode.STORE, operand: 'countdown' }, { op: OpCode.LOAD, operand: 'countdown' }, { op: OpCode.PUSH, operand: 1 }, // start with 5 { op: OpCode.CALL, operand: 1 }, { op: OpCode.HALT }, // 6: Function body { op: OpCode.LOAD, operand: 'n' }, { op: OpCode.PUSH, operand: 2 }, // 0 { op: OpCode.EQ }, { op: OpCode.JUMP_IF_FALSE, operand: 2 }, // if n !== 0, jump to recursive case { op: OpCode.PUSH, operand: 3 }, // return "done" { op: OpCode.RETURN }, // 12: Recursive case (tail call) { op: OpCode.LOAD, operand: 'countdown' }, { op: OpCode.LOAD, operand: 'n' }, { op: OpCode.PUSH, operand: 4 }, // 1 { op: OpCode.SUB }, { op: OpCode.TAIL_CALL, operand: 1 } // tail call with n-1 ], constants: [ { type: 'function_def', params: ['n'], defaults: {}, body: 6, variadic: false, kwargs: false }, toValue(5), toValue(0), toValue('done'), toValue(1) ] }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'done' }) }) test("TAIL_CALL - tail recursive sum with accumulator", async () => { // function sum(n, acc = 0) { // if (n === 0) return acc // return sum(n - 1, acc + n) // tail call // } const vm = new VM({ instructions: [ // 0: Setup { op: OpCode.MAKE_FUNCTION, operand: 0 }, { op: OpCode.STORE, operand: 'sum' }, { op: OpCode.LOAD, operand: 'sum' }, { op: OpCode.PUSH, operand: 1 }, // n = 10 { op: OpCode.PUSH, operand: 2 }, // acc = 0 { op: OpCode.CALL, operand: 2 }, { op: OpCode.HALT }, // 7: Function body { op: OpCode.LOAD, operand: 'n' }, { op: OpCode.PUSH, operand: 2 }, // 0 { op: OpCode.EQ }, { op: OpCode.JUMP_IF_FALSE, operand: 2 }, { op: OpCode.LOAD, operand: 'acc' }, { op: OpCode.RETURN }, // 13: Recursive case { op: OpCode.LOAD, operand: 'sum' }, { op: OpCode.LOAD, operand: 'n' }, { op: OpCode.PUSH, operand: 3 }, // 1 { op: OpCode.SUB }, { op: OpCode.LOAD, operand: 'acc' }, { op: OpCode.LOAD, operand: 'n' }, { op: OpCode.ADD }, { op: OpCode.TAIL_CALL, operand: 2 } ], constants: [ { type: 'function_def', params: ['n', 'acc'], defaults: {}, body: 7, variadic: false, kwargs: false }, toValue(10), toValue(0), toValue(1) ] }) const result = await vm.run() expect(result).toEqual({ type: 'number', value: 55 }) // sum of 1..10 }) test("TAIL_CALL - doesn't overflow stack with deep recursion", async () => { // This would overflow the stack with regular CALL // but should work fine with TAIL_CALL const vm = new VM({ instructions: [ // 0: Setup { op: OpCode.MAKE_FUNCTION, operand: 0 }, { op: OpCode.STORE, operand: 'deep' }, { op: OpCode.LOAD, operand: 'deep' }, { op: OpCode.PUSH, operand: 1 }, // 10000 iterations { op: OpCode.CALL, operand: 1 }, { op: OpCode.HALT }, // 6: Function body { op: OpCode.LOAD, operand: 'n' }, { op: OpCode.PUSH, operand: 2 }, // 0 { op: OpCode.LTE }, { op: OpCode.JUMP_IF_FALSE, operand: 2 }, { op: OpCode.PUSH, operand: 3 }, // "success" { op: OpCode.RETURN }, // 12: Tail recursive case { op: OpCode.LOAD, operand: 'deep' }, { op: OpCode.LOAD, operand: 'n' }, { op: OpCode.PUSH, operand: 4 }, // 1 { op: OpCode.SUB }, { op: OpCode.TAIL_CALL, operand: 1 } ], constants: [ { type: 'function_def', params: ['n'], defaults: {}, body: 6, variadic: false, kwargs: false }, toValue(10000), // This would overflow with regular recursion toValue(0), toValue('success'), toValue(1) ] }) const result = await vm.run() expect(result).toEqual({ type: 'string', value: 'success' }) }) test("TAIL_CALL - tail call to different function", async () => { // TAIL_CALL can call a different function (mutual recursion) // function even(n) { return n === 0 ? true : odd(n - 1) } // function odd(n) { return n === 0 ? false : even(n - 1) } const vm = new VM({ instructions: [ // 0: Setup both functions { op: OpCode.MAKE_FUNCTION, operand: 0 }, // even { op: OpCode.STORE, operand: 'even' }, { op: OpCode.MAKE_FUNCTION, operand: 1 }, // odd { op: OpCode.STORE, operand: 'odd' }, { op: OpCode.LOAD, operand: 'even' }, { op: OpCode.PUSH, operand: 2 }, // test with 7 { op: OpCode.CALL, operand: 1 }, { op: OpCode.HALT }, // 8: even function body { op: OpCode.LOAD, operand: 'n' }, { op: OpCode.PUSH, operand: 3 }, // 0 { op: OpCode.EQ }, { op: OpCode.JUMP_IF_FALSE, operand: 2 }, { op: OpCode.PUSH, operand: 4 }, // true { op: OpCode.RETURN }, // Tail call to odd { op: OpCode.LOAD, operand: 'odd' }, { op: OpCode.LOAD, operand: 'n' }, { op: OpCode.PUSH, operand: 5 }, // 1 { op: OpCode.SUB }, { op: OpCode.TAIL_CALL, operand: 1 }, // 19: odd function body { op: OpCode.LOAD, operand: 'n' }, { op: OpCode.PUSH, operand: 3 }, // 0 { op: OpCode.EQ }, { op: OpCode.JUMP_IF_FALSE, operand: 2 }, { op: OpCode.PUSH, operand: 6 }, // false { op: OpCode.RETURN }, // Tail call to even { op: OpCode.LOAD, operand: 'even' }, { op: OpCode.LOAD, operand: 'n' }, { op: OpCode.PUSH, operand: 5 }, // 1 { op: OpCode.SUB }, { op: OpCode.TAIL_CALL, operand: 1 } ], constants: [ { type: 'function_def', params: ['n'], defaults: {}, body: 8, // even body variadic: false, kwargs: false }, { type: 'function_def', params: ['n'], defaults: {}, body: 19, // odd body variadic: false, kwargs: false }, toValue(7), toValue(0), toValue(true), toValue(1), toValue(false) ] }) const result = await vm.run() expect(result).toEqual({ type: 'boolean', value: false }) // 7 is odd })