You are on page 1of 79

SOMMAIRE

Sommaire ............................................................................................................................................... i
Chapitre 1.
Qu'est ce que la compilation ? ................................................................................ 1
1.
Pourquoi ce cours ? ................................................................................................................... 2
2.
Structure d'un compilateur ...................................................................................................... 2
3.
Phases d'analyses ...................................................................................................................... 3
3.1 Analyse lexicale (appele aussi Analyse linaire) .............................................................. 3
3.2 Analyse syntaxique (Analyse hirarchique ou grammaticale) ......................................... 3
3.3 Analyse smantique (analyse contextuelle) ......................................................................... 3
4.
Phases de production ................................................................................................................ 3
4.1 Gnration de code ................................................................................................................. 3
4.2 Optimisation de code .............................................................................................................. 4
5.
Phases parallles ....................................................................................................................... 4
5.1 Gestion de la table des symboles........................................................................................... 4
5.2 Gestion des erreurs.................................................................................................................. 4
6.
Conclusion .................................................................................................................................. 4
Chapitre 2.
Analyse lexicale ......................................................................................................... 7
1.
Units lexicales et lexmes ...................................................................................................... 7
2.
Spcification des units lexicales ........................................................................................... 7
3.
Attributs ...................................................................................................................................... 8
4.
Analyseur lexical ....................................................................................................................... 9
5.
Erreurs lexicales ....................................................................................................................... 11
Chapitre 3.
L'outil (f)lex ......................................................................................................... 12
1.
Structure du fichier de spcifications (f)lex .................................................................. 12
2.
Les expressions rgulires (f)lex ...................................................................................... 12
3.
Variables et fonctions prdfinies ....................................................................................... 14
4.
Exemples de fichier .l ............................................................................................................. 14
5.
Exercices .................................................................................................................................... 14
Chapitre 4.
Analyse syntaxique ................................................................................................. 16
1.
Grammaires et Arbres de drivation ................................................................................... 16
2.
Grammaires .............................................................................................................................. 16
3.
Arbre de drivation ................................................................................................................. 18
4.
Mise en oeuvre d'un analyseur syntaxique ........................................................................ 20
4.1 Analyse descendante ............................................................................................................ 20
4.1.1

Exemples.................................................................................................................................. 20

4.2 Table d'analyse LL(1) ............................................................................................................ 22


4.2.1
4.2.2
4.2.3
4.2.4

Calcul de PREMIER ............................................................................................................... 22


Calcul de SUIVANT ............................................................................................................... 23
Construction de la table d'analyse .......................................................................................... 25
Analyseur syntaxique.............................................................................................................. 25

4.3 Grammaire LL(1) ................................................................................................................... 28


4.3.1
4.3.2
4.3.3
4.3.4

5.

Rcursivit gauche ................................................................................................................ 29


Grammaire propre ................................................................................................................... 31
Factorisation gauche ............................................................................................................. 31
Conclusion ............................................................................................................................... 32

Analyse ascendante................................................................................................................. 33

Cours de Compilation

6.

Erreurs syntaxiques................................................................................................................. 39
6.1 Rcupration en mode panique........................................................................................... 40
6.2 Rcupration au niveau du syntagme ................................................................................ 40
6.3 Productions d'erreur ............................................................................................................. 41
6.4 Correction globale ................................................................................................................. 41
Chapitre 5.
Traduction dirige par la syntaxe ......................................................................... 42
1.
Dfinition dirige par la syntaxe ......................................................................................... 42
2.
Arbre syntaxique dcor ........................................................................................................ 42
3.
Attributs synthtiss et hrits ............................................................................................. 43
3.1 Attributs synthtiss ............................................................................................................. 43
3.2 Attributs hrits ..................................................................................................................... 43
4.
Graphe de dpendances ......................................................................................................... 44
5.
Evaluation des attributs ......................................................................................................... 47
5.1 Aprs l'analyse syntaxique................................................................................................... 47
5.2 Pendant l'analyse syntaxique............................................................................................... 47
6.
Exercices .................................................................................................................................... 52
Chapitre 6.
L'outil yacc/bison ................................................................................................ 54
1.
Structure du fichier de spcifications bison..................................................................... 54
2.
Attributs .................................................................................................................................... 55
3.
Communication avec l'analyseur lexical : yylval ........................................................... 55
4.
Variables, fonctions et actions prdfinies ........................................................................ 56
5.
Conflits shift-reduce et reduce-reduce ................................................................................ 56
6.
Associativit et priorit des symboles terminaux ............................................................. 56
7.
Rcupration des erreurs ....................................................................................................... 57
8.
Options de compilations Bison ............................................................................................ 57
9.
Exemples de fichier .y............................................................................................................. 58
10. Exercices .................................................................................................................................... 59
Chapitre 7.
Analyse smantique ................................................................................................ 61
1.
Porte des identificateurs ...................................................................................................... 61
2.
Contrle de type ...................................................................................................................... 62
3.
Surcharge d'oprateurs et de fonctions ............................................................................... 64
4.
Fonctions polymorphes .......................................................................................................... 65
5.
Environnement d'excution .................................................................................................. 69
5.1 Organisation de la mmoire l'excution.......................................................................... 69
5.2 Allocation dynamique : gestion du tas ............................................................................... 70
6.
Gnration de code ................................................................................................................. 72
6.1 Code intermdiaire................................................................................................................ 72
6.2 Caractristiques communes aux machines cibles ............................................................. 72
6.3 Code 3 adresses simplifi .................................................................................................. 72
6.4 Production de code 3 adresses.......................................................................................... 74
6.4.1
6.4.2

7.

Expressions arithmtiques ....................................................................................................... 74


Expressions boolennes............................................................................................................ 75

Optimisation de code ............................................................................................................. 77

Cours de Compilation

ii

CHAPITRE 1. QU'EST CE QUE LA


COMPILATION ?
Tout programmeur utilise jour aprs jour un outil essentiel la ralisation de programmes
informatiques : le compilateur. Un compilateur est un logiciel particulier qui traduit un
programme crit dans un langage de haut niveau (par le programmeur) en instructions
excutables (par un ordinateur). C'est donc l'instrument fondamental la base de tout
ralisation informatique.

Figure 1.1: Chane de dveloppement d'un programme

Tout programme crit dans un langage de haut niveau (dans lequel il est fait abstraction
(sauf pour quelques instructions) de la structure et des dtails du calculateur sur lequel les
programmes sont destins tre excuts) ne peut tre excut par un ordinateur que s'il est
traduit en instructions excutables par l'ordinateur (langage machine, instructions
lmentaires directement excutables par le processeur).
Lorsque le langage cible est aussi un langage de haut niveau, on parle plutt de traducteur.
Autre diffrence entre traducteur et compilateur : dans un traducteur, il n'y a pas de perte
d'informations (on grade les commentaires, par exemple), alors que dans un compilateur il y
a perte d'informations. Une autre phase importante qui intervient aprs la compilation pour
obtenir un excutable est la phase d'ditions de liens. Un diteur de liens rsout entre autres
les rfrences des appels de routines dont le code est conserv dans des librairies. En
gnral, un compilateur comprend une partie diteur de liens. Nous n'en parlerons pas ici.
En outre, sur les systmes modernes, l'dition des liens est faite l'excution du programme
! (Le programme est plus petit et les mises jour plus faciles). On ne parlera pas non plus de
la prcompilation (cf. prprocesseur C).
Attention, il ne faut pas confondre les compilateurs et les interprteurs ! Un compilateur est
Cours de Compilation

un programme (de traduction automatique d'un programme crit dans un langage source en
un programme crit dans un langage cible). Au lieu de produire un programme cible comme
dans le cas d'un compilateur, un interprte excute lui mme au fur et mesure les
oprations spcifies par le programme source. Il analyse une instruction aprs l'autre puis
l'excute immdiatement. A l'inverse d'un compilateur, il travaille simultanment sur le
programme et sur les donnes. Gnralement les interprteurs sont assez petits.
Il existe des langages qui sont mi-chemin de l'interprtation et de la compilation. Par
exemple Java, le source est compil pour obtenir un fichier (.class) ''byte code'' qui sera
interprt par une machine virtuelle.
En rgle gnrale, le programmeur dispose d'un calculateur concret (cartes quipes de
processeurs, puces de mmoire, ...). Le langage cible est dans ce cas dfini par le type de
processeur utilis. Mais si l'on crit un compilateur pour un processeur donn, il n'est alors
pas vident de porter ce compilateur (ce programme) sur une autre machine cible.
C'est pourquoi (entre autres raisons), on introduit des machines dites abstraites qui font
abstraction des architectures relles existantes. Ainsi, on s'attache plus aux principes de
traduction, aux concepts des langages, qu' l'architecture des machines. Cf. machine abstraite
Java.

1. Pourquoi ce cours ?
Il est vident qu'il n'est pas ncessaire de comprendre comment est crit un compilateur pour
savoir comment l'utiliser. De mme, un informaticien a peu de chances d'tre impliqu dans
la ralisation ou mme la maintenance d'un compilateur pour un langage de programmation
majeur. Alors pourquoi ce cours ?
Le but de ce cours est de prsenter les principes de base inhrents la ralisation de
compilateurs. Les ides et techniques dveloppes dans ce domaine sont si gnrales et
fondamentales qu'un informaticien (et mme un scientifique non informaticien) les utilisera
trs souvent au cours de sa carrire (traitement de donnes, moteurs de recherche, etc.).
Nous verrons les principes de base inhrents la ralisation de compilateurs : analyse
lexicale, analyse syntaxique, analyse smantique, gnration de code.
En outre, comprendre comment est crit un compilateur permet de mieux comprendre les
"contraintes" imposes par les diffrents langages lorsque l'on crit un programme dans un
langage de haut niveau.
Nous avons dj tudi les outils fondamentaux utiliss pour effectuer ces analyses :
fondements de base de la thorie des langages (grammaires, automates, ...), mthodes
algorithmiques d'analyse, ...

2. Structure d'un compilateur


