Gå til indhold

Arkitektur

Phasis er en trævandrer med fire trin og en opportunistisk bytecode-VM. Datavejen er:

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

Hvert trin er en klasse af typen PHP med en lille grænseflade, som det næste trin bruger. Der er intet linktrin — parsing producerer et AST, som fortolkeren gennemløber direkte.

Lexer

src/Lexer/Lexer.php konverterer kildetekst til en strøm af forekomster af Token. Hvert token er en skrivebeskyttet struct: type (TokenType-enum, ~80 varianter), værdi, linje, kolonne.

Lexeren håndterer:

  • Tal-litteraler — decimal, hex (0x), oktal (0o), binær (0b), eksponentialform og numeriske separatorer.
  • Strenglitteraler — enkelt- og dobbeltanførselstegn, alle escape-sekvenser samt skabelonlitteraler med indlejrede udtryk.
  • Regulære udtrykslitteraler — fuld /pattern/flags-tokenisering med kendskab til, hvornår et / skal fortolkes som regex eller division.
  • Identifikatorer, nøgleord, tegnsætning.
  • Kommentarer (linje- og blokkommentarer), bevaret som trivia.
  • Unicode-kilde — UTF-8-bytes fortolket som den samme UTF-16-kilde som JS.

Outputtet er en generator, der udleverer tokens efter behov. Parseren bruger dem med ét tokens lookahead.

Parser

src/Parser/Parser.php er en håndskrevet Pratt-parser. Udtryksprioriteten findes i src/Parser/Precedence.php. Sætninger håndteres med deres egen rekursive nedstigning.

Den producerer uforanderlige AST-noder fra src/Ast/. Hver node indeholder sin kildeplacering til stack traces og fejlrapportering.

Bemærkelsesværdige former:

  • Udtryk: Literal, Identifier, BinaryExpression, UnaryExpression, AssignmentExpression, CallExpression, MemberExpression, ArrowFunction, ObjectExpression, ArrayExpression, ConditionalExpression, TemplateLiteral, SpreadElement, NewExpression, ThisExpression, SequenceExpression, TaggedTemplate, ClassExpression, YieldExpression, AwaitExpression.
  • Sætninger: BlockStatement, IfStatement, ForStatement, ForInStatement, ForOfStatement, WhileStatement, DoWhileStatement, SwitchStatement, ReturnStatement, ThrowStatement, TryStatement, BreakStatement, ContinueStatement, ExpressionStatement.
  • Deklarationer: VariableDeclaration, FunctionDeclaration, ClassDeclaration, ImportDeclaration.
  • Mønstre (destructuring): ArrayPattern, ObjectPattern, RestElement, AssignmentPattern.

Automatisk indsættelse af semikolon implementeres i parseren (ikke lexeren) i henhold til specifikationen.

Fortolker

src/Runtime/Interpreter.php gennemløber AST'et. Den besøger hver node og returnerer en JsValue (eller en Completion for sætninger, der ændrer kontrolflowet).

Vigtige støtteklasser:

  • Environment (src/Runtime/Environment.php) — leksikal scope-kæde. Hvert funktionskald opretter et nyt miljø, der peger på sin leksikale forælder. var hoistes til funktionsscopet; let / const har blokscope med en TDZ.
  • CallStack (src/Runtime/CallStack.php) — eksplicit stack med dybdegrænse. Funktions- og metodekald skubber frames på stacken; undtagelser ruller dem tilbage.
  • Completion (src/Runtime/Completion.php) — diskrimineret union af normal, return, throw, break, continue samt en valgfri værdi og et label.
  • Reference (src/Runtime/Reference.php) — specifikationens Reference-type til evaluering af venstre side i tildelinger og delete.

Værdier

JavaScript-værdier er PHP-objekter under src/Value/. Hver implementerer grænsefladen JsValue og har sine egne typekonverteringsmetoder (toNumber(), toString(), toBoolean()).

