ReefVM/tests/tail-call.test.ts

153 lines
2.8 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) #8
STORE countdown
LOAD countdown
PUSH 5
PUSH 1
PUSH 0
CALL
HALT
LOAD n
PUSH 0
EQ
JUMP_IF_FALSE #2
PUSH "done"
RETURN
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) #9
STORE sum
LOAD sum
PUSH 10
PUSH 0
PUSH 2
PUSH 0
CALL
HALT
LOAD n
PUSH 0
EQ
JUMP_IF_FALSE #2
LOAD acc
RETURN
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) #8
STORE deep
LOAD deep
PUSH 10000
PUSH 1
PUSH 0
CALL
HALT
LOAD n
PUSH 0
LTE
JUMP_IF_FALSE #2
PUSH "success"
RETURN
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) #10
STORE even
MAKE_FUNCTION (n) #23
STORE odd
LOAD even
PUSH 7
PUSH 1
PUSH 0
CALL
HALT
LOAD n
PUSH 0
EQ
JUMP_IF_FALSE #2
PUSH true
RETURN
LOAD odd
LOAD n
PUSH 1
SUB
PUSH 1
PUSH 0
TAIL_CALL
LOAD n
PUSH 0
EQ
JUMP_IF_FALSE #2
PUSH false
RETURN
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
})