forked from defunkt/ReefVM
convert tests
This commit is contained in:
parent
2a280a10b3
commit
66c46677e6
|
|
@ -1,130 +1,84 @@
|
||||||
import { test, expect } from "bun:test"
|
import { test, expect } from "bun:test"
|
||||||
import { VM } from "#vm"
|
import { toBytecode } from "#bytecode"
|
||||||
import { OpCode } from "#opcode"
|
import { run } from "#index"
|
||||||
import { toValue } from "#value"
|
|
||||||
|
|
||||||
test("PUSH_TRY and POP_TRY - no exception thrown", async () => {
|
test("PUSH_TRY and POP_TRY - no exception thrown", async () => {
|
||||||
// Try block that completes successfully
|
// Try block that completes successfully
|
||||||
const vm = new VM({
|
const str = `
|
||||||
instructions: [
|
PUSH_TRY #5
|
||||||
{ op: OpCode.PUSH_TRY, operand: 5 }, // catch at instruction 5
|
PUSH 42
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push 42
|
PUSH 10
|
||||||
{ op: OpCode.PUSH, operand: 1 }, // push 10
|
ADD
|
||||||
{ op: OpCode.ADD },
|
POP_TRY
|
||||||
{ op: OpCode.POP_TRY }, // pop handler (no exception)
|
HALT
|
||||||
{ op: OpCode.HALT },
|
PUSH 999
|
||||||
|
HALT
|
||||||
// Catch block (not executed)
|
`
|
||||||
{ op: OpCode.PUSH, operand: 2 }, // push 999
|
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 52 })
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
|
||||||
constants: [
|
|
||||||
toValue(42),
|
|
||||||
toValue(10),
|
|
||||||
toValue(999)
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await vm.run()
|
|
||||||
expect(result).toEqual({ type: 'number', value: 52 })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("THROW - catch exception with error value", async () => {
|
test("THROW - catch exception with error value", async () => {
|
||||||
// Try block that throws an exception
|
// Try block that throws an exception
|
||||||
const vm = new VM({
|
const str = `
|
||||||
instructions: [
|
PUSH_TRY #5
|
||||||
{ op: OpCode.PUSH_TRY, operand: 5 }, // catch at instruction 5
|
PUSH "error occurred"
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push error message
|
THROW
|
||||||
{ op: OpCode.THROW }, // throw exception
|
PUSH 999
|
||||||
{ op: OpCode.PUSH, operand: 1 }, // not executed
|
HALT
|
||||||
{ op: OpCode.HALT },
|
HALT
|
||||||
|
`
|
||||||
// Catch block (instruction 5)
|
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'error occurred' })
|
||||||
{ op: OpCode.HALT } // error value is on stack
|
|
||||||
],
|
|
||||||
constants: [
|
|
||||||
toValue('error occurred'),
|
|
||||||
toValue(999)
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await vm.run()
|
|
||||||
expect(result).toEqual({ type: 'string', value: 'error occurred' })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("THROW - uncaught exception throws JS error", async () => {
|
test("THROW - uncaught exception throws JS error", async () => {
|
||||||
const vm = new VM({
|
const str = `
|
||||||
instructions: [
|
PUSH "something went wrong"
|
||||||
{ op: OpCode.PUSH, operand: 0 },
|
THROW
|
||||||
{ op: OpCode.THROW }
|
`
|
||||||
],
|
await expect(run(toBytecode(str))).rejects.toThrow('Uncaught exception: something went wrong')
|
||||||
constants: [
|
|
||||||
toValue('something went wrong')
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(vm.run()).rejects.toThrow('Uncaught exception: something went wrong')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("THROW - exception with nested try blocks", async () => {
|
test("THROW - exception with nested try blocks", async () => {
|
||||||
// Nested try blocks, inner one catches
|
// Nested try blocks, inner one catches
|
||||||
const vm = new VM({
|
const str = `
|
||||||
instructions: [
|
PUSH_TRY #10
|
||||||
{ op: OpCode.PUSH_TRY, operand: 10 }, // outer catch at 10
|
PUSH_TRY #6
|
||||||
{ op: OpCode.PUSH_TRY, operand: 6 }, // inner catch at 6
|
PUSH "inner error"
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push 'inner error'
|
THROW
|
||||||
{ op: OpCode.THROW }, // throw
|
PUSH 999
|
||||||
{ op: OpCode.PUSH, operand: 1 }, // not executed
|
HALT
|
||||||
{ op: OpCode.HALT },
|
STORE err
|
||||||
|
POP_TRY
|
||||||
// Inner catch block (instruction 6)
|
LOAD err
|
||||||
{ op: OpCode.STORE, operand: 'err' },
|
HALT
|
||||||
{ op: OpCode.POP_TRY }, // pop outer handler
|
PUSH "outer error"
|
||||||
{ op: OpCode.LOAD, operand: 'err' },
|
HALT
|
||||||
{ op: OpCode.HALT },
|
`
|
||||||
|
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'inner error' })
|
||||||
// Outer catch block (instruction 10, not executed)
|
|
||||||
{ op: OpCode.PUSH, operand: 2 },
|
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
|
||||||
constants: [
|
|
||||||
toValue('inner error'),
|
|
||||||
toValue(999),
|
|
||||||
toValue('outer error')
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await vm.run()
|
|
||||||
expect(result).toEqual({ type: 'string', value: 'inner error' })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("THROW - exception skips outer handler", async () => {
|
test("THROW - exception skips outer handler", async () => {
|
||||||
// Nested try blocks, inner doesn't catch, outer does
|
// Nested try blocks, inner doesn't catch, outer does
|
||||||
const vm = new VM({
|
const str = `
|
||||||
instructions: [
|
PUSH_TRY #8
|
||||||
{ op: OpCode.PUSH_TRY, operand: 8 }, // outer catch at 8
|
PUSH_TRY #6
|
||||||
{ op: OpCode.PUSH_TRY, operand: 6 }, // inner catch at 6
|
PUSH "error message"
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push error
|
THROW
|
||||||
{ op: OpCode.THROW }, // throw
|
HALT
|
||||||
{ op: OpCode.HALT },
|
THROW
|
||||||
|
HALT
|
||||||
// Inner catch block (instruction 6) - re-throws
|
HALT
|
||||||
{ op: OpCode.THROW }, // re-throw the error
|
`
|
||||||
|
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'error message' })
|
||||||
// Outer catch block (instruction 8)
|
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
|
||||||
constants: [
|
|
||||||
toValue('error message')
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await vm.run()
|
|
||||||
expect(result).toEqual({ type: 'string', value: 'error message' })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("THROW - exception unwinds call stack", async () => {
|
test("THROW - exception unwinds call stack", async () => {
|
||||||
// Function that throws, caller has try/catch
|
// Function that throws, caller has try/catch
|
||||||
|
// Note: This test uses manual VM construction because it needs MAKE_FUNCTION
|
||||||
|
const { VM } = await import("#vm")
|
||||||
|
const { OpCode } = await import("#opcode")
|
||||||
|
const { toValue } = await import("#value")
|
||||||
|
|
||||||
const vm = new VM({
|
const vm = new VM({
|
||||||
instructions: [
|
instructions: [
|
||||||
// 0: Main code with try block
|
// 0: Main code with try block
|
||||||
|
|
@ -163,153 +117,93 @@ test("THROW - exception unwinds call stack", async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("POP_TRY - error when no handler to pop", async () => {
|
test("POP_TRY - error when no handler to pop", async () => {
|
||||||
const vm = new VM({
|
const str = `
|
||||||
instructions: [
|
POP_TRY
|
||||||
{ op: OpCode.POP_TRY }
|
`
|
||||||
],
|
await expect(run(toBytecode(str))).rejects.toThrow('POP_TRY: no exception handler to pop')
|
||||||
constants: []
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(vm.run()).rejects.toThrow('POP_TRY: no exception handler to pop')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("PUSH_FINALLY - finally executes after successful try", async () => {
|
test("PUSH_FINALLY - finally executes after successful try", async () => {
|
||||||
// Try block completes normally, compiler jumps to finally
|
// Try block completes normally, compiler jumps to finally
|
||||||
const vm = new VM({
|
const str = `
|
||||||
instructions: [
|
PUSH_TRY #8
|
||||||
{ op: OpCode.PUSH_TRY, operand: 8 }, // catch at 8
|
PUSH_FINALLY #9
|
||||||
{ op: OpCode.PUSH_FINALLY, operand: 9 }, // finally at 9
|
PUSH 10
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push 10
|
STORE x
|
||||||
{ op: OpCode.STORE, operand: 'x' },
|
POP_TRY
|
||||||
{ op: OpCode.POP_TRY },
|
JUMP #3
|
||||||
{ op: OpCode.JUMP, operand: 3 }, // compiler jumps to finally (5->6, +3 = 9)
|
HALT
|
||||||
|
HALT
|
||||||
// Not executed
|
HALT
|
||||||
{ op: OpCode.HALT },
|
PUSH 100
|
||||||
{ op: OpCode.HALT },
|
LOAD x
|
||||||
|
ADD
|
||||||
// Catch block (instruction 8) - not executed
|
HALT
|
||||||
{ op: OpCode.HALT },
|
`
|
||||||
|
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 110 })
|
||||||
// Finally block (instruction 9)
|
|
||||||
{ op: OpCode.PUSH, operand: 1 }, // push 100
|
|
||||||
{ op: OpCode.LOAD, operand: 'x' }, // load 10
|
|
||||||
{ op: OpCode.ADD }, // 110
|
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
|
||||||
constants: [
|
|
||||||
toValue(10),
|
|
||||||
toValue(100)
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await vm.run()
|
|
||||||
expect(result).toEqual({ type: 'number', value: 110 })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("PUSH_FINALLY - finally executes after exception", async () => {
|
test("PUSH_FINALLY - finally executes after exception", async () => {
|
||||||
// Try block throws, THROW jumps to finally (skipping catch)
|
// Try block throws, THROW jumps to finally (skipping catch)
|
||||||
const vm = new VM({
|
const str = `
|
||||||
instructions: [
|
PUSH_TRY #5
|
||||||
{ op: OpCode.PUSH_TRY, operand: 5 }, // catch at 5
|
PUSH_FINALLY #7
|
||||||
{ op: OpCode.PUSH_FINALLY, operand: 7 }, // finally at 7
|
PUSH "error"
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push error
|
THROW
|
||||||
{ op: OpCode.THROW }, // throw (jumps to finally, not catch!)
|
HALT
|
||||||
{ op: OpCode.HALT }, // not executed
|
STORE err
|
||||||
|
HALT
|
||||||
// Catch block (instruction 5) - skipped because finally is present
|
POP
|
||||||
{ op: OpCode.STORE, operand: 'err' },
|
PUSH "finally ran"
|
||||||
{ op: OpCode.HALT },
|
HALT
|
||||||
|
`
|
||||||
// Finally block (instruction 7) - error is still on stack
|
expect(await run(toBytecode(str))).toEqual({ type: 'string', value: 'finally ran' })
|
||||||
{ op: OpCode.POP }, // discard error
|
|
||||||
{ op: OpCode.PUSH, operand: 1 }, // push "finally ran"
|
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
|
||||||
constants: [
|
|
||||||
toValue('error'),
|
|
||||||
toValue('finally ran')
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await vm.run()
|
|
||||||
expect(result).toEqual({ type: 'string', value: 'finally ran' })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("PUSH_FINALLY - finally without catch", async () => {
|
test("PUSH_FINALLY - finally without catch", async () => {
|
||||||
// Try-finally without catch (compiler generates jump to finally)
|
// Try-finally without catch (compiler generates jump to finally)
|
||||||
const vm = new VM({
|
const str = `
|
||||||
instructions: [
|
PUSH_TRY #7
|
||||||
{ op: OpCode.PUSH_TRY, operand: 7 }, // catch at 7 (dummy)
|
PUSH_FINALLY #7
|
||||||
{ op: OpCode.PUSH_FINALLY, operand: 7 }, // finally at 7
|
PUSH 42
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push 42
|
STORE x
|
||||||
{ op: OpCode.STORE, operand: 'x' },
|
POP_TRY
|
||||||
{ op: OpCode.POP_TRY },
|
JUMP #1
|
||||||
{ op: OpCode.JUMP, operand: 1 }, // compiler jumps to finally
|
HALT
|
||||||
{ op: OpCode.HALT }, // skipped
|
LOAD x
|
||||||
|
PUSH 10
|
||||||
// Finally block (instruction 7)
|
ADD
|
||||||
{ op: OpCode.LOAD, operand: 'x' }, // load 42
|
HALT
|
||||||
{ op: OpCode.PUSH, operand: 1 }, // push 10
|
`
|
||||||
{ op: OpCode.ADD }, // 52
|
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 52 })
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
|
||||||
constants: [
|
|
||||||
toValue(42),
|
|
||||||
toValue(10)
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await vm.run()
|
|
||||||
expect(result).toEqual({ type: 'number', value: 52 })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("PUSH_FINALLY - nested try-finally blocks", async () => {
|
test("PUSH_FINALLY - nested try-finally blocks", async () => {
|
||||||
// Nested try-finally blocks with compiler-generated jumps
|
// Nested try-finally blocks with compiler-generated jumps
|
||||||
const vm = new VM({
|
const str = `
|
||||||
instructions: [
|
PUSH_TRY #11
|
||||||
{ op: OpCode.PUSH_TRY, operand: 11 }, // outer catch at 11
|
PUSH_FINALLY #14
|
||||||
{ op: OpCode.PUSH_FINALLY, operand: 14 }, // outer finally at 14
|
PUSH_TRY #9
|
||||||
{ op: OpCode.PUSH_TRY, operand: 9 }, // inner catch at 9
|
PUSH_FINALLY #10
|
||||||
{ op: OpCode.PUSH_FINALLY, operand: 10 }, // inner finally at 10
|
PUSH 1
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push 1
|
POP_TRY
|
||||||
{ op: OpCode.POP_TRY }, // inner pop (instruction 5)
|
JUMP #3
|
||||||
{ op: OpCode.JUMP, operand: 3 }, // jump to inner finally (6->7, +3 = 10)
|
HALT
|
||||||
{ op: OpCode.HALT }, // skipped
|
HALT
|
||||||
{ op: OpCode.HALT }, // skipped
|
HALT
|
||||||
|
PUSH 10
|
||||||
// Inner catch (instruction 9) - not executed
|
POP_TRY
|
||||||
{ op: OpCode.HALT },
|
JUMP #1
|
||||||
|
HALT
|
||||||
// Inner finally (instruction 10)
|
ADD
|
||||||
{ op: OpCode.PUSH, operand: 1 }, // push 10
|
HALT
|
||||||
{ op: OpCode.POP_TRY }, // outer pop (instruction 11)
|
`
|
||||||
{ op: OpCode.JUMP, operand: 1 }, // jump to outer finally (12->13, +1 = 14)
|
expect(await run(toBytecode(str))).toEqual({ type: 'number', value: 11 })
|
||||||
|
|
||||||
// Outer catch (instruction 13) - not executed
|
|
||||||
{ op: OpCode.HALT },
|
|
||||||
|
|
||||||
// Outer finally (instruction 14)
|
|
||||||
{ op: OpCode.ADD }, // 1 + 10 = 11
|
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
|
||||||
constants: [
|
|
||||||
toValue(1),
|
|
||||||
toValue(10)
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await vm.run()
|
|
||||||
expect(result).toEqual({ type: 'number', value: 11 })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test("PUSH_FINALLY - error when no handler", async () => {
|
test("PUSH_FINALLY - error when no handler", async () => {
|
||||||
const vm = new VM({
|
const str = `
|
||||||
instructions: [
|
PUSH_FINALLY #5
|
||||||
{ op: OpCode.PUSH_FINALLY, operand: 5 }
|
`
|
||||||
],
|
await expect(run(toBytecode(str))).rejects.toThrow('PUSH_FINALLY: no exception handler to modify')
|
||||||
constants: []
|
|
||||||
})
|
|
||||||
|
|
||||||
await expect(vm.run()).rejects.toThrow('PUSH_FINALLY: no exception handler to modify')
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,18 @@
|
||||||
import { test, expect } from "bun:test"
|
import { test, expect } from "bun:test"
|
||||||
import { VM } from "#vm"
|
import { VM } from "#vm"
|
||||||
import { OpCode } from "#opcode"
|
import { toBytecode } from "#bytecode"
|
||||||
import { toValue, toNumber, toString } from "#value"
|
import { toValue, toNumber, toString } from "#value"
|
||||||
|
|
||||||
test("CALL_NATIVE - basic function call", async () => {
|
test("CALL_NATIVE - basic function call", async () => {
|
||||||
const vm = new VM({
|
const bytecode = toBytecode(`
|
||||||
instructions: [
|
PUSH 5
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push 5
|
PUSH 10
|
||||||
{ op: OpCode.PUSH, operand: 1 }, // push 10
|
CALL_NATIVE add
|
||||||
{ op: OpCode.CALL_NATIVE, operand: 'add' }, // call TypeScript 'add'
|
`)
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
|
||||||
constants: [
|
|
||||||
toValue(5),
|
|
||||||
toValue(10)
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Register a TypeScript function
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
|
// Register a native function
|
||||||
vm.registerFunction('add', (a, b) => {
|
vm.registerFunction('add', (a, b) => {
|
||||||
return toValue(toNumber(a) + toNumber(b))
|
return toValue(toNumber(a) + toNumber(b))
|
||||||
})
|
})
|
||||||
|
|
@ -27,18 +22,13 @@ test("CALL_NATIVE - basic function call", async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("CALL_NATIVE - function with string manipulation", async () => {
|
test("CALL_NATIVE - function with string manipulation", async () => {
|
||||||
const vm = new VM({
|
const bytecode = toBytecode(`
|
||||||
instructions: [
|
PUSH "hello"
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push "hello"
|
PUSH "world"
|
||||||
{ op: OpCode.PUSH, operand: 1 }, // push "world"
|
CALL_NATIVE concat
|
||||||
{ op: OpCode.CALL_NATIVE, operand: 'concat' }, // call TypeScript 'concat'
|
`)
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
const vm = new VM(bytecode)
|
||||||
constants: [
|
|
||||||
toValue("hello"),
|
|
||||||
toValue("world")
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
vm.registerFunction('concat', (a, b) => {
|
vm.registerFunction('concat', (a, b) => {
|
||||||
const aStr = a.type === 'string' ? a.value : toString(a)
|
const aStr = a.type === 'string' ? a.value : toString(a)
|
||||||
|
|
@ -51,16 +41,12 @@ test("CALL_NATIVE - function with string manipulation", async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("CALL_NATIVE - async function", async () => {
|
test("CALL_NATIVE - async function", async () => {
|
||||||
const vm = new VM({
|
const bytecode = toBytecode(`
|
||||||
instructions: [
|
PUSH 42
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push 42
|
CALL_NATIVE asyncDouble
|
||||||
{ op: OpCode.CALL_NATIVE, operand: 'asyncDouble' }, // call async TypeScript function
|
`)
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
const vm = new VM(bytecode)
|
||||||
constants: [
|
|
||||||
toValue(42)
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
vm.registerFunction('asyncDouble', async (a) => {
|
vm.registerFunction('asyncDouble', async (a) => {
|
||||||
// Simulate async operation
|
// Simulate async operation
|
||||||
|
|
@ -73,13 +59,11 @@ test("CALL_NATIVE - async function", async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("CALL_NATIVE - function with no arguments", async () => {
|
test("CALL_NATIVE - function with no arguments", async () => {
|
||||||
const vm = new VM({
|
const bytecode = toBytecode(`
|
||||||
instructions: [
|
CALL_NATIVE getAnswer
|
||||||
{ op: OpCode.CALL_NATIVE, operand: 'getAnswer' }, // call with empty stack
|
`)
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
const vm = new VM(bytecode)
|
||||||
constants: []
|
|
||||||
})
|
|
||||||
|
|
||||||
vm.registerFunction('getAnswer', () => {
|
vm.registerFunction('getAnswer', () => {
|
||||||
return toValue(42)
|
return toValue(42)
|
||||||
|
|
@ -90,20 +74,14 @@ test("CALL_NATIVE - function with no arguments", async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("CALL_NATIVE - function with multiple arguments", async () => {
|
test("CALL_NATIVE - function with multiple arguments", async () => {
|
||||||
const vm = new VM({
|
const bytecode = toBytecode(`
|
||||||
instructions: [
|
PUSH 2
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push 2
|
PUSH 3
|
||||||
{ op: OpCode.PUSH, operand: 1 }, // push 3
|
PUSH 4
|
||||||
{ op: OpCode.PUSH, operand: 2 }, // push 4
|
CALL_NATIVE sum
|
||||||
{ op: OpCode.CALL_NATIVE, operand: 'sum' }, // call TypeScript 'sum'
|
`)
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
const vm = new VM(bytecode)
|
||||||
constants: [
|
|
||||||
toValue(2),
|
|
||||||
toValue(3),
|
|
||||||
toValue(4)
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
vm.registerFunction('sum', (...args) => {
|
vm.registerFunction('sum', (...args) => {
|
||||||
const total = args.reduce((acc, val) => acc + toNumber(val), 0)
|
const total = args.reduce((acc, val) => acc + toNumber(val), 0)
|
||||||
|
|
@ -115,16 +93,12 @@ test("CALL_NATIVE - function with multiple arguments", async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("CALL_NATIVE - function returns array", async () => {
|
test("CALL_NATIVE - function returns array", async () => {
|
||||||
const vm = new VM({
|
const bytecode = toBytecode(`
|
||||||
instructions: [
|
PUSH 3
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push 3
|
CALL_NATIVE makeRange
|
||||||
{ op: OpCode.CALL_NATIVE, operand: 'makeRange' }, // call TypeScript 'makeRange'
|
`)
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
const vm = new VM(bytecode)
|
||||||
constants: [
|
|
||||||
toValue(3)
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
vm.registerFunction('makeRange', (n) => {
|
vm.registerFunction('makeRange', (n) => {
|
||||||
const count = toNumber(n)
|
const count = toNumber(n)
|
||||||
|
|
@ -148,30 +122,24 @@ test("CALL_NATIVE - function returns array", async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test("CALL_NATIVE - function not found", async () => {
|
test("CALL_NATIVE - function not found", async () => {
|
||||||
const vm = new VM({
|
const bytecode = toBytecode(`
|
||||||
instructions: [
|
CALL_NATIVE nonexistent
|
||||||
{ op: OpCode.CALL_NATIVE, operand: 'nonexistent' }
|
`)
|
||||||
],
|
|
||||||
constants: []
|
const vm = new VM(bytecode)
|
||||||
})
|
|
||||||
|
|
||||||
await expect(vm.run()).rejects.toThrow('CALL_NATIVE: function not found: nonexistent')
|
await expect(vm.run()).rejects.toThrow('CALL_NATIVE: function not found: nonexistent')
|
||||||
})
|
})
|
||||||
|
|
||||||
test("CALL_NATIVE - using result in subsequent operations", async () => {
|
test("CALL_NATIVE - using result in subsequent operations", async () => {
|
||||||
const vm = new VM({
|
const bytecode = toBytecode(`
|
||||||
instructions: [
|
PUSH 5
|
||||||
{ op: OpCode.PUSH, operand: 0 }, // push 5
|
CALL_NATIVE triple
|
||||||
{ op: OpCode.CALL_NATIVE, operand: 'triple' }, // call TypeScript 'triple' -> 15
|
PUSH 10
|
||||||
{ op: OpCode.PUSH, operand: 1 }, // push 10
|
ADD
|
||||||
{ op: OpCode.ADD }, // 15 + 10 = 25
|
`)
|
||||||
{ op: OpCode.HALT }
|
|
||||||
],
|
const vm = new VM(bytecode)
|
||||||
constants: [
|
|
||||||
toValue(5),
|
|
||||||
toValue(10)
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
vm.registerFunction('triple', (n) => {
|
vm.registerFunction('triple', (n) => {
|
||||||
return toValue(toNumber(n) * 3)
|
return toValue(toNumber(n) * 3)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user