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

The resolved form of an expression: what `Feel.prepare/2` builds from the syntax
tree, and what the backends run.

The syntax tree (`Feel.AST`) describes the source as written. The analyzer turns it
into this: names are resolved to variables, locals, or built-in functions; dates
and times in `@"..."` literals are decoded; calls with named arguments to a known
function are reordered into positional ones; the entries of a context are put in
the order they can be evaluated; and every node carries its inferred type.

Every node is a tuple `{kind, meta, ...}`, as in the tree. `meta` is a map with
the node's `:span` in the source (so a diagnostic raised at run time can point at
the text) and its `:type`, a `t:Feel.Type.Union.t/0` that is `[:any, :null]` when it cannot be
known without running. The type of a node that cannot run (a branch of an `if` that is never taken) is
`Feel.Type.Union.never/0`, the union of no value (`unreachable/1`, D-173).

## Nodes

| Node                                           | Meaning                                         |
| ---------------------------------------------- | ----------------------------------------------- |
| `{:const, meta, value}`                        | a constant, including decoded dates and times   |
| `{:var, meta, name}`                           | a name bound in the bindings                    |
| `{:local, meta, name}`                         | a name bound by an enclosing `for`, `some`, `every`, function, or context entry; `meta.structure` is what the analyzer knows of the list or the context it is: the shapes of what is in it (D-193), also when it is bound to a path or a descendant of a node that has one (D-193); `meta.id` is the depth of the context that binds it, which a fact about it (`Feel.Analysis.Narrowing`) is kept with (D-193); `meta.callees` is the functions its value says it can be, for a call to find (`Feel.Analysis.Callees`, D-193): of a key of a context, a parameter that a call gives an `if`, an item or a key of functions, and the iterator of a loop over a list of them (D-193); a shape of `meta.structure` has `callees` for the item or the entry in the same way |
| `{:item, meta}`                                | the element in the brackets of a filter (`item`); `meta.depth` is the depth of the filter (D-71) |
| `{:item_field, meta, name}`                    | an entry of that element, known to be there (every element has it); `meta.depth` as above |
| `{:dynamic_name, meta, name, fallback}`        | a name that some of the elements of the filters around may have: the entry of the first element, of the filters in `meta.frames` (depths, innermost first), that has it, and else `fallback`, the node of what the name is without them |
| `{:builtin, meta, name}`                       | a built-in function: the callee of a call of one, and its value where its name stands alone (D-82), which is a `Feel.Value.Function` of the kind `:builtin`, made with the extensions of the run; `meta.type` is the function type of each of its signatures. `meta.from_binding` is `true` when a call through a local binding is resolved to this builtin; its written name may resolve differently without that binding |
| `{:negate, meta, expr}`                        | `-expr`                                         |
| `{:arithmetic, meta, op, left, right}`         | `+ - * / **`                                    |
| `{:compare, meta, op, left, right}`            | `= != < <= > >=`. `=` and `!=` of two operands that are the same read, and cannot report, are not this node but the constant (D-170) |
| `{:logic, meta, :and \| :or, left, right}`     | three-valued `and`, `or`                        |
| `{:between, meta, value, low, high}`           | `value between low and high`: `value >= low and value <= high`, with the value evaluated once (D-69) |
| `{:in, meta, subject, tests}`                  | `subject in test`, `subject in (a, b)`: the `tests` are expressions of the input `?`, which is the value of `subject`, and the node is the three-valued OR of their values (D-61) |
| `{:prefix_test, meta, op, endpoint}`           | `< 5`, `>= x`, `= 3`, `!= 3` as a value: the `Feel.Value.Range` it says, `null` with an error for an endpoint of a kind with no order (D-61). In a test it is the comparison of `?` |
| `{:range, meta, start_included, from, to, end_included}` | `[1..5)` as a value: a `Feel.Value.Range`, or `null` with an error when the endpoints are of kinds that cannot make one (D-61) |
| `{:instance_of, meta, expr, type}`             | `expr instance of type`                         |
| `{:if, meta, condition, then, otherwise}`      | `if … then … else …`. `meta.boxed` (also of `:filter` and `:quantified`) is the strict reading of a boxed expression: `null` with an error for a condition that is a value other than a boolean and `null` (D-212, `Feel.Runtime.Boxed`) |
| `{:for, meta, iterators, body}`                | `for x in xs return body`; `meta.partial` is how the body reads `partial`: `false` (not at all), `true` (as a value, the list of the results so far) or `:view` (only as `partial[index]` and `count(partial)`, which are the two nodes below, D-86) |
| `{:partial_at, meta, index}`                   | `partial[index]` in the body of a `for` whose `meta.partial` is `:view`: the result at the index, as a filter with that index takes the element of the list of the results (the first for `1`, the last for `-1`, `null` past the ends and for `0`, `null` with an error for an index that is not a whole number). `meta` is that of the filter |
| `{:partial_count, meta}`                       | `count(partial)` in such a body: the number of results so far. `meta` is that of the call |
| `{:quantified, meta, :some \| :every, bindings, test}` | `some x in xs satisfies test`          |
| `{:filter, meta, mode, source, brackets}`      | `source[brackets]`; `meta.depth` is 1 for a filter that is in no other, and one more for each that it is in |
| `{:path, meta, source, key}`                   | `source.name`, `key` the canonical name (D-65)  |
| `{:descendant, meta, source, key}`             | `source...name`, `key` as for a path (D-65)     |
| `{:call, meta, callee, arguments}`             | `callee(…)`. A built-in function is `{:builtin, meta, name}`, with the arguments in the order of its parameters as a list. Anything else is a value: the callee is any node, and the arguments are `{:positional, [node]}` or `{:named, [{name, node}]}`, matched to the parameters when the call runs (D-82). `meta.error` carries a fixed structural error, such as invalid arity, that the call reports instead of calling. Static argument-type mismatches retain runtime value validation; the inferred types appear in preparation warnings, not runtime diagnostics. `meta.extension` is `true` on a call of a built-in function that a signature of an extension may answer (D-81): it is folded with the extensions of the expression (D-45), and not as a call of DMN 1.6. `meta.signature` is the signature that the names of a call by name chose (D-56): the function is given the values and not the signature, so the run checks the values against it first (`Feel.Builtin.call/4`); a call by position and a call that an extension may answer have none |
| `{:function, meta, parameters, body}`          | `function(a, b) body`: `parameters` are `{name, type}`, the type being `:any` if none was declared; the parameters are `{:local, meta, name}` in the body. The node is a closure of the bindings it is evaluated in, so a function value (`Feel.Value.Function`, kind `:feel`) is made when the node is evaluated (D-82); `meta.closure` numbers it in the analysis, which a call of a callee that has the node finds the place it was written in by (D-193) |
| `{:list, meta, items}`                         | `[a, b]`                                        |
| `{:context, meta, entries}`                    | `{a: 1, "b": 2}`, entries in evaluation order; `meta.order` has the keys in the order they are written in (D-135), when that is another |
| `{:unary_tests, meta, kind, tests}`            | the root of a list of unary tests: each test is an expression of the input `?`, and the node is `true` or `false` |
| `{:decision_table, meta, policy, aggregation, inputs, rules, outputs}` | a decision table, as in `Feel.AST`: `inputs` are `{expression, input_values}`, `rules` are `{id, entries, results}` and `outputs` are `{name, order, default}`, each part an IR node or `nil`. The entries are `:unary_tests` nodes whose `?` is the value of the input of their column; the parts run only when the policy asks for them (`Feel.Runtime.DecisionTable`) |
| `{:match, meta, uses_input?, input, expr}`     | a test whose meaning depends on the value of `expr`: a list, a range, or a value |
| `{:member, meta, uses_input?, input, expr}`    | the same for a test of `in` (D-61): a list is `list contains`, an element of another kind is not equal to the input and not an error |
| `{:conform_bindings, meta, mode, conforms, body}`    | `body` run with the bound values of the names in `conforms` (`[{name, span, type}]`, D-138) converted to the declared `type` and verified to conform to it: a value that can be made to is converted, `null` stays `null`, and one that cannot is `null` in `body`, with an error at `span`, the first read of the name With the mode `{:report, severity}` (D-138), nothing is converted and a value that does not conform as it is stays, with a `:type_mismatch` of that severity. |
| `{:verify_bindings, meta, mode, verifications, body}`        | `body` run with the bound values of the names in `verifications` (`[{name, span, tests}]`, D-138; `{name, span, tests, marks}` for a name that is declared `!` (`:non_null`, D-48) or as a list whose elements the tests are for (`:each`, D-51), or for the member of the value at the end of a path (`{:path, steps}`, D-161: a key of a context, `:each` for every element of a list; there are several entries for a name that has constraints for several members)) verified first: a value that is not `null` and that the unary `tests` do not accept is `null` in `body`, with an error at `span`, the first read of the name With the mode `{:report, severity}` (D-138), the value stays and only a `:value_not_allowed` of that severity is given. For a name declared `!` the value is not `null` but the run ends with an error of kind `:input`. With `:each`, the tests are run on each element that is not `null`, and the first that they do not accept makes the value not allowed; with a path, on what is there, and a member that is not there or is `null` is exempt. |
| `{:allowed, meta, subject, expr, tests}`       | the result of an expression with allowed values (D-138), `subject` being `:result` (or a name): `expr` unless it is not `null` and the unary `tests` do not accept it, then `null` with an error. With `meta.each` the result is a list and the tests are for its elements (D-51): the first that they do not accept makes the result `null`, with the error that says which element. With `meta.path` (a list of keys of contexts and `:each`) the tests are for the member of the result at the end of the path, and with `each` too for the elements of that member; what is `null` or not there is exempt (a component constraint of a declared value) |
| `{:conform, meta, expr, type}`                 | the result of `expr` converted to the declared `type` (D-138): as it is, or converted so that it conforms, or `null` with an error |
| `{:quiet, meta, expr}`                         | `expr` read without reporting (D-100): what it reports when it is run, an error, a warning or an info, is dropped, and does not end a `mode: :strict` run. It is the argument of a built-in function whose parameter is quiet (`Feel.Builtin.Function.parameter/3`), and has the type of `expr` |
| `{:type, meta, type}`                          | the root of a type expression                   |

