DEUG Première Année

1996 Jacques Guizol

3. Du langage au programme
3.1.
3.2.
3.3.
3.4.
3.5.

Avant propos.
Le langage Assembleur.
Analyse lexicale, Automates.
Analyse syntaxique.
Introduction à la programmation. 

#XCPV RTQRQU
3.1.1. La problématique.
Dans le paragraphe 1.5 nous avons introduit de façon conjointe les notions
d’instruction machine, de micro-instruction, d’assembleur, de compilateur et
d’adressage. Nous allons ici éclaircir un peu ce micmac afin d’y voir un peu plus
clair et pour finalement arriver à assimiler totalement l'
illustration suivante :
Niveau des instructions
en langage évolué

Programme
source
Editeur
de texte

Niveau des instructions machine

Traducteur

Code
objet
Editeur
de liens

Chargeur

Code
exécutable

Librairies
Séquenceur

Langage de
programmation
Commandes

Utilisateur

17/09/02 18:44

Code
translatable

Micro
Machine

Niveau des commandes
électroniques

Micro
Code

Niveau des micro-instructions

• Une opération une fois effectuée. nous avons déjà dit que la machine disposait de circuits spécialisés dans l’unité de calcul (UAL) pour effectuer diverses opérations arithmétiques (addition. s’il est inutile de stocker le résultat intermédiaire. Le lien entre code et sémantique opératoire est totalement spécifique de la CPU et donc de son constructeur (par exemple. l’adresse d’enchaînement devient inutile. Ce catalogue des opérations que commande et gère la CPU constitue le répertoire ou le jeu des «opérations de la machine». L’instruction machine. On va donc essayer de réduire le plus possible la taille de ce codage.Nous ne reviendrons pas sur la partie basse du schéma. Certes il y aurait encore beaucoup de choses à dire. soustraction. Ce répertoire qui peut être assez ressemblant d’une machine à une autre de même catégorie. un tel codage a pu être utilisé sur certaines machines. Mais la plus grande spécificité réside dans son codage… 3. négation. En fonction de ces remarques. Chacun de ces circuits doit pouvoir être sollicité via une commande. les capacités mémoire étant de plus en plus importantes.2 Chaque instruction ainsi normalisée disposera des 4 arguments. etc.5. Rappelons simplement que ces commandes permettent des opérations de transferts de données (de registre à registre ou entre mémoire et registre). Le numéro alloué devient alors le code opération. • Une fois l’opération achevée. Par ailleurs. D’où un ralentissement de l’exécution et des performances médiocres. -KQO @EOLKOKJO @KJ? a LNeOAJP @Q ?K@A KLeN=PKENA 0QA B=QPEH N=FKQPAN  • Les opérations arithmétiques et logiques nécessitent pour la plupart 2 opérandes . mais le code correspondant est différent). …Ce codage est effectué très simplement : ayant la liste exhaustive de toutes les «opérations de la machine» regroupées par type. © Jacques Guizol .2. il va falloir déterminer la prochaine à effectuer par une adresse d’enchaînement. on les numérote. mais cela pourra se faire plus tard. c’est à dire le niveau le plus rudimentaire (micro-instructions et commandes) qui a été traité au §1. même s’il n’y a qu’un opérande. Si on multiplie le nombre d’adresses à coder. tests). Elles permettent également de mettre en œuvre les échanges avec l’extérieur (entrées sorties). En conséquence. exclusion). l’instruction dans son entier pourrait être codée de la façon suivante : Code opératoire Adresse 1 er opérande Adresse 2 ème opérande Adresse résultat Adresse prochaine opération Effectivement. * Page 3. division. les adresses réclament de plus en plus de bits.1. Aujourd’hui. son résultat va pouvoir être stocké à une adresse donnée. on obtient un code de l’instruction réclamant systématiquement * plusieurs mots mémoire avec pour conséquence un gaspillage de l’espace mémoire et une multiplication des accès pour réaliser le traitement dans la CPU. Nous avons vu qu’une technique consiste à maintenir en permanence l’adresse de la prochaine instruction à exécuter dans un registre : le Compteur Ordinal. les opérations de transfert de données. multiplication) et logiques (union. ainsi que des opérations diverses sur registres (décalage et incrémentation de registres. contrôle de séquence. etc. présente néanmoins quelques petites différences qui suffisent à le rendre spécifique de la machine qui en dispose. intersection. les processeurs Intel et Motorola disposent tous deux de l’opération d’addition. de même.

Toujours en référence à notre schéma de départ. Un immense progrès aujourd’hui galvaudé. modes d’adressage. mais à quel prix !!?… Il va falloir les écrire en langage machine… une suite interminable de 0 et de 1 mêlant codes opératoires. Donc finalement.2.  . nous avons vu au §1.5. L’assembleur est en fait un langage très pauvre puisqu’il se situe au même niveau que le langage machine *. D’ailleurs.AN?E !A=Q ?=@A=Q 2YEH RAQP ?D=JCAN HA IKEJ@NA ZEKP=[ >KJFKQN H= FKQEOO=J?A 0Q=J@ KJ LAJOA MQY=Q @e>QP HYEJBKNI=PEMQA ?YeP=EP ca KJ OA @AI=J@A ?KIIAJP HAO EJBKNI=PE?EAJO KJP LQ OA NALNK@QENA AP B=ENA @AO eIQHAO Effectivement. nous obtenons le code instruction machine qui a cette allure : Code opératoire Mode d'adressage Codage opérande selon M.. on résout simultanément deux problèmes : 1) au pire. "KIIA OE ?AH= JA OQBBEO=EP L=O H= @EBBE?QHPe @A IEOA =Q LKEJP @AREAJP ABBN=U=JPA 5KQO =FKQPAV QJA EJOPNQ?PEKJ  QJ AILH=?AIAJP @AOPEJe a NA?ARKEN QJ NeOQHP=P  PKQPAO HAO =@NAOOAO OKJP a ?D=JCAN ?=N PKQP = ePe @e?=He $BBN=U=JP FA RKQO @EO "YAOP @ENA OYEH AOP BNeMQAJP @Y=RKEN @AO ANNAQNO PNdO OKQRAJP JKJ @E=CJKOPEMQeAO $P =HHAV HAO NAPNKQRAN @=JO ?A B=PN=O EJ@AO?NELPE>HA 5KQO L=OOAV RKPNA LNKCN=IIA a QJ ?KHHdCQA  . la programmation en langage machine a vite cédé la place au langage d’assemblage ou assembleur. posons la question autrement ! En fonction de nos connaissances. 3 . adressage .1. bref quelque chose d’incompréhensible. immédiates. absolues.2 qu’elle pouvait revêtir plusieurs formes. que peut-on faire à ce niveau ? On peut faire exécuter des programmes certes. que trouve-t-on à ce niveau ?… Ou plutôt. les résultats intermédiaires étant conservés dans l’accumulateur. 3. Il va donc falloir coder le mode d’adressage choisi. 2) lors d’un enchaînement de calculs. un seul des deux opérandes doit être adressé .Si l’on suppose que les circuits à 2 opérandes de l’UAL ont tous une de leurs entrées reliée au registre accumulateur. Nous venons de définir ce qu’était une instruction machine. Concernant l’adresse. Mais il a l’énorme avantage de * Une instruction assembleur correspond exactement à une instruction machine. leur sauvegarde en mémoire est inutile. de présenter ses avantages et ses limites “ergonomiques” et de l’associer à ses deux compères (l’éditeur de lien et le chargeur) que nous allons découvrir et dont le rôle est devenu nécessaire dès lors que le dialogue avec la machine a pu s’établir autrement qu’en langage machine. Ainsi nous économisons encore le codage de 2 informations sous réserve d’un éventuel pré-chargement de l’accumulateur par le 1er opérande (pour débuter un calcul) et d’une possibilité de sauvegarde de l’accumulateur à une adresse mémoire (pour ranger le résultat final d’un calcul). mais de dire ce que c’est. En définitive.. il ne reste plus que le code opératoire et un espace permettant de coder l’adresse d’un argument.. le lien très étroit qui existe entre le langage d’assemblage et le langage machine le rend dépendant de la machine et de son processeur.… ). adresses (relatives. Du langage au programme Page 3.G NCPICIG #UUGODNGWT Le but n’est pas ici de présenter l’assembleur spécifique de telle ou telle machine. codes de registres.

• Etant donné qu’il est inutile que chacun réinvente à chaque instant le fil à couper le beurre. les adresses et références internes supposent une adresse origine en 0. mot. etc. de directives de déclaration (une table est totalement définie en une ligne d’assembleur qui indique un nom symbolique (adresse). Page 3. elle s’avère “incomparablement plus” agréable qu’en langage machine. noms de registres. sous une forme symbolique. Quant aux références externes. variables. D’ailleurs. C’est le cas dès que le programme doit opérer des opérations d’entrée-sortie avec un quelconque périphérique . l’assembleur est toujours d’actualité et même si certains ricanent en parlant des “fêlés de l’assembleur” il n’en reste pas moins vrai que si certaines portions d’un logiciel sont particulièrement utilisées. en fait le langage lui-même et son traducteur ont le même nom ! D >KJ "YAOP O=JO @KQPA ?A MQA HYKJ =LLAHHA QJ =>QO @A H=JC=CA La traduction va avoir pour effet. audio ou autres . • Enfin. systématiquement. à partir du programme source. appel à des sous-programmes.2. de générer un programme objet (comme la publicité et la presse de pacotille traduisent la femme source en femme objet). grâce à une instruction regroupant une code spécifique et un argument adresse. elles demeurent non résolues. un programme va aussi faire appel à toute une batterie de fonctions particulières rassemblées dans des librairies (fonctions mathématiques. on aura tout intérêt à les écrire en assembleur pour bénéficier de la meilleure optimisation possible. SUB. c’est encore le cas si une erreur fatale se produit en cours d’exécution.… ). 3.2. Mises à part les routines systèmes. Ce programme objet produit est rarement complet . c’est aussi le cas si le programme fait appel à des primitives graphiques. Ces “ morceaux” de programme sont appelés sous-programmes. etc. aux endroits désirés. Ainsi un programme va être constitué d’un regroupement de plusieurs modules de provenances diverses : • Un programme gagnera en clarté s’il est découpé en “ morceaux” chacun ayant une fonction propre. il est improbable qu’il puisse se suffire à lui-même. Ainsi le programme principal se résumera à la réservation de place pour les données communes aux divers “ morceaux” et aux appels de ceux-ci.4 © Jacques Guizol . le type de ses éléments (octet. DIV. Certaines directives ou certaines opérations permettent de faire le lien avec des informations extérieures. à des parties de librairies.disposer de codes mnémoniques pour les opérations (ADD. Au final. il offre la possibilité de désigner adresses. de traitement de chaînes de caractères.). 4JA BKEO ?A LNKCN=IIA e?NEP AJ H=JC=CA =OOAI>HAQN EH B=QP @KJ? HA PN=@QENA AJ H=JC=CA I=?DEJA 0QE OYAJ ?D=NCA  L’assembleur !   Oui. tout programme utilisateur fait appel à des “ morceaux” d’un programme particulier : le logiciel système. MP. double mot) et le nombre de ces éléments) et surtout. bien que l’écriture d’un programme en assembleur ne représente pas nécessairement une partie de plaisir. MOVE. L’éditeur de liens. tous ces petits morceaux pris ici ou là sont en fait sous la forme de code objet. statistiques. Chacun ayant été traduit séparément.

.. l’appel du sous-programme SP2 dans SP1 sera traduit par une instruction comportant l’adresse de lancement de SP2.. F3 Module de librairie L2 Editeur de liens Dans la figure ci-dessus. . . C’est cette dernière opération que doit encore subir le translatable pour devenir exécutable... le fichier la contenant est ajouté à la suite de ceux précédemment trouvés comme cela est montré dans la figure ci-contre. F2 tL1 tL1+λF3 Définitionfonct. il le sera à une adresse quelconque. Appel fonction F1 . l’adresse sera en fait tL1+tL2+tSP1+λSP2. à deux modules de librairies. Chaque “ morceau” ayant été traduit est donc en code machine. va.. Par exemple. Appel fonction F2 .. Module de librairie L1 0 λ F3 λ F4 tL2-1 . tL1+tL2+tSP1+λSP2 Vers tL1+λ F3 tL1+tL2+tSP1+tSP2 Données P . ... .. #=JO H= @eJKIEJ=PEKJ @Q ?K@A LNK@QEP L=N HYe@EPAQN @A HEAJ =LL=N=gP HA MQ=HEBE?=PEB ZPN=JOH=P=>HA[ 0QA OECJEBEAPEH  +A ?K@A LNK@QEP JYAOPEH L=O @ENA?PAIAJP ATe?QP=>HA  Nous avons bien précisé que le code objet produit débutait à l’adresse 0 et toutes les références internes étaient calculées en fonction de cela.... ... Or......... F3 tL1+λF4 Définitionfonct.. Définitionfonct.. 0 Données P λP Appel SP2 . ....... λSP2... F1 λF2 Définitionfonct. . . .. à partir de l’origine (au départ... considérer une à une les références non résolues et rechercher la définition correspondante dans les fichiers indiqués par l’utilisateur.. Définitionfonct. . est présenté l’exemple d’un programme P comportant deux sous-programmes SP1 et SP2 et faisant appel. F1 .. soit au travers de ses sous-programmes. Donc toutes les références devront être translatées.. donc à l’adresse tL1+tL2+tSP1. A l’issue de ce traitement... résoudre les références externes pour enfin produire un code translatable. tL1+tL2+λSP1 Vers tL1+tL2 +tSP1+λ SP2 Vers λF1 tL1+tL2+tSP1 Données SP2 .. 0 λF1 Définitionfonct.. 5 . Définitionfonct. . Une fois celle-ci trouvée.. Appel fonction F3 ... . ..... F2 . 0 λSP1 tSP1 -1 0 Données SP1 .C’est l’éditeur de liens (linker) qui va organiser tous ces modules. .. Du langage au programme Page 3.. Sous-programme SP2 0 λF1 λF2 tL1-1 ... . . .. F4 tL1+tL2 Données SP1 .. Comme ce module a été chargé après les 2 librairies et SP1. L’éditeur de lien.. lors de la construction du code translatable.... Appel fonction F4 .. F4 ... tL1+tL2+tSP1+tSP2+λP Vers tL1+tL2 +λSP1 Vers tL1+tL2 +tSP1+λ SP2 Vers λF2 tL1+tL2+tSP1+tSP2+tP-1 λSP2 Vers tL1+λ F4 tSP2-1 Données SP2 . . tous les modules sont donc réunis dans un seul fichier commençant à l’adresse 0 et toutes les références ont été résolues relativement à celleci et en fonction de l’ordre d’insertion des divers modules dans le fichier global.... lorsque le programme va être chargé en mémoire centrale pour être exécuté... . .. soit directement.. Appel SP2 . Appel SP1 Sous-programme SP1 tP-1 Programm e principal . ses adresses étant référencées par rapport à son propre début (adresse 0). le programme principal).. Définitionfonct.......

En ce qui concerne les instructions. réfléchissons un peu ! En fait sur quoi le chargeur intervient-il ? 2QN HAO =@NAOOAO Et quel est l’endroit par lequel passent toutes ces adresses ? +A NACEOPNA =@NAOOA @A H= "/4 Qu’a-t-on vu au § 1. la réalisation d’un exécutable figeait son emplacement en mémoire en fonction d’une configuration bien précise. il faut considérer chaque relais d’indirection adresse pour lui appliquer la translation. dont le compteur ordinal (à l’adresse de lancement) et. une fois le code chargé. 2ème génération Cette rigidité a vite été supprimée. ce qui représentait bien souvent un énorme gaspillage des quelques dizaines de milliers d’octets dont disposait la mémoire centrale. Page 3. un ou plusieurs registres (opérande implicite). on a bien compris que dans un code translatable l’origine supposée des adresses est en 0. une référence mémoire ou une adresse de branchement ? Seuls les deux derniers cas le concernent.5. avec un système tel que celui dont nous venons de parler et où le registre aurait pour valeur l’adresse de chargement. En effet. 1re génération Il y a encore une vingtaine d’années. Donc. en particulier. toutes les adresses et seulement les adresses seraient automatiquement translatées avant l’accès mémoire ! C’est cette méthode qui est utilisée pour sa mise en œuvre particulièrement simple et les facilités qu’elle apporte pour divers modes de gestions de la mémoire. des partitions. mais comment le déterminer sinon en décodant à nouveau code opératoire et/ou mode d’adressage ? Pour éviter de refaire le “ boulot” qui a déjà été fait par l’assembleur. Donc. C’est le chargeur absolu qui intervenait directement sur le code translatable… pour le translater en fonction de l’adresse de chargement en mémoire définie une fois pour toutes. lorsque le chargeur va devoir modifier le contenu de l’opérande. toutes les adresses sont relatives à l’adresse de chargement en mémoire.2. Ce travail est moins simple qu’il n’y paraît.2. permettant ainsi aux programmes d’être chargés n’importe où en mémoire et ce grâce aux chargeurs relogeables ou translateurs qui ajoutaient à chaque adresse (des données et des instructions) son déplacement par rapport à l’adresse 0.6 © Jacques Guizol . celui qui permettra d’effectuer la translation des adresses.3. un caractère (opérande immédiat).4 traitant de l’adressage relatif ? 4J OUOPdIA ?KJcQ =QPKQN @YQJ NACEOPNA @A NeBeNAJ?A AP @YQJ =@@EPEKJJAQN LANIAPP=JP @A PN=JOBKNIAN @AO =@NAOOAO NAH=PERAO AJ =@NAOOAO =>OKHQAO Or. Le rôle du chargeur se limite alors à charger physiquement le programme en mémoire et initialiser divers registres. c’est encore celui-ci qui va rendre service en codant un indicateur de relocation désignant au chargeur les opérandes à translater et eux seuls.  Le chargeur est un programme qui fait donc partie de l’environnement système. Mais qui charge le chargeur ? 3ème génération A présent. Le Chargeur. comment va-t-il déterminer si celui-ci représente un entier. Dans les deux cas.3. en ce qui concerne les données. Un énorme progrès avait consisté à disposer de diverses configurations du système permettant de gérer la mémoire découpée en “ morceaux” . les programmes résidaient seuls en mémoire.

et de ce fait. il a introduit la notion de langage symbolique.1). LISP. PROLOG. "AH= ATLHEMQA LAQPfPNA LKQNMQKE QJ H=JC=CA EJPANLNePe AOP N=NAIAJP LANBKNI=JP AJ PAILO @XATe?QPEKJ Si maintenant nous considérons un langage de programmation ne disposant pas d' interprète. à la place de "poules" nous avions rencontré "kjsdfhku". le trio code opératoire. Ainsi. SMALLTALK. un langage de programmation évolué P pour lequel nous disposons d' interpréteur I. il le fait au travers de deux niveaux d' interprétation. le processeur).4) ou la Micro-Machine (§ 1.  A votre avis. 7 . citons pêle-mêle GAP. compilateur et interprète sont deux formes de traducteurs de programmes écrits en langage évolué. il apparaît indispensable. le processeur est l' interprète (ou interpréteur) de son langage-machine. même au niveau de l' instruction machine.5.2. Effectivement. ALGOL 60. Dans ce un cas. avant l' exécution alors que l' interprète traduit chaque instruction du programme pendant l' exécution et chaque fois qu' est exécutée. PASCAL. le registre CO (Compteur Ordinal) contient-il une adresse absolue ou relative ? Quels sont les nombreux avantages de cela ? En conclusion. Cet interpréteur est en fait un programme qui est lui-même interprété comme nous l' avons vu dans le paragraphe précédent. JAVA et bien d’autres.1). les principes d’édition de liens et de chargement qui sont toujours d’actualité. par exemple. Considérons.  #PCN[UG NGZKECNG #WVQOCVGU Rapidement de nombreux langages de programmation ont vu le jour. BASIC.3. ALGOL W. Les instructions du programme écrit en P sont donc un en fait des données de l' interpréteur I. nous aurions immédiatement détecté une Du langage au programme Page 3. Si. l' Unité de Contrôle (§ 1. la traduction va être opérée par un compilateur. lorsque le programme écrit en P s' exécute. 3. nous avons utilisé le terme d' interprétation. Introduction.Ainsi le code produit par l’éditeur de lien ne subit aucune modification avant exécution. A plusieurs reprises dans le chapitre 1. lorsque nous avons présenté la Mémoire Centrale (§ 1. Tout d' abord. quel qu' soit. il va falloir lui faire subir une transformation qui le rende compatible avec les données d' interpréteur existant (éventuellement. sans entrer dans des considérations exagérément théoriques. nécessite cette un il interprétation de bas niveau opérée systématiquement pour chaque instruction machine et autant de fois quelle est rencontrée. anciens ou récents. son interprétation et finalement sa compréhension passent par trois étapes. FORTRAN.… Avant de présenter un langage quel qu’il soit. PL1. En fait. Le compilateur procède à la traduction "une bonne fois pour toutes". à présent. SIMULA.1. Ainsi le code exécutable d' programme.3. on peut dire que l’assembleur a été le déclic qui a influencé l’informatique jusqu’à aujourd’hui. on peut effectivement dire que l’éditeur de lien produit le code exécutable. C. elle Lorsque nous lisons la phrase : «les poules du couvent couvent». mode d' adressage et opérande a besoin d' être interprété pour donner lieu à l' exécution de l' instruction qui se traduit par toute une série de commandes élémentaires à l' initiative de l' Unité de Contrôle. Même si c’est modestement. Donc en définitive. COBOL. de présenter néanmoins le minimum à connaître. Dans ce cas. une lecture globale nous permet de vérifier que tous les composants sont bien des unités lexicales connues pour la langue utilisée. Parmi les plus connus.

veillera à la pertinence contextuelle de leur occurrence. Nous allons tout d’abord introduire la notion de langages réguliers *. débuts et fin de commentaires. Sinon. nous le verrons. Nous allons étudier en détail le processus constitué par la succession de ces trois analyses car il régit le mécanisme de traduction que nous nous proposons d' expliquer dans ce chapitre. Enfin. Tous ces objets sont constitués de caractères (un programme source n’est rien d’autre qu’une suite de caractères saisis sous un éditeur) assemblés selon des règles précises pour constituer des unités d’ordre supérieur. les unes après les autres. l' analyse sémantique vérifie la "cohérence de type" entre les mots syntaxiquement reliés.65” .9857” aurait produit 2 unités lexicales : les rationnels “ 1234.” et “ 65. • • • l' analyse lexicale reconnaît des chaînes de caractères qui sont des mots d' langage . Par contre. des signes (opérateurs arithmétiques ou booléens. la chaîne “ 1234.65. l’opérateur additif “ +” et l’entier “ 9857” . les unités lexicales (token). l' analyse syntaxique nous permet d' obtenir un premier niveau de compréhension en désignant essentiellement l' actant et l' action. etc. * appelés quelquefois K-langages en référence à S. limites d’instructions. les unités lexicales rencontrées dans le texte en fonction de leur présence dans le lexique (mots réservés ou opérateurs) ou de leurs règles de construction (identificateurs ou nombres). comporte des “ mots” de nature diverses : des mots dits réservés.impossibilité de comprendre le message. En effet.… ). Le “ . propres au langage. Langages réguliers et Expressions régulières. En résumé.65” et “ . la traduction d' langage dans un autre ne peut un s' envisager que si l' maîtrise parfaitement le message en langage source. on des contre-sens dramatiques peuvent apparaître. La chaîne “ 1234. la sémantique attachée aux termes (éventuellement associée à la connaissance du contexte) parachève la vérification de la cohérence et donc la compréhension de la phrase (par exemple.65+9857” va donner lieu à l’extraction de 3 unités lexicales : le rationnel “ 1234. Par exemple. que nous verrons ensuite.3. un l' analyse syntaxique considère la suite des mots reconnus par l' analyseur lexical et vérifie (valide ou invalide) leur organisation afin de déterminer si "la phrase" qu' forment est correcte dans le langage que ses règles ils décrivent . Page 3.9857” (ou “ 1234.65. 3. la phrase «les casseroles du couvent couvent».” rencontré ensuite ne constituant pas une unité lexicale en soi. bien que valide sur le plan lexical et syntaxique aurait posé quelques problèmes d' interprétation).C.2. l’analyse syntaxique.9857” ). la chaîne “ 1234. Après l' analyse lexicale. Un programme écrit dans un langage évolué. Kleene qui les a définis en 1956. des identificateurs de variables. l’analyseur lexical va détecter une erreur. Le soin étant laissé à l’analyseur syntaxique de valider ou pas la succession de ces deux nombres rationnels… L’analyse lexicale va donc avoir pour but de valider.65” .+9857” va donner lieu à l’extraction du rationnel “ 1234. des nombres.8 © Jacques Guizol .

9 . P ∪ Q = {x : x ∈ P ou x ∈ Q} b) PQ. Par exemple : Expression régulière abc abc | de | fgh a* b*c* a(bc|d)* Langage engendré {abc} {abc de fgh} {e a aa aaa aaaa aaaaa … } {e b c bb bbc bbccc cccc bbbbbbbc bbbbb … } {a abc ad abcd abcbcbc adddd abcdbcbcdbcbcddd … } Afin de rendre les écritures plus concises. on a recours à des algorithmes particuliers appelés automates d’états finis. la concaténation . PQ = {xy : x ∈ P et y ∈ Q} c) P*. on définit aussi des classes de caractères par [c1c2… cn] où chaque ci représente soit un caractère. Une suite de p éléments a sera représentée par l’ écriture ap. Pourquoi finis ? Parce que le nombre des états est fini. {e}. Donnez l’expression régulière permettant de décrire les mots du langage ainsi défini. [k-o] représente k|l|m|n|o . Un automate d’états fini est donc un 5-tuple M=(Q. ou {a} pour tout élément a de • .Soit ™ XQ DOSKDEHW ILQL 2Q GpILQLW UpFXUVLYHPHQW XQ langage régulier sur ™ GH OD IDoRQ suivante : 1) Ø (l’ensemble vide) est un langage régulier sur ™  2) {e}(l’élément vide) est un langage régulier sur ™  3) {a}est un langage régulier sur ™ SRXU FKDTXH pOpPHQW D DSSDUWHQDQW à ™  4) Si P et Q sont des langages réguliers sur ™ DORUV OH VRQW DXVVL  a) P ∪ Q . de concaténation et de fermeture. Pn = PPn-1pour n•  3 ∪ ≥ Pn 5) Rien d’autre n’est langage régulier sur ™ ¡   Donc un sous-ensemble de • * est régulier si et seulement s’il se réduit à Ø.   On définit un identificateur comme étant une suite de lettres ou de chiffres (suite alphanumérique) commençant impérativement par une lettre. la fermeture de P (P0 = {e} . l’union . Diagrammes de transitions. indique tous les états suivants possibles. nous allons définir un formalisme particulier appelé expressions régulières. soit un intervalle de caractères. Par exemple [ab] représente a|b . ci prend la forme k1-k2. Du langage au programme Page 3. Automates finis. [fhm-rt] représente f|h|m|n|o|p|q|r|t. F) où : • Q est un ensemble fini d’ états . Pourquoi automates ? Parce que le but poursuivi va être réalisé par un enchaînement automatique d’opérations qui.3. Afin de décrire de façon simple les langages réguliers sur un alphabet fini • . Dans ce dernier cas.3. 3. Afin de déterminer si une unité lexicale fait bien partie du langage décrit par une expression régulière. en fonction d’un état courant et du caractère courant. ou s’il peut être obtenu par un nombre fini d’applications des opérations d’union. ™ ˜ T0. • ˜ HVW XQH DSSOLFDWLRQ GH 4י GDQV P(Q) qui est appelée la fonction de transition d’ état . • ™ HVW O¶HQVHPEOH GHV FDUDFWères autorisés en entrée .