// Morceau de programme en C
#include <stdio.h>
int CalculDeMaValeurGeniale(float * tab)
{
int i,j;
float k1,k2;
...
k2=2*k1+sin(tab[j]);
if (k2>i)
...
La compilation se dcompose en deux phases :

Cours de Compilation

une phase d'analyses, qui va reconnatre les variables, les instructions, les oprateurs et
laborer la structure syntaxique du programme ainsi que certaines proprits smantiques
une phase de synthse et de production qui devra produire le code cible.

3. Phases d'analyses (appele aussi Analyse linaire)


Dans cette tape, il s'agit de reconnatre les "types" des "mots" lus. Pour cela, on lit le
programme source de gauche droite et les caractres sont regroups en units lexicales.
L'analyse lexicale se charge de :
liminer les caractres superflus (commentaires, espaces, ...)
Identifier les parties du texte qui ne font pas partie proprement parler du programme mais
sont des directives pour le compilateur
Identifier les symboles qui reprsentent des identificateurs, des constantes relles, entire,
chanes de caractres, des oprateurs (affectation, addition, ...), des sparateurs (parenthses,
points virgules, ...)

, les mots clefs du langage, ... C'est cela que l'on appelle des units

2.1

lexicales.
Outils thoriques utiliss : expressions rgulires et automates tats finis

3.1 Analyse syntaxique (Analyse hirarchique ou grammaticale)


Il s'agit de regrouper les units lexicales en structures grammaticales, de dcouvrir la
structure du programme. L'analyseur syntaxique sait comment doivent tre construites les
expressions, les instructions, les dclarations de variables, les appels de fonctions, ...
Exemple. En C, une slection simple doit se prsenter sous la forme :
if (expression) instruction
Si l'analyseur syntaxique reoit la suite d'units lexicales
MC_IF IDENT OPREL ENTIER ...
il doit signaler que ce n'est pas correct car il n'y a pas de ( juste aprs le if
Outils thoriques utiliss : grammaires et automates pile

3.2 Analyse smantique (analyse contextuelle)


Dans cette phase, on opre certains contrles (contrles de type, par exemple) afin de vrifier
que l'assemblage des constituants du programme a un sens. On ne peut pas, par exemple,
additionner un rel avec une chane de caractres, ou affecter une variable un nombre, ...
Outil thorique utilis : schma de traduction dirige par la syntaxe

4. Phases de production
4.1 Gnration de code
Il s'agit de produire les instructions en langage cible. En gnral, on produira dans un
premier temps des instructions pour une machine abstraite (virtuelle). Puis ensuite on fera
la traduction de ces instructions en des instructions directement excutables par la machine
relle sur laquelle on veut que le compilateur s'excute. Ainsi, le portage du compilateur sera
facilit, car la traduction en code cible virtuel sera faite une fois pour toutes,
indpendamment de la machine cible relle. Il ne reste plus ensuite qu' tudier les
problmes spcifiques la machine cible, et non plus les problmes de reconnaissance du
programme (cf. Java).

Cours de Compilation

4.2 Optimisation de code


Cette phase tente d'amliorer le code produit de telle sorte que le programme rsultant soit
plus rapide. Il y a des optimisations qui ne dpendent pas de la machine cible : limination
de calculs inutiles (faits en double), limination du code d'une fonction jamais appele,
propagation des constantes, extraction des boucles des invariants de boucle, ... Et il y a des
optimisations qui dpendent de la machine cible : remplacer des instructions gnrales par
des instructions plus efficaces et plus adaptes, utilisation optimale des registres, ...

5. Phases parallles
5.1 Gestion de la table des symboles
La table des symboles est la structure de donnes utilise servant stocker les informations
qui concernent les identificateurs du programme source (par exemple leur type, leur
emplacement mmoire, leur porte, visibilit, nombre et type et mode de passage des
paramtres d'une fonction, ...). Le remplissage de cette table (la collecte des informations) a
lieu lors des phases d'analyse. Les informations contenues dans la table des symboles sont
ncessaires lors des analyses syntaxique et smantique, ainsi que lors de la gnration de
code.

5.2 Gestion des erreurs


Chaque phase peut rencontrer des erreurs. Il s'agit de les dtecter et d'informer l'utilisateur le
plus prcisment possible : erreur de syntaxe, erreur de smantique, erreur systme, erreur
interne.
Un compilateur qui se contente d'afficher syntax error n'apporte pas beaucoup d'aide
lors de la mise au point.
Aprs avoir dtect une erreur, il s'agit ensuite de la traiter de telle manire que la
compilation puisse continuer et que d'autres erreurs puissent tre dtectes. Un compilateur
qui s'arrte la premire erreur n'est pas non plus trs performant. Bien sr, il y a des limites
ne pas dpasser et certaines erreurs (ou un trop grand nombre d'erreurs) peuvent entraner
l'arrt de l'excution du compilateur.

6. Conclusion
Figure 2.1: Structure d'un compilateur

Cours de Compilation

Pendant toutes les annes 50, les compilateurs furent tenus pour des programmes difficiles
crire. Par exemple, la ralisation du premier compilateur Fortran ncessita 18 hommesannes de travail (1957). On a dcouvert depuis des techniques systmatiques pour traiter la
plupart des tches importantes qui sont effectues lors de la compilation.

Diffrentes phases de la compilation

Outils thoriques utiliss

Phases d'analyse

analyse lexicale

expressions rgulires

(scanner)

automates tats finis

analyse syntaxique

grammaires

(parer)

automates pile

analyse smantique

traduction dirige par la syntaxe

Phases de production gnration de code

traduction dirige par la syntaxe

optimisation de code
Gestions parallles

table des symboles


traitement des erreurs

Contrairement une ide souvent rpandue, la plupart des compilateurs sont raliss (crits)
dans un langage de haut niveau, et non en assembleur. Les avantages sont multiples :
facilit de manipulation de concepts avancs
maintenabilit accrue du compilateur
portage sur d'autres machines plus ais
Par exemple, le compilateur C++ de Bjrne Stroustrup est crit en C, .... Il est mme possible

Cours de Compilation

d'crire un compilateur pour un langage L dans ce langage L (gcc est crit en C ...!)
(bootstrap).

Cours de Compilation

CHAPITRE 2.

ANALYSE LEXICALE

L'analyseur lexical constitue la premire tape d'un compilateur. Sa tche principale est de
lire les caractres d'entre et de produire comme rsultat une suite d'units lexicales que
l'analyseur syntaxique aura traiter. En plus, l'analyseur lexical ralise certaines tches
secondaires comme l'limination de caractres superflus (commentaires, tabulations, fin de
lignes, ...), et gre aussi les numros de ligne dans le programme source pour pouvoir
associer chaque erreur rencontre par la suite la ligne dans laquelle elle intervient.
Dans ce chapitre, nous abordons des techniques de spcifications et d'implantation
d'analyseurs lexicaux. Ces techniques peuvent tre appliques d'autres domaines. Le
problme qui nous intresse est la spcification et la conception de programmes qui
excutent des actions dclenches par des modles dans des chanes (traitement de
donnes, moteurs de recherche, ...).

1. Units lexicales et lexmes


Dfinition 3.1
collective.

Une unit lexicale est une suite de caractres qui a une signification

Exemples : les chanes


sont des oprateurs relationnels. L'unit lexicale est
OPREL (par exemple). Les chanes toto, ind, tab, ajouter sont des identificateurs (de
variables, ou de fonctions). Les chanes if, else, while sont des mots clefs. Les symboles
,.;() sont des sparateurs.
Dfinition 3.2 Un modle est une rgle associe une unit lexicale qui dcrit l'ensemble
des chanes du programme qui peuvent correspondre cette unit lexicale.
Dfinition 3.3 On appelle lexme toute suite de caractre du programme source qui
concorde avec le modle d'une unit lexicale.

Exemples :
L'unit lexicale IDENT (identificateurs) en C a pour modle : toute suite non vide de
caractres compose de chiffres, lettres ou du symbole "_" et qui commencent par une lettre.
Des exemples de lexmes pour cette unit lexicale sont : truc, i,a1, ajouter_valeur ...
L'unit lexicale NOMBRE (entier sign) a pour modle : toute suite non vide de chiffres
prcde ventuellement d'un seul caractre parmi
. Lexmes possibles : -12, 83204,
+0 ...
L'unit lexicale REEL a pour modle : tout lexme correspondant l'unit lexicale
NOMBRE suivi ventuellement d'un point et d'une suite (vide ou non) de chiffres, le tout
suivi ventuellement du caractre E ou e et d'un lexme correspondant l'unit lexicale
NOMBRE. Cela peut galement tre un point suivi d'une suite de chiffres, et ventuellement
du caractre E ou e et d'un lexme correspondant l'unit lexicale NOMBRE. Exemples de
lexmes : 12.4, 0.5e3, 10., -4e-1, -.103e+2 ...
Pour dcrire le modle d'une unit lexicale, on utilisera des expressions rgulires.

2. Spcification des units lexicales

Cours de Compilation

Nous disposons donc, avec les E.R., d'un mcanisme de base pour dcrire des units lexicales.
Par exemple, une ER dcrivant les identificateurs en C pourrait tre :
ident = (a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v |w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q
|R|S|T|U
|V|W|X|Y|Z) (a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r| s|t|u|v|w|x|y|z|A|B |C|D|E|F|G|H|I|J|K|L|M |N|O
|P|Q| R|S|T|U|V|W|X|Y|Z|0|1|2|3|4|5|6|7|8|9|_)*
C'est un peu chiant et illisible ...
Alors on s'autorise des dfinitions rgulires et le symbole - sur des types ordonns (lettres,
chiffres, ...).
Une dfinition rgulire est une suite de dfinitions de la forme

o chaque ri est une expression rgulire sur l'alphabet


nom diffrent.

, et chaque di est un

Exemple : l'unit lexicale IDENT (identificateurs) en C devient

Remarque IMPORTANTE : toutes les units lexicales ne peuvent pas tre exprimes par
des dfinitions rgulires. Par exemple une unit lexicale correspondant au modle "toute
suite de a et de b de la forme anbn avec
" ne peut pas tre exprime par des dfinitions
rgulires, car ce n'est pas un langage rgulier (mais c'est un langage que l'on appelle hors
contexte, on verra plus tard qu'on peut s'en sortir avec les langages hors contexte et
heureusement).
Autre exemple : il n'est pas possible d'exprimer sous forme d'ER les systmes de parenthses
bien forms, ie les mots (), (())(), ()(()()(())) .... Ce n'est pas non plus un langage rgulier
(donc les commentaires la Pascal ne forment pas un langage rgulier).

3. Attributs
Exemples :
Pour ce qui est des symboles <=, >=, <, >, <>, l'analyseur syntaxique a juste besoin de
savoir que cela correspond l'unit lexicale OPREL (oprateur relationnel). C'est seulement
lors de la gnration de code que l'on aura besoin de distinguer < de >= (par exemple).
Pour ce qui est des identificateurs, l'analyseur syntaxique a juste besoin de savoir que c'est
l'unit lexicale IDENT. Mais le gnrateur de code, lui, aura besoin de l'adresse de la variable
correspondant cet identificateur. L'analyseur smantique aura aussi besoin du type de la
variable pour vrifier que les expressions sont smantiquement correctes.
Cette information supplmentaire, inutile pour l'analyseur syntaxique mais utile pour les
autres phases du compilateur, est appele attribut.

Cours de Compilation

4. Analyseur lexical
Rcapitulons : le rle d'un analyseur lexical est la reconnaissance des units lexicales. Une
unit lexicale peut (la plupart du temps) tre exprime sous forme de dfinitions rgulires.
Dans la thorie des langages, on dfinit des automates qui sont des machines thoriques
permettant la reconnaissance de mots (voir chapitre 8). Ces machines ne marchent que pour
certains types de langages. En particulier :
Thorme 3.9 Un langage rgulier est reconnu par un automate fini.
Contentons nous pour l'instant de donner un exemple d'automate :
Le langage rgulier dcrit par l'ER (abbc|bacc)+c(c|bb)* est reconnu par l'automate :
tat a b c
0

1 4

3
4

6
5

1 4 7

8 7

Bon, un langage rgulier (et donc une ER) peut tre reconnu par un automate. Donc pour
crire un analyseur lexical de notre programme source, il "suffit" (...) donc d'crire un
programme simulant l'automate reconnaissant les units lexicales.
Lorsqu'une unit lexicale est reconnue, elle envoye l'analyseur syntaxique, qui la traite,
puis repasse la main l'analyseur lexical qui lit l'unit lexicale suivante dans le programme
source. Et ainsi de suite, jusqu' tomber sur une erreur ou jusqu' ce que le programme source
soit trait en entier (et alors on est content).
Exemple de morceau d'analyseur lexical pour le langage Pascal (en C) :
c = getchar();
switch (c) {
case ':' : c=getchar();
if (c== '=')
{
unite_lex = AFFECTATION;
c= getchar();
}

Cours de Compilation

else
unite_lex = DEUX_POINTS;
break;
case '<' : unite_lex := OPREL;
c= getchar();
if (c=='=')
{
attribut = INFEG;
c = getchar();
}
else
attribut := INF
break;
case .....

ou encore, en copiant le travail de l'automate (mais cela donne un code moins rapide, car il
contient beaucoup d'appels de fonctions)
void Etat1()
{
char c;
c= getchar();
swicth(c) {
case ':' : Etat2();
break;
case '<' : Etat5();
break;
case 'a' : Etat57();
break;
...
default : ERREUR();
}
}
void Etat36524()
...

Il n'est pas vident du tout dans tout a de s'y retrouver si on veut modifier l'analyseur, le
complter, ...
Heureusement, il existe des outils pour crire des programmes simulant des automates partir
de simples dfinitions rgulires. Par exemple : flex
Exemple de programme en flex :
chiffre
lettre
entier
ident
%%
":="
"<="

[0-9]
[a-zA-Z]
[+-]?[1-9]{chiffre}*
{lettre}({lettre}|{chiffre})*

{ return AFF; }
{
attribut = INFEG;
return OPREL;
}
if|IF|If|iF {return MC_IF;}
{entier} {return NB;}
{ident}
{return ID;}
%%
Cours de Compilation

10

main() {
yylex();
}

5. Erreurs lexicales
Peu d'erreurs sont dtectables au seul niveau lexical, car un analyseur lexical a une vision trs
locale du programme source. Les erreurs se produisent lorsque l'analyseur est confront une
suite de caractres qui ne correspond aucun des modles d'unit lexicale qu'il a sa
disposition.
Par exemple, dans un programme C, si l'analyseur rencontre esle, s'agit t-il du mot clef else
mal orthographi ou d'un identificateur ? Dans ce cas prcis, comme la chane correspond au
modle de l'unit lexicale IDENT (identificateur), l'analyseur lexical ne dtectera pas d'erreur,
et transmettra l'analyseur syntaxique qu'il a reconnu un IDENT. S'il s'agit du mot clef mal
orthographi, c'est l'analyseur syntaxique qui dtectera alors une erreur.
Autre exemple : s'il rencontre 1i, il ne sait pas s'il s'agit du chiffre 1 suivi de l'identificateur i
ou d'une erreur de frappe (et dans ce cas l'utilisateur pensait-il i1, i, 1 ou tout autre chose
?). L'analyseur lexical peut juste signaler que la chane 1i ne correspond aucune unit
lexicale dfinie.
Lorsque l'analyseur est confront une suite de caractres qui ne correspond aucun de ses
modles, plusieurs stratgies sont possibles :
- mode panique : on ignore les caractres qui posent problme et on continue3.1
- transformations du texte source : insrer un caractre, remplacer, changer, ...
La correction d'erreur par transformations du texte source se fait en calculant le nombre
minimum de transformations apporter au mot qui pose problme pour en obtenir un qui ne
pose plus de problmes. On utilise des techniques de calcul de distance minimale entre des
mots. Cette technique de rcupration d'erreur est trs peu utilise en pratique car elle est trop
coteuse implanter. En outre, au niveau lexical, elle est peu efficace, car elle entrane
d'autres erreurs lors des phases d'analyse suivantes3.2.
La stratgie la plus simple est le mode panique. En fait, en gnral, cette technique se contente
de refiler le problme l'analyseur syntaxique. Mais c'est efficace puisque c'est lui, la plupart
du temps, le plus apte le rsoudre.

Cours de Compilation

11

CHAPITRE 3.

L'OUTIL (F)LEX

De nombreux outils ont t btis pour construire des analyseurs lexicaux partir de notations
spcifiques bass sur des expressions rgulires. lex est un utilitaire d'unix. Son grand frre
flex est un produit gnu. (f)lex accepte en entre des spcifications d'units lexicales sous
forme de dfinitions rgulires et produit un programme crit dans un langage de haut niveau
(ici le langage C) qui, une fois compil, reconnat ces units lexicales (ce programme est donc
un analyseur lexical).
> (f)lex fichier_de_specif.l
donne le fichier lex.yy.c qu'il faut compiler avec la bibliothque (f)lex :
> gcc lex.yy.c -l(f)l

Le fichier de spcifications (f)lex contient des expressions rgulires suivies d'actions


(rgles de traduction). L'excutable obtenu lit le texte d'entre caractre par caractre jusqu'
ce qu'il trouve le plus long prfixe du texte d'entre qui corresponde l'une des expressions
rgulires. Dans le cas o plusieurs rgles sont possibles, c'est la premire rgle rencontre
(de haut en bas) qui l'emporte. Il excute alors l'action correspondante. Dans le cas o aucune
rgle ne peut tre slectionne, l'action par dfaut consiste copier le caractre du fichier
d'entre en sortie.

1. Structure du fichier de spcifications (f)lex


%{
dclaration (en C) de variables, constantes,
%}
dclaration de dfinitions rgulires
%%
rgles de traduction
%%
bloc principal et fonctions auxiliaires
Une dfinition rgulire permet d'associer un nom une expression rgulire (f)lex et de
se rfrer par la suite (dans la section des rgles) ce nom plutt qu' l'expression rgulire.
les rgles de traduction sont des suites d'instructions de la forme
exp1 action1
exp2 action2
Les expi sont des expressions rgulires (f)lex et doivent commencer en colonne 0. Les
actioni sont des blocs d'instructions en C (i.e. une seule instruction C ou une suite
d'instructions entre { et }) qui doivent commencer sur la mme ligne que l'expression
correspondante.
la section du bloc principal et des fonctions auxiliaires est facultative (ainsi donc que la
ligne %% qui la prcde). Elles contient les routines C dfinies par l'utilisateur et
ventuellement une fonction main() si celle par dfaut ne convient pas.

2. Les expressions rgulires (f)lex

Cours de Compilation

12

Une expression rgulire (f)lex se compose de caractres normaux et de mta-caractres


qui ont une signification spciale : $, , , [, ], , , <, >, +, -, *, /, |, ?. La figure 4.1 donne
les expressions rgulires reconnues par (f)lex. Attention, (f)lex fait la diffrence entre
les minuscules et les majuscules.
Figure 4.1: Expressions rgulires (f)lex

Les mta-caractres $,

et / ne peuvent pas apparatre dans des () ni dans des dfinitions

rgulires. A l'intrieur de [], seul


dbut ni la fin.

reste un mta-caractre. Le - ne le reste que s'il n'est ni au

Priorits :
truc|machin* est interprt comme (truc)|(machi(n*))
abc{1,3} est interprt avec lex comme (abc){1,3} et avec flex comme ab(c{1,3})
truc|machin est interprt avec lex comme ( truc)|machin et avec flex comme
(truc|machin)

Avec lex, une rfrence une dfinition rgulire n'est pas considre entre (), en flex si. Par
exemple
Cours de Compilation

13

id
[a-z][a-z0-9A-Z]
%%
truc{id}*

ne reconnat pas "truc" avec lex, mais le reconnat avec flex.

3. Variables et fonctions prdfinies


char yytext[ ] : tableau de caractres qui contient la chane d'entre qui a t accepte.
int yyleng : longueur de cette chane.
int yylex() : fonction qui lance l'analyseur (et appelle yywrap()).
int yywrap() : fonction toujours appele en fin de flot d'entre. Elle ne fait rien par dfaut,
mais l'utilisateur peut la redfinir dans la section des fonctions auxiliaires. yywrap()
retourne 0
si l'analyse doit se poursuivre (sur un autre fichier d'entre) et 1 sinon.
int main() : la fonction main() par dfaut contient juste un appel yylex(). L'utilisateur
peut la redfinir dans la section des fonctions auxiliaires.
unput(char c) : remet le caractre dans le flot d'entre.
int yylineno : numro de la ligne courante (ATTENTION : avec lex uniquement).
yyterminate() : fonction qui stoppe l'analyseur (ATTENTION : avec flex uniquement).

