Ugrás a tartalomhoz

Architektúra

A Phasis négylépcsős fa-bejáró, amely lehetőség szerint bájtkódos virtuális gépet használ. Az adatfolyam:

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

Minden lépés egy PHP osztály, amelynek egyszerű felületét a következő lépés használja. Nincs összekapcsolási lépés: a feldolgozás AST-t állít elő, amelyet az értelmező közvetlenül bejár.

Lexer

A src/Lexer/Lexer.php a forrásszöveget Token példányok adatfolyamává alakítja. Minden token csak olvasható struktúra: típus (TokenType felsorolási típus, körülbelül 80 eset), érték, sor, oszlop.

A lexer a következőket kezeli:

  • Számkonstansok – decimális, hexadecimális (0x), oktális (0o), bináris (0b), exponenciális alak, számelválasztók.
  • Karakterlánc-konstansok – egyszeres és dupla idézőjelek, minden escape-szekvencia, beágyazott kifejezéseket tartalmazó sablonkarakterláncok.
  • Reguláriskifejezés-konstansok – teljes /pattern/flags tokenizálás, annak figyelembevételével, hogy egy / reguláris kifejezésként vagy osztásként értelmezendő-e.
  • Azonosítók, kulcsszavak, írásjelek.
  • Megjegyzések (egysorosak és blokkok), amelyek mellékszövegként megmaradnak.
  • Unicode forrás – UTF-8 bájtok, amelyeket a JS UTF-16 forrásként értelmez.

A kimenet egy generátor, amely lustán szolgáltatja a tokeneket. A parser egytokenes előretekintéssel dolgozza fel őket.

Elemző

A src/Parser/Parser.php kézzel írt Pratt-parser. A kifejezésprecedenciákat a src/Parser/Precedence.php határozza meg. Az utasítások saját rekurzív leszállású feldolgozással rendelkeznek.

Az src/Ast/ alapján megváltoztathatatlan AST-csomópontokat állít elő. Minden csomópont tartalmazza a forrásbeli helyét a veremnyomokhoz és a hibajelentésekhez.

Jellegzetes formák:

  • Kifejezés: Literal, Identifier, BinaryExpression, UnaryExpression, AssignmentExpression, CallExpression, MemberExpression, ArrowFunction, ObjectExpression, ArrayExpression, ConditionalExpression, TemplateLiteral, SpreadElement, NewExpression, ThisExpression, SequenceExpression, TaggedTemplate, ClassExpression, YieldExpression, AwaitExpression.
  • Utasítás: BlockStatement, IfStatement, ForStatement, ForInStatement, ForOfStatement, WhileStatement, DoWhileStatement, SwitchStatement, ReturnStatement, ThrowStatement, TryStatement, BreakStatement, ContinueStatement, ExpressionStatement.
  • Deklaráció: VariableDeclaration, FunctionDeclaration, ClassDeclaration, ImportDeclaration.
  • Minta (destrukturálás): ArrayPattern, ObjectPattern, RestElement, AssignmentPattern.

A pontosvesszők automatikus beszúrását a specifikációnak megfelelően a parser valósítja meg, nem a lexer.

Értelmező

A src/Runtime/Interpreter.php az AST bejárója. Minden csomópontot meglátogat, és egy JsValue értéket ad vissza (vagy egy Completion értéket a vezérlési folyamatot módosító utasítások esetén).

Fontos támogató osztályok:

  • Environment (src/Runtime/Environment.php) – lexikális hatókörlánc. Minden függvényhívás új környezetet hoz létre, amely a lexikális szülőre mutat. A var a függvény hatókörébe kerül; a let és a const blokkhatókörű, TDZ-vel.
  • CallStack (src/Runtime/CallStack.php) – explicit verem mélységi korláttal. A függvény- és metódushívások veremkereteket helyeznek el; a kivételek leépítik ezeket.
  • Completion (src/Runtime/Completion.php) – a normal, return, throw, break és continue diszkriminált uniója, kiegészítve egy opcionális értékkel és címkével.
  • Reference (src/Runtime/Reference.php) – a specifikáció Reference típusa a bal oldali operandus kiértékeléséhez hozzárendelésekben és delete esetén.

Értékek

A JavaScript értékek a src/Value/ alatti PHP objektumok. Mindegyik megvalósítja a JsValue interfészt, és saját típuskonverziós metódusokkal rendelkezik (toNumber(), toString(), toBoolean()).

Osztály JS típus
JsUndefined undefined (singleton)
JsNull null (singleton)
JsBoolean true / false
JsNumber IEEE 754 double; megőrzi a NaN, ±Infinity, ±0 értékeket
JsString UTF-16 karakterlánc-szemantika
JsObject Alaposztály tulajdonságtérképpel és prototípuslánccal
JsArray A JsObject kiterjesztése, hossznyilvántartással
JsFunction Lezárás rögzített Environment értékkel
JsSymbol Symbol primitív érték
JsBigInt Tetszőleges pontosság a bcmath segítségével
JsProxy Mind a 13 csapda, visszavonható
JsPromise Szinkron végrehajtó + mikrotask-sor
JsGenerator Kétmódú: keretpillanatképes (bájtkódra fordított törzsek; lásd Bájtkód-VM § Keretpillanatképes generátorok) PHP Fiber-visszaeséssel a yield*, a try-with-yield és az aszinkron generátorok esetén
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry gyűjtemények
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray bináris adatok

