Partie 1
A présenter lors de la séance du 14 novembre 2003.
Le but de cette partie est d'écrire un analyseur lexical pour le
langage misc. Cet analyseur doit pouvoir déterminer si un fichier
donné est conforme à la grammaire lexicale de
misc. 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
misc 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 a 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 ";" } Body EOF_CH.
Le caractère EOF_CH
devient ainsi un 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 pseud-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 miscc.ScannerTest fichier-source.misc
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/miscc
. 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 editer 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.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.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.