Licence Universitaire Professionnelle

G´nie Logiciel & Th´orie des Langages e e

Techniques et outils pour la compilation
Henri Garreta
Facult´ des Sciences de Luminy - Universit´ de la M´diterran´e e e e e Janvier 2001

Table des mati`res e
1 Introduction 1.1 Structure de principe d’un compilateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Analyse lexicale 2.1 Expressions r´guli`res . . . . . . . . . . . . . . . . . . . . . e e 2.1.1 D´finitions . . . . . . . . . . . . . . . . . . . . . . . e 2.1.2 Ce que les expressions r´guli`res ne savent pas faire e e 2.2 Reconnaissance des unit´s lexicales . . . . . . . . . . . . . . e 2.2.1 Diagrammes de transition . . . . . . . . . . . . . . . 2.2.2 Analyseurs lexicaux programm´s  en dur  . . . . . e 2.2.3 Automates finis . . . . . . . . . . . . . . . . . . . . . 2.3 Lex, un g´n´rateur d’analyseurs lexicaux . . . . . . . . . . . e e 2.3.1 Structure d’un fichier source pour lex . . . . . . . . 2.3.2 Un exemple complet . . . . . . . . . . . . . . . . . . 2.3.3 Autres utilisations de lex . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 3 5 5 5 7 8 8 9 12 14 14 16 18 19 19 19 20 21 24 24 25 26 26 29 29 30 31 31 33 35

3 Analyse syntaxique 3.1 Grammaires non contextuelles . . . . . . . . . . . . . . . . . . 3.1.1 D´finitions . . . . . . . . . . . . . . . . . . . . . . . . e 3.1.2 D´rivations et arbres de d´rivation . . . . . . . . . . . e e 3.1.3 Qualit´s des grammaires en vue des analyseurs . . . . e 3.1.4 Ce que les grammaires non contextuelles ne savent pas 3.2 Analyseurs descendants . . . . . . . . . . . . . . . . . . . . . 3.2.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2.2 Analyseur descendant non r´cursif . . . . . . . . . . . e 3.2.3 Analyse par descente r´cursive . . . . . . . . . . . . . e 3.3 Analyseurs ascendants . . . . . . . . . . . . . . . . . . . . . . 3.3.1 Principe . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.2 Analyse LR(k) . . . . . . . . . . . . . . . . . . . . . . 3.4 Yacc, un g´n´rateur d’analyseurs syntaxiques . . . . . . . . . e e 3.4.1 Structure d’un fichier source pour yacc . . . . . . . . . 3.4.2 Actions s´mantiques et valeurs des attributs . . . . . . e 3.4.3 Conflits et ambigu¨ es . . . . . . . . . . . . . . . . . . ıt´

. . . . . . . . . . . . faire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1

4 Analyse s´mantique e 4.1 Repr´sentation et reconnaissance des types . . e 4.2 Dictionnaires (tables de symboles) . . . . . . . 4.2.1 Dictionnaire global & dictionnaire local 4.2.2 Tableau ` acc`s s´quentiel . . . . . . . . a e e 4.2.3 Tableau tri´ et recherche dichotomique . e 4.2.4 Arbre binaire de recherche . . . . . . . . 4.2.5 Adressage dispers´ . . . . . . . . . . . . e

. . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

37 38 42 42 43 45 46 49 51 51 51 53 54 56 56 57 58 58 60 60 63 65

5 Production de code 5.1 Les objets et leurs adresses . . . . . . . . . . . . 5.1.1 Classes d’objets . . . . . . . . . . . . . . . 5.1.2 D’o` viennent les adresses des objets ? . . u 5.1.3 Compilation s´par´e et ´dition de liens . . e e e 5.2 La machine Mach 1 . . . . . . . . . . . . . . . . . 5.2.1 Machines ` registres et machines ` pile . . a a 5.2.2 Structure g´n´rale de la machine Mach 1 e e 5.2.3 Jeu d’instructions . . . . . . . . . . . . . 5.2.4 Compl´ments sur l’appel des fonctions . . e 5.3 Exemples de production de code . . . . . . . . . 5.3.1 Expressions arithm´tiques . . . . . . . . . e 5.3.2 Instruction conditionnelle et boucles . . . 5.3.3 Appel de fonction . . . . . . . . . . . . .

2

1

Introduction

Dans le sens le plus usuel du terme, la compilation est une transformation que l’on fait subir ` un programme a ´crit dans un langage ´volu´ pour le rendre ex´cutable. Fondamentalement, c’est une traduction : un texte ´crit e e e e e en Pascal, C, Java, etc., exprime un algorithme et il s’agit de produire un autre texte, sp´cifiant le mˆme e e algorithme dans le langage d’une machine que nous cherchons ` programmer. a En g´n´ralisant un peu, on peut dire que compiler c’est lire une suite de caract`res ob´issant ` une certaine e e e e a syntaxe, en construisant une (autre) repr´sentation de l’information que ces caract`res expriment. De ce point e e de vue, beaucoup d’op´rations apparaissent comme ´tant de la compilation ; ` la limite, la lecture d’un nombre, e e a qu’on obtient en C par une instruction comme : scanf("%f", &x); est d´j` de la compilation, puisqu’il s’agit de lire des caract`res constituant l’´criture d’une valeur selon la ea e e syntaxe des nombres d´cimaux et de fabriquer une autre repr´sentation de la mˆme information, ` savoir sa e e e a valeur num´rique. e Bien sˆr, les questions qui nous int´resseront ici seront plus complexes que la simple lecture d’un nombre. u e Mais il faut comprendre que le domaine d’application des principes et m´thodes de l’´criture de compilateurs e e contient bien d’autres choses que la seule production de programmes ex´cutables. Chaque fois que vous aurez e ae ` ´crire un programme lisant des expressions plus compliqu´es que des nombres vous pourrez tirer profit des e concepts, techniques et outils expliqu´s dans ce cours. e

1.1

Structure de principe d’un compilateur

La nature de ce qui sort d’un compilateur est tr`s variable. Cela peut ˆtre un programme ex´cutable pour e e e un processeur physique, comme un Pentium III ou un G4, ou un fichier de code pour une machine virtuelle, comme la machine Java, ou un code abstrait destin´ ` un outil qui en fera ult´rieurement du code ex´cutable, ea e e ou encore le codage d’un arbre repr´sentant la structure logique d’un programme, etc. e En entr´e d’un compilateur on trouve toujours la mˆme chose : une suite de caract`res, appel´e le texte e e e e source 1 . Voici les phases dans lesquelles se d´compose le travail d’un compilateur, du moins d’un point de vue e logique2 (voyez la figure 1) : Analyse lexicale Dans cette phase, les caract`res isol´s qui constituent le texte source sont regroup´s pour e e e former des unit´s lexicales, qui sont les mots du langage. e L’analyse lexicale op`re sous le contrˆle de l’analyse syntaxique ; elle apparaˆ comme une sorte de fonction e o ıt de  lecture am´lior´e , qui fournit un mot lors de chaque appel. e e Analyse syntaxique Alors que l’analyse lexicale reconnaˆ les mots du langage, l’analyse syntaxique en reıt connaˆ les phrases. Le rˆle principal de cette phase est de dire si le texte source appartient au langage ıt o consid´r´, c’est-`-dire s’il est correct relativement ` la grammaire de ce dernier. ee a a Analyse s´mantique La structure du texte source ´tant correcte, il s’agit ici de v´rifier certaines propri´t´s e e e ee s´mantiques, c’est-`-dire relatives ` la signification de la phrase et de ses constituants : e a a – les identificateurs apparaissant dans les expressions ont-ils ´t´ declar´s ? ee e – les op´randes ont-ils les types requis par les op´rateurs ? e e – les op´randes sont-ils compatibles ? n’y a-t-il pas des conversions ` ins´rer ? e a e – les arguments des appels de fonctions ont-ils le nombre et le type requis ? etc. G´n´ration de code interm´diaire Apr`s les phases d’analyse, certains compilateurs ne produisent pas die e e e rectement le code attendu en sortie, mais une repr´sentation interm´diaire, une sorte de code pour une e e machine abstraite. Cela permet de concevoir ind´pendamment les premi`res phases du compilateur (constie e tuant ce que l’on appelle sa face avant) qui ne d´pendent que du langage source compil´ et les derni`res e e e phases (formant sa face arri`re) qui ne d´pendent que du langage cible ; l’id´al serait d’avoir plusieurs e e e faces avant et plusieurs faces arri`re qu’on pourrait assembler librement3 . e
1 Conseil : le texte source a probablement ´t´ compos´ ` l’aide d’un ´diteur de textes qui le montre sous forme de pages faites e e ea e de plusieurs lignes mais, pour ce que nous avons ` en faire ici, prenez l’habitude de l’imaginer comme s’il ´tait ´crit sur un long et a e e mince ruban, formant une seule ligne. 2 C’est une organisation logique ; en pratique certaines de ces phases peuvent ˆtre imbriqu´es, et d’autres absentes. e e 3 De la sorte, avec n faces avant pour n langages source et m faces arri`re correspondant ` m machines cibles, on disposerait e a automatiquement de n × m compilateurs distincts. Mais cela reste, pour le moment, un fantasme d’informaticien.

3

tmp1) addRéel(id(2). 2 posInit ..0) id(1) <.. les expressions inutiles 4 . tmp1) génération du code final MOVF MULF ADDF MOVF id3. 60.addRéel(id(2). e e ea a e e e – transporter ` l’ext´rieur des boucles des expressions et sous-expressions dont les op´randes ont la mˆme valeur ` toutes les it´rations a e e – d´tecter. R0 R0. 1 pos . 1 – Phases logiques de la compilation d’une instruction Optimisation du code Il s’agit g´n´ralement ici de transformer le code afin que le programme r´sultant e e e s’ex´cute plus rapidement. et supprimer..0. id1 Fig. id(1) id(3) add mul nbr(60) analyse sémantique aff addRéelle mulRéelle id(2) entVersRéel id(3) 60 génération de code intermédiaire tmp1 tmp2 tmp3 id(1) <te <<<EntVersRéel(60) mulRéel(id(3).mulRéel(id(3).. Par exemple e – d´tecter l’inutilit´ de recalculer des expressions dont la valeur est d´j` connue.pos = posInit + vit * 60 analyse lexicale id(1) aff id(2) add id(3) mul nbr(60) analyse syntaxique aff id(1) id(2) table de symboles . . R0 #60.... tmp2) tmp3 optimisation du code tmp1 <. R0 id2.. 3 vit ...

IDENTIF. e PLUSPLUS. 2. piles. =. l’interface avec les fonctions de lecture de e e ee e caract`res.1 2. e a Remarque.) et celle des commentaires e e (g´n´ralement consid´r´s comme ayant la mˆme valeur qu’un blanc). qui sont les  mots  avec lesquels les phrases e ıt e sont form´es. – le lex`me. tabulations. l’analyse syntaxique. fins de ligne. etc. D’ailleurs. pour un nombre. NOMBRE. etc. les e a 4 Dans un analyseur lexical ´crit en C. respectivement4 : PLUS. ` travers lesquelles le texte source est acquis. "-5". A propos d’une unit´ lexicale reconnue dans le texte source on doit distinguer quatre notions importantes : e – l’unit´ lexicale. e e e e e e e c – le mod`le d’un identificateur est  une suite de lettres. 123. Seules les e e e e derni`res des dix unit´s pr´c´dentes ont un attribut . INFEGAL. =. La fronti`re entre l’analyse lexicale et l’analyse syntaxique n’est pas fixe. e e e e e ++. repr´sent´e g´n´ralement par un code conventionnel . On notera que les caract`res blancs (c’est-`-dire les espaces. i et vitesse_du_vent. l’ensemble de toutes les lettres. les lex`mes e ıne e e e e correspondants sont : "+". "if". while. la gestion des fichiers et l’affichage des erreurs. "=". Comme nous allons le voir. -5. e e a Les principales sortes d’unit´s lexicales qu’on trouve dans les langages de programmation courants sont : e – les caract`res sp´ciaux simples : +. e – et les identificateurs : i. Nous verrons ci-apr`s des moyens formels pour d´finir rigoue a e e e e reusement les mod`les . un attribut. e – le mod`le qui sert ` sp´cifier l’unit´ lexicale. commen¸ant par une lettre .1 Expressions r´guli`res e e D´finitions e Les expressions r´guli`res sont une importante notation pour sp´cifier formellement des mod`les. l’ensemble des chiffres. les teche e e niques pour reconnaˆ les unit´s lexicales sont bien plus simples et efficaces que les techniques pour v´rifier la ıtre e e syntaxe. les analyseurs lexicaux assurent certaines tˆches mineures comme e a la suppression des caract`res de d´coration (blancs. SI. TANTQUE. etc. e – les constantes litt´rales : 123. le code ASCII. -5. while. qui d´pend de l’unit´ lexicale en question. ıtre Simplicit´ et efficacit´ sont les raisons d’ˆtre des analyseurs lexicaux. etc. G. Exemples : {0. ce pourrait ˆtre. on peut concevoir des compilateurs dans lesquels la syntaxe est d´finie ` partir e a des caract`res individuels. ces codes sont des pseudo-constantes introduites par des directives #define e 5 . IDENTIF. e e – les caract`res sp´ciaux doubles : <=. EGAL. pour le moment nous nous contenterons de descriptions informelles comme : e – pour les caract`res sp´ciaux simples et doubles et les mots r´serv´s. le lex`me et le mod`le co¨ e e e e e e ıncident. la plus impressionnante pour le n´ophyte. <=. Pour les e e e e d´finir correctement il nous faut faire l’effort d’apprendre un peu de vocabulaire nouveau : e Un alphabet est un ensemble de symboles. ++. de chiffres et du caract`re ’ ’. Outre la reconnaissance des unit´s lexicales. etc. C. vitesse_du_vent. Elle n´cessite la connaissance de la machine cible (r´elle. −5) . etc. "i" et "vitesse_du_vent" – ´ventuellement. n’est pas forc´ment la plus e e difficile ` r´aliser. ´ventuellement pr´c´d´e d’un signe . NOMBRE. l’analyse lexicale reconnaˆ des unit´s lexicales. {A. e e e e pour un identificateur. e e – les mots-cl´s : if. qui est la chaˆ de caract`res correspondante. virtuelle ou abstraite).1. "<=". Mais les analyseurs syntaxiques qu’il faut alors ´crire sont bien plus complexes que e e ceux qu’on obtient en utilisant des analyseurs lexicaux pour reconnaˆ les mots du langage. – le mod`le d’un nombre est  une suite de chiffres. qui se pr´sente comme un e e flot de caract`res. Pour les dix exemples pr´c´dents. et les pr´sente ` la phase suivante. "++". pour nos dix exemples +. etc. l’analyse e lexicale n’est pas une obligation. T }. 1}. "while". "123". Dans le texte source. il s’agit de sa valeur (123. il s’agit d’un renvoi ` une table dans laquelle sont plac´s tous les identificateurs a e rencontr´s (on verra cela plus loin). et a e e e notamment de ses possibilit´s en mati`re de registres.etc. if. etc. et qui la compl`te. e e G´n´ration du code final Cette phase. etc. e e 2 Analyse lexicale L’analyse lexicale est la premi`re phase de la compilation.

. En principe.xn | xi ∈ L. ε en fait partie. – L∗ est l’ensemble des chaˆ ınes faites d’un nombre quelconque de lettres . e e e 6 On prendra garde ` l’abus de langage qu’on fait ici. 2001. etc. la concat´nation des chaˆ e e e ınes anti et moine est la chaˆ antimoine. c ´ ` Expression reguliere. alors a est une expression r´guli`re qui d´finit le langage6 {a} e e e 3. Les op´rations sur les langages suivantes nous serviront ` d´finir les expressions r´guli`res. On se donne les alphabets L = {A. . on d´finit : e d´nomination e l’union de L et M la concat´nation de L et M e la fermeture de Kleene de L la fermeture positive de L notation L∪M LM L∗ L+ d´finition e { x | x ∈ L ou x ∈ M } { xy | x ∈ L et y ∈ M } { x1 x2 . ACCAGTTGAAGTGGACCTTT. L. l’ensemble des chaˆ e e ınes ADN. le contexte permet de savoir de quoi on parle. – LC est l’ensemble des chaˆ ınes form´es d’une lettre suivie d’un chiffre. on peut ´crire l’expression r´guli`re oui au lieu de (o)(u)(i) et oui|non au lieu de (oui)|(non).. Soient x et y deux expressions r´guli`res. En consid´rant qu’un caract`re est la mˆme chose qu’une chaˆ de longueur un. – C + est l’ensemble des chaˆ ınes de chiffres comportant au moins un chiffre. Z. Une expression r´guli`re r sur Σ est une formule qui d´finit e e e un langage L(r) sur Σ. e etc. {ε}. la concat´nation de x et y. . ıne. concat´nation et | e e e e e sont associatifs ` gauche. D’autre part. ε est une expression r´guli`re qui d´finit le langage {ε} e e e 2. On a donc x1 = x. quillemets. Exemples triviaux : ∅. d´finissant les langages L(x) et L(y).xn | xi ∈ L. b. sauf mesures particuli`res (apostrophes. est la chaˆ obtenue en ´crivant y e e ıne e imm´diatement apr`s x. e e e mais on ne doit pas ´crire oui∗ au lieu de (oui)∗ . c Si x et y sont deux chaˆ ınes. e – L4 est l’ensemble des chaˆ ınes de quatre lettres. etc. Si ıne x est une chaˆ on d´finit x0 = ε et. ne peuvent pas contenir des e e e caract`res blancs. . not´e xy. Par exemple. ıne e Un langage sur un alphabet Σ est un ensemble de chaˆ ınes construites sur Σ. le langage vide. e e 6 . en fonction des op´rateurs en pr´sence : il suffit de savoir que les op´rateurs ∗. x3 = xxx. . Exemples. form´s de chaˆ e ınes de longueur un. Des exemples plus int´ressants (relatifs aux alphabets e a ıne e pr´c´dents) : l’ensemble des nombres en notation binaire. . et C = {0. Soient L et M e a e e e deux langages. en employant la mˆme notation pour le caract`re a. la chaˆ a e e ıne a et l’expression r´guli`re a. Alors e e e – (x)|(y) est une expression r´guli`re d´finissant le langage L(x) ∪ L(y) e e e – (x)(y) est une expression r´guli`re d´finissant le langage L(x)L(y) e e e – (x)∗ est une expression r´guli`re d´finissant le langage (L(x))∗ e e e – (x) est une expression r´guli`re d´finissant le langage L(x) e e e La derni`re r`gle ci-dessus signifie qu’on peut encadrer une expression r´guli`re par des parenth`ses sans e e e e e changer le langage d´fini. la plupart des langages autorisent les caract`res blancs entre les unit´s lexicales. e 5 Il en d´coule que les unit´s lexicales. On note e e ε la chaˆ vide. Dans ces conditions : – L ∪ C est l’ensemble des lettres et des chiffres. ensemble des lettres. ensemble des chiffres.). 9}. . a. Bonjour.. on peut e e e ıne voir L et C comme des langages. Si a ∈ Σ. . l’ensemble des mots de la langue fran¸aise. D’autre part. de la mani`re suivante : e 1. n ∈ N et n > 0} De la d´finition de LM on d´duit celle de Ln = LL . pour n > 0.tabulations et les marques de fin de ligne) ne font g´n´ralement pas partie des alphabets5 . 1. . e e Une chaˆ (on dit aussi mot) sur un alphabet Σ est une s´quence finie de symboles de Σ. B. x2 = xx. z}. xn = xn−1 x = xxn−1 . n ∈ N et n ≥ 0} { x1 x2 . . Soit Σ un alphabet. ıne e respectivement relatifs aux alphabets pr´c´dents : 00011011. le langage r´duit ` l’unique chaˆ vide. les parenth`ses apparaissant dans les r`gles pr´c´dentes peuvent souvent e e e e e ˆtre omises. .. e e Exemples.. et v´rifient a e priorit´ ( ∗ ) > priorit´ ( concat´nation ) > priorit´ ( | ) e e e e Ainsi. – L(L ∪ C)∗ est l’ensemble des chaˆ ınes de lettres et chiffres commen¸ant par une lettre. ne comportant aucun caract`re.

´ ´ ` Definitions regulieres. Les expressions r´guli`res se construisent ` partir d’autres expressions r´guli`res ; e e a e e cela am`ne ` des expressions passablement touffues. On les all`ge en introduisant des d´finitions r´guli`res qui e a e e e e permettent de donner des noms ` certaines expressions en vue de leur r´utilisation. On ´crit donc a e e d1 → r1 d2 → r2 ... dn → rn o` chaque di est une chaˆ sur un alphabet disjoint de Σ 7 , distincte de d1 , d2 , . . . di−1 , et chaque ri une u ıne expression r´guli`re sur Σ ∪ {d1 , d2 , . . . di−1 }. e e Exemple. Voici quelques d´finitions r´guli`res, et notamment celles de identificateur et nombre, qui d´finissent e e e e les identificateurs et les nombres du langage Pascal : lettre → A | B | . . . | Z | a | b | . . . | z chiffre → 0 | 1 | . . . | 9 identificateur → lettre ( lettre | chiffre )∗ chiffres → chiffre chiffre ∗ fraction-opt → . chiffres | ε exposant-opt → ( E (+ | - | ε) chiffres ) | ε nombre → chiffres fraction-opt exposant-opt ´ ´ Notations abregees. Pour all´ger certaines ´critures, on compl`te la d´finition des expressions r´guli`res e e e e e e en ajoutant les notations suivantes : – soit x une expression r´guli`re, d´finissant le langage L(x) ; alors (x)+ est une expression r´guli`re, qui e e e e e d´finit le langage (L(x))+ , e – soit x une expression r´guli`re, d´finissant le langage L(x) ; alors (x)? est une expression r´guli`re, qui e e e e e d´finit le langage L(x) ∪ { ε }, e – si c1 , c2 , . . . ck sont des caract`res, l’expressions r´guli`re c1 |c2 | . . . |ck peut se noter [c1 c2 . . . ck ], e e e – ` l’int´rieur d’une paire de crochets comme ci-dessus, l’expression c1 –c2 d´signe la s´quence de tous les a e e e caract`res c tels que c1 ≤ c ≤ c2 . e Les d´finitions de lettre et chiffre donn´es ci-dessus peuvent donc se r´´crire : e e ee lettre → [A–Za–z] chiffre → [0–9] 2.1.2 Ce que les expressions r´guli`res ne savent pas faire e e

Les expressions r´guli`res sont un outil puissant et pratique pour d´finir les unit´s lexicales, c’est-`-dire e e e e a les constituants ´l´mentaires des programmes. Mais elles se prˆtent beaucoup moins bien ` la sp´cification de ee e a e constructions de niveau plus ´lev´, car elles deviennent rapidement d’une trop grande complexit´. e e e De plus, on d´montre qu’il y a des chaˆ e ınes qu’on ne peut pas d´crire par des expressions r´guli`res. Par e e e exemple, le langage suivant (suppos´ infini) e { a, (a), ((a)), (((a))), . . . } ne peut pas ˆtre d´fini par des expressions r´guli`res, car ces derni`res ne permettent pas d’assurer qu’il y a dans e e e e e une expression de la forme (( . . . ((a)) . . . )) autant de parenth`ses ouvrantes que de parenth`ses fermantes. e e On dit que les expressions r´guli`res  ne savent pas compter . e e Pour sp´cifier ces structures ´quilibr´es, si importantes dans les langages de programmation (penser aux e e e parenth`ses dans les expressions arithm´tiques, les crochets dans les tableaux, begin...end, {...}, if...then..., etc.) e e nous ferons appel aux grammaires non contextuelles, expliqu´es ` la section 3.1. e a
7 On

assure souvent la s´paration entre Σ∗ et les noms des d´finitions r´guli`res par des conventions typographiques. e e e e

7

2.2

Reconnaissance des unit´s lexicales e

Nous avons vu comment sp´cifier les unit´s lexicales ; notre probl`me maintenant est d’´crire un programme e e e e qui les reconnaˆ dans le texte source. Un tel programme s’appelle un analyseur lexical. ıt Dans un compilateur, le principal client de l’analyseur lexical est l’analyseur syntaxique. L’interface entre ces deux analyseurs est une fonction int uniteSuivante(void) 8 , qui renvoie ` chaque appel l’unit´ lexicale suivante a e trouv´e dans le texte source. e Cela suppose que l’analyseur lexical et l’analyseur syntaxique partagent les d´finitions des constantes convene tionnelles d´finissant les unit´s lexicales. Si on programme en C, cela veut dire que dans les fichiers sources des e e deux analyseurs on a inclus un fichier d’entˆte (fichier  .h ) comportant une s´rie de d´finitions comme9 : e e e #define #define #define #define #define etc. IDENTIF NOMBRE SI ALORS SINON 1 2 3 4 5

Cela suppose aussi que l’analyseur lexical et l’analyseur syntaxique partagent ´galement une variable globale e contenant le lex`me correspondant ` la derni`re unit´ lexicale reconnue, ainsi qu’une variable globale contenant e a e e le (ou les) attribut(s) de l’unit´ lexicale courante, lorsque cela est pertinent, et notamment lorsque l’unit´ lexicale e e est NOMBRE ou IDENTIF. On se donnera, du moins dans le cadre de ce cours, quelques  r`gles du jeu  suppl´mentaires : e e – l’analyseur lexical est  glouton  : chaque lex`me est le plus long possible10 ; e e e e – seul l’analyseur lexical acc`de au texte source. L’analyseur syntaxique n’acquiert ses donn´es d’entr´e autrement qu’` travers la fonction uniteSuivante ; a – l’analyseur lexical acquiert le texte source un caract`re ` la fois. Cela est un choix que nous faisons ici ; e a d’autres choix auraient ´t´ possibles, mais nous verrons que les langages qui nous int´ressent permettent ee e de travailler de cette mani`re. e 2.2.1 Diagrammes de transition

Pour illustrer cette section nous allons nous donner comme exemple le probl`me de la reconnaissance e des unit´s lexicales INFEG, DIFF, INF, EGAL, SUPEG, SUP, IDENTIF, respectivement d´finies par les expressions e e r´guli`res <=, <>, <, =, >=, > et lettre(lettre|chiffre)∗ , lettre et chiffre ayant leurs d´finitions d´j` vues. e e e ea Les diagrammes de transition sont une ´tape pr´paratoire pour la r´alisation d’un analyseur lexical. Au fur e e e et ` mesure qu’il reconnaˆ une unit´ lexicale, l’analyseur lexical passe par divers ´tats. Ces ´tats sont num´rot´s a ıt e e e e e et repr´sent´s dans le diagramme par des cercles. e e De chaque ´tat e sont issues une ou plusieurs fl`ches ´tiquet´es par des caract`res. Une fl`che ´tiquet´e par e e e e e e e e c relie e ` l’´tat e1 dans lequel l’analyseur passera si, alors qu’il se trouve dans l’´tat e, le caract`re c est lu dans a e e e le texte source. Un ´tat particulier repr´sente l’´tat initial de l’analyseur ; on le signale en en faisant l’extr´mit´ d’une fl`che e e e e e e ´tiquet´e debut. e e Des doubles cercles identifient les ´tats finaux, correspondant ` la reconnaissance compl`te d’une unit´ e a e e lexicale. Certains ´tats finaux sont marqu´s d’une ´toile : cela signifie que la reconnaissance s’est faite au prix e e e de la lecture d’un caract`re au-del` de la fin du lex`me11 . e a e Par exemple, la figure 2 montre les diagrammes traduisant la reconnaissance des unit´s lexicales INFEG, e DIFF, INF, EGAL, SUPEG, SUP et IDENTIF. Un diagramme de transition est dit non d´terministe lorsqu’il existe, issues d’un mˆme ´tat, plusieurs fl`ches e e e e ´tiquet´es par le mˆme caract`re, ou bien lorsqu’il existe des fl`ches ´tiquet´es par la chaˆ vide ε. Dans le cas e e e e e e e ıne
on a employ´ l’outil lex pour fabriquer l’analyser lexical, cette fonction s’appelle plutˆt yylex ; le lex`me est alors point´ par e o e e la variable globale yytext et sa longueur est donn´e par la variable globale yylen. Tout cela est expliqu´ ` la section 2.3. e ea 9 Peu importent les valeurs num´riques utilis´es, ce qui compte est qu’elles soient distinctes. e e 10 Cette r`gle est peu mentionn´e dans la litt´rature, pourtant elle est fondamentale. C’est grˆce ` elle que 123 est reconnu comme e e e a a un nombre, et non plusieurs, vitesseV ent comme un seul identificateur, et f orce comme un identificateur, et non pas comme un mot r´serv´ suivi d’un identificateur. e e 11 Il faut ˆtre attentif ` ce caract`re, car il est susceptible de faire partie de l’unit´ lexicale suivante, surtout s’il n’est pas blanc. e a e e
8 Si

8

début

0

<

1

= >
autre

2 3 4
*

rendre INFEG rendre DIFF rendre INF

= >

5 rendre EGAL 6 =
autre lettre

7 8
*

rendre SUPEG rendre SUP

lettre

9
chiffre

autre

10

* rendre IDENTIF

Fig. 2 – Diagramme de transition pour les op´rateurs de comparaison et les identificateurs e

contraire, le diagramme est dit d´terministe. Il est clair que le diagramme de la figure 2 est d´terministe. Seuls e e les diagrammes d´terministes nous int´resseront dans le cadre de ce cours. e e 2.2.2 Analyseurs lexicaux programm´s  en dur  e

Les diagrammes de transition sont une aide importante pour l’´criture d’analyseurs lexicaux. Par exemple, e a ` partir du diagramme de la figure 2 on peut obtenir rapidement un analyseur lexical reconnaissant les unit´s e INFEG, DIFF, INF, EGAL, SUPEG, SUP et IDENTIF. Auparavant, nous apportons une l´g`re modification ` nos diagrammes de transition, afin de permettre que e e a les unit´s lexicales soient s´par´es par un ou plusieurs blancs12 . La figure 3 montre le (d´but du) diagramme e e e e modifi´13 . e
blanc début < = > lettre

0

... ... ... ...

Fig. 3 – Ignorer les blancs devant une unit´ lexicale e

Et voici le programme obtenu : int uniteSuivante(void) { char c; c = lireCar();
12 Nous 13 Notez

/* etat = 0 */

etc.

appelons  blanc  une espace, un caract`re de tabulation ou une marque de fin de ligne e que cela revient ` modifier toutes les expressions r´guli`res, en rempla¸ant  <=  par  (blanc)∗ <= ,  <  par  (blanc)∗ < , a e e c

9

/* else if (c == ’>’) { c = lireCar(). /* if (c == ’=’) return INFEG. /* else { delireCar(c). /* else if (c == ’>’) return DIFF. /* return SUP. /* return INF. } Note. /* lexeme[lonLex++] = c. while (estLettre(c) || estChiffre(c)) { lexeme[lonLex++] = c.while (estBlanc(c)) c = lireCar(). au d´triment de leur s´curit´ d’utilisation. } } else if (c == ’=’) return EGAL. } delireCar(c). /* else { delireCar(c). } int estLettre(char c) { return ’A’ <= c && c <= ’Z’ || ’a’ <= c && c <= ’z’. /* return IDENTIF. /* if (c == ’=’) return SUPEG. } int estChiffre(char c) { return ’0’ <= c && c <= ’9’. On peut augmenter l’efficacit´ de ces fonctions. c = lireCar(). en en e e e e faisant 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 . /* ou bien donner une erreur } } etat = 1 */ etat = 2 */ etat = 3 */ etat = 4 */ etat = 5 */ etat = 6 */ etat = 7 */ etat = 8 */ etat = 9 */ etat = 10 */ */ Dans le programme pr´c´dent on utilise des fonctions auxiliaires. if (c == ’<’) { c = lireCar(). dont voici une version simple : e e int estBlanc(char c) { return c == ’ ’ || c == ’\t’ || c == ’\n’. return NEANT. c = lireCar(). } } else if (estLettre(c)) { lonLex = 0. } else { delireCar(c).

D´claration : e e e int carEnAvance = -1. if (carEnAvance >= 0) { r = carEnAvance. } Une autre mani`re de faire permet de se passer de la fonction ungetc.Il y plusieurs mani`res de prendre en charge la restitution d’un caract`re lu en trop (notre fonction delireCar ). SINON }.. car les diagrammes de transition e deviennent tr`s volumineux14 . } char lireCar(void) { return getc(stdin). quand la e e e e reconnaissance d’un  identificateur-ou-mot-r´serv´  est termin´e.3 e a 11 . plus facile ` programmer. on g`re une variable globale e e contenant. puis on modifie de la mani`re suivante la partie concernant les identificateurs de la fonction uniteSuivante : e . ALORS }. } char lireCar(void) { char r. e – soit on laisse l’analyseur traiter de la mˆme mani`re les mots r´serv´s et les identificateurs puis. int nbMotRes = sizeof motRes / sizeof motRes[0]. SI }. int uniteLexicale. stdin). e e e Dans les analyseurs lexicaux  en dur  on utilise souvent la deuxi`me m´thode. return r. ` la section 2. { "sinon". Leur reconnaissance peut donc se traiter de deux e e mani`res : e – soit on incorpore les mots r´serv´s au diagrammes de transition. quand il y a lieu. lexeme[lonLex++] = c. e e Si on dispose de la biblioth`que standard C on peut utiliser la fonction ungetc : e void delireCar(char c) { ungetc(c. e e a On se donne donc une table de mots r´serv´s : e e struct { char *lexeme. } ´ ´ Reconnaissance des mots reserves. } motRes[] = { { "si". } else r = getc(stdin). avec cela nos deux fonctions deviennent void delireCar(char c) { carEnAvance = c. 14 Nous /* etat = 9 */ utiliserons cette solution quand nous ´tudierons l’outil lex. on recherche le lex`me dans une table e e e e pour d´terminer s’il s’agit d’un identificateur ou d’un mot r´serv´. Pour cela... carEnAvance = -1. ce qui permet d’obtenir un analyseur e e tr`s efficace. Les mots r´serv´s appartiennent au langage d´fini par l’expression e e e r´guli`re lettre(lettre|chiffre)∗ .. tout comme les identificateurs. mais au prix d’un travail de programmation plus important. . { "alors". le caract`re lu en trop (il n’y a jamais plus d’un caract`re lu en trop). else if (estLettre(c)) { lonLex = 0. }.

e a e Autrement dit.c = lireCar(). . leurs lignes e e ont ´t´ supprim´es) : ee e ’ ’ 0 4∗ 8∗ 10∗ ’\t’ 0 4∗ 8∗ 10∗ ’\n’ 0 4∗ 8∗ 10∗ ’<’ 1 4∗ 8∗ 10∗ ’=’ 5 2 7 10∗ ’>’ 6 3 8∗ 10∗ lettre 9 4∗ 8∗ 9 chiffre erreur 4∗ 8∗ 9 autre erreur 4∗ 8∗ 10∗ 0 1 6 9 On obtiendra un analyseur peut-ˆtre plus encombrant que dans la premi`re mani`re. pour programmer un analyseur il suffira maintenant d’impl´menter la fonction transit ce e qui. i++) if (strcmp(lexeme. #define NBR_CARS 256 int transit[NBR_ETATS][NBR_CARS].2. motRes[i]. e e ee – un ensemble d’´tats F . 12 . c = lireCar().. e e On dit qu’un automate fini accepte une chaˆ d’entr´e s = c1 c2 . while (estLettre(c) || estChiffre(c)) { lexeme[lonLex++] = c. e – un ensemble fini de symboles (ou alphabet) d’entr´e Σ. e e e e a Un tel graphe est exactement ce que nous avons appel´ diagramme de transition ` la section 2. e Pour transformer un automate fini en un analyseur lexical il suffira donc d’associer une unit´ lexicale ` e a chaque ´tat final et de faire en sorte que l’acceptation d’une chaˆ produise comme r´sultat l’unit´ lexicale e ıne e e associ´e ` l’´tat final en question. c) = e2 alors le graphe a une fl`che ´tiquet´e par le caract`re c. transit : E × Σ → E. pourra se faire par une table ` double entr´e.lexeme) == 0) return motRes[i]. Voici ce programme : a e #define NBR_ETATS . c2 .. . mais certainement plus e e e rapide puisque l’essentiel du travail de l’analyseur se r´duira ` r´p´ter  bˆtement  l’action etat = transit[etat][lireCar( e a e e e jusqu’` tomber sur un ´tat final. issue de e1 et aboutissant ` e2 . } .. /* etat = 10 */ lexeme[lonLex] = ’\0’. appel´ ´tat initial.uniteLexicale. .. ck .3 Automates finis Un automate fini est d´fini par la donn´e de e e – un ensemble fini d’´tats E. for (i = 0. puisqu’elle est d´finie sur des ensembles finis. ck si et seulement si il existe dans le ıne e graphe de transition un chemin joignant l’´tat initial e0 ` un certain ´tat final ek . compos´ de k fl`ches ´tiquet´es e a e e e e e par les caract`res c1 .2. } delireCar(c). – un ´tat ε0 distingu´. 2. . Pour les e a e diagrammes des figures 2 et 3 cela donne la table suivante (les ´tats finaux sont indiqu´s en gras. e e e e Un automate peut ˆtre repr´sent´ graphiquement par un graphe o` les ´tats sont figur´s par des cercles (les e e e u e e ´tats finaux par des cercles doubles) et la fonction de transition par des fl`ches ´tiquet´es par des caract`res : e e e e e si transit(e1 . e – une fonction de transition. . return IDENTIF. Si on en reparle ici c’est qu’on peut en d´duire un autre style d’analyseur lexical. appel´s ´tats d’acceptation ou ´tats finaux. assez diff´rent de ce e e que nous avons appel´ analyseur programm´  en dur . i < nbMotRes.1 (voir la e a figure 2).

transit[1][’>’] = 3. index´ par les ´tats. ´toil´ et associ´ ` l’unit´ lexicale U (en tant que bool´en. int etat = etatInitial. ayant le num´ro NBR ETATS. int uniteSuivante(void) { char caractere. 15 Nous avons ajout´ un ´tat suppl´mentaire. e e e e a et une unit´ lexicale ERREUR pour signaler cela e 13 . transit[0][’>’] = 6. for (j = ’A’. j++) transit[i][j] = NBR_ETATS. for (j = ’a’. est d´fini par e e e – final[e] = 0 si e n’est pas un ´tat final (vu comme un bool´en. final[ 8] = . i++) for (j = 0. final[e] e e ea e e est encore vrai). j < NBR_CARS. j < NBR_CARS. voici comment les tableaux transit et final devraient ˆtre initialis´s pour correspondre aux diae e grammes des figures 2 et 315 : void initialiser(void) { int i. final[10] = . transit[0][’ ’] = 0. } if (final[etat] < 0) delireCar(caractere).int final[NBR_ETATS + 1]. for (j = 0. final[ 7] = SUPEG + 1. i < NBR_ETATS. transit[0][’<’] = 1. transit[0][’\t’] = 0. sans ´toile et associ´ ` l’unit´ lexicale U (en tant que bool´en. transit[0][’\n’] = 0. j++) transit[0][j] = 9. for (i = 0. final[e] est faux). final[NBR_ETATS] = ERREUR + 1. i < NBR_ETATS. final[ 5] = EGAL + 1.(INF + 1).(SUP + 1). final[ 4] = . j++) transit[0][j] = 9. final[ 2] = INFEG + 1.(IDENTIF + 1). Enfin. e e e a e – final[e] = −(U + 1) si e est final. j++) transit[1][j] = 4. car les unit´s lexicales sont num´rot´es au moins ` partir de z´ro). e e – final[e] = U + 1 si e est final. final[e] e ea e e est vrai. final[ 3] = DIFF + 1. etat = transit[etat][caractere]. j <= ’Z’. for (i = 0. j. j <= ’z’. qui correspond ` la mise en erreur de l’analyseur lexical. transit[0][’=’] = 5. } Notre tableau final. transit[1][’=’] = 2. while ( ! final[etat]) { caractere = lireCar().1. return abs(final[etat]) . i++) final[i] = 0.

