forked from defunkt/ReefVM
vm.set(), new VM(bytecode, globalVars)
This commit is contained in:
parent
97b6722a11
commit
e542070677
34
CLAUDE.md
34
CLAUDE.md
|
|
@ -137,37 +137,55 @@ Array format features:
|
||||||
- Function params as string arrays: `["MAKE_FUNCTION", ["x", "y=10"], ".body"]`
|
- Function params as string arrays: `["MAKE_FUNCTION", ["x", "y=10"], ".body"]`
|
||||||
- See `tests/programmatic.test.ts` and `examples/programmatic.ts` for examples
|
- See `tests/programmatic.test.ts` and `examples/programmatic.ts` for examples
|
||||||
|
|
||||||
### Native Function Registration
|
### Native Function Registration and Global Values
|
||||||
|
|
||||||
**Option 1**: Pass to `run()` or `VM` constructor (convenience)
|
**Option 1**: Pass to `run()` or `VM` constructor (convenience)
|
||||||
```typescript
|
```typescript
|
||||||
const result = await run(bytecode, {
|
const result = await run(bytecode, {
|
||||||
add: (a: number, b: number) => a + b,
|
add: (a: number, b: number) => a + b,
|
||||||
greet: (name: string) => `Hello, ${name}!`
|
greet: (name: string) => `Hello, ${name}!`,
|
||||||
|
pi: 3.14159,
|
||||||
|
config: { debug: true, port: 8080 }
|
||||||
})
|
})
|
||||||
|
|
||||||
// Or with VM constructor
|
// Or with VM constructor
|
||||||
const vm = new VM(bytecode, { add, greet })
|
const vm = new VM(bytecode, { add, greet, pi, config })
|
||||||
```
|
```
|
||||||
|
|
||||||
**Option 2**: Register with `vm.registerFunction()` (manual)
|
**Option 2**: Set values with `vm.set()` (manual)
|
||||||
```typescript
|
```typescript
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('add', (a: number, b: number) => a + b)
|
|
||||||
|
// Set functions (auto-wrapped to native functions)
|
||||||
|
vm.set('add', (a: number, b: number) => a + b)
|
||||||
|
|
||||||
|
// Set any other values (auto-converted to ReefVM Values)
|
||||||
|
vm.set('pi', 3.14159)
|
||||||
|
vm.set('config', { debug: true, port: 8080 })
|
||||||
|
|
||||||
await vm.run()
|
await vm.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
**Option 3**: Register Value-based functions (for direct Value access)
|
**Option 3**: Set Value-based functions with `vm.setValueFunction()` (advanced)
|
||||||
|
|
||||||
|
For functions that work directly with ReefVM Value types:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
vm.registerValueFunction('customOp', (a: Value, b: Value): Value => {
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
|
// Set Value-based function (no wrapping, works directly with Values)
|
||||||
|
vm.setValueFunction('customOp', (a: Value, b: Value): Value => {
|
||||||
return toValue(toNumber(a) + toNumber(b))
|
return toValue(toNumber(a) + toNumber(b))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await vm.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
Auto-wrapping handles:
|
Auto-wrapping handles:
|
||||||
- Value ↔ native type conversion (`fromValue`/`toValue`)
|
- Functions: wrapped as native functions with Value ↔ native type conversion
|
||||||
- Sync and async functions
|
- Sync and async functions
|
||||||
- Arrays, objects, primitives, null, RegExp
|
- Arrays, objects, primitives, null, RegExp
|
||||||
|
- All values converted via `toValue()`
|
||||||
|
|
||||||
### Calling Functions from TypeScript
|
### Calling Functions from TypeScript
|
||||||
|
|
||||||
|
|
|
||||||
10
GUIDE.md
10
GUIDE.md
|
|
@ -662,18 +662,18 @@ const vm = new VM(bytecode, { add, greet })
|
||||||
**Method 2**: Register after construction
|
**Method 2**: Register after construction
|
||||||
```typescript
|
```typescript
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('add', (a: number, b: number) => a + b)
|
vm.set('add', (a: number, b: number) => a + b)
|
||||||
await vm.run()
|
await vm.run()
|
||||||
```
|
```
|
||||||
|
|
||||||
**Method 3**: Value-based functions (for full control)
|
**Method 3**: Value-based functions (for full control)
|
||||||
```typescript
|
```typescript
|
||||||
vm.registerValueFunction('customOp', (a: Value, b: Value): Value => {
|
vm.setValueFunction('customOp', (a: Value, b: Value): Value => {
|
||||||
return { type: 'number', value: toNumber(a) + toNumber(b) }
|
return { type: 'number', value: toNumber(a) + toNumber(b) }
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
**Auto-wrapping**: `registerFunction` automatically converts between native TypeScript types and ReefVM Value types. Both sync and async functions work.
|
**Auto-wrapping**: `vm.set()` automatically converts between native TypeScript types and ReefVM Value types. Both sync and async functions work.
|
||||||
|
|
||||||
**Usage in bytecode**:
|
**Usage in bytecode**:
|
||||||
```
|
```
|
||||||
|
|
@ -702,12 +702,12 @@ CALL ; → "Hi, Alice!"
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Basic @named - collects all named args
|
// Basic @named - collects all named args
|
||||||
vm.registerFunction('greet', (atNamed: any = {}) => {
|
vm.set('greet', (atNamed: any = {}) => {
|
||||||
return `Hello, ${atNamed.name || 'World'}!`
|
return `Hello, ${atNamed.name || 'World'}!`
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mixed positional and @named
|
// Mixed positional and @named
|
||||||
vm.registerFunction('configure', (name: string, atOptions: any = {}) => {
|
vm.set('configure', (name: string, atOptions: any = {}) => {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
debug: atOptions.debug || false,
|
debug: atOptions.debug || false,
|
||||||
|
|
|
||||||
24
SPEC.md
24
SPEC.md
|
|
@ -622,7 +622,7 @@ const vm = new VM(bytecode, {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Or after construction:
|
// Or after construction:
|
||||||
vm.registerFunction('multiply', (a: number, b: number) => a * b)
|
vm.set('multiply', (a: number, b: number) => a * b)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Usage in Bytecode**:
|
**Usage in Bytecode**:
|
||||||
|
|
@ -637,9 +637,9 @@ CALL ; Call it like any other function
|
||||||
|
|
||||||
**Native Function Types**:
|
**Native Function Types**:
|
||||||
|
|
||||||
1. **Auto-wrapped functions** (via `registerFunction`): Accept and return native TypeScript types (number, string, boolean, array, object, etc.). The VM automatically converts between Value types and native types.
|
1. **Auto-wrapped functions** (via `vm.set()`): Accept and return native TypeScript types (number, string, boolean, array, object, etc.). The VM automatically converts between Value types and native types.
|
||||||
|
|
||||||
2. **Value-based functions** (via `registerValueFunction`): Accept and return `Value` types directly for full control over type handling.
|
2. **Value-based functions** (via `vm.setValueFunction()`): Accept and return `Value` types directly for full control over type handling.
|
||||||
|
|
||||||
**Auto-Wrapping Behavior**:
|
**Auto-Wrapping Behavior**:
|
||||||
- Parameters: `Value` → native type (number, string, boolean, array, object, null, RegExp)
|
- Parameters: `Value` → native type (number, string, boolean, array, object, null, RegExp)
|
||||||
|
|
@ -655,27 +655,27 @@ CALL ; Call it like any other function
|
||||||
**Examples**:
|
**Examples**:
|
||||||
```typescript
|
```typescript
|
||||||
// Auto-wrapped native types
|
// Auto-wrapped native types
|
||||||
vm.registerFunction('add', (a: number, b: number) => a + b)
|
vm.set('add', (a: number, b: number) => a + b)
|
||||||
vm.registerFunction('greet', (name: string) => `Hello, ${name}!`)
|
vm.set('greet', (name: string) => `Hello, ${name}!`)
|
||||||
vm.registerFunction('range', (n: number) => Array.from({ length: n }, (_, i) => i))
|
vm.set('range', (n: number) => Array.from({ length: n }, (_, i) => i))
|
||||||
|
|
||||||
// With defaults
|
// With defaults
|
||||||
vm.registerFunction('greet', (name: string, greeting = 'Hello') => {
|
vm.set('greet', (name: string, greeting = 'Hello') => {
|
||||||
return `${greeting}, ${name}!`
|
return `${greeting}, ${name}!`
|
||||||
})
|
})
|
||||||
|
|
||||||
// Variadic functions
|
// Variadic functions
|
||||||
vm.registerFunction('sum', (...nums: number[]) => {
|
vm.set('sum', (...nums: number[]) => {
|
||||||
return nums.reduce((acc, n) => acc + n, 0)
|
return nums.reduce((acc, n) => acc + n, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Value-based for custom logic
|
// Value-based for custom logic
|
||||||
vm.registerValueFunction('customOp', (a: Value, b: Value): Value => {
|
vm.setValueFunction('customOp', (a: Value, b: Value): Value => {
|
||||||
return { type: 'number', value: toNumber(a) + toNumber(b) }
|
return { type: 'number', value: toNumber(a) + toNumber(b) }
|
||||||
})
|
})
|
||||||
|
|
||||||
// Async functions
|
// Async functions
|
||||||
vm.registerFunction('fetchData', async (url: string) => {
|
vm.set('fetchData', async (url: string) => {
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
return response.json()
|
return response.json()
|
||||||
})
|
})
|
||||||
|
|
@ -912,10 +912,10 @@ const vm = new VM(bytecode, {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Or register after construction
|
// Or register after construction
|
||||||
vm.registerFunction('multiply', (a: number, b: number) => a * b)
|
vm.set('multiply', (a: number, b: number) => a * b)
|
||||||
|
|
||||||
// Or use Value-based functions
|
// Or use Value-based functions
|
||||||
vm.registerValueFunction('customOp', (a: Value, b: Value): Value => {
|
vm.setValueFunction('customOp', (a: Value, b: Value): Value => {
|
||||||
return { type: 'number', value: toNumber(a) + toNumber(b) }
|
return { type: 'number', value: toNumber(a) + toNumber(b) }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ const bytecode = toBytecode(`
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('print', (...args: Value[]): Value => {
|
vm.set('print', (...args: Value[]): Value => {
|
||||||
console.log(...args.map(toString))
|
console.log(...args.map(toString))
|
||||||
return toNull()
|
return toNull()
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import type { Bytecode } from "./bytecode"
|
import type { Bytecode } from "./bytecode"
|
||||||
import type { Value, TypeScriptFunction } from "./value"
|
import type { Value } from "./value"
|
||||||
import { VM } from "./vm"
|
import { VM } from "./vm"
|
||||||
|
|
||||||
export async function run(bytecode: Bytecode, functions?: Record<string, TypeScriptFunction>): Promise<Value> {
|
export async function run(bytecode: Bytecode, globals?: Record<string, any>): Promise<Value> {
|
||||||
const vm = new VM(bytecode, functions)
|
const vm = new VM(bytecode, globals)
|
||||||
return await vm.run()
|
return await vm.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
22
src/vm.ts
22
src/vm.ts
|
|
@ -19,15 +19,15 @@ export class VM {
|
||||||
labels: Map<number, string> = new Map()
|
labels: Map<number, string> = new Map()
|
||||||
nativeFunctions: Map<string, NativeFunction> = new Map()
|
nativeFunctions: Map<string, NativeFunction> = new Map()
|
||||||
|
|
||||||
constructor(bytecode: Bytecode, globalFunctions?: Record<string, TypeScriptFunction>) {
|
constructor(bytecode: Bytecode, globals?: Record<string, any>) {
|
||||||
this.instructions = bytecode.instructions
|
this.instructions = bytecode.instructions
|
||||||
this.constants = bytecode.constants
|
this.constants = bytecode.constants
|
||||||
this.labels = bytecode.labels || new Map()
|
this.labels = bytecode.labels || new Map()
|
||||||
this.scope = new Scope()
|
this.scope = new Scope()
|
||||||
|
|
||||||
if (globalFunctions) {
|
if (globals) {
|
||||||
for (const name of Object.keys(globalFunctions))
|
for (const name of Object.keys(globals ?? {}))
|
||||||
this.registerFunction(name, globalFunctions[name]!)
|
this.set(name, globals[name])
|
||||||
|
|
||||||
this.scope = new Scope(this.scope)
|
this.scope = new Scope(this.scope)
|
||||||
}
|
}
|
||||||
|
|
@ -48,12 +48,18 @@ export class VM {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerFunction(name: string, fn: TypeScriptFunction) {
|
set(name: string, value: any) {
|
||||||
const wrapped = isWrapped(fn) ? fn as NativeFunction : wrapNative(this, fn)
|
if (typeof value === 'function')
|
||||||
this.scope.set(name, { type: 'native', fn: wrapped, value: '<function>' })
|
this.setFunction(name, value)
|
||||||
|
else
|
||||||
|
this.scope.set(name, toValue(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
registerValueFunction(name: string, fn: NativeFunction) {
|
setFunction(name: string, fn: TypeScriptFunction) {
|
||||||
|
this.scope.set(name, { type: 'native', fn: wrapNative(this, fn), value: '<function>' })
|
||||||
|
}
|
||||||
|
|
||||||
|
setValueFunction(name: string, fn: NativeFunction) {
|
||||||
this.scope.set(name, { type: 'native', fn, value: '<function>' })
|
this.scope.set(name, { type: 'native', fn, value: '<function>' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,7 @@ describe("functions parameter", () => {
|
||||||
expect(result).toEqual({ type: 'number', value: 200 })
|
expect(result).toEqual({ type: 'number', value: 200 })
|
||||||
})
|
})
|
||||||
|
|
||||||
test("can combine with manual registerFunction", async () => {
|
test("can combine with manual vm.set", async () => {
|
||||||
const bytecode = toBytecode(`
|
const bytecode = toBytecode(`
|
||||||
LOAD add
|
LOAD add
|
||||||
PUSH 5
|
PUSH 5
|
||||||
|
|
@ -127,7 +127,7 @@ describe("functions parameter", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Register another function manually
|
// Register another function manually
|
||||||
vm.registerFunction('subtract', (a: number, b: number) => a - b)
|
vm.set('subtract', (a: number, b: number) => a - b)
|
||||||
|
|
||||||
const result = await vm.run()
|
const result = await vm.run()
|
||||||
expect(result).toEqual({ type: 'number', value: 6 })
|
expect(result).toEqual({ type: 'number', value: 6 })
|
||||||
|
|
@ -224,7 +224,7 @@ describe("functions parameter", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Override with manual registration
|
// Override with manual registration
|
||||||
vm.registerFunction('getValue', () => 200)
|
vm.set('getValue', () => 200)
|
||||||
|
|
||||||
const result = await vm.run()
|
const result = await vm.run()
|
||||||
expect(result).toEqual({ type: 'number', value: 200 })
|
expect(result).toEqual({ type: 'number', value: 200 })
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ test("LOAD - basic function call", async () => {
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Register a Value-based function
|
// Register a Value-based function
|
||||||
vm.registerValueFunction('add', (a, b) => {
|
vm.setValueFunction('add', (a, b) => {
|
||||||
return toValue(toNumber(a) + toNumber(b))
|
return toValue(toNumber(a) + toNumber(b))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -36,7 +36,7 @@ test("LOAD - function with string manipulation", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerValueFunction('concat', (a, b) => {
|
vm.setValueFunction('concat', (a, b) => {
|
||||||
const aStr = a.type === 'string' ? a.value : toString(a)
|
const aStr = a.type === 'string' ? a.value : toString(a)
|
||||||
const bStr = b.type === 'string' ? b.value : toString(b)
|
const bStr = b.type === 'string' ? b.value : toString(b)
|
||||||
return toValue(aStr + ' ' + bStr)
|
return toValue(aStr + ' ' + bStr)
|
||||||
|
|
@ -57,7 +57,7 @@ test("LOAD - async function", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerValueFunction('asyncDouble', async (a) => {
|
vm.setValueFunction('asyncDouble', async (a) => {
|
||||||
// Simulate async operation
|
// Simulate async operation
|
||||||
await new Promise(resolve => setTimeout(resolve, 1))
|
await new Promise(resolve => setTimeout(resolve, 1))
|
||||||
return toValue(toNumber(a) * 2)
|
return toValue(toNumber(a) * 2)
|
||||||
|
|
@ -77,7 +77,7 @@ test("LOAD - function with no arguments", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerValueFunction('getAnswer', () => {
|
vm.setValueFunction('getAnswer', () => {
|
||||||
return toValue(42)
|
return toValue(42)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -98,7 +98,7 @@ test("LOAD - function with multiple arguments", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerValueFunction('sum', (...args) => {
|
vm.setValueFunction('sum', (...args) => {
|
||||||
const total = args.reduce((acc, val) => acc + toNumber(val), 0)
|
const total = args.reduce((acc, val) => acc + toNumber(val), 0)
|
||||||
return toValue(total)
|
return toValue(total)
|
||||||
})
|
})
|
||||||
|
|
@ -118,7 +118,7 @@ test("LOAD - function returns array", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerValueFunction('makeRange', (n) => {
|
vm.setValueFunction('makeRange', (n) => {
|
||||||
const count = toNumber(n)
|
const count = toNumber(n)
|
||||||
const arr = []
|
const arr = []
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
|
|
@ -165,7 +165,7 @@ test("LOAD - using result in subsequent operations", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerValueFunction('triple', (n) => {
|
vm.setValueFunction('triple', (n) => {
|
||||||
return toValue(toNumber(n) * 3)
|
return toValue(toNumber(n) * 3)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -186,7 +186,7 @@ test("Native function wrapping - basic sync function with native types", async (
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Register with native TypeScript types - auto-wraps!
|
// Register with native TypeScript types - auto-wraps!
|
||||||
vm.registerFunction('add', (a: number, b: number) => {
|
vm.set('add', (a: number, b: number) => {
|
||||||
return a + b
|
return a + b
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -206,7 +206,7 @@ test("Native function wrapping - async function with native types", async () =>
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Async native function
|
// Async native function
|
||||||
vm.registerFunction('asyncDouble', async (n: number) => {
|
vm.set('asyncDouble', async (n: number) => {
|
||||||
await new Promise(resolve => setTimeout(resolve, 1))
|
await new Promise(resolve => setTimeout(resolve, 1))
|
||||||
return n * 2
|
return n * 2
|
||||||
})
|
})
|
||||||
|
|
@ -228,7 +228,7 @@ test("Native function wrapping - string manipulation", async () => {
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Native string function
|
// Native string function
|
||||||
vm.registerFunction('concat', (a: string, b: string) => {
|
vm.set('concat', (a: string, b: string) => {
|
||||||
return a + ' ' + b
|
return a + ' ' + b
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -248,7 +248,7 @@ test("Native function wrapping - with default parameters", async () => {
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Function with default parameter (like NOSE commands)
|
// Function with default parameter (like NOSE commands)
|
||||||
vm.registerFunction('ls', (path: string, link = false) => {
|
vm.set('ls', (path: string, link = false) => {
|
||||||
return link ? `listing ${path} with links` : `listing ${path}`
|
return link ? `listing ${path} with links` : `listing ${path}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -268,7 +268,7 @@ test("Native function wrapping - returns array", async () => {
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Return native array - auto-converts to Value array
|
// Return native array - auto-converts to Value array
|
||||||
vm.registerFunction('makeRange', (n: number) => {
|
vm.set('makeRange', (n: number) => {
|
||||||
return Array.from({ length: n }, (_, i) => i)
|
return Array.from({ length: n }, (_, i) => i)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -297,7 +297,7 @@ test("Native function wrapping - returns object (becomes dict)", async () => {
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Return plain object - auto-converts to dict
|
// Return plain object - auto-converts to dict
|
||||||
vm.registerFunction('makeUser', (name: string, age: number) => {
|
vm.set('makeUser', (name: string, age: number) => {
|
||||||
return { name, age }
|
return { name, age }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -326,11 +326,11 @@ test("Native function wrapping - mixed with manual Value functions", async () =>
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Native function (auto-wrapped by registerFunction)
|
// Native function (auto-wrapped by set)
|
||||||
vm.registerFunction('nativeAdd', (n: number) => n + 10)
|
vm.set('nativeAdd', (n: number) => n + 10)
|
||||||
|
|
||||||
// Manual Value function (use registerValueFunction)
|
// Manual Value function (use setValueFunction)
|
||||||
vm.registerValueFunction('manualDouble', (v) => {
|
vm.setValueFunction('manualDouble', (v) => {
|
||||||
return toValue(toNumber(v) * 2)
|
return toValue(toNumber(v) * 2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -349,7 +349,7 @@ test("Named arguments - basic named arg", async () => {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('greet', (name: string) => `Hello, ${name}!`)
|
vm.set('greet', (name: string) => `Hello, ${name}!`)
|
||||||
|
|
||||||
const result = await vm.run()
|
const result = await vm.run()
|
||||||
expect(result).toEqual({ type: 'string', value: 'Hello, Alice!' })
|
expect(result).toEqual({ type: 'string', value: 'Hello, Alice!' })
|
||||||
|
|
@ -367,7 +367,7 @@ test("Named arguments - mixed positional and named", async () => {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('makeUser', (name: string, age: number) => {
|
vm.set('makeUser', (name: string, age: number) => {
|
||||||
return { name, age }
|
return { name, age }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -393,7 +393,7 @@ test("Named arguments - named takes priority over positional", async () => {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('add', (a: number, b: number) => a + b)
|
vm.set('add', (a: number, b: number) => a + b)
|
||||||
|
|
||||||
const result = await vm.run()
|
const result = await vm.run()
|
||||||
// Named args should be: a=5, b=10
|
// Named args should be: a=5, b=10
|
||||||
|
|
@ -412,7 +412,7 @@ test("Named arguments - with defaults", async () => {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('greet', (name: string, greeting = 'Hello') => {
|
vm.set('greet', (name: string, greeting = 'Hello') => {
|
||||||
return `${greeting}, ${name}!`
|
return `${greeting}, ${name}!`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -433,7 +433,7 @@ test("Named arguments - override defaults with named args", async () => {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('greet', (name: string, greeting = 'Hello') => {
|
vm.set('greet', (name: string, greeting = 'Hello') => {
|
||||||
return `${greeting}, ${name}!`
|
return `${greeting}, ${name}!`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -455,7 +455,7 @@ test("Named arguments - with variadic function", async () => {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('sum', (multiplier: number, ...nums: number[]) => {
|
vm.set('sum', (multiplier: number, ...nums: number[]) => {
|
||||||
const total = nums.reduce((acc, n) => acc + n, 0)
|
const total = nums.reduce((acc, n) => acc + n, 0)
|
||||||
return total * multiplier
|
return total * multiplier
|
||||||
})
|
})
|
||||||
|
|
@ -466,7 +466,7 @@ test("Named arguments - with variadic function", async () => {
|
||||||
|
|
||||||
test("Named arguments - works with both wrapped and non-wrapped functions", async () => {
|
test("Named arguments - works with both wrapped and non-wrapped functions", async () => {
|
||||||
const bytecode = toBytecode(`
|
const bytecode = toBytecode(`
|
||||||
; Test wrapped function (registerFunction)
|
; Test wrapped function (vm.set)
|
||||||
LOAD wrappedAdd
|
LOAD wrappedAdd
|
||||||
PUSH "a"
|
PUSH "a"
|
||||||
PUSH 5
|
PUSH 5
|
||||||
|
|
@ -477,7 +477,7 @@ test("Named arguments - works with both wrapped and non-wrapped functions", asyn
|
||||||
CALL
|
CALL
|
||||||
STORE result1
|
STORE result1
|
||||||
|
|
||||||
; Test non-wrapped function (registerValueFunction)
|
; Test non-wrapped function (vm.setValueFunction)
|
||||||
LOAD valueAdd
|
LOAD valueAdd
|
||||||
PUSH "a"
|
PUSH "a"
|
||||||
PUSH 3
|
PUSH 3
|
||||||
|
|
@ -497,10 +497,10 @@ test("Named arguments - works with both wrapped and non-wrapped functions", asyn
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Wrapped function - auto-converts types
|
// Wrapped function - auto-converts types
|
||||||
vm.registerFunction('wrappedAdd', (a: number, b: number) => a + b)
|
vm.set('wrappedAdd', (a: number, b: number) => a + b)
|
||||||
|
|
||||||
// Non-wrapped function - works directly with Values
|
// Non-wrapped function - works directly with Values
|
||||||
vm.registerValueFunction('valueAdd', (a, b) => {
|
vm.setValueFunction('valueAdd', (a, b) => {
|
||||||
return toValue(toNumber(a) + toNumber(b))
|
return toValue(toNumber(a) + toNumber(b))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -523,7 +523,7 @@ test("@named pattern - basic atNamed parameter", async () => {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('greet', (atNamed: any = {}) => {
|
vm.set('greet', (atNamed: any = {}) => {
|
||||||
const name = atNamed.name || 'Unknown'
|
const name = atNamed.name || 'Unknown'
|
||||||
const greeting = atNamed.greeting || 'Hello'
|
const greeting = atNamed.greeting || 'Hello'
|
||||||
const extra = atNamed.extra || ''
|
const extra = atNamed.extra || ''
|
||||||
|
|
@ -548,7 +548,7 @@ test('@named pattern - atNamed parameters with varadic args', async () => {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('cmd', (atNamed: any = {}, ...rest: string[]) => {
|
vm.set('cmd', (atNamed: any = {}, ...rest: string[]) => {
|
||||||
return { rest, namedArg: atNamed['named'] }
|
return { rest, namedArg: atNamed['named'] }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -573,7 +573,7 @@ test("@named pattern - mixed positional and atOptions", async () => {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('configure', (name: string, atOptions: any = {}) => {
|
vm.set('configure', (name: string, atOptions: any = {}) => {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
debug: atOptions.debug || false,
|
debug: atOptions.debug || false,
|
||||||
|
|
@ -600,7 +600,7 @@ test("@named pattern - with default empty object", async () => {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('build', (name: string, atConfig: any = {}) => {
|
vm.set('build', (name: string, atConfig: any = {}) => {
|
||||||
return `Building ${name} with ${Object.keys(atConfig).length} options`
|
return `Building ${name} with ${Object.keys(atConfig).length} options`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -625,7 +625,7 @@ test("@named pattern - collects only unmatched named args", async () => {
|
||||||
`)
|
`)
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
vm.registerFunction('process', (file: string, mode: string, atOptions: any = {}) => {
|
vm.set('process', (file: string, mode: string, atOptions: any = {}) => {
|
||||||
// file and mode are matched, extra1 and extra2 go to atOptions
|
// file and mode are matched, extra1 and extra2 go to atOptions
|
||||||
return {
|
return {
|
||||||
file,
|
file,
|
||||||
|
|
@ -675,7 +675,7 @@ test("Native function receives Reef function as callback - basic map", async ()
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Native function that takes an array and a callback
|
// Native function that takes an array and a callback
|
||||||
vm.registerFunction('map', async (array: any[], callback: Function) => {
|
vm.set('map', async (array: any[], callback: Function) => {
|
||||||
const results = []
|
const results = []
|
||||||
for (const item of array) {
|
for (const item of array) {
|
||||||
results.push(await callback(item))
|
results.push(await callback(item))
|
||||||
|
|
@ -732,7 +732,7 @@ test("Native function receives Reef function - iterator pattern with each", asyn
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Native 'each' function (like Ruby's each)
|
// Native 'each' function (like Ruby's each)
|
||||||
vm.registerFunction('each', async (array: any[], callback: Function) => {
|
vm.set('each', async (array: any[], callback: Function) => {
|
||||||
for (const item of array) {
|
for (const item of array) {
|
||||||
await callback(item)
|
await callback(item)
|
||||||
}
|
}
|
||||||
|
|
@ -773,7 +773,7 @@ test("Native function receives Reef function - filter pattern", async () => {
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Native filter function
|
// Native filter function
|
||||||
vm.registerFunction('filter', async (array: any[], predicate: Function) => {
|
vm.set('filter', async (array: any[], predicate: Function) => {
|
||||||
const results = []
|
const results = []
|
||||||
for (const item of array) {
|
for (const item of array) {
|
||||||
if (await predicate(item)) {
|
if (await predicate(item)) {
|
||||||
|
|
@ -824,7 +824,7 @@ test("Native function receives Reef function - closure capturing", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('map', async (array: any[], callback: Function) => {
|
vm.set('map', async (array: any[], callback: Function) => {
|
||||||
const results = []
|
const results = []
|
||||||
for (const item of array) {
|
for (const item of array) {
|
||||||
results.push(await callback(item))
|
results.push(await callback(item))
|
||||||
|
|
@ -873,7 +873,7 @@ test("Native function receives Reef function - multiple arguments", async () =>
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Native reduce function (accumulator, array, callback)
|
// Native reduce function (accumulator, array, callback)
|
||||||
vm.registerFunction('reduce', async (array: any[], initial: any, callback: Function) => {
|
vm.set('reduce', async (array: any[], initial: any, callback: Function) => {
|
||||||
let acc = initial
|
let acc = initial
|
||||||
for (const item of array) {
|
for (const item of array) {
|
||||||
acc = await callback(acc, item)
|
acc = await callback(acc, item)
|
||||||
|
|
@ -913,7 +913,7 @@ test("Native function receives Reef function - returns non-primitive", async ()
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('map', async (array: any[], callback: Function) => {
|
vm.set('map', async (array: any[], callback: Function) => {
|
||||||
const results = []
|
const results = []
|
||||||
for (const item of array) {
|
for (const item of array) {
|
||||||
results.push(await callback(item))
|
results.push(await callback(item))
|
||||||
|
|
@ -965,7 +965,7 @@ test("Native function calls Reef function - basic", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('process', async function (n: number) {
|
vm.setFunction('process', async function (n: number) {
|
||||||
return await this.call('double', n)
|
return await this.call('double', n)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1006,7 +1006,7 @@ test("Native function calls multiple Reef functions", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('orchestrate', async function (n: number) {
|
vm.setFunction('orchestrate', async function (n: number) {
|
||||||
const doubled = await this.call('double', n)
|
const doubled = await this.call('double', n)
|
||||||
const tripled = await this.call('triple', n)
|
const tripled = await this.call('triple', n)
|
||||||
|
|
||||||
|
|
@ -1064,7 +1064,7 @@ test("Native function conditionally calls Reef functions", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('validate', async function (n: number) {
|
vm.setFunction('validate', async function (n: number) {
|
||||||
const isPositive = await this.call('is_positive', n)
|
const isPositive = await this.call('is_positive', n)
|
||||||
|
|
||||||
if (isPositive) {
|
if (isPositive) {
|
||||||
|
|
@ -1105,7 +1105,7 @@ test("Native function calls Reef function with closure", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('transform', async function (n: number) {
|
vm.setFunction('transform', async function (n: number) {
|
||||||
return await this.call('multiply_by_ten', n)
|
return await this.call('multiply_by_ten', n)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1143,7 +1143,7 @@ test("Native function uses Reef function as filter predicate", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('filter_evens', async function (array: any[]) {
|
vm.setFunction('filter_evens', async function (array: any[]) {
|
||||||
const results = []
|
const results = []
|
||||||
for (const item of array) {
|
for (const item of array) {
|
||||||
if (await this.call('is_even', item)) {
|
if (await this.call('is_even', item)) {
|
||||||
|
|
@ -1199,7 +1199,7 @@ test("Reef calls native calls Reef - roundtrip", async () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('native_helper', async function (n: number) {
|
vm.setFunction('native_helper', async function (n: number) {
|
||||||
const squared = await this.call('square', n)
|
const squared = await this.call('square', n)
|
||||||
return squared + 1 // Add 1 to the squared result
|
return squared + 1 // Add 1 to the squared result
|
||||||
})
|
})
|
||||||
|
|
@ -1232,7 +1232,7 @@ test("Native function calls Reef function with multiple arguments", async () =>
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('calculate', async function () {
|
vm.setFunction('calculate', async function () {
|
||||||
return await this.call('add_three', 10, 20, 30)
|
return await this.call('add_three', 10, 20, 30)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1266,7 +1266,7 @@ test("Native function calls Reef function that returns complex type", async () =
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('create_user', async function () {
|
vm.setFunction('create_user', async function () {
|
||||||
return await this.call('make_user', "Alice", 30)
|
return await this.call('make_user', "Alice", 30)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1289,7 +1289,7 @@ test("Native function calls non-existent Reef function - throws error", async ()
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('bad_caller', async function () {
|
vm.setFunction('bad_caller', async function () {
|
||||||
return await this.call('nonexistent', 42)
|
return await this.call('nonexistent', 42)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1387,7 +1387,7 @@ test("Native function receives Reef closure with default params - calls with nam
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Native function that calls the Reef closure with named arguments
|
// Native function that calls the Reef closure with named arguments
|
||||||
vm.registerFunction('map', async (array: any[], callback: Function) => {
|
vm.set('map', async (array: any[], callback: Function) => {
|
||||||
const results = []
|
const results = []
|
||||||
for (const item of array) {
|
for (const item of array) {
|
||||||
// Call with named argument to override the default
|
// Call with named argument to override the default
|
||||||
|
|
@ -1457,7 +1457,7 @@ test("Native function receives Reef closure with variadic parameters", async ()
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Native function that calls the variadic Reef closure with multiple args
|
// Native function that calls the variadic Reef closure with multiple args
|
||||||
vm.registerFunction('process', async (callback: Function) => {
|
vm.set('process', async (callback: Function) => {
|
||||||
// Call with varying number of arguments
|
// Call with varying number of arguments
|
||||||
const result1 = await callback(1, 2, 3)
|
const result1 = await callback(1, 2, 3)
|
||||||
const result2 = await callback(10, 20)
|
const result2 = await callback(10, 20)
|
||||||
|
|
@ -1508,7 +1508,7 @@ test("Native function receives Reef closure with @named parameter", async () =>
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Native function that calls Reef closure with named arguments
|
// Native function that calls Reef closure with named arguments
|
||||||
vm.registerFunction('format_messages', async (messages: any[], formatter: Function) => {
|
vm.set('format_messages', async (messages: any[], formatter: Function) => {
|
||||||
const results = []
|
const results = []
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
// Call with named arguments
|
// Call with named arguments
|
||||||
|
|
@ -1553,7 +1553,7 @@ test("Native function receives Reef closure - calls with only named arguments",
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Native function that calls Reef closure with ONLY named arguments
|
// Native function that calls Reef closure with ONLY named arguments
|
||||||
vm.registerFunction('compute', async (callback: Function) => {
|
vm.set('compute', async (callback: Function) => {
|
||||||
// First call with all named args
|
// First call with all named args
|
||||||
const result1 = await callback({ x: 10, y: 20, z: 30 })
|
const result1 = await callback({ x: 10, y: 20, z: 30 })
|
||||||
|
|
||||||
|
|
@ -1619,7 +1619,7 @@ test("Native function receives Reef closure with mixed positional and variadic",
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Native function that calls Reef closure with mixed args
|
// Native function that calls Reef closure with mixed args
|
||||||
vm.registerFunction('process_numbers', async (calculator: Function) => {
|
vm.set('process_numbers', async (calculator: Function) => {
|
||||||
// Call with fixed multiplier and variadic numbers
|
// Call with fixed multiplier and variadic numbers
|
||||||
const result1 = await calculator(10, 1, 2, 3) // 10 * (1+2+3) = 60
|
const result1 = await calculator(10, 1, 2, 3) // 10 * (1+2+3) = 60
|
||||||
const result2 = await calculator(5, 4, 6) // 5 * (4+6) = 50
|
const result2 = await calculator(5, 4, 6) // 5 * (4+6) = 50
|
||||||
|
|
|
||||||
|
|
@ -399,7 +399,7 @@ describe("RegExp", () => {
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
// Register a native function that takes a string and regex
|
// Register a native function that takes a string and regex
|
||||||
vm.registerFunction('match', (str: string, pattern: RegExp) => {
|
vm.set('match', (str: string, pattern: RegExp) => {
|
||||||
return pattern.test(str)
|
return pattern.test(str)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -422,7 +422,7 @@ describe("RegExp", () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('replace', (str: string, pattern: RegExp, replacement: string) => {
|
vm.set('replace', (str: string, pattern: RegExp, replacement: string) => {
|
||||||
return str.replace(pattern, replacement)
|
return str.replace(pattern, replacement)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -444,7 +444,7 @@ describe("RegExp", () => {
|
||||||
|
|
||||||
const vm = new VM(bytecode)
|
const vm = new VM(bytecode)
|
||||||
|
|
||||||
vm.registerFunction('extractNumbers', (str: string, pattern: RegExp) => {
|
vm.set('extractNumbers', (str: string, pattern: RegExp) => {
|
||||||
return str.match(pattern) || []
|
return str.match(pattern) || []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user