update tests

This commit is contained in:
Chris Wanstrath 2025-10-05 19:56:56 -07:00
parent 8198c555ac
commit 4b3c9e8bfc
3 changed files with 146 additions and 292 deletions

View File

@ -17,12 +17,15 @@ export type Constant =
//
// Parse bytecode from human-readable string format.
// Operand types are determined by prefix:
// Operand types are determined by prefix/literal:
// #42 -> immediate number (e.g., JUMP #5, MAKE_ARRAY #3)
// name -> variable/function name (e.g., LOAD x, CALL_NATIVE add)
// 42 -> number constant (e.g., PUSH 42)
// "str" -> string constant (e.g., PUSH "hello")
// 'str' -> string constant (e.g., PUSH 'hello')
// true -> boolean constant (e.g., PUSH true)
// false -> boolean constant (e.g., PUSH false)
// null -> null constant (e.g., PUSH null)
//
// Function definitions:
// MAKE_FUNCTION (x y) #7 -> basic function
@ -156,6 +159,16 @@ export function toBytecode(str: string): Bytecode /* throws */ {
bytecode.constants.push(toValue(parseFloat(operand)))
operandValue = bytecode.constants.length - 1
} else if (operand === 'true' || operand === 'false') {
// boolean
bytecode.constants.push(toValue(operand === 'true'))
operandValue = bytecode.constants.length - 1
} else if (operand === 'null') {
// null
bytecode.constants.push(toValue(null))
operandValue = bytecode.constants.length - 1
} else if (/^[a-zA-Z_].*$/.test(operand)) {
// variable
operandValue = operand

View File

@ -1,25 +1,13 @@
import { test, expect } from "bun:test"
import { toBytecode } from "#bytecode"
import { VM } from "#vm"
import { OpCode } from "#opcode"
test("MAKE_FUNCTION - creates function with captured scope", async () => {
const vm = new VM({
instructions: [
{ op: OpCode.MAKE_FUNCTION, operand: 0 }
],
constants: [
{
type: 'function_def',
params: [],
defaults: {},
body: 999,
variadic: false,
kwargs: false
}
]
})
const bytecode = toBytecode(`
MAKE_FUNCTION () #999
`)
const result = await vm.run()
const result = await new VM(bytecode).run()
expect(result.type).toBe('function')
if (result.type === 'function') {
expect(result.body).toBe(999)
@ -28,97 +16,45 @@ test("MAKE_FUNCTION - creates function with captured scope", async () => {
})
test("CALL and RETURN - basic function call", async () => {
// Function that returns 42
const vm = new VM({
instructions: [
// 0: Create and call the function
{ op: OpCode.MAKE_FUNCTION, operand: 0 },
{ op: OpCode.CALL, operand: 0 }, // call with 0 args
{ op: OpCode.HALT },
const bytecode = toBytecode(`
MAKE_FUNCTION () #3
CALL #0
HALT
PUSH 42
RETURN
`)
// 3: Function body (starts here, address = 3)
{ op: OpCode.PUSH, operand: 1 }, // push 42
{ op: OpCode.RETURN }
],
constants: [
{
type: 'function_def',
params: [],
defaults: {},
body: 3, // function body starts at instruction 3
variadic: false,
kwargs: false
},
{ type: 'number', value: 42 }
]
})
const result = await vm.run()
const result = await new VM(bytecode).run()
expect(result).toEqual({ type: 'number', value: 42 })
})
test("CALL and RETURN - function with one parameter", async () => {
// Function that returns its parameter
const vm = new VM({
instructions: [
// 0: Push argument and call function
{ op: OpCode.MAKE_FUNCTION, operand: 0 },
{ op: OpCode.PUSH, operand: 1 }, // argument: 100
{ op: OpCode.CALL, operand: 1 }, // call with 1 arg
{ op: OpCode.HALT },
const bytecode = toBytecode(`
MAKE_FUNCTION (x) #4
PUSH 100
CALL #1
HALT
LOAD x
RETURN
`)
// 4: Function body
{ op: OpCode.LOAD, operand: 'x' }, // load parameter x
{ op: OpCode.RETURN }
],
constants: [
{
type: 'function_def',
params: ['x'],
defaults: {},
body: 4,
variadic: false,
kwargs: false
},
{ type: 'number', value: 100 }
]
})
const result = await vm.run()
const result = await new VM(bytecode).run()
expect(result).toEqual({ type: 'number', value: 100 })
})
test("CALL and RETURN - function with two parameters", async () => {
// Function that adds two parameters
const vm = new VM({
instructions: [
// 0: Push arguments and call function
{ op: OpCode.MAKE_FUNCTION, operand: 0 },
{ op: OpCode.PUSH, operand: 1 }, // arg1: 10
{ op: OpCode.PUSH, operand: 2 }, // arg2: 20
{ op: OpCode.CALL, operand: 2 }, // call with 2 args
{ op: OpCode.HALT },
const bytecode = toBytecode(`
MAKE_FUNCTION (a b) #5
PUSH 10
PUSH 20
CALL #2
HALT
LOAD a
LOAD b
ADD
RETURN
`)
// 5: Function body
{ op: OpCode.LOAD, operand: 'a' },
{ op: OpCode.LOAD, operand: 'b' },
{ op: OpCode.ADD },
{ op: OpCode.RETURN }
],
constants: [
{
type: 'function_def',
params: ['a', 'b'],
defaults: {},
body: 5,
variadic: false,
kwargs: false
},
{ type: 'number', value: 10 },
{ type: 'number', value: 20 }
]
})
const result = await vm.run()
const result = await new VM(bytecode).run()
expect(result).toEqual({ type: 'number', value: 30 })
})

View File

@ -1,7 +1,6 @@
import { test, expect } from "bun:test"
import { toBytecode } from "#bytecode"
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
@ -9,48 +8,27 @@ test("TAIL_CALL - basic tail recursive countdown", async () => {
// 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 },
const bytecode = toBytecode(`
MAKE_FUNCTION (n) #6
STORE countdown
LOAD countdown
PUSH 5
CALL #1
HALT
LOAD n
PUSH 0
EQ
JUMP_IF_FALSE #2
PUSH "done"
RETURN
LOAD countdown
LOAD n
PUSH 1
SUB
TAIL_CALL #1
`)
// 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()
const result = await new VM(bytecode).run()
expect(result).toEqual({ type: 'string', value: 'done' })
})
@ -59,99 +37,58 @@ test("TAIL_CALL - tail recursive sum with accumulator", async () => {
// 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 },
const bytecode = toBytecode(`
MAKE_FUNCTION (n acc) #7
STORE sum
LOAD sum
PUSH 10
PUSH 0
CALL #2
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
TAIL_CALL #2
`)
// 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()
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 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 },
const bytecode = toBytecode(`
MAKE_FUNCTION (n) #6
STORE deep
LOAD deep
PUSH 10000
CALL #1
HALT
LOAD n
PUSH 0
LTE
JUMP_IF_FALSE #2
PUSH "success"
RETURN
LOAD deep
LOAD n
PUSH 1
SUB
TAIL_CALL #1
`)
// 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()
const result = await new VM(bytecode).run()
expect(result).toEqual({ type: 'string', value: 'success' })
})
@ -159,71 +96,39 @@ 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 },
const bytecode = toBytecode(`
MAKE_FUNCTION (n) #8
STORE even
MAKE_FUNCTION (n) #19
STORE odd
LOAD even
PUSH 7
CALL #1
HALT
LOAD n
PUSH 0
EQ
JUMP_IF_FALSE #2
PUSH true
RETURN
LOAD odd
LOAD n
PUSH 1
SUB
TAIL_CALL #1
LOAD n
PUSH 0
EQ
JUMP_IF_FALSE #2
PUSH false
RETURN
LOAD even
LOAD n
PUSH 1
SUB
TAIL_CALL #1
`)
// 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()
const result = await new VM(bytecode).run()
expect(result).toEqual({ type: 'boolean', value: false }) // 7 is odd
})