La classe et l'objet.
Cette feuille en Postscript

1  Encore les quatre opérations

On enrichit les expressions arithmétiques de la semaine dernière par une construction de liaison.
SE eof
EE + E          EE - E          EE * E          EE / E          Eint          E( E )
Elet id = E in E          Eid
  1. Modifier la grammaire de l'instituteur (grammaire non-ambigüe usuelle des expressions arithmétiques, avec les parenthèses) pour tenir compte des nouvelles constructions.
    SE eof
    EP          EE + P          EE - P
    PF          PP * F          PP / F
    Fint          F( E )
    On se posera au préalable la question de savoir comment on doit comprendre l'expression let x = 1 in x + x.

  2. Le document joint donne le code de la classe Instit qui est essentiellement un vérificateur de la grammaire de l'instituteur.

    Discuter l'ajout du modificateur de visibilité private aux champs et méthodes de la class Instit.

    Discuter ensuite le regroupement de trois classes Token, Lexer et Instit en un package de nom expr.

  3. Modifier le vérificateur pour reconnaître les nouvelles constructions. On supposera que le lexeur (classe Lexer) est déjà modifier et fournit les nouveaux lexèmes (classe Token) de natures Token.LET, Token.EQ, Token.IN et Token.ID.
Solution.

2  Extension par héritage

Le but de cet exercice est de construite le nouveau vérificateur de l'instituteur par héritage de l'ancien. On commence par se poser quelques question sur l'héritage lui-même. Soit une petite classe :
class A {
  
void f() { System.out.println("Je suis f de A") ; }
  
void g() { f() ; }
}
  1. Extension : Ce code compile-t-il, si oui qu'affiche-t-il ?
    class B extends A {
     
    void h() { g() ; }

     
    public static void main (String [] argv) {
       
    B b = new B () ;
       b.h() ;
     }
    }
  2. Redefinition : Même question.
    class B extends A {
     
    void f() { System.out.println("Je suis f de B") ; }

     
    public static void main (String [] argv) {
       
    B b = new B () ;
       b.g() ;
     }
    }
  3. Conversions entre classes : On pensera bien que les objets possèdent une classe exacte qui ne change jamais, mais que le compilateur ne connaît pas forcément cette classe exacte.
    class B extends A {
     
    void f() { System.out.println("Je suis f de B") ; }
     
    void h() { g() ; }
     
    public static void main (String [] argv) {
       
    A a = new B () ;
       a.g() ; a.h() ;
       
    B b = a ;
       b.g() ; b.h() ;
     }
    }
  4. Construire le nouveau vérificateur par héritage de l'ancien. Rediscuter rapidement l'usage des modificateurs de visibilité.
Solution.

3  Les nouveaux habits de notre vielle amie

Re-voici donc notre amie la classe des listes, mais cette fois il s'agit de listes dites d'association.
class List {
  
private String var ;
  
private int val ;
  
private List next ;

  
List (String var, int val, List next) {
    
this.var = var ; this.val = val ; this.next = next ;
  }
}
Les listes d'association offrent une unique fonctionnalité : on peut récupérer la valeur (type int) associée à une variable (type String) par une méthode assoc.
  1. Pouquoi les champs sont ils tous privés ?
  2. En supposant la méthode dynamique écrite, coder la méthode statique.
  3. En supposant la méthode statique écrite, coder la méthode dynamique.
  4. Finalement, la méthode assoc doit elle être statique ou dynamique ? Coder la bonne méthode.
  5. Pourquoi la méthode assoc termine-t-elle toujours.
  6. En supposant que les champs de List ne sont plus privés, montrer que la terminaison de assoc n'est plus garantie.
Solution.

4  Environnements

On souhaite maintenant mieux séparer interface et implémentation des environnements. Autrement dit les listes d'association sont une implémentation possible des environnements. L'interface des environnements est définie, justememt par une interface de Java (voir la PC 1)
interface Env {
  
void add(String v, int i) ; // Ajouter la liaison vi.
  
int get(String v)         ; // Renvoie la valeur associée à v.
}
  1. Un environnement est donc un objet d'une classe qui implémente l'interface Env. Comment sera nécessairement représenté l'environnement vide ?
  2. Coder une classe des environnements en utilisant la classe des listes d'association.
  3. Identifier un problème de coût et dire comment le résoudre.
Solution.

5  Arbre de syntaxe abstraite

On propose un codage objet des arbres de syntaxe abstraite, ce codage repose sur la définition d'une classe abstraite :
abstract class Expr {
  
abstract int eval(Env e) ;
}
  1. Peut-on créer des objets de la classe Expr ? À quoi peut elle bien servir ? Quand nous sommes nous déjà servi d'une classe abstraite ?

  2. Commençons par supposer l'existence de classes bien nommés, Mul, Div, etc. et munies de constructeurs raisonnables. Écrire la méthode parseP qui cette fois renvoie l'arbre de syntaxe abstraite correspondant à l'entrée analysée.

  3. Écrire les classes Int, Id et Let comme des classes héritant de Expr. On leur donnera une méthode toString dont on précisera si elle est une extension ou une redéfinition. On s'attachera à bien détailller leur méthode eval après avoir examiné le cas de let x = 1 in (let x = x+x in x+x) + x.

  4. Ou pourrait construire directement les classes des opérateurs binaires par héritage de Expr, mais on choisit de définir une classe intermédiaire :
    abstract class Binop extends Expr {
      
    Expr arg1, arg2 ;

      
    Binop(Expr arg1, Expr arg2) {
        
    this.arg1 = arg1 ;
        
    this.arg2 = arg2 ;
      }

      
    abstract String getOp() ;

      
    public String toString() {
        
    return getOp() + "(" + arg1 + ", " + arg2 + ")" ;
      }
    }
    Ecrire les classes Add et Sub. Comment justifier la classe intermédiaire Binop ?

  5. Comparer interfaces et classes abstraites.
Solution.


Ce document a été traduit de LATEX par HEVEA et HACHA.