Liigu sisu juurde

Arhitektuur

Phasis on neljaastmeline puu läbiv käitur, millel on oportunistlik baitkoodiga virtuaalmasin. Andmete liikumine:

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

Iga etapp on PHP klass väikese liidesepinnaga, mida kasutab järgmine etapp. Linkimisetappi pole – parsingu tulemus on AST, mida interpretaator läbib otse.

Leksikaalanalüsaator

src/Lexer/Lexer.php teisendab lähtekoodi tokenite vooguks, mis koosneb Token eksemplaridest. Iga token on kirjutuskaitstud struktuur: tüüp (TokenType enum, umbes 80 varianti), väärtus, rida ja veerg.

Leksikaalanalüsaator käsitleb järgmist:

  • Arvuliteraalid – kümnend-, kuueteistkümnend- (0x), kaheksand- (0o), kahend- (0b) ja astmikkirjapilt ning arvude eraldajad.
  • Stringiliteraalid – ühe- ja kahekordsed jutumärgid, kõik paojadad ning malliliteraalid pesastatud avaldistega.
  • Regulaaravaldiste literaalid – täielik /pattern/flags tokeniseerimine, arvestades, kas / tähistab regulaaravaldist või jagamist.
  • Tähised, võtmesõnad ja kirjavahemärgid.
  • Kommentaarid (rea- ja plokikommentaarid), mis säilitatakse trivia osana.
  • Unicode lähtekood – UTF-8 baite tõlgendatakse JS UTF-16 lähtekoodina.

Väljund on generaator, mis väljastab tokeneid laisalt. Parser tarbib neid ühe tokeni võrra ette vaadates.

Parser

src/Parser/Parser.php on käsitsi kirjutatud Pratti parser. Avaldiste prioriteedid on määratud src/Parser/Precedence.php-s. Lausetel on oma rekursiivne laskumine.

See loob src/Ast/ põhjal muutmatud AST-sõlmed. Iga sõlm sisaldab oma lähtekohta pinu jälgede ja vigadest teatamise jaoks.

Märkimisväärsed vormid:

  • Avaldis: 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.
  • Deklaratsioon: VariableDeclaration, FunctionDeclaration, ClassDeclaration, ImportDeclaration.
  • Muster (destruktureerimine): ArrayPattern, ObjectPattern, RestElement, AssignmentPattern.

Automaatne semikooloni lisamine on spetsifikatsiooni järgi rakendatud parseris (mitte leksikaalanalüsaatoris).

Interpretaator

src/Runtime/Interpreter.php läbib AST-d. See külastab iga sõlme ja tagastab JsValue (või Completion juhtvoogu muutvate lausete puhul).

Peamised abiklassid:

  • Environment (src/Runtime/Environment.php) – leksikaalsete ulatuste ahel. Iga funktsioonikutse loob uue keskkonna, mis viitab oma leksikaalsele vanemale. var tõstetakse funktsiooni ulatusse; let / const on plokiulatuses ja TDZ-ga.
  • CallStack (src/Runtime/CallStack.php) – selgesõnaline pinu koos sügavuspiiranguga. Funktsiooni- ja meetodikutsed lisavad pinuraame; erandid eemaldavad need.
  • Completion (src/Runtime/Completion.php) – eristatud ühend tüübiga normal, return, throw, break, continue ning valikulise väärtuse ja sildiga.
  • Reference (src/Runtime/Reference.php) – spetsifikatsiooni Reference-tüüp omistamiste ja delete vasaku poole hindamiseks.

Väärtused

JavaScript väärtused on src/Value/ all olevad PHP objektid. Igaüks neist rakendab liidest JsValue ja sisaldab oma tüübiteisenduse meetodeid (toNumber(), toString(), toBoolean()).

Klass JS tüüp
JsUndefined undefined (ainikeksemplar)
JsNull null (ainikeksemplar)
JsBoolean true / false
JsNumber IEEE 754 topel täpsus; säilitab NaN-i, ±Infinity ja ±0
JsString UTF-16 stringi semantika
JsObject põhiobjekt omaduste kaardi ja prototüübiahelaga
JsArray laiendab JsObject-t, jälgides pikkust
JsFunction sulund, milles on hõivatud Environment
JsSymbol primitiivne Symbol-väärtus
JsBigInt täppisarvutus bcmath abil
JsProxy kõik 13 lõksu; tühistatav
JsPromise sünkroonne täitur + mikroülesannete järjekord
JsGenerator kaherežiimiline: kaadri hetktõmmis (baitkoodiks kompileeritud kehad; vt Baitkoodi VM § Kaadri hetktõmmise generaatorid) koos PHP Fiberi varulahendusega yield*, try-with-yieldi ja asünkroonsete generaatorite jaoks
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry kogumid
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray binaarandmed

