324 lines
9.3 KiB
OCaml
324 lines
9.3 KiB
OCaml
|
|
let () = Printexc.record_backtrace true
|
||
|
|
|
||
|
|
module type GRAPH = sig
|
||
|
|
type t
|
||
|
|
type id
|
||
|
|
type value
|
||
|
|
type error = private [> `Not_found ]
|
||
|
|
|
||
|
|
val pp_error : error Fmt.t
|
||
|
|
val preds : value -> id list
|
||
|
|
val get : t -> id -> (value, error) result Lwt.t
|
||
|
|
val compare : value -> value -> int
|
||
|
|
end
|
||
|
|
|
||
|
|
module Make (Q : Ke.Sigs.R) (G : GRAPH with type id = nativeint) = struct
|
||
|
|
open Lwt
|
||
|
|
|
||
|
|
exception Graph of G.error
|
||
|
|
|
||
|
|
module Node = struct
|
||
|
|
type t = { value : G.value; mutable color : [ `White | `Black ] }
|
||
|
|
|
||
|
|
let compare a b = G.compare a.value b.value
|
||
|
|
end
|
||
|
|
|
||
|
|
module Pq = Psq.Make (Nativeint) (Node)
|
||
|
|
module Map = Map.Make (Nativeint)
|
||
|
|
|
||
|
|
let pack graph exclude roots =
|
||
|
|
let store = Hashtbl.create 128 in
|
||
|
|
let memoize get id =
|
||
|
|
try
|
||
|
|
let ret = Hashtbl.find store id in
|
||
|
|
Lwt.return (Some ret)
|
||
|
|
with Not_found -> (
|
||
|
|
get id >>= function
|
||
|
|
| Ok value ->
|
||
|
|
let node = { Node.value; color = `White } in
|
||
|
|
Hashtbl.add store id node;
|
||
|
|
Lwt.return (Some node)
|
||
|
|
| Error `Not_found -> Lwt.return None
|
||
|
|
| Error err -> Lwt.fail (Graph err))
|
||
|
|
in
|
||
|
|
let preds v = G.preds v in
|
||
|
|
let get = memoize (G.get graph) in
|
||
|
|
let all_blacks pq =
|
||
|
|
Pq.fold
|
||
|
|
(fun _ -> function
|
||
|
|
| { Node.color = `Black; _ } -> ( && ) true
|
||
|
|
| _ -> ( && ) false)
|
||
|
|
true pq
|
||
|
|
in
|
||
|
|
let propagate { Node.value; color } =
|
||
|
|
let p = preds value in
|
||
|
|
let q = Q.create ~capacity:(List.length p) Bigarray.Nativeint in
|
||
|
|
let rec go () =
|
||
|
|
match Q.pop q with
|
||
|
|
| Some id -> (
|
||
|
|
try
|
||
|
|
let node = Hashtbl.find store id in
|
||
|
|
node.Node.color <- color;
|
||
|
|
go (List.iter (Q.push q) (preds node.Node.value))
|
||
|
|
with Not_found -> go ())
|
||
|
|
| None -> ()
|
||
|
|
in
|
||
|
|
List.iter (Q.push q) p;
|
||
|
|
go ()
|
||
|
|
in
|
||
|
|
let rec garbage pq =
|
||
|
|
if all_blacks pq then Lwt.return ()
|
||
|
|
else
|
||
|
|
match Pq.pop pq with
|
||
|
|
| Some ((_, { Node.value; color = `Black }), pq) ->
|
||
|
|
Lwt_list.fold_left_s
|
||
|
|
(fun pq id ->
|
||
|
|
get id >>= function
|
||
|
|
| Some ({ Node.color = `White; _ } as node) ->
|
||
|
|
node.Node.color <- `Black;
|
||
|
|
propagate node;
|
||
|
|
Lwt.return (Pq.add id node pq)
|
||
|
|
| Some node -> Lwt.return (Pq.add id node pq)
|
||
|
|
| None -> Lwt.return pq)
|
||
|
|
pq (preds value)
|
||
|
|
>>= garbage
|
||
|
|
| Some ((_, { Node.value; _ }), pq) ->
|
||
|
|
Lwt_list.fold_left_s
|
||
|
|
(fun pq id ->
|
||
|
|
get id >>= function
|
||
|
|
| None -> Lwt.return pq
|
||
|
|
| Some node -> Lwt.return (Pq.add id node pq))
|
||
|
|
pq (preds value)
|
||
|
|
>>= garbage
|
||
|
|
| None -> Lwt.return ()
|
||
|
|
in
|
||
|
|
let collect () =
|
||
|
|
Hashtbl.fold
|
||
|
|
(fun id -> function
|
||
|
|
| { Node.color = `White; value } -> Map.add id value
|
||
|
|
| _ -> fun acc -> acc)
|
||
|
|
store Map.empty
|
||
|
|
in
|
||
|
|
Lwt_list.map_s
|
||
|
|
(fun id ->
|
||
|
|
get id >>= function
|
||
|
|
| Some node -> Lwt.return (Some (id, node))
|
||
|
|
| None -> Lwt.return None)
|
||
|
|
roots
|
||
|
|
>>= fun roots ->
|
||
|
|
Lwt_list.map_s
|
||
|
|
(fun id ->
|
||
|
|
get id >>= function
|
||
|
|
| Some node ->
|
||
|
|
node.Node.color <- `Black;
|
||
|
|
Lwt.return (Some (id, node))
|
||
|
|
| None -> Lwt.return None)
|
||
|
|
exclude
|
||
|
|
>|= List.append roots
|
||
|
|
>|= List.fold_left
|
||
|
|
(fun acc -> function None -> acc | Some x -> x :: acc)
|
||
|
|
[]
|
||
|
|
>|= Pq.of_list
|
||
|
|
>>= fun pq -> garbage pq >|= collect
|
||
|
|
end
|
||
|
|
|
||
|
|
module Git = struct
|
||
|
|
type t = unit
|
||
|
|
type id = nativeint
|
||
|
|
type value = { name : string; time : int64; ancestors : nativeint list }
|
||
|
|
type error = [ `Not_found ]
|
||
|
|
|
||
|
|
let pp_error ppf = function `Not_found -> Fmt.string ppf "`Not_found"
|
||
|
|
let store : (id, value) Hashtbl.t = Hashtbl.create 16
|
||
|
|
let preds { ancestors; _ } = ancestors
|
||
|
|
|
||
|
|
let get () id =
|
||
|
|
try Lwt.return_ok (Hashtbl.find store id)
|
||
|
|
with Not_found -> Lwt.return_error `Not_found
|
||
|
|
|
||
|
|
let compare { time = a; _ } { time = b; _ } = Int64.compare a b
|
||
|
|
end
|
||
|
|
|
||
|
|
module Packer = Make (Ke.Rke) (Git)
|
||
|
|
|
||
|
|
let json =
|
||
|
|
let open Json_encoding in
|
||
|
|
let name = req "name" string in
|
||
|
|
let time = req "time" (conv Int64.to_string Int64.of_string string) in
|
||
|
|
let ancestors =
|
||
|
|
req "ancestors" (list (conv Nativeint.to_int32 Nativeint.of_int32 int32))
|
||
|
|
in
|
||
|
|
conv
|
||
|
|
(fun { Git.name; time; ancestors } -> (name, time, ancestors))
|
||
|
|
(fun (name, time, ancestors) -> { Git.name; time; ancestors })
|
||
|
|
(obj3 name time ancestors)
|
||
|
|
|
||
|
|
type await = [ `Await ]
|
||
|
|
type error = [ `Error of Jsonm.error ]
|
||
|
|
type eoi = [ `End ]
|
||
|
|
type value = [ `Null | `Bool of bool | `String of string | `Float of float ]
|
||
|
|
|
||
|
|
let pp_json ppf v =
|
||
|
|
let rec pp_value ppf = function
|
||
|
|
| `Bool v -> Fmt.bool ppf v
|
||
|
|
| `String v -> Fmt.quote Fmt.text ppf v
|
||
|
|
| `Float v -> Fmt.float ppf v
|
||
|
|
| `Null -> Fmt.string ppf "<null>"
|
||
|
|
| `A l -> pp_arr ppf l
|
||
|
|
| `O l -> pp_obj ppf l
|
||
|
|
and pp_arr ppf arr = Fmt.(using Array.of_list (Dump.array pp_value)) ppf arr
|
||
|
|
and pp_obj ppf obj =
|
||
|
|
Fmt.Dump.iter_bindings
|
||
|
|
(fun f -> List.iter (fun (k, v) -> f k v))
|
||
|
|
Fmt.(any "object")
|
||
|
|
Fmt.string pp_value ppf obj
|
||
|
|
in
|
||
|
|
pp_value ppf v
|
||
|
|
|
||
|
|
let of_database ic : Git.value list =
|
||
|
|
let decoder = Jsonm.decoder (`Channel ic) in
|
||
|
|
|
||
|
|
let error (`Error err) = Fmt.invalid_arg "%a" Jsonm.pp_error err in
|
||
|
|
let end_of_input `End = Fmt.invalid_arg "Unexpected end of input" in
|
||
|
|
|
||
|
|
let rec arr acc k =
|
||
|
|
match Jsonm.decode decoder with
|
||
|
|
| #await -> assert false
|
||
|
|
| #error as v -> error v
|
||
|
|
| #eoi as v -> end_of_input v
|
||
|
|
| `Lexeme `Ae -> k (`A (List.rev acc))
|
||
|
|
| `Lexeme v -> base (fun v -> arr (v :: acc) k) v
|
||
|
|
and name n k =
|
||
|
|
match Jsonm.decode decoder with
|
||
|
|
| #await -> assert false
|
||
|
|
| #error as v -> error v
|
||
|
|
| #eoi as v -> end_of_input v
|
||
|
|
| `Lexeme v -> base (fun v -> k (n, v)) v
|
||
|
|
and obj acc k =
|
||
|
|
match Jsonm.decode decoder with
|
||
|
|
| #await -> assert false
|
||
|
|
| #error as v -> error v
|
||
|
|
| #eoi as v -> end_of_input v
|
||
|
|
| `Lexeme `Oe -> k (`O (List.rev acc))
|
||
|
|
| `Lexeme (`Name n) -> name n (fun v -> obj (v :: acc) k)
|
||
|
|
| `Lexeme v -> Fmt.invalid_arg "Unexpected lexeme: %a" Jsonm.pp_lexeme v
|
||
|
|
and base k = function
|
||
|
|
| #value as v -> k v
|
||
|
|
| `Os -> obj [] k
|
||
|
|
| `As -> arr [] k
|
||
|
|
| `Ae | `Oe -> Fmt.invalid_arg "Unexpected end of array/object"
|
||
|
|
| `Name n -> Fmt.invalid_arg "Unexpected key: %s" n
|
||
|
|
in
|
||
|
|
|
||
|
|
let go k =
|
||
|
|
match Jsonm.decode decoder with
|
||
|
|
| #await -> assert false
|
||
|
|
| #error as v -> error v
|
||
|
|
| #eoi as v -> end_of_input v
|
||
|
|
| `Lexeme (#Jsonm.lexeme as lexeme) -> base k lexeme
|
||
|
|
in
|
||
|
|
|
||
|
|
go Json_encoding.(destruct (list json))
|
||
|
|
|
||
|
|
let flat_json json : Jsonm.lexeme list =
|
||
|
|
let rec arr acc k = function
|
||
|
|
| [] -> k (List.rev (`Ae :: acc))
|
||
|
|
| (#value as x) :: r -> arr (x :: acc) k r
|
||
|
|
| `A l :: r -> arr [ `As ] (fun l -> arr (List.rev_append l acc) k r) l
|
||
|
|
| `O l :: r -> obj [ `Os ] (fun l -> arr (List.rev_append l acc) k r) l
|
||
|
|
and obj acc k = function
|
||
|
|
| [] -> k (List.rev (`Oe :: acc))
|
||
|
|
| (n, x) :: r ->
|
||
|
|
base (fun v -> obj (List.rev_append v (`Name n :: acc)) k r) x
|
||
|
|
and base k = function
|
||
|
|
| `A l -> arr [ `As ] k l
|
||
|
|
| `O l -> obj [ `Os ] k l
|
||
|
|
| #value as x -> k [ x ]
|
||
|
|
in
|
||
|
|
|
||
|
|
base (fun l -> l) json
|
||
|
|
|
||
|
|
external identity : 'a -> 'a = "%identity"
|
||
|
|
|
||
|
|
let show ppf map =
|
||
|
|
let json =
|
||
|
|
Json_encoding.(
|
||
|
|
construct (list json) (List.map snd (Packer.Map.bindings map)))
|
||
|
|
in
|
||
|
|
let raw = Bytes.create 0x800 in
|
||
|
|
let encoder = Jsonm.encoder `Manual in
|
||
|
|
let rec write k = function
|
||
|
|
| `Ok -> k ()
|
||
|
|
| `Partial ->
|
||
|
|
Fmt.string ppf (Bytes.sub_string raw 0 (Jsonm.Manual.dst_rem encoder));
|
||
|
|
Jsonm.Manual.dst encoder raw 0 (Bytes.length raw);
|
||
|
|
write k (Jsonm.encode encoder `Await)
|
||
|
|
in
|
||
|
|
let rec go k = function
|
||
|
|
| [] -> write k (Jsonm.encode encoder `End)
|
||
|
|
| lexeme :: r ->
|
||
|
|
write (fun () -> go k r) (Jsonm.encode encoder (`Lexeme lexeme))
|
||
|
|
in
|
||
|
|
let lexemes = flat_json json in
|
||
|
|
go identity lexemes
|
||
|
|
|
||
|
|
let run database exclude roots =
|
||
|
|
let graph = of_database (open_in database) in
|
||
|
|
let () =
|
||
|
|
List.iteri
|
||
|
|
(fun id value -> Hashtbl.add Git.store (Nativeint.of_int id) value)
|
||
|
|
graph
|
||
|
|
in
|
||
|
|
match
|
||
|
|
Lwt_main.run
|
||
|
|
Lwt.Infix.(
|
||
|
|
Packer.pack () exclude roots >|= Fmt.fmt "%a\n%!" Fmt.stdout show)
|
||
|
|
with
|
||
|
|
| () -> `Ok ()
|
||
|
|
| exception Packer.Graph err ->
|
||
|
|
`Error (false, Fmt.str "Retrieve an error: %a." Git.pp_error err)
|
||
|
|
|
||
|
|
open Cmdliner
|
||
|
|
|
||
|
|
let database =
|
||
|
|
let parser s =
|
||
|
|
if Sys.file_exists s then Ok s
|
||
|
|
else Rresult.R.error_msgf "File %s does not exists" s
|
||
|
|
in
|
||
|
|
let pp = Fmt.string in
|
||
|
|
Arg.conv (parser, pp)
|
||
|
|
|
||
|
|
let id =
|
||
|
|
let parser s =
|
||
|
|
match Nativeint.of_string_opt s with
|
||
|
|
| Some n -> Ok n
|
||
|
|
| None -> Rresult.R.error_msgf "Invalid id: %s" s
|
||
|
|
in
|
||
|
|
let pp = Fmt.nativeint in
|
||
|
|
Arg.conv (parser, pp)
|
||
|
|
|
||
|
|
let database =
|
||
|
|
let doc = "Database of graph." in
|
||
|
|
Arg.(
|
||
|
|
required
|
||
|
|
& opt (some database) None
|
||
|
|
& info [ "d"; "database" ] ~doc ~docv:"<FILE>")
|
||
|
|
|
||
|
|
let roots =
|
||
|
|
let doc = "Roots of graph." in
|
||
|
|
Arg.(non_empty & opt (list id) [] & info [ "r"; "roots" ] ~doc ~docv:"<LST>")
|
||
|
|
|
||
|
|
let exclude =
|
||
|
|
let doc = "Excluded nodes of graph." in
|
||
|
|
Arg.(
|
||
|
|
non_empty & opt (list id) [] & info [ "e"; "exclude" ] ~doc ~docv:"<LST>")
|
||
|
|
|
||
|
|
let command =
|
||
|
|
let doc = "Example of ke." in
|
||
|
|
let exits = Cmd.Exit.defaults in
|
||
|
|
Cmd.v
|
||
|
|
(Cmd.info "packer" ~version:"dev" ~doc ~exits)
|
||
|
|
Term.(ret (const run $ database $ exclude $ roots))
|
||
|
|
|
||
|
|
let () = Cmd.(exit @@ eval command)
|