elle visualise les transitions d’état à état). Ici Q = {p. La programmation de l’automate. on a la table de transition : états origine [a-z] p q échec échec q échec q r [a-z0-9] ^ Si on désire dresser la table de transition correspondant à l’analyse des nombres binaires pairs vue plus haut. C’est un graphe dont les nœuds sont étiquetés par un nom associé à chaque état.0) = q ou r L’analyse d’une chaîne telle que 11010010110001 passe successivement par les états ppqpqqpqppqqqp aboutissant alors à une impasse correspondant à un échec. étant positionné sur un état q non final et le prochain caractère lu étant a.^) = r. • (q. • (p.0) qui peut soit déboucher sur l’état q soit sur l’état r. • = {0. • (q. On peut représenter par un schéma fonctionnel ces automates d’états. Après avoir déterminé sur quoi porte cette différence. r} . • F ⊆ Q est l’ ensemble des états finaux. s’il n’existe aucune règle • (q.  Déterminez le langage engendré par le diagramme de transition suivant : La transition terminale s’écrit •(q. Par contre. il existe plusieurs transitions. F = {r} . Ici.a) contient q où p et q sont des états. Le programme ne ressemble alors plus du tout à ce qu’il était précédemment. pour l’exercice précédent.a).0) = q . Pour chaque a ∈ • tel que • (p. on va rencontrer une difficulté pour • (q. De façon générale.1} . q0 = p . lorsque pour un état fixé et un caractère lu.a). Lorsque l’automate atteint un des nœuds appartenant à F. on peut contourner la difficulté et rendre ainsi l’automate déterministe en utilisant la chaîne vide ^.• q0 ∈ Q est l’ état initial . ensemble des états finaux. q. l’automate est dit non déterministe. Cette représentation porte le nom de diagramme de transition (puisque effectivement.1) = p . Peut-on ne pas utiliser l’élément vide ^? Dans ce cas l’analyse du dernier caractère va différer. le diagramme de transition ci-contre décrit le langage des nombres binaires pairs. pouvez vous suggérer la signification que l’on peut donner à la transition utilisant l’élément vide? [a-z0-9] Début p [a-z] q ^ r (Se souvenir que l’on est en train d’analyser une chaîne de caractères qui ne s’arrête probablement pas à ce niveau).10 © Jacques Guizol . au lieu d’être calquée sur le diagramme de transition peut s’appuyer sur une table de transition tabulant l’application • telle que ci-dessous. Cet arc est étiqueté par la liste des a tels que • (p. l’analyse est en échec et on en conclut que la chaîne que l’on vient de lire n’appartient pas au langage Par exemple. Page 3.1) = p . Par exemple. l’analyse a réussi prouvant donc que l’on a rencontré une chaîne terminale du langage. il existe un arc orienté de p vers q. • (p.

Afin de procéder à une analyse grammaticale. L étant infini nous aurons nécessairement une règle de la forme Ai → aAi (1) qui permettra d’engendrer ap. Par exemple. mais ‘verbe’ n’est pas un verbe. 3. dans les grammaires formelles que nous allons voir. L’expression régulière permettant d’obtenir une suite d’éléments ‘a’ suivie d’une suite d’éléments ‘b’ est a*b* qui ne donne aucune assurance d’équilibre entre le nombre d’éléments ‘a’ et celui d’éléments ‘b’. la spécificité des langages réguliers provient du fait qu’ils peuvent toujours être engendrés par une grammaire qui ne contient pas de symbole auxiliaire auto-imbriqué. ‘verbe’. permettra d’introduire la suite infinie d’éléments b.4. réplique de la première pour l’élément a. nous allons utiliser des noms de catégories syntaxiques tels que ‘nom’. les formes “ bien parenthésées” ne peuvent pas être engendrées par un langage régulier.… Une distinction totale doit être faite entre une catégorie et le mot qui la désigne. De la même façon. on séparera le vocabulaire proprement dit de la langue (appelé vocabulaire terminal) du vocabulaire auxiliaire servant à énoncer et manipuler des règles (le vocabulaire non terminal). Du langage au programme Page 3. on aura recours à une autre règle Ai → bAj (2). Considérons par exemple les phrases en français.1. une troisième règle Aj → bAj (3). Par exemple le terme ‘nom’ est bien un nom. ‘groupe nominal’. Enfin. ‘préposition’. Si l’on veut représenter la grammaire par des règles de transition d’états. Exemple introductif. 11 . Afin d’introduire la deuxième partie des mots du langage. une fois la règle (2) et q-1 fois la règle (3)). Mais revenons à l’analyse du français ! La phrase «Handy aboie» répond au schéma ‘nom’ ‘verbe’ . où la contrainte p = q ne sera pas systématiquement satisfaite comme il l’aurait fallu pour engendrer des mots du type anbn.  #PCN[UG U[PVCZKSWG La remarque qui vient d’être faite nous permet d’effectuer la transition avec un autre type de langage introduit par des grammaires qui sont les plus importantes en terme d’application aux langages de programmation et aux techniques de compilation. Nous allons découvrir ces types de langages non réguliers dans le paragraphe suivant.états origine 0 1 ^ p q p échec q q q r Par rapport aux langages que nous allons voir ensuite. S ∅ aSb  où S est Soit le langage L ={anbn: n>0} engendré par la grammaire  S ∅ ab  récursif. «Le chien turbulent aboie» répond au schéma ‘groupe nominal’ ‘verbe’ . Les mots obtenus seront donc de la forme apbq (après avoir appliqué p fois la règle (1).

3. Outre la clarté évidente qu’apporte une telle représentation.«Le chien turbulent aboie furieusement après le facteur effrayé» ∅ Gn Gvau Gn Ph répond Pr schéma Gn ∅ Art N Adj  ‘groupe nominal’ ‘groupe verbal’ ‘préposition’ ‘groupe nominal’. © Jacques Guizol . Noam Chomsky qui. en a donné une caractérisation robuste les a appelées “ grammaires non contextuelles” (context-free) car leurs règles se formulent indépendamment de tout contexte* . l’intérêt de celleci pour mettre en évidence les niveaux d’évaluation et les ambiguïtés des grammaires.2. * Page 3. donc dans la partie gauche de celle-ci.4. Grammaires de Chomsky (C-Grammaire) La catégorie extrêmement importante des grammaires et langages que nous allons introduire a été découverte de façon différente par de nombreux chercheurs. nous verrons tout à l’heure.   On peut formaliser ces dérivations par les règles suivantes : Gv ∅ V Adv   Un moyen commode de représenter la structure syntaxique d’une phrase consiste à  … utiliser un parenthésage étiqueté par les noms de catégories syntaxiques. Par exemple une règle de réécriture morphologique pourrait s’exprimer ainsi : le <voyelle> ´→ l’<voyelle> (où <voyelle> → a|e|i|o|u|y) pour représenter l’élision. on peut représenter la dérivation selon une arborescence : Ph Gn Art N Gv Adj V Prep Adv Gn Art N Adj Le chien turbulent aboie furieusement après le facteur effrayé Il est évident que ce sont les règles de dérivation qui engendrent directement le parenthésage et donc la forme de l’arborescence. plus en détail. le premier.12 Un contexte intervient dans la configuration d’application de la règle de dérivation. #KJ? H= L=NPEA C=Q?DA @AO NdCHAO JA LKQNN= F=I=EO ?KJPAJEN MQYQJ OUI>KHA  Exactement ! On pourra même préciser encore plus l’allure de la partie gauche d’une règle dans la définition suivante. La phrase précédente se décompose ainsi : [[ [ Art Ph Gn Le ] [N chien] [ Adj ]][ [ aboie] [ turbulent Gv V Adv ][ furieusement ] Pr ep après ] [ [ le] [ Gn Art N facteur ] [ effrayé Adj ]] ] De façon équivalente.

4.b. on peut supprimer les règles correspondantes. la notation ϕ ⇒* ψ indique qu’il existe une dérivation (éventuellement vide) issue de ϕ et allant vers ψ. De même si un élément du graphe appartenant au vocabulaire auxiliaire. aidons nous d’un graphe dont les sommets représentent les éléments du vocabulaire auxiliaire A1. De façon plus générale. … Ap et les mots figurant dans les règles terminales a1. éventuellement en réorganisant les règles sans changer le langage engendré.c} . On synthétise en écrivant : S ⇒* aabcbaa. n’ est accédé par aucune flèche.2. • un vocabulaire auxiliaire (ou non-terminal) noté VA . • un nombre fini de règles «context-free» de la forme :A → ϕ avec : A ∈ VA et ϕ ∈ {VT ∪ VA }* EJOE @KJ? @=JO HAO "&N=II=ENAO HA IAI>NA C=Q?DA @AO NdCHAO JA ?KILKNPA F=I=EO @YeHeIAJP @Q RK?=>QH=ENA PANIEJ=H S ∅ aSa   Soit la grammaire définie par VT={a. les règles correspondantes peuvent être supprimées Du langage au programme Page 3. Nous traçons un arc fléché de Ai vers Aj (resp. les graphes partiels ne contenant pas l’axiome ne produisent rien : on peut enlever de la grammaire les règles et symboles correspondants. Définitions Une grammaire de Chomsky est définie par la donnée de : • un vocabulaire terminal noté VT . on peut donc se proposer de la modifier : • • en ôtant les règles et symboles qui ne produisent rien au niveau terminal . On désigne le langage engendré par L = {ncñ : n ∈ {a.  Il est clair que s’il n’est pas connexe. Par exemple. Grammaire connexe Pour étudier une grammaire. A noter au passage que si un élément du graphe orienté appartenant au vocabulaire auxiliaire est terminal (c’ est à dire qu’ aucune flèche n’ est issue de celui-ci).c] VA = {S. ak) s’il existe une production du type Ai →ϕ Aj ψ (resp. VT = {a. G = S ∅ bSb  . là encore.2. La forme générale des phrases engendrées par G répond au schéma ncñ où n représente une séquence quelconque de a et de b dont ñ est l’image miroir. autre que l’ axiome. … aq. la grammaire définie ci-contre engendre le même langage que la grammaire précédente.b}*} A toute C-grammaire correspond un C-langage éventuellement vide (lorsqu’il n’y a pas de dérivation terminale) mais la correspondance n’est pas injective : un même langage peut être engendré par plusieurs grammaires différentes.2. 3.1.4. A2.3. a2. 13 .b.VA = {S} .A} S ∅ aSa S ∅ bSb S∅c G = S ∅ abSba S ∅ cA S ∅ aA         Etant donnée une grammaire. Ai → ak). S ∅ c  On peut écrire une dérivation : S ⇒ aSa ⇒ aaSaa ⇒ aabSbaa ⇒ aabcbaa. • un axiome S appartenant àVA .

