IFT313 Introduction aux langages formels

Slides:



Advertisements
Présentations similaires
Bratec Martin ..
Advertisements

NOTIFICATION ÉLECTRONIQUE
Fragilité : une notion fragile ?
SEMINAIRE DU 10 AVRIL 2010 programmation du futur Hôtel de Ville
Phono-sémantique différentielle des monosyllabes italiens
MAGGIO 1967 BOLOGNA - CERVIA ANOMALIES DU SOMMEIL CHEZ L'HOMME
droit + pub = ? vincent gautrais professeur agrégé – avocat
Transcription de la présentation:

IFT313 Introduction aux langages formels Froduald Kabanza Département d’informatique Université de Sherbrooke Java CUP Générateur d’analyseurs LALR

Sujets Java CUP Introduction Actions sémantiques Gérer des conflits Recouvrement d’erreur IFT313 © Froduald Kabanza

Références [2] Appel, A. and Palsberg. J. Modern Compiler Implementation in Java. Second Edition. Cambridge, 2004. Section 3.4 à 3.5 [4] Aho, A., Lam, M., Sethi R., Ullman J. Compilers: Principles, Techniques, and Tools, 2nd Edition. Addison Wesley, 2007. Section 4.8 à 4.9 IFT313 © Froduald Kabanza

Java CUP CUP signifie « Construction of Useful Parser » C’est un générateur d’analyseurs LALR(1). Il est écrit en Java. Il génère des analyseurs en Java. Écrit originalement par Scott Hudson, Frank Flannery, C. Scott Ananian (University of Princeton) Maintenant maintenu par l’University of Munich. http://www2.cs.tum.edu/projects/cup/ Nous voyions une veille version (2006), alignée sur la syntaxe de Yacc (Yet Another Compiler-Compiler). Yacc est un générateur d’analyseurs syntaxiques écrit en C pour les environnements UNIX. IFT313 © Froduald Kabanza

Exemple (calcsyntax1/parser.cup) /* Terminals (tokens returned by the scanner). */ terminal PLUS, TIMES, LPAREN, RPAREN; terminal Integer NUMBER; /* Non terminals */ non terminal E, T, F; /* The grammar */ E ::= E PLUS T | T ; T ::= T TIMES F | F ; F ::= LPAREN E RPAREN | NUMBER ; IFT313 © Froduald Kabanza

Commande Commande : java –jar java-cup-11a.jar grammaire.cup Options - donne toutes les options -parser Parser spécifie le nom de classe du parser (déf: parser.java) -symbols Sym spécifie le nom de classe types de symboles (def sym.java) Il y en a d’autres : voir le manuel. -dump_grammar affiche la grammaire -dump_states affiche l’AFD LALR(1) -dump_tables affiche la table d’analyse LALR(1) -dump les trois options précédentes ensembles IFT313 © Froduald Kabanza

Génération d’un analyseur Code source Spécification Java CUP (parser.cup) Spécification JFlex (scanner.jflex) Sym.java (types symboles) Scanner.class Java CUP JFlex Sym.class Parser.java (analyseur syntaxique) Scanner.java (analyseur lexical) Parser.class AST en mémoire Ou inerprétation javac IFT313 © Froduald Kabanza

Rappel : Actions sémantiques et grammaires attribuées Une action sémantique est un code associé à une production et exécuté lorsqu’il y a une réduction avec la production durant l’analyse. Un attribut est une donnée associée à un symbole d’une production. Par exemple, si le symbole est un terminal, ça pourrait être la valeur du token (valeur double d’un token réel). Si le symbole est un non terminal, l’attribut pourrait être une donnée calculée en utilisant les actions sémantiques. Une grammaire avec des attributs est appelée une grammaire attribuée. IFT313 © Froduald Kabanza

Attributs synthétiques dans Java CUP Dans Java CUP les attributs sont optionnels. Chaque symbole dans la partie droite d’une production peut être optionnellement étiqueté par un autre symbole (un attribut). L’étiquette apparaît juste après le symbole, les deux étant séparés de « : » Les étiquettes pour une production doivent être uniques, et peuvent être utilisés dans l’action sémantique associée à la production pour référer aux valeurs des symboles correspondants. Le symbole dans la partie gauche est implicitement étiqueté par RESULT. Les valeurs des attributs par les terminaux sont fournies par le scanner. Les autres sont calculés par les actions sémantiques. IFT313 © Froduald Kabanza

Exemple E ::= E:x PLUS T:y {: RESULT = new Integer(x.intValue() + y.intValue()); :} Le symbole E dans la partie droite est étiqueté par x et le symbole T par y. RESULT est la valeur pour le symbole E dans la partie gauche de la production. Chaque symbole dans une règle de production est représenté par un objet (classe Symbol par défaut) sur la pile. Les étiquètes réfèrent aux valeurs de ces objets. x et y réfèrent à des objets de la classe Integer parce que les symboles correspondants ont été déclarés comme étant de la classe Integer. Il en va de même pour RESULT. IFT313 © Froduald Kabanza

