Ga naar de inhoud

Architectuur

Phasis is een tree walker in vier fasen met een opportunistische bytecode-VM. Het gegevenspad is:

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

Elke fase is een klasse PHP met een beperkt oppervlak dat door de volgende fase wordt gebruikt. Er is geen linkstap: het parsen levert een AST op die de interpreter rechtstreeks doorloopt.

Lexer

src/Lexer/Lexer.php zet brontekst om in een stroom instanties van Token. Elk token is een readonly-struct: type (enum TokenType, ongeveer 80 gevallen), waarde, regel en kolom.

De lexer verwerkt:

  • Numerieke literals — decimaal, hexadecimaal (0x), octaal (0o), binair (0b), exponentieel en met numerieke scheidingstekens.
  • Stringliterals — enkele en dubbele aanhalingstekens, alle escape-sequenties, template literals met geneste expressies.
  • Reguliere-expressieliterals — volledige tokenisatie van /pattern/flags, waarbij rekening wordt gehouden met de vraag of een / een regex of deling moet zijn.
  • Identifiers, keywords en interpunctie.
  • Opmerkingen (regel- en blokopmerkingen), bewaard als trivia.
  • Unicode-bron — UTF-8-bytes geïnterpreteerd als de JS UTF-16-bron.

De uitvoer is een generator die tokens lui oplevert. De parser verwerkt ze met één token vooruitkijken.

Parser

src/Parser/Parser.php is een handgeschreven Pratt-parser. De prioriteit van expressies staat in src/Parser/Precedence.php. Statements gebruiken hun eigen recursieve descent.

Deze produceert onveranderlijke AST-nodes vanuit src/Ast/. Elke node bevat de bronlocatie voor stacktraces en foutmeldingen.

Opvallende structuren:

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

Automatische puntkommainvoeging is volgens de specificatie in de parser geïmplementeerd (niet in de lexer).

Interpreter

src/Runtime/Interpreter.php doorloopt de AST. Deze bezoekt elke node en retourneert een JsValue (of een Completion voor statements die de control flow wijzigen).

Belangrijke ondersteunende klassen:

  • Environment (src/Runtime/Environment.php) — lexicale scopeketen. Elke functieaanroep maakt een nieuwe omgeving aan die naar de lexicale ouder verwijst. var wordt naar de functiescope verplaatst; let / const hebben blokscope met een TDZ.
  • CallStack (src/Runtime/CallStack.php) — expliciete stack met dieptelimiet. Functie- en methodeaanroepen voegen frames toe; uitzonderingen wikkelen deze af.
  • Completion (src/Runtime/Completion.php) — tagged union van normal, return, throw, break, continue, plus een optionele waarde en label.
  • Reference (src/Runtime/Reference.php) — het Reference-type uit de specificatie voor de evaluatie van de linkerzijde in toewijzingen en delete.

Waarden

JavaScript-waarden zijn PHP-objecten onder src/Value/. Elk object implementeert de interface JsValue en heeft eigen methoden voor typeconversie (toNumber(), toString(), toBoolean()).

Klasse Type JS
JsUndefined undefined (singleton)
JsNull null (singleton)
JsBoolean true / false
JsNumber IEEE 754 double; behoudt NaN, ±Infinity, ±0
JsString UTF-16-stringsemantiek
JsObject basisobject met propertymap en prototypeketen
JsArray breidt JsObject uit met lengtetracking
JsFunction closure met vastgelegde Environment
JsSymbol Symbol-primitief
JsBigInt willekeurige precisie via bcmath
JsProxy alle 13 traps, herroepbaar
JsPromise synchrone uitvoerder + microtaskwachtrij
JsGenerator twee modi: frame-snapshot (bytecode-gecompileerde bodies; zie Bytecode-VM § generators met frame-snapshots) met PHP Fiber als terugvaloptie voor yield*, try-with-yield en async generators
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry collecties
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray binaire gegevens

