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:
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/flagsso 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.varsa prenáša do rozsahu funkcie;let/constmajú 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ôtnormal,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í adelete.
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 naJsValue, používa sa pre argumentysetGlobal(),call()a návratové hodnoty hostiteľských funkcií. Volateľná hodnota PHP sa zmení naJsFunction, ktorého telo ju volá.PhpToJs::toPhp()—JsValuena PHP pre argumenty hostiteľskej funkcie.Engine::toPhp()(súkromné) —JsValuena PHP pre výsledkyeval()acall().
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.