The Factual modeling language

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 fact has fields, which are immutable.
  • The identity of the fact is defined by the values of the fields. If all fields are equal, the facts are the same.
  • If a field is a reference to another fact, that fact is called a prerequisite. The fact containing the reference is called a successor.
  • A fact has queries.
  • Queries find facts by their predecessor/successor relationships.

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:

  • unique - Each instance of this fact is unique, even if all fields are the same.
  • delete - This fact can be deleted.
  • undelete - Deletion of this fact can be undone. Requires delete to be specified.
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:

  • ? - Zero or one
  • * - Zero to many
  • neither - One
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 + "}"