diff --git a/src/function.ts b/src/function.ts index 61d0e87..36c107a 100644 --- a/src/function.ts +++ b/src/function.ts @@ -14,7 +14,7 @@ export function wrapNative(vm: VM, fn: Function): (this: VM, ...args: Value[]) = const wrapped = async function (this: VM, ...values: Value[]) { const nativeArgs = values.map(arg => fromValue(arg, vm)) const result = await fn.call(this, ...nativeArgs) - return toValue(result) + return toValue(result, this) } const wrappedObj = wrapped as any diff --git a/src/value.ts b/src/value.ts index 175c4a4..be65dea 100644 --- a/src/value.ts +++ b/src/value.ts @@ -1,3 +1,4 @@ +import { wrapNative } from "./function" import { OpCode } from "./opcode" import { Scope } from "./scope" import { VM } from "./vm" @@ -41,7 +42,7 @@ export function isValue(v: any): boolean { return !!(v && typeof v === 'object' && v.type && 'value' in v) } -export function toValue(v: any): Value /* throws */ { +export function toValue(v: any, vm?: VM): Value /* throws */ { if (v === null || v === undefined) return { type: 'null', value: null } @@ -49,7 +50,7 @@ export function toValue(v: any): Value /* throws */ { return v as Value if (Array.isArray(v)) - return { type: 'array', value: v.map(toValue) } + return { type: 'array', value: v.map(x => toValue(x, vm)) } if (v instanceof RegExp) return { type: 'regex', value: v } @@ -64,11 +65,12 @@ export function toValue(v: any): Value /* throws */ { case 'function': if ((v as any)[REEF_FUNCTION]) return (v as any)[REEF_FUNCTION] - throw new Error("can't toValue() a js function yet") + if (!vm) throw new Error("can't toValue() function without a vm") + return { type: 'native', fn: wrapNative(vm, v), value: '' } case 'object': const dict: Dict = new Map() - for (const key of Object.keys(v)) dict.set(key, toValue(v[key])) + for (const key of Object.keys(v)) dict.set(key, toValue(v[key], vm)) return { type: 'dict', value: dict } default: @@ -210,10 +212,10 @@ export function fnFromValue(fn: Value, vm: VM): Function { newVM.scope = fn.parentScope newVM.stack.push(fn) - newVM.stack.push(...positional.map(toValue)) + newVM.stack.push(...positional.map(x => toValue(x, vm))) for (const [key, val] of Object.entries(named)) { newVM.stack.push(toValue(key)) - newVM.stack.push(toValue(val)) + newVM.stack.push(toValue(val, vm)) } newVM.stack.push(toValue(positional.length)) newVM.stack.push(toValue(Object.keys(named).length)) diff --git a/src/vm.ts b/src/vm.ts index 5d8b9d0..bfccc5c 100644 --- a/src/vm.ts +++ b/src/vm.ts @@ -49,14 +49,11 @@ export class VM { } set(name: string, value: any) { - if (typeof value === 'function') - this.setFunction(name, value) - else - this.scope.set(name, toValue(value)) + this.scope.set(name, toValue(value, this)) } setFunction(name: string, fn: TypeScriptFunction) { - this.scope.set(name, { type: 'native', fn: wrapNative(this, fn), value: '' }) + this.scope.set(name, toValue(fn, this)) } setValueFunction(name: string, fn: NativeFunction) { diff --git a/tests/native.test.ts b/tests/native.test.ts index 67a8d0f..6aa3c54 100644 --- a/tests/native.test.ts +++ b/tests/native.test.ts @@ -1759,4 +1759,28 @@ test("builtin global functions are placed into a higher level scope", async () = expect(globals).toEqual(['sum', 'greet']) expect(locals).toEqual(['x']) +}) + + +test("builtin global scope can be values too", async () => { + const bytecode = toBytecode(` + PUSH 1 + STORE x + HALT + `) + + const vm = new VM(bytecode, { + pi: 3.14, + universe: true, + algorithms: { + bigOne: () => false, + } + }) + await vm.run() + + const locals = Array.from(vm.scope.locals.entries()).map(([name,]) => name) + const globals = Array.from(vm.scope.parent!.locals.entries()).map(([name,]) => name) + + expect(globals).toEqual(['pi', 'universe', 'algorithms']) + expect(locals).toEqual(['x']) }) \ No newline at end of file