Cours Compilation PDF
Cours Compilation PDF
Introduction à la compilation...........................................................................3
1.1. Définition d’un compilateur............................................................................................3
1.2. Compilation par analyse et synthèse...............................................................................4
1.2.1. Analyse du programme source.................................................................................4
1.2.2. Phases d'un compilateur............................................................................................5
1.2.3. Partie Frontale et finale.............................................................................................8
Analyse lexicale................................................................................................25
3.1. Le rôle de l'analyseur lexical.........................................................................................25
3.1.1. Pourquoi une analyse lexicale.................................................................................26
3.1.2. Unités lexicales, modèles et lexèmes......................................................................26
3.1.3. Attributs des unités lexicales...................................................................................27
3.2. Mémorisation du texte d'entrée: couple de tampons.....................................................28
3.3. Spécification des unités lexicales..................................................................................29
3.3.1. Chaînes et langage...................................................................................................29
3.3.2. Opération sur les langages.......................................................................................30
3.4. Reconnaissances des unités lexicales............................................................................30
3.4.1. Diagrammes de transition........................................................................................31
3.5. Un langage pour spécifier des analyseurs lexicaux (lex)..............................................35
3.6. Conception d'un générateur d'analyseurs lexicaux........................................................36
3.6.1. Reconnaissance fondée sur les Automates finis non déterministes.........................37
3.6.2. Reconnaissance fondée sur les Automates finis déterministes................................39
Analyse syntaxique...........................................................................................41
4.1. Grammaires non contextuelles......................................................................................41
4.1.1. Définition d'une grammaire non contextuelle.........................................................42
4.1.2. Définition d'une Dérivation....................................................................................42
4.2. Arbres d’analyse et dérivations.....................................................................................44
4.2.1. Ambiguïté................................................................................................................45
Associativité des opérateurs..............................................................................................45
4.2.2. Priorités des opérateurs...........................................................................................45
Contrôle de type...............................................................................................84
6.1. Expression de type........................................................................................................84
6.2. Spécification d’un contrôleur de type simple................................................................85
6.2.1. Un langage simple...................................................................................................85
6.2.2. Contrôle de type des expressions............................................................................86
6.2.3. Contrôle de type des instructions............................................................................87
6.3. Conversion de type........................................................................................................88
Bibliographie....................................................................................................101
Introduction à la compilation
:=
E *
m *
c c
mettent en œuvre n'ont pas de sens. Par exemple lorsqu'on essaye d'additionner
deux identificateurs; l'un est le nom d'un tableau et l'autre celui d'une procédure.
Instruction
d'affectation
identificateur expression
:=
+
position
expression expression
initiale nombre
identificateur
60
vitesse
Les structures syntaxiques d'un langage sont généralement exprimées par des règles
récursives (grammaire)
G( V, , R, S)
(terminaux + non terminaux, alphabet, règles de production, axiome)
A id := E
E E+E
E E
*E E
id
E nb
Exemple 1.3.
temp1 := EntierVersRéel (60) la conversion de 60 peut être faite une fois
temp2 := id3 * temp1 pour toutes au moment de la compilation
(60.0)
temp3 := id2 + temp2 temp3 n'est utilisée qu'une seule fois pour
id1 := temp3 transmettre sa valeur à id1 on peut alors
substituer id1 à temp3
on obtient donc:
Expressions régulières et
automates à états finis
Remarques
1. L'opérateur unaire « * » à la plus haute priorité et est associatif à droite.
2. La concaténation a la deuxième plus haute priorité et est associative à gauche.
3. L’opérateur « | » a la plus faible priorité et est associatif à gauche.
Selon les conventions, (a) | ((b)* (c)) est équivalente à a|b*c. Les deux expressions
dénotent l'ensemble des chaînes qui ont soit un seul a, soit un nombre quelconque
éventuellement nul, de b suivis par un c.
Exemple 2.1.
Soit = {a, b}
1. L'expression régulière a|b dénote l'ensemble {a, b}
2. L'expression régulière (a|b) (a|b) dénote {aa, ab, ba, bb}, l'ensemble de
toutes les chaînes de a et de b de longueur deux. Une autre expression
régulière pour ce même ensemble est : aa | ab | ba | bb.
3. L'expression régulière a * dénote l'ensemble de toutes les chaînes formées d'un
nombre quelconque (éventuellement nul) de a, c'est à dire {, a, aa, aaa, …}.
4. L'expression régulière (a|b)* dénote l'ensemble de toutes les chaînes constituées
d'un nombre quelconque (éventuellement nul) de a ou de b. Une autre
expression régulière pour cet ensemble est (a*b* ) *.
5. L'expression régulière a|a*b dénote l'ensemble contenant la chaîne a et toute les
chaînes constituées d'un nombre quelconque (éventuellement nul) de a suivi
d'un b.
Si deux expressions régulières r et s dénotent le même langage, on dit que r et s sont
équivalentes et on écrit r = s. Par exemple, (a | b) = (b | a). Les expressions
régulières obéissent à un certain nombre de lois algébriques qui peuvent être utilisées
pour manipuler les expressions régulières sous des formes équivalentes, le tableau 2.1
montre quelques lois algébriques qui s'appliquent aux expressions régulières r, s, t.
Axiome Description
r|s = s|r « | » est commutatif
r | (s|t) = (r|s) | t « | » est associatif
(rs)t = r(st) La concaténation est associative
r(s|t) = rs | rt La concaténation est distributive par rapport à
(s|t)r = sr|tr «| »
r = r est l'élément neutre pour la concaténation
r = r
r* = (r|)* Relation entre * et
Un automate fini non déterministe (AFN en abrégé) est un modèle mathématique qui
consiste en :
1. Un ensemble d'états E ;
2. Un ensemble de symboles d'entrées (l'alphabet des symboles d'entrée);
3. Une fonction Transiter, qui fait correspondre des couples état-symbole à des
ensembles d'états.
4. Un état e0 qui est distingué comme état de départ ou état initial
5. Un ensemble d'états F distingués comme états d'acceptation ou états
d'acceptation ou états finals.
Un AFN peut être représentée graphiquement comme un graphe orienté étiqueté,
appelé graphe de transition, dans lequel les nœuds sont les états et les arcs étiquetés
représentent la fonction de transition.
Remarque.
Ce graphe ressemble à un diagramme de transition, mais le même caractère peut
étiqueter deux transitions au plus en sortie d'un même nœud et les arcs peuvent être
étiquetés par le symbole spécifique au même titre que les symboles d'entrées.
Exemple 2.2.
Soit le langage dénoté par l'expression régulière (a | b)* abb, consistant en
l'ensemble des chaînes de a et de b se terminant par abb.
La figure 2.1 représente le graphe de transition par un AFN qui reconnaît ce langage.
L'ensemble des états de l'AFN est {0, 1, 2, 3} et l'alphabet d'entrée est {a, b}. L'état 0
est distingué comme étant l'état de départ et l'état d'acceptation 3 est indiqué par un
double cercle.
a
a b b
début 0 1 2 3
Bien d'autres suites de déplacements pourraient être faites sur la chaîne d'entrée
aabb, mais aucune des autres n'arrive à terminer dans un état d'acceptation.
Le langage défini par un AFN est l'ensemble des chaînes d'entrées qu'il accepte.
Exemple 2.3.
Soit l’expression régulière aa*|bb*. Cette expression est acceptée par l’automate de la
figure 2.4.
a
a
1
2
0 b
b
3 4
La chaîne aaa est acceptée en se déplaçant via les états 0, 1, 2, 2, et 2. Les étiquettes
de ces arcs sont : , a, a, et a dont la concaténation est aaa. Notons que disparaît
dans la concaténation.
Un automate fini déterministe (AFD en abrégé) est un cas particulier d'automate fini
non déterministe dans lequel :
1. Aucun état n'a de transitions, c'est à dire de transition sur l'entrée et
2. Pour chaque état e et chaque symbole d'entrée a, il y a au plus un arc étiqueté a
qui quitte e.
Un automate fini déterministe a au plus une transition à partir de chaque état sur
n'importe quel symbole. Chaque entrée de sa table de transition est un état unique. Il
est très facile de déterminer si un automate fini déterministe accepte une chaîne
d'entrée, puisqu'il existe au plus un chemin depuis l'état de départ étiqueté par cette
chaîne.
L'algorithme 2.1 montre comment simuler le comportement d'un AFD sur une chaîne
d'entrée.
Exemple 2.4.
La figure 2.5 présente le graphe de transition d'un automate fini déterministe qui
accepte la langage (a|b)* abb (le même que celui accepté par l'AFN de la figure 2.1).
Avec cet AFD et la chaîne d'entrée ababb, l'algorithme 1 passe par la suite les états 0,
1, 2, 1, 2 et 3 et retourne oui.
Remarque.
Dans la table de transition d'un AFN, chaque entrée est un ensemble d'états. Dans la
table de transition d'un AFD chaque entrée est un état unique.
opération Description
Ensemble des états de l'AFN accessibles depuis un état
-fermeture(e)
e de l'AFN par de -transitions uniquement
Ensemble des états de l'AFN accessibles de puis un état
-fermeture(T)
e appartenant à T par des -transitions uniquement
Ensemble des états de l'AFN vers lesquels il existe une
Transiter(T, a) transition sur le symbole à partir d'un état e
appartenant à T
Tableau 2.3. Opérations sur les états d'un AFN
Un état D est un état d'acceptation si c'est un ensemble d'états de l'AFN qui contient
au moins un état d'acceptation.
Exemple 2.5.
La figure 2.6 illustre un AFN qui accepte le langage (a|b)*abb. Appliquons
l'algorithme 2.2 à cet AFN. L'état de départ de l'AFD équivalent est -fermeture(0),
qui est A = {0,1,2,4,7}, étant donné que ce sont précisément les états accessibles
depuis l'état 0 via un chemin dans lequel chaque arc est étiqueté .
a
2 3
a b b
0 1 6 7 8 9 10
b
4 5
L'alphabet des symboles d'entrée est ici {a,b}. L'algorithme 2.3 nous dit de marquer A
et de calculer -fermeture(Transiter(A, a)). Nous calculons d'abord Transiter (A,a),
l'ensemble des états de N qui ont des transitions sur a depuis les éléments de A.
Parmi les états 0, 1, 2, 4 et 7, seuls 2 et 7 ont de telles transitions vers 3 et 8, aussi :
-fermeture(Transiter({0,1,2,4,7},a) = -fermeture({3,8}) = {1,2,3,4,6,7,8}
Appelons cet ensemble B. Nous avons alors Dtran[A,a]= B.
Parmi les états de A, seul 4 a une transition sur b vers 5 aussi l'AFD a une transition
sur b depuis A vers :
C = -fermeture({5}) = {1,2,4,5,6,7}.
Donc Dtran[A,b] = C
Si nous continuons ce processus avec les ensembles actuellement non marqués B et
C, on atteint finalement le point où tous les ensembles qui sont des états de l'AFD
sont marqués.
Les cinq différents ensembles que nous construisons réellement sont
: A = { 0,1,2,4,7} D = {1,2,4,5,6,7,9}
B = {1,2,3,4,6,7,8} E = {1,2,4,5,6,7,10}
C = {1,2,4,5,6,7}
L'état A est l'état de départ et l'état E est l'unique état d’acceptation (le tableau 2.4
donne la table du transition complète).
Symbole
Etat d'entrée
a b
A B C
B B D
C B C
D B E
E B C
Tableau 2.4. Table de transition Dtran pour le DFA
L’algorithme est dirigé par la syntaxe en ce sens qu'il utilise la structure syntaxique
de l'expression régulière pour guider le processus de construction. On va voir
d'abord comment construire les automates pour les opérations qui contiennent un
opérateur d'alternance, de concaténation ou de fermeture de Kleene. Par exemple,
pour l'expression r|s, on construit un AFN à partir des AFN reconnaissant r et s.
déb
i f
Ici i est un nouvel état de départ et f un nouvel état d'acceptation. Il est clair que cet
AFN reconnaît {}
déb a
i f
Ici encore, i est un nouvel état de départ et f un nouvel état d'acceptation. Cet
automate reconnaît {a}.
3. Supposons que N(s) et N(t) soient les AFN pour les expressions régulières s et t.
(a) Pour l'expression régulière s|t, construire l'AFN composé suivant N(s|t):
N(s
déb i f
i N(s N(t f
i N(s f
Ici, i est un nouvel état de départ et f un nouvel état d'acceptation. Dans l'AFN
composé, on peut aller de i à f directement en suivant un arc étiqueté qui
représente que appartient à (L(s))*, ou bien on peut aller de i à f en traversant
N(s) une ou plusieurs fois. Il est clair que l'automate composé reconnaît (L(s))*.
(d) Pour l'expression régulière parenthèse (s), utiliser N(s) lui même comme
AFN.
Chaque fois qu'on construit un nouvel état, on lui donne un nom distinct. Ainsi, il ne
peut y avoir deux états dans deux sous-automates qui aient le même nom. Même si le
même symbole apparaît plusieurs fois dans r, on crée, pour chaque instance de ce
symbole, un AFN séparé avec ses propres états.
Exemple 2.6.
Soit l'expression régulière r = (a|b)* abb. La figure 2.8 présente un arbre syntaxique
pour r.
- L'AFN pour r4 est le même que celui pour r3. L'AFN pour (r4)* est alors :
- Pour obtenir l'automate pour r7 =r5r6, on fusionne les états 7 et 7' en appelant
l'état résultant 7 pour obtenir
Si EF Ø alors
Retourner "oui
Sinon
Retourner "non"
L'algorithme en réalité réalise la construction des sous-ensembles à l'exécution. Il
calcule une transition depuis l'ensemble courant d'états E vers le prochain ensemble
d'états en deux étapes. Tout d'abord, il détermine l'ensemble transiter(E, a)de tous les
états qui peuvent être atteints depuis un état de E par une transition sur a, le
caractère d'entrée courant. Ensuite, il calcule ε-fermeture de transiter(E, a), c'est à dire
tous les états qui peuvent être atteints depuis transiter (E, a) par zéro ou plusieurs ε-
fermeture. L'algorithme utilise la fonction CarSuiv pour lire les caractères de x, un par
un.
Quand tous les caractères de la chaîne d'entrée x ont été traités, l'algorithme retourne
« oui » si l'ensemble des états courants contient un état d'acceptation, « non » dans le
cas contraire.
Analyse lexicale
Quand on parle d'analyse lexicale, on utilise les termes « unité lexicale » « modèle »
et « lexème » avec des spécifications bien spécifiques. Le tableau 3.1 donne des
exemples de leur utilisation. En général, il y a un ensemble de chaînes en entrée pour
lesquelles la même unité lexicale est produite en sortie. Cet ensemble de chaînes est
décrit par une règle appelée modèle associé à l'unité lexicale. On dit que le modèle
filtre chaque chaîne de l'ensemble.
Un lexème est une suite de caractères du programme source qui concorde avec le
modèle de l'unité lexicale. Par exemple, dans la déclaration Pascal : const pi=3.1416.
La sous-chaîne pi est un lexème de l'unité lexicale « identificateur ».
Les lexèmes reconnus par le modèle décrivant l'unité lexicale représentent les chaînes
de caractères du langage source qui peuvent être traitées en bloc comme une unité
lexicale.
Description informelle
Unité lexicale Lexèmes
des modèles
const const const
if if if
oprel (opérateur de
< <= = <> > >= < <= = <> > >=
relation)
lettre suivie de lettres ou
id pi compte D2
chiffres
Toute constante
nb 3.1416 0 6.02E23
numérique
Tous caractère entre"et"
littéral "cours compilation"
sauf "
Tableau 3.1. Exemples d'unités lexicales
L'analyseur lexical réunit les informations sur les unités lexicales dans les attributs
qui leur sont associés. Par exemple le modèle nb correspond à la fois à la chaîne 0 et
1, mais il est essentiel pour le générateur de code de savoir quelle chaîne a
effectivement été reconnue. Les unités lexicales influent sur les décisions de l'analyse
syntaxique les attributs influent sur la traduction des unités lexicales. En pratique,
une unité lexicale a en général un seul attribut un pointeur vers l'entrée de la table
des symboles dans laquelle l'information sur l'unité lexicale est conservée.
Exemple 3.1.
Les unités lexicales et les valeurs d'attributs associées pour l'instruction Fortran E =
M*C**2 sont données ici sous la forme de couples :
On lit N caractères d'entrées dans chacune des entrées du tampon en une seule
commande système de lecture, plutôt que d'invoquer une commande de lecture pour
chaque caractère d'entrée. S'il reste moins que N caractères en entrée, un caractère
spécial fdf est placé dans le tampon après les caractères d'entrée.
On gère deux pointeurs vers le tampon d'entrées. La chaîne de caractères entre les
deux pointeurs constitue le lexème courant. Au départ les deux pointeurs désignent
le premier caractère du prochain lexème à trouver. L'un appelé le pointeur
« Avant », lit en avant jusqu'à trouver un modèle. Une fois que le prochain lexème est
reconnu, le pointeur « Avant » est positionné sur le caractère à sa droite. Après
traitement de ce lexème, les deux pointeurs sont positionnés sur le caractère qui suit
immédiatement le lexème.
Si le pointeur « Avant » est sur le point de dépasser la marque de moitié, la moitié
droite est remplie avec N nouveaux caractères d'entrée. Si le pointeur « Avant » est
sur le point de dépasser l'extrémité droite du tampon, la partie gauche est remplie
avec N nouveaux caractères d'entrées, et le pointeur « Avant » poursuit
circulairement au début du tampon.
Le terme alphabet ou classe de caractère dénote tout ensemble fini de symboles. Des
exemples typiques de symboles sont les lettres et les caractères. L'ensemble {0,1} est
l'alphabet binaire. L' ASCII est un exemple de l'alphabet informatique.
Une chaîne sur un alphabet est une séquence finie de symboles extraits de cet
ensemble. La longueur d'une chaîne s, habituellement notée |s| est le nombre
d'occurrences de symboles dans s. La chaînes vide notée , est une chaîne spéciale de
longueur zéro. Voici la terminologie courante concernant des portions de chaînes
(tableau 3.2).
Terme Définition
Une chaîne obtenue en supprimant un nombre
Préfixe de s quelconque, éventuellement nul, de symboles en fin
de s; par exemple, ban est un préfixe de banane
Une chaîne obtenue en supprimant un nombre
Suffixe de S quelconque, éventuellement nul, de symboles début
de S; par exemple, nane est un suffixe de banane
Une chaîne obtenue en supprimant un préfixe et un
suffixe de S. Par exemple nan de banane. Tout suffixe
Sous-chaîne de S ou préfixe et une sous-chaîne. Pour toute chaîne de S,
S et sont toutes deux préfixe, suffixes et sous chaîne
de S.
Suffixe, préfixe ou sous- Toute chaîne non vide se qui est, respectivement,
chaîne propre de S préfixe, suffixe ou sous chaîne de s telle que s x
Toute chaîne obtenue en supprimant un nombre
quelconque, éventuellement nul, de symbole non
Sous suite de S
nécessairement consécutifs de S; par exemple, baan
est une sous suite de banane
Tableau 3.2. Vocabulaire pour les portions de chaînes
Opération Définition
Union de L et M notée LCM LM = { S | SL ou S M }
Concaténation de L et M notée LM LM = { St | SL et t M }
L* = Li
i0
Exemple 3.2.
Soit L l'ensemble {A, B, …, Z, ab,…, Z} et C {0, 1, …,9}. Etant donnée qu'un symbole
peut être considéré comme une chaîne de longueur 1, les ensembles L et C sont des
langages finis. Voici quelques exemples de nouveaux langages crées à partir de L et
C en appliquant les opérateurs définis dans le tableau 3.3 :
1. L C est l'ensemble des lettres et des chiffres.
2. LC est l'ensemble des chaînes formées d'une lettre suivie d'un chiffre.
3. L4 est l'ensemble des chaînes de quatre lettres.
4. L* est l'ensemble de toutes les chaînes de lettre y compris , la chaîne vide.
5. L(LC) est l'ensemble de toutes les chaînes de lettre et de chiffres commençant
par une lettre
Exemple 3.3.
instr si expr alors instr
| si expr alors instr sinon instr
|
terme id
| nb
Remarque.
Pour ce fragment de langage l'analyseur lexical reconnaîtra les mots clé si, alors sinon
au même titre que les lexèmes dénotés par oprel, id, nb. (Pour simplifier on suppose
que les mots clé sont réservés).
On suppose que les lexèmes sont séparés par un espace consistant en une suite non
vide de blancs, tabulations et fin de ligne. Notre analyseur lexical éliminera ces
espaces en comparant une chaîne avec la définition régulière bl suivante.
délim blanc | tabulation | fin de ligne
bl délim +
Notre objectif est de construire un analyseur lexical qui isole le lexème associé à la
prochaine unité lexicale du tampon d'entrée et qui produise en sortie un couple
composé de l'unité lexicale appropriée et d'une valeur d'attribut. Les attributs pour
les opérateurs de relation (oprel) sont donnés par les constantes symboliques PPQ,
PPE, EGA, DIF, PGQ, PGE.
Supposons que le tampon d'entrée soit le même que dans la figure 3.2 et que le
pointeur vers le début du tampon pointe sur le caractère qui suit la dernière unité
lexicale trouvée. On utilise un diagramme de transition pour garder trace des
informations sur les caractères rencontrés quand le pointeur « avant » parcourt le
texte d'entrée. On procède par des déplacements de position en position dans le
diagramme au fur et à mesure de la lecture des caractères.
Exemple 3.4.
- Diagramme de transition pour « si » (figure 3.3)
s i
0 1 2 Retourne Si
autre
L'étiquette autre désigne tous les caractères autres que
autre ceux qui sont indiqués explicitement sur les arcs
quittant e.
Exemple 3.5.
La figure 3.5 représente un diagramme de transition pour l'unité lexicale oprel
(opérateur de relation).
Remarque.
Comme les mots clés sont des suites de lettres, il y a des exceptions à la règle selon
laquelle une suite de lettres ou de chiffres débutant par une lettre est un
identificateur. Plutôt que de coder les exceptions dans un diagramme de transition,
une astuce consiste à traiter les mots clés comme des identificateurs spéciaux. Quand
on atteint l'état d'acceptation de la figure 3.6, on exécute un certain code pour
déterminer si le lexème amenant à cet état d'acceptation est un mot clé ou un
identificateur.
Figure 3.6. Diagramme de transition pour les identificateurs et les mots clés
Une technique simple pour séparer les mots clé des identificateurs consiste à diviser
la table de symboles en deux parties : une partie statique au début de la table de
symboles dans laquelle on place les mots clé (si, alors, sinon, etc.) avant qu'aucun
caractère n'ait été lu et une partie dynamique en bas pour les identificateurs.
L'instruction de retour qui suit l'état d'acceptation utilise UnilexId () et RangerId()
pour obtenir l'unité lexicale et la valeur d'attribut respectivement. RangerId() a accès
au tampon ou l'unité lexicale identificateur a été trouvée.
On examine la table des symboles et :
- Si on trouve le lexème avec l'indication mot clé, RangerId () rend 0.
- Si on trouve le lexème comme variable du programme, RangerId() rend un
pointeur vers l'entrée dans la table des symboles.
- Si on ne trouve pas le lexème dans la table des symboles, il y est placé en tant
que variable et un pointeur vers cette nouvelle entrée est retourné.
La procédure UnilexId(), de manière similaire, recherche le lexème dans la table des
symboles. Si le lexème est un mot clé, l'unité lexicale correspondante est retournée ;
autrement, l'unité lexicale « id » est retournée.
RangerNb() entre le lexème dans la table des symboles et rend l'unité lexicale « nb »
avec ce pointeur comme valeur lexicale.
Remarque.
On peut obtenir une suite de diagrammes de transitions pour toutes les unités
lexicales de l'exemple 3.3 si on réunit les diagrammes de transition étudiés. On doit
essayer les états de départ de plus petit numéro avant ceux de numéros plus élevés.
Le traitement de « bl » est différent : Rien n'est retrouvé quand l'état d'acceptation
est atteint ; on revient simplement à l'état de départ du premier diagramme de
transition pour rechercher un autre modèle. Voici un diagramme de transition qui
reconnaît
« bl » (figure3.9).
Remarque.
Il est préférable de rechercher d'abord les unités lexicales qui apparaissent le plus
fréquemment avant celles qui apparaissent le moins fréquemment, car on atteint un
diagramme de transition qu'après l'échec des diagrammes précédents.
Comme des espaces peuvent apparaître fréquemment, placer le diagramme de
transitions des espaces près du début peut être une amélioration par rapport au
placement à la fin.
En lisant cette section, souvenez-vous de ceci : lex écrit un fichier source C. Ce fichier
est fait de trois sortes d'ingrédients :
- des tables garnies de valeurs calculées à partir des expressions régulières
fournies,
- des morceaux de code C invariable, et notamment le « moteur » de l'automate,
c'est-à-dire la boucle qui répète inlassablement etat transit (etat; caractere),
- des morceaux de code C, trouvés dans le fichier source lex et recopiés tels quels,
à l'endroit voulu, dans le fichier produit.
Un fichier source pour lex doit avoir un nom qui se termine par « .l ». Il est fait de
trois sections, délimitées par deux lignes réduites au symbole %% :
%{
Déclarations pour le compilateur C
%}
Définitions régulières
%%
Règles
%%
Fonctions C supplémentaires
Spécification du
langage source L Générateur automatique Analyseur
(définition régulière d'analyseurs lexicaux lexical de L
lex)
m2 {action2}
…
mn {actionn}
Le but est de construire un reconnaisseur qui recherche des lexèmes dans le tampon
d'entrée. Si plus d'un modèle convient, le reconnaisseur doit choisir le lexème le plus
long. S'il y a au moins deux modèles qui reconnaissent le lexème le plus long, le
modèle listé en premier est choisi.
Un automate fini est un modèle naturel sur lequel on peut bâtir un analyseur lexical.
Le compilateur lex converti les modèles d’expressions régulières de la spécification
lex en une table de transition pour un automate fini. L'analyseur lexical lui-même
utilise un tampon d'entrée avec deux pointeurs (début, avant). Il consiste en un
simulateur d'AF qui utilise une table de transitions pour rechercher les modèles dans
le tampon d'entrée (voir figure 3.11).
On commence par construire une table de transition pour un AFN N pour le modèle
m1m2..mn. On construit un AFN pour chaque mi, on ajoute un nouvel état de
départ e0, et enfin on relie e0 aux états de départ de chaque N(mi) par des -
transitions.
On doit reconnaître le préfixe le plus long de la chaîne d'entrée qui correspond à
un modèle.
Nous apportons donc les modifications suivantes à l'algorithme 2.5 (simulation d'un
AFN)
- Chaque fois qu'on ajoute un état d'acceptation à l'ensemble d'états courant, on
enregistre la position courante dans le texte d'entrée et le modèle mi
correspondant à cet état d'acceptation.
- Si l'ensemble d'états courant contient déjà un état d'acceptation, alors seul le
modèle qui apparaît le premier dans la spécification lex est noté.
Exemple 3.7.
Supposons qu'on a le programme lex suivant :
m1 a /* Les actions sont
m2 abb omises */
m3 a*b+
Les trois unités lexicales sont reconnues par les automates de la figure 3.12
Symbole d'entrée
Etat Modèle annoncé (à l'entrée de l'état)
a b
0137 247 8 aucun
247 7 58 a
8 - 8 a*b+
7 7 8 aucun
58 - 68 a*b+
68 - 8 abb
Tableau 3.4. Table de transition d'un AFD
Remarque.
La chaîne abb correspond aux deux modèles abb et a*b+, reconnus aux états 6 et 8 de
l'AFN. l'état 68 dans l'AFD inclut donc deux états d'acceptation de l'AFN puisque
abb apparaît avant a*b+ dans la spécification de l'AFN donc seul le modèle abb est
reconnu à l'état 68.
m1 m3
a b a néant
0137 247 7 8
m1 m3
a b a
0137 247 58 néant
Ensuite il reconnaît a :
m1
0137 a 247
Analyse syntaxique
Dans notre modèle de compilateur, l'analyseur syntaxique obtient une chaîne
d'unités lexicales de l'analyseur lexical, comme illustre à la figure 4.1, et vérifie que la
chaîne peut-être engendrée par la grammaire du langage source.
On suppose que l'analyseur syntaxique signale chaque erreur de syntaxe, il doit
également supporter les erreurs les plus communes de façon à pouvoir continuer le
traitement du texte restant.
Exemple 4.1.
si S1 et S2 sont des instructions et E une expression
"si E alors S1 sinon S2" est une instruction (1)
Remarque.
Cette forme d'instruction ne peut être spécifiée en utilisant la notation des
expressions régulières.
En utilisant les variables syntaxiques inst pour dénoter la classe des instructions et
expr pour dénoter la classe des expressions, on peut exprimer (1) de façon lisible en
utilisant la production:
"instr si expr alors instr sinon instr" (2)
Exemple 4.2.
La grammaire constituée des productions suivantes définit des expressions
arithmétiques simples:
Nous disons que A si A est une production et et sont des chaînes
arbitraires de symboles grammaticaux.
si 1 2 …. n, on dit que 1 se dérive en n.
pour dire "se dérive en zéro, une ou plusieurs étapes" on peut utiliser le symbole
donc
*
pour une chaîne quelconque et
* *
si et , alors
signifie "se dérive en une ou plusieurs étapes"
soit une grammaire G et S son axiome; on peut utiliser la pour définir
relation
L(G), le langage engendré par G. On dit qu'une chaîne de terminaux w appartient à
L(G) ssi S w. La chaîne w est appelée phrase de G.
un langage qui peut être engendré par une grammaire est dit langage non
contextuel.
*
si S , où peut contenir certains non terminaux, on dit que est une proto-
phrase de G.
Exemple 4.3.
Soit la grammaire G(VT,VN,S,P)
E E+E|E*E|(E)|-E|id (3)
la chaîne –(id+id) est une phrase de la grammaire (3) car on a la
dérivation: E –E –(E) –(E+E) –(id+E) –(id+id) (4)
Les chaînes E, -E, -(E),…,-(id+id) sont toutes des proto-phrases de cette grammaire.
*
On écrit E -(id+id) pour indiquer que E se dérive en –(id+id)
A chaque étape d'une dérivation, on doit faire deux choix :
Il faut choisir le non terminal à remplacer
Quelle alternative utiliser pour ce non terminal
Dérivation gauche : Seul le non terminal le plus à gauche est remplacé à chaque
étape. On écrit on peut alors réécrire (4)
g
si *
on dit que est une proto-phrase gauche de la grammaire considérée.
S g
X Y Z
Exemple 4.4.
liste liste +
chiffre liste liste
– chiffre liste
chiffre
chiffre 0|1|2|3|4|5|6|7|8|9
liste
liste chiffre
liste - chiffre
+
chiffre 5
2
Sami KHALFOUI – Moez HAMMAMI – ENSI 44
2006/2007
Analyse syntaxique
Des feuilles d'un arbre syntaxique, lues de gauche à droite, constituent le mot des
feuilles de l'arbre, qui est la chaîne engendrée ou dérivée à partir du non terminal
situé à la racine de l'arbre syntaxique. (Dans l'exemple la chaîne engendrée est 9-
5+2).
4.2.1. Ambiguïté
Une grammaire peut avoir plus d'un arbre syntaxique qui engendre une chaîne
donnée d'unités lexicales. Une telle grammaire est dite ambiguë. Comme une telle
chaîne a habituellement plus d'une signification, on a besoin de travailler avec des
grammaires non ambiguës.
Exemple 4.5.
chaîne chaîne + chaîne| chaîne - chaîne|0|1|2|3|4|5-6|7|8|9
La figure 4.2 montre qu'une expression comme 9-5+2 a plus d'un arbre syntaxique.
Les deux arbres pour 9-5+2 correspondent aux deux manières de parenthèser
l'expression (9-5)+2 et 9-(5+2). Le deuxième parenthèsage donne à l'expression la
valeur 2 plutôt que la valeur habituelle 6.
chaîne chaîne
9 5 5 2
Figure 4.2. Deux arbres syntaxiques pour 9-5+2
Par convention 9+5+2 est équivalent à (9+5)+2 et 9-5 est équivalent à (9-5)-2. On dit
que l'opérateur + est associatif à gauche car un opérande avec des signes + de chaque
coté est traité par l'opérateur qui est à sa gauche.
+,-,*,/ sont associatifs à gauche.
L’exponentiation est associative à droite.
:= l'affectation est associative à droite.
En conséquence 5 est traitée par * dans 9+5*2 et 9*5+2 ce qui est équivalent
respectivement à 9+(5*2) et (9*5)+2.
analyse descendante
Exemple 4.6.
Considérons la grammaire
S cAd
A ab|a
et la chaîne d'entrée w = cad.
On commence par construire initialement un arbre qui contient un seul nœud
étiqueté S. Un pointeur d'entrée repère c, le premier symbole de w, nous utilisons la
première production de S pour développer l'arbre et obtenir
Figure 4.4.a
Figure 4.4.b
Nous avons alors une concordance avec le second symbole d'entrée; nous avançons
donc le pointeur à d (troisième symbole en entrée), et comparons d avec la feuille
suivante étiquetée b.
Comme b et d sont différents, nous signalons un échec et retournons à A pour voir
s'il n'existe pas une autre alternative de A, non encore essayé et qui serait susceptible
de produire une concordance.
En retournant à A, nous devons remettre le pointeur d'entrée en position 2, la
position qu'il avait quand nous sommes arrivés sur A la première fois.
Nous essayons maintenant la seconde alternative de A et obtenons l'arbre de la figure
suivante:
Figure 4.4.c
Remarque.
Une grammaire récursive à gauche peut faire boucler un analyseur par descente
récursive même s'il possède un mécanisme de retour arrière.
En effet dans le cas de A A|ab|a, en essayant de développer A, on peut
éventuellement se retrouver en train de développer A de nouveau, et cela sans avoir
consommé de symbole en entrée.
Figure 4.5.a
Figure 4.5.b
Exemple 4.7.
expr expr + terme |terme
devient :
expr terme R
R + terme R |
Quelque soit le nombre de A-productions, il est possible d'éliminer les récursivités à
gauche immédiates par la technique suivante:
Dans un premier temps, on groupe les A-productions comme suit:
A A1| A2| …| Am|1|2|….|n
où aucune i ne commence par un A. Ensuite on remplace les A-productions par:
A 1A'|2A'|….| nA'
A' 1A'| 2A'| …| m A'| (on suppose que i )
On peut construire un analyseur prédictif non récursif en tenant à jour une pile. Le
problème clé de l'analyse prédictive est la détermination de la production à
appliquer pour développer un non terminal. L'analyseur non récursif de la figure 4.6
suivante recherche la production à appliquer dans une table d'analyse (qu'on va voir
la méthode de construction ultérieurement).
Cet analyseur possède un tampon d'entrée, une pile, une table d'analyse et un flot de
sortie.
Le tampon d'entrée contient la chaîne à analyser, suivie de $ (marqueur fin)
La pile contient une séquence de symboles grammaticaux, avec $ marquant le
fond de pile. Initialement la pile contient l'axiome de la grammaire au dessus de $
La table d'analyse est un tableau à deux dimensions M [A,a], où A est un non
terminal et a est un terminal ou le symbole $.
L’analyseur syntaxique est contrôlé par un programme qui a le comportement
suivant. Ce programme considère X, le symbole en sommet de pile et a, le symbole
Exemple 4.8.
Considérons la grammaire suivante :
E TE'
E' +TE'|
T FT'
T' *FT'|
F (E)|id
Voici une table d'analyse prédictive (voir tableau 4.1) pour cette grammaire. (Jusque
là on n'a pas vu comment la construire)
Symbole d'entrée
id + * ( ) $
E E TE' E TE'
E' E' +TE' E' E'
T T FT' T FT'
T' T' T' *FT' T' T'
F F id F (E)
Tableau 4.1. Table d’analyse
On voit que les actions de l'analyseur décrivent une dérivation gauche de la chaîne
source, c'est à dire que les productions utilisées sont celles d'une dérivation gauche.
Premier et Suivant
Ces fonctions nous permettent, quand c'est possible, de remplir la table d'analyse
prédictive pour G.
Premier ().
Si est une chaîne de symboles grammaticaux, Premier() désigne l'ensemble des
*
terminaux qui commencent les chaînes qui se dérivent de . si , alors est aussi
dans premier().
Pour calculer Premier(X), pour tout symbole de la grammaire X, appliquer les règles
suivantes jusqu'à ce qu'aucun terminal ni ne puisse être ajouté aux ensembles
Premier
1. si X est un terminal, Premier (X) est {X}
2. si X est une production, ajouter à premier(X)
3. si X est un non terminal et X Y1Y2…Yk une production
Premier (X) = Premier (Y1) sauf
*
Premier (Y2) sauf si Y1 ( est dans premier())
*
Premier(Y3) sauf si Y1Y2
…
Premier(Yk-1) sauf si (Premier(Y1) Premier(Y2) .. Premier(Yk-2)
*
Premier(Yk) si Y1Y2..Yk-1
(autrement si Premier (Yj) pour tout j 1,2,..k ajouter à Premier (X))
Maintenant, nous pouvons calculer Premier pour une chaîne X1X2..Xn de la façon
suivante: Ajouter à Premier (X1X2…Xn) tous les symboles de Premier (X1) différents
de . Si est dans Premier(X1), ajouter également les symboles de premier(X2)
différents de . Si est dans Premier (X1) et dans Premier(X2) ajouter également les
symboles de Premier(X3) différents de etc. Finalement, si quel que soit i, premier
(Xi) contient , ajouter à premier(X1,X2…Xn)
Exemple 4.9.
Soit la grammaire :
E TE'
E' +TE'|
T FT'
T' *FT'|
F (E)|id
Alors :
Premier (E) = Premier(T) = Premier(F) = {(,id}
Premier(E') = {+,}
Premier(T') = {*,}
Cela s'explique par
Suivant (A).
Pour chaque non-terminal A, Suivant(A) définit l'ensemble des terminaux a qui
peuvent apparaître immédiatement à droite de A dans une proto-phrase, c'est à dire
*
l'ensemble des terminaux a tels qu'il existe une dérivation de la forme S Aa où
et sont des chaînes de symboles grammaticaux.
Remarque.
Il a pu exister au cours de la dérivation, des symboles entre A et a, mais, dans ce cas
ils se sont dérivés en et ont disparu.
Si A peut-être le symbole le plus à droite dans une proto-phrase, alors $ est dans
Suivant (A).
Pour calculer Suivant(A) pour tous les non terminaux A, appliquer les règles
suivantes jusqu'à ce que aucun terminal ne puisse être ajouté aux ensembles
SUIVANT.
1. Mettre $ dans Suivant(S), où S est l'axiome et $ est le marqueur droit
indiquant la fin du texte source.
2. S'il y a une production A B, le contenu de Premier(), excepté , est ajouté
à Suivant (B)
3. S'il existe une production A B ou une production A B telle que
*
Explication.
Suivant(E) = {$} Premier( ))
(règle 1) (car on a F → (E) règle 2)
= {$,)}
Exemple 4.11.
Appliquons l'algorithme 4.2 à la grammaire
E TE'
E' +TE'|
T FT'
T' *FT'|
F (E)|id
Puisque Premier(TE') = Premier(T) = {(,id}, la production E TE' implique que
les entrées M[E,(] et M[E,id] prennent toutes les deux la valeur E TE'
La production E' +TE' implique que l'entrée M[E',+] prend la valeur E' +TE'.
La production E' implique M[E',)] et M[E',$] prennent toutes les deux valeurs
E' car suivant(E') = {),$}
Voici donc la table d'analyse prédictive pour la grammaire (voir tableau 4.2)
Symbole d'entrée
id + * ( ) $
E E TE' E TE'
E' E' +TE' E' E'
T T FT' T FT'
T' T' T' *FT' T' T'
F F id F (E)
Tableau 4.2. Table d’analyse
en dépilant chaque fois que l'état final d'un non-terminal est atteint. L'implantation
des diagrammes de transition sera détaillée plus loin.
L'approche décrite ci-dessus est correcte à la condition que le diagramme de
transition considéré ne comporte pas de non-déterminisme, c'est-à-dire s'il n'existe
pas d'état ayant des transitions différentes sur le même symbole. Si une ambiguïté se
produit, nous devons être capables de la résoudre d'une façon ad hoc, comme dans
l'exemple suivant. Si le non-déterminisme ne peut pas être éliminé, nous ne pouvons
pas construire d'analyseur syntaxique prédictif, mais, si nous pensons que c'est la
meilleure stratégie d'analyse possible, nous pouvons construire un analyseur par
descente récursive utilisant le rebroussement pour essayer systématiquement toutes
les possibilités.
Exemple 4.12.
Soit la grammaire :
E TE'
E' +TE'|
T FT'
T' *FT'|
F (E)|id
figure 4.9(c). Nous observons alors que le premier et le troisième nœuds de la figure
4.9(c) sont équivalents et nous les fusionnons. Le résultat obtenu à la figure 4.9( d)
est illustré à nouveau comme premier diagramme de la figure 4.10. Les mêmes
techniques s'appliquent aux diagrammes pour T et T'. L'ensemble complet des
diagrammes résultants est montré à la figure 4.10. Une implantation en C de cet
analyseur syntaxique prédictif s'exécute de 20 à 25% plus vite que l'implantation en C
de la figure 4.8.
4.3.4.Grammaires LL(1)
Pour certaines grammaires, la table d'analyse peut avoir des entrées qui sont définies
de façon multiple. Par exemple, si G est récursive à gauche ou ambiguë, la table
d'analyse aura alors au moins une de ses entrées défini de façon multiple.
Exemple 4..13.
Soit la grammaire G
S iEtSS'|a
S' eS|
Eb
Les grammaires
S iEtS|iEtSeS|a (1) S iEtSS'|a (2)
E b S' eS|
Eb
Remarque. Qu'est ce qu'on doit faire lorsque la table d'analyse a des entrées à
valeurs multiples?
Réponse: Transformer la grammaire afin d'éliminer les récursivités à gauche puis
factoriser à gauche, Mais c'est pas encore sur d'obtenir une table d'analyse dans
entrées multiples.
La grammaire
S iEtSS'|a
S' eS|
E b
est un exemple de grammaires n'ayant pas de transformations la rendant LL(1). On
peut cependant l'analyser en supposant que M[S',e] = {S'eS}.
De manière générale, il n'existe pas de règles universelles par lesquelles une entrée à
valeur multiple peut être transformée en une entrée à valeur simple sans affecter le
langage reconnu par l'analyseur.
Exemple 4.14.
Soit la grammaire:
S aABe
A Abc|b (1)
B d
La phrase abbcde peut être réduite vers S par les étapes suivantes:
abbcde
aAbcde production utilisée A b
aAde production utilisée A Abc
aABe production utilisée B d
S production utilisée S aABe
On parcourt abbcde à la recherche d'une sous-chaîne qui corresponde à la partie
droite d'une production. Les sous-chaînes b et d conviennent. choisissons la sous
chaîne b la plus à gauche (c'est pas une règle) et remplaçons la par A.
Maintenant les sous-chaînes Abc,b et d correspondent aux parties droites de
production. On va choisir la production A Abc malgré que b est la sous chaîne la
plus à gauche qui corresponde à une partie droite de production (on va voir
ultérieurement les règles de choix entre les chaînes). Nous obtenons aAde.
Remplaçons d par d par B (Bd) nous obtenons aABe.
Nous pouvons maintenant remplacer cette chaîne tout toute entière par S.
Nous avons donc par une séquence de quatre réductions été capables de réduire
abbcde vers S. Ces réductions, élaborent en sens inverse la dérivation droite suivante:
S aABe aAde aAbcde abbcde
d d d d
De façon informelle, un "manche" de chaîne est une sous chaîne qui correspond à la
partie droite d'une production et dont la réduction vers le non terminal de la partie
gauche de cette production représente une étape le long de la dérivation droite
inverse.
Formellement: un manche de proto-phrase droite est:
- Une production A
- Une position dans où peut être trouvée et remplacée par A pour produire la
proto-phrase droite précédente dans une dérivation droite de .
*
C'est à dire que si S A , A dans la position suivant () est une manche
d d
De même aAbcde est une proto-phrase droite dont le manche est AAbc en
position2.
Exemple 4.15.
Soit G:
et la dérivation droite:
(1) E E+E
(2) E E *E (2) E E+E
d
(3) E (E)
E+E*E
(4) E id d
E+E*id3
d
E+id2*id3
d
id1+id2*id3
d
On a indicé les id pour faciliter la discussion; nous avons aussi souligné un manche
de chaque proto-phrase droite. Par exemple id1 est un manche de la proto-phrase
droite id1+id2*id3 car id est la partie droite de la production E id et le
remplacement de id1 par E produit la proto-phrase droite précédente E +id2*id3.
E E*E
d
E*id3
d
E+E * id3
d
E + id2*id3
d
4.4.2.Elagage du manche
S = 0 1 2 ..… n-1 n =
d d d d d
Exemple 4.16.
E E+E
E E*E et la chaîne d'entrée: id1+id2*id3
(2) E (E)
E id
Le tableau 4.3 présente une séquence de réductions qui réduit id1+id2*id3 vers
l'axiome E.
Remarque. On observe que la séquence de proto-phrases droites est exactement
l'inverse de la première dérivation droite de la grammaire 2.
Proto-phrase Droite Manche Production de réduction
id1+id2*id3 id1 Eid
E+id2*id3 id2 E id
E+E*id3 id3 Eid
E+E*E E*E EE*E
E+E E+E EE+E
E
Tableau 4.3. Réductions effectuées par un analyseur par décalage-réduction
Une bonne façon est d'utiliser une pile pour conserver les symboles grammaticaux et
un Tampon d'entrée pour contenir la chaîne à analyser.
Nous utilisons le symbole $ pour marquer à la fois le fond de pile et l'extrémité droite
du tampon d'entrée.
Sami KHALFOUI – Moez HAMMAMI – ENSI 63
2006/2007
Analyse syntaxique
Exemple 4.17.
La table suivante montre les actions que doit effectuer un analyseur par décalage-
réduction sur la chaîne id+id2*id3 sur la grammaire: E E+E |E*E| (E) | id en
utilisant la première dérivation: E E+E E+E*E E+E*id3E+id2*id3 id1 +
d d d d
id2 * id3
Pile Entrée Action
(1) $ id1+id2*id3 $ décaler
(2) $id1 +id2*id3 $ réduire par E id
(3) $E +id2*id3 $ décaler
(4) $E+ *id3 $ décaler
(5) $E+id2 *id3 $ réduire par Eid
(6) $E+E id3 $ décaler
(7) $E+E* $ décaler
(8) $E+E*id3 $ réduire par E id
(9) $E+E*E $ réduire par E E*E
(10) $ E+E $ réduire par E E+E
(11) $E $ accepter
Remarque. Du fait que la grammaire permet deux dérivations droites pour cette
entrée. Il existe une autre séquence d'actions qu'un analyseur par décalage-réduction
pourrait effectuer
Préfixes viables. Les préfixes d'une proto-phrase droite qui peuvent apparaître sur la
pile d'un analyseur par décalage réduction sont appelés préfixes viables.
4.4.4.Analyseurs LR
Cette section présente une technique efficace d'analyse syntaxique ascendante qui
peut être utilisée pour analyser une large classe de grammaires non contextuelles.
Cette technique est appelée analyse LR(K); "L" signifie parcours de l'entrée de gauche
vers la droite" (left to right scanning of the input), "R" signifie "en construisant une
dérivation droite inverse) et K indique le nombre de symboles de prévision utilisés
pour prendre les décision s d'analyse. Quand (K) est omis, K est supposé être égal à
un.
Après avoir présenté le fonctionnement d'un analyseur LR, nous présenterons trois
techniques pour construire les tables d'analyse LR pour une grammaire.
- La première méthode appelée "simple LR" (SLR en abrégé), est la plus facile à
implémenter, mais la moins puissante des trois. Pour certaines grammaires, la
production des tables d'analyse peut échouer alors qu'elle réussirait avec
d'autres méthodes.
- La seconde méthode, appelée LR canonique, est la plus puissante, mais elle est
la plus coûteuse.
- la troisième méthode appelée "LookAheadLR" (LALR en abrégé) ou LR à
prévision, a une puissance et un coût intermédiaires entre les deux autres.
Algorithme d'analyse LR
Exemple 4.18.
La table 4.4 présente les fonctions Action et Successeur des tables d'analyse LR
pour la grammaire des expressions arithmétiques restreintes aux opérateurs
binaires + et *.
(1) E E+T
(2) E T
(3) T T * F (5)
(4) T F
(5) F (E)
(6) F id
Le codage des actions est :
1. di signifie décaler et empiler l'état i
2. rj signifie réduire par la production (j)
3. acc signifie accepter et
4. une case vide signifie une erreur
Action Successeur
id + * ( ) $ E T F
0 d5 d4 1 2 3
1 d6 Acc
2 r2 d7 r2 r2
3 r4 r4 r4 r4
4 d5 d4 8 2 3
5 r6 r6 r6 r6
6 d5 d4 9 3
7 d5 d4 10
8 d6 d11
9 r1 d7 r1 r1
10 r3 r3 r3 r3
11 r5 r5 r5 r5
Tableau 4.4. Table d'analyse pour la grammaire des expressions
Notons que l'état atteint par transition sur le symbole a depuis l'état s est identifié
dans le champs Action[S,a] en même temps que l'action décaler, et que l'état atteint
par transition sur le non terminal A depuis l'état S se trouve en successeur [S,A].
Sur le texte d'entrée id*id+id, la séquence des contenus de la pile et du tampon
d'entrée est présenté à la figure 4.12:
PILE ENTREE ACTION
(1) 0 id * id + id $ décaler d5
(2) 0 id 5 * id + id $ réduire par F id (r6)
(3) 0 F 3 * id + id $ réduire par T F (r4)
(4) 0 T 2 * id + id $ décaler 7
(5) 0 T 2 * 7 id + id $ décaler 5
(6) 0 T 2 * 7 id 5 + id $ réduire par F id (r6)
(7) 0 T 2 * 7 F 10 + id $ réduire par F id
(8) 0 T 2 + id $ réduire par E T
(9) 0 E 1 + id $ décaler 6
(10) 0 E 1 + 6 id $ décaler 5
(11) 0 E 1 + 6 id 5 $ réduire par F id (r6)
(12) 0 E 1 + 6 F 3 $ réduire par T F (r4)
(13) 0 E 1 + 6 T 9 $ réduire par E E+T
(14) 0 E 1 $ accepter
A la ligne (1) l'analyseur LR est dans l'état 0 avec id comme premier symbole en
entrée. L'entrée ligne 0 colonne id de la table Action de la figure 4 est d5 qui signifie
décaler et empiler l'état 5. C'est ce qui a été fait à la ligne (2).
A ce moment, * devient le symbole d'entrée courant et l'action dans l'état 5 sur
l'entrée * est réduire par F id. Deux symboles sont dépilés, (un symbole d'état et
un symbole de la grammaire). L’état 0 est donc au sommet de la pile. Comme la
valeur du champ successeur pour l'état 0 sur F est 3, F et 3 sont empilés.
Grammaires LR
Une grammaire pour laquelle nous pouvons construire des tables d'analyse est
appelée grammaire LR.
Intuitivement, pour qu'une grammaire soit LR, il suffit qu'un analyseur par décalage-
réduction gauche-droite soit capable de reconnaître les manches quand ils
apparaissent en sommet de pile.
Il existe une différence significative entre les grammaires LL et LR. Pour qu'une
grammaire soit LR(k), on doit être capable de reconnaître l'occurrence de la partie
droite d'une production en ayant vu tout ce qui est dérivé de cette partie droite et
une prévision de k symboles en entrée. Cette condition est beaucoup moins
contraignante que pour les grammaires LL(k), pour lesquelles on doit être capable de
reconnaître l'usage d'une production à la vue des k premiers symboles de dérivés de
sa partie droite.
Remarques.
1. Le symbole d'état en sommet de pile contient toutes les informations dont
l'analyseur LR a besoin pour savoir quand un manche apparaît au sommet.
2. S'il est possible de reconnaître un manche en connaissant uniquement les
symboles grammaticaux en pile, il existe un automate à états fini qui peut, en
lisant les symboles grammaticaux de la pile depuis le fond vers le sommet,
déterminer quel manche, s'il y'en a, est en sommet de pile. Les Fonctions
Action et Successeur des tables d'analyse LR représentent essentiellement la
fonction de transition d'un automate fini.
3. L'automate fini n'a cependant pas besoin de lire la pile à chaque transition. Le
symbole d'état stocké en sommet de pile est l'état dans lequel l'automate fini
reconnaissant les manches serait s'il avait lu depuis le fond vers le sommet ,
les symboles grammaticaux de la pile . L'analyseur LR peut donc déterminer à
partie de l'état en sommet de pile, toutes les informations de la pile qu'il a
besoin de connaître.
Nous donnerons trois méthodes qui diffèrent par leur puissance et leur facilité
d'implémentation. La première appelée "Simple LR" ou SLR en abrégé, est la moins
puissante des trois en termes du nombre de grammaires pour lesquelles elle réussit
mais elle est la plus simple à implémenter.
Des tables d'analyse Construites par cette méthode sont appelées Tables SLR et
l'analyseur est appelé analyseur SLR. Une grammaire pour laquelle il est possible de
construire un analyseur SLR est appelée grammaire SLR.
Définition. Un item LR(0) (item en abrégé) d'une grammaire G est une production
de G avec un point repérant une position de sa partie droite.
Exemple 4..19.
A XYZ
A XYZ
A XYZ
A XYZ
A XYZ
Exemple 4.20.
A XYZ indique qu'on espère avoir en entrée une chaîne dérivable depuis XYZ
A XYZ indique que nous venons de voir en entrée une chaîne dérivée de X et que
nous espérons maintenant voir une chaîne dérivée de YZ.
Exemple 4.21.
(1) E E+T E E+T, E E+T, E E+T, E E+T (manche reconnu)
(2) E T E T , E T (manche reconnu)
(3) T T * F T T * F, T T * F, T T * F, T T * F (manche reconnu)
(4)T F T F , T F (manche reconnu)
(5) F (E) F (E) , F (E) , F (E) , F (E) (manche reconnu)
(6)F id F id , F id (manche reconnu)
Exemple 4.22.
Considérons la grammaire augmentée des expressions:
E' E
E E+T |T
T T*F|F (5)
F (E) |id
Si I est l'ensemble formé de l'unique item {[E' E]}, alors Fermeture (I) contient les
items:
E+T
E' →
T*F
T (E)
F
id
on a donc
E' E ici E' E est placé dans
E Fermeture (I) par la règle (1).
E+T E Comme il y'a un E
T immédiatement à droite d'un
T T*F
L'opération Transition.
Transition (I,X) où I est un ensemble d'items et X est un symbole de la grammaire.
Transition(I,X) est définie comme la fermeture de l'ensemble de tous les items
[AX] tel que [[AX] appartienne I. Intuitivement, si I est l'ensemble
d'items qui sont valides pour un préfixe viable donné , alors Transition(I,X) est
l'ensemble des items qui sont valides pour le préfixe viable X.
Exemple 4.23.
Si I est l'ensemble des deux items {[E'E], [E E+T], alors Transition(I,+)
consiste en:
fin
Exemple 4.24.
La collection canonique d'ensembles d'items LR(0) pour la grammaire augmentée :
G' (V, , R, S), avec V = {E', E, T, F, +,*,(,), id}, = {+,*,(,),id}, S = E' et R définit par
les règles de production suivantes:
E' E
E E+T |T (5)
T T*F|F
F (E) |id
est présentée ci dessous:
I0 : {fermeture ({[E' E]})}
I7 := Transition (I2, *)
E' → •E T →
E → •E+T T*•F F
E → •T → • (E) F
T→ → • id
•T*F T
→ •F
F→• I8 := Transition (I4, E)
(E) F → F → (E•)
• id E → E•+T
I1 := transition(I0,E)
I9 := transition (I6,T)
E' → E•
E E+T
E → E•+T
T → T•*F
I2 := transition(I0,T)
Transition (I4,F) =
E → T•
I3 Transition (I4, ( )
T → T•*F
= I5
I4 := Transition (I0, ( )
F → (•E)
I11 = Transition (I8, ) )
E → F (E)
•E+T E →
•T
T →
•T*F T
→ •F
F → •
(E) F →
• id
Transition (I0,+) =
Transition (I0,*) =
Transition (I0, ) ) =
I5 := Transition (I0, id )
F → id•
I6 := transition (I1,+)
E → E+•T
T → •T*F
T → •F
F → •
(E) F →
• id
La fonction de Transition pour cet ensemble est donnée sous la forme d'un
diagramme de transition d'un automate fini déterministe D (voir figure 4.13).
Figure 4.13. Diagramme de transition de l'automate fini déterministe D reconnaissant les préfixes
viables.
Remarque 1. Si chaque état D de la figure 6 est un état final et I0 est l'état initial,
alors D reconnaît exactement les préfixes viables de la grammaire G' de l'exemple 10.
Remarque 2 : On peut imaginer un NFA N dont les états sont les items eux-mêmes
avec une transition de [AX] vers [AX] étiquetée X, et il y'a une transition
de [AB] vers B étiquetée . Alors Fermeture(I) pour l'ensemble d'items
(états de N) I est exactement la -fermeture de l'ensemble des états du NFA déjà vue
dans le cours. Donc Transition (I,X) donne la transition depuis I sur le symbole X
dans le DFA produit à partir de N par la construction des sous ensembles.
Items Valides.
Nous disons que l'item [A 12] est valide pour un préfixe viable 1 s'il existe une
*
dérivation S ' αAω αβ1β 2ω(w *)
d d
Exemple 4.25.
Soit le préfixe viable E+T* (1); Cherchons les items valides pour ce préfixe.
Cherchons alors toutes les possibilités de dérivation qui font apparaître ce préfixe.
E E+T
E E+T*F
donc TT* F est un item valide pour E+T*
suffixe du 1 2
préfixe viable
Continuons
E E+T aussi E E+T
E+T*F E+T*F
E+T*(E) E+T*id
donc donc F id
F (E)
2
2
1 vide
Nous pouvons facilement calculer l'ensemble des items valides pour chaque préfixe
viable qui peut apparaître sur la pile d'un analyseur LR, en utilisant le théorème
suivant:
Théorème: L'ensemble des items valides pour le préfixe viable est exactement
l'ensemble des items atteints depuis l'état initial, le long d'un chemin étiqueté
dans le DFA construit à partir de la collection canonique d'ensembles d'items dont les
transitions sont données par transition.
Remarque.
le fait que A 12 soit valide pour 1 nous en dit beaucoup sur l'action décaler
ou réduire que l'on doit effectuer quand on trouve 1 sur la pile d'analyse.
si 2 , il suggère que nous n'avons pas encore décalé le manche sur la pile donc
on doit décaler
si 2 = , il semblerait que A 1 soit le manche et que nous devions réduire par
cette production.
Exemple 4.26.
Construisons les tables SLR pour la grammaire G' de l'exemple 4.22.
La collection canonique des ensembles d'items LR(0) pour G' est représentée dans
l'exemple 4.24.
Considérons tout d'abord l'ensemble d'items I0:
I0 :E' E l'item F (E) produit l'entée
E → •E+T Action[0,(] = décaler 4 et l'item
E → •T
T → •T*F Fid produit l'action[0,id] =
Considérons I1
E' → E• Le premier Item produit Action [1,$]
E E+T = accepter et le second item produit
Action[1,+] = décaler 6.
Considérons maintenant I2
E → T• comme Suivant(E) = {$,+,)}, le
T → T•*F premier item produit Action[2,$] =
Action[2,+] = Action[2,)] = réduire
par E T
Le second item produit Action [2,*] =
décaler 7 (car transition (I1,*) = I7
dans le DFA)
En continuant ainsi, nous obtenons les tables Action et successeur suivantes:
Action Successeur
id + * ( ) $ E T F
0 d5 d4 1 2 3
1 d6 Acc
2 r2 d7 r2 r2
3 r4 r4 r4 r4
4 d5 d4 8 2 3
5 r6 r6 r6 r6
6 d5 d4 9 3
7 d5 d4 10
8 d6 d11
9 r1 d7 r1 r1
10 r3 r3 r3 r3
11 r5 r5 r5 r5
Notation postfixée :
La notation postfixée pour une expression E peut être définie récursivement comme
suit :
1. Si E est une variable ou une constante, la notation postfixée de E est E elle-
même.
2. Si E est une expression de la forme E1 op E2, où op est un opérateur binaire, la
notation postfixée de E est '
E' 'op, où E 'et E sont les notations postfixées
E1 2 1 2
de E1 et E2 respectivement.
3. Si E est une expression de la forme (E1), la notation postfixée de E est la
notation postfixée de E1.
Remarque. Aucune parenthèse n'est nécessaire en notation postfixée, car la
position et l'arité (nombre d'arguments) des opérateurs ni permettent qu'un seul
décodage de l'expression postfixée. Par exemple, la notation postfixée (9 - 5) + 2 est
95 -2+ et celle de 9 – (5+2) est 952+-.
Exemple 5.1.
La figure 5.1 représente une définition dirigée par la syntaxe pour traduire des
expressions formées de chiffres séparés par des signes + ou – en une notation
postfixée. Est associé à chaque non terminal un attribut t dont la valeur est une
chaîne qui représente la notation postfixée de l'expression engendrée par ce terminal
dans un arbre syntaxique.
expr.t = 95 – 2 +
expr.t = 95 – terme.t = 2
expr.t = 9 terme.t = 5
terme.t = 9
9 - 5 + 2
Figure 5.2 : Valeurs des attributs aux nœuds d'un arbre syntaxique
Une définition dirigée par la syntaxe n'impose aucun ordre spécifique pour
l'évaluation des attributs sur l'ordre syntaxique ; tout ordre d'évolution qui calcule
l'attribut a après tous les attributs dont a dépend est acceptable.
On peut utiliser le parcours en profondeur. Le parcours d'un arbre débute à la racine
et récursivement, visite les fils de chaque nœud dans un ordre gauche-droite, comme
le montre la figure 5.3.
On évalue les règles sémantiques à un nœud donné après que tous les descendants
de ce nœud ont été visités.
reste
Le nœud pour une action sémantique n'a pas de fils, l'action est exécutée quand ce
nœud est visité pour la première fois.
Remarque.
Les définitions dirigées par la syntaxe mentionnées jusqu'à maintenant ont la
propriété suivante : la chaîne représentant la traduction du non-terminal en partie
gauche de chaque production est la concaténation des traduction des non terminaux
en partie droite dans le même ordre que dans la production, avec éventuellement des
chaînes venues s'insérer entre ces traductions. Une définition dirigée par la syntaxe
qui a cette propriété est simple.
Exemple 5.2.
Production Règle sémantique
Expr expr 1 + terme expr.t : = expr1.t terme.t'+'
Reste + terme reste 1 reste.t : = terme.t '+' reste1.t
chaîne additionnelle
apparaît avant
Les définitions dirigées par la syntaxe simple peuvent être implantées par des
schémas de traduction dans lesquelles les actions impriment les draines
additionnelles dans l'ordre dans lesquels ils apparaissent dans la définition.
Exemples 5.3.
La figure 5.5 représente un schéma de traduction dérivé de la définition de la figure
5.1.
expr expr + terme Imprimer ('+')
expr expr - terme Imprimer '-'
expr terme
terme 0 Imprimer ('0')
terme 1 Imprimer ('1')
---
terme 9 Imprimer ('9')
Figure 5.5. Actions traduisant des expressions en notation postfixée
expr
9 {Imprimer ('9')}
Figure 5.6. Actions traduisant 9 – 5 + 2 en 95 – 2 +
+ liste
_ 2 liste
chiffre
9 5 liste chiffre
chiffre
9 _ 5 + 2
Remarque.
Il est souhaitable qu'un schéma de traduction s'appuie sur une grammaire dont les
arbres syntaxiques sont aussi près que possible des arbres abstraits.
Remarque.
On peut désigner une définition dirigée par la syntaxe par SDTS abstraite pour
syntax Directed Translation schéma abstrait et le schéma de traduction tel qu'il a été
présenté SDTS concret.
Contrôle de type
De nombreux compilateurs Pascal combinent les contrôles statiques et la production
de code intermédiaire avec l'analyse syntaxique. Pour des constructions plus
complexes comme celles d'Ada, on peut faire intervenir une passe séparée pour le
contrôle de type, placée entre l'analyse syntaxique et la production de code
Intermédiaire, comme indiqué dans la figure 6.1.
(b) Les produits. Si Tl et T2 sont des expressions de type, leur produit cartésien Tl
X T2 est une expression de type. Nous supposons que X est associatif à
gauche.
(c) Les structures. En un certain sens, le type d'une structure est le produit des
types de ses champs. La différence entre une structure et un produit est que
les champs d'une structure ont des noms.
(d) Les pointeurs. Si T est une expression de type, pointeur(T) est une expression
de type dénotant le type «pointeur vers un objet de type T ».
(e) Les fonctions. En termes mathématiques, une fonction fait correspondre à des
éléments d'un ensemble de départ, le domaine, des éléments d'un ensemble
d'arrivée, le co-domaine. Nous pouvons considérer que les fonctions des
langages de programmation font correspondre au type domaine D le type co-
domaine A. Le type d'une telle fonction sera dénoté par l'expression de type
DA. Par exemple, la fonction intrinsèque « mod » de Pascal a pour type
domaine entier X entier, c'est-à-dire un couple d'entiers, et pour type co-
domaine entier. Nous disons donc que « mod » a pour type :
entier X entier entier.
Figure 6.2. La partie d’un schéma de traduction qui stocke le type d’un identificateur
Dans les règles qui suivent, l'attribut synthétisé type associé à E donne l'expression
de type affectée par le système de typage à l'expression engendrée par E. Les règles
sémantiques ci-dessous spécifient que les constantes représentées par les unités
lexicales littéral et nb sont de types caractère et entier respectivement:
Nous utilisons une fonction Rechercher(e) pour retrouver le type stocké dans l'entrée
référencée par e dans la table des symboles. Lorsqu'un identificateur apparaît dans
une expression, nous allons chercher son type déclaré et l'affectons à l'attribut type:
Dans un accès à un élément de tableau E1[E2], l'expression E2 doit être de type entier,
auquel cas le résultat est le type t des éléments obtenu à partir du type tableau(s,t) de
E1; nous n'utilisons pas l'ensemble s des indices du tableau.
Dans les expressions, l'opérateur postfixe ↑ retourne l'objet référencé par son
opérande. Le type de E↑ est le type t de l'objet référencé par le pointeur E:
Puisque les constructions des langages telles les instructions n'ont en général pas de
valeur associée, le type de base spécial vide peut leur être affecté. Lorsqu'une erreur
est détectée dans une instruction, le type qui est affecté à cette instruction est
erreur_de_type.
Les instructions que nous prenons en compte sont l'affectation, la conditionnelle et la
boucle «tant que». Les séquences d'instructions sont séparées par des points-
virgules. Les productions de la figure 6.3 peuvent être combinées avec celles de la
grammaire G, en changeant simplement la production pour un programme complet
en PD;I. Un programme consiste alors en des déclarations suivies d'instructions ;
nous avons toujours besoin des règles précédentes pour le contrôle des expressions,
car les instructions peuvent contenir des expressions.
Les règles pour le contrôle des instructions sont données à la figure 6.3. La première
règle contrôle que les parties gauche et droite d'une instruction d'affectation sont du
même type. Les deuxième et troisième règles spécifient que, dans l'instruction
conditionnelle et la boucle « tant que », l'expression doit être de type booléen. Les
erreurs sont propagées par la dernière règle de la figure 6.3, parce qu'une séquence
d'instructions n'est de type vide que si chaque sous-instruction est de type vide. Dans
ces règles, une discordance de types produit le type erreur_de_type ; bien entendu,
un contrôleur de type convivial signalerait en outre la nature et l'emplacement de la
discordance de types.
Exemple 6.1.
Considérions les expressions formées en appliquant un opérateur arithmétique op à
des constantes et des identificateurs, comme dans la grammaire de la figure 6.4.
Supposons que nous ayons deux types réel et entier, les entiers étant convertis en des
réels lorsque cela est nécessaire. L'attribut type du non-terminal E peut avoir pour
valeur soit entier soit réel; les règles de contrôle de type sont données à la figure 6.4.
La fonction Rechercher(e) retourne le type stocké dans l'entrée référencée par e dans
la table des symboles.
Figure 6.4. Règles de contrôle de type pour la coercition d’entier vers réel
E E1 op E2
begin
E.type : = integer
GEN (E.place : = E1.place "intop" E2.place)
end
else
if (E1.type = real) and (E2.type = real) then
begin
E.type : = real
GEN (E.place : = E1.place "real op" E2place)
end
else
if F2.type = real then
begin
U : = NewTemp ( ) ;
GEN (U : = "intoreal" E1.place)
GEN (E.place : = U "realop" E2.place
E.type : = real
end
else // E2.type = integer
begin
U : = NewTemp ( ) ;
GEN (U : = intoreal E2.place)
A id : = E
if rechercher (id.entrée) = real then
Production de code
intermédiaire
A l'issue de l'analyse syntaxique et de l'analyse sémantique, certains compilateurs
construisent explicitement une représentation intermédiaire du programme source.
On peut considérer celle ci comme un programme pour une machine abstraite (pas
de limites quand au nombre de registres). Cette représentation intermédiaire doit
avoir deux propriétés importantes: Elle doit être facile à produire et facile à traduire
en langage cible.
L'une des formes de ce code intermédiaire est le code à 3 adresses
Remarque.
Le code intermédiaire est une représentation linéarisée d'un arbre abstrait, dans
laquelle les noms explicites correspondent aux nœuds internes de l'arbre.
La terminologie "code à trois adresses" peut se justifier par le fait que chaque
instruction contient en général trois adresses, deux pour les opérandes et une pour le
résultat.
Dans les implantations de code à trois adresses plus loin dans ce chapitre, un nom
définit par le programmeur est représenté par un pointeur référençant une entrée
dans la table des symboles.
Quand on produit du code à trois adresses, on crée des variables temporaires pour
les nœuds internes d'un arbre abstrait. La valeur du non terminal E partie gauche de
la production E E1 + E2 est calculée dans un nouveau temporaire t.
Dans un compilateur, les instructions à 3 adresses peuvent être implémentées par des
structures dont les champs contiennent l'opérateur et les opérandes. Parmi ces
représentations on trouve les quadruplets et les triplets.
Quadruplets. Un quadruplet est une structure possédant quatre champs appelés
respectivement op, arg1, arg2 et résultat. Le champ op contient un code interne pour
l'opérateur.
L'instruction à trois adresses x := y op z est représentée en plaçant y dans arg1, z
dans arg2 et x dans résultat.
Les instructions utilisant les opérateurs unaires tels que x := -y ou x := y n'utilisent
pas arg2.
Les instructions de branchement conditionnel (if A relop B Goto L) et inconditionnel
(Goto L) rangent leurs étiquettes dans résultat.
Exemple 7.1.
Les quadruplets de la figure 7.2 (a) représentant l'instruction d'affectation a:= b*-
c+b*-c. Elles correspondent au code à trois adresses.
t1 := -c
t2 := b *t1
t3 := -c
t4:= b*t3
t5 := t2
+t4 a := t5
Les contenus des champs arg1, arg2 et résultat sont des pointeurs vers les entrées de
la table des symboles des noms représentées par ces champs. Les variables
temporaires doivent donc être rangées dans la table des symboles lorsqu'elles sont
crée.
Triplets. On peut éviter de ranger des variables temporaires dans la table des
symboles en référençant une valeur temporaire par la position de l'instruction qui la
calcule. Dans ce cas, les instructions à 3 adresses sont représentées par les structures
à 3 champs: op arg1, arg2 comme dans la figure 7.2 (b). Les arguments arg1 et arg2
sont soit des pointeurs vers la table des symboles, soit des pointeurs vers la structure
des triplets (pour les valeurs temporaires).
E E1 op E2 E.place := newTemp
E.code := E1.code|E2.code|E.place ':=' E1.place 'op' E2.place
E id E.place := id.place
E.code := Null
Figure 7.3. SDTS abstraite pour traduire l'instruction d'affectation et les expressions arithmétiques
E E1 op E2 {E.place := newTemp();
Gen(E.place ':=' E1.place 'op' E2.place)}
E -E1 {E.place := NewTemp();
Gen(E.place ':=' '-' E1.place)}
E (E1) {E.place ':=' E1.place}
Figure 7.4. SDTS concrète pour traduire l'instruction d'affectation et les expressions arithmétiques
Faux
Nous produirons les quadruplets dans un tableau. Les étiquettes seront les indices de
ce tableau.
La production des quadruplets pour A :=
1 si A <B aller à Vrai B+C nous a permis de connaître le
2 Aller à faux numéro du quadruplet avec lequel
3 T1 := B+C commence cette instruction qui est 3.
4 A := T1 Le branchement indéfini de la ligne 1
5 devrait alors brancher vers le quadruplet
3
Aussi on a pu savoir que l'adresse du premier quadruplet qui suit le code pour A :=
B+C est 5. Le branchement indéfini de la ligne 2 devrait brancher vers 5.
Le code devient alors:
1 si A <B aller à 3
2 Aller à 5
3 T1:= B+C
4 A := T1
5
C'est le principe de reprise arrière.
Nous allons utiliser trois fonctions pour manipuler les listes d'instructions:
1. MakeList(i) crée une nouvelle liste contenant seulement i, indice dans le
tableau des quadruplets; makelist retourne un pointeur vers la liste ainsi crée.
2. Merge (p1,p2) concatène les listes pointées par p1 et p2 et retourne un
pointeur vers la liste résultat.
3. BackPatch (p,i) insère i comme étiquette cible dans chacune des instructions
de la liste pointée par p.
M
On utilise les attributs synthétisés True et False attachés au non terminal C pour
produire le code des expressions booléennes. Quand on produit le code pour C, on
ne produit pas complètement les instructions de branchement correspondant aux
sorties vrai et faux car leurs champs étiquette n'est pas rempli. On range ces
instructions de branchement incomplètes dans les listes pointées par C.true et
C.false.
Les actions sémantiques sont expliquées comme suit:
True True
False
C C1 et C2
False
Etudions la production C C1 et MC. Si C1 est faux, C est faux aussi; Les instructions
de C1.False appartiennent donc aux instructions de C.False.
Si C1 est vrai, il faut tester C2, donc l'instruction destination de C1.true doit débuter
le code produit par C2. On obtient cette instruction destination en utilisant le
Marqueur M.
L'attribut M.quad mémorise le numéro de la première instruction de C2.code. A la
production M on associe l'action sémantique {M.quad = nextQuad.
La variable globale nextQuad contient la valeur de l'indice du prochain quadruplet
qui sera produit. Cette valeur sera affectée à C1.True quand le reste de la production
C C1et M C2 aura été traité. Le schéma de Traduction complet est le suivant:
C.True := C2.true
C.False := Merge(C1.false, C2.False)}
Remarque.
L’action sémantique (5) produit deux instructions, un branchement conditionnel et
un branchement inconditionnel. Aucun des champs étiquette n'est rempli dans ces
deux instructions. On range l'indice de la première instruction produite dans une
liste et on donne à C.True la valeur du pointeur vers cette liste. On range la seconde
instruction produite ' aller à ' dans une liste et on donne à C.Faux la valeur du
pointeur vers cette liste.
C C1 ou C2
|C1 et MC2
|non C1
|(C1)
|id1 oprel id2
|bid
A bid := C
|id := E
True
I si C alors M1 I1 N sinon M2 I2
I.next = une liste contenant des numéros de quadruplets déjà générés qui sont des
branchements conditionnels et non conditionnels dont l'étiquette est manquante et
qui devraient aller à l'instruction qui suit I en ordre d'exécution.
si le contrôle passe à la fin de I1, on doit inclure à la fin de I1 une instruction de
branchement vers la fin de I. Nous utilisons le marqueur N pour produire ce saut.
N { N.nest :=
makelist(nextSuad) GEN
('aller à ') }
N a un attribut N.next qui pointe vers la liste contenant l'unique quadruplet 'aller à '
qui a un branchement non défini
Voici maintenant les règles sémantiques:
I si C alors M1I1N sinon M2I2 { Backpatch (C.True, M1.quad);
BackPatch (C.False, M2.quad);
I. next := Merge (I1.next, N.next, I2.next)}
M {M.quad := nextquad)}
Nous reportons les destinations des branchements quand C est vrai dans le
quadruplet M1.quad(début I1) de la même façon, nous reportons les destinations des
branchements quand C est faux dans le debut de I2. La liste I.next englobe tous les
sauts dont la destination est située à l'extérieur de I1 et I2 ainsi que celui produit par
N.
de façon analogue false
True
I si C alors M I
1
next
Les Boucles
True
aller à
explicite false
I. début
vers C.vrai
C.code
C.vrai vers C.Faux
I1.code
aller à I.début
C.Faux
next True
L'instruction qui suit L1 dans l'ordre d'exécution est le début de l'instruction I. Ainsi
la liste L1.next est reprise en arrière au début du code de I, indiqué par M.quad.
L I {L.next := I.next}.
Bibliographie
D. Thalman, « Conception et implantation de langage de programmation: une
introduction à la compilation », isbn : 0-88612-020-9, 1979.