Compilation : Corrections et éclaircissements (partie 1)

Nous avons apporté quelques corrections à la donnée de la partie 1 du projet. Ce document énumère ces corrections et fournit également quelques éclaircissements supplémentaires.

Corrections

Grammaires lexicale et syntaxique

Dans la version originale de la donnée, nous utilisions les termes syntaxe lexicale et syntaxe pour désigner les deux grammaires décrites dans ce document. Le second terme peut prêter à confusion car il peut aussi bien désigner la première grammaire que la seconde. Nous avons donc remplacé ces deux termes par les termes grammaire lexicale et grammaire syntaxique respectivement.

Définition de token

La définitions de token dans la grammaire lexicale a été légérement augmentée afin de correspondre à la classe Tokens que nous vous avons fournie.

Gestion de la fin de fichier

Dans la version originale de la donnée, on indiquait que pour traiter la fin du fichier source, on modifiait la grammaire syntaxique comme suit :

Program        = { Declaration ";" } Expression EOF.

EOF            = EOF_CH

Ceci est incorrect car cela signifierait que EOF est un non-terminal de la grammaire syntaxique, ce qui est faux. Voilà comment il faut modifier la grammaire :

Program        = { Declaration ";" } Expression EOF_CH.

La grammaire syntaxique contient ainsi un terminal EOF_CH auquel correspond la classe de lexèmes EOF définie dans Tokens. On dit que 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 tout vrai lexème.

Eclaircissements

Méthodes nextToken et readToken

Dans la classe Scanner que nous vous avons fournie, une partie du travail de la méthode nextToken est délégué à la méthode readToken. Cette dernière étant privée (donc inaccessible depuis n'importe quelle autre classe), vous pouvez en faire ce que vous voulez et par exemple décider de la supprimer et d'implanter toute l'analyse dans nextToken.

Plus généralement, vous pouvez toujours supprimer ou modifier des méthodes privées que nous vous fournissons du moment que vous adaptez aussi le code des méthodes qui les utilisaient. Par contre, vous ne devriez pas toucher aux méthodes non privées car celles-ci risquent d'être utilisées par des classes que vous n'avez pas encore reçues. Bien sûr, vous pouvez aussi, si vous le désirez, ajouter de nouvelles méthodes aux classes que nous vous fournissons. Ces méthodes peuvent elles être aussi bien privées que publiques.

Méthodes nextCh

La classe Scanner définit deux méthodes nextCh. La première simplement appelle la seconde et retourne la valeur passée en argument. Sa seule utilité est de simplifier votre code. Par exemple, au lieu d'écrire

if (<cond>) {
    nextCh();
    return <token1>;
} else {
    return <token2>;
}
vous pouvez écrire
return <cond> ? nextCh(<token1>) : <token2>;

Méthodes fatal

Les méthodes fatal de la classe Global ne retournent jamais (elles lèvent toujours une exception pour stopper l'exécution), mais elles sont tout de même déclarées avec un type de retour de la classe Error. Cela permet de placer l'appel à l'intérieur d'une instruction throw :

throw global.fatal(pos, "erreur fatale");

Cela est parfois très utile. Par exemple lorsque l'on doit appeler cette méthode dans une fonction dont le type de retour n'est pas void. Dans ce cas, si on écrit un simple appel à la méthode fatal, le compilateur Java sera incapable de déterminer que cette méthode ne retourne jamais et il vous obligera de placer une instruction return quelque part après votre appel à fatal. L'utilisation du throw permet d'éviter cela.