diff --git a/tests/native.test.ts b/tests/native.test.ts index 04bd664..887b32f 100644 --- a/tests/native.test.ts +++ b/tests/native.test.ts @@ -1011,7 +1011,7 @@ test("Native function calls multiple Reef functions", async () => { const doubled = await this.call('double', n) const tripled = await this.call('triple', n) - return doubled + tripled // 10 + 15 = 25 + return doubled + tripled }) const result = await vm.run() @@ -1327,4 +1327,306 @@ test("Native function calls Reef function with named arguments", async () => { const result = await vm.run() expect(result).toEqual({ type: 'string', value: "Hi Alice!" }) -}) \ No newline at end of file +}) +test("vm.call() with only named arguments", async () => { + const bytecode = toBytecode(` + ; Reef function with default parameters + MAKE_FUNCTION (name greeting='Hello') .greet_body + STORE greet + JUMP .skip_greet + .greet_body: + LOAD greeting + PUSH " " + LOAD name + PUSH "!" + STR_CONCAT #4 + RETURN + .skip_greet: + + LOAD call_greet + PUSH 0 + PUSH 0 + CALL + `) + + const vm = new VM(bytecode, { + call_greet: async function () { + // Call with ONLY named arguments (no positional) + return await this.call('greet', { name: "Bob", greeting: "Hey" }) + } + }) + + const result = await vm.run() + expect(result).toEqual({ type: 'string', value: "Hey Bob!" }) +}) + +test("Native function receives Reef closure with default params - calls with named args", async () => { + const bytecode = toBytecode(` + ; Reef function with default parameters + MAKE_FUNCTION (x multiplier=2) .transform_body + STORE transform + JUMP .skip_transform + .transform_body: + LOAD x + LOAD multiplier + MUL + RETURN + .skip_transform: + + ; Pass to native map function + LOAD map + PUSH 1 + PUSH 2 + PUSH 3 + MAKE_ARRAY #3 + LOAD transform + PUSH 2 + PUSH 0 + CALL + `) + + const vm = new VM(bytecode) + + // Native function that calls the Reef closure with named arguments + vm.registerFunction('map', async (array: any[], callback: Function) => { + const results = [] + for (const item of array) { + // Call with named argument to override the default + results.push(await callback(item, { multiplier: 10 })) + } + return results + }) + + const result = await vm.run() + expect(result.type).toBe('array') + if (result.type === 'array') { + expect(result.value).toEqual([ + toValue(10), // 1 * 10 + toValue(20), // 2 * 10 + toValue(30) // 3 * 10 + ]) + } +}) + +test("Native function receives Reef closure with variadic parameters", async () => { + const bytecode = toBytecode(` + ; Reef function with variadic params + MAKE_FUNCTION (...nums) .sum_body + STORE sum_all + JUMP .skip_sum + .sum_body: + PUSH 0 + STORE total + LOAD nums + STORE items + PUSH 0 + STORE idx + + .loop: + LOAD idx + LOAD items + ARRAY_LEN + LT + JUMP_IF_FALSE .done + + LOAD total + LOAD items + LOAD idx + ARRAY_GET + ADD + STORE total + + LOAD idx + PUSH 1 + ADD + STORE idx + JUMP .loop + + .done: + LOAD total + RETURN + .skip_sum: + + ; Pass to native function + LOAD process + LOAD sum_all + PUSH 1 + PUSH 0 + CALL + `) + + const vm = new VM(bytecode) + + // Native function that calls the variadic Reef closure with multiple args + vm.registerFunction('process', async (callback: Function) => { + // Call with varying number of arguments + const result1 = await callback(1, 2, 3) + const result2 = await callback(10, 20) + return result1 + result2 + }) + + const result = await vm.run() + expect(result).toEqual({ type: 'number', value: 36 }) // (1+2+3) + (10+20) = 6 + 30 +}) + +test("Native function receives Reef closure with @named parameter", async () => { + const bytecode = toBytecode(` + ; Reef function with @named parameter + MAKE_FUNCTION (message @options) .format_body + STORE format_message + JUMP .skip_format + .format_body: + ; Get uppercase option (default false) + LOAD options + PUSH "uppercase" + DICT_GET + STORE use_upper + + ; Get prefix option (default empty) + LOAD options + PUSH "prefix" + DICT_GET + STORE prefix_val + + ; Build result + LOAD prefix_val + LOAD message + STR_CONCAT #2 + RETURN + .skip_format: + + ; Pass to native function + LOAD format_messages + PUSH "hello" + PUSH "world" + MAKE_ARRAY #2 + LOAD format_message + PUSH 2 + PUSH 0 + CALL + `) + + const vm = new VM(bytecode) + + // Native function that calls Reef closure with named arguments + vm.registerFunction('format_messages', async (messages: any[], formatter: Function) => { + const results = [] + for (const msg of messages) { + // Call with named arguments + results.push(await formatter(msg, { prefix: "[LOG] ", uppercase: true })) + } + return results + }) + + const result = await vm.run() + expect(result.type).toBe('array') + if (result.type === 'array') { + expect(result.value).toEqual([ + toValue('[LOG] hello'), + toValue('[LOG] world') + ]) + } +}) + +test("Native function receives Reef closure - calls with only named arguments", async () => { + const bytecode = toBytecode(` + ; Reef function expecting named arguments + MAKE_FUNCTION (x=0 y=0 z=0) .add_body + STORE add_coords + JUMP .skip_add + .add_body: + LOAD x + LOAD y + ADD + LOAD z + ADD + RETURN + .skip_add: + + ; Pass to native function + LOAD compute + LOAD add_coords + PUSH 1 + PUSH 0 + CALL + `) + + const vm = new VM(bytecode) + + // Native function that calls Reef closure with ONLY named arguments + vm.registerFunction('compute', async (callback: Function) => { + // First call with all named args + const result1 = await callback({ x: 10, y: 20, z: 30 }) + + // Second call with partial named args (rest use defaults) + const result2 = await callback({ x: 5, z: 15 }) + + return result1 + result2 + }) + + const result = await vm.run() + expect(result).toEqual({ type: 'number', value: 80 }) // (10+20+30) + (5+0+15) = 60 + 20 +}) + +test("Native function receives Reef closure with mixed positional and variadic", async () => { + const bytecode = toBytecode(` + ; Reef function with fixed param and variadic + MAKE_FUNCTION (multiplier ...nums) .scale_body + STORE scale_numbers + JUMP .skip_scale + .scale_body: + PUSH 0 + STORE total + LOAD nums + STORE items + PUSH 0 + STORE idx + + .loop: + LOAD idx + LOAD items + ARRAY_LEN + LT + JUMP_IF_FALSE .done + + LOAD total + LOAD items + LOAD idx + ARRAY_GET + LOAD multiplier + MUL + ADD + STORE total + + LOAD idx + PUSH 1 + ADD + STORE idx + JUMP .loop + + .done: + LOAD total + RETURN + .skip_scale: + + ; Pass to native function + LOAD process_numbers + LOAD scale_numbers + PUSH 1 + PUSH 0 + CALL + `) + + const vm = new VM(bytecode) + + // Native function that calls Reef closure with mixed args + vm.registerFunction('process_numbers', async (calculator: Function) => { + // Call with fixed multiplier and variadic numbers + const result1 = await calculator(10, 1, 2, 3) // 10 * (1+2+3) = 60 + const result2 = await calculator(5, 4, 6) // 5 * (4+6) = 50 + return result1 + result2 + }) + + const result = await vm.run() + expect(result).toEqual({ type: 'number', value: 110 }) // 60 + 50 +})