Preskoči na vsebino

Arhitektura

Phasis je drevesni sprehajalnik s štirimi stopnjami in oportunističnim VM za bajtno kodo. Pot podatkov je:

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

Vsaka stopnja je razred PHP z majhnim vmesnikom, ki ga uporablja naslednja stopnja. Koraka povezovanja ni — razčlenjevanje ustvari AST, po katerem interpreter hodi neposredno.

Leksikalni analizator

src/Lexer/Lexer.php pretvori izvorno besedilo v tok primerkov Token. Vsak žeton je nespremenljiva struktura: tip (naštevanje TokenType, približno 80 primerov), vrednost, vrstica, stolpec.

Leksikalni analizator obravnava:

  • Številske literale — decimalne, šestnajstiške (0x), osmiške (0o), dvojiške (0b), eksponentne in s številsko ločnico.
  • Nizovne literale — enojne in dvojne narekovaje, vsa ubežna zaporedja, predloge z ugnezdenimi izrazi.
  • Literale regularnih izrazov — popolno tokenizacijo /pattern/flags z upoštevanjem, ali naj bo / regularni izraz ali deljenje.
  • Identifikatorje, ključne besede, ločila.
  • Komentarje (vrstične in blokovne), ohranjene kot nepomembno besedilo.
  • Izvorno kodo Unicode — bajti UTF-8, interpretirani kot izvorna koda JS v UTF-16.

Izhod je generator, ki žetone ustvarja sproti. Razčlenjevalnik jih porablja z vpogledom za en žeton naprej.

Razčlenjevalnik

src/Parser/Parser.php je ročno napisan razčlenjevalnik Pratt. Prednost operatorjev v izrazih je določena v src/Parser/Precedence.php. Stavki uporabljajo lastno rekurzivno spuščanje.

Ustvari nespremenljiva vozlišča AST iz src/Ast/. Vsako vozlišče vsebuje lokacijo v izvorni kodi za sledenje skladu in poročanje o napakah.

Značilne oblike:

  • Izraz: Literal, Identifier, BinaryExpression, UnaryExpression, AssignmentExpression, CallExpression, MemberExpression, ArrowFunction, ObjectExpression, ArrayExpression, ConditionalExpression, TemplateLiteral, SpreadElement, NewExpression, ThisExpression, SequenceExpression, TaggedTemplate, ClassExpression, YieldExpression, AwaitExpression.
  • Stavek: BlockStatement, IfStatement, ForStatement, ForInStatement, ForOfStatement, WhileStatement, DoWhileStatement, SwitchStatement, ReturnStatement, ThrowStatement, TryStatement, BreakStatement, ContinueStatement, ExpressionStatement.
  • Deklaracija: VariableDeclaration, FunctionDeclaration, ClassDeclaration, ImportDeclaration.
  • Vzorec (destrukturiranje): ArrayPattern, ObjectPattern, RestElement, AssignmentPattern.

Samodejno vstavljanje podpičij je v skladu s specifikacijo implementirano v razčlenjevalniku (ne v leksikalnem analizatorju).

Interpreter

src/Runtime/Interpreter.php je sprehajalnik AST. Obišče vsako vozlišče in vrne JsValue (ali Completion za stavke, ki spremenijo potek izvajanja).

Ključni podporni razredi:

  • Environment (src/Runtime/Environment.php) — veriga leksikalnih obsegov. Vsak klic funkcije ustvari novo okolje, povezano z nadrejenim leksikalnim obsegom. var se dvigne na obseg funkcije; let / const sta omejena na blok in imata TDZ.
  • CallStack (src/Runtime/CallStack.php) — eksplicitni sklad z omejitvijo globine. Klici funkcij in metod dodajo okvirje; izjeme jih odvijejo.
  • Completion (src/Runtime/Completion.php) — označena unija normal, return, throw, break, continue z neobvezno vrednostjo in oznako.
  • Reference (src/Runtime/Reference.php) — referenčni tip iz specifikacije za vrednotenje leve strani pri prirejanjih in delete.

Vrednosti

Vrednosti JavaScript so objekti PHP pod src/Value/. Vsak implementira vmesnik JsValue in ima lastne metode za pretvorbo tipov (toNumber(), toString(), toBoolean()).

Razred Tip JS
JsUndefined undefined (singleton)
JsNull null (singleton)
JsBoolean true / false
JsNumber dvojna natančnost IEEE 754; ohrani NaN, ±Infinity, ±0
JsString semantika nizov UTF-16
JsObject osnovni objekt z zemljevidom lastnosti in verigo prototipov
JsArray razširja JsObject s sledenjem dolžini
JsFunction zaprtje z zajetim Environment
JsSymbol primitivni Symbol
JsBigInt poljubna natančnost prek bcmath
JsProxy vseh 13 pasti, preklicljiv
JsPromise sinhroni izvajalnik + čakalna vrsta mikronalog
JsGenerator dvojni način: posnetek okvira (telesa, prevedena v bajtno kodo; glejte VM za bajtno kodo § Generatorji s posnetkom okvira) s povratkom na PHP Fiber za yield*, try-with-yield in asinhrone generatorje
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry zbirke
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray binarni podatki

