Apprentissage de JFlex + Cup

publicité
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
Téléchargement