Ce projet implémente un analyseur syntaxique LL(1) pour une version simplifiée du langage C en Python. L'analyseur utilise un automate à pile avec une méthode descendante de type LL(1).
L'analyseur implémente la grammaire LL(1) suivante :
<Programme> ::= main() {<liste_declarations><liste_instructions>}
<liste_declarations> ::= <une_declaration><liste_declarations> | ε
<une_declaration> ::= <type> id ;
<liste_instructions> ::= <une_instruction><liste_instructions> | ε
<une_instruction> ::= <affectation> | <test>
<type> ::= int | float
<affectation> ::= id = nombre ;
<test> ::= if <condition> <une_instruction> else <une_instruction> ;
<condition> ::= id <operateur> nombre
<operateur> ::= < | > | =
Remarque : id et nombre sont des terminaux (identifiants et nombres respectivement).
Cette grammaire est LL(1) car :
- Pas de récursivité gauche : Toutes les productions sont récursives à droite ou non-récursives
- Ensembles FIRST disjoints : Pour chaque non-terminal avec plusieurs productions, les ensembles FIRST ne se chevauchent pas
- Lookahead déterministe : Chaque production peut être déterminée de manière unique par un seul jeton de lookahead
Ensembles FIRST et FOLLOW clés :
- FIRST(<type>) = {int, float}
- FIRST(<une_instruction>) = {id, if}
- FIRST(<operateur>) = {<, >, =}
- FOLLOW(<liste_declarations>) = {id, if, }}
- FOLLOW(<liste_instructions>) = {}}
L'analyseur est composé de trois composants principaux :
- Tokenise la chaîne d'entrée en un flux de jetons
- Reconnaît les mots-clés (
main,int,float,if,else) - Identifie les symboles (
(,),{,},=,;,<,>) - Extrait les identifiants et les nombres
- Implémente l'algorithme d'analyse LL(1) en utilisant un automate à pile
- Utilise une table d'analyse pour déterminer quelle production appliquer
- Maintient une pile pour l'analyse
- Retourne un arbre de dérivation (liste des règles de grammaire appliquées)
- Plusieurs programmes de test sont intégrés dans le code comme variables
- Facile d'ajouter de nouveaux programmes de test
python3 ll1_parser.pyCeci exécutera tous les programmes de test prédéfinis et affichera :
- Le flux de jetons
- L'arbre de dérivation (règles de grammaire appliquées)
- Le statut de succès/échec
Pour tester un nouveau programme, ajoutez simplement une nouvelle variable dans la section de test :
# Votre nouveau programme de test
NOUVEAU_PROGRAMME_TEST = """
main() {
int mavariable;
mavariable = 42;
}
"""
# Ajoutez-le à la liste des tests
test_programs = [
# ... tests existants ...
("Mon Nouveau Test", NOUVEAU_PROGRAMME_TEST),
]Vous pouvez également utiliser l'analyseur de manière programmatique :
from ll1_parser import parse_program
programme = """
main() {
int x;
x = 10;
}
"""
succes, derivations = parse_program(programme)Pour le programme d'entrée :
main() {
int x;
x = 10;
}L'analyseur produit :
Jetons :
Token(MAIN, 'main')
Token(LPAREN, '(')
Token(RPAREN, ')')
Token(LBRACE, '{')
Token(INT, 'int')
Token(ID, 'x')
Token(SEMICOLON, ';')
Token(ID, 'x')
Token(ASSIGN, '=')
Token(NUMBER, '10')
Token(SEMICOLON, ';')
Token(RBRACE, '}')
Token(EOF, '$')
Arbre de Dérivation :
1. <Programme> -> main ( ) { <liste_declarations> <liste_instructions> }
2. <liste_declarations> -> <une_declaration> <liste_declarations>
3. <une_declaration> -> <type> id ;
4. <type> -> int
5. <liste_declarations> -> ε
6. <liste_instructions> -> <une_instruction> <liste_instructions>
7. <une_instruction> -> <affectation>
8. <affectation> -> id = nombre ;
9. <liste_instructions> -> ε
- Test 1 : Programme simple avec déclarations et affectations
- Test 2 : Programme avec instruction if-else
- Test 3 : Programme valide minimal (vide)
- Test 4 : Programme avec seulement des déclarations
- Test 5 : Programme avec instructions if-else imbriquées
- Test 6 : Programme complexe avec plusieurs fonctionnalités
L'analyseur applique strictement la grammaire :
- Tous les programmes doivent commencer par
main() {et se terminer par} - Les déclarations doivent venir avant les instructions
- Toutes les instructions doivent se terminer par
; - Les conditions dans if-else doivent comparer un identifiant avec un nombre (pas deux identifiants)
- Les instructions if-else doivent avoir les deux branches
L'analyseur fournit des messages d'erreur clairs :
- Erreurs lexicales : Caractères inconnus ou jetons invalides
- Erreurs syntaxiques : Jetons inattendus, points-virgules manquants, etc.
Exemple d'erreur :
✗ Syntax error: Expected NUMBER, got ID at position 94
- Code propre : Bien structuré avec docstrings et annotations de type
- Conception modulaire : Composants analyseur lexical et syntaxique séparés
- Facile à étendre : L'ajout de nouvelles règles de grammaire ou de cas de test est simple
- Sortie complète : Affiche à la fois le flux de jetons et l'arbre de dérivation pour le débogage
Créé pour le projet du cours de Compilation.