La présentation est en train de télécharger. S'il vous plaît, attendez

La présentation est en train de télécharger. S'il vous plaît, attendez

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

Présentations similaires


Présentation au sujet: "IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)"— Transcription de la présentation:

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

2 Sujets Cest quoi un analyseur syntaxique récursif ? Comment le programmer ? Comment fonctionne un générateur danalyseur syntaxique récursif ? IFT3132© Froduald Kabanza

3 Objectifs Pouvoir programmer un analyseur syntaxique récursif pour une grammaire donnée. Connaître les fondements dun générateur danalyseur syntaxique LL tel que JavaCC. IFT3133© Froduald Kabanza

4 IFT313© Froduald Kabanza4 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, 2 nd Edition. Addison Wesley, 2007. – Section 4.4.1

5 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 lappelle souvent en anglais « predictive parser » ou « predict-match parser ». Un générateur danalyseur syntaxique non récursif : Prend une grammaire comme entrée. Produit, à partir de la grammaire, une table danalyse 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)) Lanalyseur pour la grammaire dentrée est obtenu en combinant le driver et la table danalyse. IFT3135© Froduald Kabanza

6 Rappel : Exemple G = (V, A, R, E) : V = {E, E, T, T, F} A = {(, ), +, *, n} R = { E TE E + TE | ε T FT *FT | ε F n } Table danalyse n+* E E TE $ E E +TE E ε T T FT T T ε T *FT T ε F F nF (E) () E TE IFT3136© Froduald Kabanza

7 return true Pile 0. 3. 2. 3. 2. 3. 2. 3. 2. 3. 2. 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 $ET $ETF $ETn $ET $E $E T+ $E T $ETF $ETn $ET $ETF* $ETF $ETn $ET $E $ 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 ε T T FT T T ε T *FT T ε F F nF (E) () E TE IFT3137© Froduald Kabanza

8 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 danalyse, sans utiliser le driver LL1. Lidée est de simuler directement la dérivation la plus à gauche : En associant des fonctions danalyse 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. IFT3138© Froduald Kabanza

9 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 id } Exemple de mot généré : if (id = = id) { print(id); print(id) } else print(id) IFT3139© Froduald Kabanza

10 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 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 nest pas fait ici pour une question de clarté IFT31310© Froduald Kabanza

11 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 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 dentrée du parseur { a = getNextToken(); S(); // fonction danalyse pour le symbole de départ System.out.print(Accepte : entrée correcte); } IFT31311© Froduald Kabanza

12 Exercices Pour vous convaincre que ça marche, simulez lanalyseur 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 quil imprime la dérivation de lentrée. Implémentez-le en Java. IFT31312© Froduald Kabanza

13 Observations Il est facile décrire un analyseur syntaxique récursif manuellement. Pour que lapproche 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 danalyse (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. IFT31313© Froduald Kabanza

14 Observations Il est facile décrire un analyseur syntaxique récursif manuellement. Pour que lapproche 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 danalyse pour chaque non terminal est déterministe. En dautre mots, on peut prédire la production appropriée, simplement en lisant le prochain token. IFT31314© Froduald Kabanza

15 Observations Il est très facile décrire un analyseur syntaxique récursif manuellement. Pour que lapproche 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 danalyse pour chaque non terminal est déterministe. Nous avons vu que seulement la première condition nest pas nécessairement requise pour un parseur LL(1) non récursif. Comment généraliser lapproche récursive pour que la condition 1 ne soit pas nécessaire ? IFT31315© Froduald Kabanza

16 Exemple G = (V, A, R, E) : V = {E, E, T, T, F} A = {(, ), +, *, n} R = { E TE E + TE | ε T FT *FT | ε F n } Avec lapproche précédente on sattendrait à quelque chose du genre : void E() { switch (a) case ?? : T(); Eprime(); break; default : error()} Mais quest-ce quon met aux endroits indiqués par « ?? » ? Vu que la production E TE ne commence pas par un terminal, notre approche ne fonctionne plus. Pour résoudre ce problème, il faut utiliser la table danalyse LL(1) de la grammaire, pour implémenter les cas de linstruction switch. De cette façon, on obtient un parser LL(1) récursif, équivalent au parser LL(1) non récursif. Cette grammaire illustre les limites de lapproche précédente. Par exemple, quelle est la fonction danalyse pour le non-terminal E ? IFT31316© Froduald Kabanza

17 Analyse syntaxique LL(1) récursif En général, pour avoir un analyseur syntaxique récursif, il faut utiliser une table danalyse LL(1) afin dimplémenter les cas du switch: Pour une fonction danalyse X() donnée, les cas de linstruction switch correspondent aux tokens a, tels que les entrées [X,a] sont non vides dans la table danalyse. La séquence dappels pour chaque chaque cas est une séquence de match et de fonction danalyse correspondants à la partie droite de la production dans lentrée [X,a] de la table danalyse. IFT31317© Froduald Kabanza

18 Exemple G = (V, A, R, E) : V = {E, E, T, T, F} A = {(, ), +, *, n} R = { E TE E + TE | ε T FT *FT | ε F n } Table danalyse n+* E E TE $ E E +TE E ε T T FT T T ε T *FT T ε F F nF (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()} IFT31318© Froduald Kabanza

19 Stratégies de recouvrement derreurs Une erreur apparaît lorsque la chaîne dentrée nest pas syntaxiquement correcte, c-à-d. elle nest pas dérivable de la grammaire. En pratique, on ne veut pas arrêter lanalyse à la toute première erreur. On veut continuer lanalyse syntaxique jusquà un certain nombre derreurs préfixé ou jusquà un certain niveau de sévérité de lerreur. Les stratégies de recouvrement typiques consistent à réparer la chaîne dentrée pour que lanalyse continue. En particulier : On peut insérer des tokens. Supprimer des tokens. Remplacer des tokens. IFT31319© Froduald Kabanza

20 Recouvrement derreurs par insertion de tokens Pour insérer un token manquant de linput, on na pas besoin de lajouter explicitement à la chaîne dentrée. Il suffit de prétendre que le token est présent, imprimer un message approprié et retourner normalement tel quillustré 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.); } IFT31320© Froduald Kabanza

21 Recouvrement derreurs par insertion de tokens Le recouvrement derreurs par insertion de tokens est à utiliser avec précaution parce que une cascade derreurs 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 dentrée nest 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.); } IFT31321© Froduald Kabanza

22 Recouvrement derreurs par suppression de tokens Le recouvrement derreurs par suppression de tokens est plus sécuritaire parce quil garantie toujours que la chaîne dentrée va être vidée. Pour une fonction danalyse X(), la stratégie est, en cas derreur, de sauter (supprimer) les prochains tokens jusquau 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 jusquau premier dans A. IFT31322© Froduald Kabanza

23 Générateurs danalyseurs LL(1) récursifs Un générateur danalyseur 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 danalyse LL(1) Génère un patron (template) des fonctions danalyse à partir des règles de production, utilisant la table danalyse pour implémenter le switch. Ajoute le code pour la méthode (fonction) match. Il ny a plus de pile explicite. Elle est implicitement implémentée par la pile dappels des fonctions (la pile de récursivité). JavaCC et ANTLR sont des exemple de générateurs danalyseurs LL récursifs. IFT31323© Froduald Kabanza


Télécharger ppt "IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke Analyseurs récursifs LL (1)"

Présentations similaires


Annonces Google