Chapitre
1
STRUCTURE D'UN COMPILATEUR
1. INTRODUCTION
La compilation se décompose en deux phases principales :
(i) La phase d'analyse, qui va reconnaître les variables, les instructions, les opérateurs et
élaborer la structure syntaxique du programme ainsi que certaines propriétés sémantiques,
(ii) Et la phase de synthèse qui devra produire le code machine.
Fig. 1. Structure d'un compilateur
Structure d’un Compilateur
4
1. PHASE D'ANALYSE
L'analyse d'un programme consiste à partitionner le programme en des structures intermédiaires.
Cette analyse passe par trois étapes :
(i) L’analyse lexicale,
(ii) L’analyse syntaxique,
(ii) Et enfin, l’analyse sémantique.
1.1. Analyse Lexicale
Durant l’étape de l’analyse lexicale:
(i) D'une part, le compilateur lit le programme source de gauche à droite et regroupe les
caractères en unités lexicales, i.e., identificateurs (variables, fonctions), constantes, opérateurs (+, *
, =, … ), séparateurs ( ( ) , ; ... ), mots réservés du langage (for, while, do, … ). Puis, le compilateur
se charge d’éliminer les caractères superflus (commentaires, espaces, ... ), d’identifier les parties du
texte qui ne font pas partie du programme proprement dit, et enfin, reconnaît les types des mots lus.
Les différents identificateurs sont entrés dans une table, appelée table de symboles, où sont
enregistrées des informations concernant ces identificateurs, comme l'adresse mémoire d'un
identificateur, son type, sa porté, … Certaines de ces informations sont entrées au moment de
l'analyse lexicale. D'autres seront entrées ultérieurement, quand elles seront disponibles, i.e., durant
la phase d'analyse syntaxique ou celle d'analyse sémantique.
identificateur adresse
type
porté
…
E
m
c
…
24
40
49
…
réel
réel
entier
…
programme …
f
f
…
…
…
…
Table de symboles du compilateur
Les blancs, les autres séparateurs et les commentaires qui figurent dans un programme seront
ignorés au cours de la phase d'analyse lexicale.
Exemple 1 : L'instruction C suivante :
E = m * c * c
Publicité
est formée de :
. L'identificateur E
. L'opérateur d'affectation = . L'identificateur m
Structure d’un Compilateur
5
. L'opérateur de multiplication *
. L'identificateur c
(ii) D'autre part, on détecte les erreurs lexicales, i.e., les erreurs qui se produisent au niveau de
l'écriture d'un identificateur. Cependant, la détection d'une erreur durant une phase n'arrête pas la
compilation, on passe à la phase suivante afin de détecter le maximum d'erreurs durant la
compilation.
Exemple 2 : Voici des exemples d’erreurs lexicales en C: 1x, a&, whle, doo, …
Les outils utilisés par un compilateur pour faire l’analyse lexicale sont les expressions
régulières et les automates à états finis.
1.2. Analyse Syntaxique
Durant l’étape de l’analyse syntaxique, le compilateur :
(i) D'une part, génère les arbres syntaxiques associées aux différentes expressions: Ce sont des
arbres binaires dont les nœuds internes sont des opérateurs et les feuilles sont des opérandes.
L'insertion des opérateurs dans l'arbre se fait de haut en bas en partant de l'opérateur le plus
prioritaire et en allant vers l'opérateur le mois prioritaire, e.g., = est plus prioritaire que * et +, et *
est plus prioritaire que +.
Exemple 3 : L'arbre syntaxique associée à l'instruction de l'Exemple 1 est le suivant:
(ii) D'autre part, détecte les erreurs syntaxiques, i.e., les erreurs qui se produisent au niveau de
l'écriture d'une expression, i.e., une séquence d'identificateurs et d'opérateurs représentant une
opération à réaliser.
Exemple 4 : Voici des exemples d’erreurs syntaxiques en C: x=y++2, z=* x *, f(a,b, …
Les outils utilisés par le compilateur pour faire l’analyse syntaxique sont les grammaires et les
automates à pile.
= E * m * c c
Structure d’un Compilateur
6
1.3. Analyse Sémantique
Durant l’étape d’analyse sémantique, le compilateur :
(i) D'une part, fait les contrôles suivants:
(i.i) Contrôle de type: on vérifie le type des variables impliquées dans une expression. (i.ii) Contrôle de structures des structures de contrôle: on vérifie si les structures if … else,
while … do, for …, etc. ont la bonne structure.
(i.iii) Contrôle de l'unicité: on vérifie qu'un identificateur est déclaré une et une seule fois
dans le même bloc.
(i.iv) Contrôle de l'utilisation d'un identificateur: on vérifie si un identificateur a été déclaré et
utilisé correctement.
(ii) D'autre part, détecte les erreurs sémantiques, i.e., les erreurs obtenues suite aux contrôles
précédents
Exemple 5 : Voici des exemples d’erreurs sémantiques en C :
(a)
x=t[2.5],
où t est un tableau. L'indice 2.5 est n'est pas un entier (contrôle (i.i)).
(b)
(i=1; i<10; i++)
{x=x+1}
où i est un entier. Il manque for (contrôle (i.ii)).
(c)
int x;
float x;
x est déclarée deux fois (contrôle (i.iii)).
(d)
x=y/0;
Division par 0 (contrôle (i.iv)).
Publicité
Les outils utilisés par le compilateur pour faire l’analyse sémantique sont les traductions
dirigées par la syntaxe.
2. PHASE DE SYNTHESE
La phase de synthèse se décompose en deux étapes :
(i) La génération de code
(ii) Et l’optimisation de code
2.1. Génération de Code
La génération de code consiste à générer à partir des arbres syntaxiques, générés durant la phase d'analyse, la version en langage d'assemblage, i.e., un langage dont les instructions sont des
Structure d’un Compilateur
7
instructions élémentaires, faisant appel à des opérations élémentaires comme le chargement
(MOV), la multiplication (MUL), l'addition (ADD), la soustraction (SUB) et le branchement
(GOTO). La génération de code se fait en deux temps:
(i) En un premier temps, on génère des instructions pour une machine abstraite (virtuelle) qui
fait abstraction des architectures des machines réelles existantes. Ainsi, on s'attache plus aux
principes de traduction et aux concepts des langages qu'à l'architecture des machines.
(ii) En un second temps, on traduit les instructions destinées à la machine virtuelle en des
instructions destinées à la machine réelle existante sur laquelle on veut exécuter le programme.
Ainsi, le portage du programme sera facilité car la traduction en code cible virtuel sera faite une
fois pour toutes, indépendamment de la machine cible réelle. Il ne reste plus ensuite qu'à étudier les
problèmes spécifiques à la machine cible, et non plus les problèmes de reconnaissance du
programme.
2.2. Optimisation de Code
Durant cette étape, le compilateur tente d’optimiser le code machine produit de telle sorte que le
programme résultant soit plus rapide. Il y a des optimisations qui ne dépendent pas de la machine
cible réelle telles que l’élimination de calculs inutiles, l’élimination du code d'une fonction jamais
appelée, la propagation des constantes et l’extraction des invariants de boucles. Et il y a des
optimisations qui dépendent de la machine cible réelle telles que le remplacement des instructions
générales par des instructions plus efficaces et plus adaptées et l'utilisation optimale des registres.
Exemple 6 : Voici la traduction, en langage d'assemblage du microprocesseur Intel 8088, de
l'instruction C de l'exemple 1. Cette traduction a été faite à partir de l'arbre syntaxique présenté dans
l'exemple 3.
MOV AX c
(charger c dans l'accumulateur AX)
IMUL c
IMUL m
(multiplier c par le contenu de AX, le résultat se trouvera dans AX)
(multiplier m par le contenu de AX, le résultat se trouvera dans AX)
MOV E AX
(charger le registre AX dans E)
3. LES COLLABORATEURS DU COMPILATEUR
Une fois que le compilateur a finit de générer la version en langage d'assemblage d'un
programme, commence la tâche de l'assembleur.
3.1. L'Assembleur
L'assembleur est un programme qui génère, à partir de la version écrite en langage d'assemblage
d'un programme utilisateur, la version en langage machine, i.e., langage où tout est décrit par une
succession de bits.
En général un assembleur opère en deux étapes :
Structure d’un Compilateur
8
(i) Durant la première étape, l'assembleur fait une première lecture de la version en langage
d'assemblage du programme et entre les identificateurs rencontrés dans une table de symboles,
différente de celle générée par le compilateur. Chaque identificateur, lui est associé son adresse
mémoire.
identificateur
adresse
E
Publicité
m
c
…
24
40
49
…
Table de symboles de l'assembleur
(ii) Et durant la seconde étape, l'assembleur fait une seconde lecture de la version en langage
d'assemblage du programme et traduit chaque instruction élémentaire en binaire. La traduction se
fait de la manière suivante:
(a) Chaque code opération, comme MOV, MUL, ADD, SUB et GOTO, est remplacé par son
code binaire.
(b) Chaque identificateur, comme E, c et m, est remplacé par son adresse mémoire, écrite
binaire. Cette adresse est récupérée à partir de la table de symboles, générée durant la première
étape.
(c) Chaque registre, comme AX, BX et BL, est remplacé par son numéro, écrit en binaire.
Exemple 7 : Voici une traduction, en langage machine, des instructions élémentaires de
l'exemple 6.
0011 0001 110001
MOV AX c
0001 110001
IMUL c
0001 101000
IMUL m
0011 11000 0001
MOV E AX
Structure d’un Compilateur
9
3.2. L’Éditeur de Lien
L’éditeur de lien est un programme qui accomplit deux tâches:
(i) La reliure: Il arrive que les codes machine des différentes fonctions et/ou procédures faisant
partie d'un programme soient définis dans plusieurs fichiers différents. D'autre part, il arrive aussi
que les données du programme doivent être lues à partir d'un ou plusieurs fichiers. La reliure
consiste donc à regrouper le code machine du programme principal, celui des différentes fonctions
et/ou procédures faisant partie du programme et ceux associés aux fichiers de données en un seul
code exécutable par le microprocesseur.
(ii) Le chargement: Il consiste à prendre le code machine généré par l'assembleur et le mettre
dans la RAM, à l'endroit approprié.
4. CONCLUSION
Fig. 2 Traitement d'un programme en utilisant les compilateurs.
Pendant toutes les années 50, les compilateurs furent tenus pour des programmes difficiles à
écrire. A titre d’exemple, en 1957, la réalisation du premier compilateur Fortran nécessita 18
hommes travaillant pendant toute une année. On a découvert depuis des techniques systématiques
pour traiter la plupart des tâches importantes qui sont effectuées lors de la compilation.
Contrairement à une idée souvent répandue, la plupart des compilateurs sont écrits dans un
langage de haut niveau, et non pas dans un langage d'assemblage. Les avantages sont multiples :
(i) Facilité de manipulation de concepts avancés,
(ii) Maintenabilité accrue du compilateur, (iii) Et portage plus aisé sur d'autres machines.
A titre d’exemple, le compilateur C++ de Björne Stroustrup est écrit en C.
Programme enlangage évolué(Pascal, C, ...)CompilateurAssembleurChargeur etEditeur de lienProgramme enlangage d'assemblageProgramme encode machineRAM
Structure d’un Compilateur
10
Dans ce cours, nous nous intéressons aux étapes d’analyse lexicale, analyse syntaxique et
analyse sémantique.
* * *