A set of additions to FEEL that a caller switches on when it prepares an expression (D-81).
DMN 1.6 defines FEEL, and the engines that run it add to it: functions that DMN does not have, more signatures for the ones it has, other spellings of a date. An extension is a module that says what it adds, and the core asks the extensions that are on, at the few places where it decides. Nothing in the core names an extension.
Feel.prepare("shout(name)", extensions: [MyApp.Shout])The option takes modules that implement this behaviour, and names (registry/0) that stand for one or
more of them. It is [] by default, and then the library is DMN 1.6 and nothing else. The extensions go
with the prepared expression into every run of it, as the dialect does (D-49), and a run cannot choose
other ones.
What an extension can do
An extension adds: it never changes the result of an expression that is valid without it. What it
adds was an error before (an unknown function, a call with a number of arguments that no signature
takes, a string that is not a date), or was not FEEL at all. This is what makes it safe to switch on, and
apps/bdm_tck/scripts/tck.exs --extensions checks it: with an extension on, every result of the TCK that was
right stays right. The TCK pins a few errors, though (number("1.000.000,01", ".") is "an error when too few params", 0058
#016), and an extension that accepts such a call deviates from it on purpose: it names the nodes, and any other
that stops passing is a bug of the extension (D-81).
| Callback | Adds |
|---|---|
functions/0 | functions with names that DMN 1.6 does not have. A name of several words is read as one name wherever the extension is on, as string length is |
overloads/0 | signatures for a function that DMN 1.6 has (Feel.Builtin.Function.with_overloads/2) |
temporal_text/1 | other spellings of the text of a time or a date and time, which time(...), date and time(...) and @"..." then read as DMN 1.6 writes them, and report |
The first two are lists of Feel.Builtin.Functions, with parameters, types and a call, like the built-in
functions (D-56), and are called in the same way. The third is the spelling of a text, and is reported when it
is used (D-81). Syntax is not an extension: names in backticks are read always, and reported (D-83).
data_only?/0 is not an addition but a declaration (D-222): an extension that never returns a function and makes no
diagnostic with an :origin can say so, and a host that copies a run into a worker then need not copy the source of the
elements the run does not use. An extension that does not declare it is taken to be able to return a function written
elsewhere.
An overload is the second choice
The function of DMN 1.6 runs first. Only when it reports an error does an overload get its turn, in the
order of the extensions, and its call may return :next to say that the arguments are not for it. If
every overload does, the error of the function of DMN 1.6 is the result, as it is with the extensions
off. A function of DMN 1.6 that gives null because an argument is null (:missing) is not asked
about again: that is a result and not an error.
A module
defmodule MyApp.Shout do
@behaviour Feel.Extension
alias Feel.Builtin.Function
@impl true
def name, do: :shout
@impl true
def functions do
[
%Function{
name: "shout",
signatures: [Function.signature([Function.parameter("text", [:string])], [:string])],
call: fn _env, [text] -> {:ok, String.upcase(text) <> "!"} end
}
]
end
end
Summary
Callbacks
Whether the extension adds data only: what it returns is a value that is not a function (and has none inside), and a
diagnostic it makes has no :origin (Feel.Diagnostic), so it never brings a text of another expression into a run.
A host that copies a run into a worker (Dmn.Execution) then need not copy the sources of the elements the run does not
use. An extension that does not say so, or says false, is taken to be able to (a function that returns a function
written elsewhere, with its origin, is one). Wrong true loses the element in the diagnostics of such a function in a
worker; it does not change a value. It is called once, when the set of extensions is prepared (Feel.Extensions.new/1),
inside the protected preparation, and its answer is recorded; keep it a constant. A callback that raises counts as false.
The functions that the extension adds. Their names are not among those of DMN 1.6 (a function of DMN 1.6
gets more signatures with overloads/0), and no other extension that is on defines them.
The name of the extension, for the messages about it.
More signatures for functions of DMN 1.6, each as a Feel.Builtin.Function of the name of the function.
The call of an overload gets the arguments of a call that one of its signatures accepts, and returns an
outcome or :next.
Other spellings of text, which a function or a literal could not read, as DMN 1.6 writes them, best first
(Feel.Value.Temporal.Spellings): none if the extension does not know the text. The library reads the first that
the function or the literal reads, and reports the spelling as an :info with the code :temporal_spelling;
if there is none that it reads, the error is the one it gave.
Functions
The extensions that extensions: knows by name, each with the modules it stands for.
Callbacks
@callback data_only?() :: boolean()
Whether the extension adds data only: what it returns is a value that is not a function (and has none inside), and a
diagnostic it makes has no :origin (Feel.Diagnostic), so it never brings a text of another expression into a run.
A host that copies a run into a worker (Dmn.Execution) then need not copy the sources of the elements the run does not
use. An extension that does not say so, or says false, is taken to be able to (a function that returns a function
written elsewhere, with its origin, is one). Wrong true loses the element in the diagnostics of such a function in a
worker; it does not change a value. It is called once, when the set of extensions is prepared (Feel.Extensions.new/1),
inside the protected preparation, and its answer is recorded; keep it a constant. A callback that raises counts as false.
@callback functions() :: [Feel.Builtin.Function.t()]
The functions that the extension adds. Their names are not among those of DMN 1.6 (a function of DMN 1.6
gets more signatures with overloads/0), and no other extension that is on defines them.
@callback name() :: atom()
The name of the extension, for the messages about it.
@callback overloads() :: [Feel.Builtin.Function.t()]
More signatures for functions of DMN 1.6, each as a Feel.Builtin.Function of the name of the function.
The call of an overload gets the arguments of a call that one of its signatures accepts, and returns an
outcome or :next.
Other spellings of text, which a function or a literal could not read, as DMN 1.6 writes them, best first
(Feel.Value.Temporal.Spellings): none if the extension does not know the text. The library reads the first that
the function or the literal reads, and reports the spelling as an :info with the code :temporal_spelling;
if there is none that it reads, the error is the one it gave.