Architektura
Phasis je čtyřstupňový stromový interpret s příležitostným využitím virtuálního stroje pro bajtkód. Tok dat je následující:
JS text
│
▼
┌──────────┐
│ Lexer │ src/Lexer/
└────┬─────┘
▼ tokens
┌──────────┐
│ Parser │ src/Parser/
└────┬─────┘
▼ AST nodes
┌──────────────┐
│ Interpreter │ src/Runtime/
│ + Bytecode │ src/Bytecode/
│ VM │
└────┬─────────┘
▼ JsValue
PHP host
Každá fáze je třída PHP s jednoduchým rozhraním, které využívá následující fáze. Krok propojování chybí — parsování vytvoří AST a interpret jej prochází přímo.
Lexer
src/Lexer/Lexer.php převádí zdrojový text na proud instancí Token. Každý token je neměnná struktura: typ (výčet TokenType, přibližně 80 možností), hodnota, řádek a sloupec.
Lexer zpracovává:
- Číselné literály — desítkové, šestnáctkové (
0x), osmičkové (0o), binární (0b), exponenciální a s oddělovači číslic. - Řetězcové literály — jednoduché a dvojité uvozovky, všechny escape sekvence, šablonové literály s vnořenými výrazy.
- Literály regulárních výrazů — úplné tokenizování
/pattern/flagsse zohledněním toho, kdy má být/regulární výraz a kdy dělení. - Identifikátory, klíčová slova, interpunkce.
- Komentáře (řádkové i blokové), které se zachovávají jako trivia.
- Zdroj Unicode — bajty UTF-8 interpretované jako zdroj JS v UTF-16.
Výstupem je generátor, který postupně předává tokeny. Parser je zpracovává s jednokomponentním předstihem.
Parser
src/Parser/Parser.php je ručně napsaný Prattův parser. Priorita výrazů je definována v src/Parser/Precedence.php. Příkazy používají vlastní rekurzivní sestup.
Vytváří neměnné uzly AST z src/Ast/. Každý uzel uchovává umístění ve zdrojovém kódu pro trasování zásobníku a hlášení chyb.
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. - Příkaz:
BlockStatement,IfStatement,ForStatement,ForInStatement,ForOfStatement,WhileStatement,DoWhileStatement,SwitchStatement,ReturnStatement,ThrowStatement,TryStatement,BreakStatement,ContinueStatement,ExpressionStatement. - Deklarace:
VariableDeclaration,FunctionDeclaration,ClassDeclaration,ImportDeclaration. - Vzor (destrukturalizace):
ArrayPattern,ObjectPattern,RestElement,AssignmentPattern.
Automatické vkládání středníků je podle specifikace implementováno v parseru, nikoli v lexeru.
Interpret
src/Runtime/Interpreter.php prochází AST. Navštěvuje každý uzel a vrací JsValue (nebo Completion pro příkazy měnící tok řízení).
Hlavní podpůrné třídy:
Environment(src/Runtime/Environment.php) — řetězec lexikálních rozsahů. Každé volání funkce vytvoří nové prostředí odkazující na svého lexikálního rodiče.varse přesouvá do rozsahu funkce;let/constmají rozsah bloku a TDZ.CallStack(src/Runtime/CallStack.php) — explicitní zásobník s omezenou hloubkou. Volání funkcí a metod přidávají rámce; výjimky je postupně odebírají.Completion(src/Runtime/Completion.php) — rozlišovaná unie typůnormal,return,throw,break,continuedoplněná volitelnou hodnotou a návěštím.Reference(src/Runtime/Reference.php) — typ Reference podle specifikace pro vyhodnocování levé strany při přiřazování adelete.
Hodnoty
Hodnoty JavaScript jsou objekty PHP pod src/Value/. Každá implementuje rozhraní JsValue a má vlastní metody převodu typů (toNumber(), toString(), toBoolean()).
| Třída | JS typ |
|---|---|
JsUndefined |
undefined (singleton) |
JsNull |
null (singleton) |
JsBoolean |
true / false |
JsNumber |
IEEE 754 double; zachovává NaN, ±Infinity, ±0 |
JsString |
sémantika řetězců UTF-16 |
JsObject |
základní objekt s mapou vlastností a řetězcem prototypů |
JsArray |
rozšiřuje JsObject o sledování délky |
JsFunction |
uzávěr se zachyceným Environment |
JsSymbol |
primitivní typ Symbol |
JsBigInt |
libovolná přesnost pomocí bcmath |
JsProxy |
všech 13 pastí, odvolatelné |
JsPromise |
synchronní vykonavatel + fronta mikroúloh |
JsGenerator |
duální režim: generátory se snímkem rámce (těla zkompilovaná do bajtkódu; viz Bajtkódový VM § Generátory se snímkem rámce) s náhradním řešením pomocí PHP Fiber pro yield*, try-with-yield a asynchronní generátory |
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry |
kolekce |
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray |
binární data |
Zpracování čísel je historicky zdaleka největším zdrojem chyb: NaN !== NaN musí platit, -0 === 0 musí platit a okrajové případy ToNumber vyžadují přesnou práci na úrovni bitů. Třída Spec/TypeConversion implementuje algoritmy specifikace doslovně a je důkladně testována.
Vestavěná knihovna
src/BuiltIn/ poskytuje všechny objekty standardní knihovny: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console a také 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 smyčku událostí (setTimeout, setInterval, queueMicrotask, AsyncContext).
Každá vestavěná funkce je statická metoda install() ve třídě. Engine je při vytváření volá v pořadí podle závislostí. Každá z nich definuje řetězec prototypů, instaluje metody jako instance JsFunction obalené kolem uzávěrů PHP a zpřístupní konstruktor jako globální proměnnou.
Propojení PHP↔JS
Převod hodnot přes hranici probíhá na dvou místech:
Phasis\Interop\PhpToJs::convert()— hodnota PHP naJsValue, používaná prosetGlobal(), argumentycall()a návratové hodnoty hostitelských funkcí. Volatelná hodnota PHP se změní naJsFunction, jehož tělo ji zavolá.PhpToJs::toPhp()—JsValuena PHP pro argumenty hostitelské funkce.Engine::toPhp()(privátní) —JsValuena PHP pro výsledkyeval()acall().
Pro objekty neexistuje most: objekty PHP, které nejsou volatelné, se převedou na undefined (issue #18). Třída JsToPhp v src/Bytecode/ nesouvisí s interoperabilitou; jde o transpiler popsaný níže. Pravidla převodu jsou popsána v části Převod hodnot.
Zpracování chyb
Nezachycená JS throw se změní na PHP Phasis\Exceptions\JsThrowable; chyby vyvolané samotným enginem se projeví jako Phasis\Exceptions\TypeError, RangeError nebo ReferenceError, což jsou vše podtřídy Phasis\Exceptions\RuntimeError. Uvnitř enginu zachytí JS try / catch výjimky JsThrowable, libovolné RuntimeError a Phasis\Exceptions\SyntaxError. Jiné výjimky PHP, včetně těch vyvolaných hostitelskými funkcemi, se nepřevádějí a předávají se přímo volajícímu PHP (issue #20).
Existují dva limity: horní hranice hloubky zásobníku volání (CallStack::maxDepth, 4,096 rámců a navíc ochranný limit 8,192 rámců pro transpillované uzávěry) a maxLoopIterations, nastavený přes Engine::setLimit(), který vynucuje pouze stromový interpret (issue #19). Oba vyvolají Phasis\Exceptions\InternalError. Délka řetězců, velikost výstupu ani doba běhu nejsou omezené.
Virtuální stroj s bajtkódem
Často volané funkce se převádějí dvěma způsoby. JsToPhp (v src/Bytecode/) vytváří uzávěr PHP pro těla s převahou číselných operací a lokálních proměnných, který trasovací JIT kompilátor PHP může zkompilovat do nativního kódu; zpracování převádí čtení new Ctor(args), obj.method(args) a this.member, což stačí k tomu, aby kanonický vzor volání metody třídy ve smyčce zcela obešel VM. Vše ostatní se kompiluje do plochého bajtkódu zpracovávaného pomocí VM::execute s vlastním zásobníkem volání, který udržuje každé volání JS v jediném rámci PHP a používá mezipaměť inline pro jednotlivá PC při čtení metod prototypu. Jednoduchá těla generátorů se také kompilují do bajtkódu; Op::YIELD zachytí snímek rámce na haldě, takže není nutné přecházet přes Fiber PHP.
Každý tvar, který kompilátor nedokáže převést (e.g. with, try s opětovnou vazbou v catch, yield*, asynchronní generátory), přejde ke stromovému interpretu. Podrobný popis mechanismu najdete v části Virtuální stroj s bajtkódem.