Preskočiť na obsah

Architektúra

Phasis je štvorstupňový stromový interpret s príležitostným použitím virtuálneho stroja s bajtkódom. Tok dát je takýto:

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

Každý stupeň je trieda PHP s malým rozhraním, ktoré využíva nasledujúci stupeň. Fáza prepojenia chýba — parsovanie vytvorí AST, ktorý interpret priamo prechádza.

Lexikálny analyzátor

src/Lexer/Lexer.php prevádza zdrojový text na prúd inštancií Token. Každý token je nemenná štruktúra: typ (výčet TokenType, približne 80 možností), hodnota, riadok, stĺpec.

Lexikálny analyzátor spracúva:

  • Číselné literály — desiatkové, hexadecimálne (0x), osmičkové (0o), binárne (0b), exponenciálne a s oddeľovačmi číslic.
  • Reťazcové literály — jednoduché a dvojité úvodzovky, všetky escape sekvencie a šablónové literály s vnorenými výrazmi.
  • Literály regulárnych výrazov — úplná tokenizácia /pattern/flags so zohľadnením toho, či má byť / regulárny výraz alebo delenie.
  • Identifikátory, kľúčové slová, interpunkcia.
  • Komentáre (riadkové a blokové), zachované ako nepodstatné prvky.
  • Zdroj Unicode — bajty UTF-8 interpretované ako zdroj JS UTF-16.

Výstupom je generátor, ktorý postupne vracia tokeny podľa potreby. Syntaktický analyzátor ich spracúva s jedn tokenovým náhľadom dopredu.

Parser

src/Parser/Parser.php je ručne napísaný Prattov syntaktický analyzátor. Priorita operátorov vo výrazoch je určená v src/Parser/Precedence.php. Príkazy sa analyzujú samostatne rekurzívnym zostupom.

Vytvára nemenné uzly AST z src/Ast/. Každý uzol nesie informáciu o zdrojovej pozícii pre výpis zásobníka a hlásenie chýb.

Významné tvary:

  • Výraz: Literal, Identifier, BinaryExpression, UnaryExpression, AssignmentExpression, CallExpression, MemberExpression, ArrowFunction, ObjectExpression, ArrayExpression, ConditionalExpression, TemplateLiteral, SpreadElement, NewExpression, ThisExpression, SequenceExpression, TaggedTemplate, ClassExpression, YieldExpression, AwaitExpression.
  • Príkaz: BlockStatement, IfStatement, ForStatement, ForInStatement, ForOfStatement, WhileStatement, DoWhileStatement, SwitchStatement, ReturnStatement, ThrowStatement, TryStatement, BreakStatement, ContinueStatement, ExpressionStatement.
  • Deklarácia: VariableDeclaration, FunctionDeclaration, ClassDeclaration, ImportDeclaration.
  • Vzor (destrukturalizácia): ArrayPattern, ObjectPattern, RestElement, AssignmentPattern.

Automatické vkladanie bodkočiarok sa podľa špecifikácie vykonáva v syntaktickom analyzátore, nie v lexikálnom analyzátore.

Interpret

src/Runtime/Interpreter.php prechádza strom AST. Navštívi každý uzol a vráti JsValue (alebo Completion pri príkazoch, ktoré menia tok riadenia).

Kľúčové podporné triedy:

  • Environment (src/Runtime/Environment.php) — reťazec lexikálnych rozsahov. Každé volanie funkcie vytvorí nové prostredie odkazujúce na svoj lexikálny rodičovský rozsah. var sa prenáša do rozsahu funkcie; let / const majú rozsah bloku a TDZ.
  • CallStack (src/Runtime/CallStack.php) — explicitný zásobník s limitom hĺbky. Volania funkcií a metód pridávajú rámce; výnimky ich odvíjajú.
  • Completion (src/Runtime/Completion.php) — rozlíšená únia hodnôt normal, return, throw, break, continue, s voliteľnou hodnotou a označením.
  • Reference (src/Runtime/Reference.php) — referenčný typ podľa špecifikácie na vyhodnocovanie ľavej strany priradení a delete.

Hodnoty

Hodnoty JavaScript sú objekty PHP v rámci src/Value/. Každá implementuje rozhranie JsValue a obsahuje vlastné metódy na konverziu typov (toNumber(), toString(), toBoolean()).

Trieda Typ JS
JsUndefined undefined (singleton)
JsNull null (singleton)
JsBoolean true / false
JsNumber IEEE 754 double; zachováva NaN, ±Infinity, ±0
JsString Sémantika reťazcov UTF-16
JsObject Základný objekt s mapou vlastností a reťazcom prototypov
JsArray Rozširuje JsObject a sleduje dĺžku
JsFunction Uzáver so zachyteným Environment
JsSymbol Primitívny symbol
JsBigInt Ľubovoľná presnosť prostredníctvom bcmath
JsProxy Všetkých 13 záchytných metód, možnosť odvolania
JsPromise Synchrónny vykonávač + front mikroúloh
JsGenerator Dvojrežimový: snímka rámca (tela skompilované do bajtkódu; pozri Virtuálny stroj s bajtkódom § Generátory so snímkou rámca) s náhradnou možnosťou PHP Fiber pre yield*, try-with-yield a asynchrónne generátory
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry kolekcie
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray binárne údaje

