Anda di halaman 1dari 73

Chapitre I Introduction à la compilation..............................................................

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

I.1 Définition d'un compilateur

Un compilateur est un programme qui lit un programme écrit dans un premier


langage- le langage source- et le traduit en un programme équivalent écrit dans un
autre langage, le langage cible. Au cours du processus de traduction, l'un des rôles
importants des compilateurs est de signaler à l'utilisateur la présence d'erreurs dans le
programme source.

programme source programme cible


écrit en L1 Compilateur écrit en L2

Langage source par exemple Pascal, C, Fortran


Langage cible: un autre langage de programmation ou langage machine.

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

I.2 Compilation par analyse et synthèse

Il y'a deux parties dans la compilation: l'analyse et la synthèse.


La partie analyse: partitionne le programme source en ses constituants et en crée une
représentation intermédiaire. (Elle vérifie avant si le programme est bien écrit selon
les spécifications du programme source).
La partie synthèse: construit le programme cible désiré à partir de cette
représentation intermédiaire.

I.2.1 Analyse du programme source:

Cette analyse comprend 3 phases:

1. L'analyse linéaire (analyse lexicale): le flot de caractères formant le


programme source est lu de gauche à droite et groupé en unités lexicales qui
sont des suites de caractères ayant une significatiuon collective. Cette phase
aussi répond à la question: est-ce que les mots (jetons) qui existent dans ce
programme source sont corrects? On utilise alors les automates à états fini
déterministes et non déterministes.

2. L'analyse hiérarchique (analyse grammaticale ou syntaxique = Parsing):


Cette phase répond à la question: est ce que la séquence de ces mots (resultant
de l'analyse linéaire ) forme bien des phrases cohérentes, si oui, ces unités
lexicales seront grouppées hiérarchiquement dans des collections imbriquées
ayant une signification collective (arbre syntaxique, Parse tree)

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.

I.2.2 Phases d'un compilateur

Un compilateur opère en phases, chacune d'elles transformant le programme source


d'une représentation en une autre comme le montre la figure 1:
Exemple 2 (Analyse lexicale)
Les caractères formant l'instruction d'affectation :
position := initiale + vitesse * 60
seraient groupées dans les unités lexicales suivantes:
1. L'identificateur position
2. Le symbole d'affectation :=
3. L'identificateur initiale
4. Le signe +
5. L'identificateur vitesse
6. Le signe de multiplication *
7. Le nombre 60
Les blancs séparent les caractères formant ces unités lexicales seront éliminées au
cours de l'analyse lexicale.

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.

Figure 1: Phases d'un compilateur

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.

Remarque 3: Analyse syntaxique


Cette phase consiste à regrouper les unités lexicales du programme source en
structures grammaticales qui seront employées par le compilateur pour synthétiser son
résultat. Ces phases sont en général représentées par un arbre syntaxique comme celui
de la figure suivante:
Instruction
d'affectation

identificateur expression
:=
+
position
expression expression

identificateur 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

Remarque 4 : Génération du code Intermédiaire

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)


temp2 := id3 * temp1
temp3 := id2 + temp2
id1 := temp3

Remarque 5 : Optimisation du code


Cette phase tente d'améliorer le code intermédiaire de façon que le code machine
résultant s'exécute plus rapidement.
Exemple 3 :

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:

temp1 := id3 * 60.0


id1 := id2 + temp1

Remarque 6: Production du code


La phase finale d'un compilateur est la production du code cible. Un aspect crucial de
ce processus est l'assignation de variables aux registres.
Exemple 4 : pour le code intermédiaire:

temp1 := id3 * 60.0


id1 := id2 + temp1

on obtient le code objet suivant:

LOAD id3, R1 charge le contenu de id3 dans le register R1


MULF #60.0, R1 MULF: multiplication en virgule flottante, #: constante
LOAD id2,R2
ADD R1, R2 additionne le contenu de R1 et R2, le résultat est place dans R1
STORE R1, id1 copie le contenu de R1 dans l'emplacement id1

I.2.3 Partie Frontale et finale

La construction d'un compilateur se divise généralement en deux parties:


une partie frontale et une partie finale.

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.

La partie finale : comprend les portions du compilateur qui dépondent de la machine


cible; celles ci ne dépendent pas du langage source, mais seulement du langage
intermédiaire. On trouve dans la partie finale certains aspects de l'optimisation du
code ainsi que la production de code avec les opérations nécessaires de traitement
d'erreurs et de gestion de la table des symboles.
Remarque: cette division a pour avantage de pouvoir reprendre la partie frontale d'un
compilateur et réécrire sa partie finale pour obtenir des compilateurs du même
langage source pour plusieurs machines différentes. Le sens inverse n'est pas vraiment
utilisé en pratique (réutilisation de la même partie finale pour plusieurs langages
sources)

8
Chapitre II Objectifs et fondements de l'analyse lexicale

II.1 Introduction

Ici, on traite des techniques de spécification et d'implémentation d'analyseurs lexicales


une manière simple de construire un analyseur lexical consiste à bâtir un diagramme
qui illustre la structure des unités lexicales du langage source, puis de traduire à la
main le diagramme en programme qui reconnaît les unités lexicales.

- Un outil logiciel qui automatise la construction d'analyseurs lexicaux permet à des


utilisateurs de cultures différentes d'employer des comparaisons de modèles, ou
filtrage dans leurs propre domaines d'application. Par exemple, Javis [1976] à utiliser
un constructeur d'analyseurs lexicaux pour créer un programme qui reconnaît des
imperfections dans les circuits imprimés.

- 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.

II.2 Le rôle de l'analyseur lexical

l'analyseur lexical constitue la première phase d'un compilateur, sa tache principale


