Architektūra
Phasis yra keturių etapų medžio interpretatorius su oportunistine baitinio kodo virtualiąja mašina. Duomenų kelias:
JS text
│
▼
┌──────────┐
│ Lexer │ src/Lexer/
└────┬─────┘
▼ tokens
┌──────────┐
│ Parser │ src/Parser/
└────┬─────┘
▼ AST nodes
┌──────────────┐
│ Interpreter │ src/Runtime/
│ + Bytecode │ src/Bytecode/
│ VM │
└────┬─────────┘
▼ JsValue
PHP host
Kiekvienas etapas yra PHP klasė su nedidele sąsaja, kurią naudoja kitas etapas. Susiejimo etapo nėra – analizė sukuria AST, o interpretatorius jį eina tiesiogiai.
Leksinis analizatorius
src/Lexer/Lexer.php šaltinio tekstą paverčia Token egzempliorių srautu. Kiekvienas leksemas yra tik skaitoma struktūra: tipas (TokenType išvardijimas, apie 80 atvejų), reikšmė, eilutė, stulpelis.
Leksinis analizatorius apdoroja:
- Skaičių literalius – dešimtainius, šešioliktainius (
0x), aštuntainius (0o), dvejetainius (0b), eksponentinius ir su skaitmenų skirtukais. - Eilučių literalius – su viengubomis ir dvigubomis kabutėmis, visomis kaitos sekomis, šablonines eilutes su įdėtinėmis išraiškomis.
- Reguliariųjų reiškinių literalius – visišką
/pattern/flagstokenizavimą, atsižvelgiant į tai, kada/turėtų būti reguliarusis reiškinys, o kada dalyba. - Identifikatorius, raktažodžius, skyrybos ženklus.
- Komentarus (eilutės ir blokų), išsaugomus kaip papildomą informaciją.
- Unicode šaltinį – UTF-8 baitai interpretuojami kaip JS UTF-16 šaltinis.
Išvestis yra generatorius, kuris leksemas pateikia tingiai. Analizatorius juos vartoja naudodamas vieno leksemo peržiūrą į priekį.
Analizatorius
src/Parser/Parser.php yra rankomis parašytas Pratt analizatorius. Išraiškų pirmumo tvarka apibrėžta src/Parser/Precedence.php. Sakiniams taikoma atskira rekursinio nusileidimo analizė.
Jis iš src/Ast/ sukuria nekintamus AST mazgus. Kiekviename mazge nurodyta šaltinio vieta, skirta dėklo pėdsakams ir klaidų pranešimams.
Svarbios formos:
- Išraiška:
Literal,Identifier,BinaryExpression,UnaryExpression,AssignmentExpression,CallExpression,MemberExpression,ArrowFunction,ObjectExpression,ArrayExpression,ConditionalExpression,TemplateLiteral,SpreadElement,NewExpression,ThisExpression,SequenceExpression,TaggedTemplate,ClassExpression,YieldExpression,AwaitExpression. - Sakinys:
BlockStatement,IfStatement,ForStatement,ForInStatement,ForOfStatement,WhileStatement,DoWhileStatement,SwitchStatement,ReturnStatement,ThrowStatement,TryStatement,BreakStatement,ContinueStatement,ExpressionStatement. - Deklaracija:
VariableDeclaration,FunctionDeclaration,ClassDeclaration,ImportDeclaration. - Šablonas (destruktūrizavimas):
ArrayPattern,ObjectPattern,RestElement,AssignmentPattern.
Automatinis kabliataškių įterpimas vykdomas analizatoriuje (ne leksiniame analizatoriuje), kaip nurodyta specifikacijoje.
Interpretatorius
src/Runtime/Interpreter.php pereina AST mazgus. Jis aplanko kiekvieną mazgą ir grąžina JsValue (arba Completion sakiniams, kurie keičia valdymo srautą).
Pagrindinės pagalbinės klasės:
Environment(src/Runtime/Environment.php) – leksinių sričių grandinė. Kiekvienas funkcijos iškvietimas sukuria naują aplinką, rodančią į jos leksinę tėvinę aplinką.varperkeliama į funkcijos sritį;let/constpriklauso blokui ir turi TDZ.CallStack(src/Runtime/CallStack.php) – aiškus dėklas su gylio riba. Funkcijų ir metodų iškvietimai įdeda kadrus; išimtys juos išvynioja.Completion(src/Runtime/Completion.php) – diskriminacinė sąjunga, apimantinormal,return,throw,break,continue, taip pat pasirenkamą reikšmę ir žymą.Reference(src/Runtime/Reference.php) – specifikacijos Reference tipo objektas, skirtas kairiosios pusės vertinimui priskyrimuose irdelete.
Reikšmės
JavaScript reikšmės yra PHP objektai sistemoje src/Value/. Kiekvienas įgyvendina sąsają JsValue ir turi savo tipo konvertavimo metodus (toNumber(), toString(), toBoolean()).
| Klasė | JS tipas |
|---|---|
JsUndefined |
undefined (vienintelis egzempliorius) |
JsNull |
null (vienintelis egzempliorius) |
JsBoolean |
true / false |
JsNumber |
IEEE 754 double; išsaugo NaN, ±Infinity, ±0 |
JsString |
UTF-16 eilučių semantika |
JsObject |
pagrindinis objektas su savybių žemėlapiu ir prototipų grandine |
JsArray |
išplečia JsObject ir seka ilgį |
JsFunction |
uždarinys su užfiksuotu Environment |
JsSymbol |
Symbol primityvas |
JsBigInt |
savavališko tikslumo skaičiai naudojant bcmath |
JsProxy |
visi 13 perėmimo veiksmų, atšaukiamas |
JsPromise |
sinchroninis vykdytojas ir mikro užduočių eilė |
JsGenerator |
dviejų režimų: kadro momentinė kopija (baitiniu kodu sukompiliuoti kūnai; žr. Baitinio kodo VM § Generatorių kadro momentinės kopijos) su PHP Fiber atsarginiu variantu, skirtu yield*, try-with-yield ir asinchroniniams generatoriams |
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry |
rinkiniai |
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray |
dvejetainiai duomenys |
Skaičių apdorojimas yra didžiausias istorinių klaidų šaltinis: NaN !== NaN privalo būti true, -0 === 0 privalo būti true, o ToNumber kraštinius atvejus reikia tikrinti bitų lygmeniu. Klasė Spec/TypeConversion pažodžiui įgyvendina specifikacijos algoritmus ir yra išsamiai ištestuota.
Integruota biblioteka
src/BuiltIn/ pateikia visus standartinės bibliotekos objektus: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console, taip pat Web Platform / Fetch paketus (URL, Headers, Request, Response, Blob, FormData, visas Streams, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), crypto + SubtleCrypto, WebSocket, XMLHttpRequest ir įvykių ciklą (setTimeout, setInterval, queueMicrotask, AsyncContext).
Kiekvienas integruotas elementas yra statinis install() metodas klasėje. Kuriant, Engine iškviečia juos priklausomybių tvarka. Kiekvienas apibrėžia savo prototipų grandinę, įdiegia metodus kaip JsFunction egzempliorius, apgaubiančius PHP uždarinius, ir pateikia konstruktorių kaip globalų objektą.
PHP↔JS sąsaja
Reikšmių konvertavimas tarp ribų vykdomas dviejose vietose:
Phasis\Interop\PhpToJs::convert()– PHP reikšmės įJsValue; naudojamasetGlobal(), argumentamscall()ir pagrindinio kompiuterio funkcijų grąžinamoms reikšmėms. Iškviečiamas PHP tampaJsFunction, kurio turinyje jis iškviečiamas.PhpToJs::toPhp()–JsValueį PHP pagrindinio kompiuterio funkcijos argumentams.Engine::toPhp()(privatus) –JsValueį PHP, skirtaeval()ircall()rezultatams.
Objektų sąsajos nėra: nekviečiami PHP objektai konvertuojami į undefined (problema Nr. 18). Klasė JsToPhp faile src/Bytecode/ nėra susijusi su sąveika; tai toliau aprašomas transpiliatorius. Konvertavimo taisyklės aprašytos skiltyje Reikšmių konvertavimas.
Klaidų tvarkymas
Neapdorota JS throw tampa PHP Phasis\Exceptions\JsThrowable; pačios sistemos sukeltos klaidos pateikiamos kaip Phasis\Exceptions\TypeError, RangeError arba ReferenceError, visos jos yra Phasis\Exceptions\RuntimeError poklasiai. Sistemos viduje JS try / catch pagauna JsThrowable, bet kokį RuntimeError ir Phasis\Exceptions\SyntaxError. Kitos PHP išimtys, įskaitant pagrindinio kompiuterio funkcijų iškeltas, nekonvertuojamos ir perduodamos tiesiai PHP iškvietėjui (problema Nr. 20).
Yra du apribojimai: iškvietimų dėklo riba (CallStack::maxDepth, 4,096 kadrai ir 8,192 kadrų apsauga transpiliuotiems uždariniams) ir maxLoopIterations, nustatoma per Engine::setLimit() ir taikoma tik medžio interpretatoriaus (problema Nr. 19). Abu sukelia Phasis\Exceptions\InternalError. Eilučių ilgio, išvesties dydžio ar vykdymo trukmės apribojimų nėra.
Baitinio kodo VM
Dažnai vykdomos funkcijos kompiliuojamos dviem būdais. JsToPhp (faile src/Bytecode/) sugeneruoja PHP uždarinį skaičiams ir vietiniams kintamiesiems intensyviems kūnams, kuriuos PHP sekimo JIT gali kompiliuoti į vietinį kodą; šis procesas supaprastina new Ctor(args), obj.method(args) ir this.member skaitymus, todėl įprastas klasės metodo cikle šablonas visiškai apeina VM. Visa kita kompiliuojama į plokščią baitinį kodą, kurį apdoroja VM::execute; naudojamas tinkintas iškvietimų dėklas, išlaikantis kiekvieną JS iškvietimą viename PHP kadre, ir kiekvienam PC skirtas spartinančiosios atmintinės įrašas prototipo metodų skaitymui. Paprasti generatorių kūnai taip pat kompiliuojami į baitinį kodą; Op::YIELD užfiksuoja krūvoje laikomą kadro momentinę kopiją, todėl nereikia pereiti per PHP Fiber.
Formos, kurių kompiliatorius nesupaprastina (e.g, with, try su iš naujo susiejamu gaudymo bloku, yield*, asinchroniniai generatoriai), perduodamos medžio interpretatoriui. Visas veikimo principas aprašytas skiltyje Baitinio kodo VM.