ReefVM/src/function.ts

113 lines
3.4 KiB
TypeScript

import { type Value, type NativeFunction, fromValue, toValue } from "./value"
import { VM } from "./vm"
export type ParamInfo = {
params: string[]
defaults: Record<string, Value>
variadic: boolean
named: boolean
}
const WRAPPED_MARKER = Symbol('reef-wrapped')
export function wrapNative(vm: VM, fn: Function): (this: VM, ...args: Value[]) => Promise<Value> {
if ((fn as any).raw) return fn as (this: VM, ...args: Value[]) => Promise<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, this)
}
const wrappedObj = wrapped as any
wrappedObj[WRAPPED_MARKER] = true
// Store the original function for param extraction
wrappedObj.originalFn = fn
return wrapped
}
export function isWrapped(fn: Function): boolean {
return !!(fn as any)[WRAPPED_MARKER]
}
export function getOriginalFunction(fn: NativeFunction): Function {
return (fn as any).originalFn || fn
}
export function extractParamInfo(fn: Function): ParamInfo {
const params: string[] = []
const defaults: Record<string, Value> = {}
let variadic = false
let named = false
const fnStr = fn.toString()
// Match function signature: function(a, b) or (a, b) => or async (a, b) =>
const match = fnStr.match(/(?:function\s*.*?\(|^\s*\(|^\s*async\s*\(|^\s*async\s+function\s*.*?\()([^)]*)\)/)
if (!match || !match[1]) {
return { params, defaults, variadic, named }
}
const paramStr = match[1].trim()
if (!paramStr) {
return { params, defaults, variadic, named }
}
// Split parameters by comma (naive - doesn't handle nested objects/arrays)
const paramParts = paramStr.split(',').map(p => p.trim())
for (const part of paramParts) {
// Check for rest parameters (...rest)
if (part.startsWith('...')) {
variadic = true
const paramName = part.slice(3).trim()
params.push(paramName)
}
// Check for default values (name = value)
else if (part.includes('=')) {
const eqIndex = part.indexOf('=')
const paramName = part.slice(0, eqIndex).trim()
const defaultStr = part.slice(eqIndex + 1).trim()
params.push(paramName)
// Check if this is a named parameter (atXxx pattern)
if (/^at[A-Z]/.test(paramName)) {
named = true
}
// Try to parse the default value (only simple literals)
try {
if (defaultStr === 'null') {
defaults[paramName] = toValue(null)
} else if (defaultStr === 'true') {
defaults[paramName] = toValue(true)
} else if (defaultStr === 'false') {
defaults[paramName] = toValue(false)
} else if (/^-?\d+(\.\d+)?$/.test(defaultStr)) {
defaults[paramName] = toValue(parseFloat(defaultStr))
} else if (/^['"].*['"]$/.test(defaultStr)) {
defaults[paramName] = toValue(defaultStr.slice(1, -1))
}
// For complex defaults (like {}), we skip them and let the function's own default be used
} catch {
// If parsing fails, ignore the default
}
} else {
// Regular parameter
const paramName = part.trim()
params.push(paramName)
// Check if this is a named parameter (atXxx pattern)
if (/^at[A-Z]/.test(paramName)) {
named = true
}
}
}
return { params, defaults, variadic, named }
}