où A est un non-terminal et ϕ.2. une séquence quelconque. Aucune raison de principe ne permet d’exclure des règles terminales de la forme A → ^. S ∅ c  Page 3. par exemple.S.4. Toutefois. Soit.c}.4. on ne change pas le langage produit en ajoutant à la grammaire la règle A → ϕ.4.b.2.14 © Jacques Guizol .4. Si une grammaire admet une dérivation du type A ⇒ ϕ. 3.Par exemple.  Démontrez qu’étant donné une C-grammaire qui engendre le langage L contenant des dérivations vers la chaîne vide ^. Notons qu’il a été démontré qu’il est impossible de déterminer si une grammaire est ambiguë.5.{a. il se révèle techniquement plus commode d’écarter ce genre de règles. On peut donc simplifier S ∅ aSa  S ∅ bSb la grammaire qui devient : G = S ∅ c  S ∅ abSba A S c S ∅ BaB B ∅ Bb  De même l’étude du graphe associé à la grammaire G = B ∅ b … A ∅ ASA A∅a b B S A a conduit à supprimer les règles : A → ASA et A → a 3.2. Nous dirons que cette règle a été obtenue par contraction de la dérivation. Grammaire ambiguë On dit qu’une grammaire est ambiguë si une même phrase a plusieurs structures syntaxiques distinctes.3. considérons le graphe correspondant à la grammaire précédente : On voit que A ∈ VA est terminal. Au sujet de la chaîne vide. Contraction.R} où l’ensemble des règles s’écrit : S ∅ a S b S   R = S ∅ a S . la grammaire G = {{S}. on peut lui associer une C-grammaire très voisine où ces dérivations ont disparu (sauf éventuellement une si L contient {^}) 3.

Forme normale de Backus (BNF) Lorsqu’on vous présentera les règles de syntaxe d’un langage de programmation.S2}. and. c’est l’analyseur lexical qui vérifie que l’agencement de ces caractères constitue bien une unité lexicale du langage. Cette façon de noter les règles est très proche de celle que l’on a utilisée jusqu’à présent. <>. la flèche ‘→’ sera remplacée par ‘::=’ . #KJ? AJ B=EP HAO NdCHAO AJ EP=HEMQA JYKJP L=O a =LL=N=gPNA @=JO H= CN=II=ENA Du langage au programme Page 3. /.S1. Par exemple.c}.… Ils apparaissent ici sous une forme atomique (indivisible. ).6. -. >=. On distingue aussi des éléments tels que <=. soit une grammaire simplifiée des expressions arithmétiques décrite sous forme de BNF : <expression> ::= <expr_simple><fin_expression> <fin_expression>::= ^|<comparateur><expression> <expr_simple> ::= <terme> <fin_expr_s> <fin_expr_s> ::= ^|<oper_add><expr_simple> <terme> ::= <facteur><fin_terme> <fin_terme> ::= ^ | <oper_mult><terme> <facteur> ::= (<expression>) | nombre <oper_add> ::= + | .R} où l’ensemble des règles s’écrit:  R=  S1 S1 ∅ a S2 b S1 S1 ∅ a 1S S1 ∅ c S2 ∅ a S2 b S2 S2 ∅ c  . on distingue les éléments du vocabulaire terminal (ceux qui ne sont pas entre ‘<’ et ‘>’ ) tels que (. 15 . “ d’un seul morceau” ) alors qu’ils sont composés de plusieurs caractères. on le fera en utilisant la forme normale de Backus (Backus Normal Form). Les éléments du vocabulaire auxiliaire seront caractérisés en les encadrant par les caractères ‘<’ à gauche et ‘>’ à droite . *.Considérons la phrase ‘aacbc’.| or <oper_mult> ::= * | / | and | div | mod <comparateur> ::= =|<|>|<=|>=|<> Dans cette grammaire.b. Celle-ci peut être obtenue selon les deux dérivations montrées ci-dessous. S a S a S b S a S c a S S b S c c c Afin de supprimer cette ambiguïté on peut transformer la grammaire en la redéfinissant ainsi : G = {{S1. En fait.2.  a a S1 S2 c La dérivation unique étant alors: b S1 c 3. +.… Ce sont des items lexicaux pouvant apparaître dans une <expression>. mod. les alternatives seront séparées par ‘|’.4.{a.

