Sie sind auf Seite 1von 194

1 Introduction Dans le sens le plus usuel du terme, la compilation est une transformation que lon fait subir `a un programmeecrit

dans un langage evolue pour le rendre ex ecutable. Fondamentalement, cest une traduction : un texte ecriten Pascal, C, Java, etc., exprime un algorithme et il sagit de produire un autre texte, sp eciant le memealgorithme dans le langage dune machine que nous cherchons `a programmer.En generalisant un peu, on peut dire que compiler cest lire une suite de caract`eres obeissant `a une certainesyntaxe, en construisant une (autre) representation de linformation que ces caract`eres expriment. De ce pointde vue, beaucoup doperations apparaissent comme etant de la compilation; `a la limite, la lecture dun nombre,quon obtient en C par une instruction comme : scanf("%f", &x); est dej`a de la compilation, puisquil sagit de lire des caract`eres constituant lecriture dune valeur selon lasyntaxe des nombres decimaux et de fabriquer une autre representation de la meme information, `a savoir savaleur num erique.Bien sur, les questions qui nous interesseront ici seront plus complexes que la simple lecture dun nombre.Mais il faut comprendre que le domaine dapplication des principes et methodes de lecriture de compilateurscontient bien dautres choses que la seule production de programmes executables. Chaque fois que vous aurez`a ecrire un programme lisant des expressions plus compliquees que des nombres vous pourrez tirer prot desconcepts, techniques et outils expliques dans ce cours. 1.1 Structure de principe dun compilateur La nature de ce qui sort dun compilateur est tr`es variable. Cela peut etre un programme executable pourun processeur physique, comme un Pentium III ou un G4, ou un chier de code pour une machine virtuelle,comme la machine Java, ou un code abstrait destine `a un outil qui en fera ulterieurement du code ex ecutable,ou encore le codage dun arbre representant la structure logique dun programme, etc.En entree dun compilateur on trouve toujours la meme chose : une suite de caract`eres, appelee le textesource 1 . Voici les phases dans lesquelles se decompose le travail dun compilateur, du moins dun point de vuelogique 2 (voyez la gure 1) : Analyse lexicale

Dans cette phase, les caract`eres isoles qui constituent le texte source sont regroupes pourformer des unites lexicales , qui sont les mots du langage.Lanalyse lexicale op`ere sous le controle de lanalyse syntaxique; elle apparat comme une sorte de fonctionde lecture amelioree , qui fournit un mot lors de chaque appel. Analyse syntaxique Alors que lanalyse lexicale reconnat les mots du langage, lanalyse syntaxique en re-connat les phrases. Le role principal de cette phase est de dire si le texte source appartient au langageconsidere, cest-`a-dire sil est correct relativement `a la grammaire de ce dernier. Analyse semantique La structure du texte source etant correcte, il sagit ici de verier certaines proprietessemantiques, cest-`a-dire relatives `a la signication de la phrase et de ses constituants : les identicateurs apparaissant dans les expressions ont-ils ete declares? les operandes ont-ils les types requis par les op erateurs? les operandes sont-ils compatibles? ny a-t-il pas des conversions `a inserer? les arguments des appels de fonctions ont-ils le nombre et le type requis?etc. Generation de code intermediaire Apr`es les phases danalyse, certains compilateurs ne produisent pas directement le code attendu en sortie, mais une representation intermediaire, une sorte de code pour unemachine abstraite. Cela permet de concevoir ind ependamment les premi`eres phases du compilateur (consti-tuant ce que lon appelle sa face avant ) qui ne dependent que du langage source compile et les derni`eresphases (formant sa face arri`ere ) qui ne dependent que du langage cible; lideal serait davoir plusieursfaces avant et plusieurs faces arri`ere quon pourrait assembler librement 3

. 1 Conseil : le texte source a probablement ete compose `a laide dun editeur de textes qui le montre sous forme de pages faitesde plusieurs lignes mais, pour ce que nous avons `a en faire ici, prenez lhabitude de limaginer comme sil etait ecrit sur un long etmince ruban, formant une seule ligne. 2 Cest une organisation logique; en pratique certaines de ces phases peuvent etre imbriquees, et dautres absentes. 3 De la sorte, avec n faces avant pour n langages source et m faces arri`ere correspondant `a m machines cibles, on disposeraitautomatiquement de n m compilateurs distincts. Mais cela reste, pour le moment, un fantasme dinformaticien. 3

gnration de code intermdiaire te

gnration du code finaloptimisation du codeanalyse smantique pos = posInit + vit * 60 id(1) aff id(2) add id(3) mul nbr(60) analyse lexicaleanalyse syntaxique id(1)id(3)id(2)aff addmulnbr(60)id(1)id(3)id(2)aff addRellemulRelleentVersRel 60 tmp1 <- EntVersRel(60)tmp2 <- mulRel(id(3), tmp1)tmp3 <- addRel(id(2), tmp2)id(1) <- tmp3tmp1 <- mulRel(id(3), 60.0)id(1) <- addRel(id(2), tmp1) MOVF id3, R0MULF #60.0, R0ADDF id2, R0MOVF R0, id11 pos2 posInit3 vit............... table de symboles Fig. 1 Phases logiques de la compilation dune instruction Optimisation du code Il sagit generalement ici de transformer le code an que le programme r esultantsexecute plus rapidement. Par exemple detecter linutilite de recalculer des expressions dont la valeur est dej`a connue, transporter `a lext erieur des boucles des expressions et sous-expressions dont les operandes ont la memevaleur `a toutes les iterations detecter, et supprimer, les expressions inutiles4

etc. Generation du code nal Cette phase, la plus impressionnante pour le neophyte, nest pas forcement la plusdicile `a realiser. Elle necessite la connaissance de la machine cible (r eelle, virtuelle ou abstraite), etnotamment de ses possibilites en mati`ere de registres, piles, etc. 2 Analyse lexicale Lanalyse lexicale est la premi`ere phase de la compilation. Dans le texte source, qui se presente comme unot de caract`eres, lanalyse lexicale reconnat des unites lexicales , qui sont les mots avec lesquels les phrasessont formees, et les presente `a la phase suivante, lanalyse syntaxique.Les principales sortes dunites lexicales quon trouve dans les langages de programmation courants sont : les caract`eres speciaux simples : + , = , etc. les caract`eres speciaux doubles : <= , ++ , etc. les mots-cles : if , while , etc. les constantes litterales :

123 , -5 , etc. et les identicateurs : i , vitesse_du_vent , etc.A propos dune unite lexicale reconnue dans le texte source on doit distinguer quatre notions importantes : l unite lexicale , representee generalement par un code conventionnel; pour nos dix exemples + , = , <= , ++ , if , while , 123 , -5 , i

et vitesse_du_vent , ce pourrait etre, respectivement 4 : PLUS , EGAL , INFEGAL , PLUSPLUS , SI , TANTQUE , NOMBRE , NOMBRE , IDENTIF , IDENTIF . le lex`eme , qui est la chane de caract`eres correspondante. Pour les dix exemples prec edents, les lex`emescorrespondants sont : "+"

, "=" , "<=" , "++" , "if" , "while" , "123" , "-5" , "i" et "vitesse_du_vent" eventuellement, un attribut , qui depend de lunite lexicale en question, et qui la compl`ete. Seules lesderni`eres des dix unites precedentes ont un attribut; pour un nombre, il sagit de sa valeur (123, 5);pour un identicateur, il sagit dun renvoi `a une table dans laquelle sont plac es tous les identicateursrencontres (on verra cela plus loin). le mod`ele qui sert `a specier lunite lexicale. Nous verrons ci-apr`es des moyens formels pour denir rigou-reusement les mod`eles; pour le moment nous nous contenterons de descriptions informelles comme : pour les caract`eres sp

eciaux simples et doubles et les mots reserves, le lex`eme et le mod`ele concident, le mod`ele dun nombre est une suite de chires, eventuellement precedee dun signe , le mod`ele dun identicateur est une suite de lettres, de chires et du caract`ere , commencant parune lettre .Outre la reconnaissance des unites lexicales, les analyseurs lexicaux assurent certaines taches mineures commela suppression des caract`eres de decoration (blancs, tabulations, ns de ligne, etc.) et celle des commentaires(gen eralement consideres comme ayant la meme valeur quun blanc), linterface avec les fonctions de lecture decaract`eres, `a travers lesquelles le texte source est acquis, la gestion des chiers et lachage des erreurs, etc. Remarque. La fronti`ere entre lanalyse lexicale et lanalyse syntaxique nest pas xe. Dailleurs, lanalyselexicale nest pas une obligation, on peut concevoir des compilateurs dans lesquels la syntaxe est denie `a partirdes caract`eres individuels. Mais les analyseurs syntaxiques quil faut alors ecrire sont bien plus complexes queceux quon obtient en utilisant des analyseurs lexicaux pour reconnatre les mots du langage.Simplicite et ecacite sont les raisons detre des analyseurs lexicaux. Comme nous allons le voir, les tech-niques pour reconnatre les unites lexicales sont bien plus simples et ecaces que les techniques pour verier lasyntaxe. 2.1 Expressions reguli`eres 2.1.1 Denitions Les expressions reguli`eres sont une importante notation pour specier formellement des mod`eles. Pour lesdenir correctement il nous faut faire leort dapprendre un peu de vocabulaire nouveau :Un alphabet est un ensemble de symboles. Exemples : {

0 , 1 } , { A,C,G,T } , lensemble de toutes les lettres,lensemble des chires, le code ASCII, etc. On notera que les caract`eres blancs (cest-`a-dire les espaces, les 4 Dans un analyseur lexical ecrit en C, ces codes sont des pseudo-constantes introduites par des directives #define 5

tabulations et les marques de n de ligne) ne font generalement pas partie des alphabets 5 .Une chane

(on dit aussi mot ) sur un alphabet est une sequence nie de symboles de . Exemples,respectivement relatifs aux alphabets precedents : 00011011 , ACCAGTTGAAGTGGACCTTT , Bonjour , 2001 . On note la chane vide, ne comportant aucun caract`ere.Un langage sur un alphabet est un ensemble de chanes construites sur . Exemples triviaux : , lelangage vide, { } , le langage reduit `a lunique chane vide. Des exemples plus interessants (relatifs aux alphabetsprecedents) : lensemble des nombres en notation binaire, lensemble des chanes ADN, lensemble des mots dela langue francaise, etc.Si x et y sont deux chanes, la

concatenation de x et y , notee xy , est la chane obtenue en ecrivant y immediatement apr`es x . Par exemple, la concatenation des chanes anti et moine est la chane antimoine . Si x est une chane, on denit x 0 = et, pour n> 0, x

n = x n 1 x = xx n 1 . On a donc x 1 = x , x 2 = xx , x 3 = xxx ,etc.Les

operations sur les langages suivantes nous serviront `a denir les expressions reguli`eres. Soient L et M deux langages, on denit :denomination notation denitionlunion de L et ML M { x | x L ou x M } la concatenation de L et M LM { xy

| x L et y M } la fermeture de Kleene de LL { x 1 x 2 ...x n | x i L, n N et n

0 } la fermeture positive de LL + { x 1 x 2 ...x n | x i L, n N et n> 0 } De la denition de LM on deduit celle de L

n = LL...L . Exemples. On se donne les alphabets L = { A,B,...Z,a,b,...z } , ensemble des lettres, et C = { 0 , 1 ,... 9 } ,ensemble des chires. En considerant quun caract`ere est la meme chose quune chane de longueur un, on peutvoir L et C comme des langages, formes de chanes de longueur un. Dans ces conditions : L

