Author: Christian.Queinnec@lip6.fr

## Exercices

### Piles en objet

On reprend l'exemple des piles vu précédemment à la sauce objet.

1. Définir une classe `intpile` en utilisant les listes d'Objective CAML implantant des listes d'entiers et disposant des méthodes `empile`, `depile`, `sommet` et `taille`.

       # exception PileVide

        class intpile () =
          object
            val p = ref ([] : int list)
            method empile i = p := i:: !p
            method depile () = if !p = [] then raise PileVide else p := List.tl !p
            method sommet () = if !p = [] then raise PileVide else List.hd !p
            method taille () = List.length !p
          end ;;
       exception PileVide
       class intpile :
         unit ->
         object
           val p : int list ref
           method depile : unit -> unit
           method empile : int -> unit
           method sommet : unit -> int
           method taille : unit -> int
         end

2. Créer une instance contenant 3 et 4 comme éléments de la pile.

       # let p = new intpile () ;;
       val p : intpile = <obj>
       # p#empile 3 ;;
       - : unit = ()
       # p#empile 4 ;;
       - : unit = ()

3. Définir une nouvelle classe `pile` contenant des éléments répondant à la méthode `print : unit -> unit`.

       # class pile () =
          object
            val p = ref ([] : <print : unit -> unit>  list)
            method empile i = p := i:: !p
            method depile () = if !p = [] then raise PileVide else p := List.tl !p
            method sommet () = if !p = [] then raise PileVide else List.hd !p
            method taille () = List.length !p
          end ;;
       class pile :
         unit ->
         object
           val p : < print : unit -> unit > list ref
           method depile : unit -> unit
           method empile : < print : unit -> unit > -> unit
           method sommet : unit -> < print : unit -> unit >
           method taille : unit -> int
         end

