:: Enseignements :: ESIPE :: E4INFO :: 2011-2012 :: Génération de code ::
[LOGO]

Projet - Partie 1


Première partie du projet, modification de la grammaire, implantation d'un vérificateur de type.

Avant propos

Le but de ce projet est à la fin d'avoir écrit un interpreteur ainsi qu'un compilateur vers le bytecode Java pour un petit language simple. Cette année, le langage s'appele ir2012 pour faire original. Sa syntaxe est plus proche de celle de Python ou Ruby que de celle du C ou de Java. Sa sémantique, elle plus proche d'un C--, donc c'est un langage statiquement typé (3 types possibles bool, int et string), des fonctions, des tests, des boucles, des variables. Le langage utilise deux astuces pour que sa syntaxe ressemble à du Python, comme avec les anciens C (K&R) si l'on ne declare pas le type d'une variable, d'un paramètre ou d'un type de retour, celui-ci sera implicitement déclaré comme un entier. De plus, au lieu d'utiliser l'indentation pour trouver les fins de bloc d'instruction comme en Python car cela requiert soit beaucoup de larmes soit d'écrire son parseur à la main, le language considèrera les instructions return, break et continue comme étant des fins de bloc d'instructions implicites et le saut d'une ligne vide comme étant la fin d'une fonction (et oui cela veux dire que si l'on saute une ligne pour aérer le programme ne compilera plus, comme cela tout le monde va être obligé d'écrire le même code, au saut de ligne près).
Un script est un ensemble de fonctions suivi d'un ensemble d'instructions. La première intruction de l'ensemble d'instructions sera la première instruction exécuté lors du lancement du script. Contrairement au C, l'ordre de déclaration des fonctions n'a pas d'importance (comme en Java).
Par exemple, un hello world s'écrit comme ceci:
      print "Hello World !"
    
Une fonction est déclarée en utilisant le mot-clé def suivi de son nom, des paramètres entre parenthèse puis d'un bloc d'instruction qui doit commencer par le caractère ':' et se finir soit par end soit par return, break et continue soit par un aut d'une ligne vide.
      def fibo(n):
        if n < 2:
          return 1    
        return fibo(n - 1) + fibo(n - 2)
        
      print fibo(7)
    

