Apprentissage de JFlex + Cup
Transcription
Apprentissage de JFlex + Cup
Université du Littoral M1 Apprentissage de JFlex + Cup 1 Introduction Le but de cet TP est de vous donner les bases nécessaires afin de pouvoir écrire votre analyseur syntaxique en Java. Pour Java, nous utiliserons le couple JFlex (http ://jflex.de/) et Cup (http ://www2.cs.tum.edu/projects/cup/). 1.1 Analyseur syntaxique Cup Cup est programmé en Java. La dernière version est (en 2009) la version 0.11a. L’utilisation de cette version se fait de la façon suivante : java -jar java-cup-11a.jar moncup.cup Si la version est plus ancienne, il est possible que vous deviez utiliser la commande : cup moncup.cup (moncup.cup étant le fichier contenant la spécification Cup). A partir d’un fichier d’entrée respectant le format spécifié ci-dessous, Cup génère deux fichiers : un fichier parser.java contenant la classe permettant de parser par la suite, et un fichier sym.java contenant les types des objets utilisé (symboles). 1.2 Format d’une spécification Cup Ce fichier de spécification se compose de cinq parties : 1. La première partie est la spécification optionnelle du package auquel appartiendront les fichiers parser.java et sym.java générés ainsi que les importations éventuelles d’autres packages. 2. La seconde partie, également optionnelle, contient des déclarations de code Java qui se retrouveront dans le fichier parser.java généré. 3. La troisième partie consiste en la déclaration des terminaux et des non terminaux de la grammaire avec la syntaxe suivante : terminal nom1, nom2, ...; terminal nom_class nom1, nom2, ...; non terminal nom1, nom2, ...; non terminal nom_class nom1, nom2, ...; nom_class est un nom de classe JAVA qui correspond aux attributs associés aux symboles de la grammaire : 1 – dans le cas des terminaux, c’est le type des objets associés aux unités lexicales et retournés par l’analyseur lexical. – dans le cas des non terminaux, c’est le type des objets retournés par les actions sémantiques d’une règle dont le membre gauche est un des non terminaux qui suit nom_class dans la déclaration. Par exemple, pour compter les occurrences de a dans un mot terminal a,b; non-terminal Integer S; 4. La quatrième partie est la déclaration optionnelle des différentes priorités des opérateurs ainsi que leur associativité. 5. La cinquième partie est la déclaration des règles de la grammaire ainsi que des actions sémantiques associées à ces règles. Elle débute par une déclaration optionnelle de l’axiome start with nonterminal_name; Si cette déclaration est omise, alors le membre gauche de la première règle de production rencontrée sera considéré comme l’axiome. Viennent ensuite les règles de production qui sont de la forme suivante : nonterminal_name :: = name1 name2 ... namek {: action semantique :}; où name est un symbole de terminal ou de non terminal déclaré dans la troisième partie. Dans le cas de plusieurs règles ayant le même membre gauche, on utilise | pour séparer les différentes alternatives. nonterminal_name :: = name1 name2 ... namek {: action semantique :} | name1’ name2’ ... namek’ {: action semantique :}; La partie action sémantique est simplement du code JAVA. Dans le but de référencer une valeur calculée pour un terminal (par l’analyseur lexical) ou pour un non terminal (au cours de l’analyse syntaxique), on peut associer aux symboles en partie droite des règles de production un label qui sera utilisé dans la partie action sémantique. De plus, le résultat retourné pour le non terminal en partie gauche se fera en affectant dans la partie action sémantique la variable RESULT. Par exemple, pour compter les occurrences de a dans un mot du langage an bn |0 < n, S ::= a S:m b {: RESULT = new Integer(m.intValue() + 1); :} | a b {: RESULT = new Integer(1) ;:} ; 2 2 Exercices Exercice no 1 somme d’entiers Il va falloir exprimer (par récursivité) le fait qu’une somme est un entier + une somme (cas général) ou un entier (cas d’arrêt) c Comme d’habitude, en récursivité, on réfléchit sur le(s) cas général(ux) et on en déduit le(s) cas d’arrêt(s) et dans le programme, on commence toujours par le(s) cas d’arrêt(s) Ce qui donne, avec les token fournis par JFlex : somme ::= ENTIER somme ::= somme PLUS ENTIER Écrire les fichiers JFlex et Cup correspondants. Vérifiez que les exemples suivants fonctionnent : 25 + 96 + 175 +33 + 2 13 15 + 16 + 3 7 + 25 Vous pouvez prendre comme fichier Main.java le fichier suivant pour tester votre analyseur en supposant que votre analyseur lexical s’appelle Lexer.java : /∗ MainSomme . j a v a ∗/ import j a v a . i o . ∗ ; public c l a s s MainSomme { s t a t i c public void main ( S t r i n g argv [ ] ) { try { p a r s e r p = new p a r s e r ( new Lexer ( new F i l e R e a d e r ( argv [ 0 ] ) ) ) ; Object r e s u l t = p . p a r s e ( ) . v a l u e ; System . out . p r i n t l n ( "\ nSyntaxe ␣ c o r r e c t e " ) ; } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( "\ nSyntax ␣ E r r o r " ) ; e . printStackTrace ( ) ; } } } Exercice no 2 Décoration du fichier « Décorez » le fichier calcul.cup de manière à calculer automatiquement le résultat de l’opération. Vous pouvez prendre comme fichier Main.java le fichier suivant : 3 /∗ MainSommeCalcul . j a v a ∗/ import j a v a . i o . ∗ ; public c l a s s MainSommeCalcul { s t a t i c public void main ( S t r i n g argv [ ] ) { try { p a r s e r p = new p a r s e r ( new Lexer ( new F i l e R e a d e r ( argv [ 0 ] ) ) ) ; Object r e s u l t = p . p a r s e ( ) . v a l u e ; System . out . p r i n t l n ( "=␣"+r e s u l t ) ; } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( "\ nSyntax ␣ E r r o r " ) ; e . printStackTrace ( ) ; } } } Si le fichier exemple.txt contient la ligne suivante : 25 + 96 + 175 +33 + 2 13 15+16+3 7+25 vous devriez obtenir le résultat suivant : 25 + 96 + 175 + 33 + 2 = 331 Exercice no 3 Calcul d’une somme multiple On souhaite maintenant pouvoir calculer le résultat de la somme pour chaque ligne d’un fichier. Si le fichier exemple.txt contient les données suivante : 25 + 96 + 175 +33 + 2 13 15+16+3 7+25 4 On souhaite obtenir les résultats suivants : 25 + 96 + 175 + 33 + 2 = 331 13 = 13 15 + 16 + 3 = 34 7 + 25 = 32 Pour cela, je vous conseille fortement de rajouter les deux non terminaux suivants à Cup : lst_somme représentant une liste de sommes et une_somme représentant une somme se terminant par un retour chariot. Vous devrez bien entendu prendre en compte le retour chariot dans le fichier JFlex. Exercice no 4 ajout de la multiplication et la division Modifier le fichier Cup de manière à prendre en compte les opérateurs de multiplication et division. Attention, vous devez tenir compte de la priorité des opérateurs. 5