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:
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/flagstokenizá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. Avara függvény hatókörébe kerül; aletés aconstblokkható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) – anormal,return,throw,breakéscontinuediszkriminá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 ésdeleteeseté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őlJsValuelesz; asetGlobal(), acall()argumentumai és a gazdafüggvények visszatérési értékei használják. Egy PHP hívható értékből olyanJsFunctionlesz, 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 aeval()és acall()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.