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

.. 1 pos . 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).... les expressions inutiles 4 .. tmp2) tmp3 optimisation du code tmp1 <. Par exemple e – d´tecter l’inutilit´ de recalculer des expressions dont la valeur est d´j` connue. R0 #60. et supprimer. 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.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 .0) id(1) <. 60.. 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....mulRéel(id(3). R0 R0. id1 Fig. tmp1) addRéel(id(2).addRéel(id(2). 2 posInit . tmp1) génération du code final MOVF MULF ADDF MOVF id3. .0. 3 vit . R0 id2.

de chiffres et du caract`re ’ ’. etc. ´ventuellement pr´c´d´e d’un signe . NOMBRE. C.etc. etc. – le mod`le d’un nombre est  une suite de chiffres. T }. qui sont les  mots  avec lesquels les phrases e ıt e sont form´es. {A. fins de ligne. Dans le texte source. l’ensemble des chiffres. Comme nous allons le voir. le lex`me et le mod`le co¨ e e e e e e ıncident. piles. les e a 4 Dans un analyseur lexical ´crit en C. if. D’ailleurs. while. 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. e e G´n´ration du code final Cette phase. TANTQUE. repr´sent´e g´n´ralement par un code conventionnel . "i" et "vitesse_du_vent" – ´ventuellement. "=". INFEGAL. l’ensemble de toutes les lettres. et a e e e notamment de ses possibilit´s en mati`re de registres. n’est pas forc´ment la plus e e difficile ` r´aliser. =.1 2. pour un nombre. e – le mod`le qui sert ` sp´cifier l’unit´ lexicale. etc. ` travers lesquelles le texte source est acquis. on peut concevoir des compilateurs dans lesquels la syntaxe est d´finie ` partir e a des caract`res individuels. NOMBRE. la gestion des fichiers et l’affichage des erreurs. vitesse_du_vent. -5. 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). IDENTIF. e a Remarque. La fronti`re entre l’analyse lexicale et l’analyse syntaxique n’est pas fixe. ces codes sont des pseudo-constantes introduites par des directives #define e 5 . respectivement4 : PLUS. qui est la chaˆ de caract`res correspondante. "<=". -5. qui d´pend de l’unit´ lexicale en question. 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. e PLUSPLUS. "++". i et vitesse_du_vent. commen¸ant par une lettre . Outre la reconnaissance des unit´s lexicales. Seules les e e e e derni`res des dix unit´s pr´c´dentes ont un attribut . etc. 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. 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. ce pourrait ˆtre.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’interface avec les fonctions de lecture de e e ee e caract`res. "while". −5) .) et celle des commentaires e e (g´n´ralement consid´r´s comme ayant la mˆme valeur qu’un blanc). un attribut. le code ASCII. IDENTIF. while. les lex`mes e ıne e e e e correspondants sont : "+".1. e e 2 Analyse lexicale L’analyse lexicale est la premi`re phase de la compilation. tabulations. "-5". etc. virtuelle ou abstraite). Exemples : {0. et les pr´sente ` la phase suivante. 123. On notera que les caract`res blancs (c’est-`-dire les espaces. "123". <=. ++. e – et les identificateurs : i. l’analyse lexicale reconnaˆ des unit´s lexicales. la plus impressionnante pour le n´ophyte. etc. e e e e e e e c – le mod`le d’un identificateur est  une suite de lettres. les analyseurs lexicaux assurent certaines tˆches mineures comme e a la suppression des caract`res de d´coration (blancs. Pour les dix exemples pr´c´dents. etc. 1}. etc. e – les constantes litt´rales : 123. e e – les caract`res sp´ciaux doubles : <=. 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 : +. il s’agit de sa valeur (123. l’analyse syntaxique. – le lex`me. et qui la compl`te. qui se pr´sente comme un e e flot de caract`res. e e – les mots-cl´s : if. 2. G. Elle n´cessite la connaissance de la machine cible (r´elle. 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 ++. Nous verrons ci-apr`s des moyens formels pour d´finir rigoue a e e e e reusement les mod`les . ıtre Simplicit´ et efficacit´ sont les raisons d’ˆtre des analyseurs lexicaux. l’analyse e lexicale n’est pas une obligation. =. e e e e pour un identificateur. SI. EGAL. "if". pour nos dix exemples +. etc.

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

´ ´ ` 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

/* if (c == ’=’) return INFEG. return NEANT. } int estChiffre(char c) { return ’0’ <= c && c <= ’9’. c = lireCar(). } } else if (estLettre(c)) { lonLex = 0. /* else { delireCar(c). /* else if (c == ’>’) return DIFF. if (c == ’<’) { c = lireCar(). On peut augmenter l’efficacit´ de ces fonctions. /* return SUP. /* 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. dont voici une version simple : e e int estBlanc(char c) { return c == ’ ’ || c == ’\t’ || c == ’\n’. } } else if (c == ’=’) return EGAL.while (estBlanc(c)) c = lireCar(). } int estLettre(char c) { return ’A’ <= c && c <= ’Z’ || ’a’ <= c && c <= ’z’. /* return IDENTIF. } delireCar(c). } Note. /* return INF. 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 . /* if (c == ’=’) return SUPEG. c = lireCar(). au d´triment de leur s´curit´ d’utilisation. /* lexeme[lonLex++] = c. /* else if (c == ’>’) { c = lireCar(). /* else { delireCar(c). while (estLettre(c) || estChiffre(c)) { lexeme[lonLex++] = c. } else { delireCar(c).

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

Si on en reparle ici c’est qu’on peut en d´duire un autre style d’analyseur lexical. #define NBR_CARS 256 int transit[NBR_ETATS][NBR_CARS]. .3 Automates finis Un automate fini est d´fini par la donn´e de e e – un ensemble fini d’´tats E. while (estLettre(c) || estChiffre(c)) { lexeme[lonLex++] = c. pourra se faire par une table ` double entr´e. return IDENTIF. assez diff´rent de ce e e que nous avons appel´ analyseur programm´  en dur . 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.2. ck . . } delireCar(c). . c = lireCar()... 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 . pour programmer un analyseur il suffira maintenant d’impl´menter la fonction transit ce e qui. 12 .2. /* etat = 10 */ lexeme[lonLex] = ’\0’. Voici ce programme : a e #define NBR_ETATS .1 (voir la e a figure 2). issue de e1 et aboutissant ` e2 . e a e Autrement dit. i++) if (strcmp(lexeme. . appel´s ´tats d’acceptation ou ´tats finaux. e – une fonction de transition. compos´ de k fl`ches ´tiquet´es e a e e e e e par les caract`res c1 . c) = e2 alors le graphe a une fl`che ´tiquet´e par le caract`re c.c = lireCar(). e – un ensemble fini de symboles (ou alphabet) d’entr´e Σ. transit : E × Σ → E. 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 . 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. appel´ ´tat initial. i < nbMotRes. } . – un ´tat ε0 distingu´. motRes[i]. c2 . e e e e a Un tel graphe est exactement ce que nous avons appel´ diagramme de transition ` la section 2. . e e ee – un ensemble d’´tats F . for (i = 0. puisqu’elle est d´finie sur des ensembles finis... e e On dit qu’un automate fini accepte une chaˆ d’entr´e s = c1 c2 .uniteLexicale. 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.lexeme) == 0) return motRes[i]. 2.

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

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

cette partie se compose de d´clarations qui seront simplement recopi´es au d´but du e e e e fichier produit. 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. il vaut mieux ne pas chercher ` y faire r´f´rence dans d’autres e e a ee fichiers. Exemples : e if then else . 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. e a ´ ´ ` Definitions regulieres. dans la fonction yylex. } [A-Za-z] [0-9] {lettre}|{chiffre} ` Regles. car il n’est pas sp´cifi´ laquelle des d´clarations  extern char *yytext  ou  extern char yytext[]  est pertinente. Exemples : lettre chiffre alphanum %% {lettre}{alphanum}* {chiffre}+(". e e e 19 La 18 C’est 15 . qui sera recopi´ tel quel. EGAL. etc. e variable yytext est d´clar´e dans le fichier produit par lex . Dans les exemples ci-dessus. Cette section se compose de fonctions C qui seront simplement e e recopi´es ` la fin du fichier produit. } { return IDENTIF."{chiffre}+)? expressionR´guli`re e e { return IDENTIF. e e Quand elle est pr´sente.. } { return SINON.). au bon endroit. la fonction yylex se termine en rendant comme ıne r´sultat l’unit´ lexicale reconnue. A la fin de la reconnaissance d’une unit´ lexicale la chaˆ accept´e est la valeur de la variable yytext. 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) . 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 . 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 . de e ıne e type chaˆ de caract`res19 . INF. leur signification e est claire : quand une chaˆ du texte source est reconnue. il e e e e e e faut alors les encadrer par des accolades. Il faudra appeler de nouveau cette fonction pour que l’analyse du texte source e e reprenne. de plus.. la variable enti`re yylen ıne e e ıne e donne le nombre de ses caract`res.h  contenant les d´finitions des codes conventionnels des unit´s lexicales (INFEG. } 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 . Un caract`re nul indique la fin de cette chaˆ . En plus d’autres choses. Par exemple. action est un morceau de code u e e e e source C. on trouve souvent ici une directive #include qui produit l’inclusion du fichier  . {lettre}{alphanum}* { return SI.%% 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. } { return NOMBRE. } { return ALORS. les actions ´tant toutes de la forme  return unite . 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).

avec certaines modifications.1. par e a exemple : yyin = fopen(argv[1]. sinon. Pour ´viter des collisions entre ces e e e e codes ASCII et les unit´s lexicales nomm´es. en une erreur syntaxique. EGAL. Ou. e e a e e a Fichier unitesLexicales. !=.3. return IDENTIF. INFEG. faire et rendre et les op´rateurs e e e e doubles ==. Les unit´s lexicales correspondantes sont respectivement repr´sent´es par les constantes e e e conventionnelles IDENTIF. SI. yytext). tantque. ∗. ?. } Le texte  printf("%s". parfois utile. sont l´gitimes dans lex et y ont le mˆme sens. RENDRE. " " signifie un blanc. NOMBRE. ). "." signifie le caract`re . alors. Par exemple. |. 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 exemple. ıne e e e e Bien entendu. etc. 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.. Les expressions r´guli`res accept´es par lex sont une extension de celles d´finies e e e e a ` la section 2. +. signifie un caract`re quelconque. – l’expression [^caract`res] signifie : tout caract`re n’appartenant pas ` l’ensemble d´fini par [caract`res]. e e mais qui peut conduire ` des situations confuses. a Attention. 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). 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. [. En outre. yytext)  apparaˆ tr`s fr´quemment dans les actions. c’est-`-dire (. on peut sans inconv´nient encadrer par des guillemets un caract`re ou une chaˆ qui n’en e e ıne avaient pas besoin. "[a-z]" signifie la chaˆ [a-z]. 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. e e – une chaˆ accept´e au titre d’une expression r´guli`re n’est pas recopi´e sur la sortie standard.(+|-)?[0-9]+ { yylval = atoi(yytext).h. TANTQUE. 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. avant le premier appel de yylex . ] et − ` l’int´rieur e e e e a a e des crochets. DIFF.2 Un exemple complet { printf("%s". on donne ` ces derni`res des valeurs sup´rieures ` 255. diff´rent de la marque de fin de ligne. que nous transformons l’erreur  caract`re e e e e ill´gal . } { ECHO. les mots r´serv´s si. ` priori lexicale. e a 16 . e e e trouvent soient interpr´t´s comme tels. "r") . on dispose de ceci (liste non exhaustive) : e e – un point . } ´ ` Expressions regulieres. 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. a ´ Echo du texte analyse. ıne D’autre part. Les m´ta-caract`res pr´c´demment introduits. return IDENTIF. SUPEG. return NOMBRE. Il faut ˆtre tr`s soigneux en ´crivant les d´finitions et les r`gles dans le fichier source lex. c’est-`-dire l’analyseur syntaxique. d´finies dans le fichier unitesLexicales. sur la sortie standard. 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. L’analyseur lexical produit par lex prend son texte source sur l’entr´e standard20 e et l’´crit. et qu’elle est repr´sent´e par son propre code ASCII21 . 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. En e e e e e effet. pour s´parer les op´rateurs a e e pr´vus par le langage des caract`res sp´ciaux qui n’en sont pas. <= et >=. (et non pas un caract`re ee e e quelconque). 21 Cela veut dire qu’on s’en remet au  client  de l’analyseur lexical. dit autrement. C’est un comportement voulu.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. FAIRE. SINON.

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

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

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

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

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

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.. . e ea e La r´cursivit´ ` gauche ne rend pas une grammaire ambigu¨. e e e e a ´ ` Grammaires recursives a gauche. 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. o` α est une chaˆ quelconque. . mais empˆche l’´criture d’analyseurs pour cette e ea e e e grammaire. Par exemple. Cas particulier. Une grammaire est ambigu¨ s’il existe plusieurs d´rivations gauches diff´rentes e e e pour une mˆme chaˆ de terminaux. Une production A → . Il est souvent possible de e e remplacer une grammaire ambigu¨ par une grammaire non ambigu¨ ´quivalente. la grammaire G1 de la section 3. Bonjour la r´cursion infinie.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. 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. e ıne ¨ Grammaires ambigues. 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. cela consiste ` remplacer une production telle que e e ea a 24 La question est un peu pr´matur´e ici. Dans le cas de la r´cursivit´ ` gauche simple. mais nous verrons qu’un analyseur descendant est un programme compos´ de fonctions e e e directement d´duites des productions. la figure 6 montre deux arbres de d´rivation distincts pour la chaˆ "2 * 3 + 10". du moins des analyseurs descendants24 . 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. la grammaire G1 est non ambigu¨ et ´quivalente ` la grammaire G2 ci-dessus. Une grammaire est r´cursive ` gauche s’il existe un non-terminal A e a ∗ et une d´rivation de la forme A ⇒ Aα. e 22 ..1. mais il n’y a pas une m´thode e ee e g´n´rale pour cela. 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.1 est r´cursive ` gauche. 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α.

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. la chaˆ d’entr´e commence par le terminal sinon. si. 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. La figure 7 montre l’arbre de d´rivation obtenu pour la chaˆ pr´c´dente. . ou ε-productions. Nous cherchons ` ´crire des analyseurs pr´dictifs. et on ne peut pas choisir ` coup sˆr entre αβ1 et αβ2 . e Dans l’exemple pr´c´dent.} 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 . alors la production  fin instr si → sinon instr  doit ˆtre appliqu´e. une ε-production ne peut ˆtre choisie que lorsqu’aucune autre production n’est applicable. La transformation de grammaire montr´e ci-dessus a introduit des produce tions avec un membre droit vide. e ` Factorisation a gauche. 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. βα. . Si on ne prend pas de disposition particuli`re. 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 α. . ainsi. e e e ıne e e 25 Pour se convaincre de l’´quivalence de ces deux grammaires il suffit de s’apercevoir que. notre grammaire semble devenir ambigu¨. βαα. e alors elles engendrent toutes deux le langage {β. 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.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. e a u Une transformation simple. on aura un e probl`me pour l’´criture d’un analyseur. l’apparition d’une ε-production semble rendre ambigu¨ la grammaire. appel´e factorisation ` gauche. Plus pr´cis´ment. si α et β sont des symboles terminaux. 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. sans risque d’erreur. Autrement dit. 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ˆ . au moment o` l’analyseur doit d´river le nonu e ıne e terminal fin instr 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. ce e ıtre a ıtre qui est possible quelle que soit la chaˆ d’entr´e . 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. 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.

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

e a Initialisation.2. A titre d’exemple. 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´. le plus ` gauche) est un terminal α a a – si le terminal visible ` la fenˆtre est le mˆme symbole α. cette  machine ` symboles terminaux  ne sera a rien d’autre que l’analyseur lexical pr´alablement ´crit . . ıne e ´ Iteration. et l’op´ration  faire avancer le ruban  se traduira par uniteCourante = uniteSuivante() e (ou bien.1. Sk .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). r´p´ter les op´rations suivantes e e e – si le symbole au sommet de la pile (c. le symbole visible ` la fenˆtre sera repr´sent´ par une e e a e e e variable uniteCourante. Sk ` la place. 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). voici la reconnaissance par un tel analyseur du texte "60 * vitesse + 200" avec la grammaire G3 de la section 3. uniteCourante = yylex()).Lorsque nous programmerons effectivement des analyseurs. . 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. . alors d’apr`s le terminal e visible ` la fenˆtre.1 Principe Analyseur descendant.-`-d. . – si le symbole au sommet de la pile est un non terminal S e – s’il y a une seule production S → S1 S2 . . signaler une erreur (par exemple afficher  α attendu ) . . afficher  caract`res inattendus ` la suite d’un texte e a correct ). Les ´tats successifs de la pile et de la ıne e e fenˆtre sont les suivants : e 25 . signaler une erreur (par exemple. choisir l’unique production S → a e S1 S2 . en l’occurrence l’extr´mit´ de gauche e e e e e e (c’est une pile couch´e ` l’horizontale. . qui se remplit de la droite vers la gauche). e ıne e – sinon. sans faire avancer ce dernier. a – s’il y a plusieurs productions ayant S pour membre gauche. si c’est lex qui a ´crit l’analyseur lexical. alors a e e – d´piler le symbole au sommet de la pile et e – faire avancer le terminal visible ` la fenˆtre. . e 3. Sk ayant S pour membre gauche alors d´piler S et empiler S1 S2 . 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. Au d´part. d´piler S et empiler S1 S2 . Sk pouvant convenir. Tant que la pile n’est pas vide. 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. a e – sinon. e Terminaison.

comme expliqu´ ci-apr`s. Une s´quence de symboles S1 S2 . α).2. ` l’aide d’une table ` double entr´e. e a En d´finitive. Le corps de cette fonction se d´duit des membres droits e des productions en question. e a e a e 3. 26 . la marque de fin de chaˆ e ıne. La chaˆ donn´e appartient donc ıne e bien au langage consid´r´. 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. Voici les principes de l’´criture d’un e ea e e tel analyseur : e a 1. 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. on peut le faire.2 Analyseur descendant non r´cursif e Nous ne d´velopperons pas cette voie ici. ee 3. Lorsque plusieurs productions ont le mˆme membre gauche. appel´e table a e e e a a e e d’analyse. . le symbole visible ` la fenˆtre n’est pas modifi´. ou plus simplement void S(void). . Dans cette s´lection. la fenˆtre exhibe ¶.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.. une pile de symboles comme expliqu´ ` la section pr´c´dente. Sn dans le membre droit d’une production donne lieu. Comme ce choix ne d´pend que du terminal e visible ` la fenˆtre. d’apr`s le symbole terminal e visible ` la fenˆtre. .3 Analyse par descente r´cursive e A l’oppos´ du pr´c´dent. dans e e e lequel la partie  choisir la production. 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).  se r´sume ` une consultation de la table P = Choix(S.  recona e naissance de S2 . calcul´e ` l’avance. e e e 2. dans la fonction e correspondante. repr´sentant une fonction Choix : VN × VT → P qui ` un couple (S..2. . e a e e 3. et de mani`re tr`s efficace. 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. ` une s´quence d’instructions traduisant les actions  reconnaissance de S1 . s´lectionne l’ex´cution des actions correspondant au membre droit de la production a e e e pertinente. 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. α) 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. reconnaissance de Sn . 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. . le corps de la fonction correspondante est une conditionnelle (instruction if ) ou un aiguillage (instruction switch) qui. Chaque groupe de productions ayant le mˆme membre gauche S donne lieu ` une fonction void reconnaitre S(void).

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

uniteVoulue)... case IDENTIFICATEUR: erreur("identificateur attendu"). } } La reconnaissance d’un terminal revient fr´quemment dans un analyseur. terminal(’)’).. notamment.else if (uniteCourante == IDENTIFICATEUR) terminal(IDENTIFICATEUR). terme().. . 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.. } . la fonction fin expression commence par les deux lignes if (uniteCourante == ’+’) { terminal(’+’). fin_expression(). e e e e a Par exemple. expression(). . void facteur(void) { if (uniteCourante == NOMBRE) lireUnite(). ne serait-ce pour rattraper certaines maladresses de notre d´marche. est ` l’origine de certains test redondants. L’appel g´n´ralis´ de la fonction terminal. else { 28 . } } Note. 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(). obtenant ainsi un analyseur correct dont la structure refl`te la grammaire donn´e. else switch (uniteVoulue) { case NOMBRE: erreur("nombre attendu"). else { terminal(’(’). Une version plus raisonnable des fonctions fin expression et facteur serait donc : void fin_expression(void) { if (uniteCourante == ’+’) { lireUnite(). else if (uniteCourante == IDENTIFICATEUR) lireUnite(). si on d´veloppe l’appel de terminal. Mais il n’est pas e e interdit de pratiquer ensuite certaines simplifications. la maladresse de la chose devient ´vidente e e if (uniteCourante == ’+’) { if (uniteCourante == ’+’) lireUnite(). default: erreur("%c attendu".. } else /* rien */.

else if (uniteCourante == IDENTIFICATEUR) lireUnite(). cette pile est un tableau couch´ ` l’horizontale . la fonction facteur aura tendance ` faire passer toutes les erreurs par le e a diagnostic  ’(’ attendu . expression(). Une version encore plus raisonnable de cette a fonction serait void facteur(void) { if (uniteCourante == NOMBRE) lireUnite().1 Analyseurs ascendants Principe Comme nous l’avons dit. ce qui risque de manquer d’` propos. S0 .1. ´tant donn´es une grammaire G = {VT . terminal(’)’). Lorsque dans la pile il n’y a e e plus que le symbole de d´part de la grammaire.3. } 3. avec la grammaire G1 de la section 3. Les m´thodes ascendantes. a e e 29 .terminal(’(’). partent des terminaux qui constituent la chaˆ ıne e ıne d’entr´e et  vont vers  le symbole de d´part. 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. l’analyse e ıne e ee a r´ussi. 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). identificateur ou ’(’ attendus ici"). 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. } } Comme elle est ´crite ci-dessus. si tous les symboles de la chaˆ d’entr´e ont ´t´ lus.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. } else erreur("nombre. P } et une chaˆ w ∈ VT ∗ . tant que les e e a symboles au sommet de la pile ne sont pas le membre droit d’une production de la grammaire. terminal(’)’). mais cette fois elle grandit de la gauche vers la droite. else if (uniteCourante == ’(’) { lireUnite(’(’). ou bien continuer ` d´caler. faut-il r´duire tout de suite. le e e ıne but de l’analyse syntaxique est la construction d’un arbre de d´rivation qui prouve w ∈ L(G). afin de permettre ult´rieurement une r´duction plus e a e e e juste ? A titre d’exemple. au contraire. expression(). VN . e e ea c’est-`-dire que son sommet est son extr´mit´ droite.3 3. c’est-`-dire la e a chaˆ de terminaux (nombre "*" identificateur "+" nombre) : ıne 29 Comme pr´c´demment. 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.

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 . VN . e) e e a a o` s ∈ E et e ∈ VT et de deux tables Action et Suivant.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. 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. ind´pendant du langage analys´. de construire des analyseurs ascendants plus efficaces que les analyseurs e descendants. α) = (decaler. d’une fenˆtre ` symboles terminaux (c’est-`-dire un analyseur lexical). le r´ductions faites dans l’analyse pr´c´dente construisent. e Les analyseurs LR(k) lisent la chaˆ d’entr´e de la gauche vers la droite (d’o` le L). Le principe est : les r´ductions pratiqu´es r´alisent la construction inverse d’une d´rivation droite. qui ex´cute les op´rations e e e e suivantes : Initialisation. qui repr´sentent des fonctions : u e Action : E × VT → ({ decaler } × E) ∪ ({reduire} × P ) ∪ { succes. Heureusement.4 pr´sente yacc. le point important est le choix entre r´duction et d´calage. P ). la section 3. lorsqu’on e u ıne e dit simplement LR on sous-entend k = 1. ` partir de la a a grammaire du langage . 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. c’est le cas le plus fr´quent. e Etant donn´e une grammaire G = (VT . e ıne e It´ration. 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. malgr´ les apparences. ` l’envers. d’une pile de doublets (s.3. s ) empiler (α. un des plus connus de ces outils. et acceptant une classe de langages plus large que la classe des langages trait´s par ces derniers. s ) placer la fenˆtre sur le prochain symbole de la chaˆ d’entree e ıne 30 . Placer la fenˆtre sur le premier symbole de la chaˆ d’entr´e et vider la pile. e e e e Par exemple. erreur } Suivant : E × VN → E Un analyseur LR comporte enfin un programme. des outils existent pour les construire automatiquement. un analyseur LR est constitu´ par la donn´e d’un ensemble e e e d’´tats E.2 Analyse LR(k) Il est possible. Tant que c’est possible. Comme le montre le e e e tableau ci-dessus. chaque fois qu’une r´duction est e e e possible. S0 .

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

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  . Pour cette raison.y .3.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. Si e ee e e on ´crit un analyseur  pur .2) est un morceau de code source C encadr´ par des accolades. e e – tous les autres identificateurs apparaissant dans les productions sont consid`res comme des symboles non e terminaux. ces constantes sont num´rot´es ` partir de ee e e a 256. qui les utilise. Dans un fichier source yacc : – les caract`res simples.3. terme : terme ’*’ facteur | facteur . 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. C’est e e un code que l’analyseur ex´cutera lorsque la production correspondante aura ´t´ utilis´e dans une r´duction.2) . e 32 .y .c  et  . 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.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. . La e e barre verticale  |  a la mˆme signification que dans la notation des grammaires. 31 Du 32 Dans (G1 ) moins si on le lance avec l’option -d. Une r`gle de traduction est un ensemble de productions ayant le mˆme membre e e gauche.. e Par exemple. encadr´s par des apostrophes comme dans les programmes C. facteur : nombre | identificateur | ’(’ expression ’)’ . ´ Specification de VT . VN et S0 . Chacune de ces deux parties e e a peut ˆtre absente. yacc produit31 un fichier suppl´mentaire.2). le cas de bison les noms des fichiers  . Par exemple.tab. ea e Une action s´mantique (cf. nomm´ y. c’est-`-dire un analyseur qui ne fait qu’accepter ou rejeter la chaˆ d’entr´e. e e e e ` Regles de traduction. respectivement au d´but et ` la fin de ce fichier. e e Dans les r`gles de traduction. sans actions s´mantiques) : e e %token nombre identificateur %% expression : expression ’+’ terme | terme .h  refl`tent le nom du fichier source  . et les identificateurs e e mentionn´s dans les d´clarations %token sont tenus pour des symboles terminaux.h32 . chacune associ´ ` une action s´mantique. e Dans la partie  d´clarations pour yacc  on rencontre souvent les d´clarations des unit´s lexicales.h dans e a e e l’exemple de la section 2. comme dans  yacc -d maSyntaxe. voici comment la grammaire G1 de la section 3. mais aussi lex.tab. le fichier produit pour les d´clarations ci-dessus ressemble ` ceci : e a #define #define #define . e e e destin´ ` ˆtre inclus dans le source lex (au lieu du fichier que nous avons appel´ unitesLexicales. qui les manipule en tant que e e e r´sultats de la fonction yylex. pour cette raison. – par d´faut. section 3.1. le symbole de d´part est le membre gauche de la premi`re r`gle..tab.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.

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

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

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

D´caler ou r´duire ? ( shift/reduce conflict ).. Les conflits possibles sont : 1. expression ’+’ expression ’-’ expression ’*’ expression ’/’ ’(’ expression nombre expression expression expression expression ’)’ { { { { { $$ $$ $$ $$ $$ = = = = = $1 + $3. Ce qu’il d´tecte en r´alit´ n’est pas un conflit. 2.. 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. } } } } } %% . 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. 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´. Sans rentrer dans les d´tails. 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 γ.expression : | | | | | . car les deux actions sont possibles et n’am`nent pas l’analyse ` une a e e e a impasse. on pr´f`re e e u e e ee la premi`re production. mais un analyseur. la r`gle expression. o 36 . c’est donc plutˆt un aveu d’impuissance. acces tableau → identificateur ’(’ liste expressions ’)’ . $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). yacc applique une r`gle de r´solution par d´faut et continue son travail . La grammaire pr´c´dente. 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.1. 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). e e e v´rifie ceci : e – aucune r`gle ne contient d’ε-production. cela ne r´sout pas souvent bien le probl`me. Nous avons vu ` la section 3... 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. Par exemple. e e 37 Le probl`me consistant ` choisir assez tˆt entre appel de fonction et acc`s ` un tableau. ou du moins sa partie utile. e – aucune r`gle ne contient une production ayant deux non-terminaux cons´cutifs. appel de fonction → identificateur ’(’ liste expressions ’)’ . il indique le nombre total de conflits rencontr´s et arbitrairement r´solus. $2.3 que cette grammaire est ambigu¨ . du moins sur certains textes sources. $1 . elle provoquera donc des conflits. lorsque les notations sont les mˆmes. $1 / $3.$3.. Lorsqu’il a e rencontre un conflit36 . 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 .... 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 36 N’oubliez pas que yacc ne fait pas une analyse. Comme l’exemple ci-dessus le montre37 . ` la fin de ce e e e a dernier... e e e ´ Grammaires d’operateurs. mais la e e e possibilit´ que l’analyseur produit en ait ult´rieurement.

} e expression ’+’ expression ’-’ expression ’*’ expression ’/’ ’(’ expression nombre expression expression expression expression ’)’ { { { { { $$ $$ $$ $$ $$ = = = = = $1 + $3. e e – contrˆler les types des op´randes des op´rations arithm´tiques et en d´duire le type du r´sultat. %} %token nombre %left ’+’ ’-’ %left ’*’ ’/’ %% session expression : | . $2). e e e En yacc. ` droite] c’est dire que a ⊗ b ⊗ c se lit (a ⊗ b) ⊗ c [resp. On a ajout´ des d´clarations e e e e e e e e indiquant que +.. cela se fait par des d´clarations %left et %right qui sp´cifient le sens d’associativit´ des op´rateurs. ∗ et / sont associatifs ` gauche38 et que la priorit´ de ∗ et / est sup´rieure ` celle de + et a e e a −. a ⊗ (b ⊗ c)]. n’est plus ambigu¨. ins´rer dans les expressions les conversion impos´es par certaines r`gles de compatibilit´. e ee e e ee e e e – v´rifier que les param`tres des fonctions ont les types requis. 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 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. contient l’instruction  i = (200 + j) * 3. e e e – traiter les d´clarations de variables et fonctions et m´moriser les types qui leur sont appliqu´s. session expression ’=’ { printf("r´sultat: %d\n". } } } } } %% . 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. %{ void yyerror(char *). voici une situation typique o` le contrˆle de type joue. pr´sent´e comme ci-apr`s. mˆme pour des op´rateurs simples : on tient ` ce que 100 − 10 − 1 vaille 89 et non 91 ! e e a 37 . −. Pour fixer les id´es. L’analyseur syntaxique construit un arbre abstrait e repr´sentant cette expression. : | | | | | . e u o ´crit en C. $1 * $3. Imaginons qu’un programme. La e a a question est importante. $1 . e e e e – etc. e e Ainsi.14 . comme ceci (pour ˆtre tout ` fait corrects. 4 Analyse s´mantique e Apr`s l’analyse lexicale et l’analyse syntaxique. la grammaire pr´c´dente. Nous n’en ferons pas l’´tude d´taill´e ici. o e e e e e – au besoin. 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.De telles grammaires s’appellent des grammaires d’op´rateurs. $2. $1 / $3.. 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. lorsque le langage le permet. ` 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.$3.

celui dont le type est le  plus fort  (plus complexe. successivement : e – que le + est l’addition des entiers. plus ´tendu. des adresses de fonctions rendant des adresses d’entiers. et donc que le sous-arbre e chapeaut´ par le + repr´sente une valeur de type entier. seules les feuilles (ici i. 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´. dont le type exact reste ` pr´ciser.14) ont des types pr´cis39 . rendant concrets les op´rateurs et donnant a e un type pr´cis aux sous-arbres. – pendant la compilation des instructions.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. etc.14 ¨r ¨r 200 j Dans de tels arbres. 3. des fonctions rendant des adresses d’entiers. e 38 . Le travail s´mantique ` faire e e a e e a consiste `  remonter les types . l’auteur d’un compilateur doit se donner le moyen de repr´senter ces structures de complexit´ quelconque.= ¨ r ¨ r r ¨ i ∗ ¨rr ¨ + 3. en C. provoquant une conversion de type. nous n’´tudierons pas cet aspect des compilateurs dans le cadre de ce cours. e e – que le ∗ est la multiplication des flottants. des tableaux d’adresses de fonctions rendant des adresses d’entiers. puisqu’un op´rande est flottant40 . 200. Par e e e e a e exemple.) tire ` e e e e a lui l’autre op´rande. etc. j et 3. puisque les deux op´randes sont entiers. tandis que les nœuds e internes repr´sentent des op´rations abstraites. qu’il y a lieu de convertir e l’autre op´rande vers le type flottant. ıtre La principale difficult´ de ce travail est la complexit´ des structures ` construire et ` manipuler. L’analyse s´mantique de l’arbre pr´c´dent ee e e e e e permet d’en d´duire. depuis les feuilles vers la racine. – que l’affectation qui coiffe l’arbre tout entier consiste donc en l’affectation d’un flottant ` un entier. plus pr´cis. e e e 40 Cela s’appelle la  r`gle du plus fort . En effet. 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. construire des repr´sentations des types d´clar´s dans le proe e e e gramme. des adresses (ou pointeurs) d’entiers. 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. il en d´coule que l’arbre tout e e e entier repr´sente une valeur du type entier. e Supposons par exemple que i et j aient ´t´ d´clar´es de type entier. 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. 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. Cela peut aller aussi loin qu’on veut. e e Faute de temps.14 un flottant).14 addition enti`re e ¨r ¨r 200 j 4. e En d´finitive. en C on peut avoir des entiers. reconnaˆ les types des objets intervenant dans les expressions. et que le sous-arbre chapeaut´ par ∗ repr´sente un objet de type e e e flottant.

une union) dont la structure d´pend e e de la valeur du champ classe : – si la classe est celle d’un type primitif. tFonction. typedef struct listeDescripteursTypes { struct descripteurType *info. Ainsi. tDouble. Pr´sent´e comme cela. un type se trouve repr´sent´ par une structure ` deux champs : classe. tPointeur. e a Pour les curieux. la chose est tout ` fait possible. } casTableau.. union { struct { struct descripteurType *typePointe. qui donne les informations n´cessaires e pour achever de d´finir le type. } casFonction. dans quel langage fut ´crit le tout premier compilateur de C. le champ attributs est sans objet. } casStructure. de nos jours. e e L` o` elle redevient troublante : ce nouveau compilateur de C une fois ´crit. tInt. le compilateur que nous r´aliserons ` titre de projet ne traitera que les types entier et tableau d’entiers. dont la valeur est un code e e a conventionnel qui indique de quelle sorte de type il s’agit. on devra le valider. struct { int nombreElements. 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. quel meilleur test a u e que de lui donner ` compiler. typedef struct descripteurType { typePossible classe. tTableau. } attributs. e 41 Voici 39 .Ainsi. La question est plutˆt de savoir si. } descripteurType. tFloat. tLong. tShort. 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. struct descripteurType *typeElement. struct descripteurType *typeResultat. peu int´ressant. tStructure } typePossible. } listeDescripteursTypes. 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. 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.. Pour cela. de l’œuf e e et de la poule. on peut utiliser un compilateur (existant) de C pour r´aliser un o e compilateur (nouveau) de C. Il faut e a comprendre que la question n’est pas de savoir. struct listeDescripteursTypes *suivant. par exemple. son propre texte source. la chose est parfaitement raisonnable. 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. et attributs. struct { listeDescripteursTypes *typesChamps. Attributs est un champ polymorphe (en C. struct { listeDescripteursTypes *typesArguments. } casPointeur.

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

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

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

 erreur : identificateur d´j` d´clar´ ). e 43 .2. il est e e recherch´ ensuite dans le dictionnaire global (si les deux recherches ´chouent. A l’int´rieur d’une e fonction il n’augmente pas .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. 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. 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. 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.  erreur : identificateur non e e d´clar´ ). le dictionnaire global n’augmente que lorsque le dictionnaire local n’existe pas. 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. est a e cr´´ lorsque le compilateur entre dans une fonction. e e e – Lorsque le compilateur quitte une fonction. 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. 11 – Dictionnaires. vide. 4. e 2. i est ajout´ au dictionnaire local. normalement. il ne s’y trouve pas (sinon. 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. ee a a a – Lorsque le compilateur traite la d´claration d’un identificateur i en dehors de toute fonction. il ne s’y trouve pas (sinon. on doit avoir sommet ≤ maxDico. Les op´rations n´cessaires sont : e e e 1. s’il ne s’y trouve pas. la manipulation du dictionnaire devient tr`s simple. le dictionnaire local se trouve au-dessus du dictionnaire e global (en supposant que le tableau grandit du bas vers le haut). Dans ce tableau. le dictionnaire local en cours d’utilisation est d´truit. Suite ` cette d´claration. chaque e e a e identificateur i rencontr´ est recherch´ d’abord dans le dictionnaire local . forc´ment ` l’int´rieur d’une fonction. normalement.  erreur : e identificateur d´j` d´clar´ ). Un dictionnaire local nouveau. 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. qui est le seul dictionnaire existant en ce point .– Lorsque le compilateur traite la d´claration d’un identificateur i qui se trouve ` l’int´rieur d’une fonction. En proc´dant ainsi on assure le masquage des objets globaux par les objets locaux. le dictionnaire global quand il n’y en a qu’un). Avec tout cela. 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. i est recherch´ e e dans le dictionnaire global. voir  Augmentation de la taille du e a dictionnaire  un peu plus loin). – Lorsque le compilateur compile une instruction ex´cutable. Voyez la figure 11 : lorsqu’il existe.

sommet. if (dico == NULL) erreurFatale("Erreur interne (pas assez de m´moire)"). int type. Creation d’un dictionnaire local. a Augmentation de la taille du dictionnaire. 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.h> typedef struct { char *identif. le tableau est agrandi d’autant qu’il faut pour loger ee 25 nouveaux ´l´ments : ee #include <stdlib. void creerDico(void) { maxDico = TAILLE_INITIALE_DICO. La biblioth`que C offre un moyen pratique pour r´soudre ce probl`me. ´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. ` titre e e a d’exemple. Destruction du dictionnaire local. maxDico). 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. voici la fonction qui ajoute une entr´e au dictionnaire : e void ajouterEntree(char *identif. int complement) { if (sommet >= maxDico) 44 50 25 . base. dico = malloc(maxDico * sizeof(ENTREE_DICO)). message). dico = realloc(dico.h> #include <stdio. e sommet = base = 0. int adresse. int complement. int adresse. exit(-1). placer la nouvelle entr´e ` la position sommet. 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. } ENTREE_DICO. au moment de l’entr´e dans une fonction : faire base ← sommet. } void agrandirDico(void) { maxDico = maxDico + INCREMENT_TAILLE_DICO. Voici. int type. 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 . int maxDico. e e e e a puis faire sommet ← sommet + 1. e 5. #define TAILLE_INITIALE_DICO #define INCREMENT_TAILLE_DICO ENTREE_DICO *dico. "%s\n". chaque fois que la place manque.3. e } void erreurFatale(char *message) { fprintf(stderr. if (dico == NULL) erreurFatale("Erreur interne (pas assez de m´moire)"). } Pour montrer une utilisation de tout cela.h> #include <string.

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

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

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

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

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

l’adressage dispers´ ouvert apparaˆ comme une m´thode de partitionnement de l’ene e ıt e semble des identificateurs. Si la fonction h est bien ın´ faite. Vu de cette mani`re. while (*ident != ’\0’) r = X * r + *(ident++). Si on note T cette table. int r = 0. N Voici la fonction de recherche : typedef struct maillon { char *identif. autres informations struct maillon *suivant. #define N 101 MAILLON *table[N]. return r % N. N). 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. 50 . cela donne la fonction : e int hash(char *ident. for (p = table[hash(identif. } et voici la fonction qui se charge de l’insertion d’un identificateur (suppos´ absent) : e MAILLON *insertion(char *identif) { int h = hash(identif. En C. les compartiments ont ` peu pr`s la mˆme taille. p = p->suivant) if (strcmp(identif. la table qu’on adresse ` travers la fonction de hachage n’est pas une e a table d’identificateurs. 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| . return NULL.identificateur comme l’´criture d’un nombre dans une certaine base). int N) { const int X = 23. a u e e e e e Dans le cas de l’adressage dispers´ ouvert. 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. mais une table de listes chaˆ ees dont les maillons portent des identificateurs (voyez la ın´ figure 13). } fonction de hachage identif Ensemble des identificateurs table de hachage infos suivant /* why not? */ Fig. N)]. p->identif) == 0) return p. Chaque liste chaˆ ee est un compartiment de la partition. p != NULL. MAILLON *recherche(char *identif) { MAILLON *p. } MAILLON.

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. MAILLON *suivant) { MAILLON *r = malloc(sizeof(MAILLON)).) et en mˆme temps nous introduisons une machine virtuelle. Dans ce but. Les objets statiques existent pendant toute la dur´e de l’ex´cution d’un programme .d´calage). nous faisons l’impasse sur certains aspects importants (et difficiles) de la g´n´ration de code. } 5 Production de code Nous nous int´ressons ici ` la derni`re phase de notre compilateur. r->suivant = suivant. e 2. e 51 . Les fonctions et les constantes e e sont des objets statiques en lecture seule. 5. o` sont e u u les variables globales. c’est-`-dire de modification de valeurs. adresses. pour ce qui nous occupe ici. la production de code. sont d´tect´es et signal´es . L’acc`s t[i] au i-`me ´l´ment d’un tableau t en est un exemple typique. sa valeur et pour une variable globale. Il est g´n´ralement e e e e constitu´ de deux zones : la zone du code. souvent z´ro. et c’est un outil appel´ chargeur qui. installe les e e e objets statiques dans la m´moire. e Les objets statiques sont les fonctions. table[h]).1 5. e Faute de temps. la e e e e machine Mach 1. e On appelle espace statique l’espace m´moire dans lequel sont log´s les objets statiques. Mais. pour laquelle notre compilateur produit du code et dont nous ´crirons un simulateur. pile d’ex´cution. au lieu de cela. e Les objets statiques peuvent ˆtre en lecture seule ou en lecture-´criture 53 .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. if (r == NULL) erreurFatale("Erreur interne (probl`me d’espace)"). o` sont les fonctions et les constantes. cela revient au mˆme. pour une constante.1. if (r->identif == NULL) erreurFatale("Erreur interne (probl`me d’espace)"). on peut consid´rer e e e que l’espace qu’ils occupent est allou´ par le compilateur pendant la compilation52 . apr`s traitement du fichier objet par l’´diteur de lien. les objets en lecture seule sont log´s dans des zones de la m´moire dans lesquelles les tentatives e e e d’´criture. 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. Ils sont garnis de valeurs initiales : pour une fonction. 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. identif). les constantes et les variables globales. etc. 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). son code.return table[h] = nouveauMaillon(identif. e r->identif = malloc(strlen(identif) + 1). 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. Les objets automatiques sont les variables locales des fonctions. et l’espace global. e strcpy(r->identif. le compilateur n’alloue pas la m´moire pendant la compilation . dans lequel t (l’adresse de t[0]) e e ee est la base et i le 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. e e 53 Lorsque le syst`me le permet. Les variables globales sont des objets statiques en lecture-´criture. e a e nous pr´sentons certains aspects de l’architecture des machines (registres et m´moire. } avec une fonction nouveauMaillon d´finie comme ceci : e MAILLON *nouveauMaillon(char *identif. ce qui comprend : 52 En r´alit´. e e e compilation s´par´e et ´dition de liens. return r. e e et notamment sur les algorithmes pour l’optimisation du code et pour l’allocation des registres. il en produit une certaine repr´sene e e e tation dans le fichier objet.

