Arkkitehtuuri
Phasis on nelivaiheinen puukävelijä, jossa on opportunistinen tavukoodikäyttöympäristö. Tietopolku on seuraava:
JS text
│
▼
┌──────────┐
│ Lexer │ src/Lexer/
└────┬─────┘
▼ tokens
┌──────────┐
│ Parser │ src/Parser/
└────┬─────┘
▼ AST nodes
┌──────────────┐
│ Interpreter │ src/Runtime/
│ + Bytecode │ src/Bytecode/
│ VM │
└────┬─────────┘
▼ JsValue
PHP host
Jokainen vaihe on PHP-luokka, jonka suppea rajapinta on seuraavan vaiheen käytössä. Linkitysvaihetta ei ole — jäsennys tuottaa AST:n, jota tulkki käy suoraan läpi.
Leksikaattori
src/Lexer/Lexer.php muuntaa lähdetekstin Token-instanssien virraksi. Jokainen token on vain luku -rakenteinen: tyyppi (TokenType-enumeraatio, noin 80 tapausta), arvo, rivi ja sarake.
Leksikaattori käsittelee seuraavat:
- Numeroliteraalit — desimaali, heksadesimaali (
0x), oktaali (0o), binääri (0b), eksponenttimuoto ja numeroryhmien erottimet. - Merkkijonoliteraalit — yksinkertaiset ja kaksinkertaiset lainausmerkit, kaikki escape-sekvenssit sekä sisäkkäisiä lausekkeita sisältävät mallimerkkijonot.
- Säännöllisten lausekkeiden literaalit — täydellinen
/pattern/flags-tokenisointi, jossa huomioidaan, onko/säännöllinen lauseke vai jakolasku. - Tunnisteet, avainsanat ja välimerkit.
- Kommentit (rivi- ja lohkokommentit), jotka säilytetään triviana.
- Unicode-lähde — UTF-8-tavut tulkitaan JS UTF-16-lähteeksi.
Tuloste on generaattori, joka tuottaa tokeneita laiskasti. Jäsennin kuluttaa niitä yhden tokenin ennakoinnilla.
Jäsennin
src/Parser/Parser.php on käsin kirjoitettu Pratt-jäsennin. Lausekkeiden etusijajärjestys määritellään kohteessa src/Parser/Precedence.php. Lausekkeilla on oma rekursiivinen laskeutumisjäsennyksensä.
Se tuottaa muuttumattomia AST-solmuja kohteesta src/Ast/. Jokaisessa solmussa on lähdesijainti pinon jäljityksiä ja virheraportointia varten.
Huomionarvoisia rakenteita:
- Lauseke:
Literal,Identifier,BinaryExpression,UnaryExpression,AssignmentExpression,CallExpression,MemberExpression,ArrowFunction,ObjectExpression,ArrayExpression,ConditionalExpression,TemplateLiteral,SpreadElement,NewExpression,ThisExpression,SequenceExpression,TaggedTemplate,ClassExpression,YieldExpression,AwaitExpression. - Lause:
BlockStatement,IfStatement,ForStatement,ForInStatement,ForOfStatement,WhileStatement,DoWhileStatement,SwitchStatement,ReturnStatement,ThrowStatement,TryStatement,BreakStatement,ContinueStatement,ExpressionStatement. - Määrittely:
VariableDeclaration,FunctionDeclaration,ClassDeclaration,ImportDeclaration. - Malli (hajautus):
ArrayPattern,ObjectPattern,RestElement,AssignmentPattern.
Automaattinen puolipisteen lisäys toteutetaan määrityksen mukaisesti jäsentimessä (ei leksikaattorissa).
Tulkki
src/Runtime/Interpreter.php käy AST:n läpi. Se vierailee jokaisessa solmussa ja palauttaa JsValue-arvon (tai Completion-arvon lauseille, jotka muuttavat ohjausvirtaa).
Keskeiset tukiluokat:
Environment(src/Runtime/Environment.php) — leksikaalisten näkyvyysalueiden ketju. Jokainen funktiokutsu luo uuden ympäristön, joka osoittaa leksikaaliseen ylätasoon.varnostetaan funktion näkyvyysalueeseen;let/constovat lohkokohtaisia, ja niihin liittyy TDZ.CallStack(src/Runtime/CallStack.php) — eksplisiittinen pino, jolla on syvyysraja. Funktio- ja metodikutsut lisäävät kehyksiä; poikkeukset purkavat ne.Completion(src/Runtime/Completion.php) — eroteltu unioni tyypeistänormal,return,throw,breakjacontinuesekä valinnainen arvo ja tunniste.Reference(src/Runtime/Reference.php) — määrityksen Reference-tyyppi vasemman puolen evaluointiin sijoituksissa jadelete-lausekkeissa.
Arvot
JavaScript-arvot ovat PHP-olioita kohteessa src/Value/. Kukin toteuttaa JsValue-rajapinnan ja sisältää omat tyyppimuunnosmetodinsa (toNumber(), toString(), toBoolean()).
| Luokka | JS-tyyppi |
|---|---|
JsUndefined |
undefined (singleton) |
JsNull |
null (singleton) |
JsBoolean |
true / false |
JsNumber |
IEEE 754 double; säilyttää arvot NaN, ±Infinity ja ±0 |
JsString |
UTF-16-merkkijonon semantiikka |
JsObject |
perusolio, jossa on ominaisuuskartta ja prototyyppiketju |
JsArray |
laajentaa JsObject-luokkaa ja seuraa pituutta |
JsFunction |
sulkeuma, johon on siepattu Environment |
JsSymbol |
Symbol-primitiivi |
JsBigInt |
mielivaltaisen tarkkuuden aritmetiikka bcmath-kirjastolla |
JsProxy |
kaikki 13 ansaa, peruutettavissa |
JsPromise |
synkroninen suorittaja + mikrotehtäväjono |
JsGenerator |
kaksitoiminen: kehysvedos (tavukoodiksi käännetyt rungot; katso Tavukoodikäyttöympäristö § Kehysvedosgeneraattorit) ja PHP Fiber -vararatkaisu kohteille yield*, try-with-yield ja asynkroniset generaattorit |
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry |
kokoelmat |
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray |
binääritiedot |
Lukujen käsittely on historiallisesti ollut suurin virhelähde: NaN !== NaN täytyy olla tosi, -0 === 0 täytyy olla tosi, ja ToNumber-reunatapaukset vaativat täsmällistä bittitason käsittelyä. Spec/TypeConversion-luokka toteuttaa määrityksen algoritmit sanatarkasti, ja se on testattu kattavasti.
Sisäänrakennettu kirjasto
src/BuiltIn/ tarjoaa kaikki vakiokirjaston oliot: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console sekä Web Platform- ja Fetch-paketit (URL, Headers, Request, Response, Blob, FormData, täysi Streams-tuki, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), crypto + SubtleCrypto, WebSocket, XMLHttpRequest sekä tapahtumasilmukan (setTimeout, setInterval, queueMicrotask, AsyncContext).
Jokainen sisäänrakennettu toiminto on luokan staattinen install()-metodi. Engine kutsuu niitä riippuvuusjärjestyksessä alustuksen yhteydessä. Kukin määrittää prototyyppiketjunsa, asentaa metodit JsFunction-instansseina, jotka on kääritty PHP-sulkeumiin, ja tuo konstruktorin saataville globaalina.
PHP↔JS-silta
Arvojen muuntaminen rajapinnan yli tapahtuu kahdessa kohdassa:
Phasis\Interop\PhpToJs::convert()— PHP-arvoJsValue-arvoksi; käytössä kohteillesetGlobal(),call()-argumentit ja isäntäfunktion palautusarvot. PHP-kutsukelpoisesta tuleeJsFunction, jonka runko kutsuu sitä.PhpToJs::toPhp()—JsValuearvoksi PHP isäntäfunktion argumentteja varten.Engine::toPhp()(yksityinen) —JsValuearvoksi PHP kohteideneval()jacall()tuloksia varten.
Oliosiltaa ei ole: PHP-oliot, jotka eivät ole kutsuttavia, muunnetaan muotoon undefined (ongelma #18). Kohteen src/Bytecode/ luokka JsToPhp ei liity yhteentoimivuuteen, vaan se on alla kuvattu transpilaattori. Muuntosäännöt on dokumentoitu kohdassa Arvojen muuntaminen.
Virheenkäsittely
Kiinni ottamaton JS throw muuttuu muotoon PHP Phasis\Exceptions\JsThrowable; moottorin itsensä nostamat virheet näkyvät muodossa Phasis\Exceptions\TypeError, RangeError tai ReferenceError, jotka kaikki ovat Phasis\Exceptions\RuntimeError-luokan aliluokkia. Moottorin sisällä JS try / catch ottaa kiinni kohteista JsThrowable, kaikista RuntimeError- ja Phasis\Exceptions\SyntaxError-arvoista. Muita PHP-poikkeuksia, kuten isäntäfunktioiden nostamia poikkeuksia, ei muunnettu, vaan ne välittyvät suoraan PHP-kutsujalle (ongelma #20).
Rajoituksia on kaksi: kutsupinon yläraja (CallStack::maxDepth, 4,096 kehystä sekä 8,192-kehyksen suoja transpiloituja sulkeumia varten) ja maxLoopIterations, joka asetetaan kohteessa Engine::setLimit() ja jota valvoo vain puukävelijä (ongelma #19). Molemmat nostavat Phasis\Exceptions\InternalError-poikkeuksen. Merkkijonon pituutta, tulosteen kokoa tai suoritusaikaa ei rajoiteta.
Tavukoodikäyttöympäristö
Usein kutsutut funktiot muunnetaan kahdella tavalla. JsToPhp (kohteessa src/Bytecode/) tuottaa PHP-sulkeuman numeerisille ja paikallismuuttujapainotteisille rungoille, jotka kohteen PHP jäljityspohjainen JIT-kääntäjä voi kääntää natiivikoodiksi; putki muuntaa kohteiden new Ctor(args), obj.method(args) ja this.member lukemiset. Näin tavallinen luokkametodikutsujen silmukka voi ohittaa VM:n kokonaan. Kaikki muu käännetään litteäksi tavukoodiksi, jota VM::execute suorittaa. Käytössä on mukautettu kutsupino, joka pitää jokaisen JS-kutsun yhden PHP-kehyksen sisällä, sekä PC-kohtainen välimuisti prototyyppimetodien lukemista varten. Myös yksinkertaiset generaattorirungot käännetään tavukoodiksi; Op::YIELD sieppaa kehyksen tilannevedoksen kekoon Fiberin PHP-rajapinnan ylittämisen sijaan.
Kaikki muodot, joita kääntäjä ei muunna (e.g, with, try uudelleen sidottavalla catch-käsittelijällä, yield* ja asynkroniset generaattorit), palautuvat puukävelijälle. Koko toimintamekanismi on kuvattu kohdassa Tavukoodikäyttöympäristö.