Boxic FEEL
Boxic FEEL is a native Elixir parser and evaluator for the Friendly Enough
Expression Language defined by DMN. It can be used independently of
boxic_dmn.
Installation
Add boxic_feel to mix.exs:
def deps do
[
{:boxic_feel, "~> 0.1.0"}
]
end
Quick start
context = %{
"price" => Decimal.new("12.50"),
"quantity" => Decimal.new("2")
}
{:ok, total} = Boxic.FEEL.evaluate("price * quantity", context)
Boxic.FEEL.parse/1 returns the public AST without evaluating it.
Boxic.FEEL.evaluate_ast/2 evaluates a previously parsed AST, and
Boxic.FEEL.evaluate_unary_test/3 evaluates a DMN unary test against a value.
All public entry points return {:ok, value} or
{:error, %Boxic.FEEL.Error{}}.
Compatibility
Against the vendored official DMN Technology Compatibility Kit revision
0dbcaf9b98bc3af4e36d44a7aed95e9e85703a13, Boxic passes 2,042 of the 2,060
selected FEEL cases. The remaining 18 are Java external-function integration
cases and are reported explicitly as unsupported. Compatibility on the
supported scope is 100%.
Elixir external functions
Trusted application code can expose an allowlisted set of Elixir functions to FEEL. Model text receives callable names only: it cannot construct module names, atoms, or arbitrary MFA tuples.
This is Boxic's platform-native replacement for DMN Java external functions. Both mechanisms let decisions call trusted host-language code, but the Elixir registry targets the BEAM rather than JVM classes and reflection. The official Java-specific TCK group remains unsupported because an Elixir call is not Java interoperability.
Define host functions
External functions are ordinary public Elixir functions. For example:
defmodule MyApp.Pricing do
def maximum(left, right), do: max(left, right)
# A variadic FEEL tail arrives as a list in the final Elixir argument.
def join(first, rest), do: Enum.join([first | rest], " ")
end
Create an allowlisted registry
Define a registry with the compile-time DSL:
defmodule MyApp.DecisionFunctions do
use Boxic.FEEL.ExternalFunctions
external "maximum", {MyApp.Pricing, :maximum},
parameters: [:integer, :integer],
returns: :number
external "joinWords", {MyApp.Pricing, :join},
parameters: [:string, {:varargs, :string}],
returns: :string
end
Only functions present in this registry are exposed. The DMN model never
receives {module, function} values and cannot turn strings into modules or
atoms.
Boundary types
The parameters and returns options control conversion at the trusted host
boundary:
| Type | FEEL side | Elixir side |
|---|---|---|
:feel | Any supported FEEL value | Unchanged Boxic value |
:number | Number | Decimal |
:integer | Integral number | Integer; returned integers become Decimal |
:float | Number | Float; returned floats become Decimal |
:string | String | UTF-8 binary |
:char | One-codepoint string | Unicode codepoint integer |
:boolean | Boolean | Boolean |
An empty parameters list performs no argument conversion or arity check. A
final {:varargs, type} accepts any additional FEEL arguments, converts them,
and passes them as a list in the final Elixir argument.
Call from FEEL
Use the registry directly with FEEL:
context =
MyApp.DecisionFunctions
|> Boxic.FEEL.ExternalFunctions.to_context()
{:ok, maximum} = Boxic.FEEL.evaluate("maximum(123, 456)", context)
{:ok, "foo bar baz"} =
Boxic.FEEL.evaluate(~S|joinWords("foo", "bar", "baz")|, context)
Registered context names take precedence over FEEL built-ins, so applications may deliberately replace a built-in for a particular evaluation.
Call from DMN
The DMN literal expression calls the registered name normally:
<decision id="price" name="Price">
<literalExpression>
<text>maximum(requested price, price ceiling)</text>
</literalExpression>
</decision>
Supply the registry separately when evaluating the trusted model. The registry is host configuration and is not encoded in DMN XML:
Boxic.DMN.evaluate(model, "Price", inputs,
external_functions: MyApp.DecisionFunctions
)
The registry module or an Boxic.FEEL.ExternalFunctions registry value may be
passed through :external_functions.
Errors and trust boundary
Invalid arity, argument conversion failures, invalid return values, and
exceptions raised by host code become structured Boxic.FEEL.Error values.
External functions execute in the evaluator process, so application code should
keep them deterministic and fast and should add its own timeout or isolation
when calling remote services.
Do not construct registry entries from model content or untrusted strings. The registry must remain trusted application configuration; this is what prevents the feature from becoming arbitrary BEAM code execution.
This replacement is a Boxic Elixir extension, not an implementation of DMN's Java external-function binding. The official Java-specific TCK group is therefore still selected and explicitly reported as unsupported.
License
Boxic FEEL is licensed under Apache-2.0. The official TCK corpus and local compatibility artifacts are not included in the Hex package.