est de lire les caractères d'entrée et de produire comme résultat, une suite d'unité
lexicales que l'analyseur syntaxique va utiliser(voir figure 1).

Unité lexicale
Programme Analyseur Analyseur
Obtenir prochaîne
source lexical syntaxique
unité lexicale

Table des
symboles

Figure 1 : Interaction entre un analyseur lexical


et un analyseur syntaxique

A la réception d'une commande "prochaîne unité lexicale" de l'analyseur syntaxique,


l'analyseur lexical lit les caractères d'entrée jusqu'à ce qu'il puisse obtenir la prochaîne
unité lexicale

- Cette interaction est couramment implantée en faisant de l'analyseur lexical un sous


programme de l'analyseur syntaxique.
- l'analyseur lexical peut aussi réaliser certaines taches secondaires :

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).

On divise souvent les analyseurs lexicaux en deux phase :


- lecture (taches simples, exemple éliminer les blancs)
- analyse lexicale :réalise les opérations les plus complexes.

II.2.1 Pourquoi une analyse lexicale

De nombreuse raisons justifient de découper la phase d'analyse de la compilation en


analyse lexicale et analyse syntaxique.

1) Une conception plus simple : (simplifier l'une ou l'autre des phases)


exemple : un analyseur syntaxique contiendrait les traitements relatifs aux
commentaires et aux espaces serait nettement plus complexe que celui qui les
considères déjà éliminés par un analyseur lexical.

2) L'efficacité du compilateur est améliorée :


un analyseur lexical séparé permet de construire un module spécialisé et
potentiellement plus efficace pour cette tache (utilisation de technique spécialisées de
gestion des tampons au cours de la lecture des caractères d'entrée et du traitement des
unités lexicales.

3) La portabilité du compilateur est accrue. Les particularités de l'alphabet d'entrées et


d'autres anomalies spécifiques au matériel peuvent être confiées à l'analyseur lexical(
la représentation des symboles spéciaux ou non standard, comme la ↑ en Pascal, peut
ètre restreinte à l'analyseur lexical).

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)

II.2.2 Unités lexicales, modèles et lexèmes

- 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.

- 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.

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.

Unité lexicale Lexèmes Description informelle des


modèles
const const const
if if if
oprel (opérateur de < <= = <> > >= < <= = <> > >=
relation)
id Pi compte D2 lettre suivie de lettres ou
chiffres
nb 3.1416 0 6.02E23 Toute constante numérique
littéral "cours compilation" Tous caractère entre"et"
sauf "

Figure 2 Exemples d'unités lexicales

- Dans la plupart des langages de programmation, les constructions suivantes sont