4. Exemples de fichier .l
Ce premier exemple compte le nombre de voyelles, consonnes et caractres de ponctuations
d'un texte entr au clavier.
%{
int nbVoyelles, nbConsonnes, nbPonct;
%}
consonne
[b-df-hj-np-xz]
ponctuation
[,;:?!\.]
%%
[aeiouy]
nbVoyelles++;
{consonne}
nbConsonnes++;
{ponctuation}
nbPonct++;
(.|\n)$
yyterminate();
%%
main(){
nbVoyelles = nbConsonnes = nbPonct = 0;
// printf("Chane analyser: %s", argv);
yylex();
printf("Il y a %d voyelles, %d consonnes et %d signes de
ponctuations.\n",
nbVoyelles, nbConsonnes, nbPonct);
}

5. Exercices
1. Ecrire un programme flex qui compte le nombre de mots d'un texte
2. (commande wc d'Unix) Ecrire un programme flex qui compte le nombre de caractres, de
mots et de lignes du texte source

Cours de Compilation

14

(a) en restreignant l'alphabet aux lettres minuscules et majuscules, aux chiffres et aux
caractres d'espacement.
(b) en tendant l'alphabet tous les symboles.
3. Ecrire un programme flex qui vrifie si une expression est correctement parenthse.
4. Ecrire un programme flex qui remplace dans le texte source toutes les occurrences de bon
par mauvais sauf lorsque c'est un sous mot d'un plus grand mot.
5. Ecrire un programme flex qui supprime dans le texte source tous les mots qui
commencent par une voyelle.

Cours de Compilation

15

CHAPITRE 4.

ANALYSE SYNTAXIQUE

Tout langage de programmation possde des rgles qui indiquent la structure syntaxique
d'un programme bien form. Par exemple, en Pascal, un programme bien form est compos
de blocs, un bloc est form d'instructions, une instruction de ...
La syntaxe d'un langage peut tre dcrite par une grammaire.

L'analyseur syntaxique reoit une suite d'units lexicales de la part de l'analyseur lexical et
doit vrifier que cette suite peut tre engendre par la grammaire du langage.
Le problme est donc

est ce que m appartient au langage gnr par G ?


Le principe est d'essayer de construire un arbre de drivation. Il existe deux mthodes pour
cette construction : mthode (analyse) descendante et mthode (analyse) ascendante.

1. Grammaires et Arbres de drivation


Problme (dj bauch) : tant donn un langage, comment dcrire tous les mots
acceptables ? Comment dcrire un langage ?
On a dj vu les langages rguliers, qui s'expriment l'aide d'expressions rgulires. Mais la
plupart du temps, les langages ne sont pas rguliers et ne peuvent pas s'exprimer sous forme
d'une ER. Par exemple, le langage des systmes de parenthses bien forms ne peut pas
s'exprimer par une ER. On a donc besoin d'un outil plus puissant : les grammaires

2. Grammaires
Exemples (dfinitions informelles de grammaires) :
dans le langage naturel, une phrase est compose d'un sujet suivi d'un verbe suivi d'un
complment (pour simplifier ...).
Par exemple :
L'tudiant subit un cours
On dira donc :
phrase = sujet verbe complment
Ensuite, il faut expliquer ce qu'est un sujet, un verbe, un complment. Par exemple :
sujet = article adjectif nom
| article nom adjectif
| article nom
article = le | la | un | des| l'
adjectif = malin | stupide | couleur
couleur = vert | rouge | jaune

Cours de Compilation

16

ainsi de suite ...


une expression conditionnelle en C est : if ( expression ) instruction
Par exemple :
if ( x<10 ) a=a+b

Il faut encore dfinir ce qu'est une expression et ce qu'est une instruction ...
On distingue les
symboles terminaux : les lettres du langage ( le, la, if ...dans les exemples)
symboles non-terminaux : les symboles qu'il faut encore dfinir (ceux en italique dans les
exemples
prcdents)
Dfinition 5.1 Une grammaire est la donne de G=(VT,VN,S,P) o
VT est un ensemble non vide de symboles terminaux (alphabet terminal)
VN est un ensemble de symboles non-terminaux, avec
S est un symbole initial
appel axiome
P est un ensemble de productions (rgles de rcritures)
Dfinition 5.2 Une rgle de production

prcise que la squence de symboles

) peut tre remplace par la squence de symboles


est appelle partie gauche de la production, et

(
).

partie droite de la production.

Exemple 1 :
symboles terminaux (alphabet) :
symboles non-terminaux :
axiome : S

rgles de production :

qui se rsument en

Exemple 2 : G=<VT,VN,S,P> avec


il, elle, parle, est, devient, court, reste, sympa, vite
PHRASE, PRONOM, VERBE, COMPLEMENT, VERBETAT, VERBACTION
S= PHRASE
PHRASE
Cours de Compilation

PRONOM VERBE COMPLEMENT


17

PRONOM
il | elle
VERBE
VERBETAT | VERBACTION
VERBETAT
est | devient | reste
VERBACTION
parle | court
COMPLEMENT

sympa | vite

Conventions : l'emploi de lettres capitales est rserv pour dnoter des symboles nonterminaux. Les lettres minuscules du dbut de l'alphabet sont utilises pour reprsenter des
symboles terminaux. Les lettres minuscules de la fin de l'alphabet (t, ..., z) sont utilises pour
indiquer une chane de symboles terminaux. Les lettres grecques dnotent des chanes
composes de symboles terminaux et non terminaux.

3. Arbre de drivation
On appelle drivation l'application d'une ou plusieurs rgles partir d'un mot de

On notera

.
une drivation obtenue par application d'une seule rgle de production, et

drivation obtenue par l'application de n rgles de production, o

une

Exemples :
sur la grammaire de l'exemple 1

sur la grammaire de l'exemple 2


PHRASE
PHRASE

SUJET VERBE COMPLEMENT


elle VERBETAT sympa
il parle vite
PHRASE
elle court sympa

Remarque : il est possible de gnrer des phrases syntaxiquement correctes mais qui n'ont pas
de sens. C'est l'analyse smantique qui permettra d'liminer ce problme.
Dfinition 5.3 Etant donne une grammaire G, on note L(G) le langage gnr par G et
dfini par

(c'est dire tous les mots composs uniquement de symboles terminaux (de lettres de
l'alphabet) que l'on peut former partir de S).
L'exemple 1 nous donne
Dfinition 5.4 On appelle arbre de drivation (ou arbre syntaxique) tout arbre tel que
- la racine est l'axiome
- les feuilles sont des units lexicales ou
Cours de Compilation

18

les noeuds sont des symboles non-terminaux

les fils d'un noeud


production

sont

si et seulement si

est une

(Le parcours prfixe de l'arbre donne le mot)


Exemple : Soit la grammaire ayant S pour axiome et pour rgles de production

Un arbre de drivation pour le mot accacbb est :

(drivations gauches)
ou
(drivations
droites)
Ces deux suites diffrentes de drivations donnent le mme arbre de drivation.
Dfinition 5.5 Une grammaire est dite ambigu s'il existe un mot de L(G) ayant plusieurs
arbres syntaxiques.
Remarque : la grammaire prcdente n'est pas ambigu.
Exemple de grammaire ambigu :

Soit G donne par


Cette grammaire est ambigu car le mot m= if (x>10 ) if (y<0) a=1 else a=0
possde deux arbres syntaxiques diffrents :

Cours de Compilation

19

car il y a deux interprtations syntaxiques possibles :


if (x>10)
if (y<0)
a=1
else
a=0
// finsi
// finsi

if (x>10)
if (y<0)
a=1
// finsi
else
a=0
// finsi

4. Mise en oeuvre d'un analyseur syntaxique


L'analyseur syntaxique reoit une suite d'units lexicales (de symboles terminaux) de la part
de l'analyseur lexical. Il doit dire si cette suite (ce mot) est syntaxiquement correct, c'est dire
si c'est un mot du langage gnr par la grammaire qu'il possde. Il doit donc essayer de
construire l'arbre de drivation de ce mot. S'il y arrive, alors le mot est syntaxiquement
correct, sinon il est incorrect.
Il existe deux approches (deux mthodes) pour construire cet arbre de drivation : une
mthode descendante et une mthode ascendante.
4.1 Analyse descendante
Principe : construire l'arbre de drivation du haut (la racine, c'est dire l'axiome de dpart)
vers le bas (les feuilles, c'est dire les units lexicales).
4.1.1 Exemples

Exemple 1 :

avec le mot w=accbbadbc


On part avec l'arbre contenant le seul sommet S
La lecture de la premire lettre du mot (a) nous permet d'avancer la construction

Cours de Compilation

20

puis la deuxime lettre nous amne

et ainsi de suite jusqu'

On a trouv un arbre de drivation, donc le mot appartient au langage.


Sur cet exemple, c'est trs facile parce que chaque rgle commence par un terminal diffrent,
donc on sait immdiatement laquelle prendre.
Exemple 2 :

avec le mot w=acb


On se retrouve avec

En lisant le c, on ne sait pas s'il faut prendre la rgle


ou la rgle
. Pour le
savoir, il faut lire aussi la lettre suivante (b). Ou alors, il faut se donner la possibilit de faire
des retour en arrire : on essaye la 1re rgle (
), on aboutit un chec, alors on
retourne en arrire et on essaye la deuxime rgle et l a marche.
Exemple 3 :

Cours de Compilation

avec le mot w=aaaaaaadbbcbbbc

21

Pour savoir quelle rgle utiliser, il faut cette fois-ci connatre aussi la dernire lettre du mot.
Exemple 4 : grammaire des expressions arithmtiques

avec le mot w=3*4+10*(5+11)


Pfff ... Alors l on ne voit plus rien du tout !!
Conclusion : ce qui serait pratique, a serait d'avoir une table qui nous dit : quand je lis tel
caractre et que j'en suis driver tel symbole non-terminal, alors j'applique telle rgle et je ne
me pose pas de questions. a existe, et a s'appelle une table d'analyse.
4.2 Table d'analyse LL(1)
Pour construire une table d'analyse, on a besoin des ensembles PREMIER et SUIVANT
4.2.1 Calcul de PREMIER

Pour toute chane compose de symboles terminaux et non-terminaux, on cherche


PREMIER( ) : l'ensemble de tous les terminaux qui peuvent commencer une chane qui se
drive de
C'est dire que l'on cherche toutes les lettres a telles qu'il existe une drivation
tant une chane quelconque compose de symboles terminaux et non-terminaux).

Exemple :

, donc

PREMIER(S)

, donc

PREMIER(S)

, donc

PREMIER(S)

, donc
PREMIER(S)
Il n'y a pas de drivation
Donc PREMIER(S)

Algorithme de construction des ensembles PREMIER :


Cours de Compilation

22

1. Si X est un non-terminal et
est une production de la grammaire (avec Yi
symbole terminal ou non-terminal) alors
ajouter les lments de PREMIER(Y1) sauf dans PREMIER(X)
s'il existe j (
) tel que pour tout
on a
alors ajouter les lments de PREMIER(Yj) sauf dans PREMIER(X)
si pour tout
2. Si X est un non-terminal et

PREMIER(Yi),

PREMIER(Yi), alors ajouter dans PREMIER(X)


est une production, ajouter dans PREMIER(X)

3. Si X est un terminal, PREMIER(


.
4. Recommencer jusqu' ce qu'on n'ajoute rien de nouveau dans les ensembles PREMIER.
Exemple 1 :

Exemple 2 :

4.2.2 Calcul de SUIVANT

Pour tout non-terminal A, on cherche SUIVANT(A) : l'ensemble de tous les symboles


terminaux a qui peuvent apparatre immdiatement droite de A dans une drivation :

Cours de Compilation

23

Exemple :

SUIVANT(S) car il y a les drivations

et

...

Algorithme de construction des ensembles SUIVANT :


1. Ajouter un marqueur de fin de chane (symbole $ par exemple) SUIVANT(S) (o S est
l'axiome de dpart de la grammaire)
2. S'il y a une production

o B est un non-terminal, alors

ajouter le contenu de PREMIER( ) SUIVANT(B), sauf


3. S'il y a une production
, alors ajouter SUIVANT(A) SUIVANT(B)
4. S'il y a une production
avec
PREMIER
, alors
ajouter SUIVANT(A) SUIVANT(B)
5 - Recommencer partir de l'tape 3 jusqu' ce qu'on n'ajoute rien de nouveau dans les
ensembles SUIVANT.

Exemple 1 :

Exemple 2 :
S -> aSb | cd | SAe
A ->aAdB |
B -> bb
PREMIER SUIVANT
S ac

$bae

Cours de Compilation

24

A a

ed

B B

ed

4.2.3 Construction de la table d'analyse

Une table d'analyse est un tableau M deux dimensions qui indique pour chaque symbole
non-terminal A et chaque symbole terminal a ou symbole $ la rgle de production appliquer.

Pour chaque production

faire

1. pour tout a PREMIER( ) (et


M[A,a]

), rajouter la production

dans la case

2. si PREMIER( ), alors pour chaque


SUIVANT(A) ajouter
Chaque case M[A,a] vide est une erreur de syntaxe

dans M[A,b]

Avec l'exemple 1, on obtient la table


Nb

E
E'
T
T'
F

nb

4.2.4 Analyseur syntaxique

Maintenant qu'on a la table, comment l'utiliser pour dterminer si un mot m donn est tel que
?
On utilise une pile.
Algorithme :
donnes : mot m, table d'analyse M
initialisation de la pile :
S
$

Cours de Compilation

25

et un pointeur ps sur la 1re lettre de m


repeter
Soit X le symbole en sommet de pile
Soit a la lettre pointe par ps
Si X est un non terminal alors
Si

alors
enlever X de la pile
mettre Yn puis Yn-1 puis ...puis Y1 dans la pile

mettre en sortie la production


sinon
ERREUR
finsi
Sinon
Si X=$ alors
Si a=$ alors ACCEPTER
Sinon ERREUR
Finsi
Sinon
Si X=a alors
enlever X de la pile
avancer ps
Sinon
ERREUR
finsi
finsi
finsi
jusqu' ERREUR ou ACCEPTER
Sur notre exemple (grammaire E,E',T,T',F), soit le mot
m=3+4*5
PILE

Entre Sortie

$E

3+4*5$

$ E'T

3+4*5$

$ E'T'F

3+4*5$

$ E'T'3

3+4*5$

$ E'T'

+4*5$

$ E'

+4*5$

$ E'T+

+4*5$

$ E'T

4*5$

$ E'T'F

4*5$

Cours de Compilation

nb

nb
26

$ E'T'4

4*5$

$ E'T'

*5$

$ E'T'F*

*5$

$ E'T'F

5$

$ E'T'5

5$

$ E'T'

$ E'

$ analyse syntaxique russie

nb

On obtient donc l'arbre syntaxique de la figure 5.1.


Figure 5.1: Arbre syntaxique pour 3+4*5

Si l'on essaye d'analyser maintenant le mot


m=(7+3)5
PILE

Entre Sortie

$E

(7+3)5$

$ E'T

(7+3)5$

$ E'T'F

(7+3)5$

$ E'T') E (

(7+3)5$

$ E'T') E

7+3)5$

$ E'T')E'T

7+3)5$

$ E'T')E'T'F 7+3)5$

Cours de Compilation

27

$ E'T')E'T'7

7+3)5$

$ E'T')E'T'

+3)5$

$ E'T')E'

+3)5$

$ E'T')E'T+

+3)5$

$ E'T')E'T

3)5$

$ E'T')E'T'F

3)5$

$ E'T')E'T'3

3)5$

$ E'T')E'T'

)5$

$ E'T')E'

)5$

$ E'T')

)5$

