Previous Contents Next

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 ............
    

Previous Contents Next