Pāriet uz saturu

Arhitektūra

Phasis ir četru posmu koka apstaigātājs ar oportūnistisku baitu koda virtuālo mašīnu. Datu plūsma ir šāda:

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

Katrs posms ir PHP klase ar nelielu saskarni, ko izmanto nākamais posms. Saistīšanas posma nav — parsēšana izveido AST, un interpretētājs to apstaigā tieši.

Leksiskais analizators

src/Lexer/Lexer.php pārvērš avota tekstu par Token instanču plūsmu. Katrs tokens ir tikai lasāma struktūra: tips (TokenType enum ar aptuveni 80 variantiem), vērtība, rinda, kolonna.

Leksiskais analizators apstrādā:

  • Skaitļu literāļus — decimālos, heksadecimālos (0x), oktālos (0o), bināros (0b), eksponenciālos un ar ciparu atdalītājiem.
  • Virkņu literāļus — ar vienpēdiņām un dubultpēdiņām, visas atsoļa secības, veidņu literāļus ar iegultām izteiksmēm.
  • Regulāro izteiksmju literāļus — pilnu /pattern/flags tokenizāciju, ņemot vērā, kad / jāinterpretē kā regulārā izteiksme vai dalīšana.
  • Identifikatorus, atslēgvārdus un pieturzīmes.
  • Komentārus (rindas un bloka), kas tiek saglabāti kā triviālie elementi.
  • Unicode avotu — UTF-8 baitus, kas tiek interpretēti kā JS UTF-16 avots.

Izvade ir ģenerators, kas pakāpeniski izvada tokenus. Parsētājs tos patērē, vienu tokenu turot priekšskatā.

Parsētājs

src/Parser/Parser.php ir ar roku rakstīts Pratt parsētājs. Izteiksmju prioritātes ir noteiktas src/Parser/Precedence.php. Priekšrakstiem ir sava rekursīvās nolaišanās parsēšana.

Tas no src/Ast/ veido nemainīgus AST mezglus. Katrā mezglā ir avota atrašanās vieta steka izdrukām un kļūdu ziņojumiem.

Ievērības cienīgās formas:

  • Izteiksme: Literal, Identifier, BinaryExpression, UnaryExpression, AssignmentExpression, CallExpression, MemberExpression, ArrowFunction, ObjectExpression, ArrayExpression, ConditionalExpression, TemplateLiteral, SpreadElement, NewExpression, ThisExpression, SequenceExpression, TaggedTemplate, ClassExpression, YieldExpression, AwaitExpression.
  • Priekšraksts: BlockStatement, IfStatement, ForStatement, ForInStatement, ForOfStatement, WhileStatement, DoWhileStatement, SwitchStatement, ReturnStatement, ThrowStatement, TryStatement, BreakStatement, ContinueStatement, ExpressionStatement.
  • Deklarācija: VariableDeclaration, FunctionDeclaration, ClassDeclaration, ImportDeclaration.
  • Raksts (destrukturēšana): ArrayPattern, ObjectPattern, RestElement, AssignmentPattern.

Automātiskā semikolu ievietošana ir ieviesta parsētājā (nevis leksiskajā analizatorā) atbilstoši specifikācijai.

Interpretētājs

src/Runtime/Interpreter.php ir AST apstaigātājs. Tas apmeklē katru mezglu un atgriež JsValue (vai Completion priekšrakstiem, kas maina vadības plūsmu).

Galvenās palīgklases:

  • Environment (src/Runtime/Environment.php) — leksisko tvērumu ķēde. Katrs funkcijas izsaukums izveido jaunu vidi, kas norāda uz leksisko vecākvidi. var tiek pacelts uz funkcijas tvērumu; let / const ir bloka tvēruma mainīgie ar TDZ.
  • CallStack (src/Runtime/CallStack.php) — skaidri pārvaldīts steks ar dziļuma ierobežojumu. Funkciju un metožu izsaukumi pievieno stekam kadrus; izņēmumi tos atritina.
  • Completion (src/Runtime/Completion.php) — diskriminēts apvienojums, kurā ietilpst normal, return, throw, break, continue, kā arī neobligāta vērtība un etiķete.
  • Reference (src/Runtime/Reference.php) — specifikācijā definētais Reference tips kreisās puses novērtēšanai piešķīrumos un delete.

Vērtības

JavaScript vērtības ir PHP objekti zem src/Value/. Katrs īsteno JsValue saskarni un ietver savas tipu konvertēšanas metodes (toNumber(), toString(), toBoolean()).

Klase JS tips
JsUndefined undefined (singleton)
JsNull null (singleton)
JsBoolean true / false
JsNumber IEEE 754 double; saglabā NaN, ±Infinity, ±0
JsString UTF-16 virkņu semantika
JsObject bāzes objekts ar īpašību karti un prototipu ķēdi
JsArray paplašina JsObject, sekojot līdzi garumam
JsFunction slēgums ar tvertu Environment
JsSymbol primitīvs Symbol
JsBigInt patvaļīga precizitāte, izmantojot bcmath
JsProxy visi 13 trapi; atsaucams
JsPromise sinhrons izpildītājs un mikrouzdevumu rinda
JsGenerator divu režīmu: kadru momentuzņēmuma režīms (baitu kodā kompilēti pamatkodi; skatiet Baitu koda virtuālā mašīna § Ģeneratori ar kadru momentuzņēmumiem), ar PHP Fiber rezerves režīmu priekš yield*, try-with-yield un asinhronajiem ģeneratoriem
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry kolekcijas
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray binārie dati

