diff --git a/bin/dump-ast.php b/bin/dump-ast.php new file mode 100755 index 00000000..ad98cd64 --- /dev/null +++ b/bin/dump-ast.php @@ -0,0 +1,335 @@ +#!/usr/bin/env php + + * + * 示例: + * php bin/dump-ast.php examples/hello.php + * php bin/dump-ast.php src/functions.php + */ + +require_once __DIR__ . '/../vendor/autoload.php'; + +use PhpParser\ParserFactory; +use PhpParser\Node; +use PhpParser\NodeAbstract; + +// ─── 参数解析 ───────────────────────────────────────────── + +if ($argc < 2) { + fprintf(STDERR, "用法: php %s \n", $argv[0]); + exit(1); +} + +$filePath = $argv[1]; + +if (!is_file($filePath)) { + fprintf(STDERR, "错误: 文件不存在: %s\n", $filePath); + exit(1); +} + +if (!is_readable($filePath)) { + fprintf(STDERR, "错误: 文件不可读: %s\n", $filePath); + exit(1); +} + +// ─── 解析 ───────────────────────────────────────────────── + +$code = file_get_contents($filePath); +if ($code === false) { + fprintf(STDERR, "错误: 无法读取文件: %s\n", $filePath); + exit(1); +} + +$parserFactory = new ParserFactory(); +$parser = $parserFactory->createForNewestSupportedVersion(); + +try { + $stmts = $parser->parse($code); +} catch (\PhpParser\Error $e) { + fprintf(STDERR, "解析错误: %s\n", $e->getMessage()); + exit(1); +} + +// ─── AST 结构化输出 ─────────────────────────────────────── + +/** + * 将 AST 节点递归转换为可读的数组结构(便于 print_r / var_export 输出) + */ +function nodeToArray(NodeAbstract $node): array +{ + $result = [ + 'type' => $node->getType(), + 'attributes' => [ + 'startLine' => $node->getStartLine(), + 'endLine' => $node->getEndLine(), + ], + ]; + + // 子属性 + foreach ($node->getSubNodeNames() as $name) { + $value = $node->$name; + $result[$name] = valueToArray($value); + } + + // 注释(若存在) + $comments = $node->getComments(); + if ($comments) { + $result['attributes']['comments'] = []; + foreach ($comments as $comment) { + $result['attributes']['comments'][] = $comment->getText(); + } + } + + return $result; +} + +function valueToArray(mixed $value): mixed +{ + if ($value instanceof NodeAbstract) { + return nodeToArray($value); + } + + if (is_array($value)) { + $arr = []; + foreach ($value as $k => $v) { + $arr[$k] = valueToArray($v); + } + // 判断是否为列表(连续的 int key) + if (array_is_list($arr) && count($arr) <= 1) { + return $arr; + } + // 非列表或元素较多时,只保留索引 + if (!array_is_list($arr)) { + return $arr; + } + // 对列表只返回列表 + return $arr; + } + + if (is_string($value)) { + // 过长的字符串截断显示 + return mb_strlen($value) > 120 ? mb_substr($value, 0, 120) . '...' : $value; + } + + if (is_bool($value)) { + return $value ? 'true' : 'false'; + } + + if ($value === null) { + return 'null'; + } + + return $value; +} + +// ─── 美化打印 ───────────────────────────────────────────── + +/** + * 用缩进打印 AST 结构,类似 json 的视觉风格 + */ +function printAst(array $nodes, string $indent = ''): void +{ + foreach ($nodes as $i => $node) { + if ($node instanceof NodeAbstract) { + $arr = nodeToArray($node); + printNodeArray($arr, $indent, $i); + } elseif (is_array($node)) { + echo $indent . "[$i] => array(\n"; + printAst($node, $indent . ' '); + echo $indent . ")\n"; + } else { + echo $indent . "[$i] => " . var_export($node, true) . "\n"; + } + } +} + +function printNodeArray(array $arr, string $indent, int|string $key): void +{ + $type = $arr['type']; + $sl = $arr['attributes']['startLine']; + $el = $arr['attributes']['endLine']; + unset($arr['type'], $arr['attributes']); + + echo $indent . "[$key] => $type (L$sl-$el)\n"; + + if (empty($arr)) { + return; + } + + foreach ($arr as $name => $value) { + if ($value instanceof NodeAbstract) { + $child = nodeToArray($value); + echo $indent . " $name => " . $child['type'] . " (\n"; + printNodeAttrs($child, $indent . ' '); + echo $indent . " )\n"; + } elseif (is_array($value)) { + echo $indent . " $name => array(" . (count($value) > 0 ? "\n" : ")\n"); + foreach ($value as $j => $item) { + if ($item instanceof NodeAbstract || (is_array($item) && isset($item['type']))) { + $itemArr = $item instanceof NodeAbstract ? nodeToArray($item) : $item; + echo $indent . " [$j] => " . $itemArr['type'] . " (\n"; + printNodeAttrs($itemArr, $indent . ' '); + echo $indent . " )\n"; + } else { + echo $indent . " [$j] => " . var_export($item, true) . "\n"; + } + } + if (count($value) > 0) { + echo $indent . " )\n"; + } + } else { + echo $indent . " $name => " . var_export($value, true) . "\n"; + } + } +} + +function printNodeAttrs(array $arr, string $indent): void +{ + $hasAttrs = !empty($arr['attributes']); + $attrs = $arr['attributes'] ?? []; + unset($arr['type'], $arr['attributes']); + + echo $indent . "type: " . $arr['type'] . "\n"; + echo $indent . "lines: " . $attrs['startLine'] . '-' . $attrs['endLine'] . "\n"; + + if (isset($attrs['comments'])) { + foreach ($attrs['comments'] as $c) { + echo $indent . "comment: " . trim($c) . "\n"; + } + } + + foreach ($arr as $name => $value) { + if (is_array($value)) { + echo $indent . "$name: " . json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\n"; + } else { + echo $indent . "$name: " . var_export($value, true) . "\n"; + } + } +} + +// ────────────────────────────────────────────────────────── + +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"; +echo " 文件: $filePath\n"; +echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n"; + +echo "AST_ROOT: Program (L1-" . count($stmts) . " statements)\n\n"; + +/** + * 简洁的分层输出 + */ +function dumpNode(NodeAbstract $node, int $depth = 0): void +{ + $indent = str_repeat(' ', $depth); + $type = $node->getType(); + $start = $node->getStartLine(); + $end = $node->getEndLine(); + $subInfo = ''; + + // 为常见节点类型提取关键信息 + switch (true) { + case $node instanceof Node\Expr\Variable: + $subInfo = ' $' . ($node->name === null ? '(unset)' : (is_string($node->name) ? $node->name : '...')); + break; + case $node instanceof Node\Expr\FuncCall: + $name = $node->name instanceof Node\Name ? implode('\\', $node->name->getParts()) : '...'; + $subInfo = " $name()"; + break; + case $node instanceof Node\Expr\MethodCall: + $name = is_string($node->name) ? $node->name : '...'; + $subInfo = " ->$name()"; + break; + case $node instanceof Node\Expr\StaticCall: + $class = $node->class instanceof Node\Name ? implode('\\', $node->class->getParts()) : '...'; + $name = is_string($node->name) ? $node->name : '...'; + $subInfo = " $class::$name()"; + break; + case $node instanceof Node\Expr\Assign: + $subInfo = ' ='; + break; + case $node instanceof Node\Scalar\String_: + $val = mb_strlen($node->value) > 60 ? mb_substr($node->value, 0, 60) . '...' : $node->value; + $subInfo = ' "' . $val . '"'; + break; + case $node instanceof Node\Scalar\LNumber: + $subInfo = ' ' . $node->value; + break; + case $node instanceof Node\Scalar\DNumber: + var_dump($node->getAttribute('rawValue')); + $subInfo = ' ' . $node->value; + break; + case $node instanceof Node\Expr\ConstFetch: + $subInfo = ' ' . implode('\\', $node->name->getParts()); + break; + case $node instanceof Node\Stmt\Function_: + $subInfo = ' ' . $node->name->name . '()'; + break; + case $node instanceof Node\Stmt\ClassMethod: + $subInfo = ' ' . $node->name->name . '()'; + break; + case $node instanceof Node\Stmt\Class_: + $subInfo = ' ' . ($node->name?->name ?? '(anonymous)'); + break; + case $node instanceof Node\Stmt\Namespace_: + $subInfo = ' ' . implode('\\', $node->name->getParts()); + break; + case $node instanceof Node\Stmt\If_: + $subInfo = ' (cond)'; + break; + case $node instanceof Node\Expr\BinaryOp\Concat: + $subInfo = ' .'; + break; + case $node instanceof Node\Expr\Array_: + $subInfo = ' [' . count($node->items) . ' items]'; + break; + case $node instanceof Node\Expr\ArrayDimFetch: + $subInfo = '[]'; + break; + case $node instanceof Node\Expr\PropertyFetch: + $name = is_string($node->name) ? $node->name : '...'; + $subInfo = " ->$name"; + break; + default: + if ($node instanceof Node\Expr\BinaryOp) { + $subInfo = ' (binop)'; + } + break; + } + + $line = $start === $end ? "L$start" : "L$start-$end"; + echo "{$indent}├── {$type} ({$line}){$subInfo}\n"; + + foreach ($node->getSubNodeNames() as $name) { + $value = $node->$name; + if ($value instanceof NodeAbstract) { + echo "{$indent}│ {$name}:\n"; + dumpNode($value, $depth + 2); + } elseif (is_array($value) && !empty($value)) { + $hasNode = false; + foreach ($value as $item) { + if ($item instanceof NodeAbstract) { + $hasNode = true; + break; + } + } + if ($hasNode) { + echo "{$indent}│ {$name}:\n"; + foreach ($value as $k => $item) { + if ($item instanceof NodeAbstract) { + dumpNode($item, $depth + 2); + } + } + } + } + } +} + +foreach ($stmts as $i => $stmt) { + dumpNode($stmt); +} + +echo "\n"; diff --git a/examples/bigint/bigint.php b/examples/bigint/bigint.php new file mode 100644 index 00000000..1d16fc55 --- /dev/null +++ b/examples/bigint/bigint.php @@ -0,0 +1,6 @@ +