Partie 1: Analyseur lexical
A présenter lors de la séance du 12 novembre 2004.
Le but de cette partie est d'écrire un analyseur lexical pour le
langage Eins. Cet analyseur doit pouvoir déterminer si un
fichier donné est conforme à la grammaire lexicale de
Eins. Si c'est le cas, il doit en outre retourner la suite
de lexèmes constituant le fichier. Sinon, il doit signaler une
ou éventuellement plusieurs erreurs.
Pour réaliser votre analyseur lexical, nous vous fournissons un
certain nombre de fichiers (voir ci-dessous). Votre travail est de compléter
deux de ces fichiers.
L'interface Tokens
a pour but de définir toutes les classes de lexèmes. Pour
l'instant, seules quelques unes sont définies. Il vous faut donc
examiner la grammaire
syntaxique de Eins afin de déterminer quelles sont les
classes de lexèmes manquantes (à chaque symbol terminal de la
grammaire syntaxique correspond une classe de lexèmes).
Lorsque vous avez défini toutes les classes de lexèmes, vous
pouvez compléter la méthode tokenClass de la classe
Scanner . Cette
méthode retourne simplement une représentation sous forme de
chaîne de caractères de la classe de lexèmes passée en
argument. Pour la plupart des classes, cela revient à retourner
la représentation du seul lexème faisant partie de cette classe.
Après cela, il ne vous restera plus qu'à compléter les méthodes
nextToken et readToken de la classe Scanner . Ces méthodes ont
pour but de sauter les commentaires et les espaces blancs et de
lire le prochain lexème. Elles doivent placer la classe de ce
lexème dans la variable d'instance token et la
position du premier caractère le constituant dans la variable
d'instance start . Et, si le lexème est un
identificateur, un nombre ou une chaîne de caractères, elles
doivent en plus stocker les caractères qui le constituent dans
la variable d'instance chars . Pour assembler ces
caractères en une chaîne, vous pouvez utiliser la variable
d'instance buf .
Pour détecter la fin du fichier source, on utilise le caractère
spécial EOF_CH . Celui-ci est retourné lorsque la
fin du fichier a été atteinte. On peut donc faire comme si la
grammaire lexicale avait été modifiée comme suit :
input = { inputelement } EOF_CH.
On modifie également la grammaire syntaxique comme
suit :
Program = { Declaration } Expression EOF_CH.
Le caractère EOF_CH devient ainsi un symbole
terminal de la grammaire syntaxique. Il doit donc exister une
classes de lexèmes qui lui corresponde. Cette classe est la
classe EOF définie dans Tokens .
Le caractère EOF_CH définit un pseudo-lexème, car,
dans la grammaire lexicale, EOF_CH apparaît dans la
définition de input et non comme une alternative de
token comme c'est le cas pour tous les vrais
lexèmes. L'analyseur lexical doit donc retourner tous les
lexèmes reconnus dans le fichier source plus le pseudo-lexème
EOF pour signaler la fin du fichier.
Idéalement, un compilateur devrait toujours signaler autant
d'erreurs que possible et non s'arrêter systématiquement après
la première d'entre elles. Toutefois, pour cette première
partie, nous ne vous forçons pas à implanter cela. Vous pouvez
donc vous contenter de signaler le première erreur rencontrée et
ensuite stopper le compilateur.
Pour signaler des erreurs utilisez les méthodes
error et fail fournies par la classe
Report .
Les méthodes fail impriment un message d'erreur et
terminent l'exécution du programme. Utilisez ces méthodes si une
erreur empêche toute poursuite de l'analyse ou si votre but est
de ne signaler que la première erreur rencontrée. Si au
contraire vous aimeriez essayer de continuer l'analyse après
l'erreur utilisez les méthodes error . Dans tous les
cas, utilisez autant que possible les versions incluant la
position de l'erreur.
Le programme de test ScannerTest.java
vous permet de tester votre analyseur lexical. Il lit un fichier
source et imprime la séquence de lexèmes reconnue par
l'analyseur. Le fichier source est spécifié comme argument
fourni au programme.
Pour lancer le programme à partir du répertoire racine de votre
projet, utilisez la commande suivante :
java -classpath ./classes einsc.ScannerTest fichier-source
Vous trouverez ci-dessous les fichiers dont vous aurez besoin
pour cette première partie. Il est fortement conseillé de créer
un nouveau répertoire dédié à ce projet. Placez-y le fichier
Makefile et, afin de pouvoir utiliser ce dernier,
placez tous les fichiers Java dans un sous-répertoire nommé
sources/einsc . Les fichiers générés lors de la
compilation seront placés dans un sous-répertoire nommé
classes (ne placez aucun fichier important dans ce
répertoire car celui-ci est détruit par la commande gmake
clean ). Finallement vous devez éditer votre
Makefile afin d'y entrer votre numéro de groupe. Si
vous êtes dans le groupe 5, la première definition de variable
dans le Makefile devrait être comme suit (n'oubliez
pas d'ajouter un zéro initial si votre numéro de groupe est
inférieur à 10) :
GROUP_NUMBER = 05
Maintenant vous pouvez redéfinir les permissions du répertoire
de votre projet de telle façon que seul vous et les membres de
votre groupe puissent accéder aux fichiers qu'il contient. Vous
pouvez faire cela grâce au Makefile avec la
commande qui suit :
gmake fix-permissions
Afin de garantir que chaque nouveau fichier que vous créez dans
le repértoire du projet soit accessible par tous les membres du
groupe, il vous faut exécutez la commande suivante :
chmod g+s <projectdir>
Chaque fois que vous commencer à travailler sur le projet, il
est recommandé de démarrer un nouveau terminal et d'exécuter la
commande umask 007 . Les éditeurs et
compilateurs devraient ensuite être lancés à partir de ce
terminal. Ceci vous garantit que tous les fichiers que vous
créez ne sont accessibles que par les membres de votre groupe et
personne d'autre.
Si vous ne voulez pas utiliser le makefile pour définir les
permissions, vous pouvez directement utiliser les commandes unix
correspondantes. Le nom de votre groupe unix est
compil xx où xx est un nombre à deux
chiffres correspondant à votre numéro de groupe. Utilisez la
commande unix groups pour afficher tous les groupes
unix dont vous êtes membre.
Makefile : L'utilisation de ce
Makefile est décrite ci-dessous.
Position.java : Cette classe
fournit des méthodes pour encoder la position dans un fichier
source (ligne & colonne) à l'aide d'un seul entier.
Report.java : Cette classes
fournit des services pour signaler des
erreurs.
Tokens-partial.java (à
renommer en Tokens.java) : Cette interface
définit toutes les classes de lexèmes. Pour l'instant elle n'en
contient que quelques unes. A vous de la compléter.
Scanner-partial.java (à
renommer en Scanner.java) : Cette classe
implante l'analyseur lexical. Une grande partie du code
manque. A vous de l'écrire.
ScannerTest.java : Cette
classe permet de tester votre analyseur
lexical.
Attention, ce Makefile nécessite GNU
Make . Vous devez donc utiliser la commande
gmake au lieu de make . Ci-dessous, la
liste des différentes commandes qu'il fournit :
gmake (ou gmake all ) : compile
tous les fichiers modifiés depuis la dernière compilation
réussie et place les fichiers .class dans le
sous-répertoire ./classes qui est créé si
nécessaire.
gmake force : compile de tous les fichiers (y
compris ceux qui n'ont pas changés depuis la dernière
compilation réussie). Cette opération est parfois nécessaire. Si
vous avez des erreurs très étranges à l'exécution, pensez à
faire un gmake force , c'est souvent suffisant pour
résoudre le problèmes ou du moins pour localiser sa cause.
gmake clean : efface tous les fichiers générés
et suprime le répertoire ./classes (ne placez
jamais rien d'important dans ce répertoire).
gmake distclean : même chose que gmake
clean , mais en plus suprime tous les fichiers
*.class *~ core dans le
répertoire courant et tous ses sous-répertoires.
gmake grep ARGS=<regexp> : recherche
l'expression régulière regexp dans tous les
fichiers sources.
gmake wc : compte le nombre de lignes, mots et
caractères dans tous les fichiers sources.
gmake fix-permissions : définit les
permissions des fichiers dans le repértoire de votre projet de
telle façon que seul vous et les membres de votre groupe
puissent y accéder.
|