3
I.1 Définition d'un compilateur ..................................................................................3
I.2 Compilation par analyse et synthèse ....................................................................3
I.2.1 Analyse du programme source: ....................................................................3
I.2.2 Phases d'un compilateur ................................................................................4
I.2.3 Partie Frontale et finale..................................................................................7
Chapitre II Objectifs et fondements de l'analyse lexicale ...................................9
II.1 Introduction .........................................................................................................9
II.2 Le rôle de l'analyseur lexical...............................................................................9
II.2.1 Pourquoi une analyse lexicale ....................................................................10
II.2.2 Unités lexicales, modèles et lexèmes .........................................................10
II.2.3 Attributs des unités lexicales......................................................................11
II.2.4 Spécification des unités lexicales...............................................................12
II.2.5 Opération sur les langages..........................................................................13
Chapitre III Des expressions régulières aux automates à états finis...............15
III.1 Expressions régulières .....................................................................................15
III.2 Automates à états finis .....................................................................................16
III.2.2 Automates à états finis non déterministes.................................................17
III.2.3 Automates finis déterministes...................................................................19
III.2.4 Algorithmes de Conversion ......................................................................20
Chapitre IV Reconnaissance des unités lexicales...............................................29
IV.1 Mémorisation du texte d'entrée: (couple de tampons).....................................29
IV.2 Diagrammes de transition ................................................................................30
IV.3 Un langage pour spécifier des analyseurs lexicaux (lex).................................34
Chapitre V Conception d'un générateur d'analyseurs lexicaux.......................36
V.1 Schéma d'un analyseur lexical ..........................................................................36
V.2 Reconnaissance fondée sur les Automates finis non déterministes ..................37
V.3 Reconnaissance fondée sur les Automates finis déterministes .........................39
Chapitre VI Introduction à l'analyse syntaxique, grammaires et dérivations
......................................................................................................................................40
VI.1 Introduction......................................................................................................40
VI.2 Grammaires non contextuelles ........................................................................40
VI.2.1 Définition d'une grammaire non contextuelle ..........................................41
VI.2.2 Définition d'une Dérivation .....................................................................41
VI.3 Arbres d'analyse et dérivations ........................................................................42
VI.3.1 Ambiguïté .................................................................................................43
VI.3.2 Associativité des opérateurs .....................................................................44
VI.3.3 Priorités des opérateurs.............................................................................44
Chapitre VII Analyse descendante (Top down)..................................................45
VII.1 Analyse par descente récursive ......................................................................45
VII.1.1 Principes de l'analyse par descente récursive ..........................................45
VII.1.2 Suppression de la récursivité gauche.......................................................47
VII.2 Analyse prédictive non récursive ...............................................................48
VII.2.3 Grammaires LL(1)...................................................................................54
Chapitre VIII Analyse Ascendante (décalage/réduction) ................................56
VIII.1 Les principes de l'analyse par décalage/réduction .......................................56
VIII.1.1 Définition d'un Manche..........................................................................57
VIII.1.2 Elagage du manche ................................................................................58
VIII.1.3 Implantation à l'aide d'une pile de l'analyse par décalage-réduction .....59
VIII.1.4 Définition Préfixes viables.....................................................................59
1
VIII.2 Analyseurs LR...............................................................................................60
VIII.2.1 Algorithme d'analyse LR .......................................................................60
VIII.2.2 Grammaires LR......................................................................................64
VIII.2.3 Tables d'analyse SLR .............................................................................70
Bibliographie ..............................................................................................................73
2
Chapitre I Introduction à la compilation
But Du cours : concevoir un logiciel qui étant donné la définition d'un langage
L1 (n'importe quel langage de programmation) va me donner un compilateur de
L1: Concevoir un générateur automatique de compilateurs
3
Exemple 1 : un arbre décrivant une instruction d'affectation E := m*c*c
:=
E *
m *
c c
3. L'analyse sémantique: au cours de laquelle on opère certains contrôles pour
s'assurer que l'assemblage des constituants d'un programme a un sens. Elle
répond aux questions: Est-ce que les opérandes de toutes les opérations sont
correctes (types), faut-il faire une conversion, est-ce que les indices d'un
tableau sont corrects. Si oui, extraire un programme écrit dans un autre
langage = Traduction.
Remarques 1 :
• Une fonction essentielle d'un compilateur est d'enregistrer les
identificateurs utilisés dans le programme source et de collecter de
l'information sur divers attributs de chaque identificateur. Ces attributs
fournissent de l'information concernant, par exemple, l'emplacement
mémoire assigné à un identificateur, son type, sa portée.
• Une table de symboles est une structure de données contenant un
enregistrement pour chaque identificateur, muni de champs pour chaque
4
identificateur. Cette structure de données permet de retrouver rapidement
l'enregistrement correspondant à un certain identificateur et d'accéder
rapidement aux données qu'il contient.
• Quand l'analyseur lexical détecte un identificateur, il le fait entrer dans la
table des symboles, cependant ses attributs ne peuvent normalement pas
être déterminés pendant l'analyse lexicale; par exemple, le type ne sera
déterminé que pendant l'analyse sémantique, il sera donc ajouté durant
cette phase.
5
Remarque 2 Détection et compte rendu des erreurs
chaque phase peut rencontrer des erreurs. La phase d'analyse lexicale peut détecter
des erreurs quand les caractères restant à lire ne peuvent former aucune unité lexicale
du langage. La phase d'analyse syntaxique détecte les erreurs dues au fait que le flot
d'unités lexicales n'est pas conforme aux règles structurelles (syntaxe) du langage.
Dans la phase d'analyse sémantique, le compilateur détecte les constructions ayant
une structure grammaticale correcte, mais telle que les opérations qu'elles 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.
identificateur expression
:=
+
position
expression expression
nombre
initiale identificateur
60
vitesse
Figure 2 : Arbre syntaxique pour : position := initiale + vitesse * 60
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
On peut considérer le code Intermédiaire comme un programme pour une machine qui
a une infinité de registres. Cette représentation doit avoir deux propriétés importantes;
Elle doit être facile à produire à partir de l'arbre syntaxique et facile à traduire en
langage cible.
6
Dans ce cours nous étudierons une forme intermédiaire appelée "code à 3 adresses"
qui est semblable au langage d'assemblage d'une machine dans laquelle chaque
emplacement peut jouer le rôle d'un registre. Un fragment de code à 3 adresses
consiste en une séquence d'instructions, dont chacune a au plus 3 opérandes.
Le programme source "position := initiale + vitesse * 60 pourrait être traduit en code
à trois adresses de la façon suivante:
temp1 := EntierVersRéel (60) la conversion de 60 peut être faite une fois pour
temp2 := id3 * temp1 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:
7
La partie frontale est constituée des phases qui dépendent principalement du langage
source et qui sont en grande partie indépendantes de la machine cible. Elle comprend
normalement l'analyse lexicale, l'analyse syntaxique, la création de la table des
symboles, l'analyse sémantique et la production du code intermédiaire.
La partie frontale peut aussi effectuer une certaine quantité d'optimisation du code.
Elle inclut aussi le traitement d'erreurs associé à chacune de ces phases.
8
Chapitre II Objectifs et fondements de l'analyse lexicale
II.1 Introduction
- Un avantage majeur d'un constructeur d'analyseurs lexicaux est qu'il peut utiliser les
algorithmes de filtrage les plus connus et en conséquence, créer des analyseurs
lexicaux efficaces pour des utilisateurs qui ne sont pas des experts en matière de
technique de filtrage par modèle.
Unité lexicale
Programme Analyseur Analyseur
Obtenir prochaîne
source lexical syntaxique
unité lexicale
Table des
symboles
9
- L'élimination des commentaires et des espaces(caractère blanc, tabulation,
fin de ligne)
- Relier les messages d'erreurs issus du compilateur, au programme source :
(garder la trace du nombre de caractère fin de ligne rencontrés pour
pouvoir associer un numéro de ligne à un message d'erreur).
Des outils spécialisés ont été conçus pour aider à automatiser la construction
d'analyseurs lexicaux et syntaxiques(on va les étudier ultérieurement)
- Quand on parle d'analyse lexicale, on utilise les termes "unité lexicale" "modèle" et
"lexème" avec des spécifications bien spécifiques. La figure 2 donne des exemples de
leurs utilisation.
10
- 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.
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.
11
Exemple 1 : Les unités lexicales et les valeurs d'attributs associées pour l'instruction
Fortran
Nous avons associé à cette unité un attribut à valeur entière. Le compilateur peut ranger la chaîne de
caractères composant le nombre dans la table des symboles et faire en sorte que l'attribut de l'unité
lexicale nb soit un pointeur vers la table des symboles.
Les expressions régulière sont une notation importante pour spécifier (donner une
expression formelle) des modèle. Chaque modèle reconnaît un ensemble de chaînes.
Chaînes et langage
-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.
Terme Définition
Préfixe de s Une chaîne obtenue en supprimant un
nombre quelconque, éventuellement nul,
de symboles en fin de s; par exemple, ban
est un préfixe de banane
Suffixe de S Une chaîne obtenue en supprimant un
nombre quelconque, éventuellement nul,
de symboles début de S; par exemple,
nane est un suffixe de banane
Sous-chaîne de S Une chaîne obtenue en supprimant un
préfixe et un suffixe de S. Par exemple
nan de banane. Tout suffixe ou préfixe et
12
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-chaîne propre de Toute chaîne non vide se qui est,
S respectivement, préfixe, suffixe ou sous
chaîne de s telle que s ≠ x
Sous suite de S Toute chaîne obtenue en supprimant un
nombre quelconque, éventuellement nul,
de symbole non nécessairement
consécutifs de S; par exemple, baan est
une sous suite de banane
Figure 3. Vocabulaire pour les portions de chaînes
Opération Définition
Union de L et M notée LCM L∪M = { S | S∈L ou S ∈ M }
Concaténation de L et M notée LM LM = { St | S∈L et t ∈ M }
Fermeture de kleene de L notée L* L* = ∪ i∞=0 Li
L* dénote un nombre quelconque
éventuellement nul, de concaténation de
L.
Fermeture positive de L notée L+ L+ = ∪ i∞=1 Li
L+ dénote un nombre quelconque non
nul, de concaténation de L.
13
Exemple 2 :
14
Chapitre III Des expressions régulières aux automates à états finis
Voici les règles qui définissent les expressions régulières sur un alphabet Σ. Associé à
chaque règle figure une spécification du langage dénoté par l'expression régulière
ainsi définie.
1. ε est une expression régulière qui dénote {ε}, c'est à dire l'ensemble constitué
de la chaîne vide.
2. Si a est un symbole de Σ, alors a est une expression régulière qui dénote{a},
c.a.d l'ensemble constitué de la chaîne a. D'après le contexte on peut parler de a
comme expression régulière, comme chaîne ou comme symbole.
3. Supposons que r et s soient des expressions régulières dénotant les langages
L(s) et L(r) alors :
(a) (r) | (s) est une expression régulière dénotant L(r) ∪ L(s)
(b) (r) (s) est une expression régulière dénotant L(r) L(s)
(c) (r)* est une expression régulière dénotant (L(r))*
(d) (r) est une expression régulière dénotant L(r) c.a.d on peut placer des
paires de parenthèses excédentaires autour d'expressions régulières si
on le désire
Remarque
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. | 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.
15
Exemple 1 : Soit Σ = { a, b}
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
(s|t)r=sr|tr rapport à |
εr=r ε est l'élément neutre pour la
rε=r concaténation
r* = ( r | ε)* Relation entre * et Σ
R**= r* * est idempotent
Figure 1 Propriétés algébriques des expressions régulières
Un reconnaisseur pour un langage est un programme qui prend en entrée une chaîne x
et répons "oui" si x est une chaîne du langage et "non" autrement. On compile une
expression régulière en un reconnaisseur en construisant un diagramme de transition
généralisé appelé automate fini. Un automate fini peut être déterministe ou non
déterministe, ce dernier terme signifiant qu'on peut trouver plus d'une transition
sortant d'un état sur le même symbole d'entrée.
16
III.2.2 Automates à états finis non déterministes
Un automate fini non déterministe (AFN en abrégé) est un modèle mathématique qui
consiste:
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.
Exemple 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 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 b b
début 0 1 2 3
En machine la fonction de transition d'un AFN peut être implantée à l'aide d'une table
de transition dans laquelle il y a une ligne pour chaque état et une colonne pour
chaque symbole d'entrées et ε, si nécessaire.
L'entrée pour la ligne i et le symbole a dans la table, donne l'ensemble des états qui
peuvent être atteints par une transition depuis l'état i sur le symbole a. (voir la figure
3)
17
SYMBOLE D'ENTREE
Etat a b
0 {0, 1} {0}
1 - {2}
2 - {3}
3 - -
Figure 3 Table de transition pour l'automate fini non déterministe de la
figure 2
L'AFN de la figure 2 accepte les chaînes d'entrées abb, aabb, babb, aaabb, … Par
exemple, aabb est acceptée par le chemin depuis 0, en suivant de nouveau l'arc
étiqueté a vers l'état 0, puis vers les états 1,2 et 3 via les arcs étiquetés a;b et b
respectivement.
Un chemin peut être représenté par une suite de transitions d'état appelée
déplacements. Ce diagramme montre les déplacement réalisées en acceptant la chaîne
d'entrée aabb. a a b b
0 0 1 2 3 En général, il existe plus d'une suite
de déplacements pouvant mener à
l'état d'acceptation
Bien d'autres suites de déplacements pourrait être faites sur la chaîne d'entrée aabb,
mais aucune des autres n'arrive à terminer dans un état d'acceptation.
a a b b
0 0 0 0 0
Cette suite stationne dans l'état de non acceptation 0.
Définition : Le langage défini par un AFN est l'ensemble des chaînes d'entrées qu'il
accepte.
Exemple 3 :
a
a
ε 1
2
0 b
ε b
3 4
Figure 4: Automate fini non déterministe acceptant aa*|bb*
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.
18
III.2.3 Automates finis déterministes
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 suivant montre comment simuler le comportement d'un AFD sur une
chaîne d'entrée.
Algorithme 1 simulation d'un AFD
Données : Une chaîne d'entrée x terminée par un caractère de fin de fichier fdf.
Un AFD D avec un état de départ e0 et un ensemble d'états
d'acceptation F.
Résultat : La réponse "oui" si D accepte x; "non" dans le cas contraire.
Méthode : La fonction transiter(e, c) donne l'état vers lequel il y a une transition
depuis l'état e sur le caractère d'entrée c. La fonction CarSuiv
retourne le prochain caractère de la chaîne d'entrée x.
e := e0
C:=CarSuiv();
Tanque c ≠ fdf faire
e:= Transiter(e, c);
c:= CarSuiv();
fin
si e ∈ F alors
retourner "oui"
sinon
retourner "non"
-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.
a b b
0 1 2 3
a
a
a
Figure 5 DFA acceptant (a|b)*abb
19
III.2.4 Algorithmes de Conversion
Données : Un AFN N
Résultat : Un AFD D qui accepte le même langage.
Méthode : Notre algorithme construit une table de transition Dtran pour D.
Chaque état de l'AFD est un ensemble d'états de l'AFN et on construit Dtran de telle
manière que D simulera "en parallèle" tous les déplacements possibles que N peut
effectuer sur une chaîne d'entrée donnée.
On utilise les opérations suivantes pour garder trace des ensembles d'états de l'AFN. E
représente un état de l'AFN et T représente un ensemble d'états de l'AFN.
opération Description
ε-fermeture(e) Ensemble des états de l'AFN accessibles depuis un état e
de l'AFN par de ε-transitions uniquement
ε-fermeture(T) Ensemble des états de l'AFN accessibles de puis un état e
appartenant à T par des ε-transitions uniquement
Transiter(T, a) Ensemble des états de l'AFN vers lesquels il existe une
transition sur le symbole à partir d'un état e appartenant à
T
ε
a
2 3
ε
ε
ε ε a b b
0 1 6 7 8 9 10
ε
b ε
4 5
ε
20
Avant de voir le premier symbole d'entrée, N peut appartenir à n'importe lesquels des
états de l'ensemble ε-fermeture(e0) ou e0 est l'état de départ de N (il peut être dans T =
{0, 1, 2, 4, 7}). Supposons que a est le prochain symbole d'entrée. Quand il voit a, N
peut passer dans l'un des états de l'ensemble Transiter (T, a)= Transiter({0, 1, 2, 4, 7},
a)={3, 8}.
Remarque ( Ceci est le même si on a déjà lu une partie de la chaîne et on est arrivé a
un caractère a à traiter).
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 5
Remarque :
21
-L'alphabet des symboles d'entrée est ici {a,b}. L'algorithme précédent 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(la figure 8 donne
la table du transition complète)
C b
b
a b b
A B D E
a
a a
Figure 9: DFA pour (a|b)*abb
22
Transformation d'une expression régulière en un DFA
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):
23
N(s
ε
ε
déb i f
ε
ε
Ici, i est un nouvel état de départ et f un nouvel état d'acceptation. Il y a une transition
sur ε depuis i vers les états de départ de N(s). Il y a une transition sur ε depuis les états
d'acceptation de N(s) et N(t) vers le nouvel état d'acceptation f. Les états de départ et
d'acceptation N(s) et N(t) ne sont pas les états de départ et d'acceptation de N(s|t).
Remarque :
Tout chemin depuis i vers f doit traverser soit N(s), soit N(t) exclusivement on voit
donc que l'automate composé reconnaît L(s) ∪ L (t).
i N(s N(t f
⇒ Toutes les transitions depuis l'état de départ de N(t) deviennent des transitions
depuis l'état d'acceptation de N(s). Le nouvel état fusionné perd son statut d'état de
départ ou d'acceptation dans l'AFN composé.
ε ε
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))*.
24
(d) Pour l'expression régulière parenthèse (s), utiliser N(s) lui même comme AFN.
Remarque :
Chaque fois qu'on construit un nouvel état, on lui donne un nom distinct. Ainsi, il ne
peut y avoir deux état 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.
La construction produit un AFN N(r) qui a les propriétés suivantes :
r9 r10
r7 r8 b
r5 r6 b
r4 * a
( r3 )
r1 | r2
a b
déb 2 a
3
déb b
4 5
25
On peut maintenant combiner N(r1) et N(r2) en utilisant la règle d'union pour obtenir
l'AFN
pour r3=r1|r2 :
ε
ε 2 a 3
déb 1 6
ε b
4 5 ε
L'AFN pour r4 est le même que celui pour r3. L'AFN pour (r4)* est alors :
a
2 3
ε
ε
déb 0 ε 1 6 ε 7
ε b
4 5 ε
déb a
7' 8
Pour obtenir l'automate pour r7 =r5r6, on fusionne les états 7 et 7' en appelant l'état
résultant 7 pour obtenir
ε
a
2 3
ε
ε
a
déb 0 ε 1 6 ε 7 8
ε b
4 5 ε
26
ε
a
2 3
ε
ε
a b b
déb ε ε
0 1 6 7 8 9 10
ε b
4 5 ε
Nous allons voir maintenant un algorithme qui, si on lui donne un AFN N construit
par l'algorithme 4 (construction de Thompson) et une chaîne d'entrée x, détermine si
N accepte x.
L'algorithme fonctionne en lisant le texte d'entrée, caractère par caractère, et en
calculant l'ensemble complet des états dans lesquels N peut se trouver après avoir lu
chaque préfixe de la chaîne d'entrée.
E := ε-fermeture({e0});
a:= Carsuiv();
Tant que a ≠ fdf faire début
E:=ε-fermeture (transiter(E,a));
A:= CarSuiv()
Fin
Si E∩F ≠ Ø alors
Retourner "oui
Sinon
Retourner "non"
27
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.
Cet algorithme peut être efficacement implanté en utilisant deux pile et un vecteur
indicé par les états de l'AFN.
-On utilise une pile pour garder trace de l'ensemble courant des états non
déterministe, et l'autre pile pour calculer l'ensemble suivant d'états non déterministes.
-On peut utiliser le vecteur de bits pour déterminer rapidement si un état non
déterministe est déjà dans la pile, de manière à ne pas l'ajouter une deuxième fois.
-Une fois qu'on a calculé l'état suivant sur la deuxième pile, on peut échanger le rôle
de deux piles
Exemple 7:
Soit N l'AFN de la figure 7 et soit x la chaîne d'entrée composée d'un seul caractère a,
D état de départ est ε-fermeture ({0})={0,1,2,4,7}. Sur la chaîne d'entrée a, il y a une
transition de 2 vers 3 et de 7 vers 8. Ainsi, T est {3,8}. La ε-fermeture de T donne le
prochain état {1,2,3,4,6,7,8}. Comme aucun de ces état non déterministes n'est un état
d'acceptation, l'algorithme retourne "non".
28
Chapitre IV Reconnaissance des unités lexicales
E = M * C * * 2 fdf
début de l'unité
lexicale Avant
*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.
Tout au long de cette section nous utiliserons le langage engendré par le grammaire
suivante comme exemple type.
Exemple 1:
Instr → Si expr alors instr
| Si expr alors instr sinon instr
|ε
terme → id
| nb
29
Où les terminaux si, alors, sinon, oprel, id et nb(jetons) engendrent les ensembles de
chaînes données par les définitions régulières suivantes.
Si → si
Alors → alors
Sinon → sinon
Oprel → < |<= | = | <>| > | >=
Id → lettre (lettre | chiffre)*
Nb → chiffre+(.Chiffre+)? (E(+|-)? Chiffre+)?
Objectif : Notre objectif est de construire un analyseur lexical qui isole le lexème
associé à la prochaîne 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ées par les constantes symboliques
PPQ, PPE, EGA, DIF, PGQ, PGE.
autre
autre
30
La figure 2 présente un diagramme de transition pour les modèles >= et >
> =
0 6 7
*
8
- Ce diagramme fonctionne comme suit : Dans l'état de départ o, on lit le prochain caractère d'entrée.
On suit l'arc > depuis 0 vers 6 si le caractère d'entrée est >. Sinon, on n'a réussi à reconnaître ni > ni >
=.
- En atteignant l'état 6, on lit le prochain caractère d'entrée, l'arc étiqueté = entre l'état
6 et l'état 7 doit être suivi si le caractère d'entrée est =. Autrement, l'arc étiqueté autre
conduit à l'état 8. Le double cercle sur l'état 7 indique que c'est un état d'acceptation,
état dans lequel l'unité lexicale >= a été reconnue.
- Noter que le caractère > et un autre caractère sont lus quand on progresse dans la
suite d'arcs depuis l'état initial jusqu'à l'état d'acceptation 8. Etant donné que le
caractère supplémentaire ne fait pas partie de l'opérateur de relation ≥ on doit reculer
le pointeur avant d'un caractère. On utilise une * pour signaler les états dans lesquels
ce recul dans l'entrée doit être fait.
- En général, il peut y avoir plusieurs diagrammes de transition, chacun d'entre-eux
spécifiant un groupe d'unités lexicales. Si un échec se produit quand on parcoure un
diagramme de transition, alors on recule le pointeur avant là ou il était à l'état initial
de ce diagramme et on active le diagramme de transition suivant (le pointeur avant est
reculé à la position du pointeur début).
- Si un échec intervient dans tous les diagrammes de transition, alors une erreur
lexicale a été détectée et on appelle une routine de récupération sur erreur
La figure 3 représente un diagramme de transition pour l'unité lexicale oprel
(opérateur de relation).
< =
0 1 2 Retourne (oprel, PPE)
>
3 Retourne (oprel, DIF)
autre
*
= 4 Retourne (oprel, PPQ)
31
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 4, 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.
Lettre ou chiffre
lettre autre
9 10 11 * Retourne(UnilexId (), RangerId())
* 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) 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 Unilex Id ( ) et Ranger Id
pour obtenir l'unité lexicale et la valeur d'attribut respectivement.
* Ranger Id travaille comme suit : elle 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é, Ranger Id ( ) rend 0.
- Si on trouve le lexème comme variable du programme, Ranger Id rend un
pointeur vers l'entrée dans la table des symboles.
- Si on ne trouve pas le lexème dans le table des symboles, il y est placé en tant
que variable et un pointeur vers cette nouvelle entrée est retourné.
* La procédure Unilex Id ( ), 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.
→Le lexème pour une unité lexicale possible doit être le plus long possible.
Exemple 3 : Quand l'entrée est 12.3E4 l'analyseur ne doit pas s'arrêter après avoir vu
12 ou 12.3.
Partant des états 25, 20 et 12 de la figure 5 on atteint les états d'acceptation après
avoir vu 12, 12.3 et 12.3E4, respectivement (on suppose que 12.3E4 n'est pas suivi
d'un chiffre.
32
chif
chif chif
chif
. chif E +ou chif * Return (RangerNb())
12 13 14 15 16 17 18 19
E chif
chif chif
. autre
20 chif 21 22 chif 23 24 * Return (RangerNb())
chif
autre
chif
25 26 27 * Return (RangerNb())
E chif
autre
autre
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 1 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.
33
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 :
délim
délim autre *
28 29 30
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.
* Le programme source Lex contenant les expressions régulières du langage ainsi que
les actions associés.
* Le programme lex.yy.c = représentation sous forme d'un tableau d'un diagramme de
transition construit à partir des exp reg de Lex.l avec une fonction qui utilise la table
pour reconnaître les lexèmes.
34
Expression régulière
1
3
Automate fini non déterministe
35
Chapitre V Conception d'un générateur d'analyseurs lexicaux
Dans cette section, nous étudions la conception d'un outil logiciel qui construit
automatiquement un analyseur lexical à partir d'un programme dans le langage Lex.
Générateur
Spécification du Automatique Analyseur lexical
langage source L d'Analyseurs de L.
(définition régulière) Lexicaux
lex.
m1 {action1} Un fragment à exécuter à chaque fois qu'on rencontre dans le texte d'entrée
un lexème qui concorde avec l'expression régulière mi.
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'expression 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.
36
lexème
début avant
simulateur d'automates à
états fini
Table de transition
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.
Nous apportons donc les modifications suivantes à l'algo 3.4 (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é.
* On continue à effectuer des transitions jusqu'à atteindre la terminaison (un ensemble
d'états n'ayant pas de transition sortante sur le symbole d'entrée courant.
37
Exemple 1 : 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 suivante :
a a
1 2 1 2
ε
a b b ε a b b
3 4 5 6 0 3 4 5 6
a b ε a b
b b
7 8 7 8
Figure 1 (a) NFA pour a, abb et a*b+ Figure 1 (b) NFA combinés
0 a 2 a 7 b 8 a
néant
1 4
3 7
7 Jeton 1 reconnu aab unité lexicale a*b+
m1
m3
0 a 2 b b m2 et m3 m3
5 6 8
1 4 b
8 8
3 7
7
38
V.3 Reconnaissance fondée sur les Automates finis déterministes
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.
Sur la chaîne d'entrée aaba, l'AFD parcourt les états suggérés par la simulation de
l'AFN dans la fig. 3.36, soit :
m1 m3
a 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
a
0137 247
39
Chapitre VI Introduction à l'analyse syntaxique, grammaires et
dérivations
VI.1 Introduction
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 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.
unité arbre
lexicale d'analyse
programme analyseur analyseur reste de la partie représentation
source lexical syntaxique frontale intermédiaire
lire unité
lexicale
table des
symboles
40
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:
Exemple 2:
La grammaire constitiée des productions suivantes définit des expressions
arithmétiques simples:
expr → exp op expr Dans cette grammaire les symboles
expr → (expr) terminaux sont : id + - * / ↑( )
expr → – expr les symboles non terminaux sont expr et
expr → id op et l'axiome est expr
op → +
op → – en abrégé:
op → *
op → / expr → exp op expr|(expr)|– expr| id
op →↑ op → +| – | * | / | ↑
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.
le symbole ⇒ signifie "se dérive en une étape"
*
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 α ⇒ γ
41
+
⇒ signifie "se dérive en une ou plusieurs étapes"
+
soit une grammaire G et S son axiome; on peut utiliser la relation ⇒ pour définir
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 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 treminal le plus à gauche est remplacé à chaque
étape. On écrit α ⇒ β on peut alors réécrire (4)
g
X Y Z
42
Formellement, étant donné une grammaire non contextuelle, un arbre syntaxique est
un arbre possédant les propriétés suivantes:
La racine est étiquetée par l'axiome
chaque feuille est étiquetée par une unité lexicale ou par ε
chaque nœud intérieur est étiqueté par un non terminal.
Si A est le non-terminal étiquetant un nœud intérieur et si les étiquettes des fils de ce
nœud sont, de gauche à droite, X1,X2,..,Xn, alors A→ X1X2…Xn est une production,
ici X1,X2, …,Xn représentant soit un non terminal, soit un terminal. Un cas
particulier A → ε qui signifie qu'un nœud étiqueté A a un seul fils étiqueté ε.
Exemple 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
9
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).
VI.3.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 5:
chaîne → chaîne + chaîne| chaîne - chaîne|0|1|2|3|4|5-6|7|8|9
La figure 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.
43
chaîne chaîne
9 5 5 2
Figure 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.
44
Chapitre VII Analyse descendante (Top down)
Dans cette section, nous introduisons les idées de base de l'analyse descendante qui
comprend plusieurs formes:
analyse descendante
sans rebroussement
avec Rebroussement (Analyse prédictive)
(analyse descendante récursive)
L'analyse descendante peut-être considérée comme une tentative pour déterminer une
dérivation gauche associée à une chaîne d'entrée.
Elle peut-être aussi vue comme une tentative pour construire un arbre d'analyse de la
chaîne d'entrée, en partant de la racine et en créant les nœuds de l'arbre suivant un
ordre prédéfini.
Nous présentons ici une analyse qui peut impliquer des retours arrière (nécessité de
passages répétés sur la chaîne d'entrée.
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
S
c A d
Figure2.a
45
La feuille la plus à gauche, étiquetée c, correspond au premier symbole de w. Nous
avançons maintenant le pointeur d'entrée sur a, second symbole de w, et nous
considérons la feuille suivante étiquetée A. Nous pouvons alors développer A en
utilisant la première alternative pour A et nous obtenons l'arbre suivant:
c A d
a b
Figure 2.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:
S
c A d
a
Figure 2.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.
⇒ Nous devons éliminer la récursivité à gauche
(notons que le terminal d'entrée ne change que quand un terminal de la partie droite
est accepté)
46
VII.1.2 Suppression de la récursivité gauche
A
A
A
A
A
β α α … α α
Figure 3.a
A
R
β α α ……. α ε
Figure 3.b
Exemple 2:
expr → expr + terme |terme
devient :
47
expr → terme R
R → + terme R | ε
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 suivante
recherche la production à appliquer dans une table d'analyse (qu'on va voir la méthode
de construction ultérieurement)
Tampon d'entrée a + b $
Pile
X
Programme
Y Flot de sortie
d'analyse prédictive
Z
$
Table d'analyse M
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 d'entrée
courant, ces deux symboles déterminent l'action de l'analyseur. Il y a trois possibilités:
1. si X = a = $, l'analyseur s'arrête et annonce la réussite finale de l'analyse.
2. Si X = a ≠ $ , l'analyseur enlève X de la pile et avance son pointeur d'entrée
sur le symbole suivant.
3. Si X est un non terminal, le programme consulte l'entrée M[X, a] de la table
d'analyse M. Cette entrée sera soit une X-production de la grammaire, soit une
erreur. Si par exemple, M[X, a] = {X→ UVW}, l'analyseur remplace X en
sommet de pile par WVU (avec U au sommet).(Nous supposerons que
48
l'analyseur se contente, pour tout résultat d'imprimer la production utilisée). si
M[X, a] = erreur, l'analyseur appelle une procédure de récupération sur erreur.
Le comportement de l'analyseur peut décrire en termes de ses configurations
qui décrivent le contenu de sa pile et le texte d'entrée restant.
Exemple 3:
Considérons la grammaire:
E→ TE'
E'→ +TE'|ε
T→ FT'
T'→ *FT'|ε
F →(E)|id
49
Voici une table d'analyse prédictive 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)
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.
50
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
E→ TE'
E'→ +TE'|ε
T→ FT'
T'→ *FT'|ε
F →(E)|id
Premier (E) = Premier(T) = Premier(F) = {(,id}
Premier(E') = {+,ε}
Premier(T') = {*,ε}
cela s'explique par
Premier(E) = Premier(T) sauf ε ∪[Premier(E') si ε ∈Premier(T)]
Premier(T) = Premier(F) sauf ε ∪[Premier(T') si ε ∈Premier(F)]
Premier(F) = Premier(( ) ∪ Premier(id)
={(,id}
donc Premier(T) = Premier(F) = {(,id} car ε ∉ Premier(F)
et Premier(E) = Premier(T) = {(,id} car ε ∉ Premier(T)
51
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
*
Premier(β) contient ε (β ⇒ ε), les éléments de SUIVANT(A) sont ajoutés à
SUIVANT(B).
Explication:
Suivant(E) = {$} ∪ Premier( ))
(règle 1) (car on a F → (E) règle 2)
= {$,)}
52
Suivant(F) = Premier(T') sauf ε ∪ suivant (T)
*
règle 2 appliquée à T →FT' règle 3 sur T→*FT' et T' ⇒ ε
={*} ∪ {+,$,)}
={*,+,$,)}
Exemple 6:
53
VII.2.3 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.
• L'entrée M[S',e] contient à la fois S' → eS et S' → ε, car Suivant(S') = {e,$} (donc
la règle 3, si ε est dans Premier(α), ajouter A →α à M[A,b] pour chaque terminal
b dans Suivant((A))
• D'autre part, premier(α) = {e} donc mettre S' → eS dans M[S',e]
• La grammaire est ambiguë (choix de la production à utiliser quand on voit e
(sinon). Nous pouvons résoudre cette ambiguïté en choisissant S' →eS
Une grammaire dont la table d'analyse n'a aucune entrée définie de façon multiple est
appelée LL(1). Le premier "L" de LL(1) signifie "Parcours de l'entrée de la gauche
vers la droite" (left to right scanning of the input), le second "L" signifie "Dérivation
gauche" (Leftmost Dérivation) et le "1" indique qu'on utilise un seul symbole d'entrée
de prévision à chaque étape nécessitant la prise d'une décision d'action d'analyse.
54
F→ (E)|id est LL(1)
Les grammaires
S → iEtS|iEtSeS|a (1) S → iEtSS'|a (2)
E →b S' → eS| ε
E→b
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.
55
Chapitre VIII Analyse Ascendante (décalage/réduction)
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
56
VIII.1.1 Définition d'un Manche
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.
*
C'est à dire que si S ⇒ αAω ⇒ αβω, A→β dans la position suivant (α) est une
d d
De même aAbcde est une proto-phrase droite dont le manche est A→Abc en
position2.
Exemple 2 :
Soit G: et la dérivation droite:
(1) E →E+E
E ⇒ E+E
(2) E →E *E (2) 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*id3
d
57
⇒ E+E * id3
d
⇒E + id2*id3
d
Considérons la proto-phrase droite E+E*id3. Dans cette dernière dérivation, E+E est
un manche de E+E*id3 alors que, selon la première dérivation id3 est un manche de
cette même proto-phrase droite.
L'inverse de la séquence des productions utilisées dans les réductions est une
dérivation droite de la chaîne d'entrée.
Exemple 3
E →E+E et la chaîne d'entrée: id1+id2*id3
E →E*E (2)
E →(E)
E →id
La figure 1 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 E→id
E+id2*id3 id2 E →id
E+E*id3 id3 E→id
E+E*E E*E E→E*E
E+E E+E E→E+E
E
Figure 1 Réductions effectuées par un analyseur par décalage/réduction
58
VIII.1.3 Implantation à l'aide d'une pile de l'analyse par décalage-réduction
Une bonne façon est d'utiliser une pile pour conserver les symbols gramaticaux 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.
Initialement la pile est vide et la chaîne ω est dans le tampon d'entrée.
PILE ENTREE
$ ω$
L'analyseur opère en décalant zéro, un ou plusieurs symboles d'entrée du tampon vers
la pile jusqu'à ce qu'un manche β se trouve en sommet de pile.
Exemple 4
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*id3→E+id2*id3 ⇒
d d d d
id1 + 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 E→id
(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
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
59
VIII.2 Analyseurs LR
Ce chapitre 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 mois 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.
Tampon d'entrée a1 … ai …. an $
Pile
Sm Programmme
Xm d'analyse LR flot de sortie
Sm-1
Xm-1
…
S0 action successeur
60
- Il détermine Sm, l'état en sommet de la pile, et ai, le symbole terminal d'entrée
courant.
- Il consulte Action [Sm,ai] ( l'entrée de la table des actions pour l'état Sm et le
terminal ai) qui peut avoir l'une des quatre valeurs:
1. décaler S, où S est un état.
2. réduire par une production de la grammaire A →β
3. accepter et
4. erreur.
- La fonction successeur prend comme argument un état et un symbole non
terminal et retourne un état.
Une configuration d'un analyseur LR est un couple dont le premier composant est
le contenu de la pile et le second est la chaîne d'entrée restant à analyser.
(S0X1S1 X2 S2 ……..XmSm, ai ai+1… an $)
Cette configuration représente la proto-phrase droite:
X1 X2…Xm ai ai+1…an
L'action suivante de l'analyseur est déterminée par la lecture de ai , le symbole
d'entrée courant, Sm, l'état en sommet de pile, et la consultation de l'entrée
Action[Sm,ai] de la table des actions d'analyse.
Les configurations résultantes, après chacun des quatres types d'actions sont les
suivantes:
1. si Action [Sm,ai] = décaler S, l'analyseur exécute une action décaler,
atteignant la configuration: (S0X1S1X2S2….XmSm ai S, ai+1…an$)
Ici l'analyseur a à la fois epilé le symbole d'entrée courant ai et le prochain
état S, qui est donné par Action[Sm,ai], ai+1 devient le symbole d'entée
courant.
2. si Action [Sm, ai] = réduire par A → β, alors l'analyseur exécute une
action réduire, atteignant la configuration:
(S0X1S1X2S2….Xm-rSm-r A S, ai ai+1…an$)
où S = Successeur[Sm-r,A], r = la longeur de β, partie droite de la production..
Ici l'analyseur commence par dépiler 2r symboles ( r symboles d'états et r
symboles grammaticaux), exposant ainsi l'état Sm-r au sommet. L'analyseur
empile alors à la fois A, partie gauche de la production et S, l'entrée pour
successeur [Sm-r,A].
Nous supposerons que la sortie est formée de la liste des productions par
lesquelles on réduit.
3. si Action[Sm, ai] = accepter, l'analyse est terminée.
4. si Action [Sm, ai] = erreur, l'analyseur a découvert une erreur et appelle
une routine de récupération sur erreur.
61
Algorithme 1 Analyse LR
Donnée : une chaîne d'entrée w et des tables d'analyse LR (les fonctions
Action et successeur) pour une grammaire G.
Résultat: si w est dans L(G), une analyse ascendante de w, sinon une
indication d'erreur.
Méthode : Initialement, l'analyseur a S0 en pile, où S0 est l'état initial et w$
dans son tampon d'entrée.
initialiser le pointeur ps sur le premier symbole de w$
répéter indéfiniment
début
soit S l'état en sommet de pile et a le symbole pointé par ps;
si Action [s,a] = décaler S' alors
début
empiler a puis S'
avancer ps sur le prochain symbole d'entrée
fin
sinon si Action[s,a] = réduire par A →β alors
début
dépiler 2*|β| symboles;
soit S' le nouvel état sommet de pile;
empiler A puis successeur [S',A];
emettre en sortie une identification de production A →β
fin
sinon si Action [S,a] = accepter alors
retourner
sinon Erreur ()
Fin
(1) E →E+T
(2) E →T
(3) T →T * F (5)
(4) T →F
(5) F → (E)
(6) F →id
62
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
Figure 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 suivante:
à 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.
63
VIII.2.2 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.
64
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 7:
(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)
L'opération fermeture
si I est un ensemble d'items pour une grammaire G, Fermeture de I est l'ensemble
d'items construit à partir de I par les deux règles:
65
1. Initialement, placer chaque item de I dans Fermeture (I)
2. Si A → α•Bβ est dans fermeture (I), et B → γ est une production, ajouter
l'item B→•γ à Fermeture (I), s'il ne s'y trouve pas déjà. Nous appliquons
cette règle jusqu'à ce qu'aucun nouvel item ne puisse plus être ajouté à
Fermeture de I.
autrement : soit A → α•Bβ avec B un non terminal, espérer avoir une chaîne
dérivable depuis B c'est espérer avoir toute chaîne dérivable à partir de toute partie
droite B → γ.
Exemple 8 : 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 Fermeture
E → •E+T (I) par la règle (1). Comme il y'a un E
E → •T immédiatement à droite d'un point,
T → •T*F par la règle (2) nous ajoutons les E-
T → •F productions avec des points aux
F → • (E) extrémités gauches. C'est –à dire
F → • id E→•E+T et E → •T, nous avons
maintenant un T immédiatement à
droite d'un point, nous ajoutons donc
T → •T*F et T → •F puis le F à
droite d'un point implique l'ajout de
F→•(E) et F → •id. On ne peut plus
alors ajouter aucun autre item à
Fermeture (I) par la règle (2).
66
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
[A→αX•β] tel que [[A→α•Xβ] 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 9: si I est l'ensemble des deux items {[E'→E•], [E→ E•+T], alors
Transition(I,+) consiste en:
Exemple 10 :
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
67
→ •E]})}
I0 : {fermeture ({[E'→ I7 := Transition (I2, *)
E' → •E T → T*•F
E → •E+T F → • (E)
E → •T F → • id
T → •T*F
T → •F I8 := Transition (I4, E)
F → • (E) F → (E•)
F → • id E → E•+T
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.
68
Figure 6 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 [A→α•Xβ] vers [A→αX•β] étiquetée X, et il y'a une
transition de [A→α•Bβ] 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
Définition: Nous disons que l'item [A →β1•β2] est valide pour un préfixe viable αβ1
*
s'il existe une dérivation S ' ⇒ αAω ⇒ αβ1β 2ω ( w ∈ Σ*)
d d
En général, un item sera valide pour plusieurs préfixes viables.
Exemple 11:
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 T→T* • F est un item valide pour E+T*
β1 β2
suffixe du
préfixe viable
69
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.
Exemple 12: L'automate de la figure 6 sera dans l'état I7 après avoir lu E+T* l'état
I7 contient les items: T→T*•F F→ •(E) F → •id
Remarque:
le fait que A →β1•β2 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.
70
a) si [A →α•aβ ] est dans Ii et Transition (Ii,a) = Ij, remplir Action[i,a]
avec "décaler j". Ici a doit être un terminal.
b) si [A →α•] est dans Ii, remplir Action [i,a] avec "réduire par A →α"
pour tous les a de suivant (A); ici A ne doit pas être S'.
c) si [S' →S•] est dans Ii, remplir Action[i,$] avec "accepter"
Les tables d'analyse formées des fonctions Action et successeur déterminées par
l'algorithme 3 sont appelées tables SLR(1) pour G.
un analyseur LR utilisant les tables SLR(1) pour G est appelé analyseur SLR(1)
pour G et une grammaire ayant des tables d'analyse SLR(1) est SLR(1). Nous
omettons en general le (1) apres SLR, car nous ne considérerons pas ici
d'analyseurs utilisant plus d'un symbole de prévision.
Exemple 13: construisons les tables SLR pour la grammaire G' de l'exemple 10.
La collection canonique des ensembles d'items LR(0) pour G' est représentée dans
l'exemple 10.
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 F→•id produit l'action[0,id] =
T → •T*F décaler5 les autres items de I0 ne
T → •F
F → • (E)
produisent aucune action.
→
F → • 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 premier
T → T•*F 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)
71
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
72
Bibliographie
73