Dmn.Compiler.Types (dmn v0.1.0)

Copy Markdown View Source

The types of a model, as FEEL has them: the item definitions read into a Feel.ItemDefinitions (DMN 1.6, 7.3.3), and from them the type, the allowed values and the type constraints of every variable (Feel.ItemDefinitions.scope/2).

dmn has no typing of its own. What a typeRef means, what an implicit conversion is, and how a value that is not of its type is made null with an error, are FEEL's (DMN 1.6, 10.3.2.9); this module only hands it the definitions as data.

Summary

Types

What a value of a declared type has to satisfy, as the node of FEEL for a declared value (Feel.AST) takes it: nil for nothing, the source of a list of unary tests for the allowed values of the value itself (the common case, and all that a typeRef of a built-in type or of a plain item definition with allowedValues has), or the list of every constraint of the type (Feel.ItemDefinitions.constraint/0): the allowed values and the type constraint of the type, of the type it refers to, and of its components, each down to the member it is for.

How the types of a model are written in the view of another one: the prefix that its own item definitions get (the name of the import and a dot, nothing for an import with no name), and what its imports are called in that view, each as the model writes the name of the import and a dot, and the prefix that the viewer gives the types of the model it names. nil for the model that looks, whose types are as they are.

What a model sees of the models it imports: the models to read the item definitions of, each with the frame its types are written in, and the frames by {namespace, prefix} (the model and the prefix of an import), for the types of the elements read from them.

Functions

The item definitions for the type names that the extensions give (Dmn.Extension.type_aliases/1), as data for Feel.ItemDefinitions.new/1, to follow the definitions of the models of the view.

What a typeRef declares, as the node of FEEL's syntax tree for a declared value takes it (Feel.AST): {type, constraints}, nil for no type, {:unread, problems} for one that FEEL cannot read. As for result/2, every constraint of the type is kept.

The item definitions of a model as data for Feel.ItemDefinitions.new/1, and the names that were given twice (the first definition of a name is the one that stands).

The outputTypeRef of each item definition that is a function type, by the name of the definition, from the data that definitions/2 gave (Feel.ItemDefinitions.new/1 takes the same list). A definition that is the name of another (it has a typeRef and nothing else but its constraints, and is not a collection) is that type, so it has the output of the function type at the end of the chain of names; a chain that goes round in a circle has none.

A function type from formal parameters and a variable's declared type as the result, in the caller's type frame. Decision services use service_type/5 for their final result. Business knowledge models use knowledge_type/4 to interpret a function item on their own variable. A type that cannot be read is Any.

function_type/4 of a business knowledge model, whose variable is the type of the function (output_declared/3): when it is a function item (DMN 1.6, 7.3.3) the result is its outputTypeRef, and the parameters stay the formal parameters of the model. The variable of a decision service describes its own function too; its output decisions are separately kept as value types by service_type/5.

The canonical name of an element of the model, as the scope of FEEL has it.

What the variable of a business knowledge model or a decision service declares for what the function gives (declared/2, for the result). The variable of an invocable is the type of the function: an item definition with a functionItem (DMN 1.6, 7.3.3), whose outputTypeRef is the type of the result. A model that gives the type of the result directly (typeRef="number") is read so too.

A type reference as the viewer writes it, given the frame of the model that wrote it: the prefix of the model before a type of its own, the viewer's prefix for the model that a type is of through an import of the model. A built-in type, no type, and the type of the model that looks (nil) are as they are.

The declared type of the value of a variable and what it has to satisfy, for the result of a decision: {type, constraints}, with nil for no type and no constraints. A type that FEEL cannot read is {:unread, problems}.

A service's result after its output decisions and its own declaration have been enforced. Several outputs form a context by decision name. The service variable describes the function; only that declaration unwraps a function layer, preserving function-valued output decisions.

What model sees of the models it imports (and that they import), each of them once (models are by namespace).

Types

constraints()

@type constraints() :: String.t() | [Feel.ItemDefinitions.constraint()] | nil

