catala/compiler/catala_utils/message.ml

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

270 lines
8.1 KiB
OCaml
Raw Normal View History

2023-05-30 16:41:49 +03:00
(** Error formatting and helper functions *)
(**{1 Terminal formatting}*)
2023-06-07 19:10:50 +03:00
(* Adds handling of color tags in the formatter *)
let color_formatter ppf =
2023-06-07 19:15:14 +03:00
Ocolor_format.prettify_formatter ppf;
ppf
2023-06-07 19:10:50 +03:00
2023-06-07 19:15:14 +03:00
(* Sets handling of tags in the formatter to ignore them (don't print any color
codes) *)
2023-06-07 19:10:50 +03:00
let unstyle_formatter ppf =
2023-06-07 19:15:14 +03:00
Format.pp_set_mark_tags ppf false;
ppf
(* SIDE EFFECT AT MODULE LOAD: this turns on handling of tags in
[Format.sprintf] etc. functions (ignoring them) *)
2023-06-07 19:10:50 +03:00
let () = ignore (unstyle_formatter Format.str_formatter)
2023-06-07 19:15:14 +03:00
(* Note: we could do the same for std_formatter, err_formatter... but we'd
rather promote the use of the formatting functions of this module and the
below std_ppf / err_ppf *)
2023-06-07 19:10:50 +03:00
let has_color oc =
2023-06-07 19:10:50 +03:00
match !Cli.style_flag with
| Cli.Never -> false
| Always -> true
| Auto -> Unix.(isatty (descr_of_out_channel oc))
2023-06-07 19:10:50 +03:00
(* Here we create new formatters to stderr/stdout that remain separate from the
ones used by [Format.printf] / [Format.eprintf] (which remain unchanged) *)
2023-06-07 19:10:50 +03:00
let formatter_of_out_channel oc =
let ppf = Format.formatter_of_out_channel oc in
if has_color oc then color_formatter ppf else unstyle_formatter ppf
2023-06-07 19:10:50 +03:00
let std_ppf = lazy (formatter_of_out_channel stdout)
let err_ppf = lazy (formatter_of_out_channel stderr)
2023-06-07 19:15:14 +03:00
let ignore_ppf = lazy (Format.make_formatter (fun _ _ _ -> ()) (fun () -> ()))
2023-06-07 19:10:50 +03:00
2023-06-07 19:15:14 +03:00
let unformat (f : Format.formatter -> unit) : string =
2023-06-07 19:10:50 +03:00
let buf = Buffer.create 1024 in
let ppf = unstyle_formatter (Format.formatter_of_buffer buf) in
2023-06-07 19:15:14 +03:00
Format.pp_set_margin ppf max_int;
(* We won't print newlines anyways, but better not have them in the first
place (this wouldn't remove cuts in a vbox for example) *)
2023-06-07 19:10:50 +03:00
let out_funs = Format.pp_get_formatter_out_functions ppf () in
Format.pp_set_formatter_out_functions ppf
2023-06-07 19:15:14 +03:00
{
out_funs with
Format.out_newline = (fun () -> out_funs.out_string " " 0 1);
Format.out_indent = (fun _ -> ());
};
2023-06-07 19:10:50 +03:00
f ppf;
Format.pp_print_flush ppf ();
Buffer.contents buf
(**{2 Message types and output helpers *)
2023-06-07 19:10:50 +03:00
type content_type = Error | Warning | Debug | Log | Result
let get_ppf = function
| Result -> Lazy.force std_ppf
| Debug when not !Cli.debug_flag -> Lazy.force ignore_ppf
| Warning when !Cli.disable_warnings_flag -> Lazy.force ignore_ppf
| Error | Log | Debug | Warning -> Lazy.force err_ppf
(**{3 Markers}*)
2023-06-07 19:10:50 +03:00
let print_time_marker =
let time : float ref = ref (Unix.gettimeofday ()) in
fun ppf () ->
2023-06-07 19:15:14 +03:00
let new_time = Unix.gettimeofday () in
let old_time = !time in
time := new_time;
let delta = (new_time -. old_time) *. 1000. in
if delta > 50. then
2023-06-19 18:29:51 +03:00
Format.fprintf ppf "@{<bold;black>[TIME] %.0fms@}@ " delta
2023-06-07 19:10:50 +03:00
2023-06-08 15:36:28 +03:00
let pp_marker target ppf =
2023-06-07 19:10:50 +03:00
let open Ocolor_types in
2023-06-07 19:15:14 +03:00
let tags, str =
match target with
| Debug -> [Bold; Fg (C4 magenta)], "[DEBUG]"
| Error -> [Bold; Fg (C4 red)], "[ERROR]"
| Warning -> [Bold; Fg (C4 yellow)], "[WARNING]"
| Result -> [Bold; Fg (C4 green)], "[RESULT]"
| Log -> [Bold; Fg (C4 black)], "[LOG]"
2023-06-07 19:10:50 +03:00
in
if target = Debug then print_time_marker ppf ();
Format.pp_open_stag ppf (Ocolor_format.Ocolor_styles_tag tags);
Format.pp_print_string ppf str;
Format.pp_close_stag ppf ()
(**{2 Printers}*)
2023-05-30 16:41:49 +03:00
(** {1 Message content} *)
2023-05-30 17:22:05 +03:00
module Content = struct
2023-06-07 19:10:50 +03:00
type message = Format.formatter -> unit
type position = { pos_message : message option; pos : Pos.t }
type t = { message : message; positions : position list }
2023-05-30 17:22:05 +03:00
2023-06-07 19:10:50 +03:00
let of_message (message : message) : t = { message; positions = [] }
2023-06-07 19:15:14 +03:00
let of_string (s : string) : t =
{ message = (fun ppf -> Format.pp_print_string ppf s); positions = [] }
2023-06-13 21:10:42 +03:00
let internal_error_prefix =
"Internal Error, please report to \
2023-06-19 18:29:51 +03:00
https://github.com/CatalaLang/catala/issues : "
2023-06-13 21:10:42 +03:00
let prepend_message (content : t) prefix : t =
{
content with
message = (fun ppf -> Format.fprintf ppf "%t@,%t" prefix content.message);
}
let mark_as_internal_error (content : t) : t =
{
content with
message =
(fun ppf ->
Format.fprintf ppf "%s@,%t" internal_error_prefix content.message);
}
2023-05-30 17:22:05 +03:00
end
2023-05-30 16:41:49 +03:00
2023-06-07 19:10:50 +03:00
open Content
let emit_content (content : Content.t) (target : content_type) : unit =
let { message; positions } = content in
2023-05-30 16:41:49 +03:00
match !Cli.message_format_flag with
| Cli.Human ->
2023-06-08 15:36:28 +03:00
let ppf = get_ppf target in
Format.fprintf ppf "@[<v>@[<hov 0>%t%t%t@]%a@]@." (pp_marker target)
(fun ppf ->
match target with
| Log | Error | Warning -> Format.pp_print_char ppf ' '
| Result | Debug -> Format.pp_print_space ppf ())
message
2023-06-07 19:15:14 +03:00
(fun ppf l ->
Format.pp_print_list
~pp_sep:(fun _ () -> ())
(fun ppf pos ->
2023-06-07 19:10:50 +03:00
Format.pp_print_cut ppf ();
Format.pp_print_cut ppf ();
2023-06-07 19:15:14 +03:00
Option.iter
(fun msg -> Format.fprintf ppf "%t@," msg)
pos.pos_message;
2023-06-07 19:10:50 +03:00
Pos.format_loc_text ppf pos.pos)
2023-06-07 19:15:14 +03:00
ppf l)
2023-06-07 19:10:50 +03:00
positions
2023-05-30 16:41:49 +03:00
| Cli.GNU ->
(* The top message doesn't come with a position, which is not something the
GNU standard allows. So we look the position list and put the top message
everywhere there is not a more precise message. If we can'r find a
position without a more precise message, we just take the first position
in the list to pair with the message. *)
2023-06-07 19:10:50 +03:00
let ppf = get_ppf target in
let () =
if
positions != []
&& List.for_all
2023-06-07 19:15:14 +03:00
(fun (pos' : Content.position) -> Option.is_some pos'.pos_message)
positions
2023-05-30 16:41:49 +03:00
then
2023-06-08 15:36:28 +03:00
Format.fprintf ppf "@{<blue>%s@}: %t %s@\n"
2023-06-07 19:10:50 +03:00
(Pos.to_string_short (List.hd positions).pos)
2023-06-08 15:36:28 +03:00
(pp_marker target) (unformat message)
2023-06-07 19:10:50 +03:00
in
2023-06-07 19:15:14 +03:00
Format.pp_print_list ~pp_sep:Format.pp_print_newline
2023-06-07 19:10:50 +03:00
(fun ppf pos' ->
2023-06-08 15:36:28 +03:00
Format.fprintf ppf "@{<blue>%s@}: %t %s"
2023-06-07 19:15:14 +03:00
(Pos.to_string_short pos'.pos)
2023-06-08 15:36:28 +03:00
(pp_marker target)
2023-06-07 19:15:14 +03:00
(match pos'.pos_message with
| None -> unformat message
| Some msg' -> unformat msg'))
ppf positions
2023-05-30 16:41:49 +03:00
(** {1 Error exception} *)
2023-05-30 17:22:05 +03:00
exception CompilerError of Content.t
2023-05-30 16:41:49 +03:00
(** {1 Error printing} *)
2023-06-07 19:15:14 +03:00
let raise_spanned_error
?(span_msg : Content.message option)
(span : Pos.t)
format =
2023-06-07 19:10:50 +03:00
Format.kdprintf
(fun message ->
2023-05-30 16:41:49 +03:00
raise
(CompilerError
2023-06-07 19:15:14 +03:00
{ message; positions = [{ pos_message = span_msg; pos = span }] }))
2023-05-30 16:41:49 +03:00
format
2023-06-07 19:15:14 +03:00
let raise_multispanned_error_full
(spans : (Content.message option * Pos.t) list)
format =
2023-06-07 19:10:50 +03:00
Format.kdprintf
(fun message ->
2023-05-30 16:41:49 +03:00
raise
(CompilerError
{
2023-06-07 19:10:50 +03:00
message;
2023-05-30 16:41:49 +03:00
positions =
2023-06-07 19:15:14 +03:00
List.map (fun (pos_message, pos) -> { pos_message; pos }) spans;
2023-05-30 16:41:49 +03:00
}))
format
2023-06-07 19:10:50 +03:00
let raise_multispanned_error spans format =
raise_multispanned_error_full
2023-06-07 19:15:14 +03:00
(List.map
(fun (msg, pos) ->
Option.map (fun s ppf -> Format.pp_print_string ppf s) msg, pos)
spans)
2023-06-07 19:10:50 +03:00
format
2023-05-30 16:41:49 +03:00
let raise_error format =
2023-06-07 19:10:50 +03:00
Format.kdprintf
(fun message -> raise (CompilerError { message; positions = [] }))
2023-05-30 16:41:49 +03:00
format
let raise_internal_error format =
raise_error ("%s" ^^ format) internal_error_prefix
(** {1 Warning printing}*)
let assert_internal_error condition fmt =
if condition then raise_internal_error ("assertion failed: " ^^ fmt)
else Format.ifprintf (Format.formatter_of_out_channel stdout) fmt
2023-06-07 19:15:14 +03:00
let emit_multispanned_warning
(pos : (Content.message option * Pos.t) list)
format =
2023-06-07 19:10:50 +03:00
Format.kdprintf
(fun message ->
2023-05-30 16:41:49 +03:00
emit_content
{
2023-06-07 19:10:50 +03:00
message;
2023-05-30 16:41:49 +03:00
positions =
2023-06-07 19:15:14 +03:00
List.map (fun (pos_message, pos) -> { pos_message; pos }) pos;
2023-05-30 16:41:49 +03:00
}
Warning)
format
2023-06-07 19:15:14 +03:00
let emit_spanned_warning
?(span_msg : Content.message option)
(span : Pos.t)
format =
2023-05-30 16:41:49 +03:00
emit_multispanned_warning [span_msg, span] format
let emit_warning format = emit_multispanned_warning [] format
let emit_log format =
2023-06-07 19:10:50 +03:00
Format.kdprintf
(fun message -> emit_content { message; positions = [] } Log)
format
let emit_debug format =
2023-06-07 19:10:50 +03:00
Format.kdprintf
(fun message -> emit_content { message; positions = [] } Debug)
format
let emit_result format =
2023-06-07 19:10:50 +03:00
Format.kdprintf
(fun message -> emit_content { message; positions = [] } Result)
format