A complete example

passwd.fmli

(* An entry of "/etc/passwd" is represented by a record of
   type [(!passwd_file, !password) entry] *)
type ('a, 'b) entry =
    { login: 'a string;
      password: 'b string
    }

(* Input from "/etc/passwd" *)
type noneq in_channel
val open_in: unit -{!passwd_file ||}-> in_channel
val input_entry: in_channel
   -{!passwd_file | End_of_file: !passwd_file |}->
   (!passwd_file, !password) entry
val close_in: in_channel -{!passwd_file ||}-> unit

passwd.ml

type entry =
    { login: string;
      password: string
    }

type in_channel = Pervasives.in_channel

let open_in () =
  Pervasives.open_in "/etc/passwd"

let rec input_entry chan =
  let line = input_line chan in
  try
    let i1 = String.index line ':' in
    let i2 = String.index_from line (i1 + 1) ':' in
    { login = String.sub line 0 i1;
      password = String.sub line (i1 + 1) (i2 - i1 - 1)
    }
  with
    Not_found -> input_entry chan

let close_in chan =
  Pervasives.close_in chan

shadow.fmli

(* An entry of "/etc/shadow" is represented by a record of
   type [(!shadow_file, !shadow_password) entry] *)
type ('a, 'b) entry =
    { login: 'a string;
      password: 'b string;
      rem: 'b string;
    }

(* Input from "/etc/shadow" *)
type noneq in_channel
val open_in: unit -{!shadow_file ||}-> in_channel
val input_entry: in_channel
   -{!shadow_file | End_of_file: !shadow_file |}->
   (!shadow_file, !shadow_password) entry
val close_in: in_channel -{!shadow_file ||}-> unit

(* Output to "/etc/shadow" *)
type noneq out_channel
val open_out: unit -{!shadow_file ||}-> out_channel
val output_entry:
   out_channel -> (!shadow_file, !shadow_password) entry
   -{!shadow_file ||}-> unit
val close_out: out_channel -{!shadow_file ||}-> unit

shadow.ml

type entry =
    { login: string;
      password: string;
      rem: string;
    }

type in_channel = Pervasives.in_channel

let open_in () =
  Pervasives.open_in "/etc/shadow"

let rec input_entry chan =
  try
    let line = input_line chan in
    let i1 = String.index line ':' in
    let i2 = String.index_from line (i1 + 1) ':' in
    let ln = String.length line in
    { login = String.sub line 0 i1;
      password = String.sub line (i1 + 1) (i2 - i1 - 1);
      rem = String.sub line (i2 + 1) (ln - i2 - 1)
    }
  with
    Not_found -> input_entry chan

let close_in chan =
  Pervasives.close_in chan

type out_channel = Pervasives.out_channel

let open_out () =
  Pervasives.open_out "/etc/shadow"

let output_entry chan e =
  Printf.fprintf chan "%s:%s:%s\n" e.login e.password e.rem

let close_out chan =
  Pervasives.close_out chan

verbose.fmli

flow !arg < !stderr
and  !arg < !stdout


affects !arg
raises !arg


val message : !stdout string -{!stdout ||}-> unit

verbose.fml

flow !arg < !stderr, !stdout

(** [!verbose_mode] is true if the verbose mode is
    active. *)
let verbose_mode : (!arg bool, _) ref = ref false

(** Parse command-line arguments.  If the option "-v" if
    found then [verbose_mode] is set to true.  If any other
    option is encountered then an error message is printed
    and the exception [Exit] is raised. *)
let _ =
  for i = 1 to Array.length Sys.argv - 1 do
    match Sys.argv.(i) with
      "-v" -> verbose_mode := true
    | option ->
 prerr_string "Invalid option ";
 prerr_endline option;
 raise Exit
  done

(** [print message] print a message on the standard output
    if the verbose mode is enabled. Otherwise, it does
    nothing. *)
let message s =
  if !verbose_mode then print_endline s

main.fml

flow !passwd_file < !shadow_file
and  !passwd_file, !shadow_file < !stdout
and  !passwd_file, !shadow_file < !shadow_password
and  !password < !shadow_password

(** The module [StringMap] implements association tables
    indexed by strings. *)
module StringMap = Map.Make (struct
  type 'a t = 'a string
  let compare = Pervasives.compare
end)



(** [read_shadow ()] reads the content of /etc/passwd
    and returns a map associating each login to its
    entry. *)
let read_shadow () =

  let in_chan = Shadow.open_in () in

  let rec loop accu =
    try
      let entry = Shadow.input_entry in_chan in
      loop (StringMap.add entry.Shadow.login entry accu)
    with End_of_file ->
      Shadow.close_in in_chan;
      accu
  in

  loop StringMap.empty


(** [read_passwd shadow_map] generates /etc/shadow from
    /etc/passwd and the entries in [shadow_map] *)
let read_passwd shadow_map =

  let in_chan = Passwd.open_in ()
  and out_chan = Shadow.open_out () in

  let rec loop () =

    try

      let passwd_entry = Passwd.input_entry in_chan in
      Verbose.message passwd_entry.Passwd.login;

      let shadow_entry =
 try
   StringMap.find passwd_entry.Passwd.login shadow_map
 with
   Not_found ->
     Verbose.message "  creating an entry";
     { Shadow.login = passwd_entry.Passwd.login;
       Shadow.password = passwd_entry.Passwd.password;
       Shadow.rem = ""
     }
      in

      Shadow.output_entry out_chan shadow_entry

    with

      End_of_file -> ()

  in

  loop ();

  Passwd.close_in in_chan;
  Shadow.close_out out_chan


let _ =
  let shadow_map = read_shadow () in
  read_passwd shadow_map