解析器
Phasis 将其 JavaScript 解析器作为独立的 PHP API 提供。你可以将源文本解析为带类型的 AST、遍历它,并导出为 ESTree 形状的数组或 JSON,而无需构建 Engine 或执行任何内容。
parseSource
public static function parseSource(string $source, string $sourceType = 'script'): Program
Phasis\Parser\Parser 的单次调用入口。解析 $source 并返回 Phasis\Ast\Program 根节点。
use Phasis\Parser\Parser;
$program = Parser::parseSource('const x = [1, 2].map(n => n * 2);');
$module = Parser::parseSource('import { x } from "./x.js"; export default x;', 'module');
$sourceType 选择语法目标,其命名与 JS 解析器中的同名选项一致:
| 值 | 目标 |
|---|---|
script |
经典 Script。顶层 import/export 属于语法错误,await 是标识符 |
module |
ES 模块。隐式严格模式,可解析 import/export 和顶层 await |
其他任何值都会抛出 \InvalidArgumentException。
如需更精细的控制(例如为直接 eval 上下文预设严格模式、读取 //# sourceURL pragma),请直接构造解析器:new Parser($source) 加上 setModuleMode()、setStrictMode() 和 parse()。
节点
每个 AST 类都继承 Phasis\Ast\Node,并位于 Phasis\Ast\{Declaration, Expression, Pattern, Statement} 之下。节点是普通的只读值对象:
$node->type()返回节点的类型字符串,也就是类的基名('BinaryExpression'、'ArrowFunction'等)。$node->location是一个Phasis\Lexer\SourceLocation,包含line(从 1 开始)、column(从 0 开始)和offset(字节偏移量)。只存储起始位置;节点不包含结束偏移量。- 其他所有公共字段都是节点的子节点和属性(
$binary->left、$binary->operator、$binary->right)。
大多数类型名称与 ESTree 一致。不同之处如下:
| Phasis 类型 | ESTree 类型 |
|---|---|
ArrowFunction |
ArrowFunctionExpression |
TaggedTemplate |
TaggedTemplateExpression |
ClassMethod |
MethodDefinition |
ClassProperty |
PropertyDefinition |
AssignmentProperty |
Property(位于 ObjectPattern 内) |
ExportDeclaration |
ExportNamedDeclaration / ExportDefaultDeclaration / ExportAllDeclaration |
ImportSpecifier |
ImportSpecifier / ImportDefaultSpecifier / ImportNamespaceSpecifier |
有几处结构也有所不同:类主体在类节点上是一个普通元素数组(没有 ClassBody 包装器);导入/导出名称、语句标签和 FunctionExpression 名称都以字符串而非嵌套的 Identifier 节点存储;super 会解析为一个名为 super 的 Identifier(没有 Super 节点类)。Phasis\Ast\EstreeSerializer 会将所有这些结构转换为 ESTree 格式。
语法错误
输入无效时会抛出 Phasis\Exceptions\SyntaxError(通常是其子类 Phasis\Parser\ParseError)。异常消息以位置信息结尾,结构化位置信息可从异常对象中获取:
use Phasis\Exceptions\SyntaxError;
use Phasis\Parser\Parser;
try {
Parser::parseSource("const a = 1;\nconst b = ;");
} catch (SyntaxError $e) {
echo $e->getMessage(); // Unexpected token (got ; ";") at line 2, column 10
echo $e->location->line; // 2
echo $e->location->column; // 10
echo $e->location->offset; // 23
}
遍历器
Phasis\Ast\Walker 以类似 estraverse 的方式,通过 enter/leave 回调按深度优先顺序遍历树:
public static function walk(Node $node, callable $enter, ?callable $leave = null): void
两个回调都会接收 (Node $node, ?Node $parent)。从 $enter 返回 Walker::SKIP 会跳过该节点的子节点;但该节点自己的 $leave 仍会触发。
use Phasis\Ast\Expression\Identifier;
use Phasis\Ast\Node;
use Phasis\Ast\Walker;
$names = [];
Walker::walk($program, function (Node $node) use (&$names) {
if ($node instanceof Identifier) {
$names[] = $node->name;
}
if ($node instanceof \Phasis\Ast\Expression\ArrowFunction) {
return Walker::SKIP;
}
});
遍历是只读的。节点是只读值对象,因此不支持替换或修改。
序列化器
Phasis\Ast\Serializer 会使用 Phasis 类型名称和字段布局,将带类型的 AST 转换为数组或 JSON,并剔除解析器内部的缓存字段:
public static function toArray(Node $node): array
public static function toJson(Node $node, bool $pretty = false): string
这是 bin/phasis --ast 打印的格式。
EstreeSerializer
Phasis\Ast\EstreeSerializer 则会将带类型的 AST 转换为 ESTree 形状的数组:采用 ESTree 类型名称(见上面的差异表)、ESTree 字段名称和顺序、合成的 ClassBody / specifier / source-Literal 节点,并为每个从源代码解析的节点添加 start 字节偏移量。由于节点只存储起始位置,因此不提供 end 偏移量;字符串 Literal 节点也不包含 raw 文本。
public static function toArray(Node $node): array
public static function toJson(Node $node, bool $pretty = false): string
public static function summarize(Node $node): string
summarize() 会生成确定性的摘要,每个节点占一行(类型以及 name= / value= / op= / kind= 属性,每层缩进两个空格)。该格式与 acorn 测试夹具在 tests/Popular/acorn 中生成的 AST 摘要逐字节相同,因此同一个基准既可验证 JS 解析器路径,也可验证此 PHP 导出:
use Phasis\Ast\EstreeSerializer;
use Phasis\Parser\Parser;
echo EstreeSerializer::summarize(Parser::parseSource('let x = a + 1;'));
// Program
// VariableDeclaration kind=let
// VariableDeclarator
// Identifier name=x
// BinaryExpression op=+
// Identifier name=a
// Literal value=1
CLI
bin/phasis --ast script.js 解析一个文件(脚本目标)并以两空格缩进打印 Serializer JSON 转储。完整的标志参考请参阅 CLI。