Skaitļu apstrāde vēsturiski ir lielākais kļūdu avots: NaN !== NaN jābūt true, -0 === 0 jābūt true, un ToNumber robežgadījumiem nepieciešama precizitāte bitu līmenī. Klase Spec/TypeConversion īsteno specifikācijas algoritmus vārds vārdā, un tā ir izsmeļoši testēta.

Iebūvētā bibliotēka

src/BuiltIn/ nodrošina visus standarta bibliotēkas objektus: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console, kā arī Web Platform / Fetch pakotnes (URL, Headers, Request, Response, Blob, FormData, pilnas Streams, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), crypto + SubtleCrypto, WebSocket, XMLHttpRequest un notikumu cilpu (setTimeout, setInterval, queueMicrotask, AsyncContext).

Katrs iebūvētais objekts ir statiska install() metode klasē. Engine izsauc šīs metodes izveides laikā, ievērojot atkarību secību. Katra metode definē prototipu ķēdi, instalē metodes kā JsFunction instances, kas ietītas ap PHP slēgumiem, un padara konstruktoru pieejamu kā globālu objektu.

PHP↔JS tilts

Vērtību konvertēšana pāri robežai notiek divās vietās:

  • Phasis\Interop\PhpToJs::convert() — PHP vērtība uz JsValue; izmanto setGlobal(), call() argumentiem un resursdatora funkciju atgrieztajām vērtībām. Izsaucams PHP kļūst par JsFunction, kura pamatā ir tā izsaukšana.
  • PhpToJs::toPhp() — no JsValue uz PHP resursdatora funkcijas argumentiem.
  • Engine::toPhp() (privāta) — no JsValue uz PHP eval() un call() rezultātiem.

Objektu tilta nav: PHP objekti, kas nav izsaucami, tiek konvertēti uz undefined (problēma #18). Klase JsToPhp src/Bytecode/ nav saistīta ar sadarbspēju; tas ir tālāk aprakstītais transpilers. Konvertēšanas noteikumi ir dokumentēti sadaļā Vērtību konvertēšana.

Kļūdu apstrāde

Nepārtverts JS throw kļūst par PHP Phasis\Exceptions\JsThrowable; paša dzinēja radītās kļūdas tiek parādītas kā Phasis\Exceptions\TypeError, RangeError vai ReferenceError — visas ir Phasis\Exceptions\RuntimeError apakšklases. Dzinēja iekšienē JS try / catch pārtver JsThrowable, ikvienu RuntimeError un Phasis\Exceptions\SyntaxError. Citi PHP izņēmumi, tostarp resursdatora funkciju izmestie, netiek konvertēti un tiek nodoti tieši PHP izsaucējam (problēma #20).

Pastāv divi ierobežojumi: izsaukumu steka maksimums (CallStack::maxDepth, 4,096 kadri, kā arī 8,192 kadru aizsardzība transpilerētajiem slēgumiem) un maxLoopIterations, kas iestatāms ar Engine::setLimit() un ko ievēro tikai koka apstaigātājs (problēma #19). Abi izraisa Phasis\Exceptions\InternalError. Nav virknes garuma, izvades lieluma vai izpildes laika ierobežojumu.

Baitu koda virtuālā mašīna

Bieži izsauktās funkcijas tiek pārveidotas divos veidos. JsToPhp (src/Bytecode/) izveido PHP slēgumu skaitliskiem vai lokālo mainīgo ziņā intensīviem pamatkodiem, ko PHP trasējošais JIT var kompilēt uz mašīnkodu; konveijers pārveido new Ctor(args), obj.method(args) un this.member nolasīšanu, kas ļauj standarta klases metodes izsaukumam ciklā pilnībā apiet virtuālo mašīnu. Viss pārējais tiek kompilēts plakanā baitu kodā, ko izpilda VM::execute, izmantojot pielāgotu izsaukumu steku, kas visus JS izsaukumus uztur vienā PHP kadrā, un katram PC ir iekļautā kešatmiņa prototipa metožu nolasīšanai. Arī vienkārši ģeneratoru pamatkodi tiek kompilēti baitu kodā, un Op::YIELD tver steka kadra momentuzņēmumu kaudzē, nevis pāriet uz PHP Fiber.

Jebkura forma, ko kompilators nepārveido (e.g. with, try ar atkārtotu piesaisti tvēruma blokā, yield*, asinhronie ģeneratori), tiek nodota koka apstaigātājam. Pilnu mehānisma aprakstu skatiet sadaļā Baitu koda virtuālā mašīna.