mark functions as raw=true to deal with Value directly

This commit is contained in:
Chris Wanstrath 2025-10-29 13:10:05 -07:00
parent 052f989e82
commit 030eb74871
2 changed files with 420 additions and 0 deletions

View File

@ -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)

View File

@ -1784,3 +1784,421 @@ 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)])
}
}
})