C est lensemble des lettres et des chires, LC est lensemble des chanes formees dune lettre suivie dun chire, L 4 est lensemble des chanes de quatre lettres, L est lensemble des chanes faites dun nombre quelconque de lettres; en fait partie, C + est lensemble des chanes de chires comportant au moins un chire, L ( L C ) est lensemble des chanes de lettres et chires commencant par une lettre. Expression reguli`ere. Soit un alphabet. Une expression reguli`ere r sur est une formule qui denitun langage

L ( r ) sur , de la mani`ere suivante :1. est une expression reguli`ere qui denit le langage { } 2. Si a , alors a est une expression reguli`ere qui denit le langage 6 { a } 3. Soient x et y deux expressions reguli`eres, denissant les langages L ( x ) et

L ( y ). Alors ( x ) | ( y ) est une expression reguli`ere denissant le langage L ( x ) L ( y ) ( x )( y ) est une expression reguli`ere denissant le langage L ( x ) L

( y ) ( x ) est une expression reguli`ere denissant le langage ( L ( x )) ( x ) est une expression reguli`ere denissant le langage L ( x )La derni`ere r`egle ci-dessus signie quon peut encadrer une expression r eguli`ere par des parenth`eses sanschanger le langage deni. Dautre part, les parenth`eses apparaissant dans les r`egles precedentes peuvent souventetre omises, en fonction des operateurs en presence : il sut de savoir que les op erateurs , concatenation et | sont associatifs `a gauche, et verient priorite (

) > priorite (concatenation) > priorite ( | )Ainsi, on peut ecrire lexpression reguli`ere oui au lieu de (o)(u)(i) et oui | non au lieu de (oui) | (non) ,mais on ne doit pas ecrire oui au lieu de (oui) . 5

Il en decoule que les unites lexicales, sauf mesures particuli`eres (apostrophes, quillemets, etc.), ne peuvent pas contenir descaract`eres blancs. Dautre part, la plupart des langages autorisent les caract`eres blancs entre les unites lexicales. 6 On prendra garde `a labus de langage quon fait ici, en employant la meme notation pour le caract`ere a , la chane a et lexpressionreguli`ere a . En principe, le contexte permet de savoir de quoi on parle. 6

Definitions reguli`eres. Les expressions reguli`eres se construisent `a partir dautres expressions r eguli`eres;cela am`ene `a des expressions passablement touues. On les all`ege en introduisant des denitions reguli`eres quipermettent de donner des noms `a certaines expressions en vue de leur r eutilisation. On ecrit donc

d 1 r 1 d 2 r 2 ... d n r n o`u chaque d i est une chane sur un alphabet disjoint de 7 , distincte de d 1 ,d 2 ,...d i

1 , et chaque r i uneexpression reguli`ere sur { d 1 ,d 2 ,...d i 1 } . Exemple. Voici quelques denitions reguli`eres, et notamment celles de identicateur et nombre , qui denissentles identicateurs et les nombres du langage Pascal : lettre A | B

| ... | Z | a | b | ... | z chire 0 | 1 | ... | 9 identicateur lettre ( lettre | chire

) chires chire chire fraction-opt . chires | exposant-opt ( E (+ | | ) chires ) | nombre chires fraction-opt exposant-opt Notations abregees.

Pour alleger certaines ecritures, on compl`ete la denition des expressions r eguli`eresen ajoutant les notations suivantes : soit x une expression reguli`ere, denissant le langage L ( x ); alors ( x ) + est une expression reguli`ere, quidenit le langage ( L ( x )) + , soit x une expression reguli`ere, denissant le langage L ( x ); alors ( x )? est une expression reguli`ere, quidenit le langage L ( x

) { } , si c 1 ,c 2 ,...c k sont des caract`eres, lexpressions reguli`ere c 1 | c 2 | ... | c k peut se noter [ c 1 c 2

...c k ] , `a linterieur dune paire de crochets comme ci-dessus, lexpression c 1 c 2 designe la sequence de tous lescaract`eres c tels que c 1 c c 2 .Les denitions de lettre et chire donnees ci-dessus peuvent donc se reecrire : lettre [ AZaz

] chire [ 09 ] 2.1.2 Ce que les expressions reguli`eres ne savent pas faire Les expressions reguli`eres sont un outil puissant et pratique pour denir les unites lexicales, cest-`a-direles constituants elementaires des programmes. Mais elles se pretent beaucoup moins bien `a la specication deconstructions de niveau plus eleve, car elles deviennent rapidement dune trop grande complexite.De plus, on demontre quil y a des chanes quon ne peut pas d ecrire par des expressions reguli`eres. Parexemple, le langage suivant (suppos e inni) { a , (a) , ((a)) , (((a))) , ... } ne peut pas etre deni par des expressions reguli`eres, car ces derni`eres ne permettent pas dassurer quil y a dansune expression de la forme (( ... ((a)) ...

)) autant de parenth`eses ouvrantes que de parenth`eses fermantes.On dit que les expressions reguli`eres ne savent pas compter .Pour specier ces structures equilibrees, si importantes dans les langages de programmation (penser auxparenth`eses dans les expressions arithmetiques, les crochets dans les tableaux, begin...end , { ... } , if...then... , etc.)nous ferons appel aux grammaires non contextuelles, expliquees `a la section 3.1. 7 On assure souvent la separation entre et les noms des denitions reguli`eres par des conventions typographiques. 7

2.2 Reconnaissance des unites lexicales Nous avons vu comment specier les unites lexicales; notre probl`eme maintenant est decrire un programmequi les reconnat dans le texte source. Un tel programme sappelle un analyseur lexical

.Dans un compilateur, le principal client de lanalyseur lexical est lanalyseur syntaxique. Linterface entre cesdeux analyseurs est une fonction int uniteSuivante(void) 8 , qui renvoie `a chaque appel lunite lexicale suivantetrouvee dans le texte source.Cela suppose que lanalyseur lexical et lanalyseur syntaxique partagent les denitions des constantes conven-tionnelles denissant les unites lexicales. Si on programme en C, cela veut dire que dans les chiers sources desdeux analyseurs on a inclus un chier dentete (chier .h ) comportant une serie de denitions comme 9 : #define IDENTIF 1#define NOMBRE 2#define SI 3#define ALORS 4#define SINON 5 etc.Cela suppose aussi que lanalyseur lexical et lanalyseur syntaxique partagent egalement une variable globalecontenant le lex`eme correspondant `a la derni`ere unite lexicale reconnue, ainsi quune variable globale contenantle (ou les) attribut(s) de lunite lexicale courante, lorsque cela est pertinent, et notamment lorsque lunite lexicaleest NOMBRE ou IDENTIF .On se donnera, du moins dans le cadre de ce cours, quelques r`egles du jeu supplementaires : lanalyseur lexical est glouton

: chaque lex`eme est le plus long possible 10 ; seul lanalyseur lexical acc`ede au texte source. Lanalyseur syntaxique nacquiert ses donnees dentreeautrement qu`a travers la fonction uniteSuivante ; lanalyseur lexical acquiert le texte source un caract`ere `a la fois . Cela est un choix que nous faisons ici;dautres choix auraient ete possibles, mais nous verrons que les langages qui nous interessent permettentde travailler de cette mani`ere. 2.2.1 Diagrammes de transition Pour illustrer cette section nous allons nous donner comme exemple le probl`eme de la reconnaissancedes unites lexicales INFEG , DIFF , INF , EGAL , SUPEG , SUP , IDENTIF , respectivement denies par les expressionsreguli`eres <= ,

<> , < , = , >= , > et lettre ( lettre | chire ) , lettre et chire ayant leurs denitions dej`a vues.Les diagrammes de transition sont une etape preparatoire pour la realisation dun analyseur lexical. Au furet `a mesure quil reconnat une unite lexicale, lanalyseur lexical passe par divers etats . Ces etats sont numeroteset representes dans le diagramme par des cercles.De chaque etat e

sont issues une ou plusieurs `eches etiquetees par des caract`eres. Une `eche etiquetee par c relie e `a letat e 1 dans lequel lanalyseur passera si, alors quil se trouve dans letat e , le caract`ere c est lu dansle texte source.Un etat particulier represente letat initial de lanalyseur; on le signale en en faisant lextremite dune `echeetiquetee debut .Des doubles cercles identient les etats naux, correspondant `a la reconnaissance compl`ete dune unitelexicale. Certains etats naux sont marques dune etoile : cela signie que la reconnaissance sest faite au prixde la lecture dun caract`ere au-del`a de la n du lex`eme 11 .Par exemple, la gure 2 montre les diagrammes traduisant la reconnaissance des unites lexicales INFEG , DIFF , INF , EGAL , SUPEG

, SUP et IDENTIF .Un diagramme de transition est dit non deterministe lorsquil existe, issues dun meme etat, plusieurs `echesetiquetees par le meme caract`ere, ou bien lorsquil existe des `eches etiquetees par la chane vide . Dans le cas 8 Si on a employe loutil lex pour fabriquer lanalyser lexical, cette fonction sappelle plutot yylex ; le lex`eme est alors pointe parla variable globale yytext et sa longueur est donnee par la variable globale yylen . Tout cela est explique `a la section 2.3. 9 Peu importent les valeurs numeriques utilisees, ce qui compte est quelles soient distinctes. 10 Cette r`egle est peu mentionnee dans la litterature, pourtant elle est fondamentale. Cest grace `a elle que 123 est reconnu comme un nombre, et non plusieurs, vitesseV ent

comme un seul identicateur, et force comme un identicateur, et non pas comme unmot reserve suivi dun identicateur. 11 Il faut etre attentif `a ce caract`ere, car il est susceptible de faire partie de lunit e lexicale suivante, surtout sil nest pas blanc. 8

123478650< =>= autre => autredbut ** rendre INFEGrendre DIFFrendre INFrendre EGALrendre SUPEGrendre SUP 9 10 * lettre autrelettrechiffre rendre IDENTIF Fig. 2 Diagramme de transition pour les operateurs de comparaison et les identicateurscontraire, le diagramme est dit deterministe. Il est clair que le diagramme de la gure 2 est deterministe.

Seulsles diagrammes deterministes nous interesseront dans le cadre de ce cours . 2.2.2 Analyseurs lexicaux programmes en dur Les diagrammes de transition sont une aide importante pour lecriture danalyseurs lexicaux. Par exemple,`a partir du diagramme de la gure 2 on peut obtenir rapidement un analyseur lexical reconnaissant les unites INFEG , DIFF , INF , EGAL , SUPEG , SUP et IDENTIF .Auparavant, nous apportons une leg`ere modication `a nos diagrammes de transition, an de permettre queles unites lexicales soient separees par un ou plusieurs blancs 12 . La gure 3 montre le (debut du) diagrammemodie 13 .

0 dbut < blanc => lettre ............ Fig. 3 Ignorer les blancs devant une unite lexicaleEt voici le programme obtenu : int uniteSuivante(void) {char c;c = lireCar(); /* etat = 0 */ 12 Nous appelons blanc une espace, un caract`ere de tabulation ou une marque de n de ligne 13 Notez que cela revient `a modier toutes les expressions reguli`eres, en remplacant <= par ( blanc ) <=

, < par ( blanc ) < ,etc. 9

while (estBlanc(c))c = lireCar();if (c == <) {c = lireCar(); /* etat = 1 */if (c == =)return INFEG; /* etat = 2 */else if (c == >)return DIFF; /* etat = 3 */else {delireCar(c); /* etat = 4 */return INF;}}else if (c == =)return EGAL; /* etat = 5 */else if (c == >) {c = lireCar(); /* etat = 6 */if (c == =)return SUPEG; /* etat

= 7 */else {delireCar(c); /* etat = 8 */return SUP;}}else if (estLettre(c)) {lonLex = 0; /* etat = 9 */lexeme[lonLex++] = c;c = lireCar();while (estLettre(c) || estChiffre(c)) {lexeme[lonLex++] = c;c = lireCar();}delireCar(c); /* etat = 10 */return IDENTIF;}else {delireCar(c);return NEANT; /* ou bien donner une erreur */}} Dans le programme precedent on utilise des fonctions auxiliaires, dont voici une version simple : int estBlanc(char c) {return c == || c == \t || c == \n;}int estLettre(char c) {return A <= c && c <= Z || a <= c && c <= z;}int estChiffre(char c) {return 0 <= c && c <= 9;} Note. On peut augmenter lecacite de ces fonctions, au detriment de leur securite dutilisation, en enfaisant des macros : #define estBlanc(c) ((c) == || (c) == \t || (c) == \n)#define estLettre(c) (A <= (c) && (c) <= Z || a <= (c) && (c) <= z)#define estChiffre(c) (0 <= (c) && (c) <= 9) 10

Il y plusieurs mani`eres de prendre en charge la restitution dun caract`ere lu en trop (notre fonction delireCar ).Si on dispose de la biblioth`eque standard C on peut utiliser la fonction ungetc : void delireCar(char c) {ungetc(c, stdin);}char lireCar(void) {return getc(stdin);} Une autre mani`ere de faire permet de se passer de la fonction ungetc . Pour cela, on g`ere une variable globalecontenant, quand il y a lieu, le caract`ere lu en trop (il ny a jamais plus d un caract`ere lu en trop). Declaration : int carEnAvance = -1; avec cela nos deux fonctions deviennent

void delireCar(char c) {carEnAvance = c;}char lireCar(void) {char r;if (carEnAvance >= 0) {r = carEnAvance;carEnAvance = -1;}elser = getc(stdin);return r;} Reconnaissance des mots reserves. Les mots reserves appartiennent au langage deni par lexpressionreguli`ere lettre ( lettre | chire ) , tout comme les identicateurs. Leur reconnaissance peut donc se traiter de deuxmani`eres : soit on incorpore les mots reserves au diagrammes de transition, ce qui permet dobtenir un analyseurtr`es ecace, mais au prix dun travail de programmation plus important, car les diagrammes de transitiondeviennent tr`es volumineux 14 , soit on laisse lanalyseur traiter de la meme mani`ere les mots reserves et les identicateurs puis, quand lareconnaissance dun identicateur-ou-mot-reserve est terminee, on recherche le lex`eme dans une tablepour determiner sil sagit dun identicateur ou dun mot reserve.Dans les analyseurs lexicaux en dur on utilise souvent la deuxi`eme methode, plus facile `a programmer.On se donne donc une table de mots reserves : struct {char *lexeme;int uniteLexicale;} motRes[] = {{ "si", SI },{ "alors", ALORS },{ "sinon", SINON },...};int nbMotRes = sizeof motRes / sizeof motRes[0];

puis on modie de la mani`ere suivante la partie concernant les identicateurs de la fonction uniteSuivante : ...else if (estLettre(c)) {lonLex = 0; /* etat = 9 */lexeme[lonLex++] = c; 14 Nous utiliserons cette solution quand nous etudierons loutil lex , `a la section 2.3 11

c = lireCar();while (estLettre(c) || estChiffre(c)) {lexeme[lonLex++] = c;c = lireCar();}delireCar(c); /* etat = 10 */lexeme[lonLex] = \0;for (i = 0; i < nbMotRes; i++)if (strcmp(lexeme, motRes[i].lexeme) == 0)return motRes[i].uniteLexicale;return IDENTIF;}... 2.2.3 Automates nis Un automate ni est deni par la donnee de un ensemble ni detats E , un ensemble ni de symboles (ou alphabet ) dentree , une fonction de transition, transit : E

