Logo EPFL
LAMP
Ecole Polytechnique Fédérale de Lausanne
Compilation 2004/2005
French only
Partie 4 : Analyse des noms et des types

A présenter lors de la séance du 14 janvier 2005.

  But

Le but de cette partie est d'ajouter la phase d'analyse sémantique au compilateur Eins. 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 Eins, 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 contenant 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,
  • la sorte du symbole (variable, function, classe ou champ),
  • le type du symbole.

L'analyse des noms vérifie également que les identificateurs utilisés sont préalablement définis, et qu'en tout point du programme, il n'existe pas deux identificateurs visibles ayant le même nom.

Le langage Eins dispose de deux classes prédéfinies, qui doivent faire partie de la portée principale du programme. Il y a la classe Unit qui sert de type aux expressions qui agissent par effet de bord mais ne retournent rien d'intéressant, et la classe List qui représente les listes d'entiers. Voici comment elles auraient pu être définies par l'utilisateur:

   class Unit {}
          
   class List {
     val head: Int;
     val tail: Int List;
   }
        

Le langage Eins dispose aussi de quatre fonctions d'entrée-sortie prédéfinies, qui doivent, tout comme les classes prédéfinies, faire partie de la portée principale du programme. Ces fonctions ont le profil suivant :

   def printInt(i: Int): Unit;
   def readInt(): Int;
   def printChar(c: Int): Unit;
   def readChar(): Int;
        
Pour cette partie, ces fonctions seront traitées exactement comme des fonctions normales, si l'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.

Il y a deux notions de types, ceux qui peuvent être écrits par l'utilisateur et qu'on nomme types externes, et ceux qui sont utilisés par le compilateur pour faire son analyse, qu'on appelle types internes.

Les types externes de Eins sont :

  • le type primitif Int,
  • les types d'objets, prédéfinis comme Unit, List, ou définis par l'utilisateur,
  • les types fonctionnels, qui sont de la forme (type, ...) => type,
Les types internes de Eins sont :
  • tous les types externes, plus
  • le type Null, et
  • les types de classes.

Ces types sont liés entre eux par une relation de sous-typage. L'interprétation de cette relation est qu'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 alors T1 <: T2.

Le type Null est le type de la valeur null qui représente une référence nulle d'objet. Comme null peut être utilisé partout où l'on attend un objet, son type, Null, doit être un sous-type de tous les types d'objets.

Certains nœuds ne possèdent pas de type, comme par exemple ceux correspondant aux définitions de fonction ou aux instructions d'un bloc. 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 (à ne pas confondre avec le pseudo-type BAD_TYPE décrit ci-dessous).

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éfini dans la classe Type. Il peut aussi arriver que, malgré une erreur, on puisse donner un type à un nœud. Par exemple, on pourra toujours donner le type Int à l'expression e1 + e2, même si l'une des deux opérandes est mal typée.

Pour cette partie, nous vous fournissons les fichiers suivants :

  • Type-partial.java (à renommer en Type.java) contient la définition de la classe Type et de ses sous-classes, qui modélisent les types internes de Eins. Ces classes définissent des méthodes permettant de comparer deux types et de calculer le maximum de deux types. Sont à compléter la méthode isSubType, modélisant la relation de sous-typage, et les sous-classes FunType, modélisant les types fonctionnels, ObjType, modélisant les types d'objets, et ClassType, modélisant les types de classes.
  • Symbol.java contient la classe Symbol, qui modélise les symboles.
  • Scope.java contient la classe Scope, qui modélise les portées.
  • Analyzer-partial.java (à renommer en Analyzer.java) contient le visiteur d'analyse sémantique. Il s'agit du principal fichier à compléter.
  • AnalyzerTest.java contient le programme de test.