Gérer des conflits Si une grammaire n’est pas LALR(1), normalement Java CUP va générer des conflits (shift/reduce ou reduce/reduce) En particulier, ce sera le cas si la grammaire est ambiguë (vu qu’elle ne peut pas être LR (k) quelque soit le cas; elle donne lieu à plusieurs arbres d’analyse). Toutefois, on peut utiliser une grammaire non LALR (1), voire ambiguë, à condition de spécifier des règles de désambiguïsation de sorte qu’il y ait un seul arbre d’analyse : Ces règles indiquent comment gérer les conflits (shift/reduce, reduce/reduce). Cela permet de travailler avec des grammaires plus simples et plus facile à comprendre, ou plus expressives que les grammaires LALR(1). IFT313 © Froduald Kabanza

Règles par défaut Par défaut Java CUP résout les conflits dans la table d’analyse LALR(1) en utilisant les règles suivantes: Un conflit reduce/reduce est résolu en choisissant l’élément correspondant à la règle de production qui apparaît en premier lieu dans la spécification de la grammaire. Un conflit shift/reduce est résolu en faveur du shift. Ceci permet entre autres de résoudre correctement le conflit shift/reduce généré par l’ambiguïté sous-jacente à l’instruction if-then-else. Il revient au programmeur de s’assurer que les règles par défaut correspondent à ce qu’il veut. Si ce n’est pas le cas, il a deux choix : (a) spécifier des règles de désambiguïsation; (b) utiliser une grammaire équivalente non ambiguë. IFT313 © Froduald Kabanza

Règles spécifiées explicitement On peut spécifier des règles de précédence et d’associativité comme suit : precedence left terminal [, terminal...]; precedence right terminal [, terminal...]; precedence nonassoc terminal [, terminal...]; IFT313 © Froduald Kabanza

Règles spécifiées explicitement L’ordre de précédence (priorité), du plus élevé au moins élevé, va du bas vers le haut. Ainsi, les déclarations suivantes indiquent que : l’addition et la soustraction ont la même priorité; la multiplication et la division ont la mêmes priorités; et ces derniers ont une priorité plus élevée que l’addition et la soustraction: precedence left ADD, MINUS; precedence left TIMES, DIVIDE; La précédence résout des conflit shift/reduce. Par exemple, avec les déclarations précédentes, étant donné l’entrée 3 + 4 * 8, L’analyseur doit déterminer s’il faut réduire ‘3 + 4’ ou avancer (shift) '*' sur la pile. Puisque '*' a une plus grande priorité que '+', il va être mis sur la pile, de sorte que la multiplication sera effectuée avant l’addition. IFT313 © Froduald Kabanza

Règles spécifiées explicitement Java CUP assigne une priorité à chaque terminal, en se basant sur l’ordre de leurs déclarations dans les spécification ‘precedence’, dans l’ordre inverse de leur apparition. Java CUP assigne aussi une priorité à chaque production : c’est la priorité du dernier terminal dans la partie droite de la production. Par exemple, expr ::= expr TIMES expr a la même précédence que TIMES. IFT313 © Froduald Kabanza

Règles spécifiées explicitement Lorsqu’on a un conflit shift/reduce : Si le terminal (avant le point) a une plus grande priorité que la production correspondant à l’élément reduce, on fait shift. S’ils ont la même priorité, l’associativité du terminal détermine ce qu’on fait: Si l’associativité du terminal avant le point est left, on fait reduce. Par exemple avec les déclarations précédentes, avec une entrée du genre 3 + 4 + 5, l’analyseur va toujours faire reduce de gauche à droite, en commençant par 3 + 4. Si l’associativité du terminal avant le point est right, on fait shift. Ainsi les réductions se feront de droite à gauche. Par exemple si on avait déclaré PLUS comme étant associatif à droite, dans l’exemple précédent 4 + 5 serait réduit avant d’additionner avec 3. Sinon on fait reduce. Lorsqu’on a un conflit reduce/reduce, on réduit avec la production ayant la plus grande priorité. Dans les situations où le terminal le plus à droite dans une production ne donne pas la priorité souhaitée, on peut spécifier la bonne priorité en utilisant %prec <terminal>. Ainsi la priorité de la règle sera comme celle du terminal (qui doit dans ce cas être défini dans la section de déclaration des précédences). IFT313 © Froduald Kabanza

Règles spécifiées explicitement Si des terminaux sont déclarés nonassoc, deux occurrences successifs de terminaux de même priorité génèrent une erreur. Par exemple, si '= =' est declaré nonassoc une erreur serait générée avec l’entrée 6 = = 7 = = 8 = = 9. Tous les terminaux non déclarés dans les spécifications précédence/associativité ont une priorité inférieure aux autres. Les productions sans terminaux ont aussi un priorité moindre inférieure aux autres Si un conflit shift/reduce ou reduce/reduce implique de tels terminaux il est reporté. IFT313 © Froduald Kabanza

Recouvrement d’erreur Java CUP utilise un token spécial error pour spécifier des recouvrements d’erreurs. Par exemple, on pourrait avoir des productions du genre: stmt ::= expr SEMI | while_stmt SEMI | if_stmt SEMI | ... | error SEMI ; Ceci signifie que si aucune des production normales pour stmt ne correspond à l’entrée, une erreur syntaxique devrait être signalée. Le recouvrement se fera en sautant les tokens erronés (ceci revient à les scanner et les réduire par error) jusqu’au point où l’analyse peut se poursuivre correctement en lisant un point-virgule (SEMI). IFT313 © Froduald Kabanza