
# 解析器

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](https://github.com/estree/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](https://phasis.inline0.dev/zh/docs/cli)。
