Към съдържанието

Архитектура

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, който интерпретаторът обхожда директно.

Лексикален анализатор

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 double; запазва NaN, ±Infinity, ±0
JsString семантика на низовете UTF-16
JsObject базов обект с карта на свойствата и верига на прототипите
JsArray разширява JsObject с проследяване на дължината
JsFunction затваряне със захванат Environment
JsSymbol примитив Symbol
JsBigInt произволна точност чрез bcmath
JsProxy всичките 13 капана, с възможност за отмяна
JsPromise синхронен изпълнител + опашка за микрозадачи
JsGenerator двоен режим: генератори със снимка на рамката (компилирани до байткод тела; вижте Bytecode VM § Генератори със снимка на рамката) с резервен вариант чрез PHP Fiber за yield*, try-with-yield и асинхронни генератори
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, пълни потоци, 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). Класът JsToPhp в src/Bytecode/ не е свързан с взаимодействието; това е транспилаторът, описан по-долу. Правилата за преобразуване са описани в Преобразуване на стойности.

Обработка на грешки

Неуловен 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).

Има два лимита: таванът на стека за извиквания (CallStack::maxDepth, 4,096 рамки, плюс предпазител от 8,192 рамки за транспилирани затваряния) и maxLoopIterations, задаван чрез Engine::setLimit(), който се прилага само от обхождащото дърво (проблем № 19). И двата генерират Phasis\Exceptions\InternalError. Няма лимити за дължина на низовете, размер на изхода или време за изпълнение.

Виртуална машина за байткод

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

Всеки шаблон, който компилаторът не може да преобразува (e.g. with, try с повторно обвързване на catch, yield*, асинхронни генератори), преминава към обхождащото дърво. Вижте Виртуална машина за байткод за пълното описание на механизма.