Comment coder un interpréteur de langage simple

1. Définition du langage

Nous allons implémenter un interpréteur pour un langage simple qui supporte :
✔ Opérations arithmétiques (+, -, *, /)
✔ Parenthèses pour gérer la priorité (( ))
✔ Variables simples (x = 10)

📌 Exemple de code interprété :

txtCopierModifierx = 5 + 3
y = x * 2
print(y)   # Affichera 16

2. Étape 1 : Analyse lexicale (Lexer)

L’analyse lexicale découpe le texte en tokens (éléments du langage).

📌 Exemple d’entrée :

txtCopierModifierx = 10 + 5

Lexeur produit les tokens suivants :

scssCopierModifierTOKEN(ID, "x"), TOKEN(ASSIGN, "="), TOKEN(NUM, "10"), TOKEN(PLUS, "+"), TOKEN(NUM, "5")

📌 Code du Lexer :

pythonCopierModifierimport re

class Token:
    def __init__(self, type_, value):
        self.type = type_
        self.value = value
    
    def __repr__(self):
        return f"TOKEN({self.type}, {self.value})"

class Lexer:
    def __init__(self, text):
        self.text = text
        self.pos = 0
        self.token_exprs = [
            (r'[ \t]+', None),        # Espaces ignorés
            (r'\d+', 'NUM'),          # Nombres entiers
            (r'[a-zA-Z_][a-zA-Z0-9_]*', 'ID'),  # Identifiants (variables)
            (r'=', 'ASSIGN'),         # Signe égal
            (r'\+', 'PLUS'),          # Opérateur addition
            (r'-', 'MINUS'),          # Opérateur soustraction
            (r'\*', 'MULT'),          # Opérateur multiplication
            (r'/', 'DIV'),            # Opérateur division
            (r'\(', 'LPAREN'),        # Parenthèse ouvrante
            (r'\)', 'RPAREN')         # Parenthèse fermante
        ]

    def tokenize(self):
        tokens = []
        while self.pos < len(self.text):
            match = None
            for pattern, tag in self.token_exprs:
                regex = re.compile(pattern)
                match = regex.match(self.text, self.pos)
                if match:
                    if tag:  # Ignore les espaces
                        tokens.append(Token(tag, match.group(0)))
                    self.pos = match.end()
                    break
            if not match:
                raise ValueError(f"Caractère invalide : {self.text[self.pos]}")
        return tokens

3. Étape 2 : Analyse syntaxique (Parser)

Le parser construit un arbre syntaxique abstrait (AST – Abstract Syntax Tree) à partir des tokens.

📌 Exemple d’arbre syntaxique pour x = 10 + 5

markdownCopierModifier   =
  / \
 x   +
    / \
   10  5

📌 Code du Parser :

pythonCopierModifierclass ASTNode:
    pass

class BinOp(ASTNode):  # Noeud binaire (opérations)
    def __init__(self, left, op, right):
        self.left = left
        self.op = op
        self.right = right

class Num(ASTNode):  # Noeud nombre
    def __init__(self, value):
        self.value = value

class Var(ASTNode):  # Noeud variable
    def __init__(self, name):
        self.name = name

class Assign(ASTNode):  # Noeud assignation
    def __init__(self, var, expr):
        self.var = var
        self.expr = expr

class Parser:
    def __init__(self, tokens):
        self.tokens = tokens
        self.pos = 0

    def consume(self, type_):
        if self.pos < len(self.tokens) and self.tokens[self.pos].type == type_:
            token = self.tokens[self.pos]
            self.pos += 1
            return token
        raise ValueError(f"Attendu {type_}, trouvé {self.tokens[self.pos].type}")

    def factor(self):  # Gestion des nombres et parenthèses
        token = self.tokens[self.pos]
        if token.type == 'NUM':
            self.pos += 1
            return Num(int(token.value))
        elif token.type == 'ID':
            self.pos += 1
            return Var(token.value)
        elif token.type == 'LPAREN':
            self.consume('LPAREN')
            node = self.expr()
            self.consume('RPAREN')
            return node
        raise ValueError("Expression invalide")

    def term(self):  # Multiplication et division
        node = self.factor()
        while self.pos < len(self.tokens) and self.tokens[self.pos].type in ('MULT', 'DIV'):
            op = self.tokens[self.pos]
            self.pos += 1
            node = BinOp(node, op.type, self.factor())
        return node

    def expr(self):  # Addition et soustraction
        node = self.term()
        while self.pos < len(self.tokens) and self.tokens[self.pos].type in ('PLUS', 'MINUS'):
            op = self.tokens[self.pos]
            self.pos += 1
            node = BinOp(node, op.type, self.term())
        return node

    def assignment(self):  # Assignation de variables
        if self.pos < len(self.tokens) and self.tokens[self.pos].type == 'ID':
            var = Var(self.tokens[self.pos].value)
            self.pos += 1
            self.consume('ASSIGN')
            expr = self.expr()
            return Assign(var, expr)
        return self.expr()

4. Étape 3 : Évaluation et exécution

L’interpréteur exécute l’AST en évaluant chaque nœud.

📌 Code de l’Interpréteur :

pythonCopierModifierclass Interpreter:
    def __init__(self):
        self.variables = {}  # Stocke les variables

    def visit(self, node):
        if isinstance(node, Num):
            return node.value
        elif isinstance(node, Var):
            return self.variables.get(node.name, 0)
        elif isinstance(node, BinOp):
            left = self.visit(node.left)
            right = self.visit(node.right)
            if node.op == 'PLUS':
                return left + right
            elif node.op == 'MINUS':
                return left - right
            elif node.op == 'MULT':
                return left * right
            elif node.op == 'DIV':
                return left / right
        elif isinstance(node, Assign):
            value = self.visit(node.expr)
            self.variables[node.var.name] = value
            return value
        else:
            raise ValueError("Noeud inconnu")

    def execute(self, tree):
        return self.visit(tree)

5. Exécution complète

📌 Utilisation du lexer, parser et interpréteur :

pythonCopierModifiercode = "x = 10 + 5"
tokens = Lexer(code).tokenize()
ast = Parser(tokens).assignment()
interpreter = Interpreter()
result = interpreter.execute(ast)
print(interpreter.variables)  # {'x': 15}

Notre interpréteur fonctionne ! 🎉 Il est capable de lire, analyser et exécuter du code simple.


Conclusion

Nous avons construit un interpréteur de langage simple en Python, comprenant :
Un Lexer pour découper le code en tokens.
Un Parser pour analyser la syntaxe et générer un AST.
Un Interpréteur pour exécuter les expressions et gérer les variables.

🚀 Prochaine étape : ajouter des boucles et des fonctions !

carle
carle