Author: Christian.Queinnec@lip6.fr

## Exercises

The three proposed exercises manipulate file descriptors, processes, respectively pipes and signals. The first two exercises stem from Unix system programming. The Objective CAML code can be compared with the C code in the Unix or Linux distributions.

### Counting Words: the `wc` Command

We want to (re)program the Unix `wc` command, which counts the number of lines, words or characters contained in a text file. Words are separated by a space character, a tab, or a carriage return. We do not count the separators.

1. Write a first version (`wc1`) of the command, which only handles a single file. The name of the file is passed as an argument on the command line.
   On définit une liste de séparateurs ainsi que trois variables globales qui serviront aux différents comptages.

       # let seps = " \t" ;;
       val seps : string = " \t"
       # let nl = ref 0 ;;
       val nl : int ref = {contents=0}
       # let nw = ref 0 ;;
       val nw : int ref = {contents=0}
       # let nc = ref 0 ;;
       val nc : int ref = {contents=0}

   La fonction `counts` est chargée du comptage d'une ligne.

       # let counts s =
          let was_sep = ref true in
          let n = String.length s in
            for i=0 to n-1 do
              let is_sep = String.contains seps s.[i] in
              if is_sep && (not !was_sep) then incr nw ;
              was_sep := is_sep
            done ;
            if not !was_sep then incr nw ;
            nc := !nc+n+1;
            incr nl ;;
       val counts : string -> unit = <fun>

   La fonction `count` itère la fonction de comptage d'une ligne sur l'ensemble des lignes d'un fichier.

       # let count f =
          nl := 0; nw := 0; nc := 0;
          let f_in = open_in f in
           try
             while true do
              counts (input_line f_in)
             done
           with
             End_of_file -> close_in f_in ;;
       val count : string -> unit = <fun>

   La fonction principale appelle la fonction de comptage sur le nom de fichier passé en argument et affiche les résultats.

       # let print_count() =
          Printf.printf"\t%d" !nl ;
          Printf.printf"\t%d" !nw ;
          Printf.printf"\t%d\n" !nc ;;
       val print_count : unit -> unit = <fun>

       # let main() =
          try
            if Array.length Sys.argv < 2
            then print_string "wc1: missing file name\n"
            else ( count Sys.argv.(1) ; print_count() )
          with e -> Printf.printf "wc1: %s\n" (Printexc.to_string e) ;;
       val main : unit -> unit = <fun>

2. Write a more elaborated version (`wc2`), which can handle the three options `-l`, `-c`, `-w` as well as several file names. The options indicate if we want to count the number of lines, characters or words. The output of each result shall be preceded by the name of the file.
   On reprend les variables globales et les fonctions `counts` et `count` de la question précédente.
   On se donne trois variable globales donnant le statut des trois options possibles.

       # let l_opt = ref false
        and w_opt = ref false
        and c_opt = ref false ;;
       val l_opt : bool ref = {contents=false}
       val w_opt : bool ref = {contents=false}
       val c_opt : bool ref = {contents=false}

   On redéfinit l'affichage en fonction du statut des options.

       # let print_count f =
          Printf.printf "%s:" f ;
          if !l_opt then Printf.printf"\t%d" !nl ;
          if !w_opt then Printf.printf"\t%d" !nw ;
          if !c_opt then Printf.printf"\t%d" !nc;
          print_newline() ;;
       val print_count : string -> unit = <fun>

   La ligne de commande est analysée pour mettre à jour le statut des options ainsi que la liste des fichiers à traiter.

       # let f_list = ref ([]:string list) ;;
       val f_list : string list ref = {contents=[]}

       # let read_args () =
          let usage_msg = "wc2 [-l] [-w] [-w] files..." in
          let add_f f = f_list := f::!f_list in
          let spec_list =
            [ ("-l", Arg.Set l_opt, "affichage nombre de lignes") ;
              ("-w", Arg.Set w_opt, "affichage nombre de mots") ;
              ("-c", Arg.Set c_opt, "affichage nombre de caractères") ] in
          Arg.parse spec_list add_f usage_msg ;;
       val read_args : unit -> unit = <fun>

   La fonction principale itère le comptage sur la liste des fichiers.

       # let main() =
         try
           read_args() ;
           List.iter (fun f -> count f; print_count f) !f_list
         with
           e -> Printf.printf "wc2: %s\n" (Printexc.to_string e) ;;
       val main : unit -> unit = <fun>

### Pipes for Spell Checking

This exercise uses pipes to concatenate a suite of actions. Each action takes the result of the preceding action as argument. The communication is realized by pipes, connecting the output of one process to the input of the following, in the style of the Unix command line symbol `|` .

1. Write a function `pipe_two_progs` of type string * string list -> string * string list -> unit such that pipe_two_progs (p1,[a1; ...; an]) (p2,[b1; ...; bp]) starts the programs p1 a1 ... an and p2 b1 ... bp, redirecting the standard output of p1 to the standard input of p2. ai and bi are the command line arguments of each program.
   Voici une façon trés `` unixienne '' d'implanter cette fonction.

       # let pipe_two_progs (p1, args1) (p2, args2) =
          let in2, out1 = Unix.pipe() in
          match Unix.fork() with
              0 ->
               Unix.close in2 ;
               Unix.close Unix.stdout ;
               ignore(Unix.dup out1) ;
               Unix.close out1 ;
               Unix.execvp p1 (Array.of_list args1)
            | _ ->
               Unix.close out1 ;
               Unix.close Unix.stdin ;
               ignore(Unix.dup in2) ;
                Unix.close in2 ;
                Unix.execvp p2 (Array.of_list args2) ;;
       val pipe_two_progs : string * string list -> string * string list -> unit =
         <fun>

