IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de...

23
IFT313 Introduction aux langages formels Froduald Kabanza Département d’informatique Université de Sherbrooke Analyseurs récursifs LL (1)

Transcript of IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de...

Page 1: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

IFT313 Introduction aux langages formels

Froduald Kabanza

Département d’informatique

Université de Sherbrooke

Analyseurs récursifs LL (1)

Page 2: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Sujets

• C’est quoi un analyseur syntaxique récursif ?

• Comment le programmer ?

• Comment fonctionne un générateur d’analyseur

syntaxique récursif ?

IFT313 2© Froduald Kabanza

Page 3: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Objectifs

• Pouvoir programmer un analyseur syntaxique récursif pour une grammaire donnée.

• Connaître les fondements d’un générateur d’analyseur

syntaxique LL tel que JavaCC.

IFT313 3© Froduald Kabanza

Page 4: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

© Froduald Kabanza

4IFT313

Références

[2] Appel, A. and Palsberg. J. Modern Compiler Implementation in Java.

Second Edition. Cambridge, 2004.– Section 3.2

[4] Aho, A., Lam, M., Sethi R., Ullman J. Compilers: Principles, Techniques, and

Tools, 2nd Edition. Addison Wesley, 2007.– Section 4.4.1

Page 5: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Rappel : Analyseur LL(1) non récursif- Un analyseur syntaxique LL non récursif exécute une boucle dans laquelle,

à chaque étape, soit il prédit la production à appliquer ou il reconnaît (match) le prochain lexème (token).

Pour cette raison, on l’appelle souvent en anglais « predictive parser »

ou « predict-match parser ».

- Un générateur d’analyseur syntaxique non récursif :• Prend une grammaire comme entrée.• Produit, à partir de la grammaire, une table d’analyse qui prédit la

production à appliquer en fonction du non terminal au sommet de la pile et du prochain lexème (token).

• Le générateur a accès à du code pour un driver LL(1) (qui est essentiellement un automate à pile LL(1))

• L’analyseur pour la grammaire d’entrée est obtenu en combinant le driver et la table d’analyse.

IFT313 5© Froduald Kabanza

Page 6: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Rappel : Exemple

G = (V, A, R, E) :

V = {E, E’, T, T’, F}

A = {(, ), +, *, n}

R = {

E TE’

E’ + TE’ | ε

T FT’ T’ *FT’ | ε F ( ) | E

n }

Table d’analyse

n + *

E E TE’

$

E’ E’+TE’ E’ ε E’ εT T FT’ TFT’

T’ T’ ε T’*FT’ T’ ε T’ εF F n F(E)

( )

E TE’

IFT313 6© Froduald Kabanza

Page 7: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

return true

Pile

0.3.3.3.2.3.3.2.3.3.2.3.2.3.2.3.3.1.

Étape Règle Algorithm LLDriver

0. stack = ($S); a = in.read(); x=stack.top();

while (true) {

1. if (x = = $) && (a= = $)

return true ;

2. if (x = = a) && (a != $) {

pop a from stack; a = in.read();

continue;}

3. if x is a nonterminal {

if M[x,a] is error exit with error;

let x y in M[x,a]

pop x from stack; push y on stack;

continue; }

4. exit with error;}

Entrée

Entrée : n+n*n

$E$E’T$E’T’F$E’T’n$E’T’$E’$E’ T+$E’ T$E’T’F$E’T’n$E’T’$E’T’F*$E’T’F$E’T’n$E’T’$E’$

n+n*n$ n+n*n$ n+n*n$n+n*n$ +n*n$ +n*n$ +n*n$ n*n$ n*n$ n*n$ *n$ *n$ n$ n$ $ $ $

E TE’T FT’F n

T’ εE’ +TE’

T FT’F n

T’ *FT’

F n T’ εE’ ε

n + *

E E TE’

$

E’ E’+TE’ E’ ε E’ ε

T T FT’ TFT’

T’ T’ ε T’*FT’ T’ ε T’ ε

F F n F(E)

( )

E TE’

IFT313 7© Froduald Kabanza

Page 8: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Analyse LL(1) descendante récursive - On peut aussi définir un analyseur LL(1) directement à partir des règles de

productions et de la table d’analyse, sans utiliser le driver LL1.

- L’idée est de simuler directement la dérivation la plus à gauche : En associant des fonctions d’analyse aux différents symboles de la

grammaire (terminaux et non terminaux). En faisant les appels de fonctions selon la structure de la grammaire.

- Aux terminaux on associe une fonction match(Token) qui va matcher le prochain token.

- A chaque non terminal X, on associe une fonction X() dont le corps appelle des fonctions correspondant aux parties droites des règles dont X est la partie gauche.

IFT313 8© Froduald Kabanza

Page 9: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Exemple

G = (V, A, R, S) :

V = {S, T, L, E}

A ={if, else, {, }, ;, =, ), (, id, print}

R = {

S if T S else S

S { S L | print(E)

T (id = = id)

L } | ;S L

E id }

Exemple de mot généré : if (id = = id) { print(id); print(id) } else print(id)

