ReefVM/tests/bytecode.test.ts
2025-10-05 22:24:43 -07:00

248 lines
4.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 () .body
PUSH 0
PUSH 0
CALL
HALT
.body:
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) .add
PUSH 10
PUSH 20
PUSH 2
PUSH 0
CALL
HALT
.add:
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) .add
PUSH 10
PUSH 1
PUSH 0
CALL
HALT
.add:
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) .countdown
STORE countdown
LOAD countdown
PUSH 5
PUSH 1
PUSH 0
CALL
HALT
.countdown:
LOAD n
PUSH 0
EQ
JUMP_IF_FALSE .recurse
PUSH "done"
RETURN
.recurse:
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) .sum
PUSH 0
PUSH 0
CALL
HALT
.sum:
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") .greet
PUSH 0
PUSH 0
CALL
HALT
.greet:
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) .add ; 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
.add: ; 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 })
})
test("labels - basic jump", async () => {
const bytecode = toBytecode(`
JUMP .end
PUSH 999
.end:
PUSH 42
`)
const vm = new VM(bytecode)
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 42 })
})
test("labels - conditional jump", async () => {
const bytecode = toBytecode(`
PUSH 1
JUMP_IF_FALSE .else
PUSH 10
JUMP .end
.else:
PUSH 20
.end:
`)
const vm = new VM(bytecode)
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 10 })
})
test("labels - loop", async () => {
const bytecode = toBytecode(`
PUSH 0
STORE i
.loop:
LOAD i
PUSH 3
LT
JUMP_IF_FALSE .end
LOAD i
PUSH 1
ADD
STORE i
JUMP .loop
.end:
LOAD i
`)
const vm = new VM(bytecode)
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 3 })
})