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')
|
const WRAPPED_MARKER = Symbol('reef-wrapped')
|
||||||
|
|
||||||
export function wrapNative(vm: VM, fn: Function): (this: VM, ...args: Value[]) => Promise<Value> {
|
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 wrapped = async function (this: VM, ...values: Value[]) {
|
||||||
const nativeArgs = values.map(arg => fromValue(arg, vm))
|
const nativeArgs = values.map(arg => fromValue(arg, vm))
|
||||||
const result = await fn.call(this, ...nativeArgs)
|
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(globals).toEqual(['pi', 'universe', 'algorithms'])
|
||||||
expect(locals).toEqual(['x'])
|
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