directement ou indirectement).– les variables d´clar´es ` l’int´rieur des fonctions. la lutte contre l’´miettement de l’espace e e e disponible. op´rateur delete de C++) ces objets existent jusqu’` la terminaison du e e a programme55 . puisque leur e e u existence n’est pas certaine. 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. en anglais). comme Java. e Ces variables occupent un espace qui n’existe pas pendant toute la dur´e de l’ex´cution du programme. 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. On place donc les objets dynamiques dans un troisi`me espace. Lorsqu’une fonction se termine. etc. elle d´pend des conditions d’ex´cution. Plus pr´cis´ment. c’est-`-dire son allocation sous forme de morceaux de tailles variables.2. 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. appel´ espace local de la fonction. e sens d'empilement espace local de h espace local de f espace local de g espace local de f Fig. ` la demande du a a programme. 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. 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. e e e La gestion du tas. puisque cette pile grandit et diminue en accord avec les appels e e et retours des fonctions. 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. e En d´finitive. la r´cup´ration des morceaux rendus par ce dernier. 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.e. Lorsque l’activation d’une fonction se termine son espace local e est d´truit. alors que les moments de la cr´ation et de la destruction des objets dynamiques e ne sont pas connus a priori. 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. son espace local est cr´´ au-dessus des espaces locaux e ee des fonctions actives (i. 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++). Un espace local nouveau est allou´ chaque fois qu’une fonction est appel´e. commenc´es et non termin´es) ` ce moment-l`. Si leur destruction n’est pas explicitement e demand´e (fonction free de C. l’activation d’une fonction commence par l’allocation e e d’un espace. 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. 52 . o 3. distinct des e deux pr´c´dents. e e mais uniquement lorsqu’il est utile. appel´ le tas (heap. Ils ne peuvent pas davantage ˆtre e e e h´berg´s dans la pile des espaces locaux.4). e e a e – les arguments formels de ces derni`res. Les objets dynamiques ne peuvent pas ˆtre log´s dans les zones o` sont les objets statiques.

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 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. TC vaut 0. cette variable a constamment pour valeur le nombre d’unit´s (des octets.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. ces fonctions limitent la croissance du tas. des mots. a e Plus pr´cis´ment. 15 – Organisation de la m´moire e – – – – le code (espace statique en lecture seule). La pile et le tas. Ensuite. le compilateur utilise les trois vae e riables suivantes : e TC (Taille du Code) Pendant la compilation. e la pile (stack ). comme un opcode ou un op´rande. 5. mais elles ne sont pas cens´es en r´duire la taille. la production d’un ´l´ment de code. contenant variables locales. de sorte que si un e peu plus tard on cherche ` obtenir l’adresse d’un autre objet on obtiendra une valeur diff´rente. contenant les variables globales.tas (objets dynamiques) espace disponible pile (objets locaux) espace global code espace statique TEG TC Fig. contenant les variables allou´es dynamiquement. ´voluent au cours de l’ex´cution : le tas ne fait que e e e grossir56 . 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. contenant le programme et les constantes. pendant qu’il produit la traduction d’un programme. e Au d´but de la compilation. delete) est de bien g´rer les parcelles de m´moire emprunt´e puis o e e e e rendue par un programme . cela d´pend de la machine) du programme en langage machine couramment produites. e e 53 . le tas (heap). l’espace global (espace statique en lecture-´criture).1. e La rencontre de la pile et du tas (voyez la figure 15) est un accident mortel pour l’ex´cution du programme. 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. e Cela se produit lorsqu’un programme alloue trop d’objets dynamiques et/ou de taille trop importante. la pile grossit lors des appels de fonctions et diminue lors des terminaisons des fonctions appel´es. si memoire repr´sente l’espace (ou le fichier) dans lequel e e le code machine est m´moris´. se e e ee e traduit par les deux affectations : memoire[T C] ← element TC ← TC + 1 56 En effet. en revanche.

mais un outil qui lui e e e est associ´. e e Au d´but de la compilation TEG vaut 0. 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. 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. 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. Par la suite. Si le compilateur n’est pas dans une fonction TEL n’est pas d´fini. e e 57 Une cons´quence tangible de la disparition des identificateurs est l’impossibilit´ de  d´compiler  les fichiers objets. 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. 5. Par e ea la suite.4). par cons´quent. 54 . produit son introduction dans le dictionnaire ad´quat.Par cons´quent. tel peut ˆtre le cas dans les langages qui obligent ` mettre tout le e a programme dans un seul fichier. que ce soit le nom d’une variable locale. r´sultats de e e o e e compilations s´par´es. e e e Nous n’aurons donc pas besoin d’´diteur de liens dans notre syst`me. 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. le langage dont nous ´crirons le compilateur ne supportera pas la compilation s´par´e. ce qui est plus embˆtant. dans une organisation comme celle de la figure 15. ce qui e e e n’est pas grave.1. e e e Notez ´galement que le principal int´ress´ par cette affaire n’est pas le compilateur. ne risquent pas d’ˆtre visibles dans un autre fichier. 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.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´. pour m´moriser l’adresse d’une fonction fon il suffit de faire. 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. n’interviennent pas dans le calcul e de TEL (la question des adresses des arguments formels sera trait´e ` la section 5. e A l’entr´e de chaque fonction TEL est remis ` z´ro.2. le compilateur remplace chaque occurrence de i dans une expression par le nombre adri . Mais les choses sont plus compliqu´es dans les langages. 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. La d´claration d’un identificateur i. comme C ou Java. Les objets locaux. qui d´pend de son type. Faute de temps. l’´diteur de liens (ou linker ) dont le rˆle est de concat´ner plusieurs fichiers objets. En effet. dans une organisation comme celle de la figure 15. cette variable a pour valeur la somme des tailles des variables locales de la fonction en cours de compilation. a e e e a – TEG est la taille de l’espace global . d’une variable globale ou e e e d’une fonction. notons-la adri . afin de permettre aux d´bogueurs d’acc´der e e aux variables et fonctions par leurs noms originaux. 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. e Notez que cette question ne concerne que les objets globaux. pour en faire un unique programme ex´cutable. et en compl´tant de telles  r´f´rences e e e ee insatisfaites  par les adresses des objets correspondants. 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. de fait. qui ne sont d´j` pas visibles ea en dehors de la fonction dans laquelle ils sont d´clar´s. mais aussi la difficult´ de les d´boguer. u e e e TEL (Taille de l’Espace Local) A l’int´rieur d’une fonction. 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. 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). associ´ ` une adresse. bien qu’´tant des objets locaux.

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. 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. lorsque le langage machine le permet. et APPELR qui prend une r´f´rence e ee ee relative. 16 – R´f´rence absolue et r´f´rence relative ` une mˆme fonction A ee ee a e Par exemple. une ee a e ee e a r`gle suivie par les compilateurs. e e Appelons module un fichier produit par le compilateur. Le rˆle de l’´diteur de liens est de concat´ner (mettre o e e bout ` bout) plusieurs modules. dans le fichier qui sort de l’´diteur de liens. ´ ´ ´ ´ References absolues et references relatives. ae a Bien entendu. la figure 16 montre le cas d’un langage machine dans lequel il y a deux instructions pour appeler une fonction : APPELA. 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.Pour les curieux. Un probl`me apparaˆ imm´diatement : si on ne fait rien. 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. C’est facile ` voir : chaque module apporte une fonction d’adresse 0 et probablement e a une variable globale d’adresse 0. il concat`ne d’un cˆt´ les zones de code (objets statiques en lecture a e e e oe seule) de chaque module. qui sont exprim´es e ıt e e comme des d´placements relatifs ` une base propre ` chaque module. 55 . 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. les r´f´rences e e e ee relatives contenues dans ces modules n’ont pas ` ˆtre mises ` jour. ee Structure des modules. sauf pour le module e a a qui se retrouve en tˆte. 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. Dans l’ex´cutable final. qui prend comme op´rande une r´f´rence absolue. ee a e ee qui se trouve dans un autre module. une seule fonction et une seule variable globale peuvent e avoir l’adresse 0. est : produire des r´f´rences intra-modules e ee relatives et des r´f´rences inter-modules absolues. cela ne concerne que l’acc`s aux objets qui se trouvent dans le mˆme module que la r´f´rence . 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. En g´n´ral. afin d’obtenir une unique zone de code totale et un unique espace global total. 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). ` l’adresse 0. 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. vont devenir fausses. Ainsi. Lorsque l’´diteur de liens concat`ne des modules pour former un ex´cutable. e a a ee e base x fonction A fonction A y APPELA x APPELR y Fig. 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. les adresses des objets statiques. et d’un autre cot´ les espaces globaux (objets statiques en lecture ´criture) de chaque e e module.

