Vous êtes sur la page 1sur 31

Chapitre 3: Analyse syntaxique descendante

Dérivation
Qualités des grammaires
Ambigüités
Récursivité à gauche
Factorisation à gauche
Analyseurs descendants
Principe
Analyse par descente récursive
Construction de la table d’analyse

1
Chapitre 3: Analyse syntaxique descendante

Généralités:

Le langage décrit par une grammaire régulière peut être reconnu par un automate fini,
et réciproquement.

les constructions des langages de programmation peuvent être définies par des
grammaires non contextuelles.

Structure hiérarchique: (récursivité)

l’instruction: si (expression) alors instruction1 sinon instruction2

Peut être exprimée de façon lisible en utilisant la production

Instr → si expr alors instr sinon instr

2
Une grammaire non contextuelle, on dit parfois grammaire BNF (pour Backus-Naur
form), est un quadruplet G = (T , V, S0, P) formé de :

- un ensemble T de symboles terminaux,


- un ensemble V de symboles non terminaux,
- un symbole S0  V particulier, appelé symbole de départ ou axiome,
- un ensemble P de productions, qui sont des règles de la forme

S → S1S2 … Sk avec S  V et Si  V  T

3
Exemple: grammaire des expressions arithmétiques simples:

expr → expr op expr


expr → ( expr )
expr → - expr
expr → id
op → +
op → -
op → *
op → /

Les symboles terminaux sont: id + - * / ( )


Les non-terminaux sont: expr, op et l’axiome est expr

On peut écrire cette grammaire de façon abrégée:

E → E A E | (E ) | -E| id
A→+|-|*|/ 4
Dérivation
Définition : la dérivation est la processus par lequel une grammaire définit un langage

Soit G = (T , V, S0, P) une grammaire non contextuelle,


A  V un symbole non terminal et  (T  V)* une suite de symboles,
tels qu'il existe dans P une production A → .

Quelles que soient les suites de symboles  et ,

on dit que Ase dérive en une étape en la suite 


ce qui s‘écrit:

A

5
Si n on dit que  se dérive en n en n étapes, et on écrit
 n
n

Enfin, si se dérive en en un nombre quelconque, éventuellement nul, d‘étapes


on dit simplement que se dérive en et on écrit 




6
Soit G = (T , V, S0, P) une grammaire non contextuelle; le langage engendré par G est
l’ensemble des chaînes de symboles terminaux qui dérivent de S 0 :

* *
L(G)={ w ∈T /S 0 ⇒ w }

Si w  L(G) on dit que w est une phrase de G. Plus généralement, si   (V  T )*



est tel que S0 ===>

alors on dit que  est une proto-phrase de G.

Une proto-phrase dont tous les symboles sont terminaux est une phrase.

7
Par exemple, soit la grammaire :

Expression → expression "+" terme | terme


Terme → terme "*" facteur | facteur
Facteur → nombre | identificateur | "(" expression ") "

et considérons la chaîne "60 * vitesse + 200" qui, une fois lue par l'analyseur lexical,
se présente ainsi :

w = ( nombre "*" identificateur "+" nombre ).

Expression → expression "+" terme


→ terme "+" terme *
→ terme "*" facteur "+" terme expression ===> w
→ facteur "*" facteur "+" terme
→ nombre "*" facteur "+" terme
→ nombre "*" identificateur "+" terme
→ nombre "*" identificateur "+" facteur
→ nombre "*" identificateur "+" nombre
8
La dérivation précédente est appelée une dérivation gauche car elle est entièrement
composée de dérivations en une étape dans lesquelles à chaque fois c'est le non-terminal
le plus à gauche qui est réécrit.

Chaque étape d’une dérivation gauche peut s’écrire: wA  w


g

Où w est formé uniquement de terminaux , A  est la production utilisée et  est


une chaîne de symboles grammaticaux.
*
Si S ⇒g α , on dit que  est une proto-phrase gauche de la grammaire considérée.

On peut définir de même une dérivation droite, où à chaque étape c'est le non-terminal
le plus à droite qui est réécrit.

9
Arbre de dérivation

expression → expression "+" terme


→ terme "+" terme
→ terme "*" facteur "+" terme
→ facteur "*" facteur "+" terme
→ nombre "*" facteur "+" terme
→ nombre "*" identificateur "+" terme
→ nombre "*" identificateur "+" facteur
→ nombre "*" identificateur "+" nombre

