forked from defunkt/ReefVM
230 lines
6.7 KiB
TypeScript
230 lines
6.7 KiB
TypeScript
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
|
|
})
|