:: Enseignements :: Master :: M2 :: 2013-2014 :: Machine Virtuelle (et bazard autour ...) ::
[LOGO]

Lab 1 - AST walker


Le but de ce premier lab est d'implanter un interpréteur pour la langage Talk en parcourant l'arbre de syntax abstrait (AST).
Pour préparer ce premier lab, deux exercice d'introduction vont vous permettre de comprendre le langage Talk qu'il faut implanter ainsi pour la syntaxe des lambdas introduit dans la version 8 de Java.
La préparation du lab est à uploader sur la plateforme de elearning: Lab 1.

Exercice 1 - Le langage Talk (Préparation)

Le but de ce premier exercice est de comprendre comment fonctionne le langage Talk.
Créer un fichier pour chaque exemple demandé, cela vous permettra d'avoir des tests pour tester votre code que vous écrirez dans la suite de ce lab.

  1. Ecrire un code permettant d'additionner les valeurs 2 et 3.
  2. A quoi correspond l'opération '!' du language Talk ?
  3. Ecrire un code permettant de declarer une variable 'a' initialisée avec la valeur du produit de 3 par 5 puis utiliser la valeur de la variable 'a' comme valeur de retour du script.
  4. Ecrire une lambda qui prend une valeur en paramètre et renvoie la valeur de celle-ci. Stocker la valeur correspondant à la lambda dans une variable 'identity' puis appeler la lambda avec la valeur 42.
  5. Déclarer une variable 'a' initilisée avec la valeur 12, créer un lambda que l'on stockera dans une variable 'add' qui prend un paramètre 'x' et renvoie la somme entre la valeur de 'x' et la valeur de 'a' (si vous préférer, la lambda à un paramètre normal 'x' et un paramètre lié (bound) 'a'. Appeler la lambda avec la valeur 21.

Exercice 2 - Les lambdas de Java 8 (Préparation)

Un petit exercice pour comprendre comment marche les lambdas en Java.
Voilà une vidéo de la présentation que j'ai fait pour un Java User Group: Les lambdas en Java

