type PhpNullish = null | undefined
type PhpInput = {} | PhpNullish
type PhpScalar = string | number | boolean
type PhpPrimitive = PhpScalar | bigint
type NumericLike = number | bigint | string
type PhpList<T = PhpInput> = T[]
type PhpAssoc<T = PhpInput> = { [key: string]: T }
type PhpArrayLike<T = PhpInput> = PhpList<T> | PhpAssoc<T>
type PhpFunctionValue = (...args: PhpInput[]) => PhpInput
interface PhpRuntimeAssoc { [key: string]: PhpRuntimeValue }
interface PhpRuntimeList extends Array<PhpRuntimeValue> {}
type PhpRuntimeValue = PhpPrimitive | PhpNullish | PhpRuntimeList | PhpRuntimeAssoc | PhpFunctionValue
type PhpCallableArgs = PhpInput[]
type PhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput> = ( ...args: TArgs ) => TResult
type PhpCallableScope = PhpInput
type PhpCallableTuple<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput> = readonly [ PhpCallableScope, string | PhpCallable<TArgs, TResult>, ]
type PhpCallableDescriptor<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput> = | string | PhpCallable<TArgs, TResult> | PhpCallableTuple<TArgs, TResult>
type PhpKeyComparatorDescriptor = PhpCallableDescriptor<[string, string], NumericLike>
function isPhpList<T = PhpInput>(value: PhpInput): value is PhpList<T> { return Array.isArray(value) }
function isObjectLike(value: PhpInput): value is PhpArrayLike<PhpInput> { return typeof value === 'object' && value !== null }
function isPhpAssocObject<T = PhpInput>(value: PhpInput): value is PhpAssoc<T> { return isObjectLike(value) && !isPhpList(value) }
function isPhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput>( value: PhpInput, ): value is PhpCallable<TArgs, TResult> { return typeof value === 'function' }
interface IniEntry { local_value?: PhpInput }
type LocaleEntry = PhpAssoc<PhpInput> & { sorting?: (left: PhpInput, right: PhpInput) => number }
type LocaleCategoryMap = PhpAssoc<string | undefined>
interface LocutusRuntimeContainer { php?: PhpAssoc<PhpInput> }
interface PhpGlobalProcessLike { env?: PhpAssoc<string | undefined> }
interface PhpGlobalBufferLike { from?: (...args: PhpInput[]) => PhpInput }
interface PhpGlobalKnownEntryMap { process: PhpGlobalProcessLike Buffer: PhpGlobalBufferLike }
type GlobalWithLocutus = { $locutus?: LocutusRuntimeContainer [key: string]: PhpInput }
interface PhpRuntimeState { ini: PhpAssoc<IniEntry | undefined> locales: PhpAssoc<LocaleEntry | undefined> localeCategories: LocaleCategoryMap pointers: PhpList<PhpInput> locale_default: string | undefined }
const isIniBag = (value: PhpInput): value is PhpAssoc<IniEntry | undefined> => isPhpAssocObject<IniEntry | undefined>(value)
const isLocaleBag = (value: PhpInput): value is PhpAssoc<LocaleEntry | undefined> => isPhpAssocObject<LocaleEntry | undefined>(value)
const isLocaleCategoryBag = (value: PhpInput): value is LocaleCategoryMap => isPhpAssocObject<string | undefined>(value)
const globalContext: GlobalWithLocutus = typeof window === 'object' && window !== null ? window : typeof global === 'object' && global !== null ? global : {}
const ensurePhpRuntimeObject = (): PhpAssoc<PhpInput> => { let locutus = globalContext.$locutus if (typeof locutus !== 'object' || locutus === null) { locutus = {} globalContext.$locutus = locutus }
let php = locutus.php if (typeof php !== 'object' || php === null) { php = {} locutus.php = php }
return php }
function ensurePhpRuntimeState(): PhpRuntimeState { const php = ensurePhpRuntimeObject() const iniValue = php.ini const localesValue = php.locales const localeCategoriesValue = php.localeCategories const pointersValue = php.pointers
const ini = isIniBag(iniValue) ? iniValue : {} const locales = isLocaleBag(localesValue) ? localesValue : {} const localeCategories = isLocaleCategoryBag(localeCategoriesValue) ? localeCategoriesValue : {} const pointers: PhpList<PhpInput> = Array.isArray(pointersValue) ? pointersValue : []
if (iniValue !== ini) { php.ini = ini } if (localesValue !== locales) { php.locales = locales } if (localeCategoriesValue !== localeCategories) { php.localeCategories = localeCategories } if (pointersValue !== pointers) { php.pointers = pointers }
const localeDefaultValue = php.locale_default const localeDefault = typeof localeDefaultValue === 'string' ? localeDefaultValue : undefined
return { ini, locales, localeCategories, pointers, locale_default: localeDefault, } }
function getPhpGlobalEntry<TKey extends keyof PhpGlobalKnownEntryMap>( key: TKey, ): PhpGlobalKnownEntryMap[TKey] | undefined
function getPhpGlobalEntry(key: string): PhpInput | undefined
function getPhpGlobalEntry(key: string): PhpInput | undefined { const value = globalContext[key] return typeof value === 'undefined' ? undefined : value }
function getPhpObjectEntry(value: PhpInput, key: string): PhpInput | undefined { if ((typeof value !== 'object' && typeof value !== 'function') || value === null) { return undefined }
let current: object | null = value while (current) { const descriptor = Object.getOwnPropertyDescriptor(current, key) if (descriptor) { if (typeof descriptor.get === 'function') { const getterValue = descriptor.get.call(value) return typeof getterValue === 'undefined' ? undefined : getterValue } const directValue = descriptor.value return typeof directValue === 'undefined' ? undefined : directValue } current = Object.getPrototypeOf(current) }
return undefined }
type CallbackValue = PhpInput
interface CallbackResolverOptions { invalidMessage: string missingScopeMessage?: (scopeName: string) => string }
interface ResolvedCallback<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = CallbackValue> { fn: PhpCallable<TArgs, TResult> scope: CallbackValue }
function resolvePhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = CallbackValue>( callback: PhpCallableDescriptor<TArgs, TResult>, options: CallbackResolverOptions, ): ResolvedCallback<TArgs, TResult> { if (isPhpCallable<TArgs, TResult>(callback)) { return { fn: callback, scope: null } }
if (typeof callback === 'string') { const candidate = getPhpGlobalEntry(callback) if (isPhpCallable<TArgs, TResult>(candidate)) { return { fn: candidate, scope: null } } throw new Error(options.invalidMessage) }
if (Array.isArray(callback) && callback.length >= 2) { const scopeDescriptor = callback[0] const callableDescriptor = callback[1]
let scope: CallbackValue if (typeof scopeDescriptor === 'string') { scope = getPhpGlobalEntry(scopeDescriptor) if (typeof scope === 'undefined' && options.missingScopeMessage) { throw new Error(options.missingScopeMessage(scopeDescriptor)) } } else { scope = scopeDescriptor }
if (isPhpCallable<TArgs, TResult>(callableDescriptor)) { return { fn: callableDescriptor, scope } }
if (typeof callableDescriptor === 'string' && (isObjectLike(scope) || typeof scope === 'function')) { const candidate = getPhpObjectEntry(scope, callableDescriptor) if (isPhpCallable<TArgs, TResult>(candidate)) { return { fn: candidate, scope } } } }
throw new Error(options.invalidMessage) }
type SortContextValue = PhpRuntimeValue
function uksort<T>( this: PhpAssoc<SortContextValue> & { window?: PhpAssoc<SortContextValue> }, inputArr: Record<string, T>, sorter: PhpKeyComparatorDescriptor, ): boolean | Record<string, T> {
const tmpArr: Record<string, T> = {} const keys: string[] = [] let i = 0 let k = '' let sortByReference = false let populateArr: Record<string, T> = {} let comparator: ((a: string, b: string) => number) | undefined
if (sorter) { let normalizedSorter: PhpKeyComparatorDescriptor if (typeof sorter === 'string') { if (!this.window) { return false } normalizedSorter = [this.window, sorter] } else if (Array.isArray(sorter)) { const [scopeDescriptor, callableDescriptor] = sorter if (typeof callableDescriptor === 'undefined') { return false } const scopeValue: SortContextValue = typeof scopeDescriptor === 'string' ? (this.window?.[scopeDescriptor] ?? this[scopeDescriptor]) : scopeDescriptor normalizedSorter = [scopeValue, callableDescriptor] } else { normalizedSorter = sorter }
try { const resolved = resolvePhpCallable<[string, string], NumericLike>(normalizedSorter, { invalidMessage: 'uksort(): Invalid callback', missingScopeMessage: (scopeName: string) => 'Object not found: ' + scopeName, }) comparator = (a: string, b: string): number => Number(resolved.fn.apply(resolved.scope, [a, b])) } catch (_error) { return false } }
for (k in inputArr) { if (inputArr.hasOwnProperty(k)) { keys.push(k) } }
try { if (typeof comparator === 'function') { keys.sort(comparator) } else { keys.sort() } } catch (_e) { return false }
const runtime = ensurePhpRuntimeState() const iniVal = String(runtime.ini['locutus.sortByReference']?.local_value ?? '') || 'on' sortByReference = iniVal === 'on' populateArr = sortByReference ? inputArr : populateArr
for (i = 0; i < keys.length; i++) { const key = keys[i] if (key === undefined) { continue } k = key const value = inputArr[k] if (value === undefined) { continue } tmpArr[k] = value if (sortByReference) { delete inputArr[k] } } for (const i in tmpArr) { if (tmpArr.hasOwnProperty(i)) { const value = tmpArr[i] if (value === undefined) { continue } populateArr[i] = value } }
return sortByReference || populateArr }
|