
# Архитектура

Phasis — это четырехэтапный обход дерева с виртуальной машиной байт-кода, включаемой при подходящих условиях. Поток данных:

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

Каждый этап — класс PHP с небольшим интерфейсом, который использует следующий этап. Этапа связывания нет: парсер создает AST, а интерпретатор обходит его напрямую.

## Лексер

`src/Lexer/Lexer.php` преобразует исходный текст в поток экземпляров `Token`. Каждый токен — неизменяемая структура: тип (перечисление `TokenType`, около 80 вариантов), значение, строка, столбец.

Лексер обрабатывает:

- Числовые литералы: десятичные, шестнадцатеричные (`0x`), восьмеричные (`0o`), двоичные (`0b`), экспоненциальные, с разделителями разрядов.
- Строковые литералы: одинарные и двойные кавычки, все escape-последовательности, шаблонные литералы с вложенными выражениями.
- Литералы регулярных выражений: полная токенизация `/pattern/flags` с учетом того, когда `/` должно трактоваться как регулярное выражение, а когда — как деление.
- Идентификаторы, ключевые слова, знаки пунктуации.
- Комментарии в строке и блоке, сохраняемые как вспомогательные элементы.
- Исходный код Unicode: байты UTF-8 интерпретируются как исходный код JS UTF-16.

Результат — генератор, который лениво выдает токены. Парсер считывает их с предварительным просмотром одного токена.

## Парсер

`src/Parser/Parser.php` — написанный вручную парсер Pratt. Приоритет операторов выражений задается в `src/Parser/Precedence.php`. Для операторов используется отдельный рекурсивный нисходящий разбор.

Он создает неизменяемые узлы AST из `src/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 — это объекты PHP под управлением `src/Value/`. Каждый реализует интерфейс `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` | два режима: снимок кадра (скомпилированные в байткод тела; см. [Глава «Виртуальная машина байткода § Генераторы со снимком кадра»](https://phasis.inline0.dev/ru/docs/advanced/bytecode-vm)) с резервным переходом на PHP Fiber для `yield*`, try-with-yield и асинхронных генераторов |
| `JsMap`, `JsSet`, `JsWeakMap`, `JsWeakSet`, `JsWeakRef`, `JsFinalizationRegistry` | коллекции |
| `JsArrayBuffer`, `JsSharedArrayBuffer`, `JsDataView`, `JsTypedArray` | двоичные данные |

Обработка чисел — главный источник ошибок, оставшихся с прежних версий: условие `NaN !== NaN` должно быть истинным, условие `-0 === 0` должно быть истинным, а пограничные случаи 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` вызывает их в порядке зависимостей. Каждый определяет свою цепочку прототипов, устанавливает методы как экземпляры `JsFunction`, обернутые в замыкания PHP, и предоставляет конструктор как глобальную сущность.

## Мост 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/ru/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`. Ограничений на длину строк, размер вывода или время выполнения нет.

## Виртуальная машина байткода

Горячие функции преобразуются двумя способами. `JsToPhp` (в `src/Bytecode/`) создает замыкание PHP для тел с большим количеством операций над числами и локальными переменными, которое трассирующий JIT-компилятор PHP может скомпилировать в машинный код. Конвейер преобразует обращения к `new Ctor(args)`, `obj.method(args)` и `this.member`, чего достаточно, чтобы типичный шаблон с методом класса в цикле полностью обходил виртуальную машину. Все остальное компилируется в плоский байткод, который выполняет `VM::execute` с пользовательским стеком вызовов: каждый вызов JS сохраняется в одном кадре PHP, а обращения к методам прототипа используют встроенный кэш для каждой точки выполнения. Простые тела генераторов тоже компилируются в байткод, а `Op::YIELD` сохраняет снимок кадра в куче вместо перехода через PHP Fiber.

Любая форма кода, которую компилятор не преобразует (e.g. `with`, `try` с повторным связыванием catch, `yield*`, асинхронные генераторы), передается обходчику дерева. Полный механизм описан в разделе [Виртуальная машина байткода](https://phasis.inline0.dev/ru/docs/advanced/bytecode-vm).
