Interprète de modèles en pratique

Dans le dernier article, nous avons examiné la théorie du modèle Interpreter, appris ce qu’est un arbre AST et comment faire abstraction des expressions terminales et non terminales. Cette fois, éloignons-nous de la théorie et voyons comment ce modèle est appliqué dans des projets commerciaux sérieux que nous utilisons tous au quotidien !

Spoiler : Vous utilisez peut-être le modèle Interpreter en ce moment, simplement en lisant ce texte dans votre navigateur !

L’un des exemples les plus frappants et, peut-être, les plus importants de l’utilisation de ce modèle dans l’industrie est JavaScript. Le langage, qui a été créé à l’origine « sur le genou », fonctionne aujourd’hui sur des milliards d’appareils précisément grâce au concept d’interprétation.

10 jours qui ont changé Internet

L’histoire de JavaScript est pleine de légendes. En 1995, Brendan Eich, alors qu’il travaillait chez Netscape Communications, s’est vu confier la tâche de créer un langage de script simple pouvant s’exécuter directement dans un navigateur (Netscape Navigator) pour rendre les pages Web interactives. La direction voulait quelque chose avec une syntaxe similaire à Java alors très populaire, mais destiné non pas aux ingénieurs professionnels, mais aux concepteurs Web.

Eich n’a eu que 10 jours pour écrire le premier prototype du langage, qui s’appelait alors Mocha (puis LiveScript, et seulement ensuite JavaScript pour des raisons marketing). Cette ruée n’était pas fortuite : Microsoft était sur ses talons, qui préparait en même temps activement son propre langage de script VBScript à intégrer dans le navigateur Internet Explorer. Netscape devait de toute urgence publier sa réponse afin de ne pas perdre dans la guerre imminente des navigateurs.

Nous n’avions tout simplement pas le temps d’écrire un compilateur complexe en code machine. La solution évidente et la plus rapide pour Eich était l’architecture du classique Interpreter.

Le premier interprète (SpiderMonkey) fonctionnait comme ceci :

  1. Il lit le code source du texte du script à partir de la page.
  2. L’analyseur lexical a divisé le texte en jetons.
  3. L’analyseur a construit un arbre de syntaxe abstraite (AST). En termes de modèle Interpreter, cet arbre se composait d’expressions terminales (chaînes, nombres comme 42) et de non-terminaux (appels de fonction, instructions comme If, ​​​​While).
  4. Ensuite, la machine virtuelle a « parcouru » cet arbre étape par étape, exécutant les instructions qui y sont intégrées à chaque nœud (en appelant une méthode similaire à Interpret()).

Contexte et objets

Vous vous souvenez de l’objet Context que nous avons dû passer à la méthode Interpret(Context context) dans l’implémentation classique ? L’interpréteur en a besoin pour stocker l’état actuel de la mémoire.

Dans le cas de JavaScript, le rôle de ce contexte au niveau supérieur est joué par un Objet global (par exemple, une fenêtre dans un navigateur). Lorsque votre nœud AST essaie, par exemple, d’écrire du texte à l’écran via document.write(“Hello”), l’interpréteur accède à son contexte (l’objet document) et appelle l’API interne du navigateur souhaitée.

C’est grâce à l’interpréteur que JavaScript est capable d’interagir si facilement avec le DOM (Document Object Model) – ce ne sont que des objets dans un contexte accessibles par les nœuds de l’arborescence.

Évolution de l’interpréteur : Compilation JIT

Historiquement, JS dans les navigateurs est longtemps resté un « pur » interprète. Et cela avait un gros inconvénient : une vitesse lente. L’analyse de l’arborescence et la traversée lente de chaque nœud à chaque fois que le script était exécuté ralentissaient les applications Web complexes.

Avec l’avènement du moteur V8 de Google (intégré à Chrome) en 2008, une révolution s’est produite. Les ingénieurs ont réalisé qu’un seul interprète ne suffisait pas pour le Web moderne. Le moteur est devenu plus complexe : il construit toujours l’arborescence AST, mais utilise désormais la compilation JIT (Just-In-Time).

Les moteurs JS modernes (V8, SpiderMonkey) fonctionnent comme un pipeline complexe :

  1. L’interpréteur de base rapide et stupide commence à exécuter votre code JS instantanément, sans même attendre sa compilation (le modèle classique fonctionne toujours ici).
  2. En parallèle, le moteur surveille les sections de code « chaudes » (boucles ou fonctions appelées des milliers de fois).
  3. Ces sections sont compilées par le compilateur JIT directement dans un code machine optimisé, en contournant l’interpréteur lent.

