ReefVM/tests/native.test.ts

290 lines
6.9 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 Value-based function
vm.registerValueFunction('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.registerValueFunction('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.registerValueFunction('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.registerValueFunction('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.registerValueFunction('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.registerValueFunction('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.registerValueFunction('triple', (n) => {
return toValue(toNumber(n) * 3)
})
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 25 })
})
test("Native function wrapping - basic sync function with native types", async () => {
const bytecode = toBytecode(`
PUSH 5
PUSH 10
CALL_NATIVE add
`)
const vm = new VM(bytecode)
// Register with native TypeScript types - auto-wraps!
vm.registerFunction('add', (a: number, b: number) => {
return a + b
})
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 15 })
})
test("Native function wrapping - async function with native types", async () => {
const bytecode = toBytecode(`
PUSH 42
CALL_NATIVE asyncDouble
`)
const vm = new VM(bytecode)
// Async native function
vm.registerFunction('asyncDouble', async (n: number) => {
await new Promise(resolve => setTimeout(resolve, 1))
return n * 2
})
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 84 })
})
test("Native function wrapping - string manipulation", async () => {
const bytecode = toBytecode(`
PUSH "hello"
PUSH "world"
CALL_NATIVE concat
`)
const vm = new VM(bytecode)
// Native string function
vm.registerFunction('concat', (a: string, b: string) => {
return a + ' ' + b
})
const result = await vm.run()
expect(result).toEqual({ type: 'string', value: 'hello world' })
})
test("Native function wrapping - with default parameters", async () => {
const bytecode = toBytecode(`
PUSH "/home/user"
CALL_NATIVE ls
`)
const vm = new VM(bytecode)
// Function with default parameter (like NOSE commands)
vm.registerFunction('ls', (path: string, link = false) => {
return link ? `listing ${path} with links` : `listing ${path}`
})
const result = await vm.run()
expect(result).toEqual({ type: 'string', value: 'listing /home/user' })
})
test("Native function wrapping - returns array", async () => {
const bytecode = toBytecode(`
PUSH 3
CALL_NATIVE makeRange
`)
const vm = new VM(bytecode)
// Return native array - auto-converts to Value array
vm.registerFunction('makeRange', (n: number) => {
return Array.from({ length: n }, (_, i) => i)
})
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("Native function wrapping - returns object (becomes dict)", async () => {
const bytecode = toBytecode(`
PUSH "Alice"
PUSH 30
CALL_NATIVE makeUser
`)
const vm = new VM(bytecode)
// Return plain object - auto-converts to dict
vm.registerFunction('makeUser', (name: string, age: number) => {
return { name, age }
})
const result = await vm.run()
expect(result.type).toBe('dict')
if (result.type === 'dict') {
expect(result.value.get('name')).toEqual(toValue('Alice'))
expect(result.value.get('age')).toEqual(toValue(30))
}
})
test("Native function wrapping - mixed with manual Value functions", async () => {
const bytecode = toBytecode(`
PUSH 5
CALL_NATIVE nativeAdd
CALL_NATIVE manualDouble
`)
const vm = new VM(bytecode)
// Native function (auto-wrapped by registerFunction)
vm.registerFunction('nativeAdd', (n: number) => n + 10)
// Manual Value function (use registerValueFunction)
vm.registerValueFunction('manualDouble', (v) => {
return toValue(toNumber(v) * 2)
})
const result = await vm.run()
expect(result).toEqual({ type: 'number', value: 30 })
})