跳转到正文

解析器

Phasis 将其 JavaScript 解析器作为独立的 PHP API 提供。你可以将源文本解析为带类型的 AST、遍历它,并导出为 ESTree 形状的数组或 JSON,而无需构建 Engine 或执行任何内容。

parseSource

PHP
public static function parseSource(string $source, string $sourceType = 'script'): Program

Phasis\Parser\Parser 的单次调用入口。解析 $source 并返回 Phasis\Ast\Program 根节点。

PHP
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)。异常消息以位置信息结尾,结构化位置信息可从异常对象中获取:

PHP
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 回调按深度优先顺序遍历树:

PHP
public static function walk(Node $node, callable $enter, ?callable $leave = null): void

两个回调都会接收 (Node $node, ?Node $parent)。从 $enter 返回 Walker::SKIP 会跳过该节点的子节点;但该节点自己的 $leave 仍会触发。

PHP
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,并剔除解析器内部的缓存字段:

PHP
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 文本。

PHP
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 导出:

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。