traitées comme des unités lexicales : mots clés, opérateurs, identificateurs, constantes,
chaînes littérales et symbole de ponctuation ( parenthèses, virgules, :).
- Dans l'exemple précédent quand la suite de caractères pi apparaît dans le programme
source, une unité lexicale représentant un identificateur est retournée à l'analyseur
syntaxique ( souvent en passant un entier correspondant à l'unité lexicale, c'est cet
entier qui est référencé par l'unité lexicale id).
- Un modèle est une règle qui décrit l'ensemble des lexèmes pouvant représenter une
unité lexicale particulière dans les programmes sources.
- Le modèle de l'unité lexicale const est limité à la simple chaîne const qui
orthographie le mot clé.
- Le modèle de l'unité lexicale oprel est l'ensemble des six opérateurs de relation de
pascal.
- Pour décrire précisément les modèles d'unités lexicales plus complexes, comme id et
nb nous utiliserons la notation des expressions régulières que nous allons voir.

II.2.3 Attributs des 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.

11
Exemple 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 :

<id, pointeur vers l'entrée de la table des symboles associés à E>


<op-affectation> /* aucune valeur d'attribut n'est nécessaire, le premier composant suffit pour identifier
le lexème.
<id, pointeur vers l'entrée de la table des symboles associés à M>
<op-multiplication,>
<id, pointeur vers l'entrée de la table des symboles associés à C>
<op-exponentiation,>
<nb, valeur entière 2>

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.

II.2.4 Spécification des unités lexicales

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

- Le terme langage dénote un ensemble quelconque de chaînes construites sur un


alphabet fixé.
- Si x et y sont deux chaînes, alors la concaténation de x et de y, qui s'écrit xy, est la
chaîne formées en joignant x et y. Par exemple si x = porte et y = mine alors
sy=portemine. La chaîne vide est l'élément neutre pour la concaténation; cela signifie
que Sε = εS = S.
- Si l'on considère la concaténation comme un produit, on peut définir l'exponentiation
de chaîne comme suit : on définit S0 comme ε et pour i > 0, on définit Si comme Si-1 S.
Comme εS est S elle même, S1 = S. Donc S2 = SS ainsi de suite.

II.2.5 Opération sur les langages

Plusieurs opérations importantes peuvent s'appliquer aux langages. Pour l'analyse


lexicale, On s'interresse principalement à l'union, la concaténation et la fermeture
définis dans le tableau suivant : on peut aussi généraliser l'opérateur d'exponentiation
aux langages en définissant L0 comme {ε}, et Li comme Li-1 L. Ainsi, Li et L
concaténé i-1 fois avec lu i même.

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.

Figure 4. Opérations sur les langages

13
Exemple 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 la table précédente :
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(L∪C) est l'ensemble de toutes les chaînes de lettre et de chiffres
commençant par une lettre

14
Chapitre III Des expressions régulières aux automates à états finis

III.1 Expressions régulières

En Pascal, un identificateur est une lettre suivie d'un nombre quelconque,


éventuellement nul, de lettres ou de chiffres. Une expression régulière est une notation
qui nous permettra de définir avec précision les ensembles de ce genre. Avec cette
notation, on peut définir les identificateurs de Pascal comme : lettre(lettre|chiffre)*
La barre verticale signifie "ou", les parenthèses sont utilisées pour grouper des sous-
expressions, l'étoile signifie "un nombre quelconque, éventuellement nul, d'instances
de " l'expression parenthèse, et la juxtaposition de lettre au reste de l'expression
signifie la concaténation.
On construit une expression régulière à partir d'expressions régulières plus simples en
utilisant un ensemble de règles de définition. Chaque expression régulière r dénote un
langage L( r). Les règles de définition spécifient comment L(r) est formé en
combinant de manières variées les langages dénotées par les sous expressions de r.

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}

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, la figure 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
(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

III.2 Automates à états finis

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.

⇒ Il y a conflit temps/ place :


* Les automates finis déterministes produisent des connaisseurs plus rapides que
les automates finis non déterministes.
* Un automate fini déterministe peut être beaucoup plus volumineux qu'un
automate fini non déterministe.

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.

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: 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

Figure 2 : un automate fini non déterministe

Implantation d'un AFN

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

Un AFN accepte une chaîne d'entrée x si et seulement si il existe un certain chemin


dans le graphe de transition entre l'état de départ et un état d'acceptation, tel que les
étiquettes d'arcs le long de ce chemin épellent le mot x.

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"

Exemple 4 : La figure suivante 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'AFNde la figure 2).

-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

Transformation d'un AFN en AFD:


Si on utilise un AFN pour vérifier si une chaîne x ∈ langage, on trouvera plusieurs
chemins qui épellent la même chaîne x, on doit les avoir tous pris en compte avant
d'en trouver un qui mène à l'acceptation ou de découvrir qu'aucun d'entre eux ne mène
à un état d'acceptation.
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.
L'idée ⇒ chaque état de l'AFD correspond à un ensemble d'états de l'AFN.

Algorithme 2 Construction d'un AFD à partir d'un AFN

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

Figure 6 Opérations sur les états d'un AFN

ε
a
2 3
ε
ε
ε ε a b b
0 1 6 7 8 9 10
ε
b ε
4 5
ε

Figure 7 Un NFA pour le langage (a|b)*abb

ε-fermeture (0) = {0, 1, 2, 0, 4, 7} , Transiter ({0, 1, 2, 4, 7}, a ) = { 3, 8}


ε-fermeture({3, 8}) = {1, 2, 3, 4, 6, 7, 8}

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}.

Quand on autorise des ε-transitions, N peut être dans un des états de ε-


fermeture(Transiter(T, a)), après avoir lu le a = ε-fermeture({3, 8})={1, 2, 3, 4, 6, 7,
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).

On construit Détats, l'ensemble des états de D et Dtran, la table de transition de D, de


la manière suivante :
-Chaque état de D correspond à un ensemble d'états de l'AFN dans lesquels N pourrait
se trouver après avoir lu une suite de symboles d'entrée, en incluant toutes les ε-
transitions possibles avant ou après la lecture des symboles.
L'état de départ D est ε-fermeture (e0). On ajoute des états et des transitions à D' en
utilisant l'algorithme 3

Algorithme 3 La construction des sous-ensembles


Au départ ε-fermeture(e0) est l'unique état de Détats et il est non marqué;
Tant que il existe un état non marqué T dans Détats faire début
Marquer T;
Pour chaque symbole d'entrée a faire début
U : = ε-fermeture(Transiter(T, a));
Si U n'appartient pas à Détats alors
Ajouter U comme nœud non marqué de Détats;
Dtran[T,a] : = U
Fin
Fin

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

-Appliquons l'algorithme 2 à N (dernier AFN (figure 7))


-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é ε.

Remarque :

Un chemin peut n'avoir aucun arc. On atteint à partir de lui même.

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

Etat Symbole d'entrée


a b
A B C
B B D
C B C
D B E
E B C
Figure 8 Table de transition Dtran pour le DFA

-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)

Voici le graphe de transition correspondant à l'AFD

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

Une façon de construire un reconnaisseur à partir d'une expression régulière consiste à


construire un NFA à partir d'une expression régulière ensuite simuler le
comportement de l'NFA sur une chaîne d'entrée en utilisant les algorithmes que nous
allons voir. Si la vitesse d'exécution est essentielle, on peut convertir le NFA en DFA.

Construction d'un NFA à partir d'une expression régulière


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. P ar exemple, pour
l'expression r|s, on construit un NFA à partir des NFA reconnaissant r et s.

Algorithme 4: Construction d'un NFA à partir d'une expression régulière, ou


construction de Thompson.
Donnée: Une expression régulière r sur un alphabet ∑
Résultat : un NFA N qui reconnaît L(r).
Méthode: On décompose d'abord r en ses sous expressions. Puis en utilisant les
règles (1) et (2) ci dessous, on construit des NFA pour chacun des symboles de base
de r, c'est à dire doit ε soit les symboles de l'alphabet.
Les symboles de base correspondent aux parties (1) et (2) dans la définition d'une
expression régulière.
Remarque : Si un symbole a apparaît plusieurs fois dans r , un NFA séparé est
construit pour chaque occurrence. Ensuite, en se guidant sur la structure syntaxique de
l'expression régulière r, on combine régulièrement ces NFA en utilisant la règle (3) ci
dessous, jusqu'à obtenir le NFA pour l'expression régulière complète.
Chaque NFA intermédiaire produit aucours de la construction correspond à une sous-
expression de r et a plusieurs propriétés importantes: il a exactement un état final,
aucun arc ne rentre dans l'état de départ et aucun arc ne quitte l'état final.

1) Pour ε, construire l'AFN:

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 {ε}

2)Pour un a appartenant à Σ, construire l'AFN :

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).

(b) Pour l'expression régulière st, construire l'AFN composé N(st) :

i N(s N(t f

L'état de départ de N(s) devient l'état de départ de l'AFN composé et l'état


d'acceptation de N(s) est fusionné avec l'état de départ de N(t)

⇒ 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é.

(c) Pour l'expression régulière S*, construire l'AFN composé N(S*) :

ε ε
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 :

1. N(r) a au plus deux fois d'états qu'il y a de symboles et d'opérateurs dans r. Ce


ci revient du fait que chaque étape de la construction crée au plus deux nouveaux
états
2. N(r) a également un état de départ et un état d'acceptation. L'état d'acceptation
n'a pas de transition sortante. Cette propriété s'applique de la même manière à
chacun des sous automates le constituant.
3. Chaque état de N(r) a soit une transition sortante sur un symbole de Σ, soit au
plus deux transitions sortantes sur ε.

Exemple 6: soit l'expression régulière r = (a|b)* abb. La figure 10 présente un arbre


syntaxique pour r
r11

r9 r10

r7 r8 b

r5 r6 b

r4 * a

( r3 )

r1 | r2

a b

Figure 10 : Décomposition de (a|b)*abb

pour le composant r1, le premier a, on construit l'AFN

déb 2 a
3

pour r2, on construit

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 ε

L'AFN pour r6= a est :


ε

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 ε

En continuant ainsi on obtient l'AFN pour r11=(a|b)*abb

26
ε
a
2 3
ε
ε
a b b
déb ε ε
0 1 6 7 8 9 10
ε b
4 5 ε

Figure 11 AFN obtenu par la construction de Thompson pour r11=(a|b)*abb

Simulation d'un AFN

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.

Algorithme (5) Simulation d'un AFN

Données Un AFN N construit par l'algorithme 4 et une chaîne d'entrée x, On suppose


que x est terminée par un caractère de fin de fichier fdf. N a un état de départ e0 et un
ensemble d'états d'acceptation F.

Résultat : La réponse "oui" si N accepte x;"non" dans le cas contraire.

Méthode : Appliquer l'algorithme suivant à la chaîne d'entrée x

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.

Implantation à l'aide de deux piles

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

Avant de voir les mécanismes de reconnaissance des unités lexicales, voyons


comment le texte d'entrée est mémorisé.

IV.1 Mémorisation du texte d'entrée: (couple de tampons)

On utilise un tampon divisé en deux moitiés de N caractères, comme la figure 1 (N


pourra être le nombre de caractères dans un bloc disque, par exemple 1024 ou 4096

E = M * C * * 2 fdf
début de l'unité
lexicale Avant

Figure 1 Un tampon d'entrée en deux moitiés

*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

exp → terme oprel terme


| terme

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+)?

Lettre et chiffre sont définis comme suit :


Lettre → A | B | … | Z | a | b | … |z
Chiffre → 0 | 1 | … |9
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 +

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.

IV.2 Diagrammes de transition

Supposons que le tampon d'entrée soit comme la figure 1


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 2 : diagramme de transition de Si


L'étiquette autre désigne tous les caractères
S i autres que ceux qui sont indiqués
0 1 2 Retourne Si
explicitement sur les arcs quittant e.

autre
autre

30
La figure 2 présente un diagramme de transition pour les modèles >= et >

> =
0 6 7

*
8

Figure 2 diagramme de transition pour >=

- 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)

5 Retourne (oprel, EGA)


>
=
6 7 Retourne (oprel, PGE)
autre
8 * Retourne (oprel, PGQ)

Figure 3 : Diagramme de transition pour les opérateurs de relation

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())

Figure 4 : 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) 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.

Nombre sans signe :

Soit la définition régulière suivante dénotant des nombres sans signe :


nb → chiffre+ (.chiffre+)? (E(+/-)? Chiffre+) ?

chiffres fractions ? exposant ?

- Cette définition fraction et exposant sont optionnels.

→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())

Figure 5 : NFAs pour les nombres sans signe

Les diagrammes de transition avec les états de départ 25, 20 et 12 correspondent à


chiffres, chiffres fraction et chiffres fraction ? Exposant respectivement, on doit donc
essayer les états de départ dans l'ordre inverse 12, 20, 25.
Une autre solution consiste à les rassembler en un seul diagramme.

chif chif Return (RangerNb())


chif
. E +ou autre *
12 chif 13 14 chif 15 16 17 chif 18 19

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.

IV.3 Un langage pour spécifier des analyseurs lexicaux (lex)

Lex est généralement utilisé de la manière décrite par la figure suivante :


Programme
Source Lex lex.l → Compilateur → lex.yy.c
Lex
Lex.yy.c → → a. out
Compilateur C
Flot d'entrée → → suite d'unités lexicales
a. out

Figure 6 : Création d'un analyseur lexical avec Lex

* 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.

Une autre manière d'obtenir un analyseur lexical pour un langage L. consiste à


spécifier les unités lexicales de L à l'aide des expressions régulières, ensuite soit :
1) Traduire les expressions régulières en AFD et le simuler pour reconnaître les
jetons.
2) Pour une traduction plus simple, traduire l'Expression Régulière en AFN,
ensuite traduire systématiquement l'AFN en AFD et le simuler.

34
Expression régulière

1
3
Automate fini non déterministe

Automate fini déterministe

figure 7 différents modes d'obtention d'un analyseur lexical

35
Chapitre V Conception d'un générateur d'analyseurs lexicaux

V.1 Schéma d'un analyseur lexical

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.

Nous supposons que nous disposons d'une spécification d'analyseur lexical de la


forme :

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.

Spécification Compilateur Table de (a) compilateur lex


Lex Lex transition
AFN
AFD

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

Schéma d'un analyseur lexical

V.2 Reconnaissance fondée sur les Automates finis non déterministes

On commence par construire une table de transition pour un AFN N pour le modèle
m1m2..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'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.

⇒ On recule le pointeur avant jusqu'à la position à laquelle a eu lieu la dernière


concordance

⇒ - Le modèle correspondant identifie l'unité lexicale.


- Le lexème est la chaîne entre le pointeur début et avant.
* Si aucun modèle ne marche ⇒ transfert du contrôle à une fonction de récupération
sur erreur.

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

Soit la chaîne d'entrée aaba


m1 m3

0 a 2 a 7 b 8 a
néant
1 4
3 7
7 Jeton 1 reconnu aab unité lexicale a*b+

Suite d'ensembles d'états traversés et de modèles reconnus au cours du traitement de


la chaîne d'entrée aaba
Supposons que la chaîne d'entrée est la suivante : aab/ab/abbb
On continue le schéma précédant en revenant à l'état de départ de l'AFN avec le
quatrième caractère.
m1 m3
0 a 2 b 5 a néant
1 4 8
3 7
7
On choisit m2 car il
apparaît le premier dans la
Jeton 2 reconnu ab unité lexicale a*b+ spécification lex

m1
m3
0 a 2 b b m2 et m3 m3
5 6 8
1 4 b
8 8
3 7
7

Jeton 3 reconnu abbb unité lexicale a*b+

38
V.3 Reconnaissance fondée sur les Automates finis déterministes

Si on convertit l'AFN de la figure 1 (b) on obtient la table de transition suivante :


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
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.
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

Jet 1: aab lexème a*b+

Considérons le second exemple : aba

m1 m3
a b a
0137 247 58 néant

Cet état comprend l'état d'acceptation 8 du NFA donc a*b+


est reconnu et le lexème est ab

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

Figure 1: Emplacement d'un analyseur syntaxique dans un modèle de


compilateur

Les méthodes utilisées (par l'analyse syntaxique) communément sont soit


descendantes soit ascendantes. Comme leurs nom l'indique, les analyseurs
descendants construisent des arbres d'analyse de haut en bas, alors que les analyseurs
ascendants partent des feuilles et remontent vers le racine.
Dans les deux cas, l'entrée de l'analyseur est parcourue de la gauche vers la droite, un
symbole à la fois.

Les méthodes descendantes ou ascendantes les plus efficaces travaillent uniquement


sur des sous classes de grammaires hors contexte, mais certaines de ces sous classes
sont suffisamment expressives pour décrire la plupart des constructions syntaxiques
des langages de programmation.

VI.2 Grammaires non contextuelles

Beaucoup de constructions de langages de programmation, ont une structure


récursive; ces constructions peuvent-être définies par des grammaires non
contextuelles.
Exemple 1:
si S1 et S2 sont des instructions et E une expression
"si E alors S1 sinon S2" est une instruction (1)

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:

"instr→ si expr alors instr sinon instr" (2)

VI.2.1 Définition d'une grammaire non contextuelle

une grammaire G (VT, VN, S, P)


VT : ensemble des symboles terminaux de la grammaire (unités lexicales)
VN: ensemble des symboles non terminaux de la grammaire (variables syntaxiques
qui dénotent un ensemble de chaînes)
S: l'axiome est un non terminal particulier, tel que l'ensemble des chaînes qu'il dénote
est le langage défini par la grammaire.
P: ensemble de règles de production de la grammaire; spécifient la manière dont les
terminaux et les non terminaux peuvent être combinés pour former les chaînes.

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 → +| – | * | / | ↑

VI.2.2 Définition d'une Dérivation

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

E ⇒ –E ⇒ –(E) ⇒ –(E+E) ⇒ –(id+E) ⇒ –(id+id) (4)


g g g g g

Chaque étape d'une dérivation gauche peut s'écrire:


ωAγ ⇒ ωδγ ou ω est formé uniquement de terminaux A → δ est la production
g

utilisée et γ est une chaîne de symboles grammaticaux.


α se dérive en β par une dérivation gauche est notée α ⇒ β
g
*
si S ⇒ α on dit que α est une proto-phrase gauche de la grammaire considérée.
g

Dérivation droite = le terminal la plus à droite est remplacée à chaque étape.

VI.3 Arbres d'analyse et dérivations

Définition Arbre syntaxique: un arbre syntaxique illustre visuellement la manière


dont l'axiome d'une grammaire se dérive en une chaîne du langage. Si le non terminal
A est défini par la production A → XYZ, alors un arbre syntaxique peut posséder un
nœud intérieur étiqueté A et avoir trois fils étiquetés X,Y et Z, de gauche à droite.

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

chaîne chaîne chaîne chaîne


+ _

chaîne _ chaîne 2 9 chaîne chaîne


+

9 5 5 2
Figure 2 : Deux arbres syntaxiques pour 9-5+2

VI.3.2 Associativité des opérateurs

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.

VI.3.3 Priorités des opérateurs

9+5*2 a deux interprétations (9+5)*2 ou 9+(5*2)


on a besoin de connaître la priorité relative des opérateurs dès qu'il y'a plus d'un type
d'opérateurs en présence.
On dira que * a une priorité supérieure à celle de + si * agit sur ses opérandes avant +.
Dans l'arithmétique usuelle *, / ont une priorité supérieure à +,-
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.

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)

récursive non récursive


(à la main) (génération automatique)

Figure 1: Arborescence des méthodes d'analyse decendantes

Nous définissons la classe des grammaires LL(1) à partir desquelles on peut


construire automatiquement des analyseurs prédictifs.

VII.1 Analyse par descente récursive

VII.1.1 Principes de l'analyse par descente 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.

Exemple 1: 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
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

La feuille a correspond au second symbole de w et la feuille d au troisième symbole.


Comme nous avons produit un arbre d'analyse pour w, nous nous arrêtons et
annonçons le succès final de l'analyse.

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

Définition: une grammaire est récursive à gauche si elle contient un non-terminal A


+
tel qu'il existe une dérivation A ⇒ Aα, où α est une chaîne quelconque.
Considérons tout d'abord le non terminal A dans les deux productions:
A → Aα| β où α et β sont des suites de terminaux et de non terminaux qui ne
commencent pas par A. Par exemple expr → expr + terme|terme A = expr,
α=+terme et β =terme.

Une application répétée de cette production fabrique une suite de α à droite de A


comme la figure 3.a. Quand A est finalement remplacé par β, on obtient un β et une
suite éventuellement vide de α.

A
A
A
A
A

β α α … α α

Figure 3.a

On peut obtenir le même effet, en réécrivant la production définissant A de la manière


suivante:
A → βR
R → αR|ε
Ici R est un nouveau terminal. La production R→ αR est récursive à droite.

A
R

β α α ……. α ε

Figure 3.b

Exemple 2:
expr → expr + terme |terme
devient :

47
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 → Aα1| Aα2| …| Aαm|β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 ≠ ε)

VII.2 Analyse prédictive non récursive

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

Figure4: Modèle d'analyseur prédictif non récursif

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.

Algorithme 1 Analyse prédictive non récursive

Donnée: une chaîne w et une table d'analyse M pour une grammaire G.


résultat: Si w est dans L(G), une dérivation gauche de w, sinon une indication
d'erreur.
Méthode: Initialement, l'analyseur est dans une configuration dans laquelle il a $S
dans sa pile avec S, l'axiome de G au sommet et w$ dans son tampon d'entrée.

Le programme est le suivant:


Positionner le pointeur source ps sur le premier symbole de w$
Répéter
soit X le symbole en sommet de pile et a le symbole repéré par ps
si X est un terminal ou $ alors
si x = a alors
enlever X de la pile et avancer ps
sinon erreur()
sinon /* X est un non terminal */
si M[X,a] = X→ Y1Y2…Yk alors
début
enlever X de la pile
Mettre Yk,Yk-1,…,Y1 sur la pile, avec Y1 au sommet;
émettre en sortie la production X→ Y1Y2…Yk
fin
sinon
erreur()

jusqu'à X = $ /* la pile est vide*/

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)