What a value of a declared type has to satisfy, as the node of FEEL for a declared value (Feel.AST) takes it: nil for nothing, the source of a list of unary tests for the allowed values of the value itself (the common case, and all that a typeRef of a built-in type or of a plain item definition with allowedValues has), or the list of every constraint of the type (Feel.ItemDefinitions.constraint/0): the allowed values and the type constraint of the type, of the type it refers to, and of its components, each down to the member it is for.

frame()

@type frame() :: %{prefix: String.t(), imports: [{String.t(), String.t()}]} | nil

How the types of a model are written in the view of another one: the prefix that its own item definitions get (the name of the import and a dot, nothing for an import with no name), and what its imports are called in that view, each as the model writes the name of the import and a dot, and the prefix that the viewer gives the types of the model it names. nil for the model that looks, whose types are as they are.

view()

@type view() :: %{
  copies: [{Dmn.Model.t(), frame()}],
  frames: %{optional({String.t(), String.t()}) => frame()}
}

What a model sees of the models it imports: the models to read the item definitions of, each with the frame its types are written in, and the frames by {namespace, prefix} (the model and the prefix of an import), for the types of the elements read from them.

Functions

aliases(definitions, arg2, aliases)

@spec aliases([Feel.ItemDefinitions.definition()], view(), %{
  optional(String.t()) => String.t()
}) :: [
  Feel.ItemDefinitions.definition()
]

The item definitions for the type names that the extensions give (Dmn.Extension.type_aliases/1), as data for Feel.ItemDefinitions.new/1, to follow the definitions of the models of the view.

A name is added for every place that the types of a model of the view are written: with the import Imp the name dateTime is Imp.dateTime too, since a typeRef of an imported model is written as the viewer has it (qualify/2). It is added where no definition has the name, so a model that defines dateTime has its own type, and the first of the models that share a prefix to define it has it.

iex> view = %{copies: [{nil, nil}, {nil, %{prefix: "Imp.", imports: []}}], frames: %{}}
iex> Dmn.Compiler.Types.aliases([%{name: "Imp.dateTime", type_ref: "string"}], view, %{"dateTime" => "date and time"})
[%{name: "dateTime", type_ref: "date and time"}]

declared(definitions, type_ref)

@spec declared(Feel.ItemDefinitions.t(), String.t() | nil) ::
  {Feel.Type.t(), constraints()} | nil | {:unread, list()}

What a typeRef declares, as the node of FEEL's syntax tree for a declared value takes it (Feel.AST): {type, constraints}, nil for no type, {:unread, problems} for one that FEEL cannot read. As for result/2, every constraint of the type is kept.

definitions(model, frame \\ nil)

@spec definitions(Dmn.Model.t(), frame()) ::
  {[Feel.ItemDefinitions.definition()], [String.t()]}

The item definitions of a model as data for Feel.ItemDefinitions.new/1, and the names that were given twice (the first definition of a name is the one that stands).

With the frame of a model that is imported (view/2) the definitions are written as the viewer has them: with a prefix of "Imp." the definition tPerson is Imp.tPerson, and a type it names (tAddress) is Imp.tAddress.

function_outputs(data)

@spec function_outputs([Feel.ItemDefinitions.definition()]) :: %{
  optional(String.t()) => String.t()
}

The outputTypeRef of each item definition that is a function type, by the name of the definition, from the data that definitions/2 gave (Feel.ItemDefinitions.new/1 takes the same list). A definition that is the name of another (it has a typeRef and nothing else but its constraints, and is not a collection) is that type, so it has the output of the function type at the end of the chain of names; a chain that goes round in a circle has none.

iex> data = [
...>   %{name: "F", function: %{output: "tAge", parameters: []}},
...>   %{name: "Alias", type_ref: "F"},
...>   %{name: "AliasOfAlias", type_ref: "Alias"},
...>   %{name: "Loop", type_ref: "Loop"},
...>   %{name: "tAge", type_ref: "number"}
...> ]
iex> Dmn.Compiler.Types.function_outputs(data)
%{"F" => "tAge", "Alias" => "tAge", "AliasOfAlias" => "tAge"}

function_type(definitions, parameters, variable, frame \\ nil)

A function type from formal parameters and a variable's declared type as the result, in the caller's type frame. Decision services use service_type/5 for their final result. Business knowledge models use knowledge_type/4 to interpret a function item on their own variable. A type that cannot be read is Any.