E , un etat 0 distingue, appele etat initial , un ensemble detats F , appeles etats dacceptation ou etats naux .Un automate peut etre represente graphiquement par un graphe o`u les etats sont gures par des cercles (lesetats naux par des cercles doubles) et la fonction de transition par des `eches etiquetees par des caract`eres :si transit ( e 1 ,c )= e 2 alors le graphe a une `eche etiquetee par le caract`ere c , issue de e 1 et aboutissant `a

e 2 .Un tel graphe est exactement ce que nous avons appele diagramme de transition `a la section 2.2.1 (voir lagure 2). Si on en reparle ici cest quon peut en d eduire un autre style danalyseur lexical, assez dierent de ceque nous avons appele analyseur programme en dur .On dit quun automate ni accepte une chane dentree s = c 1 c 2 ...c k si et seulement si il existe dans legraphe de transition un chemin joignant letat initial e 0 `a un certain etat nal e k , compose de k

`eches etiqueteespar les caract`eres c 1 ,c 2 ,...c k .Pour transformer un automate ni en un analyseur lexical il sura donc dassocier une unite lexicale `achaque etat nal et de faire en sorte que lacceptation dune chane produise comme resultat lunite lexicaleassociee `a letat nal en question.Autrement dit, pour programmer un analyseur il sura maintenant dimplementer la fonction transit cequi, puisquelle est denie sur des ensembles nis, pourra se faire par une table `a double entree. Pour lesdiagrammes des gures 2 et 3 cela donne la table suivante (les etats naux sont indiques en gras, leurs lignesont ete supprimees) : \t \n < = > lettre chire autre 00001 5 69 erreur erreur 1 4 4 4 4

234 4 4 6 8 8 8 8 78 8 8 8 9 10 10

10 10 10 10 99 10 On obtiendra un analyseur peut-etre plus encombrant que dans la premi`ere mani`ere, mais certainement plusrapide puisque lessentiel du travail de lanalyseur se reduira `a repeter betement laction etat = transit[etat][lireCar jusqu`a tomber sur un etat nal. Voici ce programme : #define NBR_ETATS ...#define NBR_CARS 256int transit[NBR_ETATS][NBR_CARS]; 12

int final[NBR_ETATS + 1];int uniteSuivante(void) {char caractere;int etat = etatInitial;while ( ! final[etat]) {caractere = lireCar();etat = transit[etat] [caractere];}if (final[etat] < 0)delireCar(caractere);return abs(final[etat]) - 1;}

Notre tableau final , indexe par les etats, est deni par final[ e ] = 0 si e nest pas un etat nal (vu comme un booleen, final[ e ] est faux), final[ e ] = U + 1 si e est nal, sans etoile et associe `a lunite lexicale U (en tant que booleen, final[ e ] est vrai, car les unites lexicales sont numerotees au moins `a partir de zero), final[

e ] = ( U + 1) si e est nal, etoile et associe `a lunite lexicale U (en tant que booleen, final[ e ] est encore vrai).Enn, voici comment les tableaux transit et final devraient etre initialises pour correspondre aux dia-grammes des gures 2 et 3 15 : void initialiser(void) {int i, j;for (i = 0; i < NBR_ETATS; i++) final[i] = 0;final[ 2] = INFEG + 1;final[ 3] = DIFF + 1;final[ 4] = - (INF + 1);final[ 5] = EGAL + 1;final[ 7] = SUPEG + 1;final[ 8] = - (SUP + 1);final[10] = - (IDENTIF + 1);final[NBR_ETATS] = ERREUR + 1;for (i = 0; i < NBR_ETATS; i++)for (j = 0; j < NBR_CARS; j+ +)transit[i][j] = NBR_ETATS;transit[0][ ] = 0;transit[0][\t] = 0;transit[0][\n] = 0;transit[0][<] = 1;transit[0][=] = 5;transit[0][>] = 6;for (j = A; j <= Z; j+ +) transit[0][j] = 9;for (j = a; j <= z; j++) transit[0][j] = 9;for (j = 0; j < NBR_CARS; j++) transit[1][j] = 4;transit[1][=] = 2;transit[1][>] = 3; 15 Nous avons ajoute un etat supplementaire, ayant le numero

NBR ETATS , qui correspond `a la mise en erreur de lanalyseur lexical,et une unite lexicale ERREUR pour signaler cela 13

for (j = 0; j < NBR_CARS; j++) transit[6][j] = 8;transit[6][=] = 7;for (j = 0; j < NBR_CARS; j++) transit[9][j] = 10;for (j = A; j <= Z; j++) transit[9][j] = 9;for (j = a; j <= z; j++) transit[9][j] = 9;for (j = 0; j <= 9; j++) transit[9][j] = 9;} 2.3 Lex , un generateur danalyseurs lexicaux Les analyseurs lexicaux bases sur des tables de transitions sont les plus ecaces... une fois la table detransition construite. Or, la construction de cette table est une operation longue et delicate.Le programme lex 16 fait cette construction automatiquement : il prend en entree un ensemble dexpressionsreguli`eres et produit en sortie le texte source dun programme C qui, une fois compile, est lanalyseur lexicalcorrespondant au langage deni par les expressions reguli`eres en question.Plus precisement (voyez la gure 4), lex produit un chier source C, nomme lex.yy.c , contenant la denitionde la fonction int yytext(void) , qui est lexacte homologue de notre fonction uniteSuivante de la section 2.2.2 :un programme appelle cette fonction et elle renvoie une unit e lexicale reconnue dans le texte source.

monProg source analyser rsultat del'analyse gcc lex.yy.cmonProg lex expressionsrgulires lex.yy.c autres modules Fig. 4 Utilisation de lex 2.3.1 Structure dun chier source pour lex En lisant cette section, souvenez-vous de ceci : lex ecrit un chier source C. Ce chier est fait de trois sortesdingredients : des tables garnies de valeurs calculees `a partir des expressions reguli`eres fournies, des morceaux de code C invariable, et notamment le moteur de lautomate, cest-`a-dire la boucle quirep`ete inlassablement etat transit ( etat,caractere ), des morceaux de code C, trouves dans le chier source lex

et recopies tels quels, `a lendroit voulu, dans lechier produit.Un chier source pour lex doit avoir un nom qui se termine par .l . Il est fait de trois sections, delimiteespar deux lignes reduites 17 au symbole %% : %{ declarations pour le compilateur C %} denitions reguli`eres 16 Lex est un programme gratuit quon trouve dans le syst`eme UNIX pratiquement depuis ses debuts. De nos jours on utilisesouvent ex , une version amelioree de lex qui appartient `a la famille GNU. 17 Notez que les symboles %% , %

{ et % } , quand ils apparaissent, sont ecrits au debut de la ligne, aucun blanc ne les pr ec`ede. 14

%% r`egles %% fonctions C supplementaires La partie declarations pour le compilateur C et les symboles %{ et %} qui lencadrent peuvent etre omis.Quand elle est presente, cette partie se compose de declarations qui seront simplement recopiees au debut duchier produit. En plus dautres choses, on trouve souvent ici une directive #include qui produit linclusion duchier .h contenant les denitions des codes conventionnels des unites lexicales ( INFEG , INF , EGAL , etc.).La troisi`eme section fonctions C supplementaires

peut etre absente egalement (le symbole %% qui la separede la deuxi`eme section peut alors etre omis). Cette section se compose de fonctions C qui seront simplementrecopiees `a la n du chier produit. Definitions reguli`eres. Les denitions reguli`eres sont de la forme identicateur expressionReguli`ere o`u identicateur est ecrit au debut de la ligne (pas de blancs avant) et separe de expressionReguli`ere par desblancs. Exemples : lettre [A-Za-z]chiffre [0-9] Les identicateurs ainsi denis peuvent etre utilises dans les r`egles et dans les denitions subsequentes; ilfaut alors les encadrer par des accolades. Exemples : lettre [A-Za-z]chiffre [0-9]alphanum {lettre}|{chiffre}%%{lettre}{alphanum}* { return IDENTIF; }{chiffre}+("."{chiffre}+)? { return NOMBRE; } R`egles. Les r`egles sont de la forme expressionReguli`ere { action } o`u expressionReguli`ere est ecrit au debut de la ligne (pas de blancs avant); action

est un morceau de codesource C, qui sera recopie tel quel, au bon endroit, dans la fonction yylex . Exemples : if { return SI; }then { return ALORS; }else { return SINON; } ... {lettre}{alphanum}* { return IDENTIF; } La r`egle expressionReguli`ere { action } signie `a la n de la reconnaissance dune chane du langage deni par expressionReguli`ere executez action .Le traitement par lex dune telle r`egle consiste donc `a recopier l action indiquee `a un certain endroit de la fonction yylex 18 . Dans les exemples ci-dessus, les actions etant toutes de la forme return

unite , leur signicationest claire : quand une chane du texte source est reconnue, la fonction yylex se termine en rendant commeresultat lunite lexicale reconnue. Il faudra appeler de nouveau cette fonction pour que lanalyse du texte sourcereprenne.A la n de la reconnaissance dune unite lexicale la chane acceptee est la valeur de la variable yytext , detype chane de caract`eres 19 . Un caract`ere nul indique la n de cette chane; de plus, la variable enti`ere yylen donne le nombre de ses caract`eres. Par exemple, la r`egle suivante reconnat les nombres entiers et en calcule lavaleur dans une variable yylval : 18 Cest parce que ces actions sont copiees dans une fonction quon a le droit dy utiliser linstruction return . 19 La variable yytext est declaree dans le chier produit par lex ; il vaut mieux ne pas chercher `a y faire reference dans dautreschiers, car il nest pas specie laquelle des declarations

extern char *yytext ou extern char yytext[] est pertinente. 15

(+|-)?[0-9]+ { yylval = atoi(yytext); return NOMBRE; } Expressions reguli`eres. Les expressions reguli`eres acceptees par lex sont une extension de celles denies`a la section 2.1. Les meta-caract`eres pr ecedemment introduits, cest-`a-dire (, ), | , , +,?, [, ] et `a linterieurdes crochets, sont legitimes dans lex et y ont le meme sens. En outre, on dispose de ceci (liste non exhaustive) : un point . signie un caract`ere quelconque, dierent de la marque de n de ligne, on peut encadrer par des guillemets un caract`ere ou une chane, pour eviter que les meta-caract`eres qui sytrouvent soient interpretes comme tels. Par exemple, "."

signie le caract`ere . (et non pas un caract`erequelconque), "" signie un blanc, "[a-z]" signie la chane [a-z] , etc.,Dautre part, on peut sans inconvenient encadrer par des guillemets un caract`ere ou une chane qui nenavaient pas besoin, lexpression [^ caract`eres ] signie : tout caract`ere nappartenant pas `a lensemble deni par [ caract`eres ] , lexpression ^ expressionReguli`ere signie : toute chane reconnue par expressionReguli`ere `a la conditionquelle soit au debut dune ligne, lexpression expressionReguli`ere $

signie : toute chane reconnue par expressionReguli`ere `a la conditionquelle soit `a la n dune ligne. Attention. Il faut etre tr`es soigneux en ecrivant les denitions et les r`egles dans le chier source lex . Eneet, tout texte qui nest pas exactement `a sa place (par exemple une d enition ou une r`egle qui ne commencentpas au debut de la ligne) sera recopi e dans le chier produit par lex . Cest un comportement voulu, parfois utile,mais qui peut conduire `a des situations confuses. Echo du texte analyse. Lanalyseur lexical produit par lex prend son texte source sur lentree standard 20 et lecrit, avec certaines modications, sur la sortie standard. Plus pr ecisement : tous les caract`eres qui ne font partie daucune chane reconnue sont recopies sur la sortie standard (ils traversent lanalyseur lexical sans en etre aectes), une chane acceptee au titre dune expression reguli`ere nest pas recopiee sur la sortie standard.Bien entendu, pour avoir les chanes acceptees dans le texte ecrit par lanalyseur il sut de le prevoir danslaction correspondante. Par exemple, la r`egle suivante reconnat les identicateurs et fait en sorte quils gurentdans le texte sorti : [A-Za-z][A-Za-z0-9]* { printf("%s", yytext); return IDENTIF; } Le texte

printf("%s", yytext) apparat tr`es frequemment dans les actions. On peut labreger en ECHO : [A-Za-z][A-Za-z0-9]* { ECHO; return IDENTIF; } 2.3.2 Un exemple complet Voici le texte source pour creer lanalyseur lexical dun langage comportant les nombres et les identicateursdenis comme dhabitude, les mots reserves si , alors , sinon , tantque , faire et rendre et les operateursdoubles == , != , <= et >=

. Les unites lexicales correspondantes sont respectivement representees par les constantesconventionnelles IDENTIF , NOMBRE , SI , ALORS , SINON , TANTQUE , FAIRE , RENDRE , EGAL , DIFF , INFEG , SUPEG ,denies dans le chier unitesLexicales.h .Pour les operateurs simples on decide que tout caract`ere non reconnu par une autre expression reguli`ere estune unite lexicale, et quelle est represent ee par son propre code ASCII

21 . Pour eviter des collisions entre cescodes ASCII et les unites lexicales nomm ees, on donne `a ces derni`eres des valeurs superieures `a 255.Fichier unitesLexicales.h : #define IDENTIF 256#define NOMBRE 257#define SI 258#define ALORS 259#define SINON 260#define TANTQUE 261 20 On peut changer ce comportement par defaut en donnant une valeur `a la variable yyin , avant le premier appel de yylex ; parexemple : yyin = fopen(argv[1], "r"); 21 Cela veut dire quon sen remet au client de lanalyseur lexical, cest-`a-dire lanalyseur syntaxique, pour separer les op erateursprevus par le langage des caract`eres speciaux qui nen sont pas. Ou, dit autrement, que nous transformons lerreur caract`ereillegal , `a priori lexicale, en une erreur syntaxique. 16

#define FAIRE 262#define RENDRE 263#define EGAL 264#define DIFF 265#define INFEG 266#define SUPEG 267extern int valNombre;extern char valIdentif[]; Fichier analex.l (le source pour lex ): %{#include "unitesLexicales.h"%}chiffre [0-9]lettre [A-Za-z]%%[" "\t\n] { ECHO; /* rien */ }{chiffre}+ { ECHO; valNombre = atoi(yytext); return NOMBRE; };si { ECHO; return SI; }alors { ECHO; return ALORS; }sinon { ECHO;

return SINON; }tantque { ECHO; return TANTQUE; }fairerendre { ECHO; return FAIRE; }{lettre}({lettre}|{chiffre})* {ECHO; strcpy(valIdentif, yytext); return IDENTIF; }"==" { ECHO; return EGAL; }"!=" { ECHO; return DIFF; }"<=" { ECHO; return INFEG; }">=" { ECHO; return SUPEG; }. { ECHO; return yytext[0]; }%%int valNombre;char valIdentif[256];int yywrap(void) {return 1;} Fichier principal.c (purement demonstratif) : #include <stdio.h>#include "unitesLexicales.h"int main(void) {int unite;do {unite = yylex();printf(" (unite: %d", unite);if (unite == NOMBRE)printf(" val: %d", valNombre);else if (unite == IDENTIF)printf(" %s", valIdentif);printf(")\n"); 17

} while (unite != 0);return 0;} Fichier essai.txt pour essayer lanalyseur : si x == 123 alorsy = 0; Creation dun executable et essai sur le texte precedent (rappelons que lex sappelle ex dans le mondeLinux); $ est le prompt du syst`eme : $ flex analex.l$ gcc lex.yy.c principal.c -o monprog$ monprog < essai.txtsi (unite: 258)x (unite: 256 x)== (unite: 264)123 (unite: 257 val: 123)alors (unite: 259)y (unite: 256 y)= (unite: 61)0 (unite: 257 0); (unite: 59)$ Note. La fonction yywrap qui apparat dans notre chier source pour

lex est appelee lorsque lanalyseurrencontre la n du chier `a analyser 22 . Outre deventuelles actions utiles dans telle ou telle application par-ticuli`ere, cette fonction doit rendre une valeur non nulle pour indiquer que le ot dentree est denitivementepuise, ou bien ouvrir un autre ot dentree. 2.3.3 Autres utilisations de lex Ce que le programme genere par lex fait necessairement, cest reconnatre les chanes du langage denipar les expressions reguli`eres donnees. Quand une telle reconnaissance est accomplie, on nest pas oblige derenvoyer une unite lexicale pour signaler la chose; on peut notamment declencher laction quon veut et ne pasretourner `a la fonction appelante . Cela permet dutiliser lex pour eectuer dautres sortes de programmes quedes analyseurs lexicaux.Par exemple, supposons que nous disposons de textes contenant des indications de prix en francs. Voicicomment obtenir rapidement un programme qui met tous ces prix en euros : %%[0-9]+("."[0-9]*)?[" "\t\n]*F(rancs|".")?[" "\t\n] {printf("%.2f EUR ", atof(yytext) / 6.55957); }%%int yywrap(void) {return 1;}int main(void) {yylex();} Le programme precedent exploite le fait que tous les caract`eres qui ne font pas partie dune chane reconnuesont recopies sur la sortie; ainsi, la plupart des caract`eres du texte donne seront recopies tels quels. Les chanesreconnues sont denies par lexpression reguli`ere [0-9]+("."[0-9]*)?[" "\t\n]*F(rancs|".")?[" "\t\n] cette expression se lit, successivement : 22 Dans certaines versions de lex une version simple de la fonction

yywrap , reduite `a { return 1; } , est fournie et on na pas `asen occuper. 18

une suite dau moins un chire, eventuellement, un point suivi dun nombre quelconque de chires, eventuellement, un nombre quelconque de blancs (espaces, tabulations, ns de ligne), un F majuscule obligatoire, eventuellement, la chane rancs ou un point (ainsi, F , F. et Francs sont tous trois acceptes), enn, un blanc obligatoire.Lorsquune chane saccordant `a cette syntaxe est reconnue, comme

99.50 Francs , la fonction atof obtient la valeur que [le debut de] cette chane represente. Il sut alors de mettre dans le texte de sortie leresultat de la division de cette valeur par le taux adequat; soit, ici, 15.17 EUR . 3 Analyse syntaxique 3.1 Grammaires non contextuelles Les langages de programmation sont souvent denis par des r`egles recursives , comme : on a une expression en ecrivant successivement un terme, + et une expression ou on obtient une instruction en ecrivant `a lasuite

si , une expression, alors , une instruction et, eventuellement, sinon et une instruction . Les grammairesnon contextuelles sont un formalisme particuli`erement bien adapte `a la description de telles r`egles. 3.1.1 Denitions Une grammaire non contextuelle , on dit parfois grammaire BNF (pour Backus-Naur form 23 ), est un qua-druplet G =( V T ,V N ,S 0 ,P

) forme de un ensemble V T de symboles terminaux , un ensemble V N de symboles non terminaux , un symbole S 0 V N particulier, appele symbole de depart ou axiome , un ensemble P de productions , qui sont des r`egles de la forme S S

1 S 2 ...S k avec S V N et S i V N V T Compte tenu de lusage que nous en faisons dans le cadre de lecriture de compilateurs, nous pouvonsexpliquer ces elements de la mani`ere suivante :1. Les symboles terminaux sont les symboles elementaires qui constituent les chanes du langage, les phrases.Ce sont donc les unites lexicales, extraites du texte source par lanalyseur lexical (il faut se rappeler quelanalyseur syntaxique ne connat pas les caract`eres dont le texte source est fait, il ne voit ce dernier quecomme une suite dunites lexicales).2. Les symboles non terminaux sont des variables syntaxiques designant des ensembles de chanes de symbolesterminaux.3. Le symbole de depart est un symbole non terminal particulier qui designe le langage en son entier.4. Les productions peuvent etre interpretees de deux mani`eres : comme des r`egles decriture

(on dit plutot de reecriture ), permettant dengendrer toutes les chanescorrectes. De ce point de vue, la production S S 1 S 2 ...S k se lit pour produire un S correct [sous-entendu : de toutes les mani`eres possibles] il fait produire un S 1 [de toutes les mani`eres possibles] suividun S 2 [de toutes les mani`eres possibles] suivi dun ... suivi dun S k [de toutes les mani`eres possibles] , comme des r`egles danalyse

, on dit aussi reconnaissance . La production S S 1 S 2 ...S k se lit alors pour reconnatre un S , dans une suite de terminaux donnee, il faut reconnatre un S 1 suivi dun S 2 suivi dun ... suivi dun S k La denition dune grammaire devrait donc commencer par lenumeration des ensembles V T

et V N . Enpratique on se limite `a donner la liste des productions, avec une convention typographique pour distinguer lessymboles terminaux des symboles non terminaux, et on convient que : V T est lensemble de tous les symboles terminaux apparaissant dans les productions, V N est lensemble de tous les symboles non terminaux apparaissant dans les productions, le symbole de depart est le membre gauche de la premi`ere production.En outre, on all`ege les notations en decidant que si plusieurs productions ont le meme membre gauche 23 J. Backus a invente le langage FORTRAN en 1955, P. Naur le langage Algol en 1963. 19

S S 1 , 1 S 1 , 2

... S 1 ,k 1 S S 2 , 1 S 2 , 2 ... S 2 ,k 2 ... S S n, 1 S n, 2 ... S

n,k n alors on peut les noter simplement S S 1 , 1 S 1 , 2 ... S 1 ,k 1 | S 2 , 1 S 2 , 2 ... S 2

,k 2 | ... | S n, 1 S n, 2 ... S n,k n Dans les exemples de ce document, la convention typographique sera la suivante : une chane en italiques represente un symbole non terminal, une chane en caract`eres teletype ou "entre guillemets" , represente un symbole terminal.A titre dexemple, voici la grammaire G 1 denissant le langage dont les chanes sont les expressions arith-metiques formees avec des nombres, des identicateurs et les deux operateurs + et *

, comme 60 * vitesse+ 200 . Suivant notre convention, les symboles non terminaux sont expression , terme et facteur ; le symbolede depart est expression : expression expression "+" terme | termeterme terme "*" facteur | facteur ( G

1 ) facteur nombre | identificateur | "(" expression ")" 3.1.2 Derivations et arbres de derivation Derivation. Le processus par lequel une grammaire denit un langage sappelle derivation . Il peut etreformalise de la mani`ere suivante :Soit G =( V T ,V N ,S 0 ,P ) une grammaire non contextuelle, A

V N un symbole non terminal et ( V T V N ) une suite de symboles, tels quil existe dans P une production A . Quelles que soient les suites de symboles et , on dit que A se derive en une etape en la suite

ce qui secrit A Cette denition justie la denomination grammaire non contextuelle (on dit aussi grammaire independantedu contexte ou context free ). En eet, dans la suite A les chanes et sont le contexte du symbole A . Ceque cette denition dit, cest que le symbole A se reecrit dans la chane quel que soit le contexte , dans lelequel A

apparat.Si 0 1 n on dit que 0 se derive en n en n etapes, et on ecrit 0 n n .Enn, si se derive en

en un nombre quelconque, eventuellement nul, detapes on dit simplement que se derive en et on ecrit .Soit G = { V T ,V N ,S 0 ,P } une grammaire non contextuelle; le langage engendre par G est lensemble deschanes de symboles terminaux qui derivent de S 0 : L

( G )= w V T | S 0 w Si w L ( G ) on dit que w est une phrase de G

. Plus generalement, si ( V T V N ) est tel que S 0 alors on dit que est une proto-phrase de G . Une proto-phrase dont tous les symboles sont terminaux est unephrase.Par exemple, soit encore la grammaire G 1 : expression

expression "+" terme | termeterme terme "*" facteur | facteur ( G 1 ) facteur nombre | identificateur | "(" expression ")" et considerons la chane "60 * vitesse + 200" qui, une fois lue par lanalyseur lexical, se presente ainsi :

w =( nombre "*" identificateur "+" nombre ). Nous avons expression w , cest `a dire w L ( G 1 ); eneet, nous pouvons exhiber la suite de derivations en une etape :20

expression expression "+" terme terme "+" terme

terme "*" facteur "+" terme facteur "*" facteur "+" terme nombre "*" facteur "+" terme nombre "*" identificateur "+" terme nombre "*" identificateur "+" facteur nombre "*" identificateur "+" nombre Derivation gauche. La derivation precedente est appelee une derivation gauche

car elle est enti`erementcomposee de derivations en une etape dans lesquelles `a chaque fois cest le non-terminal le plus `a gauche qui estreecrit. On peut denir de meme une derivation droite , o`u `a chaque etape cest le non-terminal le plus `a droitequi est reecrit. Arbre de derivation. Soit w une chane de symboles terminaux du langage L ( G ); il existe donc unederivation telle que S 0 w . Cette derivation peut etre representee graphiquement par un arbre, appele arbrede derivation , deni de la mani`ere suivante : nombre "+" expressionexpression termetermefacteur terme "*" facteur facteur identificateur nombre60 * vitesse + 200 Fig. 5 Arbre de derivation la racine de larbre est le symbole de depart, les nuds interieurs sont etiquetes par des symboles non terminaux, si un nud interieur

e est etiquete par le symbole S et si la production S S 1 S 2 ...S k a ete utilisee pourderiver S alors les ls de e sont des nuds etiquetes, de la gauche vers la droite, par S 1 , S 2 ... S k , les feuilles sont etiquetees par des symboles terminaux et, si on allonge verticalement les branches delarbre (sans les croiser) de telle mani`ere que les feuilles soient toutes `a la meme hauteur, alors, lues de lagauche vers la droite, elles constituent la chane w

.Par exemple, la gure 5 montre larbre de derivation representant la d erivation donnee en exemple ci-dessus.On notera que lordre des derivations (gauche, droite) ne se voit pas sur larbre. 3.1.3 Qualites des grammaires en vue des analyseurs Etant donnee une grammaire G = { V T ,V N ,S 0 ,P } , faire lanalyse syntaxique dune chane w V T cestrepondre `a la question w appartient-elle au langage L ( G

)? . Parlant strictement, un analyseur syntaxique estdonc un programme qui nextrait aucune information de la chane analysee, il ne fait quaccepter (par d efaut)ou rejeter (en annoncant une erreurs de syntaxe) cette chane.En realit e on ne peut pas empecher les analyseurs den faire un peu plus car, pour prouver que w L ( G )il faut exhiber une derivation S 0 w , cest-`a-dire construire un arbre de derivation dont la liste des feuillesest w . Or, cet arbre de derivation est dej`a une premi`ere information extraite de la chane source, un debut de comprehension de ce que le texte signie.21

Nous examinons ici des qualites quune grammaire doit avoir et des defauts dont elle doit etre exempte pourque la construction de larbre de derivation de toute chane du langage soit possible et utile. Grammaires ambigues. Une grammaire est ambigue sil existe plusieurs derivations gauches di erentespour une meme chane de terminaux. Par exemple, la grammaire G 2 suivante est ambigue : expression expression "+" expression | expression "*" expression | facteur ( G 2 ) facteur nombre | identificateur |

"(" expression ")" En eet, la gure 6 montre deux arbres de derivation distincts pour la chane "2 * 3 + 10" . Ils correspondentaux deux derivations gauches distinctes : expression expression "+" expression expression "*" expression "+" expression facteur "*" expression "+" expression nombre "*" expression "+" expression

nombre "*" facteur "+" expression nombre "*" nombre "+" expression nombre "*" nombre "+" facteur nombre "*" nombre "+" nombre et expression expression "*" expression facteur "*" expression nombre "*" expression nombre "*"

expression "+" expression nombre "*" facteur "+" expression nombre "*" nombre "+" expression nombre "*" nombre "+" facteur nombre "*" nombre "+" nombre nombre expr facteur nombre expr facteur "*"nombre expr facteur nombre expr facteur "+"nombre facteur expr expr "+" expr

nombre facteur expr

expr "*"2 * 3 + 10 2 * 3 + 10 expr Fig. 6 Deux arbres de derivation pour la meme chaneDeux grammaires sont dites equivalentes si elles engendrent le meme langage. Il est souvent possible deremplacer une grammaire ambigue par une grammaire non ambigue equivalente, mais il ny a pas une methodegenerale pour cela. Par exemple, la grammaire G 1 est non ambigue et equivalente `a la grammaire G 2 ci-dessus. Grammaires recursives`a gauche. Une grammaire est recursive `a gauche sil existe un non-terminal A et une derivation de la forme A A , o`u

est une chane quelconque. Cas particulier, on dit quon a unerecursivite `a gauche simple si la grammaire poss`ede une production de la forme A A .La recursivite `a gauche ne rend pas une grammaire ambigue, mais empeche lecriture danalyseurs pour cettegrammaire, du moins des analyseurs descendants 24 .Par exemple, la grammaire G 1 de la section 3.1.1 est recursive `a gauche, et meme simplement : expression expression "+" terme | termeterme terme "*" facteur | facteur ( G

1 ) facteur nombre | identificateur | "(" expression ")" Il existe une methode pour obtenir une grammaire non recursive `a gauche equivalente `a une grammairedonnee. Dans le cas de la recursivite `a gauche simple, cela consiste `a remplacer une production telle que 24 La question est un peu prematuree ici, mais nous verrons quun analyseur descendant est un programme compose de fonctionsdirectement deduites des productions. Une production A ... donne lieu `a une fonction reconnaitre A dont le corps est faitdappels aux fonctions reconnaissant les symboles du membre droit de la production. Dans le cas dune production A A on seretrouve donc avec une fonction reconnaitre A

qui commence par un appel de reconnaitre A . Bonjour la recursion innie... 22

A A | par les deux productions 25 A A

A A | En appliquant ce procede `a la grammaire G 1 on obtient la grammaire G 3 suivante : expression terme n expression n expression "+" terme n expression | terme facteur n terme ( G

3 ) n terme "*" facteur n terme | facteur nombre | identificateur | "(" expression ")" A propos des -productions. La transformation de grammaire montree ci-dessus a introduit des produc-tions avec un membre droit vide, ou -productions. Si on ne prend pas de disposition particuli`ere, on aura unprobl`eme pour lecriture dun analyseur, puisquune production telle que n expression "+" terme n expression

| impliquera notamment que une mani`ere de reconnatre une n expression consiste `a ne rien reconnatre , cequi est possible quelle que soit la chane dentree; ainsi, notre grammaire semble devenir ambigue. On resout ceprobl`eme en imposant aux analyseurs que nous ecrirons la r`egle de comportement suivante : dans la derivation dun non-terminal, une -production ne peut etre choisie que lorsquaucune autre production nest applicable. Dans lexemple precedent, cela donne : si la chane dentree commence par + alors on doit necessairementchoisir la premi`ere production. Factorisation`a gauche. Nous cherchons `a ecrire des analyseurs predictifs . Cela veut dire qu`a toutmoment le choix entre productions qui ont le meme membre gauche doit pouvoir se faire, sans risque derreur,en comparant le symbole courant de la chane `a analyser avec les symboles susceptibles de commencer lesderivations des membres droits des productions en comp etition.Une grammaire contenant des productions comme A

1 | 2 viole ce principe car lorsquil faut choisir entre les productions A 1 et A 2 le symbole courant estun de ceux qui peuvent commencer une derivation de , et on ne peut pas choisir `a coup sur entre 1 et 2 .Une transformation simple, appelee factorisation `a gauche , corrige ce defaut (si les symboles susceptibles decommencer une reecriture de 1 sont distincts de ceux pouvant commencer une reecriture de

2 ): A A A 1 | 2 Exemple classique. Les grammaires de la plupart des langages de programmation denissent ainsi linstruc-tion conditionnelle : instr si si expr alors instr | si expr alors instr sinon

instr Pour avoir un analyseur predictif il faudra operer une factorisation `a gauche : instr si si expr alors instr n instr si n instr si sinon instr | Comme precedemment, lapparition dune -production semble rendre ambigue la grammaire. Plus precisement,la question suivante se pose : ny a-t-il pas deux arbres de derivation possibles pour la chane 26 : si alors si alors sinon Nous lavons dej`a dit, on l`eve cette ambigute en imposant que la

-production ne peut etre choisie quesi aucune autre production nest applicable. Autrement dit, si, au moment o`u lanalyseur doit deriver le non-terminal n instr si , la chane dentree commence par le terminal sinon , alors la production n instr si sinon instr doit etre appliquee. La gure 7 montre larbre de derivation obtenu pour la chane precedente. 25 Pour se convaincre de lequivalence de ces deux grammaires il sut de sapercevoir que, si et sont des symboles terminaux,alors elles engendrent toutes deux le langage { ,,,,... } 26 Autre formulation de la meme question : sinon

se rattache-t-il `a la premi`ere ou `a la deuxi`eme instruction si ? 23

si expr alors instr fin_instr_siinstr_si si expr alors instr fin_instr_si sinon

instr Fig. 7 Arbre de derivation pour la chane si alors si alors sinon 3.1.4 Ce que les grammaires non contextuelles ne savent pas faire Les grammaires non contextuelles sont un outil puissant, en tout cas plus puissant que les expressionsreguli`eres, mais il existe des langages (pratiquement tous les langages de programmation, excusez du peu...!)quelles ne peuvent pas decrire compl`etement.On demontre par exemple que le langage L = wcw | w ( a | b )

, o`u a , b et c sont des terminaux, ne peutpas etre decrit par une grammaire non contextuelle. L est fait de phrases comportant deux chanes de a et b identiques, separees par un c , comme ababcabab . Limportance de cet exemple provient du fait que L modeliselobligation, quont la plupart des langages, de verier que les identicateurs apparaissant dans les instructionsont bien ete prealablement d eclares (la premi`ere occurrence de w dans wcw correspond `a la declaration dunidenticateur, la deuxi`eme occurrence de w

`a lutilisation de ce dernier).Autrement dit, lanalyse syntaxique ne permet pas de verier que les identicateurs utilises dans les pro-grammes font lobjet de d eclarations prealables. Ce probl`eme doit necessairement etre remis `a une phaseulterieure danalyse semantique. 3.2 Analyseurs descendants Etant donnee une grammaire G =( V T ,V N ,S 0 ,P ), analyser une chane de symboles terminaux w V T cestconstruire un arbre de derivation prouvant que S 0 w .Les grammaires des langages que nous cherchons `a analyser ont un ensemble de proprietes quon resume endisant que ce sont des grammaires LL(1). Cela signie quon peut en ecrire des analyseurs : lisant la chane source de la gauche vers la droite (gauche = left, cest le premier L), cherchant `a construire

une derivation gauche (cest le deuxi`eme L), dans lesquels un seul symbole de la chane source est accessible `a chaque instant et permet de choisir,lorsque cest necessaire, une production parmi plusieurs candidates (cest le 1 de LL(1)). A propos du symbole accesible. Pour reechir au fonctionnement de nos analyseurs il est utile dimaginerque la chane source est ecrite sur un ruban delant derri`ere une fenetre, de telle mani`ere quun seul symboleest visible `a la fois; voyez la gure 8. Un m ecanisme permet de faire avancer jamais reculer le ruban, pourrendre visible le symbole suivant. if unit courante avancer chane sourcedjexaminechane sourcerestant examiner Fig. 8 Fenetre `a symboles terminaux24

Lorsque nous programmerons eectivement des analyseurs, cette machine `a symboles terminaux ne serarien dautre que lanalyseur lexical prealablement ecrit; le symbole visible `a la fenetre sera represente par unevariable uniteCourante , et loperation

faire avancer le ruban se traduira par uniteCourante = uniteSuivante() (ou bien, si cest lex qui a ecrit lanalyseur lexical, uniteCourante = yylex() ). 3.2.1 Principe Analyseur descendant. Un analyseur descendant construit larbre de derivation de la racine (le symbolede depart de la grammaire) vers les feuilles (la chane de terminaux).Pour en decrire sch ematiquement le fonctionnement nous nous donnons une fenetre `a symboles terminauxcomme ci-dessus et une pile de symboles, cest-`a-dire une sequence de symboles terminaux et non terminaux`a laquelle on ajoute et on enl`eve des symboles par une meme extremite, en loccurrence lextremite de gauche(cest une pile couchee `a lhorizontale, qui se remplit de la droite vers la gauche). Initialisation. Au depart, la pile contient le symbole de depart de la grammaire et la fenetre montre lepremier symbole terminal de la chane dentree. Iteration. Tant que la pile nest pas vide, repeter les operations suivantes si le symbole au sommet de la pile (c.-`a-d. le plus `a gauche) est un terminal

si le terminal visible `a la fenetre est le meme symbole , alors depiler le symbole au sommet de la pile et faire avancer le terminal visible `a la fenetre, sinon, signaler une erreur (par exemple acher attendu ); si le symbole au sommet de la pile est un non terminal S sil y a une seule production S S 1 S 2 ...S k ayant S pour membre gauche alors depiler S et empiler S 1 S 2

...S k `a la place, sil y a plusieurs productions ayant S pour membre gauche, alors dapr`es le terminalvisible `a la fenetre, sans faire avancer ce dernier, choisir lunique production S S 1 S 2 ...S k pouvant convenir, depiler S et empiler S 1 S 2 ...S k . Terminaison. Lorsque la pile est vide si le terminal visible `a la fenetre est la marque qui indique la n de la chane dentree alors lanalysea reussi : la chane appartient au langage engendre par la grammaire, sinon, signaler une erreur (par exemple, acher

caract`eres inattendus `a la suite dun textecorrect ).A titre dexemple, voici la reconnaissance par un tel analyseur du texte "60 * vitesse + 200" avec lagrammaire G 3 de la section 3.1.3 : expression terme n expression n expression "+" terme n expression | terme facteur n terme ( G 3 ) n terme "*" facteur n terme |

facteur nombre | identificateur | "(" expression ")" La chane dentree est donc (nombre "*" identif "+" nombre) . Les etats successifs de la pile et de lafenetre sont les suivants :25

fenetre pile nombre expression nombre terme n expression nombre facteur n terme n expression

nombre nombre n terme n expression "*" n terme n expression "*" "*" facteur n terme n expression identificateur facteur n terme n expression identificateur identificateur n terme n expression "+" n terme n expression "+" n expression "+" n expression "+" "+" terme n expression nombre terme n expression nombre facteur n expression nombre nombre n expression n expression

Lorsque la pile est vide, la fenetre exhibe , la marque de n de chane. La chane donnee appartient doncbien au langage considere. 3.2.2 Analyseur descendant non recursif Nous ne developperons pas cette voie ici, mais nous pouvons remarquer quon peut realiser des programmesiteratifs qui implantent lalgorithme explique `a la section precedente.La plus grosse diculte est le choix dune production chaque fois quil faut deriver un non terminal quiest le membre gauche de plusieurs productions de la grammaire. Comme ce choix ne depend que du terminalvisible `a la fenetre, on peut le faire, et de mani`ere tr`es ecace, `a laide dune table `a double entree, appelee tabledanalyse , calculee `a lavance, representant une fonction Choix : V N V T P qui `a un couple ( S, ) formedun non terminal (le sommet de la pile) et un terminal (le symbole visible `a la fenetre) associe la productionquil faut utiliser pour deriver S .Pour avoir un analyseur descendant non recursif il sut alors de se donner une fenetre `a symboles terminaux(cest-`a-dire un analyseur lexical), une pile de

symboles comme explique `a la section precedente, une tabledanalyse comme explique ici et un petit programme qui implante lalgorithme de la section precedente, danslequel la partie choisir la production... se resume `a une consultation de la table P = Choix ( S, ).En denitive, un analyseur descendant est donc un couple forme dune table dont les valeurs sont intimementliees `a la grammaire analysee et dun programme tout `a fait independant de cette grammaire. 3.2.3 Analyse par descente recursive A loppose du precedent, un analyseur par descente recursive est un type danalyseur descendant dans lequelle programme de lanalyseur est etroitement lie `a la grammaire analysee. Voici les principes de lecriture duntel analyseur :1. Chaque groupe de productions ayant le meme membre gauche S donne lieu `a une fonction void recon-naitre S(void) , ou plus simplement void S(void) . Le corps de cette fonction se deduit des membres droitsdes productions en question, comme explique ci-apr`es.2. Lorsque plusieurs productions ont le meme membre gauche, le corps de la fonction correspondante estune conditionnelle (instruction if

) ou un aiguillage (instruction switch ) qui, dapr`es le symbole terminalvisible `a la fenetre, selectionne lexecution des actions correspondant au membre droit de la productionpertinente. Dans cette selection, le symbole visible `a la fenetre nest pas modie.3. Une sequence de symboles S 1 S 2 ...S n dans le membre droit dune production donne lieu, dans la fonctioncorrespondante, `a une sequence dinstructions traduisant les actions reconnaissance de S 1 , recon-naissance de S 2 , ...

reconnaissance de S n .26

4. Si S est un symbole non terminal, laction

reconnaissance de S se reduit `a lappel de fonction recon-naitre S() .5. Si est un symbole terminal, laction reconnaissance de consiste `a considerer le symbole terminalvisible `a la fenetre et sil est egal `a , faire passer la fenetre sur le symbole suivant 27 , sinon, annoncer une erreur (par exemple, acher attendu ).Lensemble des fonctions ecrites selon les prescriptions precedentes forme lanalyseur du langage considere.Linitialisation de lanalyseur consiste `a positionner la fenetre sur le premier terminal de la chane dentree.On lance lanalyse en appellant la fonction associee au symbole de depart de la grammaire. Au retour de cettefonction si la fenetre `a terminaux montre la marque de n de chane, lanalyse a reussi, sinon la chane est erronee 28 (on peut par exemple acher le message

caract`eres illegaux apr`es uneexpression correcte ).Exemple. Voici encore la grammaire G 3 de la section 3.1.3 : expression terme n expression n expression "+" terme n expression | terme facteur n terme ( G 3 ) n terme "*" facteur n terme |

facteur nombre | identificateur | "(" expression ")" et voici lanalyseur par descente recursive correspondant : void expression(void) {terme();fin_expression();}void fin_expression(void) {if (uniteCourante == +) {terminal(+);terme();fin_expression();}else/* rien */;}void terme(void) {facteur();fin_terme();}void fin_terme(void) {if (uniteCourante == *) {terminal(*);facteur();fin_terme();}else/* rien */;}void facteur(void) {if (uniteCourante == NOMBRE)terminal(NOMBRE); 27 Notez que cest ici le seul endroit, dans cette description, o`u il est indique de faire avancer la fenetre. 28 Une autre attitude possible -on la vue adoptee par certains compilateurs de Pascal- consiste `a ne rien verier au retour dela fonction associee au symbole de depart. Cela revient `a considerer que, si le texte source comporte un programme correct, peuimportent les eventuels caract`eres qui pourraient suivre. 27

else if (uniteCourante == IDENTIFICATEUR)terminal(IDENTIFICATEUR);else {terminal(();expression();terminal());}} La reconnaissance dun terminal revient frequemment dans un analyseur. Nous en avons fait une fonctionseparee (on suppose que erreur est une fonction qui ache un message et termine le programme) :

void terminal(int uniteVoulue) {if (uniteCourante == uniteVoulue)lireUnite();elseswitch (uniteVoulue) {case NOMBRE:erreur("nombre attendu");case IDENTIFICATEUR:erreur("identificateur attendu");default:erreur("%c attendu", uniteVoulue);}} Note. Nous avons ecrit le programme precedent en appliquant systematiquement les r`egles donnees plushaut, obtenant ainsi un analyseur correct dont la structure re`ete la grammaire donnee. Mais il nest pasinterdit de pratiquer ensuite certaines simplications, ne serait-ce pour rattraper certaines maladresses de notredemarche. Lappel generalise de la fonction terminal , notamment, est `a lorigine de certains test redondants.Par exemple, la fonction n expression commence par les deux lignes if (uniteCourante == +) {terminal(+);... si on developpe lappel de terminal , la maladresse de la chose devient evidente if (uniteCourante == +) {if (uniteCourante == +)lireUnite();... Une version plus raisonnable des fonctions n expression et facteur serait donc : void fin_expression(void) {if (uniteCourante == +) {lireUnite();terme();fin_expression();}else/* rien */;}...void facteur(void) {if (uniteCourante == NOMBRE)lireUnite();else if (uniteCourante == IDENTIFICATEUR)lireUnite();else { 28

terminal(();expression();terminal());}}

Comme elle est ecrite ci-dessus, la fonction facteur aura tendance `a faire passer toutes les erreurs par lediagnostic ( attendu , ce qui risque de manquer d`a propos. Une version encore plus raisonnable de cettefonction serait void facteur(void) {if (uniteCourante == NOMBRE)lireUnite();else if (uniteCourante == IDENTIFICATEUR)lireUnite();else if (uniteCourante == () {lireUnite(();expression();terminal());}elseerreur("nombre, identificateur ou ( attendus ici");} 3.3 Analyseurs ascendants 3.3.1 Principe Comme nous lavons dit, etant donnees une grammaire G = { V T ,V N ,S 0 ,P } et une chane w V

T , lebut de lanalyse syntaxique est la construction dun arbre de derivation qui prouve w L ( G ). Les methodesdescendantes construisent cet arbre en partant du symbole de d epart de la grammaire et en allant vers lachane de terminaux. Les methodes ascendantes, au contraire, partent des terminaux qui constituent la chanedentree et vont vers le symbole de depart.Le principe general des methodes ascendantes est de maintenir une pile de symboles 29 dans laquelle sontempiles (lempilement sappelle ici decalage ) les terminaux au fur et `a mesure quils sont lus, tant que lessymboles au sommet de la pile ne sont pas le membre droit dune production de la grammaire. Si les k symbolesdu sommet de la pile constituent le membre droit dune production alors ils peuvent etre depiles et remplacespar le membre gauche de cette production (cette operation sappelle reduction

). Lorsque dans la pile il ny aplus que le symbole de depart de la grammaire, si tous les symboles de la chane dentree ont ete lus, lanalysea reussi.Le probl`eme majeur de ces methodes est de faire deux sortes de choix : si les symboles au sommet de la pile constituent le membre droit de deux productions distinctes, laquelleutiliser pour eectuer la reduction? lorsque les symboles au sommet de la pile sont le membre droit dune ou plusieurs productions, faut-ilr eduire tout de suite, ou bien continuer `a decaler, an de permettre ult erieurement une reduction plusjuste?A titre dexemple, avec la grammaire G 1 de la section 3.1.1 : expression expression "+" terme | termeterme terme "*" facteur | facteur ( G 1 ) facteur nombre

| identificateur | "(" expression ")" voici la reconnaissance par un analyseur ascendant du texte dentree "60 * vitesse + 200" , cest-`a-dire lachane de terminaux (nombre "*" identificateur "+" nombre) : 29 Comme precedemment, cette pile est un tableau couche `a lhorizontale; mais cette fois elle grandit de la gauche vers la droite,cest-`a-dire que son sommet est son extremite droite. 29

fenetre pile action nombre decalage "*" nombre reduction "*" facteur reduction

"*" terme decalage identificateur terme "*" decalage "+" terme "*" identificateur reduction "+" terme "*" facteur reduction "+" terme reduction "+" expression decalage nombre expression "+" decalage expression

"+" nombre reduction expression "+" facteur reduction expression "+" terme reduction expression succ`esOn dit que les methodes de ce type eectuent une analyse par decalage-reduction . Comme le montre letableau ci-dessus, le point important est le choix entre r eduction et decalage, chaque fois quune reduction estpossible. Le principe est : les reductions pratiquees realisent la construction inverse dune derivation droite .Par exemple, le reductions faites dans lanalyse precedente construisent, `a lenvers, la derivation droitesuivante : expression expression "+" terme expression "+"

facteur expression "+" nombre terme "+" nombre terme "*" facteur "+" nombre terme "*" identificateur "+" nombre facteur "*" identificateur "+" nombre nombre "*" identificateur "+" nombre 3.3.2 Analyse LR( k ) Il est possible, malgre les apparences, de construire des analyseurs ascendants plus ecaces que les analyseursdescendants, et acceptant une classe de langages plus large que la classe des langages traites par ces derniers.Le principal inconvenient de ces analyseurs est quils necessitent des tables quil est extremement dicilede construire `a la main. Heureusement, des outils existent pour les construire automatiquement, `a partir de lagrammaire du langage; la section 3.4 presente yacc

, un des plus connus de ces outils.Les analyseurs LR( k ) lisent la chane dentree de la gauche vers la droite (do`u le L), en construisantlinverse dune derivation droite (do`u le R) avec une vue sur la chane dentree large de k symboles; lorsquondit simplement LR on sous-entend k = 1, cest le cas le plus frequent.Etant donnee une grammaire G =( V T ,V N ,S 0 ,P ), un analyseur LR est constitue par la donnee dun ensembledetats E , dune fenetre `a symboles terminaux (cest-`a-dire un analyseur lexical), dune pile de doublets ( s,e )o`u s E et e

V T et de deux tables Action et Suivant , qui representent des fonctions : Action : E V T ( { decaler } E ) ( { reduire } P ) {

succes, erreur } Suivant : E V N E Un analyseur LR comporte enn un programme, independant du langage analys e, qui execute les operationssuivantes : Initialisation. Placer la fenetre sur le premier symbole de la chane dentree et vider la pile. Iteration. Tant que cest possible, repeter :soit s letat au sommet de la pile et le terminal visible `a la fenetresi Action ( s, )=( decaler,s )empiler ( ,s

)placer la fenetre sur le prochain symbole de la chane dentree30

sinon, si Action ( s, )=( reduire,A )depiler autant delements de la pile quil y a de symboles dans (soit ( ,s ) le nouveau sommet de la pile)empiler ( A,Suivant ( s

,A ))sinon, si Action ( s, )= succes arretsinonerreur. Note. En realite, notre pile est redondante. Les etats de lanalyseur representent les diverses congurationsdans lesquelles la pile peut se trouver, il ny a donc pas besoin dempiler les symboles, les etats susent. Nousavons utilise une pile de couples ( etat,symbole ) pour clarier lexplication. 3.4 Yacc , un generateur danalyseurs syntaxiques Comme nous lavons indique, les tables Action et Suivant dun analyseur LR sont diciles `a construiremanuellement, mais il existe des outils pour les deduire automatiquement des productions de la grammaireconsid eree.Le programme yacc 30 est un tel generateur danalyseurs syntaxiques. Il prend en entree un chier sourceconstitue essentiellement des productions dune grammaure non contextuelle

G et sort `a titre de resultat unprogramme C qui, une fois compile, est un analyseur syntaxique pour le langage L ( G ). monProg source analyser rsultat del'analyse gcc y.tab.cmonProg yacc grammaire noncontextuelle y.tab.c autres modules lex.yy.cy.tab.h Fig. 9 Utilisation courante de yacc Dans la description de la grammaire donnee `a yacc on peut associer des actions semantiques aux productions;ce sont des bouts de code source C que yacc place aux bons endroits de lanalyseur construit. Ce dernier peut ainsiexecuter des actions ou produire des informations deduites du texte source, cest-`a-dire devenir un compilateur.Un analyseur syntaxique requiert pour travailler un analyseur lexical qui lui delivre le ot dentree sous formedunites lexicales successives. Par defaut, yacc suppose que lanalyseur lexical disponible a ete fabrique par

lex .Autrement dit, sans quil faille de declaration speciale pour cela, le programme produit par yacc comporte desappels de la fonction yylex aux endroits o`u lacquisition dune unite lexicale est necessaire. 3.4.1 Structure dun chier source pour yacc Un chier source pour yacc doit avoir un nom termine par .y . Il est fait de trois sections, delimitees pardeux lignes reduites au symbole %% : %{ declarations pour le compilateur C %} 30 Dans le monde Linux on trouve une version amelioree de yacc , nommee bison , qui appartient `a la famille GNU. Le nom de yacc provient de yet another compiler compiler (

encore un compilateur de compilateurs... ), bison est issu de la confusion de yacc avec yak ou yack , gros bovin du Tibet. 31

declarations pour yacc %% r`egles (productions + actions semantiques) %% fonctions C supplementaires Les parties declarations pour le compilateur C et fonctions C supplementaires sont simplementrecopiees dans le chier produit, respectivement au debut et `a la n de ce chier. Chacune de ces deux partiespeut etre absente.Dans la partie

declarations pour yacc on rencontre souvent les declarations des unites lexicales, sous uneforme qui laisse yacc se charger dinventer des valeurs conventionnelles : %token NOMBRE IDENTIF VARIABLE TABLEAU FONCTION%token OU ET EGAL DIFF INFEG SUPEG%token SI ALORS SINON TANTQUE FAIRE RETOUR Ces declarations dunites lexicales interessent yacc , qui les utilise, mais aussi lex , qui les manipule en tant queresultats de la fonction yylex . Pour cette raison, yacc produit 31 un chier supplementaire, nomme y.tab.h 32 ,destine `a etre inclus dans le source lex (au lieu du chier que nous avons appele unitesLexicales.h danslexemple de la section 2.3.2). Par exemple, le chier produit pour les d eclarations ci-dessus ressemble `a ceci : #define NOMBRE 257#define IDENTIF 258#define VARIABLE 259...#define FAIRE 272#define RETOUR 273

Notez que yacc consid`ere que tout caract`ere est susceptible de jouer le role dunite lexicale (comme cela aete le cas dans notre exemple de la section 2.3.2); pour cette raison, ces constantes sont numerotees `a partir de256. Specification de V T , V N et S 0 . Dans un chier source yacc : les caract`eres simples, encadres par des apostrophes comme dans les programmes C, et les identicateursmentionnes dans les declarations %token sont tenus pour des symboles terminaux, tous les autres identicateurs apparaissant dans les productions sont consid`eres comme des symboles nonterminaux, par defaut, le symbole de depart est le membre gauche de la premi`ere r`egle. R`egles de traduction. Une r`egle de traduction est un ensemble de productions ayant le meme membregauche, chacune associe `a une action semantique.Une action s emantique (cf. section 3.4.2) est un morceau de code source C encadre par des accolades. Cestun code que lanalyseur executera lorsque la production correspondante aura ete utilisee dans une reduction. Sion ecrit un analyseur

pur , cest-`a-dire un analyseur qui ne fait quaccepter ou rejeter la chane dentr ee, alorsil ny a pas dactions semantiques et les r`egles de traduction sont simplement les productions de la grammaire.Dans les r`egles de traduction, le m eta-symbole est indique par deux points : et chaque r`egle (cest-`a-dire chaque groupe de productions avec le meme membre gauche) est terminee par un point-virgule ; . Labarre verticale | a la meme signication que dans la notation des grammaires.Par exemple, voici comment la grammaire G 1 de la section 3.1.1 : expression expression "+" terme

| termeterme terme "*" facteur | facteur ( G 1 ) facteur nombre | identificateur | "(" expression ")" serait ecrite dans un chier source yacc (pour obtenir un analyseur pur, sans actions semantiques) : %token nombre identificateur%%expression : expression + terme | terme ;terme : terme * facteur | facteur ;facteur : nombre | identificateur | ( expression ) ; 31 Du moins si on le lance avec loption

-d , comme dans yacc -d maSyntaxe.y . 32 Dans le cas de bison les noms des chiers .tab.c et .tab.h re`etent le nom du chier source .y . 32

Lanalyseur syntaxique se presente comme une fonction int yyparse(void) , qui rend 0 lorsque la chanedentree est acceptee, une valeur non nulle dans le cas contraire. Pour avoir un analyseur syntaxique autonomeil sut donc dajouter, en troisi`eme section du chier precedent : %%int main(void) {if (yyparse() == 0)printf("Texte correct\n");} En realite, il faut aussi ecrire la fonction appelee en cas derreur. Cest une fonction de prototype void yyerror(char *message) , elle est appelee par lanalyseur avec un message derreur (par defaut parse error

,ce nest pas tr`es informatif!). Par exemple : void yyerror(char *message) {printf(" <<< %s\n", message);} N.B. Leet precis de linstruction ci-dessus, cest-`a-dire la presentation eective des messages derreur,depend de la mani`ere dont lanalyseur lexical ecrit les unites lexicales au fur et `a mesure de lanalyse. 3.4.2 Actions semantiques et valeurs des attributs Une action semantique est une sequence dinstructions C ecrite, entre accolades, `a droite dune production.Cette sequence est recopiee par yacc dans lanalyseur produit, de telle mani`ere quelle sera executee, pendantlanalyse, lorsque la production correspondante aura ete employee pour faire une reduction (cf. analyse pardecalage-reduction `a la section 3.3).Voici un exemple simple, mais complet. Le programme suivant lit une expression arithmetique inxee 33 formee de nombres, didenticateurs et des operateurs + et , et ecrit la representation en notation postxee 34 de la meme expression.Fichier lexique.l : %{#include "syntaxe.tab.h"extern char nom[]; /* cha^ne de caract`eres partag ee avec lanalyseur syntaxique */ %}chiffre [0-9]lettre [A-Za-z]%%[" "\t\n] { } {chiffre}+ { yylval = atoi(yytext); return nombre; }{lettre}({lettre}|{chiffre})* { strcpy(nom, yytext); return identificateur; }. { return yytext[0]; }%%int yywrap(void) {return 1;}

Fichier syntaxe.y : %{char nom[256]; /* cha^ne de caract`eres partagee avec lanalyseur lexical */ %} 33 Dans la notation inxee, un operateur binaire est ecrit entre ses deux op erandes. Cest la notation habituelle, et elle est ambigue;cest pourquoi on lui associe un syst`eme dassociativite et de priorite des operateurs, et la possibilite dutiliser des parenth`eses. 34 Dans la notation postixee, appelee aussi notation polonaise inverse , un operateur binaire est ecrit `a la suite de ses deuxoperandes; cette notation na besoin ni de priorites des operateurs ni de parenth`eses, et elle nest pas ambigue. 33

%token nombre identificateur%%expression : expression + terme { printf(" +"); }| terme;terme : terme * facteur { printf(" *"); }| facteur;facteur : nombre { printf(" %d", yylval); }| identificateur { printf(" %s", nom); }| ( expression );% %void yyerror(char *s) {printf("<<< \n%s", s);}main() {if (yyparse() == 0)printf(" Expression correcte\n");} Construction et essai de cet analyseur : $ flex lexique.l$ bison syntaxe.y$ gcc lex.yy.c syntaxe.tab.c -o rpn$ rpn2 + A * 1002 A 100 * + Expression correcte$ rpn2 * A + 1002 A * 100 + Expression correcte$ rpn2 * (A + 100)2 A 100 + * Expression correcte$ Attributs. Un symbole, terminal ou non terminal, peut avoir un attribut

, dont la valeur contribue `a lacaracterisation du symbole. Par exemple, dans les langages qui nous interessent, la reconnaissance du lex`eme "2001" donne lieu `a lunite lexicale NOMBRE avec lattribut 2001.Un analyseur lexical produit par lex transmet les attributs des unites lexicales `a un analyseur syntaxiqueproduit par yacc `a travers une variable yylval qui, par defaut 35 , est de type int . Si vous allez voir le chier .tab.h fabrique par yacc et destine `aetre inclus dans lanalyseur lexical, vous y trouverez, outre les d enitionsdes codes des unites lexicales, les declarations : #define YYSTYPE int...extern YYSTYPE yylval; Nous avons dit que les actions semantiques sont des bouts de code C que yacc se borne `a recopier danslanalyseur produit. Ce nest pas tout `a fait exact, dans les actions semantiques on peut mettre certains symbolesbizarres, que yacc remplace par des expressions C correctes. Ainsi,

$1 , $2 , $3 , etc. designent les valeurs desattributs des symboles constituant le membre droit de la production concernee, tandis que $$ designe la valeurde lattribut du symbole qui est le membre gauche de cette production. 35 Un mecanisme puissant et relativement simple permet davoir des attributs polymorphes, pouvant prendre plusieurs typesdistincts. Nous ne letudierons pas dans le cadre de ce cours. 34

Laction semantique { $$ = $1; } est implicite et il ny a pas besoin de lecrire.Par exemple, voici notre analyseur precedent, modie (leg`erement) pour en faire un calculateur de bureaueectuant les quatre operations elementaires sur des nombres entiers, avec gestion des parenth`eses et de lapriorite des operateurs :Fichier lexique.l : le meme que precedemment, `a ceci pr`es que les identicateurs ne sont plus reconnus.Fichier syntaxe.y :

%{void yyerror(char *);%}%token nombre%%session : session expression = { printf("resultat : %d\n", $2); }|;expression : expression + terme { $$ = $1 + $3; }| expression - terme { $$ = $1 - $3; }| terme;terme : terme * facteur { $$ = $1 * $3; }| terme / facteur { $$ = $1 / $3; }| facteur;facteur : nombre| ( expression ) { $$ = $2; };%%void yyerror(char *s) {printf("<<< \n%s", s);}main() {yyparse();printf("Au revoir!\n");} Exemple dutilisation; represente la touche n de chier , qui depend du syst`eme utilise (Ctrl-D, pourUNIX) : $ go2 + 3 =resultat : 5(2 + 3)*(1002 - 1 - 1) =resultat : 5000 Au revoir! 3.4.3 Conits et ambigutes Voici encore une grammaire equivalente `a la precedente, mais plus compacte : ...%%session : session expression = { printf("resultat: %d\n", $2); }|; 35

PolyCompil Download this Document for FreePrintMobileCollectionsReport Document Report this document? Please tell us reason(s) for reporting this document Haut du formulaire
9e994d785ff3b4c

doc

Spam or junk

Porn adult content

Hateful or offensive If you are the copyright owner of this document and want to report it, please follow these directions to submit a copyright infringement notice. Report Cancel Bas du formulaire This is a private document. Info and Rating Reads: 437 Uploaded: 04/10/2011 Category: Uncategorized. Rated:
0 5 false false 0

Copyright: Attribution Non-commercial

Follow

Mehdi Fekih Sections show allcollapse prev | next

1 Introduction 1.1 Structure de principe dun compilateur 2 Analyse lexicale 2.1 Expressions reguli`eres 2.1.1 Denitions 2.1.2 Ce que les expressions reguli`eres ne savent pas faire 2.2 Reconnaissance des unites lexicales 2.2.1 Diagrammes de transition 2.2.2 Analyseurs lexicaux programmes en dur 2.2.3 Automates nis 2.3 Lex, un generateur danalyseurs lexicaux 2.3.1 Structure dun chier source pour lex 2.3.2 Un exemple complet 2.3.3 Autres utilisations de lex 3 Analyse syntaxique 3.1 Grammaires non contextuelles 3.1.1 Denitions 3.1.2 Derivations et arbres de derivation 3.1.3 Qualites des grammaires en vue des analyseurs 3.1.4 Ce que les grammaires non contextuelles ne savent pas faire 3.2 Analyseurs descendants 3.2.1 Principe 3.2.2 Analyseur descendant non recursif 3.2.3 Analyse par descente recursive 3.3 Analyseurs ascendants 3.3.1 Principe 3.3.2 Analyse LR(k) 3.4 Yacc, un generateur danalyseurs syntaxiques

3.4.1 Structure dun chier source pour yacc 3.4.2 Actions semantiques et valeurs des attributs 3.4.3 Conits et ambigutes 4 Analyse semantique 4.1 Representation et reconnaissance des types 4.2 Dictionnaires (tables de symboles) 4.2.1 Dictionnaire global & dictionnaire local 4.2.2 Tableau `a acc`es sequentiel 4.2.3 Tableau trie et recherche dichotomique 4.2.4 Arbre binaire de recherche 4.2.5 Adressage disperse 5 Production de code 5.1 Les objets et leurs adresses 5.1.1 Classes dobjets 5.1.2 Do`u viennent les adresses des objets? 5.1.3 Compilation separee et edition de liens 5.2 La machine Mach 1 5.2.1 Machines `a registres et machines `a pile 5.2.2 Structure generale de la machine Mach 1 5.2.3 Jeu dinstructions 5.2.4 Complements sur lappel des fonctions 5.3 Exemples de production de code 5.3.1 Expressions arithmetiques 5.3.2 Instruction conditionnelle et boucles 5.3.3 Appel de fonction Share & Embed More from this user PreviousNext

Das könnte Ihnen auch gefallen