
# 架构

Phasis 是一个四阶段树遍历器，并带有机会式字节码 VM。数据流如下：

```
JS text
  │
  ▼
┌──────────┐
│  Lexer   │  src/Lexer/
└────┬─────┘
     ▼ tokens
┌──────────┐
│  Parser  │  src/Parser/
└────┬─────┘
     ▼ AST nodes
┌──────────────┐
│ Interpreter  │  src/Runtime/
│  + Bytecode  │  src/Bytecode/
│      VM      │
└────┬─────────┘
     ▼ JsValue
PHP host
```

每个阶段都是一个 PHP 类，对外接口精简，由下一阶段使用。没有链接步骤——解析会生成 AST，解释器直接遍历 AST。

## 词法分析器

`src/Lexer/Lexer.php` 将源文本转换为 `Token` 实例流。每个令牌都是只读结构体，包含类型（`TokenType` 枚举，约 80 种情况）、值、行和列。

词法分析器处理：

- 数字字面量——十进制、十六进制（`0x`）、八进制（`0o`）、二进制（`0b`）、指数形式和数字分隔符。
- 字符串字面量——单引号和双引号、所有转义序列、带嵌套表达式的模板字面量。
- 正则表达式字面量——完整的 `/pattern/flags` 令牌化，并能判断 `/` 应解析为正则表达式还是除法。
- 标识符、关键字、标点符号。
- 注释（行注释和块注释），作为附属信息保留。
- Unicode 源——将 UTF-8 字节视为 JS UTF-16 源。

输出是一个惰性生成令牌的生成器。解析器通过单令牌前瞻来消费这些令牌。

## 解析器

`src/Parser/Parser.php` 是手写的 Pratt 解析器。表达式优先级定义在 `src/Parser/Precedence.php` 中。语句则使用独立的递归下降解析器。

它根据 `src/Ast/` 生成不可变 AST 节点。每个节点都带有源代码位置，供堆栈跟踪和错误报告使用。

值得注意的结构：

- **表达式**：`Literal`、`Identifier`、`BinaryExpression`、`UnaryExpression`、`AssignmentExpression`、`CallExpression`、`MemberExpression`、`ArrowFunction`、`ObjectExpression`、`ArrayExpression`、`ConditionalExpression`、`TemplateLiteral`、`SpreadElement`、`NewExpression`、`ThisExpression`、`SequenceExpression`、`TaggedTemplate`、`ClassExpression`、`YieldExpression`、`AwaitExpression`。
- **语句**：`BlockStatement`、`IfStatement`、`ForStatement`、`ForInStatement`、`ForOfStatement`、`WhileStatement`、`DoWhileStatement`、`SwitchStatement`、`ReturnStatement`、`ThrowStatement`、`TryStatement`、`BreakStatement`、`ContinueStatement`、`ExpressionStatement`。
- **声明**：`VariableDeclaration`、`FunctionDeclaration`、`ClassDeclaration`、`ImportDeclaration`。
- **模式**（解构）：`ArrayPattern`、`ObjectPattern`、`RestElement`、`AssignmentPattern`。

自动分号插入是在解析器中（而不是词法分析器中）根据规范实现的。

## 解释器

`src/Runtime/Interpreter.php` 是 AST 遍历器。它访问每个节点并返回一个 `JsValue`（对于改变控制流的语句，则返回 `Completion`）。

关键的辅助类：

- **`Environment`**（`src/Runtime/Environment.php`）——词法作用域链。每次函数调用都会创建一个指向其词法父级的新环境。`var` 会提升到函数作用域；`let` / `const` 是块级作用域，并有 TDZ。
- **`CallStack`**（`src/Runtime/CallStack.php`）——带深度限制的显式栈。函数和方法调用会压入栈帧；异常会展开这些栈帧。
- **`Completion`**（`src/Runtime/Completion.php`）——由 `normal`、`return`、`throw`、`break`、`continue` 组成的判别联合类型，另含可选的值和标签。
- **`Reference`**（`src/Runtime/Reference.php`）——规范中的 Reference 类型，用于赋值和 `delete` 中的左值求值。

## 值

JavaScript 值是在 `src/Value/` 下的 PHP 对象。每个值都实现 `JsValue` 接口，并带有自己的类型转换方法（`toNumber()`、`toString()`、`toBoolean()`）。

