Logo EPFL
Ecole Polytechnique Fédérale de Lausanne
Compilation 2003/2004: Projet
français
Partie 4

A présenter lors de la séance du 9 janvier 2004.

  But

Le but de cette partie est d'ajouter la phase d'analyse sémantique au compilateur misc. Cette analyse sémantique effectue deux tâches: l'analyse des noms et l'analyse des types. Au même titre que l'analyse lexicale se basait sur la grammaire lexicale et l'analyse syntaxique sur la grammaire syntaxique, l'analyse sémantique se base sur la grammaire attribuée de misc, qui a été vue au cours.

L'analyse des noms consiste à associer à chaque identificateur du programme sa définition, et donc à vérifier que les identificateurs utilisés dans le programme sont effectivement définis.

L'analyse des types consiste à vérifier que les types des différentes parties du programme sont corrects.

Une fois l'analyse sémantique effectuée avec succès, la production de code pourra enfin commencer, car le programme sera garanti correct par rapport aux règles statiques du langage.

L'analyse des noms associe à chaque identificateur du programme sa définition. Pour ce faire, elle stocke directement dans l'arbre, dans chaque nœud contenant un identificateur, un symbole qui contient les informations relatives à l'identificateur. Cela signifie qu'il vous faudra ajouter un champ de type Symbol à ces classes de nœud.

Chaque symbole contient les informations suivantes :

  • la position à laquelle le symbole est déclaré,
  • le nom du symbole,
  • le type du symbole, et
  • si le symbole représente une fonction (déclarée par un def) ou une variable.

L'analyse des noms vérifie également que les identificateurs utilisés sont préalablement définis, et qu'un même identificateur n'est pas défini plus d'une fois dans une portée. En misc, les portées suivantes existent :

  • la portée globale du programme,
  • une portée pour les fonctions,
  • une portée pour chaque liste d'arguments d'une fonction, et
  • une portée pour chaque bloc.

Le langage misc dispose de 4 fonctions d'entrée-sortie prédéfinies, qui doivent faire partie de la portée principale du programme. Ces fonctions ont le profil suivant :

  1. printInt(Int): Unit
  2. printChar(Int): Int Unit
  3. readInt(): Int
  4. readChar(): Int
Pour cette partie, ces fonctions seront traitées exactement comme des fonctions normales, si on excepte bien entendu le fait qu'elles sont automatiquement définies. Ce n'est qu'au moment de la production de code qu'elles devront être traitées différemment.

L'analyse des types calcule le type des différentes parties du programme et vérifie qu'ils sont corrects.

Les types existant en misc sont :

  • le type Any,
  • le type Unit,
  • le type Int,
  • les types liste: List[type],
  • les types fonctionnels: (type, ...) => type,
  • le type bottom (pour lequel il n'existe pas de syntaxe concrète).
Ces types sont liés entre eux par une relation de sous-typage. Pour mémoire, un type t1 est un sous-type d'un autre type t2 si des valeurs de type t1 peuvent être utilisées partout où des valeurs de type t2 sont attendues. On écrit t1 <: t2 si t1 est un sous-type de t2.

Le type bottom est utile pour donner un type à la liste vide, notée List() en misc. En effet, la liste vide peut être une liste de n'importe quoi, le type de ses éléments n'étant pas connu, et son type doit donc refléter cette propriété. En lui attribuant le type List[bottom], étant donné la relation de sous-typage décrite plus haut, on obtient le résultat désiré. Vous pouvez, pour vous en convaincre, vérifier que le type de la liste vide est un sous-type de List[List[int]] (par exemple), selon la relation de sous-typage énoncée plus haut.

Certains nœuds ne possèdent pas de type, comme par exemple ceux correspondant aux définitions de fonction ou aux instructions while. A ces nœuds, il faut attribuer le pseudo-type NO_TYPE (défini dans la classe Type) qui représente l'absence de type.

Lors des phases précédentes, vous étiez autorisé à vous arrêter après la première erreur. Ceci n'est pas le cas pour cette phase; cette fois-ci, toutes les erreurs doivent être signalées. Il peut arriver qu'à cause d'une erreur, il soit impossible de calculer le type d'un nœud. Dans ce cas, vous pouvez lui attribuer le type BAD_TYPE définit dans la classe Type.

Pour cette partie, nous vous fournissons les fichiers suivants :

  • Type.java contient la définition de la classe Type et de ses sous-classes, qui modélisent les types misc. Ces classes définissent, entre autres, des méthodes permettant de comparer et de calculer le maximum de deux types. Les sous-classes FunType, modélisant les types fonctionnels, et ListType, modélisant les types listes, sont à compléter.
  • Symbol.java contient la classe Symbol, qui modélise les symboles.
  • Scope.java contient la classe Scope, qui modélise les portées.
  • Analyzer.java contient le visiteur d'analyse sémantique. Il s'agit du principal fichier à compléter.
  • AnalyzerTest.java contient le programme de test.
Valid XHTML 1.0! Valid CSS 1.0!