Author: Christian.Queinnec@lip6.fr

## Calculatrice à mémoire

On reprend maintenant l'exemple de la calculatrice décrite dans le chapitre précédent mais en la dotant cette fois d'une interface utilisateur rendant notre programme propre à être utilisé comme une calculette de bureau. Cette boucle permet d'entrer les opérations directement et de voir s'afficher les résultats sans avoir à appliquer explicitement la fonction de transition pour chaque pression d'une touche.
Nous ajoutons quatre nouvelles touches : C qui remet à 0 l'affichage, M qui met en mémoire un résultat, m qui restitue cette mémoire et OFF qui << éteint >> la calculatrice. Ceci correspond au type suivant :

    # type touche = Plus | Moins | Fois | Par | Egal | Chiffre of int
                 | MemoireIn | MemoireOut | Clear | Off ;;

Il faut alors définir une fonction de traduction des caractères frappés au clavier en des valeurs de type touche. L'exception `Touche_non_valide` permet de traiter le cas des caractères ne représentant pas une touche de la calculette. La fonction `code` du module `Char` traduit un caractère en son code ASCII.

    # exception Touche_non_valide ;;
    exception Touche_non_valide
    # let traduction c = match c with
         '+' -> Plus
       | '-' -> Moins
       | '*' -> Fois
       | '/' -> Par
       | '=' -> Egal
       | 'C' | 'c' -> Clear
       | 'M' -> MemoireIn
       | 'm' -> MemoireOut
       | 'o' | 'O' -> Off
       | '0'..'9' as c -> Chiffre ((Char.code c) - (Char.code '0'))
       | _ -> raise Touche_non_valide ;;
    val traduction : char -> touche = <fun>

Dans un style impératif, la fonction de transition ne calculera plus un nouvel état, mais modifiera physiquement l'état de la calculette. Il faut donc redéfinir le type `etat` de façon à ce que ses champs soient modifiables. Enfin, on définit l'exception `Touche_off` pour traiter l'activation de la touche OFF.

    # type etat = {
      mutable dce : int;     (* dernier calcul effectué      *)
      mutable dta : bool;    (* vrai si touche = chiffre     *)
      mutable doa : touche;  (* dernier opérateur actionné   *)
      mutable vaf : int;     (* valeur affichée              *)
      mutable mem : int      (* mémoire de la calculette     *) };;

    # exception Touche_off ;;
    exception Touche_off
    # let transition et tou = match tou with
         Clear -> et.vaf <- 0
       | Chiffre n  ->  et.vaf <- ( if et.dta then et.vaf*10+n else n );
                        et.dta <- true
       | MemoireIn  ->  et.dta <- false ;
                        et.mem <- et.vaf
       | MemoireOut ->  et.dta <- false ;
                        et.vaf <- et.mem
       | Off -> raise Touche_off
       |  _  -> let dce = match et.doa with
                             Plus  -> et.dce + et.vaf
                           | Moins -> et.dce - et.vaf
                           | Fois  -> et.dce * et.vaf
                           | Par   -> et.dce / et.vaf
                           | Egal  -> et.vaf
                           | _ -> failwith "transition: filtre impossible"
                       in
                  et.dce <- dce ;
                  et.dta <- false ;
                  et.doa <- tou ;
                  et.vaf <- et.dce;;
    val transition : etat -> touche -> unit = <fun>

On définit la fonction `go` qui lance la calculette. Sa valeur de retour est `()` puisque ne nous importe que l'effet produit par l'exécution sur l'environnement (entrée/sortie, modification de l'état). Son argument est également la constante `()` puisque la calculette est autonome (elle définit elle-même son état initial) et interactive (les données du calcul sont rentrées au clavier au fur et à mesure des besoins). Les transitions s'effectuent dans une boucle infinie (while true do) dont on pourra sortir par l'exception `Touche_off`.

    # let go () =
       let etat = { dce=0; dta=false; doa=Egal; vaf=0; mem=0 }
       in try
            while true do
              try
                let entree = traduction (input_char stdin)
                in transition etat entree ;
                   print_newline () ;
                   print_string "affichage : " ;
                   print_int etat.vaf ;
                   print_newline ()
              with
                Touche_non_valide -> ()  (* pas d'effet *)
            done
          with
            Touche_off -> () ;;
    val go : unit -> unit = <fun>

Notons que l'état initial doit être soit passé en paramètre, soit déclaré localement à l'intérieur de la fonction `go` pour qu'il soit initialisé à chaque application de cette fonction. Si nous avions utilisé une valeur `etat_initial` comme dans le programme fonctionnel, la calculatrice repartirait dans le même état que celui qu'elle avait lorsqu'elle a été coupée. Il aurait été alors difficile d'utiliser deux calculatrices dans le même programme.