.e. c’est-`-dire non publics. chaque identificateur est le nom d’un e objet que le module en question met ` la disposition des autres modules. e – ´mission du fichier ex´cutable final. comme X = Y + Z. au choix. fonction g . Inversement. cette formule signifiant  ajoutez le contenu de Y ` celui de Z et rangez e a le r´sultat dans X  (X. il faut la d´composer en des instructions plus e e simples. et il est associ´ ` l’adresse de a ea l’objet concern´ dans la section de code . ` savoir les r´f´rences ` des objets externes (i. form´ du code corrig´.2. ce code comporte un certain nombre de valeurs incorrectes. non d´finis dans ce a ee a e module) dont l’adresse n’est pas connue au moment de la compilation. e a 5. 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. – la table des objets publics 58 d´finis dans le module .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 . ??? objets publics . e e a 59 Cela se prononce. tous les objets globaux sont publics par d´faut. dans cette table.  m`que ouane  ou  machun . dans certains langages. il y a deux grandes familles de machines : e e e 58 Quels sont les objets publics. tout objet global est public. e 56 .. 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. dans cette table. Y et Z correspondent ` des emplacements dans la m´moire de l’ordinateur). un objet public est n´cessairement global... La nature de ces instructions plus simples d´pend du type de machine dont on dispose... Enfin. 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.. la e e e e machine cible du compilateur que nous r´aliserons ` titre de projet. e e e e 5. qu’il faudra corriger e e a ee lorsque l’adresse de l’objet sera connue. Relativement e a ` la mani`re dont les op´rations sont exprim´es. – la table des r´f´rences insatisfaites de la section de code . adresse dans la section de code) : – la section de code contient la traduction en langage machine d’un fichier source . 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. au contenu incorrect. 17 – Un module objet Par exemple. e section de code références insatisfaites .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. Dans ea e d’autres langages.. e a e Une telle expression est trop compliqu´e pour le processeur. chaque identificateur r´f´renc´ ee ee e mais non d´fini est associ´ ` l’adresse de l’´l´ment de code. e En r´sum´.. dans des langages comme le C. x Fig..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. fonction f . x y fonction f y .. APPELA . et une qualification e sp´ciale (dans le cas de C c’est la qualification static) permet d’en rendre certains priv´s. 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. e e a – d´calage des r´f´rences absolues contenues dans ces modules (dont les m´moris´es dans les tables)..

