Hoppa till innehåll

Arkitektur

Phasis är en fyrastegs trädvandring med en opportunistisk bytekod-VM. Dataflödet är:

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

Varje steg är en klass i PHP med ett litet gränssnitt som nästa steg använder. Det finns inget länkningssteg – parsningen skapar en AST som interpreteraren går igenom direkt.

Lexer

src/Lexer/Lexer.php omvandlar källtext till en ström av Token-instanser. Varje token är en skrivskyddad struct: typ (TokenType-enum, ~80 fall), värde, rad, kolumn.

Lexern hanterar:

  • Numeriska literaler – decimala, hexadecimala (0x), oktala (0o), binära (0b), exponentiella samt tal med sifferavgränsare.
  • Strängliteraler – enkla och dubbla citattecken, alla escape-sekvenser och malliteraler med nästlade uttryck.
  • Reguljära uttryck som literaler – fullständig tokenisering av /pattern/flags med hänsyn till om en / ska tolkas som regex eller division.
  • Identifierare, nyckelord, interpunktion.
  • Kommentarer (rad- och blockkommentarer), bevarade som trivia.
  • Källkod i Unicode – UTF-8-byte tolkade som samma JS-källa.

Utdata är en generator som ger token efter behov. Parsern förbrukar dem med en tokens framförhållning.

Parsern

src/Parser/Parser.php är en handskriven Pratt-parser. Prioriteten för uttryck anges i src/Parser/Precedence.php. Satser använder egen rekursiv nedstigning.

Den skapar oföränderliga AST-noder från src/Ast/. Varje nod innehåller sin källposition för stackspårningar och felrapportering.

Noterbara strukturer:

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

Automatisk infogning av semikolon implementeras i parsern (inte lexern) enligt specifikationen.

Interpreterare

src/Runtime/Interpreter.php är AST-vandraren. Den besöker varje nod och returnerar ett JsValue (eller ett Completion för satser som ändrar kontrollflödet).

Viktiga stödklasser:

  • Environment (src/Runtime/Environment.php) – lexikal scope-kedja. Varje funktionsanrop skapar en ny miljö som pekar på sin lexikala förälder. var hissas upp till funktionsscopet; let / const är blockscope-begränsade med en TDZ.
  • CallStack (src/Runtime/CallStack.php) – explicit stack med djupgräns. Funktions- och metodanrop lägger till stackramar; undantag avvecklar dem.
  • Completion (src/Runtime/Completion.php) – diskriminerad union av normal, return, throw, break, continue samt ett valfritt värde och en etikett.
  • Reference (src/Runtime/Reference.php) – specifikationens Reference-typ för utvärdering av vänsterled vid tilldelningar och delete.

Värden

JavaScript-värden är PHP-objekt under src/Value/. Varje värde implementerar gränssnittet JsValue och har egna typkonverteringsmetoder (toNumber(), toString(), toBoolean()).

Klass Typen JS
JsUndefined undefined (singleton)
JsNull null (singleton)
JsBoolean true / false
JsNumber IEEE 754-double; bevarar NaN, ±Infinity, ±0
JsString UTF-16-strängsemantik
JsObject basobjekt med egenskapskarta och prototypkedja
JsArray utökar JsObject med längdspårning
JsFunction closure med infångad Environment
JsSymbol Symbol-primitiv
JsBigInt godtycklig precision via bcmath
JsProxy alla 13 fällor, kan återkallas
JsPromise synkron exekverare + mikrouppgiftskö
JsGenerator två lägen: ramögonblicksbild (bytekodkompilerade kroppar; se Bytekod-VM § Generatorer med ramögonblicksbild) med PHP Fiber-reservlösning för yield*, try-with-yield och asynkrona generatorer
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry samlingar
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray binärdata

Talhantering är den enskilt största källan till historiska buggar: NaN !== NaN måste vara sant, -0 === 0 måste vara sant, och gränsfall för ToNumber kräver exakt hantering på bitnivå. Klassen Spec/TypeConversion implementerar specifikationens algoritmer ordagrant och är uttömmande testad.

Inbyggt bibliotek

src/BuiltIn/ tillhandahåller alla standardbiblioteksobjekt: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console, plus webbplattformens och Fetch-paketen (URL, Headers, Request, Response, Blob, FormData, fullständiga Streams, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), crypto + SubtleCrypto, WebSocket, XMLHttpRequest och händelseslingan (setTimeout, setInterval, queueMicrotask, AsyncContext).

Varje inbyggd funktion är en statisk metod install() i en klass. Engine anropar dem i beroendeordning vid konstruktionen. Varje funktion definierar sin prototypkedja, installerar metoder som JsFunction-instanser omslutna runt PHP-closures och exponerar konstruktorn som global.

Brygga mellan PHP↔JS

Värdekonvertering över gränsen sker på två ställen:

  • Phasis\Interop\PhpToJs::convert() – värde från PHP till JsValue, används för setGlobal(), call()-argument och returvärden från värdfunktioner. Ett anropbart PHP blir ett JsFunction vars kropp anropar det.
  • PhpToJs::toPhp() – från JsValue till PHP för argumenten till en värdfunktion.
  • Engine::toPhp() (privat) – från JsValue till PHP för resultaten från eval() och call().

Det finns ingen objektbrygga: objekt från PHP som inte är anropbara konverteras till undefined (problem nr 18). Klassen JsToPhp i src/Bytecode/ har inget med interoperabilitet att göra; det är transpilern som beskrivs nedan. Konverteringsreglerna finns i Värdekonvertering.

Felhantering

Ett ouppfångat JS throw blir ett PHP Phasis\Exceptions\JsThrowable; fel som motorn själv ger upphov till visas som Phasis\Exceptions\TypeError, RangeError eller ReferenceError, som alla är underklasser till Phasis\Exceptions\RuntimeError. Inne i motorn fångar en JS try / catch JsThrowable, alla RuntimeError och Phasis\Exceptions\SyntaxError. Andra undantag från PHP, inklusive sådana som kastas av värdfunktioner, konverteras inte utan skickas direkt vidare till anroparen PHP (problem nr 20).

Det finns två gränser: taket för anropsstacken (CallStack::maxDepth, 4,096 ramar, plus en skyddsgräns på 8,192 ramar för transpilerade closures) och maxLoopIterations, som ställs in via Engine::setLimit() och bara upprätthålls av trädvandraren (problem nr 19). Båda kastar Phasis\Exceptions\InternalError. Det finns inga gränser för stränglängd, utdatas storlek eller exekveringstid.

Bytekod-VM

Hetta funktioner sänks på två sätt. JsToPhp (i src/Bytecode/) genererar en PHP-closure för kroppar med mycket numerik eller många lokala variabler, som PHPs spårnings-JIT kan kompilera till maskinkod; pipelinen sänker new Ctor(args), obj.method(args) och this.member-läsningar, vilket räcker för att det typiska mönstret med en klassmetod i en loop ska kringgå VM:n helt. Allt annat kompileras till platt bytekod som VM::execute kör, med en anpassad anropsstack som håller varje JS-anrop inom en enda PHP-ram och en cache per PC för läsningar av prototypmetoder. Enkla generatorkroppar kompileras också till bytekod, där Op::YIELD fångar en ögonblicksbild av en ram på heapen i stället för att gå via PHP Fiber.

Former som kompilatorn inte sänker (e.g. with, try med en catch som binder om, yield*, asynkrona generatorer) faller tillbaka till trädvandraren. Se Bytekod-VM för hela mekanismen.