const visitedObjects = new Map<object, true>()
import { getPhpObjectEntry } from '../_helpers/_phpRuntimeState.ts' import { type PhpAssoc, type PhpRuntimeValue, toPhpArrayObject } from '../_helpers/_phpTypes.ts' import { echo } from '../strings/echo.ts'
type DumpValue = PhpRuntimeValue type DomLikeNode = { nodeName: string nodeType?: number namespaceURI?: string nodeValue?: DumpValue }
const hasNodeName = (value: DumpValue): value is DomLikeNode => { return typeof value === 'object' && value !== null && 'nodeName' in value }
const isLocutusResource = ( value: DumpValue, getFuncName: (fn: { toString: () => string }) => string, ): value is { var_dump: () => string } => { if (typeof value !== 'object' || value === null || !('var_dump' in value) || !('constructor' in value)) { return false } const maybeResource = value const varDump = getPhpObjectEntry(maybeResource, 'var_dump') const constructorValue = getPhpObjectEntry(maybeResource, 'constructor') return ( typeof varDump === 'function' && typeof constructorValue === 'function' && getFuncName(constructorValue) === 'LOCUTUS_Resource' ) }
export function var_dump(...args: DumpValue[]): string {
let output = '' const padChar = ' ' const padVal = 4 const _getFuncName = function (fn: { toString: () => string }): string { const name = /\W*function\s+([\w$]+)\s*\(/.exec(fn.toString()) if (!name) { return '(Anonymous)' } return name[1] ?? '(Anonymous)' }
const _repeatChar = function (len: number, padCharacter: string): string { let str = '' for (let i = 0; i < len; i++) { str += padCharacter } return str } const _getInnerVal = function (val: DumpValue, thickPad: string): string { let ret = '' if (val === null) { ret = 'NULL' } else if (typeof val === 'boolean') { ret = 'bool(' + val + ')' } else if (typeof val === 'string') { ret = 'string(' + val.length + ') "' + val + '"' } else if (typeof val === 'number') { if (Number.isInteger(val)) { ret = 'int(' + val + ')' } else { ret = 'float(' + val + ')' } } else if (typeof val === 'undefined') { ret = 'undefined' } else if (typeof val === 'function') { const funcLines = val.toString().split('\n') ret = '' for (let i = 0, fll = funcLines.length; i < fll; i++) { const line = funcLines[i] ?? '' ret += (i !== 0 ? '\n' + thickPad : '') + line } } else if (val instanceof Date) { ret = 'Date(' + val + ')' } else if (val instanceof RegExp) { ret = 'RegExp(' + val + ')' } else if (hasNodeName(val)) { switch (val.nodeType) { case 1: if (typeof val.namespaceURI === 'undefined' || val.namespaceURI === 'https://www.w3.org/1999/xhtml') { ret = 'HTMLElement("' + val.nodeName + '")' } else { ret = 'XML Element("' + val.nodeName + '")' } break case 2: ret = 'ATTRIBUTE_NODE(' + val.nodeName + ')' break case 3: ret = 'TEXT_NODE(' + val.nodeValue + ')' break case 4: ret = 'CDATA_SECTION_NODE(' + val.nodeValue + ')' break case 5: ret = 'ENTITY_REFERENCE_NODE' break case 6: ret = 'ENTITY_NODE' break case 7: ret = 'PROCESSING_INSTRUCTION_NODE(' + val.nodeName + ':' + val.nodeValue + ')' break case 8: ret = 'COMMENT_NODE(' + val.nodeValue + ')' break case 9: ret = 'DOCUMENT_NODE' break case 10: ret = 'DOCUMENT_TYPE_NODE' break case 11: ret = 'DOCUMENT_FRAGMENT_NODE' break case 12: ret = 'NOTATION_NODE' break } } return ret }
const _formatArray = function ( obj: DumpValue, curDepth: number, padVal: number, padChar: string, visitedObjectMap: Map<object, true>, ): string { if (curDepth > 0) { curDepth++ }
const basePad = _repeatChar(padVal * (curDepth - 1), padChar) const thickPad = _repeatChar(padVal * (curDepth + 1), padChar) let str = '' let val = ''
if (typeof obj === 'object' && obj !== null) { if (visitedObjectMap.has(obj)) { return 'Circular Reference Detected\n' } else { visitedObjectMap.set(obj, true) }
if (isLocutusResource(obj, _getFuncName)) { return obj.var_dump() } let lgth = 0 const objRecord = toPhpArrayObject<DumpValue>(obj) for (const someProp in objRecord) { if (Object.hasOwn(objRecord, someProp)) { lgth++ } } str += 'array(' + lgth + ') {\n' for (const key in objRecord) { if (!Object.hasOwn(objRecord, key)) { continue } const objVal = objRecord[key] if ( typeof objVal === 'object' && objVal !== null && !(objVal instanceof Date) && !(objVal instanceof RegExp) && !hasNodeName(objVal) ) { str += thickPad str += '[' str += key str += '] =>\n' str += thickPad str += _formatArray(objVal, curDepth + 1, padVal, padChar, visitedObjectMap) } else { val = _getInnerVal(objVal, thickPad) str += thickPad str += '[' str += key str += '] =>\n' str += thickPad str += val str += '\n' } } str += basePad + '}\n' } else { str = _getInnerVal(obj, thickPad) } return str }
output = _formatArray(args[0], 0, padVal, padChar, visitedObjects) for (let i = 1; i < args.length; i++) { output += '\n' + _formatArray(args[i], 0, padVal, padChar, visitedObjects) }
echo(output)
return output }
|