etc. par cons´quent.4).1. le code X = Y + Z se traduira donc ainsi (notant encore X. c’est-`-dire le nombre total de cellules occup´es dans la m´moire. organis´es comme le montre la figure e e e e 18 (c’est la structure de la figure 15. n’importe quel registre peut intervenir dans e a e e une op´ration arithm´tique ou autre . avec un compilae teur optimisateur. R2. 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 expressions complexes sont traduites par des suites d’instructions dont la plupart ne mentionnent que des registres. les instructions qui expriment ces op´rations doivent e e e e sp´cifier leurs op´randes. 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 . 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. Y et Z les adresses des variables X. Et. not´s ici R1. 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.R2 R2. 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 . au contraire. Y et Z) : MOVE MOVE ADD MOVE Y. d´cembre 2001. BEG pointe la variable globale d’adresse 0). 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. Les machines ` pile.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. Inversement.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.. le tas en moins).R1 Z. a e a e e Pour une telle machine. e e a 5. 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. e – l’op´rande d’un op´rateur unaire est la valeur au sommet de la pile . Les machines ` registres poss`dent un certain nombre de registres. Car il faut savoir que les machines  physiques  existantes sont presque toujours des machines ` registres.R2 R1.2. la machine pour laquelle nous g´n´rerons du code sera une machine ` 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. Si on vise une telle machine.2. disposent d’une pile (qui peut ˆtre la pile des variables locales) au a e sommet de laquelle se font toutes les op´rations. la valeur de BEL change lorsque l’activation d’une fonction commence ou finit (` ce e a propos voir la section 5. 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 . SP (Sommet de la Pile) indique constamment le sommet de la pile. a e e 60 Se rappeler que de nos jours. Les algorithmes d’optimisation et d’allocation des registres sortant du cadre de ce cours. Y et Z les adresses des variables X. 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. 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. l’affectation X = Y + Z devra ˆtre traduite en quelque e e e chose comme (notant X. e a 57 .

e e ee e puis effectue une instruction RETOUR. qui est celle d’une cellule. e 3. −4 ↔ (n − 1)-`me argument. o` sera d´pos´ le r´sultat de la fonction. La fonction appelante lib`re l’espace occup´ par les valeurs des arguments effectifs. Terminaison du travail : la fonction appel´e remet BEL et SP comme ils ´taient lorsqu’elle a ´t´ activ´e.2. elle occupe alors deux cellules cons´cutives. n ´tant le nombre d’arguments formels. a e 5. ` la fin de ce polycopi´. d’abord du point de vue de la fonction appelante (1). Au-del` de cet espace.3 Jeu d’instructions La machine Mach 1 est une machine ` pile. elle occupe alors une cellule. e e L’ensemble de toutes ces valeurs forme l’ espace local  de la fonction en cours. 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. 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. cette fois) relatifs ` e a e e a BEL : −3 ↔ n-`me argument. puis e u e e e elle empile les valeurs des arguments effectifs. 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. donne la liste des instructions. 1 ↔ deuxi`me variable locale. Les constantes enti`res et les  adresses  ont la mˆme e e e e taille. La table 1. puis alloue l’espace local. Ce d´placement est −(n + 3). prend la e e e valeur de SP pour BEL courant. d’abord du point de vue de la fonction appel´e (3) puis de celui de la fonction e e appelante (4). 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. ensuite le retour.espace disponible SP pile BEL espace global BEG CO code Fig. etc. La fonction appelante r´serve un mot vide sur la pile. La fonction appel´e empile le  BEL courant  (qui est en train de devenir  BEL pr´c´dent ). puis de celui de la fonction appel´e (2). la a pile est utilis´e pour le calcul des expressions courantes. 18 – Organisation de la m´moire de la machine Mach 1 e 5. elle ex´cute une instruction APPEL (qui empile e l’adresse de retour). e ee e e e e 58 .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. 2. a Chaque instruction est faite soit d’un seul opcode. Globalement. Ensuite. Voici ce qui se passe (voyez la figure 19) : 1.2. e e 4. etc. e – les arguments formels sont ´galement atteints ` travers des d´placements (n´gatifs. soit d’un opcode et un op´rande. Reprise du calcul interrompu par l’appel. 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.

ce n’est pas sur que ce soit un inconv´nient. et e le r´sultat de la fonction lui-mˆme poss`de l’adresse locale −(n + 3). par les adresses locales respectives −(n + 3) + 1. argn sont atteints. 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. 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. comme en Pascal ou Java. −(n + 3) + 2. 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. car ce sont les valeurs d’expressions e qui doivent ˆtre ´valu´es dans le contexte de la fonction appelante. loc. e e e a u e Mais il y a un petit inconv´nient61 . 1 BEL précédent adr. e ´ En g´n´ral les conventions d’appel ne sont pas laiss´es ` l’appr´ciation des auteurs de compilateurs. . il soit au sommet de la pile. 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. les argument arg1 .haut de la pile SP évaluation en cours var. dites conventions d’appel. qui est la seule ` en connaˆ le nombre . que la e fonction appelante et la fonction appel´e soient d’accord sur le nombre d’arguments de cette derni`re. ` e e a l’int´rieur de la fonction. tellement il semble naturel d’exiger. arg 1 résultat espace local de la fonction appelante bas de la pile BEL Fig. k . c’est e e a ıtre donc cette fonction qui doit les enlever de la pile... e e 59 .. Edict´es e e e a e e par les concepteurs du syst`me d’exploitation. au retour de la fonction appel´e.. Cela oblige la fonction appel´e ` connaˆ e e e e a ıtre 61 Et encore.. apr`s nettoyage des arguments. retour arg n . Il y a donc un ensemble de conventions. voir la figure 19). 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. arg2 . n´gatifs dans le cas des arguments. pourra appeler une fonction ´crite dans un autre langage L2 et e e compil´e par un autre compilateur. et compil´e donc par le compilateur de L1 . var. e e e – les variables locales sont allou´es par la fonction appel´e. −(n + 3) + n = −3. celui de L2 . Dans notre syst`me. . loc. Il est donc naturel de donner ` cette e e e a derni`re la responsabilit´ de les enlever de la pile.. e u e ce qui revient au mˆme... Les conventions d’appel. ` quel endroit la fonction appel´e trouvera ses arguments et o` doit-elle d´poser son e a e u e r´sultat. lorsqu’elle se termine (l’instruction SORTIE fait ce travail). – le r´sultat est d´pos´ l` o` il faut pour que. elles sont suivies par tous les compilateurs  homologu´s . une fonction ´crite dans un langage e e e e e e L1 .

5. b. finExpArith(). dans lequel une e occurrence d’un identificateur dans une expression indique toujours ` une variable simple. 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. ` travers des exemples. 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. voire frustr´. nous envisageons momentan´ment un langage ultra-simple. } } (les fonctions terme et finTerme sont analogues aux deux pr´c´dentes. int´ressons-nous ` l’expression 123 * x + c. sans tableaux ni appels de fonctions. et interdit l’´criture de fonctions admettant un nombre variable d’arguments e (de telles fonctions sont possibles en C. a 62 Plus 60 . c. 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. dans un deuxi`me temps. terme(). syntaxique. } Pour commencer. finExpArith(). entier uneFonction() { entier a. 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().). y = 123 * x + c. a e e priori cette ex´cution a lieu ult´rieurement. Si vous ne deviez retenir qu’une seule chose de ce cours. on ajoute –sans rien lui enlever– les op´rations qui produisent e e du code pour la machine M . e a e Ce principe est tout ` fait fondamental. la traduction des expressions arithm´tiques. .. imaginons que notre langage e ee source est une sorte de  C francis´ .le nombre n d’arguments effectifs. .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. a 5.. y.. z. A titre d’exemple. e e e On est alors surpris. que ce soit cela. et donc que c’est elle qui d´termine la structure g´n´rale de ce dernier. a 63 Attention. comment ajouter ` notre analyseur les op´rations a a e de g´n´ration de code qui en font un compilateur. est extrˆmement simple et ´l´gante. dans un deuxi`me temps. Dans cette section nous expliquons. aussi a e complexes soient-elles. s´mantique. avec ‘∗’ et ‘/’ dans les rˆles de ‘+’ et e e o ‘−’.. en constatant la place consid´rable que les ouvrages sp´cialis´s consacrent ` e e e e a l’analyse (lexicale. songez ` printf et scanf). pour comprendre pourquoi nous faisons que ce dernier produise ce qu’il produit.1 Expressions arithm´tiques e Puisque la machine Mach 1 est une machine ` pile. etc. mais nous devons l’imaginer en mˆme temps que nous ´crivons le e e e e compilateur. e e e e e qui produit comme r´sultat un programme P en langage machine. Enfin. } void finExpArith(void) { if (uniteCourante == ’+’ || uniteCourante == ’-’) { uniteCourante = uniteSuivante(). ` lui ajouter les op´rations qui construisent les informations en question. et facteur dans le rˆle de terme). 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. d’autre part l’ex´cution du programme P .

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