4. Définir une classe paramétrée `['a] pile` avec les mêmes méthodes.

       # class ['a] ppile () =
          object
            val p = ref ([] : 'a  list)
            method empile i = p := i:: !p
            method depile () = if !p = [] then raise PileVide else p := List.tl !p
            method sommet () = if !p = [] then raise PileVide else (List.hd !p)
            method taille () = List.length !p
          end ;;
       class ['a] ppile :
         unit ->
         object
           val p : 'a list ref
           method depile : unit -> unit
           method empile : 'a -> unit
           method sommet : unit -> 'a
           method taille : unit -> int
         end

5. Comparer les différentes classes de piles.

### Liaison retardée

Cet exercice illustre la notion de liaison retardée indépendamment de la notion de sous-typage.
Soit le programme donné par la suite :

1. Dessiner les relations entre classes
2. Tracer les différents envois de messages.
3. En supposant être dans un mode caractère sans écho, indiquer ce qu'affiche le programme.

    exception CrLf;;
    class lecture_chaine (m) =
      object (self)
      val msg = m
      val mutable res = ""

      method lire_char =
        let c = input_char stdin in
          if (c != '\n') then begin
            output_char stdout c; flush stdout
          end;
          String.make 1 c

      method  private lire_chaine_aux =
          while true do
            let s = self#lire_char in
              if s = "\n" then raise CrLf
              else res <- res ^ s;
          done

      method private lire_chaine_aux2 =
        let s = self#lire_char in
              if s = "\n" then raise CrLf
              else begin res <- res ^ s; self#lire_chaine_aux2 end

      method lire_chaine =
        try
          self#lire_chaine_aux
        with End_of_file  -> ()
          | CrLf -> ()

      method input = res <- ""; print_string msg; flush stdout;
                     self#lire_chaine

      method get = res
    end;;

    class lecture_mdp (m) =
      object (self)
      inherit lecture_chaine m
      method lire_char = let c  = input_char stdin in
                         if (c != '\n') then begin
                           output_char stdout '*'; flush stdout
                         end;

                         let s = " " in s.[0] <- c; s
    end;;

    let login = new lecture_chaine("Login : ");;
    let passwd = new lecture_mdp("Passwd : ");;
    login#input;;
    passwd#input;;
    print_string (login#get);;print_newline();;
    print_string (passwd#get);;print_newline();;

### Classes abstraites et évaluateur d'expressions

Cet exercice a pour but de montrer la factorisation de code réalisée par l'utilisation de classes abstraites.
Les expressions arithmétiques construites sont toutes des instances d'une sous-classe de la classe abstraite expr_ar.

1. Définir une classe abstraite expr_ar pour les expressions arithmétiques contenant deux méthodes abstraites : `eval` de type float et `print` de type unit qui respectivement évalue et affiche une expression arithmétique.

       # class virtual expr_ar () =
          object
            method virtual eval : unit -> float
            method virtual print : unit -> unit
          end ;;
       class virtual expr_ar :
         unit ->
         object
           method virtual eval : unit -> float
           method virtual print : unit -> unit
         end

2. Définir une classe concrète constante sous-classe de expr_ar.

       # class constante x =
          object
            inherit expr_ar ()
            val c = x
            method eval () = c
            method print () = print_float c
          end ;;
       class constante :
         float ->
         object
           val c : float
           method eval : unit -> float
           method print : unit -> unit
         end

       (* autre solution : *)

       # class const x =
          object
            inherit expr_ar ()
            method eval () = x
            method print () = print_float x
          end ;;
       class const :
         float -> object method eval : unit -> float method print : unit -> unit end

3. Définir une sous-classe abstraite bin_op de expr_ar implantant les méthodes `eval` et `print` en utilisant deux nouvelles méthodes abstraites `oper` de type (float * float) -> float ( utilisée par `eval`) et `symbole` de type string (utilisée par `print`).

       # class virtual bin_op g d  =
          object (this)
            inherit expr_ar ()
            val fg = g
            val fd = d
            method virtual symbole : string
            method virtual oper : float * float -> float
            method eval () =
              let x = fg#eval()
              and y = fd#eval() in
              this#oper(x,y)
            method print () =
              fg#print () ;
              print_string (this#symbole) ;
              fd#print ()
          end ;;
       class virtual bin_op :
         (< eval : unit -> float; print : unit -> 'b; .. > as 'a) ->
         (< eval : unit -> float; print : unit -> unit; .. > as 'c) ->
         object
           val fd : 'c
           val fg : 'a
           method eval : unit -> float
           method virtual oper : float * float -> float
           method print : unit -> unit
           method virtual symbole : string
         end

4. Définir les classes concrètes add et mul sous-classes de bin_op implantant chacune les méthodes `oper` et `symbole`.

       # class add x y =
          object
            inherit bin_op x y
            method symbole = "+"
            method oper(x,y) = x +. y
          end ;;
       class add :
         (< eval : unit -> float; print : unit -> 'b; .. > as 'a) ->
         (< eval : unit -> float; print : unit -> unit; .. > as 'c) ->
         object
           val fd : 'c
           val fg : 'a
           method eval : unit -> float
           method oper : float * float -> float
           method print : unit -> unit
           method symbole : string
         end

       # class mul x y =
          object
            inherit bin_op x y
            method symbole = "*"
            method oper(x,y) = x *. y
          end ;;
       class mul :
         (< eval : unit -> float; print : unit -> 'b; .. > as 'a) ->
         (< eval : unit -> float; print : unit -> unit; .. > as 'c) ->
         object
           val fd : 'c
           val fg : 'a
           method eval : unit -> float
           method oper : float * float -> float
           method print : unit -> unit
           method symbole : string
         end

5. Dessiner l'arbre d'héritage.
6. Écrire une fonction qui prenant une suite de `Genlex.token` construit un objet de type expr_ar.

       # open Genlex ;;
       # exception Found of expr_ar ;;
       exception Found of expr_ar

       # let rec create accu l =
          let r  = match Stream.next l with
              Float f -> new constante f
            | Int i -> ( new constante (float i) :> expr_ar)
            | Kwd k  ->
                let v1 = accu#sommet() in accu#depile();
                let v2 = accu#sommet() in accu#depile();
                ( match k with
                      "+" -> ( new add v2 v1 :> expr_ar)
                    | "*" -> (  new mul v2 v1 :> expr_ar)
                    | ";" -> raise (Found (accu#sommet()))
                    | _ -> failwith "aux : bas keyword"    )
            |  _ -> failwith "aux : bad case"
          in
          create (accu#empile (r :> expr_ar); accu) l ;;
       val create :
         < depile : unit -> 'a; empile : expr_ar -> 'b; sommet : unit -> expr_ar;
           .. > ->
         Genlex.token Stream.t -> 'c = <fun>

       # let gl = Genlex.make_lexer ["+"; "*"; ";"] ;;
       val gl : char Stream.t -> Genlex.token Stream.t = <fun>

       # let run () =
          let s = Stream.of_channel stdin in
          create (new ppile ()) (gl s) ;;
       val run : unit -> 'a = <fun>

7. Tester ce programme en lisant l'entrée standard, en utilisant l'analyseur lexical générique `Genlex`. On pourra entrer les expressions à évaluer sous forme post-fixée.

### Jeu de la vie en objet

On définit les 2 classes suivantes :

- `cell` : pour les cellules du monde, répondant à la méthode `isAlive : unit -> bool`
- `world` : ayant un tableau de `cell` et répondant aux messages :

      display : unit -> unit
      nextGen : unit -> unit
      setCell : int * int -> cell -> unit
      getCell : int * int -> cell

1. Écrire la classe `cell`.

       # class cell a =
          object
            val mutable v = (a : bool)
            method isAlive = v
          end ;;
       class cell : bool -> object val mutable v : bool method isAlive : bool end

2. Écrire une classe abstraite `absWorld` qui implante les méthodes `display`, `getCell` et `setCell` et laisse abstraite la méthode `nextGen`.

       # class virtual absWorld n m  =
          object(self)
            val mutable tcell = Array.create_matrix n m (new cell false)
            val maxx = n
            val maxy = m
            val mutable gen = 0
            method private dessine(c) =
             if c#isAlive then print_string "*"
             else print_string "."
            method display() =
              for i = 0 to (maxx-1) do
                for j=0 to (maxy -1) do
                  print_string " " ;
                  self#dessine(tcell.(i).(j))
                done ;
                print_newline()
              done
            method getCell(i,j) = tcell.(i).(j)
            method setCell(i,j,c) = tcell.(i).(j) <- c
            method getCells = tcell
          end ;;
       class virtual absWorld :
         int ->
         int ->
         object
           val mutable gen : int
           val maxx : int
           val maxy : int
           val mutable tcell : cell array array
           method private dessine : cell -> unit
           method display : unit -> unit
           method getCell : int * int -> cell
           method getCells : cell array array
           method setCell : int * int * cell -> unit
         end

3. Écrire une classe `world`, sous-classe de `absWorld`, qui implante la méthode `nextGen` selon les règles de croissance.

       # class world n m =
          object(self)
            inherit absWorld n m
            method neighbors(x,y) =
              let r = ref 0 in
              for i=x-1 to x+1 do
                let k = (i+maxx) mod maxx in
                for j=y-1 to y+1 do
                  let l = (j + maxy) mod maxy in
                    if tcell.(k).(l)#isAlive then incr r
                done
              done;
              if tcell.(x).(y)#isAlive then decr r ;
              !r

            method nextGen() =
              let w2 = new world maxx maxy in
              for i=0 to maxx-1 do
                for j=0 to maxy -1 do
                  let n = self#neighbors(i,j) in
                  if tcell.(i).(j)#isAlive
                  then (if (n = 2) || (n = 3) then w2#setCell(i,j,new cell true))
                  else (if n = 3 then w2#setCell(i,j,new cell true))
                done
              done ;
              tcell <- w2#getCells ;
              gen <- gen + 1
          end ;;
       class world :
         int ->
         int ->
         object
           val mutable gen : int
           val maxx : int
           val maxy : int
           val mutable tcell : cell array array
           method private dessine : cell -> unit
           method display : unit -> unit
           method getCell : int * int -> cell
           method getCells : cell array array
           method neighbors : int * int -> int
           method nextGen : unit -> unit
           method setCell : int * int * cell -> unit
         end

4. Écrire le programme principal qui crée un monde vide, lui ajoute certaines cellules, puis rentre dans une boucle d'interaction qui affiche le monde, attend une interaction, calcule ensuite la génération suivante et repart dans la boucle.

       # exception Fin;;
       exception Fin

       # let main () =
          let a = 10 and b = 12 in
          let w = new world a b in
          w#setCell(4,4,new cell true) ;
          w#setCell(4,5,new cell true) ;
          w#setCell(4,6,new cell true) ;
          try
            while true do
              w#display() ;
              if ((read_line()) = "F") then raise Fin else w#nextGen()
            done
          with Fin -> () ;;
       val main : unit -> unit = <fun>