forked from defunkt/ReefVM
mark functions as raw=true to deal with Value directly
This commit is contained in:
parent
052f989e82
commit
030eb74871
|
|
@ -11,6 +11,8 @@ export type ParamInfo = {
|
|||
const WRAPPED_MARKER = Symbol('reef-wrapped')
|
||||
|
||||
export function wrapNative(vm: VM, fn: Function): (this: VM, ...args: Value[]) => Promise<Value> {
|
||||
if ((fn as any).raw) return fn as (this: VM, ...args: Value[]) => Promise<Value>
|
||||
|
||||
const wrapped = async function (this: VM, ...values: Value[]) {
|
||||
const nativeArgs = values.map(arg => fromValue(arg, vm))
|
||||
const result = await fn.call(this, ...nativeArgs)
|
||||
|
|
|
|||
|
|
@ -1783,4 +1783,422 @@ test("builtin global scope can be values too", async () => {
|
|||
|
||||
expect(globals).toEqual(['pi', 'universe', 'algorithms'])
|
||||
expect(locals).toEqual(['x'])
|
||||
})
|
||||
|
||||
// Raw flag tests - functions that work directly with Values
|
||||
|
||||
test("raw flag - basic raw function receives Values directly", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD rawAdd
|
||||
PUSH 5
|
||||
PUSH 10
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
// Raw function that works directly with Value types
|
||||
const rawAdd = (a: any, b: any) => {
|
||||
// Verify we receive Value objects, not unwrapped numbers
|
||||
expect(a).toEqual({ type: 'number', value: 5 })
|
||||
expect(b).toEqual({ type: 'number', value: 10 })
|
||||
|
||||
return toValue(toNumber(a) + toNumber(b))
|
||||
}
|
||||
rawAdd.raw = true
|
||||
|
||||
vm.set('rawAdd', rawAdd)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'number', value: 15 })
|
||||
})
|
||||
|
||||
test("raw flag - async raw function", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD asyncRawDouble
|
||||
PUSH 42
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
const asyncRawDouble = async (a: any) => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1))
|
||||
expect(a).toEqual({ type: 'number', value: 42 })
|
||||
return toValue(toNumber(a) * 2)
|
||||
}
|
||||
asyncRawDouble.raw = true
|
||||
|
||||
vm.set('asyncRawDouble', asyncRawDouble)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'number', value: 84 })
|
||||
})
|
||||
|
||||
test("raw flag - raw function with string Value", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD rawConcat
|
||||
PUSH "hello"
|
||||
PUSH "world"
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
const rawConcat = (a: any, b: any) => {
|
||||
// Verify we receive Value objects
|
||||
expect(a.type).toBe('string')
|
||||
expect(b.type).toBe('string')
|
||||
|
||||
return toValue(toString(a) + ' ' + toString(b))
|
||||
}
|
||||
rawConcat.raw = true
|
||||
|
||||
vm.set('rawConcat', rawConcat)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'string', value: 'hello world' })
|
||||
})
|
||||
|
||||
test("raw flag - raw function with array Value", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD rawArrayLength
|
||||
PUSH 1
|
||||
PUSH 2
|
||||
PUSH 3
|
||||
MAKE_ARRAY #3
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
const rawArrayLength = (arr: any) => {
|
||||
// Verify we receive a Value object with type 'array'
|
||||
expect(arr.type).toBe('array')
|
||||
expect(Array.isArray(arr.value)).toBe(true)
|
||||
|
||||
return toValue(arr.value.length)
|
||||
}
|
||||
rawArrayLength.raw = true
|
||||
|
||||
vm.set('rawArrayLength', rawArrayLength)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'number', value: 3 })
|
||||
})
|
||||
|
||||
test("raw flag - raw function with dict Value", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD rawDictKeys
|
||||
PUSH "name"
|
||||
PUSH "Alice"
|
||||
PUSH "age"
|
||||
PUSH 30
|
||||
MAKE_DICT #2
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
const rawDictKeys = (dict: any) => {
|
||||
// Verify we receive a Value object with type 'dict'
|
||||
expect(dict.type).toBe('dict')
|
||||
expect(dict.value instanceof Map).toBe(true)
|
||||
|
||||
// Return number of keys
|
||||
return toValue(dict.value.size)
|
||||
}
|
||||
rawDictKeys.raw = true
|
||||
|
||||
vm.set('rawDictKeys', rawDictKeys)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'number', value: 2 })
|
||||
})
|
||||
|
||||
test("raw flag - raw function with variadic parameters", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD rawSum
|
||||
PUSH 1
|
||||
PUSH 2
|
||||
PUSH 3
|
||||
PUSH 4
|
||||
PUSH 4
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
const rawSum = (...args: any[]) => {
|
||||
// All args should be Value objects
|
||||
let total = 0
|
||||
for (const arg of args) {
|
||||
expect(arg.type).toBe('number')
|
||||
total += toNumber(arg)
|
||||
}
|
||||
return toValue(total)
|
||||
}
|
||||
rawSum.raw = true
|
||||
|
||||
vm.set('rawSum', rawSum)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'number', value: 10 })
|
||||
})
|
||||
|
||||
test("raw flag - compare raw vs wrapped function behavior", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
; Call wrapped function
|
||||
LOAD wrappedAdd
|
||||
PUSH 5
|
||||
PUSH 10
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
STORE result1
|
||||
|
||||
; Call raw function
|
||||
LOAD rawAdd
|
||||
PUSH 5
|
||||
PUSH 10
|
||||
PUSH 2
|
||||
PUSH 0
|
||||
CALL
|
||||
STORE result2
|
||||
|
||||
; Both should give same result
|
||||
LOAD result1
|
||||
LOAD result2
|
||||
EQ
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
// Wrapped function receives native JS types
|
||||
vm.set('wrappedAdd', (a: number, b: number) => {
|
||||
// These are native numbers, not Values
|
||||
expect(typeof a).toBe('number')
|
||||
expect(typeof b).toBe('number')
|
||||
return a + b
|
||||
})
|
||||
|
||||
// Raw function receives Values
|
||||
const rawAdd = (a: any, b: any) => {
|
||||
// These are Value objects
|
||||
expect(a.type).toBe('number')
|
||||
expect(b.type).toBe('number')
|
||||
return toValue(toNumber(a) + toNumber(b))
|
||||
}
|
||||
rawAdd.raw = true
|
||||
vm.set('rawAdd', rawAdd)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'boolean', value: true })
|
||||
})
|
||||
|
||||
test("raw flag - raw function with null Value", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD rawCheckNull
|
||||
PUSH null
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
const rawCheckNull = (val: any) => {
|
||||
// Verify we receive a null Value
|
||||
expect(val).toEqual({ type: 'null', value: null })
|
||||
return toValue(val.type === 'null')
|
||||
}
|
||||
rawCheckNull.raw = true
|
||||
|
||||
vm.set('rawCheckNull', rawCheckNull)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'boolean', value: true })
|
||||
})
|
||||
|
||||
test("raw flag - raw function with boolean Value", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD rawNegate
|
||||
PUSH true
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
const rawNegate = (val: any) => {
|
||||
// Verify we receive a boolean Value
|
||||
expect(val.type).toBe('boolean')
|
||||
expect(val.value).toBe(true)
|
||||
return toValue(!val.value)
|
||||
}
|
||||
rawNegate.raw = true
|
||||
|
||||
vm.set('rawNegate', rawNegate)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'boolean', value: false })
|
||||
})
|
||||
|
||||
test("raw flag - mixed raw and wrapped functions", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
; Call wrapped first
|
||||
LOAD wrapped
|
||||
PUSH 10
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
STORE temp
|
||||
|
||||
; Call raw with result
|
||||
LOAD raw
|
||||
LOAD temp
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
// Wrapped function
|
||||
vm.set('wrapped', (n: number) => {
|
||||
expect(typeof n).toBe('number')
|
||||
return n * 2
|
||||
})
|
||||
|
||||
// Raw function
|
||||
const raw = (val: any) => {
|
||||
expect(val.type).toBe('number')
|
||||
return toValue(toNumber(val) + 5)
|
||||
}
|
||||
raw.raw = true
|
||||
vm.set('raw', raw)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'number', value: 25 }) // (10 * 2) + 5
|
||||
})
|
||||
|
||||
test("raw flag - raw function can manipulate Value structure directly", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD rawTransform
|
||||
PUSH 1
|
||||
PUSH 2
|
||||
PUSH 3
|
||||
MAKE_ARRAY #3
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
const rawTransform = (arr: any) => {
|
||||
// Direct manipulation of Value structure
|
||||
expect(arr.type).toBe('array')
|
||||
|
||||
// Transform array by doubling each element
|
||||
const doubled = arr.value.map((v: any) => toValue(toNumber(v) * 2))
|
||||
|
||||
return { type: 'array', value: doubled } as any
|
||||
}
|
||||
rawTransform.raw = true
|
||||
|
||||
vm.set('rawTransform', rawTransform)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result.type).toBe('array')
|
||||
if (result.type === 'array') {
|
||||
expect(result.value).toEqual([
|
||||
toValue(2),
|
||||
toValue(4),
|
||||
toValue(6)
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
test("raw flag - raw function receives Reef function as Value", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
; Define a Reef function
|
||||
MAKE_FUNCTION (x) .double_body
|
||||
STORE double
|
||||
JUMP .skip_double
|
||||
.double_body:
|
||||
LOAD x
|
||||
PUSH 2
|
||||
MUL
|
||||
RETURN
|
||||
.skip_double:
|
||||
|
||||
; Pass it to raw function
|
||||
LOAD rawInspect
|
||||
LOAD double
|
||||
PUSH 1
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
const rawInspect = (fn: any) => {
|
||||
// Verify we receive a function Value
|
||||
expect(fn.type).toBe('function')
|
||||
return toValue(true)
|
||||
}
|
||||
rawInspect.raw = true
|
||||
|
||||
vm.set('rawInspect', rawInspect)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'boolean', value: true })
|
||||
})
|
||||
|
||||
test("raw flag - raw function can return any Value type", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
LOAD rawMakeComplex
|
||||
PUSH 0
|
||||
PUSH 0
|
||||
CALL
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
|
||||
const rawMakeComplex = () => {
|
||||
// Return a complex nested structure
|
||||
const innerArray = [toValue(1), toValue(2), toValue(3)]
|
||||
const dict = new Map()
|
||||
dict.set('numbers', { type: 'array', value: innerArray })
|
||||
dict.set('name', toValue('test'))
|
||||
|
||||
return { type: 'dict', value: dict } as any
|
||||
}
|
||||
rawMakeComplex.raw = true
|
||||
|
||||
vm.set('rawMakeComplex', rawMakeComplex)
|
||||
|
||||
const result = await vm.run()
|
||||
expect(result.type).toBe('dict')
|
||||
if (result.type === 'dict') {
|
||||
expect(result.value.get('name')).toEqual(toValue('test'))
|
||||
const numbers = result.value.get('numbers')
|
||||
expect(numbers?.type).toBe('array')
|
||||
if (numbers?.type === 'array') {
|
||||
expect(numbers.value).toEqual([toValue(1), toValue(2), toValue(3)])
|
||||
}
|
||||
}
|
||||
})
|
||||
Loading…
Reference in New Issue
Block a user