Un interpréteur est un programme qui lit, analyse et exécute du code écrit dans un langage spécifique sans le compiler au préalable. Créer un interpréteur simple est une excellente manière de comprendre les concepts fondamentaux de la programmation, tels que l’analyse syntaxique, l’évaluation d’expressions et la gestion de l’environnement d’exécution.
Dans cet article, nous allons voir les étapes essentielles pour construire un interpréteur minimaliste pour un langage simple, en Python.
1. Comprendre les composants d’un interpréteur
Un interpréteur se compose généralement de plusieurs parties :
1️⃣ Lexer (Analyse lexicale) : Convertit le code source en une liste de tokens (unités lexicales).
2️⃣ Parser (Analyse syntaxique) : Transforme la liste de tokens en un arbre syntaxique abstrait (AST).
3️⃣ Interpréteur (Évaluation et exécution) : Parcourt l’AST et exécute les instructions.
Exemple de code source simple dans notre mini-langage :
txtCopierModifierx = 5 + 3;
print(x);
L’interpréteur doit comprendre :
- Que
x = 5 + 3;est une affectation de variable. - Que
print(x);affiche la valeur dex.
2. Étape 1 : Création du Lexer
Le lexer découpe le texte en tokens. Voici les types de tokens que nous allons gérer :
| Token | Exemple |
|---|---|
| Identificateur | x, print |
| Nombre | 5, 3 |
| Opérateur | +, -, *, / |
| Symbole | =, ;, (, ) |
Implémentation du Lexer en Python
pythonCopierModifierimport re
# Définition des types de tokens
TOKEN_TYPES = [
("NUMBER", r"\d+"),
("IDENTIFIER", r"[a-zA-Z_][a-zA-Z0-9_]*"),
("OPERATOR", r"[+\-*/=]"),
("SYMBOL", r"[();]"),
("WHITESPACE", r"\s+"),
]
class Lexer:
def __init__(self, source_code):
self.source_code = source_code
self.tokens = []
def tokenize(self):
position = 0
while position < len(self.source_code):
match_found = False
for token_type, pattern in TOKEN_TYPES:
regex = re.compile(pattern)
match = regex.match(self.source_code, position)
if match:
text = match.group(0)
if token_type != "WHITESPACE": # On ignore les espaces
self.tokens.append((token_type, text))
position += len(text)
match_found = True
break
if not match_found:
raise SyntaxError(f"Caractère inconnu : {self.source_code[position]}")
return self.tokens
# Test du Lexer
lexer = Lexer("x = 5 + 3; print(x);")
tokens = lexer.tokenize()
print(tokens) # Output attendu : [('IDENTIFIER', 'x'), ('OPERATOR', '='), ('NUMBER', '5'), ('OPERATOR', '+'), ('NUMBER', '3'), ('SYMBOL', ';'), ('IDENTIFIER', 'print'), ('SYMBOL', '('), ('IDENTIFIER', 'x'), ('SYMBOL', ')'), ('SYMBOL', ';')]
3. Étape 2 : Création du Parser (AST)
Le parser transforme la liste de tokens en un arbre syntaxique abstrait (AST). Cet arbre représente la structure logique du programme.
Nous allons créer un AST simple avec trois types de nœuds :
NumberNode: Représente un nombre.BinaryOpNode: Représente une opération mathématique (+,-,*,/).AssignmentNode: Représente l’affectation (x = ...).
Implémentation du Parser
pythonCopierModifierclass NumberNode:
def __init__(self, value):
self.value = value
class BinaryOpNode:
def __init__(self, left, operator, right):
self.left = left
self.operator = operator
self.right = right
class AssignmentNode:
def __init__(self, identifier, expression):
self.identifier = identifier
self.expression = expression
class Parser:
def __init__(self, tokens):
self.tokens = tokens
self.position = 0
def eat(self, expected_type):
if self.position < len(self.tokens) and self.tokens[self.position][0] == expected_type:
self.position += 1
return self.tokens[self.position - 1]
else:
raise SyntaxError(f"Token attendu : {expected_type}")
def parse_expression(self):
left = self.parse_term()
while self.position < len(self.tokens) and self.tokens[self.position][1] in "+-":
operator = self.eat("OPERATOR")[1]
right = self.parse_term()
left = BinaryOpNode(left, operator, right)
return left
def parse_term(self):
token = self.eat("NUMBER")
return NumberNode(int(token[1]))
def parse_statement(self):
if self.tokens[self.position][0] == "IDENTIFIER":
identifier = self.eat("IDENTIFIER")[1]
self.eat("OPERATOR") # Le "="
expression = self.parse_expression()
self.eat("SYMBOL") # Le ";"
return AssignmentNode(identifier, expression)
def parse(self):
return self.parse_statement()
# Test du Parser
parser = Parser(tokens)
ast = parser.parse()
print(ast)
4. Étape 3 : Création de l’Interpréteur
L’interpréteur exécute l’AST. Il maintient une table des variables et évalue les expressions.
Implémentation de l’Interpréteur
pythonCopierModifierclass Interpreter:
def __init__(self):
self.variables = {}
def evaluate(self, node):
if isinstance(node, NumberNode):
return node.value
elif isinstance(node, BinaryOpNode):
left_value = self.evaluate(node.left)
right_value = self.evaluate(node.right)
if node.operator == '+':
return left_value + right_value
elif node.operator == '-':
return left_value - right_value
elif isinstance(node, AssignmentNode):
value = self.evaluate(node.expression)
self.variables[node.identifier] = value
return value
# Exécution de l'interpréteur
interpreter = Interpreter()
result = interpreter.evaluate(ast)
print(interpreter.variables) # Doit afficher {'x': 8}
5. Conclusion et améliorations possibles
Nous avons vu comment construire un interpréteur minimaliste en trois étapes :
✅ Lexer : Convertit le texte en tokens.
✅ Parser : Transforme les tokens en un AST.
✅ Interpréteur : Exécute le programme et stocke les variables.
Améliorations possibles
🚀 Ajouter les opérations * et /.
🚀 Implémenter des fonctions et des boucles.
🚀 Ajouter une instruction print pour afficher des valeurs.
Cet exercice permet de mieux comprendre le fonctionnement des langages de programmation. Si vous souhaitez aller plus loin, explorez l’implémentation d’un langage complet avec gestion des types et interprétation avancée !

















