package provider

  1. Overview
  2. Docs

Source file provider.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
open! Import

exception E of Sexp.t

let () =
  Sexplib0.Sexp_conv.Exn_converter.add [%extension_constructor E] (function
    | E sexp -> sexp
    | _ -> assert false)
;;

let raise_s msg sexp = raise (E (Sexp.List [ Atom msg; sexp ]))

module Trait = struct
  type ('t, 'module_type, 'tag) t = ('t, 'module_type, 'tag) Trait0.t

  module Create = Trait0.Create
  module Create0 = Trait0.Create0
  module Create1 = Trait0.Create1
  module Create2 = Trait0.Create2

  let runtime_trait_info = Runtime_trait_info.default

  module Info = struct
    type t =
      { id : int
      ; name : string option
      }

    let sexp_of_id_default _ = Sexp.Atom "#id"
    let sexp_of_id = ref sexp_of_id_default

    let sexp_of_t { id; name } =
      let sexp_of_id id = !sexp_of_id id in
      Sexp.List
        [ List [ Atom "id"; sexp_of_id id ]
        ; List [ Atom "name"; Atom (name |> Option.value ~default:"<none>") ]
        ]
    ;;

    let register_name (t : _ Trait0.t) ~name =
      Runtime_trait_info.set_name runtime_trait_info t ~name
    ;;
  end

  let info (t : _ t) =
    { Info.id = Trait0.uid t; name = Runtime_trait_info.get_name runtime_trait_info t }
  ;;

  module Uid = struct
    type t = int

    let sexp_of_t = Sexplib0.Sexp_conv.sexp_of_int
    let equal = Int.equal
    let compare = Int.compare
    let hash = Int.hash
    let seeded_hash = Int.seeded_hash
  end

  let uid = Trait0.uid
  let compare_by_uid t1 t2 = Uid.compare (uid t1) (uid t2)
  let same = Trait0.same
end

module Binding = struct
  type 'a t =
    | T :
        { trait : ('t, 'module_type, _) Trait.t
        ; implementation : 'module_type
        }
        -> 't t

  let uid (T { trait; implementation = _ }) = Trait.uid trait
  let info (T { trait; implementation = _ }) = Trait.info trait
  let compare_by_uid t1 t2 = Trait.Uid.compare (uid t1) (uid t2)
end

let implement (type a i) (trait : (a, i, _) Trait.t) ~impl:(implementation : i) =
  Binding.T { trait; implementation }
;;

(* We sort the element by their extension_id in increasing order. Element.(0) is
   a cache of the most recently looked up method. *)
type ('t, -'tags) t = 't Binding.t array

let dedup_sorted_keep_last =
  let[@tail_mod_cons] rec aux list ~compare =
    match list with
    | [] -> []
    | [ elt ] -> [ elt ]
    | elt1 :: (elt2 :: _ as tl) ->
      if compare elt1 elt2 = 0 then aux tl ~compare else elt1 :: aux tl ~compare
  in
  aux
;;

let make (type a) (bindings : a Binding.t list) : (a, _) t =
  let bindings =
    bindings
    |> List.stable_sort ~compare:Binding.compare_by_uid
    |> dedup_sorted_keep_last ~compare:Binding.compare_by_uid
  in
  match bindings with
  | [] -> [||]
  | hd :: _ ->
    (* We initialize the cache arbitrarily with the left most trait. *)
    Array.of_list (hd :: bindings)
;;

let same_trait_uids : type a b tags1 tags2. (a, tags1) t -> (b, tags2) t -> bool =
  fun t1 t2 ->
  (* We skip the cell 0 which contains the cache. *)
  if Array.length t1 <> Array.length t2
  then false
  else
    Array.for_alli t1 ~f:(fun i implementation ->
      i = 0 || 0 = Binding.compare_by_uid implementation t2.(i))
;;

let is_empty t = Array.length t = 0
let cache t = if Array.length t = 0 then None else Some (Binding.uid t.(0))

let bindings t =
  match Array.to_list t with
  | [] -> []
  | _ :: tl -> tl
;;

let extend t ~with_ = make (bindings t @ with_)

let rec binary_search
  : type a implementation tags b.
    (a, tags) t
    -> trait:(a, implementation, tags) Trait.t
    -> update_cache:bool
    -> if_not_found:(trait_info:Trait.Info.t -> b)
    -> if_found:(implementation -> b)
    -> from:int
    -> to_:int
    -> b
  =
  fun t ~trait ~update_cache ~if_not_found ~if_found ~from ~to_ ->
  if from > to_
  then if_not_found ~trait_info:(Trait.info trait)
  else (
    let mid = (from + to_) / 2 in
    let (Binding.T { trait = elt; implementation } as binding) = t.(mid) in
    match Trait.compare_by_uid elt trait |> Ordering.of_int with
    | Less ->
      binary_search t ~trait ~update_cache ~if_not_found ~if_found ~from:(mid + 1) ~to_
    | Greater ->
      binary_search t ~trait ~update_cache ~if_not_found ~if_found ~from ~to_:(mid - 1)
    | Equal ->
      (match Trait0.same_witness elt trait with
       | Equal ->
         if update_cache then t.(0) <- binding;
         if_found implementation
       | Not_equal ->
         (* Because [Trait0.t] is abstract, traits are necessarily created
            through the [Trait0.Create*] functors, which only create 0-arg [T]
            extensible variants. As a result, for all traits (a, b) the
            following holds: [(uid a = uid b) => phys_equal a b]. *)
         assert false))
;;

let make_lookup
  : type a implementation tags b.
    (a, tags) t
    -> trait:(a, implementation, tags) Trait.t
    -> update_cache:bool
    -> if_not_found:(trait_info:Trait.Info.t -> b)
    -> if_found:(implementation -> b)
    -> b
  =
  fun t ~trait ~update_cache ~if_not_found ~if_found ->
  if Array.length t = 0
  then if_not_found ~trait_info:(Trait.info trait)
  else (
    let (Binding.T { trait = cached_id; implementation }) = t.(0) in
    match Trait0.same_witness trait cached_id with
    | Equal -> if_found implementation
    | Not_equal ->
      binary_search
        t
        ~trait
        ~update_cache
        ~if_not_found
        ~if_found
        ~from:1
        ~to_:(Array.length t - 1))
;;

module If_not_found = struct
  let raise ~trait_info =
    raise_s
      "Trait not implemented"
      (Sexp.List [ List [ Atom "trait_info"; trait_info |> Trait.Info.sexp_of_t ] ])
  ;;

  let none ~trait_info:_ = None
  let false_ ~trait_info:_ = false
end

let lookup t ~trait =
  make_lookup
    t
    ~trait
    ~update_cache:true
    ~if_not_found:If_not_found.raise
    ~if_found:Fun.id
;;

let lookup_opt t ~trait =
  make_lookup
    t
    ~trait
    ~update_cache:true
    ~if_not_found:If_not_found.none
    ~if_found:Option.some
;;

let implements t ~trait =
  (* Only checking for the availability of the trait doesn't affect the cache,
     we leave it untouched in this case. *)
  make_lookup
    t
    ~trait
    ~update_cache:false
    ~if_not_found:If_not_found.false_
    ~if_found:(Fun.const true)
;;

type -'tags packed =
  | T :
      { t : 't
      ; provider : ('t, 'tags) t
      }
      -> 'tags packed

module Private = struct
  module Import = Import

  let same_trait_uids = same_trait_uids
  let cache = cache
  let dedup_sorted_keep_last = dedup_sorted_keep_last

  module Trait0 = Trait0
end