Les entrées vides représentent des erreurs


les entrées non vides indiquent les productions qu'il faut utiliser pour développer le
non terminal en sommet de pile.
Sur la chaîne id+id*id, l'analyseur prédictif effectue la séquence d'actions de la figure
5. dans la colonne symbole d'entrée, le pointeur d'entrée repère le symbole le plus à
gauche de la chaîne.
Pile Entrée Sortie
$E id+id*id$
$E'T id+id*id$ E→ TE'
$E'T'F id+id*id$ T→ FT'
$E'T'id id+id*id$ F →id
$E'T' +id*id$
$E' +id*id$ T'→ ε
$E'T+ +id*id$ E'→ +TE'
$E'T id*id$
$E'T'F id*id$ T→ FT'
$E'T'id id*id$ F →id
$E'T' *id$
$E'T'F* *id$ T'→ *FT'
$E'T'F id$
$E'T'id id$ F →id
$E'T' $
$E' $ T'→ ε
$ $ E'→ ε
Figure 5 Transitions effectuées par un analyseur prédictif sur la chaîne source
id+id*id

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.

Définitions: 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(α).

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)

Premier (E') = Premier(+)∪{ε} = {+,ε}


Premier(T') = Premier(*)∪{ε} = {*,ε}

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).

Exemple 5: sur la même grammaire


E→ TE'
E'→ +TE'|ε
T→ FT'
T'→ *FT'|ε
F →(E)|id

Suivant (E) = Suivant(E') = {),$}


Suivant(T) = Suivant(T') = {+,),$}
Suivant(F) = {+,*,),$}

Explication:
Suivant(E) = {$} ∪ Premier( ))
(règle 1) (car on a F → (E) règle 2)
= {$,)}

