aboutsummaryrefslogtreecommitdiffstats
path: root/lib/branch.ml
blob: a1382c61d30da3639ef7dd6c66754318336b7921 (plain) (blame)
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
module String_map = Map.Make (String)

type state = Term.t Label.Map.t list String_map.t
(* relation name -> stored tuples, the whole tree of one state *)

type t = {
  schemas : (Label.t * World.primitive) list String_map.t;
  states : (string, state) Hashtbl.t;  (* digest -> state; append-only *)
  mutable tips : (string * string) list;  (* branch -> digest; the one mutable place *)
}

let forced_label name =
  match Label.of_string name with Ok l -> l | Error _ -> assert false

let relation_l = forced_label "relation"
let tuple_l = forced_label "tuple"
let before_l = forced_label "before"
let after_l = forced_label "after"

(* Canonical serialization: relations in name order (the map gives
   it), tuples in term order, printed in canonical notation. Equal
   states digest equally because terms are canonical by
   construction. *)
let digest_of_state (state : state) =
  let buffer = Buffer.create 256 in
  String_map.iter
    (fun name tuples ->
      Buffer.add_string buffer name;
      let terms =
        List.map (fun tuple -> Term.record (Label.Map.bindings tuple)) tuples
        |> List.filter_map Result.to_option
        |> List.sort Term.compare
      in
      List.iter
        (fun term -> Buffer.add_string buffer (Format.asprintf "|%a" Term.pp term))
        terms;
      Buffer.add_char buffer '\n')
    state;
  Digest.to_hex (Digest.string (Buffer.contents buffer))

let store world state =
  let digest = digest_of_state state in
  if not (Hashtbl.mem world.states digest) then
    Hashtbl.add world.states digest state;
  digest

let create ~schemas =
  let catalog =
    List.fold_left
      (fun acc (name, schema) -> String_map.add name schema acc)
      String_map.empty schemas
  in
  let root : state =
    String_map.map (fun _ -> []) catalog
  in
  let world = { schemas = catalog; states = Hashtbl.create 16; tips = [] } in
  let root_digest = store world root in
  world.tips <- [ ("main", root_digest) ];
  world

let state_at world = function
  | Some (Term.String digest) -> Hashtbl.find_opt world.states digest
  | Some _ -> None
  | None -> (
      match List.assoc_opt "main" world.tips with
      | Some digest -> Hashtbl.find_opt world.states digest
      | None -> None)

let find world ?at name =
  match (String_map.find_opt name world.schemas, state_at world at) with
  | Some schema, Some state ->
      let facts = Option.value ~default:[] (String_map.find_opt name state) in
      let schema = List.map (fun (l, p) -> (l, World.Primitive p)) schema in
      Some (World.Table { schema; facts })
  | _ -> None

(* [Insert]: the transition relation between states (decision 3).
   Ground relation name, ground tuple, ground predecessor state;
   the tuple must match the schema exactly and satisfy its
   primitive domains, or there is no such transition. *)
let insert world bindings =
  let ground label =
    Option.bind (Label.Map.find_opt label bindings) Pattern.to_term
  in
  match (ground relation_l, ground tuple_l, ground before_l) with
  | Some (Term.String relation), Some (Term.Record tuple), Some (Term.String before)
    -> (
      match
        (String_map.find_opt relation world.schemas, Hashtbl.find_opt world.states before)
      with
      | Some schema, Some state ->
          let conforms =
            List.length schema = Label.Map.cardinal tuple
            && List.for_all
                 (fun (label, domain) ->
                   match Label.Map.find_opt label tuple with
                   | Some value -> World.member_of_primitive value domain
                   | None -> false)
                 schema
          in
          if not conforms then World.T_fail
          else
            let facts = Option.value ~default:[] (String_map.find_opt relation state) in
            let successor = String_map.add relation (facts @ [ tuple ]) state in
            World.T_state (Term.string (store world successor))
      | _ -> World.T_fail)
  | Some _, Some _, Some _ -> World.T_fail
  | _ ->
      (* Something essential is not ground yet: wait for the chain. *)
      World.T_stuck

let transition world name bindings =
  match name with "Insert" -> Some (insert world bindings) | _ -> None

let tip world branch =
  Option.map Term.string (List.assoc_opt branch world.tips)

(* The single effect: compare-and-swap on the tip (decision 8). *)
let commit world ~branch ~expect ~next =
  match (expect, next, List.assoc_opt branch world.tips) with
  | Term.String expected, Term.String next_digest, Some current
    when String.equal current expected && Hashtbl.mem world.states next_digest ->
      world.tips <-
        (branch, next_digest) :: List.remove_assoc branch world.tips;
      Ok ()
  | _ -> Error `Conflict

(* Linearity (decision 6): within one expression, a state variable
   is consumed at most once as [before:] and produced at most once
   as [after:]. Sequencing comes from the chain, and the chain must
   be a chain. *)
let linear expr =
  let rec applications = function
    | World.Apply (name, bindings) -> [ (name, bindings) ]
    | World.And (a, b) | World.Or (a, b) | World.Not (a, b) | World.Forall (a, b)
      ->
        applications a @ applications b
  in
  let uses label =
    applications expr
    |> List.filter (fun (name, _) -> String.equal name "Insert")
    |> List.filter_map (fun (_, bindings) ->
           match Label.Map.find_opt label bindings with
           | Some (Pattern.Var v) -> Some v
           | _ -> None)
  in
  let duplicate vars =
    let sorted = List.sort String.compare vars in
    let rec scan = function
      | a :: b :: _ when String.equal a b -> Some a
      | _ :: rest -> scan rest
      | [] -> None
    in
    scan sorted
  in
  match (duplicate (uses before_l), duplicate (uses after_l)) with
  | Some v, _ ->
      Error
        (Printf.sprintf
           "state %s consumed twice: a fork inside one expression is not a chain" v)
  | _, Some v -> Error (Printf.sprintf "state %s produced twice" v)
  | None, None -> Ok ()