2. We revisit the spell checker function from the exercise on page ?? to write a first program. Modify it so that the list of faulty words is sent without treatment in the form of one line per word to the standard output.

       # let orthographe dico nom =
          let f = open_in nom in
            try
              while true do
                let s = input_line f in
                let ls = mots s in
                 List.iter (Printf.printf"%s\n") (verifie dico ls)
               done ;
               failwith "cas impossible"
             with
               End_of_file -> close_in f
             | x -> close_in f ; raise x  ;;
       val orthographe : arbre_lex -> string -> unit = <fun>

3. The second program takes a sequence of character strings from its standard input and sorts it in lexicographical order. The function `Sort.list` can be used, which sorts a list in an order defined by a given predicate. The sorted list is written to the standard output.

       # let trie () =
          let l = ref [] in
          try
            while true do l := Sort.list (<) ((input_line stdin)::!l) done
          with
            End_of_file -> List.iter (Printf.printf"%s\n") !l ;;
       val trie : unit -> unit = <fun>

4. Test the function `pipe_two_progs` with the two programs.
5. Write a function `pipe_n_progs` to connect a list of programs.

       # pipe_two_progs ("orthographe",["";Sys.argv.(1)]) ("tri",[]) ;;

   Pour alléger un peu l'écriture on se donne deux fonctions de redirection de l'entrée et de la sortie standard.

       # let dup_stdin in_descr =
          if in_descr<>Unix.stdin then Unix.dup2 in_descr Unix.stdin ;
          Unix.close in_descr ;;
       val dup_stdin : Unix.file_descr -> unit = <fun>

       # let dup_stdout out_descr =
          if out_descr<>Unix.stdout then Unix.dup2 out_descr Unix.stdout ;
          Unix.close out_descr ;;
       val dup_stdout : Unix.file_descr -> unit = <fun>

   Pour itérer le pipeline, on définit une fonction récursive dont le premier argument donne le canal d'entrée du premier processus à chaîner.

       # let rec pipe_n_progs_loop in_descr = function
            [p,args] ->
              dup_stdin in_descr ;
              Unix.execvp p (Array.of_list args)
          | (p,args)::ps ->
              let in2, out1 = Unix.pipe() in
              ( match Unix.fork() with
                    0 ->
                      Unix.close in2 ;
                      dup_stdin in_descr ;
                      dup_stdout out1 ;
                      Unix.execvp p (Array.of_list args)
                  | _ ->
                      Unix.close out1 ;
                      pipe_n_progs_loop in2 ps )
           | _ -> () ;;
       val pipe_n_progs_loop :
         Unix.file_descr -> (string * string list) list -> unit = <fun>

       # let pipe_n_progs ps = pipe_n_progs_loop Unix.stdin ps ;;
       val pipe_n_progs : (string * string list) list -> unit = <fun>

6. Write a program to suppress multiple occurrences of elements in a list.

       # let rmdup () =
         let l = ref [] in
          try
            while true do
              let x = input_line stdin in
                if not (List.mem x !l) then l := x::!l
            done
          with End_of_file
           -> List.iter (Printf.printf"%s\n") !l ;;
       val rmdup : unit -> unit = <fun>

7. Test the function `pipe_n_progs` with these three programs.

       # pipe_n_progs [ ("orthographe",["";Sys.argv.(1)]); ("tri",[]); ("rmdup",[]) ] ;;

### Interactive Trace

In a complex calculation it may be useful to interact with the program to verify the progression. For this purpose we revisit the exercise on page ?? on the computation of prime numbers contained in an interval.

1. Modify the program so that a global variable `result` always contains the last prime number found.

       # let result = ref 0 ;;
       val result : int ref = {contents=0}
       # let rec eras = function
            [] -> []
          | p::q ->
             result := p ;
              p :: (eras (List.filter (fun x -> x mod p <> 0) q)) ;;
       val eras : int list -> int list = <fun>

2. Write a function `sigint_handle` which handles the signal `sigint` and writes the content of `result` to the output.

       # let sigint_handle (_ : int) =
         Printf.printf"Current prime number : %d\n" !result;
         flush stdout ;;
       val sigint_handle : int -> unit = <fun>

3. Modify the default signal handling of `sigint` by associating with it the preceding function `sigint_handle`.

       # Sys.set_signal Sys.sigint (Sys.Signal_handle sigint_handle) ;;
       - : unit = ()

4. Compile the program, then start the executable with an upper bound for the computation time. During the computation, send the signal `sigint` to the process, by the Unix `kill` command as well as by the key combination `CTRL-C`.

       $ ocamlc premiers.ml
       $ premiers 15000
       Current prime number : 2539
       Current prime number : 8263
       2 3 5 7 11 13 17 19 23 29 31 37 41 43 ............