C’est cette combinaison du démarrage instantané de l’interpréteur et de la puissance de calcul de la compilation qui a permis à JavaScript de conquérir le monde, devenant le langage des serveurs (Node.js) et des applications mobiles (React Native).

Interprète dans l’industrie du jeu vidéo

Malgré la domination du C++ dans l’informatique lourde, le modèle Interpreter est une norme industrielle en matière de développement de jeux pour la création de logique de jeu. Pour quoi? Pour que les concepteurs de jeux puissent créer des jeux sans risquer de « laisser tomber » le moteur ou sans avoir besoin de le recompiler constamment.

Un excellent exemple historique est UnrealScript – le langage dans lequel la logique des jeux Unreal Tournament et Gears of War a été écrite dans Unreal Engine 1, 2 et 3. Le texte a été compilé en bytecode compact et abstrait, qui a ensuite été (interprété) étape par étape par la machine virtuelle du moteur.

Scripts de graphiques visuels (Blueprints)

Aujourd’hui, le texte a été remplacé par une programmation visuelle – le système Blueprints dans Unreal Engine 4 et 5.

Si vous avez déjà ouvert un Blueprint dans Unreal Engine, vous avez vu de nombreux nœuds connectés par des fils. Sur le plan architectural, l’ensemble du graphique Blueprints est un immense arbre de syntaxe abstraite (AST) dessiné à l’écran :

  1. Expressions terminales : Nœuds constants. Par exemple, un nœud qui stocke simplement le nombre 42 ou une chaîne. Ils renvoient une valeur spécifique lorsqu’ils sont interprétés.
  2. Expressions non-terminales : nœuds de calcul (Ajouter) ou nœuds de contrôle de flux (Branche). Ils ont des entrées d’arguments, que l’interpréteur évalue d’abord de manière récursive avant de produire le résultat sous forme de broche de sortie.

Et le rôle de contexte ici est joué par la mémoire d’une instance d’un objet de jeu spécifique (acteur). La machine d’interprétation « parcourt » ce graphique en toute sécurité, demandant des données et effectuant des transitions.

Où d’autre l’interprète est-il utilisé ?

Le modèle d’interpréteur peut être trouvé dans presque tous les systèmes complexes où des instructions dynamiques doivent être exécutées. Voici quelques exemples de logiciels commerciaux :

  • Langages de programmation interprétés (Python, Ruby, PHP). L’ensemble de leur exécution est basé sur le modèle classique. Par exemple, l’implémentation de référence CPython analyse d’abord votre script .py dans un AST, le compile en bytecode, puis une énorme machine virtuelle (boucle de calcul) interprète ce bytecode étape par étape.
  • Machine virtuelle Java (JVM). Initialement, le code Java n’est pas compilé en instructions machine, mais en bytecode. Lorsque vous exécutez l’application, la JVM agit comme un interprète (mais avec une compilation JIT agressive, tout comme dans la V8).
  • Bases de données et SQL Lorsque vous émettez une requête SQL (utilisateurs SELECT * FROM) dans PostgreSQL ou MySQL, le moteur de base de données agit comme un interpréteur. Il effectue une analyse lexicale, construit un arbre de requêtes AST, génère un plan d’exécution, puis « interprète » littéralement ce plan en itérant sur les lignes des tables.
  • Expressions régulières (RegEx). Tout moteur d’expression régulière analyse en interne un modèle de chaîne (par exemple, ^\d{3}-\d{2}$) dans un graphe d’état (NFA/DFA Automata), que l’interpréteur interne traverse ensuite, en faisant correspondre chaque caractère d’entrée avec les sommets de ce graphe.
  • Unity Shader Graph / Unreal Material Editor – interprète les nœuds visuels dans un code de shader modulaire (GLSL/HLSL).
  • Nœuds géométriques Blender : interprètez les opérations mathématiques et géométriques pour générer de manière procédurale des modèles 3D en temps réel.

Total

Le modèle Interpreter a depuis longtemps dépassé le cadre de « l’écriture de votre propre calculatrice ». Il s’agit de la norme industrielle la plus puissante. Des moteurs JavaScript qui exécutent chaque jour des gigaoctets de code dans les coulisses des navigateurs, aux concepteurs de jeux qui vous permettent de créer une logique complexe sans connaissance du C++, les interpréteurs restent l’un des concepts architecturaux les plus importants du développement informatique moderne.