ReefVM/tests/bytecode.test.ts
2025-10-05 21:29:30 -07:00

189 lines
3.8 KiB
TypeScript

import { test, expect } from "bun:test"
import { toBytecode } from "#bytecode"
import { OpCode } from "#opcode"
import { VM } from "#vm"
test("string compilation", () => {
const str = `
PUSH 1
PUSH 5
ADD
`
expect(toBytecode(str)).toEqual({
instructions: [
{ op: OpCode.PUSH, operand: 0 },
{ op: OpCode.PUSH, operand: 1 },
{ op: OpCode.ADD },
],
constants: [
{ type: 'number', value: 1 },
{ type: 'number', value: 5 }
]
})
})
test("MAKE_FUNCTION - basic function", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION () #5
PUSH 0
PUSH 0
CALL
HALT
PUSH 42
RETURN
`)
const vm = new VM(bytecode)
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 42 })
})
test("MAKE_FUNCTION - function with parameters", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (x y) #7
PUSH 10
PUSH 20
PUSH 2
PUSH 0
CALL
HALT
LOAD x
LOAD y
ADD
RETURN
`)
const vm = new VM(bytecode)
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 30 })
})
test("MAKE_FUNCTION - function with default parameters", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (x y=100) #6
PUSH 10
PUSH 1
PUSH 0
CALL
HALT
LOAD x
LOAD y
ADD
RETURN
`)
const vm = new VM(bytecode)
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 110 })
})
test("MAKE_FUNCTION - tail recursive countdown", async () => {
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 vm = new VM(bytecode)
const result = await vm.run()
expect(result).toEqual({ type: 'string', value: 'done' })
})
test("MAKE_FUNCTION - multiple default values", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (a=1 b=2 c=3) #5
PUSH 0
PUSH 0
CALL
HALT
LOAD a
LOAD b
LOAD c
ADD
ADD
RETURN
`)
const vm = new VM(bytecode)
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 6 })
})
test("MAKE_FUNCTION - default with string", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (name="World") #5
PUSH 0
PUSH 0
CALL
HALT
LOAD name
RETURN
`)
const vm = new VM(bytecode)
const result = await vm.run()
expect(result).toEqual({ type: 'string', value: 'World' })
})
test("semicolon comments are ignored", () => {
const bytecode = toBytecode(`
; This is a comment
PUSH 1 ; push one
PUSH 5 ; push five
ADD ; add them
`)
expect(bytecode).toEqual({
instructions: [
{ op: OpCode.PUSH, operand: 0 },
{ op: OpCode.PUSH, operand: 1 },
{ op: OpCode.ADD },
],
constants: [
{ type: 'number', value: 1 },
{ type: 'number', value: 5 }
]
})
})
test("semicolon comments work with functions", async () => {
const bytecode = toBytecode(`
MAKE_FUNCTION (x y) #7 ; function with two params
PUSH 10 ; first arg
PUSH 20 ; second arg
PUSH 2 ; positional count
PUSH 0 ; named count
CALL ; call the function
HALT
; Function body starts here
LOAD x ; load first param
LOAD y ; load second param
ADD ; add them
RETURN ; return result
`)
const vm = new VM(bytecode)
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 30 })
})