
# 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. `var` est remonté à la portée de la fonction ; `let` / `const` sont 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 de `normal`, `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 et `delete`.

## 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](https://phasis.inline0.dev/fr/docs/advanced/bytecode-vm)) 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 vers `JsValue`, utilisée par `setGlobal()`, les arguments `call()` et les valeurs renvoyées par les fonctions hôtes. Un appelable PHP devient un `JsFunction` dont le corps l’appelle.
- **`PhpToJs::toPhp()`** — `JsValue` vers PHP pour les arguments d’une fonction hôte.
- **`Engine::toPhp()`** (privé) — `JsValue` vers PHP pour les résultats de `eval()` et `call()`.

Il n’existe pas de pont pour les objets : les objets PHP non appelables sont convertis en `undefined` ([problème #18](https://github.com/inline0/phasis/issues/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](https://phasis.inline0.dev/fr/docs/interop/values).

## 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](https://github.com/inline0/phasis/issues/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](https://github.com/inline0/phasis/issues/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](https://phasis.inline0.dev/fr/docs/advanced/bytecode-vm) pour le fonctionnement complet.
