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
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
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.
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 ;
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.
Génération d’un analyseur
Java CUP
Spécification Java CUP (parser.cup)
Spécification JFlex
(scanner.jflex)
Parser.java
(analyseur syntaxique)
Sym.java
(types symboles)
JFlex
javac
Scanner.java
(analyseur lexical) Parser.class Scanner.class
Code source
Sym.class
AST en mémoire Ou inerprétation
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.
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 pro- duction.
– 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 cor- respondants ont été déclarés comme étant de la classe Integer.
– Il en va de même pour RESULT.
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, re- duce/reduce).
• Cela permet de travailler avec des grammaires plus simples et plus facile à comprendre, ou plus expressives que les grammaires LALR(1).
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ë.
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...];
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
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 in- verse 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.
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éduc- tions 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’addi- tionner avec 3.
– Sinon on fait reduce.
– Lorsqu’on a un conflit reduce/reduce, on réduit avec la production ayant la plus grande prior- ité.
– 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
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é.
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 corre- spond à l’entrée, une erreur syntaxique devrait être signalée. Le recou- vrement 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 cor- rectement en lisant un point-virgule (SEMI).