A literal function's `meta.type` keeps its observable runtime signature bound separate from its more
precise body result: the former is an arrow, the latter a call refinement in an overload where
needed (`Feel.Type.Arrows`). Signature tests read only the arrows. Literal closures retain this
distinction in lists and contexts; known-callee and shape metadata support additional reanalysis.
Its `meta.returns` is the public result type of that signature, captured by kind-only analysis
in its written environment and used by both backends. Refinement, call specialization and
optimization preserve it even when they narrow the body's inferred type.
An enforced recursive result stays exact in runtime metadata (`result_spec/1`); its observable
analysis result is widened to `Any | Null`, with structural body bounds used only for calls.

A filter has a `mode`, decided at analysis from the type of the brackets (`Feel.Analysis.Filters.mode/2`):
`:predicate` when no number can come out of them (keep the elements for which they are true),
`:index` when only a number can and they do not read the element (take that element; they are run once,
outside the filter), and `:dynamic` when it cannot be known and the evaluator must decide from the value
(`Feel.Runtime.Filter.dynamic/5`). The brackets are run for each element with the element bound under the
depth of the filter, so that a filter inside the brackets of another can read both.

> #### Not every node is produced yet {: .warning}
>
> The set is bigger than what the analyzer makes of the syntax tree, and the nodes it makes are the ones
> both backends run. `:type` is not produced by any entry point (a type is read on its own by
> `Feel.Type.parse/2`). `:builtin` is only ever the callee of a `:call`.
>
> A new kind of node needs a clause in `Feel.Backend.Interpreter.Walk` (its `value_of/3`) and one in `Feel.Backend.Beam.Emitter`.