$ E'T'

* T E'

5$ ERREUR !!

Donc ce mot n'appartient pas au langage gnr par cette grammaire.


4.3 Grammaire LL(1)
L'algorithme prcdent ne peut pas tre appliqu toutes les grammaires. En effet, si la table
d'analyse comporte des entres multiples (plusieurs productions pour une mme case M[A,a]),
on ne pourra pas faire une telle analyse descendante car on ne pourra pas savoir quelle
production appliquer.
Dfinition 5.6 On appelle grammaire LL(1) une grammaire pour laquelle la table d'analyse
dcrite prcdemment n'a aucune case dfinie de faon multiple.
Le terme "LL(1)" signifie que l'on parcourt l'entre de gauche droite (L pour Left to right
scanning), que l'on utilise les drivations gauches (L pour Leftmost derivation), et qu'un seul
symbole de pr-vision est ncessaire chaque tape ncessitant la prise d'une dcision
d'action d'analyse (1).
Par exemple, nous avons dja vu la grammaire

Nous avons PREMIER


SUIVANT
A

, PREMIER

, SUIVANT

et

, ce qui donne la table d'analyse


c

b d $

S
A

Cours de Compilation

28

Il y a deux rductions pour la case M[A,c], donc ce n'est pas une grammaire LL(1). On ne peut
pas utiliser cette mthode d'analyse. En effet, on l'a dja remarqu, pour pouvoir choisir entre
la production
et la production
, il faut lire la lettre qui suit celle que l'on
pointe (donc deux symboles de pr-vision sont ncessaires)5.1.
Autre exemple :

n'est pas LL(1)


Thorme 5.7 Une grammaire ambigu ou rcursive gauche ou non factorise gauche
n'est pas LL(1)
"Ambigu ", on a dj vu, on sait ce que a veut dire. Voyons les autres termes
4.3.1 Rcursivit gauche

Dfinition 5.8 Une grammaire est immdiatement rcursive gauche si elle contient un
non-terminal A tel qu'il existe une production
o est une chane quelconque.
Exemple :

Cette grammaire contient plusieurs rcursivits gauche immdiates.


Elimination de la rcursivit gauche immdiate :

Remplacer toute rgle de la forme

par les deux rgles :

Thorme 5.9 La grammaire ainsi obtenue reconnat le mme langage que la grammaire
initiale.
Sur l'exemple, on obtient :

Cette grammaire reconnat le mme langage que la premire.


Par exemple, le mot dbbcaa s'obtient partir de la premire grammaire par les drivations
.
Il s'obtient partir de la deuxime grammaire par

Cours de Compilation

29

.
Remarque : ici on peut se passer de A'.
C'est dire en fait que
est quivalent
(qui, elle, n'est pas
immdiatement rcursive gauche, mais droite, ce qui n'a rien voir).
Dfinition 5.10 Une grammaire est rcursive gauche si elle contient un non-terminal A tel
qu'il existe une drivation

est une chane quelconque.

Exemple :
Le non-terminal S est rcursif gauche car
rcursif gauche).

(mais il n'est pas immdiatement

Elimination de la rcursivit gauche :

Ordonner les non-terminaux


Pour i=1 n faire
pour j=1 i-1 faire
remplacer chaque production de la forme

par

fin pour
liminer les rcursivits gauche immdiates des productions Ai
fin pour
Thorme 5.11 La grammaire ainsi obtenue reconnat le mme langage que la grammaire
initiale.
Sur l'exemple :
On ordonne S,A
i=1 pas de rcursivit immdiate dans
i=2 et j=1 on obtient
on limine la rec. immdiate :

Bref, on a obtenu la grammaire :

Cours de Compilation

30

Autre exemple :
S->Sa | TSc | d
T->TbT|
On obtient la grammaire :
S -> TScS' | dS'
S'->aS'|
T->T'
T'->bTT'|
Or on a S -> TScS' -> T'ScS' -> ScS' damned une rcursivit gauche !!!! Eh oui, l'algo ne
marche pas toujours lorsque la grammaire possde une rgle A->
4.3.2 Grammaire propre

Dfinition 5.12 Une grammaire est dite propre si elle ne contient aucune production A->
Comment rendre une grammaire propre ? En rajoutant une production dans laquelle le A est
remplac par , ceci pour chaque A apparaissant en partie droite d'une production, et pour
chaque A d'un A ->
Exemple :
S -> a Tb|aU
T -> bTaTA|
U -> a U | b
devient
S -> a Tb|ab|aU
T -> bTaTA|baTA|bTaA|baA
U -> a U | b
4.3.3 Factorisation gauche

L'ide de base est que pour dvelopper un non-terminal A quand il n'est pas vident de choisir
l'alternative utiliser (ie quelle production prendre), on doit rcrire les productions de A de
faon diffrer la dcision jusqu' ce que suffisamment de texte ait t lu pour faire le bon
choix.

Exemple :
Au dpart, pour savoir s'il faut choisir
ou
, il faut avoir lu la
5ime lettre du mot (un a ou un c). On ne peut donc pas ds le dpart savoir quelle production
prendre. Ce qui est incompatible avec une grammaire LL(1). (Remarque : mais pas avec une
grammaire LL(5), mais ce n'est pas notre problme.)
Factorisation gauche :
Pour chaque non-terminal A
trouver le plus long prfixe

Cours de Compilation

commun deux de ses alternatives ou plus

31

Si
, remplacer
pas par ) par les deux rgles

(o les

ne commencent

finpour
Recommencer jusqu' ne plus en trouver.

Exemple :

Factorise gauche, cette grammaire devient :


4.3.4 Conclusion

Si notre grammaire est LL(1), l'analyse syntaxique peut se faire par l'analyse descendante vue
ci-dessus. Mais comment savoir que notre grammaire est LL(1) ? Etant donne une
grammaire

1. la rendre non ambigu. Il n'y a pas de mthodes. Une grammaire ambigu


est une grammaire qui a t mal conue.
2. liminer la rcursivit gauche si ncessaire
3. la factoriser gauche si ncessaire
4. construire la table d'analyse
Il ne reste plus qu'a esprer que a soit LL(1). Sinon, il faut concevoir une autre mthode pour
l'analyse syntaxique.
Exemple : grammaire des expressions arithmtiques avec les oprateurs + et *
. Mais elle est ambigu. Pour lever l'ambigut, on considre les
priorits classiques des oprateurs et on obtient la grammaire non ambigu :

Aprs suppression de la rcursivit gauche, on obtient

Cours de Compilation

32

Inutile de factoriser gauche.


Cette grammaire est LL(1) (c'est l'exemple que l'on a utilis tout le temps).
Autre exemple : la grammaire

n'est pas LL(1). Or elle n'est pas rcursive gauche, elle est factorise
gauche et elle n'est pas ambige !

5. Analyse ascendante
Principe : construire un arbre de drivation du bas (les feuilles, ie les units lexicales) vers le
haut (la racine, ie l'axiome de dpart).
Le modle gnral utilis est le modle par dcallages-rductions. C'est dire que l'on ne
s'autorise que deux oprations :

dcalage (shift) : dcaler d'une lettre le pointeur sur le mot en entre


rduction (reduce) : rduire une chane (suite conscutive de terminaux et non
terminaux gauche du pointeur sur le mot en entre et finissant sur ce pointeur) par un
non-terminal en utilisant une des rgles de production

Exemple :

avec le mot u=aacbaacbcbcbcbacbc


on ne peut rien rduire, donc on dcale
on ne peut rien rduire, donc on dcale
on ne peut rien rduire, donc on dcale
ah ! On peut rduire par
on ne peut rien rduire, donc on dcale

...
on peut utiliser
on ne peut rien rduire, donc on dcale
on ne peut rien rduire, donc on dcale
On peut utiliser
On peut utiliser

Cours de Compilation

33

dcalage
dcalage
rduction par
rduction par
rduction par
dcalage
dcalage
rduction par
rduction par
dcalage
dcalage
dcalage
rduction par
dcalage
dcalage
rduction par
rduction par
rduction par
termin !!!! On a gagn, le mot est bien dans le langage.
En mme temps, on a construit l'arbre (figure 5.2).
Figure 5.2: Analyse ascendante

Cours de Compilation

34

Conclusion : a serait bien d'avoir une table qui nous dit si on dcale ou si on rduit, et par
quoi, lorsque le pointeur est sur une lettre donne.
Table d'analyse LR
(on l'appelle comme a parce que, de mme que la mthode descendante vue prcdemment
ne permettait d'analyser que les grammaires LL(1), cette mthode va permettre d'analyser les
grammaires dites LR).
En fait, ce n'est pas vraiment un tableau, c'est plutt une sorte d'automate, qu'on appelle
automate pile. Cette table va nous dire ce qu'il faut faire quand on lit une lettre a et qu'on est
dans un tat i
- soit on dcale Dans ce cas, on empile la lettre lue et on va dans un autre tat j. Ce qui sera
not dj
- soit on rduit par la rgle de production numro p, c'est dire qu'on remplace la chane en
sommet de pile (qui correspond la partie droite de la rgle numro p) par le non-terminal de
la partie gauche de la rgle de production, et on va dans l'tat j qui dpend du non-terminal en
question. On note a rp
- soit on accepte le mot. Ce qui sera not ACC
- soit c'est une erreur. Case vide
Construction de la table d'analyse : utilise aussi les ensembles SUIVANT ( et donc
PREMIER), plus ce qu'on appelle des fermetures de 0-items. Un 0-item (ou plus simplement
item) est une production de la grammaire avec un "." quelque part dans la partie droite. Par
exemple (sur la gram ETF) : E -> E . + T ou encore T-> F. ou encore F -> . ( E )
Fermeture d'un ensemble d'items I :
1- Mettre chaque item de I dans Fermeture(I)
2- Pour chaque item i de Fermeture(I) de la forme

.B

Pour chaque production B


rajouter l'item B . dans Fermeture(I)
3- Recommencer 2 jusqu' ce qu'on n'ajoute rien de nouveau

Cours de Compilation

35

Exemple : soit la grammaire des expressions arithmtiques

et soit l'ensemble d'items { T T * . F, E


{T T*.F, E E.+T, F .nb, F .(E)}

E.+T}. La fermeture de cet ensemble d'items est :

Transition par X d'un ensemble d'items I :

(I,X)=Fermeture ( tous les items

X.

Sur l'exemple ETF : soit I={T T*.F, E E.+T, F


(I,F) = { T T*F.}
(I,+) = { E
E+.T, T .T*F, T .F, F .nb,F

.X
.nb, F

est dans I)
.(E)} on aura

.(E)}

Collection des items d'une grammaire :


0- Rajouter l'axiome S' avec la production S' S
1- Mettre dans l'item I0 la Fermeture de S'
.S})
2- Mettre I0 dans Collection
3 - Pour chaque I dans Collection faire
Pour chaque X tel que (I, X) est non vide
ajouter ce (I, X) dans Collection
Fin pour
4 - Recommencer 3 jusqu' ce qu'on n'ajoute rien de nouveau
Construction de la table d'analyse SLR :
1- Construire la collection d'items {I0, ... In}
2- l'tat i est contruit partir de Ii :
a) pour chaque (Ii , a) = Ij : mettre dcalage par j dans la case M[i,a]
b) pour chaque (Ii , A) = Ij : mettre aller j dans la case M[i,A]
c) pour chaque
. contenu dans I i :
pour chaque a de SUIVANT(A) faire
mettre reduction par numro (de la rgle
) dans la case M[i,a]
Avec notre exemple ETF, on obtient la table d'analyse LR
tat nb +
0

d5

d4

1 2 3

d6

r2 d7

r2

r2

r4 r4

r4

r4

d5

E T F

ACC

d4

Cours de Compilation

8 2 3

36

r6 r6

r6

r6

d5

d4

9 3

d5

d4

10

d6

d11

r1 d7

r1

r1

10

r3 r3

r3

r3

11

r5 r5

r5

r5

Analyseur syntaxique SLR : On part dans l'tat 0, et on empile et dpile non seulement les
symboles (comme lors de l'analyseur LL) mais aussi les tats successifs.
Exemple : l'analyse du mot
est donne figure 5.3. La figure 5.4 donne l'analyse
du mot 3+4*2$. On peut aussi dessiner l'arbre obtenu (figure 5.5)

Figure: Analyse LR du mot

Figure: Analyse LR du mot

Cours de Compilation

37

Figure 5.5: arbre de drivation du mot 3+4*2

Remarques :

cette mthode permet d'analyser plus de grammaires que la mthode descendante (car
il y a plus de grammaires SLR que LL)

Cours de Compilation

38