10
Qualités des grammaires
*
pour prouver que w  L(G) il faut exhiber une dérivation S0 ===>w,
c'est-à-dire construire un arbre de dérivation dont la liste des feuilles est w.
Grammaires ambigües: Une grammaire est ambigüe s'il existe plusieurs dérivations
gauches différentes pour une même chaîne de terminaux.

Exemple:
E→E+E
E→E*E
E→F
F → nombre
F → identificateur
F → (E)

11
E→E+E
E→E*E
E→F
F → nb
F → id
F → (E)

E → E+E → E * E + E → F * E + E E → E*E → F * E → nb * E
→ nb * E + E → nb * F + E→ nb * nb + E → nb * E + E → nb * F + E→ nb * nb + E
→ nb * nb + F→ nb * nb + nb → nb * nb + F→ nb * nb + nb

12
E→E+E E→E+T
E→E*E E→T
E→F T→T*F
F → nb → T→F
F → id F → nb
F → (E) F → id
F → (E)

E → E+T → T +T → T * F + T
→ F * F + T → nb * F + T→ nb * nb + T
→ nb * nb + F→ nb * nb + nb

13
Grammaires récursives à gauche.

Une grammaire est récursive à gauche s'il existe un non-terminal A et une dérivation
de la forme

A  Aα
Oùest une chaîne quelconque.

Cas particulier, on dit qu'on a une récursivité à gauche simple si la grammaire possède
une production de la forme
A  Aα

Exemple de grammaire récursive à gauche

14
Suppression de la récursivité à gauche

La méthode consiste à remplacer une production telle que:

Par les productions non récursives suivantes:

En appliquant ce procédé à :

On obtient:

15
Factorisation à gauche
On souhaite écrire des analyseurs prédictifs

Si on a une production de la forme


La factorisation à gauche de cette production donne:

Exemple:
Instr_si ---> si expr alors instr
| si expr alors instr sinon instr

La factorisation à gauche donne:

Instr_si ---> si expr alors instr fin_instr_si


Fin_instr_si ---> sinon instr |

Problème des -productions


dans la dérivation d'un non-terminal, une -production ne peut être choisie que
lorsqu'aucune autre production n'est applicable
16
17
Analyseurs descendants

Etant donnée une grammaire G = (T , V, S0, P), analyser une chaine de symboles terminaux
*
w  T* revient à construire un arbre de dérivation (en préordre) prouvant que S0 ===>w.

Les grammaires des langages qu’on veut analyser par analyse descendante ont un ensemble
de propriétés dit LL(1):
i.e. qu’on peut écrire des analyseurs:

- lisant la chaîne source de la gauche vers la droite (L)

- cherchant à construire une dérivation gauche (L)

- dans lesquels un seul symbole de la chaîne source est accessible à chaque instant et
permet de choisir, lorsque c’est nécessaire, une production parmi plusieurs candidates (1)

on va donc se restreindre a des classes de grammaires non ambigües, et où on sait en outre


construire un automate à pile déterministe correspondant.
18
Principe
Un analyseur descendant construit l’arbre de dérivation de la racine (axiome)
vers les feuilles (la chaîne de terminaux). Pour cela nous avons besoin d’une fenêtre de lecture
des symboles terminaux et d’une pile P de symboles.

Initialisation
au départ la pile contient l’axiome et la fenêtre montre le premier symbole terminal
de la chaîne à analyser.
Itération:
Tant que la pile n’est pas vide, répéter les opérations suivantes:
- si le symbole au sommet de la pile est un terminal , alors
si le terminal visible à la fenêtre de lecture est  alors
- dépiler le symbole au sommet de la pile
- lire le prochain symbole
- sinon signaler une erreur
- si le symbole au sommet de la pile est un non terminal S alors
- s’il y a une seule production S---> S1S2…SK alors dépiler S et empiler SKSk-1…S1 avec S1 au sommet
- s’il y a plusieurs production ayant S pour membre gauche alors
d’après le terminal visible à la fenêtre, sans faire avancer ce dernier, choisir l’unique production
S---> S1S2…SK qui peut convenir et empiler SKSk-1…S1 avec S1 au sommet

Terminaison = lorsque le pile est vide


si on atteint la fin de la chaîne en lecture alors l’analyse a réussi sinon signaler une erreur
19
Analyse descendante du texte:  60 * vitesse +200
i.e. la chaîne de terminaux

(nombre *   identificateur +  nombre)

