Rapport d’analyse d’un analyseur lexical en C
Faiçal Bhar
19 mars 2025
Table des matières
1 Introduction 2
2 Algorithme principal de l’analyseur syntaxique 2
3 Structure générale du programme 2
4 Analyse des constantes et types de données 2
4.1 Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
4.2 Types de tokens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
4.3 Types de transitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
4.4 Structure de la table des symboles . . . . . . . . . . . . . . . . . . . . . . . . . . 3
5 Variables globales 3
6 Implémentation des automates 4
6.1 Initialisation des automates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
6.2 Fonction de transition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
7 Gestion de la table des symboles 5
7.1 Recherche de symboles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
7.2 Ajout de symboles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
7.3 Déclaration d’identifiants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
8 Analyse lexicale 6
8.1 Reconnaissance de lexèmes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
8.2 Analyse de fichier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
9 Fonction principale et affichage 8
9.1 Fonction principale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
9.2 Affichage de la table des symboles . . . . . . . . . . . . . . . . . . . . . . . . . . 9
9.3 Execution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
10 Analyse détaillée de l’algorithme d’analyse lexicale 9
10.1 Phase d’initialisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
10.2 Phase d’analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
10.3 Gestion des cas particuliers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
1
1 Introduction
Ce rapport présente une analyse détaillée d’un programme C qui implémente un analyseur
lexical. L’analyseur lexical, également appelé scanneur ou tokenizer, est la première phase d’un
compilateur ou d’un interpréteur. Son rôle est de transformer une séquence de caractères (le code
source) en une séquence de tokens (unités lexicales) qui seront ensuite utilisés par l’analyseur
syntaxique.
Le programme étudié est conçu pour analyser des fichiers sources en C et identifier différents
types de tokens tels que les mots-clés, les identifiants, les opérateurs et les nombres. Il utilise
des automates à états finis pour reconnaı̂tre les différents types de lexèmes et maintient une
table des symboles pour stocker les informations sur les identifiants et les mots-clés rencontrés.
2 Algorithme principal de l’analyseur syntaxique
1 Algorithme principal de l analyseur syntaxique
2 1: Initialiser la table des symboles avec les mots cles ( if , else , while ,
...)
3 2: Ouvrir le fichier source en lecture
4 3: while non fin de fichier do
5 4: token <- Appeler AnalyseurLexical ()
6 5: if token valide then
7 6: Envoyer token l analyseur syntaxique
8 7: else
9 8: Afficher " Erreur lexicale "
10 9: end if
11 10: end while
Listing 1 – Algorithme principal de l’analyseur syntaxique
3 Structure générale du programme
Le programme est organisé en plusieurs parties distinctes :
1. Définition des constantes et des types de données
2. Déclaration des variables globales
3. Implémentation des fonctions d’initialisation et de gestion des automates
4. Implémentation des fonctions de gestion de la table des symboles
5. Implémentation des fonctions d’analyse lexicale
6. Fonction principale et fonctions d’affichage
4 Analyse des constantes et types de données
4.1 Constantes
Le programme définit plusieurs constantes importantes :
1 # define MAX_IDENT_LENGTH 30
2 # define MAX_KEYWORDS 10
3 # define MAX_SYMBOLS 100
4 # define MAX_STATES 20
5 # define MAX_ASCII 128
2
Ces constantes définissent :
— La longueur maximale d’un identifiant (30 caractères)
— Le nombre maximal de mots-clés reconnus (10)
— La taille maximale de la table des symboles (100 entrées)
— Le nombre maximal d’états dans les automates (20)
— La plage des caractères ASCII standard (128)
4.2 Types de tokens
Le programme définit également des constantes pour les différents types de tokens :
1 # define TOKEN_KEYWORD 1
2 # define TOKEN_IDENTIFIER 2
3 # define TOKEN_OPERATOR 3
4 # define TOKEN_NUMBER 4
5 # define TOKEN_ERROR -1
4.3 Types de transitions
Pour distinguer les différents types d’automates, le programme définit :
1 # define TRANS_ID 0
2 # define TRANS_OP 1
4.4 Structure de la table des symboles
La structure Symbol est utilisée pour stocker les informations sur les identifiants et les
mots-clés :
1 typedef struct {
2 char lexeme [ MAX_IDENT_LENGTH ];
3 int type ; // 0 pour mot - c l , 1 pour identifiant
4 int declared ; // 0 non d c l a r , 1 d clar
5 } Symbol ;
Chaque entrée de la table des symboles contient :
— Le lexème (chaı̂ne de caractères)
— Le type (mot-clé ou identifiant)
— Un indicateur de déclaration (pour les identifiants)
5 Variables globales
Le programme utilise plusieurs variables globales :
1 int mat_trans_id [ MAX_STATES ][ MAX_ASCII ]; // Matrice pour identifiants
2 int mat_trans_op [ MAX_STATES ][ MAX_ASCII ]; // Matrice pour o p r a t e u r s
3 Symbol table_symboles [ MAX_SYMBOLS ];
4 int nb_symboles = 0;
5 char keywords [ MAX_KEYWORDS ][ MAX_IDENT_LENGTH ] =
6 { " if " , " else " , " while " , " for " , " int " , " float " , " char " , " return " , " void " ,
" main " };
Ces variables représentent :
— mat trans id : Matrice de transition pour l’automate des identifiants
— mat trans op : Matrice de transition pour l’automate des opérateurs
— table symboles : Table des symboles pour stocker les identifiants et mots-clés
3
— nb symboles : Nombre actuel de symboles dans la table
— keywords : Liste des mots-clés reconnus par l’analyseur
6 Implémentation des automates
6.1 Initialisation des automates
La fonction initialiser automates() configure les matrices de transition pour les auto-
mates :
1 void i n i t i a l i s e r _ a u t o m a t e s () {
2 // Initialiser toutes les transitions -1 ( invalide )
3 for ( int i = 0; i < MAX_STATES ; i ++) {
4 for ( int j = 0; j < MAX_ASCII ; j ++) {
5 mat_trans_id [ i ][ j ] = -1;
6 mat_trans_op [ i ][ j ] = -1;
7 }
8 }
9
10 // Automate pour identifiants et mots - c l s
11 // tat 0: tat initial
12 // tat 1: tat final pour identifiants
13
14 // Transitions pour lettres (a -z , A - Z )
15 for ( char c = ’a ’; c <= ’z ’; c ++) {
16 mat_trans_id [0][( int ) c ] = 1;
17 mat_trans_id [1][( int ) c ] = 1;
18 }
19
20 for ( char c = ’A ’; c <= ’Z ’; c ++) {
21 mat_trans_id [0][( int ) c ] = 1;
22 mat_trans_id [1][( int ) c ] = 1;
23 }
24
25 // Transitions pour chiffres (0 -9)
26 for ( char c = ’0 ’; c <= ’9 ’; c ++) {
27 mat_trans_id [1][( int ) c ] = 1;
28 }
29
30 // Transitions pour underscore
31 mat_trans_id [0][( int ) ’_ ’] = 1;
32 mat_trans_id [1][( int ) ’_ ’] = 1;
33
34 // Automate pour o p r a t e u r s
35 // tat 0: tat initial
36 // tat 1: tat final pour o p r a t e u r s simples
37 // tat 2: tat final pour o p r a t e u r s c o m p o s s
38
39 // O p r a t e u r s simples
40 mat_trans_op [0][( int ) ’+ ’] = 1;
41 mat_trans_op [0][( int ) ’ - ’] = 1;
42 mat_trans_op [0][( int ) ’* ’] = 1;
43 mat_trans_op [0][( int ) ’/ ’] = 1;
44 mat_trans_op [0][( int ) ’% ’] = 1;
45 mat_trans_op [0][( int ) ’= ’] = 1;
46 mat_trans_op [0][( int ) ’ < ’] = 1;
47 mat_trans_op [0][( int ) ’ > ’] = 1;
48 mat_trans_op [0][( int ) ’! ’] = 1;
49
4
50 // O p r a t e u r s c o m p o s s
51 mat_trans_op [1][( int ) ’= ’] = 2; // += , -= , *= , /= , %= , == , <= , >= , !=
52
53 // Autres o p r a t e u r s c o m p o s s
54 mat_trans_op [0][( int ) ’& ’] = 1;
55 mat_trans_op [1][( int ) ’& ’] = 2; // &&
56
57 mat_trans_op [0][( int ) ’| ’] = 1;
58 mat_trans_op [1][( int ) ’| ’] = 2; // ||
59
60 // Initialisation de la table des symboles avec les mots - c l s
61 for ( int i = 0; i < MAX_KEYWORDS ; i ++) {
62 ajouter_symbole ( keywords [ i ] , TOKEN_KEYWORD ) ;
63 }
64 }
Cette fonction réalise les actions suivantes :
— Initialise toutes les transitions à -1 (invalide)
— Configure l’automate pour les identifiants :
— Les lettres (a-z, A-Z) et underscore peuvent commencer un identifiant
— Les lettres, chiffres et underscore peuvent continuer un identifiant
— Configure l’automate pour les opérateurs :
— Les opérateurs simples (+, -, *, /, %, =, ¡, ¿, !) sont reconnus
— Les opérateurs composés (+=, -=, *=, /=, %=, ==, ¡=, ¿=, !=, &&, ——) sont
également reconnus
— Initialise la table des symboles avec les mots-clés prédéfinis
6.2 Fonction de transition
La fonction transition() utilise les matrices de transition pour déterminer l’état suivant :
1 int transition ( int etat , char c , int type ) {
2 if ( etat < 0 || etat >= MAX_STATES || ( int ) c < 0 || ( int ) c >= MAX_ASCII )
{
3 return -1;
4 }
5
6 if ( type == TRANS_ID ) {
7 return mat_trans_id [ etat ][( int ) c ];
8 } else if ( type == TRANS_OP ) {
9 return mat_trans_op [ etat ][( int ) c ];
10 }
11
12 return -1; // Type de transition non reconnu
13 }
Cette fonction :
— Vérifie que l’état et le caractère sont valides
— Utilise la matrice de transition appropriée selon le type d’automate
— Retourne l’état suivant ou -1 si la transition n’est pas définie
7 Gestion de la table des symboles
7.1 Recherche de symboles
La fonction rechercher symbole() permet de rechercher un lexème dans la table des sym-
boles :
5
1 int re ch er ch er _sy mb ol e ( char * lexeme ) {
2 for ( int i = 0; i < nb_symboles ; i ++) {
3 if ( strcmp ( table_symboles [ i ]. lexeme , lexeme ) == 0) {
4 return i ;
5 }
6 }
7 return -1; // Non t r o u v
8 }
7.2 Ajout de symboles
La fonction ajouter symbole() permet d’ajouter un nouveau symbole à la table :
1 int ajouter_symbole ( char * lexeme , int type ) {
2 int index = re ch er ch er _sy mb ol e ( lexeme ) ;
3
4 if ( index != -1) {
5 return index ; // Symbole d j pr sent
6 }
7
8 if ( nb_symboles < MAX_SYMBOLS ) {
9 strcpy ( table_symboles [ nb_symboles ]. lexeme , lexeme ) ;
10 table_symboles [ nb_symboles ]. type = type ;
11 table_symboles [ nb_symboles ]. declared = 0; // Par d f a u t , non
d clar
12 nb_symboles ++;
13 return nb_symboles - 1;
14 }
15
16 return -1; // Table pleine
17 }
7.3 Déclaration d’identifiants
La fonction declarer identifiant() permet de marquer un identifiant comme déclaré :
1 void d e c l a r e r _ i d e n t i f i a n t ( char * lexeme ) {
2 int index = re ch er ch er _sy mb ol e ( lexeme ) ;
3 if ( index != -1 && table_symboles [ index ]. type == TOKEN_IDENTIFIER ) {
4 table_symboles [ index ]. declared = 1;
5 }
6 }
8 Analyse lexicale
8.1 Reconnaissance de lexèmes
La fonction reconnaitre lexeme() utilise les automates pour déterminer le type d’un
lexème :
1 int re co nn ai tr e_l ex em e ( char * lexeme ) {
2 // V r i f i e r si c ’ est un mot - c l
3 if ( est_mot_cle ( lexeme ) ) {
4 return TOKEN_KEYWORD ;
5 }
6
7 // V r i f i e r si c ’ est un o p r a t e u r
6
8 int etat = 0;
9 int i = 0;
10 while ( lexeme [ i ] != ’ \0 ’) {
11 etat = transition ( etat , lexeme [ i ] , TRANS_OP ) ;
12 if ( etat == -1) break ;
13 i ++;
14 }
15
16 if (( etat == 1 || etat == 2) && lexeme [ i ] == ’ \0 ’) {
17 return TOKEN_OPERATOR ;
18 }
19
20 // V r i f i e r si c ’ est un identifiant
21 etat = 0;
22 i = 0;
23 while ( lexeme [ i ] != ’ \0 ’) {
24 etat = transition ( etat , lexeme [ i ] , TRANS_ID ) ;
25 if ( etat == -1) break ;
26 i ++;
27 }
28
29 if ( etat == 1 && lexeme [ i ] == ’ \0 ’) {
30 return TOKEN_IDENTIFIER ;
31 }
32
33 // V r i f i e r si c ’ est un nombre
34 if ( isdigit ( lexeme [0]) ) {
35 i = 0;
36 while ( lexeme [ i ] != ’ \0 ’) {
37 if (! isdigit ( lexeme [ i ]) && lexeme [ i ] != ’. ’) {
38 break ;
39 }
40 i ++;
41 }
42 if ( lexeme [ i ] == ’ \0 ’) {
43 return TOKEN_NUMBER ;
44 }
45 }
46
47 return TOKEN_ERROR ;
48 }
Cette fonction :
— Vérifie d’abord si le lexème est un mot-clé
— Essaie ensuite de reconnaı̂tre un opérateur en utilisant l’automate correspondant
— Tente de reconnaı̂tre un identifiant si les tests précédents ont échoué
— Vérifie si le lexème est un nombre (entier ou décimal)
— Retourne TOKEN ERROR si le lexème n’appartient à aucune catégorie
8.2 Analyse de fichier
La fonction analyser fichier() est le cœur de l’analyseur lexical :
1 void analyser_fichier ( char * nom_fichier ) {
2 FILE * fichier = fopen ( nom_fichier , " r " ) ;
3 if ( fichier == NULL ) {
4 printf ( " Erreur : Impossible d ’ ouvrir le fichier % s \ n " , nom_fichier ) ;
5 return ;
6 }
7
7
8 char c ;
9 char buffer [ MAX_IDENT_LENGTH ];
10 int pos = 0;
11 int ligne = 1;
12 int colonne = 0;
13
14 printf ( " Analyse lexicale du fichier % s :\ n " , nom_fichier ) ;
15
16 while (( c = fgetc ( fichier ) ) != EOF ) {
17 colonne ++;
18
19 if ( c == ’\ n ’) {
20 ligne ++;
21 colonne = 0;
22 continue ;
23 }
24
25 // Traitement des c a r a c t r e s ...
26 // [ Code d t a i l l omis pour concision ]
27 }
28
29 // Traitement du dernier l e x m e si n c e s s a i r e
30 if ( pos > 0) {
31 buffer [ pos ] = ’ \0 ’;
32 int type = re co nn ai tr e_l ex em e ( buffer ) ;
33
34 // Traitement selon le type ...
35 }
36
37 fclose ( fichier ) ;
38 }
Cette fonction réalise une analyse caractère par caractère du fichier source. Elle :
— Lit le fichier caractère par caractère
— Accumule les caractères dans un buffer pour former des lexèmes
— Utilise la fonction reconnaitre lexeme() pour déterminer le type de chaque lexème
— Gère les espaces, tabulations et sauts de ligne
— Détecte les déclarations d’identifiants
— Affiche les tokens reconnus avec leur position (ligne, colonne)
9 Fonction principale et affichage
9.1 Fonction principale
La fonction main() est simple et directe :
1 int main () {
2 i n i t i a l i s e r _ a u t o m a t e s () ;
3
4 // Exemple d ’ utilisation
5 analyser_fichier ( " programme . c " ) ;
6
7 a f f i c h e r _ t a b l e _ s y m b o l e s () ;
8
9 return 0;
10 }
Elle :
— Initialise les automates
8
— Analyse un fichier source nommé ”programme.c”
— Affiche la table des symboles
9.2 Affichage de la table des symboles
La fonction afficher table symboles() présente le contenu de la table des symboles :
1 void a f f i c h e r _ t a b l e _ s y m b o l e s () {
2 printf ( " Table des symboles :\ n " ) ;
3 printf ( " Indice | Lexeme | Type | D clar
\n");
4 printf ( " - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -+ - - - - - - - - - -+ - - - - - - - -\ n " ) ;
5
6 for ( int i = 0; i < nb_symboles ; i ++) {
7 printf ( " % -6 d | % -30 s | " , i , table_symboles [ i ]. lexeme ) ;
8
9 if ( table_symboles [ i ]. type == TOKEN_KEYWORD ) {
10 printf ( " Mot - c l | ");
11 } else if ( table_symboles [ i ]. type == TOKEN_IDENTIFIER ) {
12 printf ( " Identif . | " ) ;
13 } else {
14 printf ( " Autre | ");
15 }
16
17 printf ( " % s \ n " , table_symboles [ i ]. declared ? " Oui " : " Non " ) ;
18 }
19 }
9.3 Execution
10 Analyse détaillée de l’algorithme d’analyse lexicale
L’algorithme d’analyse lexicale implémenté dans ce programme utilise une approche ca-
ractère par caractère pour construire les lexèmes. Voici les étapes principales :
10.1 Phase d’initialisation
1. Initialisation des matrices de transition pour les automates
2. Chargement des mots-clés dans la table des symboles
10.2 Phase d’analyse
1. Lecture du fichier caractère par caractère
2. Accumulation des caractères dans un buffer pour former des lexèmes
3. Traitement spécial pour les espaces, tabulations et sauts de ligne
4. Détection des fins de lexèmes et application des automates
5. Mise à jour de la table des symboles avec les identifiants rencontrés
6. Détection des déclarations d’identifiants
9
10
10.3 Gestion des cas particuliers
1. Détection des opérateurs composés (par exemple, ==, +=, &&)
2. Gestion des nombres décimaux (avec point)
3. Traitement des symboles de ponctuation (, , [, ], (, ), ;, ,)
Le programme analysé dans ce rapport implémente un analyseur lexical fonctionnel pour un
sous-ensemble du langage C. Il utilise des automates à états finis pour reconnaı̂tre les différents
types de tokens et maintient une table des symboles pour stocker les informations sur les
identifiants et les mots-clés.
L’approche utilisée est basée sur des matrices de transition, ce qui permet une implémentation
efficace des automates. La gestion des identifiants et des mots-clés est réalisée à travers une
table des symboles simple mais fonctionnelle.
Bien que le programme présente certaines limites, il constitue une base solide pour un
analyseur lexical plus complet. L’ajout de fonctionnalités supplémentaires comme la gestion
des commentaires, des chaı̂nes de caractères et des directives de préprocesseur permettrait d’en
faire un outil plus puissant pour l’analyse de programmes C.
11