Arvude käsitlemine on ajalooliselt olnud suurim vigade allikas: NaN !== NaN peab kehtima, -0 === 0 peab kehtima ning ToNumberi erijuhtumid nõuavad täpsust bititasemel. Klass Spec/TypeConversion rakendab spetsifikatsiooni algoritme sõna-sõnalt ja seda on ammendavalt testitud.

Sisseehitatud teek

src/BuiltIn/ pakub kõiki standardteegi objekte: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console, lisaks Web Platformi / Fetchi paketid (URL, Headers, Request, Response, Blob, FormData, täielik Streams, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), crypto + SubtleCrypto, WebSocket, XMLHttpRequest ja sündmuste tsükli (setTimeout, setInterval, queueMicrotask, AsyncContext).

Iga sisseehitatud objekt on klassi staatiline install()-meetod. Engine kutsub need konstruktsiooni käigus sõltuvuste järjekorras välja. Igaüks määratleb oma prototüübiahela, paigaldab meetodid kui JsFunction eksemplarid, mis on ümbritsetud PHP sulunditega, ning avalikustab konstruktori globaalse objektina.

PHP↔JS sild

Väärtuste teisendamine üle piiri toimub kahes kohas:

  • Phasis\Interop\PhpToJs::convert() – PHP väärtusest JsValue-ks; seda kasutatakse setGlobal(), call() argumentide ja hostifunktsiooni tagastusväärtuste puhul. PHP-ga väljakutsutavast objektist saab JsFunction, mille sisu seda välja kutsub.
  • PhpToJs::toPhp() – JsValue-st PHP-ks hostifunktsiooni argumentide jaoks.
  • Engine::toPhp() (privaatne) – tulemuste jaoks JsValue-st PHP-ks: eval() ja call().

Objektisilda pole: mitte väljakutsutavad PHP objektid teisendatakse väärtuseks undefined (probleem #18). Klass JsToPhp failis src/Bytecode/ pole koostalitlusvõimega seotud; see on allpool kirjeldatud transpiiler. Teisendusreeglid on kirjas jaotises Väärtuste teisendamine.

Vigade käsitlemine

Püüdmata JS throw muutub objektiks PHP Phasis\Exceptions\JsThrowable; mootori enda tekitatud vead kuvatakse kujul Phasis\Exceptions\TypeError, RangeError või ReferenceError, mis kõik on klassi Phasis\Exceptions\RuntimeError alamklassid. Mootori sees püüab JS try / catch kinni JsThrowable, kõik RuntimeError ja Phasis\Exceptions\SyntaxError. Muid PHP erandeid, sealhulgas hostifunktsioonide tekitatuid, ei teisendata ning need levivad otse PHP kutsujani (probleem #20).

Piiranguid on kaks: pinu ülempiir (CallStack::maxDepth, 4,096 raami, lisaks 8,192 raami piirang transpiilitud sulunditele) ja maxLoopIterations, mille väärtus määratakse parameetriga Engine::setLimit() ja mille jõustamist kontrollib ainult puu läbiv käitur (probleem #19). Mõlemad tekitavad Phasis\Exceptions\InternalError. Stringi pikkusele, väljundi suurusele ega käitusajale piiranguid pole.

Baitkoodi VM

Sageli käivitatavad funktsioonid kompileeritakse kahel viisil. JsToPhp (asukohas src/Bytecode/) väljastab PHP sulundi arvudest ja lokaalm muutujatest sõltuvatele funktsioonikehadele, mille PHP jälgiv JIT-kompilaator saab masinkoodiks kompileerida; töötlusahel teisendab new Ctor(args), obj.method(args) ja this.member lugemised, mis võimaldab tüüpilisel klassimeetodi-tsüklis mustril virtuaalmasinast täielikult mööda minna. Kõik muu kompileeritakse lamendatud baitkoodiks, mida käitab VM::execute; sellel on kohandatud väljakutsete pinu, mis hoiab kõik JS väljakutsed ühes PHP raamis, ning prototüübimeetodite lugemiste jaoks on olemas PC-põhine vahemälu. Ka lihtsad generaatorikehad kompileeritakse baitkoodiks; Op::YIELD salvestab hunnikusse pinuraami hetktõmmise, selle asemel et läbida PHP Fiberit.

Iga kompilaatori poolt toetamata struktuur (e.g, with, try ümberseotud püüdmisplokiga, yield*, asünkroonsed generaatorid) suunatakse tagasi puu läbivale käiturile. Kogu mehhanismi kohta vt Baitkoodi VM.