IFT313 Introduction aux langages formels

Slides:



Advertisements
Présentations similaires
IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke planiart.usherbrooke.ca/kabanza/cours/ift313.
Advertisements

IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke planiart.usherbrooke.ca/kabanza/cours/ift313.
IFT451 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke planiart.usherbrooke.ca/kabanza/cours/ift313.
IFT313 Introduction aux langages formels
IFT313 Introduction aux langages formels
IFT313 Introduction aux langages formels
IFT313 Introduction aux langages formels Froduald Kabanza Département dinformatique Université de Sherbrooke planiart.usherbrooke.ca/kabanza/cours/ift313.
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 dinformatique Université de Sherbrooke Automates à pile LR Notion de poignée.
Chapitre 3 La numération octale et hexadécimale. Chapitre 3 : La numération octale et hexadécimale 1 - Introduction 2 - Le système Octal Définition.
TP 1 BIS Programmation structurée à l’aide de fonctions (FC) et de bloc fonctionnels (FB)
Comment utiliser le débogueur de Visual Studio /8/2015 INF145 1 Créé par Julien Galarneau Allaire, révisé par Eric Thé S.E.G.
Ce videoclip produit par l’Ecole Polytechnique Fédérale de Lausanne
LES TABLEAUX EN JAVA.
Session 1 6 mars 2017 Plateforme ICONICS Justine Guégan
IFT313 Introduction aux langages formels
Algorithme et programmation
Plateforme CountrySTAT Aperçu global de la préparation des tableaux dans la nouvelle plateforme CountrySTAT FORMATION DES POINTS FOCAUX SUR LE SYSTEME.
Lois fondamentales de l'algèbre de Boole
Ajouter le code dans une page html
Ce videoclip produit par l’Ecole Polytechnique Fédérale de Lausanne
Les opérations sur les nombres
L’Instruction de Test Alternatif
IFT 615 – Intelligence artificielle Recherche heuristique
JAVA et POO : Notion d'héritage
Javadoc et débogueur Semaine 03 Version A16.
Bddictionnairique Phase 1
Fonctions.
Bases de programmation en Python
Principes de programmation (suite)
Initiation à la programmation impérative et algorithmique
Algorithmique & Langage C
Présentation Structure données abstraite (TDA) Rappel : File
Programmation Impérative II
Semaine #4 INF130 par Frédérick Henri.
Codage Indenter le code Limiter la portée des variables Traiter les erreurs en premier Utiliser le switch au delà de 3 tests en cascades Ne jamais utiliser.
1.2 dénombrement cours 2.
IFT313 IFT313 Introduction aux langages formels Froduald Kabanza Département d’informatique Université de Sherbrooke planiart.usherbrooke.ca/kabanza/cours/ift313.
PROGRAMMATION ET ENSEIGNEMENT
Chapter 12: Structures de données
Introduction aux langages formels
Exercice PHP DEUST TMIC
Formation sur les bases de données relationnelles.
Exercice : le jeu. Vous devez concevoir l’algorithme permettant de jouer avec votre calculatrice : elle détermine au hasard un nombre caché entier entre.
Connaître les équivalences entre fractions
Les Pronoms Secondaire 3, Enrichi.
IFT313 Introduction aux langages formels
IFT313 Introduction aux langages formels
IFT313 Introduction aux langages formels
IFT313 Introduction aux langages formels
Université de la méditerranée
IFT313 Introduction aux langages formels
IFT313 Introduction aux langages formels
Langages de programmation TP11
IFT313 Introduction aux langages formels
Le code de Huffman: est une méthode de compression statistique de données qui permet de réduire la longueur du codage d'un alphabet. Le code de Huffman.
MATHÉMATIQUES FINANCIÈRES I
IFT313 Introduction aux langages formels
Reconnaissance de formes: lettres/chiffres
IFT313 Introduction aux langages formels
IFT313 Introduction aux langages formels
H. Wertz -- Exécution Symbolique
Opérateurs et fonctions arithmétiques Opérateurs de relation Opérateurs logiques Cours 02.
Tris Simples/Rapides.
PROGRAMMATION ET ENSEIGNEMENT
INTERFACE ET POLYMORPHISME
Arbre binaire.
Chapter 11: Récursivité Java Software Solutions Second Edition
Python Nicolas THIBAULT
La loi des signes.
Transcription de la présentation:

IFT313 Introduction aux langages formels Froduald Kabanza Département d’informatique 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 © Froduald Kabanza

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 © Froduald Kabanza

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 IFT313 © Froduald Kabanza

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 © Froduald Kabanza

Rappel : Exemple Table d’analyse 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 } n + * E E  TE’ $ E’ E’+TE’ E’ ε T T FT’ TFT’ T’ T’ ε T’*FT’ F F n F(E) ( ) IFT313 © Froduald Kabanza

n + * E E  TE’ $ E’ E’+TE’ E’ ε T T FT’ TFT’ T’ T’ ε T’*FT’ F F n F(E) ( ) Entrée : n+n*n Étape Règle Pile Entrée 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;} 0. 3. 2. 1. E  TE’ T  FT’ F  n T’  ε E’  +TE’ T’  *FT’ T’ ε E’ ε $E $E’T $E’T’F $E’T’n $E’T’ $E’ $E’ T+ $E’ T $E’T’F* $ n+n*n$ +n*n$ n*n$ *n$ n$ $ return true IFT313 © Froduald Kabanza

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 © Froduald Kabanza

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

Analyseur LL(1) récursif 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(else); S(); break; case ‘{’ : match(‘{’); S(); L(); break; case print : match(print); match(‘(’); E(); match(‘)’); break; default: error();} void T() case ‘(’ : match(‘(’); match(id); match(=); match(=); match(id); match(‘)’); break; default : error();} 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 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 © Froduald Kabanza

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() 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 © Froduald Kabanza

Exercices Pour vous convaincre que ça marche, simulez l’analyseur sur les entrées suivantes : Entrée correcte syntaxiquement : if (id = = id) { print(id); print(id) } else Entrée incorrecte syntaxiquement : if else (id = = id) Modifiez le parseur pour qu’il imprime la dérivation de l’entrée. Implémentez-le en Java. IFT313 © Froduald Kabanza

Observations Il est facile d’écrire un analyseur syntaxique récursif manuellement. Pour que l’approche précédente fonctionne il faut que : 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. 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 © Froduald Kabanza

Observations Il est facile d’écrire un analyseur syntaxique récursif manuellement. Pour que l’approche précédente fonctionne il faut que : La partie droite de chaque production commence par un terminal. 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 © Froduald Kabanza

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 : La partie droite de chaque production commence par un terminal 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 © Froduald Kabanza

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 } 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 terminal, 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’instruction 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 l’approche précédente. Par exemple, quelle est la fonction d’analyse pour le non terminal E ? IFT313 © Froduald Kabanza

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 © Froduald Kabanza

Exemple case EOF : break; Table d’analyse 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 } n + * E E  TE’ $ E’ E’+TE’ E’ ε T T FT’ TFT’ T’ T’ ε T’*FT’ F F n F(E) ( ) void E() { switch (a) case n : T(); Eprime(); break; case ( : T(); Eprime(); default : error()} void E’() { switch (a) case + : match(+); T();E’(); break; case ) : break; case EOF : break; default : error()} IFT313 © Froduald Kabanza

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 © Froduald Kabanza

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 + : match(+); T();E’(); break; case ) : break; case EOF : break; default : print(“Expected +, ), or EOF.”); } void E() { switch (a) case n : T(); Eprime(); break; case ( : T(); Eprime(); default : print(“Expected num or )”;} IFT313 © Froduald Kabanza

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 + : match(+); T();E’(); break; case ) : break; case EOF : break; default : print(“Expected +, ), or EOF.”); } void E() { switch (a) case n : T(); Eprime(); break; case ( : T(); Eprime(); default : print(“Expected num or )”;} IFT313 © Froduald Kabanza

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). Follow[Eprime] = { ), $ } void E’() { switch (a) case + : match(+); T();E’(); break; case ) : break; case EOF : break; default : print(“Expected +, ), or EOF.”); skipTo(Follow[E’]);} skipTo(A) supprime les prochains tokens jusqu’au premier dans A. IFT313 © Froduald Kabanza

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 © Froduald Kabanza