support roundtrip value conversions

This commit is contained in:
Chris Wanstrath 2025-10-26 12:52:57 -07:00
parent d50b143c9d
commit 97b6722a11
3 changed files with 43 additions and 2 deletions

View File

@ -13,5 +13,5 @@ export { wrapNative, isWrapped, type ParamInfo, extractParamInfo, getOriginalFun
export { OpCode } from "./opcode" export { OpCode } from "./opcode"
export { Scope } from "./scope" export { Scope } from "./scope"
export type { Value, TypeScriptFunction, NativeFunction } from "./value" export type { Value, TypeScriptFunction, NativeFunction } from "./value"
export { isValue, toValue, toString, toNumber, fromValue, toNull } from "./value" export { isValue, toValue, toString, toNumber, fromValue, toNull, fnFromValue } from "./value"
export { VM } from "./vm" export { VM } from "./vm"

View File

@ -4,6 +4,7 @@ import { VM } from "./vm"
export type NativeFunction = (...args: Value[]) => Promise<Value> | Value export type NativeFunction = (...args: Value[]) => Promise<Value> | Value
export type TypeScriptFunction = (this: VM, ...args: any[]) => any export type TypeScriptFunction = (this: VM, ...args: any[]) => any
const REEF_FUNCTION = Symbol('__reefFunction')
export type Value = export type Value =
| { type: 'null', value: null } | { type: 'null', value: null }
@ -61,6 +62,8 @@ export function toValue(v: any): Value /* throws */ {
case 'string': case 'string':
return { type: 'string', value: v } return { type: 'string', value: v }
case 'function': case 'function':
if ((v as any)[REEF_FUNCTION])
return (v as any)[REEF_FUNCTION]
throw new Error("can't toValue() a js function yet") throw new Error("can't toValue() a js function yet")
case 'object': case 'object':
const dict: Dict = new Map() const dict: Dict = new Map()
@ -190,7 +193,7 @@ export function fnFromValue(fn: Value, vm: VM): Function {
if (fn.type !== 'function') if (fn.type !== 'function')
throw new Error('Value is not a function') throw new Error('Value is not a function')
return async function (...args: any[]) { const wrapper = async function (...args: any[]) {
let positional: any[] = args let positional: any[] = args
let named: Record<string, any> = {} let named: Record<string, any> = {}
@ -226,4 +229,9 @@ export function fnFromValue(fn: Value, vm: VM): Function {
return fromValue(newVM.stack.pop() || toNull(), vm) return fromValue(newVM.stack.pop() || toNull(), vm)
} }
// support roundtrips, eg fromValue(toValue(fn))
; (wrapper as any)[REEF_FUNCTION] = fn
return wrapper
} }

View File

@ -80,3 +80,36 @@ test("isValue - edge cases with type and value properties", () => {
expect(isValue({ type: 'number', val: 42 })).toBe(false) expect(isValue({ type: 'number', val: 42 })).toBe(false)
expect(isValue({ typ: 'number', value: 42 })).toBe(false) expect(isValue({ typ: 'number', value: 42 })).toBe(false)
}) })
test("toValue - converts wrapped Reef functions back to original Value", async () => {
const { VM, toBytecode, fnFromValue } = await import("#reef")
// Create a Reef function
const bytecode = toBytecode([
["MAKE_FUNCTION", ["x"], ".body"],
["STORE", "add1"],
["JUMP", ".end"],
[".body:"],
["LOAD", "x"],
["PUSH", 1],
["ADD"],
["RETURN"],
[".end:"],
["HALT"]
])
const vm = new VM(bytecode)
await vm.run()
const reefFunction = vm.scope.get("add1")!
expect(reefFunction.type).toBe("function")
// Convert to JS function
const jsFunction = fnFromValue(reefFunction, vm)
expect(typeof jsFunction).toBe("function")
// Convert back to Value - should return the original Reef function
const backToValue = toValue(jsFunction)
expect(backToValue).toBe(reefFunction) // Same reference
expect(backToValue.type).toBe("function")
})