The Factual modeling language describes a historic model as a collection if interrelated facts. Factual compilers produce code for libraries like UpdateControls.Correspondence. The rules of Factual are:
A Factual file represents a namespace -- a self-contained collection of interrelated facts.
factual_file -> "namespace" dotted_identifier ";" import* fact*
A Factual file may import other namespaces. When doing so, it explicitly aliases the facts that it is importing. If one identifier is given, it is both the local and true name of the fact. If two are given, the identifier on the left is the local name, and the identifier on the right is the true name.
import -> "import" dotted_identifier "{" alias* "}
alias -> identifier ("=" identifier)? ";"
The remainder of a Factual file consists of facts. A fact is a named set of fields, properties, queries, and predicates. Facts can be modified with:
fact -> "fact" identifier "{" modifier* member* "}"
modifier -> "unique" | "delete" | "undelete"
member -> field | property | query | predicate
A field is a named value or fact reference that cannot change. A property is a named value or fact reference that can change.
field -> type identifier ";" property -> "property" type identifier ";"
The type of a field or a property can be a native data type, a reference to another fact, or a structure. Structures are declared in place and are not named. A type also expresses cardinality:
type -> (native_type | identifier | structure) ("?" | "*")?
native_type -> "int" | "float" | "char" | "string" | "date" | "time"
structure -> "(" type identifier ("," type identifier)* ")"
A query is a list of sets that describe related facts. A set is of the form: "All facts of type type such that fact.field = other fact". The first set in the list relates facts to "this" -- the scope of the query. Each subsequent set relates facts to the prior set. The final set determines the result of the query.
query -> identifier "*" identifier "{" set+ "}"
set -> identifier identifier ":" path "=" path condition?
path -> dotted_identifier | "this"
clause -> "not"? identifier "." identifier
condition -> "where" clause ("and" clause)*
A predicate determines whether a set is empty or not. Predicates can be used in queries.
predicate -> "bool" identifier "{" "not"? "exists" set + "}"