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

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

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

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

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

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

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

7

2.2

Reconnaissance des unit´s lexicales e

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

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

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

8

début

0

<

1

= >
autre

2 3 4
*

rendre INFEG rendre DIFF rendre INF

= >

5 rendre EGAL 6 =
autre lettre

7 8
*

rendre SUPEG rendre SUP

lettre

9
chiffre

autre

10

* rendre IDENTIF

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

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

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

0

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

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

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

/* etat = 0 */

etc.

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

9

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

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

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

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

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

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

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

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

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

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

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

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

e 22 . la grammaire G1 est non ambigu¨ et ´quivalente ` la grammaire G2 ci-dessus. mais empˆche l’´criture d’analyseurs pour cette e ea e e e grammaire. du moins des analyseurs descendants24 . 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. 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. mais nous verrons qu’un analyseur descendant est un programme compos´ de fonctions e e e directement d´duites des productions. cela consiste ` remplacer une production telle que e e ea a 24 La question est un peu pr´matur´e ici. Il est souvent possible de e e remplacer une grammaire ambigu¨ par une grammaire non ambigu¨ ´quivalente. 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α. la grammaire G2 suivante est ambigu¨ : e ıne e expression → expression "+" expression | expression "*" expression | facteur facteur → nombre | identificateur | "(" expression ")" (G2 ) En effet. e ea e La r´cursivit´ ` gauche ne rend pas une grammaire ambigu¨. Cas particulier. 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 e e e a ´ ` Grammaires recursives a gauche.1 est r´cursive ` gauche. Par exemple.1. Par exemple. Une production A → . .. 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. Par exemple. 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. e ıne ¨ Grammaires ambigues. o` α est une chaˆ quelconque. la figure 6 montre deux arbres de d´rivation distincts pour la chaˆ "2 * 3 + 10". Une grammaire est ambigu¨ s’il existe plusieurs d´rivations gauches diff´rentes e e e pour une mˆme chaˆ de terminaux. 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. . Dans le cas de la r´cursivit´ ` gauche simple.. Dans le cas d’une production A → Aα on se retrouve donc avec une fonction reconnaitre A qui commence par un appel de reconnaitre A. la grammaire G1 de la section 3.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

[4] John Levine. [2] Alfred Aho and Jeffrey Ullman. Inc. O’Reilly & Associates. Compilateurs. Principes. 66 . 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. Modern Compiler Implementation in C. techniques et outils.2.. 1998. 1972. EMPC 200 PILE 1 EMPG 1 EMPG 2 EMPC 25 DIV EMPC 100 SUB APPEL α PILE -2 EMPC 2 MUL SUB DEPG 0 ... Dunod. Paris. 1990. Cambridge University Press. and Jeffrey Ullman. .. 1991.. 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 . Lex & yacc. Ravi Sethi. The Theory of Parsing..4) : α allocation de l’espace local (une variable) debut de l’instruction si ] ] simulation du – unaire ] β le r´sultat de la fonction e R´f´rences ee [1] Alfred Aho. Translating and Compiling.... Tony Mason. Prentice Hall Inc. and Doug Brown.. [3] Andrew Appel.

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

Sign up to vote on this title
UsefulNot useful