153 lines
2.8 KiB
TypeScript
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
|
|
})
|