• Aucun résultat trouvé

Analyse descendante (Bibliographie : Aho, Sethi, Ullmann. Compilateurs. Principes, techniques et outils)

N/A
N/A
Protected

Academic year: 2022

Partager "Analyse descendante (Bibliographie : Aho, Sethi, Ullmann. Compilateurs. Principes, techniques et outils)"

Copied!
17
0
0

Texte intégral

(1)

Analyse descendante

(Bibliographie : Aho, Sethi, Ullmann. Compilateurs.

Principes, techniques et outils)

Nous introduisons les idées de base sous-tendant l'analyse descendante efficace et montrons comment construire un analyseur syntaxique efficace, sans rebroussement, appelé analyseur prédictif. Nous

définissons la classe des grammaires LL(1) à partir desquelles on peut construire automatiquement des analyseurs prédictifs.

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 vue aussi 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 en préordre.

La forme générale d'analyse descendante appelée descente récursive, qui peut impliquer des retours arrière, c.à.d. nécessiter des passages répétés sur le texte source.

Cependant, les analyseurs avec rebroussement ne sont pas très fréquents. Une des raisons est que le rebroussement est rarement nécessaire pour analyser les constructions des langages de

programmation. Pour des situations comme analyse des langues naturelles, le rebroussement n'est pas très efficace et on lui préfère d'autres méthodes.

(2)

Ex. d'analyse avec le rebroussement.

Grammaire : S  cAd A  ab | a et la chaîne d'entrée w = cad.

Pour construire de façon descendante un arbre d'analyse pour cette chaîne, nous construisons 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 alors la première production de S pour développer l'arbre et obtenir :

S

c A d

La feuille la plus à gauche étiqueté c, correspond au premier symbole de w ; en conséquence, 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 :

S

c A d

a b

Nous avons alors une concordance avec le second symbole d'entrée ; nous avançons donc le pointeur d'entrée sur 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ée 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

(3)

première fois. Nous essayons donc maintenant la seconde alternative de A et obtenons l'arbre suivant :

S

c A d

a

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

Analyse prédictive non récursive.

Analyse descendante ne peut pas être utilisée pour les grammaires récursives à gauche.

Suppression de la récursivité à gauche.

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. Les méthodes d'analyse descendante ne peuvent pas fonctionner avec les grammaires récursives à gauche ; on a donc

besoin d'une transformation grammaticale qui élimine cette récursivité à gauche.

On peut éliminer une production récursive à gauche en la réécrivant.

Considérons le non-terminal A dans les deux productions : A  A | 

(4)

Le non-terminal A est récursif à gauche car la production A  A

contient A comme symbole le plus à gauche de sa partie droite. Une application répétée de cette production fabrique une suite de  à droite de A :

A

A 

A

………

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

A

 R

 R

……….

 R

 R

(5)

Ici R est un nouveau non-terminal.

Il est possible de construire un analyseur prédictif non récursif en tenant à jour une pile explicite, plutôt que la pile implicite résultant des appels récursifs. 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 recherche la production à appliquer dans une table d'analyse. Nous verrons plus tard comment cette table peut, pour certaines grammaires être construite directement.

Un analyseur syntaxique prédictif dirigé par table 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 $, symbole utilisé comme marqueur de fin. La pile contient une séquence de symboles grammaticaux, avec $ marquant le fond de la pile. Initialement, la pile contient l’axiome de la grammaire au-dessus du $. La table d’analyse est un tableau à deux dimensions M[A,a] ou A est un non-terminal et a est un terminal ou un $.

(6)
(7)

L’analyse syntaxique est contrôlée 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 3 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). L’analyseur se

contente, pour tout résultat, d’imprimer la production utilisée : n’importe quelle autre action pourrait être exécutée à la place.

Si M[X,a]=erreur, l’analyseur appelle une procédure de récupération sur erreur.

Le comportement de l’analyseur peut se décrire en termes de ses configurations, qui décrivent le contenu de sa pile et le texte d’entrée restant.

Algorithme de l’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 pour 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.

(8)

