forked from defunkt/ReefVM
151 lines
3.4 KiB
TypeScript
151 lines
3.4 KiB
TypeScript
import { test, expect } from "bun:test"
|
|
import { VM } from "#vm"
|
|
import { toBytecode } from "#bytecode"
|
|
import { toValue, toNumber, toString } from "#value"
|
|
|
|
test("CALL_NATIVE - basic function call", async () => {
|
|
const bytecode = toBytecode(`
|
|
PUSH 5
|
|
PUSH 10
|
|
CALL_NATIVE add
|
|
`)
|
|
|
|
const vm = new VM(bytecode)
|
|
|
|
// Register a native function
|
|
vm.registerFunction('add', (a, b) => {
|
|
return toValue(toNumber(a) + toNumber(b))
|
|
})
|
|
|
|
const result = await vm.run()
|
|
expect(result).toEqual({ type: 'number', value: 15 })
|
|
})
|
|
|
|
test("CALL_NATIVE - function with string manipulation", async () => {
|
|
const bytecode = toBytecode(`
|
|
PUSH "hello"
|
|
PUSH "world"
|
|
CALL_NATIVE concat
|
|
`)
|
|
|
|
const vm = new VM(bytecode)
|
|
|
|
vm.registerFunction('concat', (a, b) => {
|
|
const aStr = a.type === 'string' ? a.value : toString(a)
|
|
const bStr = b.type === 'string' ? b.value : toString(b)
|
|
return toValue(aStr + ' ' + bStr)
|
|
})
|
|
|
|
const result = await vm.run()
|
|
expect(result).toEqual({ type: 'string', value: 'hello world' })
|
|
})
|
|
|
|
test("CALL_NATIVE - async function", async () => {
|
|
const bytecode = toBytecode(`
|
|
PUSH 42
|
|
CALL_NATIVE asyncDouble
|
|
`)
|
|
|
|
const vm = new VM(bytecode)
|
|
|
|
vm.registerFunction('asyncDouble', async (a) => {
|
|
// Simulate async operation
|
|
await new Promise(resolve => setTimeout(resolve, 1))
|
|
return toValue(toNumber(a) * 2)
|
|
})
|
|
|
|
const result = await vm.run()
|
|
expect(result).toEqual({ type: 'number', value: 84 })
|
|
})
|
|
|
|
test("CALL_NATIVE - function with no arguments", async () => {
|
|
const bytecode = toBytecode(`
|
|
CALL_NATIVE getAnswer
|
|
`)
|
|
|
|
const vm = new VM(bytecode)
|
|
|
|
vm.registerFunction('getAnswer', () => {
|
|
return toValue(42)
|
|
})
|
|
|
|
const result = await vm.run()
|
|
expect(result).toEqual({ type: 'number', value: 42 })
|
|
})
|
|
|
|
test("CALL_NATIVE - function with multiple arguments", async () => {
|
|
const bytecode = toBytecode(`
|
|
PUSH 2
|
|
PUSH 3
|
|
PUSH 4
|
|
CALL_NATIVE sum
|
|
`)
|
|
|
|
const vm = new VM(bytecode)
|
|
|
|
vm.registerFunction('sum', (...args) => {
|
|
const total = args.reduce((acc, val) => acc + toNumber(val), 0)
|
|
return toValue(total)
|
|
})
|
|
|
|
const result = await vm.run()
|
|
expect(result).toEqual({ type: 'number', value: 9 })
|
|
})
|
|
|
|
test("CALL_NATIVE - function returns array", async () => {
|
|
const bytecode = toBytecode(`
|
|
PUSH 3
|
|
CALL_NATIVE makeRange
|
|
`)
|
|
|
|
const vm = new VM(bytecode)
|
|
|
|
vm.registerFunction('makeRange', (n) => {
|
|
const count = toNumber(n)
|
|
const arr = []
|
|
for (let i = 0; i < count; i++) {
|
|
arr.push(toValue(i))
|
|
}
|
|
return { type: 'array', value: arr }
|
|
})
|
|
|
|
const result = await vm.run()
|
|
expect(result.type).toBe('array')
|
|
if (result.type === 'array') {
|
|
expect(result.value.length).toBe(3)
|
|
expect(result.value).toEqual([
|
|
toValue(0),
|
|
toValue(1),
|
|
toValue(2)
|
|
])
|
|
}
|
|
})
|
|
|
|
test("CALL_NATIVE - function not found", async () => {
|
|
const bytecode = toBytecode(`
|
|
CALL_NATIVE nonexistent
|
|
`)
|
|
|
|
const vm = new VM(bytecode)
|
|
|
|
await expect(vm.run()).rejects.toThrow('CALL_NATIVE: function not found: nonexistent')
|
|
})
|
|
|
|
test("CALL_NATIVE - using result in subsequent operations", async () => {
|
|
const bytecode = toBytecode(`
|
|
PUSH 5
|
|
CALL_NATIVE triple
|
|
PUSH 10
|
|
ADD
|
|
`)
|
|
|
|
const vm = new VM(bytecode)
|
|
|
|
vm.registerFunction('triple', (n) => {
|
|
return toValue(toNumber(n) * 3)
|
|
})
|
|
|
|
const result = await vm.run()
|
|
expect(result).toEqual({ type: 'number', value: 25 })
|
|
})
|