par “ phrase” . signés ou pas. c’est ce qui explique la dérivation terminale nombre. l’analyseur syntaxique va considérer la succession de ces unités lexicales typées afin de vérifier si elles constituent des unités syntaxiques compatibles avec les règles syntaxiques. Fonctionnement d’un analyseur syntaxique. On notera que la formulation de <terme> + <fin_terme> dans la grammaire du premier type peut aussi être représentée par la forme itérative. En cas de succès.4. dans l’ exemple qui nous intéresse ici. par exemple. De la même manière que l’analyseur lexical procède à l’analyse d’un texte. $J @Y=QPNAO PANIAO HAO PANIEJ=QT @A HY=J=HUOAQN OUJP=TEMQA OAN=EAJP AJ B=EP HAO PULAO =OOK?EeO =QT QJEPeO HATE?=HAO NA?KJJQAO L=N HY=J=HUOAQN HATE?=H Exact. * Page 3. Plus précisément. caractère par caractère.… ). mult. l’analyseur va devoir contrôler qu’une dérivation de l’axiome au moyen des règles de grammaire s’identifie à la phrase. En fait. On remarque que certaines des règles (<expr_simple> et <terme>) de notre exemple peuvent s’écrire sous une deuxième forme sans modifier le langage engendré : <expr_simple> ::= <terme> |<terme><oper_add><expr_simple> <terme> ::= <facteur> | <facteur><oper_mult><terme> 1 Ici. © Jacques Guizol . Par exemple : nombre facteur ( terme expression ) terme facteur facteur op.2. 3. On constate les deux formes que peut prendre la dérivation de <terme>: soit la forme directement tirée de la règle qui fait apparaître une récursivité sur <terme>. terme ou facteur op. qui correspond en fait à une catégorie syntaxique définie au niveau lexical par des règles de construction des entiers ou des rationnels. chaque entrée lexicale pourrait être typée par la catégorie syntaxique à laquelle elle appartient et c’ est elle qui jouerait l’ interface entre analyseur syntaxique et analyseur lexical. disposant d’une “ phrase” *. la phrase est reconnue valide dans le langage défini . ATTENTION : Le fait qu’ une grammaire comporte une ou plusieurs règles présentant des alternatives qui débutent par un même symbole ne veut pas dire pour autant qu’ elle soit ambiguë au sens où on l’ a défini en 3. il faut entendre une dérivation terminale de l’axiome du langage considéré (phrase en français. Une autre représentation peut être faite en figurant chaque règle par un diagramme syntaxique. elle est déclarée invalide.4. en cas d’échec. la dérivation est unique. mult.16 Ce terme de phrase a été choisi en référence à une langue (et non pas un langage). soit une forme plus synthétique engendrant une simple boucle sur <facteur>.2 On peut même aller plus loin ! En supposant que l’ analyseur lexical associe un type à chacun des mots qu’ il est supposé reconnaître. Ici. mais les règles sont devenues non déterministes puisque les alternatives débutent par des dérivations identiques (en gras). programme en langage Pascal. pour reconnaître les unités lexicales.3.5. les dérivations en l’élément {^} ont disparu.

3. En particulier. la dérivation qui a permis de les générer. Hélas. tant en complexité (et donc temps de calcul) qu’en taille mémoire nécessaire.Avant d’aller plus loin. Les grammaires LL(k) et LR(k) Tout d’abord. notée ⇒ ou de d-dérivation. cela se traduit par une construction déterministe de l’arbre de dérivation. On parlera respectivement de g-dérivaG D tion. S ⇒ * w. soit commence par un non terminal. VT. abc est la partie fermée de α et ^sa partie ouverte. En terme d’arbre de dérivation. sans ambiguïté. On dit que x est la partie fermée de α et β est la partie ouverte de α. 3. S. où x est dans VT*. donc. ayant construit un arbre de dérivation partiel wAα. va donner en utilisant la même série de dérivations : G G G G S (1)⇒ SaSB (1)⇒ (SaSb) aSb (2)⇒ aSbaSb (2)⇒ abaSb (2)⇒ abab (en priorité gauche) D D D D S (1)⇒ SaSb (1)⇒ Sa(SaSb)b (2)⇒ SaSabb (2)⇒ Saabb (2)⇒ aabb (en priorité droite). Par exemple si α = abacAaB.4. en partant de l’axiome et en descendant vers une chaîne terminale wxy. parmi lesquelles les aspects liés aux performances. R) et une chaîne obtenue par la g-dérivation S ⇒ *α telle que α = xβ. et β. on peut déterminer la règle utilisée pour dériver A. ce “ chouia de plus” correspondant au k prochains symboles de w. 17 . ont aboutit à la définition de nouvelles classes de grammaires particulièrement bien adaptées à l’analyse puisqu’elles autorisent la construction d’analyseurs déterministes ce que ne garantissaient pas les C-grammaires. cette efficacité a un prix : ces grammaires ne peuvent engendrer tous les C-langages. soit est ^. abac est la partie fermée de a et AaB. Les grammaires LL(k) : Pour introduire intuitivement ces grammaires LL(k). on peut construire la prochaine dérivation. alors connaissant w et les k premiers symboles de xy.1. Le deuxième caractère L (resp. Enfin. obtenir un analyseur de ce langage en est une autre. k désigne le nombre de symboles de la chaîne terminale qui doivent être visibles pour déterminer. Si α = abc. Par exemple S::=SaSb (1)|^ (2). Définition préliminaire : G Soit une grammaire G =(VA. Ces classes de grammaires ont un nom un peu barbare : ce sont les grammaires LL(k) et les grammaires LR(k). et c’est ce qui nous intéresse. en considérant seulement la partie fermée de αi et un “ chouia de plus” . Le premier caractère (L pour les deux) signifie que la chaîne terminale à analyser va être traitée à partir de la gauche (Left). elles permettent de représenter toutes les caractéristiques des langages de programmation qui sont définis habituellement par des C-grammaires. R) signifie que les dérivations vont se faire prioritairement à gauche (resp. notée ⇒ . si nous avons déjà obtenu S ⇒ G G G G α1 ⇒ α2⇒ … ⇒ αi tel que αi ⇒ * w. Du langage au programme Page 3. il convient de préciser en préambule que définir un langage en utilisant une C-grammaire est une chose . disons que pendant la G G construction d’une g-dérivation terminale. αi+1. Toutefois. à droite). De nombreuses raisons. de gauche à droite. alors.. que signifient ces sigles. sa partie ouverte.

R) où R est défini par : S ::= AB . B. C ::= ab . R) où R = {S::= aAaa | bAba . alors. A::=S|^. E ::= bba.  Soit la grammaire G = ({S. VT. D. {a. une grammaire G = (VA.S. Etudions les dérivations correspondantes en priorité droite de S . b}. s’il existe plusieurs règles du type Ai → β. A ::= b | ^}. soit la grammaire G = ({S. R) est dite LR(k) si étant parvenu en S ⇒* αβw. soit nous aurons aa qui ne peut être qu’une dérivation de S → aS. elle devient LL(1).R) où R={S::=aS |a} Cette grammaire engendre des phrases du type an.b}. G est-elle LL(k) ? Que vaut k ? Les grammaires LR(k) : Considérant seulement les dérivations en priorité droite. Elle n’est pas LL(1) . C. S. étant en présence d’un a de la chaîne terminale. D ::= bb . Donc G est LL(2). S. en effet. A. E}. S Réduction restant à réaliser α A Réduction en cours β Réduction précédemment effectuée u v k terminaux visibles w Par exemple. les k premiers symboles de w permettent de déterminer la règle Ai0 → β qu’il convient d’appliquer et donc réduire αβ en αAi0 .{a}. S.18 D © Jacques Guizol (1) (2) .S Dérivation précédemment effectuée : analyse de la portion w α A Dérivation à réaliser : analyse de la portion y Dérivation en cours : analyse de la portion x w x y k terminaux visibles Exemple : Soit G=({S}. Si par contre on considère 2 symboles terminaux. A::= a . qui provient de S → a. Si nous modifions légèrement les règles en les reécrivant : S::=aA . A}. D D ACD ⇒ ACbb ⇒ Aabbb  S ⇒ AB ⇒ D AaE ⇒ Aabba D Page 3. soit nous aurons a^.{a. on ne peut pas savoir s’il a été engendré par la règle S → aS ou par la règle terminale S → a. B ::= CD | aE . La règle B comporte deux alternatives CD et aE.

ceux auxquels on n’a pas encore trouvé d’ancêtre dans l’arbre de dérivation). puisque ce caractère. Dès que la g-dérivation aboutit à un symbole terminal qui est présent dans la fenêtre d’analyse. * Une telle gestion est dite “ LIFO” (Last In. ϕ. on voit bien que l’on ne peut déterminer l’alternative ayant donné naissance à la chaîne ab. nous supposerons que k vaut 1. les deux sont effacés. First Out). Vous entendrez surement un jour parler de file… La gestion d’une file est dite “ FIFO” (First In. en commençant par la dernière introduite * . la partie droite. nous avons ici αβw où α = A. 3. Ces informations mémorisées sont ensuite retrouvées et prélevées. Les deux types d’analyse vont utiliser la même technique de base. • si la règle n’est pas unique. à savoir une pile. Par contre. Initialisation : c’est donc l’axiome qui est placé dans la pile. Les grammaires LL permettent d’opérer de façon déterministe une analyse descendante (de l’axiome vers la phrase). b. Nous essayons de réduire ab. il y a erreur. servant à stocker des informations dans un ordre bien précis (quel que soit le critère qui régit cet ordre qui est alors représenté par l’ordre de stockage). une fenêtre d’analyse contient les k premiers symboles de la chaîne terminale qui n’ont pas été encore traités (c’est à dire. Dans ce qui va suivre. la phrase fait bien partie du langage.4. Term inaison : Si fenêtre d’analyse et pile sont vides simultanément l’analyse a réussi. alors : • si l’unité lexicale TF apparaissant dans la fenêtre est concordante avec TP. Du langage au programme Page 3. la fenêtre d’analyse laissant apparaître la première unité lexicale de la phrase. en considérant 2 caractères de w. une erreur est signalée. oubliés . il sont supprimés des deux côtés.Pour reprendre les symboles utilisés dans la définition. le symbole TF visible à la fenêtre d’analyse doit permettre d’oter l’ambiguïté de choix de la règle puisque la grammaire est supposée LL(1). Par ailleurs. est identique pour les deux dérivations. alors que les grammaires LR permettent d’opérer de façon déterministe une analyse ascendante (de la phrase vers l’axiome). Chacun des deux types de grammaires que nous venons de présenter est plus particulièrement adapté à un type d’analyse. • si l’élément en tête de pile TP est un élément terminal.2. alors : • si la grammaire contient une règle unique du type TP → ϕ. En ne considérant qu’un symbole de w. First Out).3. 19 . Dans le cas contraire. on constate que la d-dérivation (1) est caractérisée par le couple bb alors que la d-dérivation (2) est caractérisée par le couple ba. Déroulem ent : à chaque instant. • sinon. Une pile est en fait un tableau supposé infini. β = ab et w = bb (pour la dérivation (1)) ou w = ba (pour la dérivation (2)). Analyse descendante Dans cette méthode d’analyse. est empilée . la pile contient la suite des symboles non terminaux et terminaux de la partie gauche de l’arbre de dérivation. • si l’élément en tête de pile TP est un élément non terminal. ce qui implique que nous allons considérer des grammaires LL(1) ou LR(1). Cette grammaire est donc LR(2).

Déroulem ent : à chaque instant. une erreur est signalée. De la même manière l’opérateur ‘+’ a été analysé et codé en le classant comme OP_ADD. Ce travail de l’analyseur lexical permet de communiquer à l’analyseur syntaxique toutes les informations qui lui seront nécessaires pour valider la phrase (le programme).Si nous considérons la phrase «123. PAS) ^ <fin_expr_s>(→ ^) ^ vide vide Remarques : • On notera qu’un type (entier ou réel) est associé à chacun des deux nombres pour constituer une unité lexicale appartenant à la classe ‘NOMBRE’. Page 3. • • si le membre droit d’une des règles de la grammaire est cohérent avec la suite des éléments qui sont en tête de pile.3. la fenêtre d’analyse laisse apparaître la première unité lexicale de la phrase. l’analyse n’aurait pas pu être déterministe. Analyse ascendante Initialisation : la pile est vide. sinon l’élément terminal apparaissant dans la fenêtre est empilé. il aurait fallu disposer d’au moins 2 symboles terminaux pour découvrir l’éventuel <op_mult> qui aurait permis d’opérer un choix pertinent .4) NOMBRE(reel.2. Term inaison : Si la fenêtre atteint le dernier symbole à analyser alors que la pile se réduit à l’axiome. PAS) NOMBRE(entier. 123.6 dans sa première formulation. Il est en effet trivial de démontrer qu’elle est LL(1). 123.4) NOMBRE(reel. 123. pour cette analyse descendante. En (*) il eût été impossible de déterminer quelle dérivation de <terme> était pertinente car la dérivation terminale de <facteur> correspondant à au moins un symbole (beaucoup plus si <facteur> se dérive en ‘(<expression>)’ ).4) OP_ADD(+) OP_ADD(+) OP_ADD(+) OP_ADD(+) NOMBRE(entier. la phrase a bien été engendrée par la grammaire considérée. la grammaire que nous avions introduite en 3.3.4) NOMBRE(reel. Si par contre. L’élément suivant apparaît dans la fenêtre (décalage). l’analyse a réussi. 3.4 + PAS». 123.4. alors ceux-ci sont remplacés dans la pile par le membre gauche de la règle sélectionnée (réduction). on a : Pile Elément de fenêtre d’analyse <expr_simple>(→ <terme><fin_expr_s>) <terme>(→ <facteur><fin_terme>) (*) <facteur>(→ <nombre>) <nombre> <fin_terme>(→ ^) ^ <fin_expr_s>(→<op_add><terme><fin_expr_s>) <op_add> <terme>(→ <facteur><fin_terme>) <facteur>(→ <nombre>) <nombre> <fin_terme>(→ ^) NOMBRE(reel. Dans le cas contraire. PAS) NOMBRE(entier.4. nous avions opté pour sa seconde forme. • Nous avons choisi.20 © Jacques Guizol . Cette seconde forme de la grammaire n’est pas LL(1).

4.4) OP_ADD(+) OP_ADD(+) OP_ADD(+) NOMBRE(entier. Au retour de celle-ci. 21 . L’analyseur syntaxique Ayant présenté dans le paragraphe précédent les méthodes d’analyse utilisables pour des grammaires LL(k) et pour des grammaires LR(k).4. à la seconde qui ne l’était pas. qui était LL(1). Analyseur descendant La technique la plus utilisée pour écrire un analyseur descendant consiste à tirer partie de la récursivité des règles. c’est celle que nous avons adoptée pour l’analyse ascendante.PAS) vide <expr_simple> <terme> <expr_simple> <facteur> <terme> Niveau syntaxique <facteur> <nombre> <op_add> <nombre> Niveau lexical 123. Par contre.4 + PAS Remarque : • Pour l’analyse descendante nous avons préféré la première forme de la grammaire.4. il ne restera plus qu’à Du langage au programme Page 3.1.4. Par exemple. les tests permettant de vérifier la présence des terminaux éventuels du membre droit de la règle. cette seconde forme étant LR(1). la règle <expr_simple>::=<terme><fin_expr_s> va permettre de définir la procédure expr_simple qui va faire un appel à la procédure terme. 3.123. nous allons maintenant considérer la mise en pratique de celles-ci pour aboutir finalement à l’écriture de compilateurs. un <terme> sera réputé avoir été reconnu . 3.Pile Elément de fenêtre d’analyse <facteur> (→ <nombre>) <terme> (→ <facteur>) <op_add> <facteur> (→ <nombre>) <terme> (→ <facteur>) <expr_simple> (→ <terme>) <expr_simple> (→ <terme><op_add><expr_simple>) (R) (R) (D) (R) (R) (R) (R) NOMBRE(réel. Chaque membre gauche donne lieu à une procédure dont le corps est composé de deux classes d’instructions : • • les appels de procédures qui correspondent aux non-terminaux du membre droit de la règle (un ou plusieurs de ces appels pouvant éventuellement être récursifs).

variable terminal : item. fin_terme. la pile est donc implicite. si terminal = ‘(’ alors debut expression. debut si type(item_suivant) = op_add alors expr_simple. debut terme. rendu indispensable par la présence de l’opérateur. debut terminal := item_suivant. debut facteur. chaque procédure a pour rôle de reconnaître une unité syntaxique dont le niveau dans la grammaire correspond au niveau d’appel de la procédure correspondante . puis. si item_suivant • ‘)’ alors erreur fin sinon si type(terminal) • nombre alors erreur. si type(item_suivant) = op_mult alors terme.22 © Jacques Guizol . une ‘expression simple’ sera réputée reconnue et ce résultat sera utilisé par la hiérarchie de niveau supérieur. A l’exécution. une nouvelle <fin_expr_s> sera analysée (récursivité). variable terminal : item. si aucune erreur n’a été détectée entre l’appel de la procédure et son achèvement. debut terme. fin. fin. procedure expr_simple. pour chaque test d’occurrence d’un symbole terminal. procedure fin_terme. debut facteur. Ainsi. Si le test est positif. fin. pour des raisons de clarté. debut terminal := item_suivant. fin. puis de se positionner sur le suivant s’il existe en s’empressant d’oublier le précédent. fin. Par ailleurs. si terminal = ‘(’ alors debut expression. fin. procedure facteur. Grammaire 1re forme LL(1) Grammaire 2ème forme non LL(1) procedure expr_simple. procedure terme.traiter la <fin_expr_s> qui elle va débuter par un test d’occurrence d’un <op_add>. nous avons passé sous silence les appels à une procédure qui pourrait être appelée item_suivant et qui a pour fonction de présenter à l’analyseur. fin_expr_s. fin. procedure facteur. procedure terme. debut si type(item_suivant) = op_mult alors terme. il va y avoir recherche d’un <terme>. procedure fin_expr_s. si item_suivant • ‘)’ alors erreur fin sinon si type(terminal) • nombre alors erreur. l’item courant (représentant le contenu de la fenêtre d’analyse dont on a parlé précédemment). Page 3. si type(item_suivant) = op_add alors expr_simple. fin.

on commence par analyser un facteur commun au deux dérivations.4 + PAS Mais si l’analyseur est bâti selon ce schéma. si le symbole courant est un opérateur additif.3. c’est la table LR(k) d’initialisation. on est de fait dans la première alternative et l’analyse du terme s’achève. Toutefois. Dans la procédure terme. Le programme de l’analyseur suit donc cette grammaire. Cette analyse terminée. Considérons par exemple la règle contenant une récursivité à gauche <terme>::=<terme><oper_mult><facteur>. C’est comme si on avait pu tester en tête de procédure la présence de l’opérateur additif. Une de ces lignes. nous avons présenté une méthode basée sur les tables de transitions. nous pouvons utiliser le séquencement des instructions qui permet de retarder le choix. c’est celle-là qui nous permettait d’éviter de nous engager dans des impasses découlant d’un choix erroné d’alternative sur la base du seul terminal visible dans la fenêtre. on est de fait dans la seconde alternative et on demande l’analyse d’un terme de niveau inférieur. de la même façon que nous tirons partie de la récursivité des règles pour définir le programme d’analyse. chaque ligne Ti étant appelée ‘table LR(k)’.Remarques : • Lorsque nous avons présenté le principe général d’analyse descendante de ce langage. Nous montrons donc l’analyseur basé sur la seconde forme. Dans le cas contraire.4. elle peut tout à fait convenir pour procéder à l’analyse d’un terme du type 123. T0 est particularisée.2. Du langage au programme Page 3. 3.3. Le système d’analyse étant dans un certain état. nous avons opté pour la grammaire dans sa première forme car étant LL(1).4. un analyseur LR(k) n’est rien de plus qu’un ensemble de lignes dans une table générale. l’apparition d’un symbole dans la chaîne analysée place le système dans un autre état. • L’écriture de cet analyseur nous montre à quel point il est important de ne considérer que des récursivités à droite dans une analyse descendante. “ Sur le papier” . la procédure terme va se rappeler indéfiniment alors que l’état de la fenêtre d’analyse va demeurer inchangé…  "A MQE AOP OKIIA PKQPA =OOAV CeJ=JP Aucune grammaire comportant dans ses règles une récursivité à gauche ne peut être LL(k). par exemple.4 + PAS : <terme> <terme> <op_add> <facteur> <nombre> <facteur> <nombre> 123. Analyseur ascendant En 3. De façon générale. Nous allons présenter ici une méthode d’analyse de langage engendré par une grammaire LR(k) qui s’inspire de la précédente. où l’on traitait des analyseurs de langages réguliers. 23 .

Ici. 2) la fonction d’analyse f de la table en tête de pile est appliquée à u. les étapes 2.24 Si α = X1… Xr. et une fonction de déplacement g. les k premiers éléments de la chaîne d’entrée.Chaque table LR(k) comporte deux fonctions : une fonction d’analyse f. la chaîne à analyser est complète et la fenêtre comporte ses k premiers symboles. 7. ^. aabb. La fonction de déplacement g de T’ est appliquée à A pour déterminer la nouvelle table T’’ qui va être accédée. Ici. 6. ^. Enlever 2|a| symboles supprime de la pile à la fois les symboles de α et les tables LR appelées. b. d) si f(u) = ‘accepté’. On retourne en 1). a analyse déplacement b ^ S a b T0 2 X 2 T1 X X T1 S X A X T2 X T2 2 2 X T3 X T3 S S X X T4 T5 T4 2 2 X T6 X X T5 1 X 1 X X X T6 S S X X T4 T7 T7 1 1 X X X X X i = réduire en utilisant la règle i S = décaler (shift) X = erreur A = accepté Soit la grammaire LR(1) définie par les règles : [0] S’::=S .Ø) → (T0ST1. On calcule T’’ = g(S) dans la table LK de T2. 2) (1) → (T0ST1aT2. abb. • • Une fonction d’analyse f a pour argument une chaîne u de VT*k. la table LR(k) est T0. la pile est vide. contenus dans la fenêtre d’analyse . 4. a) si f(u) = ‘décaler’. On empile S. © Jacques Guizol . b. 3. [2] S::=^. Détaillons les étapes de l’analyse ascendante effectuée par l’analyseur défini ci-contre sur la chaîne terminale aabb : (T0. ST3aT4ST6bT7. de longueur |α| = r. 2221) (6) → (T0ST1aT2ST3. bb. c) si f(u) = ‘erreur’. abb. produit de la réduction. 22) (3) → (T0ST1aT2ST3aT4. En particulier. alors 2|α| symboles * sont prélevés en tête de pile. Une table T’ est alors présente en tête de pile. réduits par application de la règle [1] S → SaSb. Une fonction de déplacement g a pour argument un symbole X de VA∪VT et prend pour valeur le nom d’une autre table LR(k) ou erreur. bb. * Page 3. A et T’’ sont empilés. l’analyse s’arrête. 5. 2) (2) → (T0ST1aT2ST3. la chaîne d’entrée est reconnue valide pour le langage. soit T3 qui est donc empilé avant de retourner en 1). 2221) (8) → (T0ST1. 8 répondent à ce cas. erreur ou accepté. l’analyse s’arrête. les étapes 1. 222) (5) → (T0ST1aT2ST3aT4ST6bT7. et que la règle i est de la forme A → α. Il reste donc en tête de pile T0ST1aT2. b) si f(u) = ‘reduire i’. Un message indique l’endroit où l’erreur est intervenue. réduirei (reducei). l’étape 10 répond à ce cas. 22) (4) → (T0ST1aT2ST3aT4ST6. [1] S::=SaSb . représentant la fenêtre d’analyse (de longueur k).aabb. l’étape (7) supprime en tête de pile 8 éléments. 9 répondent à ce cas. Ici. La valeur de f(u) est soit décaler (shift). 22211) (9) → Accepté (10) Fonctionnement : 1) Déterminer u. la tête de pile est da la forme T0X1T1X2T2… XrTr . On retourne en 1). A l’initialisation. 2221) (7) → (T0ST1aT2ST3bT5. alors le premier élément d’entrée est supprimé de la chaîne d’entrée pour être transféré en sommet de pile.

<tête_de_programme> ::= PROGRAM <identificateur> . traitant de l’analyse lexicale pour le premier. et 3._de_constante> <constante> ::= <nombre_sans_signe> ::= <signe><nombre_sans_signe> ::= <identificateur_de_constante> ::= <signe><identificateur_de_constante> ::= <chaine> <identificateur_de_constante> ::= <identificateur> Les nombres <nombre_sans_signe> ::= <entier_sans_signe> | <réel_sans_signe> <entier_sans_signe> ::= <chiffre> { <chiffre> } Du langage au programme Page 3. <bloc> ::= <partie_déclaration><instruction_composée> <partie_déclaration> ::= <partie_définition_de_constante> <partie_définition_de_type> <partie_déclaration_de_variable> <partie_déclaration_procédure_et_fonction> <instruction composée> ::= BEGIN <suite_d'instructions> END <identificateur> ::= <lettre> {<lettre_chiffre_ou_sslg>} <lettre_chiffre_ou_sslg> ::= <lettre> | <chiffre> | _ A | B | … … … | Z | a | b | … … … | z <lettre> ::= <chiffre> ::= 0 | 1 | 2 | .5. On peut dès lors présenter un langage de programmation avec lequel vous ferez vos premières armes. sous une forme très proche de la grammaire origine.Alors qu’un analyseur descendant LL(1) peut s’exprimer.3. 9 Partie définition de constantes <partie_définition_de_constante> ::= ^ | CONST <déf_de_constante> <déf. . qui précèdent. Avec ces deux paragraphes 3.4.  +PVTQFWEVKQP C NC RTQITCOOCVKQP 3.1. et de l’analyse syntaxique pour le second. <fin_déf. sous forme BNF. Nous allons tout d’abord donner ici._constante> <fin_déf._de_constante> ::= <identificateur> = <constante> . Programme <programme> ::= <tête_de_programme><bloc> . on l’a vu. des outils logiciels effectuent très facilement la construction automatique de ces tables. la syntaxe d’un sous-ensemble du langage que vous allez être appelés à utiliser pendant cette première année. Définition du langage Pascal. on constate que la structure de l’analyseur ascendant que l’on vient de montrer n’a plus aucun lien avec la grammaire correspondante. . 25 . nous avons donc “ bouclé la boucle” . Par contre. Nous n’aborderons pas ici le problème de constitution de la table dont la réalisation “ à la main” est le plus souvent difficile._constante> ::= ^ | <déf. la figure de la première page de ce chapitre est totalement explicitée.

<constante > <identificateur de type> ::= <identificateur> <type_structuré> ::= <type tableau> <type_tableau> ::= ARRAY [ <type_index> ] of <type> <type_index> ::= <type_simple><liste_type_index> <liste_type_index> ::= ^ | . <partie_déclaration_de_procédure_et_de_fonction> <déclaration_de_procédure_et_de_fonction>::= <déclaration_de_procédure> | <déclaration_de_fonction> Déclaration de procédure <déclaration_de_procedure> ::= <tête_de_procedure> <bloc> <tête_de_procedure> ::= PROCEDURE <identificateur> <section_paramètres_formels> .<réel_sans_signe> ::= <entier_sans_signe> . <liste_décl_variable> ::= <décl_de_variable> | <décl_de_variable> . <type_index> Partie déclaration de variables <partie_déclaration_de_variable>::= ^ | VAR <liste_décl_variable> . ::= <définition_de_type> | <définition_de_type> . <groupe_paramètres_formels> <groupe de parametres formels> ::= VAR <liste_de_paramètres_formels> | <liste_de_paramètres_formels> <liste_de_paramètres_formels> Page 3. <entier_sans_signe> E <exposant> | <entier_sans_signe> E <exposant> <exposant> ::= <entier_sans_signe> |<signe><entier_sans_signe> <signe> ::= + | - <chaine> ::= ’ <caractère> {<caractère> } ’ Partie définition de type <partie_définition_de_type> <liste_de_type> ::= <vide> | TYPE <liste_de_type> . <section_paramètres_formels> ::= ^ | ( <groupe_paramètres_formels> <fin_groupe_paramètres_formels> ) <fin_groupe_paramètres_formels> ::= ^ | .. <entier_sans_signe> | <entier_sans_signe> . <liste_décl_variable> <décl_de_variable> ::= <liste_ident> : <type> <liste_ident> ::= <identificateur> | <identificateur> .26 ::= <liste_ident> : <identificateur_de_type> © Jacques Guizol . <type> ::= <type_simple> | <type_structuré> <type_simple> ::= <type_intervalle> | <identificateur_de_type> <type intervalle> ::= <constante> . <liste_ident> Partie déclaration de procédure et de fonction <partie_déclaration_de_procédure_et_de_fonction> ::= ^ | <déclaration_de_procédure_et_de_fonction> . <liste_de_type> <définition_de_type> ::= <identificateur> = <type> .

<instruction> . <type_du_résultat> ::= <identificateur_de_type> Les instructions disponibles <suite_d'instructions> ::= <instruction> .| or <terme> <facteur> | ::= <terme><opérateur_multiplicatif><facteur> <opérateur_multiplicatif> ::= <facteur> ::= * | / | div | mod | and <variable> | <constante_sans_signe> | <désignateur_de_fonction> | ( <expression> ) | not <facteur> <constante_sans_signe> ::= <nombre_sans_signe> | <chaîne> | <identificateur_de_constante> | nil <désignateur_de_fonction> ::= <identificateur_de_fonction> | <identificateur_de_fonction> ( <liste_param_effectif> ) Du langage au programme Page 3.<expression>} ] <variable_tableau> ::= <variable> Expressions <expression> ::= <expression_simple> | <expression_simple> <opérateur_de_relation><expression_simple> <opérateur_de_relation> ::= = | <> | < | > | <= | >= | in <expression_simple> ::= <signe_unaire><terme> | <expression_simple><opérateur_additif><terme> <signe_unaire> ::= ^ | <signe> <opérateur_additif> ::= + | . <suite_d'instructions> <instruction> ::= <instruction_simple>|<instruction_structurée> <instruction simple> ::= ^ | <instruction_d'affection> | <instruction_procédure> Les instructions simples <instruction_d'affectation> ::= <variable> := <expression> ::= <identificateur_de_fonction> := <expression> Variable <variable> ::= <identificateur> | <variable_indexée> <variable_indexée> ::= <variable_tableau> [<expression>{ . 27 .Déclaration de fonction <déclaration_de_fonction> ::= <tête_de_fonction><bloc> ::= FUNCTION <identificateur> <tête_de_fonction> <section_paramètres_formels> : <type_du_résultat>.

les effets de bord qu’ils peuvent cacher ou encore les structures Page 3. leur fonctionnement. certains objets ou certaines instructions qui viennent d’être présentés formellement et qui seront repris en TD seulement sous leur aspect utilitaire. <liste_de_cas> <cas> ::= <liste_de_constantes> : <instruction> <liste_de_constantes> ::= <constante> | <constante> . OTHERWISE <instruction> <autres_cas> Les instructions répétitives <instruction_répétitive> ::= <instruction_pour> | <instruction_répéter> | <instruction_tantque> <instruction_pour> ::= FOR <variable_de_controle> := <liste_de_pour> DO <instruction> <liste_de_pour> ::= <valeur_initiale> TO <valeur_finale> | <valeur_initiale> DOWNTO <valeur_finale> <variable_de_controle> ::= <identificateur> <valeur_initiale> ::= <expression> <valeur_finale> ::= <expression> <instruction_repeter> ::= REPEAT <suite d'instructions> UNTIL <expression> <instruction_tant_que> ::= WHILE <expression> DO <instruction> L’objet des paragraphes qui suivent est de vous montrer. sous un aspect plus orienté vers la machine.<liste_param_effectif> ::= <paramètre_effectif> | <paramètre_effectif> . Cela nous a paru nécessaire afin de bien comprendre leur organisation.28 © Jacques Guizol . <liste_de_constantes> ::= . <liste_param_effectif> <identificateur_de_fonction>::= <identificateur> Appel de procédure <instruction_procédure> ::= <identificateur_de_procédure> | <identificateur_de_procédure> ( <liste_param_effectif> ) <identificateur_de_procédure> ::= <identificateur> <paramètre_effectif> ::= <expression> | <variable> Les instructions structurées <instruction_structurée> ::= <instruction_composée> | <instruction_conditionnelle> | <instruction_répétitive> | <instruction_cas> <instruction_conditionnelle> ::= IF <expression> THEN <instruction> | IF <expression> THEN <instruction> ELSE <instruction> ::= CASE <expression> OF <liste_de_cas> <instruction_cas> <autres_cas> END <liste_de_cas> ::= <cas> | <cas> .

D. 3. Si toutes les variables ont des noms différents. s’obtient en effectuant des divisions entières successives de N par b2. plusieurs questions restent sans réponse.. Dans ces conditions. C. N:= N div b2.implicites. qui conditionnent néanmoins leur bonne marche. l’élément numéro j aura pour adresse : adresse(r[j]) = adresse(r[0]) + k x j = r + k x j. Nous avons vu dans le chapitre 2 la manière de transcrire en base b2 un nombre N donné en base b1. Nous avions évoqué les restes sous la forme r0.… .. A supposer que les 26 varia- bles soient suffisantes. rn. On pourrait imaginer d’avoir prévu autant de variables entières A. k. double précision… ). ou dans X. Ainsi la succession des restes obtenus étant r0. considérons un exemple concret. Chaque élément occupe un certain nombre de mots. pris dans l’ordre inverse. Mais là encore. alors Resultat sinon N mod b2. N div b2. réel. r. B.2. r[n]. r2. rappelons le. L’écriture en base b2 est la suite des restes obtenus. En supposant que c’est un programme qui déroule cet algorithme. il faudra distinguer chacun des cas selon que le dernier reste sera contenu dans la variable Z ou dans Y. sans parler de l’allure que va prendre le programme.… ou dans B ou dans A. r[2]. Cette solution rejoint naturellement la notation que nous avions adoptée pour présenter le problème. si N=0 debut B:= N:= fin. il aura donc fallu que leur valeur ait été mémorisée dès leur obtention. La solution réside dans la structure de tableau dont chaque élément va être atteint par le programmeur au moyen d’un indice. la distinction s’opère par un indice particulièrement bien adapté aux schémas itératifs de programmes. … . le caractère figé des noms de variables va poser de gros problèmes pour LES instructions d’impression. Pour avoir accès aux autres.. le nombre s’écrira rnrn-1rn-2… r1r0. r représente l’adresse effective du premier élément du tableau (r[0] si le premier indice est 0). r1. Donc si. Cette transformation. chaque itération va être dédiée à une variable en particulier. fin. 29 . On suppose donc que le nombre comportera au plus 26 digits de la base… Et s’il en comporte 27. le programme ci-contre prévoit 26 itérations (variables nommées de A à Z). le premier chiffre imprimé sera donc le dernier calculé. Du langage au programme Page 3. Les éléments d’un tableau sont rangés en mémoire les uns à la suite des autres. … . et donc pouvoir les imprimer. … Voyons à quoi ressemblerait alors la partie du programme calculant les restes : si N=0 debut A:= N:= fin. rn. selon son type (caractère. Cet exemple fait bien sentir la nécessité de pouvoir disposer d’une collection d’objets de même type dont la méthode d’accès serait plus adaptée à un traitement itératif que celle que nous venons de voir. r1.. r2. la structure de tableau nous permettra de manipuler les objets sous la forme r[0]... r[1]. N div b2. long entier. Les tableaux Pour illustrer ce qui va suivre. cela en supposant savoir calculer dans la base b1. est ici commun à tous les restes . ou plus ? “ Resultat” va avoir pour tâche d’imprimer les restes à partir du dernier calculé. alors Resultat sinon N mod b2. entier.5. . si N=0 alors Resultat sinon debut Z:= N mod b2. Par exemple. Le nom de variable. transparentes pour l’utilisateur.

l’adresse d’une quelconque ligne li = adresse(l0) + c x k x i où adresse(l0) = adresse(r0) = r. qui est en fait le j-ème élément de la i-ème ligne. La définition sous forme BNF de la syntaxe du langage Pascal a permis d’introduire deux types d’opérateurs : les opérateurs additifs et les opérateurs multiplicatifs. on en déduit son adresse : adresse(r[i. La figure suivante visualise l’arbre de dérivation de cette expression en fonction de la syntaxe proposée dans le paragraphe précédent. Par exemple : 1 2 4 5  3 0 2 7  1  4  6   ligne 0  ligne 1 peut se décomposer en :  ligne 2  ligne 3  1 2 4 5 2 1 3 7 4 0 8 6 . 3. mais bel et bien de la définition du langage. le premier facteur du terme droit est évalué à 5. Si c est le nombre 8 d’éléments par ligne.j]) = r + c x k x i + j x k = r + k x (c x i + j) On vérifie.2]=r[0. Supposons par exemple que nous écrivions l’expression 3*2+5*(4+2). La connaissance de la priorité de ceux-ci est nécessaire au programmeur pour qu’il ne fasse pas d’erreur dans l’écriture des expressions. L’évaluation va suivre la même démarche que celle adoptée par la dérivation : • Pour évaluer l’expression totale. qu’ici.3.0] + 3x2 + 2 = r + 8 en supposant k = 1.j]. le terme gauche peut donc être évalué par 3*2. Nous allons voir que la priorité des opérateurs ne provient pas d’une convention arbitraire quelconque. © Jacques Guizol . Son évaluation va-t-elle s’opérer selon le schéma 3*(2+(5*(4+2))) avec pour résultat 96 ou selon le schéma ((3*2)+5)*(4+2) avec pour résultat 66 ou selon encore d’autres schémas calculatoires mettant en jeu d’autres priorités inter-classes (d’opérateurs) et intraclasse*. on va considérer une implantation en mémoire ligne par ligne. son évaluation va passer par celle de deux termes (de niveau 3) qui seront ensuite additionnés . •• * Page 3. soit 6. Si nous considérons à présent l’élément r[i. Si le tableau comporte deux indices. gauche-droite pour la seconde. par exemple.5. Expressions : hiérarchie des opérateurs. l’adresse de l’élément r[2. chaque utilisation d’un tableau apparaissant dans un programme source sera traduite dans le code par un adressage indexé mettant en œ uvre un calcul de ce type. •• l’évaluation des facteurs du terme gauche donne 3 pour le premier et 2 pour le second .30 Les deux évaluations proposées ici supposent des opérateurs de même priorité et un sens d’évaluation droite-gauche pour la première.Ainsi. Le deuxième facteur étant une expression entre parenthèses. il faut d’abord évaluer chaque terme (de niveau 1) qui la compose puis ensuite les additionner . •• l’évaluation du terme gauche nécessite l’évaluation de chacun de ses facteurs (de niveau 2) qui seront ensuite multipliés . de même pour l’évaluation du terme droit (on pourrait d’ailleurs imaginer que ces deux termes puissent être évalués simultanément sur une machine qui s’y prête) .

La partie droite est une expression dont le type doit être concordant avec celui de la variable affectée (on ne peut pas mélanger les torchons et les serviettes). Sa syntaxe est caractérisée par le signe d’affectation ‘:=’ qui s’insère entre ce qu’il est convenu d’appeler le membre gauche et le membre droit (de l’affectation). <expression> <expression_simple> <expression_simple> <op_additif> <terme> <terme> <terme> <op _multipl><facteur> <facteur> <terme> <op _multipl> <facteur> <facteur> <expression> <cte_ss_signe> <cte_ss_signe> <expression_simple> <cte_ss_signe> <expression_simple><op_additif><terme> <terme> <facteur> <facteur> <cte_ss_signe> <cte_ss_signe> 2 3 * 1 2 2 + 2 5 * 2( 1 34 + 32 ) En définitive. L’affectation va avoir pour conséquence de ranger à l’adresse mémoire correspondant à la variable du membre gauche.4. grâce à l’arbre de dérivation. • Les deux termes de niveau 2 sont à présent évalués.6. Le résultat serait-il le même en considérant la définition de ‘expression’ donnée au § 3. 52 (86-34). 3. la Du langage au programme Page 3. 73 (52+21) et enfin 69 (73-4).   En fonction de ce qui vient d’être dit et en considérant la grammaire donnée précédemment pouvez-vous imaginer quel type de compilateur va analyser vos programmes. soit 6.••• Ces deux termes sont évalués à 4 pour le premier et à 2 pour le second.2. L’instruction d’affectation C’est certainement l’instruction qui intervient le plus souvent dans un programme.4. qu’un opérateur multiplicatif est prioritaire sur un opérateur additif (puisque le premier est introduit à un niveau inférieur au second) et à niveau de priorité égale. on constate. Enfin. 31 . L’expression prend donc pour valeur 4+2. soit 5*6 = 30. l’évaluation s’opère de gauche à droite : 86-34+21-4 passera par les résultats successifs 86. Le membre gauche se limite toujours exclusivement à une variable. l’expression de niveau 1 a donc pour valeur 6 + 30 = 36. •• On peut donc à présent évaluer le terme droit de l’expression générale. LL ou LR ? En supposant que les calculs entiers sont effectués sur 16 bits pouvez vous indiquer le résultat de l’évaluation de l’expression 76*431+85-623.5.

5. une simple impression… I:=C[I] until I = 0. A := A . Page 3. aller à ‘boucle’. il est représenté par un tableau C construit de telle façon que dans la case n°i figure le numéro du successeur de l’élément i. jusqu’à <expr_booléenne>. Si on désirait que le chaînage soit bouclé. considérons une liste d’éléments chaînés • → • → • → • → • → • . Si elle n’a pas été sauvée dans un autre emplacement mémoire. 2 5 1 6 4 3 Chaque élément est représenté par un nombre strictement positif .B. Ainsi le chaînage peut être restitué par la portion de programme : I:= 2. mais dépend d’un changement d’état (état est pris ici dans le sens “ état de la mémoire” et en particulier valeur des variables intervenant dans le test de fin de boucle). elle est définitivement perdue.B . le test doit être vérifié pour “rester dans la boucle” alors que dans le second cas. On remarque que dans le premier cas. Payez-vous une gauffre en démolissant ce trait de génie ! (concentrez votre attention sur la première des trois instructions) 3. La différence essentielle entre l’instruction “ tant que” et l’instruction “ répéter” provient du fait que la première effectue un contrôle à priori. la valeur qui était précédemment contenue a totalement disparu. … a pour signification : … a pour signification : boucle: si <expr_booléenne> alors boucle: <instruction> . B := A . ces deux instructions permettent de gérer des portions de programme itératives pour lesquelles le nombres d’itérations n’est pas connu à l’avance.  si non<expr_booléenne> alors aller à ‘boucle’. Les instructions “répéter” et “tant que” A l’inverse de la boucle “ pour” que nous verrons ensuite. A titre d’exemple. pense avoir trouvé la technique du siècle en proposant la séquence A := A + B . repeat <exploiter élément I> La mention <exploiter élément I> représente le traitement qu’est supposé effectuer le programme sur chacun des éléments chaînés.  En fonction de ce qui vient d’être dit pouvez-vous indiquer la séquence des instructions qui réalisent l’échange des valeurs de deux variables de type numérique ? Un petit génie. quant au chaînage. <instruction> . D’où le codage : n° case 1 2 3 4 5 6 valeur 6 5 0 3 1 4 On constate que le dernier élément du chaînage reçoit pour code de successeur la valeur 0. il suffirait de donner à la case C[3] la valeur 2.32 © Jacques Guizol .valeur résultant de l’évaluation de l’expression du membre droit.5. comme il y en beaucoup. Le programmeur décidant de remplacer une boucle “tant que” par une boucle “répéter” ou réciproquement. devra donc veiller à inverser le test. la validité du test signe l’ arrêt de la boucle. alors que la seconde évalue le test à posteriori. Par exemple. La différence est bien montrée dans le tableau suivant : Sémantique de l’instruction “ tant que” Sémantique de l’instruction “ répéter” while <expr_booléenne> do répéter <instruction> <instruction> . En conséquence.

(test) fin. faites un petit programme Pascal de 4 ou 5 lignes mettant en évidence le fonctionnement effectif de la boucle “pour” dans la version Pascal que vous utilisez. cela en opposition avec les cas où la valeur de l’indice de boucle intervient dans la ou les instruction(s) régie(s) par la boucle. les instructions seront exécutées au moins une fois (S prend alors la valeur 1). Ce qui distingue ces deux modes de fonctionnement se résume à la place du test dit “ de fin de boucle” qui compare l’indice de boucle avec la valeur limite. peut-on déterminer ce que va valoir S si avant le début de la boucle. Les sous-programmes (procédures et fonctions) Dans ce qui suit. Boucle non muette : P = ∏ i = N! i =1 P:=1. pour quelle raison doit-on initialiser la variable résultat ? Un dernier point à préciser au sujet de cette instruction. $D 3=JP MQA RKQO U fPAO NAI=NMQAV HAO EJOPNQ?PEKJO @YEJEPE=HEO=PEKJ MQE LNe?d@AJP HAO >KQ?HAO "D=MQA BKEO MQYQJA R=HAQN NeOQHPA @YQJ ?=H?QH EPeN=PEB H= R=NE=>HA H= NA?AR=JP @KEP fPNA EJEPE=HEOeA 5KQO NAI=NMQANAV MQA HKNOMQYEH OY=CEP @YQJA OKIIA H= R=HAQN EJEPE=HA AOP  HKNOMQYEH OY=CEP @YQJ LNK@QEP ?YAOP  +YeHeIAJP JAQPNA ca RKQO N=LLAHHA MQAHMQA ?DKOA   Au fait. les instructions contenues peuvent ne pas être exécutées (c’est le cas où la somme S prend la valeur 0). for i:=1 to N do for i:=1 to N do S:=S + 1.6.3. i:=i + 1. étudions les deux types de fonctionnement envisageables : Fonctionnement n°1 Fonctionnement n°2 i:=1.7. Boucle muette : S = ∑ 1 = N i=1 S:=0. En reprenant le morceau de programme précédent calculant la somme S. Du langage au programme Page 3. 33 . Tout dépend de la “ sémantique” de “ pour” . tant que i <= N faire (test) répéter début <instruction> <instruction> i:=i + 1. jusqu’à i > N. Une boucle est dite muette si l’indice de boucle a pour seule utilité de décompter le nombre d’itérations . i:=1.5. Pour cela. Le test de fin de boucle est-il fait en entrée ou en sortie ? 3. nous confondrons procédure et fonction sous leur nom générique de sous-programme. S va-t-il valoir 0 ou 1. ainsi que leur mode de fonctionnement. En utilisant les deux autres instructions répétitives vues précédemment. P:=P * i.5. La boucle “ pour” L’utilité de la boucle “ pour” intervient dans la mise en œ uvre de calculs itératifs où le nombre d’itérations est connu à l’avance. N vaut 0. Si ce test s’opère en début de boucle. Si par contre le test est effectué en fin de boucle. Le but de ce paragraphe est de montrer les intérêts (évidents et cachés) des sous-programmes.  Dès que vous en aurez l’occasion.

Deux possibilités s’offrent à cet utilisateur. fin.34 © Jacques Guizol . La séquence de calcul de la suite convergente définie par :   Xn+1 = 2 Xn + Xn    racine peut s’écrire : RACCAR : X:=1.1. et sur la gestion de la mémoire en cours d’exécution. avant traduction. le programmeur aspire légitimement à soulager à la fois le texte source et luimême en ne la faisant apparaître qu’une fois.Y. Le programmeur est soulagé. par exemple.7. utilisant en deux endroits ce calcul. en deux endroits. La différence fondamentale qu’il y a entre macro et sousprogramme est que la première ne constitue qu’une simplification d’édition de texte. De la“macro” au sous-programme. utilise le calcul d’une racine carrée. si ABS < 0 alors ABS := -ABS. ABS := X . Lorsqu’une série d’instructions intervient en plusieurs endroits dans un programme. soit il crée un sous-programme. En fait une macro étant définie par une suite de caractères qui la compose. 3. Y et ABS étant modifiées. mais aussi sur la constitution et la taille du code. Considérons. Cette notion de macro ayant été introduite. alors que la seconde a non seulement des répercussions sur l’écriture du source. La valeur A est la limite (si A• 0) de la  X0 = 1  1 A  . X := Y. leur valeur initiale a dû être sauvegardée si cette modification doit poser problème au reste du programme. à chaque endroit où son nom apparaît. On remarque que les variables étant fixées une bonne fois pour toutes. • que les variables X. • qu’en fin de calcul le résultat sera contenu dans la variable X. ABS:=1. un programme qui. peut donc se schématiser comme suit: Page 3. tant que ABS >=1E-4 faire début Y := (X + A/X)/2. l’exécution de cette portion de programme présuppose… • que la variable A a été préalablement affectée à la valeur dont on veut déterminer la racine carrée.nous allons opérer une transition entre un programme où une portion apparaît identique à elle même en plusieurs endroits et un programme équivalent doté du sous-programme correspondant. Soit il crée une macro contenant les instructions à dupliquer. celui-ci sera remplacé in-extenso par les caractères qui la définissent. utilisons la pour bien faire comprendre celle de sous-programme. La structure du programme complet.5. mais le programme source n’en est pas simplifié pour autant.

Il faudrait donc qu’en E1 se trouve une instruction “ vers PP3” . On est donc amené à placer en E1 une instruction de cas : selon APPELANT faire 1 : vers PP2 . on doit exécuter la portion n°3. 35 . après la dernière instruction de la portion n°1. Séquence de calcul de la racine (2) … Portion n° 3 A ce niveau. Il faudrait donc que l’instruction se trouvant en E1 diffère selon l’endroit où l’appel s’est opéré dans le programme. la séquence donnée plus haut sera effectivement reproduite dans le fichier source. On obtient finalement le programme suivant. il convient de préciser que. sera exécuté le calcul adressé par RACCAR.… Dans la visualisation ci-contre. à 1 en fin de portion n°1. l’exécution doit se poursuivre par la portion n°2. “ l’instruction provoquant le calcul de la racine” va être en fait “ aller à RACCAR” . si ABS < 0 alors ABS := -ABS. Pour mieux comprendre ce qui va suivre. représentation symbolique d’une adresse dans le programme. à 2 en fin de portion n°2. pour provoquer le calcul de la racine. On y parvient en plaçant en E1 une instruction “ vers PP2” . Mais l’explication terminée. son utilisation est non seulement superflue. mais même à proscrire. … PP2 : Instruction provoquant le calcul de la racine (1) … PP3 : Portion n° 1 Portion n° 2 Instruction provoquant le calcul de la racine (2) … Portion n° 3 RACCAR : X:=1. Celui-ci terminé. Il est bien évident que ceci ne constitue pas une instruction du langage et qu’en ces endroits. ABS:=1. Portion n° 1 Séquence de calcul de la racine (1) … Portion n° 2 Essayons d’éviter cela en utilisant des instructions permettant de dérouter l’exécution vers la portion calculant la racine.Y. tant que ABS >=1E-4 faire début Y := (X + A/X)/2. bien que Pascal possède l’instruction de rupture de séquence inconditionnelle. X := Y. une instruction “ vers RACCAR” permet d’aboutir à ce calcul. 2 : vers PP3 . En fin de portion n°2. Or nous y avons précédemment placé une instruction “ vers PP2” . les occurrences du calcul de la racine sont des appels macro-éditeur. on désire à nouveau effectuer un calcul de racine. nous nous empresserons d’oublier l’existence même de cette instruction !! Ce préambule étant fait. nous allons néanmoins utiliser ce type d’instruction. Là encore. où APPELANT est une variable qui sera affectée avant chacune des instructions “ vers RACCAR” . Lorsque celuici se termine. à l’étiquette E1. nous ne l’avons pas mentionnée dans la grammaire donnée plus haut car Pascal étant un langage structuré. fin. Ainsi. ABS := X . fin. Du langage au programme Page 3. E1 : FIN : fin de programme Après la portion n°1. on suppose donc disposer d’une instruction permettant de déplacer l’exécution vers un endroit précisé par ce que l’on appellera une étiquette.

fin. On remarque déjà que la partie terminale étiquetée par E1 n’a rien à voir avec le calcul d’une racine carrée. 2:vers PP3. versMOYRAC. X := Y. pour obtenir enfin : Exécution Portion n°1 MOYRAC: APPELANT := 1. on va donc transformer cette suite d’instructions .… PP2 : RACCAR : X:=1. PP2 : RACCAR: Exécution Portion n°2 APPELANT := 2. Le calcul se fait sur A MOYRAC : A := U. vers RACCAR. le résultat se trouvant à la fin du calcul dans la variable R. RSP1: suivant APPELANT faire 1:vers RSP1. E1 : selon APPELANT faire 1 : vers PP2. APPELANT := 2. PP3 : Exécution Portion n°3 Page 3. comme on l’a fait tout à l’heure. vers RACCAR. 2:vers RSP2. versRACCAR. Elle n’est rendue nécessaire que pour conserver à l’exécution la séquentialité désirée. FIN : fin de programme APPELANT := 1. 2:vers RSP2. RACCAR: APPELANT := 1. Supposons que le programme utilisant à deux reprises le calcul d’une racine carrée effectue en fait la moyenne des racines carrées de deux nombres U et V. le reste constituant le programme principal. fin. Si on désire calculer la moyenne de deux racines carrées à plusieurs endroits dans un programme. vers RACCAR. 2 : vers PP3. fin. … Portion n° 3 La partie de programme qui se situe entre les étiquettes RACCAR et E1 comprises va devenir le sous-programme. RSP2 : R := (RU + X) / 2. © Jacques Guizol suivant APPELANT faire 1:vers RSP1. … PP3 : Portion n° 1 Portion n° 2 APPELANT := 2. si ABS < 0 alors ABS := -ABS. tant que ABS >=1E-4 faire début Y := (X + A/X)/2. ABS := X . RSP2: suivant APPELANT faire 1:vers PP2. ABS:=1. APPELANT := 1. versMOYRAC. Elle devra être modifiée si le nombre et/ou la place des appels sont modifiés.Y. vers RACCAR. versRACCAR. fin. RSP1 : RU := X.36 APPELANT := 2. Le résultat est dans X A := V. . fin.

PP3: .. Mais cette solution n’est encore pas satisfaisante.. RSP2: R:=(RU + X) / 2. vers MOYRAC. vont être traduits. RETOUR[NIVEAU]:=2.. En effet... Programme principal . Nous allons voir qu’en faisant cela. fin: Sous-programme MOYRAC MOYRAC:A:=U.. La valeur de APPELANT positionnée dans le programme principal à 1 est modifiée dans MOYRAC de telle façon qu’en fin d’exécution de MOYRAC.Considérons la figure précédente où l’on utilise la même variable APPELANT pour caractériser les adresses de retour. suivant RETOUR[NIVEAU] faire 1:vers PP2. lors de la compilation. Lors de chaque sortie de sous-programme une adresse va être dépilée et le pointeur d’instruction va être affecté avec cette valeur. elle vaut 2 alors qu’à l’appel de MOYRAC elle avait été affectée à 1. le retour.2. nous résolvons tous les problèmes en suspens et même le problème épineux des sous-programmes récursifs. De cette façon.. NIVEAU:=NIVEAU + 1. suivant RETOUR[NIVEAU] faire 3:vers PP4.. 5:vers RSP2.1). et c’est exactement ce qui va être fait. NIVEAU:=NIVEAU + 1. A chaque appel de sous-programme l’adresse de retour va être empilée. A:=V. de même que le retour d’un sous-programme. RETOUR[NIVEAU]:=5.. NIVEAU:=NIVEAU-1. Au fur et à mesure que l’on descend dans les niveaux. NIVEAU:=NIVEAU + 1.. RSP1: RU:=X. RETOUR[NIVEAU]:=4.2.2). Sous-programme RACCAR RACCAR:X:=1. 2:vers PP3. X:=Y. NIVEAU:=NIVEAU + 1. Donc la différenciation des variables ne pourrait plus convenir… 3. si ABS<0 alors ABS:=-ABS. On voit que le tableau RETOUR fonctionne comme une pile (voir § 3. Or.3. vers RACCAR. Il est donc incapable de réaliser ce qui est proposé ici. 37 . RETOUR[NIVEAU]:=3.. RETOUR[NIVEAU]:=1.. PP2: . . NIVEAU := 1.7. En fait. 4:vers RSP1. En fait il faudrait conserver simultanément les valeurs de APPELANT affectées par le programme principal et par le sous-programme MOYRAC.. On est donc appelé à utiliser des variables différentes pour chaque niveau d’appel. vers RACCAR. NIVEAU:=NIVEAU-1. mais aussi directement par le programme principal. tant que ABS>=1E-4 faire début Y:=(X+A/X)/2. vers MOYRAC. cette gestion est implicite. NIVEAU:=NIVEAU + 1... on dépile un nombre (on revient à la case précédente du tableau) que l’on interprète pour retrouver l’adresse à laquelle doit se poursuivre l’exécution au niveau supérieur. fin. par des instructions machines (Par exemple. l’utilisateur lorsqu’il programme en langage évolué est incapable de connaître l’adresse d’une quelconque instruction de son programme (voir § 3. La pile … A moins d’envisager comme variable un tableau dont l’indice serait le niveau d’appel.4.. que l’on remonte d’un niveau.5. on pourrait imaginer que RACCAR soit appelé par MOYRAC.. au lieu de s’effectuer vers PP2. L’appel de sous-programme apparaissant dans le programme écrit en langage évolué. PP4: .. se fait vers PP3. Ainsi toute la portion n°2 est ignorée. /HQPiP MQA @YAILEHAN @AO JKI>NAO MQA HYKJ @KEP EJPANLNePAN AJOQEPA LKQN @ePANIEJAN HAO =@NAOOAO ?KNNAOLKJ@=JPAO LKQNMQKE JA L=O AILEHAN @ENA?PAIAJP HAO =@NAOOAO  En effet. Du langage au programme Page 3. on empile des valeurs représentatives (on passe à la case suivante du tableau) des adresses de retour et à chaque fois que l’on termine un sous-programme et donc. ABS:=X-Y.. vers RACCAR..ABS:=1.

8 A:=V. voire au niveau de l’index (en phase de dépilement)..4. qui sera β au lieu de α.γ.. En fait ces signes n’apparaissent pas dans le programme. … ici α. α est empilé.ou RTS -Return SubRoutine). I passe à 3 (I:=I-1). α est empilé une 3ème fois. On dépile donc à nouveau une valeur. si ABS<0 alors ABS:=-ABS. RACCAR. β: . δ. Nous obtenons finalement un source composé des trois parties suivantes : Program Principal. α est empilée. gérer une pile propre à l’espace mémoire de la tâche en cours d’exécution. on suppose la pile vide. on dépile φ pour se retrouver à nouveau dans MOYRAC(6) qui s’achève. entre autre. RACCAR se terminant. 13 14 φ: R:=(RU + X) / 2. Enfin. 3ème niveau.. RACCAR. l’état d’une pile dépend de son index et du contenu des cases qui se situent en dessous. le premier appel à RACCAR survient. On est à nouveau dans MOYRAC qui appelle pour la deuxième fois RACCAR.. I passe à 2.α γ γ α α α α α β β β β β β 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Au départ. α est empilé une 4ème fois. 4ème niveau. Au premier appel de MOYRAC. l’appel direct à RACCAR est représenté par les états 14 et 15..sur Motorola. © Jacques Guizol . 15 fin_sp. est donc dépilée (4) et on revient dans MOYRAC à cette adresse. L’index progresse (2)... Nous avons précédemment présenté ce qu’est une pile (§ 3. Sous_Programme MOYRAC. adresse de retour dans le programme principal. I valant 0. fin. La figure ci-dessous montre les divers états de la pile en corrélation avec les instructions du programme précédent.. I passe à 1(I:=I+1). La deuxième exécution de MOYRAC va déboucher sur une succession semblable d’états (8 à 13) mis à part le premier élément de pile.. Ce mécanisme étant bien compris.38 Supposons que ce sous-programme soit appelé alors que I a la valeur 4. qui nous ramène dans le programme principal (7). Au 1er niveau. Sous_Programme RACCAR.7. fin. En fait.3. 7 δ: RU:=X. L’adresse de retour est dépilée. α: I:=I+1.. γ: . .δ et φ ne sont en aucune façon des étiquettes. BSR -Branch SubRoutine. 2èmeniveau. α.ABS:=1.. DEMO. On se retrouve au 3ème niveau en α. C’est l’interprétation de ces instructions machines qui va. on ressort du sous-programme.1). fin_sp. Généralisation : les sous-programmes récursifs. 6/12 ABS:=X-Y. 1 A:=U.... δ est empilée (3). I passe à 1. MOYRAC étant exécutée. Ils ne sont là que pour faciliter l’explication qui va suivre où ils représenteront une adresse d’instruction. X:=Y. l’index pointe sur la première case vide (1). Page 3. l’adresse de retour.β. On se retrouve au 4ème niveau en α. 2 X:=1.5. 3/9 tant que ABS>=1E-4 4/10 faire début 5/11 Y:=(X+A/X)/2. MOYRAC. φ est alors empilée (5). Voyons comment elle est gérée. 5ème niveau. 3. α est empilé une 2ème fois.3. I passe à 0. 2 RACCAR.. RACCAR s’achevant l’adresse la plus récemment empilée. considérons le sous-programme récursif suivant: Sous-Programme DEMO si I>0 alors début I:=I-1. fin_sp.. fin. α: . MOYRAC.

39 . enfin le quatrième permet de constater que N vaut 0 afin de stopper la récursivité. α. On revient au programme principal. les retours successifs du sous-programme RESTE. Il suffit pour cela de déclarer une variable R dans le sous programme. Variables locales Tout à l’heure. On constate qu’à chaque niveau. on peut l’utiliser pour stocker les valeurs successives de R calculées à chaque niveau d’appel. la valeur du reste a elle même été déposée. mais disposant de la pile d’adresses de retour.I passe à 2. vont donner accès. le troisième calcule (0. aux valeurs successivement empilées de R… dans l’ordre inverse de leur calcul ! La dernière adresse.1. I passe à 4. fin_sp. Etudions la suite des couples (N. si N>0 alors début R:=N mod B. si bien que l’on n’a accès qu’aux dernières valeurs calculées. Page 3. Soit N initialisé à 21 et B à 3. Nous pouvons adapter ce sous-programme en vue de lui faire calculer la conversion de N en base B. avant d’empiler les adresses de retour. On se retrouve au 2ème niveau en α. Il s’écrit donc : Sous_Programme RESTE. 3. le sous-programme RESTE calculant le reste de la division entière par B d’un nombre N en supposant que B et N aient été affectés préalablement au début d’exécution. Il aurait fallu que les valeurs successives de R soient sauvées. Certes.1). symétriques des appels. par exemple. via la pile. si nous reprenons le programme ci-dessus auquel nous avons rajouté la déclaration d’une variable entière R.2).7. N:=N div B. la pile a l’allure ci-contre. Considérons.2. le deuxième calcule (2. I passe à 3. renverra au programme principal. Or les valeurs successives de N et R ont été remplacées au fur et à mesure des appels de RESTE.4. par exemple. soit 210. la conversion de 23 en base 3.5. RESTE.0). Le premier appel calcule (7. nous avons vu que le fait que les variables utilisées par les sousprogrammes soient modifiées par ceux-ci. 0 pour N et 2 pour R.5. On aimerait bien alors pouvoir atteindre les valeurs successives calculées pour imprimer. β: fin. Ainsi. On se retrouve au 1er niveau en α. Mais nous allons voir que cette gestion des variables est très ressemblante à celle des adresses de retour et qu’en conséquence elles pourraient être gérées de la même façon si l’utilisateur le désire. en particulier en ce qui concerne la “ remontée des niveaux” . β 2 β 1 β 0 α Du langage au programme Dans ces conditions. on pourrait le faire dans un tableau comme on l’a proposé en 3. Ce petit exemple nous a permis de détailler le fonctionnement d’un sous-programme récursif. impliquait la nécessité de sauvegarder leur valeur si celle-ci s’avérait utile dans le programme appelant.R) calculés par ce sous-programme. lorsqu’au quatrième appel de RESTE on aboutit à une valeur nulle de N.

Les premières instructions du code du sousprogramme consisteront donc à dépiler ces paramètres pour pouvoir les utiliser ensuite.2) pour obtenir le sinus d’un angle x. cela ne vous servirait à rien car la bibliothèque ne contient que le code de ces sous-programmes qui ont été compilés séparément. peut bien entendu être étendue aux appels internes du programme utilisateur évitant ainsi les instructions d’affectation préalables. de façon dynamique. Certes on peut imaginer d’opérer. De plus. Lors de l’appel. on affectait la variable A à la valeur concernée et le résultat se trouvait dans X. Par contre.7. Bien entendu. La compilation va avoir pour effet d’en assurer la traduction de la façon suivante. Les paramètres de sous-programmes Un sous-programme peut avoir pour vocation d’être utilisable pour différentes valeurs de paramètres.40 Par exemple. Par exemple. Mais cette façon de faire implique une ou plusieurs instructions d’affectation préalables et dédie le sous-programme à ces variables.5. Ceux-là sont appelés paramètres effectifs. De la même manière. immédiatement après l’identificateur de celle-ci. entre parenthèses. Devinez comment on va y parvenir… 2E FA NeLKJ@O ^AJ QPEHEO=JP H= LEHA` FYAOLdNA MQA FA JA LAN@N=E L=O H= B=?A Effectivement. © Jacques Guizol . c’est notre bonne vieille pile qui va nous tirer de là ! Dans la définition de la syntaxe du langage Pascal. dans la définition de la procédure ou de la fonction. etc. celles des données sur lesquelles va opérer le sous-programme. Ils sont appelés paramètres formels. mais réalisable. le programme de transcription d’un nombre dans une base donnée peut être utilisé pour différents nombres et pour différentes bases. il est impossible à l’utilisateur de connaître les diverses adresses d’un programme et. Or. En effet. les paramètres à ces sous-programmes disponibles dans les bibliothèques. l’appel comportera ce même identificateur. ou le logarithme décimal d’un réel r. on l’a déjà dit. en particulier. comme on l’a fait jusqu’à présent.5. les variables éventuelles contenant le résultat* . et quand bien même vous le sauriez. l’adresse de retour demeure dans la pile pour être exploitée à la fin du sous-programme.2. dans le calcul de la racine carrée. en cas de sous-programme récursif. en affectant avant l’appel les variables nécessaires au déroulement du sous-programme et en exploitant. Cette façon d’opérer. ces affectations préalables devront se faire non seulement au niveau appelant. chacun des paramètres. que nous avons justifiée en référence aux appels à des éléments contenus en bibliothèque. il vous sera impossible de savoir comment ces sous-programmes ont été écrits. Il va donc falloir trouver un moyen “ universel” pour transmettre.3. Cette formulation de base est commune à tous les langages habituels. suivi. au retour. des paramètres qui sont nécessaires. mais aussi dans le sous-programme lui-même. * Page 3. Tout cela est bien pénible. on a vu que les paramètres étaient très facilement identifiables. ils apparaissent. la pile va recevoir successivement l’adresse de retour puis. lorsque vous allez utiliser des sous-programmes contenus dans une bibliothèque (voir § 3. encore une fois. ou la transposée d’une matrice M.

  A noter au passage que lorsque l’ ensemble des paramètres transmettant des résultats se réduit à un élément. tandis que RACCAR pourrait. MOYRAC pourrait ainsi devenir une fonction ayant deux paramètres (les valeurs dont on désire obtenir la moyenne de leur racine carrée). le paramètre résultat disparaît de l’ appel. être une fonction à un paramètre (la valeur dont on veut extraire la racine carrée). par exemple. la troisième. On vient de voir la technique permettant de transmettre les paramètres à un sousprogramme. C’est le cas typique d’un appel dit par valeur. Il va falloir qu’il puisse disposer d’un moyen pour cela. Si l’expression s’était réduite à une variable. Cette règle générale étant fixée. Supposons que le paramètre effectif soit une expression. Or le seul lien qu’il y a entre un sous-programme et le niveau appelant se limite aux seuls paramètres. il n’y a aucune raison apparente pour que cette variable soit modifiée. 2E ?A ?KILKNPAIAJP AOP OUOPeI=PEMQA JA LAQPEH L=O =RKEN @AO NeLAN?QOOEKJO LeJ=HEO=JPAO  En effet. cette variable aurait été évaluée (en chargeant le contenu de l’adresse correspondante) et c’est cette valeur qui aurait été placée dans la pile. C’ est l’ écriture de l’ appel lui-même (associé à ses paramètres d’ entrées) qui constitue un facteur dans une expression plus générale du niveau appelant. Les deux premières contraintes sont contrôlées au niveau de la compilation . Il est hors de question de considérer que cette place est dans la pile. Celle-ci présuppose une cohérence totale entre paramètres formels et paramètres effectifs en ce qui concerne le nombre de ces paramètres. leur type et leur ordre. considérons un paramètre en particulier. nous sommes volontairement resté vague sur celui-ci : on a parlé de transmission. En procédant de la sorte. Avant d’en terminer avec les paramètres des sous-programmes. Dans les exemples précédents. A noter encore : les paramètres sont donc transmis au sous-programme via la pile. En fait. en toute logique. 2E FA ?KILNAJ@O >EAJ HA LNK>HdIA OKQHARe PKQP a HYDAQNA †  OAHKJ HAMQAH EH B=HH=EP O=QRAC=N@AN HAO R=NE=>HAO OANR=JP a ?KIIQJEMQAN HAO R=HAQNO AOP a LNeOAJP EILHE?EPAIAJP NeOKHQ  Effectivement. Lui appartenant. Bien entendu. on pourra avantageusement donner la préférence à l’ utilisation d’ une fonction plutôt qu’ une procédure. elle aussi. Du langage au programme Page 3. de la même façon. Cela revient à considérer que l’on a fait une copie de la variable et c’est cette copie qui est transmise en paramètre. et cela est naturel ! L’utilisateur n’a pas à se préoccuper de ce qu’il va advenir à sa variable x lorsqu’il va calculer sin(x). cette dernière ne servant qu’ à l’ interfaçage sous-programme/appelant. mais l’utilisateur peut le modifier s’il le désire ! Supposons par exemple que l’utilisateur désire connaître les modifications supportées par un ou plusieurs paramètres au cours de l’exécution du sous-programme. 3*X+B. Pour lui. ces paramètres auront un statut de variables locales. Jusqu’ici. 41 . Le code déroulé pour effectuer l’appel comportant cette expression en paramètre va tout d’abord évaluer celle ci. Ils vont donc occuper une place en mémoire. d’empilement et de dépilement de paramètres sans préciser s’il s’agissait de leur valeur ou de leur adresse. il convient de préciser un dernier point concernant le mode de transmission de ceux-ci. il va les utiliser et donc les référencer par l’ intermédiaire des identificateurs correspondant aux paramètres formels. est laissée à l’attention de l’utilisateur. pour ensuite empiler la valeur obtenue dans la pile. elle ne peut subir que des modifications qu’il désire ou qu’il consent. et c’est bien pourquoi ce comportement est celui qui est adopté par défaut .

c’est la valeur qui sera recopiée dans la pile. soit son adresse ! Dans le cas où c’est l’adresse qui est transmise chaque référence du sous-programme à cette variable concernera cette adresse et non pas une adresse locale. et autres chausse-trapes du même genre… Espérons qu’à l’issue de ce “ parcours du combattant” vous aurez bien assimilé la règle. assez simple qui détermine la portée des identificateurs.42 © Jacques Guizol .6. Page 3. l’analyse des appels traduira la présence de ce terminal en générant un code effectuant un transfert de l’adresse du paramètre effectif vers la pile. par exemple. somme toute. alors que dans l’autre cas. d’embûches. les autres ne le seront pas. 3*X+B. Le principe étant trouvé.7. par adresse ou par nom. Visibilité (portée) des identificateurs Depuis tout à l’heure. Il est bien évident que le paramètre transmis ne peut pas être constitué par l’expression formelle qui ne prend valeur que lorsque ses variables en ont elles mêmes reçu une. consiste à l’évaluer. la valeur trouvée constituant le paramètre. Lors de la compilation. Ainsi toute modification agira directement sur l’adresse transmise et donc sur la variable du niveau appelant constituant le paramètre effectif. de variables du niveau appelant. il est bien évident qu’on ne pourra pas changer sa valeur.5. La syntaxe permet de faire la distinction au niveau de la définition des paramètres formels : les paramètres transmis par adresse seront précédés du symbole terminal VAR. Si c’est une expression telle que. et qu’on a vue précédemment. En effet.… On peut raisonnablement se poser les questions suivantes : «que se passe-t-il en cas d’homographie des identificateurs de variables ? Le conflit est-il toujours fatal ? Les différents niveaux de programme ont-ils leur importance en l’occurrence ?» Afin de tenter de répondre à ces questions et à d’autres que vous vous poserez sûrement dans votre apprentissage à la programmation. Si par contre le paramètre effectif est une variable. de variables passées en paramètres. comme on vient de le voir. de pièges.Si un paramètre effectif n’est pas une variable cela sera impossible. la seule chose que l’on puisse faire. 3. nous allons prendre un exemple constitué de morceaux de programme et sous-programmes que l’on s’est amusé à truffer d’erreurs. si c’est une constante. il ne reste plus qu’à définir une méthode permettant à l’utilisateur d’indiquer s’il entend opérer un appel par valeur (pris par défaut) ou. On est donc revenu au cas précédent. nous parlons de variables locales. on pourra transmettre des résultats du sous-programme au niveau appelant. comme on l’a vu tout à l’heure. ce qui est transmis au sousprogramme peut être considéré soit comme la valeur de cette variable. Par ce moyen.

ATTENTION : dans ce cas là. var C:char). procedure essai1 (A:real.limite.. cas représenté par ‘1’. 0 bidon limite intervalle tab A B C D essai1 E essai2 in1 in2 L L L L L L L L L ? L ? ? S S S S L L L L S L L ? ? S S S S S S L S S L L L L S S L 1 S S S S L ? L ? ? N = double L = locale S = supérieure ? = invisible Dans le tableau donné ci-dessus. avec tous les inconvénients que cela entraîne. end.in2:boolean):integer. essai2:real.) est fausse à plus d’un titre : • essai1 apparaît dans une expression alors que c’est une procédure . D : real. L’intersection comporte un signe signifiant que soit B ne connaît pas I. Pour en terminer avec le programme ci-dessus. soit I avait déjà été défini à un niveau supérieur et dans ce cas. Revenu au niveau appelant. 43 .. varE:tab. C. soit I est local à B... begin . • la variable affectée E. cas représenté par ‘?’. end.. n’est pas définie à ce niveau. var A : integer.essai2:integer).12*D) then in2:=4.... begin .. end. type tab = array[intervalle] of char. • les listes paramètres effectifs/paramètres formels sont de longueurs inégales .. essai2 procedure tab (essai1.. Pour la durée d’exécution du bloc courant..... soit I est défini plus d’une fois à un même niveau. dans le fonction essai2.. B : longint.. deux configurations peuvent se présenter : soit I n’a jamais été défini avant (?YAOP QJ ( Je@EP). if essai2(limite.....12*D) then in2:=4. chaque ligne correspond à un identificateur I du programme Pascal.... la définition locale masque celle de niveau supérieur. Quant à l’instruction if essai2(limite. D.. varB: boolean.... cette affectation sera donc perdue et la valeur de la fonction sera quelconque.. end. function (in1:longint. il a voulu être I à la place de I (?YAOP QJ ( VJKCKQ@).. b i d o e n s s a e i s 1 s a t i a 2 b const limite = 30. soit I est un identificateur d’un niveau supérieur à celui de B. intervalle = 0. on constate que la première instruction (E:= essai1(C[A]) + B. C : tab... Par exemple. begin E:= essai1(C[A]) + B. cas représenté par ‘L’... type intervalle = 0.E:longint. Dans le cas où I est local. l’ affectation terminale de la valeur de la fonction s’ opèrera sur la variable locale et non sur l’ identificateur de fonction.program bidon.. begin ..255. chaque colonne représentant les blocs B des différents niveaux de ce même programme. ..essai2:integer. l’ identificateur du niveau supérieur est totalement inaccessible. elle comporte 3 erreurs…  … que vous découvrirez vous même !… Du langage au programme Page 3. cas représenté par ‘S’..