371 lines
10 KiB
OCaml
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
|