TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

335 lines
11 KiB

#!/usr/bin/env php
<?php
/**
* 导出 PHP 文件的 AST 语法树结构
*
* 用法: php bin/dump-ast.php <file.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 <file.php>\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";