# `arguments`

```elixir
@type arguments() :: {:positional, [t()]} | {:named, [{Feel.Name.t(), t()}]}
```

The arguments of a call. A call to a built-in function has them as a list, in the
order of its parameters (`Feel.Analysis.Calls`); a call to a function only known at
run time keeps them as they were written, by position or with the names (D-82).

# `arithmetic_operator`

```elixir
@type arithmetic_operator() :: :add | :subtract | :multiply | :divide | :power
```

# `binding_mode`

```elixir
@type binding_mode() :: :enforce | {:report, :error | :warning}
```

What a node that verifies the bound values does with one that does not hold: `:enforce` makes it
`null` (converted first, for a type), and `{:report, severity}` leaves it and reports it (D-138).
The analyzer decides it, once, from the `:declarations` option (D-138).

# `comparison_operator`

```elixir
@type comparison_operator() :: :eq | :neq | :lt | :lte | :gt | :gte
```

# `domain`

```elixir
@type domain() :: t() | {:through, t(), t()}
```

What a `for` iterator ranges over: a list, or `from..to`.

# `filter_mode`

```elixir
@type filter_mode() :: :predicate | :index | :dynamic
```

# `iterator`

```elixir
@type iterator() :: {Feel.Name.t(), domain()}
```

# `key`