IFT313 9© Froduald Kabanza

Page 10: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Analyseur LL(1) récursif

G = (V, A, R, E) :

V = {S, T, L, E} A ={ if, else, {, }, ;, =, ),

(, id, print }

R = {

S if T S else S

S { S L | print(E)

T (id = = id)

L } | ;S L

E id }

Token a; // Variable globale : contiendra le prochain token

void match (GrammarSymbol x){ if (x.equals(a.text()) a = getNextToken(); else error();}

void S() { switch (a) case if : match(if); T(); S(); match(‘;’); match(else); S(); match(‘;’); break; case ‘{’ : match(‘{’); S(); L(); break; case print : match(print); match(‘(’); E(); match(‘)’); break; default: error();}

void T() { switch (a) case ‘(’ : match(‘(’); match(id); match(=); match(=); match(id); match(‘)’); break; default : error();}

Note : En pratique ‘;’ sera représenté par un symbole (ex. SEMI). Idem pour {, }, (, ).Ce n’est pas fait ici pour une question de clarté

IFT313 10© Froduald Kabanza

Page 11: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Analyseur LL(1) récursif (suite)

G = (V, A, R, S) :

V = {S, T, L, E}

A ={ if, else, {, }, ;, =, ),

(, id, print }

R = {

S if T S else S

S { S L | print(E)

T (id = = id)

L } | ;S L

E id }

void L() { switch (a) case ‘}’ : match(‘}’); break; case ‘;’ : match(‘;’); S(); L(); break; default : error();}

void E() { switch (a) case id : match(id); break; default : error();}

void main () // Point d’entrée du parseur { a = getNextToken(); S(); // fonction d’analyse pour le symbole de départ System.out.print(“Accepte : entrée correcte”);}

IFT313 11© Froduald Kabanza

Page 12: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Exercices

- Pour vous convaincre que ça marche, simulez l’analyseur sur les entrées suivantes :

Entrée incorrecte syntaxiquement :

if else (id = = id)

Entrée correcte syntaxiquement :

if (id = = id) { print(id); print(id) } else print(id);

- Modifiez le parseur pour qu’il imprime la dérivation de l’entrée.- Implémentez-le en Java.

IFT313 12© Froduald Kabanza

Page 13: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Observations- Il est facile d’écrire un analyseur syntaxique récursif manuellement.

- Pour que l’approche précédente fonctionne il faut que :

1. La partie droite de chaque production commence par un terminal

Parce que le switch de chaque fonction X() se fait sur les terminaux qui commencent les partie droite des production dont X est la partie gauche.

2. Deux productions ayant la même partie gauche doivent avoir des parties droites commençant par des préfixes différents.

Parce que les deux règles ont la même fonction d’analyse (c-à-d., la fonction correspondant au non terminal dans la partie gauche de chaque production). Si elle partagent le même préfixe, le switch ne pourra pas tenir compte des deux à la fois.

IFT313 13© Froduald Kabanza

Page 14: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Observations- Il est facile d’écrire un analyseur syntaxique récursif manuellement.

- Pour que l’approche précédente fonctionne il faut que :

1. La partie droite de chaque production commence par un terminal.

2. Deux productions ayant la même partie gauche doivent avoir des parties droites commençant par des préfixes différents.

- Ces conditions nous garantissent que la fonction d’analyse pour chaque non terminal est déterministe.

En d’autre mots, on peut prédire la production appropriée, simplement en lisant le prochain token.

IFT313 14© Froduald Kabanza

Page 15: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Observations- Il est très facile d’écrire un analyseur syntaxique récursif manuellement. - Pour que l’approche précédente fonctionne il faut que :

1. La partie droite de chaque production commence par un terminal

2. Deux productions ayant la même partie gauche doivent avoir des parties droites commençant par des préfixes différents.

- Ces conditions nous garantissent que la fonction d’analyse pour chaque non terminal est déterministe.

- Nous avons vu que seulement la première condition n’est pas nécessairement requise pour un parseur LL(1) non récursif.

- Comment généraliser l’approche récursive pour que la condition 1 ne soit pas nécessaire ?

IFT313 15© Froduald Kabanza

Page 16: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

ExempleG = (V, A, R, E) :

V = {E, E’, T, T’, F}

A = {(, ), +, *, n}

R = {

E TE’

E’ + TE’ | ε

T FT’ T’ *FT’ | ε F ( ) | E n }

Avec l’approche précédente on s’attendrait à quelque chose

du genre :

void E()

{ switch (a)

case ?? : T(); Eprime(); break;

default : error()}

Mais qu’est-ce qu’on met aux endroits indiqués par « ?? » ?

Vu que la production E TE’ ne commence pas par un ter-minal, notre approche ne fonctionne plus.

Pour résoudre ce problème, il faut utiliser la table d’analyse LL(1) de la grammaire, pour implémenter les cas de l’ins-truction switch.

De cette façon, on obtient un parser LL(1) récursif, équi-valent au parser LL(1) non récursif.

Cette grammaire illustre les limites de l’approche précédente.

Par exemple, quelle est la fonction d’analyse pour le non-terminal E ?

IFT313 16© Froduald Kabanza

Page 17: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Analyse syntaxique LL(1) récursif

- En général, pour avoir un analyseur syntaxique récursif, il faut utiliser une table d’analyse LL(1) afin d’implémenter les cas du switch:

Pour une fonction d’analyse X() donnée, les cas de l’instruction switch correspondent aux tokens a, tels que les entrées [X,a] sont non vides dans la table d’analyse.

La séquence d’appels pour chaque chaque cas est une séquence de match et de fonction d’analyse correspondants à la partie droite de la production dans l’entrée [X,a] de la table d’analyse.

IFT313 17© Froduald Kabanza

Page 18: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Exemple

G = (V, A, R, E) :

V = {E, E’, T, T’, F}

A = {(, ), +, *, n}

R = {

E TE’

E’ + TE’ | ε

T FT’ T’ *FT’ | ε F ( ) | E

n }

Table d’analyse

n + *

E E TE’

$

E’ E’+TE’ E’ ε E’ εT T FT’ TFT’

T’ T’ ε T’*FT’ T’ ε T’ εF F n F(E)

( )

E TE’

void E() { switch (a) case n : T(); Eprime(); break; case ( : T(); Eprime(); break; default : error()}

void Eprime() { switch (a) case + : match(+); T();E’(); break; case ) : break; case EOF : break; default : error()}

IFT313 18© Froduald Kabanza

Page 19: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Stratégies de recouvrement d’erreurs

- Une erreur apparaît lorsque la chaîne d’entrée n’est pas syntaxiquement correcte, c-à-d. elle n’est pas dérivable de la grammaire.

- En pratique, on ne veut pas arrêter l’analyse à la toute première erreur.

On veut continuer l’analyse syntaxique jusqu’à un certain nombre d’erreurs préfixé ou jusqu’à un certain niveau de sévérité de l’erreur.

- Les stratégies de recouvrement typiques consistent à réparer la chaîne d’entrée pour que l’analyse continue. En particulier :

- On peut insérer des tokens.- Supprimer des tokens.- Remplacer des tokens.

IFT313 19© Froduald Kabanza

Page 20: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Recouvrement d’erreurs par insertion de tokens

- Pour insérer un token manquant de l’input, on n’a pas besoin de l’ajouter explicitement à la chaîne d’entrée.

- Il suffit de prétendre que le token est présent, imprimer un message approprié et retourner normalement tel qu’illustré par les exemples suivants pour E() et Eprime().

void E() { switch (a) case n : T(); Eprime(); break; case ( : T(); Eprime(); break; default : print(“Expected num or )”;}

void Eprime() { switch (a) case + : match(+); T();E’(); break; case ) : break; case EOF : break; default : print(“Expected +, ), or EOF.”); }

IFT313 20© Froduald Kabanza

Page 21: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Recouvrement d’erreurs par insertion de tokens

- Le recouvrement d’erreurs par insertion de tokens est à utiliser avec précaution parce que une cascade d’erreurs risque de mener à une à une boucle sans fin : tokens sont insérés (ou supposés présents) sans cesse, de sorte que la chaine d’entrée n’est jamais vidée.

void E() { switch (a) case n : T(); Eprime(); break; case ( : T(); Eprime(); break; default : print(“Expected num or )”;}

void Eprime() { switch (a) case + : match(+); T();E’(); break; case ) : break; case EOF : break; default : print(“Expected +, ), or EOF.”); }