fenêtre pile
nombre expression
nombre terme fin_expression
nombre facteur fin_terme fin_expression
nombre nombre fin_terme fin_expression
‘’*’’ fin_terme fin_expression
‘’*’’ * facteur fin_terme fin_expression
identificateur facteur fin_terme fin_expression
identificateur identificateur fin_terme fin_expression
‘’+’’ fin_terme fin_expression
‘’+’’  fin_expression
‘’+’’ fin_expression
‘’+’’ ‘’+’’ terme fin_expression
nombre terme fin_expression
nombre facteur fin_terme fin_expression
nombre nombre fin_terme fin_expression

¶ fin_terme fin_expression

¶  fin_expression

¶ fin_expression

 20

reste une question d'importance : « comment choisir l’unique production
S---> S1S2…SK qui peut convenir » ?

Réponse : Construire une table d’expansion?

l'idée est simple : pour décider si on réalise l'expansion X→β lorsque le premier
caractère de l'entrée est c, on va chercher à déterminer si c fait partie des premiers
caractères des mots reconnus par β

une difficulté se pose pour une production telle que X→ε, et il faut alors considérer aussi
l'ensemble des caractères qui peuvent suivre X

déterminer les premiers et les suivants nécessite également de déterminer si un mot


peut se dériver en ε

21
Définitions

Définition (null)

Soit α ∈(T U V )* . null(α) est vrai si et seulement si on peut dériver  à partir de α i.e.
*
α  
Définition premier (first)

Soit α ∈(T U V )* . first(α) est l'ensemble de tous les premiers terminaux des mots dérivés
de α, i.e.

{ a∈T / ∃w et α →aw }
Définition suivant (follow)

Soit X ∈V . follow(X) est l'ensemble de tous les terminaux qui peuvent apparaître après X
dans une dérivation, i.e.

*
{ a∈T / ∃u , w et S → uXaw }
22
pour calculer null() il suffit de déterminer null(X) pour X  V

null(X) est vrai si et seulement si

- il existe une production X 


 

- ou il existe une production X  Y1  Ym où null(Yi ) pour tout i

23
pour calculer les null(Xi ), on part donc de null(X1) = false, . . .,null(Xn) = false
et on applique les équations jusqu'a obtention du point fixe.

24
de même, les équations définissant first() sont mutuellement récursives

first ( X )   first (  )
X
et
first (a )  a
first ( X )  first ( X ), si  NULL( X )
first ( X )  first ( X )  first (  ), si NULL ( X )

25
les équations définissant follow() sont aussi mutuellement récursives

follow( X )   first (  )   follow(Y )


Y  αX Y  αX , NULL (  )

note : il faut introduite # dans les suivants du symbole de départ (ce que l'on peut faire
directement, ou en ajoutant une règle ) S → E #

26
Application : construction de la table d'expansion

A l'aide des premiers et des suivants, on construit la table d'expansion M(X,a) de la


manière suivante:

pour chaque production X→β


- on pose M(X,a) =  pour tout a  first()
- si null(), on pose aussi M(X,a) = pour tout a  follow(X)

27
Analyse par descente récursive
Un analyseur par descente récursive est un type d’analyseur dans lequel le programme de
l’analyseur est étroitement liés à la grammaire analysée.

1 – chaque groupe de productions ayant le même membre gauche S donne lieu à une
fonction

void reconnaitre_S(void)  ou simplement void S(void)

2 – lorsque plusieurs productions ont le même membre gauche, le corps de la fonction


correspondante est un if ou un switch.

3 – Une séquence de symboles S1S2…Sn dans le membre droit d’une production donne lieu,
dans la fonction correspondante, à une séquence d’instructions traduisant les actions
« reconnaissance de S1 », « reconnaissance de S2 », …    « reconnaissance de Sn   » .

28
4- si S est un symbole non terminal, l’action « reconnaissance de S » se réduit à l’appel
de fonction
reconnaitre_S()

5 – Si  est une symbole terminal, l’action « reconnaissance de  » consiste à regarder


le terminal visible à la fenêtre et

- s’il est égal à , faire passer la fenêtre sur le symbole suivant


- sinon annoncer une erreur.

L’ensemble des fonctions écrites suivant les prescriptions ci-dessus forme l’analyseur du
langage considéré

29
30
remarques

- la table d'expansion n'est pas explicite : elle est dans le code de chaque fonction.

- la pile non plus n'est pas explicite : elle est réalisée par la pile d'appels.

31

Vous aimerez peut-être aussi