```elixir
@type key() :: Feel.Name.t() | {:dynamic, Feel.AST.tokens()}
```

What a path or descendant selects: a canonical name, or tokens resolved at run time. The analyzer
makes a path's key a canonical name always (D-65); the other form is for a name that only the keys of the
value can end (D-27).

# `meta`

```elixir
@type meta() :: %{
  :span =&gt; span(),
  :type =&gt; Feel.Type.Union.t() | Feel.Type.Union.never(),
  optional(atom()) =&gt; term()
}
```

# `piece`

```elixir
@type piece() :: domain() | [piece()]
```

A piece of the IR that names are looked for in (`local_names/1`): a node, the domain of an iterator, or a list of them.

# `span`

```elixir
@type span() :: Feel.Diagnostic.span()
```

A byte range in the source: `from` inclusive, `to` exclusive.

# `t`

```elixir
@type t() ::
  {:const, meta(), Feel.Value.t()}
  | {:var, meta(), Feel.Name.t()}
  | {:local, meta(), Feel.Name.t()}
  | {:item, meta()}
  | {:item_field, meta(), Feel.Name.t()}
  | {:dynamic_name, meta(), Feel.Name.t(), t()}
  | {:builtin, meta(), Feel.Name.t()}
  | {:negate, meta(), t()}
  | {:arithmetic, meta(), arithmetic_operator(), t(), t()}
  | {:compare, meta(), comparison_operator(), t(), t()}
  | {:logic, meta(), :and | :or, t(), t()}
  | {:between, meta(), t(), t(), t()}
  | {:in, meta(), t(), [t()]}
  | {:prefix_test, meta(), comparison_operator(), t()}
  | {:range, meta(), boolean(), t(), t(), boolean()}
  | {:instance_of, meta(), t(), Feel.Type.t()}
  | {:if, meta(), t(), t(), t()}
  | {:for, meta(), [iterator(), ...], t()}
  | {:partial_at, meta(), t()}
  | {:partial_count, meta()}
  | {:quantified, meta(), :some | :every, [{Feel.Name.t(), t()}, ...], t()}
  | {:filter, meta(), filter_mode(), t(), t()}
  | {:path, meta(), t(), key()}
  | {:descendant, meta(), t(), key()}
  | {:call, meta(), t(), arguments() | [t()]}
  | {:function, meta(), [{Feel.Name.t(), Feel.Type.t()}], t()}
  | {:list, meta(), [t()]}
  | {:context, meta(), [{String.t(), t()}]}
  | {:unary_tests, meta(), :any | :positive | :negated, [t()]}
  | {:decision_table, meta(), Feel.AST.decision_policy(),
     Feel.AST.decision_aggregation(), [{t(), t() | nil}],
     [{String.t(), [t() | nil], [t()]}],
     [{String.t() | nil, t() | nil, t() | nil}]}
  | {:match, meta(), boolean(), t(), t()}
  | {:member, meta(), boolean(), t(), t()}
  | {:conform_bindings, meta(), binding_mode(),
     [{Feel.Name.t(), span(), Feel.Type.t()}], t()}
  | {:verify_bindings, meta(), binding_mode(),
     [
       {Feel.Name.t(), span(), t()}
       | {Feel.Name.t(), span(), t(),
          [:non_null | :each | {:path, [String.t() | :each]}]}
     ], t()}
  | {:allowed, meta(), Feel.Name.t() | :result, t(), t()}
  | {:conform, meta(), t(), Feel.Type.t()}
  | {:quiet, meta(), t()}
  | {:type, meta(), Feel.Type.t()}
```

# `callable_type`

```elixir
@spec callable_type(t()) :: Feel.Type.Union.t()
```

Returns the type of a node as a reader that compares and calls functions sees it: `plain_type/1` with the overloads kept
(`Feel.Type.Union.positive_keeping_overloads/1`, D-171). A built-in function with a signature for each kind it takes is all of its
function types, and what is passed to a parameter of a function type, or called, has to be asked as that.

    iex> {:ok, abs} = Feel.Builtin.value("abs")
    iex> node = {:const, %{span: {0, 1}, type: Feel.Type.Union.of_value(abs)}, abs}
    iex> length(Feel.IR.callable_type(node))
    1
    iex> length(Feel.IR.plain_type(node))
    1
    iex> match?([{:overload, _, _}], Feel.IR.callable_type(node))
    true

# `domain_span`

