php-parse 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. #!/usr/bin/env php
  2. <?php
  3. foreach ([__DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php'] as $file) {
  4. if (file_exists($file)) {
  5. require $file;
  6. break;
  7. }
  8. }
  9. ini_set('xdebug.max_nesting_level', 3000);
  10. // Disable XDebug var_dump() output truncation
  11. ini_set('xdebug.var_display_max_children', -1);
  12. ini_set('xdebug.var_display_max_data', -1);
  13. ini_set('xdebug.var_display_max_depth', -1);
  14. list($operations, $files, $attributes) = parseArgs($argv);
  15. /* Dump nodes by default */
  16. if (empty($operations)) {
  17. $operations[] = 'dump';
  18. }
  19. if (empty($files)) {
  20. showHelp("Must specify at least one file.");
  21. }
  22. $lexer = new PhpParser\Lexer\Emulative(array('usedAttributes' => array(
  23. 'startLine', 'endLine', 'startFilePos', 'endFilePos', 'comments'
  24. )));
  25. $parser = (new PhpParser\ParserFactory)->create(PhpParser\ParserFactory::PREFER_PHP7, $lexer);
  26. $dumper = new PhpParser\NodeDumper(['dumpComments' => true]);
  27. $prettyPrinter = new PhpParser\PrettyPrinter\Standard;
  28. $serializer = new PhpParser\Serializer\XML;
  29. $traverser = new PhpParser\NodeTraverser();
  30. $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver);
  31. foreach ($files as $file) {
  32. if (strpos($file, '<?php') === 0) {
  33. $code = $file;
  34. echo "====> Code $code\n";
  35. } else {
  36. if (!file_exists($file)) {
  37. die("File $file does not exist.\n");
  38. }
  39. $code = file_get_contents($file);
  40. echo "====> File $file:\n";
  41. }
  42. try {
  43. $stmts = $parser->parse($code);
  44. } catch (PhpParser\Error $e) {
  45. if ($attributes['with-column-info'] && $e->hasColumnInfo()) {
  46. $startLine = $e->getStartLine();
  47. $endLine = $e->getEndLine();
  48. $startColumn = $e->getStartColumn($code);
  49. $endColumn = $e->getEndColumn($code);
  50. $message .= $e->getRawMessage() . " from $startLine:$startColumn to $endLine:$endColumn";
  51. } else {
  52. $message = $e->getMessage();
  53. }
  54. die($message . "\n");
  55. }
  56. foreach ($operations as $operation) {
  57. if ('dump' === $operation) {
  58. echo "==> Node dump:\n";
  59. echo $dumper->dump($stmts), "\n";
  60. } elseif ('pretty-print' === $operation) {
  61. echo "==> Pretty print:\n";
  62. echo $prettyPrinter->prettyPrintFile($stmts), "\n";
  63. } elseif ('serialize-xml' === $operation) {
  64. echo "==> Serialized XML:\n";
  65. echo $serializer->serialize($stmts), "\n";
  66. } elseif ('var-dump' === $operation) {
  67. echo "==> var_dump():\n";
  68. var_dump($stmts);
  69. } elseif ('resolve-names' === $operation) {
  70. echo "==> Resolved names.\n";
  71. $stmts = $traverser->traverse($stmts);
  72. }
  73. }
  74. }
  75. function showHelp($error = '') {
  76. if ($error) {
  77. echo $error . "\n\n";
  78. }
  79. die(<<<OUTPUT
  80. Usage: php-parse [operations] file1.php [file2.php ...]
  81. or: php-parse [operations] "<?php code"
  82. Turn PHP source code into an abstract syntax tree.
  83. Operations is a list of the following options (--dump by default):
  84. -d, --dump Dump nodes using NodeDumper
  85. -p, --pretty-print Pretty print file using PrettyPrinter\Standard
  86. --serialize-xml Serialize nodes using Serializer\XML
  87. --var-dump var_dump() nodes (for exact structure)
  88. -N, --resolve-names Resolve names using NodeVisitor\NameResolver
  89. -c, --with-column-info Show column-numbers for errors (if available)
  90. -h, --help Display this page
  91. Example:
  92. php-parse -d -p -N -d file.php
  93. Dumps nodes, pretty prints them, then resolves names and dumps them again.
  94. OUTPUT
  95. );
  96. }
  97. function parseArgs($args) {
  98. $operations = array();
  99. $files = array();
  100. $attributes = array(
  101. 'with-column-info' => false,
  102. );
  103. array_shift($args);
  104. $parseOptions = true;
  105. foreach ($args as $arg) {
  106. if (!$parseOptions) {
  107. $files[] = $arg;
  108. continue;
  109. }
  110. switch ($arg) {
  111. case '--dump':
  112. case '-d':
  113. $operations[] = 'dump';
  114. break;
  115. case '--pretty-print':
  116. case '-p':
  117. $operations[] = 'pretty-print';
  118. break;
  119. case '--serialize-xml':
  120. $operations[] = 'serialize-xml';
  121. break;
  122. case '--var-dump':
  123. $operations[] = 'var-dump';
  124. break;
  125. case '--resolve-names':
  126. case '-N';
  127. $operations[] = 'resolve-names';
  128. break;
  129. case '--with-column-info':
  130. case '-c';
  131. $attributes['with-column-info'] = true;
  132. break;
  133. case '--help':
  134. case '-h';
  135. showHelp();
  136. break;
  137. case '--':
  138. $parseOptions = false;
  139. break;
  140. default:
  141. if ($arg[0] === '-') {
  142. showHelp("Invalid operation $arg.");
  143. } else {
  144. $files[] = $arg;
  145. }
  146. }
  147. }
  148. return array($operations, $files, $attributes);
  149. }