open Printf

type punctuation =
    unit -> string

type 'a printer =
    unit -> 'a -> string

(* ------------------------------------------------------------------------- *)

(* Newlines and indentation. *)

let maxindent =
  120

let whitespace =
  String.make maxindent ' '

let indentation =
  ref 0

let nl () =
  "\n" ^ String.sub whitespace 0 !indentation

let indent ofs producer () x =
  let old_indentation = !indentation in
  let new_indentation = old_indentation + ofs in
  if new_indentation <= maxindent then
    indentation := new_indentation;
  let result = sprintf "%t%a" nl producer x in
  indentation := old_indentation;
  result

(* ------------------------------------------------------------------------- *)

(* Lists. *)

let rec list elem () xs =
  match xs with
  | [] ->
      ""
  | x :: xs ->
      sprintf "%a%a" elem x (list elem) xs

let rec preclist delim elem () xs =
  match xs with
  | [] ->
      ""
  | x :: xs ->
      sprintf "%t%a%a" delim elem x (preclist delim elem) xs

let rec termlist delim elem () xs =
  match xs with
  | [] ->
      ""
  | x :: xs ->
      sprintf "%a%t%a" elem x delim (termlist delim elem) xs

let seplist sep elem () xs =
  match xs with
  | [] ->
      ""
  | x :: xs ->
      sprintf "%a%a" elem x (preclist sep elem) xs

let annlist announcement list () xs =
  match xs with
  | [] ->
      ""
  | _ :: _ ->
      sprintf "%t%a" announcement list xs

(* ------------------------------------------------------------------------- *)

(* Punctuation. *)

let space () =
  sprintf " "

let comma () =
  sprintf ", "

let semicolon () =
  sprintf "; "

let var () =
  sprintf "var "

let seminl () =
  sprintf "%t%t" semicolon nl

let nlspace k () =
  sprintf "%t%s" nl (String.make k ' ')

let nlnl () =
  sprintf "%t%t" nl nl

(* ------------------------------------------------------------------------- *)

(* [atmost n delimiter stop] normally prints a [delimiter], except that,
   every [n] calls, it prints a [stop] in addition. *)

let atmost n (delimiter : punctuation) (stop : punctuation) : punctuation =
  let i =
    ref 0
  in
  function () ->
    incr i;
    delimiter() ^
    if !i = n then begin
      i := 0;
      stop()
    end
    else
      ""

(* ------------------------------------------------------------------------- *)

(* Tables. *)

let width column =
  List.fold_left (fun width x ->
    max width (String.length x)
  ) 0 column

let pad width x =
  let y = String.make width ' ' in
  String.blit x 0 y 0 (String.length x);
  y

let pad column =
  List.map (pad (width column)) column

let rec zipcat column1 column2 =
  List.fold_right2 (fun x1 x2 column ->
    (x1 ^ x2) :: column
  ) column1 column2 []

let catenate columns =
  match columns with
  | [] ->
      []
  | column :: columns ->
      List.fold_left (fun table column ->
        zipcat table (pad column)
      ) (pad column) columns

let transposerev lines =
  match lines with
  | [] ->
      []
  | line :: lines ->
      List.fold_left (fun columns line ->
        List.fold_right2 (fun x column columns ->
          (x :: column) :: columns
        ) line columns []
      ) (List.map (fun x -> [ x ]) line) lines

(* ------------------------------------------------------------------------- *)

(* Conditional. *)

let showif flag printer x =
  if flag then begin
    Printf.fprintf stdout "%s%!" (sprintf "%a" printer x);
    x
  end
  else
    x