genCode(pDesc->adresse). Dans un compilateur ultra-simplifi´ qui e e e ignorerait les appels de fonction et les tableaux. } /* 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). 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().. en imaginant l’ex´cution de la s´quence ci-dessus. facteur(). uniteCourante = uniteSuivante(). terminal(’. } } void finterme(void) { int uc = uniteCourante. expArith(). } 62 . genCode(uc == ’+’ ? ADD : SOUS). } else . Consid´rons maintenant l’instruction compl`te y = 123 * x + c. genCode(pDesc->classe == VARIABLE_GLOBALE ? DEPG : DEPL)... if (uniteCourante == ’+’ || uniteCourante == ’-’) { uniteCourante = uniteSuivante(). genCode(uc == ’*’ ? MUL : DIV).void finExpArith(void) { int uc = uniteCourante. } else . 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.’). expArith(). finTerme(). terminal(’. if (uniteCourante == ’*’ || uniteCourante == ’/’) { uniteCourante = uniteSuivante(). terminal(’=’). terminal(’=’). finExpArith().’).. on constate que son effet global aura bien ´t´ d’ajouter e e ee une valeur au sommet de la pile. } } Au final. terme().

instruction(). si elle est fausse (c. expr2 . La partie de l’analyseur syntaxique correspondant ` la r`gle pr´c´dente ressemblerait ` ceci64 : a e e e a void instruction(void) { .. .3). si l’expression est vraie (c. else if (uniteCourante == TANTQUE) { /* boucle "tant que" */ uniteCourante = uniteSuivante().. Pour l’instruction  tant que  toute enti`re on aura e donc un code comme ceci (les rep`res dans la colonne de gauche. 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. terminal(ALORS). non nulle) le corps de la boucle est ex´cut´.3.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 . Comme pr´vu.1. nulle) l’ex´cution e e a e continue apr`s le corps de la boucle .. instr2 .. } else if (uniteCourante == SI) { /* instruction conditionnelle */ uniteCourante = uniteSuivante()... e e e e L’effet de cette instruction est connu : l’expression est ´valu´e . l’ex´cution du code pr´c´dent laisse la pile comme elle ´tait e e e e e e en commen¸ant. cela d´pend de la complexit´ de l’expression et de l’instruction en question). comme α et β. c 5. instruction().. instruction(). instrk ceux produits par la compilation de l’instruction. } Commen¸ons par le code ` g´n´rer ` l’occasion d’une boucle  tant que .. dans le cas de l’instruction conditionnelle. 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.. exprn les codes-machine produits par la compilation de l’expression et instr1 . expression(). | tantque expression faire instruction | si expression alors instruction | si expression alors instruction sinon instruction | . } } else . repr´sentent les adresses des e e instructions) : 64 Notez que. if (uniteCourante == SINON) { uniteCourante = uniteSuivante().-`-d. e a e e puis on revient ` l’´valuation de l’expression et on recommence tout. e 63 . expression()..-`-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 → ... . terminal(FAIRE).

