233 lines
4.7 KiB
TypeScript
233 lines
4.7 KiB
TypeScript
import { test, expect, describe } from "bun:test"
|
|
import { run, VM } from "#index"
|
|
import { toBytecode } from "#bytecode"
|
|
|
|
describe("functions parameter", () => {
|
|
test("pass functions to run()", async () => {
|
|
const bytecode = toBytecode(`
|
|
LOAD add
|
|
PUSH 5
|
|
PUSH 3
|
|
PUSH 2
|
|
PUSH 0
|
|
CALL
|
|
HALT
|
|
`)
|
|
|
|
const result = await run(bytecode, {
|
|
add: (a: number, b: number) => a + b
|
|
})
|
|
|
|
expect(result).toEqual({ type: 'number', value: 8 })
|
|
})
|
|
|
|
test("pass functions to VM constructor", async () => {
|
|
const bytecode = toBytecode(`
|
|
LOAD multiply
|
|
PUSH 10
|
|
PUSH 2
|
|
PUSH 2
|
|
PUSH 0
|
|
CALL
|
|
HALT
|
|
`)
|
|
|
|
const vm = new VM(bytecode, {
|
|
multiply: (a: number, b: number) => a * b
|
|
})
|
|
|
|
const result = await vm.run()
|
|
expect(result).toEqual({ type: 'number', value: 20 })
|
|
})
|
|
|
|
test("pass multiple functions", async () => {
|
|
const bytecode = toBytecode(`
|
|
LOAD add
|
|
PUSH 10
|
|
PUSH 5
|
|
PUSH 2
|
|
PUSH 0
|
|
CALL
|
|
STORE sum
|
|
LOAD multiply
|
|
LOAD sum
|
|
PUSH 3
|
|
PUSH 2
|
|
PUSH 0
|
|
CALL
|
|
HALT
|
|
`)
|
|
|
|
const result = await run(bytecode, {
|
|
add: (a: number, b: number) => a + b,
|
|
multiply: (a: number, b: number) => a * b
|
|
})
|
|
|
|
expect(result).toEqual({ type: 'number', value: 45 })
|
|
})
|
|
|
|
test("auto-wraps native functions", async () => {
|
|
const bytecode = toBytecode(`
|
|
LOAD concat
|
|
PUSH "hello"
|
|
PUSH "world"
|
|
PUSH 2
|
|
PUSH 0
|
|
CALL
|
|
HALT
|
|
`)
|
|
|
|
const result = await run(bytecode, {
|
|
concat: (a: string, b: string) => a + " " + b
|
|
})
|
|
|
|
expect(result).toEqual({ type: 'string', value: 'hello world' })
|
|
})
|
|
|
|
test("works with async functions", async () => {
|
|
const bytecode = toBytecode(`
|
|
LOAD delay
|
|
PUSH 100
|
|
PUSH 1
|
|
PUSH 0
|
|
CALL
|
|
HALT
|
|
`)
|
|
|
|
const result = await run(bytecode, {
|
|
delay: async (n: number) => {
|
|
await new Promise(resolve => setTimeout(resolve, 1))
|
|
return n * 2
|
|
}
|
|
})
|
|
|
|
expect(result).toEqual({ type: 'number', value: 200 })
|
|
})
|
|
|
|
test("can combine with manual registerFunction", async () => {
|
|
const bytecode = toBytecode(`
|
|
LOAD add
|
|
PUSH 5
|
|
PUSH 3
|
|
PUSH 2
|
|
PUSH 0
|
|
CALL
|
|
STORE sum
|
|
LOAD subtract
|
|
LOAD sum
|
|
PUSH 2
|
|
PUSH 2
|
|
PUSH 0
|
|
CALL
|
|
HALT
|
|
`)
|
|
|
|
const vm = new VM(bytecode, {
|
|
add: (a: number, b: number) => a + b
|
|
})
|
|
|
|
// Register another function manually
|
|
vm.registerFunction('subtract', (a: number, b: number) => a - b)
|
|
|
|
const result = await vm.run()
|
|
expect(result).toEqual({ type: 'number', value: 6 })
|
|
})
|
|
|
|
test("no functions parameter (undefined)", async () => {
|
|
const bytecode = toBytecode(`
|
|
PUSH 42
|
|
HALT
|
|
`)
|
|
|
|
const result = await run(bytecode)
|
|
expect(result).toEqual({ type: 'number', value: 42 })
|
|
})
|
|
|
|
test("empty functions object", async () => {
|
|
const bytecode = toBytecode(`
|
|
PUSH 99
|
|
HALT
|
|
`)
|
|
|
|
const result = await run(bytecode, {})
|
|
expect(result).toEqual({ type: 'number', value: 99 })
|
|
})
|
|
|
|
test("function throws error", async () => {
|
|
const bytecode = toBytecode(`
|
|
LOAD divide
|
|
PUSH 0
|
|
PUSH 1
|
|
PUSH 0
|
|
CALL
|
|
HALT
|
|
`)
|
|
|
|
try {
|
|
await run(bytecode, {
|
|
divide: (n: number) => {
|
|
if (n === 0) throw new Error("Cannot divide by zero")
|
|
return 100 / n
|
|
}
|
|
})
|
|
expect(true).toBe(false) // Should not reach here
|
|
} catch (e: any) {
|
|
expect(e.message).toContain("Cannot divide by zero")
|
|
}
|
|
})
|
|
|
|
test("complex workflow with multiple function calls", async () => {
|
|
const bytecode = toBytecode(`
|
|
LOAD add
|
|
PUSH 5
|
|
PUSH 3
|
|
PUSH 2
|
|
PUSH 0
|
|
CALL
|
|
STORE result
|
|
LOAD multiply
|
|
LOAD result
|
|
PUSH 2
|
|
PUSH 2
|
|
PUSH 0
|
|
CALL
|
|
STORE final
|
|
LOAD format
|
|
LOAD final
|
|
PUSH 1
|
|
PUSH 0
|
|
CALL
|
|
HALT
|
|
`)
|
|
|
|
const result = await run(bytecode, {
|
|
add: (a: number, b: number) => a + b,
|
|
multiply: (a: number, b: number) => a * b,
|
|
format: (n: number) => `Result: ${n}`
|
|
})
|
|
|
|
expect(result).toEqual({ type: 'string', value: 'Result: 16' })
|
|
})
|
|
|
|
test("function overriding - later registration wins", async () => {
|
|
const bytecode = toBytecode(`
|
|
LOAD getValue
|
|
PUSH 5
|
|
PUSH 1
|
|
PUSH 0
|
|
CALL
|
|
HALT
|
|
`)
|
|
|
|
const vm = new VM(bytecode, {
|
|
getValue: () => 100
|
|
})
|
|
|
|
// Override with manual registration
|
|
vm.registerFunction('getValue', () => 200)
|
|
|
|
const result = await vm.run()
|
|
expect(result).toEqual({ type: 'number', value: 200 })
|
|
})
|
|
})
|