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 2 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 3 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 4 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 T F ::= ::= ::= IFT313 E PLUS T | T ; T TIMES F | F ; LPAREN E RPAREN | NUMBER ; © Froduald Kabanza 5 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. IFT313 © Froduald Kabanza 6 Génération d’un analyseur Spécification Java CUP (parser.cup) Spécification JFlex (scanner.jflex) Sym.java Code source Scanner.class (types symboles) Java CUP JFlex Sym.class Parser.java (analyseur syntaxique) Scanner.java (analyseur lexical) javac IFT313 © Froduald Kabanza Parser.class AST en mémoire Ou inerprétation 7 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 9 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 10 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 11 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: 1. 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. 2. 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 12 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 13 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 14 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 15 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 16 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 17 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 18