diff --git a/bin/parser-tree.ts b/bin/parser-tree.ts new file mode 100755 index 0000000..cf5ee9c --- /dev/null +++ b/bin/parser-tree.ts @@ -0,0 +1,192 @@ +#!/usr/bin/env bun + +// WARNING: [[ No human has been anywhere near this file. It's pure Claude slop. +// Enter at your own risk. ]] + +import { readFileSync } from 'fs' + +type CallInfo = { + method: string + line: number + calls: Set + isRecursive?: boolean +} + +// Parse the parser file and extract method calls +function analyzeParser(filePath: string): Map { + const content = readFileSync(filePath, 'utf-8') + const lines = content.split('\n') + const methods = new Map() + + // Find all method definitions + const methodRegex = /^\s*(\w+)\s*\([^)]*\):\s*/ + + let currentMethod: string | null = null + let braceDepth = 0 + let classDepth = 0 + + for (let i = 0; i < lines.length; i++) { + const line = lines[i] || '' + + // Track if we're inside the Parser class + if (line.includes('class Parser')) { + classDepth = braceDepth + 1 // Will be the depth after we process this line's brace + } + + // Check for method definition (only inside class, at class level) + // Check BEFORE incrementing braceDepth + if (classDepth > 0 && braceDepth === classDepth) { + const methodMatch = line.match(methodRegex) + if (methodMatch && !line.includes('class ')) { + currentMethod = methodMatch[1]! + methods.set(currentMethod, { + method: currentMethod, + line: i + 1, + calls: new Set() + }) + } + } + + // Track brace depth + braceDepth += (line.match(/{/g) || []).length + braceDepth -= (line.match(/}/g) || []).length + + // Find method calls within current method + if (currentMethod && braceDepth > 0) { + // Match this.methodName() calls + const callRegex = /this\.(\w+)\s*\(/g + let match + while ((match = callRegex.exec(line)) !== null) { + const calledMethod = match[1]! + const info = methods.get(currentMethod)! + info.calls.add(calledMethod) + + // Mark recursive calls + if (calledMethod === currentMethod) { + info.isRecursive = true + } + } + } + + // Reset when method ends + if (braceDepth === 0) { + currentMethod = null + } + } + + return methods +} + +// Build tree structure starting from a root method +function buildTree( + method: string, + callGraph: Map, + visited: Set, + indent = '', + isLast = true, + depth = 0, + maxDepth = 3 +): string[] { + const lines: string[] = [] + const info = callGraph.get(method) + + if (!info) return lines + + // Add current method + const prefix = depth === 0 ? '' : (isLast ? '└─> ' : '├─> ') + const suffix = info.isRecursive ? ' (recursive)' : '' + const lineNum = `[line ${info.line}]` + lines.push(`${indent}${prefix}${method}() ${lineNum}${suffix}`) + + // Stop if we've reached max depth + if (depth >= maxDepth) { + return lines + } + + // Prevent infinite recursion in tree display + if (visited.has(method)) { + return lines + } + + const newVisited = new Set(visited) + newVisited.add(method) + + // Helper methods to filter out (low-level utilities) + const helperPatterns = /^(is|next|peek|expect|current|op)/i + + // Get sorted unique calls (filter out recursive self-calls for display) + const calls = Array.from(info.calls) + .filter(c => callGraph.has(c)) // Only show parser methods + .filter(c => c !== method) // Don't show immediate self-recursion + .filter(c => !helperPatterns.test(c)) // Filter out helpers + .sort() + + // Add children + const newIndent = indent + (isLast ? ' ' : '│ ') + calls.forEach((call, idx) => { + const childLines = buildTree( + call, + callGraph, + newVisited, + newIndent, + idx === calls.length - 1, + depth + 1, + maxDepth + ) + lines.push(...childLines) + }) + + return lines +} + +// Main +const parserPath = './src/parser/parser2.ts' +const maxDepth = parseInt(process.argv[2] || '5') + +console.log('Parser Call Tree for', parserPath) +console.log(`Max depth: ${maxDepth}`) +console.log('═'.repeat(60)) +console.log() + +const callGraph = analyzeParser(parserPath) + +// Start from parse() method +const tree = buildTree('parse', callGraph, new Set(), '', true, 0, maxDepth) +console.log(tree.join('\n')) + +// Show some stats +console.log('\n' + '═'.repeat(60)) +console.log('Stats:') +console.log(` Total methods: ${callGraph.size}`) +console.log(` Entry point: parse()`) + +// Find methods that are never called (potential dead code or entry points) +const allCalled = new Set() +for (const info of callGraph.values()) { + info.calls.forEach(c => allCalled.add(c)) +} + +const uncalled = Array.from(callGraph.keys()) + .filter(m => !allCalled.has(m) && m !== 'parse') + .sort() + +if (uncalled.length > 0) { + console.log(`\n Uncalled methods: ${uncalled.join(', ')}`) +} + +// Find most-called methods +const callCount = new Map() +for (const info of callGraph.values()) { + for (const called of info.calls) { + callCount.set(called, (callCount.get(called) || 0) + 1) + } +} + +const topCalled = Array.from(callCount.entries()) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5) + +console.log(`\n Most-called methods:`) +for (const [method, count] of topCalled) { + console.log(` ${method}() - called ${count} times`) +}