knowledge_type(definitions, parameters, variable, frame \\ nil)

function_type/4 of a business knowledge model, whose variable is the type of the function (output_declared/3): when it is a function item (DMN 1.6, 7.3.3) the result is its outputTypeRef, and the parameters stay the formal parameters of the model. The variable of a decision service describes its own function too; its output decisions are separately kept as value types by service_type/5.

name(name)

@spec name(String.t()) :: String.t()

The canonical name of an element of the model, as the scope of FEEL has it.

output_declared(definitions, type_ref, function_outputs \\ %{})

@spec output_declared(Feel.ItemDefinitions.t(), String.t() | nil, %{
  optional(String.t()) => String.t()
}) ::
  {Feel.Type.t(), constraints()} | nil | {:unread, list()}

What the variable of a business knowledge model or a decision service declares for what the function gives (declared/2, for the result). The variable of an invocable is the type of the function: an item definition with a functionItem (DMN 1.6, 7.3.3), whose outputTypeRef is the type of the result. A model that gives the type of the result directly (typeRef="number") is read so too.

FEEL's function type has the type of the output and none of its constraints, so function_outputs (function_outputs/1) says which type each function item names, and the result is held to the constraints of that type as a declared value is.

qualify(type_ref, arg2)

@spec qualify(String.t() | nil, frame()) :: String.t() | nil

A type reference as the viewer writes it, given the frame of the model that wrote it: the prefix of the model before a type of its own, the viewer's prefix for the model that a type is of through an import of the model. A built-in type, no type, and the type of the model that looks (nil) are as they are.

iex> frame = %{prefix: "Imp.", imports: [{"Other.", "Other."}]}
iex> Dmn.Compiler.Types.qualify("tPerson", frame)
"Imp.tPerson"
iex> Dmn.Compiler.Types.qualify("Other.tAddress", frame)
"Other.tAddress"
iex> Dmn.Compiler.Types.qualify("number", frame)
"number"
iex> Dmn.Compiler.Types.qualify("list<number!>", frame)
"list<number!>"
iex> Dmn.Compiler.Types.qualify("tPerson", nil)
"tPerson"
iex> Dmn.Compiler.Types.qualify(nil, frame)
nil

result(definitions, arg2)

@spec result(Feel.ItemDefinitions.t(), Dmn.Model.InformationItem.t() | nil) ::
  {Feel.Type.t() | nil, constraints()} | {:unread, list()}

The declared type of the value of a variable and what it has to satisfy, for the result of a decision: {type, constraints}, with nil for no type and no constraints. A type that FEEL cannot read is {:unread, problems}.

All the constraints of the type are kept (D-161 of feel): the allowed values of a component, the type constraint, a collection's allowed values for its elements, a second list of allowed values. The result of an expression is verified against all of them, and none is left out.

service_type(definitions, parameters, outputs, variable, frame \\ nil)

A service's result after its output decisions and its own declaration have been enforced. Several outputs form a context by decision name. The service variable describes the function; only that declaration unwraps a function layer, preserving function-valued output decisions.

view(viewer, models)

@spec view(Dmn.Model.t(), %{optional(String.t()) => Dmn.Model.t()}) :: view()

What model sees of the models it imports (and that they import), each of them once (models are by namespace).

The item definitions of a model that is imported are those of the importing model under the name of the import: with the import Imp, the definition tPerson is Imp.tPerson, and a type that it names (tAddress) is Imp.tAddress, since that is what the importing model calls it. A model that model does not import but a model it imports does is not named by model (DMN 1.6, 6.3.3: an import is explicit), but its types are needed for the types of the model that names it, so they are read once, under a prefix that no model can write (U+0001, the namespace and U+0001, as the other names that the compiler makes), and a type that an imported model names through an import of its own is that, whichever way it was reached.

Under the path of imports instead, a model that is reached by two paths is read twice: two models of two imports each, 15 levels deep, are 32,768 copies of one definition (79 s). An import with no name is read where the model that has it is, since the types of the model are written without a prefix.

copies are in the order of the reading, model itself first.