A számok kezelése a korábbi hibák legnagyobb forrása: a NaN !== NaN feltételnek igaznak kell lennie, a -0 === 0 feltételnek igaznak kell lennie, a ToNumber szélső esetei pedig pontos, bitszintű odafigyelést igényelnek. A Spec/TypeConversion osztály szó szerint valósítja meg a specifikáció algoritmusait, és kimerítő tesztelésen esik át.

Beépített könyvtár

A src/BuiltIn/ biztosítja az összes szabványos könyvtári objektumot: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console, valamint a Web Platform / Fetch csomagokat (URL, Headers, Request, Response, Blob, FormData, teljes Streams, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), továbbá a crypto + SubtleCrypto, WebSocket, XMLHttpRequest és az eseményciklust (setTimeout, setInterval, queueMicrotask, AsyncContext).

Minden beépített elem egy statikus install() metódus egy osztályon. A Engine a létrehozáskor, függőségi sorrendben hívja meg ezeket. Mindegyik meghatározza a prototípusláncát, a metódusokat PHP lezárásokba burkolt JsFunction példányokként telepíti, és a konstruktort globálisként teszi elérhetővé.

PHP↔JS híd

A határon átnyúló értékkonverzió két helyen található:

  • Phasis\Interop\PhpToJs::convert() — PHP értékből JsValue lesz; a setGlobal(), a call() argumentumai és a gazdafüggvények visszatérési értékei használják. Egy PHP hívható értékből olyan JsFunction lesz, amelynek törzse meghívja azt.
  • PhpToJs::toPhp() — JsValue értékből PHP lesz a gazdafüggvény argumentumainál.
  • Engine::toPhp() (privát) — JsValue értékből PHP lesz a eval() és a call() eredményeinél.

Nincs objektumhíd: a nem hívható PHP objektumok undefined értékké alakulnak (18. probléma). Az src/Bytecode/-beli JsToPhp osztály nem kapcsolódik az együttműködéshez; ez az alább ismertetett transzpiláló. A konverziós szabályokat az Értékkonverzió dokumentálja.

Hibakezelés

Egy el nem kapott JS throw értékből PHP Phasis\Exceptions\JsThrowable lesz; a motor által előidézett hibák Phasis\Exceptions\TypeError, RangeError vagy ReferenceError formájában jelennek meg, amelyek mind a Phasis\Exceptions\RuntimeError alosztályai. A motoron belül egy JS try / catch elkapja a JsThrowable értékeket, bármely RuntimeError értéket és a Phasis\Exceptions\SyntaxError értéket. Az egyéb PHP kivételek, többek között a gazdafüggvények által dobottak, nem alakulnak át, hanem közvetlenül továbbterjednek a PHP hívóhoz (20. probléma).

Két korlát van: a hívási verem felső határa (CallStack::maxDepth, 4,096 keret, továbbá egy 8,192 keretes őr a transzpilált lezárásokhoz), valamint a maxLoopIterations, amely a Engine::setLimit() segítségével állítható be, és amelyet csak a fastruktúra-bejáró érvényesít (19. probléma). Mindkettő Phasis\Exceptions\InternalError hibát dob. Nincs korlát a karakterlánc hosszára, a kimenet méretére vagy a végrehajtási időre.

Bájtkód-VM

A gyakran futó függvények kétféleképpen fordulnak le. A JsToPhp (a src/Bytecode/ része) PHP lezárást állít elő a numerikus vagy sok lokális változót használó törzsekhez, amelyeket a PHP nyomkövető JIT-je natív kódra fordíthat; a folyamat lefordítja a new Ctor(args), obj.method(args) és this.member olvasásokat is, így a kanonikus, ciklusban hívott osztálymetódus-minta teljesen megkerüli a VM-et. Minden más lapos bájtkódra fordul, amelyet a VM::execute futtat; egyedi hívásiverem-kezelése minden JS hívást egyetlen PHP kereten belül tart, a prototípusmetódusok olvasásához pedig programszámlálóhelyenkénti inline gyorsítótárat használ. Az egyszerű generátortörzsek is bájtkódra fordulnak, és a Op::YIELD a PHP Fiber átlépése helyett egy halomkeret-pillanatképet rögzít.

Minden olyan alak, amelyet a fordító nem tud lefordítani (e.g, with, újrakötést végző catch-csel használt try, yield*, aszinkron generátorok), visszaesik a fastruktúra-bejáróra. A teljes működésről lásd a Bájtkód-VM leírást.