Spracovanie čísel je historicky najväčším zdrojom chýb: NaN !== NaN musí byť true a -0 === 0 musí byť true; okrajové prípady ToNumber si vyžadujú presné bitové spracovanie. Trieda Spec/TypeConversion implementuje algoritmy špecifikácie doslovne a je vyčerpávajúco testovaná.

Vstavaná knižnica

src/BuiltIn/ poskytuje všetky objekty štandardnej knižnice: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console a tiež balíky Web Platform / Fetch (URL, Headers, Request, Response, Blob, FormData, úplné Streams, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), crypto + SubtleCrypto, WebSocket, XMLHttpRequest a slučku udalostí (setTimeout, setInterval, queueMicrotask, AsyncContext).

Každá vstavaná funkcia je statická metóda install() na triede. Engine ich pri vytváraní volá v poradí podľa závislostí. Každá definuje reťazec prototypov, nainštaluje metódy ako inštancie JsFunction obalené okolo uzáverov PHP a sprístupní konštruktor ako globálnu premennú.

Prepojenie PHP↔JS

Konverzia hodnôt cez hranicu prebieha na dvoch miestach:

  • Phasis\Interop\PhpToJs::convert() — hodnota PHP na JsValue, používa sa pre argumenty setGlobal(), call() a návratové hodnoty hostiteľských funkcií. Volateľná hodnota PHP sa zmení na JsFunction, ktorého telo ju volá.
  • PhpToJs::toPhp() — JsValue na PHP pre argumenty hostiteľskej funkcie.
  • Engine::toPhp() (súkromné) — JsValue na PHP pre výsledky eval() a call().

Neexistuje prepojenie objektov: objekty PHP, ktoré nie sú volateľné, sa konvertujú na undefined (problém č. 18). Trieda JsToPhp v src/Bytecode/ nesúvisí s interoperabilitou; ide o transpiler opísaný nižšie. Pravidlá konverzie sú uvedené v časti Konverzia hodnôt.

Spracovanie chýb

Nezachytená výnimka JS throw sa zmení na PHP Phasis\Exceptions\JsThrowable; chyby vyvolané samotným jadrom sa zobrazia ako Phasis\Exceptions\TypeError, RangeError alebo ReferenceError, ktoré sú všetky podtriedami Phasis\Exceptions\RuntimeError. V jadre zachytáva JS try / catch výnimky JsThrowable, všetky RuntimeError a Phasis\Exceptions\SyntaxError. Ostatné výnimky PHP vrátane tých, ktoré vyhodia hostiteľské funkcie, sa nekonvertujú a prejdú priamo k volajúcemu PHP (problém č. 20).

Existujú dva limity: strop zásobníka volaní (CallStack::maxDepth, 4,096 rámcov a ochrana s 8,192 rámcami pre transpilarované uzávery) a maxLoopIterations, nastavovaný cez Engine::setLimit(), ktorý vynucuje iba stromový interpret (problém č. 19). Pri prekročení oboch limitov sa vyhodí Phasis\Exceptions\InternalError. Neplatia žiadne limity dĺžky reťazca, veľkosti výstupu ani času vykonávania.

Virtuálny stroj s bajtkódom

Horúce funkcie sa prekladajú dvoma spôsobmi. JsToPhp (v src/Bytecode/) vytvára PHP uzáver pre telá funkcií s číselnými hodnotami a množstvom lokálnych premenných, ktorý dokáže trasovací JIT nástroja PHP skompilovať do natívneho kódu; pipeline prekladá čítania new Ctor(args), obj.method(args) a this.member, čo stačí na to, aby typický vzor metódy triedy vo vnútri cyklu úplne obišiel VM. Všetko ostatné sa skompiluje do plochého bajtkódu, ktorý vykonáva VM::execute. Používa vlastný zásobník volaní, ktorý udržiava každé volanie JS v jednom rámci PHP, a inline cache pre čítania metód prototypu pre každý PC. Jednoduché telá generátorov sa tiež kompilujú do bajtkódu a Op::YIELD zachytáva snímku rámca na halde namiesto prechodu cez Fiber PHP.

Všetky konštrukcie, ktoré kompilátor nepreloží (e.g. with, try s premenenou väzbou v bloku catch, yield*, asynchrónne generátory), sa vykonávajú pomocou stromového interpretera. Úplný opis mechanizmu nájdete v časti Virtuálny stroj bajtkódu.