aboutsummaryrefslogblamecommitdiffstats
path: root/lib/branch.ml
blob: 994653e5d6c228fdcc645b10cb2d4cc17787406b (plain) (tree)




















































































                                                                                       
                                                                                       











































































                                                                                          
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.Reference 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 ()