` l’endroit voulu. j ’A’. e e Le programme lex 16 fait cette construction automatiquement : il prend en entr´e un ensemble d’expressions e r´guli`res et produit en sortie le texte source d’un programme C qui. e e e Plus pr´cis´ment (voyez la figure 4). De nos jours on utilise e e souvent flex. Un fichier source pour lex doit avoir un nom qui se termine par  . sont ´crits au d´but de la ligne. souvenez-vous de ceci : lex ´crit un fichier source C.1 Structure d’un fichier source pour lex En lisant cette section. la construction de cette table est une op´ration longue et d´licate. est l’analyseur lexical e e e correspondant au langage d´fini par les expressions r´guli`res en question. caractere). Il est fait de trois sections. j++) transit[6][j] = 8.3. quand ils apparaissent... e a e e a – des morceaux de code C invariable.for (j = 0. < j j j NBR_CARS. e expressions régulières lex lex. e e e e a – des morceaux de code C. for for for for } (j (j (j (j = = = = 0. j++) transit[9][j] = = 10. 9. j++) transit[9][j] = <= ’z’. d´limit´es e e par deux lignes r´duites17 au symbole %% : e %{ d´clarations pour le compilateur C e %} d´finitions r´guli`res e e e est un programme gratuit qu’on trouve dans le syst`me UNIX pratiquement depuis ses d´buts. lex produit un fichier source C. 9.c lex. j < NBR_CARS. Or. 4 – Utilisation de lex 2. une fois la table de e transition construite. c’est-`-dire la boucle qui r´p`te inlassablement etat ← transit (etat. nomm´ lex. e e e e 16 Lex 14 . Ce fichier est fait de trois sortes e d’ingr´dients : e – des tables garnies de valeurs calcul´es ` partir des expressions r´guli`res fournies.c.2. e e a 17 Notez que les symboles %%. ’0’. 9.yy. 2. contenant la d´finition e e e e de la fonction int yytext(void).3 Lex. transit[6][’=’] = 7. %{ et %}. trouv´s dans le fichier source lex et recopi´s tels quels. une version am´lior´e de lex qui appartient ` la famille GNU.l .2 : un programme appelle cette fonction et elle renvoie une unit´ lexicale reconnue dans le texte source. j++) transit[9][j] <= ’Z’. et notamment le  moteur  de l’automate. une fois compil´.c gcc autres modules monProg source à analyser monProg résultat de l'analyse Fig. aucun blanc ne les pr´c`de. dans le fichier produit. ’a’.yy. un g´n´rateur d’analyseurs lexicaux e e Les analyseurs lexicaux bas´s sur des tables de transitions sont les plus efficaces. qui est l’exacte homologue de notre fonction uniteSuivante de la section 2.yy. j++) transit[9][j] = <= ’9’.

} [A-Za-z] [0-9] {lettre}|{chiffre} ` Regles. cette partie se compose de d´clarations qui seront simplement recopi´es au d´but du e e e e fichier produit. } { return NOMBRE. de plus. la variable enti`re yylen ıne e e ıne e donne le nombre de ses caract`res. etc. Par exemple. En plus d’autres choses..h  contenant les d´finitions des codes conventionnels des unit´s lexicales (INFEG. au bon endroit. il e e e e e e faut alors les encadrer par des accolades. Exemples : lettre chiffre alphanum %% {lettre}{alphanum}* {chiffre}+(". dans la fonction yylex. A la fin de la reconnaissance d’une unit´ lexicale la chaˆ accept´e est la valeur de la variable yytext. Cette section se compose de fonctions C qui seront simplement e e recopi´es ` la fin du fichier produit.%% r`gles e %% fonctions C suppl´mentaires e La partie  d´clarations pour le compilateur C  et les symboles %{ et %} qui l’encadrent peuvent ˆtre omis. Les r`gles sont de la forme e { action } o` expressionR´guli`re est ´crit au d´but de la ligne (pas de blancs avant) . e variable yytext est d´clar´e dans le fichier produit par lex . } { return SINON. e e e 19 La 18 C’est 15 ."{chiffre}+)? expressionR´guli`re e e { return IDENTIF. Les d´finitions r´guli`res sont de la forme e e e identificateur expressionR´guli`re e e o` identificateur est ´crit au d´but de la ligne (pas de blancs avant) et s´par´ de expressionR´guli`re par des u e e e e e e blancs. } La r`gle e expressionR´guli`re e e { action } signifie  ` la fin de la reconnaissance d’une chaˆ du langage d´fini par expressionR´guli`re ex´cutez action . leur signification e est claire : quand une chaˆ du texte source est reconnue. Dans les exemples ci-dessus. on trouve souvent ici une directive #include qui produit l’inclusion du fichier  .). action est un morceau de code u e e e e source C. } { return ALORS. qui sera recopi´ tel quel. e a ´ ´ ` Definitions regulieres. Il faudra appeler de nouveau cette fonction pour que l’analyse du texte source e e reprenne. Un caract`re nul indique la fin de cette chaˆ . a ıne e e e e Le traitement par lex d’une telle r`gle consiste donc ` recopier l’action indiqu´e ` un certain endroit de la fonction e a e a yylex 18 . {lettre}{alphanum}* { return SI. } { return IDENTIF.. car il n’est pas sp´cifi´ laquelle des d´clarations  extern char *yytext  ou  extern char yytext[]  est pertinente. la r`gle suivante reconnaˆ les nombres entiers et en calcule la e e ıt valeur dans une variable yylval : parce que ces actions sont copi´es dans une fonction qu’on a le droit d’y utiliser l’instruction return. Exemples : e if then else . EGAL. de e ıne e type chaˆ de caract`res19 . Exemples : lettre chiffre [A-Za-z] [0-9] Les identificateurs ainsi d´finis peuvent ˆtre utilis´s dans les r`gles et dans les d´finitions subs´quentes . e e Quand elle est pr´sente. il vaut mieux ne pas chercher ` y faire r´f´rence dans d’autres e e a ee fichiers. INF. la fonction yylex se termine en rendant comme ıne r´sultat l’unit´ lexicale reconnue. e e La troisi`me section  fonctions C suppl´mentaires  peut ˆtre absente ´galement (le symbole %% qui la s´pare e e e e e de la deuxi`me section peut alors ˆtre omis). les actions ´tant toutes de la forme  return unite .

avant le premier appel de yylex . ] et − ` l’int´rieur e e e e a a e des crochets. yytext)  apparaˆ tr`s fr´quemment dans les actions. On peut l’abr´ger en ECHO : ıt e e e Voici le texte source pour cr´er l’analyseur lexical d’un langage comportant les nombres et les identificateurs e d´finis comme d’habitude. 21 Cela veut dire qu’on s’en remet au  client  de l’analyseur lexical. Par exemple. e e mais qui peut conduire ` des situations confuses. ` priori lexicale. (et non pas un caract`re ee e e quelconque). SI. on peut sans inconv´nient encadrer par des guillemets un caract`re ou une chaˆ qui n’en e e ıne avaient pas besoin. que nous transformons l’erreur  caract`re e e e e ill´gal . SUPEG. ". return IDENTIF. sont l´gitimes dans lex et y ont le mˆme sens. ). Il faut ˆtre tr`s soigneux en ´crivant les d´finitions et les r`gles dans le fichier source lex. diff´rent de la marque de fin de ligne.3. yytext). signifie un caract`re quelconque.. FAIRE. ıne e e e e Bien entendu. – l’expression [^caract`res] signifie : tout caract`re n’appartenant pas ` l’ensemble d´fini par [caract`res]. pour avoir les chaˆ ınes accept´es dans le texte ´crit par l’analyseur il suffit de le pr´voir dans e e e l’action correspondante. e e – une chaˆ accept´e au titre d’une expression r´guli`re n’est pas recopi´e sur la sortie standard. Pour ´viter des collisions entre ces e e e e codes ASCII et les unit´s lexicales nomm´es. Par exemple. " " signifie un blanc. e a 16 . e Pour les op´rateurs simples on d´cide que tout caract`re non reconnu par une autre expression r´guli`re est e e e e e une unit´ lexicale. la r`gle suivante reconnaˆ les identificateurs et fait en sorte qu’ils figurent e ıt dans le texte sorti : [A-Za-z][A-Za-z0-9]* [A-Za-z][A-Za-z0-9]* 2. e e a e e a Fichier unitesLexicales. } { ECHO. ∗. Les expressions r´guli`res accept´es par lex sont une extension de celles d´finies e e e e a ` la section 2. INFEG. sur la sortie standard. RENDRE. parfois utile. avec certaines modifications. +. ALORS. on donne ` ces derni`res des valeurs sup´rieures ` 255. Les unit´s lexicales correspondantes sont respectivement repr´sent´es par les constantes e e e conventionnelles IDENTIF.1. a ´ Echo du texte analyse. sinon. faire et rendre et les op´rateurs e e e e doubles ==.h." signifie le caract`re . [. e e a e e – l’expression  ^expressionR´guli`re  signifie : toute chaˆ reconnue par expressionR´guli`re ` la condition e e ıne e e a qu’elle soit au d´but d’une ligne. par e a exemple : yyin = fopen(argv[1]. et qu’elle est repr´sent´e par son propre code ASCII21 . |. En e e e e e effet. En outre. DIFF. pour s´parer les op´rateurs a e e pr´vus par le langage des caract`res sp´ciaux qui n’en sont pas. EGAL. NOMBRE.2 Un exemple complet { printf("%s". en une erreur syntaxique. return IDENTIF. return NOMBRE. on dispose de ceci (liste non exhaustive) : e e – un point . } Le texte  printf("%s". d´finies dans le fichier unitesLexicales. etc. Plus pr´cisement : e e – tous les caract`res qui ne font partie d’aucune chaˆ reconnue sont recopi´s sur la sortie standard (ils e ıne e  traversent  l’analyseur lexical sans en ˆtre affect´s). "[a-z]" signifie la chaˆ [a-z].h : #define #define #define #define #define #define IDENTIF NOMBRE SI ALORS SINON TANTQUE 256 257 258 259 260 261 20 On peut changer ce comportement par d´faut en donnant une valeur ` la variable yyin. c’est-`-dire l’analyseur syntaxique. e e e trouvent soient interpr´t´s comme tels. tout texte qui n’est pas exactement ` sa place (par exemple une d´finition ou une r`gle qui ne commencent a e e pas au d´but de la ligne) sera recopi´ dans le fichier produit par lex. c’est-`-dire (. e e – on peut encadrer par des guillemets un caract`re ou une chaˆ pour ´viter que les m´ta-caract`res qui s’y e ıne. alors. L’analyseur lexical produit par lex prend son texte source sur l’entr´e standard20 e et l’´crit. a Attention. Ou. C’est un comportement voulu. dit autrement. ?. ıne D’autre part. <= et >=. les mots r´serv´s si. "r") . } ´ ` Expressions regulieres.(+|-)?[0-9]+ { yylval = atoi(yytext). e – l’expression  expressionR´guli`re$  signifie : toute chaˆ reconnue par expressionR´guli`re ` la condition e e ıne e e a qu’elle soit ` la fin d’une ligne. !=. SINON. Les m´ta-caract`res pr´c´demment introduits. tantque. TANTQUE.

return INFEG. valNombre). return ALORS. }. return FAIRE. 17 . strcpy(valIdentif.h" int main(void) { int unite. return SUPEG. return EGAL. else if (unite == IDENTIF) printf(" ’%s’". printf(" (unite: %d". } "<=" { ECHO. { ECHO. } sinon { ECHO. Fichier analex. } . } Fichier principal.#define #define #define #define #define #define FAIRE RENDRE EGAL DIFF INFEG SUPEG 262 263 264 265 266 267 extern int valNombre. return DIFF. do { unite = yylex(). unite). return IDENTIF. } ">=" { ECHO. return TANTQUE. } tantque { ECHO. si { ECHO. } "==" { ECHO.l (le source pour lex ) : %{ #include "unitesLexicales. extern char valIdentif[]. valNombre = atoi(yytext). return SI.h> #include "unitesLexicales. char valIdentif[256]. /* rien */ } {chiffre}+ { ECHO. yytext). valIdentif). return yytext[0]. } "!=" { ECHO.h" %} chiffre [0-9] lettre [A-Za-z] %% [" "\t\n] { ECHO. } {lettre}({lettre}|{chiffre})* { ECHO. int yywrap(void) { return 1. if (unite == NOMBRE) printf(" val: %d". return SINON. } fairerendre { ECHO. } %% int valNombre. } alors { ECHO.c (purement d´monstratif) : e #include <stdio. return NOMBRE. printf(")\n").

ainsi. Voici comment obtenir rapidement un programme qui met tous ces prix en euros : %% [0-9]+(".} while (unite != 0).3.2f EUR ". on n’est pas oblig´ de e e e e renvoyer une unit´ lexicale pour signaler la chose . } Le programme pr´c´dent exploite le fait que tous les caract`res qui ne font pas partie d’une chaˆ reconnue e e e ıne sont recopi´s sur la sortie . supposons que nous disposons de textes contenant des indications de prix en francs.")?[" "\t\n] { printf("%. cette fonction doit rendre une valeur non nulle pour indiquer que le flot d’entr´e est d´finitivement e e e ´puis´."[0-9]*)?[" "\t\n]*F(rancs|". ou bien ouvrir un autre flot d’entr´e. r´duite ` { return 1 . } Fichier essai.l $ gcc lex.yy. Cela permet d’utiliser lex pour effectuer d’autres sortes de programmes que a des analyseurs lexicaux.txt si (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) . return 0.c principal. la plupart des caract`res du texte donn´ seront recopi´s tels quels. c’est reconnaˆ e ee e ıtre les chaˆ ınes du langage d´fini e par les expressions r´guli`res donn´es. Outre d’´ventuelles actions utiles dans telle ou telle application para e ticuli`re. } int main(void) { yylex()."[0-9]*)?[" "\t\n]*F(rancs|". successivement : 22 Dans certaines versions de lex une version simple de la fonction yywrap. Quand une telle reconnaissance est accomplie.55957).c -o monprog $ monprog < essai. on peut notamment d´clencher l’action qu’on veut et ne pas e e retourner ` la fonction appelante.")?[" "\t\n] cette expression se lit.3 Autres utilisations de lex Ce que le programme g´n´r´ par lex fait n´cessairement. atof(yytext) / 6. } %% int yywrap(void) { return 1. La fonction yywrap qui apparaˆ dans notre fichier source pour lex est appel´e lorsque l’analyseur ıt e rencontre la fin du fichier ` analyser22 . $ est le prompt du syst`me : e $ flex analex. }. 18 . Cr´ation d’un ex´cutable et essai sur le texte pr´c´dent (rappelons que lex s’appelle flex dans le monde e e e e Linux) . Par exemple.txt pour essayer l’analyseur : si x == 123 alors y = 0. est fournie et on n’a pas ` e a a s’en occuper. Les chaˆ e e e e ınes reconnues sont d´finies par l’expression r´guli`re e e e [0-9]+(". e e e 2. (unite: 59) $ Note.

Lorsqu’une chaˆ s’accordant ` cette syntaxe est reconnue.– une suite d’au moins un chiffre. Backus a invent´ le langage FORTRAN en 1955. sinon et une instruction .1. Naur le langage Algol en 1963. – ´ventuellement. comme :  on a une expression e e e en ´crivant successivement un terme. . ici. suivi d’un Sk [de toutes les mani`res possibles] . il ne voit ce dernier que ıt e comme une suite d’unit´s lexicales). Ce sont donc les unit´s lexicales. P. comme  99. Les symboles non terminaux sont des variables syntaxiques d´signant des ensembles de chaˆ e ınes de symboles terminaux. un nombre quelconque de blancs (espaces. un blanc obligatoire.1 Analyse syntaxique Grammaires non contextuelles Les langages de programmation sont souvent d´finis par des r`gles r´cursives. alors. e ea e 3. Sk se lit  pour produire un S correct [sousentendu : de toutes les mani`res possibles] il fait produire un S1 [de toutes les mani`res possibles] suivi e e d’un S2 [de toutes les mani`res possibles] suivi d’un . Les grammaires e non contextuelles sont un formalisme particuli`rement bien adapt´ ` la description de telles r`gles. Les productions peuvent ˆtre interpr´t´es de deux mani`res : e ee e – comme des r`gles d’´criture (on dit plutˆt de r´´criture). e – un F majuscule obligatoire. S0 . la fonction atof ıne a obtient la valeur que [le d´but de] cette chaˆ repr´sente. permettant d’engendrer toutes les chaˆ e e o ee ınes correctes. une expression.  F. extraites du texte source par l’analyseur lexical (il faut se rappeler que e l’analyseur syntaxique ne connaˆ pas les caract`res dont le texte source est fait. 4. – le symbole de d´part est le membre gauche de la premi`re production. – un ensemble VN de symboles non terminaux. . . e 2. qui sont des r`gles de la forme e S → S1 S2 . et on convient que : – VT est l’ensemble de tous les symboles terminaux apparaissant dans les productions. – un symbole S0 ∈ VN particulier. . De ce point de vue. avec une convention typographique pour distinguer les a symboles terminaux des symboles non terminaux.  15. appel´ symbole de d´part ou axiome. on all`ge les notations en d´cidant que si plusieurs productions ont le mˆme membre gauche e e e 23 J. e ıne e – enfin. e – ´ventuellement.  et  Francs  sont tous trois accept´s). nous pouvons e expliquer ces ´l´ments de la mani`re suivante : ee e 1. e e 3. une instruction et. soit. .1 D´finitions e Une grammaire non contextuelle. Sk se lit alors  pour reconnaˆ un S. Sk avec S ∈ VN et Si ∈ VN ∪ VT Compte tenu de l’usage que nous en faisons dans le cadre de l’´criture de compilateurs. . En e e e pratique on se limite ` donner la liste des productions. e 19 . e e e – comme des r`gles d’analyse. la production S → S1 S2 . – ´ventuellement.  F .50 Francs . on dit parfois grammaire BNF (pour Backus-Naur form23 ). est un quadruplet G = (VT . Il suffit alors de mettre dans le texte de sortie le e ıne e r´sultat de la division de cette valeur par le taux ad´quat . Les symboles terminaux sont les symboles ´l´mentaires qui constituent les chaˆ ee ınes du langage. Le symbole de d´part est un symbole non terminal particulier qui d´signe le langage en son entier. dans une suite de terminaux donn´e. – VN est l’ensemble de tous les symboles non terminaux apparaissant dans les productions. e e – un ensemble P de productions. . . les phrases. e e En outre. La production S → S1 S2 . ’+’ et une expression  ou  on obtient une instruction en ´crivant ` la e e a suite si. . . on dit aussi reconnaissance. fins de ligne). la chaˆ rancs ou un point (ainsi. ´ventuellement. suivi d’un Sk  La d´finition d’une grammaire devrait donc commencer par l’´num´ration des ensembles VT et VN .17 EUR . e e 3 3. P ) form´ de e – un ensemble VT de symboles terminaux. VN . il faut reconnaˆ un S1 suivi d’un S2 ıtre e ıtre suivi d’un . tabulations. un point suivi d’un nombre quelconque de chiffres.

2 D´rivations et arbres de d´rivation e e (G1 ) ´ Derivation. S1. . c’est que le symbole A se r´´crit dans la chaˆ γ quel que soit le contexte α. ıne e e e e A titre d’exemple. c’est ` dire w ∈ L(G1 ) .2 .1 S2. terme et facteur . le symbole de d´part est expression : e expression → expression "+" terme | terme terme → terme "*" facteur | facteur facteur → nombre | identificateur | "(" expression ")" 3. P } une grammaire non contextuelle . Soit G = {VT .kn alors on peut les noter simplement S → S1. . ıne e – une chaˆ en caract`res t´l´type ou "entre guillemets".2 . . .2 . A ∈ VN un symbole non terminal et γ ∈ (VT ∪VN )∗ une suite de symboles. . S2.1 Sn.1 Sn. comme  60 * vitesse e e e + 200 . Sn. . dans la suite αAβ les chaˆ ınes α et β sont le contexte du symbole A.2 . les symboles non terminaux sont expression. . d’´tapes on dit simplement que α e e e se d´rive en β et on ´crit e e α ⇒ β. et on ´crit e e e α0 ⇒ αn . ´ventuellement nul. Il peut ˆtre e e e formalis´ de la mani`re suivante : e e Soit G = (VT . . en a effet. VN .k2 | .1. Si α0 ⇒ α1 ⇒ · · · ⇒ αn on dit que α0 se d´rive en αn en n ´tapes. Sn.1 S1. Nous avons expression ⇒ w. Suivant notre convention. P ) une grammaire non contextuelle. si α se d´rive en β en un nombre quelconque. soit encore la grammaire G1 : expression → expression "+" terme | terme terme → terme "*" facteur | facteur facteur → nombre | identificateur | "(" expression ")" (G1 ) ∗ ∗ ∗ n et consid´rons la chaˆ "60 * vitesse + 200" qui. une fois lue par l’analyseur lexical. on dit que αAβ se d´rive en une ´tape en la suite αγβ ce qui s’´crit e e e αAβ ⇒ αγβ Cette d´finition justifie la d´nomination grammaire non contextuelle (on dit aussi grammaire ind´pendante e e e du contexte ou context free).2 . S1. . . . S2.S → S1.k1 | S2. VN . En effet. Ce que cette d´finition dit. tels qu’il existe dans P une production A → γ.kn Dans les exemples de ce document. S → Sn.. des identificateurs et les deux op´rateurs + et *. Quelles que soient les suites de symboles α et β.1 S2.2 .k1 S → S2. . S0 .. se pr´sente ainsi : e ıne e ∗ w = ( nombre "*" identificateur "+" nombre ). S0 . la convention typographique sera la suivante : – une chaˆ en italiques repr´sente un symbole non terminal. si α ∈ (VT ∪ VN )∗ est tel que S0 ⇒ α e e alors on dit que α est une proto-phrase de G.1 S1. repr´sente un symbole terminal. . | Sn. Le processus par lequel une grammaire d´finit un langage s’appelle d´rivation. . Enfin. Plus g´n´ralement. nous pouvons exhiber la suite de d´rivations en une ´tape : e e 20 . voici la grammaire G1 d´finissant le langage dont les chaˆ e ınes sont les expressions arithm´tiques form´es avec des nombres. Par exemple. le langage engendr´ par G est l’ensemble des e chaˆ ınes de symboles terminaux qui d´rivent de S0 : e L (G) = w ∈ VT ∗ | S0 ⇒ w Si w ∈ L(G) on dit que w est une phrase de G.k2 . β dans le e ee ıne lequel A apparaˆ ıt. Une proto-phrase dont tous les symboles sont terminaux est une phrase.

e 3. d´fini de la mani`re suivante : e e e expression expression terme terme facteur nombre 60 "*" * identificateur vitesse Fig. La d´rivation pr´c´dente est appel´e une d´rivation gauche car elle est enti`rement e e e e e e compos´e de d´rivations en une ´tape dans lesquelles ` chaque fois c’est le non-terminal le plus ` gauche qui est e e e a a r´´crit. e 21 . S2 . En r´alit´ on ne peut pas empˆcher les analyseurs d’en faire un peu plus car.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. VN . elles constituent la chaˆ w. il ne fait qu’accepter (par d´faut) ıne e e ou rejeter (en annon¸ant une erreurs de syntaxe) cette chaˆ c ıne. P }. . ıne Par exemple. la figure 5 montre l’arbre de d´rivation repr´sentant la d´rivation donn´e en exemple ci-dessus. il existe donc une ıne ∗ d´rivation telle que S0 ⇒ w. de la gauche vers la droite. un analyseur syntaxique est e a donc un programme qui n’extrait aucune information de la chaˆ analys´e. Or. lues de la e a e gauche vers la droite. c’est-`-dire construire un arbre de d´rivation dont la liste des feuilles e a e est w. e e e – si un nœud int´rieur e est ´tiquet´ par le symbole S et si la production S → S1 S2 . S0 . par S1 . Sk . Sk a ´t´ utilis´e pour e e e ee e d´river S alors les fils de e sont des nœuds ´tiquet´s. Parlant strictement. o` ` chaque ´tape c’est le non-terminal le plus ` droite ee e e e ua e a qui est r´´crit.1. ee ´ Arbre de derivation. alors. 5 – Arbre de d´rivation e – la racine de l’arbre est le symbole de d´part. . e – les nœuds int´rieurs sont ´tiquet´s par des symboles non terminaux. . On peut d´finir de mˆme une d´rivation droite. faire l’analyse syntaxique d’une chaˆ w ∈ VT ∗ c’est e ıne r´pondre ` la question  w appartient-elle au langage L(G) ? . e e e – les feuilles sont ´tiquet´es par des symboles terminaux et. .3 Qualit´s des grammaires en vue des analyseurs e terme facteur facteur "+" + nombre 200 Etant donn´e une grammaire G = {VT . si on allonge verticalement les branches de e e l’arbre (sans les croiser) de telle mani`re que les feuilles soient toutes ` la mˆme hauteur. Cette d´rivation peut ˆtre repr´sent´e graphiquement par un arbre. pour prouver que w ∈ L(G) e e e ∗ il faut exhiber une d´rivation S0 ⇒ w. droite) ne se voit pas sur l’arbre. e e e e On notera que l’ordre des d´rivations (gauche. cet arbre de derivation est d´j` une premi`re information extraite de la chaˆ source. Soit w une chaˆ de symboles terminaux du langage L(G) . un d´but de ea e ıne e  compr´hension  de ce que le texte signifie. appel´ arbre e e e e e e de d´rivation.

.. e e e e a ´ ` Grammaires recursives a gauche. mais empˆche l’´criture d’analyseurs pour cette e ea e e e grammaire. Dans le cas de la r´cursivit´ ` gauche simple. Cas particulier. du moins des analyseurs descendants24 . cela consiste ` remplacer une production telle que e e ea a 24 La question est un peu pr´matur´e ici.Nous examinons ici des qualit´s qu’une grammaire doit avoir et des d´fauts dont elle doit ˆtre exempte pour e e e que la construction de l’arbre de d´rivation de toute chaˆ du langage soit possible et utile. 6 – Deux arbres de d´rivation pour la mˆme chaˆ e e ıne Deux grammaires sont dites ´quivalentes si elles engendrent le mˆme langage. o` α est une chaˆ quelconque. Par exemple. on dit qu’on a une e u ıne r´cursivit´ ` gauche simple si la grammaire poss`de une production de la forme A → Aα. Une production A → . .1. Dans le cas d’une production A → Aα on se retrouve donc avec une fonction reconnaitre A qui commence par un appel de reconnaitre A. la grammaire G1 de la section 3. e ea e La r´cursivit´ ` gauche ne rend pas une grammaire ambigu¨. Ils correspondent e ıne aux deux d´rivations gauches distinctes : e 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 expr expr expr facteur nombre 2 * "*" expr facteur nombre 3 + "+" expr facteur nombre expr facteur nombre expr "*" expr facteur expr "+" expr facteur 10 2 nombre nombre * 3 + 10 Fig. Une grammaire est ambigu¨ s’il existe plusieurs d´rivations gauches diff´rentes e e e pour une mˆme chaˆ de terminaux. Il est souvent possible de e e remplacer une grammaire ambigu¨ par une grammaire non ambigu¨ ´quivalente. Une grammaire est r´cursive ` gauche s’il existe un non-terminal A e a ∗ et une d´rivation de la forme A ⇒ Aα. . la grammaire G1 est non ambigu¨ et ´quivalente ` la grammaire G2 ci-dessus. e 22 . donne lieu ` une fonction reconnaitre A dont le corps est fait e a d’appels aux fonctions reconnaissant les symboles du membre droit de la production. e ıne ¨ Grammaires ambigues. la grammaire G2 suivante est ambigu¨ : e ıne e expression → expression "+" expression | expression "*" expression | facteur facteur → nombre | identificateur | "(" expression ")" (G2 ) En effet. Par exemple. Par exemple. Bonjour la r´cursion infinie.1 est r´cursive ` gauche. la figure 6 montre deux arbres de d´rivation distincts pour la chaˆ "2 * 3 + 10". et mˆme simplement : e a e expression → expression "+" terme | terme terme → terme "*" facteur | facteur facteur → nombre | identificateur | "(" expression ")" (G1 ) Il existe une m´thode pour obtenir une grammaire non r´cursive ` gauche ´quivalente ` une grammaire e e a e a donn´e. mais nous verrons qu’un analyseur descendant est un programme compos´ de fonctions e e e directement d´duites des productions. mais il n’y a pas une m´thode e ee e g´n´rale pour cela.

ou ε-productions. alors la production  fin instr si → sinon instr  doit ˆtre appliqu´e. On r´sout ce ıne e e e probl`me en imposant aux analyseurs que nous ´crirons la r`gle de comportement suivante : dans la d´rivation e e e e d’un non-terminal. au moment o` l’analyseur doit d´river le nonu e ıne e terminal fin instr si. une ε-production ne peut ˆtre choisie que lorsqu’aucune autre production n’est applicable. puisqu’une production telle que e e fin expression → "+" terme fin expression | ε impliquera notamment que  une mani`re de reconnaˆ une fin expression consiste ` ne rien reconnaˆ . . Nous cherchons ` ´crire des analyseurs pr´dictifs. βαα. Autrement dit. e e e e e la question suivante se pose : n’y a-t-il pas deux arbres de d´rivation possibles pour la chaˆ 26 : e ıne si α alors si β alors γ sinon δ Nous l’avons d´j` dit. si. cela donne : si la chaˆ d’entr´e commence par + alors on doit n´cessairement e e ıne e e choisir la premi`re production. βααα. . sans risque d’erreur. Cela veut dire qu’` tout a e e a moment le choix entre productions qui ont le mˆme membre gauche doit pouvoir se faire. e a u Une transformation simple. e en comparant le symbole courant de la chaˆ ` analyser avec les symboles susceptibles de commencer les ıne a d´rivations des membres droits des productions en comp´tition. corrige ce d´faut (si les symboles susceptibles de e a e commencer une r´´criture de β1 sont distincts de ceux pouvant commencer une r´´criture de β2 ) : ee ee A → αA A → β1 | β2 Exemple classique. e e Une grammaire contenant des productions comme A → αβ1 | αβ2 viole ce principe car lorsqu’il faut choisir entre les productions A → αβ1 et A → αβ2 le symbole courant est un de ceux qui peuvent commencer une d´rivation de α. e alors elles engendrent toutes deux le langage {β. βα. notre grammaire semble devenir ambigu¨. La figure 7 montre l’arbre de d´rivation obtenu pour la chaˆ pr´c´dente. on l`ve cette ambigu¨ e en imposant que la ε-production ne peut ˆtre choisie que ea e ıt´ e si aucune autre production n’est applicable. ce e ıtre a ıtre qui est possible quelle que soit la chaˆ d’entr´e .} 26 Autre formulation de la mˆme question :  sinon  se rattache-t-il ` la premi`re ou ` la deuxi`me instruction  si  ? e a e a e 23 . . l’apparition d’une ε-production semble rendre ambigu¨ la grammaire. e ` Factorisation a gauche. on aura un e probl`me pour l’´criture d’un analyseur. appel´e factorisation ` gauche. Si on ne prend pas de disposition particuli`re. et on ne peut pas choisir ` coup sˆr entre αβ1 et αβ2 . Les grammaires de la plupart des langages de programmation d´finissent ainsi l’instruce tion conditionnelle : instr si → si expr alors instr | si expr alors instr sinon instr Pour avoir un analyseur pr´dictif il faudra op´rer une factorisation ` gauche : e e a instr si → si expr alors instr fin instr si fin instr si → sinon instr | ε Comme pr´c´demment. la chaˆ d’entr´e commence par le terminal sinon. si α et β sont des symboles terminaux. e e e ıne e e 25 Pour se convaincre de l’´quivalence de ces deux grammaires il suffit de s’apercevoir que. ainsi. Plus pr´cis´ment. e Dans l’exemple pr´c´dent.A → Aα | β par les deux productions25 A → βA A → αA | ε En appliquant ce proc´d´ ` la grammaire G1 on obtient la grammaire G3 suivante : e ea expression → terme fin expression fin expression → "+" terme fin expression | ε terme → facteur fin terme fin terme → "*" facteur fin terme | ε facteur → nombre | identificateur | "(" expression ")" (G3 ) A propos des ε-productions. La transformation de grammaire montr´e ci-dessus a introduit des produce tions avec un membre droit vide.

mais il existe des langages (pratiquement tous les langages de programmation. analyser une chaˆ de symboles terminaux w ∈ VT ∗ c’est e ıne ∗ construire un arbre de d´rivation prouvant que S0 ⇒ w. s´par´es par un c.2 Analyseurs descendants Etant donn´e une grammaire G = (VT .instr_si si expr α si alors expr β alors instr instr γ fin_instr_si fin_instr_si sinon instr δ Fig. comme ababcabab. la deuxi`me occurrence de w ` l’utilisation de ce dernier). Un m´canisme permet de faire avancer –jamais reculer– le ruban. excusez du peu. VN . e Les grammaires des langages que nous cherchons ` analyser ont un ensemble de propri´t´s qu’on r´sume en a ee e disant que ce sont des grammaires LL(1). b et c sont des terminaux. e e ∗ On d´montre par exemple que le langage L = wcw | w ∈ (a|b) . une production parmi plusieurs candidates (c’est le 1 de LL(1)). if chaîne source déjà examinée unité courante avancer chaîne source restant à examiner Fig. o` a. !) e e qu’elles ne peuvent pas d´crire compl`tement. qu’ont la plupart des langages. pour a e rendre visible le symbole suivant. de telle mani`re qu’un seul symbole ıne e e e e e est visible ` la fois . 7 – Arbre de d´rivation pour la chaˆ si α alors si β alors γ sinon δ e ıne ε 3. e a Autrement dit. ne peut e u pas ˆtre d´crit par une grammaire non contextuelle. e e 3.1.. 8 – Fenˆtre ` symboles terminaux e a 24 . P ). Cela signifie qu’on peut en ´crire des analyseurs : e – lisant la chaˆ source de la gauche vers la droite (gauche = left. Pour r´fl´chir au fonctionnement de nos analyseurs il est utile d’imaginer e e que la chaˆ source est ´crite sur un ruban d´filant derri`re une fenˆtre. Ce probl`me doit n´cessairement ˆtre remis ` une phase e e e e e a ult´rieure d’analyse s´mantique. voyez la figure 8. ıne – cherchant ` construire une d´rivation gauche (c’est le deuxi`me L). e A propos du symbole accesible.. S0 .4 Ce que les grammaires non contextuelles ne savent pas faire Les grammaires non contextuelles sont un outil puissant. c’est le premier L). de v´rifier que les identificateurs apparaissant dans les instructions e ont bien ´t´ pr´alablement d´clar´s (la premi`re occurrence de w dans wcw correspond ` la d´claration d’un ee e e e e a e identificateur. l’analyse syntaxique ne permet pas de v´rifier que les identificateurs utilis´s dans les proe e grammes font l’objet de d´clarations pr´alables. a e e – dans lesquels un seul symbole de la chaˆ source est accessible ` chaque instant et permet de choisir. L est fait de phrases comportant deux chaˆ e e ınes de a et b identiques. L’importance de cet exemple provient du fait que L mod´lise e e e l’obligation. ıne a lorsque c’est n´cessaire. en tout cas plus puissant que les expressions r´guli`res.

cette  machine ` symboles terminaux  ne sera a rien d’autre que l’analyseur lexical pr´alablement ´crit . e a Initialisation. Au d´part. c’est-`-dire une s´quence de symboles terminaux et non terminaux a e a ` laquelle on ajoute et on enl`ve des symboles par une mˆme extr´mit´. ıne e ´ Iteration. . . e 3.1 Principe Analyseur descendant.1. A titre d’exemple. . . Tant que la pile n’est pas vide. voici la reconnaissance par un tel analyseur du texte "60 * vitesse + 200" avec la grammaire G3 de la section 3. Sk . en l’occurrence l’extr´mit´ de gauche e e e e e e (c’est une pile couch´e ` l’horizontale. Un analyseur descendant construit l’arbre de d´rivation de la racine (le symbole e de d´part de la grammaire) vers les feuilles (la chaˆ de terminaux). Lorsque la pile est vide – si le terminal visible ` la fenˆtre est la marque qui indique la fin de la chaˆ d’entr´e alors l’analyse a e ıne e a r´ussi : la chaˆ appartient au langage engendr´ par la grammaire. qui se remplit de la droite vers la gauche). d´piler S et empiler S1 S2 . . e ıne e – sinon. a e – sinon. Sk pouvant convenir. r´p´ter les op´rations suivantes e e e – si le symbole au sommet de la pile (c. Les ´tats successifs de la pile et de la ıne e e fenˆtre sont les suivants : e 25 . alors d’apr`s le terminal e visible ` la fenˆtre. uniteCourante = yylex()). . a – s’il y a plusieurs productions ayant S pour membre gauche. Sk ayant S pour membre gauche alors d´piler S et empiler S1 S2 . et l’op´ration  faire avancer le ruban  se traduira par uniteCourante = uniteSuivante() e (ou bien. . la pile contient le symbole de d´part de la grammaire et la fenˆtre montre le e e e premier symbole terminal de la chaˆ d’entr´e. Sk ` la place. e ıne Pour en d´crire sch´matiquement le fonctionnement nous nous donnons une fenˆtre ` symboles terminaux e e e a comme ci-dessus et une pile de symboles. – si le symbole au sommet de la pile est un non terminal S e – s’il y a une seule production S → S1 S2 . e Terminaison.2. afficher  caract`res inattendus ` la suite d’un texte e a correct ). signaler une erreur (par exemple. alors a e e – d´piler le symbole au sommet de la pile et e – faire avancer le terminal visible ` la fenˆtre. si c’est lex qui a ´crit l’analyseur lexical. sans faire avancer ce dernier.-`-d. le plus ` gauche) est un terminal α a a – si le terminal visible ` la fenˆtre est le mˆme symbole α.Lorsque nous programmerons effectivement des analyseurs.3 : expression → terme fin expression fin expression → "+" terme fin expression | ε terme → facteur fin terme fin terme → "*" facteur fin terme | ε facteur → nombre | identificateur | "(" expression ")" (G3 ) La chaˆ d’entr´e est donc (nombre "*" identif "+" nombre). . signaler une erreur (par exemple afficher  α attendu ) . le symbole visible ` la fenˆtre sera repr´sent´ par une e e a e e e variable uniteCourante. choisir l’unique production S → a e S1 S2 .

 reconnaissance de Sn . dans e e e lequel la partie  choisir la production. appel´e table a e e e a a e e d’analyse. Comme ce choix ne d´pend que du terminal e visible ` la fenˆtre. on peut le faire. α). e Pour avoir un analyseur descendant non r´cursif il suffit alors de se donner une fenˆtre ` symboles terminaux e e a (c’est-`-dire un analyseur lexical). comme expliqu´ ci-apr`s. e a e e 3. Voici les principes de l’´criture d’un e ea e e tel analyseur : e a 1. . . calcul´e ` l’avance. mais nous pouvons remarquer qu’on peut r´aliser des programmes e e it´ratifs qui implantent l’algorithme expliqu´ ` la section pr´c´dente. dans la fonction e correspondante. Chaque groupe de productions ayant le mˆme membre gauche S donne lieu ` une fonction void reconnaitre S(void).2.  recona e naissance de S2 . une table a e a e e d’analyse comme expliqu´ ici et un petit programme qui implante l’algorithme de la section pr´c´dente. . ee 3. Une s´quence de symboles S1 S2 . 26 . .2 Analyseur descendant non r´cursif e Nous ne d´velopperons pas cette voie ici..3 Analyse par descente r´cursive e A l’oppos´ du pr´c´dent.2. La chaˆ donn´e appartient donc ıne e bien au langage consid´r´. et de mani`re tr`s efficace.fenˆtre e nombre nombre nombre nombre "*" "*" identificateur identificateur "+" "+" "+" "+" nombre nombre nombre ¶ ¶ ¶ pile terme terme terme terme terme terme terme terme ε fin fin fin fin fin fin fin fin fin fin fin fin fin fin fin expression expression expression expression expression expression expression expression expression expression expression expression expression expression expression expression ε facteur fin nombre fin fin "*" facteur fin facteur fin identificateur fin fin "+" terme terme facteur nombre Lorsque la pile est vide. d’apr`s le symbole terminal e visible ` la fenˆtre. s´lectionne l’ex´cution des actions correspondant au membre droit de la production a e e e pertinente. repr´sentant une fonction Choix : VN × VT → P qui ` un couple (S. la fenˆtre exhibe ¶. ou plus simplement void S(void). la marque de fin de chaˆ e ıne. le symbole visible ` la fenˆtre n’est pas modifi´. Sn dans le membre droit d’une production donne lieu. une pile de symboles comme expliqu´ ` la section pr´c´dente. Lorsque plusieurs productions ont le mˆme membre gauche. le corps de la fonction correspondante est une conditionnelle (instruction if ) ou un aiguillage (instruction switch) qui. e a e a e 3.  se r´sume ` une consultation de la table P = Choix(S. α) form´ e a e a e d’un non terminal (le sommet de la pile) et un terminal (le symbole visible ` la fenˆtre) associe la production a e qu’il faut utiliser pour d´river S. Le corps de cette fonction se d´duit des membres droits e des productions en question. Dans cette s´lection.. e ea e e La plus grosse difficult´ est le choix d’une production chaque fois qu’il faut d´river un non terminal qui e e est le membre gauche de plusieurs productions de la grammaire. un analyseur descendant est donc un couple form´ d’une table dont les valeurs sont intimement e e li´es ` la grammaire analys´e et d’un programme tout ` fait ind´pendant de cette grammaire. ` une s´quence d’instructions traduisant les actions  reconnaissance de S1 . e a En d´finitive. e e e 2. . ` l’aide d’une table ` double entr´e. un analyseur par descente r´cursive est un type d’analyseur descendant dans lequel e e e e le programme de l’analyseur est ´troitement li´ ` la grammaire analys´e.

L’ensemble des fonctions ´crites selon les prescriptions pr´c´dentes forme l’analyseur du langage consid´r´. e – sinon la chaˆ est erron´e28 (on peut par exemple afficher le message  caract`res ill´gaux apr`s une ıne e e e e expression correcte ). } void terme(void) { facteur().4. fin_expression(). Cela revient ` consid´rer que. Si S est un symbole non terminal. } void facteur(void) { if (uniteCourante == NOMBRE) terminal(NOMBRE). e e 28 Une 27 Notez (G3 ) 27 .1. fin_expression(). } void fin_expression(void) { if (uniteCourante == ’+’) { terminal(’+’). Exemple. e a ıne. Au retour de cette e e fonction – si la fenˆtre ` terminaux montre la marque de fin de chaˆ l’analyse a r´ussi. l’action  reconnaissance de α  consiste ` consid´rer le symbole terminal a e visible ` la fenˆtre et a e – s’il est ´gal ` α.3 : expression → terme fin expression fin expression → "+" terme fin expression | ε terme → facteur fin terme fin terme → "*" facteur fin terme | ε facteur → nombre | identificateur | "(" expression ")" et voici l’analyseur par descente r´cursive correspondant : e void expression(void) { terme(). e a e – sinon. fin_terme(). } void fin_terme(void) { if (uniteCourante == ’*’) { terminal(’*’). 5. afficher  α attendu ). faire passer la fenˆtre sur le symbole suivant27 . facteur(). peu e e a e importent les ´ventuels caract`res qui pourraient suivre. fin_terme(). a e ıne e On lance l’analyse en appellant la fonction associ´e au symbole de d´part de la grammaire. o` il est indiqu´ de faire avancer la fenˆtre. l’action  reconnaissance de S  se r´duit ` l’appel de fonction recone a naitre S(). u e e autre attitude possible -on l’a vue adopt´e par certains compilateurs de Pascal. terme(). e e e ee L’initialisation de l’analyseur consiste ` positionner la fenˆtre sur le premier terminal de la chaˆ d’entr´e.consiste ` ne rien v´rifier au retour de e a e la fonction associ´e au symbole de d´part. Si α est un symbole terminal. } else /* rien */. } else /* rien */. dans cette description. que c’est ici le seul endroit. annoncer une erreur (par exemple. Voici encore la grammaire G3 de la section 3. si le texte source comporte un programme correct.

. la maladresse de la chose devient ´vidente e e if (uniteCourante == ’+’) { if (uniteCourante == ’+’) lireUnite(). notamment. Mais il n’est pas e e interdit de pratiquer ensuite certaines simplifications. } else /* rien */. else { terminal(’(’). else switch (uniteVoulue) { case NOMBRE: erreur("nombre attendu"). terme(). } } La reconnaissance d’un terminal revient fr´quemment dans un analyseur.. terminal(’)’). la fonction fin expression commence par les deux lignes if (uniteCourante == ’+’) { terminal(’+’). . . Nous en avons fait une fonction e s´par´e (on suppose que erreur est une fonction qui affiche un message et termine le programme) : e e void terminal(int uniteVoulue) { if (uniteCourante == uniteVoulue) lireUnite(). Nous avons ´crit le programme pr´c´dent en appliquant syst´matiquement les r`gles donn´es plus e e e e e e haut. L’appel g´n´ralis´ de la fonction terminal. est ` l’origine de certains test redondants. uniteVoulue). default: erreur("%c attendu". e e e e a Par exemple.else if (uniteCourante == IDENTIFICATEUR) terminal(IDENTIFICATEUR). si on d´veloppe l’appel de terminal. else { 28 . case IDENTIFICATEUR: erreur("identificateur attendu").. expression(). void facteur(void) { if (uniteCourante == NOMBRE) lireUnite(). Une version plus raisonnable des fonctions fin expression et facteur serait donc : void fin_expression(void) { if (uniteCourante == ’+’) { lireUnite().. } } Note. obtenant ainsi un analyseur correct dont la structure refl`te la grammaire donn´e. else if (uniteCourante == IDENTIFICATEUR) lireUnite(). fin_expression().. } .. ne serait-ce pour rattraper certaines maladresses de notre d´marche.

ce qui risque de manquer d’` propos. S0 . afin de permettre ult´rieurement une r´duction plus e a e e e juste ? A titre d’exemple.3 3. Lorsque dans la pile il n’y a e e plus que le symbole de d´part de la grammaire. VN . identificateur ou ’(’ attendus ici").3. ou bien continuer ` d´caler. si tous les symboles de la chaˆ d’entr´e ont ´t´ lus. P } et une chaˆ w ∈ VT ∗ .1. terminal(’)’). mais cette fois elle grandit de la gauche vers la droite. ´tant donn´es une grammaire G = {VT . c’est-`-dire la e a chaˆ de terminaux (nombre "*" identificateur "+" nombre) : ıne 29 Comme pr´c´demment. } } Comme elle est ´crite ci-dessus. expression().terminal(’(’). partent des terminaux qui constituent la chaˆ ıne e ıne d’entr´e et  vont vers  le symbole de d´part. expression(). terminal(’)’). e e ea c’est-`-dire que son sommet est son extr´mit´ droite. faut-il r´duire tout de suite. Une version encore plus raisonnable de cette a fonction serait void facteur(void) { if (uniteCourante == NOMBRE) lireUnite(). le e e ıne but de l’analyse syntaxique est la construction d’un arbre de d´rivation qui prouve w ∈ L(G). e e Le principe g´n´ral des m´thodes ascendantes est de maintenir une pile de symboles29 dans laquelle sont e e e empil´s (l’empilement s’appelle ici d´calage) les terminaux au fur et ` mesure qu’ils sont lus. cette pile est un tableau couch´ ` l’horizontale . Les m´thodes ascendantes.1 : expression → expression "+" terme | terme terme → terme "*" facteur | facteur facteur → nombre | identificateur | "(" expression ")" (G1 ) voici la reconnaissance par un analyseur ascendant du texte d’entr´e "60 * vitesse + 200". e Le probl`me majeur de ces m´thodes est de faire deux sortes de choix : e e – si les symboles au sommet de la pile constituent le membre droit de deux productions distinctes. Les m´thodes e e descendantes construisent cet arbre en partant du symbole de d´part de la grammaire et en  allant vers  la e chaˆ de terminaux. a e e 29 . l’analyse e ıne e ee a r´ussi. avec la grammaire G1 de la section 3. laquelle utiliser pour effectuer la r´duction ? e – lorsque les symboles au sommet de la pile sont le membre droit d’une ou plusieurs productions. else if (uniteCourante == ’(’) { lireUnite(’(’). au contraire. Si les k symboles du sommet de la pile constituent le membre droit d’une production alors ils peuvent ˆtre d´pil´s et remplac´s e e e e par le membre gauche de cette production (cette op´ration s’appelle r´duction). } else erreur("nombre. la fonction facteur aura tendance ` faire passer toutes les erreurs par le e a diagnostic  ’(’ attendu . else if (uniteCourante == IDENTIFICATEUR) lireUnite(). } 3.1 Analyseurs ascendants Principe Comme nous l’avons dit. tant que les e e a symboles au sommet de la pile ne sont pas le membre droit d’une production de la grammaire.

e) e e a a o` s ∈ E et e ∈ VT et de deux tables Action et Suivant. qui ex´cute les op´rations e e e e suivantes : Initialisation.fenˆtre e nombre "*" "*" "*" identificateur "+" "+" "+" "+" nombre ¶ ¶ ¶ ¶ pile nombre facteur terme terme "*" terme "*" terme "*" terme expression expression expression expression expression expression identificateur facteur "+" "+" nombre "+" facteur "+" terme action d´calage e r´duction e r´duction e d´calage e d´calage e r´duction e r´duction e r´duction e d´calage e d´calage e r´duction e r´duction e r´duction e succ`s e On dit que les m´thodes de ce type effectuent une analyse par d´calage-r´duction. ` partir de la a a grammaire du langage . c’est le cas le plus fr´quent. s ) placer la fenˆtre sur le prochain symbole de la chaˆ d’entree e ıne 30 . un analyseur LR est constitu´ par la donn´e d’un ensemble e e e d’´tats E. le r´ductions faites dans l’analyse pr´c´dente construisent. la section 3. e Le principal inconv´nient de ces analyseurs est qu’ils n´cessitent des tables qu’il est extrˆmement difficile e e e de construire ` la main.3. Placer la fenˆtre sur le premier symbole de la chaˆ d’entr´e et vider la pile. qui repr´sentent des fonctions : u e Action : E × VT → ({ decaler } × E) ∪ ({reduire} × P ) ∪ { succes. Tant que c’est possible. d’une pile de doublets (s. la d´rivation droite e e e a e suivante : expression ⇒ expression "+" terme ⇒ expression "+" facteur ⇒ expression "+" nombre ⇒ terme "+" nombre ⇒ terme "*" facteur "+" nombre ⇒ terme "*" identificateur "+" nombre ⇒ facteur "*" identificateur "+" nombre ⇒ nombre "*" identificateur "+" nombre 3. e e e e Par exemple. r´p´ter : e e e soit s l’´tat au sommet de la pile et α le terminal visible ` la fenˆtre e a e si Action(s. de construire des analyseurs ascendants plus efficaces que les analyseurs e descendants. VN . et acceptant une classe de langages plus large que la classe des langages trait´s par ces derniers. Comme le montre le e e e tableau ci-dessus. erreur } Suivant : E × VN → E Un analyseur LR comporte enfin un programme.4 pr´sente yacc. le point important est le choix entre r´duction et d´calage. d’une fenˆtre ` symboles terminaux (c’est-`-dire un analyseur lexical). en construisant ıne e u l’inverse d’une d´rivation droite (d’o` le R) avec une vue sur la chaˆ d’entr´e large de k symboles . ` l’envers. e Les analyseurs LR(k) lisent la chaˆ d’entr´e de la gauche vers la droite (d’o` le L).2 Analyse LR(k) Il est possible. un des plus connus de ces outils. S0 . P ). α) = (decaler. lorsqu’on e u ıne e dit simplement LR on sous-entend k = 1. malgr´ les apparences. des outils existent pour les construire automatiquement. e ıne e It´ration. Le principe est : les r´ductions pratiqu´es r´alisent la construction inverse d’une d´rivation droite. s ) empiler (α. ind´pendant du langage analys´. e Etant donn´e une grammaire G = (VT . chaque fois qu’une r´duction est e e e possible. Heureusement.

Ce dernier peut ainsi ex´cuter des actions ou produire des informations d´duites du texte source. ee Le programme yacc 30 est un tel g´n´rateur d’analyseurs syntaxiques. ). Il prend en entr´e un fichier source e e e constitu´ essentiellement des productions d’une grammaure non contextuelle G et sort ` titre de r´sultat un e a e programme C qui.sinon. qui appartient ` la famille GNU. les tables Action et Suivant d’un analyseur LR sont difficiles ` construire e a manuellement. bison est issu de la confusion de yacc avec yak ou yack.tab. e a e ce sont des bouts de code source C que yacc place aux bons endroits de l’analyseur construit.4. A → β) d´piler autant d’´l´ments de la pile qu’il y a de symboles dans β e ee (soit (γ.y . c’est-`-dire devenir un compilateur.h yacc y. si Action(s.. les ´tats suffisent. symbole) pour clarifier l’explication. Par d´faut. e grammaire non contextuelle y. En r´alit´. A)) sinon. un g´n´rateur d’analyseurs syntaxiques e e Comme nous l’avons indiqu´. Les ´tats de l’analyseur repr´sentent les diverses configurations e e e e dans lesquelles la pile peut se trouver. une fois compil´.. u e 3. d´limit´es par e e e deux lignes r´duites au symbole %% : e %{ d´clarations pour le compilateur C e %} 30 Dans le monde Linux on trouve une version am´lior´e de yacc. Il est fait de trois sections. 9 – Utilisation courante de yacc Dans la description de la grammaire donn´e ` yacc on peut associer des actions s´mantiques aux productions . sans qu’il faille de d´claration sp´ciale pour cela. e e a Un analyseur syntaxique requiert pour travailler un analyseur lexical qui lui d´livre le flot d’entr´e sous forme e e d’unit´s lexicales successives. Note.c gcc monProg monProg résultat de l'analyse Fig. le programme produit par yacc comporte des e e appels de la fonction yylex aux endroits o` l’acquisition d’une unite lexicale est n´cessaire. si Action(s. α) = succes arrˆt e sinon erreur. α) = (reduire.c lex. il n’y a donc pas besoin d’empiler les symboles. yacc suppose que l’analyseur lexical disponible a ´t´ fabriqu´ par lex. Suivant(s . e e ee e Autrement dit.4 Yacc. Le nom de e e e a yacc provient de “yet another compiler compiler” ( encore un compilateur de compilateurs. s ) le nouveau sommet de la pile) empiler (A. Nous e avons utilis´ une pile de couples (etat. e 3. mais il existe des outils pour les d´duire automatiquement des productions de la grammaire e consid´r´e.tab.1 Structure d’un fichier source pour yacc Un fichier source pour yacc doit avoir un nom termin´ par  . est un analyseur syntaxique pour le langage L(G).c autres modules source à analyser y.tab. 31 . notre pile est redondante. nomm´e bison. gros bovin du Tibet.yy.

. terme : terme ’*’ facteur | facteur . e 32 .h  refl`tent le nom du fichier source  .tab. alors e a ıne e il n’y a pas d’actions s´mantiques et les r`gles de traduction sont simplement les productions de la grammaire.3. Pour cette raison. 31 Du 32 Dans (G1 ) moins si on le lance avec l’option -d. comme dans  yacc -d maSyntaxe.d´clarations pour yacc e %% r`gles (productions + actions s´mantiques) e e %% fonctions C suppl´mentaires e Les parties  d´clarations pour le compilateur C  et  fonctions C suppl´mentaires  sont simplement e e recopi´es dans le fichier produit.4. #define #define NOMBRE IDENTIF VARIABLE FAIRE RETOUR 257 258 259 272 273 Notez que yacc consid`re que tout caract`re est susceptible de jouer le rˆle d’unit´ lexicale (comme cela a e e o e ´t´ le cas dans notre exemple de la section 2. Chacune de ces deux parties e e a peut ˆtre absente. ´ Specification de VT . qui les utilise. e e e e ` Regles de traduction. e Dans la partie  d´clarations pour yacc  on rencontre souvent les d´clarations des unit´s lexicales. facteur : nombre | identificateur | ’(’ expression ’)’ . sous une e e e forme qui laisse yacc se charger d’inventer des valeurs conventionnelles : %token NOMBRE IDENTIF VARIABLE TABLEAU FONCTION %token OU ET EGAL DIFF INFEG SUPEG %token SI ALORS SINON TANTQUE FAIRE RETOUR Ces d´clarations d’unit´s lexicales int´ressent yacc. e e e destin´ ` ˆtre inclus dans le source lex (au lieu du fichier que nous avons appel´ unitesLexicales.1 : expression → expression "+" terme | terme terme → terme "*" facteur | facteur facteur → nombre | identificateur | "(" expression ")" serait ´crite dans un fichier source yacc (pour obtenir un analyseur pur.tab. nomm´ y. et les identificateurs e e mentionn´s dans les d´clarations %token sont tenus pour des symboles terminaux. Une r`gle de traduction est un ensemble de productions ayant le mˆme membre e e gauche. encadr´s par des apostrophes comme dans les programmes C. – par d´faut. section 3.h32 .y . ces constantes sont num´rot´es ` partir de ee e e a 256. Par exemple.. yacc produit31 un fichier suppl´mentaire. . le m´ta-symbole → est indiqu´ par deux points  :  et chaque r`gle (c’este e e e a `-dire chaque groupe de productions avec le mˆme membre gauche) est termin´e par un point-virgule  . e Par exemple. e e Dans les r`gles de traduction. sans actions s´mantiques) : e e %token nombre identificateur %% expression : expression ’+’ terme | terme .3. voici comment la grammaire G1 de la section 3.h dans e a e e l’exemple de la section 2. pour cette raison. Si e ee e e on ´crit un analyseur  pur . le fichier produit pour les d´clarations ci-dessus ressemble ` ceci : e a #define #define #define . Dans un fichier source yacc : – les caract`res simples. chacune associ´ ` une action s´mantique. mais aussi lex.2).c  et  . qui les manipule en tant que e e e r´sultats de la fonction yylex. e e – tous les autres identificateurs apparaissant dans les productions sont consid`res comme des symboles non e terminaux. C’est e e un code que l’analyseur ex´cutera lorsque la production correspondante aura ´t´ utilis´e dans une r´duction.2) est un morceau de code source C encadr´ par des accolades. le symbole de d´part est le membre gauche de la premi`re r`gle. ea e Une action s´mantique (cf.2) . VN et S0 .1.y .tab. La e e barre verticale  |  a la mˆme signification que dans la notation des grammaires. respectivement au d´but et ` la fin de ce fichier. c’est-`-dire un analyseur qui ne fait qu’accepter ou rejeter la chaˆ d’entr´e. le cas de bison les noms des fichiers  .

yytext). return identificateur. C’est la notation habituelle.2 Actions s´mantiques et valeurs des attributs e Une action s´mantique est une s´quence d’instructions C ´crite. e e e a Cette s´quence est recopi´e par yacc dans l’analyseur produit. e e ce n’est pas tr`s informatif !). return nombre.tab. L’effet pr´cis de l’instruction ci-dessus. en troisi`me section du fichier pr´c´dent : e e e %% int main(void) { if (yyparse() == 0) printf("Texte correct\n"). C’est une fonction de prototype void e e e e yyerror(char *message). de telle mani`re qu’elle sera ex´cut´e.y : %{ char nom[256]. e a e d´pend de la mani`re dont l’analyseur lexical ´crit les unit´s lexicales au fur et ` mesure de l’analyse. e e e e e 33 . c’est-`-dire la pr´sentation effective des messages d’erreur. e Fichier lexique. pendant e e e e e l’analyse.3). e e e e a 3. e e e e e e 34 Dans la notation postifix´e. d’identificateurs et des op´rateurs + et ∗.B. %% int yywrap(void) { return 1. lorsque la production correspondante aura ´t´ employ´e pour faire une r´duction (cf.  analyse par ee e e d´calage-r´duction  ` la section 3. appel´e aussi notation polonaise inverse.h" extern char nom[]. et elle n’est pas ambigu¨. } return yytext[0]. entre accolades. } En r´alit´. et elle est ambigu¨ . qui rend 0 lorsque la chaˆ e ıne d’entr´e est accept´e. elle est appel´e par l’analyseur avec un message d’erreur (par d´faut  parse error . } 33 Dans la notation infix´e. une valeur non nulle dans le cas contraire. e e e e e c’est pourquoi on lui associe un syst`me d’associativit´ et de priorit´ des op´rateurs. un op´rateur binaire est ´crit entre ses deux op´randes. un op´rateur binaire est ´crit ` la suite de ses deux e e e e a op´randes . message). et ´crit la repr´sentation en notation postfix´e34 e e e e e de la mˆme expression. } N. il faut aussi ´crire la fonction appel´e en cas d’erreur. Le programme suivant lit une expression arithm´tique infix´e33 e e form´e de nombres.L’analyseur syntaxique se pr´sente comme une fonction int yyparse(void). mais complet. ` droite d’une production. e e a Voici un exemple simple. cette notation n’a besoin ni de priorit´s des op´rateurs ni de parenth`ses. } strcpy(nom. /* cha^ne de caract`res partag´e avec l’analyseur syntaxique */ ı e e %} chiffre [0-9] lettre [A-Za-z] %% [" "\t\n] {chiffre}+ {lettre}({lettre}|{chiffre})* .l : %{ #include "syntaxe. %} /* cha^ne de caract`res partag´e avec l’analyseur lexical */ ı e e { { { { } yylval = atoi(yytext).4. Pour avoir un analyseur syntaxique autonome e e il suffit donc d’ajouter. et la possibilit´ d’utiliser des parenth`ses. } Fichier syntaxe. Par exemple : e void yyerror(char *message) { printf(" <<< %s\n".

pouvant prendre plusieurs types e distincts. tandis que $$ d´signe la valeur e e de l’attribut du symbole qui est le membre gauche de cette production. } { printf(" *").c -o rpn rpn + A * 100 2 A 100 * + Expression correcte $ rpn 2 * A + 100 2 A * 100 + Expression correcte $ rpn 2 * (A + 100) 2 A 100 + * Expression correcte $ Attributs. $2. Nous ne l’´tudierons pas dans le cadre de ce cours. etc. extern YYSTYPE yylval. vous y trouverez. : | . Ce n’est pas tout ` fait exact.l bison syntaxe. Par exemple. } main() { if (yyparse() == 0) printf(" Expression correcte\n"). dans les langages qui nous int´ressent. Ainsi. la reconnaissance du lex`me e e e "2001" donne lieu ` l’unit´ lexicale NOMBRE avec l’attribut 2001. les d´clarations : e e #define YYSTYPE int .. est de type int. dans les actions s´mantiques on peut mettre certains symboles a e bizarres. } { printf(" %s". dont la valeur contribue ` la a caract´risation du symbole.tab. $3.yy. d´signent les valeurs des e attributs des symboles constituant le membre droit de la production concern´e. terminal ou non terminal. 35 Un m´canisme puissant et relativement simple permet d’avoir des attributs polymorphes. que yacc remplace par des expressions C correctes. outre les d´finitions e eae e des codes des unit´s lexicales. } { printf(" %d".y gcc lex. $1. peut avoir un attribut.tab. Si vous allez voir le fichier a e  .%token nombre identificateur %% expression terme facteur : | . } %% void yyerror(char *s) { printf("<<< \n%s".c syntaxe. Un symbole. yylval). s). a e Un analyseur lexical produit par lex transmet les attributs des unit´s lexicales ` un analyseur syntaxique e a produit par yacc ` travers une variable yylval qui. nom). par d´faut35 . : | | . Nous avons dit que les actions s´mantiques sont des bouts de code C que yacc se borne ` recopier dans e a l’analyseur produit.h  fabriqu´ par yacc et destin´ ` ˆtre inclus dans l’analyseur lexical. e 34 .. expression ’+’ terme terme terme ’*’ facteur facteur nombre identificateur ’(’ expression ’)’ { printf(" +"). } Construction et essai de cet analyseur : $ $ $ $ 2 flex lexique.

4. } { $$ = $1 / $3. e e Par exemple. modifi´ (l´g`rement) pour en faire un calculateur de bureau e e e e e effectuant les quatre op´rations ´l´mentaires sur des nombres entiers.y : %{ void yyerror(char *). e e e a e Fichier syntaxe. ¶ repr´sente la touche  fin de fichier . pour e e e e UNIX) : $ go 2 + 3 = r´sultat : 5 e (2 + 3)*(1002 . } e 35 . } main() { yyparse(). qui d´pend du syst`me utilis´ (Ctrl-D. session expression ’=’ { printf("r´sultat : %d\n".1) = r´sultat : 5000 e ¶ Au revoir! 3. } { $$ = $2.L’action s´mantique { $$ = $1 . %% session : session expression ’=’ | . } { $$ = $1 . avec gestion des parenth`ses et de la e ee e priorit´ des op´rateurs : e e Fichier lexique.1 .$3. $2). $2). { printf("r´sultat: %d\n". voici notre analyseur pr´c´dent.. ` ceci pr`s que les identificateurs ne sont plus reconnus. } e expression ’+’ terme expression ’-’ terme terme terme ’*’ facteur terme ’/’ facteur facteur nombre ’(’ expression ’)’ { $$ = $1 + $3. : | | .. } Exemple d’utilisation . %} %token nombre %% session expression terme facteur : | . } { $$ = $1 * $3.l : le mˆme que pr´c´demment.3 Conflits et ambigu¨ es ıt´ Voici encore une grammaire ´quivalente ` la pr´c´dente. s). : | . : | | . } est implicite et il n’y a pas besoin de l’´crire. } %% void yyerror(char *s) { printf("<<< \n%s". mais plus compacte : e a e e . printf("Au revoir!\n").

. Ce qu’il d´tecte en r´alit´ n’est pas un conflit. Lorsqu’il a e rencontre un conflit36 . D´caler ou r´duire ? ( shift/reduce conflict ).expression : | | | | | .. Nous avons vu ` la section 3. Comment r´duire ? ( reduce/reduce conflict ) Ce conflit se produit lorsque l’algorithme ne peut pas e choisir entre deux productions distinctes dont les membres droits permettent tous deux de r´duire les symboles e au sommet de la pile. e e e v´rifie ceci : e – aucune r`gle ne contient d’ε-production. Sans rentrer dans les d´tails.. on e e e e e e e e e e e consulte donc la table de symboles pour savoir si c’est un nom de proc´dure ou bien un nom de tableau..... 2. mais la e e e possibilit´ que l’analyseur produit en ait ult´rieurement. La grammaire pr´c´dente.. e e e ´ Grammaires d’operateurs. e – aucune r`gle ne contient une production ayant deux non-terminaux cons´cutifs. ou du moins sa partie utile. $1 / $3. c’est donc plutˆt un aveu d’impuissance. appel de fonction → identificateur ’(’ liste expressions ’)’ . yacc applique une r`gle de r´solution par d´faut et continue son travail . La r´solution par d´faut est : dans l’ordre o` les r`gles sont ´crites dans le fichier source pour yacc. Du point de vue de la e puissance des analyseurs syntaxiques. ` la fin de ce e e e a dernier.1. e e 36 N’oubliez pas que yacc ne fait pas une analyse. la production d´finissant un appel de fonction et celle d´finissant un e e e acc`s ` un tableau pourraient ressembler ` ceci : e a a .$3. acces tableau → identificateur ’(’ liste expressions ’)’ . On trouve un exemple typique d’un tel conflit dans les grammaires de langages (il y en a !) o` on note avec u des parenth`ses aussi bien les appels de fonctions que les acc`s aux tableaux. Ce conflit se produit lorsque l’algorithme de yacc n’arrive e e pas ` choisir entre d´caler et r´duire. Les conflits possibles sont : 1. expression ’+’ expression ’-’ expression ’*’ expression ’/’ ’(’ expression nombre expression expression expression expression ’)’ { { { { { $$ $$ $$ $$ $$ = = = = = $1 + $3. car les deux actions sont possibles et n’am`nent pas l’analyse ` une a e e e a impasse.3 que cette grammaire est ambigu¨ .. la r`gle expression. Comme l’exemple ci-dessus le montre37 . elle provoquera donc des conflits. e a o e a e est souvent r´solu par des consid´rations s´mantiques : l’identificateur qui pr´c`de la parenth`se est cens´ avoir ´t´ d´clar´. lorsque les notations sont les mˆmes. $1 * $3.. Il est imp´ratif de comprendre e e e la cause de ces conflits et il est fortement recommand´ d’essayer de les supprimer (par exemple en transformant e la grammaire). Par exemple. mais un analyseur. e e 37 Le probl`me consistant ` choisir assez tˆt entre appel de fonction et acc`s ` un tableau. } } } } } %% . et la question est : faut-il r´duire ces symboles en instr si (ce qui revient ` associer la partie sinon δ au premier si) ou bien faut-il e a d´caler (ce qui provoquera plus tard une r´duction revenant ` associer la partie sinon δ au second si) ? e e a R´solution par d´faut : l’analyseur fait le d´calage (c’est un comportement  glouton  : chacun cherche ` e e e a manger le plus de terminaux possibles). du moins sur certains textes sources. il indique le nombre total de conflits rencontr´s et arbitrairement r´solus. Un exemple typique de ce conflit a pour origine la grammaire usuelle de l’instruction conditionnelle instr si → si expr alors instr | si expr alors instr sinon instr Le conflit apparaˆ pendant l’analyse d’une chaˆ comme ıt ıne si α alors si β alors γ sinon δ lorsque le symbole courant est sinon : au sommet de la pile se trouve alors si β alors γ. il e e e est facile d’imaginer qu’on trouvera dans de telles grammaires des productions compl`tement diff´rentes avec e e les mˆmes membres droits. cela ne r´sout pas souvent bien le probl`me. o 36 . $1 . on pr´f`re e e u e e ee la premi`re production. $2.

e ee e e ee e e e – v´rifier que les param`tres des fonctions ont les types requis. voici une situation typique o` le contrˆle de type joue. $2. ` la place de i et j nous aurions dˆ e e a a u repr´senter des renvois ` la table des symboles) : e a 38 Dire qu’un op´rateur ⊗ est associatif ` gauche [resp.. La e a a question est importante.De telles grammaires s’appellent des grammaires d’op´rateurs. Des exemples de tˆches li´es au e o a e contrˆle de type sont : o – construire et m´moriser des repr´sentations des types d´finis par l’utilisateur. On a ajout´ des d´clarations e e e e e e e e indiquant que +. Pour fixer les id´es. e e e e – etc. contient l’instruction  i = (200 + j) * 3. comme ceci (pour ˆtre tout ` fait corrects. ins´rer dans les expressions les conversion impos´es par certaines r`gles de compatibilit´. $2). cela se fait par des d´clarations %left et %right qui sp´cifient le sens d’associativit´ des op´rateurs. $1 / $3. o e e e e e – au besoin.14 . e e Ainsi. e e e En yacc. L’analyseur syntaxique construit un arbre abstrait e repr´sentant cette expression. n’est plus ambigu¨. e e e e mais il se trouve qu’il est tr`s simple de les rendre non ambigu¨s : il suffit de pr´ciser par ailleurs le sens de e e e l’associativit´ et la priorit´ de chaque op´rateur. a ⊗ (b ⊗ c)]. pr´sent´e comme ci-apr`s.$3. lorsque le langage le permet. e e e e l’ordre de ces d´clarations donnant leur priorit´ : ` chaque nouvelle d´claration les op´rateurs d´clar´s sont plus e e a e e e e prioritaires que les pr´c´dents. } e expression ’+’ expression ’-’ expression ’*’ expression ’/’ ’(’ expression nombre expression expression expression expression ’)’ { { { { { $$ $$ $$ $$ $$ = = = = = $1 + $3. Nous n’en ferons pas l’´tude d´taill´e ici. la grammaire pr´c´dente.. Imaginons qu’un programme. $1 * $3. 4 Analyse s´mantique e Apr`s l’analyse lexicale et l’analyse syntaxique. e e – contrˆler les types des op´randes des op´rations arithm´tiques et en d´duire le type du r´sultat. %{ void yyerror(char *). −. e e e – v´rifier que toute variable r´f´renc´e et toute fonction appel´e ont bien ´t´ pr´alablement d´clar´es. : | | | | | . e u o ´crit en C. ` droite] c’est dire que a ⊗ b ⊗ c se lit (a ⊗ b) ⊗ c [resp. mˆme pour des op´rateurs simples : on tient ` ce que 100 − 10 − 1 vaille 89 et non 91 ! e e a 37 . $1 . %} %token nombre %left ’+’ ’-’ %left ’*’ ’/’ %% session expression : | . ∗ et / sont associatifs ` gauche38 et que la priorit´ de ∗ et / est sup´rieure ` celle de + et a e e a −. l’´tape suivante dans la conception d’un compilateur est e e l’analyse s´mantique dont la partie la plus visible est le contrˆle de type. session expression ’=’ { printf("r´sultat: %d\n". } } } } } %% . e e e – traiter les d´clarations de variables et fonctions et m´moriser les types qui leur sont appliqu´s.

et a qu’il faut donc ins´rer une op´ration de troncation flottant → entier . e 39 Le type d’une constante est donn´ par une convention lexicale (exemple : 200 repr´sente un entier. provoquant une conversion de type. e 38 . en C. l’auteur d’un compilateur doit se donner le moyen de repr´senter ces structures de complexit´ quelconque. Par e e e e a e exemple. e e e 40 Cela s’appelle la  r`gle du plus fort . e e – que le ∗ est la multiplication des flottants. celui dont le type est le  plus fort  (plus complexe. des tableaux d’adresses de fonctions rendant des adresses d’entiers. e e a a dans les langages modernes les types sont d´finis par des proc´d´s r´cursifs qu’on peut composer ` volont´. des adresses (ou pointeurs) d’entiers. etc. il en d´coule que l’arbre tout e e e entier repr´sente une valeur du type entier. et donc que le sous-arbre e chapeaut´ par le + repr´sente une valeur de type entier. 3. – pendant la compilation des instructions. des fonctions rendant des adresses d’entiers. en C on peut avoir des entiers. reconnaˆ les types des objets intervenant dans les expressions. En effet.14 ¨r ¨r 200 j Dans de tels arbres.14 addition enti`re e ¨r ¨r 200 j 4. depuis les feuilles vers la racine.) tire ` e e e e a lui l’autre op´rande. dont le type exact reste ` pr´ciser. et que le sous-arbre chapeaut´ par ∗ repr´sente un objet de type e e e flottant. le contrˆle de type transforme l’arbre pr´c´dent en quelque chose qui ressemble ` ceci : e o e e a affectation d’entiers ¨rr ¨¨ r ¨ r i flottant→entier multiplication flottante ¨rr ¨¨ r entier →flottant 3. le type e e d’une variable ou le type rendu par une fonction est sp´cifi´ par la d´claration de la variable ou la fonction en question. etc.14 un flottant).= ¨ r ¨ r r ¨ i ∗ ¨rr ¨ + 3. plus pr´cis. e En d´finitive. puisque les deux op´randes sont entiers. construire des repr´sentations des types d´clar´s dans le proe e e e gramme. Cela peut aller aussi loin qu’on veut. e e Faute de temps. tandis que les nœuds e internes repr´sentent des op´rations abstraites. e Supposons par exemple que i et j aient ´t´ d´clar´es de type entier. 200.1 Repr´sentation et reconnaissance des types e Une partie importante du travail s´mantique qu’un compilateur fait sur un programme est e – pendant la compilation des d´clarations.14) ont des types pr´cis39 . L’analyse s´mantique de l’arbre pr´c´dent ee e e e e e permet d’en d´duire. qu’il y a lieu de convertir e l’autre op´rande vers le type flottant. – que l’affectation qui coiffe l’arbre tout entier consiste donc en l’affectation d’un flottant ` un entier. j et 3. ıtre La principale difficult´ de ce travail est la complexit´ des structures ` construire et ` manipuler. rendant concrets les op´rateurs et donnant a e un type pr´cis aux sous-arbres. elle est suivie par la plupart des langages : lorsque les op´randes d’une op´ration e e e arithm´tique ne sont pas de mˆme type. plus ´tendu. des adresses de fonctions rendant des adresses d’entiers. nous n’´tudierons pas cet aspect des compilateurs dans le cadre de ce cours. successivement : e – que le + est l’addition des entiers. Le travail s´mantique ` faire e e a e e a consiste `  remonter les types . seules les feuilles (ici i. puisqu’un op´rande est flottant40 .

struct listeDescripteursTypes *suivant. Pr´sent´e comme cela. struct { listeDescripteursTypes *typesArguments. peu int´ressant. struct { listeDescripteursTypes *typesChamps. typedef struct descripteurType { typePossible classe. le champ attributs est sans objet. Ainsi. } casStructure. on devra le valider. tFonction. e 41 Voici 39 . puisqu’il est lui-mˆme ´crit en C ? Le r´sultat obtenu devra ˆtre un nouvel a e e e e ex´cutable identique ` celui du compilateur. son propre texte source. voici n´anmoins une suggestion de structures de donn´es pour la repr´sentation des prine e e cipaux types du langage C dans un compilateur qui serait lui-mˆme41 ´crit en C : e e typedef enum { tChar. dans quel langage fut ´crit le tout premier compilateur de C. par exemple. une union) dont la structure d´pend e e de la valeur du champ classe : – si la classe est celle d’un type primitif. Attributs est un champ polymorphe (en C. struct descripteurType *typeElement. } casTableau. tFloat. Pour cela. un type se trouve repr´sent´ par une structure ` deux champs : classe. la chose est parfaitement raisonnable. } descripteurType. de l’œuf e e et de la poule. union { struct { struct descripteurType *typePointe. le compilateur que nous r´aliserons ` titre de projet ne traitera que les types entier et tableau d’entiers. La question est plutˆt de savoir si. on peut utiliser un compilateur (existant) de C pour r´aliser un o e compilateur (nouveau) de C. tPointeur. de nos jours. } casFonction.Ainsi. la chose est tout ` fait possible. tDouble. tShort.. dont la valeur est un code e e a conventionnel qui indique de quelle sorte de type il s’agit. On voit l` un des int´rˆts qu’il y a ` ´crire le compilateur d’un langage dans le langage e a a e e ae ` compiler : on dispose ipso facto d’un formidable test de validation : au terme d’un certain processus de construction (on dit a plutˆt bootstrap) on doit poss´der un compilateur C capable de compiler son propre texte source S et de donner un r´sultat C(S) o e e v´rifiant C(S) = C. } attributs. tInt. } listeDescripteursTypes.. } casPointeur. e a Pour les curieux. struct descripteurType *typeResultat. tStructure } typePossible. typedef struct listeDescripteursTypes { struct descripteurType *info. si tant e est qu’il y eut un jour un compilateur de C alors que la veille il n’y en avait pas – cela est le probl`me. Il faut e a comprendre que la question n’est pas de savoir. une question troublante qui finit par apparaˆ ıtre dans tous les cours de compilation : peut-on ´crire le compilateur d’un e langage en utilisant le langage qu’il s’agit de compiler ? Malgr´ l’apparent paradoxe. tTableau. qui donne les informations n´cessaires e pour achever de d´finir le type. tLong. quel meilleur test a u e que de lui donner ` compiler. struct { int nombreElements. et attributs. e e L` o` elle redevient troublante : ce nouveau compilateur de C une fois ´crit.

return res.casPointeur.. *pTmp2.typeElement = pTmp1.nombreElements = N. a e e e e a 40 . res->classe = classe. le nombre de cases du tableau. .casTableau.typePointe = nouveau(tChar). alors le champ attribut se compose de l’adresse d’un descripteur de type (le type rendu par la fonction) et l’adresse d’une liste de types (les types des arguments de cette derni`re). pCour->info->attributs.. – s’il s’agit d’un type structure.. int uniteLexicale. l’attribut est l’adresse d’une liste chaˆ ee dont chaque maillon contient un ın´ pointeur42 sur un descripteur de type. la e e construction  ` la main  du descripteur correspondant au type de la variable d´clar´e. pCour->info = nouveau(tPointeur). pTmp2->attributs. si le champ classe indique qu’il s’agit d’un type fonction. au lieu d’un pointeur e e ın´ sur un tel descripteur. pTmp1->attributs.. /* pTmp1 va pointer la description de la structure */ pTmp1 = nouveau(tStructure). comme suit : a e e struct { char *lexeme. e On facilite l’allocation dynamique de telles structures en se donnant une fonction : descripteurType *nouveau(typePossible classe) { descripteurType *res = malloc(sizeof(descripteurType)). qui pointe sur la description du type commun des ´l´ments ee du tableau. voici un exemple purement d´monstratif. /* maillon de liste d´crivant le type pointeur sur caract`re */ e e pSuiv = pCour. par la variable tmp2) : e a . et typeElement. pCour->suiv = pSuiv. Apparemment plus lourde ` g´rer. assert(res != NULL). La variable est motRes (nous l’avons utilis´e ` la section 2. ` la fin de la construction.typesChamps = pCour. Voici le code qui construit a e un tel descripteur (point´. listeDescripteursTypes *pCour. pTmp2->attributs.casTableau. pCour->info = nouveau(tInt). 42 Il aurait aussi ´t´ viable de faire que chaque maillon de la liste chaˆ ee contienne un descripteur de type. la solution adopt´e ici se r´v`le ` l’usage la plus souple. alors le champ attributs pointe sur la description du type des objets point´s. elle est d´clar´e comme un tableau de N ´l´ments e a e e ee qui sont des structures ` deux champs : un pointeur sur un caract`re et un entier. – enfin.casStructure.– si le champ classe indique qu’il s’agit d’un type pointeur. } Pour montrer le fonctionnement de cette structure de donn´es. pCour->suiv = NULL. en C. /* pTmp2 va pointer la description du type tableau */ pTmp2 = nouveau(tTableau). descripteurType *pTmp1.2). pCour = malloc(sizeof(listeDescripteursTypes)). } motRes[N]. e e – si la valeur du champ classe est tTableau alors il faut deux attributs pour d´finir le type : nombreElements. /* maillon de liste d´crivant le type entier */ e pCour = malloc(sizeof(listeDescripteursTypes)).2. *pSuiv.

casStructure.casTableau. default: return 1.typeElement). b->attributs. tableau de NL lignes: */ pTmp2 = nouveau(tTableau). pour donner encore un exemple de manipulation de ces structures de donn´es. plus pr´cis´ment. pTmp2->attributs. case tTableau: return a->attributs. switch (a->classe) { case tPointeur: return memeType(a->attributs. b->attributs. tableau de NC flottants: */ pTmp1 = nouveau(tTableau). . a = a->suiv.typePointe).casFonction.typeElement = nouveau(tFloat). b = b->suiv. pTmp1->attributs. b->attributs.typesChamps.casTableau.typeResultat) && memeListeTypes(a->attributs. /* description d’une ligne.Dans le mˆme ordre d’id´es. voici un utilitaire e fondamental dans les syst`mes de contrˆle de type : la fonction bool´enne qui fournit la r´ponse ` la question e o e e a  deux descripteurs donn´s d´crivent-ils des types identiques ?  : e e int memeType(descripteurType *a.typeElement = pTmp1... case tStructure: return memeListeTypes(a->attributs.casTableau.casPointeur.casFonction.typesChamps). pTmp1->attributs.typePointe.).casTableau.casFonction.nombreElements = NL. case tFonction: return memeType(a->attributs..typeElement. listeDescripteursTypes *b) { while (a != NULL && b != NULL) { if ( ! memeType(a->info. pTmp2->attributs. } 41 . b->attributs. } return a == NULL && b == NULL.casStructure.nombreElements = NC.casTableau.casFonction. voici la construction manuelle du descripteur du type  matrice de N L × N C e e flottants  ou.typesArguments. b->attributs. /* description d’une matrice. Enfin.typeResultat. cela e e ee s’´crit : float matrice[NL][NC] .casTableau.  tableau de N L ´l´ments qui sont des tableaux de N C flottants  (en C.nombreElements == b->attributs. A la fin de la construction le descripteur cherch´ est point´ par pTmp2 : e e e . b->info)) return 0.nombreElements && memeType(a->attributs. descripteurType *b) { if (a->classe != b->classe) return 0.typesArguments)..casTableau. } } int memeListeTypes(listeDescripteursTypes *a.casTableau.casPointeur.

la partie ex´cutable des programmes est l’ensemble des corps des fonctions dont le e programme se compose. avec des expressions comme  le compilateur entre dans la partie e ex´cutable  ou  le compilateur entre (ou sort) d’une fonction . Quel que soit le degr´ de complexit´ des types support´s par notre come e e e e pilateur.1. e entre lesquelles se trouvent des d´clarations de variables. n d´signe o . depuis sa d´claration jusqu’` la fin de cette fonction . que nous allons employer. sauf aux endroits o` un objet local de mˆme e a u e nom le masque.1 Dictionnaire global & dictionnaire local Dans les langages qui nous int´ressent. appel´e aussi dictionnaire. parfois. non o. contenant les noms des objets globaux couramment d´clar´s. voir ci-apr`s. e e e e 46 Par masquage d’un objet o par un objet o de mˆme nom n on veut dire que o n’est pas alt´r´ ni d´truit. Un objet global e e e est visible45 depuis sa d´claration jusqu’` la fin du texte source. e Les variables d´clar´es ` l’int´rieur des fonctions sont des objets locaux. quand le compilateur se trouve dans47 une fonction il faut poss´der deux dictionnaires : un e e dictionnaire global. e Grosso modo le dictionnaire fonctionne ainsi : – quand le compilateur trouve un identificateur dans une d´claration. il n’est pas visible depuis les u e e e a autres fonctions. a e e Tout cela justifie un langage imag´. u e 47 Le compilateur lit le programme ` compiler s´quentiellement. Dans ces conditions.4. e En d´finitive. quand la compilation progresse. cf. a 45 On dit qu’un objet o ayant le nom n est visible en un point d’un programme si une occurrence de n en ce point est comprise comme d´signant o. ensuite e e il utilise les informations que le dictionnaire associe ` l’identificateur.1. un programme est essentiellement une collection de fonctions. En Pascal il faut ajouter ` cela le corps du programme. a Nous allons voir que la question est un peu plus compliqu´e que cela. e 4. etc. identif type tEntier adresse complemt 180 v i t e s s e \0 Fig. c’est-`-dire les services que le compie a lateur en attend. masquent des objets dont les e e noms se trouvant dans le dictionnaire global). l’utilisation des dictionnaires que fait le compilateur se pr´cise : e question des adresses des objets qui se trouvent dans les programmes sera ´tudi´e en d´tail ` la section 5. e Les variables d´clar´es entre les fonctions et les fonctions elles-mˆmes sont des objets globaux. dans laquelle se trouveront les e e identificateurs couramment d´clar´s. son adresse 43 et e e e a d’autres informations. correspondant ` la position de l’unit´ lexicale courante . comme son type. et un dictionnaire local dans e e lequel se trouvent les noms des objets locaux couramment d´clar´s (qui. celui-ci devra g´rer une table de symboles. puis il l’ajoute au dictionnaire e ea e e avec le type que la d´claration sp´cifie. Un objet local est visible dans la e e a e fonction o` il est d´clar´. diverses impl´mentations possibles. Java. il le cherche dans le dictionnaire en e esp´rant ne pas le trouver (sinon c’est l’erreur  identificateur d´j` d´clar´ ). puis. En tout point o` il est visible. chacun associ´ ` certains attributs. l’unit´ lexicale avance. A tout instant il en est ` un certain endroit a e e a du texte source. e e e a les langages comme C. dans les sections suivantes. e e – quand le compilateur trouve un identificateur dans la partie ex´cutable44 d’un programme.2. figure 10. Cela ne pr´juge en rien de la correction ou de la l´galit´ de l’emploi de n en ce point. du d´but vers la fin. il le cherche e dans le dictionnaire avec l’espoir de le trouver (sinon c’est l’erreur  identificateur non d´clar´ ). un objet local masque 46 tout ´ventuel objet global qui aurait u e le mˆme nom..2 Dictionnaires (tables de symboles) Dans les langages de programmation modernes. 10 – Une entr´e dans le dictionnaire e Nous ´tudions pour commencer le cahier des charges du dictionnaire. A l’int´rieur des fonctions se trouvent ´galement e e e des d´clarations de variables. etc. les variables et les fonctions doivent ˆtre d´clar´es avant e e e d’ˆtre utilis´es dans les instructions. mais devient e e e e inaccessible car. dans la portion de programme o` le masquage a lieu. e 44 Dans 43 La 42 .

Voyez la figure 11 : lorsqu’il existe. Avec tout cela.  erreur : identificateur d´j` d´clar´ ). e e e – Lorsque le compilateur quitte une fonction. Suite ` cette d´claration. le dictionnaire local en cours d’utilisation est d´truit.2 Tableau ` acc`s s´quentiel a e e L’impl´mentation la plus simple des dictionnaires consiste en un tableau dans lequel les identificateurs sont e plac´s dans l’ordre o` leurs d´clarations ont ´t´ trouv´es dans le texte source. e a e i est recherch´ dans le dictionnaire local exclusivement . ee Notez ceci : tout au long d’une compilation le dictionnaire global ne diminue jamais. – Lorsque le compilateur compile une instruction ex´cutable. il ne s’y trouve pas (sinon. e 2. qui est le seul dictionnaire existant en ce point . il est e e recherch´ ensuite dans le dictionnaire global (si les deux recherches ´chouent. e 43 . il ne s’y trouve pas (sinon. les recherches e u e ee e sont s´quentielles. quand on est ` l’int´rieur (a) et ` l’ext´rieur (b) des fonctions a e a e Trois variables sont essentielles dans la gestion du dictionnaire : maxDico est le nombre maximum d’entr´es possibles (` ce propos.– Lorsque le compilateur traite la d´claration d’un identificateur i qui se trouve ` l’int´rieur d’une fonction.  erreur : identificateur non e e d´clar´ ). 48 En parcourant le tableau du haut vers le bas on assure le masquage d’un objet global par un objet local de mˆme nom. Suite ` cette d´claration. e sommet est le nombre d’entr´es valides dans le dictionnaire . Recherche d’un identificateur pendant le traitement d’une expression ex´cutable : rechercher l’identificateur en parcourant dans le sens des indices d´croissants 48 la portion de tableau comprise entre les bornes e sommet − 1 et 0. i est ajout´ au dictionnaire ea e e a e e global. ee a base est le premier ´l´ment du dictionnaire du dessus (c’est-`-dire le dictionnaire local quand il y en a deux. puisque e les objets locaux ne sont pas visibles ` l’ext´rieur de la fonction. maxDico xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx maxDico sommet base dictionnaire local dictionnaire global sommet xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxx dictionnaire global base (b) 0 (a) Fig. on doit avoir sommet ≤ maxDico. Un dictionnaire local nouveau. est a e cr´´ lorsque le compilateur entre dans une fonction.  erreur : e identificateur d´j` d´clar´ ). la manipulation du dictionnaire devient tr`s simple. le dictionnaire global quand il n’y en a qu’un). 11 – Dictionnaires. i est recherch´ e e dans le dictionnaire global. ea e e a e e Il n’y a strictement aucun int´rˆt ` savoir si i figure ` ce moment-l` dans le dictionnaire global. chaque e e a e identificateur i rencontr´ est recherch´ d’abord dans le dictionnaire local . le dictionnaire local se trouve au-dessus du dictionnaire e global (en supposant que le tableau grandit du bas vers le haut). i est ajout´ au dictionnaire local. 4. Les op´rations n´cessaires sont : e e e 1. Recherche d’un identificateur pendant le traitement d’une d´claration (que ce soit ` l’int´rieur d’une e a e fonction ou ` l’ext´rieur de toute fonction) : rechercher l’identificateur dans la portion de tableau comprise a e entre les bornes sommet − 1 et base. normalement. En proc´dant ainsi on assure le masquage des objets globaux par les objets locaux. voir  Augmentation de la taille du e a dictionnaire  un peu plus loin). normalement. vide. forc´ment ` l’int´rieur d’une fonction. s’il ne s’y trouve pas.2. Dans ce tableau. le dictionnaire global n’augmente que lorsque le dictionnaire local n’existe pas. A l’int´rieur d’une e fonction il n’augmente pas . ee a a a – Lorsque le compilateur traite la d´claration d’un identificateur i en dehors de toute fonction.

if (dico == NULL) erreurFatale("Erreur interne (pas assez de m´moire)"). Destruction du dictionnaire local.h> typedef struct { char *identif. ` titre e e a d’exemple. int complement) { if (sommet >= maxDico) 44 50 25 . Creation d’un dictionnaire local. int adresse. #define TAILLE_INITIALE_DICO #define INCREMENT_TAILLE_DICO ENTREE_DICO *dico. int complement. int adresse. la fonction realloc qui permet d’auge e e menter la taille d’un espace allou´ dynamiquement tout en pr´servant le contenu de cet espace. sommet. 4. la d´claration et les fonctions de gestion d’un dictionnaire r´alis´ dans un tableau ayant au d´part la e e e e place pour 50 ´l´ments . message). } ENTREE_DICO. int maxDico. le tableau est agrandi d’autant qu’il faut pour loger ee 25 nouveaux ´l´ments : ee #include <stdlib. base. } Pour montrer une utilisation de tout cela. dico = malloc(maxDico * sizeof(ENTREE_DICO)). "%s\n". } void agrandirDico(void) { maxDico = maxDico + INCREMENT_TAILLE_DICO. a Augmentation de la taille du dictionnaire. if (dico == NULL) erreurFatale("Erreur interne (pas assez de m´moire)"). voici la fonction qui ajoute une entr´e au dictionnaire : e void ajouterEntree(char *identif. e } void erreurFatale(char *message) { fprintf(stderr. void creerDico(void) { maxDico = TAILLE_INITIALE_DICO. Ajout d’une entr´e dans le dictionnaire (que ce soit ` l’int´rieur d’une fonction ou ` l’ext´rieur de toute e a e a e fonction) : apr`s avoir v´rifi´ que sommet < maxDico. La biblioth`que C offre un moyen pratique pour r´soudre ce probl`me. au moment de l’entr´e dans une fonction : faire base ← sommet. Voici. int type.h> #include <string. e 5. dico = realloc(dico.3. ´tant e a a e entendu qu’on ne connaˆ pas ` l’avance la grosseur (en nombre de d´clarations) des programmes que notre ıt a e compilateur devra traiter.h> #include <stdio. int type. maxDico). e e e e a puis faire sommet ← sommet + 1. placer la nouvelle entr´e ` la position sommet. exit(-1). e sommet = base = 0. Une question technique assez aga¸ante qu’il faut r´gler c e lors de l’impl´mentation d’un dictionnaire par un tableau est le choix de la taille ` donner ` ce tableau. ` la sortie d’une fonction : faire sommet ← base puis base ← 0. chaque fois que la place manque.

i = inf. les insertions se font en temps constant). while (i <= j) { /* invariant: i <= position <= j + 1 */ k = (i + j) / 2. } 4. int inf.3 Tableau tri´ et recherche dichotomique e L’impl´mentation des dictionnaires expliqu´e ` la section pr´c´dente est facile ` mettre en œuvre et suffisante e e a e e a pour des applications simples.2. if (dico[sommet]. identif) == 0. qui effectue la recherche de l’identificateur repr´sent´ par ident e e dans la partie du tableau. if (strcmp(dico[k]. j = sup. int *ptrPosition) { int i. c’est-`-dire : ee e a – si l’identificateur est dans le tableau. soit i = j + 1 */ *ptrPosition = i. dico[sommet]. bornes incluses. c’est beaucoup mieux). suppos´ ordonn´. k. voici la fonction existe.identif. en moyenne. soit O(n) . mais maintenant il faut imaginer que les ´l´ments d’indices allant de base ` ee a sommet − 1 et. comprise entre les indices inf et sup. Le r´sultat de e e e la fonction (1 ou 0. i > j. a Une premi`re am´lioration des dictionnaires consiste ` maintenir des tableaux ordonn´s.identif = malloc(strlen(identif) + 1). Dans la pratique on recherche des impl´mentations plus efficaces. 49 On estime que plus de 50% du temps d’une compilation est d´pens´ en recherches dans les dictionnaires. if (sommet >= maxDico) agrandirDico(). int adresse.agrandirDico(). sont plac´s en ordre croissant des a e identificateurs. de plus. char *identif. e – si l’identificateur ne se trouve pas dans le tableau. mais pas tr`s efficace (la complexit´ des recherches est. lorsqu’il y a lieu.identif. au retour de la fonction. int type. } Voici la fonction qui ajoute une entr´e au dictionnaire : e void ajouterEntree(int position. dico[sommet]. En outre. une e e e entr´e concernant cet identificateur. j. Dans un tel contexte. sommet++.identif. e int existe(char *identif. } /* ici. l’indice de l’entr´e correspondante. car un compilateur passe beaucoup de temps49 ` rechercher des identificateurs dans les dictionnaires. de l’ordre de e e n e 2 . e e 45 . ceux d’indices allant de 0 ` base − 1. la variable point´e par ptrPosition contient la position e de l’´l´ment recherch´. else j = k . dico[sommet]. les cas ´ch´ant. identif) < 0) i = k + 1. e strcpy(dico[sommet]. return i <= sup && strcmp(dico[i].adresse = adresse.identif == NULL) erreurFatale("Erreur interne (pas assez de m´moire)"). dico[sommet]. La e figure 11 est toujours valable.type = type. int complt) { int i. interpr´t´s comme vrai ou faux ) est la r´ponse ` la question  l’´l´ment cherch´ se trouve-t-il ee e a ee e dans le tableau ? . permettant des e e a e recherches par dichotomie (la complexit´ d’une recherche devient ainsi O(log2 n). identif).complement = complement. int sup. l’indice auquel il faudrait ins´rer.1.

int placement. voici la fonction qui traite la d´claration d’un objet local : e e . i--) dico[i + 1] = dico[i]. identif). mais p´nalise les insertions. dico[position]. c’est la gestion d’une table de mots r´serv´s. if (dico[position]. lexeme. type. 46 .2.identif == NULL) erreurFatale("Erreur interne (pas assez de m´moire)").2. adresse. e Un arbre binaire de recherche. dico[position]. – pour tout nœud r appartenant au sous-arbre droit de p on a r→inf o ≥ p→inf o. qui n’est pas la gestion du dictionnaire mais lui est proche.for (i = sommet .complement = complt. ajouterEntree(placement. sommet . Gaston. ou plus pr´cis´ment l’analyseur lexical. 4. i >= position. Bernard. ajout´s ` l’arbre successivement et dans cet ordre50 : e a Denis ¨r ¨rr ¨ ¨¨ Bernard ¨r ¨r ¨ r Andr´ Charles e rr Fernand ¨r ¨¨ rr Ernest Gaston 50 Le nombre d’´l´ments et. fait de nombreuses recherches dans cette e e table qui ne subit jamais la moindre insertion. Fernand. base. v´rifiant la propri´t´ suivante : pour tout nœud p de l’arbre e e ee – pour tout nœud q appartenant au sous-arbre gauche de p on a q→inf o ≤ p→inf o... les choses ne ee e e se passent pas aussi bien.adresse = adresse. dico[position]. e a e e else { ici se place l’obtention des informations type.. ou ABR.identif. Voici. l’insertion d’un ´l´ment n’oblige pas e e ın´ ee a ` pousser ceux qui d’un point de vue logique se placent apr`s lui. u Il y a cependant une tˆche. . } .1. o` on peut sans a u r´serve employer un tableau ordonn´. e Les arbres binaires de recherche ont les avantages des tables ordonn´es. En pratique.. Ernest e et Charles. complement).identif = malloc(strlen(identif) + 1). comme celle de la section e e e e 2. le compilateur. puisqu’` chaque insertion il a e e a faut pousser d’un cran la moiti´ (en moyenne) des ´l´ments du dictionnaire.. dont la complexit´ devient O(n).. est un arbre binaire ´tiquet´ par des valeurs appartenant ` un e e a ensemble ordonn´. &placement)) erreurFatale("identificateur d´j` d´clar´"). Or. surtout. adresse.2. ´tant des structures chaˆ ees.type = type. dico[position]. sans leurs inconv´nients puisque. etc. Pour e fixer les id´es. pour ce qui est de l’efficacit´ de la e e recherche.1. Nous constatons que l’utilisation de tableaux tri´s permet d’optimiser la recherche. pendant la compilation d’un e ee programme il y a beaucoup d’insertions et on ne peut pas n´gliger a priori le poids des insertions dans le calcul e du coˆt de la gestion des identificateurs. En effet. l’ordre d’insertion font que cet ABR est parfaitement ´quilibr´. } La fonction ajouterEntree utilise un param`tre position dont la valeur provient de la fonction existe. sommet++. par exemple. if (existe(lexeme. l’ABR obtenu avec les  identificateurs  Denis. e strcpy(dico[position]. complement. dont la complexit´ passe e e de O(n) ` O(log2 n). Andr´.4 Arbre binaire de recherche Cette section suppose la connaissance de la structure de donn´es arbre binaire.

Dans cette fonction. *droite. NOEUD *dicoGlobal = NULL.. else ptr = ptr->droite. *dicoLocal = NULL. } Cette fonction est utilis´e pour rechercher des identificateurs apparaissant dans les parties ex´cutables des e e fonctions. la rencontre d’un nœud associ´ e e a ` l’identificateur qu’on cherche est consid´r´e comme une erreur grave. Elle rend l’adresse du nœud a e cherch´. } NOEUD. p = rechercher(lexeme. un tel arbre serait r´alis´ dans un programme comme le montre la figure 12... if (p == NULL) { p = recherche(lexeme. ou NULL en cas d’´chec de la recherche : e e NOEUD *rechercher(char *identif. Fig. Elle sera donc appel´e de la mani`re suivante : e e . dicoLocal).. Voici la fonction qui recherche le nœud correspondant ` un identificateur donn´. dicoGlobal)... if (p == NULL) erreurFatale("identificateur non d´clar´"). ptr->info. . e e Denis autres infos Bernard autres infos autres infos Fernand . else if (c < 0) ptr = ptr->gauche. La fonction rend l’adresse du nœud ee nouvelle cr´´ : ee 47 .. dans la foul´e. if (c == 0) return ptr. .Techniquement. Pendant la compilation des d´clarations. .. } return NULL. e e } exploitation des informations du nœud point´ par p e . les recherches se font avec la fonction suivante. struct noeud *gauche.....identif). NOEUD *ptr) { while (ptr != NULL) { int c = strcmp(identif. 12 – R´alisation effective des maillons d’un ABR e Pour r´aliser les dictionnaires par des ABR il faudra donc se donner les d´clarations : e e typedef struct noeud { ENTREE_DICO info. qui effectue la e recherche et. l’ajout d’un nouveau nœud.

ptr->info. e e e – la destruction d’un dictionnaire est une op´ration beaucoup plus complexe que dans les m´thodes qui e e utilisent un tableau. Il y a un moyen de rendre beaucoup plus simple la lib´ration de l’espace occup´ par un arbre.B. char *identif. ce qui ne peut ˆtre suppos´ que si les identifie e e e cateurs sont tr`s nombreux et uniform´ment r´partis. else return ptr->droite = nouveauNoeud(identif. puisque le temps n´cessaire pour faire une insertion dans un ee e e e ABR est n´gligeable. else if (ptr->droite != NULL) ptr = ptr->droite. lorsque le dictionnaire est vide : le pointeur point´ par adrDico (en pratique e e il s’agit soit de dicoLocal soit de dicoGlobal ) vaut NULL et doit changer de valeur. .... c’est pourquoi il y a deux ∗∗ dans la d´claration NOEUD **adrDico. C’est beaucoup de travail pour pas grand-chose. complt). int adresse. ptr = *adrDico. complt). N. Cela peut se programmer de la mani`re suivante : e void liberer(NOEUD *dico) { if (dico != NULL) { liberer(dico->gauche). } } Comme on le voit. le pointeur de la racine du dictionnaire dans lequel il faut faire l’insertion est pass´ par adresse. doit se faire chaque fois que le compilateur sort d’une fonction. complt). sans ses inconv´nients. for (. qu’on utilise ` la place malloc. on l’´viterait en d´cidant que les dictionnaires ne sont jamais vides (il suffit de leur cr´er e e e d’office un  nœud bidon  sans signification). free(dico). en l’occurrence le dictionnaire local. type. adresse. Cela consiste e e ae ` ´crire sa propre fonction d’allocation. et qui alloue un espace dont on maˆ a ıtrise la remise ` z´ro.. int complt) { NOEUD *ptr. adresse. ´ Restitution de l’espace alloue. car ` chaque comparaison on divise par deux la taille de l’ensemble susceptible de a contenir l’´l´ment cherch´. pour rendre l’espace occup´ par un ABR il faut le parcourir enti`rement (alors que dans e e le cas d’un tableau la modification d’un index suffit). int type.NOEUD *insertion(NOEUD **adrDico. p = insertion( &dicoLocal.. L’impl´mentation d’un dictionnaire par un ABR poss`de l’efficacit´ de e e e la recherche dichotomique. } } Exemple d’appel (cas des d´clarations locales) : e . else return ptr->gauche = nouveauNoeud(identif. leType. e e if (c < 0) if (ptr->gauche != NULL) ptr = ptr->gauche. type. Par exemple : a e 48 . leComplement). type.) { int c = strcmp(identif. liberer(dico->droite). if (*adrDico == NULL) return *adrDico = nouveauNoeud(identif. adresse. Cela ne sert qu’` couvrir e e a le cas de la premi`re insertion. La destruction d’un dictionnaire. Dans la fonction insertion. if (c == 0) erreurFatale("identificateur deja d´clar´"). lAdresse. H´las.identif). lexeme. cette m´thode a deux d´fauts : e e e e – la recherche n’est dichotomique que si l’arbre est ´quilibr´.

NOEUD *monAlloc(void) { if (niveauAllocation >= MAX_ESPACE) return NULL. tandis qu’` d’autres endroits cette derni`re est a a e peu remplie. N [ le nombre de i ∈ I tels que h(i) = j est ` peu pr`s le mˆme. . } void toutLiberer(void) { niveauAllocation = 0. c’est-`-dire qui ` deux identificateurs distincts ferait correspondre deux valeurs distinctes. l’adressage dispers´ ferm´. – utilisez des fonctions qui font intervenir tous les caract`res des identificateurs . poids maxi. N [. un calcul est certainement plus e a e rapide qu’une recherche. Il en existe une autre. Une fonction assez souvent utilis´e consiste ` consid´rer les caract`res d’un identificateur comme les coeffie a e e cients d’un polynˆme P (X) dont on calcule la valeur pour un X donn´ (ou. Cela s’appelle adressage dispers´. a a On ne dispose g´n´ralement pas d’une telle fonction car l’ensemble I des identificateurs pr´sents dans le e e e programme n’est pas connu a priori. par exemple par une op´ration de modulo.#define MAX_ESPACE 1000 NOEUD espace[MAX_ESPACE]. ` voir un o e e a 51 La technique expliqu´e ici est celle dite adressage dispers´ ouvert. else return &espace[niveauAllocation++]. } 4. i2 tels que i1 = i2 et h(i1 ) = h(i2 ) (on appelle e cela une collision) sont peu nombreux. C’est-`-dire que h n’est pas injective. il s’agit d’´viter que e e ces derniers s’amoncellent ` certains endroits de la table. e e e e e a – ´crivez des fonctions qui donnent comme r´sultat de grandes valeurs . par exemple : poids. sur l’ensemble des identificateurs possibles. int niveauAllocation = 0. etc. N [ qui serait e e – rapide. Parmi les conseils qu’on trouve le plus souvent : a e – prenez N premier (une des recettes les plus donn´es. dans laquelle e e e e toute l’information se trouve dans le tableau adress´ par la fonction de hachage (il n’y a pas de listes chaˆ ees associ´s aux cases e ın´ e du tableau). Soit I l’ensemble des identificateurs existant dans un programme. si on s’en tient ` des op´rations simples. La litt´rature sp´cialis´e rapporte de nome e e breuses recettes. on obtient cette position par un calcul sur les caract`res de l’identificateur . mais e e a – si N ≥ |I|. en effet. mais il n’y a probablement pas de solution universelle. ce qui revient au mˆme. poidsTotal. les ´ventuels d´fauts (dissym´tries.) de la fonction initiale ont tendance ` disparaˆ a ıtre. etc. car elle est tr`s e e e e e utilis´e dans les compilateurs r´els. ou hash-code 51 . a – injective. De plus.5 Adressage dispers´ e Une derni`re technique de gestion d’une table de symboles m´rite d’ˆtre mentionn´e ici. facile ` calculer. une fonction qui ne ferait intervenir que les cinq premiers caract`res ne serait pas tr`s bonne ici. N [. dans les proe grammes on rencontre souvent des  grappes  de noms. la taille de la table n’est souvent pas suffisante pour permettre l’injectivit´ (qui requiert N ≥ |I|). Dans ce cas on souhaite qu’elles soient ´galement r´parties : pour e e e |I| chaque j ∈ [0. e Il est difficile de dire ce qu’est une bonne fonction de hachage. on esp`re que les couples d’identificateurs i1 . voire pr´sente des cases vides. accumue e e e lations. N la taille de la table d’identificateurs. e On se contente donc d’une fonction h prenant. des valeurs uniform´ment r´parties sur l’intervalle [0.2. les collisions sont in´vitables. Il est facile a e e a de voir pourquoi : h ´tant la fonction qui  place  les identificateurs dans la table. car une fonction de hachage n’est bonne que par rapport ` un ensemble d’identificateurs donn´. c’est-`-dire N . lorsque ces valeurs sont ramen´es ` l’intervalle [0. mais plus personne ne se donne la peine d’en rappeler e la justification). poids1. – si N < |I|. L’id´al serait de poss´der une fonction h : I → [0. Le principe en est assez e e e simple : au lieu de rechercher la position de l’identificateur dans la table. 49 .

N Voici la fonction de recherche : typedef struct maillon { char *identif. return r % N. MAILLON *recherche(char *identif) { MAILLON *p. } et voici la fonction qui se charge de l’insertion d’un identificateur (suppos´ absent) : e MAILLON *insertion(char *identif) { int h = hash(identif. } MAILLON. les compartiments ont ` peu pr`s la mˆme taille. L’efficacit´ de la m´thode provient alors du fait qu’au a e e e e lieu de faire une recherche dans une structure de taille |I| on fait un calcul et une recherche dans une structure de taille |I| . int r = 0. Si on note T cette table. alors Tj est le pointeur de tˆte de la liste chaˆ ee dans laquelle sont les e ın´ identificateurs i tels que h(i) = j. Si la fonction h est bien ın´ faite. N)]. l’adressage dispers´ ouvert apparaˆ comme une m´thode de partitionnement de l’ene e ıt e semble des identificateurs. la table qu’on adresse ` travers la fonction de hachage n’est pas une e a table d’identificateurs. return NULL.identificateur comme l’´criture d’un nombre dans une certaine base). Chaque liste chaˆ ee est un compartiment de la partition. Vu de cette mani`re. cela donne la fonction : e int hash(char *ident. p != NULL. while (*ident != ’\0’) r = X * r + *(ident++). } fonction de hachage identif Ensemble des identificateurs table de hachage infos suivant /* why not? */ Fig. int N) { const int X = 23. p = p->suivant) if (strcmp(identif. En C. a u e e e e e Dans le cas de l’adressage dispers´ ouvert. 13 – Adressage dispers´ ouvert e L` o` les m´thodes d’adressage dispers´ diff´rent entre elles c’est dans la mani`re de g´rer les collisions. #define N 101 MAILLON *table[N]. autres informations struct maillon *suivant. for (p = table[hash(identif. 50 . p->identif) == 0) return p. mais une table de listes chaˆ ees dont les maillons portent des identificateurs (voyez la ın´ figure 13). N).

return r. e Les objets statiques sont les fonctions. Les fonctions et les constantes e e sont des objets statiques en lecture seule. nous faisons l’impasse sur certains aspects importants (et difficiles) de la g´n´ration de code. e 2. etc. les constantes et les variables globales. identif).d´calage). une valeur initiale explicit´e par l’auteur du programme (dans les langages qui le permettent) ou bien une e valeur initiale implicite. pour une constante. on peut consid´rer e e e que l’espace qu’ils occupent est allou´ par le compilateur pendant la compilation52 .) et en mˆme temps nous introduisons une machine virtuelle.return table[h] = nouveauMaillon(identif. Les objets statiques existent pendant toute la dur´e de l’ex´cution d’un programme . if (r == NULL) erreurFatale("Erreur interne (probl`me d’espace)"). } 5 Production de code Nous nous int´ressons ici ` la derni`re phase de notre compilateur. la e e e e machine Mach 1. o` base est une adresse connue et d´calage un nombre entier qu’il faut ajouter ` base pour e e e u e a obtenir l’adresse voulue. sont d´tect´es et signal´es . souvent z´ro.1 5. e strcpy(r->identif. les objets en lecture seule sont log´s dans des zones de la m´moire dans lesquelles les tentatives e e e d’´criture. e 51 . Elle est presque toujours exprim´e comme un d´calage54 par rapport au e e e e d´but de la zone contenant l’objet en question. L’adresse d’un objet statique est un nombre entier qui indique la premi`re (parfois l’unique) cellule de e la m´moire occup´e par l’objet. 5. e r->identif = malloc(strlen(identif) + 1). e e et notamment sur les algorithmes pour l’optimisation du code et pour l’allocation des registres. pile d’ex´cution. MAILLON *suivant) { MAILLON *r = malloc(sizeof(MAILLON)). installe les e e e objets statiques dans la m´moire. e e e compilation s´par´e et ´dition de liens. il en produit une certaine repr´sene e e e tation dans le fichier objet. cela revient au mˆme. apr`s traitement du fichier objet par l’´diteur de lien. et l’espace global. o` sont les fonctions et les constantes. Les objets automatiques sont les variables locales des fonctions. pour laquelle notre compilateur produit du code et dont nous ´crirons un simulateur. la production de code. sa valeur et pour une variable globale. e On appelle espace statique l’espace m´moire dans lequel sont log´s les objets statiques. if (r->identif == NULL) erreurFatale("Erreur interne (probl`me d’espace)").1. Ils sont garnis de valeurs initiales : pour une fonction. Dans ce but. o` sont e u u les variables globales. table[h]). au lieu de cela. les petits syst`mes n’offrent pas cette s´curit´ (qui e a e e e e e e limite les d´gˆts lors des utilisations inad´quates des pointeurs et des indices des tableaux). adresses. } avec une fonction nouveauMaillon d´finie comme ceci : e MAILLON *nouveauMaillon(char *identif. son code. et c’est un outil appel´ chargeur qui. e a e 54 Le mot d´calage (les anglophones disent offset) fait r´f´rence ` une m´thode d’adressage employ´e tr`s souvent : une entit´ est e ee a e e e e rep´r´e par un couple (base. L’acc`s t[i] au i-`me ´l´ment d’un tableau t en est un exemple typique.1 Les objets et leurs adresses Classes d’objets Dans les langages qui nous int´ressent les programmes manipulent trois classes d’objets : e 1. r->suivant = suivant. Les variables globales sont des objets statiques en lecture-´criture. le compilateur n’alloue pas la m´moire pendant la compilation . e a e nous pr´sentons certains aspects de l’architecture des machines (registres et m´moire. c’est-`-dire de modification de valeurs. e Les objets statiques peuvent ˆtre en lecture seule ou en lecture-´criture 53 . pour ce qui nous occupe ici. e e 53 Lorsque le syst`me le permet. dans lequel t (l’adresse de t[0]) e e ee est la base et i le d´calage. e Faute de temps. ce qui comprend : 52 En r´alit´. Il est g´n´ralement e e e e constitu´ de deux zones : la zone du code. Mais.

font l’objet d’algorithmes savants impl´ment´s dans des fonctions comme malloc et free e e ou des op´rateurs comme new et delete. appel´ espace local de la fonction.2. distinct des e deux pr´c´dents. 52 . la r´cup´ration des morceaux rendus par ce dernier. la fonction qui se termine ee est la plus r´cemment activ´e de celles qui ont ´t´ commenc´es et ne sont pas encore termin´es. op´rateur delete de C++) ces objets existent jusqu’` la terminaison du e e a programme55 .4). alors que les moments de la cr´ation et de la destruction des objets dynamiques e ne sont pas connus a priori. la lutte contre l’´miettement de l’espace e e e disponible. mˆme si cette fonction ´tait e e e e d´j` active et donc qu’un espace local pour elle existait d´j` (c’est le cas d’une fonction qui s’appelle ea ea elle-mˆme. Il en e e ee e e d´coule que les espaces locaux des fonctions peuvent ˆtre allou´s dans une m´moire g´r´e comme une pile e e e e ee (voyez la figure 14) : lorsqu’une fonction est activ´e. Lorsqu’une fonction se termine. e e a e – les arguments formels de ces derni`res. directement ou indirectement). On place donc les objets dynamiques dans un troisi`me espace. e sens d'empilement espace local de h espace local de f espace local de g espace local de f Fig. c’est un m´canisme de r´cup´ration de la m´moire inutilis´e qui se charge de d´truire les e e e e e e objets dynamiques dont on peut prouver qu’ils n’interviendront plus dans le programme en cours. Si leur destruction n’est pas explicitement e demand´e (fonction free de C. puisque leur e e u existence n’est pas certaine. comme Java. en anglais). la m´moire utilis´e par un programme en cours d’ex´cution est divis´e en quatre espaces (voyez e e e e e la figure 15) : 55 Dans certains langages. commenc´es et non termin´es) ` ce moment-l`.– les variables d´clar´es ` l’int´rieur des fonctions. e e mais uniquement lorsqu’il est utile. son espace local est cr´´ au-dessus des espaces locaux e ee des fonctions actives (i. e e e La gestion du tas. e Ces variables occupent un espace qui n’existe pas pendant toute la dur´e de l’ex´cution du programme. de taille suffisante pour contenir ses arguments et ses e variables locales (plus un petit nombre d’informations techniques additionnelles expliqu´es en d´tail ` la e e a section 5. 14 – Empilement d’espaces locaux (f a appel´ g qui a appel´ f qui a appel´ h) e e e La propri´t´ suivante est importante : chaque fois qu’une fonction se termine. c’est-`-dire son allocation sous forme de morceaux de tailles variables. Les objets dynamiques sont allou´s lorsque le programme le demande explicitement (par exemple ` travers e a la fonction malloc de C ou l’op´rateur new de Java et C++). Les objets dynamiques ne peuvent pas ˆtre log´s dans les zones o` sont les objets statiques. o 3. e En d´finitive.e. ` la demande du a a programme. Lorsque l’activation d’une fonction se termine son espace local e est d´truit. puisque cette pile grandit et diminue en accord avec les appels e e et retours des fonctions. l’activation d’une fonction commence par l’allocation e e d’un espace. Ils ne peuvent pas davantage ˆtre e e e h´berg´s dans la pile des espaces locaux. Un espace local nouveau est allou´ chaque fois qu’une fonction est appel´e. elle d´pend des conditions d’ex´cution. etc. e e a a son espace local est celui qui se trouve au sommet de la pile et il suffit de le d´piler pour avoir au sommet e l’espace local de la fonction qui va reprendre le contrˆle. appel´ le tas (heap. Plus pr´cis´ment.

2 D’o` viennent les adresses des objets ? u Puisque l’adresse d’un objet est un d´calage par rapport ` la base d’un certain espace qui d´pend de la e a e classe de l’objet. ou bien lorsqu’un programme fait trop d’appels de fonctions et/ou ces derni`res ont des espaces locaux de taille trop e importante. e e 53 . 15 – Organisation de la m´moire e – – – – le code (espace statique en lecture seule). de sorte que si un e peu plus tard on cherche ` obtenir l’adresse d’un autre objet on obtiendra une valeur diff´rente. la production d’un ´l´ment de code. le compilateur utilise les trois vae e riables suivantes : e TC (Taille du Code) Pendant la compilation. la pile grossit lors des appels de fonctions et diminue lors des terminaisons des fonctions appel´es. en revanche. ´voluent au cours de l’ex´cution : le tas ne fait que e e e grossir56 . pendant qu’il produit la traduction d’un programme. delete) est de bien g´rer les parcelles de m´moire emprunt´e puis o e e e e rendue par un programme . l’espace global (espace statique en lecture-´criture).tas (objets dynamiques) espace disponible pile (objets locaux) espace global code espace statique TEG TC Fig. mais elles ne sont pas cens´es en r´duire la taille. e Cela suppose que d’autres op´rations du compilateur font par ailleurs grandir ce compteur. le rˆle des fonctions de restitution de m´moire (free. e Cela se produit lorsqu’un programme alloue trop d’objets dynamiques et/ou de taille trop importante. du point de vue du compilateur  obtenir l’adresse d’un objet  c’est simplement m´moriser e l’´tat courant d’un certain compteur dont la valeur exprime le niveau de remplissage de l’espace correspondant. se e e ee e traduit par les deux affectations : memoire[T C] ← element TC ← TC + 1 56 En effet. contenant le programme et les constantes. e La rencontre de la pile et du tas (voyez la figure 15) est un accident mortel pour l’ex´cution du programme. des mots. e Au d´but de la compilation. a e Plus pr´cis´ment. contenant les variables globales. cela d´pend de la machine) du programme en langage machine couramment produites. 5. La pile et le tas. si memoire repr´sente l’espace (ou le fichier) dans lequel e e le code machine est m´moris´.1. le tas (heap). contenant variables locales. TC vaut 0. Ensuite. e la pile (stack ). cette variable a constamment pour valeur le nombre d’unit´s (des octets. contenant les variables allou´es dynamiquement. ces fonctions limitent la croissance du tas. comme un opcode ou un op´rande. e Les tailles du code et de l’espace global sont connues d`s la fin de la compilation et ne changent pas pendant e l’ex´cution du programme.

produit son introduction dans le dictionnaire ad´quat. et en compl´tant de telles  r´f´rences e e e ee insatisfaites  par les adresses des objets correspondants. u e e e TEL (Taille de l’Espace Local) A l’int´rieur d’une fonction. pour m´moriser l’adresse d’une fonction fon il suffit de faire. Cela implique qu’` la fin de la compilation il e e e a y a dans le fichier produit quelque trace des noms des variables et fonctions mentionn´es dans le fichier source. On peut donc penser que dans le code qui sort d’un compilateur les identificateurs qui se trouvaient dans le texte source ont disparu57 et. par cons´quent. Si le compilateur n’est pas dans une fonction TEL n’est pas d´fini. d’une variable globale ou e e e d’une fonction. e e e Nous n’aurons donc pas besoin d’´diteur de liens dans notre syst`me.3 Compilation s´par´e et ´dition de liens e e e Tout identificateur apparaissant dans une partie ex´cutable d’un programme doit avoir ´t´ pr´alablement e ee e d´clar´. e T C + T EG est le d´calage (relatif ` l’origine g´n´rale de la m´moire du programme) correspondant au e a e e e niveau initial de la pile. C’est pourquoi la plupart des compilateurs ont e e e une option de compilation qui provoque la conservation des identificateurs avec le code. Par la suite. associ´ ` une adresse. ce qui e e e n’est pas grave. o` le texte d’un programme peut e u se trouver ´clat´ dans plusieurs fichiers sources destin´s ` ˆtre compil´s ind´pendamment les uns des autres e e e a e e e (on appelle cela la compilation s´par´e). En effet. cette variable a pour valeur la somme des tailles des variables locales de la fonction en cours de compilation. en v´rifiant que les objets r´f´renc´s mais e e e e ee e non d´finis dans certains fichiers sont bien d´finis dans d’autres fichiers. Les objets locaux. qui ne sont d´j` pas visibles ea en dehors de la fonction dans laquelle ils sont d´clar´s. r´sultats de e e o e e compilations s´par´es. comme C ou Java. mais un outil qui lui e e e est associ´. e e 57 Une cons´quence tangible de la disparition des identificateurs est l’impossibilit´ de  d´compiler  les fichiers objets. qui d´pend de son type. Par la suite. Mais les choses sont plus compliqu´es dans les langages. de fait. Faute de temps. e A l’entr´e de chaque fonction TEL est remis ` z´ro.Par cons´quent. e a A la fin de la compilation les valeurs des variables TC et TEG sont pr´cieuses : e – TC est la taille du code donc. dans une organisation comme celle de la figure 15. tel peut ˆtre le cas dans les langages qui obligent ` mettre tout le e a programme dans un seul fichier. e e Au d´but de la compilation TEG vaut 0. mais aussi la difficult´ de les d´boguer. 5. le langage dont nous ´crirons le compilateur ne supportera pas la compilation s´par´e. notons-la adri . le rˆle de TEG dans la d´claration d’une variable e o e globale varg se r´sume ` ceci : e a adresse(varg) ← T EG T EG ← T EG + taille(varg) o` taille(varg) repr´sente la taille de la variable en question. dans une organisation comme celle de la figure 15. le rˆle de TEL dans la d´claration d’une e a e o e variable locale varl se r´sume ` ceci : e a adresse(varl) ← T EL T EL ← T EL + taille(varl) Les arguments formels de la fonction. 54 . e e e Notez ´galement que le principal int´ress´ par cette affaire n’est pas le compilateur. pour en faire un unique programme ex´cutable. La d´claration d’un identificateur i. afin de permettre aux d´bogueurs d’acc´der e e aux variables et fonctions par leurs noms originaux. le compilateur remplace chaque occurrence de i dans une expression par le nombre adri . ce qui est plus embˆtant. que ce soit le nom d’une variable locale. dans ces langages il doit ˆtre possible qu’une variable ou une e e e fonction d´clar´e dans un fichier soit mentionn´e dans un autre.1. au moment o` commence e e u la compilation de la fonction : adresse(f on) ← T C TEG (Taille de l’Espace Global) Cette variable a constamment pour valeur la somme des tailles des variables globales dont le compilateur a rencontr´ la d´claration. Par e ea la suite. e Notez que cette question ne concerne que les objets globaux. ne risquent pas d’ˆtre visibles dans un autre fichier. l’´diteur de liens (ou linker ) dont le rˆle est de concat´ner plusieurs fichiers objets. bien qu’´tant des objets locaux. a e e e a – TEG est la taille de l’espace global .4). elle est aussi le d´calage e (relatif ` l’origine g´n´rale de la m´moire du programme) correspondant ` la base de l’espace global.2. n’interviennent pas dans le calcul e de TEL (la question des adresses des arguments formels sera trait´e ` la section 5.

Ainsi. e a a ee e base x fonction A fonction A y APPELA x APPELR y Fig. cela ne concerne que l’acc`s aux objets qui se trouvent dans le mˆme module que la r´f´rence . 55 . voici n´anmoins quelques explications sur l’´diteur de liens et la structure des fichiers objets e e dans les langages qui autorisent la compilation s´par´e. qui sont exprim´es e ıt e e comme des d´placements relatifs ` une base propre ` chaque module. ´ ´ ´ ´ References absolues et references relatives. Nous venons de voir que ces r´f´rences doivent ˆtre corrig´es lorsque le module qui les contient est d´cal´ ee e e e e et ne commence pas lui-mˆme. 16 – R´f´rence absolue et r´f´rence relative ` une mˆme fonction A ee ee a e Par exemple. Lorsque l’´diteur de liens concat`ne des modules pour former un ex´cutable. pour tenir compte du fait que le d´but de chaque module ne ee a e correspond plus ` l’adresse 0 mais ` une adresse ´gale ` la somme des tailles des modules qui ont ´t´ mis devant a a e a ee lui. e e a On dit qu’une instruction fait une r´f´rence relative ` un objet statique lorsque ce dernier est rep´r´ par un ee a ee d´calage relatif ` l’adresse ` laquelle se trouve la r´f´rence elle-mˆme. Un probl`me apparaˆ imm´diatement : si on ne fait rien. Il faut donc que l’´diteur de liens passe en revue tout le contenu des modules qu’il concat`ne et en corrige e e toutes les r´f´rences ` des objets statiques. L’int´rˆt des r´f´rences relatives saute aux yeux : elles sont insensibles aux d´calages du module dans lequel ee ee e elles se trouvent. est : produire des r´f´rences intra-modules e ee relatives et des r´f´rences inter-modules absolues. e e e ee On dit qu’une instruction fait une r´f´rence absolue ` un objet statique (variable ou fonction) si l’adresse ee a de ce dernier est indiqu´e par un d´calage relatif ` la base de l’espace concern´ (l’espace global ou l’espace du e e a e code). une ee a e ee e a r`gle suivie par les compilateurs. Il nous reste ` comprendre comment l’´diteur de liens arrive ` attribuer ` une a e a a r´f´rence ` un objet non d´fini dans un module (cela s’appelle une r´f´rence insatisfaite) l’adresse de l’objet. une seule fonction et une seule variable globale peuvent e avoir l’adresse 0. En pratique le travail mentionn´ ci-dessus se trouve e all´g´ par le fait que les langages-machines supportent deux mani`res de faire r´f´rence aux objets statiques. dans le fichier qui sort de l’´diteur de liens. ee Structure des modules. il concat`ne d’un cˆt´ les zones de code (objets statiques en lecture a e e e oe seule) de chaque module. les r´f´rences e e e ee relatives contenues dans ces modules n’ont pas ` ˆtre mises ` jour. qui prend comme op´rande une r´f´rence absolue. afin d’obtenir une unique zone de code totale et un unique espace global total. e e Appelons module un fichier produit par le compilateur. la figure 16 montre le cas d’un langage machine dans lequel il y a deux instructions pour appeler une fonction : APPELA. ` l’adresse 0.Pour les curieux. lorsque le langage machine le permet. Le rˆle de l’´diteur de liens est de concat´ner (mettre o e e bout ` bout) plusieurs modules. sauf pour le module e a a qui se retrouve en tˆte. e e ee il n’y a aucun int´rˆt ` repr´senter par une r´f´rence relative un acc`s ` un objet d’un autre module. et APPELR qui prend une r´f´rence e ee ee relative. C’est facile ` voir : chaque module apporte une fonction d’adresse 0 et probablement e a une variable globale d’adresse 0. les adresses des objets statiques. vont devenir fausses. ee a e ee qui se trouve dans un autre module. et d’un autre cot´ les espaces globaux (objets statiques en lecture ´criture) de chaque e e module. En g´n´ral. Dans l’ex´cutable final. ae a Bien entendu.

chaque identificateur est le nom d’un e objet que le module en question met ` la disposition des autres modules.. fonction g . dans des langages comme le C. dans cette table. il y a deux grandes familles de machines : e e e 58 Quels sont les objets publics. e 56 . tous les objets globaux sont publics par d´faut. la e e e e machine cible du compilateur que nous r´aliserons ` titre de projet. c’est-`-dire non publics. e En r´sum´. e e a – d´calage des r´f´rences absolues contenues dans ces modules (dont les m´moris´es dans les tables). e ee e e – r´union des tables d’objets publics et utilisation de la table obtenue pour corriger les r´f´rences insatisfaites e ee pr´sentes dans les sections de code.. e e e e 5. il faut la d´composer en des instructions plus e e simples.. form´ du code corrig´. e a 5. aucun objet n’est public par d´faut et il faut une d´claration explicite pour rendre publics les objets globaux que e e le programmeur souhaite. qu’il faudra corriger e e a ee lorsque l’adresse de l’objet sera connue. e section de code références insatisfaites .. La nature de ces instructions plus simples d´pend du type de machine dont on dispose.. tout objet global est public. ce code comporte un certain nombre de valeurs incorrectes. – la table des objets publics 58 d´finis dans le module . fonction f . – la table des r´f´rences insatisfaites de la section de code . 17 – Un module objet Par exemple. Y et Z correspondent ` des emplacements dans la m´moire de l’ordinateur)... adresse dans la section de code) : – la section de code contient la traduction en langage machine d’un fichier source . au choix.. et il est associ´ ` l’adresse de a ea l’objet concern´ dans la section de code . et une qualification e sp´ciale (dans le cas de C c’est la qualification static) permet d’en rendre certains priv´s. x y fonction f y .  m`que ouane  ou  machun .2 La machine Mach 1 Nous continuons notre expos´ sur la g´n´ration de code par la pr´sentation de la machine Mach 159 . comme X = Y + Z.1 Machines ` registres et machines ` pile a a Les langages ´volu´s permettent l’´criture d’expressions en utilisant la notation alg´brique ` laquelle nous e e e e a sommes habitu´s. e e a 59 Cela se prononce. c’est-`-dire les objets d´finis dans un module qu’on peut r´f´rencer depuis d’autres modules ? a e ee On l’a d´j` dit. Dans ea e d’autres langages. Inversement. Enfin.. e – ´mission du fichier ex´cutable final. e a e Une telle expression est trop compliqu´e pour le processeur. APPELA . Relativement e a ` la mani`re dont les op´rations sont exprim´es. la figure 17 repr´sente un module contenant la d´finition d’une fonction publique f et l’appel e e d’une fonction non d´finie dans ce module g. ??? objets publics . cette formule signifiant  ajoutez le contenu de Y ` celui de Z et rangez e a le r´sultat dans X  (X.2. non d´finis dans ce a ee a e module) dont l’adresse n’est pas connue au moment de la compilation. dans cette table.. le travail de l’´diteur de liens se compose sch´matiquement des tˆches suivantes : e e e e a – concat´nation des sections de code des modules donn´s ` lier ensemble.Pour cela il faut savoir que chaque fichier produit par le compilateur se compose de trois parties : une section de code et deux tables faites de couples (identificateur. au contenu incorrect. x Fig. dans certains langages. chaque identificateur r´f´renc´ ee ee e mais non d´fini est associ´ ` l’adresse de l’´l´ment de code.. un objet public est n´cessairement global.. ` savoir les r´f´rences ` des objets externes (i.e.

Y et Z) : MOVE MOVE ADD MOVE Y. a e e 60 Se rappeler que de nos jours. Inversement. Si on vise une telle machine. etc. Y et Z les adresses des variables X. au contraire. e a 57 . la valeur de BEL change lorsque l’activation d’une fonction commence ou finit (` ce e a propos voir la section 5.2. Les algorithmes d’optimisation et d’allocation des registres sortant du cadre de ce cours.. l’affectation X = Y + Z devra ˆtre traduite en quelque e e e chose comme (notant X. par cons´quent. Les machines ` pile. n’importe quel registre peut intervenir dans e a e e une op´ration arithm´tique ou autre . le code X = Y + Z se traduira donc ainsi (notant encore X. Car il faut savoir que les machines  physiques  existantes sont presque toujours des machines ` registres. le tas en moins). Y et Z) : PUSH PUSH ADD POP Y Z X // // // // met la valeur de Y au sommet de la pile met la valeur de Z au sommet de la pile remplace les deux valeurs au sommet de la pile par leur somme enl`ve la valeur au sommet de la pile et la range dans X e Il est a priori plus facile de produire du code de bonne qualit´ pour une machine ` pile plutˆt que pour une e a o machine ` registres. e BEL (Base de l’Espace Local) indique la cellule autour de laquelle est organis´ l’espace local de la fonction en cours d’ex´cution . le microprocesseur d’un ordinateur personnel moyennement puissant tourne ` 2 e a GHz (c’est sa cadence interne) alors que son bus et sa m´moire ne travaillent qu’` 133 MHz.2. l’ex´cution d’une op´ration unaire e e e e consiste toujours ` d´piler une valeur x et ` empiler le r´sultat x de l’op´ration. ou plus exactement la premi`re cellule e libre au-dessus de la pile. Les registres suivants ont un rˆle essentiel dans le o fonctionnement de la machine : CO (Compteur Ordinal) indique constamment la cellule contenant l’instruction que la machine est en train d’ex´cuter.2 Structure g´n´rale de la machine Mach 1 e e La m´moire de la machine Mach 1 est faite de cellules num´rot´es.X // // // // d´place la valeur de Y dans R1 e d´place la valeur de Z dans R2 e ajoute R1 ` R2 a d´place la valeur de R2 dans X e 2.R2 R1. qui sont les a e e seuls composants susceptibles d’intervenir dans une op´rations (autre qu’un transfert de m´moire ` registre ou e e a r´ciproquement) ` titre d’op´randes ou de r´sultats. a pour une raison d’efficacit´ facile ` comprendre : les op´rations qui ne concernent que des registres restent e a e internes au microprocesseur et ne sollicitent ni le bus ni la m´moire de la machine60 .1. R2.R1 Z. e e a 5. c’est-`-dire le nombre total de cellules occup´es dans la m´moire. Et. la machine pour laquelle nous g´n´rerons du code sera une machine ` pile. e BEG (Base de l’Espace Global) indique la premi`re cellule de l’espace r´serv´ aux variables globales (autree e e ment dit. SP (Sommet de la Pile) indique constamment le sommet de la pile. Plus pr´cis´ment : e e e – les op´randes d’un op´rateur binaire sont toujours les deux valeurs au sommet de la pile . d´cembre 2001.R2 R2. les instructions qui expriment ces op´rations doivent e e e e sp´cifier leurs op´randes. avec un compilae teur optimisateur. disposent d’une pile (qui peut ˆtre la pile des variables locales) au a e sommet de laquelle se font toutes les op´rations. l’ex´cution e e e d’une op´ration binaire ⊗ consiste toujours ` d´piler deux valeurs x et y et ` empiler le r´sultat x ⊗ y de e a e a e l’op´ration. Les machines ` registres poss`dent un certain nombre de registres. BEG pointe la variable globale d’adresse 0). mais ce d´faut est largement corrig´ dans les compilateurs commerciaux par l’utilisation a e e de savants algorithmes qui optimisent l’utilisation des registres. a e a e e Pour une telle machine. not´s ici R1.4). organis´es comme le montre la figure e e e e 18 (c’est la structure de la figure 15. Y et Z les adresses des variables X. les expressions complexes sont traduites par des suites d’instructions dont la plupart ne mentionnent que des registres. e – l’op´rande d’un op´rateur unaire est la valeur au sommet de la pile .

la a pile est utilis´e pour le calcul des expressions courantes. La fonction appelante r´serve un mot vide sur la pile. 1 ↔ deuxi`me variable locale. puis e u e e e elle empile les valeurs des arguments effectifs. puis de celui de la fonction appel´e (2). soit d’un opcode et un op´rande. 18 – Organisation de la m´moire de la machine Mach 1 e 5. puis alloue l’espace local. La fonction appel´e empile le  BEL courant  (qui est en train de devenir  BEL pr´c´dent ). etc. Terminaison du travail : la fonction appel´e remet BEL et SP comme ils ´taient lorsqu’elle a ´t´ activ´e.2. e – les arguments formels sont ´galement atteints ` travers des d´placements (n´gatifs. Au-del` de cet espace. −4 ↔ (n − 1)-`me argument. La table 1. a e 5. d’abord du point de vue de la fonction appelante (1). e 3. etc. Elle se retrouve alors avec une pile au sommet de laquelle est le r´sultat de la fonction e qui vient d’ˆtre appel´e. et suppose donc l’accord e e entre la fonction appelante et la fonction appel´e au sujet du nombre d’arguments de la fonction appel´e. ensuite le retour. Voici ce qui se passe (voyez la figure 19) : 1. elle ex´cute une instruction APPEL (qui empile e l’adresse de retour). e e ee e puis effectue une instruction RETOUR. e e L’ensemble de toutes ces valeurs forme l’ espace local  de la fonction en cours. e e u e e a e – la cellule o` la fonction doit d´poser son r´sultat est atteinte elle aussi ` travers un d´placement relatif a ` BEL. Reprise du calcul interrompu par l’appel. cette fois) relatifs ` e a e e a BEL : −3 ↔ n-`me argument. Pendant la dur´e de l’activation de la fonction : e – les variables locales sont atteintes ` travers des d´placements (positifs ou nuls) relatifs ` BEL : 0 ↔ premi`re a e a e variable locale. e e 4. La fonction appelante lib`re l’espace occup´ par les valeurs des arguments effectifs. Ce d´placement est −(n + 3). a Chaque instruction est faite soit d’un seul opcode.4 Compl´ments sur l’appel des fonctions e La cr´ation et la destruction de l’espace local des fonctions a lieu ` quatre moments bien d´termin´s : e a e e l’activation de la fonction. Globalement. la situation est la mˆme qu’apr`s une op´ration arithm´tique : les e e e e e e op´randes ont ´t´ d´pil´s et remplac´s par le r´sultat.espace disponible SP pile BEL espace global BEG CO code Fig. o` sera d´pos´ le r´sultat de la fonction. qui est celle d’une cellule. donne la liste des instructions.2. Ensuite. ` la fin de ce polycopi´. prend la e e e valeur de SP pour BEL courant. e ee e e e e 58 . d’abord du point de vue de la fonction appel´e (3) puis de celui de la fonction e e appelante (4). elle occupe alors deux cellules cons´cutives.3 Jeu d’instructions La machine Mach 1 est une machine ` pile. elle occupe alors une cellule. Les constantes enti`res et les  adresses  ont la mˆme e e e e taille. 2. n ´tant le nombre d’arguments formels.

k . c’est e e a ıtre donc cette fonction qui doit les enlever de la pile. Cela oblige la fonction appel´e ` connaˆ e e e e a ıtre 61 Et encore. qui pr´cisent e a ` quel endroit la fonction appelante doit d´poser les valeurs des arguments et o` trouvera-t-elle le r´sultat ou. Edict´es e e e a e e par les concepteurs du syst`me d’exploitation. Dans notre syst`me. ce e e qui a une cons´quence tr`s importante : puisque tous les compilateurs produisent des codes dans lesquels les e e param`tres et le r´sultat des fonctions sont pass´s de la mˆme mani`re.. −(n + 3) + n = −3. loc. arg2 . . Il est donc naturel de donner ` cette e e e a derni`re la responsabilit´ de les enlever de la pile. apr`s nettoyage des arguments. . pourra appeler une fonction ´crite dans un autre langage L2 et e e compil´e par un autre compilateur. e ´ En g´n´ral les conventions d’appel ne sont pas laiss´es ` l’appr´ciation des auteurs de compilateurs. argn sont atteints. var.haut de la pile SP évaluation en cours var. – le r´sultat est d´pos´ l` o` il faut pour que. au retour de la fonction appel´e. e e e a u e Mais il y a un petit inconv´nient61 . e e e – les variables locales sont allou´es par la fonction appel´e. 1 BEL précédent adr. que la e fonction appelante et la fonction appel´e soient d’accord sur le nombre d’arguments de cette derni`re. tellement il semble naturel d’exiger. loc. ` e e a l’int´rieur de la fonction. e u e ce qui revient au mˆme. et compil´e donc par le compilateur de L1 . Il y a donc un ensemble de conventions. L’explication e pr´c´dente montre qu’il y a une diff´rence importante entre ces deux sortes d’objets locaux : e e e – les arguments sont install´s dans la pile par la fonction appelante. dites conventions d’appel. une fonction ´crite dans un langage e e e e e e L1 ... ` quel endroit la fonction appel´e trouvera ses arguments et o` doit-elle d´poser son e a e u e r´sultat. et e le r´sultat de la fonction lui-mˆme poss`de l’adresse locale −(n + 3). comme en Pascal ou Java. il soit au sommet de la pile. −(n + 3) + 2. lorsqu’elle se termine (l’instruction SORTIE fait ce travail). retour arg n .. les argument arg1 . car ce sont les valeurs d’expressions e qui doivent ˆtre ´valu´es dans le contexte de la fonction appelante. 19 – Espace local d’une fonction ´ Qui cree l’espace local d’une fonction ? Les arguments d’une fonction et les variables d´clar´es ` e e a l’int´rieur de cette derni`re sont des objets locaux et ont donc des adresses qui sont des d´placements relatifs ` e e e a BEL (positifs dans le cas des variables locales. par les adresses locales respectives −(n + 3) + 1. ce n’est pas sur que ce soit un inconv´nient. elles sont suivies par tous les compilateurs  homologu´s . e Nous avons choisi ici des conventions d’appel simples et pratiques (beaucoup de syst`mes font ainsi) puisque : e e e – les arguments sont empil´s dans l’ordre dans lequel ils se pr´sentent dans l’expression d’appel. e e 59 .. celui de L2 . qui est la seule ` en connaˆ le nombre . n´gatifs dans le cas des arguments. arg 1 résultat espace local de la fonction appelante bas de la pile BEL Fig... Les conventions d’appel.. voir la figure 19).

et interdit l’´criture de fonctions admettant un nombre variable d’arguments e (de telles fonctions sont possibles en C. etc. songez ` printf et scanf).1 Expressions arithm´tiques e Puisque la machine Mach 1 est une machine ` pile. a 63 Attention. que ce soit cela. Enfin.). finExpArith(). avec ‘∗’ et ‘/’ dans les rˆles de ‘+’ et e e o ‘−’. e e e On est alors surpris.. dans lequel une e occurrence d’un identificateur dans une expression indique toujours ` une variable simple. C’est que62 : e La bonne mani`re d’´crire un compilateur du langage L pour la machine M consiste ` ´crire un analyseur e e ae du langage L auquel. imaginons que notre langage e ee source est une sorte de  C francis´ . est extrˆmement simple et ´l´gante. nous envisageons momentan´ment un langage ultra-simple. dans un deuxi`me temps. a 62 Plus 60 . pour comprendre pourquoi nous faisons que ce dernier produise ce qu’il produit.. on ajoute –sans rien lui enlever– les op´rations qui produisent e e du code pour la machine M . syntaxique. 5. e e e e e qui produit comme r´sultat un programme P en langage machine. entier uneFonction() { entier a.. et facteur dans le rˆle de terme). d’autre part l’ex´cution du programme P . e a e Ce principe est tout ` fait fondamental. dans un deuxi`me temps. terme(). y = 123 * x + c. la traduction des expressions arithm´tiques. . } } (les fonctions terme et finTerme sont analogues aux deux pr´c´dentes. } Pour commencer. ` lui ajouter les op´rations qui construisent les informations en question. z. une version simplifi´e63 de la fonction facteur pourrait commencer o e comme ceci : g´n´ralement : la bonne fa¸on d’obtenir l’information port´e par un texte soumis ` une syntaxe consiste ` ´crire l’analyseur e e c e a ae syntaxique correspondant et. a 5. comment ajouter ` notre analyseur les op´rations a a e de g´n´ration de code qui en font un compilateur. y.3 Exemples de production de code Quand on n’est pas connaisseur de la question on peut croire que la g´n´ration de code machine est la partie e e la plus importante d’un compilateur. Il faudra se souvenir de ceci : quand on r´fl´chit ` la g´n´ration e e e e a e e de code on est concern´ par deux ex´cutions diff´rentes : d’une part l’ex´cution du compilateur que nous r´alisons.3. sans tableaux ni appels de fonctions. Si vous ne deviez retenir qu’une seule chose de ce cours. c. s´mantique. A titre d’exemple. Son analyse syntaxique aura ´t´ faite par e a ee des fonctions expArith (expression arithm´tique) et finExpArith ressemblant ` ceci : e a void expArith(void) { terme(). } void finExpArith(void) { if (uniteCourante == ’+’ || uniteCourante == ’-’) { uniteCourante = uniteSuivante(). mais nous devons l’imaginer en mˆme temps que nous ´crivons le e e e e compilateur. finExpArith(). . voire frustr´. int´ressons-nous ` l’expression 123 * x + c. aussi a e complexes soient-elles. et donc que c’est elle qui d´termine la structure g´n´rale de ce dernier. et consid´rons la situation suivante (on suppose qu’il n’y a pas d’autres e e d´clarations de variables que celles qu’on voit ici) : e entier x. en constatant la place consid´rable que les ouvrages sp´cialis´s consacrent ` e e e e a l’analyse (lexicale. ` travers des exemples. Dans cette section nous expliquons. a e e priori cette ex´cution a lieu ult´rieurement.le nombre n d’arguments effectifs.. b.

. la compilation de l’expression x doit donner e EMPG EMPL 0 2 (car x est. Pour obtenir ces codes il suffit de transformer la fonction facteur comme e ceci : void facteur(void) { if (uniteCourante == NOMBRE) { genCode(EMPC). uniteCourante = uniteSuivante(). } else if (uniteCourante == IDENTIFICATEUR) { ENTREE_DICO *pDesc = rechercher(lexeme). La compilation d’une instruction du langage source produit une suite d’instructions du langage machine (plus ou moins longue. else . la premi`re variable globale) et celle de l’expression c doit donner e (car c est la troisi`me variable locale). genCode(pDesc->adresse). les fonctions expArith et terme n’ont pas besoin d’ˆtre modifi´es... selon la complexit´ de l’expression) dont l’ex´cution a pour effet global d’ajouter une valeur au e e sommet de la pile. genCode(atoi(lexeme)). la compilation de l’expression 123 doit donner le code e EMPC 123 De mˆme. selon la complexit´ de l’expression) dont l’ex´cution laisse la pile dans l’´tat o` e e e u elle se trouvait avant de commencer l’instruction en question. ` savoir le r´sultat de l’´valuation de l’expression.void facteur(void) { if (uniteCourante == NOMBRE) uniteCourante = uniteSuivante(). Par cons´quent. a la cons´quence fondamentale a ea e suivante : 1. } else . dans notre exemple.2.. } La fonction genCode se charge de placer un ´l´ment (un opcode ou un op´rande) dans le code. a e e 2. expliqu´ ` la section 5. } Le principe de fonctionnement d’une machine ` pile. genCode(pDesc->classe == VARIABLE_GLOBALE ? EMPG : EMPL).1. La compilation d’une expression produit une suite d’instructions du langage machine (plus ou moins longue. Si nous ee e supposons que ce dernier est rang´ dans la m´moire. l’effet d’une telle expression doit ˆtre de mettre au a ea e sommet de la pile la valeur 123. Seules les e e fonctions dans lesquelles des op´rateurs apparaissent explicitement doivent l’ˆtre : e e 61 . ce pourrait ˆtre tout simplement : e e e void genCode(int element) { mem[TC++] = element. } Pour la production de code. Une mani`re de retrouver quel doit ˆtre le code produit pour la compilation de l’expression 123 * x + c e e consiste ` se dire que 123 est d´j` une expression correcte . uniteCourante = uniteSuivante(). else if (uniteCourante == IDENTIFICATEUR) uniteCourante = uniteSuivante().

genCode(pDesc->classe == VARIABLE_GLOBALE ? DEPG : DEPL).. } /* instruction d’affectation */ pour qu’il produise du code il faut transformer cet analyseur comme ceci : void instruction(void) { if (uniteCourante == IDENTIFICATEUR) { /* instruction d’affectation */ ENTREE_DICO *pDesc = rechercher(lexeme). } else .. on constate que son effet global aura bien ´t´ d’ajouter e e ee une valeur au sommet de la pile.’). le code produit ` l’occasion de la compilation de l’expression 123 * x + c sera donc : a EMPC 123 EMPG 0 MUL EMPL 2 ADD et. uniteCourante = uniteSuivante(). finExpArith(). } } Au final. facteur(). expArith(). terminal(’=’). } } void finterme(void) { int uc = uniteCourante.’).. if (uniteCourante == ’*’ || uniteCourante == ’/’) { uniteCourante = uniteSuivante(). terme(). genCode(pDesc->adresse).. expArith().void finExpArith(void) { int uc = uniteCourante. genCode(uc == ’+’ ? ADD : SOUS). } 62 . } else . if (uniteCourante == ’+’ || uniteCourante == ’-’) { uniteCourante = uniteSuivante(). on peut imaginer qu’elle aura ´t´ analys´e par une fonction ee e instruction commen¸ant comme ceci : c void instruction(void) { if (uniteCourante == IDENTIFICATEUR) { uniteCourante = uniteSuivante(). Consid´rons maintenant l’instruction compl`te y = 123 * x + c. en imaginant l’ex´cution de la s´quence ci-dessus. terminal(’. Dans un compilateur ultra-simplifi´ qui e e e ignorerait les appels de fonction et les tableaux. terminal(’=’). finTerme(). genCode(uc == ’*’ ? MUL : DIV). terminal(’.

l’utilisation d’un analyseur r´cursif descendant nous a permis de faire e une factorisation ` gauche originale du d´but commun des deux formes de l’instruction si (` propos de factorisation ` gauche voyez a e a a ´ventuellement la section 3. e a e e puis on revient ` l’´valuation de l’expression et on recommence tout. terminal(FAIRE). . nulle) l’ex´cution e e a e continue apr`s le corps de la boucle . non nulle) le corps de la boucle est ex´cut´.3. expr2 . instruction(). si l’expression est vraie (c.-`-d. repr´sentent les adresses des e e instructions) : 64 Notez que..on voit alors que le code finalement produit par la compilation de y = 123 * x + c aura ´t´ : ee EMPC 123 EMPG 0 MUL EMPL 2 ADD DEPG 1 (y est la deuxi`me variable globale). a e Notons expr1 . si elle est fausse (c.. } else if (uniteCourante == SI) { /* instruction conditionnelle */ uniteCourante = uniteSuivante(). instruction(). } } else . instrk ceux produits par la compilation de l’instruction. terminal(ALORS). c 5. instr2 .. else if (uniteCourante == TANTQUE) { /* boucle "tant que" */ uniteCourante = uniteSuivante(). l’ex´cution du code pr´c´dent laisse la pile comme elle ´tait e e e e e e en commen¸ant... | tantque expression faire instruction | si expression alors instruction | si expression alors instruction sinon instruction | ..1.-`-d...2 Instruction conditionnelle et boucles La plupart des langages modernes poss`dent des instructions conditionnelles et des  boucles tant que  e d´finies par des productions analogues ` la suivante : e a instruction → .. } Commen¸ons par le code ` g´n´rer ` l’occasion d’une boucle  tant que . expression(). Dans ce code on trouvera les suites c a e e a d’instructions g´n´r´es pour l’expression et pour l’instruction que la syntaxe pr´voit (ces deux suites peuvent e ee e ˆtre tr`s longues. La partie de l’analyseur syntaxique correspondant ` la r`gle pr´c´dente ressemblerait ` ceci64 : a e e e a void instruction(void) { . Pour l’instruction  tant que  toute enti`re on aura e donc un code comme ceci (les rep`res dans la colonne de gauche. if (uniteCourante == SINON) { uniteCourante = uniteSuivante(). expression()..3). comme α et β. instruction(). cela d´pend de la complexit´ de l’expression et de l’instruction en question).. exprn les codes-machine produits par la compilation de l’expression et instr1 . Comme pr´vu. e 63 . dans le cas de l’instruction conditionnelle. e e e e L’effet de cette instruction est connu : l’expression est ´valu´e . ..

genCode(alpha).. } Instruction conditionnelle. il n’est pas difficile d’imaginer ce qu’il faut ajouter.. la suite (apr`s l’instruction si. TC)... instrk SAUT α β Le point le plus difficile. Il faut donc produire quelque chose comme SIFAUX 0 et noter que la case dans laquelle est inscrit le 0 est ` corriger ult´rieurement (quand la valeur de β sera connue).. } o` repCode (pour  r´parer le code ) est une fonction aussi simple que genCode (cette fonction est tr`s simple u e e parce que nous supposons que notre compilateur produit du code dans la m´moire ..... Ce qui donne a e le programme : void instruction(void) { int alpha... /* ici. beta = TC */ } else .) e 64 . genCode(SAUT). exprn SIFAUX β instr1 instr2 . instruction(). terminal(FAIRE). l’adresse β n’est pas connue. else if (uniteCourante == TANTQUE) { /* boucle "tant que" */ uniteCourante = uniteSuivante().. ici... repCode(aCompleter. genCode(SIFAUX). est de r´aliser qu’au moment o` le compilateur doit produire l’instruction e u SIFAUX β.. expression(). dans l’analyseur a u montr´ plus haut pour lui faire produire le code suivant : e . e expr2 .. exprn SIFAUX β instr1 instr2 .α expr1 expr2 . expr1 d´but de l’instruction si. Dans le mˆme ordre d’id´es. et o`... aCompleter = TC. genCode(0).alors.. alpha = TC. elle serait consid´rablement e e plus complexe si le code ´tait produit dans un fichier) : e void repCode(int place.alors.. instrk β . int valeur) { mem[place] = valeur.. aCompleter... . voici le code ` produire dans le cas de l’inse e a truction conditionnelle ` une branche ..

.alors. w sont les seules variables globales de ce programme) : entier u... On note alors1 .sinon. w / 25 .distance(v... e d´but de la premi`re branche e e β d´but de la deuxi`me branche e e γ 5.. .. x = a .) e Appel de fonction Pour finir cette galerie d’exemples. . sinon2 . alorsk SAUT γ sinon1 sinon2 . entier b) { entier x. v. retour x.sinon. } Voici le segment du code de la fonction principale qui est la traduction de l’affectation pr´c´dente : e e 65 .........x. v.3..Et voici le code ` produire pour l’instruction conditionnelle ` deux branches.3 la suite (apr`s l’instruction si. sinonm ceux e produits par la compilation de la deuxi`me branche : e expr1 expr2 .... d´but de l’instruction si. . a a alorsk les codes-machine produits par la compilation de la premi`re branche et sinon1 .100) * 2.alors. examinons quel doit ˆtre le code produit ` l’occasion de l’appel d’une e a fonction. . aussi bien du cˆt´ de la fonction appelante que de celui de la fonction appel´e... sinonm . alors2 .. w.b.... exprn SIFAUX β alors1 alors2 .. entier principale() { ... si x < 0 alors x = .. } et supposons que l’on a d’autre part le code (u. oe e Supposons que l’on a d’une part compil´ la fonction suivante : e entier distance(entier a. u := 200 ..

Compilateurs. 1990. ENTREE PILE 1 EMPL -4 EMPL -3 SUB DEPL 0 EMPL 0 EMPC 0 INF SIFAUX β EMPC 0 EMPL 0 SUB DEPL 0 EMPL 0 DEPL -5 SORTIE RETOUR ... Principes.. O’Reilly & Associates. Cambridge University Press. Inc. Modern Compiler Implementation in C. The Theory of Parsing. 1991.. Lex & yacc.. Ravi Sethi. . 1972. [2] Alfred Aho and Jeffrey Ullman. [4] John Levine. and Doug Brown. Prentice Hall Inc. EMPC 200 PILE 1 EMPG 1 EMPG 2 EMPC 25 DIV EMPC 100 SUB APPEL α PILE -2 EMPC 2 MUL SUB DEPG 0 ... [3] Andrew Appel. and Jeffrey Ullman. 66 .2.. Dunod.4) : α allocation de l’espace local (une variable) debut de l’instruction si ] ] simulation du – unaire ] β le r´sultat de la fonction e R´f´rences ee [1] Alfred Aho. Translating and Compiling. Paris.. Tony Mason.. 1998. emplacement pour le r´sultat de la fonction e premier argument ceci ach`ve le calcul du second argument e on vire les arguments (ensuite le r´sultat de la fonction est au sommet) e ceci ach`ve le calcul du membre droit de l’affectation e et voici le code de la fonction distance (voyez la section 5. techniques et outils.

e e e a e ADDition. puis fait la mˆme chose que SAUT. D´pile deux valeurs et empile le r´sultat de leur soustraction. puis une valeur i. e D´pile deux valeurs et empile 1 si elles sont ´gales. 0 sinon. e e INFerieur. INFerieur ou EGal. Empile la valeur indiqu´e. L’ex´cution continue par l’instruction ayant l’adresse ine diqu´e. Appel de sous-programme. e Saut conditionnel. l’ex´cution contie e nue par l’instruction ayant l’adresse indiqu´e. Extrait la valeur qui est au sommet de la pile et l’affiche Saut inconditionnel. DEPiler dans un ´l´ment de Tableau.Tab. Empile la valeur courante de BEL. e Comme ci-dessus. Copie la valeur de BEL dans SP. D´pile deux valeurs et empile le reste de leur division euclidienne. e e e l’ex´cution continue normalement. Empile la valeur de la variable d´termin´e par le d´placement (relatif ` BEG) donn´ par adresse. D´pile la valeur qui est au sommet et la e range dans la variable d´termin´e par le d´placement relatif ` BEL donn´ par e e e a e adresse (entier relatif). D´pile deux valeurs et empile le r´sultat de leur multiplication. Empile la valeur de la cellule qui se trouve i cases au-del` de la variable d´termin´e par le d´placement (relatif ` BEG) indiqu´ a e e e a e par adresse. ee e Ensuite range v dans la cellule qui se trouve i cases au-del` de la variable a d´termin´e par le d´placement (relatif ` BEG) indiqu´ par adresse. D´pile une valeur v. EMPiler la valeur d’une variable Globale. e MODulo. Empile la valeur de la variable d´termin´e par le d´placement relatif ` BEL donn´ par adresse (entier relae e e a e tif). D´pile une valeur et continue l’ex´cution par l’inse e truction dont c’est l’adresse. Entr´e dans un sous-programme. e e SOUStraction. e EMPiler la valeur d’une variable Locale. e Retour de sous-programme. D´pile deux valeurs et empile 1 si la premi`re est inf´rieure e e e ou ´gale ` la seconde. puis copie e la valeur de SP dans BEL. Ajoute nbreMots. 0 sinon. Allocation et restitution d’espace dans la pile. D´pile la valeur qui est au sommet ee e de la pile. soit i cette valeur. e DEPL adresse EMPG DEPG adresse adresse EMPT adresse DEPT adresse ADD SOUS MUL DIV MOD EGAL INF INFEG NON LIRE ECRIV SAUT SIVRAI adresse adresse SIFAUX APPEL RETOUR ENTREE SORTIE PILE STOP adresse adresse nbreMots 67 . 0 sinon. D´pile deux valeurs et empile le quotient de leur division euclidienne. DEPiler dans une variable Locale. e e e a e DEPiler dans une variable Globale. 0 sinon. D´pile une valeur et si elle est non nulle. ` SP e a La machine s’arrˆte. Empile l’adresse de l’instruction suivante. D´pile deux valeurs et empile le r´sultat de leur addition. e e DIVision. EMPiler la valeur d’un ´l´ment de Tableau. puis d´pile une e valeur et la range dans BEL. Si la valeur d´pil´e est nulle. D´pile la valeur qui est au sommet et la e range dans la variable d´termin´e par le d´placement (relatif ` BEG) donn´ e e e a e par adresse. qui est un entier positif ou n´gatif. e Obtient de l’utilisateur un nombre et l’empile ECRIre Valeur. 1 – Les instructions de la machine Mach 1 opcode EMPC EMPL op´rande e valeur adresse explication EMPiler Constante. en permutant nul et non nul. D´pile deux valeurs et empile 1 si la premi`re est inf´rieure ` la e e e a seconde. e a D´pile une valeur et empile 1 si elle est nulle. e e MULtiplication. Sortie d’un sous-programme.

Sign up to vote on this title
UsefulNot useful