Dmn.Compiler (dmn v0.1.0)

Copy Markdown View Source

Compiles a Dmn.Model to a Dmn.Compiled: a prepared FEEL expression for each element of the decision requirements graph, and the plan that says what each reads.

Everything that does not depend on the data is done here, once:

  1. The models. The models that the first one imports are read through the resolver of the caller (Dmn.Imports, Dmn.Resolver); their elements are elements of the same graph. An element of an imported model is called Import.Name by the model that imports it, and its types are Import.tName.
  2. The types. The item definitions of a model, and those of the models it imports under the names of the imports, become a Feel.ItemDefinitions (Dmn.Compiler.Types). The declared type of a variable is FEEL's type, with its allowed values and its type constraints. By default recursive item references are errors. With features: [:recursion], guarded data cycles retain finite graphs: every cycle must cross a context member or list element, and none may cross a function signature. Runtime validation checks complete finite values; analysis uses conservative upper bounds (DD-22).
  3. The graph. The elements are indexed by their id (and the namespace of their model, for those of an import), the requirements are resolved to the elements they name, and a cycle is an error (DMN 1.6, 6.3: the requirements are acyclic): the elements on it have no logic. Unless the caller asks for recursion: true (DD-22): then business knowledge models and decision services that read themselves or each other are recursion groups, which are compiled as any unit is and run with links to one another (Feel.Recursion); a cycle that has a decision on it is still an error.
  4. The units. Each decision, business knowledge model and decision service has a scope (Feel.Scope): the input data and decisions it requires, each of its declared type and allowed values, and the functions it invokes, each as the function type it has. Its boxed expression is mapped to the syntax tree of FEEL (Dmn.Compiler.Expression, and Dmn.Compiler.Table for a decision table), and Feel.prepare_ast/3 analyzes it, types it with that scope, and compiles it for the backend (the interpreter unless backend: says otherwise). The declared type of a decision's variable, and its allowed values, are how its result is converted and verified.

A decision is never run here, and nothing is evaluated: a text that cannot be read, a name that is not in scope and a table with an entry that cannot be satisfied are diagnostics of the element (Dmn.Diagnostic), and the model is compiled all the same, as DMN has an expression that cannot be evaluated give null (7.3.8). Dmn.Compiled.diagnostics has all of them.

Summary

Functions

compile(root, options \\ [])

@spec compile(Dmn.Model.t(), keyword()) ::
  {:ok, Dmn.Compiled.t()} | {:error, Dmn.Error.t()}

Compiles a model.

Options:

  • :resolver: how the models that it imports are found (Dmn.Resolver). None by default, and then an import is a diagnostic.
  • :limits: the Dmn.Limits that the imported models are read within.
  • :feel_limits: the Feel.Limits that the expressions are prepared and run within (Feel.Limits.default/0 by default). The compiled model keeps them (Dmn.Compiled.feel_limits), and a run keeps to them unless it is given :limits of its own, which replace them (Dmn.Run).
  • :check_tables: whether the decision tables are asked what their types say of them (Dmn.Compiler.Checks); true by default.
  • :backend: what the prepared expressions run on (Feel.Backend): :interpreter (the default), which generates no code and loads nothing into the VM, or :beam, which compiles each expression to a module and is faster. The BEAM backend loads modules and mints atoms that are never freed, so it is for models that are trusted (DD-41). Both give the same results. With recursion: true the business knowledge models that have logic, and the decision services, stay on the interpreter, whatever is asked, since they can invoke themselves.
  • :features: what the caller switches on (Dmn.Features, DD-29): :recursion, and the extensions (Dmn.Extension) by name or module. None by default. The two options below are the spellings that came first, and add to it.
  • :recursion: whether a business knowledge model or a decision service may invoke itself, and may be invoked by what it invokes (DD-22). false by default, and then a requirement that goes round in a circle is an error and an element that invokes itself by name has a :recursion_not_enabled error as well as the unknown-name diagnostic. It also enables guarded recursive item definitions, validating complete finite data and constraints. Alias-only cycles and cycles through function signatures remain errors. Only the caller can say true: nothing in a model switches it on. A decision on a requirements cycle is an error either way.
  • :extensions: the additions to DMN 1.6 to compile with (Dmn.Extension), names or modules. None by default. An extension that gives type names (:dmn_1_1_types) adds them as item definitions of the model, after its own and those of the models it imports, so a type that the model defines has the name. An extension that runs external functions (Dmn.Extension.external_functions/0) has the functionDefinition of that kind compiled to the external function of FEEL, and the expressions of the model are prepared with the extensions of FEEL it brings.

Returns {:error, error} when the graph exceeds Dmn.Limits.max_units. Otherwise {:ok, compiled}: what is wrong with the model is in the diagnostics (Dmn.Compiled.errors/1), and an error of an element is also an error of its unit, which a run says and which ends a strict run. Dmn.validate/1 is the question whether there is any.