183 lines
4.9 KiB
TypeScript
183 lines
4.9 KiB
TypeScript
import { test, expect } from "bun:test"
|
|
import { VM } from "#vm"
|
|
import { OpCode } from "#opcode"
|
|
import { toValue, toNumber, toString } from "#value"
|
|
|
|
test("CALL_NATIVE - basic function call", async () => {
|
|
const vm = new VM({
|
|
instructions: [
|
|
{ op: OpCode.PUSH, operand: 0 }, // push 5
|
|
{ op: OpCode.PUSH, operand: 1 }, // push 10
|
|
{ op: OpCode.CALL_NATIVE, operand: 'add' }, // call TypeScript 'add'
|
|
{ op: OpCode.HALT }
|
|
],
|
|
constants: [
|
|
toValue(5),
|
|
toValue(10)
|
|
]
|
|
})
|
|
|
|
// Register a TypeScript 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 vm = new VM({
|
|
instructions: [
|
|
{ op: OpCode.PUSH, operand: 0 }, // push "hello"
|
|
{ op: OpCode.PUSH, operand: 1 }, // push "world"
|
|
{ op: OpCode.CALL_NATIVE, operand: 'concat' }, // call TypeScript 'concat'
|
|
{ op: OpCode.HALT }
|
|
],
|
|
constants: [
|
|
toValue("hello"),
|
|
toValue("world")
|
|
]
|
|
})
|
|
|
|
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 vm = new VM({
|
|
instructions: [
|
|
{ op: OpCode.PUSH, operand: 0 }, // push 42
|
|
{ op: OpCode.CALL_NATIVE, operand: 'asyncDouble' }, // call async TypeScript function
|
|
{ op: OpCode.HALT }
|
|
],
|
|
constants: [
|
|
toValue(42)
|
|
]
|
|
})
|
|
|
|
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 vm = new VM({
|
|
instructions: [
|
|
{ op: OpCode.CALL_NATIVE, operand: 'getAnswer' }, // call with empty stack
|
|
{ op: OpCode.HALT }
|
|
],
|
|
constants: []
|
|
})
|
|
|
|
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 vm = new VM({
|
|
instructions: [
|
|
{ op: OpCode.PUSH, operand: 0 }, // push 2
|
|
{ op: OpCode.PUSH, operand: 1 }, // push 3
|
|
{ op: OpCode.PUSH, operand: 2 }, // push 4
|
|
{ op: OpCode.CALL_NATIVE, operand: 'sum' }, // call TypeScript 'sum'
|
|
{ op: OpCode.HALT }
|
|
],
|
|
constants: [
|
|
toValue(2),
|
|
toValue(3),
|
|
toValue(4)
|
|
]
|
|
})
|
|
|
|
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 vm = new VM({
|
|
instructions: [
|
|
{ op: OpCode.PUSH, operand: 0 }, // push 3
|
|
{ op: OpCode.CALL_NATIVE, operand: 'makeRange' }, // call TypeScript 'makeRange'
|
|
{ op: OpCode.HALT }
|
|
],
|
|
constants: [
|
|
toValue(3)
|
|
]
|
|
})
|
|
|
|
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 vm = new VM({
|
|
instructions: [
|
|
{ op: OpCode.CALL_NATIVE, operand: 'nonexistent' }
|
|
],
|
|
constants: []
|
|
})
|
|
|
|
await expect(vm.run()).rejects.toThrow('CALL_NATIVE: function not found: nonexistent')
|
|
})
|
|
|
|
test("CALL_NATIVE - using result in subsequent operations", async () => {
|
|
const vm = new VM({
|
|
instructions: [
|
|
{ op: OpCode.PUSH, operand: 0 }, // push 5
|
|
{ op: OpCode.CALL_NATIVE, operand: 'triple' }, // call TypeScript 'triple' -> 15
|
|
{ op: OpCode.PUSH, operand: 1 }, // push 10
|
|
{ op: OpCode.ADD }, // 15 + 10 = 25
|
|
{ op: OpCode.HALT }
|
|
],
|
|
constants: [
|
|
toValue(5),
|
|
toValue(10)
|
|
]
|
|
})
|
|
|
|
vm.registerFunction('triple', (n) => {
|
|
return toValue(toNumber(n) * 3)
|
|
})
|
|
|
|
const result = await vm.run()
|
|
expect(result).toEqual({ type: 'number', value: 25 })
|
|
})
|