(* 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 "@[%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 "(@["; 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 "@["; 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 "@[(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 "@["; Oid.Tree.iter vals ~f:(fun oid { data; name } -> if not (Dict.is_empty data) then let () = match name with | None -> Format.fprintf ppf "@[(%a@ " Oid.pp oid | Some name -> Format.fprintf ppf "@[(%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