en TP on va utiliser un outil (bison) qui construit tout seul une table d'analyse LR
(LALR en fait, mais c'est presque pareil) partir d'une grammaire donne
dans cette mthode d'analyse, a n'a strictement aucune importance que la grammaire
soit rcursive gauche, mme au contraire, on prfre.
Les grammaires ambiges provoquent des conflits
o conflit dcalage/rduction : on ne peut pas dcider la lecture du terminal a s'il
faut rduire une production
ou dcaler le terminal
o conflit rduction/rduction : on ne peut pas dcider la lecture du terminal a
s'il faut rduire une production

ou une production

On doit alors rsoudre les conflits en donnant des priorits aux actions (dcaler ou
rduire) et aux productions.
Par exemple, soit la grammaire
Soit analyser 3+4+5. Lorsqu'on lit le 2ime + on a le choix entre
o

rduire ce qu'on a dj lu par


. Ce qui nous donnera finalement le
calcul (3+4)+5
o dcaler ce +, ce qui nous donnera finalement le calcul 3+(4+5).
Ici on s'en cague car c'est pareil. Mais bon, + est associatif gauche, donc on prfrera
rduire. Soit analyser 3+4*5. Lorsqu'on lit le * on a encore un choix shift/reduce. Si
l'on rduit on calcule (3+4)*5, si on dcale on calcule 3+(4*5) ! On ne peut plus s'en
foutre ! Il faut dcaler
Soit analyser 3*4+5. On ne s'en fout pas non plus, il faut rduire !
Bref, il faut mettre quelque part dans l'analyseur le fait que * est prioritaire sur +.

6. Erreurs syntaxiques
Beaucoup d'erreurs sont par nature syntaxiques (ou rvles lorsque les units lexicales
provenant de l'analyseur lexical contredisent les rgles grammaticales). Le gestionnaire
d'erreur doit
- indiquer la prsence de l'erreur de faon claire et prcise
- traiter l'erreur rapidement pour continuer l'analyse
- traiter l'erreur le plus efficacement possible de manire ne pas en crer de nouvelles.
Heureusement, les erreurs communes (confusion entre deux sparateurs (par exemple entre ;
et ,), oubli de ;, ...) sont simples et un mcanisme simple de traitement suffit en gnral.
Cependant, une erreur peut se produire longtemps avant d'tre dtecte (par exemple l'oubli
d'un { ou } dans un programme C). La nature de l'erreur est alors trs difficile dduire. La
plupart du temps, le gestionnaire d'erreurs doit deviner ce que le programmeur avait en tte.
Lorsque le nombre d'erreur devient trop important, il est plus raisonnable de stopper
l'analyse5.4.
Il existe plusieurs stratgies de rcupration sur erreur : mode panique, au niveau du
syntagme5.5, productions d'erreur, correction globale. Une rcupration inadquate peut
provoquer une avalanche nfastes d'erreurs illgitimes, c'est dire d'erreurs qui n'ont pas t
faites par le programmeur mais sont la consquence du changement d'tat de l'analyseur lors
de la rcupration sur erreur. Ces erreurs illgitimes peuvent tre syntaxiques mais galement

Cours de Compilation

39

smantiques. Par exemple, pour se rcuprer d'une erreur, l'analyseur syntaxique peut sauter
la dclaration d'une variable. Lors de l'utilisation de cette variable, l'analyseur smantique
indiquera qu'elle n'a pas t dclare.
6.1 Rcupration en mode panique
C'est la mthode la plus simple implanter. Quand il dcouvre une erreur, l'analyseur
syntaxique limine les symboles d'entre les uns aprs les autres jusqu' en rencontrer un qui
appartienne un ensemble d'units lexicales de synchronisation, c'est dire (par exemple) les
dlimiteurs (;, end ou }), dont le rle dans un programme source est clair.
Bien que cette mthode saute en gnral une partie considrable du texte source sans en
vrifier la validit, elle a l'avantage de la simplicit et ne peut pas entrer dans une boucle
infinie.
6.2 Rcupration au niveau du syntagme
Quand une erreur est dcouverte, l'analyseur syntaxique peut effectuer des corrections locales.
Par exemple, remplacer une , par un ;, un wihle par un while, insrer un ; ou une (, ...Le
choix de la modification faire n'est pas vident du tout du tout en gnral. En outre, il faut
faire attention ne pas faire de modifications qui entranerait une boucle infinie (par exemple
dcider d'insrer systmatiquement un symbole juste avant le symbole courant).
L'inconvnient majeure de cette mthode est qu'il est pratiquement impossible de grer les
situations dans lesquelles l'erreur relle s'est produite bien avant le point de dtection.
On implante cette rcupration sur erreur en remplissant les cases vides des tables d'analyse
par des pointeurs vers des routines d'erreur. Ces routines remplacent, insrent ou suppriment
des symboles d'entre et mettent les messages appropris.
Exemple : grammaire des expressions arithmtiques

La table d'analyse LR avec routines d'erreur est


tat nb +

e1

d3 e1 e1 d2 e2

e3 d4 d5 e3 e2 ACC

d3 e1 e1 d2 e2

e1

r4 r4 r4 r4 r4

r4

d3 e1 e1 d2 e2

e1

d3 e1 e1 d2 e2

e1

e3 d4 d5 e3 d9

e4

r1 r1 d5 r1 r1

r1

r2 r2 r2 r2 r2

r2

r3 r3 r3 r3 r3

r3

Cours de Compilation

40

Les routines d'erreur tant :


e1 :
(routine appele depuis les tats 0, 2, 4 et 5 lorsque l'on rencontre un oprateur ou la
fin de chane d'entre alors qu'on attend un oprande ou une parenthse ouvrante)
Emettre le diagnostic operande manquant
Empiler un nombre quelconque et aller dans l'tat 3
e2 :
(routine appele depuis les tats 0,1,2,4 et 5 la vue d'une parenthse fermante)
Emettre le diagnostic parenthse fermante excdentaire
Ignorer cette parenthse fermante
e3 :
(routine appele depuis les tats 1 ou 6 lorsque l'on rencontre un nombre ou une
parenthse fermante alors que l'on attend un oprateur)
Emettre le diagnostic oprateur manquant
Empiler + (par exemple) et aller l'tat 4
e4 :
(routine appele depuis l'tat 6 qui attend un oprateur ou une parenthse fermante
lorsque l'on rencontre la fin de chane)
Emettre le diagnostic parenthse fermante oublie
Empiler une parenthse fermante et aller l'tat 9
6.3 Productions d'erreur
Si l'on a une ide assez prcise des erreurs courantes qui peuvent tre rencontres, il est
possible d'augmenter la grammaire du langage avec des productions qui engendrent les
constructions errones.
Par exemple (pour un compilateur C) :
if E I

(erreur : il manque les parenthses)

if ( E ) then I (erreur : il n'y a pas de then en C)

6.4 Correction globale


Dans l'idal, il est souhaitable que le compilateur effectue aussi peu de changements que
possible. Il existe des algorithmes qui permettent de choisir une squence minimale de
changements correspondant globalement au cot de correction le plus faible.
Malheureusement, ces mthodes sont trop coteuses en temps et en espace pour tre
implantes en pratique et ont donc uniquement un intrt thorique. En outre, le programme
correct le plus proche n'est pas forcment celui que le programmeur avait en tte ...

Cours de Compilation

41

CHAPITRE 5.

TRADUCTION DIRIGEE PAR LA


SYNTAXE

Un schma de traduction dirige par la syntaxe (TDS) est un formalisme permettant d'associer
des actions une production d'une rgle de grammaire.

1. Dfinition dirige par la syntaxe


Chaque symbole de la grammaire (terminal ou non) possde un ensemble d'attributs .
Chaque rgle de production de la grammaire possde un ensemble de rgles smantiques
qui permettent
de calculer la valeur des attributs associs aux symboles apparaissant dans la production.

Une rgle smantique est une suite d'instructions algorithmiques : elle peut contenir des
affectations, des si-sinon, des instructions d'affichage, ...
Dfinition 6.1 On appelle dfinition dirige par la syntaxe (DDS), la donne d'une
grammaire et de son ensemble de rgles smantiques.
On notera X.a l'attribut a du symbole X. S'il y a plusieurs symboles X dans une production, on
, et X(0) s'il est en partie gauche.

les notera

Exemple : Grammaire
attributs : nba (calcul du nombre de a), nbc (du nombre de c)
une DDS permettant de calculer ces attributs est alors :
production

Rgle smantique
S(0).nba:=S(1).nba+1
S(0).nbc:=S(1).nbc
S(0).nba:=S(1).nba+1
S(0).nbc:=S(1).nbc
S(0).nba:=S(1).nba+S(2).nba+1
S(0).nbc:=S(1).nbc+S(2).nbc+2
S(0).nba:=0
S(0).nbc:=0

2. Arbre syntaxique dcor


Dfinition 6.2 On appelle arbre syntaxique dcor un arbre syntaxique sur les noeuds
duquel on rajoute la valeur de chaque attribut.

Cours de Compilation

42

Exemple : la figure 6.1 donne l'arbre syntaxique dcor pour le mot acaacabb et la DDS
dfinie prcdemment.
Figure 6.1: Calcul du nombre de a et c pour
acaacabb

3. Attributs synthtiss et hrits


On distingue deux types d'attributs : les synthtiss et les hrits.
3.1 Attributs synthtiss
Un attribut est dit synthtis lorsqu'il est calcul pour le non terminal de la partie gauche en
fonction des attributs des non terminaux de la partie droite.
Sur l'arbre dcor : la valeur d'un attribut en un noeud se calcule en fonction des attributs de
ses fils. C'est dire que le le calcul de l'attribut se fait des feuilles vers la racine.
Les attributs synthtiss peuvent tre facilement valus lors d'une analyse ascendante (donc
par exemple avec yacc/bison). Mais pas du tout lors d'une analyse descendante.
Dans l'exemple donn, nba et nbc sont des attributs synthtiss.
3.2 Attributs hrits
Un attribut est dit hrit lorsqu'il est calcul partir des attributs du non terminal de la partie
gauche, et ventuellement des attributs d'autres non terminaux de la partie droite.
Sur l'arbre dcor : la valeur d'un attribut un noeud se calcule en fonction des attributs des
frres et du pre. C'est dire que le calcul de l'attribut se fait de la racine vers les feuilles.
Si les attributs d'un noeud donn ne dpendent pas des attributs de ses frres droits, alors les
attributs hrits peuvent tre facilement valus lors d'une analyse descendante, mais pas lors
d'une analyse ascendante.
Remarque : l'axiome de la grammaire doit tre de la forme
pour pouvoir initialiser le calcul.

(un seul non-terminal)

Figure 6.2: Niveau d'imbrication des )

Cours de Compilation

43

Exemple : calcul du niveau d'imbrication des ) dans un systme de parenthses bien form.

Grammaire :
DDS :
production action smantique
S.nb:=0
S(1).nb:=S(0).nb+1
S(2).nb:=S(0).nb
crire S.nb
La figure 6.2 donne l'arbre dcor pour le mot (())(()())()

4. Graphe de dpendances
Une DDS peut utiliser la fois des attributs synthtiss et des attributs hrits. Il n'est alors
pas forcmment vident de s'y retrouver pour calculer ces attributs. L'attribut machin dpend
de l'attribut bidule qui lui-mme dpend de l'attribut truc ...Il faut tablir un ordre
d'valuation des rgles smantiques. On construira ce qu'on appelle un graphe de
dpendances.
Exemple : Soit la DDS suivante (totalement stupide) qui fait intervenir deux attributs hrits
h et r et deux attributs synthtiss s et t.
production

action smantique

Cours de Compilation

44

E.h:=2*E.s+1
S.r:=E.t
E(0).s:=E(1).s+E(2).s
E(0).t:=3*E(1).t-E(2).t
E(1).h:=E(0).h
E(2).h:=E(0).h+4
nombre E.s:= nombre
E.t:=E.h +1
La figure 6.3 donne un exemple d'arbre dcor.
Figure 6.3: Attributs hrits et synthtiss

Dfinition 6.3 On appelle graphe de dpendances le graphe orient reprsentant les


interdpendances entre les divers attributs. Le graphe a pour sommet chaque attribut. Il y a un
arc de a b ssi le calcul de b dpend de a.
On construit le graphe de dpendances pour chaque rgle de production, ou bien directement
le graphe de dpendances d'un arbre syntaxique donn. C'est ce dernier qui permet de voir
dans quel ordre valuer les attributs. Mais il est toujours utile d'avoir le graphe de
dpendances pour chaque rgle de production afin d'obtenir "facilement" le graphe pour
n'importe quel arbre syntaxique.
Figure 6.4: Graphes de dpendances pour les productions

Figure 6.5: Graphe de dpendances de l'arbre


syntaxique

Cours de Compilation

45

:
Sur l'exemple : La figure 6.4 donne le graphe de dpendances pour chaque rgle de
production, et la figure 6.5 le graphe de dpendance de l'arbre syntaxique.
Remarque : si le graphe de dpendance contient un cycle, l'valuation des attributs est alors
impossible.
Exemple : Soit la DDS suivante (a hrit et b synthtis)
production action smantique
S.a:=S.b
S(0).b:=S(1).b+S(2).b+T.b
S(1).a:=S(0).a
S(2).a:=S(0).a+1
T.a:=S(0).a+S(0).b+S(1).a
T(0).b:=P.a+P.b
P.a:=T(0).a+3
T(1).a:=...

Le graphe de dpendance associ peut contenir un cycle (voir figure 6.6), le calcul est donc
impossible.
Figure 6.6: Cycle dans le graphe de
dpendance

Cours de Compilation

46

5. Evaluation des attributs


5.1 Aprs l'analyse syntaxique
On peut faire le calcul des attributs indpendamment de l'analyse syntaxique : lors de
l'analyse syntaxique, on construit (en dur) l'arbre syntaxique6.1, puis ensuite, lorsque
l'analyse syntaxique est termine, le calcul des attributs s'effectue sur cet arbre par des
parcours de cet arbre (avec des aller-retours dans l'arbre suivant l'ordre d'valuation des
attributs).
Cette mthode est trs coteuse en mmoire (stockage de l'arbre). Mais l'avantage est que
l'on n'est pas dpendant de l'ordre de visite des sommets de l'arbre syntaxique impos par
l'analyse syntaxique (une analyse descendante impose un parcours en profondeur du haut
vers le bas, de la gauche vers la droite, une analyse ascendante un parcours du bas vers le
haut, ...). On peut galement construire l'arbre en dur pour une sous-partie seulement du
langage.

5.2 Pendant l'analyse syntaxique


On peut valuer les attributs en mme tant que l'on effectue l'analyse syntaxique. Dans ce
cas, on utilisera une pile pour conserver les valeurs des attributs, cette pile pouvant tre la
mme que celle de l'analyseur syntaxique, ou une autre.
Cette fois-ci, l'ordre d'valuation des attributs est tributaire de l'ordre dans lequel les noeuds
de l'arbre syntaxique sont "cres"6.2 par la mthode d'analyse.
Exemple avec un attribut synthtis : valuation d'une expression arithmtique avec une
analyse ascendante.
Une TDS est

production

action smantique

traduction avec une pile

E(0).val:=E(1).val+T.val tmpT = depiler()


tmpE = depiler()
empiler(tmpE+tmpT)
E(0).val:=E(1).val-T.val tmpT = depiler()
tmpE = depiler()
empiler(tmpE-tmpT)
E.val:=T.val

Cours de Compilation

47

T(0).val:=T(1).val*F.val tmpF = depiler()


tmpT = depiler()
empiler(tmpT*tmpF)
T.val:=F.val
F.val:=E.val
nb

F.val:=nb

empiler(nb)

L'analyse syntaxique s'effectue partir de la table d'analyse LR donne. Par exemple, pour le
mot 2*(10+3) on obtiendra :

Pile

entre action

pile des attributs

2*(10+3)$ d5

$
$

*(10+3)$ r6 :

*(10+3)$ r4 :

*(10+3)$ d7

(10+3)$ d4

10+3)$ d5

10

+3)$ r6 :

+3)$ r4 :

2 10

+3)$ r2 :

2 10

+3)$ d6

2 10

3)$ d5

2 10

)$ r6 :

)$ r4 :

2 10 3

)$ r1 :

2 13

)$ d11

2 13

$
$

nb

nb

nb

r5 :

2 10

2 10 3

2 13

$ r3 :

26

$ r2 :

26

$ ACCEPT !!!

26

Cours de Compilation

48

Exemple avec un attribut hrit : reprenons l'attribut hrit qui calcule le niveau
d'imbrication des ) dans un systme de parenthses bien form.

production action smantique

traduction avec une pile

S.nb:=0

empiler(0)

empiler(sommet()+1)
crire S.nb

Ecrire depiler()

On effectue une analyse descendante, donc il nous faut la table d'analyse LL :


PREMIER(S')=PREMIER
donc

, SUIVANT

et SUIVANT

S'
S
Analysons le mot (()(()))()

Pile

entre action

pile des attributs critures

$ S'

(()(()))()$

$S

(()(()))()$

01

$ S)S(

(()(()))()$

01

$ S)S

()(()))()$

012

$ S)S)S(

()(()))()$

012

$ S)S)S

)(()))()$

01

$ S)S)

)(()))()$

01

$ S)S

(()))()$

012

$ S)S)S(

(()))()$

012

$ S)S)S

()))()$

0123

$ S)S)S)S(

()))()$

0123

$ S)S)S)S

)))()$

012

$ S)S)S)

)))()$

012

Cours de Compilation

49

$ S)S)S

))()$

01

$ S)S)

))()$

01

$ S)S

)()$

$ S)

)()$

$S

()$

01

$ S)S(

()$

01

$ S)S

)$

$ S)

)$

$S

FINI
Mais, vu que les attributs synthtiss s'valuent avec une analyse ascendante et les attributs
hrits avec une analyse descendante, comment on fait quand on a la fois des attributs
synthtiss et hrits ? Hein ?
Souvent, on peut utiliser des attributs synthtiss qui font la mme chose que les hrits
que l'on voulait.
Exemple : compter le nombre de bit 1 dans un mot binaire.

Grammaire :
DDS avec attribut hrit :
production action smantique
B.nb:=0
B(1).nb:=B(0).nb
B(1).nb:=B(0).nb+1
crire B.nb
DDS avec attribut synthtis :
production action smantique
crire B.nb
B(0).nb:=B(1).nb

