diff --git a/src/function.ts b/src/function.ts index 36c107a..9226810 100644 --- a/src/function.ts +++ b/src/function.ts @@ -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 { + if ((fn as any).raw) return fn as (this: VM, ...args: Value[]) => Promise + const wrapped = async function (this: VM, ...values: Value[]) { const nativeArgs = values.map(arg => fromValue(arg, vm)) const result = await fn.call(this, ...nativeArgs) diff --git a/tests/native.test.ts b/tests/native.test.ts index 6aa3c54..4b43e7c 100644 --- a/tests/native.test.ts +++ b/tests/native.test.ts @@ -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)]) + } + } }) \ No newline at end of file