IFT313 21© Froduald Kabanza

Page 22: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Recouvrement d’erreurs par suppression de tokens

- Le recouvrement d’erreurs par suppression de tokens est plus sécuritaire parce qu’il garantie toujours que la chaîne d’entrée va être vidée.

- Pour une fonction d’analyse X(), la stratégie est, en cas d’erreur, de sauter (supprimer) les prochains tokens jusqu’au premier token qui est dans Follow(X).

void Eprime() { switch (a) case + : match(+); T();E’(); break; case ) : break; case EOF : break; default : print(“Expected +, ), or EOF.”); skipTo(Follow[Eprime]);}

Follow[Eprime] = { ), $ }

skipTo(A) supprime les prochains tokens jusqu’au premier dans A.

IFT313 22© Froduald Kabanza

Page 23: IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)

Générateurs d’analyseurs LL(1) récursifs

- Un générateur d’analyseur LL(1) récursif reçoit comme entrée une grammaire et donne comme sortie un analyseur LL(1) récursif correspondant.

- Pour ce faire : Il génère une table d’analyse LL(1) Génère un patron (template) des fonctions d’analyse à partir des règles

de production, utilisant la table d’analyse pour implémenter le switch. Ajoute le code pour la méthode (fonction) match.

- Il n’y a plus de pile explicite. Elle est implicitement implémentée par la pile d’appels des fonctions (la pile de récursivité).

- JavaCC et ANTLR sont des exemple de générateurs d’analyseurs LL récursifs.

IFT313 23© Froduald Kabanza