Getalverwerking is historisch gezien de grootste bron van bugs: NaN !== NaN moet waar zijn, -0 === 0 moet waar zijn; randgevallen van ToNumber vereisen exacte zorg op bitniveau. De klasse Spec/TypeConversion implementeert de specificatiealgoritmen letterlijk en is uitgebreid getest.

Ingebouwde bibliotheek

src/BuiltIn/ biedt elk object uit de standaardbibliotheek: Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console, plus de Web Platform-/Fetch-pakketten (URL, Headers, Request, Response, Blob, FormData, volledige Streams, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), crypto + SubtleCrypto, WebSocket, XMLHttpRequest en de eventloop (setTimeout, setInterval, queueMicrotask, AsyncContext).

Elke ingebouwde functie is een statische methode install() van een klasse. Engine roept ze bij de constructie aan in afhankelijkheidsvolgorde. Elke functie definieert de prototypeketen, installeert methoden als JsFunction-instanties die zijn verpakt rond PHP-closures, en stelt de constructor beschikbaar als globale variabele.

Bridge tussen PHP en JS

Waardeconversie over de grens vindt op twee plekken plaats:

  • Phasis\Interop\PhpToJs::convert() — waarde van PHP naar JsValue, gebruikt voor setGlobal(), call()-argumenten en retourwaarden van hostfuncties. Een aanroepbare PHP wordt een JsFunction waarvan de body deze aanroept.
  • PhpToJs::toPhp() — van JsValue naar PHP voor de argumenten van een hostfunctie.
  • Engine::toPhp() (privé) — van JsValue naar PHP voor de resultaten van eval() en call().

Er is geen objectbridge: PHP-objecten die niet aanroepbaar zijn, worden omgezet naar undefined (probleem #18). De klasse JsToPhp in src/Bytecode/ houdt geen verband met interoperabiliteit; het is de transpiler die hieronder wordt beschreven. Conversieregels staan beschreven in Waardeconversie.

Foutafhandeling

Een niet-afgevangen JS-throw wordt een PHP-Phasis\Exceptions\JsThrowable; fouten die de engine zelf veroorzaakt, worden weergegeven als Phasis\Exceptions\TypeError, RangeError of ReferenceError, allemaal subklassen van Phasis\Exceptions\RuntimeError. Binnen de engine vangt een JS-try / catch JsThrowable, elk RuntimeError en Phasis\Exceptions\SyntaxError op. Andere PHP-uitzonderingen, ook die van hostfuncties, worden niet omgezet en gaan rechtstreeks door naar de PHP-aanroeper (probleem #20).

Er zijn twee limieten: de limiet voor de aanroepstack (CallStack::maxDepth, 4,096 frames, plus een bewaking van 8,192 frames voor getranspilede closures) en maxLoopIterations, ingesteld via Engine::setLimit(), die alleen de tree-walker afdwingt (probleem #19). Beide veroorzaken Phasis\Exceptions\InternalError. Er zijn geen limieten voor tekenreekslengte, uitvoergrootte of uitvoeringstijd.

Bytecode-VM

Veelgebruikte functies worden op twee manieren omgezet. JsToPhp (in src/Bytecode/) genereert een PHP-closure voor bodies met veel numerieke bewerkingen of lokale variabelen, die de tracing-JIT van PHP naar native code kan compileren; de pipeline zet new Ctor(args)-, obj.method(args)- en this.member-lezingen om, genoeg om het canonieke patroon van een klassmethode in een lus de VM volledig te laten omzeilen. Al het overige wordt gecompileerd naar vlakke bytecode die wordt verwerkt door VM::execute, met een aangepaste aanroepstack die elke JS-aanroep binnen één PHP-frame houdt en een inlinecache per PC voor het lezen van prototypemethoden. Eenvoudige generatorbodies worden ook naar bytecode gecompileerd; Op::YIELD legt daarbij een momentopname van een heapframe vast in plaats van over te gaan naar PHP Fiber.

Vormen die de compiler niet omzet (e.g. with, try met opnieuw gebonden catch, yield*, async-generators) vallen terug op de tree-walker. Zie Bytecode-VM voor de volledige werking.