(****************************************************************************)
(*                                                                          *)
(*                                   OCaml                                  *)
(*                                                                          *)
(*                            INRIA Rocquencourt                            *)
(*                                                                          *)
(*  Copyright  2006   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, with the special   *)
(*  exception on linking described in LICENSE at the top of the Camlp4      *)
(*  source tree.                                                            *)
(*                                                                          *)
(****************************************************************************)

(* Authors:
 * - Daniel de Rauglaudre: initial version
 * - Nicolas Pouillard: refactoring
 *)
(* camlp4r *)

open Format;

module ObjTools = struct

  value desc obj =
    if Obj.is_block obj then
      "tag = " ^ string_of_int (Obj.tag obj)
    else "int_val = " ^ string_of_int (Obj.obj obj);

  (*Imported from the extlib*)
  value rec to_string r =
    if Obj.is_int r then
      let i = (Obj.magic r : int)
      in string_of_int i ^ " | CstTag" ^ string_of_int (i + 1)
    else (* Block. *)
      let rec get_fields acc =
        fun
        [ 0 -> acc
        | n -> let n = n-1 in get_fields [Obj.field r n :: acc] n ]
      in
      let rec is_list r =
        if Obj.is_int r then
          r = Obj.repr 0 (* [] *)
        else
          let s = Obj.size r and t = Obj.tag r in
          t = 0 && s = 2 && is_list (Obj.field r 1) (* h :: t *)
      in
      let rec get_list r =
        if Obj.is_int r then []
        else let h = Obj.field r 0 and t = get_list (Obj.field r 1) in [h :: t]
      in
      let opaque name =
        (* XXX In future, print the address of value 'r'.  Not possible in
        * pure OCaml at the moment.
        *)
        "<" ^ name ^ ">"
      in
      let s = Obj.size r and t = Obj.tag r in
      (* From the tag, determine the type of block. *)
      match t with
      [ _ when is_list r ->
              let fields = get_list r in
              "[" ^ String.concat "; " (List.map to_string fields) ^ "]"
      | 0 ->
              let fields = get_fields [] s in
              "(" ^ String.concat ", " (List.map to_string fields) ^ ")"
      | x when x = Obj.lazy_tag ->
              (* Note that [lazy_tag .. forward_tag] are < no_scan_tag.  Not
              * clear if very large constructed values could have the same
              * tag. XXX *)
              opaque "lazy"
      | x when x = Obj.closure_tag ->
              opaque "closure"
      | x when x = Obj.object_tag ->
              let fields = get_fields [] s in
              let (_class, id, slots) =
                      match fields with
                      [ [h; h'::t] -> (h, h', t)
                      | _ -> assert False ]
              in
              (* No information on decoding the class (first field).  So just print
              * out the ID and the slots. *)
              "Object #" ^ to_string id ^ " (" ^ String.concat ", " (List.map to_string slots) ^ ")"
      | x when x = Obj.infix_tag ->
              opaque "infix"
      | x when x = Obj.forward_tag ->
              opaque "forward"
      | x when x < Obj.no_scan_tag ->
              let fields = get_fields [] s in
              "Tag" ^ string_of_int t ^
              " (" ^ String.concat ", " (List.map to_string fields) ^ ")"
      | x when x = Obj.string_tag ->
              "\"" ^ String.escaped (Obj.magic r : string) ^ "\""
      | x when x = Obj.double_tag ->
              Utils.float_repres (Obj.magic r : float)
      | x when x = Obj.abstract_tag ->
              opaque "abstract"
      | x when x = Obj.custom_tag ->
              opaque "custom"
      | _ ->
              failwith ("ObjTools.to_string: unknown tag (" ^ string_of_int t ^ ")") ];

  value print ppf x = fprintf ppf "%s" (to_string x);
  value print_desc ppf x = fprintf ppf "%s" (desc x);

end;

value default_handler ppf x = do {
  let x = Obj.repr x;
  if Obj.tag x <> 0 then
    fprintf ppf "Camlp4: Uncaught exception: %s"
      (Obj.obj (Obj.field x 0) : string)
  else do {
    fprintf ppf "Camlp4: Uncaught exception: %s"
      (Obj.obj (Obj.field (Obj.field x 0) 0) : string);
    if Obj.size x > 1 then do {
      pp_print_string ppf " (";
      for i = 1 to Obj.size x - 1 do
        if i > 1 then pp_print_string ppf ", " else ();
        ObjTools.print ppf (Obj.field x i);
      done;
      pp_print_char ppf ')'
    }
    else ();
  };
  fprintf ppf "@."
};

value handler = ref (fun ppf default_handler exn -> default_handler ppf exn);

value register f =
  let current_handler = handler.val in
  handler.val :=
    fun ppf default_handler exn ->
      try f ppf exn with exn -> current_handler ppf default_handler exn;

module Register (Error : Sig.Error) = struct
  let current_handler = handler.val in
  handler.val :=
    fun ppf default_handler ->
      fun [ Error.E x -> Error.print ppf x
          | x -> current_handler ppf default_handler x ];
end;


value gen_print ppf default_handler =
  fun
  [ Out_of_memory -> fprintf ppf "Out of memory"
  | Assert_failure (file, line, char) ->
      fprintf ppf "Assertion failed, file %S, line %d, char %d"
                  file line char
  | Match_failure (file, line, char) ->
      fprintf ppf "Pattern matching failed, file %S, line %d, char %d"
                  file line char
  | Failure str -> fprintf ppf "Failure: %S" str
  | Invalid_argument str -> fprintf ppf "Invalid argument: %S" str
  | Sys_error str -> fprintf ppf "I/O error: %S" str
  | Stream.Failure -> fprintf ppf "Parse failure"
  | Stream.Error str -> fprintf ppf "Parse error: %s" str
  | x -> handler.val ppf default_handler x ];

value print ppf = gen_print ppf default_handler;

value try_print ppf = gen_print ppf (fun _ -> raise);

value to_string exn =
  Format.asprintf "%a" print exn;

value try_to_string exn =
  Format.asprintf "%a" try_print exn;