Obravnava števil je največji zgodovinski vir napak: NaN !== NaN mora biti resničen, -0 === 0 mora biti resničen, robni primeri ToNumber pa zahtevajo natančno obravnavo na ravni bitov. Razred Spec/TypeConversion dobesedno implementira algoritme iz specifikacije in je temeljito preizkušen.

Vgrajena knjižnica

src/BuiltIn/ zagotavlja vse objekte standardne knjižnice: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console ter pakete Web Platform / Fetch (URL, Headers, Request, Response, Blob, FormData, polne tokove Streams, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), crypto + SubtleCrypto, WebSocket, XMLHttpRequest in zanko dogodkov (setTimeout, setInterval, queueMicrotask, AsyncContext).

Vsaka vgrajena komponenta je statična metoda install() razreda. Engine jih med ustvarjanjem pokliče v vrstnem redu odvisnosti. Vsaka določi svojo verigo prototipov, namesti metode kot primerke JsFunction, ovite okoli zaprtij PHP, in konstruktor razkrije kot globalno vrednost.

Most PHP↔JS

Pretvorba vrednosti čez mejo poteka na dveh mestih:

  • Phasis\Interop\PhpToJs::convert() — vrednost PHP v JsValue; uporablja se za argumente setGlobal() in call() ter povratne vrednosti gostiteljskih funkcij. Klicna vrednost PHP postane JsFunction, katere telo jo pokliče.
  • PhpToJs::toPhp() — JsValue v PHP za argumente gostiteljske funkcije.
  • Engine::toPhp() (zasebno) — JsValue v PHP za rezultate eval() in call().

Mostu za objekte ni: objekti PHP, ki niso klicljivi, se pretvorijo v undefined (težava št. 18). Razred JsToPhp v src/Bytecode/ ni povezan z interoperabilnostjo; gre za transpiler, opisan spodaj. Pravila pretvorbe so dokumentirana v Pretvorba vrednosti.

Obravnava napak

Neujeta napaka JS throw postane PHP Phasis\Exceptions\JsThrowable; napake, ki jih sproži sam pogon, se prikažejo kot Phasis\Exceptions\TypeError, RangeError ali ReferenceError, ki so vsi podrazredi Phasis\Exceptions\RuntimeError. Znotraj pogona JS try / catch ujame JsThrowable, vse RuntimeError in Phasis\Exceptions\SyntaxError. Druge izjeme PHP, tudi tiste, ki jih sprožijo gostiteljske funkcije, se ne pretvorijo in se neposredno prenesejo klicatelju PHP (težava št. 20).

Obstajata dve omejitvi: zgornja meja klicnega sklada (CallStack::maxDepth, 4,096 okvirjev in dodatna varovalka za 8,192 okvir pri transpajliranih zaprtjih) ter maxLoopIterations, nastavljena prek Engine::setLimit(), ki jo uveljavlja le drevesni sprehajalnik (težava št. 19). Obe sprožita Phasis\Exceptions\InternalError. Omejitev dolžine nizov, velikosti izhoda ali časa izvajanja ni.

VM za bajtno kodo

Funkcije, ki se pogosto izvajajo, se pretvorijo na dva načina. JsToPhp (v src/Bytecode/) ustvari zaprtje PHP za telesa z veliko številskimi operacijami ali lokalnimi spremenljivkami, ki jih sledilni JIT v PHP lahko prevede v izvorno kodo; cevovod pretvori branje new Ctor(args), obj.method(args) in this.member, kar zadošča, da značilni vzorec metode razreda v zanki v celoti obide VM. Vse drugo se prevede v plosko bajtno kodo, ki jo porablja VM::execute; uporablja lasten klicni sklad, ki vsak klic JS ohrani znotraj enega samega okvira PHP, ter predpomnilnik za posamezni PC pri branju metod prototipov. Tudi preprosta telesa generatorjev se prevedejo v bajtno kodo; Op::YIELD zajame posnetek okvira na kopici namesto prehoda čez PHP Fiber.

Vse oblike, ki jih prevajalnik ne pretvori (e.g. with, try s ponovno vezanim catch, yield*, asinhroni generatorji), se vrnejo k drevesnemu sprehajalniku. Celoten mehanizem je opisan v razdelku VM za bajtno kodo.