|
|
|
(*
|
|
|
|
* Copyright (c) 2018-present, Facebook, Inc.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
|
|
|
*)
|
|
|
|
|
|
|
|
(** Configuration options *)
|
|
|
|
|
|
|
|
(** Extension of Cmdliner supporting lighter-weight option definition *)
|
|
|
|
module Cmdliner : sig
|
|
|
|
include module type of Cmdliner
|
|
|
|
|
|
|
|
val mk : default:'a -> 'a Term.t -> 'a ref
|
|
|
|
(** [mk ~default term] is a ref which, after [parse] is called, contains
|
|
|
|
the value of the command line option specified by [term]. *)
|
|
|
|
|
|
|
|
val parse : Term.info -> (unit -> unit Term.ret) -> unit
|
|
|
|
(** [parse info validate] parses the command line according to the options
|
|
|
|
declared by calls to [mk], using manual and version [info], and
|
|
|
|
calling [validate] to check usage constraints not expressible in the
|
|
|
|
[Term] language. *)
|
|
|
|
end = struct
|
|
|
|
include Cmdliner
|
|
|
|
|
|
|
|
(** existential package of a Term and a setter for a ref to receive the
|
|
|
|
parsed value *)
|
|
|
|
type arg = Arg : 'a Term.t * ('a -> unit) -> arg
|
|
|
|
|
|
|
|
(** convert a list of arg packages to a term for the tuple of all the arg
|
|
|
|
terms, and apply it to a function that sets all the receiver refs *)
|
|
|
|
let tuple args =
|
|
|
|
let pair (Arg (trm_x, set_x)) (Arg (trm_y, set_y)) =
|
|
|
|
let trm_xy = Term.(const (fun a b -> (a, b)) $ trm_x $ trm_y) in
|
|
|
|
let set_xy (a, b) = set_x a ; set_y b in
|
|
|
|
Arg (trm_xy, set_xy)
|
|
|
|
in
|
|
|
|
let init = Arg (Term.const (), fun () -> ()) in
|
|
|
|
let (Arg (trm, set)) = List.fold_right ~f:pair args ~init in
|
|
|
|
Term.app (Term.const set) trm
|
|
|
|
|
|
|
|
let args : arg list ref = ref []
|
|
|
|
|
|
|
|
let mk ~default arg =
|
|
|
|
let var = ref default in
|
|
|
|
let set x = var := x in
|
|
|
|
args := Arg (arg, set) :: !args ;
|
|
|
|
var
|
|
|
|
|
|
|
|
let parse info validate =
|
|
|
|
match Term.eval (Term.(ret (const validate $ tuple !args)), info) with
|
|
|
|
| `Ok () -> ()
|
|
|
|
| `Error _ -> Caml.exit 1
|
|
|
|
| `Help | `Version -> Caml.exit 0
|
|
|
|
end
|
|
|
|
|
|
|
|
open Cmdliner
|
|
|
|
|
|
|
|
let compile_only =
|
|
|
|
let default = false in
|
|
|
|
mk ~default Arg.(value & flag & info ["c"; "compile-only"])
|
|
|
|
|
|
|
|
let input =
|
|
|
|
mk ~default:""
|
|
|
|
Arg.(required & pos ~rev:true 0 (some string) None & info [])
|
|
|
|
|
|
|
|
let output =
|
|
|
|
let default = None in
|
|
|
|
mk ~default Arg.(value & opt (some string) default & info ["o"; "output"])
|
|
|
|
|
|
|
|
let trace =
|
|
|
|
let default = Map.empty (module String) in
|
|
|
|
let parse s =
|
|
|
|
try
|
|
|
|
let index_from s i =
|
|
|
|
Option.merge ~f:min
|
|
|
|
(String.index_from s i '+')
|
|
|
|
(String.index_from s i '-')
|
|
|
|
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
|
|
|
|
List.fold parts ~init:default ~f:(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} =
|
|
|
|
match Map.find m mod_name with
|
|
|
|
| Some c -> c
|
|
|
|
| None -> {trace_mod= None; trace_funs= default}
|
|
|
|
in
|
|
|
|
Map.set m ~key:mod_name
|
|
|
|
~data:
|
|
|
|
{ trace_mod
|
|
|
|
; trace_funs=
|
|
|
|
Map.set trace_funs ~key:fun_name ~data:enabled }
|
|
|
|
| mod_name, None, enabled ->
|
|
|
|
Map.set m ~key:mod_name
|
|
|
|
~data:{trace_mod= Some enabled; trace_funs= default} )
|
|
|
|
|> fun x -> `Ok x
|
|
|
|
with _ -> `Error ("Invalid trace spec: " ^ s)
|
|
|
|
in
|
|
|
|
let print fs c =
|
|
|
|
let pf fmt = Format.fprintf fs fmt in
|
|
|
|
Map.iteri c ~f:(fun ~key:mod_name ~data:{trace_mod; trace_funs} ->
|
|
|
|
( match trace_mod with
|
|
|
|
| Some true -> pf "+%s" mod_name
|
|
|
|
| Some false -> pf "-%s" mod_name
|
|
|
|
| None -> () ) ;
|
|
|
|
Map.iteri trace_funs ~f:(fun ~key:fun_name ~data:fun_enabled ->
|
|
|
|
if fun_enabled then pf "+%s.%s" mod_name fun_name
|
|
|
|
else pf "-%s.%s" mod_name fun_name ) )
|
|
|
|
in
|
|
|
|
mk ~default Arg.(value & opt (parse, print) default & info ["t"; "trace"])
|
|
|
|
|
|
|
|
let trace_all =
|
|
|
|
let default = false in
|
|
|
|
mk ~default Arg.(value & flag & info ["v"; "trace-all"])
|
|
|
|
|
|
|
|
let info = Term.info "sledge" ~version:Version.version
|
|
|
|
let validate () = `Ok ()
|
|
|
|
|
|
|
|
let run main =
|
|
|
|
parse info validate ;
|
|
|
|
Trace.init ~config:{trace_all= !trace_all; trace_mods_funs= !trace} () ;
|
|
|
|
main ~input:!input ~output:!output ~compile_only:!compile_only
|