. expr1 d´but de l’instruction si. . Dans le mˆme ordre d’id´es. instrk β . /* ici. int valeur) { mem[place] = valeur. instrk SAUT α β Le point le plus difficile.... la suite (apr`s l’instruction si. } Instruction conditionnelle. genCode(0)... e expr2 . expression().. dans l’analyseur a u montr´ plus haut pour lui faire produire le code suivant : e . repCode(aCompleter. aCompleter.alors. 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).. beta = TC */ } else ..) e 64 . } 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. il n’est pas difficile d’imaginer ce qu’il faut ajouter. genCode(SIFAUX).. instruction().alors. elle serait consid´rablement e e plus complexe si le code ´tait produit dans un fichier) : e void repCode(int place... terminal(FAIRE). exprn SIFAUX β instr1 instr2 . genCode(SAUT)........ l’adresse β n’est pas connue. voici le code ` produire dans le cas de l’inse e a truction conditionnelle ` une branche . aCompleter = TC..... TC).α expr1 expr2 . exprn SIFAUX β instr1 instr2 .. genCode(alpha). est de r´aliser qu’au moment o` le compilateur doit produire l’instruction e u SIFAUX β. alpha = TC. et o`. else if (uniteCourante == TANTQUE) { /* boucle "tant que" */ uniteCourante = uniteSuivante(). ici.

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

. 1998. 1972. and Jeffrey Ullman..2. Compilateurs. Paris. [4] John Levine... and Doug Brown. Dunod. Modern Compiler Implementation in C.. Inc.. Cambridge University Press. 1991. O’Reilly & Associates. EMPC 200 PILE 1 EMPG 1 EMPG 2 EMPC 25 DIV EMPC 100 SUB APPEL α PILE -2 EMPC 2 MUL SUB DEPG 0 .. techniques et outils. Lex & yacc. . 1990. [3] Andrew Appel. Prentice Hall Inc. 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 . Ravi Sethi. Principes. [2] Alfred Aho and Jeffrey Ullman. Translating and Compiling.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.. 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. Tony Mason.. 66 .. The Theory of Parsing.

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

Sign up to vote on this title
UsefulNot useful