A l'intérieur d'un bloc on écrit des instructions, celle-ci se termine soit par un retour à la ligne soit par un point virgule (';') si l'on veut mettre plusieurs intructions sur une ligne.
Il existe 6 instructions :
  1. La déclaration de variable qui se fait en utilisant le mot clé var.
              var a = 7
            
    Une variable est forcément initialisée lors de sa déclaration (et en passant null n'existe pas).
    Il est possible de déclarer le type de la variable (implicitement le type est int) en indiquant le type entre var et le nom de la variable.
              var int a = 7
            
  2. L'affectation d'une variable
              a = 42
            
    dans ce cas, la variable devra avoir été déclarée au préalable (et avec le bon type). Noté que l'assignation est une instruction donc contrairement au C, ce code n'est pas valide a = b = c .
  3. Le test if ... elif ... else. elif est l'équivalent d'un 'else if' comme en Python (notez que elif s'appele elseif en PHP et elsif en Ruby.
            if value < 10:
              print "small"
            elif value < 20:
              print "medium"
            else:
              print "large"
            end
            
    Notez de plus, que dans l'exemple si au lieu de print "large" l'instruction était un return, break ou continue, le end pour finir le bloc aurait été superflue.
  4. La boucle for qui est composé de 3 partie optionelle séparée par des virgules (les virgules ne sont pas optionelles !) suivi d'un bloc d'instructions. La première partie est une déclaration ou une affectation. La seconde partie est une expression booléen, la boucle torunera tant que l'expression sera vraie. La troisième partie est une affectation ou une expression. La semantique de la boucle est la même que celle du C.
            var sum = 0
            for var i = 0, i < n, i = i + 1:
              sum = sum + i
            end
            
    De plus, à l'intérieur du corps de la boucle, l'instruction break permet de sortir de la boucle (contrairement à Java, il n'est pas possible d'utiliser des labels pour les boucles) et l'instruction continue permet de sauter la fin du corps de la boucle pour atterir juste avant l'exécution de la troisième partie de la boucle.
  5. Les appels de fonctions. Dans ce cas, la valeur de retour de la fonction sera ignoré.
  6. La fonction built-in print qui permet d'afficher n'importe quelle expression sur la console.

Les expressions sont soit des constantes (true, false, 42, "hello"), soit des variables (a, b, foo), soit des expressions unaire (+ et -), soit des expressions binaires de calcul (+, -, *, /, %), soit des tests d'égalités (== et !=), soit des opérateurs de comparaison (<=, <, >, >=), soit des appels de fonction, soit les 3 fonctions built-in scan_bool, scan_int et scan_string.
Les expressions unaires, les expressions binaires de calcul et les opérateurs de comparaison ne s'appliquent que sur les entiers. Les opérateurs == et != requiert que les deux opérandes est le même type.

Le fichier ZIP contient tout ce qu'il faut pour démarrer: ir2012-td3.zip, il suffit de le dézipper dans votre répertoire workspace de eclipse puis de créer un nouveau projet nommé ir2012, dans ce cas eclipse verra qu'il existeb déjà :)
Le code contenu servira de base pour les exercices 1 & 2, le fichier ant permettant d'utiliser Tatoo est rangé dans le répertoire lab1 ainsi que la description de la grammaire du langage ir2012.ebnf. Les sources sont stockés dans lab1/src et les sources générées dans lab1/gen-src. Un petit ensemble d'exemples est fourni dans le répertoire samples.
De plus ain de vous aidez, il existe dans le répertoire lib un jar exécutable nommé typechecker-slim.jar qui implante déjà un typechecker. Cela vous permettra de tester la sortie de votre programme par rapport à la sortie de ce jar exécutable. Pour exécuter le jar: java -jar lib/typechecker-slim.jar samples/mon_super_samples.ir2012

Exercice 1 - Modification de la grammaire

  1. Tester que la grammaire actuelle permet de déclarer une variable et d'appeler print sur la variable. Pour cela, executer la commande java -cp lab1/classes:lib/tatoo-runtime.jar fr.umlv.ir2012.GrammarTester et écriver votre script sur l'entrée standard en terminant par un Ctrl-D (CTRL-Z sous Windows). Vous pouvez aussi écrire votre script dans un fichier texte et passer celui-ci en paramètre de la même commande.
  2. Modifier l'expression regulière correspondant au nom des variables (et des méthodes) pour accepter autre chose que 'a'. Disons, qu'un identifiants doit commencer par une lettre et être suivi d'une lettre ou d'un chiffre et que '_' est considéré comme une lettre valide. Autre précisions, on accepte les majuscules et les minuscules.
  3. Vérifier si les priorités/associativités des opérateurs sont les bonnes. Indice, regarder 1 + 2 * 4.
  4. Le langage permet de nommer les paramètres lors d'un appel de fonction, écrivez un exemple d'appel de fonction avec des paramètres nommés.
  5. Un Mogwai a tapé sur mon clavier et supprimé les productions correspondant à l'instruction for de la grammaire, réparez ces bétises en ajoutant les productions pour que les exemples marchent.

Exercice 2 - Implantation d'un type-checker

Le but est d'écrire un visiteur qui va vérifier si le code est bien typé, que les variables sot bien déclarés etc. Concrètement, cela revient fournir une implantation à toute les méthodes visit du visiteur nommé TypeChecker (là où il y a des TODOs) mais avant il faut comprendre un peu le code et les classes annexes qui vous ont été founies.
Pour rendre la chose plus fun, j'ai enlevé les commentaires, à vous de les ajouter.

  1. A quoi sert la classe Env ? Noté que vous avez le doit de faire un CTRL+SHIFT+G sur le nom de la classe ou sur son constructeur pour voir là où elle est utilisée.
    Une fois que vous aurez trouver à quoi elle sert, ajouter un commentaire de documentation indiquant juste à quoi elle sert.
  2. A quoi servent les classes Symbol et SymbolTable ?
    De la même façon, ajouter la documentation qu'il convient. Rappel, on écrit toujours dans le commentaire de doc de la classe à quoi elle sert, quelle est sont rôle et des les commentaires de doc des méthodes comment on utilise les méthodes.
  3. Dans la classe TypeChecker, à quoi sert la méthode createFunctionNameToFunctionMap ?
    Rappel 2: on ne veut pas savoir ce qu'elle fait mais à quoi elle sert.
  4. A quoi sert le paramètre node de la méthode reportError ?
  5. A quoi sert la méthode dispatch ?
Maintenant, vous devriez avoir une idée plus précise de la façon dont la classe TypeChecker doit être implantée.