ligo/simple-utils/trace.ml
2019-05-12 20:50:18 +00:00

371 lines
10 KiB
OCaml

module J = Yojson.Basic
type error = [`Assoc of (string * J.t) list]
module JSON_string_utils = struct
let member = J.Util.member
let string = J.Util.to_string_option
let int = J.Util.to_int_option
let swap f l r = f r l
let unit x = Some x
let bind f = function None -> None | Some x -> Some (f x)
let bind2 f = fun l r -> match l, r with
None, None -> None
| None, Some _ -> None
| Some _, None -> None
| Some l, Some r -> Some (f l r)
let default d = function
Some x -> x
| None -> d
let string_of_int = bind string_of_int
let (||) l r = l |> default r
let (|^) = bind2 (^)
end
let mk_error ?(error_code : int option) ~(title : string) ?(message : string option) () =
let collapse l =
List.fold_left (fun acc -> function None -> acc | Some e -> e::acc) [] (List.rev l) in
`Assoc
(collapse
[(match error_code with Some c -> Some ("error_code", `Int c) | None -> None);
Some ("title", `String title);
(match message with Some m -> Some ("message", `String m) | None -> None)])
type error_thunk = unit -> error
type annotation = J.t (* feel free to add different annotations here. *)
type annotation_thunk = unit -> annotation
type 'a result =
Ok of 'a * annotation_thunk list
| Errors of error_thunk list
let ok x = Ok (x, [])
let fail err = Errors [err]
(* When passing a constant string where a thunk is expected, we wrap it with thunk, as follows:
(thunk "some string")
We always put the parentheses around the call, to increase grep and sed efficiency.
When a trace function is called, it is passed a `(fun () -> …)`.
If the `…` is e.g. error then we write `(fun () -> error title msg ()` *)
let thunk x () = x
let error title message () = mk_error ~title:(title ()) ~message:(message ()) ()
let simple_error str () = mk_error ~title:str ()
let simple_fail str = fail @@ simple_error str
(* To be used when wrapped by a "trace_strong" for instance *)
let dummy_fail = simple_fail "dummy"
let map f = function
| Ok (x, annotations) ->
(match f x with
Ok (x', annotations') -> Ok (x', annotations' @ annotations)
| Errors _ as e' -> ignore annotations; e')
| Errors _ as e -> e
let apply f = function
| Ok (x, annotations) -> Ok (f x, annotations)
| Errors _ as e -> e
let (>>?) x f = map f x
let (>>|?) = apply
module Let_syntax = struct
let bind m ~f = m >>? f
module Open_on_rhs_bind = struct end
end
let trace_strong err = function
| Ok _ as o -> o
| Errors _ -> Errors [err]
let trace err = function
| Ok _ as o -> o
| Errors errs -> Errors (err :: errs)
let trace_r err_thunk_may_fail = function
| Ok _ as o -> o
| Errors errs ->
match err_thunk_may_fail () with
| Ok (err, annotations) -> ignore annotations; Errors (err :: errs)
| Errors errors_while_generating_error ->
(* TODO: the complexity could be O(n*n) in the worst case,
this should use some catenable lists. *)
Errors (errors_while_generating_error
@ errs)
let trace_f f error x =
trace error @@ f x
let trace_f_2 f error x y =
trace error @@ f x y
let trace_f_ez f name =
trace_f f (error (thunk "in function") name)
let trace_f_2_ez f name =
trace_f_2 f (error (thunk "in function") name)
let to_bool = function
| Ok _ -> true
| Errors _ -> false
let to_option = function
| Ok (o, annotations) -> ignore annotations; Some o
| Errors _ -> None
let trace_option error = function
| None -> fail error
| Some s -> ok s
let bind_map_option f = function
| None -> ok None
| Some s -> f s >>? fun x -> ok (Some x)
let rec bind_list = function
| [] -> ok []
| hd :: tl -> (
hd >>? fun hd ->
bind_list tl >>? fun tl ->
ok @@ hd :: tl
)
let bind_ne_list = fun (hd , tl) ->
hd >>? fun hd ->
bind_list tl >>? fun tl ->
ok @@ (hd , tl)
let bind_smap (s:_ X_map.String.t) =
let open X_map.String in
let aux k v prev =
prev >>? fun prev' ->
v >>? fun v' ->
ok @@ add k v' prev' in
fold aux s (ok empty)
let bind_fold_smap f init (smap : _ X_map.String.t) =
let aux k v prev =
prev >>? fun prev' ->
f prev' k v
in
X_map.String.fold aux smap init
let bind_map_smap f smap = bind_smap (X_map.String.map f smap)
let bind_map_list f lst = bind_list (List.map f lst)
let bind_map_ne_list : _ -> 'a X_list.Ne.t -> 'b X_list.Ne.t result = fun f lst -> bind_ne_list (X_list.Ne.map f lst)
let bind_iter_list : (_ -> unit result) -> _ list -> unit result = fun f lst ->
bind_map_list f lst >>? fun _ -> ok ()
let bind_location (x:_ Location.wrap) =
x.wrap_content >>? fun wrap_content ->
ok { x with wrap_content }
let bind_map_location f x = bind_location (Location.map f x)
let bind_fold_list f init lst =
let aux x y =
x >>? fun x ->
f x y
in
List.fold_left aux (ok init) lst
let bind_fold_map_list = fun f acc lst ->
let rec aux (acc , prev) f = function
| [] -> ok (acc , prev)
| hd :: tl ->
f acc hd >>? fun (acc' , hd') ->
aux (acc' , hd' :: prev) f tl
in
aux (acc , []) f lst >>? fun (_acc' , lst') ->
ok @@ List.rev lst'
let bind_fold_map_right_list = fun f acc lst ->
let rec aux (acc , prev) f = function
| [] -> ok (acc , prev)
| hd :: tl ->
f acc hd >>? fun (acc' , hd') ->
aux (acc' , hd' :: prev) f tl
in
aux (acc , []) f (List.rev lst) >>? fun (_acc' , lst') ->
ok lst'
let bind_fold_right_list f init lst =
let aux x y =
x >>? fun x ->
f x y
in
X_list.fold_right' aux (ok init) lst
let bind_find_map_list error f lst =
let rec aux lst =
match lst with
| [] -> fail error
| hd :: tl -> (
match f hd with
| Errors _ -> aux tl
| o -> o
)
in
aux lst
let bind_list_iter f lst =
let aux () y = f y in
bind_fold_list aux () lst
let bind_or (a, b) =
match a with
| Ok _ as o -> o
| _ -> b
let bind_lr (type a b) ((a : a result), (b:b result)) : [`Left of a | `Right of b] result =
match (a, b) with
| (Ok _ as o), _ -> apply (fun x -> `Left x) o
| _, (Ok _ as o) -> apply (fun x -> `Right x) o
| _, Errors b -> Errors b
let bind_lr_lazy (type a b) ((a : a result), (b:unit -> b result)) : [`Left of a | `Right of b] result =
match a with
| Ok _ as o -> apply (fun x -> `Left x) o
| _ -> (
match b() with
| Ok _ as o -> apply (fun x -> `Right x) o
| Errors b -> Errors b
)
let bind_and (a, b) =
a >>? fun a ->
b >>? fun b ->
ok (a, b)
let bind_pair = bind_and
let bind_map_pair f (a, b) =
bind_pair (f a, f b)
let generic_try err f =
try (
ok @@ f ()
) with _ -> fail err
let specific_try handler f =
try (
ok @@ f ()
) with exn -> fail ((handler ()) exn)
let sys_try f =
let handler () = function
| Sys_error str -> error (thunk "Sys_error") (fun () -> str)
| exn -> raise exn
in
specific_try handler f
let sys_command command =
sys_try (fun () -> Sys.command command) >>? function
| 0 -> ok ()
| n -> fail (fun () -> error (thunk "Nonzero return code") (fun () -> (string_of_int n)) ())
let trace_sequence f lst =
let lazy_map_force : 'a . (unit -> 'a) list -> (unit -> 'a list) = fun l ->
fun () ->
List.rev @@ List.rev_map (fun a -> a ()) l in
let rec aux acc_x acc_annotations = function
| hd :: tl -> (
match f hd with
(* TODO: what should we do with the annotations? *)
| Ok (x, annotations) -> aux (x :: acc_x) (lazy_map_force annotations :: acc_annotations) tl
| Errors _ as errs -> errs
)
| [] ->
let old_annotations () = List.map (fun a -> `List (a ())) @@ List.rev acc_annotations in
(* Builds a JSON annotation { "type": "list"; "content": [[…], …] } *)
let annotation = fun () -> `Assoc [("type", `String "list"); ("content", `List (old_annotations ()))]
in Ok (List.rev acc_x, [annotation]) in
aux [] lst
let json_of_error = J.to_string
let error_pp out (e : error) =
let open JSON_string_utils in
let e : J.t = (match e with `Assoc _ as e -> e) in
let message = e |> member "message" |> string in
let title = e |> member "title" |> string || "(no title)" in
let error_code = unit " " |^ (e |> member "error_code" |> int |> string_of_int) || "" in
Format.fprintf out "%s" (error_code ^ ": " ^ title ^ (unit ":" |^ message || ""))
let error_pp_short out (e : error) =
let open JSON_string_utils in
let e : J.t = (match e with `Assoc _ as e -> e) in
let title = e |> member "title" |> string || "(no title)" in
let error_code = unit " " |^ (e |> member "error_code" |> int |> string_of_int) || "" in
Format.fprintf out "%s" (error_code ^ ": " ^ title)
let errors_pp =
Format.pp_print_list
~pp_sep:Format.pp_print_newline
error_pp
let errors_pp_short =
Format.pp_print_list
~pp_sep:Format.pp_print_newline
error_pp_short
let pp_to_string pp () x =
Format.fprintf Format.str_formatter "%a" pp x ;
Format.flush_str_formatter ()
let errors_to_string = pp_to_string errors_pp
module Assert = struct
let assert_fail ?(msg="didn't fail") = function
| Ok _ -> simple_fail msg
| _ -> ok ()
let assert_true ?(msg="not true") = function
| true -> ok ()
| false -> simple_fail msg
let assert_equal ?msg expected actual =
assert_true ?msg (expected = actual)
let assert_equal_int ?msg expected actual =
let msg =
let default = Format.asprintf "Not equal int : expected %d, got %d" expected actual in
X_option.unopt ~default msg in
assert_equal ~msg expected actual
let assert_equal_bool ?msg expected actual =
let msg =
let default = Format.asprintf "Not equal bool : expected %b, got %b" expected actual in
X_option.unopt ~default msg in
assert_equal ~msg expected actual
let assert_none ?(msg="not a none") opt = match opt with
| None -> ok ()
| _ -> simple_fail msg
let assert_list_size ?(msg="lst doesn't have the right size") lst n =
assert_true ~msg List.(length lst = n)
let assert_list_empty ?(msg="lst isn't empty") lst =
assert_true ~msg List.(length lst = 0)
let assert_list_same_size ?(msg="lists don't have same size") a b =
assert_true ~msg List.(length a = length b)
let assert_list_size_2 ~msg = function
| [a;b] -> ok (a, b)
| _ -> simple_fail msg
let assert_list_size_1 ~msg = function
| [a] -> ok a
| _ -> simple_fail msg
end