Programme d'analyse syntaxique prédictive

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 ;

Emettre en sortie la production XY1Y2…Yk Fin

Sinon Erreur() Jusqu'à X=$ /* la pile est vide */

(9)

Exemple

Considérons la grammaire : E  E + T | T

T  T * F | F F  (E) | id

Eliminons la récursivité à gauche : E  T E'

E'  + T E' |  T  F T'

T'  * F T' |  F  (E) | id

Table d'analyse prédictive pour cette grammaire Non-

terminal

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)

(10)

Sur la chaîne source id + id * id l'analyseur prédictif effectue la séquence d'entrée suivante :

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'  

(11)

PREMIER et SUIVANT

La construction d’un analyseur Synt. Préd. est facilitée par 2 fonctions associées à une grammaire G. Ces fonctions PREMIER et SUIVANT, nous permettent, quand c’est possible, de remplir les entrées de la table d’analyse prédictive pour G.

Si α est une chaîne de symboles grammaticaux, PREMIER(α)

désigne l’ensemble des terminaux qui commencent les chaînes qui se dérivent de α. Si αε, alors ε est aussi dans PREMIER(α).

Pour chaque non-terminal A, SUIVANT(A) définit l’ensemble des terminaux a qui peuvent apparaître immédiatement à droite de A dans une protophrase, c.a.d. l’ensemble des terminaux a tels qu’il existe une dérivation de la forme S αAaβ où α et β sont de chaînes de symboles grammaticaux. Il a pu exister, au cours de la dérivation, des symboles entre A et a , dans ce cas, ils se sont dérivés en ε et ont disparu. Si A peut être le symbole le plus à droite d’une proto-phrase, alors $ est dans SUIVANT(A).

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, mettre a dans PREMIER(X) s’il existe i tel que a est dans PREMIER(Yi) et ε est dans tous les PREMIER(Y1), …, PREMIER(Yi-1), c.a.d.

Y1…Yi-1 ε. Si ε est dans PREMIER(Yj) pour tous les j=1,…,k, ajouter ε à PREMIER(X), mais si Y1 ε, on ajoute

PREMIER(Y2), etc.

On peut calculer PREMIER pour une chaîne X1X2…Xn de la façon

(12)

Pour calculer SUIVANT(A) pour tous les non-terminaux A, appliquer les règles suivantes jusqu’à ce qu’aucun terminal ne puisse être ajouté aux ensembles SUIVANT.

1. Mettre $ dans SUIVANT(S), où S est l’axiome et $ est le marqueur de fin.

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 ε (c.a.d. β ε), les éléments de

SUIVANT(A) sont ajoutés à SUIVANT(B).

Exemple

Considérons à nouveau la grammaire E  T E'

E'  + T E' |  T  F T'

T'  * F T' |  F  (E) | id Alors :

