Architecture
Phasis est un parcours d’arbre en quatre étapes doté d’une VM de bytecode opportuniste. Le cheminement des données est le suivant :
JS text
│
▼
┌──────────┐
│ Lexer │ src/Lexer/
└────┬─────┘
▼ tokens
┌──────────┐
│ Parser │ src/Parser/
└────┬─────┘
▼ AST nodes
┌──────────────┐
│ Interpreter │ src/Runtime/
│ + Bytecode │ src/Bytecode/
│ VM │
└────┬─────────┘
▼ JsValue
PHP host
Chaque étape est une classe PHP dont l’interface réduite est utilisée par l’étape suivante. Il n’y a pas d’étape de liaison : l’analyse produit un AST que l’interpréteur parcourt directement.
Analyse lexicale
src/Lexer/Lexer.php convertit le texte source en flux d’instances Token. Chaque jeton est une structure en lecture seule : type (énumération TokenType, ~80 cas), valeur, ligne, colonne.
L’analyseur lexical prend en charge :
- Littéraux numériques — décimaux, hexadécimaux (
0x), octaux (0o), binaires (0b), exponentiels et séparateurs numériques. - Littéraux de chaîne — guillemets simples et doubles, toutes les séquences d’échappement, littéraux de gabarit avec expressions imbriquées.
- Littéraux d’expressions régulières — tokenisation complète
/pattern/flags, qui détermine si un/doit être interprété comme une expression régulière ou une division. - Identifiants, mots-clés, ponctuation.
- Commentaires (de ligne et de bloc), conservés comme trivia.
- Source Unicode — les octets UTF-8 sont interprétés comme la source UTF-16 de JS.
La sortie est un générateur qui produit les jetons à la demande. Le parseur les consomme avec une anticipation d’un jeton.
Analyseur
src/Parser/Parser.php est un parseur Pratt écrit à la main. La priorité des expressions se trouve dans src/Parser/Precedence.php. Les instructions sont analysées par descente récursive.
Il produit des nœuds AST immuables à partir de src/Ast/. Chaque nœud contient son emplacement dans le code source pour les traces de pile et les rapports d’erreurs.
Formes notables :
- Expression :
Literal,Identifier,BinaryExpression,UnaryExpression,AssignmentExpression,CallExpression,MemberExpression,ArrowFunction,ObjectExpression,ArrayExpression,ConditionalExpression,TemplateLiteral,SpreadElement,NewExpression,ThisExpression,SequenceExpression,TaggedTemplate,ClassExpression,YieldExpression,AwaitExpression. - Instruction :
BlockStatement,IfStatement,ForStatement,ForInStatement,ForOfStatement,WhileStatement,DoWhileStatement,SwitchStatement,ReturnStatement,ThrowStatement,TryStatement,BreakStatement,ContinueStatement,ExpressionStatement. - Déclaration :
VariableDeclaration,FunctionDeclaration,ClassDeclaration,ImportDeclaration. - Motif (déstructuration) :
ArrayPattern,ObjectPattern,RestElement,AssignmentPattern.
L’insertion automatique de points-virgules est implémentée dans le parseur (et non dans l’analyseur lexical), conformément à la spécification.
Interpréteur
src/Runtime/Interpreter.php parcourt l’AST. Il visite chaque nœud et renvoie une JsValue (ou une Completion pour les instructions qui modifient le flux de contrôle).
Principales classes auxiliaires :
Environment(src/Runtime/Environment.php) — chaîne de portées lexicales. Chaque appel de fonction crée un nouvel environnement lié à son parent lexical.varest remonté à la portée de la fonction ;let/constsont limités au bloc et soumis à une zone morte temporelle (TDZ).CallStack(src/Runtime/CallStack.php) — pile explicite avec limite de profondeur. Les appels de fonctions et de méthodes empilent des frames ; les exceptions les dépilent.Completion(src/Runtime/Completion.php) — union discriminée denormal,return,throw,break,continue, avec une valeur et une étiquette facultatives.Reference(src/Runtime/Reference.php) — type Reference de la spécification pour l’évaluation du membre de gauche dans les affectations etdelete.
Valeurs
Les valeurs JavaScript sont des objets PHP sous src/Value/. Chacune implémente l’interface JsValue et possède ses propres méthodes de conversion de type (toNumber(), toString(), toBoolean()).
| Classe | Type JS |
|---|---|
JsUndefined |
undefined (singleton) |
JsNull |
null (singleton) |
JsBoolean |
true / false |
JsNumber |
double IEEE 754 ; conserve NaN, ±Infinity, ±0 |
JsString |
Sémantique des chaînes UTF-16 |
JsObject |
Objet de base avec table de propriétés et chaîne de prototypes |
JsArray |
Étend JsObject avec suivi de la longueur |
JsFunction |
Fermeture avec Environment capturé |
JsSymbol |
Primitive Symbol |
JsBigInt |
Précision arbitraire via bcmath |
JsProxy |
13 pièges, révocables |
JsPromise |
Exécuteur synchrone + file de microtâches |
JsGenerator |
Double mode : frame-snapshot (corps compilés en bytecode ; voir VM de bytecode § Générateurs frame-snapshot) avec repli sur PHP Fiber pour yield*, try-with-yield et les générateurs asynchrones |
JsMap, JsSet, JsWeakMap, JsWeakSet, JsWeakRef, JsFinalizationRegistry |
collections |
JsArrayBuffer, JsSharedArrayBuffer, JsDataView, JsTypedArray |
données binaires |
La gestion des nombres est historiquement la principale source de bogues : NaN !== NaN doit être vrai, -0 === 0 doit être vrai, et les cas limites de ToNumber exigent une précision exacte au niveau du bit. La classe Spec/TypeConversion implémente à la lettre les algorithmes de la spécification et est testée de façon exhaustive.
Bibliothèque intégrée
src/BuiltIn/ fournit tous les objets de la bibliothèque standard : Array, String, Object, Math, JSON, Date, RegExp, Map, Set, Promise, Proxy, Reflect, Symbol, BigInt, TypedArray, Temporal, Intl, console, ainsi que les modules Web Platform / Fetch (URL, Headers, Request, Response, Blob, FormData, Streams complet, EventTarget, AbortController/AbortSignal, DOMException, structuredClone, performance, TextEncoder/TextDecoder, atob/btoa), crypto + SubtleCrypto, WebSocket, XMLHttpRequest et la boucle d’événements (setTimeout, setInterval, queueMicrotask, AsyncContext).
Chaque élément intégré est une méthode statique install() d’une classe. Lors de la construction, Engine les appelle dans l’ordre des dépendances. Chacun définit sa chaîne de prototypes, installe des méthodes sous forme d’instances JsFunction encapsulant des fermetures PHP et expose le constructeur en tant que global.
Pont PHP↔JS
La conversion des valeurs à la frontière s’effectue à deux endroits :
Phasis\Interop\PhpToJs::convert()— valeur PHP versJsValue, utilisée parsetGlobal(), les argumentscall()et les valeurs renvoyées par les fonctions hôtes. Un appelable PHP devient unJsFunctiondont le corps l’appelle.PhpToJs::toPhp()—JsValuevers PHP pour les arguments d’une fonction hôte.Engine::toPhp()(privé) —JsValuevers PHP pour les résultats deeval()etcall().
Il n’existe pas de pont pour les objets : les objets PHP non appelables sont convertis en undefined (problème #18). La classe JsToPhp dans src/Bytecode/ n’a aucun lien avec l’interopérabilité ; il s’agit du transcompilateur décrit ci-dessous. Les règles de conversion sont documentées dans Conversion des valeurs.
Gestion des erreurs
Une exception JS throw non interceptée devient une exception PHP Phasis\Exceptions\JsThrowable ; les erreurs levées par le moteur lui-même apparaissent sous la forme de Phasis\Exceptions\TypeError, RangeError ou ReferenceError, toutes des sous-classes de Phasis\Exceptions\RuntimeError. Dans le moteur, un JS try / catch intercepte JsThrowable, tout RuntimeError et Phasis\Exceptions\SyntaxError. Les autres exceptions PHP, y compris celles lancées par des fonctions hôtes, ne sont pas converties et sont transmises directement à l’appelant PHP (problème #20).
Il existe deux limites : le plafond de la pile d’appels (CallStack::maxDepth, 4,096 frames, plus une garde de 8,192 frames pour les fermetures transcompilées) et maxLoopIterations, défini via Engine::setLimit(), que seul le parcours d’arbre applique (problème #19). Les deux lèvent Phasis\Exceptions\InternalError. Il n’y a pas de limite sur la longueur des chaînes, la taille de sortie ou la durée d’exécution.
VM de bytecode
Les fonctions sollicitées sont compilées de deux façons. JsToPhp (dans src/Bytecode/) génère une fermeture PHP pour les corps utilisant principalement des nombres ou des variables locales ; le JIT de traçage de PHP peut la compiler en code natif. Le processus compile les lectures de new Ctor(args), obj.method(args) et this.member, ce qui suffit pour que le schéma classique d’une méthode de classe dans une boucle contourne entièrement la VM. Tout le reste est compilé en bytecode linéaire exécuté par VM::execute, avec une pile d’appels personnalisée qui maintient chaque appel JS dans un seul frame PHP et un cache en ligne par PC pour les lectures de méthodes de prototype. Les corps simples de générateurs sont également compilés en bytecode ; Op::YIELD capture un instantané de frame dans le tas plutôt que de passer par Fiber PHP.
Toute forme que le compilateur ne peut pas abaisser (e.g. with, try avec un bloc catch qui redéfinit la liaison, yield*, générateurs asynchrones) revient au parcours d’arbre. Consultez VM de bytecode pour le fonctionnement complet.