Cours de Compilation

50

B(0).nb:=B(1).nb+1
B.nb:=0
La figure 6.7 donne les arbres dcors pour le mot 10011.
Figure 6.7: Arbre dcor pour 10011 avec des
attributs (a)hrits (b) synthtiss

Parfois, il est ncessaire de modifier la grammaire. Par exemple, considrons une DDS de
typage de variables dans une dclaration Pascal de variables (en Pascal, une dclaration de
variable consiste en une liste d'identificateurs spars par des virgules, suivie du caractre ':',
suivie du type. Par exemple : a,b : integer)

production
:T

Id

action smantique
L.type:=T.type
mettre dans la table des symboles le type L.type pour Id

, Id

L(1).type:=L(0).type
mettre dans la table des symboles le type L(0).type pour Id

integer T.type:=entier
char

T.type:=caractere

C'est un attribut hrit. Pas possible de trouver un attribut synthtis faisant la mme chose
avec cette grammaire. Changeons la grammaire !

production

action smantique

Id L

mettre dans la table des symboles le type L.type pour Id

, Id L

L(0).type:=L(1).type
mettre dans la table des symboles le type L(1).type pour Id

:T

L.type:=T.type

Cours de Compilation

51

integer T.type:=entier
char

T.type:=caractere

Et voil !
Mais bon, ce n'est pas toujours vident de concevoir une DDS n'utilisant que des attributs
hrits ou de concevoir une autre grammaire permettant de n'avoir que des synthtiss.
Heureusement, il existe des mthodes automatiques qui transforment une DDS avec des
attributs hrits et synthtiss en une DDS quivalente n'ayant que des attributs synthtiss.
Mais nous n'en parlerons pas ici, cf bouquins.
Une autre ide peut tre de faire plusieurs passes d'analyses, suivant le graphe de
dpendances de la DDS. Par exemple (DDS donne prcdemment) : une passe ascendante
permettant d'valuer un certain attribut synthtis s, puis une passe descendante pour
valuer des attributs hrits h et r, puis enfin un passe ascendante pour valuer un attribut
synthtis t.

6. Exercices
1.
(a)
Ecrire une DDS n'utilisant que des attributs synthtiss qui calcule le nombre de a
contenus dans un mot de (a|b)*. Donner un arbre dcor pour le mot bbabaab
(b)
Mme question avec des attributs hrits.
2.
Considrons la grammaire suivante qui gnre des expressions arithmtiques formes
de constantes entires et relles et de l'oprateur +

Lorsque l'on additionne 2 entiers, le rsultat est un entier, sinon c'est un rel
(a)
Ecrire une DDS donnant le type de l'expression
(b)
Donner un arbre dcor pour le mot 5+3.05+10
3.
On considre un robot qui peut tre command pour se dplacer d'un pas vers l'est,
l'ouest, le nord ou le sud partir de sa position courante.
Une squence de tels dplacements peut tre engendre par la grammaire
debut L fin
est | sud | nord | ouest
La position du robot est donne dans le plan (le nord tant en haut). (0,0) est la
position initiale du robot.
(a)
Ecrire une DDS affichant les positions successives (x,y) du robot.
(b)

Cours de Compilation

52

Donner un arbre dcor pour le mot debut est est nord ouest sud sud
nord ouest fin

4.
(a)
Ecrire une DDS donnant le nombre maximum de a conscutifs dans un mot de (a|b)*
(exemple : pour aababaaaba il faut donner 3)
(b)
Donner un arbre dcor pour le mot aababaaaba
5.
Ecrire une DDS permettant de traduire un entier sous forme binaire en sa valeur
dcimale. Donner un exemple d'arbre dcor.
6.
Mme exercice que prcdemment avec des rels.
7.
Considrons la grammaire suivante qui gnre des expressions arithmtiques formes
des oprateurs + et *, et de la variable x et de constantes :
E
E+T|T
T
T*F|F
F
nb | x
(a)
Ecrire une DDS qui calcule la drive d'une telle expression
(b)
Donner un arbre syntaxique dcor pour la chane 2*x*x+5*x+3

Cours de Compilation

53

CHAPITRE 6.

L'OUTIL YACC/BISON

De nombreux outils ont t btis pour construire des analyseurs syntaxiques partir de
grammaires. C'est dire des outils qui construisent automatiquement une table d'analyse
partir d'une grammaire donne. YACC est un utilitaire d'unix, bison est un produit gnu.
YACC/bison accepte en entre la description d'un langage sous forme de rgles de
productions et produit un programme crit dans un langage de haut niveau (ici, le langage
C) qui, une fois compil, reconnat des phrases de ce langage (ce programme est donc un
analyseur syntaxique).
YACC signifie Yet Another Compiler Compiler, c'est dire encore un compilateur de compilateur.
Cela n'est pas tout fait exact, yacc/bison tout seul ne permet pas d'crire un compilateur,
il faut rajouter une analyse lexicale ( l'aide de (f)lex par exemple) ainsi que des actions
smantiques pour l'analyse smantique et la gnration de code.
YACC/bison construit une table d'analyse LALR qui permet de faire une analyse
ascendante. Il utilise donc le modle dcallages-rductions.

1. Structure du fichier de spcifications bison


%{
dclaration (en C) de variables, constantes, inclusions de fichiers,
%}
dclarations des units lexicales utilises
dclarations de priorits et de types
%%
rgles de production et actions smantiques
%%
routines C et bloc principal
Les symboles terminaux utilisables dans la description du langage sont
- des units lexicales que l'on doit imprativement dclarer par %token nom. Par exemple :
%token MC_sinon
%token NOMBRE

- des caractres entre quotes. Par exemple : '+' 'a'


- des chanes de caractres entre guillemets. Par exemple : "+=" "while"
Les symboles non-terminaux sont les caractres ou les chanes de caractres non dclares
comme units lexicales.
yacc/bison fait la diffrence entre majuscules et minuscules. SI et si ne dsignent pas le
mme objet.
Les rgles de production sont des suites d'instructions de la forme
non-terminal :
|
|
;

prod1
prod2
...
prodn

Les actions smantiques sont des instructions en C insres dans les rgles de production.
Elles sont excutes chaque fois qu'il y a rduction par la production associe.

Cours de Compilation

54

Exemples :
G : S B 'X' {printf("mot reconnu");}
;
S : A {print("reduction par A");} T {printf("reduction par T");} 'a'
;

La section du bloc principal doit contenir une fonction yylex() effectuant l'analyse
lexicale du texte, car l'analyseur syntaxique l'appelle chaque fois qu'il a besoin du terminal
suivant. On peut
- soit crire cette fonction
- soit utiliser la fonction produite par un compilateur (f)lex appliqu un fichier de
spcifications nom.l. Dans ce cas, il suffira d'inclure le fichier lex.yy.c produit par
(f)lex et de rajouter la bibliothque (f)lex lors de la compilation C du fichier nom.tab.c
(avec cc nom.tab.c -ly -l(f)l).

2. Attributs
A chaque symbole (terminal ou non) est associ une valeur (de type entier par dfaut). Cette
valeur peut tre utilise dans les actions smantiques (comme un attribut synthtis). Le
symbole $$ rfrence la valeur de l'attribut associ au non-terminal de la partie gauche, tandis
que $i rfrence la valeur associe au i-me symbole (terminal ou non-terminal) ou action
smantique de la partie droite.
Exemple :
expr : expr '+' expr { tmp=$1+$3;} '+' expr { $$=tmp+$6;};
Par dfaut, lorsqu'aucune action n'est indique, yacc/bison gnre l'action {$$=$1;}

3. Communication avec l'analyseur lexical : yylval


L'analyseur syntaxique et l'analyseur lexical peuvent communiquer entre eux par
l'intermdiaire de la variable int yylval.
Dans une action lexicale (donc dans le fichier (f)lex par exemple), l'instruction
return(unit) permet de renvoyer l'analyseur syntaxique l'unit lexicale unit. La valeur
de cette unit lexicale peut tre range dans yylval.
L'analyseur syntaxique prendra automatiquement le contenu de yylval comme valeur de
l'attribut associ cette unit lexicale.
La variable yylval est de type YYSTYPE (dclar dans la bibliothque yacc/bison) qui est
un int par dfaut. On peut changer ce type par un
#define YYSTYPE autre_type_C
ou encore par
%union { champs d'une union C }
qui dclarera automatiquement YYSTYPE comme tant une telle union.

Par exemple
%union {
int entier;
double reel;
char * chaine;
}
Cours de Compilation

55

permet de stocker dans yylval la fois des int, des double et des char *.
L'analyseur lexical pourra par exemple contenir
{nombre}

{
yylval.entier=atoi(yytext);
return NOMBRE;
}

Le type des lexmes doit alors tre prcis en utilisant les noms des champs de l'union
%token <entier> NOMBRE
%token <chaine> IDENT CHAINE COMMENT

On peut galement typer des non-terminaux (pour pouvoir associer une valeur de type autre
que int un non-terminal) par
%type <entier> S
%type <chaine> expr

4. Variables, fonctions et actions prdfinies


YYACCEPT : instruction qui permet de stopper l'analyseur syntaxique. Dans ce cas, yyparse
retourne la valeur 0 (succs).
YYABORT : instruction qui permet galement de stopper l'analyseur.
yyparse retourne alors 1, ce qui peut tre utilis pour signifier l'chec de l'analyseur.
yyparse() : appel de l'analyseur syntaxique.
main() : le main par dfaut se contente d'appeler yyparse(). L'utilisateur peut crire son
propre main dans la partie du bloc principal.
%start non-terminal : action pour signifier quel non-terminal est l'axiome. Par dfaut, c'est le
premier dcrit dans les rgles de production.

5. Conflits shift-reduce et reduce-reduce


Lorsque l'analyseur est confront des conflits, il rend compte du type et du nombre de
conflits rencontrs :
> bison exemple.y
conflicts: 6 shift/reduce, 2 reduce/reduce

Il y a un conflit reduce/reduce lorsque le compilateur a le choix entre (au moins) deux


productions pour rduire une chane. Les conflits shift/reduce apparaissent lorsque le
compilateur a le choix entre rduire par une production et dcaller le pointeur sur la chane
d'entre.
yacc/bison rsoud les conflits de la manire suivante :
conflit reduce/reduce : la production choisie est celle apparaissant en premier dans la
spcification.
conflit shift/reduce : c'est le shift qui est effectu.
Pour voir comment bison a rsolu les conflits, il est ncessaire de consulter la table d'analyse
qu'il a construit. Pour cel, il faut compiler avec l'option -v. Le fichier contenant la table
s'appelle y.output

6. Associativit et priorit des symboles terminaux


On peut essayer de rsoudre soit mme les conflits (ou tout du moins prciser comment on
veut les rsoudre) en donnant des associativits (droite ou gauche) et des priorits aux
symboles terminaux.
Les dclarations suivantes (dans la section des dfinitions)

Cours de Compilation

56

%left term1 term2


%right term3
%left term4
%nonassoc term5
indiquent que les symboles terminaux term1, term2 et term4 sont associatifs gauche, term3
est associatif droite, alors que term5 n'est pas associatif.
Les priorits des symboles sont donnes par l'ordre dans lequel apparat leur dclaration
d'associativit, les premiers ayant la plus faible priorit. Lorsque les symboles sont dans la
mme dclaration d'associativit, ils ont la mme priorit. La priorit (ainsi que
l'associativit) d'une production est dfinie comme tant celle de son terminal le plus
droite. On peut forcer la priorit d'une production en faisant suivre la production de la
dclaration
%prec terminal-ou-unite-lexicale ce qui a pour effet de donner comme priorit (et
comme associativit) la production celle du terminal-ou-unite-lexicale (ce terminal-ou-unitelexicale devant tre dfini, mme de manire factice, dans la partie Ib).
Un conflit shift/reduce, i.e. un choix entre entre une rduction
et un dcallage d'un
symbole d'entre a, est alors rsolu en appliquant les rgles suivantes :
si la priorit de la production
est suprieure celle de a, c'est la rduction qui est
effectue
si les priorits sont les mmes et si la production est associative gauche, c'est la rduction
qui est effectue
dans les autres cas, c'est le shift qui est effectu.

7. Rcupration des erreurs


Lorsque l'analyseur produit par bison rencontre une erreur, il appelle par dfaut la fonction
yyerror(char *) qui se contente d'afficher le message parse error, puis il s'arrte.
Cette fonction peut tre redfinie par l'utilisateur.
Il est possible de traiter de manire plus explicite les erreurs en utilisant le mot cl bison
error. On peut rajouter dans toute production de la forme

une production

error

Dans ce cas, une production d'erreur sera traite comme une production classique. On pourra
donc lui associer une action smantique contenant un message d'erreur.
Ds qu'une erreur est rencontre, tous les caractres sont avals jusqu' rencontrer le caractre
correspondant . Exemple : La production
instr error ;
indique l'analyseur qu' la vue d'une erreur, il doit sauter jusqu'au del du prochain ";" et
supposer qu'une instr vient d'tre reconnue.
La routine yyerrok replace l'analyseur dans le mode normal de fonctionnement c'est dire
que l'analyse syntaxique n'est pas interrompue.

8. Options de compilations Bison


l'option -v produit un fichier nom.output contenant un descriptif des conflits shift/reduce
et reduce/reduce dtects et indique de quelle faon ils ont t rsolus. Il est fortement
conseill de consulter ce fichier afin de vrifier que les conflits sont rsolus comme on le
dsire.
Cours de Compilation

57

l'option -d produit un fichier nom.tab.h contenant les dfinitions (sous forme de


#define) des units lexicales rencontres et du type YYSTYPE s'il est redfini.

9. Exemples de fichier .y
Cet exemple reconnat les mots qui ont un nombre pair de a et impair de b. Le mot doit se
terminer par le symbole $. Cet exemple ne fait pas appel la fonction yylex gnre par le
compilateur (f)lex.
%%
mot : PI '$'
;

{printf("mot accepte\n");YYACCEPT;}

PP : 'a' IP
| 'b' PI
| /* vide */
;
IP : 'a' PP
| 'b' II
| 'a'
;
PI : 'a' II
| 'b' PP
| 'b'
;
II : 'a' PI
| 'b' IP
| 'a' 'b'
| 'b' 'a'
;
%%
int yylex() {
char car=getchar();
if (car=='a' || car=='b' || car=='$') return(car);
else printf("ERREUR : caractere non reconnu : %c ",car);
}
Ce deuxime exemple lit des listes d'entiers prcdes soit du mot somme, soit du mot
produit. Une liste d'entiers est compose d'entiers spars par des virgules et se termine par
un point. Lorsque la liste dbute par somme, l'excutable doit afficher la somme des entiers,
lorsqu'elle dbute par produit, il doit en afficher le produit. Le fichier doit se terminer par $.
Cet exemple utilise la fonction yylex gnre par le compilateur flex partir du fichier de
spcification exemple2.l suivant
%{
#include <stdlib.h>
int nbligne=0;
%}
chiffre
[0-9]
entier
{chiffre}+
espace
[ \t]
%%
somme
return(SOMME);
produit
return(PRODUIT);

Cours de Compilation

58

\n
[.,]
{entier}

nbligne++;
return(yytext[0]);
{
yylval=atoi(yytext);
return(NOMBRE);

}
{espace}+
/* rien */;
"$"
return(FIN);
.
printf("ERREUR ligne %d : %c
inconnu\n",nbligne,yytext[0]);
%%
Le fichier de spcifications yacc est alors le suivant
%token SOMME
%token PRODUIT
%token NOMBRE
%token FIN
%%
texte : liste texte
| FIN {printf("Merci et a bientot\n");YYACCEPT;}
;
liste : SOMME sentiers '.' {printf("la somme est %d\n",$2);}
| PRODUIT pentiers '.' {printf("le produit est %d\n",$2);}
;
sentiers : sentiers ',' NOMBRE {$$=$1+$3;}
| NOMBRE {$$=$1;}
;
pentiers : pentiers ',' NOMBRE {$$=$1*$3;}
| NOMBRE {$$=$1;}
;
%%
#include "lex.yy.c"

Le Makefile pour compiler tout a est


CC=gcc
exemple2 : lex.yy.c exemple2.tab.c
$(CC) exemple2.tab.c -o exemple2 -ly -lfl
lex.yy.c : exemple2.l
flex exemple2.l
exemple2.tab.c : exemple2.y lex.yy.c
bison exemple2.y

10. Exercices
1.
Ecrire un fichier de spcification Bison permettant de reconnaitre si une expression est
correctement parenthse ou non.
2.
Modifier l'exercice prcdent pour afficher les niveaux d'imbrication.
Exemple : l'expression (()()(()))() doit donner 1 2 2 2 3 1.

Cours de Compilation

59

3.
Modifier l'exercice 1 en considrant les mots begin et end au lieu des parenthses.
4.
Mini-calculateur de bureau. On considre la grammaire des expressions
arithmtiques suivante :

NOMBRE
(o NOMBRE est un nombre entier)
(a)
crire un programme qui indique si une expression arithmtique donne est correcte
ou non.
(b)
Complter les fichiers de spcifications prcdents pour

permettre la reconnaissance de squences d'expressions, raison d'une par


ligne,
autoriser les lignes blanches entre les expressions.

(c)
Complter les fichiers de spcifications prcdents pour que le programme retourne la
valeur de chaque expression arithmtique rentre, en considrant qu'on ne traite que
des entiers.
(d)
Mme question en considrant galement des nombres rels.
5.
Mme exercice que prcdemment en considrant cette fois la grammaire ambigu
suivante :
NOMBRE

Cours de Compilation

60

CHAPITRE 7.

ANALYSE SEMANTIQUE

Certaines proprits fondamentales du langage source traduire ne peuvent tre dcrites


l'aide de la seule grammaire hors contexte du langage, car justement elles dpendent du
contexte. Par exemple (exemples seulement, car cela dpend du langage source ) :
- on ne peut pas utiliser dans une instruction une variable qui n'a pas t dclare au pralable
- on ne peut pas dclarer deux fois une mme variable
- lors d'un appel de fonction, il doit y avoir autant de paramtres formels que de paramtres
effectifs, et leur type doit correspondre
- on ne peut pas multiplier un rel avec une chane
Le rle de l'analyse smantique (que l'on appelle aussi analyse contextuelle) est donc de
vrifier ces contraintes.
Elle se fait en gnral en mme temps que l'analyse syntaxique, l'aide d'actions smantiques
insres dans les rgles de productions (i.e. l'aide de TDS).
Les contraintes vrifier dpendant fortement des langages, il n'y a pas vraiment de mthode
universelle pour effectuer l'analyse smantique. Dans ce chapitre, nous donnerons une liste de
problmes rencontrs plutt que des solutions qui marchent tous les coups.

1. Porte des identificateurs


On appelle porte d'un identificateur la (les) partie(s) du programme o il est valide et a la
signification qui lui a t donn lors de sa dclaration.
Cette notion de validit et visibilit dpend bien sr des langages. Par exemple, en Cobol tous
les identificateurs sont partout visibles. En Fortran, C, Pascal, ..., les identificateurs qui sont
dfinis dans un bloc ne sont visibles qu' l'intrieur de ce bloc; un identificateur dclar dans
un sous-bloc masque un identificateur de mme nom dclar dans un bloc de niveau infrieur.
En Algol, Pascal, Ada et dans les langages fonctionnels, il peut y avoir une imbrication
rcursive des blocs sur une profondeur quelconque (cf letrec)...
Par exemple, l'analyseur smantique doit vrifier si chaque utilisation d'un identificateur est
lgale, ie si cet identificateur a t dclar et cela de manire unique dans son bloc. Il faut
donc mmoriser tous les symboles rencontrs au fur et mesure de l'avance dans le texte
source. Pour cela on utilise une structure de donnes adquate que l'on appelle une table des
symboles.
La table des symboles contient toutes les informations ncessaires sur les symboles
(variables, fonctions, ...) du programme :
- identificateur (le nom du symbole dans le programme)
- son type (entier, chane, fonction qui retourne un rel,...)
- son emplacement mmoire
- si c'est une fonction : la liste de ses paramtres et leurs types
- ...
Lors de la dclaration 8.1 d'une variable, elle est stocke dans cette table (avec les
informations que l'on peut dja connaitre). Il faut pralablement regarder si cette variable n'est
pas dj contenue dans la table. Si c'est le cas, c'est une erreur. Lors de l'utilisation d'une
variable, il faut vrifier qu'elle fait partie de la table (on peut alors rcuprer son type, donner
ou modifier une valeur, ...).
Il faut donc munir la table des symboles de procdures d'ajout, suppression et recherche. La

Cours de Compilation

61

structure d'une table des symboles peut aller du plus simple au plus compliqu (simple tableau
tri ou non tri, liste linaire, forme arborescente, table d'adressage dispers (avec donc des
fonctions de hachage), ...), cela dpend de la complexit du langage compiler, de la rapidit
que l'on souhaite pour le compilateur, de l'humeur du concepteur du compilateur, ....
Dans certains langages, on peut autoriser qu'un identificateur ne soit pas dclar dans son bloc
B condition qu'il ait t dclar dans un bloc d'un niveau infrieur qui contient B. Il faut
galement grer le cas o un identificateur est dclar dans au moins deux blocs diffrents B
contenu dans B' (dans ce cas, l'identificateur de plus haut niveau cache les autres).
On peut alors utiliser une pile pour empiler la table des symboles et grer ainsi la porte des
identificateurs. Mais il y a d'autres mthodes, comme par exemple coder la porte dans la
table des symboles, pour chaque symbole.
Figure 9.1: Porte des identificateurs : empilement de la table des symboles

