Архітектура
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), експоненційні, з роздільниками чисел. - Рядкові літерали — одинарні й подвійні лапки, усі керівні послідовності, шаблонні рядки з вкладеними виразами.
- Літерали регулярних виразів — повна токенізація
/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 |
два режими: знімок кадру (тіла, скомпільовані в байткод; див. Віртуальна машина байткоду § Генератори зі знімками кадрів) з резервним варіантом на основі 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, повний 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 (issue #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 (issue #20).
Є два обмеження: межа глибини стека викликів (CallStack::maxDepth, 4,096 кадрів, плюс захист на 8,192 кадрів для транспільованих замикань) і maxLoopIterations, що задається через Engine::setLimit() і контролюється лише інтерпретатором дерева (issue #19). Обидва спричиняють Phasis\Exceptions\InternalError. Обмежень на довжину рядків, розмір виводу чи час виконання немає.
Віртуальна машина байткоду
Гарячі функції оптимізуються двома способами. JsToPhp (у src/Bytecode/) генерує замикання PHP для тіл із числовими операціями чи багатьма локальними змінними, яке трасувальний JIT-компілятор PHP може скомпілювати в машинний код; конвеєр оптимізує читання new Ctor(args), obj.method(args) і this.member, чого достатньо, щоб типовий шаблон із методом класу в циклі повністю оминув віртуальну машину. Усе інше компілюється у плоский байткод, який обробляє VM::execute із власним стеком викликів: кожен виклик JS залишається в межах одного кадру PHP, а для читання методів прототипу використовується кеш для кожного PC. Прості тіла генераторів також компілюються в байткод; Op::YIELD зберігає знімок кадру в купі замість переходу до Fiber PHP.
Будь-яка форма, яку компілятор не оптимізує (e.g. with, try із повторним прив’язуванням у catch, yield*, асинхронні генератори), обробляється інтерпретатором дерева. Повний опис механізму див. у розділі Віртуальна машина байткоду.