Přejít k obsahu

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í:

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á 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/flags se 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. var se přesouvá do rozsahu funkce; let / const mají 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, continue doplně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í a delete.

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 na JsValue, používaná pro setGlobal(), argumenty call() a návratové hodnoty hostitelských funkcí. Volatelná hodnota PHP se změní na JsFunction, jehož tělo ji zavolá.
  • PhpToJs::toPhp() — JsValue na PHP pro argumenty hostitelské funkce.
  • Engine::toPhp() (privátní) — JsValue na PHP pro výsledky eval() a call().

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.