(* * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. *) module Char = struct include Char let is_lowercase = function 'a' .. 'z' | '_' -> true | _ -> false let is_uppercase = function 'A' .. 'Z' -> true | _ -> false end module String = struct include StringLabels let is_empty str = length str = 0 let lsplit2 str ~on = match index_opt str on with | Some pos -> Some ( sub str ~pos:0 ~len:pos , sub str ~pos:(pos + 1) ~len:(length str - pos - 1) ) | None -> None let subo ?(pos = 0) ?len str = let len = match len with Some i -> i | None -> length str - pos in sub str ~pos ~len end module Map = Map.Make (String) (** Debug trace logging *) type ('a, 'b) fmt = ('a, Format.formatter, unit, 'b) format4 type 'a printf = ('a, unit) fmt -> 'a type pf = {pf: 'a. 'a printf} let fs = Format.err_formatter let flush = Format.pp_print_newline fs type trace_mod_funs = {trace_mod: bool option; trace_funs: bool Map.t} type trace_mods_funs = trace_mod_funs Map.t type config = {trace_all: bool; trace_mods_funs: trace_mods_funs; colors: bool} let none = {trace_all= false; trace_mods_funs= Map.empty; colors= false} let all = {none with trace_all= true} let config = ref none let parse s = try if String.equal s "*" then Ok all else let default = Map.empty in let index_from s i = match (String.index_from_opt s i '+', String.index_from_opt s i '-') with | None, o | o, None -> o | Some m, Some n -> Some (min m n) in let rec split s rev_parts i = match index_from s (i + 1) with | Some j when j = i -> split s rev_parts j | Some j -> split s (String.sub s ~pos:i ~len:(j - i) :: rev_parts) j | _ -> List.rev (String.subo s ~pos:i :: rev_parts) in let parts = split s [] 0 in let trace_mods_funs = List.fold_left (fun m part -> let parse_part part = let sign, rest = match part.[0] with | '-' -> (false, String.subo part ~pos:1) | '+' -> (true, String.subo part ~pos:1) | _ -> (true, part) in assert (not (String.is_empty rest)) ; assert (Char.is_uppercase rest.[0]) ; match String.lsplit2 rest ~on:'.' with | Some (mod_name, fun_name) -> assert (Char.is_lowercase fun_name.[0]) ; (mod_name, Some fun_name, sign) | None -> (rest, None, sign) in match parse_part part with | mod_name, Some fun_name, enabled -> let {trace_mod; trace_funs} = try Map.find mod_name m with Not_found -> {trace_mod= None; trace_funs= default} in Map.add mod_name { trace_mod ; trace_funs= Map.add fun_name enabled trace_funs } m | mod_name, None, enabled -> Map.add mod_name {trace_mod= Some enabled; trace_funs= default} m ) default parts in Ok {none with trace_mods_funs} with Assert_failure _ as exn -> Error exn let pp_styled style fmt fs = Format.pp_open_box fs 2 ; if not !config.colors then Format.kfprintf (fun fs -> Format.pp_close_box fs ()) fs fmt else ( ( match style with | `Bold -> Format.fprintf fs "@<0>\027[1m" | `Cyan -> Format.fprintf fs "@<0>\027[36m" | `Magenta -> Format.fprintf fs "@<0>\027[95m" ) ; Format.kfprintf (fun fs -> Format.fprintf fs "@<0>\027[0m" ; Format.pp_close_box fs () ) fs fmt ) let init ?(colors = false) ?(margin = 240) ?config:(c = none) () = Format.set_margin margin ; Format.set_max_indent (margin - 1) ; Format.pp_set_margin fs margin ; Format.pp_set_max_indent fs (margin - 1) ; Format.pp_open_vbox fs 0 ; at_exit flush ; config := {c with colors} (** split a string such as [Dune__exe__Module.Submodule.Subsubmodule.function.subfunction] into [(Module, function.subfunction)] *) let split_mod_fun_name s = let fun_name_end = String.length s in let rec fun_name_start_ s i = match String.rindex_from_opt s i '.' with | Some j -> if Char.is_uppercase s.[j + 1] then fun_name_start_ s j else j + 1 | None -> 0 in let fun_name_start = fun_name_start_ s (fun_name_end - 1) in let fun_name = String.sub s ~pos:fun_name_start ~len:(fun_name_end - fun_name_start) in let mod_name_end = match String.index_from_opt s 0 '.' with | Some i -> i | None -> fun_name_end in let rec mod_name_start_ s i = if i <= 1 then None else if not (Char.equal '_' s.[i]) then mod_name_start_ s (i - 1) else if not (Char.equal '_' s.[i - 1]) then mod_name_start_ s (i - 2) else Some (i + 1) in let mod_name_start = match mod_name_start_ s (mod_name_end - 2) with | Some pos -> pos | None -> 0 in let mod_name = String.sub s ~pos:mod_name_start ~len:(mod_name_end - mod_name_start) in (mod_name, fun_name) let enabled mod_fun_name = let {trace_all; trace_mods_funs; _} = !config in if Map.is_empty trace_mods_funs then trace_all else let mod_name, fun_name = split_mod_fun_name mod_fun_name in match Map.find mod_name trace_mods_funs with | {trace_mod; trace_funs} -> ( try Map.find fun_name trace_funs with Not_found -> ( match trace_mod with | Some mod_enabled -> mod_enabled | None -> trace_all ) ) | exception Not_found -> trace_all let kprintf mod_fun_name k fmt = if enabled mod_fun_name then Format.kfprintf k fs fmt else Format.ifprintf fs fmt let fprintf mod_fun_name fs fmt = if enabled mod_fun_name then Format.fprintf fs fmt else Format.ifprintf fs fmt let printf mod_fun_name fmt = fprintf mod_fun_name fs fmt let info mod_fun_name fmt = if not (enabled mod_fun_name) then Format.ifprintf fs fmt else let mod_name, fun_name = split_mod_fun_name mod_fun_name in Format.fprintf fs "@\n@[<4>| %s.%s:" mod_name fun_name ; Format.kfprintf (fun fs -> Format.fprintf fs "@]") fs fmt let infok_ mod_fun_name fmt = if not (enabled mod_fun_name) then Format.ifprintf fs fmt else let mod_name, _ = split_mod_fun_name mod_fun_name in Format.fprintf fs "@\n@[<4>| %s." mod_name ; Format.kfprintf (fun fs -> Format.fprintf fs "@]") fs fmt let infok mod_fun_name k = k {pf= (fun fmt -> infok_ mod_fun_name fmt)} let incf mod_fun_name fmt = if not (enabled mod_fun_name) then Format.ifprintf fs fmt else let mod_name, fun_name = split_mod_fun_name mod_fun_name in Format.fprintf fs "@\n@[<2>@[( %s.%s:" mod_name fun_name ; Format.kfprintf (fun fs -> Format.fprintf fs "@]") fs fmt let decf mod_fun_name fmt = if not (enabled mod_fun_name) then Format.ifprintf fs fmt else let mod_name, fun_name = split_mod_fun_name mod_fun_name in Format.fprintf fs "@]@\n@[<2>) %s.%s:@ " mod_name fun_name ; Format.kfprintf (fun fs -> Format.fprintf fs "@]") fs fmt let call mod_fun_name k = k {pf= (fun fmt -> incf mod_fun_name fmt)} let retn mod_fun_name k result = k {pf= (fun fmt -> decf mod_fun_name fmt)} result ; result let trace : ?call:(pf -> unit) -> ?retn:(pf -> 'a -> unit) -> ?rais:(pf -> exn -> Printexc.raw_backtrace -> unit) -> string -> (unit -> 'a) -> 'a = fun ?call ?retn ?rais mod_fun_name k -> let call = Option.value call ~default:(fun {pf} -> pf "") in let retn = Option.value retn ~default:(fun {pf} _ -> pf "") in let rais = Option.value rais ~default:(fun {pf} exc _ -> pf "%s" (Printexc.to_string exc) ) in call {pf= (fun fmt -> incf mod_fun_name fmt)} ; match k () with | result -> retn {pf= (fun fmt -> decf mod_fun_name fmt)} result ; result | exception exc -> let bt = Printexc.get_raw_backtrace () in rais {pf= (fun fmt -> decf mod_fun_name fmt)} exc bt ; Printexc.raise_with_backtrace exc bt let raisef ?margin exn fmt = let fs = Format.str_formatter in ( match margin with | Some m -> Format.pp_set_margin fs m ; Format.pp_set_max_indent fs (m - 1) | None -> () ) ; Format.pp_open_box fs 2 ; Format.kfprintf (fun fs () -> Format.pp_close_box fs () ; raise (exn (Format.flush_str_formatter ())) ) fs fmt let fail fmt = let margin = Format.pp_get_margin fs () in raisef ~margin (fun msg -> Format.fprintf fs "@\n%s@." msg ; Failure msg ) fmt