2. Contrle de type
Lorsque le langage source est un langage typ, il faut vrifier la pertinence des types des
objets manipuls dans les phrases du langage.
Exemples : en C, on ne peut pas additionner un double et un char *, multiplier un int et
un struct, indicer un tableau avec un float, affecter un struct * un char ... Certaines
oprations sont par contre possibles : affecter un int un double, un char un int, ...
(conversions implicites).
Dfinition 8.1 On appelle statique un contrle de type effectu lors de la compilation, et
dynamique un contrle de type effectu lors de l'excution du programme cible.

Cours de Compilation

62

Le contrle dynamique est viter car il est alors trs difficile pour le programmeur de voir
d'o vient l'erreur (quand il y en a une). En pratique cependant, certains contrles ne peuvent
tre fait que dynamiquement. Par exemple, si l'on a
int tab[10];
int i;
...
... tab[i] ...

le compilateur ne pourra pas garantir en gnral8.2 qu' l'excution la valeur de i sera


comprise entre 0 et 9.
Exemples de TDS de contrle de type :
Rgle de production action smantique
I.type :=

si Id.type=E.type alors vide


sinon erreur_type_incompatible(ligne,Id.type,E.type)

I(0).type := si E.type=booleen alors I(1).type


sinon erreur_type(ligne,...)
E.type :=

Recherche_Table(Id)

E(0).type := si E(1).type=entier et E(2).type=entier alors entier


sinon
si (E(1).type
ou (E(2).type

reel et E(1).type
reel et E(2).type

entier)
entier) alors

erreur_type_incompatible(ligne,E(1).type,E(2).type)
sinon
reel
E(0).type := si E(1).type=entier et E(2).type=entier alors entier
sinon erreur_type(ligne,...)

a a l'air assez simple, non ?


Maintenant imaginons le boulot du compilateur C lorsqu'il se trouve confront un truc du
genre s->t.f(p[*i])-&j
Il faut alors trouver une reprsentation pratique pour les expressions de type.
Exemple : codage des expressions de type par Ritchie et Johnson pour un compilateur C.
On considre les constructions de types suivantes :
pointeur(t) : un pointeur vers le type t
fretourne(t) : une fonction (dont les arguments ne sont pas spcifis) qui retourne un objet de
type t tableau(t) : un tableau (de taille indetermine) d'objets de type t

Cours de Compilation

63

Ces constructeurs tant unaires, les expressions de type forms en les appliquant des types
de base ont une structure trs uniforme. Par exemple
caractre
fretourne(caratre)
tableau(caractre)
fretourne(pointeur(entier))
tableau(pointeur(fretourne(entier)))
Chaque constructeur peut tre reprsente par une squence de bits selon un procd
d'encodage simple, de mme que chaque type de base. Par exemple
constructeur codage
pointeur

01

tableau

10

fretourne

11

type de base codage


entier

0000

boolen

0001

caractre

0010

rl

0011

Les expressions de type peuvent maintenant tre encode comme des squences de bit
expression de type

codage

caractre 000000 0010


fretourne(caractre) 000011 0001
pointeur(fretourne(caractre)) 000111 0001
tableau(pointeur(fretourne(caractre))) 100111 0001
Cette mthode a au moins deux avantages : conomie de place et il y a une trace des diffrents
constructeurs appliqus.

3. Surcharge d'oprateurs et de fonctions


Des oprateurs (ou des fonctions) peuvent tre surchargs c'est dire avoir des significations
diffrentes suivant le contexte. Par exemple, en C, la division est la division entire si les deux
oprandes sont de type entier, et la division relle sinon. Dans certains langages8.3, on peut
redfinir un oprateur standard, par exemple en Ada les instructions
function "*" (i,j : integer) return complexe;
function "*" (x,y : complexe) return complexe;
surchargent l'oprateur * pour effectuer aussi la multiplication de nombres complexes (le type
complexe devant tre dfini). Maintenant, si l'on rencontre 3*5, doit-on considrer la

Cours de Compilation

64

multiplication standard qui retourne un entier ou celle qui retourne un complexe ? Pour
rpondre cette question, il faut regarder comment est utilise l'expression. Si c'est dans
2+(3*5) c'est un entier, si c'est dans z*(3*5) o z est un complexe, alors c'est un
complexe. Bref, au lieu de remonter un seul type dans la TDS, il faudra remonter un ensemble
de types possibles, jusqu' ce que l'on puisse conclure. Et je ne parle pas de la gnration de
code ...

4. Fonctions polymorphes
Une fonction (ou un oprateur) est dite polymorphe lorsque l'on peut l'appliquer des
arguments de types variables (et non fixs l'avance). Par exemple, l'oprateur & en C est
polymorphe puisqu'il s'applique aussi bien un entier qu' un caractre, une structure
dfinie par le programmeur, .... On peut galement, dans certains langages, se dfinir une
fonction calculant la longueur d'une liste d'lments, quelque soit le type de ces lments.
Cette fois-ci, il ne s'agit plus seulement de vrifier l'quivalence de deux types, mais il faut
les unifier.
Un des problmes qui se pose alors est l'infrence de type, c'est dire la dtermination du
type d'une construction du langage partir de la faon dont elle est utilise. Ces problmes
ne sont pas simples et sont tudis dans le cadre des recherches en logique combinatoire et
en lambda-calcul. Le lambda-calcul permet de dfinir et d'appliquer les fonctions sans
s'occuper des types.

5. La table des symboles


Pour un compilateur, la TDS est lattribut hrit de premier rang, ensuite cest la structure de
donne la plus importante aprs larbre syntaxique. Les principales oprations associes
une TDS sont :
- insertion. Utilise pour enregistrer les informations fournies par les
dclarations.
- Recherche. Elle permet dextraire les informations associes un nom
- Suppression. Cette opration permet de retirer les informations fournies par
une dclaration qui nest plus valide (applicable).
Ces trois caractristiques vont orienter le choix des structures de donnes appropries pour
lorganisation de la TDS pour un accs rapide et facile.

5.1 La structure de la TDS


Avant tout il faudrait savoir quune TDS est un dictionnaire. Lefficacit des trois oprations
de base est traite dans le cours de structure de donnes.
Couramment, limplantation des dictionnaire peut se faire par liste linaire, par arbre de
recherche (binaire, B-tree, AVL) et les tables de hachage.
5.1.1 Les listes linaires
insertion en temps constant O(1), recherche et suppression en temps linaire suivant la taille
de la liste O(n).
=>Bon candidat pour limplmentation des compilateurs pour lequel les phases danalyse
nest pas critique (les prototypes ou compilateur exprimentaux, les interprteurs pour des
petits programmes).

Cours de Compilation

65

5.1.2 Les arbres de recherches


insertion assez coteuse
Complexit des suppressions
inefficacit 2/3 => Trs peu utilis pour les TDS
5.1.3 Les tables de hachage
offre un temps constant pour les trois oprations O(1)
=> trs bon candidat pour limplmentation des TDS
Une table de hachage est un tableau index par les entiers. Une fonction de hachage permet
de transformer la cl de recherche (ici le nom de lidentificateur) en un entier appel valeur
de hachage qui correspond lindice du tableau o est stock litem recherch.
Attention
la fonction de hachage doit distribuer uniformment les cls=> quilibrage de la Table,
assurer les performances de recherche et de suppression
la fonction de hachage doit sexcuter en temps constant ou pire en temps linaire (taille des
cls).
Gestion des collisions : le plus simple cest de considrer chaque entre de la table comme
une liste chane

Indice
s

Buckets

Liste des Items

size

2
3

temp

4
5
6
Exemple de TDS de taille 7. Nous avons insr les identificateurs (i, j, size, temp) et size et j
ont la mme valeur de hachage. Sur la figure size prcde j, ceci dpend de la mthode
dinsertion des items dans une liste. Communment linsertion se fait en tte, puisque un
identificateur nouvellement dclar a plus de chance dtre utilis immdiatement.
Le nombre dentre de la TDS est connu pendant la construction du compilateur. Cette taille
est gnralement un nombre premier (permet de dfinir des meilleurs fonctions de hachage).
Par exemple il est prfrable de choisir 211 au lieu de 200.
5.1.4 Les fonctions de hachage
Convertir une chane de caractres (nom de lidentificateur) en un entier de lintervalle *0..
size-1] o size est la taille de la table. Premirement convertir chaque caractre de la chane
en un entier, en pascal on utilisera la fonction ord qui retourne la valeur ASCII dun
caractre, en C cest automatique si on utilise un caractre dans une expression arithmtique.
Ensuite combiner ces codes (en les additionnant) pour obtenir un entier unique. Enfin le

Cours de Compilation

66

moduler (mod en pascal et % en C) afin de linsrer dans lintervalle *0..size-1].

h(id ) ci mod size


ci id
Dans cette combinaison, on peut ignorer certains caractres et ne considrer que les premier
caratres, ou bien le premier, celui du milieu et le dernier. Cette mthode nest pas adapte
pour un compilateur puisque les programmeurs utilisent gnralement des noms comme
temp1, temp2, temp, etc. et cette mthode causera frquemment des collisions. Une autre
mthode populaire consiste additionner tous les caractres. L aussi on nest pas sorti de
lauberge, puisque toutes les permutations dune chanes causeront des collisions, par
exemple xtemp et tepmx.
Solution : utiliser une constante comme facteur multiplicatif. Cette constante doit tre une
puissance de 2 pour simplifier lexponentiation (qui se rduire un dcalage)

