3 \section{Introduction à la compilation}
5 \subsection{Le problème posé est...}
7 Donner à un ordinateur un fichier contenant du texte, le lui faire lire et comprendre de manière à lui faire exécuter un certain nombre de tâches associées à ce fichier
9 $\Rightarrow$ On fait une compilation.\\
11 \subsection{Les diverses phases d'une compilation}
13 Détaillons succintement les différentes phases d'une compilation...
15 \subsubsection{L'analyse lexicale}
17 On analyse le flux d'entrée de manière à le découper en unités lexicales, ou \emph{lexèmes}\index{lexèmes}.
20 Dans \emph{if (temps == beau ) \{} etc., les unités lexicales sont \og if \fg{}, \og ( \fg{}, \og temps \fg{}, \og == \fg{}, \og beau \fg{}, \og ) \fg{}.
24 \subsubsection{L'analyse syntaxique}
26 Les contraintes à respecter pour que le texte soit compréhensible sont-elles respectées ? En d'autres termes, le flux de lexèmes est-il conforme à la syntaxe du langage utilisé (par comparaison à la grammaire du langage, \emph{c.f.} ci-dessous) ?
28 \subsubsection{L'analyse sémantique}
30 Reconnaître la signification d'un texte syntaxiquement correct : essayer de comprendre ce que cela signifie (le sens).
32 Cela implique notemment la transformation de la source en une forme utilisable, qui fasse apparaître le sens du texte.
34 \begin{Ex}[d'analyse sémantique]
35 \emph{toto = titi + tutu;} est une instruction d'affectation à la variable \og toto \fg{} d'une valeur exprimée par une expression algébrique, constituée de la somme des variables \og titi \fg{} et \og tutu \fg{}.
40 Certains compilateurs utilisent des structures arborescentes :
43 \begin{picture}(94,47)(0,-47)
44 \put(0,-47){\framebox(94,47){}}
45 \node[NLangle=0.0,Nw=19.29,Nh=8.0,Nmr=2.0](n0)(33.52,-7.88){Affectation}
47 \node[NLangle=0.0,Nw=13.17,Nh=8.0,Nmr=2.0](n1)(12.02,-24.0){toto}
49 \node[NLangle=0.0,Nw=21.4,Nmr=2.0](n2)(61.62,-24.34){Addition}
51 \node[NLangle=0.0,Nw=13.17,Nmr=2.0](n5)(40.02,-40.0){titi}
53 \node[NLangle=0.0,Nw=13.17,Nmr=2.0](n6)(84.02,-40.0){tutu}
71 \subsubsection{Compilation proprement dite}
73 Utiliser effectivement le résultat de l'analyse sémantique pour obtenir le résultat escompté, ce qui est demandé : production de code machine, traduction d'un texte dans une autre langue, etc.
76 En général, ces différentes phases sont menées en parallèle.
80 \section{Les grammaires}
82 \subsection{Définition de la notion de grammaire}
84 \begin{Def}[Grammaire]
85 Une \emph{grammaire}\index{grammaire} est un ensemble de règles de syntaxe qui décrivent quels sont les constructions correctes qui sont possibles dans le langage utilisé, à l'aide de l'alphabet utilisé (alphabet ou vocabulaire).
88 Il existe de nombreux types de grammaires, et encore bien plus de formalismes exprimés pour représenter cette grammaire.\\
90 Nous utiliseront pour commencer un seul symbolisme pour remprésenter les grammaires : la formalisation BNF (Backus-Naur Form).
92 \subsection{Le formalisme BNF}
94 Dans la syntaxe BNF, une grammaire est constituée d'un ensemble de règles.\\
96 Chaque règle est constituée :
99 \item d'un premier membre,
100 \item suivi du symbole de réécriture (::=),
101 \item suivi d'un second membre, qui peut être vide.
106 On utilise (et on distingue) des symboles terminaux et des symboles non-terminaux (ST et SNT).
109 \subsection{Les symboles terminaux}
111 \begin{Def}[Symbole terminal]
112 Un \emph{symbole terminal}\index{symbole!terminal} est un symbole qui peut effectivement intervenir dans le texte analysé.
117 Dans \emph{if (temps == beau)}, \emph{if} est un symbole terminal (on trouve le mot dans le programme).
121 Les symboles terminaux sont entourés par : \og \fg{}.
125 \subsection{Les symboles non terminaux}
127 \begin{Def}[Symbole non terminal]
128 Un \emph{symbole non terminal}\index{symbole!non terminal} (SNT) est un symbole introduit (par commodité, ou plutôt par nécessité) par le rédacteur de la grammaire pour décrire les parties du fichier d'entrée qui représentent un tout logique et permettant de simplifier l'écriture de la grammaire.
132 Les symboles non-terminaux sont entourés par des chevrons: $<$ $>$.
135 Le premier membre d'une règle de grammaire est un SNT (la règle en constitue la définition), le second membre est une famille ordonnée (éventuellement vide) de symboles, terminaux ou non.\\
137 Ainsi, chaque règle de la grammaire consiste en la définition d'un symbole non-terminal. Cette dernière est terminée quand tous les SNT ont reçu une définition. Une règle s'écrit finalement sous la forme :
140 $<$SNT$>$::= suite (éventuellement vide) de ST et SNT
146 Voici un bout de grammaire (pour la définition d'une fonction) :\\
149 $<fct>$ & $::=$ & $<type> <nom> '' ( '' <parametres> '' ) '' <bloc>$\\
151 $<type>$ & $::=$ & $'' int ''$\\
157 \begin{Def}[Axiome de la grammaire]
158 Parmi tous les SNT, l'un d'entre eux doit désigner l'ensemble du texte à analyser, on l'appelle \emph{axiome de la grammaire}\index{grammaire!axiome}.
163 $<programme~en~C> ::= <entete> <suite~de~fct>$
166 La grammaire est terminée quand tous les SNT ont reçu au moins une définition.
170 \subsection{Exercices}
173 Les mots du langage $\cal L$ sont constitués d'un nombre, éventuellement nul, de $a$, suivi d'un $b$, suivi d'au moins un $c$.
175 Donner une grammaire BNF de ce langage.
180 Les mots du langage $\cal L$ commencent par un caractère $a$, suivi d'un nombre pair (éventuellement aucun) de caractères $b$, puis de deux caractères $c$.
182 Donner une grammaire BNF de ce langage.
187 Les mots du langage $\cal L$ sont les noms de variables en C : ils commencent obligatoirement parune lettre (majuscule ou minuscule), et se poursuivent par un nombre quelconque de chiffres, lettres ou underscore.
189 Donner une grammaire BNF de ce langage.
194 Ecrire la grammaire, en syntaxe BNF, des formes propositionnelles. On rappelle que les opérateurs sont, par ordre de priorité croissante :
196 \item Implication et équivalence (au même niveau, le moins prioritaire). Qu'ils ne sont pas associatifs (on ne peut ni les répéter, ni les faire coexister, au même niveau (c'est-à-dire, sans parenthèse), dans une expression (par exemple, $a \longrightarrow b \longleftarrow c$, ou $a \longrightarrow b \longrightarrow c$ sont incorrects).
197 \item Disjonction et conjonction (au même niveau, prioritaires sur implication et équivalence). Ils sont associatifs, mais on ne peut pas les mélanger : on peut écrire $a \ou b \ou c$, sans parenthèse, mais pas $a \ou b \et c$.
198 \item Négation, prioritaire sur tous les autres. Elle peut être répétée. Il s'agit d'un opérateur unaire préfixé.
201 Les noms de variable commencent par un caractère alphabétique, suivi éventuellement d'un nombre quelconque de caractères alphanumériques ou de soulignements. Il n'y a pas de constantes dans les expressions. Les opérateurs sont réalisés au clavier par les trois caractères consécutifs $<->$ pour l'équivalence, les deux caractères consécutifs $->$ pour l'implication, les lettres consécutives $ou$ pour la disjonction, $et$ pour la conjonction, et $non$ pour la négation. L'expression peut évidemment comporter des parenthèses, et ne peut être vide.
206 Le langage considéré est le prototypage des fonctions en C.
208 Donner une grammaire BNF de ce langage.
212 On accepte les deux types de phrases suivantes :
214 \item \og Marie est la mère du frère de Sonia.\fg{}
215 \item \og Qui est le père de l'oncle de la mère du petit fils de Paul ? \fg{}
217 ...et tous leurs dérivés. \'Ecrire la grammaire correspondante.
221 \section{Un exemple complet}
223 \og Les expressions correctes sont constituées d'un nombre quelconque, mais non nul, de 0, suivi d'un nombre quelconque, mais non nul, de 1. \fg{}
225 \subsection{Principes généraux}
227 On conseille de suivre cette démarche :
230 \item Commencer par écrire la grammaire du langage (des expressions correctes).
231 \item Ecrire l'analyseur syntaxique pur.
232 \item Passer à l'analyseur syntaxique avec messages d'erreur.
233 \item Puis à l'analyseur syntaxique avec interprétation sémantique.
238 Suivez ce cheminement :
240 \item Ecrivez cette grammaire.
241 \item Programmez, en C, l'analyseur syntaxique pur.
242 \item Ecrire le programme principal associé (le main).
243 \item Le modifier en analyseur syntaxique avec messages d'erreur, et adaptez le programme principal en conséquence.
244 \item Améliorez le programme pour qu'il devienne un analyseur syntaxique avec interprétation sémantique : ici, comptez le nombre de 0 et de 1, en cas de réussite.
249 \subsection{La grammaire du langage}
252 $<$ expression $>$ & ::= &$<$ groupe0 $>~<$ groupe1 $>$\\
254 $<$ groupe0 $>$ & ::= & “0" $<$ suite0 $>$\\
256 $<$ suite0 $>$ & ::= & $<$ groupe0 $>$\\
260 $<$ groupe1 $>$ & ::= & “1" $<$ suite1 $>$\\
262 $<$ suite1 $>$ & ::= & $<$ groupe1 $>$\\
270 \item Subdiviser au maximum les expressions en sous-expressions cohérentes, en n'hésitant pas à multiplier les niveaux.
272 \item Retarder au maximum les alternatives (en multipliant les niveaux) pour ne les faire intervenir que lorsqu'on ne peut plus faire autrement.
277 \subsection{Analyseur syntaxique pur}
281 Voici le code de l'analyseur pur : il répond par \og bon \fg{} ou \og mauvais \fg{}.
311 ... même chose pour groupe1() et suite1().
314 Une fonction prévue pour analyser une sous-expression ne connaît pas ce qui précède et ne s'occupe pas de ce qui suit.
317 Passons au programme principal :
322 printf("Une expression a analyser ? \n");
336 Toujours commencer par l'analyseur syntaxique pur.
340 \subsection{Analyseur syntaxique avec messages d'erreur}
348 printf("L'expression doit commencer par 0\n")
362 Le programme principal devient alors :
366 printf("Une expression a analyser ? \n");
373 printf("Pas de 0 apres le(s) un(s).\n");
375 printf("Caractere interdit : %c\n",*ss);
380 \subsection{Analyseur syntaxique avec interprétation sémantique}
388 printf("L'expression doit commencer par 0\n")
401 Pareil pour groupe1 et suite1. Le programme principal devient alors :
405 printf("Une expression a analyser ? \n");
408 Expression = expression()
413 printf("Nombre de 0 : %d, nomre de 1 : %d",expression.zero, expression.un);
415 printf("Caractere interdit : %c\n",*ss);
420 où la structure \emph{Expression} et la fonction \emph{expression()} sont ainsi définis :
428 struct Expression expression(){
438 Cet exemple, et d'autres, seront (re)vus en TP.
444 % \section{Exemples approfondis}
446 % \og Les expressions correctes sont constituées d'un nombre quelconque, mais non nul, de 0, suivi d'un nombre quelconque, mais non nul, de 1. \fg{}
448 % \subsection{Premier exemple}
450 % \subsubsection{Principes généraux}
452 % On conseille de suivre cette démarche :
455 % \item Commencer par écrire la grammaire du langage (des expressions correctes).
456 % \item Ecrire l'analyseur syntaxique pur.
457 % \item Passer à l'analyseur syntaxique avec messages d'erreur.
458 % \item Puis à l'analyseur syntaxique avec interprétation sémantique.
463 % Suivez ce cheminement :
465 % \item Ecrivez cette grammaire.
466 % \item Programmez, en C, l'analyseur syntaxique pur.
467 % \item Ecrire le programme principal associé (le main).
468 % \item Le modifier en analyseur syntaxique avec messages d'erreur, et adaptez le programme principal en conséquence.
469 % \item Améliorez le programme pour qu'il devienne un analyseur syntaxique avec interprétation sémantique : ici, comptez le nombre de 0 et de 1, en cas de réussite.
474 % \subsubsection{La grammaire du langage}
476 % \begin{tabular}{lll}
477 % $<$ expression $>$ & ::= &$<$ groupe0 $>~<$ groupe1 $>$\\
479 % $<$ groupe0 $>$ & ::= & “0" $<$ suite0 $>$\\
481 % $<$ suite0 $>$ & ::= & $<$ groupe0 $>$\\
485 % $<$ groupe1 $>$ & ::= & “1" $<$ suite1 $>$\\
487 % $<$ suite1 $>$ & ::= & $<$ groupe1 $>$\\
495 % \item Subdiviser au maximum les expressions en sous-expressions cohérentes, en n'hésitant pas à multiplier les niveaux.
497 % \item Retarder au maximum les alternatives (en multipliant les niveaux) pour ne les faire intervenir que lorsqu'on ne peut plus faire autrement.
502 % \subsubsection{Analyseur syntaxique pur}
506 % Voici le code de l'analyseur pur : il répond par \og bon \fg{} ou \og mauvais \fg{}.
530 % if (groupe0() == 0)
536 % ... même chose pour groupe1() et suite1().
539 % Une fonction prévue pour analyser une sous-expression ne connaît pas ce qui précède et ne s'occupe pas de ce qui suit.
542 % Passons au programme principal :
547 % printf("Une expression a analyser ? \n");
550 % if (expression()==1)
554 % printf("Mauvais\n");
556 % printf("Mauvais\n");
561 % Toujours commencer par l'analyseur syntaxique pur.
565 % \subsubsection{Analyseur syntaxique avec messages d'erreur}
573 % printf("L'expression doit commencer par 0\n")
587 % Le programme principal devient alors :
591 % printf("Une expression a analyser ? \n");
594 % if (expression()==1)
597 % else if (*ss == '0')
598 % printf("Pas de 0 apres le(s) un(s).\n");
600 % printf("Caractere interdit : %c\n",*ss);
605 % \subsubsection{Analyseur syntaxique avec interprétation sémantique}
613 % printf("L'expression doit commencer par 0\n")
626 % Pareil pour groupe1 et suite1. Le programme principal devient alors :
630 % printf("Une expression a analyser ? \n");
633 % Expression = expression()
637 % else if (*ss == '0')
638 % printf("Nombre de 0 : %d, nomre de 1 : %d",expression.zero, expression.un);
640 % printf("Caractere interdit : %c\n",*ss);
645 % où la structure \emph{Expression} et la fonction \emph{expression()} sont ainsi définis :
653 % struct Expression expression(){
654 % struct Expression a;
655 % a.zero = groupe0();
662 % \subsection{Deuxième Exemple}
665 % \og Les expressions correctes sont constituées d'un nombre quelconque mais non nul de 'a', suivi d'un 'b', suivi d'un nombre quelconque éventuellement nul de 'c'.\fg{}
669 % Faire le même travail que précédemment.
673 % \subsubsection{La grammaire}
676 % \begin{tabular}{lll}
677 % $<$ expression $>$ & ::= &$<$ groupe a $>~“b"~<$ liste c $>$\\
679 % $<$ groupe a $>$ & ::= & “a" $<$ suite a $>$\\
681 % $<$ suite a $>$ & ::= & $<$ groupe a $>$\\
685 % $<$ liste c $>$ & ::= & “c" $<$ liste c $>$\\
690 % \subsubsection{Analyseur syntaxique pur}
692 % Cet analyseur syntaxique pur répond par \og Bon \fg{} ou \og Mauvais \fg .
720 % if (groupea() == 0)
738 % printf("Une expression a analyser ? \n");
741 % if (expression()==1)
745 % printf("Mauvais\n");
747 % printf("Mauvais\n");
753 % \subsubsection{Analyseur syntaxique avec message d'erreur}
762 % printf("L'expression doit commencer par a \n");
777 % printf("Une expression a analyser ? \n");
780 % if (expression()==1)
784 % printf("Il n'y a que des c apres le b\n");
793 % printf("Apres le(s) a il doit y avoir un b");
801 % \subsubsection{Analyseur syntaxique avec interprétation sémantique}
811 % printf("L'expression doit commencer par a \n");
825 % struct Expression expression(){
826 % struct Expression un;
828 % if (un.a != 0 && *ss == 'b'){
833 % printf("Apres le(s) a il doit y avoir un b");
856 % struct Expression un;
857 % printf("Une expression a analyser ? \n");
862 % printf("Nombre de a : %d, nombre de b : 1, nombre de c : %d \n",un.a, un.c);
865 % printf("Il n'y a que des c apres le b\n");
874 % printf("Apres le(s) a il doit y avoir un b");
881 % \subsection{Troisième exemple : les expressions algébriques}
884 % \subsubsection{Présentation du problème}
886 % Les expressions algébriques à analyser sont constituées :
888 % \item des opérateurs + et *,
889 % \item des noms de variable : un caractère alphabétique.
894 % a+b+c*(d+e*(f+g))*((h+i)*j+k*l)+(m+n+o)*(p+q)*r
897 % On souhaite ici faire le même travail que précédemment, tout en sachant que la grammmaire devra tenir compte des deux opérateurs \emph{hiérarchisés}.
900 % \subsubsection{Règles générales d'écriture d'une grammaire hiérarchisée}
902 % Les règles sont les suivantes :
907 % \item L'analyse commence par l'opérateur le moins prioritaire ; cela revient à découper l'expression suivant cet opérateur.
909 % En dehors des parenthèses :
911 % \begin{tabular}{lll}
912 % $<$ somme $>$ & ::= &$<$ terme $>~<$ finSomme $>$\\
914 % $<$ finSomme $>$ & ::= & “+" $<$ somme $>$\\
921 % \item Elle continue par le deuxième opérateur, dans l'ordre croissant des priorités.
923 % Les sous-expressions analysées sont dépourvues des opérateurs précédents (en-dehors des parenthèses).
925 % \begin{tabular}{lll}
926 % $<$ terme $>$ & ::= &$<$ facteur $>~<$ finProduit $>$\\
928 % $<$ finProduit $>$ & ::= & “*" $<$ terme $>$\\
935 % \item On ne fait intervenir les parenthèses qu'après avoir épuisé tous les opérateurs.
937 % Les sous-expressions analysées ne comptortent alors plus aucun opérateur : plus que des lettres et des parenthèses.
940 % \begin{tabular}{lll}
941 % $<$ facteur $>$ & ::= &$<$ variable $>$\\
943 % & ::= & “(" $<$ somme $>$ “)"\\
945 % $<$ variable $>$ & ::= & “a" ... “z" (notation non BNF)\\
951 % \'Ecrire l'analyseur syntaxique pur.
955 % \subsubsection{Analyseur syntaxique pur}
957 % On pensera à inclure la bibliothèque \emph{ctype}, permettant la reconnaissance de caractères.
981 % if (facteur() == 1;
982 % return finProduit();
1009 % if (isalpha(*ss)){
1017 % printf("\n Une expression a analyser ? ");
1024 % printf("Mauvais\n");
1026 % printf("Mauvais\n")
1031 % Adaptez la grammaire pour qu'elle gère la soustraction et la division.
1036 % \subsubsection{Grammaire enrichie}
1038 % \begin{tabular}{lll}
1039 % $<$ somme $>$ & ::= &$<$ terme $>~<$ finSomme $>$\\
1041 % $<$ finSomme $>$ & ::= & $<$ operateurAdditif $>~<$ somme $>$\\
1045 % $<$ operateurAdditif $>$ & ::= & “+"\\
1049 % $<$ terme $>$ & ::= &$<$ facteur $>~<$ finProduit $>$\\
1051 % $<$ finProduit $>$ & ::= & $<$ operateurMultiplicatif $>~<$terme $>$\\
1055 % $<$ operateurMultiplicatif $>$ & ::= & “*"\\
1059 % $<$ facteur $>$ & ::= &$<$ variable $>$\\
1061 % & ::= & “(" $<$ somme $>$ “)"\\
1063 % $<$ variable $>$ & ::= & “a" ... “z"\\
1067 % \subsubsection{Version avec messages d'erreur}
1069 % \begin{lstlisting}
1070 % #include <stdio.h>
1071 % #include <ctype.h>
1078 % return finSomme();
1083 % if (*ss == '+' || *ss == '-'){
1091 % if (facteur() == 1;
1092 % return finProduit();
1097 % if (*ss == '*' || *ss == '/'){
1106 % if (*ss == '-' || *ss == '+') ss++;
1113 % printf("Il manque une parenthese fermante.\n");
1117 % if (*ss == '-' || *ss == '+') ss++;
1118 % if (isalpha(*ss)){
1122 % printf("\n\n ATTENTION :\n");
1123 % if (*ss == '+' || *ss == '-' || *ss == '/' || *ss == '*')
1124 % printf("Deux operateurs ne peuvent pas se suivre");
1127 % printf("L'expression ne peut pas se terminer par un operateur");
1129 % printf("Ce caractere ne fait pas partie de la grammaire : \" %c \" \n.", *ss);
1136 % if (isalpha(*ss)){
1144 % printf("\n Une expression a analyser ? ");
1147 % if (*ss == '*' || *ss '/'){
1148 % printf("Ne peut pas commencer par un operateur binaire")
1155 % printf("Attention\n");
1157 % printf("Deux variables ne peuvent se suivre.\n");
1158 % else if (*ss == '(' || *ss == ')')
1159 % printf("Caractere mal place \" %c "\", *ss)
1161 % printf("Ce caractere ne fait pas parti de la grammaire \" %c \" \n",*ss);
1169 % \subsubsection{Version avec interprétation sémantique}
1172 % On utilise les arbres : a+b+c devient
1173 % \begin{lstlisting}
1181 % \begin{lstlisting}
1189 % On est en effet obligé d'associer à gauche pour la soustraction (sinon on change l'expression).
1192 % Programmez cette structure d'arbre, puis la version sémantique.
1197 % \begin{lstlisting}
1198 % typedef enum {FEUILLE, UNAIRE, BINAIRE} Tnoeud;
1200 % typedef struct arbre{
1216 % #include <stdio.h>
1217 % #include <ctype.h>
1218 % #include <stdlib.h>
1219 % #define amalloc (arbre*)malloc (sizeof(arbre))
1220 % #define ANULL (arbre*) NULL
1222 % arbre* feuille(char var){
1224 % if ((a = amalloc) != ANULL{
1225 % a->selecteur = FEUILLE;
1231 % arbre* unaire(char op, arbre* fs){
1234 % if ((a=amalloc) != ANULL){
1242 % arbre* binaire(char op, arbre* fg, arbre* fd){
1244 % if (fg != ANULL && fd != ANULL)
1245 % if ((a=amalloc) != ANULL){
1254 % void printa(arbre* a){
1256 % case FEUILLE : printf("%c",a->t.var);
1258 % case UNAIRE : printf("%c(",a->t.un.op);
1259 % printa(a->t.un.fs);
1262 % case BINAIRE : printf("%c(",a->t.bin.op);
1263 % printa(a->t.bin.fg);
1265 % printa(a->t.bin.fd);
1271 % void print_arbre(arbre* a){
1275 % printf("Arbre vide\n");
1279 % #define cmalloc(n) (char*)malloc((n)*sizeof(char))
1280 % #define crealloc(ptr,n) (char*)realloc(ptr,(n)*sizeof(char))
1281 % #define CNULL (char*)NULL
1284 % char* lire_chaine(){
1285 % char *s, *ss, *ssmax;
1287 % if ((s = cmalloc(BLOC))==CNULL){
1288 % fprintf(stderr,"Memoire saturee\n");
1293 % smax = s + taille;
1294 % while ((c=getchar()) != '\n'){
1297 % if ((s=crealloc(c,taille+BLOC))==CNULL){
1298 % fprintf(stderr,"Memoire saturee\n")
1307 % return crealloc(s,ss-s);
1313 % \subsubsection{Le cas du moins unaire}
1315 % On souhaite rajouter le - unaire ; son analyse intervient au niveau du facteur :
1318 \centerline{\x{Fin du Chapitre}}