Comme cet exercice et les exercices suivants requiert Java 8, il vous faut utiliser le JDK 8 ainsi qu'un eclipse compatible avec le jdk8, la page de configuration d'Eclipse. Et si vous voulez utiliser un autre IDE.

  1. On considère le suivant qui calcul le minimum des valeurs d'un tableau
              public static int min(int[] values) {
                if (values.length == 0) {
                  throw new IllegalArgumentException();
                }
                int min = values[0];
                for(int i = 1; i < values.length; i++) {
                  min = Math.min(min, values[i]);
                }
                return min;
              }
             
    Ecrire le code permettant de calculer le maximum.
  2. Bien sûr, faire du copier/coller n'est pas la bonne, idée, il vaut mieux essayer de partager le code.
    Transformer la méthode min en une méthode reduce qui prend en second paramètre (en plus du tableau d'entier donc), une réference sur une interface java.util.function.IntBinaryOperator et remplacer l'appel à Math.min par l'appel à la méthode de l'interface.
    Puis créer un méthode min qui appel la méthode reduce en envoyant en second paramètre une classe anonyme, le corps de la méthode de la classe anonyme appelera Math.min.
    Tutorial sur les classes anonymes
  3. Transformer le code de la méthode min pour utiliser une lambda au lieu d'utiliser une classe anonyme.
    Tutorial sur les lambdas
  4. Finalement, il est plus simple ici d'utiliser une method reference qu'une lambda.
    Faite la transformation qui s'impose.
Pour vous aidez, vous pouvez utiliser les slides du cours de L3: function et lambdas.pdf

Exercice 3 - AST interpreter (Préparation)

Le but de cet exercice est d'écrire un interpreteur parcourant l'arbre correspondant à un script du language Talk et affichant sa valeur.
Archive au format ZIP contenant une base de code (le lexer, parseur et les classes définissant l'AST)
vm2014-lab1.zip

  1. On cherche à comprendre ce que fait la méthode main de la classe fr.umlv.talk.main.ASTInterpreterMain.
    Quelle est la respondsabilité de la classe fr.umlv.talk.ast.ASTBuilder.
    A quoi sert la méthode ASTBuilder.createScript ?
    Que fait ligne à ligne la méthode main de la classe ASTInterpreterMain ?
  2. Rappeler à quoi sert le Visitor Pattern.
    Regarder la classe fr.umlv.talk.ast.Visitor et fr.umlv.talk.astinterp.ASTInterpreter et indiquer les différences par rapport au Visitor Pattern classique.
  3. On va maintenant ajouter du code dans la classe fr.umlv.talk.astinterp.ASTInterpreter pour que l'interpretation du script Talk suivant affiche 3.
               3
             

    Que se passe t'il si l'on execute le main de la classe ASTInterpreterMain sur le script ci-dessus ?
    Quel code doit on écrire pour interpréter un block d'expression sachant qu'il faut appeler le visiteur sur l'ensemble des expressions du bloc.
    Quel code doit on écrire pour interpréter un litéral ?
    Faire en sorte que le script ci-dessus s'exécute correctement.
  4. Quelle valeur doit renvoyer l'exécution d'un script vide ?
    Comment faire pour déclarer une telle valeur dans ASTInterpreter ?
    Faire les changements qui s'imposent.
  5. On cherche maintenant à interpréter un script permetant de déclarer des variables et de les utiliser.
              a = 3
              a
             
    Que doit afficher l'interpretation du script suivant ?
    Que se passe t'il si on essaye d'exécuter l'interpreteur sur le script ?
  6. Sachant que l'on doit enregistrer la valeur d'une variable pour pouvoir obtenir la valeur d'une variable plus tard, on va utiiser la classe Env et sa table de hachage.
    Completer les méthodes lookup et register de la classe Env puis compléter le visiteur.
  7. Vérifier que les codes suivants ne sont pas valides.
    définition d'une variable déjà définie
               a = 3
               a = 4
             
    et utilisation de variable non définie.
               a
             
  8. Quelle est la méthode du visiteur qui permet d'interpreter le code suivant.
              -3
             

    Sachant que l'ensemble des opérations (unaire et binaire) du language Talk s'exécute sur des entiers, faire en sorte que l'on puisse interpréter le code ci-dessus.
  9. Faire en sorte que l'on puisse interpréter le code
              + 3
             
    Faire de même avec l'opérateur '!' (attention à implanter correctement son comportement).
  10. Les ifs ou les switchs sur des types de choses sont à bannir en programmation objet. Comment faire ici pour associer l'opération à effectuer avec l'opérateur sans if ni switch ?
    Implanter la solution retenue
  11. Implanter les opérations binaires (+, -, *, /, %, ==, !=, < <=, > et >=).
  12. On cherche maintenant à implanter la création de lambda, pour cela créer une classe Function qui représentera à l'exécution une lambda. Faire en sorte que l'on puisse représenter les lambdas suivantes
               lambda1 = []
               lambda2 = |x| [ x ]
               lambda3 = |x, y| [ x + y ]
             
    Ajouter un toString qui affiche les paramètres de la lambda, par exemple, [x, y] Function@0x123456.
    Ajouter ce qu'il faut au visiteur pour qui soit possible de créer une lambda.
  13. Enfin, on veut maintenant être capable d'appeler une instance de Function en lui passant des paramètres. On donc cherche à ce que le code suivant fonctionne
               f = |x| [ x + 1 ]
               f(3)
             

    L'appel de fonction peut se décomposer en deux parties, la première qui consiste à calculer les arguments et vérifier que l'on appel bien un object Function (donc le code 2() doit renvoyer une failure) puis on doit exécuter le code de la lambda correspondant en ayant correctement initialisé les paramètres avec les valeurs des arguments.
    En terme de programmation, le calcul de la valeur des arguments se fera dans le visiteur tandis que l'appel du code de la lambda se sera dans une méthode de la classe Function.

Exercice 4 - Pour aller plus loin

Le langage Talk ne supporte pas des fonctions nommées mais des lambdas (des fonctions anonymes) mais la plupart des lambdas vont être initialisées et stockées dans une variable locale, par exemple
          identity = |x| [ x ]
        
Dans ces cas, il est possible d'utiliser le nom de la variable local comme nom de la lambda pour founir plus d'information à l'utilisateur.
En terme d'implantation, l'idée est de propager le nom de la variable de gauche vers l'expression de droite, si l'expression est une lambda, on peut stocker le nom dans l'objet Function correspondant, sinon on considèrera que la lambda n'a pas de nom.

  1. Ecrire le code qui permet de propager le nom d'une variable vers la lambda.
  2. Vérifier que le nom foo est bien propagé dans ce cas
              foo = ([])
            
  3. Vérifier que la second lambda est bien nommée x.
               bar = |x| [x]
               bar([])