module Error = struct
open Concepts.Condition
let unbound_variable name =
condition "unbound-variable" "A comparison mentioned a variable that nothing has bound"
("variable" |=| Concepts.Value.String name)
let absent_attribute target attribute =
condition "absent-attribute"
"A membership named an attribute that the tuples of its target do not carry"
( "target" |=| Concepts.Value.String (Kernel.Path.to_string target)
& "attribute" |=| Concepts.Value.String attribute )
let open_constraint name variables =
condition "open-constraint"
"A constraint is asserted of every state and so must be closed, and this one leaves \
variables that nothing covers"
( "constraint" |=| Concepts.Value.String name
& "variables" |=| Concepts.Value.String (String.concat ", " variables) )
let violated name =
condition "constraint-violated"
"A candidate state was refused because the violations of a constraint imposed on it are not \
empty"
("constraint" |=| Concepts.Value.String name)
end
type binding_expr = Var of string | Const of Concepts.Value.value
type membership = {target: Kernel.Path.t; binding: binding_expr BatMap.String.t}
type formula =
| Exists of comprehension
| Not of formula
| And of formula * formula
| Or of formula * formula
| Eq of {left: binding_expr; right: binding_expr}
| Truth of bool
and comprehension = {quantifiers: membership list; body: formula}
type scope = Protocols.Handle.t
let variables_of quantifiers =
List.fold_left
(fun acc {binding; _} ->
BatMap.String.values binding
|> BatEnum.fold
(fun acc -> function Var name -> BatSet.String.add name acc | Const _ -> acc)
acc )
BatSet.String.empty quantifiers
let rec free = function
| Truth _ -> BatSet.String.empty
| Not formula -> free formula
| And (left, right) | Or (left, right) -> BatSet.String.union (free left) (free right)
| Eq {left; right} ->
let mentioned = function
| Var name -> BatSet.String.singleton name
| Const _ -> BatSet.String.empty
in
BatSet.String.union (mentioned left) (mentioned right)
| Exists {quantifiers; body} -> BatSet.String.diff (free body) (variables_of quantifiers)
let attributes_of ({quantifiers; _} as comprehension) =
BatSet.String.union (variables_of quantifiers) (free (Exists comprehension))
(* Both takes nested negation; SQL also gets it from outer joins, which this form lacks. *)
type polarity = Positive | Negative | Both
let merged existing polarity =
match existing, polarity with
| Positive, Positive -> Positive
| Negative, Negative -> Negative
| _ -> Both
let at target (candidate, _) = Kernel.Path.equal candidate target
let relevance formula =
let flipped = function Positive -> Negative | Negative -> Positive | Both -> Both in
let record polarity occurrences {target; _} =
let polarity =
match List.find_opt (at target) occurrences with
| None -> polarity
| Some (_, existing) -> merged existing polarity
in
(target, polarity) :: List.filter (Fun.negate (at target)) occurrences
in
let rec walk polarity occurrences = function
| Truth _ | Eq _ -> occurrences
| Not formula -> walk (flipped polarity) occurrences formula
| And (left, right) | Or (left, right) -> walk polarity (walk polarity occurrences left) right
| Exists {quantifiers; body} ->
walk polarity (List.fold_left (record polarity) occurrences quantifiers) body
in
walk Positive [] formula
type environment = Concepts.Value.value BatMap.String.t
(* Cursors a walk holds open are suspended inside it when a caller
abandons the walk, so they are tracked to be closed on release. *)
module Live = struct
let none () = ref []
let opened live handle = live := handle :: !live
let closed live handle =
if List.memq handle !live then begin
live := List.filter (( != ) handle) !live;
Protocols.Handle.release handle
end
let abandoned live =
List.iter Protocols.Handle.release !live;
live := []
end
let resolve scope target = Kernel.Path.lookup scope target
let value_of environment = function
| Const value -> Ok value
| Var name ->
BatMap.String.find_opt name environment
|> Option.to_result ~none:(Error.unbound_variable name)
let fixed environment = function
| Const value -> Some value
| Var name -> BatMap.String.find_opt name environment
let pushed environment binding =
BatMap.String.enum binding
|> BatEnum.fold
(fun pushed (attribute, expression) ->
match fixed environment expression with
| Some value -> BatMap.String.add attribute value pushed
| None -> pushed )
Protocols.Generative.nothing
let agreeing environment expression value =
match expression, fixed environment expression with
| _, Some held -> if Concepts.Value.equal held value then Some environment else None
| Var name, None -> Some (BatMap.String.add name value environment)
| Const _, None -> None
let extend target binding environment tuple =
let open Utilities.Result in
BatMap.String.enum binding
|> BatEnum.fold
(fun acc (attribute, expression) ->
let* acc = acc in
match acc with
| None -> Ok None
| Some environment -> (
match BatMap.String.find_opt attribute tuple.Concepts.Tuple.attributes with
| None -> Error (Error.absent_attribute target attribute)
| Some value -> Ok (agreeing environment expression value) ) )
(Ok (Some environment))
type step = Continue | Stop
let rec holds ~live scope environment formula =
let open Utilities.Result in
match formula with
| Truth truth -> Ok truth
| Not formula -> holds ~live scope environment formula |> Result.map not
| And (left, right) ->
let* left = holds ~live scope environment left in
if left then holds ~live scope environment right else Ok false
| Or (left, right) ->
let* left = holds ~live scope environment left in
if left then Ok true else holds ~live scope environment right
| Eq {left; right} ->
let* left = value_of environment left in
let* right = value_of environment right in
Ok (Concepts.Value.equal left right)
| Exists comprehension ->
let* step = assignments ~live scope environment comprehension ~visit:(fun _ -> Stop) in
Ok (step = Stop)
(* An assignment reached through several tuples is still one
assignment, which the walk settles rather than its callers, since a
relation built over it has to be a set. *)
and assignments ~live scope environment {quantifiers; body} ~visit =
let open Utilities.Result in
let fingerprint environment =
Concepts.Tuple.hash {Concepts.Tuple.type_= ""; attributes= environment}
|> Concepts.Hash.to_raw_string
in
let rec walk seen environment = function
| [] -> (
let* satisfied = holds ~live scope environment body in
let taken = fingerprint environment in
match satisfied, BatSet.String.mem taken seen with
| false, _ | true, true -> Ok (seen, Continue)
| true, false -> Ok (BatSet.String.add taken seen, visit environment) )
| {target; binding} :: rest ->
let* handle = resolve scope target in
let* generative = Protocols.Generative.require handle in
let* cursor = Protocols.Generative.generate generative (pushed environment binding) in
Live.opened live cursor;
Fun.protect
~finally:(fun () -> Live.closed live cursor)
(fun () ->
let* scan = Protocols.Cursor.require cursor in
let rec drain seen =
let* tuple = Protocols.Cursor.next scan in
match tuple with
| None -> Ok (seen, Continue)
| Some tuple -> (
let* extended = extend target binding environment tuple in
match extended with
| None -> drain seen
| Some environment -> (
let* seen, step = walk seen environment rest in
match step with Stop -> Ok (seen, Stop) | Continue -> drain seen ) )
in
drain seen )
in
walk BatSet.String.empty environment quantifiers |> Result.map snd
type expression = {label: string; comprehension: comprehension; state: scope}
let rendered {label; comprehension; _} =
let expression = function
| Var name -> "?" ^ name
| Const value -> "=" ^ Concepts.Value.to_string value
in
let quantifier {target; binding} =
BatMap.String.enum binding
|> BatEnum.map (fun (attribute, held) -> attribute ^ ":" ^ expression held)
|> BatList.of_enum
|> String.concat ";"
|> Printf.sprintf "%s[%s]" (Kernel.Path.to_string target)
in
let rec formula = function
| Truth truth -> if truth then "T" else "F"
| Not inner -> Printf.sprintf "!(%s)" (formula inner)
| And (left, right) -> Printf.sprintf "&(%s,%s)" (formula left) (formula right)
| Or (left, right) -> Printf.sprintf "|(%s,%s)" (formula left) (formula right)
| Eq {left; right} -> Printf.sprintf "eq(%s,%s)" (expression left) (expression right)
| Exists {quantifiers; body} ->
Printf.sprintf "E(%s,%s)"
(List.map quantifier quantifiers |> String.concat "")
(formula body)
in
label ^ "/" ^ formula (Exists comprehension)
let provenance {quantifiers; _} variable =
List.concat_map
(fun {target; binding} ->
BatMap.String.enum binding
|> BatEnum.filter_map (fun (attribute, expression) ->
match expression with
| Var name when String.equal name variable ->
Some {Protocols.Schematics.source= Kernel.Path.to_list target; attribute}
| Var _ | Const _ -> None )
|> BatList.of_enum )
quantifiers
let unknown_domain = ""
let domain_of state origins =
let open Utilities.Result in
match origins with
| {Protocols.Schematics.source; attribute} :: _ -> (
let* handle = resolve state (Kernel.Path.of_list source) in
match Protocols.Schematics.from handle with
| None -> Ok unknown_domain
| Some schematics -> (
let* description = Protocols.Schematics.describe schematics in
match description with
| Protocols.Schematics.Relation description ->
BatMap.String.find_opt attribute description
|> Option.fold ~none:unknown_domain ~some:(fun attribute ->
attribute.Protocols.Schematics.domain )
|> Result.ok
| Protocols.Schematics.Multigroup _ | Protocols.Schematics.Tuple _ -> Ok unknown_domain )
)
| [] -> Ok unknown_domain
module Derivation = struct
type plan = expression
let identity plan = rendered plan |> Bytes.of_string |> Concepts.Hash.hash_of_bytes
let describe {comprehension; state; _} =
attributes_of comprehension
|> BatSet.String.elements
|> List.map (fun variable ->
let origins = provenance comprehension variable in
domain_of state origins
|> Result.map (fun domain -> variable, {Protocols.Schematics.domain; provenance= origins}) )
|> Utilities.List.sequence
|> Result.map BatMap.String.of_list
let modes {comprehension; _} =
let supplied = free (Exists comprehension) |> BatSet.String.elements in
Ok
(Concepts.Mode.of_list
[ Concepts.Mode.decides_when (attributes_of comprehension |> BatSet.String.elements);
Concepts.Mode.generates_when supplied Concepts.Cardinality.Finite ] )
let generate {label; comprehension; state} binding =
let open Utilities.Result in
let heading = attributes_of comprehension in
let tuple_of environment =
{ Concepts.Tuple.type_= label;
attributes= BatMap.String.filter (fun name _ -> BatSet.String.mem name heading) environment
}
in
let live = Live.none () in
let produce ~yield =
let* _ =
assignments ~live state binding comprehension ~visit:(fun environment ->
yield (tuple_of environment);
Continue )
in
Ok ()
in
Ok (Kernel.Generator.cursor_of ~finally:(fun () -> Live.abandoned live) produce)
end
module Derived = Kernel.EphemeralRelation.Make (Derivation)
module Program = Protocols.Program.Make (struct
type t = expression
end)
let execute expression = Derived.derive expression
class evaluator =
object (self)
inherit Kernel.Lifecycle.null
inherit Kernel.Identity.of_id
method invoke = execute
method protocols : Protocols.Handle.protocol list = [Program.make self]
end
let make () = new evaluator |> Protocols.Handle.make
type t = {name: string; body: formula}
type change = Assertion | Retraction
let declare ~name body =
let uncovered = free body in
if BatSet.String.is_empty uncovered then Ok {name; body}
else Error (Error.open_constraint name (BatSet.String.enum uncovered |> BatList.of_enum))
let name {name; _} = name
let body {body; _} = body
(* Decker: a denial yields its own quantifiers, any other closed
formula denies itself into degree zero. *)
let denial = function
| Not (Exists comprehension) -> comprehension
| formula -> {quantifiers= []; body= Not formula}
let violations {name; body} state = {label= name; comprehension= denial body; state}
let relevant {body; _} ~relation ~change =
match List.find_opt (at relation) (relevance body) with
| None -> false
| Some (_, Both) -> true
| Some (_, Negative) -> change = Assertion
| Some (_, Positive) -> change = Retraction
let with_handle handle body =
Fun.protect ~finally:(fun () -> Protocols.Handle.release handle) (fun () -> body handle)
let vacant relation =
let open Utilities.Result in
let* enumerable = Protocols.Enumerable.require relation in
let* cursor = Protocols.Enumerable.enumerate enumerable in
with_handle cursor (fun cursor ->
let* scan = Protocols.Cursor.require cursor in
let* witness = Protocols.Cursor.next scan in
Ok (Option.is_none witness) )
let satisfied interpreter constrains state =
let open Utilities.Result in
let* relation = Program.invoke interpreter (violations constrains state) in
with_handle relation vacant
let admits interpreter state constraints =
let open Utilities.Result in
List.fold_left
(fun acc constrains ->
let* () = acc in
let* upheld = satisfied interpreter constrains state in
if upheld then Ok () else Error (Error.violated (name constrains)) )
(Ok ()) constraints