| 类 | JS 类型 |
|---|---|
| `JsUndefined` | `undefined`（单例）|
| `JsNull` | `null`（单例）|
| `JsBoolean` | `true` / `false` |
| `JsNumber` | IEEE 754 双精度；保留 NaN、±Infinity、±0 |
| `JsString` | UTF-16 字符串语义 |
| `JsObject` | 带属性映射和原型链的基础对象 |
| `JsArray` | 扩展 `JsObject`，并跟踪长度 |
| `JsFunction` | 带有捕获的 `Environment` 的闭包 |
| `JsSymbol` | Symbol 原始值 |
| `JsBigInt` | 通过 bcmath 实现任意精度 |
| `JsProxy` | 全部 13 个陷阱，且可撤销 |
| `JsPromise` | 同步执行器 + 微任务队列 |
| `JsGenerator` | 双模式：帧快照模式（字节码编译的主体；参见 [字节码 VM § 帧快照生成器](https://phasis.inline0.dev/zh/docs/advanced/bytecode-vm)），并在 `yield*`、带 yield 的 try 语句和异步生成器中使用 PHP Fiber 回退方案 |
| `JsMap`、`JsSet`、`JsWeakMap`、`JsWeakSet`、`JsWeakRef`、`JsFinalizationRegistry` | 集合 |
| `JsArrayBuffer`、`JsSharedArrayBuffer`、`JsDataView`、`JsTypedArray` | 二进制数据 |

数字处理是历史问题中占比最大的一类：必须保证 `NaN !== NaN` 为 true，`-0 === 0` 也必须为 true；ToNumber 的边缘情况需要精确到位级处理。`Spec/TypeConversion` 类逐字实现了规范算法，并经过全面测试。

## 内置库

`src/BuiltIn/` 提供所有标准库对象：`Array`、`String`、`Object`、`Math`、`JSON`、`Date`、`RegExp`、`Map`、`Set`、`Promise`、`Proxy`、`Reflect`、`Symbol`、`BigInt`、`TypedArray`、`Temporal`、`Intl`、`console`，以及 Web Platform / Fetch 套件（`URL`、`Headers`、`Request`、`Response`、`Blob`、`FormData`、完整 Streams、`EventTarget`、`AbortController`/`AbortSignal`、`DOMException`、`structuredClone`、`performance`、`TextEncoder`/`TextDecoder`、`atob`/`btoa`）、`crypto` + `SubtleCrypto`、`WebSocket`、`XMLHttpRequest`，以及事件循环（`setTimeout`、`setInterval`、`queueMicrotask`、`AsyncContext`）。

每个内置对象都是类上的一个静态 `install()` 方法。构造时，`Engine` 会按依赖顺序调用它们。每个方法都会定义其原型链，将方法安装为包装在 PHP 闭包中的 `JsFunction` 实例，并将构造函数作为全局对象公开。

## PHP↔JS 桥接

跨边界的值转换分布在两个位置：

- **`Phasis\Interop\PhpToJs::convert()`**——将 PHP 值转换为 `JsValue`，用于 `setGlobal()`、`call()` 参数和宿主函数返回值。PHP 可调用对象会变成一个 `JsFunction`，其主体会调用该对象。
- **`PhpToJs::toPhp()`**——将 `JsValue` 转换为 PHP，用于宿主函数的参数。
- **`Engine::toPhp()`**（私有）——将 `JsValue` 转换为 PHP，用于 `eval()` 和 `call()` 的结果。

没有对象桥接：不可调用的 PHP 对象会转换为 `undefined`（[问题 #18](https://github.com/inline0/phasis/issues/18)）。`JsToPhp` 类在 `src/Bytecode/` 中，与互操作无关；它是下文介绍的转译器。转换规则详见 [值转换](https://phasis.inline0.dev/zh/docs/interop/values)。

## 错误处理

未捕获的 JS `throw` 会变成 PHP `Phasis\Exceptions\JsThrowable`；引擎自身抛出的错误会以 `Phasis\Exceptions\TypeError`、`RangeError` 或 `ReferenceError` 的形式呈现，这些都是 `Phasis\Exceptions\RuntimeError` 的子类。在引擎内部，JS `try` / `catch` 会捕获 `JsThrowable`、任何 `RuntimeError` 和 `Phasis\Exceptions\SyntaxError`。其他 PHP 异常（包括宿主函数抛出的异常）不会转换，而是直接传播给 PHP 调用方（[问题 #20](https://github.com/inline0/phasis/issues/20)）。

有两个限制：调用栈上限（`CallStack::maxDepth`，4,096 个栈帧，以及针对转译闭包的 8,192 帧保护）和 `maxLoopIterations`，通过 `Engine::setLimit()` 设置，且仅由树遍历器强制执行（[问题 #19](https://github.com/inline0/phasis/issues/19)）。两者都会抛出 `Phasis\Exceptions\InternalError`。字符串长度、输出大小或执行时间均无限制。

## 字节码 VM

热点函数会以两种方式降低。`JsToPhp`（在 `src/Bytecode/` 中）会为数值运算或大量使用局部变量的主体生成 PHP 闭包，PHP 的跟踪式 JIT 可以将其编译为本机代码；该流水线会降低 `new Ctor(args)`、`obj.method(args)` 和 `this.member` 读取，足以让典型的循环内类方法模式完全绕过 VM。其他代码则会编译为由 `VM::execute` 执行的扁平字节码，并使用自定义调用栈，让每次 JS 调用都留在单个 PHP 帧中，同时为原型方法读取提供按 PC 设置的内联缓存。简单的生成器主体也会编译为字节码，此时 `Op::YIELD` 会捕获堆帧快照，而不是跨越 PHP Fiber。

编译器无法降低的结构（e.g、`with`、带有重新绑定 catch 的 `try`、`yield*`、异步生成器）会回退到树遍历器。完整机制请参阅 [字节码 VM](https://phasis.inline0.dev/zh/docs/advanced/bytecode-vm)。