PREMIER(E) = PREMIER(T) = PREMIER(F) = {(, id}

PREMIER(E') = {+, }

PREMIER(T') = {*, }

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

SUIVANT(T) = SUIVANT(T') = {+, ), $}

SUIVANT(F) = {+, *, ), $}

Par exemple, id et la ( sont ajoutés à PREMIER(F) par la règle (3) de la définition de PREMIER avec i=1 dans chaque cas, car

PREMIER(id) = {id} et PREMIER(() = {(} d'après la règle (1). Puis, par la règle (3) avec i = 1, la production T  FT' implique que id et la parenthèse ouvrante sont également dans PREMIER(T). De même, on trouve que  est dans PREMIER(E') d'après la règle (2).

(13)

Pour calculer les ensembles SUIVANT, nous initialisons

SUIVANT(E) avec $, d'après règle (1) du calcul de SUIVANT.

D'après la règle (2) appliquée à la production F  (E), la parenthèse fermante est également dans SUIVANT(E). D'après la règle (3) appliquée à la production E  TE', $ et la parenthèse fermante sont dans SUIVANT(E'). Comme E'  , ils sont également dans

SUIVANT(T). La production E  TE' implique, par la règle (2), que les symboles de PREMIER(E') autres que  doivent être placés dans SUIVANT(T). Nous avons déjà vu que $ est dans SUIVANT(T).

(14)

Construction des tables d’un analyseur prédictif

L’algorithme suivant peut être utilisé pour construire une table pour l’analyse prédictive d’une grammaire G. L’idée sous-tendant cet algorithme est la suivante. Soit Aα une production et a est 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 également développer 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(A).

Algo

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). Si ε est dans PREMIER(α) et $ est dans SUIVANT(A), ajouter A α à M[A,$].

4. Faire de chaque entrée non définie de M une erreur.

(15)

Exemple

Appliquons l'algorithme donné à la grammaire des expressions arithmétique :

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 que les entrées M[E',)] et M[E',$] prennent tous les deux la valeur E'   car SUIVANT(E') = {),$}

(16)

Grammaires LL(1)

L’algo décrit peut être appliqué à une grammaire G pour produire des tables d’analyse M. Cependant, pour certaines grammaires, M peut avoir des entrées qui sont définies de façon multiple. Par exemple, si G est récursive à gauche ou ambiguë, M aura alors au moins une de ses entrées définie de façon multiple.

Exemple

Considérons la grammaire : S  iEtSS' | a

S'  eS |  E  b

PREMIER(S) = {i, a}

PREMIER(S') = {e, }

PREMIER(E) = {b}

SUIVANT(S) = SUIVANT(S') ={$,e}

SUIVANT(E) = {t}

Non- terminal

Symbole d'entrée

a b e i t $

S S a S iEtSS'

S' S'  

S'  eS

S'  

E E  b

L'entrée M[S',e] contient à la fois S'   et S'  eS, car SUIVANT(S') = {e,$}.

La grammaire est ambiguë et cette ambiguïté se manifeste par un choix sur la production à utiliser quand on voit un e (sinon). Nous pouvons résoudre cette ambiguïté en choisissant S'  eS. Ce choix correspond à l'association du sinon avec le alors précédent le plus proche.

(17)

Une grammaire dont la table d’analyse n’a aucune entrée définie de façon multiple est appelée LL(1).

 Parcours de l’entrée de la gauche vers la droite. (Left to right scanning) Leftmost derivation.

 Dérivation gauche (Leftmost derivation)

 Un seul symbole d’entrée de pré-vision

Les grammaires LL(1) ont un certain nombre de propriétés

distinctives. Aucune grammaire ambiguë ou récursive à gauche ne peut être LL(1). On peut également montrer qu’une grammaire est LL(1) ssi 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).

La grammaire des expressions arithmétiques est LL(1). Les

grammaires modélisant les instructions si-alors-sinon, ne le sont pas.

Références

Documents relatifs

possibilit´es (la division par 2! correspond au fait que la d´ecomposition en produit de cycles `a supports disjoints est unique modulo l’ordre des cycles, et pour arranger

De toute manière il semble que l'algèbre de Mohammed-ben-Mousa soit restée inconnue en Occident pendant les xn e et xm e siècles, et que cet au- teur ne fut alors célèbre en

On reviendra sur les relations de similarité et de divisibilité entre les sous-résultants (elles sont déjà bien connues, voir les ouvrages précités ci-dessus) grâce à de

sons les conditions nécessaires pour l’utilisation des algorithmes de Monte-Carlo par chaînes de Markov (MCMC) et nous introduisons quelques algorithmes MCMC,

En déduire qu’un couplage de poids minimum sur les sommets de degré impair dans T est inférieur ou égal à la moitié du poids de C opt... Préambule : technique pour montrer

Dans le cas où E est de dimension finie, la sphère S est compacte et l'existence d'un arc d'accumulation est assurée ; mais, faute d'un analogue de la proposition 4 du § 2.2, nous

L’objectif principal de notre thèse est d’évaluer la sécurité de cet algorithme basé sur l’application de la règle d’Ottawa et sur les délais de prise en charge

En utilisant la notion de densité, écrire un texte court pour expliquer pourquoi il est possible d’utiliser un matériau qui coule habituellement pour construire