1#!/usr/bin/env php 2<?php 3 4foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) { 5 if (file_exists($file)) { 6 require $file; 7 break; 8 } 9} 10 11ini_set('xdebug.max_nesting_level', 3000); 12 13// Disable XDebug var_dump() output truncation 14ini_set('xdebug.var_display_max_children', -1); 15ini_set('xdebug.var_display_max_data', -1); 16ini_set('xdebug.var_display_max_depth', -1); 17 18list($operations, $files, $attributes) = parseArgs($argv); 19 20/* Dump nodes by default */ 21if (empty($operations)) { 22 $operations[] = 'dump'; 23} 24 25if (empty($files)) { 26 showHelp("Must specify at least one file."); 27} 28 29$lexer = new PhpParser\Lexer\Emulative(['usedAttributes' => [ 30 'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments' 31]]); 32$parser = (new PhpParser\ParserFactory)->create( 33 PhpParser\ParserFactory::PREFER_PHP7, 34 $lexer 35); 36$dumper = new PhpParser\NodeDumper([ 37 'dumpComments' => true, 38 'dumpPositions' => $attributes['with-positions'], 39]); 40$prettyPrinter = new PhpParser\PrettyPrinter\Standard; 41 42$traverser = new PhpParser\NodeTraverser(); 43$traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver); 44 45foreach ($files as $file) { 46 if (strpos($file, '<?php') === 0) { 47 $code = $file; 48 fwrite(STDERR, "====> Code $code\n"); 49 } else { 50 if (!file_exists($file)) { 51 fwrite(STDERR, "File $file does not exist.\n"); 52 exit(1); 53 } 54 55 $code = file_get_contents($file); 56 fwrite(STDERR, "====> File $file:\n"); 57 } 58 59 if ($attributes['with-recovery']) { 60 $errorHandler = new PhpParser\ErrorHandler\Collecting; 61 $stmts = $parser->parse($code, $errorHandler); 62 foreach ($errorHandler->getErrors() as $error) { 63 $message = formatErrorMessage($error, $code, $attributes['with-column-info']); 64 fwrite(STDERR, $message . "\n"); 65 } 66 if (null === $stmts) { 67 continue; 68 } 69 } else { 70 try { 71 $stmts = $parser->parse($code); 72 } catch (PhpParser\Error $error) { 73 $message = formatErrorMessage($error, $code, $attributes['with-column-info']); 74 fwrite(STDERR, $message . "\n"); 75 exit(1); 76 } 77 } 78 79 foreach ($operations as $operation) { 80 if ('dump' === $operation) { 81 fwrite(STDERR, "==> Node dump:\n"); 82 echo $dumper->dump($stmts, $code), "\n"; 83 } elseif ('pretty-print' === $operation) { 84 fwrite(STDERR, "==> Pretty print:\n"); 85 echo $prettyPrinter->prettyPrintFile($stmts), "\n"; 86 } elseif ('json-dump' === $operation) { 87 fwrite(STDERR, "==> JSON dump:\n"); 88 echo json_encode($stmts, JSON_PRETTY_PRINT), "\n"; 89 } elseif ('var-dump' === $operation) { 90 fwrite(STDERR, "==> var_dump():\n"); 91 var_dump($stmts); 92 } elseif ('resolve-names' === $operation) { 93 fwrite(STDERR, "==> Resolved names.\n"); 94 $stmts = $traverser->traverse($stmts); 95 } 96 } 97} 98 99function formatErrorMessage(PhpParser\Error $e, $code, $withColumnInfo) { 100 if ($withColumnInfo && $e->hasColumnInfo()) { 101 return $e->getMessageWithColumnInfo($code); 102 } else { 103 return $e->getMessage(); 104 } 105} 106 107function showHelp($error = '') { 108 if ($error) { 109 fwrite(STDERR, $error . "\n\n"); 110 } 111 fwrite($error ? STDERR : STDOUT, <<<OUTPUT 112Usage: php-parse [operations] file1.php [file2.php ...] 113 or: php-parse [operations] "<?php code" 114Turn PHP source code into an abstract syntax tree. 115 116Operations is a list of the following options (--dump by default): 117 118 -d, --dump Dump nodes using NodeDumper 119 -p, --pretty-print Pretty print file using PrettyPrinter\Standard 120 -j, --json-dump Print json_encode() result 121 --var-dump var_dump() nodes (for exact structure) 122 -N, --resolve-names Resolve names using NodeVisitor\NameResolver 123 -c, --with-column-info Show column-numbers for errors (if available) 124 -P, --with-positions Show positions in node dumps 125 -r, --with-recovery Use parsing with error recovery 126 -h, --help Display this page 127 128Example: 129 php-parse -d -p -N -d file.php 130 131 Dumps nodes, pretty prints them, then resolves names and dumps them again. 132 133 134OUTPUT 135 ); 136 exit($error ? 1 : 0); 137} 138 139function parseArgs($args) { 140 $operations = []; 141 $files = []; 142 $attributes = [ 143 'with-column-info' => false, 144 'with-positions' => false, 145 'with-recovery' => false, 146 ]; 147 148 array_shift($args); 149 $parseOptions = true; 150 foreach ($args as $arg) { 151 if (!$parseOptions) { 152 $files[] = $arg; 153 continue; 154 } 155 156 switch ($arg) { 157 case '--dump': 158 case '-d': 159 $operations[] = 'dump'; 160 break; 161 case '--pretty-print': 162 case '-p': 163 $operations[] = 'pretty-print'; 164 break; 165 case '--json-dump': 166 case '-j': 167 $operations[] = 'json-dump'; 168 break; 169 case '--var-dump': 170 $operations[] = 'var-dump'; 171 break; 172 case '--resolve-names': 173 case '-N'; 174 $operations[] = 'resolve-names'; 175 break; 176 case '--with-column-info': 177 case '-c'; 178 $attributes['with-column-info'] = true; 179 break; 180 case '--with-positions': 181 case '-P': 182 $attributes['with-positions'] = true; 183 break; 184 case '--with-recovery': 185 case '-r': 186 $attributes['with-recovery'] = true; 187 break; 188 case '--help': 189 case '-h'; 190 showHelp(); 191 break; 192 case '--': 193 $parseOptions = false; 194 break; 195 default: 196 if ($arg[0] === '-') { 197 showHelp("Invalid operation $arg."); 198 } else { 199 $files[] = $arg; 200 } 201 } 202 } 203 204 return [$operations, $files, $attributes]; 205} 206