n n i
h ci mod size
i 1

#define

SIZE

#define

SHIFT

int hash( char *key) {


int temp = 0 ;
int i = 0 ;
while (key[ i ] != \0) {
temp = ((temp << SHIFT) + key[ i ]) ;
++i;
}
return temp % SIZE;
}

5.2 Grammaire attribue utilisant une TDS

6. Infrence de type et contrle de type


Linfrence de type consiste pour un compilateur traiter et maintenir des informations
sur les types de donnes. Ces informations sont ensuite utilises pour assurer la smantique
des rgles de typage.
Thoriquement, un type de donnes est un ensemble de valeurs auxquelles sont associes
des oprations. Par exemple, le type integer de Pascal, est un sous-ensembles des entiers
mathmatiques avec les oprations telles que +, -, *, /. En pratique ces ensembles sont dcrits

Cours de Compilation

67

par des expressions de type telle que integer ou des expressions structures comme array
[1..100] of real.

Cours de Compilation

68

7. Environnement d'excution
Avant d'attaquer le problme de la gnration du code cible, il faut se poser des questions sur
l'organisation de la mmoire :
- gestion du flot de contrle (appels et retour de fonctions, instructions de saut (du type break
en C))
- stockage des variables
- allocation dynamique de mmoire
7.1 Organisation de la mmoire l'excution
Questions lies au langage source :
- les fonctions peuvent-elles tre rcursives ?
- une fonction peut-elle faire rfrence des noms non locaux ?
- comment sont passs les paramtres lors d'un appel de fonction ?
- peut-on allouer dynamiquement de la mmoire ?
- doit-on librer explicitement l'espace mmoire allou dynamiquement ?
- que deviennent les variables locales une fonction lorsque l'on sort de la fonction ?
- ...
Questions lies la machine cible :
- quelle est la longueur d'un mot ou d'une adresse ?
- quelles sont les entits directement adressables de la machine ?
- existe-t-il des instructions pour accder directement et efficacement des parties d'entits
directement adressables ?
- est-ce possible de rassembler plusieurs "petits" objets (boolen, caractre, ...) dans des units
plus
grandes ?
- y-a-t-il des restrictions d'adressage (conditions d'alignements) ?
- ...
Les rponses ces questions influent sur la faon d'organiser la mmoire.
(modle gnral)
On divise le bloc de mmoire alloues l'excution par le systme d'exploitation cible en 4
zones :
code cible
donnes statiques
Pile de contrle

tas
donnes statiques : la taille de certaines donnes est connue ds la compilation. On les place
alors dans une zone dtermine statiquement. L'avantage c'est que l'on peut alors compiler
directement leur adresse dans le code cible.

Cours de Compilation

69

pile de contrle : elle permet de grer les appels de fonctions. On y stocke les
enregistrements d'activation, c'est dire toutes les informations ncessaires lors de l'activation
et du retour d'une fonction (adresse de retour, valeur de certains registres, ...). Lors d'un appel
de fonction, un protocole d'appel alloue un enregistrement d'activation, remplit ses champs et
l'empile. Lors du retour, la squence de retour dpile et restaure l'tat de la machine afin de
permettre la fonction appelante de continuer l'excution.
tas : contient tout le reste, c'est dire les variables alloues dynamiquement au cours de
l'excution du programme.
7.2 Allocation dynamique : gestion du tas
Les allocations peuvent tre explicites (instructions new en Pascal, malloc en C, ...) ou
implicites (utilisation de cons en Lisp, appels implicites aux constructeurs en C++, ...). Les
donnes alloues sont conserves jusqu leur libration explicite (instructions free en C,
dispose en Pascal, ...) ou implicite (appels implicites des destructeurs en C++, garbage
collector en Lisp, ...).

Allocation explicite de blocs de taille fixe


gestion des blocs libre par une liste

Allocation explicite de blocs de taille variable


Mme principe avec des infos en plus dans chaque bloc allou (taille du bloc, ...)
Problme : si l'utilisateur demande allouer un bloc de taille suprieure au plus grand
des blocs libres.

Libration implicite
Intrt : vite les rebuts (ou miettes), c'est dire les zones mmoires alloues mais
devenues inaccessibles, et les rfrences fantmes, c'est dire une rfrence une
zone qui a t libre.
Modle de format de bloc :
taille du bloc
compteur de rfrence et/ou marquage
pointeurs vers des blocs
informations utilisateurs
Il y a deux mthodes pour grer la libration implicite :

Compteur de rfrences : On suppose qu'un bloc est utilis s'il est possible au
programme utilisateur de faire rfrence l'info contenue dedans (par un
pointeur ou en suivant une liste de pointeurs).

Cours de Compilation

70

On incrmente alors le compteur de rfrence chaque nouvelle rfrence ce


bloc, et on le dcrmente chaque "disparition" de rfrence (sortie de
fonction dans laquelle la rfrence est locale par exemple). Lorsque le
compteur devient nul, on peut librer le bloc.
Exemple : si on a une instruction (C) p=q, le compteur du bloc rfrenc par p
est dcrment, celui de q est incrment.
Attention, cette mthode merde si on a des blocs qui forment une liste
circulaire.
Marquage : L'autre approche consiste suspendre temporairement l'excution
du programme et faire du ramasse-miettes. Cela necessite que tous les
pointeurs vers le tas soient connus. On commence par marquer tout le tas en
tant qu'inutilis. Puis on passe en revue tous les pointeurs en cours en marquant
utilis tout bloc atteint.
Compression De temps autres, on peut suspendre temporairement l'excution
du programme pour faire de la compression, c'est dire dplacer les blocs
utiliss vers une extrmit du tas, afin de rassembler toute la mmoire libre en
un seul grand bloc9.1. Il ne faut pas oublier de mettre jour toutes les
rfrences des blocs que l'on dplace9.2.

Cours de Compilation

71

8. Gnration de code
Il s'agit prsent de produire le code cible.
Bien qu'un texte source puisse tre traduit directement en langage cible, on utilise en gnral
une forme intermdiaire indpendante de la machine cible. On produira donc dans un premier
temps du code intermdiaire, que l'on pourra ensuite optimiser, puis enfin partir du code
intermdiaire on produira le code cible.
8.1 Code intermdiaire
Le langage intermdiaire doit tre facile produire et facile traduire dans le langage cible
(quelqu'il soit).
Pourquoi passer par un langage intermdiaire ?
le "reciblage" est facilit. En effet, le langage cible dpend normemment de la machine sur
laquelle
il va tourner. Utiliser un langage intermdiaire permet de porter plus rapidement le
compilateur,
puisque les 9/10 du boulot sont dja fait lorsqu'on est arriv produire du code intermdiaire.
l'optimisation du code est facilit. Il existe de nombreux algorithmes et mthodes
d'optimisation de
code pour les codes intermdiaires les plus utiliss.
8.2 Caractristiques communes aux machines cibles
(cf cours Archi/Systme ...)

hirarchie de mmoire : registres du processeur, mmoire principale, mmoire cache,


mmoire auxiliaire (les deux dernires, nous, en compil, on s'en foue)
les registres : universels, flottants, de donnes et d'adresse (parfois), de base et d'index
(parfois), compteur d'instruction.
L'accs aux registres est plus rapide que l'accs la mmoire principale, donc un bon
compilateur est un compilateur qui utilise le plus possible les registres pour les
rsultats intermdiaires, les calculs d'adresse, la transmission des paramtres d'une
fonction, le retour des valeurs d'une fonction, ...
Bien sr, le nombre de registres est limit. Un problme important de la gnration de
code sera donc l'allocation des registres.
jeu d'instructions :
- instructions de calcul (arithmtique, boolen, ...), souvent diffrencies selon la
longueur des
oprandes
- instructions de transfert (entre et dans la mmoire et les registres)
- instructions de branchements
- instructions de communication avec le sytme d'exploitation et les priphriques
(entres/sorties)

8.3 Code 3 adresses simplifi


Nous allons voir un code intermdiaire souvent utilis : le code 3 adresses.

Cours de Compilation

72

Un programme crit en code 3 adresses est une squences d'instructions numrotes, ces
instructions pouvant tre
affectation binaire

(num) x := y op z

affectation unaire

(num) x := opu z

affectation indice

(num) y[i]:= z

copie

(num) x:=y

branchement inconditionnel (num1) aller a (num2)


branchement conditionnel

(num1) si x oprel y aller a (num2)

lecture

(num) lire x

ecriture

(num) ecrire y

avec
op

oprateur binaire comme +, *, -, /, ET , OU,

opu

oprateur unaire comme

oprel

...

, NON, ...

oprateur relationnel (
10.1

adresse

y, z

adresse de variable, registre ou constante

de variable ou registre

et c'est tout.
le nombre de registres disponibles est illimit
Exemple 1 : le fragment de programme C a=b*c+b*(b-c)*(-c) devient
(1)
(2)
(3)
(4)

t1
t2
t3
t4

:=
:=
:=
:=

b*c
b-c
b*t2
-c

(5) t5 := t3*t4
(6) t6 := t1+t5
(7) a := t6

ou encore, en optimisant le nombre de registres utiliss


(1)
(2)
(3)
(4)

t1
t2
t2
t3

:=
:=
:=
:=

b*c
b-c
b*t2
-c

(5) t2 := t3*t4
(6) t1 := t1+t5
(7) a := t1

Exemple 2 : soit le fragment de code C suivant


z=0;
do {
x++;
if (y<10)
{

Cours de Compilation

73

z=z+2*x;
h=5;
}
else
h=-5;
y+=h;
} while (x<=50 && z<=5*y);

Le code 3 adresses correspondant est


(1) t1 := 0
(2) z := t1
(3) t2 := x + 1
(4) x := t2
(5) si y>=10 aller a (12)
(6) t3 := 2*x
(7) t4 := t3+z
(8) z := t4
(9) t5 := 5
(10) h := t5

(11)
(12)
(13)
(14)
(15)
(16)
(17)
(18)
(19)
(20)

aller a (14)
t6 := -5
h := t6
t7 := y+h
y := t7
si x>50 aller a (20)
t8 := 5*y
si z>t8 aller a (20)
aller a (3)
...

8.4 Production de code 3 adresses


Il est (assez) simple d'insrer des actions smantiques dans la grammaire du langage afin de
produire ce code. On utilise donc une TDS.
8.4.1 Expressions arithmtiques

Exemple 1 : gnration de code pour les expressions arithmtiques.

Soit la grammaire

La TDS utilise deux attributs : code et place (pour le nom du registre qui contient la valeur du
non-terminal)
Production

Rgle smantique
I.code=

E.code
Id.place ":=" E.place

E.place=

Id.place

E.code=

""

E(0).place= E(1).place
E(0).code= E(1).code
E(0).place= NouvRegistre()
E(0).code= E(1).code
E(2).code
E(0).place ":="E(1).place "+" E(2).place
etc.

Cours de Compilation

74

Il faudrait aussi penser aux numros des instructions, ce n'est pas fait dans cet exemple. Mais
en fait, les seuls numros importants sont lorsqu'il y a des branchements, le reste du temps on
s'en foue compltement.
8.4.2 Expressions boolennes

Dfinition 10.1 On appelle valuation paresseuse l'valuation de tous les termes qui
composent une expression boolenne
Dfinition 10.2 On appelle valuation court-circuit l'valuation d'une expression
boolenne qui n'value que les termes ncessaires
Soit la grammaire

Pour une instruction, on considre un attribut suivant qui contient l'tiquette de l'instruction
suivante effectuer (ce n'est pas toujours celle qui suit dans le code car il peut y avoir des
branchements).
La TDS qui produit le code paresseux est
Production

Rgle smantique
I(1).suivant= I(0).suivant
I(2).suivant= I(0).suivant
Vrai=NouvEtiquette()
Faux=NouvEtiquette()
I(0).code =

L.code
"si" L.place"=vrai aller a" Vrai
"aller a" Faux
"(" Vrai ")" I(1).code
"aller a" I(1).suivant
"(" Faux ")" I(2).code

L(0).place = NouvRegistre()
L(0).code = L(1).code
L(2).code
L(0).place ":=" L(1).place "ou" L(2).place
L.place = NouvRegistre()

Cours de Compilation

75

L.code = L.place ":=" Id.place oprel.valeur Id.place


L(0).place = NouvRegistre()
L(0).code = L(0).place ":= non" L(1).place
etc.
Un des problmes qui se pose est qu'on ne peut pas connatre toutes les tiquettes destination
de toutes les instructions de branchement en une seule passe. On peut rsoudre le problme en
faisant deux passes, ou bien encore le contourner par la technique de reprise arrire. On
produit une srie de branchements dont les destinations sont temporairement indfinies. Ces
instructions sont conserves dans une liste d'instructions de branchement dont les champs
tiquette seront remplis quand leurs valeurs seront dtermines.
La TDS pour un code court-circuit aura 2 attributs (hrits) : vrai qui donne l'tiquette de
l'instruction effectuer si l'expression est vraie, et faux l'tiquette si l'expression est fausse.
On aura toujours l'attribut (synthtis) code.
Donc :

La TDS pour un code court-circuit est :


Production

Rgle smantique
L.code=

"si" Id(1).place oprel.valeur Id(2).place "aller a" L.vrai


"aller a" L.faux

L.vrai = NouvEtiquette()
L.faux = NouvEtiquette()
I(1).suivant= I(0).suivant
I(2).suivant= I(0).suivant
I(0).code =

L.code
"(" L.vrai ")"

I(1).code
"aller a" I.suivant

"(" L.faux ")"

I(2).code

L(1).vrai = L(0).vrai
L(1).faux = NouvEtiquette()

Cours de Compilation

76

L(2).vrai = L(0).vrai
L(2).faux = L(0).faux
L(0).code =

L(1).code
"(" L(1).faux ")"

L(2).code

L(1).vrai = NouvEtiquette()
L(1).faux = L(0).faux
L(2).vrai = L(0).vrai
L(2).faux = L(0).faux
L(0).code =

L(1).code
"(" L(1).vrai ")"

L(2).code

L(1).vrai = L(0).faux
L(1).faux = L(0).vrai
L(0).code = L(1).code
L.code = "aller a" L.vrai
L.code = "aller a" L.faux
Avec cette TDS, l'expression si a<b ou c<d et e<f alors ... sera traduite par

(L1)
(l2)
(Lvrai)
(Lfaux)
(Lsuiv)

si a<b aller a Lvrai


aller a L1
si c<d aller a L2
aller a Lfaux
si e<f aller a Lvrai
aller a Lfaux
...
...
aller a Lsuiv
...
...
...

On remarque que le code n'est pas optimal, la seconde instruction est inutile ici. Pour corriger
cela, il faudra optimiser le code produit.

9. Optimisation de code
Le terme "optimisation" est abusif parce qu'il est rarement garanti que le code rsultat soit le
meilleur possible.
On obtient une amlioration maximale pour un effort minimal si on peut identifier les parties
du programme les plus frquemment excutes (on laisse alors tomber les autres parties). En
effet, en gnral, dans la plupart des programmes, 90% du temps d'excution est pass dans
seulement 10% du code. Mais, of course, ce n'est pas vident de deviner quels sont les "points
chauds" du programme10.2.
Cours de Compilation

77

You might also like