Suivant(E') = Suivant(E) car la règle 3 s'applique sur E → TE'


= {$,)}

Suivant(T) = Premier(E') sauf ε ∪ Suivant(E')


règle 2 sur E' →+TE' règle 3 appliquée sur E' → +TE'|ε
= {+} ∪{$,)}
={+,$,)}

Suivant (T') = Suivant(T) règle 3 sur T→FT'


= {+,$, )}

52
Suivant(F) = Premier(T') sauf ε ∪ suivant (T)
*
règle 2 appliquée à T →FT' règle 3 sur T→*FT' et T' ⇒ ε
={*} ∪ {+,$,)}
={*,+,$,)}

Construction des tables d'un analyseur prédictif


l'idée est la suivante: soit A→ α une production et a dans Premier(α) alors, l'analyseur
développe A en α chaque fois que le symbole d'entrée courant est a. Une complication
*
se produit quand α = ε ou α ⇒ ε. Dans ce cas, nous devons égalemet developper A en
α si le symbole d'entrée courant est dans suivant(A) ou si le $ d'entrée a été atteint et
si $ est dans Suivant de A.

Algorithme 2: Construction d'une table d'analyse prédictive


Donnée: une grammaire G
Résultat: une table d'analyse M pour G
Méthode:
1. Pour chaque production A→ α de la grammaire, procéder aux étapes 2 et 3.
2. Pour chaque terminal a dans Premier(α), ajouter A→α à M[A,a].
3. Si ε est dans Premier (α), ajouter A→α à M[A,b]. pour chaque terminal b dans
suivant (A).
4. si ε est dans Premier (α) et $ est dans suivant(A) ajouter A →α à M[A, $].
5. Faire de chaque entrée non définie de L une erreur

