import { type Value, type NativeFunction, fromValue, toValue } from "./value" import { VM } from "./vm" export type ParamInfo = { params: string[] defaults: Record variadic: boolean named: boolean } const WRAPPED_MARKER = Symbol('reef-wrapped') export function wrapNative(vm: VM, fn: Function): (this: VM, ...args: Value[]) => Promise { if ((fn as any).raw) return fn as (this: VM, ...args: Value[]) => Promise 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 = {} 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 } }