3373 lines
103 KiB
OCaml
3373 lines
103 KiB
OCaml
(* Taken from bap (https://github.com/BinaryAnalysisPlatform/bap) which is under
|
|
the MIT license:
|
|
https://github.com/BinaryAnalysisPlatform/bap/blob/345d13d1114e2640c183f864b1a62b25b9b96b1c/lib/knowledge/bap_knowledge.ml
|
|
*)
|
|
|
|
open Core_kernel [@@warning "-D"] [@@warning "-D"]
|
|
open Monads.Std
|
|
module Unix = Caml_unix [@@warning "-49"]
|
|
|
|
type ('a, 'b) eq = ('a, 'b) Type_equal.t = T : ('a, 'a) eq
|
|
|
|
module Order = struct
|
|
type partial = LT | EQ | GT | NC [@@deriving sexp, equal]
|
|
|
|
module type S = sig
|
|
type t
|
|
|
|
val order : t -> t -> partial
|
|
end
|
|
end
|
|
|
|
type conflict = exn = ..
|
|
|
|
module Conflict = struct
|
|
type t = conflict = ..
|
|
|
|
let to_string = Stdlib.Printexc.to_string
|
|
let pp ppf err = Format.fprintf ppf "%s" (to_string err)
|
|
let register_printer = Stdlib.Printexc.register_printer
|
|
let sexp_of_t err = Sexp.Atom (to_string err)
|
|
end
|
|
|
|
module type Id = sig
|
|
type t [@@deriving sexp, hash]
|
|
|
|
val zero : t
|
|
val pp : Format.formatter -> t -> unit
|
|
val of_string : string -> t
|
|
|
|
include Comparable.S_binable with type t := t
|
|
include Binable.S with type t := t
|
|
end
|
|
|
|
module Oid : sig
|
|
include Id
|
|
|
|
val zero : t
|
|
val first : t
|
|
val succ : t -> t
|
|
val fits : int -> bool
|
|
val of_int : int -> t
|
|
val fits_int : t -> bool
|
|
val to_int : t -> int
|
|
val to_int63 : t -> Int63.t
|
|
val incr : t ref -> unit
|
|
|
|
module Tree : sig
|
|
type key = t
|
|
type 'a t
|
|
|
|
val empty : 'a t
|
|
val is_empty : 'a t -> bool
|
|
val find_exn : 'a t -> key -> 'a
|
|
val find : 'a t -> key -> 'a option
|
|
val max_elt : 'a t -> (key * 'a) option
|
|
val min_elt : 'a t -> (key * 'a) option
|
|
val mem : 'a t -> key -> bool
|
|
val singleton : key -> 'a -> 'a t
|
|
val set : 'a t -> key -> 'a -> 'a t
|
|
val remove : 'a t -> key -> 'a t
|
|
val update : 'a t -> key -> f:('a option -> 'a) -> 'a t
|
|
val update_with : 'a t -> key -> has:('a -> 'a) -> nil:(unit -> 'a) -> 'a t
|
|
val merge : 'a t -> 'a t -> f:(key -> 'a -> 'a -> 'a) -> 'a t
|
|
val iter : 'a t -> f:(key -> 'a -> unit) -> unit
|
|
val fold : 'a t -> init:'b -> f:(key -> 'a -> 'b -> 'b) -> 'b
|
|
val keys : 'a t -> key list
|
|
val elements : 'a t -> 'a list
|
|
val to_list : 'a t -> (key * 'a) list
|
|
val to_sequence : 'a t -> (key * 'a) Sequence.t
|
|
end
|
|
end = struct
|
|
include Int63
|
|
|
|
let first = one
|
|
let is_null x = x = zero
|
|
let to_int = to_int_trunc
|
|
let to_int63 x = x
|
|
let fits _ = true [@@inline]
|
|
let fits_int _ = true [@@inline]
|
|
let pp ppf x = Format.fprintf ppf "<%#0Lx>" (to_int64 x)
|
|
|
|
module Tree = struct
|
|
type key = t
|
|
|
|
(* sets bit to zero and all lower bits to one *)
|
|
let mask ~bit x =
|
|
let m = one lsl to_int bit in
|
|
x lor (m - one) land lnot m
|
|
|
|
let ( lsr ) = shift_right_logical
|
|
let clz v = of_int (clz v) [@@inline]
|
|
let numbits v = of_int 63 - clz v [@@inline]
|
|
let highest_bit x = numbits x - one
|
|
let is_zero ~bit x = x land (one lsl to_int bit) = zero
|
|
|
|
module Key = struct
|
|
(*
|
|
|
|
+-----------+-------------------------+
|
|
| branching | payload |
|
|
+-----------+-------------------------+
|
|
62 57 56 0
|
|
|
|
Note, that we store the branching bit position,
|
|
not the mask itself.
|
|
|
|
*)
|
|
type t = { key : key } [@@unboxed]
|
|
|
|
let payload_size = 57
|
|
let branching_size = 6
|
|
let branching_mask = of_int64_exn (-144115188075855872L)
|
|
|
|
(* 0b1111110000....0 *)
|
|
let payload_mask =
|
|
of_int64_exn 144115188075855871L (* 0b0000001111....1 *)
|
|
|
|
let branching { key } = (key land branching_mask) lsr 57 [@@inline]
|
|
let payload { key } = key land payload_mask [@@inline]
|
|
let create ~branching ~payload = { key = (branching lsl 57) lor payload }
|
|
|
|
type order = NA | LB | RB
|
|
|
|
let compare k k' =
|
|
let x = payload k in
|
|
let bit = branching k in
|
|
let m = one lsl to_int bit in
|
|
let y = k' lor (m - one) land lnot m in
|
|
if x = y then if k' land m = zero then LB else RB else NA
|
|
[@@inline]
|
|
|
|
let equal { key = k1 } { key = k2 } = equal k1 k2 [@@inline]
|
|
|
|
let pp ppf key =
|
|
Format.fprintf ppf "%a:%a" pp (branching key) pp (payload key)
|
|
end
|
|
|
|
type +'a t = Bin of Key.t * 'a t * 'a t | Tip of key * 'a | Nil
|
|
|
|
let empty = Nil
|
|
let[@inline] is_empty = function Nil -> true | _ -> false
|
|
let branching_bit a b = highest_bit (a lxor b)
|
|
|
|
let rec find_exn t k =
|
|
match t with
|
|
| Nil -> raise Stdlib.Not_found
|
|
| Tip (k', v) when k = k' -> v
|
|
| Tip _ -> raise Stdlib.Not_found
|
|
| Bin (k', l, r) -> (
|
|
match Key.compare k' k with
|
|
| NA -> raise Stdlib.Not_found
|
|
| LB -> find_exn l k
|
|
| RB -> find_exn r k)
|
|
|
|
let find t k = try Some (find_exn t k) with Stdlib.Not_found -> None
|
|
|
|
let mem k t =
|
|
try
|
|
ignore (find_exn k t);
|
|
true
|
|
with Stdlib.Not_found -> false
|
|
|
|
let node payload branching l r =
|
|
match (l, r) with
|
|
| Nil, o | o, Nil -> o
|
|
| _ -> Bin (Key.create ~branching ~payload, l, r)
|
|
|
|
let of_key key l r =
|
|
match (l, r) with Nil, o | o, Nil -> o | _ -> Bin (key, l, r)
|
|
|
|
let join t1 p1 t2 p2 =
|
|
let switch = branching_bit p1 p2 in
|
|
let prefix = mask p1 ~bit:switch in
|
|
if is_zero p1 ~bit:switch then node prefix switch t1 t2
|
|
else node prefix switch t2 t1
|
|
|
|
let singleton k v = Tip (k, v)
|
|
|
|
let rec update_with t k ~has ~nil =
|
|
match t with
|
|
| Nil -> Tip (k, nil ())
|
|
| Tip (k', v') ->
|
|
if k = k' then Tip (k, has v') else join t k' (Tip (k, nil ())) k
|
|
| Bin (k', l, r) -> (
|
|
match Key.compare k' k with
|
|
| NA -> join (Tip (k, nil ())) k t (Key.payload k')
|
|
| LB -> Bin (k', update_with l k ~has ~nil, r)
|
|
| RB -> Bin (k', l, update_with r k ~has ~nil))
|
|
[@@specialise]
|
|
|
|
let rec update t k ~f =
|
|
match t with
|
|
| Nil -> Tip (k, f None)
|
|
| Tip (k', v') ->
|
|
if k = k' then Tip (k, f (Some v')) else join t k' (Tip (k, f None)) k
|
|
| Bin (k', l, r) -> (
|
|
match Key.compare k' k with
|
|
| NA -> join (Tip (k, f None)) k t (Key.payload k')
|
|
| LB -> Bin (k', update l k f, r)
|
|
| RB -> Bin (k', l, update r k f))
|
|
[@@specialise]
|
|
|
|
let rec set t k v =
|
|
match t with
|
|
| Nil -> Tip (k, v)
|
|
| Tip (k', _) -> if k = k' then Tip (k, v) else join t k' (Tip (k, v)) k
|
|
| Bin (k', l, r) -> (
|
|
match Key.compare k' k with
|
|
| NA -> join (Tip (k, v)) k t (Key.payload k')
|
|
| LB -> Bin (k', set l k v, r)
|
|
| RB -> Bin (k', l, set r k v))
|
|
|
|
let rec remove t k =
|
|
match t with
|
|
| Nil -> Nil
|
|
| Tip (k', _) -> if k = k' then Nil else t
|
|
| Bin (k', l, r) -> (
|
|
match Key.compare k' k with
|
|
| NA -> t
|
|
| LB -> of_key k' (remove l k) r
|
|
| RB -> of_key k' l (remove r k))
|
|
|
|
let rec merge t1 t2 ~f =
|
|
match (t1, t2) with
|
|
| Nil, t | t, Nil -> t
|
|
| Tip (k, v1), t | t, Tip (k, v1) ->
|
|
update t k ~f:(function None -> v1 | Some v2 -> f k v1 v2)
|
|
| Bin (p1, l1, r1), Bin (p2, l2, r2) -> (
|
|
if Key.equal p1 p2 then of_key p1 (merge l1 l2 ~f) (merge r1 r2 ~f)
|
|
else
|
|
let k1 = Key.payload p1 and k2 = Key.payload p2 in
|
|
let b1 = Key.branching p1 and b2 = Key.branching p2 in
|
|
match Key.compare p1 k2 with
|
|
| NA -> join t1 k1 t2 k2
|
|
| RB ->
|
|
if is_zero ~bit:b1 k2 then Bin (p1, merge l1 t2 ~f, r1)
|
|
else Bin (p1, l1, merge r1 t2 ~f)
|
|
| LB ->
|
|
if is_zero ~bit:b2 k1 then Bin (p2, merge t1 l2 ~f, r2)
|
|
else Bin (p2, l2, merge t1 r2 ~f))
|
|
[@@specialise]
|
|
|
|
let rec iter t ~f =
|
|
match t with
|
|
| Nil -> ()
|
|
| Tip (k, v) -> f k v
|
|
| Bin (_, l, r) ->
|
|
iter l ~f;
|
|
iter r ~f
|
|
[@@specialise]
|
|
|
|
let rec fold t ~init ~f =
|
|
match t with
|
|
| Nil -> init
|
|
| Tip (k, v) -> f k v init
|
|
| Bin (_, l, r) -> fold r ~f ~init:(fold l ~init ~f)
|
|
[@@specialise]
|
|
|
|
let rec max_elt = function
|
|
| Nil -> None
|
|
| Tip (k, v) -> Some (k, v)
|
|
| Bin (_, _, r) -> max_elt r
|
|
|
|
let rec min_elt = function
|
|
| Nil -> None
|
|
| Tip (k, v) -> Some (k, v)
|
|
| Bin (_, _, r) -> min_elt r
|
|
|
|
let elements = fold ~f:(fun _ x xs -> x :: xs) ~init:[]
|
|
let keys = fold ~f:(fun x _ xs -> x :: xs) ~init:[]
|
|
|
|
let to_list tree =
|
|
let rec list acc = function
|
|
| Nil -> acc
|
|
| Tip (k, x) -> (k, x) :: acc
|
|
| Bin (_, l, r) -> list (list acc l) r
|
|
in
|
|
list [] tree
|
|
|
|
let to_sequence tree =
|
|
let open Sequence.Generator in
|
|
let rec seq = function
|
|
| Nil -> return ()
|
|
| Tip (k, x) -> yield (k, x)
|
|
| Bin (_, l, r) -> seq l >>= fun () -> seq r
|
|
in
|
|
run (seq tree)
|
|
end
|
|
end
|
|
|
|
module Pid = Oid
|
|
|
|
let user_package = "user"
|
|
let keyword_package = "keyword"
|
|
|
|
type fullname = { package : string; name : string }
|
|
[@@deriving bin_io, equal, compare, sexp]
|
|
|
|
module Name : sig
|
|
type t [@@deriving bin_io, compare, sexp]
|
|
|
|
val create : ?package:string -> string -> t
|
|
val read : ?package:string -> string -> t
|
|
val show : t -> string
|
|
val unqualified : t -> string
|
|
val package : t -> string
|
|
val str : unit -> t -> string
|
|
val hash : t -> int
|
|
val full : t -> fullname
|
|
|
|
module Full : sig
|
|
type t = fullname
|
|
|
|
val create : ?package:string -> string -> t
|
|
val read : ?package:string -> string -> t
|
|
val short : t -> string
|
|
val package : t -> string
|
|
val to_string : t -> string
|
|
|
|
include Base.Comparable.S with type t := t
|
|
end
|
|
|
|
val normalize_name :
|
|
[ `Literal | `Reading ] -> package:string -> string -> string
|
|
|
|
val normalize_package : [ `Literal | `Reading ] -> string -> string
|
|
val find_separator : string -> int option
|
|
|
|
include Base.Comparable.S with type t := t
|
|
include Binable.S with type t := t
|
|
include Stringable.S with type t := t
|
|
include Pretty_printer.S with type t := t
|
|
end = struct
|
|
let full { package; name } =
|
|
if String.(package = keyword_package || package = user_package) then name
|
|
else package ^ ":" ^ name
|
|
|
|
let separator = ':'
|
|
let escape_char = '\\'
|
|
let escapeworthy = [ separator ]
|
|
let is_escaped s = String.Escaping.is_char_escaped s ~escape_char
|
|
|
|
let find_separator s =
|
|
if String.is_empty s then None
|
|
else String.Escaping.index s ~escape_char separator
|
|
|
|
let is_separator_unescaped s p c =
|
|
Char.equal separator c && not (is_escaped s p)
|
|
|
|
let unescaped_exists_so_escape ?(skip_pos = -1) s =
|
|
let buf = Buffer.create (String.length s + 1) in
|
|
Stdlib.StringLabels.iteri s ~f:(fun p c ->
|
|
if p <> skip_pos && is_separator_unescaped s p c then
|
|
Buffer.add_char buf escape_char;
|
|
Buffer.add_char buf c);
|
|
Buffer.contents buf
|
|
|
|
let has_unescaped ?pos s =
|
|
Option.is_some
|
|
@@ String.lfindi ?pos s ~f:(fun p c -> is_separator_unescaped s p c)
|
|
|
|
let escape_all_unescaped ?(is_keyword = false) s =
|
|
match s with
|
|
| "" -> s
|
|
| ":" -> if is_keyword then s else "\\:"
|
|
| _ ->
|
|
let pos = if is_keyword then 1 else 0 in
|
|
if has_unescaped ~pos s then unescaped_exists_so_escape ~skip_pos:pos s
|
|
else s
|
|
|
|
let escape_all_literally =
|
|
unstage @@ String.Escaping.escape ~escapeworthy ~escape_char
|
|
|
|
let unescape = unstage @@ String.Escaping.unescape ~escape_char
|
|
|
|
(* invariant, keywords are always prefixed with [:] *)
|
|
let normalize_name input ~package name =
|
|
match input with
|
|
| `Literal ->
|
|
if String.equal package keyword_package then
|
|
":" ^ escape_all_literally name
|
|
else escape_all_literally name
|
|
| `Reading ->
|
|
let escape = escape_all_unescaped in
|
|
if String.equal package keyword_package then
|
|
if not (String.is_prefix ~prefix:":" name) then ":" ^ escape name
|
|
else ":" ^ escape @@ String.subo ~pos:1 name
|
|
else escape name
|
|
|
|
let normalize_package input package =
|
|
let package = if String.is_empty package then user_package else package in
|
|
match input with
|
|
| `Literal -> escape_all_literally package
|
|
| `Reading -> escape_all_unescaped package
|
|
|
|
module Full = struct
|
|
type t = fullname
|
|
|
|
let create ?(package = user_package) name =
|
|
let package = normalize_package `Literal package in
|
|
let name = normalize_name `Literal ~package name in
|
|
{ package; name }
|
|
|
|
let short x = unescape @@ x.name
|
|
let package x = unescape @@ x.package
|
|
let to_string name = full name
|
|
|
|
let read ?(package = user_package) s : t =
|
|
let package = normalize_package `Literal package in
|
|
let escape = escape_all_unescaped in
|
|
match find_separator s with
|
|
| None ->
|
|
let name = normalize_name `Reading ~package s in
|
|
{ package; name }
|
|
| Some 0 ->
|
|
let package = keyword_package and name = escape ~is_keyword:true s in
|
|
{ package; name }
|
|
| Some len ->
|
|
let package = escape (String.sub s ~pos:0 ~len) in
|
|
let name =
|
|
normalize_name `Reading ~package @@ String.subo s ~pos:(len + 1)
|
|
in
|
|
{ package; name }
|
|
|
|
include Base.Comparable.Make (struct
|
|
type t = fullname [@@deriving compare, sexp]
|
|
end)
|
|
end
|
|
|
|
module Id : sig
|
|
type t [@@deriving bin_io, compare, sexp]
|
|
|
|
val intern : fullname -> t
|
|
val fullname : t -> fullname
|
|
val hash : t -> int
|
|
end = struct
|
|
let registry = Hashtbl.create (module Int63)
|
|
|
|
(* using FNV-1a algorithm *)
|
|
let hash_name =
|
|
let open Int63 in
|
|
let init = of_int64_exn 0xCBF29CE484222325L in
|
|
let m = of_int64_exn 0x100000001B3L in
|
|
let hash init =
|
|
String.fold ~init ~f:(fun h c -> h lxor of_int (Char.to_int c) * m)
|
|
in
|
|
fun { package; name } -> hash (hash init package) name
|
|
|
|
let intern name =
|
|
let id = hash_name name in
|
|
match Hashtbl.find registry id with
|
|
| None ->
|
|
Hashtbl.add_exn registry id name;
|
|
id
|
|
| Some name' ->
|
|
if equal_fullname name name' then id
|
|
else
|
|
invalid_argf
|
|
"Names %S and %S have the same hash value, Change one of them."
|
|
(full name) (full name') ()
|
|
|
|
let fullname id =
|
|
match Hashtbl.find registry id with
|
|
| Some name -> name
|
|
| None -> { package = "id"; name = sprintf "%Lx" (Int63.to_int64 id) }
|
|
|
|
include Int63
|
|
|
|
let sexp_of_t id = Sexp.Atom (Full.to_string (fullname id))
|
|
|
|
let t_of_sexp = function
|
|
| Sexp.Atom str -> intern (Full.read str)
|
|
| _ -> invalid_arg "KB.Name.sexp_of_t: expects an atom"
|
|
end
|
|
|
|
type t = Id.t [@@deriving bin_io, compare, sexp]
|
|
|
|
let full = Id.fullname
|
|
let create ?package name = Id.intern @@ Full.create ?package name
|
|
let keyword = create ~package:keyword_package
|
|
let read ?package name = Id.intern @@ Full.read ?package name
|
|
let package t = Full.package Id.(fullname t)
|
|
let short t = Full.short Id.(fullname t)
|
|
let unqualified t = short t
|
|
let to_string t = Full.to_string Id.(fullname t)
|
|
let show t = to_string t
|
|
let of_string s = read s
|
|
let str () s = to_string s
|
|
let pp ppf x = Format.fprintf ppf "%s" (show x)
|
|
let hash = Id.hash
|
|
|
|
include Base.Comparable.Make (struct
|
|
type t = Id.t [@@deriving bin_io, compare, sexp]
|
|
end)
|
|
end
|
|
|
|
module Agent : sig
|
|
type t
|
|
type id
|
|
type reliability
|
|
type signs
|
|
|
|
val register :
|
|
?desc:string -> ?package:string -> ?reliability:reliability -> string -> t
|
|
|
|
val registry : unit -> id list
|
|
val authorative : reliability
|
|
val reliable : reliability
|
|
val trustworthy : reliability
|
|
val doubtful : reliability
|
|
val unreliable : reliability
|
|
val name : id -> Name.t
|
|
val desc : id -> string
|
|
val reliability : id -> reliability
|
|
val set_reliability : id -> reliability -> unit
|
|
val pp : Format.formatter -> t -> unit
|
|
val pp_id : Format.formatter -> id -> unit
|
|
val pp_reliability : Format.formatter -> reliability -> unit
|
|
|
|
(* the private interface *)
|
|
|
|
val weight : t -> int
|
|
val id : t -> id
|
|
|
|
include Base.Comparable.S with type t := t
|
|
end = struct
|
|
module Id = String
|
|
|
|
type t = Id.t
|
|
type agent = Id.t
|
|
type id = Id.t
|
|
type reliability = A | B | C | D | E [@@deriving sexp]
|
|
type info = { name : Name.t; desc : string; rcls : reliability }
|
|
type signs = Set.M(String).t
|
|
|
|
let agents : (agent, info) Hashtbl.t = Hashtbl.create (module String)
|
|
let authorative = A
|
|
let reliable = B
|
|
let trustworthy = C
|
|
let doubtful = D
|
|
let unreliable = E
|
|
let weight = function A -> 16 | B -> 8 | C -> 4 | D -> 2 | E -> 1
|
|
let id x = x
|
|
|
|
let register ?(desc = "no description provided") ?package
|
|
?(reliability = trustworthy) name =
|
|
let name = Name.create ?package name in
|
|
let agent = Stdlib.Digest.string (Name.show name) in
|
|
if Hashtbl.mem agents agent then
|
|
failwithf
|
|
"An agent with name `%a' already exists, please choose another name"
|
|
Name.str name ();
|
|
Hashtbl.add_exn agents agent { desc; name; rcls = reliability };
|
|
agent
|
|
|
|
let registry () = Hashtbl.keys agents
|
|
let info agent = Hashtbl.find_exn agents agent
|
|
let name agent = (info agent).name
|
|
let desc agent = (info agent).desc
|
|
let reliability agent = (info agent).rcls
|
|
let weight agent = weight (reliability agent)
|
|
|
|
let set_reliability agent rcls =
|
|
Hashtbl.update agents agent ~f:(function
|
|
| None -> assert false
|
|
| Some agent -> { agent with rcls })
|
|
|
|
let pp ppf agent = Name.pp ppf (name agent)
|
|
let pp_reliability ppf r = Sexp.pp ppf (sexp_of_reliability r)
|
|
|
|
let pp_id ppf agent =
|
|
let { name; desc; rcls } = info agent in
|
|
Format.fprintf ppf "Class %a %a - %s" pp_reliability rcls Name.pp name desc
|
|
|
|
include (String : Base.Comparable.S with type t := t)
|
|
end
|
|
|
|
module Opinions : sig
|
|
type 'a t
|
|
|
|
val empty : equal:('a -> 'a -> bool) -> 'a -> 'a t
|
|
val inspect : ('a -> Sexp.t) -> 'a t -> Sexp.t
|
|
val add : Agent.t -> 'a -> 'a t -> 'a t
|
|
|
|
val of_list :
|
|
equal:('a -> 'a -> bool) -> 'a -> (Agent.t, 'a) List.Assoc.t -> 'a t
|
|
|
|
val choice : 'a t -> 'a
|
|
val compare_votes : 'a t -> 'a t -> int
|
|
val join : 'a t -> 'a t -> 'a t
|
|
end = struct
|
|
type 'a opinion = { opinion : 'a; votes : Set.M(Agent).t }
|
|
|
|
type 'a t = {
|
|
opinions : 'a opinion list;
|
|
equal : 'a -> 'a -> bool;
|
|
empty : 'a;
|
|
}
|
|
|
|
let empty ~equal empty = { opinions = []; equal; empty }
|
|
|
|
let inspect sexp_of_opinion { opinions } =
|
|
Sexp.List
|
|
(List.rev_map opinions ~f:(fun { opinion } -> sexp_of_opinion opinion))
|
|
|
|
let add_opinion op ({ opinions; equal } as ops) =
|
|
let casted, opinions =
|
|
List.fold opinions ~init:(false, [])
|
|
~f:(fun (casted, opinions) ({ opinion; votes } as elt) ->
|
|
if (not casted) && equal opinion op.opinion then
|
|
(true, { opinion; votes = Set.union votes op.votes } :: opinions)
|
|
else (casted, elt :: opinions))
|
|
in
|
|
if casted then { ops with opinions }
|
|
else { ops with opinions = op :: opinions }
|
|
|
|
let add agent opinion ({ empty; equal } as ops) =
|
|
if equal opinion empty then ops
|
|
else add_opinion { opinion; votes = Set.singleton (module Agent) agent } ops
|
|
|
|
let join x y =
|
|
List.fold y.opinions ~init:x ~f:(fun ops op -> add_opinion op ops)
|
|
|
|
let votes_sum =
|
|
Set.fold ~init:0 ~f:(fun sum agent -> sum + Agent.weight agent)
|
|
|
|
let count_votes { opinions } =
|
|
List.fold opinions ~init:0 ~f:(fun sum { votes } -> sum + votes_sum votes)
|
|
|
|
let compare_votes x y = compare (count_votes x) (count_votes y)
|
|
|
|
let of_list ~equal bot =
|
|
let init = empty ~equal bot in
|
|
List.fold ~init ~f:(fun opts (agent, data) -> add agent data opts)
|
|
|
|
let compare x y =
|
|
let w1 = votes_sum x.votes and w2 = votes_sum y.votes in
|
|
match Int.compare w1 w2 with
|
|
| 0 -> Set.compare_direct x.votes y.votes
|
|
| n -> n
|
|
|
|
let choice { opinions; empty } =
|
|
List.max_elt opinions ~compare |> function
|
|
| Some { opinion } -> opinion
|
|
| None -> empty
|
|
end
|
|
|
|
module Domain = struct
|
|
type 'a t = {
|
|
inspect : 'a -> Sexp.t;
|
|
empty : 'a;
|
|
order : 'a -> 'a -> Order.partial;
|
|
join : 'a -> 'a -> ('a, conflict) result;
|
|
name : string;
|
|
}
|
|
|
|
let inspect d = d.inspect
|
|
let empty d = d.empty
|
|
let order d = d.order
|
|
let join d = d.join
|
|
let name d = d.name
|
|
let is_empty { empty; order } x = Order.equal_partial (order empty x) EQ
|
|
|
|
type conflict += Join : string * ('a -> Sexp.t) * 'a * 'a -> conflict
|
|
|
|
let () =
|
|
Conflict.register_printer @@ function
|
|
| Join (dom, inspect, x, y) ->
|
|
Option.some
|
|
@@ Format.asprintf "Domain %s doesn't have a join for values %a and %a"
|
|
dom Sexp.pp_hum (inspect x) Sexp.pp_hum (inspect y)
|
|
| _ -> None
|
|
|
|
let make_join name inspect order x y =
|
|
match order x y with
|
|
| Order.GT -> Ok x
|
|
| EQ | LT -> Ok y
|
|
| NC -> Error (Join (name, inspect, x, y))
|
|
|
|
let define ?(inspect = sexp_of_opaque) ?join ~empty ~order name =
|
|
{
|
|
inspect;
|
|
empty;
|
|
order;
|
|
name;
|
|
join =
|
|
(match join with Some f -> f | None -> make_join name inspect order);
|
|
}
|
|
|
|
let partial_of_total order x y : Order.partial =
|
|
match order x y with 0 -> EQ | 1 -> GT | _ -> LT
|
|
|
|
let total ?inspect ?join ~empty ~order name =
|
|
define ?inspect ?join ~empty name ~order:(partial_of_total order)
|
|
|
|
let flat ?inspect ?join ~empty ~equal name =
|
|
define ?inspect ?join ~empty name ~order:(fun x y ->
|
|
match (equal empty x, equal empty y) with
|
|
| true, true -> EQ
|
|
| true, false -> LT
|
|
| false, true -> GT
|
|
| false, false -> if equal x y then EQ else NC)
|
|
|
|
let powerset (type t o)
|
|
(module S : Comparator.S with type t = t and type comparator_witness = o)
|
|
?(inspect = S.comparator.sexp_of_t) name =
|
|
let empty = Set.empty (module S) in
|
|
let order x y : Order.partial =
|
|
if Set.equal x y then EQ
|
|
else if Set.is_subset x y then LT
|
|
else if Set.is_subset y x then GT
|
|
else NC
|
|
in
|
|
let join x y = Ok (Set.union x y) in
|
|
let module Inspectable = struct
|
|
include S
|
|
|
|
let sexp_of_t = inspect
|
|
end in
|
|
let inspect = [%sexp_of: Base.Set.M(Inspectable).t] in
|
|
define ~inspect ~empty ~order ~join name
|
|
|
|
let opinions ?(inspect = sexp_of_opaque) ~empty ~equal name =
|
|
let empty = Opinions.empty ~equal empty in
|
|
let order = partial_of_total Opinions.compare_votes in
|
|
let inspect = Opinions.inspect inspect in
|
|
define ~inspect ~empty ~order name
|
|
|
|
let mapping (type k o d)
|
|
(module K : Comparator.S with type t = k and type comparator_witness = o)
|
|
?(inspect = sexp_of_opaque) ?join ~equal name =
|
|
let empty = Map.empty (module K) in
|
|
let join =
|
|
match join with
|
|
| Some join -> join
|
|
| None ->
|
|
fun x y ->
|
|
if equal x y then Ok y else Error (Join (name, inspect, x, y))
|
|
in
|
|
let join x y =
|
|
let module Join = struct
|
|
exception Conflict of conflict
|
|
end in
|
|
try
|
|
Result.return
|
|
@@ Map.merge x y ~f:(fun ~key:_ -> function
|
|
| `Left v | `Right v -> Some v
|
|
| `Both (x, y) -> (
|
|
match join x y with
|
|
| Error conflict -> raise @@ Join.Conflict conflict
|
|
| Ok z -> Some z))
|
|
with Join.Conflict err -> Error err
|
|
in
|
|
let inspect xs =
|
|
Sexp.List (Map.keys xs |> List.map ~f:K.comparator.sexp_of_t)
|
|
in
|
|
let order x y =
|
|
Map.symmetric_diff x y ~data_equal:equal
|
|
|> Sequence.fold ~init:(0, 0, 0) ~f:(fun (l, m, r) -> function
|
|
| _, `Left _ -> (l + 1, m, r)
|
|
| _, `Right _ -> (l, m, r + 1)
|
|
| _, `Unequal _ -> (l, m + 1, r))
|
|
|> function
|
|
| 0, 0, 0 -> Order.EQ
|
|
| 0, 0, _ -> LT
|
|
| _, 0, 0 -> GT
|
|
| _, _, _ -> NC
|
|
in
|
|
define ~inspect ~join ~empty ~order name
|
|
|
|
let optional ?(inspect = sexp_of_opaque) ?join ~equal name =
|
|
let join_data =
|
|
match join with
|
|
| Some join -> join
|
|
| None ->
|
|
fun x y ->
|
|
if equal x y then Ok y else Error (Join (name, inspect, x, y))
|
|
in
|
|
let inspect = sexp_of_option inspect in
|
|
let join x y =
|
|
match (x, y) with
|
|
| None, x | x, None -> Ok x
|
|
| Some x, Some y -> (
|
|
match join_data x y with
|
|
| Ok x -> Ok (Some x)
|
|
| Error err -> Error err)
|
|
in
|
|
flat ~inspect ~join ~empty:None ~equal:(Option.equal equal) name
|
|
|
|
let string =
|
|
define "string" ~empty:"" ~inspect:sexp_of_string ~order:(fun x y ->
|
|
match (String.is_empty x, String.is_empty y) with
|
|
| true, true -> EQ
|
|
| true, false -> GT
|
|
| false, true -> LT
|
|
| false, false -> partial_of_total String.compare x y)
|
|
|
|
let bool = optional ~inspect:sexp_of_bool ~equal:Bool.equal "bool"
|
|
end
|
|
|
|
module Persistent = struct
|
|
type 'a t =
|
|
| String : string t
|
|
| Define : { of_string : string -> 'a; to_string : 'a -> string } -> 'a t
|
|
| Derive : {
|
|
of_persistent : 'b -> 'a;
|
|
to_persistent : 'a -> 'b;
|
|
persistent : 'b t;
|
|
}
|
|
-> 'a t
|
|
|
|
let string = String
|
|
let define ~to_string ~of_string = Define { to_string; of_string }
|
|
|
|
let derive ~to_persistent ~of_persistent persistent =
|
|
Derive { to_persistent; of_persistent; persistent }
|
|
|
|
let of_binable : type a. (module Binable.S with type t = a) -> a t =
|
|
fun r ->
|
|
Define { to_string = Binable.to_string r; of_string = Binable.of_string r }
|
|
|
|
let rec to_string : type a. a t -> a -> string =
|
|
fun p x ->
|
|
match p with
|
|
| String -> x
|
|
| Define { to_string } -> to_string x
|
|
| Derive { to_persistent; persistent } ->
|
|
to_string persistent (to_persistent x)
|
|
|
|
let rec of_string : type a. a t -> string -> a =
|
|
fun p s ->
|
|
match p with
|
|
| String -> s
|
|
| Define { of_string } -> of_string s
|
|
| Derive { of_persistent; persistent } ->
|
|
of_persistent (of_string persistent s)
|
|
|
|
module Chunk = struct
|
|
(* bin_io will pack len+data, and restore it correspondingly *)
|
|
type t = { data : string } [@@deriving bin_io]
|
|
end
|
|
|
|
module KV = struct
|
|
type t = { key : string; data : string } [@@deriving bin_io]
|
|
end
|
|
|
|
module Chunks = struct
|
|
type t = Chunk.t list [@@deriving bin_io]
|
|
end
|
|
|
|
module KVS = struct
|
|
type t = KV.t list [@@deriving bin_io]
|
|
end
|
|
|
|
let chunks = of_binable (module Chunks)
|
|
let kvs = of_binable (module KVS)
|
|
|
|
let list p =
|
|
derive chunks
|
|
~to_persistent:(List.rev_map ~f:(fun x -> { Chunk.data = to_string p x }))
|
|
~of_persistent:(List.rev_map ~f:(fun { Chunk.data } -> of_string p data))
|
|
|
|
let array p =
|
|
derive chunks
|
|
~to_persistent:
|
|
(Array.fold ~init:[] ~f:(fun xs x ->
|
|
{ Chunk.data = to_string p x } :: xs))
|
|
~of_persistent:
|
|
(Array.of_list_rev_map ~f:(fun { Chunk.data } -> of_string p data))
|
|
|
|
let sequence p =
|
|
derive chunks
|
|
~to_persistent:(fun xs ->
|
|
Sequence.to_list_rev
|
|
@@ Sequence.map xs ~f:(fun x -> { Chunk.data = to_string p x }))
|
|
~of_persistent:(fun xs ->
|
|
Sequence.of_list
|
|
@@ List.rev_map xs ~f:(fun { Chunk.data } -> of_string p data))
|
|
|
|
let set c p =
|
|
derive (list p) ~to_persistent:Set.to_list ~of_persistent:(Set.of_list c)
|
|
|
|
let map c pk pd =
|
|
derive kvs
|
|
~to_persistent:
|
|
(Map.fold ~init:[] ~f:(fun ~key ~data xs ->
|
|
{ KV.key = to_string pk key; KV.data = to_string pd data } :: xs))
|
|
~of_persistent:
|
|
(List.fold ~init:(Map.empty c) ~f:(fun xs { KV.key; data } ->
|
|
let key = of_string pk key and data = of_string pd data in
|
|
Map.add_exn xs ~key ~data))
|
|
|
|
let name = of_binable (module Name)
|
|
end
|
|
|
|
type 'a obj = Oid.t
|
|
|
|
module Registry = struct
|
|
type info = { desc : string option }
|
|
|
|
type 'a rule = {
|
|
name : Name.t;
|
|
provides : 'a;
|
|
requires : Name.t list;
|
|
parameters : string list list;
|
|
comment : string;
|
|
}
|
|
|
|
type unfinished = Unifinished
|
|
type finished = Name.t
|
|
type def = unfinished rule
|
|
type doc = finished rule
|
|
|
|
let sexp_of_rule { name } = Name.sexp_of_t name
|
|
|
|
module Rule = struct
|
|
type t = finished rule
|
|
|
|
let hash { provides = name } = Name.hash name
|
|
let sexp_of_t = sexp_of_rule
|
|
|
|
include
|
|
Base.Comparable.Inherit
|
|
(Name)
|
|
(struct
|
|
type t = finished rule
|
|
|
|
let component { name } = name
|
|
let sexp_of_t = sexp_of_rule
|
|
end)
|
|
end
|
|
|
|
let public = Hashtbl.create (module Name)
|
|
let classes = Hashtbl.create (module String)
|
|
let slots = Hashtbl.create (module String)
|
|
let rules = Hash_set.create (module Rule)
|
|
|
|
let is_present ~package namespace name =
|
|
match Hashtbl.find namespace package with
|
|
| None -> false
|
|
| Some names -> Map.mem names name
|
|
|
|
let register kind namespace ?desc ?(package = user_package) name =
|
|
if is_present ~package namespace name then
|
|
failwithf
|
|
"Failed to declare a new %s, there is already a %s named `%s' in \
|
|
package `%s'"
|
|
kind kind name package ();
|
|
let info = { desc } in
|
|
Hashtbl.update namespace package ~f:(function
|
|
| None -> Map.singleton (module String) name info
|
|
| Some names -> Map.add_exn names ~key:name ~data:info);
|
|
Name.create ~package name
|
|
|
|
let start_rule ?package name =
|
|
{
|
|
name = Name.create ?package name;
|
|
provides = Unifinished;
|
|
requires = [];
|
|
parameters = [];
|
|
comment = "";
|
|
}
|
|
|
|
let rule_require name rule = { rule with requires = name :: rule.requires }
|
|
let rule_provide name rule = { rule with provides = name }
|
|
|
|
let rule_dynamic params rule =
|
|
{ rule with parameters = params :: rule.parameters }
|
|
|
|
let rule_comment comment rule = Hash_set.add rules { rule with comment }
|
|
let add_class = register "class" classes
|
|
let add_slot = register "property" slots
|
|
let is_public cls = Hashtbl.mem public cls
|
|
let public_class cls = Hashtbl.add_exn public cls []
|
|
|
|
let update_class ~cls ~slot =
|
|
if is_public cls then Hashtbl.add_multi public cls slot
|
|
|
|
let find namespace name =
|
|
let names = Hashtbl.find_exn namespace (Name.package name) in
|
|
Map.find_exn names (Name.unqualified name)
|
|
end
|
|
|
|
module Documentation = struct
|
|
module type Element = sig
|
|
type t
|
|
|
|
val name : t -> Name.t
|
|
val desc : t -> string
|
|
end
|
|
|
|
let agents = Agent.registry
|
|
|
|
module Agent = struct
|
|
type t = Agent.id
|
|
|
|
let of_agent = Agent.id
|
|
let name = Agent.name
|
|
let desc = Agent.desc
|
|
end
|
|
|
|
module Pair = struct
|
|
type t = Name.t * Registry.info
|
|
|
|
let name = fst
|
|
let desc (_, { Registry.desc }) = match desc with None -> "" | Some d -> d
|
|
end
|
|
|
|
module Class = Pair
|
|
module Property = Pair
|
|
|
|
module Rule = struct
|
|
open Registry
|
|
|
|
type t = Rule.t
|
|
|
|
let name t = t.name
|
|
let desc r = r.comment
|
|
let property name = (name, Registry.(find slots) name)
|
|
let provides r = property r.provides
|
|
let requires r = List.map ~f:property r.requires
|
|
let parameters r = r.parameters
|
|
|
|
let refmt input =
|
|
let max_column = 70 in
|
|
let buffer = Buffer.create 64 in
|
|
Buffer.add_string buffer "-- ";
|
|
let column = ref 3 in
|
|
let prev = ref ' ' in
|
|
let in_white () = Char.(!prev = ' ') in
|
|
let push c =
|
|
if !column >= max_column && in_white () then (
|
|
Buffer.add_string buffer "\n-- ";
|
|
column := 4);
|
|
Buffer.add_char buffer c;
|
|
if Char.is_whitespace c then prev := ' ' else prev := c;
|
|
incr column
|
|
in
|
|
let skip = () in
|
|
String.iter input ~f:(fun c ->
|
|
if Char.is_whitespace c then if in_white () then skip else push ' '
|
|
else push c);
|
|
Buffer.contents buffer
|
|
|
|
let pp_parameters ppf = function
|
|
| [] -> ()
|
|
| ps ->
|
|
List.iter ps ~f:(fun ps ->
|
|
let pp_sep ppf () = Format.fprintf ppf ", " in
|
|
Format.fprintf ppf "(%a)"
|
|
Format.(pp_print_list ~pp_sep pp_print_string)
|
|
ps)
|
|
|
|
let pp ppf { parameters; provides; requires; name; comment } =
|
|
if String.(comment <> "") then Format.fprintf ppf "%s@\n" (refmt comment);
|
|
Format.fprintf ppf "@[<v2>%a%a ::=@\n" Name.pp name pp_parameters
|
|
parameters;
|
|
let max_len = ref (String.length (Name.to_string provides)) in
|
|
List.iter requires ~f:(fun name ->
|
|
let len = String.length (Name.to_string name) in
|
|
max_len := Int.max len !max_len;
|
|
Format.fprintf ppf "%a@\n" Name.pp name);
|
|
let sep = String.make !max_len '-' in
|
|
Format.fprintf ppf "%s@\n%a@]@\n" sep Name.pp provides
|
|
end
|
|
|
|
let classes () =
|
|
Hashtbl.to_alist Registry.public
|
|
|> List.map ~f:(fun (cls, slots) ->
|
|
( (cls, Registry.(find classes) cls),
|
|
List.map slots ~f:(fun slot -> (slot, Registry.(find slots) slot))
|
|
))
|
|
|
|
let rules () = Hash_set.to_list Registry.rules
|
|
end
|
|
|
|
module Class = struct
|
|
type +'s info = { name : Name.t; sort : 's }
|
|
|
|
let id { name } = name
|
|
|
|
type (+'a, +'s) t = 's info
|
|
|
|
let newclass ?(public = false) ?desc ?package name sort =
|
|
let name = Registry.add_class ?desc ?package name in
|
|
if public then Registry.public_class name;
|
|
{ name; sort }
|
|
|
|
let declare :
|
|
?public:bool ->
|
|
?desc:string ->
|
|
?package:string ->
|
|
string ->
|
|
's ->
|
|
('k, 's) t =
|
|
fun ?public ?desc ?package name data ->
|
|
newclass ?public ?desc ?package name data
|
|
|
|
let refine { name } sort = { name; sort }
|
|
let same x y = Name.equal x.name y.name
|
|
|
|
let equal : type a b.
|
|
(a, _) t -> (b, _) t -> (a obj, b obj) Type_equal.t option =
|
|
fun x y -> Option.some_if (same x y) Type_equal.T
|
|
|
|
let assert_equal x y =
|
|
match equal x y with
|
|
| Some t -> t
|
|
| None ->
|
|
failwithf
|
|
"assert_equal: wrong assertion, classes of %s and %s are different"
|
|
(Name.to_string x.name) (Name.to_string y.name) ()
|
|
|
|
let sort { sort } = sort
|
|
let name { name } = name
|
|
end
|
|
|
|
module Dict = struct
|
|
module Key = struct
|
|
module Uid = Int
|
|
|
|
let last_id = ref 0
|
|
|
|
type 'a witness = ..
|
|
|
|
module type Witness = sig
|
|
type t
|
|
type _ witness += Id : t witness
|
|
end
|
|
|
|
type 'a typeid = (module Witness with type t = 'a)
|
|
|
|
type 'a t = {
|
|
ord : Uid.t;
|
|
key : 'a typeid;
|
|
name : Name.t;
|
|
show : 'a -> Sexp.t;
|
|
}
|
|
|
|
let newtype (type a) () : a typeid =
|
|
let module Type = struct
|
|
type t = a
|
|
type _ witness += Id : t witness
|
|
end in
|
|
(module Type)
|
|
|
|
let create ~name show =
|
|
let key = newtype () in
|
|
incr last_id;
|
|
{ key; ord = !last_id; name; show }
|
|
|
|
let uid { ord } = ord [@@inline]
|
|
|
|
let compare k1 k2 =
|
|
let k1 = uid k1 and k2 = uid k2 in
|
|
(Uid.compare [@inlined]) k1 k2
|
|
[@@inline]
|
|
|
|
let name x = x.name
|
|
let to_sexp x = x.show
|
|
let equal x y = Int.equal x.ord y.ord [@@inline]
|
|
|
|
let same (type a b) x y : (a, b) Type_equal.t =
|
|
if equal x y then
|
|
let module X = (val x.key : Witness with type t = a) in
|
|
let module Y = (val y.key : Witness with type t = b) in
|
|
match X.Id with
|
|
| Y.Id -> Type_equal.T
|
|
| _ -> failwith "broken type equality"
|
|
else failwith "types are not equal"
|
|
|
|
let ( < ) x y = uid x < uid y [@@inline]
|
|
let ( > ) x y = uid x > uid y [@@inline]
|
|
let ( = ) x y = uid x = uid y [@@inline]
|
|
let ( <> ) x y = uid x <> uid y [@@inline]
|
|
|
|
(** Allen's Interval Algebra
|
|
|
|
The Allen's Interval Algebra [1,2] describes 13 possible relations
|
|
between two intervals. See also [3] for the nice visualizations and an
|
|
available description.
|
|
|
|
[1]: https://doi.org/10.1145/182.358434 [2]:
|
|
https://doi.org/10.1111/j.1467-8640.1989.tb00329.x [3]:
|
|
https://www.thomasalspaugh.org/pub/fnd/allen.html *)
|
|
module Interval = struct
|
|
type order =
|
|
| Before
|
|
| Meets
|
|
| Overlaps
|
|
| Finished
|
|
| Contains
|
|
| Starts
|
|
| Equals
|
|
| Started
|
|
| During
|
|
| Finishes
|
|
| Overlapped
|
|
| Met
|
|
| After
|
|
|
|
let invert f a b c d = f c d a b [@@inline]
|
|
let meets _ b c _ = b = c [@@inline]
|
|
let met a b c d = invert meets a b c d [@@inline] [@@specialise]
|
|
let before _ b c _ = b < c [@@inline]
|
|
let after a b c d = invert before a b c d [@@inline] [@@specialise]
|
|
let overlaps a b c d = a < c && b < d && b > c [@@inline]
|
|
let overlapped a b c d = invert overlaps a b c d [@@inline] [@@specialise]
|
|
let starts a b c d = a = c && b < d [@@inline]
|
|
let started a b c d = invert starts a b c d [@@inline] [@@specialise]
|
|
let finishes a b c d = a > c && b = d [@@inline]
|
|
let finished a b c d = invert finishes a b c d [@@inline] [@@specialise]
|
|
let during a b c d = a > c && b < d [@@inline]
|
|
let contains a b c d = invert during a b c d [@@inline] [@@specialise]
|
|
let equals a b c d = a = c && b = d [@@inline]
|
|
|
|
let relate a b c d =
|
|
match () with
|
|
| () when meets a b c d -> Meets
|
|
| () when met a b c d -> Met
|
|
| () when before a b c d -> Before
|
|
| () when after a b c d -> After
|
|
| () when overlaps a b c d -> Overlaps
|
|
| () when overlapped a b c d -> Overlapped
|
|
| () when starts a b c d -> Starts
|
|
| () when started a b c d -> Started
|
|
| () when finishes a b c d -> Finishes
|
|
| () when finished a b c d -> Finished
|
|
| () when during a b c d -> During
|
|
| () when contains a b c d -> Contains
|
|
| () when equals a b c d -> Equals
|
|
| () -> assert false
|
|
[@@inline]
|
|
end
|
|
|
|
(** Extension of the Allen's Algebra over points.
|
|
|
|
A point can have only five relations to an interval. *)
|
|
module Point = struct
|
|
type order =
|
|
| Before (* preceeds the interval *)
|
|
| Starts (* equal to the start *)
|
|
| During (* inside of the interval *)
|
|
| Finishes (* equal to the end *)
|
|
| After (* follows the interval *)
|
|
|
|
let before p a _ = p < a [@@inline]
|
|
let starts p a _ = p = a [@@inline]
|
|
let during p a b = p > a && p < b [@@inline]
|
|
let finishes p _ b = p = b [@@inline]
|
|
let after p _ b = p > b [@@inline]
|
|
|
|
let relate p a b =
|
|
match () with
|
|
| () when before p a b -> Before
|
|
| () when starts p a b -> Starts
|
|
| () when during p a b -> During
|
|
| () when finishes p a b -> Finishes
|
|
| () when after p a b -> After
|
|
| () -> assert false
|
|
[@@inline]
|
|
end
|
|
end
|
|
|
|
type 'a key = 'a Key.t
|
|
|
|
(* five leaves holding from zero to four elements and
|
|
three non-leaf trees that can either lean left (when
|
|
the left tree/leg is shorter, lean right (the right one
|
|
is shorter), or stand on equal legs.
|
|
*)
|
|
type record =
|
|
| T0
|
|
| T1 : 'a key * 'a -> record
|
|
| T2 : 'a key * 'a * 'b key * 'b -> record
|
|
| T3 : 'a key * 'a * 'b key * 'b * 'c key * 'c -> record
|
|
| T4 : 'a key * 'a * 'b key * 'b * 'c key * 'c * 'd key * 'd -> record
|
|
| LL : record * 'a key * 'a * record -> record (* h(x) = h(y) - 1 *)
|
|
| EQ : record * 'a key * 'a * record -> record (* h(x) = h(y) *)
|
|
| LR : record * 'a key * 'a * record -> record (* h(x) = h(y) + 1 *)
|
|
|
|
type t = record
|
|
|
|
let pp_field ppf (k, v) =
|
|
Format.fprintf ppf "%s : %a"
|
|
(Name.to_string (Key.name k))
|
|
Sexp.pp_hum (Key.to_sexp k v)
|
|
|
|
let rec pp_fields ppf = function
|
|
| T0 -> ()
|
|
| T1 (ka, a) -> Format.fprintf ppf "%a" pp_field (ka, a)
|
|
| T2 (ka, a, kb, b) ->
|
|
Format.fprintf ppf "%a;@ %a" pp_field (ka, a) pp_field (kb, b)
|
|
| T3 (ka, a, kb, b, kc, c) ->
|
|
Format.fprintf ppf "%a;@ %a;@ %a" pp_field (ka, a) pp_field (kb, b)
|
|
pp_field (kc, c)
|
|
| T4 (ka, a, kb, b, kc, c, kd, d) ->
|
|
Format.fprintf ppf "%a;@ %a;@ %a;@ %a" pp_field (ka, a) pp_field (kb, b)
|
|
pp_field (kc, c) pp_field (kd, d)
|
|
| LR (x, ka, a, y) ->
|
|
Format.fprintf ppf "%a;@ %a;@ %a" pp_fields x pp_field (ka, a) pp_fields
|
|
y
|
|
| LL (x, ka, a, y) ->
|
|
Format.fprintf ppf "%a;@ %a;@ %a" pp_fields x pp_field (ka, a) pp_fields
|
|
y
|
|
| EQ (x, ka, a, y) ->
|
|
Format.fprintf ppf "%a;@ %a;@ %a" pp_fields x pp_field (ka, a) pp_fields
|
|
y
|
|
|
|
let pp ppf t = Format.fprintf ppf "{@[<2>@,%a@]}" pp_fields t
|
|
|
|
let pp_elt ppf (k, v) =
|
|
Format.fprintf ppf "%d:%a" (Key.uid k) Sexp.pp_hum (Key.to_sexp k v)
|
|
|
|
let pp_elt ppf (k, _) = Format.fprintf ppf "%d" (Key.uid k)
|
|
|
|
let rec pp_tree ppf = function
|
|
| T0 -> Format.fprintf ppf "()"
|
|
| T1 (ka, a) -> Format.fprintf ppf "(%a)" pp_elt (ka, a)
|
|
| T2 (ka, a, kb, b) ->
|
|
Format.fprintf ppf "(%a,%a)" pp_elt (ka, a) pp_elt (kb, b)
|
|
| T3 (ka, a, kb, b, kc, c) ->
|
|
Format.fprintf ppf "(%a,%a,%a)" pp_elt (ka, a) pp_elt (kb, b) pp_elt
|
|
(kc, c)
|
|
| T4 (ka, a, kb, b, kc, c, kd, d) ->
|
|
Format.fprintf ppf "(%a,%a,%a,%a)" pp_elt (ka, a) pp_elt (kb, b) pp_elt
|
|
(kc, c) pp_elt (kd, d)
|
|
| LR (x, k, a, y) ->
|
|
Format.fprintf ppf "LR(%a,%a,%a)" pp_tree x pp_elt (k, a) pp_tree y
|
|
| LL (x, k, a, y) ->
|
|
Format.fprintf ppf "LL(%a,%a,%a)" pp_tree x pp_elt (k, a) pp_tree y
|
|
| EQ (x, k, a, y) ->
|
|
Format.fprintf ppf "EQ(%a,%a,%a)" pp_tree x pp_elt (k, a) pp_tree y
|
|
|
|
let empty = T0
|
|
let is_empty = function T0 -> true | _ -> false
|
|
|
|
(*
|
|
- LL (x,y) : h(x) = h(y) - 1
|
|
- EQ (x,y) : h(x) = h(y)
|
|
- LR (x,y) : h(x) = h(y) + 1
|
|
*)
|
|
|
|
let ( <$ ) k1 k2 =
|
|
let k1 = Key.uid k1 and k2 = Key.uid k2 in
|
|
(Key.Uid.( < ) [@inlined]) k1 k2
|
|
[@@inline]
|
|
|
|
let make0 = T0 [@@inlined]
|
|
let make1 k a = T1 (k, a) [@@inline]
|
|
let make2 ka a kb b = T2 (ka, a, kb, b) [@@inline]
|
|
let make3 ka a kb b kc c = T3 (ka, a, kb, b, kc, c) [@@inline]
|
|
let make4 ka a kb b kc c kd d = T4 (ka, a, kb, b, kc, c, kd, d) [@@inline]
|
|
|
|
let make5 ka a kb b kc c kd d ke e =
|
|
EQ (make2 ka a kb b, kc, c, make2 kd d ke e)
|
|
[@@inline]
|
|
|
|
let make6 ka a kb b kc c kd d ke e kf f =
|
|
EQ (T2 (ka, a, kb, b), kc, c, T3 (kd, d, ke, e, kf, f))
|
|
[@@inline]
|
|
|
|
let make7 ka a kb b kc c kd d ke e kf f kg g =
|
|
EQ (T3 (ka, a, kb, b, kc, c), kd, d, T3 (ke, e, kf, f, kg, g))
|
|
[@@inline]
|
|
|
|
let make8 ka a kb b kc c kd d ke e kf f kg g kh h =
|
|
EQ (T3 (ka, a, kb, b, kc, c), kd, d, T4 (ke, e, kf, f, kg, g, kh, h))
|
|
[@@inline]
|
|
|
|
let make9 ka a kb b kc c kd d ke e kf f kg g kh h ki i =
|
|
EQ (T4 (ka, a, kb, b, kc, c, kd, d), ke, e, T4 (kf, f, kg, g, kh, h, ki, i))
|
|
[@@inline]
|
|
|
|
let make10 ka a kb b kc c kd d ke e kf f kg g kh h ki i kj j =
|
|
LL (make4 ka a kb b kc c kd d, ke, e, make5 kf f kg g kh h ki i kj j)
|
|
[@@inline]
|
|
|
|
type 'r visitor = { visit : 'a. 'a key -> 'a -> 'r -> 'r }
|
|
|
|
let rec foreach x ~init f =
|
|
match x with
|
|
| T0 -> init
|
|
| T1 (ka, a) -> f.visit ka a init
|
|
| T2 (ka, a, kb, b) -> f.visit ka a init |> f.visit kb b
|
|
| T3 (ka, a, kb, b, kc, c) ->
|
|
f.visit ka a init |> f.visit kb b |> f.visit kc c
|
|
| T4 (ka, a, kb, b, kc, c, kd, d) ->
|
|
f.visit ka a init |> f.visit kb b |> f.visit kc c |> f.visit kd d
|
|
| LL (x, k, a, y) -> foreach y f ~init:(f.visit k a @@ foreach x ~init f)
|
|
| EQ (x, k, a, y) -> foreach y f ~init:(f.visit k a @@ foreach x ~init f)
|
|
| LR (x, k, a, y) -> foreach y f ~init:(f.visit k a @@ foreach x ~init f)
|
|
|
|
type ('b, 'r) app = { app : 'a. 'a key -> 'a -> 'b -> 'r }
|
|
|
|
let cmp x y = Key.compare x y [@@inline]
|
|
let eq x y = Key.compare x y = 0 [@@inline]
|
|
|
|
exception Rol_wrong_rank of record
|
|
exception Ror_wrong_rank of record
|
|
|
|
let rol = function
|
|
| LL (x, ka, a, LL (y, kb, b, z)) ->
|
|
(*
|
|
* h(x) = m-2
|
|
* h(LL(y,b,z)=m
|
|
* h(y)=m-2
|
|
* h(z)=m-1
|
|
* ----------------
|
|
* h(EQ(x,a,y)) = m-1
|
|
* h(EQ(EQ(x,ka,a,y),b,z)) = m
|
|
*)
|
|
EQ (EQ (x, ka, a, y), kb, b, z)
|
|
| LL (x, ka, a, EQ (y, kb, b, z)) ->
|
|
(*
|
|
* h(x) = m-2
|
|
* h(EQ(y,b,z))=m
|
|
* h(y)=m-1
|
|
* h(z)=m-1
|
|
* ----------------
|
|
* h(LL(x,a,y)) = m
|
|
* h(LR(LL(x,a,y),b,z)) = m+1
|
|
*)
|
|
LR (LL (x, ka, a, y), kb, b, z)
|
|
| LL (w, ka, a, LR (LL (x, kb, b, y), kc, c, z)) ->
|
|
(*
|
|
* h(w) = m-2
|
|
* h(LR(LL(x,b,y),c,z))=m
|
|
* h(z)=m-2
|
|
* h(LL(x,b,y))=m-1
|
|
* h(y)=m-2
|
|
* h(x)=m-3
|
|
* ----------------
|
|
* h(LR(w,a,x))=m-1, h(x) < h(w)
|
|
* h(EQ(y,kc,c,z))=m-1, h(y) = h(z)
|
|
* h(EQ (LR(w,ka,a,x),kb,b,EQ(y,kc,c,z))) = m
|
|
*)
|
|
EQ (LR (w, ka, a, x), kb, b, EQ (y, kc, c, z))
|
|
| LL (w, ka, a, LR (EQ (x, kb, b, y), kc, c, z)) ->
|
|
(*
|
|
* h(w) = m-2
|
|
* h(LR(EQ(x,b,y),c,z))=m
|
|
* h(z)=m-2
|
|
* h(EQ(x,b,y))=m-1
|
|
* h(y)=m-2
|
|
* h(x)=m-2
|
|
* ----------------
|
|
* h(EQ(w,a,x))=m-1, h(x) = h(w)
|
|
* h(EQ(y,kc,c,z))=m-1, h(y) = h(z)
|
|
* h(EQ (EQ(w,ka,a,x),kb,b,EQ(y,kc,c,z))) = m
|
|
*)
|
|
EQ (EQ (w, ka, a, x), kb, b, EQ (y, kc, c, z))
|
|
| LL (w, ka, a, LR (LR (x, kb, b, y), kc, c, z)) ->
|
|
(*
|
|
* h(w) = m-2
|
|
* h(LR(LR(x,b,y),c,z))=m
|
|
* h(z)=m-2
|
|
* h(LR(x,b,y))=m-1
|
|
* h(y)=m-3
|
|
* h(x)=m-2
|
|
* ----------------
|
|
* h(EQ(w,a,x))=m-1, h(x) = h(w)
|
|
* h(LL(y,kc,c,z))=m-1, h(y) < h(z)
|
|
* h(EQ (EQ(w,ka,a,x),kb,b,LL(y,kc,c,z))) = m
|
|
*)
|
|
EQ (EQ (w, ka, a, x), kb, b, LL (y, kc, c, z))
|
|
| r -> raise (Rol_wrong_rank r)
|
|
[@@inline]
|
|
|
|
let ror = function
|
|
| LR (LR (x, ka, a, y), kb, b, z) ->
|
|
(*
|
|
* h(z) = m-2
|
|
* h(LR(x,a,y))=m
|
|
* h(y)=m-2
|
|
* h(x)=m-1
|
|
* ------------------
|
|
* h(EQ(y,b,z))=m-1, h(y) = h(z)
|
|
* h(EQ (x,a,EQ(y,kb,b,z))) = m
|
|
*)
|
|
EQ (x, ka, a, EQ (y, kb, b, z))
|
|
| LR (EQ (x, ka, a, y), kb, b, z) ->
|
|
(*
|
|
* h(z) = m-2
|
|
* h(EQ(x,a,y))=m
|
|
* h(y)=m-1
|
|
* h(x)=m-1
|
|
* ------------------
|
|
* h(LR(y,b,z))=m, h(y) > h(z)
|
|
* h(LL (x,a,LR(y,b,z))) = m+1, h(x) < m
|
|
*)
|
|
LL (x, ka, a, LR (y, kb, b, z))
|
|
| LR (LL (w, ka, a, LR (x, kb, b, y)), kc, c, z) ->
|
|
(*
|
|
* h(z) = m-2
|
|
* h(LL (w,a,LR(x,b,y)))=m
|
|
* h(LR(x,b,y))=m-1
|
|
* h(w)=m-2
|
|
* h(x)=m-2
|
|
* h(y)=m-3
|
|
* -------------------------
|
|
* h(EQ(w,a,x)) = m-1, h(x) = h(w)
|
|
* h(LL(y,c,z)) = m-1, h(y) < h(z)
|
|
*)
|
|
EQ (EQ (w, ka, a, x), kb, b, LL (y, kc, c, z))
|
|
| LR (LL (w, ka, a, EQ (x, kb, b, y)), kc, c, z) ->
|
|
(*
|
|
* h(z) = m-2
|
|
* h(LL (w,a,EQ(x,b,y)))=m
|
|
* h(EQ(x,b,y))=m-1
|
|
* h(w)=m-2
|
|
* h(x)=m-2
|
|
* h(y)=m-2
|
|
* -------------------------
|
|
* h(EQ(w,a,x)) = m-1, h(x) = h(w)
|
|
* h(EQ(y,c,z)) = m-1, h(y) = h(z)
|
|
*)
|
|
EQ (EQ (w, ka, a, x), kb, b, EQ (y, kc, c, z))
|
|
| LR (LL (w, ka, a, LL (x, kb, b, y)), kc, c, z) ->
|
|
(*
|
|
* h(z) = m-2
|
|
* h(LL (w,a,LL(x,b,y)))=m
|
|
* h(LL(x,b,y))=m-1
|
|
* h(w)=m-2
|
|
* h(x)=m-3
|
|
* h(y)=m-2
|
|
* -------------------------
|
|
* h(LR(w,a,x)) = m-1, h(x) < h(w)
|
|
* h(EQ(y,c,z)) = m-1, h(y) = h(z)
|
|
*)
|
|
EQ (LR (w, ka, a, x), kb, b, EQ (y, kc, c, z))
|
|
| r -> raise (Ror_wrong_rank r)
|
|
[@@inline]
|
|
|
|
let rank_increases was now =
|
|
match (was, now) with
|
|
| (T0 | T1 _ | T2 _ | T3 _ | T4 _), LR _
|
|
| (T0 | T1 _ | T2 _ | T3 _ | T4 _), EQ _
|
|
| (T0 | T1 _ | T2 _ | T3 _ | T4 _), LL _ ->
|
|
true
|
|
| EQ _, LL _ | EQ _, LR _ -> true
|
|
| LR _, LL _ | LL _, LR _ -> false
|
|
| _ -> false
|
|
[@@inline]
|
|
|
|
(* [p += c] updates the right subtree of [p] with [c].
|
|
pre: rank p > 1 /\ rank c > 1 *)
|
|
let ( += ) p c' =
|
|
match p with
|
|
| LL (b, k, x, c) ->
|
|
if rank_increases c c' then rol (LL (b, k, x, c')) else LL (b, k, x, c')
|
|
| LR (b, k, x, c) ->
|
|
if rank_increases c c' then EQ (b, k, x, c') else LR (b, k, x, c')
|
|
| EQ (b, k, x, c) ->
|
|
if rank_increases c c' then LL (b, k, x, c') else EQ (b, k, x, c')
|
|
| _ -> failwith "+=: rank < 2"
|
|
[@@inline]
|
|
|
|
(* [b =+ p] updates the left subtree of [p] with [b].
|
|
pre: rank p > 1 /\ rank b > 1 *)
|
|
let ( =+ ) b' p =
|
|
match p with
|
|
| LL (b, k, x, c) ->
|
|
if rank_increases b b' then EQ (b', k, x, c) else LL (b', k, x, c)
|
|
| LR (b, k, x, c) ->
|
|
if rank_increases b b' then ror (LR (b', k, x, c)) else LR (b', k, x, c)
|
|
| EQ (b, k, x, c) ->
|
|
if rank_increases b b' then LR (b', k, x, c) else EQ (b', k, x, c)
|
|
| _ -> failwith "=+: rank < 2"
|
|
[@@inline]
|
|
|
|
(* pre:
|
|
- a is not in t;
|
|
- for all functions except [bal] t is balanced;
|
|
- for [bal] the input is t is disbalanced.
|
|
|
|
post:
|
|
- a is in t', and len t' = len t + 1
|
|
- h(t') >= h(t)
|
|
- t' is balanced
|
|
*)
|
|
let rec insert : type a. a key -> a -> record -> record =
|
|
fun ka a -> function
|
|
| T0 -> make1 ka a
|
|
| T1 (kb, b) -> if ka <$ kb then make2 ka a kb b else make2 kb b ka a
|
|
| T2 (kb, b, kc, c) ->
|
|
if ka <$ kb then make3 ka a kb b kc c
|
|
else if ka <$ kc then make3 kb b ka a kc c
|
|
else make3 kb b kc c ka a
|
|
| T3 (kb, b, kc, c, kd, d) ->
|
|
if ka <$ kc then
|
|
if ka <$ kb then make4 ka a kb b kc c kd d
|
|
else make4 kb b ka a kc c kd d
|
|
else if ka <$ kd then make4 kb b kc c ka a kd d
|
|
else make4 kb b kc c kd d ka a
|
|
| T4 (kb, b, kc, c, kd, d, ke, e) ->
|
|
if ka <$ kd then
|
|
if ka <$ kc then
|
|
if ka <$ kb then make5 ka a kb b kc c kd d ke e
|
|
else make5 kb b ka a kc c kd d ke e
|
|
else make5 kb b kc c ka a kd d ke e
|
|
else if ka <$ ke then make5 kb b kc c kd d ka a ke e
|
|
else make5 kb b kc c kd d ke e ka a
|
|
| LL (b, k, _, c) as t ->
|
|
if ka <$ k then insert ka a b =+ t else t += insert ka a c
|
|
| LR (b, k, _, c) as t ->
|
|
if ka <$ k then insert ka a b =+ t else t += insert ka a c
|
|
| EQ (b, k, _, c) as t ->
|
|
if ka <$ k then insert ka a b =+ t else t += insert ka a c
|
|
|
|
(* [merge k x y] *)
|
|
type merge = { merge : 'a. 'a key -> 'a -> 'a -> 'a }
|
|
|
|
let merge : type a b. merge -> a key -> b key -> b -> a -> a =
|
|
fun { merge } ka kb b a ->
|
|
let T = Key.same ka kb in
|
|
merge kb b a
|
|
|
|
let app = merge
|
|
|
|
let rec upsert ~update:ret ~insert:add ka a t =
|
|
match t with
|
|
| T0 -> add (make1 ka a)
|
|
| T1 (kb, b) ->
|
|
if eq ka kb then ret (fun f -> make1 ka (app f ka kb b a))
|
|
else add (insert ka a t)
|
|
| T2 (kb, b, kc, c) ->
|
|
if eq ka kb then ret (fun f -> make2 ka (app f ka kb b a) kc c)
|
|
else if eq ka kc then ret (fun f -> make2 kb b ka (app f ka kc c a))
|
|
else add (insert ka a t)
|
|
| T3 (kb, b, kc, c, kd, d) -> (
|
|
match cmp ka kc with
|
|
| 0 -> ret (fun f -> make3 kb b ka (app f ka kc c a) kd d)
|
|
| 1 ->
|
|
if eq ka kd then ret (fun f -> make3 kb b kc c ka (app f ka kd d a))
|
|
else add (insert ka a t)
|
|
| _ ->
|
|
if eq ka kb then ret (fun f -> make3 ka (app f ka kb b a) kc c kd d)
|
|
else add @@ insert ka a t)
|
|
| T4 (kb, b, kc, c, kd, d, ke, e) -> (
|
|
match cmp ka kd with
|
|
| 0 -> ret @@ fun f -> make4 kb b kc c ka (app f ka kd d a) ke e
|
|
| 1 ->
|
|
if eq ka ke then
|
|
ret @@ fun f -> make4 kb b kc c kd d ka (app f ka ke e a)
|
|
else add @@ insert ka a t
|
|
| _ -> (
|
|
match cmp ka kc with
|
|
| 0 -> ret @@ fun f -> make4 kb b ka (app f ka kc c a) kd d ke e
|
|
| 1 -> add @@ insert ka a t
|
|
| _ ->
|
|
if eq ka kb then
|
|
ret @@ fun f -> make4 ka (app f ka kb b a) kc c kd d ke e
|
|
else add @@ insert ka a t))
|
|
| LL (x, kb, b, y) -> (
|
|
match cmp ka kb with
|
|
| 0 -> ret @@ fun f -> LL (x, ka, app f ka kb b a, y)
|
|
| 1 ->
|
|
upsert ka a y
|
|
~update:(fun k -> ret @@ fun f -> LL (x, kb, b, k f))
|
|
~insert:(fun y -> add (t += y))
|
|
| _ ->
|
|
upsert ka a x
|
|
~update:(fun k -> ret @@ fun f -> LL (k f, kb, b, y))
|
|
~insert:(fun x -> add (x =+ t)))
|
|
| EQ (x, kb, b, y) -> (
|
|
match cmp ka kb with
|
|
| 0 -> ret @@ fun f -> EQ (x, ka, app f ka kb b a, y)
|
|
| 1 ->
|
|
upsert ka a y
|
|
~update:(fun k -> ret @@ fun f -> EQ (x, kb, b, k f))
|
|
~insert:(fun y -> add (t += y))
|
|
| _ ->
|
|
upsert ka a x
|
|
~update:(fun k -> ret @@ fun f -> EQ (k f, kb, b, y))
|
|
~insert:(fun x -> add (x =+ t)))
|
|
| LR (x, kb, b, y) -> (
|
|
match cmp ka kb with
|
|
| 0 -> ret @@ fun f -> LR (x, ka, app f ka kb b a, y)
|
|
| 1 ->
|
|
upsert ka a y
|
|
~update:(fun k -> ret @@ fun f -> LR (x, kb, b, k f))
|
|
~insert:(fun y -> add (t += y))
|
|
| _ ->
|
|
upsert ka a x
|
|
~update:(fun k -> ret @@ fun f -> LR (k f, kb, b, y))
|
|
~insert:(fun x -> add (x =+ t)))
|
|
[@@specialise]
|
|
|
|
let monomorphic_merge : type t. t key -> (t -> t -> t) -> merge =
|
|
fun k f ->
|
|
{
|
|
merge =
|
|
(fun (type a) (kb : a key) (b : a) (a : a) : a ->
|
|
let T = Key.same k kb in
|
|
f b a);
|
|
}
|
|
[@@specialise]
|
|
|
|
let update f ka a x =
|
|
let f = monomorphic_merge ka f in
|
|
upsert ka a x ~update:(fun k -> k f) ~insert:(fun x -> x)
|
|
[@@specialise]
|
|
|
|
let set ka a x =
|
|
let f = monomorphic_merge ka (fun _ x -> x) in
|
|
upsert ka a x ~update:(fun k -> k f) ~insert:(fun x -> x)
|
|
|
|
exception Field_not_found
|
|
|
|
let return (type a b) (k : a key) (ka : b key) (a : b) : a =
|
|
let T = Key.same k ka in
|
|
a
|
|
[@@inline]
|
|
|
|
let rec get k = function
|
|
| T0 -> raise Field_not_found
|
|
| T1 (ka, a) -> if eq k ka then return k ka a else raise Field_not_found
|
|
| T2 (ka, a, kb, b) -> (
|
|
match cmp k kb with
|
|
| 0 -> return k kb b
|
|
| 1 -> raise Field_not_found
|
|
| _ -> if eq k ka then return k ka a else raise Field_not_found)
|
|
| T3 (ka, a, kb, b, kc, c) -> (
|
|
match cmp k kb with
|
|
| 0 -> return k kb b
|
|
| 1 -> if eq k kc then return k kc c else raise Field_not_found
|
|
| _ -> if eq k ka then return k ka a else raise Field_not_found)
|
|
| T4 (ka, a, kb, b, kc, c, kd, d) -> (
|
|
match cmp k kc with
|
|
| 0 -> return k kc c
|
|
| 1 -> if eq k kd then return k kd d else raise Field_not_found
|
|
| _ -> (
|
|
match cmp k kb with
|
|
| 0 -> return k kb b
|
|
| 1 -> raise Field_not_found
|
|
| _ -> if eq k ka then return k ka a else raise Field_not_found))
|
|
| LL (x, ka, a, y) -> (
|
|
match cmp k ka with 0 -> return k ka a | 1 -> get k y | _ -> get k x)
|
|
| EQ (x, ka, a, y) -> (
|
|
match cmp k ka with 0 -> return k ka a | 1 -> get k y | _ -> get k x)
|
|
| LR (x, ka, a, y) -> (
|
|
match cmp k ka with 0 -> return k ka a | 1 -> get k y | _ -> get k x)
|
|
|
|
let find k x = try Some (get k x) with Field_not_found -> None
|
|
|
|
let fold_merge (type a) m x y =
|
|
foreach y ~init:x
|
|
{
|
|
visit =
|
|
(fun (type b c) (ka : b key) (a : b) x ->
|
|
upsert ka a x ~insert:(fun x -> x) ~update:(fun k -> k m));
|
|
}
|
|
|
|
let merge_11 m ka a kb b =
|
|
match Key.compare ka kb with
|
|
| 0 -> make1 ka (app m ka kb b a)
|
|
| 1 -> make2 kb b ka a
|
|
| _ -> make2 ka a kb b
|
|
[@@inline]
|
|
|
|
let merge_12 m ka a kb b kc c =
|
|
match Key.Point.relate ka kb kc with
|
|
| Before -> make3 ka a kb b kc c
|
|
| Starts -> make2 ka (app m ka kb b a) kc c
|
|
| During -> make3 kb b ka a kc c
|
|
| Finishes -> make2 kb b ka (app m ka kc c a)
|
|
| After -> make3 kb b kc c ka a
|
|
[@@inline]
|
|
|
|
let merge_13 m ka a kb b kc c kd d =
|
|
match Key.Point.relate ka kb kd with
|
|
| Before -> make4 ka a kb b kc c kd d
|
|
| Starts -> make3 ka (app m ka kb b a) kc c kd d
|
|
| Finishes -> make3 kb b kc c kd (app m kd ka a d)
|
|
| After -> make4 kb b kc c kd d ka a
|
|
| During -> (
|
|
match Key.compare ka kc with
|
|
| 0 -> make3 kb b kc (app m kc ka a c) kd d
|
|
| 1 -> make4 kb b kc c ka a kd d
|
|
| _ -> make4 kb b ka a kc c kd d)
|
|
[@@inline]
|
|
|
|
let merge_22 m ka a kb b kc c kd d =
|
|
match Key.Interval.relate ka kb kc kd with
|
|
| Meets -> make3 ka a kb (app m kb kc c b) kd d
|
|
| Met -> make3 kc c kd (app m kd ka a d) kb b
|
|
| Before -> make4 ka a kb b kc c kd d
|
|
| After -> make4 kc c kd d ka a kb b
|
|
| Overlaps -> make4 ka a kc c kb b kd d
|
|
| Overlapped -> make4 kc c ka a kd d kb b
|
|
| Starts -> make3 ka (app m ka kc c a) kb b kd d
|
|
| Started -> make3 ka (app m ka kc c a) kd d kb b
|
|
| Finishes -> make3 kc c ka a kb (app m kb kd d b)
|
|
| Finished -> make3 ka a kc c kb (app m kb kd d b)
|
|
| During -> make4 kc c ka a kb b kd d
|
|
| Contains -> make4 ka a kc c kd d kb b
|
|
| Equals -> make2 ka (app m ka kc c a) kb (app m kb kd d b)
|
|
[@@inline]
|
|
|
|
let merge m x y =
|
|
if phys_equal x y then x
|
|
else
|
|
match (x, y) with
|
|
| T0, x | x, T0 -> x
|
|
| T1 (ka, a), T1 (kb, b) -> merge_11 m ka a kb b
|
|
| T1 (ka, a), T2 (kb, b, kc, c) -> merge_12 m ka a kb b kc c
|
|
| T2 (kb, b, kc, c), T1 (ka, a) -> merge_12 m ka a kb b kc c
|
|
| T1 (ka, a), T3 (kb, b, kc, c, kd, d) -> merge_13 m ka a kb b kc c kd d
|
|
| T3 (kb, b, kc, c, kd, d), T1 (ka, a) -> merge_13 m ka a kb b kc c kd d
|
|
| T2 (ka, a, kb, b), T2 (kc, c, kd, d) -> merge_22 m ka a kb b kc c kd d
|
|
| _ -> fold_merge m x y
|
|
[@@inline]
|
|
|
|
let sexp_of_t dict =
|
|
Sexp.List
|
|
(foreach ~init:[] dict
|
|
{
|
|
visit =
|
|
(fun k x xs ->
|
|
Sexp.List
|
|
[ Sexp.Atom (Name.to_string (Key.name k)); Key.to_sexp k x ]
|
|
:: xs);
|
|
})
|
|
|
|
let pp_key ppf { Key.name } = Format.fprintf ppf "%s" (Name.to_string name)
|
|
end
|
|
|
|
module Record = struct
|
|
module Key = Dict.Key
|
|
module Uid = Dict.Key.Uid
|
|
|
|
type record = Dict.t
|
|
type t = record
|
|
type 'a key = 'a Dict.key
|
|
|
|
module Repr = struct
|
|
type entry = { name : Name.t; data : string } [@@deriving bin_io]
|
|
type t = entry list [@@deriving bin_io]
|
|
end
|
|
|
|
type vtable = {
|
|
order : 'a. 'a key -> 'a -> 'a -> Order.partial;
|
|
join : 'a. 'a key -> 'a -> 'a -> ('a, conflict) result;
|
|
inspect : 'a. 'a key -> 'a -> Sexp.t;
|
|
}
|
|
|
|
type slot_io = {
|
|
reader : string -> record -> record;
|
|
writer : record -> string option;
|
|
}
|
|
|
|
let io : slot_io Hashtbl.M(Name).t = Hashtbl.create (module Name)
|
|
let vtables : vtable Hashtbl.M(Uid).t = Hashtbl.create (module Uid)
|
|
let empty = Dict.empty
|
|
let is_empty = Dict.is_empty
|
|
let uid = Key.uid
|
|
let domain k = Hashtbl.find_exn vtables (uid k)
|
|
|
|
exception Not
|
|
|
|
let ( <:= ) x y =
|
|
try
|
|
Dict.foreach ~init:() x
|
|
{
|
|
visit =
|
|
(fun k x () ->
|
|
match Dict.find k y with
|
|
| None -> raise Not
|
|
| Some y -> (
|
|
match (domain k).order k x y with
|
|
| LT | EQ -> ()
|
|
| GT | NC -> raise Not));
|
|
};
|
|
true
|
|
with Not -> false
|
|
|
|
let order : t -> t -> Order.partial =
|
|
fun x y ->
|
|
if phys_equal x y then EQ
|
|
else
|
|
match (x, y) with
|
|
| T0, (T1 _ | T2 _ | T3 _ | T4 _) -> LT
|
|
| (T1 _ | T2 _ | T3 _ | T4 _), T0 -> GT
|
|
| _ -> (
|
|
match (x <:= y, y <:= x) with
|
|
| true, false -> LT
|
|
| true, true -> EQ
|
|
| false, true -> GT
|
|
| false, false -> NC)
|
|
|
|
exception Merge_conflict of conflict
|
|
|
|
let domain_merge =
|
|
{
|
|
Dict.merge =
|
|
(fun k x y ->
|
|
match (domain k).join k x y with
|
|
| Ok x -> x
|
|
| Error err -> raise (Merge_conflict err));
|
|
}
|
|
|
|
let resolving_merge on_conflict =
|
|
{
|
|
Dict.merge =
|
|
(fun k x y ->
|
|
match (domain k).join k x y with
|
|
| Ok b -> b
|
|
| Error err -> (
|
|
match on_conflict with
|
|
| `drop_left -> y
|
|
| `drop_right -> x
|
|
| `fail -> raise (Merge_conflict err)));
|
|
}
|
|
|
|
let commit (type p) _ (key : p Key.t) v x =
|
|
match v with
|
|
| Dict.T0 -> Ok (Dict.make1 key x)
|
|
| _ -> (
|
|
try
|
|
Result.return
|
|
@@ Dict.upsert key x v ~insert:Fn.id ~update:(fun k -> k domain_merge)
|
|
with Merge_conflict err -> Error err)
|
|
|
|
let put k v x = Dict.set k x v
|
|
|
|
let get : type a. a Key.t -> a Domain.t -> record -> a =
|
|
fun k { Domain.empty } data ->
|
|
match Dict.find k data with None -> empty | Some x -> x
|
|
|
|
let splice = Dict.app
|
|
|
|
let join x y =
|
|
try Ok (Dict.merge domain_merge x y) with Merge_conflict err -> Error err
|
|
|
|
let try_merge ~on_conflict x y =
|
|
try Ok (Dict.merge (resolving_merge on_conflict) x y)
|
|
with Merge_conflict err -> Error err
|
|
|
|
let eq = Dict.Key.same
|
|
|
|
let register_persistent (type p) (key : p Key.t) (p : p Persistent.t) =
|
|
let slot = Key.name key in
|
|
Hashtbl.add_exn io ~key:slot
|
|
~data:
|
|
{
|
|
reader =
|
|
(fun x dict ->
|
|
let x = Persistent.of_string p x in
|
|
Dict.insert key x dict);
|
|
writer =
|
|
(fun dict ->
|
|
match Dict.find key dict with
|
|
| None -> None
|
|
| Some s -> Some (Persistent.to_string p s));
|
|
}
|
|
|
|
include
|
|
Binable.Of_binable
|
|
(Repr)
|
|
(struct
|
|
type t = record
|
|
|
|
let to_binable s =
|
|
Dict.foreach s ~init:[]
|
|
{
|
|
visit =
|
|
(fun k _ xs ->
|
|
let name = Key.name k in
|
|
match Hashtbl.find io name with
|
|
| None -> xs
|
|
| Some { writer } -> (
|
|
match writer s with
|
|
| None -> xs
|
|
| Some data -> Repr.{ name; data } :: xs));
|
|
}
|
|
|
|
let of_binable entries =
|
|
List.fold entries ~init:empty ~f:(fun s { Repr.name; data } ->
|
|
match Hashtbl.find io name with
|
|
| None -> s
|
|
| Some { reader } -> reader data s)
|
|
end) [@@warning "-D"]
|
|
|
|
let eq = Dict.Key.same
|
|
|
|
let register_domain : type p. p Key.t -> p Domain.t -> unit =
|
|
fun key dom ->
|
|
let vtable =
|
|
{
|
|
order =
|
|
(fun (type a) (k : a key) (x : a) (y : a) ->
|
|
let T = eq k key in
|
|
dom.order x y);
|
|
inspect =
|
|
(fun (type a) (k : a key) (x : a) ->
|
|
let T = eq k key in
|
|
dom.inspect x);
|
|
join =
|
|
(fun (type a) (k : a key) (x : a) (y : a) : (a, conflict) result ->
|
|
let T = eq k key in
|
|
dom.join x y);
|
|
}
|
|
in
|
|
Hashtbl.add_exn vtables ~key:(uid key) ~data:vtable
|
|
|
|
let sexp_of_t x = Dict.sexp_of_t x
|
|
let t_of_sexp = opaque_of_sexp
|
|
let inspect = sexp_of_t
|
|
let pp_text ppf s = Format.fprintf ppf "@[<1>\"%a\"@]" Format.pp_print_text s
|
|
let is_text = String.exists ~f:Char.is_whitespace
|
|
|
|
let rec pp_hum ppf = function
|
|
| Sexp.Atom s ->
|
|
if is_text s then pp_text ppf s else Format.pp_print_string ppf s
|
|
| Sexp.List xs ->
|
|
Format.fprintf ppf "(@[<hv>";
|
|
Format.pp_print_list pp_hum ppf xs ~pp_sep:Format.pp_print_space;
|
|
Format.fprintf ppf "@])"
|
|
|
|
let pp_payload ppf = function
|
|
| [ Sexp.Atom str ] ->
|
|
Format.fprintf ppf "@[<1>\"%a\"@]" Format.pp_print_text str
|
|
| other -> Format.fprintf ppf "%a" pp_hum (Sexp.List other)
|
|
|
|
let pp ppf x = pp_hum ppf (inspect x)
|
|
|
|
let pp_slots slots ppf x =
|
|
let slots = Set.of_list (module String) slots in
|
|
let no_name = Option.is_none (Set.nth slots 1) in
|
|
match (inspect x : Sexp.t) with
|
|
| Atom _ -> assert false
|
|
| List xs ->
|
|
let first = ref true in
|
|
Format.fprintf ppf "@[<v>";
|
|
List.iter xs ~f:(function
|
|
| Sexp.List (Atom slot :: payload) as data when Set.mem slots slot ->
|
|
if not first.contents then Format.fprintf ppf "@,";
|
|
first := false;
|
|
if no_name then Format.fprintf ppf "%a" pp_payload payload
|
|
else Format.fprintf ppf "%a" pp_hum data
|
|
| _ -> ());
|
|
Format.fprintf ppf "@]"
|
|
end
|
|
|
|
module Knowledge = struct
|
|
type +'a value = { cls : 'a; data : Record.t; time : Int63.t }
|
|
type (+'a, +'s) cls = ('a, 's) Class.t
|
|
type 'a obj = Oid.t
|
|
type 'p domain = 'p Domain.t
|
|
type 'a persistent = 'a Persistent.t
|
|
type 'a ord = Oid.comparator_witness
|
|
type conflict = Conflict.t = ..
|
|
type pid = Pid.t
|
|
type oid = Oid.t [@@deriving bin_io, compare, sexp]
|
|
type cell = { car : oid; cdr : oid } [@@deriving bin_io, compare, sexp]
|
|
|
|
module Cell = struct
|
|
type t = cell
|
|
|
|
include Comparable.Make_binable (struct
|
|
type t = cell [@@deriving bin_io, compare, sexp]
|
|
end)
|
|
end
|
|
|
|
module Env = struct
|
|
type workers = { waiting : unit Pid.Tree.t; current : unit Pid.Tree.t }
|
|
type work = Done | Work of workers
|
|
|
|
type info = {
|
|
data : Record.t;
|
|
comp : work Map.M(Name).t;
|
|
name : fullname option;
|
|
}
|
|
|
|
type objects = {
|
|
last : Oid.t;
|
|
vals : info Oid.Tree.t;
|
|
objs : Oid.t Map.M(Name.Full).t;
|
|
pubs : Oid.Set.t Map.M(String).t;
|
|
}
|
|
|
|
let empty_class =
|
|
{
|
|
last = Oid.first;
|
|
vals = Oid.Tree.empty;
|
|
objs = Map.empty (module Name.Full);
|
|
pubs = Map.empty (module String);
|
|
}
|
|
|
|
type t = {
|
|
classes : objects Map.M(Name).t;
|
|
package : string;
|
|
context : Dict.t;
|
|
}
|
|
end
|
|
|
|
type state = Env.t
|
|
|
|
let empty : Env.t =
|
|
{
|
|
package = user_package;
|
|
classes = Map.empty (module Name);
|
|
context = Dict.empty;
|
|
}
|
|
|
|
let noinfo : Env.info =
|
|
{ data = Record.empty; comp = Map.empty (module Name); name = None }
|
|
|
|
type 'a knowledge = {
|
|
run :
|
|
'r. reject:(conflict -> 'r) -> accept:('a -> state -> 'r) -> state -> 'r;
|
|
}
|
|
|
|
module Knowledge = struct
|
|
type 'a t = 'a knowledge
|
|
type _ error = conflict
|
|
|
|
let fail p : 'a t = { run = (fun ~reject ~accept:_ _ -> reject p) }
|
|
[@@inline]
|
|
|
|
let catch x err =
|
|
{
|
|
run =
|
|
(fun ~reject ~accept s ->
|
|
x.run s ~accept ~reject:(fun p -> (err p).run ~reject ~accept s));
|
|
}
|
|
[@@inline]
|
|
|
|
include Monad.Make (struct
|
|
type 'a t = 'a knowledge
|
|
|
|
let return x : 'a t = { run = (fun ~reject:_ ~accept s -> accept x s) }
|
|
[@@inline]
|
|
|
|
let bind : 'a t -> ('a -> 'b t) -> 'b t =
|
|
fun x f ->
|
|
{
|
|
run =
|
|
(fun ~reject ~accept s ->
|
|
x.run s ~reject ~accept:(fun x s -> (f x).run ~reject ~accept s));
|
|
}
|
|
[@@inline]
|
|
|
|
let map : 'a t -> f:('a -> 'b) -> 'b t =
|
|
fun x ~f ->
|
|
{
|
|
run =
|
|
(fun ~reject ~accept s ->
|
|
x.run s ~reject ~accept:(fun x s -> accept (f x) s));
|
|
}
|
|
[@@inline]
|
|
|
|
let map = `Custom map
|
|
end)
|
|
end
|
|
|
|
open Knowledge.Syntax
|
|
|
|
module Slot = struct
|
|
type ('p, 'r) action = { run : Oid.t -> 'r; pid : pid }
|
|
|
|
type (+'a, 'p) t = {
|
|
cls : ('a, unit) cls;
|
|
dom : 'p Domain.t;
|
|
key : 'p Dict.Key.t;
|
|
name : Name.t;
|
|
desc : string option;
|
|
promises : (pid, ('p, unit knowledge) action) Hashtbl.t;
|
|
watchers : (pid, ('p, 'p -> unit knowledge) action) Hashtbl.t;
|
|
}
|
|
|
|
type pack = Pack : ('a, 'p) t -> pack
|
|
|
|
let repository = Hashtbl.create (module Name)
|
|
|
|
let register slot =
|
|
Hashtbl.update repository slot.cls.name ~f:(function
|
|
| None -> [ Pack slot ]
|
|
| Some xs -> Pack slot :: xs)
|
|
|
|
let enum { Class.name } = Hashtbl.find_multi repository name
|
|
|
|
let declare ?(public = false) ?desc ?persistent ?package cls name
|
|
(dom : 'a Domain.t) =
|
|
let name = Registry.add_slot ?desc ?package name in
|
|
let key = Dict.Key.create ~name dom.inspect in
|
|
if public then Registry.update_class ~cls:cls.Class.name ~slot:name;
|
|
Option.iter persistent (Record.register_persistent key);
|
|
Record.register_domain key dom;
|
|
let promises = Hashtbl.create (module Pid) in
|
|
let watchers = Hashtbl.create (module Pid) in
|
|
let cls = Class.refine cls () in
|
|
let slot = { cls; dom; key; name; desc; promises; watchers } in
|
|
register slot;
|
|
slot
|
|
|
|
let cls x = x.cls
|
|
let domain x = x.dom
|
|
let name { name } = name
|
|
let desc x = match x.desc with None -> "no description" | Some s -> s
|
|
end
|
|
|
|
type (+'a, 'p) slot = ('a, 'p) Slot.t
|
|
|
|
module Value = struct
|
|
type +'a t = 'a value
|
|
|
|
(* we could use an extension variant or create a new OCaml object
|
|
instead of incrementing a second, but they are less reliable
|
|
and heavier *)
|
|
let next_second =
|
|
let current = ref Int63.zero in
|
|
fun () ->
|
|
Int63.incr current;
|
|
!current
|
|
|
|
let empty cls = { cls; data = Record.empty; time = next_second () }
|
|
let is_empty { data } = Record.is_empty data
|
|
let order { data = x } { data = y } = Record.order x y
|
|
let refine { data; cls; time } s = { data; time; cls = Class.refine cls s }
|
|
let cls { cls } = cls
|
|
let create cls data = { cls; data; time = next_second () }
|
|
|
|
let put { Slot.key; dom } v x =
|
|
if Domain.is_empty dom x then v
|
|
else { v with data = Record.put key v.data x; time = next_second () }
|
|
|
|
let get { Slot.key; dom } { data } = Record.get key dom data
|
|
|
|
let has { Slot.key; dom } { data } =
|
|
not @@ Domain.is_empty dom @@ Record.get key dom data
|
|
|
|
let strip : type a b. (a value, b value) Type_equal.t -> (a, b) Type_equal.t
|
|
=
|
|
fun T -> T
|
|
|
|
type strategy = [ `drop_left | `drop_right ]
|
|
|
|
let merge ?(on_conflict = `drop_old) x y =
|
|
let on_conflict : strategy =
|
|
match on_conflict with
|
|
| `drop_old ->
|
|
if Int63.(x.time < y.time) then `drop_left else `drop_right
|
|
| `drop_new ->
|
|
if Int63.(x.time < y.time) then `drop_right else `drop_left
|
|
| #strategy as other -> other
|
|
in
|
|
match Record.try_merge ~on_conflict x.data y.data with
|
|
| Ok data -> { x with time = next_second (); data }
|
|
| Error _ ->
|
|
(* try_merge fails only if `fail is passed *)
|
|
assert false
|
|
|
|
let join x y =
|
|
match Record.join x.data y.data with
|
|
| Ok data -> Ok { x with data; time = next_second () }
|
|
| Error c -> Error c
|
|
|
|
module type S = sig
|
|
type t [@@deriving sexp]
|
|
|
|
val empty : t
|
|
val domain : t domain
|
|
|
|
include Base.Comparable.S with type t := t
|
|
include Binable.S with type t := t
|
|
end
|
|
|
|
module Comparator = Base.Comparator.Make1 (struct
|
|
type 'a t = 'a value
|
|
|
|
let sexp_of_t = sexp_of_opaque
|
|
|
|
let compare x y =
|
|
match Record.order x.data y.data with
|
|
| LT -> -1
|
|
| EQ -> 0
|
|
| GT -> 1
|
|
| NC -> Int63.compare x.time y.time
|
|
end)
|
|
|
|
include Comparator
|
|
|
|
type 'a ord = comparator_witness
|
|
|
|
let derive : type a b.
|
|
(a, b) cls ->
|
|
(module S
|
|
with type t = (a, b) cls t
|
|
and type comparator_witness = (a, b) cls ord) =
|
|
fun cls ->
|
|
let module R = struct
|
|
type t = (a, b) cls value
|
|
|
|
let sexp_of_t x = Record.sexp_of_t x.data
|
|
let t_of_sexp = opaque_of_sexp
|
|
let empty = empty cls
|
|
|
|
include
|
|
Binable.Of_binable
|
|
(Record)
|
|
(struct
|
|
type t = (a, b) cls value
|
|
|
|
let to_binable : 'a value -> Record.t = fun { data } -> data
|
|
|
|
let of_binable : Record.t -> 'a value =
|
|
fun data -> { cls; data; time = next_second () }
|
|
end) [@@warning "-D"]
|
|
|
|
type comparator_witness = Comparator.comparator_witness
|
|
|
|
include Base.Comparable.Make_using_comparator (struct
|
|
type t = (a, b) cls value
|
|
|
|
let sexp_of_t = sexp_of_t
|
|
|
|
include Comparator
|
|
end)
|
|
|
|
let domain =
|
|
Domain.define ~empty ~order ~join ~inspect:sexp_of_t
|
|
(Name.unqualified (Class.name cls))
|
|
end in
|
|
(module R)
|
|
|
|
let pp ppf x = Record.pp ppf x.data
|
|
let pp_slots slots ppf x = Record.pp_slots slots ppf x.data
|
|
end
|
|
|
|
module Class = struct
|
|
include Class
|
|
|
|
let property = Slot.declare
|
|
|
|
module Abstract = struct
|
|
let property = Slot.declare
|
|
end
|
|
end
|
|
|
|
let get () : state knowledge =
|
|
{ run = (fun ~reject:_ ~accept s -> accept s s) }
|
|
[@@inline]
|
|
|
|
let put s = { run = (fun ~reject:_ ~accept _ -> accept () s) } [@@inline]
|
|
|
|
let gets f = { run = (fun ~reject:_ ~accept s -> accept (f s) s) }
|
|
[@@inline] [@@specialise]
|
|
|
|
let update f = { run = (fun ~reject:_ ~accept s -> accept () (f s)) }
|
|
[@@inline] [@@specialise]
|
|
|
|
let objects { Class.name } =
|
|
get () >>| fun { classes } ->
|
|
match Map.find classes name with
|
|
| None -> Env.empty_class
|
|
| Some objs -> objs
|
|
[@@inline]
|
|
|
|
let update_objects { Class.name } f =
|
|
update @@ fun state ->
|
|
let objs =
|
|
f
|
|
@@
|
|
match Map.find state.classes name with
|
|
| None -> Env.empty_class
|
|
| Some objs -> objs
|
|
in
|
|
{ state with classes = Map.set state.classes name objs }
|
|
[@@specialise]
|
|
|
|
let map_update_objects { Class.name } f =
|
|
get () >>= fun state ->
|
|
let objs =
|
|
match Map.find state.classes name with
|
|
| None -> Env.empty_class
|
|
| Some objs -> objs
|
|
in
|
|
f objs @@ fun objs res ->
|
|
put { state with classes = Map.set state.classes name objs } >>| fun () ->
|
|
res
|
|
[@@specialise]
|
|
|
|
module Object = struct
|
|
type +'a t = 'a obj
|
|
type 'a ord = Oid.comparator_witness
|
|
|
|
let with_new_object objs f =
|
|
let next = Oid.succ objs.Env.last in
|
|
f next { objs with Env.last = next }
|
|
|
|
let create : ('a, _) cls -> 'a obj Knowledge.t =
|
|
fun cls ->
|
|
objects cls >>= fun objs ->
|
|
with_new_object objs @@ fun obj objs ->
|
|
( update @@ function
|
|
| { classes } as s ->
|
|
{ s with classes = Map.set classes ~key:cls.name ~data:objs } )
|
|
>>| fun () -> obj
|
|
|
|
let null _ = Oid.zero
|
|
let is_null = Oid.equal Oid.zero
|
|
|
|
(* an interesting question, what we shall do if
|
|
1) an symbol is deleted
|
|
2) a data object is deleted?
|
|
|
|
So far we ignore both deletes.
|
|
*)
|
|
let delete { Class.name } obj =
|
|
update @@ function
|
|
| { classes } as s ->
|
|
{
|
|
s with
|
|
classes =
|
|
Map.change classes name ~f:(function
|
|
| None -> None
|
|
| Some objs ->
|
|
Some { objs with vals = Oid.Tree.remove objs.vals obj });
|
|
}
|
|
|
|
let scoped cls scope =
|
|
create cls >>= fun obj ->
|
|
scope obj >>= fun r ->
|
|
delete cls obj >>| fun () -> r
|
|
|
|
let do_intern =
|
|
let is_public { package } obj { Env.pubs } =
|
|
match Map.find pubs package with
|
|
| None -> false
|
|
| Some pubs -> Set.mem pubs obj
|
|
in
|
|
let unchanged id = Knowledge.return id in
|
|
let publicize { package } obj : Env.objects -> Env.objects =
|
|
fun objects ->
|
|
{
|
|
objects with
|
|
pubs =
|
|
Map.update objects.pubs package ~f:(function
|
|
| None -> Set.singleton (module Oid) obj
|
|
| Some pubs -> Set.add pubs obj);
|
|
}
|
|
in
|
|
let createsym ~public name classes clsid objects s =
|
|
with_new_object objects @@ fun obj objects ->
|
|
let vals =
|
|
Oid.Tree.update_with objects.vals obj
|
|
~has:(fun info -> { info with name = Some name })
|
|
~nil:(fun () -> { noinfo with name = Some name })
|
|
in
|
|
let objs = Map.add_exn objects.objs name obj in
|
|
let objects = { objects with objs; vals } in
|
|
let objects = if public then publicize name obj objects else objects in
|
|
put { s with classes = Map.set classes clsid objects } >>| fun () -> obj
|
|
in
|
|
|
|
fun ?(public = false) ?desc:_ name { Class.name = id } ->
|
|
get () >>= fun ({ classes } as s) ->
|
|
let objects =
|
|
match Map.find classes id with
|
|
| None -> Env.empty_class
|
|
| Some objs -> objs
|
|
in
|
|
match Map.find objects.objs name with
|
|
| None -> createsym ~public name classes id objects s
|
|
| Some obj when not public -> unchanged obj
|
|
| Some obj ->
|
|
if is_public name obj objects then unchanged obj
|
|
else
|
|
let objects = publicize name obj objects in
|
|
put { s with classes = Map.set classes id objects } >>| fun () ->
|
|
obj
|
|
|
|
(* any [:] in names here are never treated as separators,
|
|
contrary to [read], where they are, and [do_intern] where
|
|
a leading [:] in a name will be left for keywords *)
|
|
let intern ?public ?desc ?package name cls =
|
|
match package with
|
|
| Some package ->
|
|
do_intern ?public ?desc (Name.Full.create ~package name) cls
|
|
| None ->
|
|
get () >>= fun { Env.package } ->
|
|
let name =
|
|
{ package; name = Name.normalize_name `Literal ~package name }
|
|
in
|
|
do_intern ?public ?desc name cls
|
|
|
|
let uninterned_repr cls obj = Format.asprintf "#<%s %a>" cls Oid.pp obj
|
|
|
|
let to_string { Class.name = cls as cname } { Env.package; classes } obj =
|
|
let cls =
|
|
if String.equal package (Name.package cls) then Name.unqualified cls
|
|
else Name.to_string cls
|
|
in
|
|
match Map.find classes cname with
|
|
| None -> uninterned_repr cls obj
|
|
| Some { Env.vals } -> (
|
|
match Oid.Tree.find vals obj with
|
|
| Some { name = Some fname } ->
|
|
if String.equal fname.package package then fname.name
|
|
else Name.Full.to_string fname
|
|
| _ -> uninterned_repr cls obj)
|
|
|
|
let repr cls obj =
|
|
if is_null obj then !!"nil"
|
|
else get () >>| fun env -> to_string cls env obj
|
|
|
|
let read cls = function
|
|
| "nil" -> !!(null cls)
|
|
| input -> (
|
|
try
|
|
Scanf.sscanf input "#<%s %s@>" @@ fun _ obj ->
|
|
Knowledge.return (Oid.of_string obj)
|
|
with _ ->
|
|
get () >>= fun { Env.package } ->
|
|
do_intern (Name.Full.read ~package input) cls)
|
|
|
|
let cast : type a b. (a obj, b obj) Type_equal.t -> a obj -> b obj =
|
|
fun Type_equal.T x -> x
|
|
|
|
let id x = Oid.to_int63 x
|
|
|
|
module type S = sig
|
|
type t [@@deriving sexp]
|
|
|
|
include Base.Comparable.S with type t := t
|
|
include Binable.S with type t := t
|
|
end
|
|
|
|
let derive : type a.
|
|
(a, _) cls ->
|
|
(module S with type t = a obj and type comparator_witness = a ord) =
|
|
fun _ ->
|
|
let module Comparator = struct
|
|
type t = a obj
|
|
|
|
let sexp_of_t = Oid.sexp_of_t
|
|
let t_of_sexp = Oid.t_of_sexp
|
|
|
|
type comparator_witness = a ord
|
|
|
|
let comparator = Oid.comparator
|
|
end in
|
|
let module R = struct
|
|
include Comparator
|
|
|
|
include
|
|
Binable.Of_binable
|
|
(Oid)
|
|
(struct
|
|
type t = a obj
|
|
|
|
let to_binable = Fn.id
|
|
let of_binable = Fn.id
|
|
end) [@@warning "-D"]
|
|
|
|
include Base.Comparable.Make_using_comparator (Comparator)
|
|
end in
|
|
(module R)
|
|
end
|
|
|
|
type conflict +=
|
|
| Non_monotonic_update of {
|
|
slot : Name.t;
|
|
repr : string;
|
|
error : Conflict.t;
|
|
trace : Stdlib.Printexc.raw_backtrace;
|
|
}
|
|
|
|
let () =
|
|
Conflict.register_printer (function
|
|
| Non_monotonic_update { slot; repr; error; trace } ->
|
|
Option.some
|
|
@@ Format.asprintf
|
|
"Unable to update the slot %a of %s,\n%a\nBacktrace:\n%s" Name.pp
|
|
slot repr Conflict.pp error
|
|
(Stdlib.Printexc.raw_backtrace_to_string trace)
|
|
| _ -> None)
|
|
|
|
let non_monotonic slot obj error trace =
|
|
Object.repr (Slot.cls slot) obj >>= fun obj ->
|
|
Knowledge.fail
|
|
(Non_monotonic_update { slot = Slot.name slot; repr = obj; error; trace })
|
|
|
|
let commit : type a p. (a, p) slot -> a obj -> p -> unit Knowledge.t =
|
|
fun slot obj x ->
|
|
get () >>= function
|
|
| { classes } as s -> (
|
|
let ({ Env.vals } as objs) =
|
|
match Map.find classes slot.cls.name with
|
|
| None -> Env.empty_class
|
|
| Some objs -> objs
|
|
in
|
|
try
|
|
put
|
|
{
|
|
s with
|
|
classes =
|
|
Map.set classes ~key:slot.cls.name
|
|
~data:
|
|
{
|
|
objs with
|
|
vals =
|
|
Oid.Tree.update_with vals obj
|
|
~nil:(fun () ->
|
|
{ noinfo with data = Record.(put slot.key empty x) })
|
|
~has:(fun info ->
|
|
match
|
|
Record.commit slot.dom slot.key info.data x
|
|
with
|
|
| Ok data -> { info with data }
|
|
| Error err -> raise (Record.Merge_conflict err));
|
|
};
|
|
}
|
|
with Record.Merge_conflict err ->
|
|
non_monotonic slot obj err @@ Stdlib.Printexc.get_raw_backtrace ())
|
|
|
|
let notify { Slot.watchers } obj data =
|
|
Hashtbl.data watchers
|
|
|> Knowledge.List.iter ~f:(fun { Slot.run } -> run obj data)
|
|
|
|
let provide : type a p. (a, p) slot -> a obj -> p -> unit Knowledge.t =
|
|
fun slot obj x ->
|
|
if Object.is_null obj || Domain.is_empty slot.dom x then Knowledge.return ()
|
|
else commit slot obj x >>= fun () -> notify slot obj x
|
|
|
|
let pids = ref Pid.zero
|
|
|
|
type conflict += Empty : ('a, 'b) slot -> conflict | Reject : conflict
|
|
|
|
let reject () = Knowledge.fail Reject
|
|
let guard cnd = if not cnd then reject () else Knowledge.return ()
|
|
let proceed ~unless:cnd = guard (not cnd)
|
|
let on cnd yes = if cnd then yes else reject ()
|
|
let unless cnd no = if cnd then reject () else no
|
|
|
|
let with_empty ~missing scope =
|
|
Knowledge.catch (scope ()) (function
|
|
| Empty _ | Reject -> Knowledge.return missing
|
|
| other -> Knowledge.fail other)
|
|
|
|
let register_watcher (type a b) (s : (a, b) slot) run =
|
|
Pid.incr pids;
|
|
let pid = !pids in
|
|
Hashtbl.add_exn s.watchers pid { run; pid };
|
|
pid
|
|
|
|
let register_promise (type a b) (s : (a, b) slot) run =
|
|
Pid.incr pids;
|
|
let pid = !pids in
|
|
Hashtbl.add_exn s.promises pid { run; pid };
|
|
pid
|
|
|
|
let remove_promise (s : _ slot) pid = Hashtbl.remove s.promises pid
|
|
let remove_watcher (s : _ slot) pid = Hashtbl.remove s.watchers pid
|
|
|
|
let wrap (s : _ slot) get obj =
|
|
let missing = Domain.empty s.dom in
|
|
with_empty ~missing @@ fun () -> get obj
|
|
|
|
let promising s ~promise:get scoped =
|
|
let pid =
|
|
register_promise s @@ fun obj ->
|
|
wrap s get obj >>= fun x ->
|
|
if Domain.is_empty s.dom x then Knowledge.return () else provide s obj x
|
|
in
|
|
scoped () >>= fun r ->
|
|
remove_promise s pid;
|
|
Knowledge.return r
|
|
|
|
let promise s get =
|
|
ignore @@ register_promise s
|
|
@@ fun obj ->
|
|
wrap s get obj >>= fun x ->
|
|
if Domain.is_empty s.dom x then Knowledge.return () else provide s obj x
|
|
|
|
let uid { Slot.name } = name
|
|
|
|
type slot_status = Sleep | Awoke | Ready of Dict.record
|
|
|
|
let is_empty { Slot.dom; key } v = Domain.is_empty dom (Record.get key dom v)
|
|
[@@inline]
|
|
|
|
let status : ('a, _) slot -> 'a obj -> slot_status knowledge =
|
|
fun slot obj ->
|
|
objects slot.cls >>| fun { vals } ->
|
|
match Oid.Tree.find_exn vals obj with
|
|
| exception Stdlib.Not_found -> Sleep
|
|
| { data; comp = slots } -> (
|
|
match Map.find slots (uid slot) with
|
|
| Some (Work _) -> Awoke
|
|
| other -> (
|
|
match (other, Record.is_empty data) with
|
|
| Some (Work _), _ -> assert false
|
|
| None, true -> Sleep
|
|
| Some Done, true -> Ready Record.empty
|
|
| Some Done, false -> Ready data
|
|
| None, false -> if is_empty slot data then Sleep else Ready data))
|
|
|
|
let update_slot : ('a, _) slot -> 'a obj -> _ -> unit knowledge =
|
|
fun slot obj f ->
|
|
update_objects slot.cls @@ fun ({ vals } as objs) ->
|
|
let vals =
|
|
Oid.Tree.update_with vals obj
|
|
~nil:(fun () ->
|
|
{ noinfo with comp = Map.singleton (module Name) (uid slot) (f None) })
|
|
~has:(fun info ->
|
|
{ info with comp = Map.update info.comp (uid slot) ~f })
|
|
in
|
|
{ objs with vals }
|
|
|
|
let enter_slot : ('a, _) slot -> 'a obj -> unit knowledge =
|
|
fun s x ->
|
|
update_slot s x @@ function
|
|
| Some _ -> assert false
|
|
| None -> Work { waiting = Pid.Tree.empty; current = Pid.Tree.empty }
|
|
|
|
let leave_slot : ('a, 'p) slot -> 'a obj -> unit Knowledge.t =
|
|
fun s x ->
|
|
update_slot s x @@ function Some (Work _) -> Done | _ -> assert false
|
|
|
|
let update_work s x f =
|
|
update_slot s x @@ function Some (Work w) -> f w | _ -> assert false
|
|
|
|
let enter_promise s x p =
|
|
update_work s x @@ fun { waiting; current } ->
|
|
Work { waiting; current = Pid.Tree.set current p () }
|
|
|
|
let leave_promise s x p =
|
|
update_work s x @@ fun { waiting; current } ->
|
|
Work { waiting; current = Pid.Tree.remove current p }
|
|
|
|
let enqueue_promises s x =
|
|
update_work s x @@ fun { waiting; current } ->
|
|
Work
|
|
{ current; waiting = Pid.Tree.merge current waiting ~f:(fun _ _ _ -> ()) }
|
|
|
|
let no_work = Env.Work { waiting = Pid.Tree.empty; current = Pid.Tree.empty }
|
|
|
|
let dequeue_waiting : ('a, 'p) slot -> 'a obj -> _ Knowledge.t =
|
|
fun s x ->
|
|
map_update_objects s.cls @@ fun ({ vals } as objs) k ->
|
|
let ({ Env.comp = works } as info) = Oid.Tree.find_exn vals x in
|
|
Map.find_exn works (uid s) |> function
|
|
| Env.Done -> assert false
|
|
| Env.Work { waiting } ->
|
|
let waiting =
|
|
Pid.Tree.fold waiting ~init:[] ~f:(fun p () ps ->
|
|
Hashtbl.find_exn s.Slot.promises p :: ps)
|
|
in
|
|
let info = { info with comp = Map.set works (uid s) no_work } in
|
|
let objs = { objs with vals = Oid.Tree.set vals x info } in
|
|
k objs waiting
|
|
|
|
let initial_promises { Slot.promises } = Hashtbl.data promises
|
|
|
|
let current : type a p. (a, p) slot -> a obj -> p Knowledge.t =
|
|
fun slot id ->
|
|
objects slot.cls >>| fun { Env.vals } ->
|
|
match Oid.Tree.find_exn vals id with
|
|
| exception Stdlib.Not_found -> slot.dom.empty
|
|
| { data } -> Record.get slot.key slot.dom data
|
|
|
|
let rec collect_inner : ('a, 'p) slot -> 'a obj -> _ -> _ =
|
|
fun slot obj promises ->
|
|
current slot obj >>= fun was ->
|
|
Knowledge.List.iter promises ~f:(fun { Slot.run; pid } ->
|
|
enter_promise slot obj pid >>= fun () ->
|
|
run obj >>= fun () -> leave_promise slot obj pid)
|
|
>>= fun () ->
|
|
dequeue_waiting slot obj >>= fun waiting ->
|
|
match waiting with
|
|
| [] -> Knowledge.return ()
|
|
| promises -> (
|
|
current slot obj >>= fun now ->
|
|
match slot.dom.order now was with
|
|
| EQ | LT -> Knowledge.return ()
|
|
| GT | NC -> collect_inner slot obj promises)
|
|
|
|
let collect : type a p. (a, p) slot -> a obj -> p Knowledge.t =
|
|
fun slot id ->
|
|
if Object.is_null id then !!(Domain.empty slot.dom)
|
|
else
|
|
status slot id >>= function
|
|
| Ready v -> Knowledge.return @@ Record.get slot.key slot.dom v
|
|
| Awoke -> enqueue_promises slot id >>= fun () -> current slot id
|
|
| Sleep ->
|
|
enter_slot slot id >>= fun () ->
|
|
collect_inner slot id (initial_promises slot) >>= fun () ->
|
|
leave_slot slot id >>= fun () -> current slot id
|
|
|
|
let observe s run = ignore @@ register_watcher s run
|
|
|
|
let observing s ~observe:run scoped =
|
|
let pid = register_watcher s run in
|
|
scoped () >>= fun r ->
|
|
remove_watcher s pid;
|
|
Knowledge.return r
|
|
|
|
let require (slot : _ slot) obj =
|
|
collect slot obj >>= fun x ->
|
|
if Domain.is_empty slot.dom x then Knowledge.fail (Empty slot) else !!x
|
|
|
|
let resolve slot obj = collect slot obj >>| Opinions.choice
|
|
|
|
let suggest agent slot obj x =
|
|
current slot obj >>= fun opinions ->
|
|
provide slot obj (Opinions.add agent x opinions)
|
|
|
|
let wrap_opinion get obj =
|
|
with_empty ~missing:None @@ fun () -> get obj >>| Option.some
|
|
|
|
let propose agent s get =
|
|
ignore @@ register_promise s
|
|
@@ fun obj ->
|
|
wrap_opinion get obj >>= function
|
|
| None -> Knowledge.return ()
|
|
| Some opinions -> suggest agent s obj opinions
|
|
|
|
let proposing agent s ~propose:get scoped =
|
|
let pid =
|
|
register_promise s @@ fun obj ->
|
|
wrap_opinion get obj >>= function
|
|
| None -> Knowledge.return ()
|
|
| Some opinions -> suggest agent s obj opinions
|
|
in
|
|
scoped () >>= fun r ->
|
|
remove_promise s pid;
|
|
Knowledge.return r
|
|
|
|
module Domain = struct
|
|
include Domain
|
|
|
|
let inspect_obj name x =
|
|
Sexp.Atom (Format.asprintf "#<%s %a>" name Oid.pp x)
|
|
|
|
let obj { Class.name } =
|
|
let name = Name.to_string name in
|
|
total ~inspect:(inspect_obj name) ~empty:Oid.zero ~order:Oid.compare name
|
|
end
|
|
|
|
module Order = Order
|
|
module Persistent = Persistent
|
|
|
|
module Symbol = struct
|
|
let intern = Object.intern
|
|
let keyword = keyword_package
|
|
|
|
let in_package package f =
|
|
get () >>= function
|
|
| { Env.package = old_package } as s ->
|
|
put { s with package } >>= fun () ->
|
|
f () >>= fun r ->
|
|
update (fun s -> { s with package = old_package }) >>| fun () -> r
|
|
|
|
exception Import of fullname * fullname [@@deriving sexp_of]
|
|
|
|
let intern_symbol name obj cls =
|
|
Knowledge.return Env.{ cls with objs = Map.add_exn cls.objs name obj }
|
|
|
|
(* imports names inside a class.
|
|
|
|
All names that [needs_import] will be imported
|
|
into the [package]. If the [package] already had
|
|
the same name but with different value, then a
|
|
[strict] import will raise an error, otherwise it
|
|
will be overwritten with the new value.
|
|
*)
|
|
let import_class ~strict ~package ~needs_import :
|
|
Env.objects -> Env.objects knowledge =
|
|
fun cls ->
|
|
Oid.Tree.to_sequence cls.vals
|
|
|> Knowledge.Seq.fold ~init:cls ~f:(fun cls (obj, (info : Env.info)) ->
|
|
match info.name with
|
|
| None -> Knowledge.return cls
|
|
| Some sym ->
|
|
if not (needs_import cls sym obj) then Knowledge.return cls
|
|
else
|
|
let obj' =
|
|
match Map.find cls.objs { package; name = sym.name } with
|
|
| None -> Oid.zero
|
|
| Some obj' -> obj'
|
|
in
|
|
if (not strict) || Oid.(obj' = zero || obj' = obj) then
|
|
intern_symbol sym obj cls
|
|
else
|
|
let info = Oid.Tree.find_exn cls.vals obj' in
|
|
let sym' = Option.value_exn info.name in
|
|
Knowledge.fail (Import (sym, sym')))
|
|
|
|
let package_exists package =
|
|
Map.exists ~f:(fun { Env.objs } ->
|
|
Map.existsi objs ~f:(fun ~key:name ~data:_ ->
|
|
String.equal package name.package))
|
|
|
|
let name_exists name = Map.exists ~f:(fun { Env.objs } -> Map.mem objs name)
|
|
|
|
exception Not_a_package of string [@@deriving sexp_of]
|
|
exception Not_a_symbol of fullname [@@deriving sexp_of]
|
|
|
|
let check_name classes = function
|
|
| `Pkg pkg ->
|
|
if package_exists pkg classes then Knowledge.return ()
|
|
else Knowledge.fail (Not_a_package pkg)
|
|
| `Sym sym ->
|
|
if name_exists sym classes then Knowledge.return ()
|
|
else Knowledge.fail (Not_a_symbol sym)
|
|
|
|
let current = function
|
|
| Some p -> Knowledge.return (Name.normalize_package `Literal p)
|
|
| None -> gets (fun s -> s.package)
|
|
|
|
let import ?(strict = false) ?package imports : unit knowledge =
|
|
current package >>= fun package ->
|
|
get () >>= fun s ->
|
|
Knowledge.List.fold ~init:s.classes imports ~f:(fun classes name ->
|
|
let name =
|
|
match Name.find_separator name with
|
|
| None -> `Pkg name
|
|
| Some _ -> `Sym (Name.Full.read name)
|
|
in
|
|
let needs_import { Env.pubs } sym obj =
|
|
match name with
|
|
| `Sym s -> [%compare.equal: fullname] sym s
|
|
| `Pkg p -> (
|
|
match Map.find pubs p with
|
|
| None -> false
|
|
| Some pubs -> Set.mem pubs obj)
|
|
in
|
|
check_name classes name >>= fun () ->
|
|
Map.to_sequence classes
|
|
|> Knowledge.Seq.fold ~init:classes
|
|
~f:(fun classes (clsid, objects) ->
|
|
import_class ~strict ~package ~needs_import objects
|
|
>>| fun objects -> Map.set classes clsid objects))
|
|
>>= fun classes -> put { s with classes }
|
|
|
|
let package = get () >>| fun { Env.package } -> package
|
|
let set_package name = update @@ fun s -> { s with package = name }
|
|
end
|
|
|
|
module Syntax = struct
|
|
include Knowledge.Syntax
|
|
include Knowledge.Let
|
|
|
|
let ( --> ) x p = collect p x
|
|
let ( <-- ) p f = promise p f
|
|
let ( // ) c s = Object.read c s
|
|
|
|
let ( -->? ) x p =
|
|
collect p x >>= function
|
|
| None -> Knowledge.fail (Empty p)
|
|
| Some x -> !!x
|
|
|
|
let ( >>=? ) x f =
|
|
{
|
|
run =
|
|
(fun ~reject ~accept s ->
|
|
x.run s ~reject ~accept:(fun x s ->
|
|
match x with
|
|
| None -> accept None s
|
|
| Some x -> (f x).run ~accept ~reject s));
|
|
}
|
|
[@@inline] [@@specialise]
|
|
|
|
let ( >>|? ) x f =
|
|
{
|
|
run =
|
|
(fun ~reject ~accept s ->
|
|
x.run s ~reject ~accept:(fun x s ->
|
|
match x with None -> accept None s | Some x -> accept (f x) s));
|
|
}
|
|
[@@inline] [@@specialise]
|
|
|
|
let ( let*? ) = ( >>=? )
|
|
let ( let+? ) = ( >>|? )
|
|
|
|
let ( and+ ) x y =
|
|
{
|
|
run =
|
|
(fun ~reject ~accept s ->
|
|
x.run s ~reject ~accept:(fun x s ->
|
|
y.run s ~reject ~accept:(fun y s -> accept (x, y) s)));
|
|
}
|
|
[@@inline] [@@specialise]
|
|
|
|
let ( and* ) = ( and+ )
|
|
let ( .$[] ) v s = Value.get s v
|
|
let ( .$[]<- ) v s x = Value.put s v x
|
|
|
|
let ( .?[] ) v s =
|
|
match v.$[s] with Some v -> !!v | None -> Knowledge.fail (Empty s)
|
|
|
|
let ( .![] ) v s =
|
|
let r = v.$[s] in
|
|
if Domain.is_empty (Slot.domain s) r then Knowledge.fail (Empty s)
|
|
else !!r
|
|
end
|
|
|
|
module type S = sig
|
|
include Monad.S with type 'a t = 'a knowledge and module Syntax := Syntax
|
|
|
|
include
|
|
Monad.Fail.S with type 'a t := 'a knowledge and type 'a error = conflict
|
|
end
|
|
|
|
include (Knowledge : S)
|
|
|
|
let compute_value : type a p. (a, p) cls -> p obj -> unit knowledge =
|
|
fun cls obj ->
|
|
Slot.enum cls
|
|
|> Base.List.filter ~f:(function Slot.Pack { promises } ->
|
|
not (Hashtbl.is_empty promises))
|
|
|> List.iter ~f:(fun (Slot.Pack s) -> ignore_m @@ collect s obj)
|
|
|
|
let get_value cls obj =
|
|
compute_value cls obj >>= fun () ->
|
|
objects cls >>| fun { Env.vals } ->
|
|
match Oid.Tree.find_exn vals obj with
|
|
| exception Stdlib.Not_found -> Value.empty cls
|
|
| { data = x } -> Value.create cls x
|
|
|
|
let run cls obj s =
|
|
(obj >>= get_value cls).run s
|
|
~reject:(fun err -> Error err)
|
|
~accept:(fun x s -> Ok (x, s))
|
|
|
|
let pp_fullname ~package ppf { package = p; name } =
|
|
if String.equal package p then Format.fprintf ppf "%s" name
|
|
else Format.fprintf ppf "%s:%s" p name
|
|
|
|
let pp_state ppf { Env.classes; package } =
|
|
Format.fprintf ppf "@[<v0>(in-package %s)@;" package;
|
|
Map.iteri classes ~f:(fun ~key:name ~data:{ vals } ->
|
|
if not (Oid.Tree.is_empty vals) then (
|
|
Format.fprintf ppf "(in-class %a)@;" (pp_fullname ~package)
|
|
(Name.full name);
|
|
Format.fprintf ppf "@[<v>";
|
|
Oid.Tree.iter vals ~f:(fun oid { data; name } ->
|
|
if not (Dict.is_empty data) then
|
|
let () =
|
|
match name with
|
|
| None -> Format.fprintf ppf "@[<hv2>(%a@ " Oid.pp oid
|
|
| Some name ->
|
|
Format.fprintf ppf "@[<hv2>(%a@ " (pp_fullname ~package)
|
|
name
|
|
in
|
|
Format.fprintf ppf "%a)@]@;" Record.pp_hum (Dict.sexp_of_t data));
|
|
Format.fprintf ppf "@]"));
|
|
Format.fprintf ppf "@]"
|
|
|
|
module Io = struct
|
|
type version = V1 | V2 [@@deriving bin_io]
|
|
|
|
module List = Base.List
|
|
|
|
type data = {
|
|
key : Oid.t;
|
|
sym : fullname option;
|
|
data : (Name.t * string) array;
|
|
comp : Name.t list;
|
|
}
|
|
[@@deriving bin_io]
|
|
|
|
type v1 = data list [@@deriving bin_io]
|
|
type v2 = Oid.t * v1 [@@deriving bin_io]
|
|
type 'a objects = 'a [@@deriving bin_io]
|
|
type 'a payload = (Name.t * 'a) list [@@deriving bin_io]
|
|
|
|
type 'a canonical = { version : version; payload : 'a payload }
|
|
[@@deriving bin_io]
|
|
|
|
let magic = "CMU:KB"
|
|
|
|
let check_magic data =
|
|
let len = String.length magic in
|
|
if String.(Bigstring.To_string.subo ~len data <> magic) then
|
|
invalid_arg "Not a valid knowledge base";
|
|
len
|
|
|
|
let make_value data =
|
|
let init = Record.empty in
|
|
Array.fold data ~init ~f:(fun record (name, data) ->
|
|
match Hashtbl.find Record.io name with
|
|
| None -> record
|
|
| Some { Record.reader = read } -> read data record)
|
|
|
|
let expand_comp comp =
|
|
List.fold comp
|
|
~init:(Map.empty (module Name))
|
|
~f:(fun works slot -> Map.add_exn works slot Env.Done)
|
|
|
|
let add_object ({ Env.vals; objs } as self) { key; sym; data; comp } =
|
|
let self =
|
|
{
|
|
self with
|
|
vals =
|
|
Oid.Tree.set vals key
|
|
{ data = make_value data; comp = expand_comp comp; name = sym };
|
|
}
|
|
in
|
|
match sym with
|
|
| None -> self
|
|
| Some s -> { self with objs = Map.add_exn objs s key }
|
|
|
|
let names_in_syms =
|
|
Oid.Tree.fold
|
|
~init:(Set.empty (module String))
|
|
~f:(fun _ { package; name } names ->
|
|
Set.add (Set.add names package) name)
|
|
|
|
(* let names = Map.fold
|
|
* ~init:(Set.empty (module String))
|
|
* ~f:(fun ~key:_ ~data:{Env.syms} names ->
|
|
* Set.union names @@
|
|
* names_in_syms syms) *)
|
|
|
|
let serialize_record record =
|
|
let fields =
|
|
Dict.foreach record ~init:[]
|
|
{
|
|
visit =
|
|
(fun k _ xs ->
|
|
let name = Record.Key.name k in
|
|
match Hashtbl.find Record.io name with
|
|
| None -> xs
|
|
| Some { writer } -> (
|
|
match writer record with
|
|
| None -> xs
|
|
| Some data -> (name, data) :: xs));
|
|
}
|
|
in
|
|
let result = Array.of_list fields in
|
|
Array.sort result ~compare:(fun (k1, _) (k2, _) -> Name.compare k1 k2);
|
|
result
|
|
|
|
let collect_comps comp oid =
|
|
match Oid.Tree.find comp oid with
|
|
| None -> []
|
|
| Some works -> Map.keys works
|
|
|
|
let to_canonical { Env.classes } : v2 canonical =
|
|
let payload =
|
|
Map.to_alist classes
|
|
|> List.map ~f:(fun (cid, { Env.vals; last }) ->
|
|
let data =
|
|
Oid.Tree.to_list vals
|
|
|> List.filter_map ~f:(fun (oid, { Env.data; name; comp }) ->
|
|
let data = serialize_record data in
|
|
let comp = Map.keys comp in
|
|
if Array.is_empty data && Option.is_none name then None
|
|
else Some { key = oid; sym = name; data; comp })
|
|
in
|
|
(cid, (last, data)))
|
|
in
|
|
{ version = V2; payload }
|
|
|
|
let init_last : state -> state =
|
|
fun state ->
|
|
{
|
|
state with
|
|
classes =
|
|
Map.map state.classes ~f:(fun cls ->
|
|
{
|
|
cls with
|
|
last =
|
|
(match Oid.Tree.max_elt cls.vals with
|
|
| None -> cls.last
|
|
| Some (k, _) -> Oid.succ k);
|
|
});
|
|
}
|
|
|
|
let of_canonical_v1 { payload } =
|
|
let init = Map.empty (module Name) in
|
|
let classes =
|
|
List.fold payload ~init ~f:(fun state (cid, objs) ->
|
|
Map.add_exn state ~key:cid
|
|
~data:(List.fold objs ~f:add_object ~init:Env.empty_class))
|
|
in
|
|
init_last { empty with classes }
|
|
|
|
let of_canonical_v2 { payload } =
|
|
let init = Map.empty (module Name) in
|
|
let classes =
|
|
List.fold payload ~init ~f:(fun state (cid, (last, objs)) ->
|
|
let init = { Env.empty_class with last } in
|
|
Map.add_exn state ~key:cid
|
|
~data:(List.fold objs ~f:add_object ~init))
|
|
in
|
|
{ empty with classes }
|
|
|
|
let of_bigstring data =
|
|
let pos_ref = ref (check_magic data) in
|
|
let version = bin_read_version data ~pos_ref in
|
|
match version with
|
|
| V1 ->
|
|
of_canonical_v1
|
|
{ version; payload = bin_read_payload bin_read_v1 data ~pos_ref }
|
|
| V2 ->
|
|
of_canonical_v2
|
|
{ version; payload = bin_read_payload bin_read_v2 data ~pos_ref }
|
|
|
|
let load path =
|
|
let fd = Unix.openfile path Unix.[ O_RDONLY ] 0o400 in
|
|
try
|
|
let data =
|
|
Bigarray.array1_of_genarray
|
|
@@ Unix.map_file fd Bigarray.char Bigarray.c_layout false [| -1 |]
|
|
in
|
|
let r = of_bigstring data in
|
|
Unix.close fd;
|
|
r
|
|
with exn ->
|
|
Unix.close fd;
|
|
raise exn
|
|
|
|
let blit_canonical_to_bigstring repr buf =
|
|
Bigstring.From_string.blito ~src:magic ~dst:buf ();
|
|
let pos = String.length magic in
|
|
let _p = bin_write_canonical bin_write_v2 ~pos buf repr in
|
|
()
|
|
|
|
let to_bigstring state =
|
|
let repr = to_canonical state in
|
|
let size = String.length magic + bin_size_canonical bin_size_v2 repr in
|
|
let data = Bigstring.create size in
|
|
blit_canonical_to_bigstring repr data;
|
|
data
|
|
|
|
let save state path =
|
|
let repr = to_canonical state in
|
|
let size = String.length magic + bin_size_canonical bin_size_v2 repr in
|
|
let fd = Unix.openfile path Unix.[ O_RDWR; O_CREAT; O_TRUNC ] 0o660 in
|
|
try
|
|
let dim = [| size |] in
|
|
let buf =
|
|
Bigarray.array1_of_genarray
|
|
@@ Unix.map_file fd Bigarray.char Bigarray.c_layout true dim
|
|
in
|
|
blit_canonical_to_bigstring repr buf;
|
|
Unix.close fd
|
|
with exn ->
|
|
Unix.close fd;
|
|
raise exn
|
|
end
|
|
|
|
let save = Io.save
|
|
and load = Io.load
|
|
and to_bigstring = Io.to_bigstring
|
|
and of_bigstring = Io.of_bigstring
|
|
|
|
let objects cls =
|
|
objects cls >>| fun { vals } -> Sequence.of_list (Oid.Tree.keys vals)
|
|
|
|
module Context = struct
|
|
type 'a var = { nil : 'a knowledge; key : 'a Dict.Key.t }
|
|
|
|
let declare ?(inspect = sexp_of_opaque) ?package name init =
|
|
let name = Name.create ?package name in
|
|
{ nil = init; key = Dict.Key.create ~name inspect }
|
|
|
|
let set { key } x =
|
|
update @@ fun s -> { s with context = Dict.set key x s.context }
|
|
|
|
let get { key; nil } =
|
|
get () >>= fun { context = s } ->
|
|
match Dict.find key s with None -> nil | Some x -> !!x
|
|
|
|
let update v f = get v >>= fun x -> set v (f x)
|
|
|
|
let with_var v x f =
|
|
get v >>= fun x' ->
|
|
set v x >>= fun () ->
|
|
f () >>= fun r ->
|
|
set v x' >>| fun () -> r
|
|
end
|
|
|
|
module Rule = struct
|
|
type def = Registry.def
|
|
type doc = Registry.doc
|
|
|
|
let declare = Registry.start_rule
|
|
let require { Slot.name } = Registry.rule_require name
|
|
let provide { Slot.name } = Registry.rule_provide name
|
|
let dynamic = Registry.rule_dynamic
|
|
let comment = Registry.rule_comment
|
|
end
|
|
|
|
module Conflict = Conflict
|
|
module Agent = Agent
|
|
|
|
type 'a opinions = 'a Opinions.t
|
|
type agent = Agent.t
|
|
|
|
let sexp_of_conflict = Conflict.sexp_of_t
|
|
|
|
module Name = Name
|
|
|
|
type name = Name.t
|
|
|
|
module Documentation = Documentation
|
|
|
|
module Enum = struct
|
|
module type S = sig
|
|
type t
|
|
|
|
val declare : ?package:string -> string -> t
|
|
val read : ?package:string -> string -> t
|
|
val name : t -> Name.t
|
|
val unknown : t
|
|
val is_unknown : t -> bool
|
|
val domain : t domain
|
|
val persistent : t persistent
|
|
val hash : t -> int
|
|
val members : unit -> t list
|
|
|
|
include Base.Comparable.S with type t := t
|
|
include Binable.S with type t := t
|
|
include Stringable.S with type t := t
|
|
include Pretty_printer.S with type t := t
|
|
include Sexpable.S with type t := t
|
|
end
|
|
|
|
module Make () = struct
|
|
type t = Name.t [@@deriving bin_io, sexp]
|
|
|
|
let unknown = Name.of_string ":unknown"
|
|
let elements = Hash_set.of_list (module Name) [ unknown ]
|
|
|
|
let declare ?package name =
|
|
let name = Name.create ?package name in
|
|
if Hash_set.mem elements name then
|
|
invalid_argf
|
|
"Enum.declare: the element %s is already declared please choose a \
|
|
unique name"
|
|
(Name.to_string name) ();
|
|
Hash_set.add elements name;
|
|
name
|
|
|
|
let read ?package name =
|
|
let name = Name.read ?package name in
|
|
if not (Hash_set.mem elements name) then
|
|
invalid_argf "Enum.read: %s is not a member of the given enumeration."
|
|
(Name.to_string name) ();
|
|
name
|
|
|
|
let name x = x
|
|
let is_unknown = Name.equal unknown
|
|
let hash = Name.hash
|
|
let members () = Hash_set.to_list elements
|
|
|
|
include Base.Comparable.Make (Name)
|
|
include (Name : Stringable.S with type t := t)
|
|
include (Name : Pretty_printer.S with type t := t)
|
|
|
|
let domain = Domain.flat "enum" ~inspect:sexp_of_t ~empty:unknown ~equal
|
|
let persistent = Persistent.name
|
|
end
|
|
end
|
|
end
|
|
|
|
type 'a knowledge = 'a Knowledge.t
|