Klasse JS type
JsUndefined undefined (singleton)
JsNull null (singleton)
JsBoolean true / false
JsNumber IEEE 754 double; bevarer NaN, ±Infinity, ±0
JsString UTF-16-strengsemantik
JsObject basisobjekt med egenskabskort og prototypekæde
JsArray udvider JsObject med længdesporing
JsFunction closure med indfanget Environment
JsSymbol Symbol-primitiv
JsBigInt vilkårlig præcision via bcmath
JsProxy alle 13 traps, kan tilbagekaldes
JsPromise synkron executor + microtask-kø
JsGenerator dobbelt tilstand: framesnapshot (bytecodekompilerede bodies; se Bytecode VM § Generators med framesnapshot) med PHP Fiber-fallback til yield*, try-with-yield og async generators
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry samlinger
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray binære data

Talhåndtering er den største historiske kilde til fejl: NaN !== NaN skal være sand, -0 === 0 skal være sand, og kanttilfælde for ToNumber kræver præcision på bitniveau. Klassen Spec/TypeConversion implementerer specifikationsalgoritmerne ordret og er udtømmende testet.

Indbygget bibliotek

src/BuiltIn/ leverer alle standardbiblioteksobjekter: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console samt Web Platform-/Fetch-pakkerne (URL, Headers, Request, Response, Blob, FormData, fulde Streams, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), crypto + SubtleCrypto, WebSocket, XMLHttpRequest og event loop (setTimeout, setInterval, queueMicrotask, AsyncContext).

Hver indbygget funktion er en statisk install()-metode på en klasse. Engine kalder dem i afhængighedsrækkefølge under oprettelsen. Hver enkelt definerer sin prototypekæde, installerer metoder som JsFunction-instanser pakket omkring PHP-closures og eksponerer konstruktøren som en global.

Bro mellem PHP↔JS

Værdikonvertering på tværs af grænsen foregår to steder:

  • Phasis\Interop\PhpToJs::convert() — PHP-værdi til JsValue, brugt af setGlobal(), call()-argumenter og returværdier fra værtsfunktioner. En kaldbar PHP bliver til en JsFunction, hvis body kalder den.
  • PhpToJs::toPhp() — JsValue til PHP for argumenterne til en værtsfunktion.
  • Engine::toPhp() (privat) — JsValue til PHP for resultaterne af eval() og call().

Der findes ingen objektbro: PHP-objekter, der ikke kan kaldes, konverteres til undefined (issue #18). Klassen JsToPhp i src/Bytecode/ er ikke relateret til interoperabilitet; det er transpilereren, der beskrives nedenfor. Konverteringsreglerne er dokumenteret under Værdikonvertering.

Fejlhåndtering

En ikke-fanget JS throw bliver til en PHP Phasis\Exceptions\JsThrowable; fejl, som motoren selv frembringer, vises som Phasis\Exceptions\TypeError, RangeError eller ReferenceError, alle underklasser af Phasis\Exceptions\RuntimeError. Inde i motoren fanger en JS try / catch JsThrowable, enhver RuntimeError og Phasis\Exceptions\SyntaxError. Andre PHP-undtagelser, herunder dem, der udløses af værtsfunktioner, konverteres ikke og går direkte videre til PHP-kalderen (issue #20).

Der findes to grænser: grænsen for kaldestakken (CallStack::maxDepth, 4,096 frames samt en vagt på 8,192 frames for transpilerede closures) og maxLoopIterations, der indstilles via Engine::setLimit(), og som kun håndhæves af træfortolkeren (issue #19). Begge udløser Phasis\Exceptions\InternalError. Der er ingen grænser for strenglængde, outputstørrelse eller køretid.

Bytecode-VM

Varme funktioner sænkes på to måder. JsToPhp (i src/Bytecode/) udsender en PHP-closure for bodies med mange tal eller lokale variabler, som PHPs tracing-JIT kan kompilere til native kode; pipelinen sænker læsning af new Ctor(args), obj.method(args) og this.member, hvilket er nok til, at det typiske mønster med en klassemetode i en løkke helt omgår VM'en. Alt andet kompileres til flad bytecode, som VM::execute bruger, med en brugerdefineret kaldestak, der holder alle JS-kald inden for én enkelt PHP-frame og en inline-cache pr. PC til læsning af prototypemetoder. Enkle generator-bodies kompileres også til bytecode, hvor Op::YIELD indfanger et framesnapshot på heap'en i stedet for at passere gennem PHP Fiber.

Alle former, som compileren ikke sænker (e.g. with, try med en catch med ny binding, yield*, async generators), falder tilbage til træfortolkeren. Se Bytecode VM for hele mekanismen.