vm.call() native functions too
This commit is contained in:
parent
f79fea33c5
commit
e1e7cdf1ef
23
CLAUDE.md
23
CLAUDE.md
|
|
@ -169,9 +169,9 @@ Auto-wrapping handles:
|
|||
- Sync and async functions
|
||||
- Arrays, objects, primitives, null, RegExp
|
||||
|
||||
### Calling Reef Functions from TypeScript
|
||||
### Calling Functions from TypeScript
|
||||
|
||||
Use `vm.call()` to invoke Reef functions from TypeScript:
|
||||
Use `vm.call()` to invoke Reef or native functions from TypeScript:
|
||||
|
||||
```typescript
|
||||
const bytecode = toBytecode(`
|
||||
|
|
@ -186,24 +186,29 @@ const bytecode = toBytecode(`
|
|||
RETURN
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
const vm = new VM(bytecode, {
|
||||
log: (msg: string) => console.log(msg) // Native function
|
||||
})
|
||||
await vm.run()
|
||||
|
||||
// Positional arguments
|
||||
// Call Reef function with positional arguments
|
||||
const result1 = await vm.call('add', 5, 3) // → 8
|
||||
|
||||
// Named arguments (pass final object)
|
||||
// Call Reef function with named arguments (pass final object)
|
||||
const result2 = await vm.call('add', 5, { y: 20 }) // → 25
|
||||
|
||||
// All named arguments
|
||||
// Call Reef function with all named arguments
|
||||
const result3 = await vm.call('add', { x: 10, y: 15 }) // → 25
|
||||
|
||||
// Call native function
|
||||
await vm.call('log', 'Hello!')
|
||||
```
|
||||
|
||||
**How it works**:
|
||||
- Looks up function in VM scope
|
||||
- Converts it to a callable JavaScript function using `fnFromValue`
|
||||
- Looks up function (Reef or native) in VM scope
|
||||
- For Reef functions: converts to callable JavaScript function using `fnFromValue`
|
||||
- For native functions: calls directly
|
||||
- Automatically converts arguments to ReefVM Values
|
||||
- Executes the function in a fresh VM context
|
||||
- Converts result back to JavaScript types
|
||||
|
||||
### Label Usage (Preferred)
|
||||
|
|
|
|||
24
GUIDE.md
24
GUIDE.md
|
|
@ -676,9 +676,9 @@ CALL ; atOptions receives {debug: true, port: 8080}
|
|||
|
||||
Named arguments that match fixed parameter names are bound to those parameters. Remaining unmatched named arguments are collected into the `atXxx` parameter as a plain JavaScript object.
|
||||
|
||||
### Calling Reef Functions from TypeScript
|
||||
### Calling Functions from TypeScript
|
||||
|
||||
Once you have Reef functions defined in the VM, you can call them from TypeScript using `vm.call()`:
|
||||
You can call both Reef and native functions from TypeScript using `vm.call()`:
|
||||
|
||||
```typescript
|
||||
const bytecode = toBytecode(`
|
||||
|
|
@ -695,26 +695,32 @@ const bytecode = toBytecode(`
|
|||
RETURN
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode)
|
||||
const vm = new VM(bytecode, {
|
||||
log: (msg: string) => console.log(msg) // Native function
|
||||
})
|
||||
await vm.run()
|
||||
|
||||
// Call with positional arguments
|
||||
// Call Reef function with positional arguments
|
||||
const result1 = await vm.call('greet', 'Alice')
|
||||
// Returns: "Hello Alice!"
|
||||
|
||||
// Call with named arguments (pass as final object)
|
||||
// Call Reef function with named arguments (pass as final object)
|
||||
const result2 = await vm.call('greet', 'Bob', { greeting: 'Hi' })
|
||||
// Returns: "Hi Bob!"
|
||||
|
||||
// Call with only named arguments
|
||||
// Call Reef function with only named arguments
|
||||
const result3 = await vm.call('greet', { name: 'Carol', greeting: 'Hey' })
|
||||
// Returns: "Hey Carol!"
|
||||
|
||||
// Call native function
|
||||
await vm.call('log', 'Hello from TypeScript!')
|
||||
```
|
||||
|
||||
**How it works**:
|
||||
- `vm.call(functionName, ...args)` looks up the function in the VM's scope
|
||||
- Converts it to a callable JavaScript function
|
||||
- Calls it with the provided arguments (automatically converted to ReefVM Values)
|
||||
- `vm.call(functionName, ...args)` looks up the function (Reef or native) in the VM's scope
|
||||
- For Reef functions: converts to callable JavaScript function
|
||||
- For native functions: calls directly
|
||||
- Arguments are automatically converted to ReefVM Values
|
||||
- Returns the result (automatically converted back to JavaScript types)
|
||||
|
||||
**Named arguments**: Pass a plain object as the final argument to provide named arguments. If the last argument is a non-array object, it's treated as named arguments. All preceding arguments are treated as positional.
|
||||
|
|
|
|||
28
src/vm.ts
28
src/vm.ts
|
|
@ -35,11 +35,15 @@ export class VM {
|
|||
const value = this.scope.get(name)
|
||||
|
||||
if (!value) throw new Error(`Can't find ${name}`)
|
||||
if (value.type !== 'function') throw new Error(`Can't call ${name}`)
|
||||
if (value.type !== 'function' && value.type !== 'native') throw new Error(`Can't call ${name}`)
|
||||
|
||||
if (value.type === 'native') {
|
||||
return await this.callNative(value.fn, args)
|
||||
} else {
|
||||
const fn = fnFromValue(value, this)
|
||||
return await fn(...args)
|
||||
}
|
||||
}
|
||||
|
||||
registerFunction(name: string, fn: Fn) {
|
||||
const wrapped = isWrapped(fn) ? fn as NativeFunction : wrapNative(this, fn)
|
||||
|
|
@ -685,4 +689,26 @@ export class VM {
|
|||
const result = fn(a, b)
|
||||
this.stack.push({ type: 'boolean', value: result })
|
||||
}
|
||||
|
||||
async callNative(nativeFn: NativeFunction, args: any[]): Promise<Value> {
|
||||
const originalFn = getOriginalFunction(nativeFn)
|
||||
|
||||
const lastArg = args[args.length - 1]
|
||||
if (lastArg && !Array.isArray(lastArg) && typeof lastArg === 'object') {
|
||||
const paramInfo = extractParamInfo(originalFn)
|
||||
const positional = args.slice(0, -1)
|
||||
const named = lastArg
|
||||
|
||||
args = [...positional]
|
||||
for (let i = positional.length; i < paramInfo.params.length; i++) {
|
||||
const paramName = paramInfo.params[i]!
|
||||
if (named[paramName] !== undefined) {
|
||||
args[i] = named[paramName]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await originalFn.call(this, ...args)
|
||||
return toValue(result)
|
||||
}
|
||||
}
|
||||
|
|
@ -1630,3 +1630,114 @@ test("Native function receives Reef closure with mixed positional and variadic",
|
|||
const result = await vm.run()
|
||||
expect(result).toEqual({ type: 'number', value: 110 }) // 60 + 50
|
||||
})
|
||||
|
||||
test("vm.call() with native function - basic sync", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
HALT
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode, {
|
||||
add: (a: number, b: number) => a + b
|
||||
})
|
||||
|
||||
await vm.run()
|
||||
|
||||
// Call native function via vm.call()
|
||||
const result = await vm.call('add', 10, 20)
|
||||
expect(result).toEqual({ type: 'number', value: 30 })
|
||||
})
|
||||
|
||||
test("vm.call() with native function - async", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
HALT
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode, {
|
||||
asyncDouble: async (n: number) => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1))
|
||||
return n * 2
|
||||
}
|
||||
})
|
||||
|
||||
await vm.run()
|
||||
|
||||
const result = await vm.call('asyncDouble', 21)
|
||||
expect(result).toEqual({ type: 'number', value: 42 })
|
||||
})
|
||||
|
||||
test("vm.call() with native function - returns array", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
HALT
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode, {
|
||||
makeRange: (n: number) => Array.from({ length: n }, (_, i) => i)
|
||||
})
|
||||
|
||||
await vm.run()
|
||||
|
||||
const result = await vm.call('makeRange', 4)
|
||||
expect(result.type).toBe('array')
|
||||
if (result.type === 'array') {
|
||||
expect(result.value).toEqual([
|
||||
toValue(0),
|
||||
toValue(1),
|
||||
toValue(2),
|
||||
toValue(3)
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
test("vm.call() with native function - returns object (becomes dict)", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
HALT
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode, {
|
||||
makeUser: (name: string, age: number) => ({ name, age })
|
||||
})
|
||||
|
||||
await vm.run()
|
||||
|
||||
const result = await vm.call('makeUser', "Alice", 30)
|
||||
expect(result.type).toBe('dict')
|
||||
if (result.type === 'dict') {
|
||||
expect(result.value.get('name')).toEqual(toValue('Alice'))
|
||||
expect(result.value.get('age')).toEqual(toValue(30))
|
||||
}
|
||||
})
|
||||
|
||||
test("vm.call() with native function - named arguments", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
HALT
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode, {
|
||||
greet: (name: string, greeting = 'Hello') => `${greeting}, ${name}!`
|
||||
})
|
||||
|
||||
await vm.run()
|
||||
|
||||
// Call with positional
|
||||
const result1 = await vm.call('greet', "Alice")
|
||||
expect(result1).toEqual({ type: 'string', value: "Hello, Alice!" })
|
||||
|
||||
// Call with named args
|
||||
const result2 = await vm.call('greet', "Bob", { greeting: "Hi" })
|
||||
expect(result2).toEqual({ type: 'string', value: "Hi, Bob!" })
|
||||
})
|
||||
|
||||
test("vm.call() with native function - variadic parameters", async () => {
|
||||
const bytecode = toBytecode(`
|
||||
HALT
|
||||
`)
|
||||
|
||||
const vm = new VM(bytecode, {
|
||||
sum: (...nums: number[]) => nums.reduce((acc, n) => acc + n, 0)
|
||||
})
|
||||
|
||||
await vm.run()
|
||||
|
||||
const result = await vm.call('sum', 1, 2, 3, 4, 5)
|
||||
expect(result).toEqual({ type: 'number', value: 15 })
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user