Exemple 6:

Appliquons l'algorithme 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


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)

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.

Exemple 7: Soit la grammaire G


S → iEtSS'|a
S' → eS|ε
E→b

Voici la table d'analyse pour cette grammaire.


Symbole d'entrée
a b e i t $
S S →a S→ iEtSS'
S' S' → ε S'→ε
S' → eS
E E→b

• 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.

Les grammaire LL(1) ont un certain nombre de propriétés distinctives.


• Aucune grammaire ambiguë ou récursive à gauche ne peut être LL(1).
• Une grammaire est LL(1) si et seulement si, chaque fois que A →α |β sont deux
productions distinctes de G, les conditions suivantes s'appliquent:
1. Pour aucun terminal a, α et β ne se dérivent toutes les deux en des
chaînes commençant par a.
2. Une des chaînes au plus α et β peut se dériver en la chaîne vide.
*
3. Si β ⇒ ε, α ne se dérive pas en une chaîne commençant par un
terminal de Suivant(A). Cela causerait une entrée multiple (voir
exemple précédent) car l'un des premiers de α serait égale à un suivant
de A.
On peut alors vérifier que la grammaire
E→ TE'
E'→ +TE'|ε
T →FT'
T'→ *FT'|ε

54
F→ (E)|id est LL(1)

