(***********************************************************************)
(*                                                                     *)
(*                           Objective Caml                            *)
(*                                                                     *)
(*            Xavier Leroy, projet Cristal, INRIA Rocquencourt         *)
(*                                                                     *)
(*  Copyright 1996 Institut National de Recherche en Informatique et   *)
(*  en Automatique.  All rights reserved.  This file is distributed    *)
(*  under the terms of the GNU Library General Public License.         *)
(*                                                                     *)
(***********************************************************************)

(* $Id: printf.ml,v 1.10 1999/11/17 18:58:28 xleroy Exp $ *)

external format_int: string -> int -> string = "format_int"
external format_float: string -> float -> string = "format_float"

let fprintf outchan format =
  let format = (Obj.magic format : string) in
  let rec doprn i =
    if i >= String.length format then
      Obj.magic ()
    else begin
      let c = String.unsafe_get format i in
      if c <> '%' then begin
        output_char outchan c;
        doprn (succ i)
      end else begin
        let j = skip_args (succ i) in
        match String.unsafe_get format j with
          '%' ->
            output_char outchan '%';
            doprn (succ j)
        | 's' ->
            Obj.magic(fun s ->
              if j <= i+1 then
                output_string outchan s
              else begin
                let p =
                  try
                    int_of_string (String.sub format (i+1) (j-i-1))
                  with _ ->
                    invalid_arg "fprintf: bad %s format" in
                if p > 0 && String.length s < p then begin
                  output_string outchan
                                (String.make (p - String.length s) ' ');
                  output_string outchan s
                end else if p < 0 && String.length s < -p then begin
                  output_string outchan s;
                  output_string outchan
                                (String.make (-p - String.length s) ' ')
                end else
                  output_string outchan s
              end;
              doprn (succ j))
        | 'c' ->
            Obj.magic(fun c ->
              output_char outchan c;
              doprn (succ j))
        | 'd' | 'i' | 'o' | 'x' | 'X' | 'u' ->
            Obj.magic(fun n ->
              output_string outchan
                            (format_int (String.sub format i (j-i+1)) n);
              doprn (succ j))
        | 'f' | 'e' | 'E' | 'g' | 'G' ->
            Obj.magic(fun f ->
              output_string outchan
                            (format_float (String.sub format i (j-i+1)) f);
              doprn (succ j))
        | 'b' ->
            Obj.magic(fun b ->
              output_string outchan (string_of_bool b);
              doprn (succ j))
        | 'a' ->
            Obj.magic(fun printer arg ->
              printer outchan arg;
              doprn(succ j))
        | 't' ->
            Obj.magic(fun printer ->
              printer outchan;
              doprn(succ j))
        | c ->
            invalid_arg ("fprintf: unknown format")
      end
    end

  and skip_args j =
    match String.unsafe_get format j with
      '0' .. '9' | ' ' | '.' | '-' -> skip_args (succ j)
    | c -> j

  in doprn 0

let printf fmt = fprintf stdout fmt
and eprintf fmt = fprintf stderr fmt

let bprintf_internal tostring buf format =
  let format = (Obj.magic format : string) in
  let rec doprn i =
    if i >= String.length format then
      if tostring then begin
        let res = Obj.magic (Buffer.contents buf) in
        Buffer.clear buf; (* just in case [bs]printf is partially applied *)
        res
      end else
        Obj.magic ()
    else begin
      let c = String.unsafe_get format i in
      if c <> '%' then begin
        Buffer.add_char buf c;
        doprn (succ i)
      end else begin
        let j = skip_args (succ i) in
        match String.unsafe_get format j with
          '%' ->
            Buffer.add_char buf '%';
            doprn (succ j)
        | 's' ->
            Obj.magic(fun s ->
              if j <= i+1 then
                Buffer.add_string buf s
              else begin
                let p =
                  try
                    int_of_string (String.sub format (i+1) (j-i-1))
                  with _ ->
                    invalid_arg "fprintf: bad %s format" in
                if p > 0 && String.length s < p then begin
                  Buffer.add_string buf
                                (String.make (p - String.length s) ' ');
                  Buffer.add_string buf s
                end else if p < 0 && String.length s < -p then begin
                  Buffer.add_string buf s;
                  Buffer.add_string buf
                                (String.make (-p - String.length s) ' ')
                end else
                  Buffer.add_string buf s
              end;
              doprn (succ j))
        | 'c' ->
            Obj.magic(fun c ->
              Buffer.add_char buf c;
              doprn (succ j))
        | 'd' | 'i' | 'o' | 'x' | 'X' | 'u' ->
            Obj.magic(fun n ->
              Buffer.add_string buf
                            (format_int (String.sub format i (j-i+1)) n);
              doprn (succ j))
        | 'f' | 'e' | 'E' | 'g' | 'G' ->
            Obj.magic(fun f ->
              Buffer.add_string buf
                            (format_float (String.sub format i (j-i+1)) f);
              doprn (succ j))
        | 'b' ->
            Obj.magic(fun b ->
              Buffer.add_string buf (string_of_bool b);
              doprn (succ j))
        | 'a' ->
            if tostring then
              Obj.magic(fun printer arg ->
                Buffer.add_string buf (printer () arg);
                doprn(succ j))
            else
              Obj.magic(fun printer arg ->
                printer buf arg;
                doprn(succ j))
        | 't' ->
            if tostring then
              Obj.magic(fun printer ->
                Buffer.add_string buf (printer ());
                doprn(succ j))
            else
              Obj.magic(fun printer ->
                printer buf;
                doprn(succ j))
        | c ->
            invalid_arg ("sprintf: unknown format")
      end
    end

  and skip_args j =
    match String.unsafe_get format j with
      '0' .. '9' | ' ' | '.' | '-' -> skip_args (succ j)
    | c -> j

  in doprn 0

let bprintf buf fmt = bprintf_internal false buf fmt

let sprintf fmt = bprintf_internal true (Buffer.create 16) fmt
