Architectuur
Phasis is een tree walker in vier fasen met een opportunistische bytecode-VM. Het gegevenspad is:
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.varwordt naar de functiescope verplaatst;let/consthebben 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 vannormal,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 endelete.
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 naarJsValue, gebruikt voorsetGlobal(),call()-argumenten en retourwaarden van hostfuncties. Een aanroepbare PHP wordt eenJsFunctionwaarvan de body deze aanroept.PhpToJs::toPhp()— vanJsValuenaar PHP voor de argumenten van een hostfunctie.Engine::toPhp()(privé) — vanJsValuenaar PHP voor de resultaten vaneval()encall().
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.