Les grammaires
S → iEtS|iEtSeS|a (1) S → iEtSS'|a (2)
E →b S' → eS| ε
E→b

ne sont pas LL(1)


La grammaire (1) n'est pas LL(1) à cause de la règle (1)
La grammaire (2) n'est pas LL(1) à cause des productions S' → eS| ε qui est sous la
*
forme A →α |β, avec β ⇒ ε mais α=eS se dérive en une chaîne commençant par e ∈
Suivant S' = {e,$}.

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)

VIII.1 Les principes de l'analyse par décalage/réduction

Dans cette section, nous introduisons un modèle général d'analyse syntaxique


ascendante, connu sous le nom d'analyse par décalage-réduction. On va consacrer la
section suivante à une forme d'analyse par décalage-réduction facile à implémenter,
appelée analyse par précédence d'opérateurs. Une méthode beaucoup plus
générale, appelée analyse LR, est présentée à la section qui suit. Elle est utilisée dans
un grand nombre de constructeurs automatiques d'analyseurs syntaxiques.
L'analyse par décalage-réduction a pour but de construire un arbre d'analyse pour une
chaîne source en commençant par les feuilles (le bas) et en remontant vers la racine
(le haut). Ce processus peut-être considéré comme la "réduction" d'une chaîne w vers
l'axiome de la grammaire. A chaque étape de réduction, une sous-chaîne particulière
correspondant à la partie droite d'une production est remplacée par le symbole de la
partie gauche de cette production. Si la sous chaîne est choisie correctement à chaque
étape, une dérivation droite est ainsi élaborée en sens inverse.

Exemple 1 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 (B→d) 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

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.

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
d d

manche de αβω, la chaîne ω à droite du manche, ne contient que des terminaux.

Remarque: Dans l'exemple 1. Si nous remplaçons b par A dans la seconde chaîne


aAbcde nous obtenons la chaîne aAAcde qui par la suite ne peut être réduite vers S
donc A→b dans la position 3 n'est pas un manche.
Dans l'exemple 1, abbcde est une proto-phrase droite dont le manche est A →b en
position 2.

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

