1324 lines
38 KiB
OCaml
1324 lines
38 KiB
OCaml
(* Yoann Padioleau
|
|
*
|
|
* Copyright (C) 1998-2013 Yoann Padioleau
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
* version 2.1 as published by the Free Software Foundation, with the
|
|
* special exception on linking described in file license.txt.
|
|
*
|
|
* This library is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file
|
|
* license.txt for more details.
|
|
*)
|
|
(*###########################################################################*)
|
|
(* Prelude *)
|
|
(*###########################################################################*)
|
|
|
|
(*****************************************************************************)
|
|
(* Prelude *)
|
|
(*****************************************************************************)
|
|
|
|
(* The following functions should be in their respective sections but
|
|
* because some functions in some sections use functions in other
|
|
* sections, and because I don't want to take care of the order of
|
|
* those sections, of those dependencies, I put the functions causing
|
|
* dependency problem here. C is better than OCaml on this with the
|
|
* ability to declare prototypes, enabling some form of forward
|
|
* reference.
|
|
*)
|
|
|
|
let (+>) o f = f o
|
|
|
|
let spf = Printf.sprintf
|
|
|
|
exception Timeout
|
|
exception UnixExit of int
|
|
|
|
let rec drop n xs =
|
|
match (n,xs) with
|
|
| (0,_) -> xs
|
|
| (_,[]) -> failwith "drop: not enough"
|
|
| (n,x::xs) -> drop (n-1) xs
|
|
|
|
let take n xs =
|
|
let rec next n xs acc =
|
|
match (n,xs) with
|
|
| (0,_) -> List.rev acc
|
|
| (_,[]) -> failwith "Common.take: not enough"
|
|
| (n,x::xs) -> next (n-1) xs (x::acc) in
|
|
next n xs []
|
|
|
|
let rec enum_orig x n =
|
|
if x = n then [n] else x::enum_orig (x+1) n
|
|
|
|
let enum x n =
|
|
if not(x <= n)
|
|
then failwith (Printf.sprintf "bad values in enum, expect %d <= %d" x n);
|
|
let rec enum_aux acc x n =
|
|
if x = n then n::acc else enum_aux (x::acc) (x+1) n
|
|
in
|
|
List.rev (enum_aux [] x n)
|
|
|
|
let push v l =
|
|
l := v :: !l
|
|
|
|
|
|
let debugger = ref false
|
|
|
|
let unwind_protect f cleanup =
|
|
if !debugger then f () else
|
|
try f ()
|
|
with e -> begin cleanup e; raise e end
|
|
|
|
let finalize f cleanup =
|
|
(* bug: we can not just call f in debugger mode because
|
|
* this change the semantic of the program. I originally
|
|
* put this code below:
|
|
* if !debugger then f () else
|
|
* because I wanted some errors to pop-out to the top so I can
|
|
* debug them but because now I use save_excursion and finalize
|
|
* quite a lot this changes too much the semantic.
|
|
* TODO: maybe I should not use save_excursion so much ? maybe
|
|
* -debugger helps see code that I should refactor ?
|
|
*)
|
|
try
|
|
let res = f () in
|
|
cleanup ();
|
|
res
|
|
with e ->
|
|
cleanup ();
|
|
raise e
|
|
|
|
|
|
let (unlines: string list -> string) = fun s ->
|
|
(String.concat "\n" s) ^ "\n"
|
|
|
|
let (lines: string -> string list) = fun s ->
|
|
let rec lines_aux = function
|
|
| [] -> []
|
|
| [x] -> if x = "" then [] else [x]
|
|
| x::xs ->
|
|
x::lines_aux xs
|
|
in
|
|
Str.split_delim (Str.regexp "\n") s +> lines_aux
|
|
|
|
let save_excursion reference newv f =
|
|
let old = !reference in
|
|
reference := newv;
|
|
finalize f (fun _ -> reference := old;)
|
|
|
|
let memoized ?(use_cache=true) h k f =
|
|
if not use_cache
|
|
then f ()
|
|
else
|
|
try Hashtbl.find h k
|
|
with Not_found ->
|
|
let v = f () in
|
|
begin
|
|
Hashtbl.add h k v;
|
|
v
|
|
end
|
|
|
|
exception Todo
|
|
exception Impossible
|
|
|
|
exception Multi_found (* to be consistent with Not_found *)
|
|
|
|
let exn_to_s exn =
|
|
Printexc.to_string exn
|
|
|
|
(*###########################################################################*)
|
|
(* Basic features *)
|
|
(*###########################################################################*)
|
|
|
|
(*****************************************************************************)
|
|
(* Debugging/logging *)
|
|
(*****************************************************************************)
|
|
|
|
let pr s =
|
|
print_string s;
|
|
print_string "\n";
|
|
flush stdout
|
|
|
|
let pr2 s =
|
|
prerr_string s;
|
|
prerr_string "\n";
|
|
flush stderr
|
|
|
|
let pr_xxxxxxxxxxxxxxxxx () =
|
|
pr "-----------------------------------------------------------------------"
|
|
|
|
let pr2_xxxxxxxxxxxxxxxxx () =
|
|
pr2 "-----------------------------------------------------------------------"
|
|
|
|
let _already_printed = Hashtbl.create 101
|
|
let disable_pr2_once = ref false
|
|
|
|
let xxx_once f s =
|
|
if !disable_pr2_once then pr2 s
|
|
else
|
|
if not (Hashtbl.mem _already_printed s)
|
|
then begin
|
|
Hashtbl.add _already_printed s true;
|
|
f ("(ONCE) " ^ s);
|
|
end
|
|
|
|
let pr2_once s = xxx_once pr2 s
|
|
|
|
|
|
(* start of dumper.ml *)
|
|
|
|
(* Dump an OCaml value into a printable string.
|
|
* By Richard W.M. Jones (rich@annexia.org).
|
|
* dumper.ml 1.2 2005/02/06 12:38:21 rich Exp
|
|
*)
|
|
open Printf
|
|
open Obj
|
|
|
|
let rec dump2 r =
|
|
if is_int r then
|
|
string_of_int (magic r : int)
|
|
else ( (* Block. *)
|
|
let rec get_fields acc = function
|
|
| 0 -> acc
|
|
| n -> let n = n-1 in get_fields (field r n :: acc) n
|
|
in
|
|
let rec is_list r =
|
|
if is_int r then (
|
|
if (magic r : int) = 0 then true (* [] *)
|
|
else false
|
|
) else (
|
|
let s = size r and t = tag r in
|
|
if t = 0 && s = 2 then is_list (field r 1) (* h :: t *)
|
|
else false
|
|
)
|
|
in
|
|
let rec get_list r =
|
|
if is_int r then []
|
|
else let h = field r 0 and t = get_list (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 = size r and t = tag r in
|
|
|
|
(* From the tag, determine the type of block. *)
|
|
if is_list r then ( (* List. *)
|
|
let fields = get_list r in
|
|
"[" ^ String.concat "; " (List.map dump2 fields) ^ "]"
|
|
)
|
|
else if t = 0 then ( (* Tuple, array, record. *)
|
|
let fields = get_fields [] s in
|
|
"(" ^ String.concat ", " (List.map dump2 fields) ^ ")"
|
|
)
|
|
|
|
(* Note that [lazy_tag .. forward_tag] are < no_scan_tag. Not
|
|
* clear if very large constructed values could have the same
|
|
* tag. XXX *)
|
|
else if t = lazy_tag then opaque "lazy"
|
|
else if t = closure_tag then opaque "closure"
|
|
else if t = object_tag then ( (* Object. *)
|
|
let fields = get_fields [] s in
|
|
let clasz, 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 #" ^ dump2 id ^
|
|
" (" ^ String.concat ", " (List.map dump2 slots) ^ ")"
|
|
)
|
|
else if t = infix_tag then opaque "infix"
|
|
else if t = forward_tag then opaque "forward"
|
|
|
|
else if t < no_scan_tag then ( (* Constructed value. *)
|
|
let fields = get_fields [] s in
|
|
"Tag" ^ string_of_int t ^
|
|
" (" ^ String.concat ", " (List.map dump2 fields) ^ ")"
|
|
)
|
|
else if t = string_tag then (
|
|
"\"" ^ String.escaped (magic r : string) ^ "\""
|
|
)
|
|
else if t = double_tag then (
|
|
string_of_float (magic r : float)
|
|
)
|
|
else if t = abstract_tag then opaque "abstract"
|
|
else if t = custom_tag then opaque "custom"
|
|
else if t = final_tag then opaque "final"
|
|
else failwith ("dump: impossible tag (" ^ string_of_int t ^ ")")
|
|
)
|
|
|
|
let dump v = dump2 (repr v)
|
|
|
|
(* end of dumper.ml *)
|
|
|
|
(*
|
|
let (dump : 'a -> string) = fun x ->
|
|
Dumper.dump x
|
|
*)
|
|
|
|
let pr2_gen x = pr2 (dump x)
|
|
|
|
(*****************************************************************************)
|
|
(* Profiling *)
|
|
(*****************************************************************************)
|
|
|
|
type prof = ProfAll | ProfNone | ProfSome of string list
|
|
let profile = ref ProfNone
|
|
let show_trace_profile = ref false
|
|
|
|
let check_profile category =
|
|
match !profile with
|
|
| ProfAll -> true
|
|
| ProfNone -> false
|
|
| ProfSome l -> List.mem category l
|
|
|
|
let _profile_table = ref (Hashtbl.create 100)
|
|
|
|
let adjust_profile_entry category difftime =
|
|
let (xtime, xcount) =
|
|
(try Hashtbl.find !_profile_table category
|
|
with Not_found ->
|
|
let xtime = ref 0.0 in
|
|
let xcount = ref 0 in
|
|
Hashtbl.add !_profile_table category (xtime, xcount);
|
|
(xtime, xcount)
|
|
) in
|
|
xtime := !xtime +. difftime;
|
|
xcount := !xcount + 1;
|
|
()
|
|
|
|
let profile_start category = failwith "todo"
|
|
let profile_end category = failwith "todo"
|
|
|
|
|
|
(* subtil: don't forget to give all argumens to f, otherwise partial app
|
|
* and will profile nothing.
|
|
*
|
|
* todo: try also detect when complexity augment each time, so can
|
|
* detect the situation for a function gets worse and worse ?
|
|
*)
|
|
let profile_code category f =
|
|
if not (check_profile category)
|
|
then f ()
|
|
else begin
|
|
if !show_trace_profile then pr2 (spf "> %s" category);
|
|
let t = Unix.gettimeofday () in
|
|
let res, prefix =
|
|
try Some (f ()), ""
|
|
with Timeout -> None, "*"
|
|
in
|
|
let category = prefix ^ category in (* add a '*' to indicate timeout func *)
|
|
let t' = Unix.gettimeofday () in
|
|
|
|
if !show_trace_profile then pr2 (spf "< %s" category);
|
|
|
|
adjust_profile_entry category (t' -. t);
|
|
(match res with
|
|
| Some res -> res
|
|
| None -> raise Timeout
|
|
);
|
|
end
|
|
|
|
|
|
let _is_in_exclusif = ref (None: string option)
|
|
|
|
let profile_code_exclusif category f =
|
|
if not (check_profile category)
|
|
then f ()
|
|
else begin
|
|
|
|
match !_is_in_exclusif with
|
|
| Some s ->
|
|
failwith (spf "profile_code_exclusif: %s but already in %s " category s);
|
|
| None ->
|
|
_is_in_exclusif := (Some category);
|
|
finalize
|
|
(fun () ->
|
|
profile_code category f
|
|
)
|
|
(fun () ->
|
|
_is_in_exclusif := None
|
|
)
|
|
|
|
end
|
|
|
|
let profile_code_inside_exclusif_ok category f =
|
|
failwith "Todo"
|
|
|
|
|
|
let (with_open_stringbuf: (((string -> unit) * Buffer.t) -> unit) -> string) =
|
|
fun f ->
|
|
let buf = Buffer.create 1000 in
|
|
let pr s = Buffer.add_string buf (s ^ "\n") in
|
|
f (pr, buf);
|
|
Buffer.contents buf
|
|
|
|
|
|
(* todo: also put % ? also add % to see if coherent numbers *)
|
|
let profile_diagnostic () =
|
|
if !profile = ProfNone then "" else
|
|
let xs =
|
|
Hashtbl.fold (fun k v acc -> (k,v)::acc) !_profile_table []
|
|
+> List.sort (fun (k1, (t1,n1)) (k2, (t2,n2)) -> compare t2 t1)
|
|
in
|
|
with_open_stringbuf (fun (pr,_) ->
|
|
pr "---------------------";
|
|
pr "profiling result";
|
|
pr "---------------------";
|
|
xs +> List.iter (fun (k, (t,n)) ->
|
|
pr (Printf.sprintf "%-40s : %10.3f sec %10d count" k !t !n)
|
|
)
|
|
)
|
|
|
|
|
|
|
|
let report_if_take_time timethreshold s f =
|
|
let t = Unix.gettimeofday () in
|
|
let res = f () in
|
|
let t' = Unix.gettimeofday () in
|
|
if (t' -. t > float_of_int timethreshold)
|
|
then pr2 (Printf.sprintf "Note: processing took %7.1fs: %s" (t' -. t) s);
|
|
res
|
|
|
|
let profile_code2 category f =
|
|
profile_code category (fun () ->
|
|
if !profile = ProfAll
|
|
then pr2 ("starting: " ^ category);
|
|
let t = Unix.gettimeofday () in
|
|
let res = f () in
|
|
let t' = Unix.gettimeofday () in
|
|
if !profile = ProfAll
|
|
then pr2 (spf "ending: %s, %fs" category (t' -. t));
|
|
res
|
|
)
|
|
|
|
(*****************************************************************************)
|
|
(* Test *)
|
|
(*****************************************************************************)
|
|
|
|
(* See OUnit *)
|
|
|
|
(*****************************************************************************)
|
|
(* Persistence *)
|
|
(*****************************************************************************)
|
|
|
|
let get_value filename =
|
|
let chan = open_in filename in
|
|
let x = input_value chan in (* <=> Marshal.from_channel *)
|
|
(close_in chan; x)
|
|
|
|
let write_value valu filename =
|
|
let chan = open_out filename in
|
|
(output_value chan valu; (* <=> Marshal.to_channel *)
|
|
(* Marshal.to_channel chan valu [Marshal.Closures]; *)
|
|
close_out chan)
|
|
|
|
(*****************************************************************************)
|
|
(* Composition/Control *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Error managment *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Arguments/options and command line (cocci and acomment) *)
|
|
(*****************************************************************************)
|
|
|
|
(*
|
|
* todo? isn't unison or scott-mcpeak-lib-in-cil handles that kind of
|
|
* stuff better ? That is the need to localize command line argument
|
|
* while still being able to gathering them. Same for logging.
|
|
* Similiar to the type prof = PALL | PNONE | PSOME of string list.
|
|
* Same spirit of fine grain config in log4j ?
|
|
*
|
|
* todo? how mercurial/cvs/git manage command line options ? because they
|
|
* all have a kind of DSL around arguments with some common options,
|
|
* specific options, conventions, etc.
|
|
*
|
|
*
|
|
* todo? generate the corresponding noxxx options ?
|
|
* todo? generate list of options and show their value ?
|
|
*
|
|
* todo? make it possible to set this value via a config file ?
|
|
*
|
|
*
|
|
*)
|
|
|
|
type arg_spec_full = Arg.key * Arg.spec * Arg.doc
|
|
type cmdline_options = arg_spec_full list
|
|
|
|
(* the format is a list of triples:
|
|
* (title of section * (optional) explanation of sections * options)
|
|
*)
|
|
type options_with_title = string * string * arg_spec_full list
|
|
type cmdline_sections = options_with_title list
|
|
|
|
|
|
(* ---------------------------------------------------------------------- *)
|
|
|
|
(* now I use argv as I like at the call sites to show that
|
|
* this function internally use argv.
|
|
*)
|
|
let parse_options options usage_msg argv =
|
|
let args = ref [] in
|
|
(try
|
|
Arg.parse_argv argv options (fun file -> args := file::!args) usage_msg;
|
|
args := List.rev !args;
|
|
!args
|
|
with
|
|
| Arg.Bad msg -> Printf.eprintf "%s" msg; exit 2
|
|
| Arg.Help msg -> Printf.printf "%s" msg; exit 0
|
|
)
|
|
|
|
|
|
|
|
|
|
let usage usage_msg options =
|
|
Arg.usage (Arg.align options) usage_msg
|
|
|
|
|
|
(* for coccinelle *)
|
|
|
|
(* If you don't want the -help and --help that are appended by Arg.align *)
|
|
let arg_align2 xs =
|
|
Arg.align xs +> List.rev +> drop 2 +> List.rev
|
|
|
|
|
|
let short_usage usage_msg ~short_opt =
|
|
usage usage_msg short_opt
|
|
|
|
let long_usage usage_msg ~short_opt ~long_opt =
|
|
pr usage_msg;
|
|
pr "";
|
|
let all_options_with_title =
|
|
(("main options", "", short_opt)::long_opt) in
|
|
all_options_with_title +> List.iter
|
|
(fun (title, explanations, xs) ->
|
|
pr title;
|
|
pr_xxxxxxxxxxxxxxxxx();
|
|
if explanations <> ""
|
|
then begin pr explanations; pr "" end;
|
|
arg_align2 xs +> List.iter (fun (key,action,s) ->
|
|
pr (" " ^ key ^ s)
|
|
);
|
|
pr "";
|
|
);
|
|
()
|
|
|
|
|
|
(* copy paste of Arg.parse. Don't want the default -help msg *)
|
|
let arg_parse2 l msg short_usage_fun =
|
|
let args = ref [] in
|
|
let f = (fun file -> args := file::!args) in
|
|
let l = Arg.align l in
|
|
(try begin
|
|
Arg.parse_argv Sys.argv l f msg;
|
|
args := List.rev !args;
|
|
!args
|
|
end
|
|
with
|
|
| Arg.Bad msg -> (* eprintf "%s" msg; exit 2; *)
|
|
let xs = lines msg in
|
|
(* take only head, it's where the error msg is *)
|
|
pr2 (List.hd xs);
|
|
short_usage_fun();
|
|
raise (UnixExit (2))
|
|
| Arg.Help msg -> (* printf "%s" msg; exit 0; *)
|
|
raise Impossible (* -help is specified in speclist *)
|
|
)
|
|
|
|
|
|
(* ---------------------------------------------------------------------- *)
|
|
|
|
type flag_spec = Arg.key * Arg.spec * Arg.doc
|
|
type action_spec = Arg.key * Arg.doc * action_func
|
|
and action_func = (string list -> unit)
|
|
|
|
type cmdline_actions = action_spec list
|
|
exception WrongNumberOfArguments
|
|
|
|
let options_of_actions action_ref actions =
|
|
actions +> List.map (fun (key, doc, _func) ->
|
|
(key, (Arg.Unit (fun () -> action_ref := key)), doc)
|
|
)
|
|
|
|
let (action_list: cmdline_actions -> Arg.key list) = fun xs ->
|
|
List.map (fun (a,b,c) -> a) xs
|
|
|
|
let (do_action: Arg.key -> string list (* args *) -> cmdline_actions -> unit) =
|
|
fun key args xs ->
|
|
let assoc = xs +> List.map (fun (a,b,c) -> (a,c)) in
|
|
let action_func = List.assoc key assoc in
|
|
action_func args
|
|
|
|
|
|
(* todo? if have a function with default argument ? would like a
|
|
* mk_action_0_or_1_arg ?
|
|
*)
|
|
|
|
let mk_action_0_arg f =
|
|
(function
|
|
| [] -> f ()
|
|
| _ -> raise WrongNumberOfArguments
|
|
)
|
|
|
|
let mk_action_1_arg f =
|
|
(function
|
|
| [file] -> f file
|
|
| _ -> raise WrongNumberOfArguments
|
|
)
|
|
|
|
let mk_action_2_arg f =
|
|
(function
|
|
| [file1;file2] -> f file1 file2
|
|
| _ -> raise WrongNumberOfArguments
|
|
)
|
|
|
|
let mk_action_3_arg f =
|
|
(function
|
|
| [file1;file2;file3] -> f file1 file2 file3
|
|
| _ -> raise WrongNumberOfArguments
|
|
)
|
|
|
|
let mk_action_4_arg f =
|
|
(function
|
|
| [file1;file2;file3;file4] -> f file1 file2 file3 file4
|
|
| _ -> raise WrongNumberOfArguments
|
|
)
|
|
|
|
let mk_action_n_arg f = f
|
|
|
|
(*****************************************************************************)
|
|
(* Equality *)
|
|
(*****************************************************************************)
|
|
let (=|=) : int -> int -> bool = (=)
|
|
let (=<=) : char -> char -> bool = (=)
|
|
let (=$=) : string -> string -> bool = (=)
|
|
let (=:=) : bool -> bool -> bool = (=)
|
|
|
|
let (=*=) = (=)
|
|
|
|
(*###########################################################################*)
|
|
(* Basic types *)
|
|
(*###########################################################################*)
|
|
|
|
(*****************************************************************************)
|
|
(* Bool *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Char *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Num *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Tuples *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Maybe *)
|
|
(*****************************************************************************)
|
|
|
|
(* type 'a maybe = Just of 'a | None *)
|
|
let (>>=) m1 m2 =
|
|
match m1 with
|
|
| None -> None
|
|
| Some x -> m2 x
|
|
|
|
(*
|
|
(*http://roscidus.com/blog/blog/2013/10/13/ocaml-tips/#handling-option-types*)
|
|
let (|?) maybe default =
|
|
match maybe with
|
|
| Some v -> v
|
|
| None -> Lazy.force default
|
|
*)
|
|
|
|
let map_opt f = function
|
|
| None -> None
|
|
| Some x -> Some (f x)
|
|
|
|
let do_option f = function
|
|
| None -> ()
|
|
| Some x -> f x
|
|
let opt = do_option
|
|
|
|
(* not sure why but can't use let (?:) a b = ... then at use time ocaml yells*)
|
|
let (|||) a b =
|
|
match a with
|
|
| Some x -> x
|
|
| None -> b
|
|
|
|
type ('a,'b) either = Left of 'a | Right of 'b
|
|
(* with sexp *)
|
|
type ('a, 'b, 'c) either3 = Left3 of 'a | Middle3 of 'b | Right3 of 'c
|
|
(* with sexp *)
|
|
|
|
|
|
let partition_either f l =
|
|
let rec part_either left right = function
|
|
| [] -> (List.rev left, List.rev right)
|
|
| x :: l ->
|
|
(match f x with
|
|
| Left e -> part_either (e :: left) right l
|
|
| Right e -> part_either left (e :: right) l) in
|
|
part_either [] [] l
|
|
|
|
let partition_either3 f l =
|
|
let rec part_either left middle right = function
|
|
| [] -> (List.rev left, List.rev middle, List.rev right)
|
|
| x :: l ->
|
|
(match f x with
|
|
| Left3 e -> part_either (e :: left) middle right l
|
|
| Middle3 e -> part_either left (e :: middle) right l
|
|
| Right3 e -> part_either left middle (e :: right) l) in
|
|
part_either [] [] [] l
|
|
|
|
let rec filter_some = function
|
|
| [] -> []
|
|
| None :: l -> filter_some l
|
|
| Some e :: l -> e :: filter_some l
|
|
|
|
let map_filter f xs = xs +> List.map f +> filter_some
|
|
|
|
let rec find_some_opt p = function
|
|
| [] -> None
|
|
| x :: l ->
|
|
match p x with
|
|
| Some v -> Some v
|
|
| None -> find_some_opt p l
|
|
|
|
let find_some p xs =
|
|
match find_some_opt p xs with
|
|
| None -> raise Not_found
|
|
| Some x -> x
|
|
|
|
let rec find_opt f xs =
|
|
find_some_opt (fun x -> if f x then Some x else None) xs
|
|
|
|
|
|
(*****************************************************************************)
|
|
(* Regexp, can also use PCRE *)
|
|
(*****************************************************************************)
|
|
|
|
let (matched: int -> string -> string) = fun i s ->
|
|
Str.matched_group i s
|
|
|
|
let matched1 = fun s -> matched 1 s
|
|
let matched2 = fun s -> (matched 1 s, matched 2 s)
|
|
let matched3 = fun s -> (matched 1 s, matched 2 s, matched 3 s)
|
|
let matched4 = fun s -> (matched 1 s, matched 2 s, matched 3 s, matched 4 s)
|
|
let matched5 = fun s -> (matched 1 s, matched 2 s, matched 3 s, matched 4 s, matched 5 s)
|
|
let matched6 = fun s -> (matched 1 s, matched 2 s, matched 3 s, matched 4 s, matched 5 s, matched 6 s)
|
|
let matched7 = fun s -> (matched 1 s, matched 2 s, matched 3 s, matched 4 s, matched 5 s, matched 6 s, matched 7 s)
|
|
|
|
let _memo_compiled_regexp = Hashtbl.create 101
|
|
let candidate_match_func s re =
|
|
(* old: Str.string_match (Str.regexp re) s 0 *)
|
|
let compile_re =
|
|
memoized _memo_compiled_regexp re (fun () -> Str.regexp re)
|
|
in
|
|
Str.string_match compile_re s 0
|
|
|
|
let match_func s re =
|
|
profile_code "Common.=~" (fun () -> candidate_match_func s re)
|
|
|
|
let (=~) s re =
|
|
match_func s re
|
|
|
|
let split sep s = Str.split (Str.regexp sep) s
|
|
|
|
let join sep xs = String.concat sep xs
|
|
|
|
(*****************************************************************************)
|
|
(* Strings *)
|
|
(*****************************************************************************)
|
|
|
|
(* ruby *)
|
|
let i_to_s = string_of_int
|
|
let s_to_i = int_of_string
|
|
|
|
let null_string s =
|
|
s =$= ""
|
|
|
|
(*****************************************************************************)
|
|
(* Filenames *)
|
|
(*****************************************************************************)
|
|
|
|
type filename = string (* TODO could check that exist :) type sux *)
|
|
(* with sexp *)
|
|
type dirname = string (* TODO could check that exist :) type sux *)
|
|
(* with sexp *)
|
|
|
|
(* file or dir *)
|
|
type path = string
|
|
|
|
let chop_dirsymbol = function
|
|
| s when s =~ "\\(.*\\)/$" -> matched1 s
|
|
| s -> s
|
|
|
|
(* pre: prj_path must not contain regexp symbol *)
|
|
let filename_without_leading_path prj_path s =
|
|
let prj_path = chop_dirsymbol prj_path in
|
|
if s =$= prj_path
|
|
then "."
|
|
else
|
|
if s =~ ("^" ^ prj_path ^ "/\\(.*\\)$")
|
|
then matched1 s
|
|
else
|
|
failwith
|
|
(spf "cant find filename_without_project_path: %s %s" prj_path s)
|
|
|
|
let readable ~root s =
|
|
filename_without_leading_path root s
|
|
|
|
let is_directory file =
|
|
(Unix.stat file).Unix.st_kind =*= Unix.S_DIR
|
|
|
|
(*****************************************************************************)
|
|
(* Dates *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Lines/words/strings *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Process/Files *)
|
|
(*****************************************************************************)
|
|
|
|
let command2 s = ignore(Sys.command s)
|
|
|
|
exception CmdError of Unix.process_status * string
|
|
|
|
let process_output_to_list2 ?(verbose=false) command =
|
|
let chan = Unix.open_process_in command in
|
|
let res = ref ([] : string list) in
|
|
let rec process_otl_aux () =
|
|
let e = input_line chan in
|
|
res := e::!res;
|
|
if verbose then pr2 e;
|
|
process_otl_aux() in
|
|
try process_otl_aux ()
|
|
with End_of_file ->
|
|
let stat = Unix.close_process_in chan in (List.rev !res,stat)
|
|
|
|
let cmd_to_list ?verbose command =
|
|
let (l,exit_status) = process_output_to_list2 ?verbose command in
|
|
match exit_status with
|
|
| Unix.WEXITED 0 -> l
|
|
| _ -> raise (CmdError (exit_status,
|
|
(spf "CMD = %s, RESULT = %s"
|
|
command (String.concat "\n" l))))
|
|
|
|
let cmd_to_list_and_status = process_output_to_list2
|
|
|
|
|
|
(* tail recursive efficient version *)
|
|
let cat file =
|
|
let chan = open_in file in
|
|
let rec cat_aux acc () =
|
|
(* cant do input_line chan::aux() cos ocaml eval from right to left ! *)
|
|
let (b, l) = try (true, input_line chan) with End_of_file -> (false, "") in
|
|
if b
|
|
then cat_aux (l::acc) ()
|
|
else acc
|
|
in
|
|
cat_aux [] () +> List.rev +> (fun x -> close_in chan; x)
|
|
|
|
let read_file file =
|
|
let ic = open_in file in
|
|
let size = in_channel_length ic in
|
|
let buf = Bytes.create size in
|
|
really_input ic buf 0 size;
|
|
close_in ic;
|
|
buf
|
|
|
|
let write_file ~file s =
|
|
let chan = open_out file in
|
|
(output_string chan s; close_out chan)
|
|
|
|
(* could be in control section too *)
|
|
|
|
let filemtime file =
|
|
(Unix.stat file).Unix.st_mtime
|
|
|
|
(*
|
|
Using an external C functions complicates the linking process of
|
|
programs using commons/. Thus, I replaced realpath() with an OCaml-only
|
|
similar functions fullpath().
|
|
|
|
external c_realpath: string -> string option = "caml_realpath"
|
|
|
|
let realpath2 path =
|
|
match c_realpath path with
|
|
| Some s -> s
|
|
| None -> failwith (spf "problem with realpath on %s" path)
|
|
|
|
let realpath2 path =
|
|
let stat = Unix.stat path in
|
|
let dir, suffix =
|
|
match stat.Unix.st_kind with
|
|
| Unix.S_DIR -> path, ""
|
|
| _ -> Filename.dirname path, Filename.basename path
|
|
in
|
|
|
|
let oldpwd = Sys.getcwd () in
|
|
Sys.chdir dir;
|
|
let realpath_dir = Sys.getcwd () in
|
|
Sys.chdir oldpwd;
|
|
Filename.concat realpath_dir suffix
|
|
|
|
let realpath path =
|
|
profile_code "Common.realpath" (fun () -> realpath2 path)
|
|
*)
|
|
|
|
let fullpath file =
|
|
if not (Sys.file_exists file)
|
|
then failwith (spf "fullpath: file %s does not exist" file);
|
|
let dir, base =
|
|
if Sys.is_directory file
|
|
then file, None
|
|
else Filename.dirname file, Some (Filename.basename file)
|
|
in
|
|
let old = Sys.getcwd () in
|
|
Sys.chdir dir;
|
|
let here = Sys.getcwd () in
|
|
Sys.chdir old;
|
|
match base with
|
|
| None -> here
|
|
| Some x -> Filename.concat here x
|
|
|
|
(* Why a use_cache argument ? because sometimes want disable it but dont
|
|
* want put the cache_computation funcall in comment, so just easier to
|
|
* pass this extra option.
|
|
*)
|
|
let cache_computation2 ?(verbose=false) ?(use_cache=true) file ext_cache f =
|
|
if not use_cache
|
|
then f ()
|
|
else begin
|
|
if not (Sys.file_exists file)
|
|
then begin
|
|
pr2 ("WARNING: cache_computation: can't find file " ^ file);
|
|
pr2 ("defaulting to calling the function");
|
|
f ()
|
|
end else begin
|
|
let file_cache = (file ^ ext_cache) in
|
|
if Sys.file_exists file_cache &&
|
|
filemtime file_cache >= filemtime file
|
|
then begin
|
|
if verbose then pr2 ("using cache: " ^ file_cache);
|
|
get_value file_cache
|
|
end
|
|
else begin
|
|
let res = f () in
|
|
write_value res file_cache;
|
|
res
|
|
end
|
|
end
|
|
end
|
|
let cache_computation ?verbose ?use_cache a b c =
|
|
profile_code "Common.cache_computation" (fun () ->
|
|
cache_computation2 ?verbose ?use_cache a b c)
|
|
|
|
|
|
(* emacs/lisp inspiration (eric cooper and yaron minsky use that too) *)
|
|
let (with_open_outfile: filename -> (((string -> unit) * out_channel) -> 'a) -> 'a) =
|
|
fun file f ->
|
|
let chan = open_out file in
|
|
let pr s = output_string chan s in
|
|
unwind_protect (fun () ->
|
|
let res = f (pr, chan) in
|
|
close_out chan;
|
|
res)
|
|
(fun e -> close_out chan)
|
|
|
|
let (with_open_infile: filename -> ((in_channel) -> 'a) -> 'a) = fun file f ->
|
|
let chan = open_in file in
|
|
unwind_protect (fun () ->
|
|
let res = f chan in
|
|
close_in chan;
|
|
res)
|
|
(fun e -> close_in chan)
|
|
|
|
(* now in prelude:
|
|
* exception Timeout
|
|
*)
|
|
|
|
(* it seems that the toplevel block such signals, even with this explicit
|
|
* command :(
|
|
* let _ = Unix.sigprocmask Unix.SIG_UNBLOCK [Sys.sigalrm]
|
|
*)
|
|
|
|
(* could be in Control section *)
|
|
|
|
(* subtil: have to make sure that timeout is not intercepted before here, so
|
|
* avoid exn handle such as try (...) with _ -> cos timeout will not bubble up
|
|
* enough. In such case, add a case before such as
|
|
* with Timeout -> raise Timeout | _ -> ...
|
|
*
|
|
* question: can we have a signal and so exn when in a exn handler ?
|
|
*)
|
|
let timeout_function ?(verbose=false) timeoutval = fun f ->
|
|
try
|
|
begin
|
|
Sys.set_signal Sys.sigalrm (Sys.Signal_handle (fun _ -> raise Timeout ));
|
|
ignore(Unix.alarm timeoutval);
|
|
let x = f () in
|
|
ignore(Unix.alarm 0);
|
|
x
|
|
end
|
|
with Timeout ->
|
|
begin
|
|
if verbose then pr2 "timeout (we abort)";
|
|
raise Timeout;
|
|
end
|
|
| e ->
|
|
(* subtil: important to disable the alarm before relaunching the exn,
|
|
* otherwise the alarm is still running.
|
|
*
|
|
* robust?: and if alarm launched after the log (...) ?
|
|
* Maybe signals are disabled when process an exception handler ?
|
|
*)
|
|
begin
|
|
ignore(Unix.alarm 0);
|
|
(* log ("exn while in transaction (we abort too, even if ...) = " ^
|
|
Printexc.to_string e);
|
|
*)
|
|
if verbose then pr2 "exn while in timeout_function";
|
|
raise e
|
|
end
|
|
|
|
(* creation of tmp files, a la gcc *)
|
|
|
|
let _temp_files_created = ref ([] : filename list)
|
|
|
|
(* ex: new_temp_file "cocci" ".c" will give "/tmp/cocci-3252-434465.c" *)
|
|
let new_temp_file prefix suffix =
|
|
let processid = i_to_s (Unix.getpid ()) in
|
|
let tmp_file = Filename.temp_file (prefix ^ "-" ^ processid ^ "-") suffix in
|
|
push tmp_file _temp_files_created;
|
|
tmp_file
|
|
|
|
let save_tmp_files = ref false
|
|
let erase_temp_files () =
|
|
if not !save_tmp_files then begin
|
|
!_temp_files_created +> List.iter (fun s ->
|
|
(* pr2 ("erasing: " ^ s); *)
|
|
command2 ("rm -f " ^ s)
|
|
);
|
|
_temp_files_created := []
|
|
end
|
|
|
|
let erase_this_temp_file f =
|
|
if not !save_tmp_files then begin
|
|
_temp_files_created :=
|
|
List.filter (function x -> not (x =$= f)) !_temp_files_created;
|
|
command2 ("rm -f " ^ f)
|
|
end
|
|
|
|
(*###########################################################################*)
|
|
(* Collection-like types *)
|
|
(*###########################################################################*)
|
|
|
|
(*****************************************************************************)
|
|
(* List *)
|
|
(*****************************************************************************)
|
|
|
|
let exclude p xs =
|
|
List.filter (fun x -> not (p x)) xs
|
|
|
|
let rec (span: ('a -> bool) -> 'a list -> 'a list * 'a list) =
|
|
fun p -> function
|
|
| [] -> ([], [])
|
|
| x::xs ->
|
|
if p x then
|
|
let (l1, l2) = span p xs in
|
|
(x::l1, l2)
|
|
else ([], x::xs)
|
|
|
|
let rec take_safe n xs =
|
|
match (n,xs) with
|
|
| (0,_) -> []
|
|
| (_,[]) -> []
|
|
| (n,x::xs) -> x::take_safe (n-1) xs
|
|
|
|
let group_by f xs =
|
|
(* use Hashtbl.find_all property *)
|
|
let h = Hashtbl.create 101 in
|
|
|
|
(* could use Set *)
|
|
let hkeys = Hashtbl.create 101 in
|
|
|
|
xs |> List.iter (fun x ->
|
|
let k = f x in
|
|
Hashtbl.replace hkeys k true;
|
|
Hashtbl.add h k x
|
|
);
|
|
Hashtbl.fold (fun k _ acc -> (k, Hashtbl.find_all h k)::acc) hkeys []
|
|
|
|
let group_by_multi fkeys xs =
|
|
(* use Hashtbl.find_all property *)
|
|
let h = Hashtbl.create 101 in
|
|
|
|
(* could use Set *)
|
|
let hkeys = Hashtbl.create 101 in
|
|
|
|
xs |> List.iter (fun x ->
|
|
let ks = fkeys x in
|
|
ks |> List.iter (fun k ->
|
|
Hashtbl.replace hkeys k true;
|
|
Hashtbl.add h k x;
|
|
)
|
|
);
|
|
Hashtbl.fold (fun k _ acc -> (k, Hashtbl.find_all h k)::acc) hkeys []
|
|
|
|
|
|
(* you should really use group_assoc_bykey_eff *)
|
|
let rec group_by_mapped_key fkey l =
|
|
match l with
|
|
| [] -> []
|
|
| x::xs ->
|
|
let k = fkey x in
|
|
let (xs1,xs2) = List.partition (fun x' -> let k2 = fkey x' in k=*=k2) xs
|
|
in
|
|
(k, (x::xs1))::(group_by_mapped_key fkey xs2)
|
|
|
|
let rec zip xs ys =
|
|
match (xs,ys) with
|
|
| ([],[]) -> []
|
|
| ([],_) -> failwith "zip: not same length"
|
|
| (_,[]) -> failwith "zip: not same length"
|
|
| (x::xs,y::ys) -> (x,y)::zip xs ys
|
|
|
|
let null xs =
|
|
match xs with [] -> true | _ -> false
|
|
|
|
let index_list xs =
|
|
if null xs then [] (* enum 0 (-1) generate an exception *)
|
|
else zip xs (enum 0 ((List.length xs) -1))
|
|
|
|
let index_list_0 xs = index_list xs
|
|
|
|
let index_list_1 xs =
|
|
xs +> index_list +> List.map (fun (x,i) -> x, i+1)
|
|
|
|
let sort_prof a b =
|
|
profile_code "Common.sort_by_xxx" (fun () -> List.sort a b)
|
|
|
|
type order = HighFirst | LowFirst
|
|
let compare_order order a b =
|
|
match order with
|
|
| HighFirst -> compare b a
|
|
| LowFirst -> compare a b
|
|
|
|
let sort_by_val_highfirst xs =
|
|
sort_prof (fun (k1,v1) (k2,v2) -> compare v2 v1) xs
|
|
let sort_by_val_lowfirst xs =
|
|
sort_prof (fun (k1,v1) (k2,v2) -> compare v1 v2) xs
|
|
|
|
let sort_by_key_highfirst xs =
|
|
sort_prof (fun (k1,v1) (k2,v2) -> compare k2 k1) xs
|
|
let sort_by_key_lowfirst xs =
|
|
sort_prof (fun (k1,v1) (k2,v2) -> compare k1 k2) xs
|
|
|
|
(*****************************************************************************)
|
|
(* Assoc *)
|
|
(*****************************************************************************)
|
|
type ('a, 'b) assoc = ('a * 'b) list
|
|
|
|
(*****************************************************************************)
|
|
(* Arrays *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Matrix *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Set. Have a look too at set*.mli *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Hash *)
|
|
(*****************************************************************************)
|
|
|
|
let hash_to_list h =
|
|
Hashtbl.fold (fun k v acc -> (k,v)::acc) h []
|
|
+> List.sort compare
|
|
|
|
let hash_of_list xs =
|
|
let h = Hashtbl.create 101 in
|
|
xs +> List.iter (fun (k, v) -> Hashtbl.replace h k v);
|
|
h
|
|
|
|
|
|
(*****************************************************************************)
|
|
(* Hash sets *)
|
|
(*****************************************************************************)
|
|
|
|
type 'a hashset = ('a, bool) Hashtbl.t
|
|
(* with sexp *)
|
|
|
|
let hashset_to_list h =
|
|
hash_to_list h +> List.map fst
|
|
|
|
let hashset_of_list xs =
|
|
xs +> List.map (fun x -> x, true) +> hash_of_list
|
|
|
|
let hkeys h =
|
|
let hkey = Hashtbl.create 101 in
|
|
h +> Hashtbl.iter (fun k v -> Hashtbl.replace hkey k true);
|
|
hashset_to_list hkey
|
|
|
|
let group_assoc_bykey_eff2 xs =
|
|
let h = Hashtbl.create 101 in
|
|
xs +> List.iter (fun (k, v) -> Hashtbl.add h k v);
|
|
let keys = hkeys h in
|
|
keys +> List.map (fun k -> k, Hashtbl.find_all h k)
|
|
|
|
let group_assoc_bykey_eff xs =
|
|
profile_code "Common.group_assoc_bykey_eff" (fun () ->
|
|
group_assoc_bykey_eff2 xs)
|
|
|
|
(*****************************************************************************)
|
|
(* Stack *)
|
|
(*****************************************************************************)
|
|
|
|
type 'a stack = 'a list
|
|
(* with sexp *)
|
|
|
|
(*****************************************************************************)
|
|
(* Tree *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Graph. Have a look too at Ograph_*.mli *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Generic op *)
|
|
(*****************************************************************************)
|
|
let sort xs = List.sort Pervasives.compare xs
|
|
|
|
(*###########################################################################*)
|
|
(* Misc functions *)
|
|
(*###########################################################################*)
|
|
|
|
(*###########################################################################*)
|
|
(* Postlude *)
|
|
(*###########################################################################*)
|
|
|
|
(*****************************************************************************)
|
|
(* Flags and actions *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Postlude *)
|
|
(*****************************************************************************)
|
|
|
|
(*****************************************************************************)
|
|
(* Misc *)
|
|
(*****************************************************************************)
|
|
|
|
|
|
|
|
(* now in prelude: exception UnixExit of int *)
|
|
let exn_to_real_unixexit f =
|
|
try f ()
|
|
with UnixExit x -> exit x
|
|
|
|
let pp_do_in_zero_box f = Format.open_box 0; f (); Format.close_box ()
|
|
|
|
|
|
let main_boilerplate f =
|
|
if not (!Sys.interactive) then
|
|
exn_to_real_unixexit (fun () ->
|
|
|
|
Sys.set_signal Sys.sigint (Sys.Signal_handle (fun _ ->
|
|
pr2 "C-c intercepted, will do some cleaning before exiting";
|
|
(* But if do some try ... with e -> and if do not reraise the exn,
|
|
* the bubble never goes at top and so I cant really C-c.
|
|
*
|
|
* A solution would be to not raise, but do the erase_temp_file in the
|
|
* syshandler, here, and then exit.
|
|
* The current solution is to not do some wild try ... with e
|
|
* by having in the exn handler a case: UnixExit x -> raise ... | e ->
|
|
*)
|
|
Sys.set_signal Sys.sigint Sys.Signal_default;
|
|
raise (UnixExit (-1))
|
|
));
|
|
|
|
(* The finalize below makes it tedious to go back to exn when use
|
|
* 'back' in the debugger. Hence this special case. But the
|
|
* Common.debugger will be set in main(), so too late, so
|
|
* have to be quicker
|
|
*)
|
|
if Sys.argv +> Array.to_list +> List.exists (fun x -> x =$= "-debugger")
|
|
then debugger := true;
|
|
|
|
finalize (fun ()->
|
|
pp_do_in_zero_box (fun () ->
|
|
try
|
|
f (); (* <---- here it is *)
|
|
with Unix.Unix_error (e, fm, argm) ->
|
|
pr2 (spf "exn Unix_error: %s %s %s\n"
|
|
(Unix.error_message e) fm argm);
|
|
raise (Unix.Unix_error (e, fm, argm))
|
|
))
|
|
(fun()->
|
|
if !profile <> ProfNone
|
|
then begin
|
|
pr2 (profile_diagnostic ());
|
|
Gc.print_stat stderr;
|
|
end;
|
|
erase_temp_files ();
|
|
)
|
|
)
|
|
(* let _ = if not !Sys.interactive then (main ()) *)
|
|
|
|
let follow_symlinks = ref false
|
|
|
|
let arg_symlink () =
|
|
if !follow_symlinks
|
|
then " -L "
|
|
else ""
|
|
|
|
let grep_dash_v_str =
|
|
"| grep -v /.hg/ |grep -v /CVS/ | grep -v /.git/ |grep -v /_darcs/" ^
|
|
"| grep -v /.svn/ | grep -v .git_annot | grep -v .marshall"
|
|
|
|
let files_of_dir_or_files_no_vcs_nofilter xs =
|
|
xs +> List.map (fun x ->
|
|
if is_directory x
|
|
then
|
|
(* todo: should escape x *)
|
|
let cmd = (spf "find %s '%s' -noleaf -type f %s"
|
|
(arg_symlink()) x grep_dash_v_str) in
|
|
let (xs, status) =
|
|
cmd_to_list_and_status cmd in
|
|
(match status with
|
|
| Unix.WEXITED 0 -> xs
|
|
| _ -> raise (CmdError (status,
|
|
(spf "CMD = %s, RESULT = %s"
|
|
cmd (String.concat "\n" xs))))
|
|
)
|
|
else [x]
|
|
) +> List.concat
|
|
|
|
|
|
(*****************************************************************************)
|
|
(* Maps *)
|
|
(*****************************************************************************)
|
|
|
|
module SMap = Map.Make (String)
|
|
type 'a smap = 'a SMap.t
|