fix nested globals

This commit is contained in:
Chris Wanstrath 2025-10-28 22:59:51 -07:00
parent e542070677
commit 052f989e82
4 changed files with 35 additions and 12 deletions

View File

@ -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

View File

@ -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: '<function>' }
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))

View File

@ -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: '<function>' })
this.scope.set(name, toValue(fn, this))
}
setValueFunction(name: string, fn: NativeFunction) {

View File

@ -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'])
})