(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.

Remarque: La chaîne apparaissant à droite d'un manche contient uniquement des


symboles terminaux.
Puisque il existe une autre dérivation droite pour la même chaîne.
E ⇒ E*E
d

⇒ E*id3
d

57
⇒ E+E * id3
d

⇒E + id2*id3
d

⇒ id1+id2*id3 La grammaire 2 est ambiguë


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.

VIII.1.2 Elagage du manche

La réduction d'un manche par la partie gauche de la production correspondante est


"l'élagage du manche"
Une dérivation droite inverse peut être obtenue par "élagage du manche"
Plus précisément, nous commençons avec une chaîne de terminaux ω que nous
désirons analyser. Si ω est une phrase de la grammaire considérée alors ω = γn où γn
est la nième proto-phrase droite d'une dérivation droite encore inconnue.
S = γ0 ⇒ γ1 ⇒ γ2 ⇒ ..… ⇒ γn-1 ⇒ γn = ω
d d d d d

Pour reconstruire cette dérivation en ordre inverse, on repère le manche βn dans γn et


on remplace βn par la partie gauche d'une production An→βn pour obtenir la (n-1)ème
proto-phrase droite γn-1. Nous poursuivons le même travail pour obtenir γn-2 ensuite
après un nombre d'étapes on obtient une proto-phrase droite formée uniquement par S.
Nous arrêtons et annonçons la réussite finale de l'analyse.

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.

L'analyseur réduit alors β vers la partie gauche de la production appropriée.


L'analyseur recommence ce cycle jusqu'à ce qu'il détecte une erreur ou jusqu'à ce que
la pile contienne l'axiome et que le tampon d'entrée soit vide
PILE ENTREE
$S $
S'il atteint cette configuration, l'analyseur s'arrête et annonce la réussite finale de
l'analyse.

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

VIII.1.4 Définition 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

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.

VIII.2.1 Algorithme d'analyse LR

Tampon d'entrée a1 … ai …. an $

Pile
Sm Programmme
Xm d'analyse LR flot de sortie
Sm-1
Xm-1

S0 action successeur

Figure 2 : Schéma d'un analyseur LR

La figure 2 montre qu'un modèle d'analyseur LR consiste en : un tampon d'entrée,


un flot de sortie, une pile , un programme pilote et des tables d'analyse
subdivisées en deux parties (Action et Successeur).
Le programme moteur est le même pour tous les analyseurs LR; seules les tables
d'analyse changent d'un analyseur à l'autre. Le programme d'analyse lit les unités
lexicales l'une après l'autre dans le tampon d'entrée, il utilise une pile pour y ranger les
chaînes de la forme S0X1S1X2S2…XmSm, où Sm est au sommet. chaque Xi est un
symbole de la grammaire et chaque Si est un symbole appelé état. La combinaison du
numéro de l'état en sommet de pile et du symbole d'entrée courant est utilisée pour
indicer les tables et déterminer l'action d'analyse "décaler ou réduire" à effectuer.
Les tables d'analyse contiennent deux parties, une fonction d'actions d'analyse;
Action et une fonction de transfert; Successeur.

Le programme dirigeant l'analyseur LR se comporte de la façon suivante:

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

Figure 3 Programme d'analyse LR

Exemple 5: La figure 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

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:

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

à 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.

Construction des tables d'analyse SLR à partir d'une grammaire

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 5: A → XYZ ⇒ A → •XYZ


A → X•YZ
A → XY•Z
A → XYZ•

la production A → ε fournit uniquement l'item A → •

Intuitivement : un item indique la "quantité" de partie droite qui a été reconnue, à un


moment donné, au cours du processus d'analyse.
Exemple 6 :
A → XYZ indique qu'on espère avoir en entrée une chaîne dérivable depuis XYZ
A → X•YZ 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 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'idée centrale de la méthode SLR est de construire, tout d'abord, à partir de la


grammaire, un automate fini déterministe pour reconnaître les préfixes viables.
- Les items sont regroupés en ensembles qui constituent les états de l'analyseur
SLR
- Les items peuvent-être vus comme les états d'un NFA reconnaissant les
préfixes viables, et le "regroupement" est exactement la construction des sous
ensembles présentée lors de la conversion NFA → DFA.
- une collection d'ensembles d'items LR(0), que nous appellerons collection
LR(0) canonique fournit la base de la construction des analyseurs SLR.
- Pour construire la collection LR(0) canonique pour une grammaire, nous
définissons une grammaire augmentée et deux fonctions Fermeture et
Transition.

Définition: Si G est une grammaire d'axiome S, alors G', la grammaire augmentée


de G, est G avec un nouvel axiome S' et une nouvelle production S'→ S.
but: indiquer à l'analyseur quand il doit arrêter l'analyse et annoncer que la chaîne
d'entrée a été acceptée.

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.

Intuitivement : si A → α•Bβ est dans Fermeture (I) cela indique que à un


certain point du processus d'analyse, nous pourrions voir se présenter dans
l'entrée, une sous-chaîne dérivable depuis Bβ comme entrée. Si B → γ est une
production, nous supposons que nous pourrions également voir, à ce moment là
une chaîne dérivable depuis γ.

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:

E → E+•T Nous calculons Transition(I,+) en recherchant dans I, les items


T → •T*F ayant + immédiatement à droite du point, E'→→ E• ne convient
T → •F
pas, mais E →E•+T répond au critère.
F → • (E)
F → • id → E+•T]},
Nous faisons franchir au point + afin d'obtenir {[E→
puis nous calculons Fermeture sur cet ensemble.

Algorithme 2 Construction des ensembles d'items

Voici l'algorithme pour construire C, la collection canonique d'ensembles d'items


LR(0) pour la grammaire augmentée G'.

Procedure Items (G');


début
C := {fermeture ({[S' → •S]})}
répéter
pour chaque ensemble d'items I de C et pour chaque symbole de la
grammaire X tel que Transition(I,X) soit non vide et non encore dans C
ajouter Transition (I,X) à C
fin pour
jusqu'à ce qu'aucun nouvel ensemble d'items ne puisse être plus ajouté à C
fin

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

est présentée ci dessous:

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

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) = I3


E → T• Transition (I4, ( ) = I5
T → T•*F

I3 := Transition (I0,F ) I10 = Transition (I7, F)


T → F• T → T*F•

I4 := Transition (I0, ( ) I11 = Transition (I8, ) )


F → (•E) F → (E)•
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

Figure 5 Collection LR(0) canonique pour la grammaire G'

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.

VIII.2.3 Tables d'analyse SLR

On va voir comment construire les fonctions Action et Successeur à partir du DFA


qui reconnaît les préfixes viables.

Algorithme 3 Construction des Tables d'analyse SLR


Donnée : Une grammaire augmentée G'
Résultat : les tables d'analyse SLR des fonctions Action et Successeur pour G'
Méthode:
1. Construire C = {I0,I1,…,In}, la collection des ensembles d'items LR(0)
2. L'état i est construit à partir de Ii. Les actions d'analyse pour l'état i sont
déterminés comme suit:

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"

si les règles précédentes engendrent des actions conflictuelles, nous disons


que la grammaire n'est pas SLR(1). Dans ce cas, l'algorithme échoue et ne
produit pas d'analyseur.
3. On construit les transitions successeur pour l'état i pour tout non terminal A en
utilisant la règle: si Transition (Ii,A) = Ij, alors Successeur [i,A] = j
4. Toutes les entrées non définies par les règles (2) et (3) sont positionnées à
"erreur"
5. L'état initial de l'analyseur est celui qui est construit à partir de l'ensemble
d'items contenant [S' →•S].

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

1. A. Aho, R. Sethi, J. Ullman, "Compilateurs, principes, techniques et outils


cours et exercices ", Edition Dunod, 2000.
2. D. Cohen, "Introduction to computer Theory", Edition Wiley, 1996.
3. H.R. Lewis, C.H. Papadimitriou, "Elements of The Theory of computation",
Edition Prentice Hall, 1998.

73

Anda mungkin juga menyukai