```elixir
@spec domain_span(t() | {:through, t(), t()}) :: span()
```

Returns the byte range of the domain of an iterator (D-86): the node, or the two ends of `from..to`.

    iex> from = {:const, %{span: {9, 10}, type: [:null]}, nil}
    iex> to = {:const, %{span: {12, 14}, type: [:null]}, nil}
    iex> Feel.IR.domain_span({:through, from, to})
    {9, 14}

# `kind`

```elixir
@spec kind(t()) :: atom()
```

Returns the kind of a node, the first element of its tuple.

    iex> Feel.IR.kind({:item, %{span: {0, 4}, type: :any}})
    :item

# `local_names`

```elixir
@spec local_names(piece()) :: MapSet.t(Feel.Name.t())
```

Returns the names that the `:local` nodes in a piece of the IR read (D-86): the iterators, parameters and keys of
contexts that it names, wherever they are bound. A name that a construct inside the piece binds again is in it too, so
these are the names the piece may read from outside, and perhaps more.

The nodes are looked for in tuples and lists. Metadata (a map) is not looked in, and nor is a constant, whose value is
data and not code.

    iex> local = fn name -> {:local, %{span: {0, 1}, type: [:number]}, name} end
    iex> sum = {:arithmetic, %{span: {0, 5}, type: [:number]}, :add, local.("a"), local.("b")}
    iex> Feel.IR.local_names({:list, %{span: {0, 9}, type: [:list]}, [sum, local.("a")]})
    MapSet.new(["a", "b"])

# `plain_type`

```elixir
@spec plain_type(t()) :: Feel.Type.Union.t() | Feel.Type.Union.never()
```

Returns the type of a node as a reader that looks into it sees it: the exact type, with the kinds that are a kind less some of
it (`Feel.Type.Except`, D-152) as the whole kind (`Feel.Type.Union.positive/1`). It holds every value that `type/1` holds. What
only passes a type on (the branches of an `if`, the entries of a context) reads `type/1`, and what asks what the type is (is it
a list, which entries has it) reads this one.

    iex> except = {:except, {:list, [:any, :null]}, [{:list, [:number, :null]}]}
    iex> node = {:const, %{span: {0, 1}, type: [except, :null]}, nil}
    iex> Feel.IR.plain_type(node)
    [{:list, [:any, :null]}, :null]

# `result_spec`

```elixir
@spec result_spec(t()) :: Feel.Type.t()
```

The result type exposed by a closure's runtime signature. An enforced recursive result
declaration retains its exact graph; an analysis approximation cannot replace that evidence.
Checks around the result preserve it, while another conversion establishes a new boundary.
The analyzer stores this selection in the function node's `meta.returns` before optimization;
backends use the stored type rather than selecting again from a rewritten body.

# `span`

```elixir
@spec span(t()) :: span()
```

Returns the byte range of a node's source text.

    iex> Feel.IR.span({:item, %{span: {0, 4}, type: :any}})
    {0, 4}

# `type`

```elixir
@spec type(t()) :: Feel.Type.Union.t() | Feel.Type.Union.never()
```

Returns the inferred type of a node, a `Feel.Type.Union`.

    iex> Feel.IR.type({:const, %{span: {0, 1}, type: [:number]}, Decimal.new(1)})
    [:number]

# `unreachable`

```elixir
@spec unreachable(t()) :: t()
```

Returns a piece of the IR as it is when it cannot run (D-173): the type of every node in it is
`Feel.Type.Union.never/0`, the union of no value. The analyzer does it to a branch of an `if` that the type of the
condition rules out, or in which a read finds that the facts of the conditions around it leave its name no value, so
that nothing in the branch keeps a type that is true of no run (a name that the facts empty would be the type it had
before the conditions).

A constant keeps its value, and metadata other than the type stays as it is. A node that has `never` for its type is
taken to have nothing in it that has not (the analyzer makes them only here), and is not looked into. A node of this
type that a run reaches is a wrong type, which `Feel.Paranoid` reports.

    iex> const = fn value -> {:const, %{span: {0, 1}, type: Feel.Type.Union.of_value(value)}, value} end
    iex> sum = {:arithmetic, %{span: {0, 5}, type: [:number]}, :add, const.(Decimal.new(1)), const.(Decimal.new(2))}
    iex> {:arithmetic, meta, :add, {:const, left, _}, _right} = Feel.IR.unreachable(sum)
    iex> {meta.type, left.type}
    {[], []}

---

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