# `Feel`
[🔗](https://github.com/FeeJai/BDM/blob/feel-v0.1.0/apps/feel/lib/feel.ex#L1)

FEEL, the Friendly Enough Expression Language of DMN 1.6.

> #### Work in progress {: .warning}
>
> `parse/2` reads the whole grammar (see `Feel.Syntax.Parser`). `prepare/2`, `run/3`, and
> `eval/3` handle most of it: literals, names, `+ - * / **`, the comparisons, `and`, `or` and
> `if`, lists and contexts, paths, filters, `in`, `between`, ranges, `instance of`, dates, times
> and durations, unary tests, function definitions and the call of a function value (D-82), and all
> 86 built-in functions (D-56, D-73, D-79, D-87), and `for`, `some` and `every` (D-86). A function that
> is defined outside the expression is an `:analysis` error with the code `:not_implemented`, found when
> the expression is prepared. The README says where it stands.

## Two ways to run an expression

`eval/3` takes source text and runs it once. For repeated use, `prepare/2`
does the work that does not depend on the data (parsing, name resolution,
checks), and `run/3` or `test/4` then runs the prepared expression:

    {:ok, expression} = Feel.prepare("price * quantity")
    {:ok, total, diagnostics} = Feel.run(expression, %{"price" => ..., "quantity" => ...})

## Results and errors

Evaluation follows the DMN error model. A semantic error, such as a type
mismatch, makes the affected result `null` and produces a `t:Feel.Diagnostic.t/0`.
In `:lenient` mode (the default) evaluation continues and the diagnostics are
returned with the value. In `:strict` mode evaluation halts at the first
diagnostic, and the result is `{:error, error}`. A `null` operand of an arithmetic operator and of `<=`
and `>=` is such a diagnostic (`:null_operand`, D-103), so a strict run ends at one; `<`, `>`, `and`, `or`
and `not` give `null` and report nothing.

`{:error, error}` is also returned when the source cannot be parsed or
analyzed, and when a resource limit is hit. The `!` variants raise the returned
`t:Feel.Error.t/0`. Argument errors raised inside a worker become input errors.
Invalid API argument shapes can still raise before a worker starts.

## Containment

Public preparation, parsing and evaluation calls use `Feel.Worker` by default.
`guard:` configures deadlines, process heap policy and input/result transfer caps.
`sandbox: false` skips this boundary for trusted input, while keeping `Feel.Limits`.
This flag applies to each call and cannot escape an enclosing worker. BEAM
preparation and execution require the explicit trusted opt-out. Host callables
remain trusted capabilities; see the [security model](architecture.html#security-model).

## Entry points and dialects

`prepare/2` accepts three independent options. `:entry` selects the grammar
entry point: a full expression, or a list of unary tests (as used by decision
table input entries). `:dialect` selects FEEL, S-FEEL, or B-FEEL (`Feel.Dialect`). `:scope`
supplies the names the expression may refer to. A type such as `list<number>`
is not an entry point; `Feel.Type.parse/2` reads those.

S-FEEL is a restriction of the syntax: an expression that is outside it is a syntax error
(`Feel.Syntax.SFeel`), and one that is inside it runs as FEEL. B-FEEL has the grammar of FEEL
and will have other semantics; it is not written yet and runs as FEEL. The dialect goes with the
prepared expression into every run of it; a run cannot choose one (D-49).

## Extensions

`:extensions` switches on additions to FEEL that DMN 1.6 does not have, such as the functions of an
engine (`Feel.Extension`, D-81). There are none by default, and an extension only adds: what is valid
FEEL runs as it does without. Like the dialect, they go with the prepared expression into every run.

Errors are chosen per run through `:mode`; they are not a property of the
prepared expression.

# `bindings`

```elixir
@type bindings() :: %{optional(String.t()) =&gt; Feel.Value.t()}
```

Values for the names an expression refers to, keyed by name.

# `prepare_option`

```elixir
@type prepare_option() ::
  {:scope, Feel.Scope.t()}
  | {:scope_index, Feel.Syntax.ScopeIndex.t() | nil}
  | {:entry, :expression | :unary_tests}
  | {:dialect, Feel.Dialect.t()}
  | {:extensions, [atom()] | Feel.Extensions.t()}
  | {:s_feel_grammar, :practical | :literal}
  | {:unknown_names, :warn | :null | :error}
  | {:result_allowed_values, String.t()}
  | {:result_type, Feel.Type.t()}
  | {:input, Feel.Type.t()}
  | {:declarations, Feel.Analysis.Declarations.stance()}
  | {:analysis, Feel.Analysis.Refinement.tier()}
  | {:limits, Feel.Limits.t()}
  | {:backend, :interpreter | :beam}
  | {:optimize, boolean()}
  | {:sandbox, boolean()}
  | {:guard, keyword()}
```

Options that `prepare/2` understands.

# `result`

```elixir
@type result(value) :: {:ok, value, [Feel.Diagnostic.t()]} | {:error, Feel.Error.t()}
```

The result of running an expression.

# `run_option`

```elixir
@type run_option() ::
  {:mode, :lenient | :strict}
  | {:clock, DateTime.t() | (-&gt; DateTime.t())}
  | {:entropy, (non_neg_integer() -&gt; binary())}
  | {:limits, Feel.Limits.t()}
  | {:origin, term()}
  | {:contexts, :map | :ordered}
  | {:budget, Feel.Runtime.Budget.t()}
  | {:sandbox, boolean()}
  | {:guard, keyword()}
```

Options that `run/3`, `test/4`, and `eval/3` understand at run time.

# `source`

```elixir
@type source() :: String.t()
```

FEEL source text.

# `eval`

```elixir
@spec eval(source(), bindings(), [prepare_option() | run_option()]) ::
  result(Feel.Value.t())
```

Parses and runs `source` once.

Takes the options of `prepare/2` and of `run/3`; `:limits` applies to both. When no
`:scope` is given, the scope is made from `bindings`: each name with the type of its
value, so a name that is not bound is `null`, with a warning, and an error with `unknown_names: :error` (D-37).
A non-string binding key is an input error. With `sandbox: false`, building the
implicit scope raises `ArgumentError` for it.

The diagnostics are those of the run and those of `prepare/2` (`warnings/1`). Where the run
reports what `prepare/2` had warned of, at the same span and with the same code, it is
reported once, as the run has it, with the hint of the warning if the run has none.

    iex> {:ok, three, []} = Feel.eval("1 + 2")
    iex> Decimal.equal?(three, 3)
    true
    iex> Feel.eval("age >= 18", %{"age" => Decimal.new(21)})
    {:ok, true, []}
    iex> {:ok, nil, [warning, null_operand]} = Feel.eval("agee >= 18", %{"age" => Decimal.new(21)})
    iex> {warning.code, warning.severity, warning.hint}
    {:unknown_name, :warning, ~s(did you mean "age"?)}
    iex> {null_operand.code, null_operand.severity}
    {:null_operand, :error}
    iex> {:error, error} =
    ...>   Feel.eval("agee >= 18", %{"age" => Decimal.new(21)}, unknown_names: :error)
    iex> {error.code, hd(error.diagnostics).hint}
    {:unknown_name, ~s(did you mean "age"?)}
    iex> {:ok, nil, [diagnostic]} = Feel.eval("1 / 0")
    iex> {diagnostic.code, diagnostic.span}
    {:division_by_zero, {0, 5}}
    iex> {:error, error} = Feel.eval("1 / 0", %{}, mode: :strict)
    iex> error.kind
    :strict

# `eval!`

```elixir
@spec eval!(source(), bindings(), [prepare_option() | run_option()]) :: Feel.Value.t()
```

Like `eval/3`, but returns the value alone and raises `t:Feel.Error.t/0` on
`{:error, error}`. Diagnostics are discarded.

# `free_names`

```elixir
@spec free_names(Feel.Expression.t()) :: MapSet.t(String.t())
```

Returns the names that a prepared expression reads from its bindings.

# `parse`

```elixir
@spec parse(source(), [prepare_option()]) ::
  {:ok, Feel.AST.t()} | {:error, Feel.Error.t()}
```

Parses `source` and returns its syntax tree without analyzing or running it.

Takes the `:entry` and `:dialect` options of `prepare/2`, plus `:scope`,
because names that contain spaces or operators can only be recognized with
the scope in hand. A caller that parses many texts against one scope, and adds names to it as it goes, makes a
`Feel.Syntax.ScopeIndex` once and gives it as `:scope_index`: the names are then read from it, and the cost of the scope
is not paid for each text.

Syntax errors have kind `:syntax`; source and nesting limits have kind `:limit`.
The shared sandbox can also return deadline/transfer limits or worker failures.
Argument errors in parsing become `:input` errors with code `:invalid_options`
by default; direct calls with `sandbox: false` raise `ArgumentError` instead.

`entry: :unary_tests` reads a list of unary tests, with `-` for any input and
`not(...)` for a negated list; see `Feel.Syntax.Parser.parse_unary_tests/2`. Any
unsupported `:entry` is an argument error (`:expression` is the default).
The parser reads FEEL. With `dialect: :s_feel`
the tree is then checked against S-FEEL (`Feel.Syntax.SFeel`), and anything outside it is a
`:syntax` error with the code `:outside_s_feel`; `:s_feel_grammar` is explained at `prepare/2`.
A dialect outside `:feel`, `:s_feel` and `:b_feel` is an argument error.

    iex> {:ok, {:binary, _meta, :add, _left, _right}} = Feel.parse("1 + 2")
    iex> {:error, error} = Feel.parse("1 +")
    iex> {error.kind, error.span}
    {:syntax, {3, 3}}
    iex> {:ok, {:unary_tests, _meta, :positive, [_, _]}} = Feel.parse("< 5, 10", entry: :unary_tests)

# `prepare`

```elixir
@spec prepare(source(), [prepare_option()]) ::
  {:ok, Feel.Expression.t()} | {:error, Feel.Error.t()}
```

Prepares `source` for repeated use.

Parses the source, resolves names against the `:scope`, checks calls and
types where it can, and builds the form that `run/3` executes.

Argument errors raised during preparation become `:input` errors with code
`:invalid_options` under the default sandbox. The `ArgumentError` descriptions
below apply to direct calls with `sandbox: false`.

## Options

  * `:scope` - a `t:Feel.Scope.t/0` with the names and types the expression
    may refer to, and the allowed values of some of them: a bound value that is not
    allowed is `null` with an error (D-138). Without it, any name is accepted and resolved at run time.
  * `:entry` - `:expression` (default) or `:unary_tests`.
  * `:dialect` - `:feel` (default), `:s_feel`, or `:b_feel`. Raises `ArgumentError` for any
    other. With `:s_feel`, an expression that is not in S-FEEL is a `:syntax` error with the code
    `:outside_s_feel`; one that is runs as FEEL (D-49).
  * `:extensions` - a list of the extensions to switch on: names (`Feel.Extension.registry/0`) or modules that
    implement `Feel.Extension`. `[]` by default. They add functions, more signatures for functions, and
    the like, and change nothing that is valid FEEL (D-81). Raises `ArgumentError` for one that is
    neither. They go with the prepared expression into every run, and `run/3` raises for the option.
  * `:s_feel_grammar` - `:practical` (default) or `:literal`. Read only with `dialect: :s_feel`:
    the printed grammar of S-FEEL has no parentheses for grouping and no `null`, which
    `:practical` accepts and `:literal` reports (D-49).
  * `:unknown_names` - what a name that is not in the `:scope` is: `:warn` (the default), a
    variable that is looked up in the bindings when the expression runs, with a warning, since a
    scope need not have every name; `:null`, which makes it `null` with a warning; or `:error`.
  * `:result_type` - the type of the result, as a `t:Feel.Type.t/0`. The result is
    converted to it as a decision's is to its variable's, and is `null` with an error
    when it cannot be (D-138). The `result_type` of the scope, by default.
  * `:result_allowed_values` - the allowed values of the result: the source text of a list
    of unary tests, such as `"[0..100]"`. A result that none of them accepts is `null`,
    with a `:value_not_allowed` error; a result that is `null` is exempt (D-138). The
    `result_allowed_values` of the scope, by default.
  * `:input` - the type of the input `?` of unary tests (`entry: :unary_tests`), as a `t:Feel.Type.t/0`: `Any` by
    default. It is the type of a name declared in a scope: the input of `test/4` is converted to it, or is `null` with an
    error, as the value of a name is (D-138), and a `!` after the type makes a `null` input end the run with an `:input` error
    (D-48). What `:declarations` says of the names it says of the input. The tests are analyzed for an input of this type, so
    `? > 5` is a comparison of a number, and `? instance of number` is `true` for a `number!`.
  * `:declarations` - the stance on the declarations of the `:scope`: what a run does with a bound value that does not fit
    the declared type or the allowed values of its name (D-138), and whether the allowed values narrow the types, so
    that `age` with `[0..150]` is `number in [0..150]` and `age + 1` is `number in [1..151]` (D-138). One of
    `t:Feel.Analysis.Declarations.stance/0`, `:enforce` by default:

    | Stance | A bound value that does not fit | The allowed values |
    | ------ | ------------------------------- | ------------------ |
    | `:enforce` (default) | is converted, or is `null` with an error | narrow the types |
    | `:enforce_wide` | the same | do not narrow the types |
    | `:trust` | is not looked at: the host guarantees the values | narrow the types |
    | `:audit` | stays, and is reported as an error | narrow the types |
    | `:report` | stays, and is reported: an error for a declared type, a warning for allowed values | do not narrow |
    | `:ignore` | is not looked at | do not narrow |

    Narrowing lets the analysis prove things and the optimizer take out checks. It is sound when the run makes the values
    what the types say (`:enforce`), and the host's word when it does not (`:trust`, `:audit`): if a value is not allowed
    after all, the types are wrong, and so is what the optimizer removed because of them. `:report` and `:ignore` rely on
    no allowed value. `Feel.Analysis.Declarations` has the rest. Raises `ArgumentError` for any other value, and for
    `:trust` and `:audit` with `analysis: :core`, which has no sets of values to narrow by. The options `:verify` and
    `:narrow`, which this one replaces, raise `ArgumentError` that names the stance to use.
  * `:analysis` - the precision of inferred bounds (`Feel.Analysis.Refinement`, D-133). `:full`
    (the default) refines possible values and condition facts within representation and work budgets.
    `:core` keeps kinds and nullability, such as `number` instead of `number in [6..max]`, with no
    condition facts. Under the same declaration assumptions, both must preserve runtime values,
    ordered diagnostics and strict failures. Preparation warnings, optimizer proofs and preparation
    cost can differ. `declarations: :enforce` still verifies allowed values in core analysis without
    using them to narrow; `:trust` and `:audit` require full analysis.
    Literal function signatures come from kind-only analysis in their written environment and stay
    the same in both tiers (D-219). Full analysis makes an additional bounded core pass when the
    syntax contains functions; call refinements can retain a more precise body result. Runtime
    built-in argument diagnostics describe actual values; preparation warnings may describe
    different inferred bounds in each tier.
  * `:limits` - a `t:Feel.Limits.t/0` capping the size of results, and the work of preparing the expression: an error of the kind
    `:limit` for a source that is too large, a name that is too long, nesting that is too deep, conditions that say too much about
    one subject (`:max_narrowing_facts`, D-154), or, with `backend: :beam`, an expression that would make more than
    `:max_generated_statements` statements of code (code `:too_large_to_compile`, D-154; the interpreter makes none).
  * `:backend` - `:interpreter` (default) or `:beam`; the latter requires `sandbox: false`. See `Feel.Backend`.
  * `:sandbox` - `true` (default) contains the complete operation; `false` runs trusted input directly.
  * `:guard` - options of `Feel.Worker.run/2`, used when containment is enabled.
  * `:optimize` - whether `Feel.Optimizer` removes from the IR what the analysis has settled
    before the backend compiles it. `true` by default; the result of a run is the same
    either way (D-45). `Feel.Expression.unoptimized_ir/1` is the IR as the analyzer made it.

# `prepare_ast`

```elixir
@spec prepare_ast(Feel.AST.t(), source(), [prepare_option()]) ::
  {:ok, Feel.Expression.t()} | {:error, Feel.Error.t()}
```

Prepares a syntax tree that was not parsed from `source`, for a caller that builds one.

A layer above FEEL that has its own notation, as DMN has boxed expressions, can map it to a `t:Feel.AST.t/0` and have it analyzed,
typed, optimized and compiled as an expression is. The tree is made of the nodes of `Feel.AST` (the table there is the whole
contract) and is trusted to be well formed: a node that is not one, or a name that is not a list of tokens, raises. What it
is not trusted for is meaning, which the analyzer checks as it does for a parsed tree.

`source` is the text that the `:span`s of the nodes are byte ranges of. It is not read; it is kept in the expression, and a
diagnostic points into it. A caller that builds a tree from several texts gives it their concatenation and moves the spans of
each to where it is (`Feel.AST.shift/2`).

Takes the options of `prepare/2`, except `:entry` and `:dialect`, which are those of a tree (an expression, and `:feel`).
Argument errors inside the default worker become input errors; `sandbox: false`
exposes them directly as exceptions.

    iex> {:ok, tree} = Feel.parse("a + 1")
    iex> {:ok, expression} = Feel.prepare_ast(tree, "a + 1", scope: Feel.Scope.new(%{"a" => :number}))
    iex> Feel.run(expression, %{"a" => Decimal.new(1)})
    {:ok, Decimal.new(2), []}

# `run`

```elixir
@spec run(Feel.Expression.t(), bindings(), [run_option()]) :: result(Feel.Value.t())
```

Runs a prepared expression.

## Options

  * `:mode` - `:lenient` (default) or `:strict`.
  * `:clock` - a `DateTime`, or a zero-arity function returning one, used by
    `now()` and `today()`. The clock is read once per run, so repeated calls
    within one run agree.
  * `:entropy` - a function from a number of bytes to that many bytes, where the random functions of an
    extension get them (D-81): `:crypto.strong_rand_bytes/1` by default.
  * `:limits` - a `t:Feel.Limits.t/0`.
  * `:budget` - a process-owned `Feel.Runtime.Budget` handle for cumulative iteration, call and diagnostic limits.
    Share it with `sandbox: false`, or create and use it inside one enclosing worker.
  * `:sandbox` - `true` (default) contains the complete run; `false` executes trusted input in the caller.
  * `:guard` - options of `Feel.Worker.run/2`, used when containment is enabled.
  * `:origin` - which text this run is of, a term of the host's (D-201). The diagnostics of the run have it as their
    `:origin`, and so do those of a function written in the expression, wherever that function is called later, so that
    a host that runs several texts can find the span of a diagnostic in the one it is a range of. `nil` by default.
  * `:contexts` - what a context in the result is (D-135). `:map` (default) is a plain map with string keys, as the
    specification and the TCK have a context: the entries by name, with no order, so two contexts are equal as `=` says
    when their maps are. `:ordered` is a `t:Feel.Value.Context.t/0`, which keeps the order of the entries, and is what
    `get entries`, `context put` and `string(...)` of the same run saw.

A context in `bindings` is either: a plain map is a context in the order of its keys, and `Feel.Value.Context.new/1`
gives one in another order.

The dialect and the extensions are those of the prepared expression, and a `:dialect` or `:extensions`
option is an input error, or raises `ArgumentError` with `sandbox: false` (D-49, D-81).

# `test`

```elixir
@spec test(Feel.Expression.t(), Feel.Value.t(), bindings(), [run_option()]) ::
  result(boolean())
```

Tests `input` against a prepared list of unary tests.

The expression must have been prepared with `entry: :unary_tests`, and any other raises
`ArgumentError`. `input` is the value of `?` while the tests run. The result is `true`
when the tests are satisfied, and `false` otherwise; it is never `null`.

The tests are alternatives, so the input is satisfied by any one of them. `-` is
satisfied by every input, `null` included, and `not(...)` by an input that none of its
tests matches. A test that gives `null` is not satisfied, `not(...)` included: a `null`
input is not `< 5`, nor is it `not(< 5)`. A test is read as follows:

  * `< 5`, `>= x`, `= 3`: the input compared with the value.
  * `[1..5]`, `(1..5]`: the input within the interval.
  * `1`, `"a"`, `null`: the input equal to the value.
  * An expression of `?`, such as `? > 5`: satisfied when it is `true`. One that is not a boolean, such as `? + 1`,
    is equal to the input when it has a value, and is `null`, so not satisfied, when it has none (D-209): a
    `null` input is not `? + 1`, whatever type the input is declared to be.

The other options and the bindings are those of `run/3`.

    iex> {:ok, tests} = Feel.prepare("< 5", entry: :unary_tests)
    iex> Feel.test(tests, Decimal.new(2))
    {:ok, true, []}
    iex> Feel.test(tests, Decimal.new(7))
    {:ok, false, []}
    iex> Feel.test(tests, nil)
    {:ok, false, []}
    iex> {:ok, tests} = Feel.prepare("not(1, [5..9])", entry: :unary_tests)
    iex> Feel.test(tests, Decimal.new(3))
    {:ok, true, []}

# `type`

```elixir
@spec type(Feel.Expression.t()) :: Feel.Type.t()
```

Returns the inferred result type of a prepared expression, or `:any` when it
cannot be determined without running it.

It is a type of the specification, where every type includes `null`, so
`Feel.type/1` of `1 < 2` is `:boolean` whether or not the result can be `null`. The
type with the null explicit is the type of the node of `expression.ir`, a
`Feel.Type.Union` (D-39).

    iex> {:ok, expression} = Feel.prepare("1 + 2")
    iex> Feel.type(expression)
    :number
    iex> Feel.Type.Union.format(Feel.IR.type(expression.ir))
    "number = 3"

# `warnings`

```elixir
@spec warnings(Feel.Expression.t()) :: [Feel.Diagnostic.t()]
```

Returns the diagnostics that `prepare/2` produced without failing.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
