113 lines
3.4 KiB
TypeScript
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 }
|
|
}
|