ReefVM/tests/tail-call.test.ts
2025-10-05 22:24:43 -07:00

163 lines
3.0 KiB
TypeScript

import { test, expect } from "bun:test"
import { toBytecode } from "#bytecode"
import { VM } from "#vm"
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 bytecode = toBytecode(`
MAKE_FUNCTION (n) .countdown
STORE countdown
LOAD countdown
PUSH 5
PUSH 1
PUSH 0
CALL
HALT
.countdown:
LOAD n
PUSH 0
EQ
JUMP_IF_FALSE .recurse
PUSH "done"
RETURN
.recurse:
LOAD countdown
LOAD n
PUSH 1
SUB
PUSH 1
PUSH 0
TAIL_CALL
`)
const result = await new VM(bytecode).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 bytecode = toBytecode(`
MAKE_FUNCTION (n acc) .sum
STORE sum
LOAD sum
PUSH 10
PUSH 0
PUSH 2
PUSH 0
CALL
HALT
.sum:
LOAD n
PUSH 0
EQ
JUMP_IF_FALSE .recurse
LOAD acc
RETURN
.recurse:
LOAD sum
LOAD n
PUSH 1
SUB
LOAD acc
LOAD n
ADD
PUSH 2
PUSH 0
TAIL_CALL
`)
const result = await new VM(bytecode).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 bytecode = toBytecode(`
MAKE_FUNCTION (n) .deep
STORE deep
LOAD deep
PUSH 10000
PUSH 1
PUSH 0
CALL
HALT
.deep:
LOAD n
PUSH 0
LTE
JUMP_IF_FALSE .recurse
PUSH "success"
RETURN
.recurse:
LOAD deep
LOAD n
PUSH 1
SUB
PUSH 1
PUSH 0
TAIL_CALL
`)
const result = await new VM(bytecode).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 bytecode = toBytecode(`
MAKE_FUNCTION (n) .even
STORE even
MAKE_FUNCTION (n) .odd
STORE odd
LOAD even
PUSH 7
PUSH 1
PUSH 0
CALL
HALT
.even:
LOAD n
PUSH 0
EQ
JUMP_IF_FALSE .even_recurse
PUSH true
RETURN
.even_recurse:
LOAD odd
LOAD n
PUSH 1
SUB
PUSH 1
PUSH 0
TAIL_CALL
.odd:
LOAD n
PUSH 0
EQ
JUMP_IF_FALSE .odd_recurse
PUSH false
RETURN
.odd_recurse:
LOAD even
LOAD n
PUSH 1
SUB
PUSH 1
PUSH 0
TAIL_CALL
`)
const result = await new VM(bytecode).run()
expect(result).toEqual({ type: 'boolean', value: false }) // 7 is odd
})