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

Filters and indexes: `source[predicate]` (DMN 1.6, section 10.3.2.5 and Table 69).

The functions are the part of a filter that both backends share. What differs is how the part in
brackets is run for an element, so the backends pass it as a function; the rest is here.

## The source

`null` filters to `null`, a list is a list, and any other value is a list of one (the implicit
conversion of a filter context, section 10.3.2.9.4: `3[item > 2]` is `[3]`, Table 45). So a value
that is not a list is its own only element, and `100[1]` is `100`. The conversion is reported, once for the
filter, as an `:info` `:implicit_conversion` at its span (D-46).

## A predicate

`predicate/4` runs the part in brackets for each element, with the element as `item` and, if it is a
context, its entries as names (the backend binds both), and keeps the elements for which it is
exactly `true`: `false`, `null`, and anything that is not a boolean drop the element, and report only
what the part itself reports (`[{x: 1}, {x: null}][x < 2]` is `[{x: 1}]`, 0069 #029). The result is a
list, `[]` if nothing is kept. The order is kept.

## A strict predicate

A filter of a boxed expression (`strict`, `Feel.Runtime.Boxed`, D-212) is `null` with an error, for the whole filter, when the
brackets are a value that is neither a boolean nor `null` for any element: the other elements do not change it. A number is an
index, decided before this (`dynamic/6`), and `null` drops the element, as without it.

## An index

`index/2` takes the element: the first for `1`, the last for `-1`, and `null` for `0` and for a
number that is past the ends, with nothing reported (0069 #002, #003, #008). A number with a scale
but an integral value (`1.0`) is an index; one that is not integral, and a value that is neither a
number nor `null`, are `null` with an error, since the specification does not give them a meaning
(notes 05, E40). A source that is not a list has one element, so `x[1]` is `x`, and any other number
is `null` (0069 #018 to #023).

## When it is not known which

Whether the brackets hold an index or a predicate is decided by the analysis when the types say
(`Feel.Analysis.Filters`). When they do not, `dynamic/5` decides by the value (notes 05, E40, D13):
the part is run once for the first element (`null` for an empty list), and silently; if that is a
number, the brackets hold an index, which is then run outside the filter, where `item` and the
entries of an element are not names; anything else, and it is a predicate.

## Names

The entries of an element are found by `member/2` among the elements of the filters around, the
innermost first. `field/2` is the entry of a context that is known to have it.

# `for_item`

```elixir
@type for_item() :: (Feel.Value.t(), Feel.Runtime.Env.t() -&gt;
                 {Feel.Value.t(), Feel.Runtime.Env.t()})
```

Runs the part in brackets for one element, in the environment.

# `outside`

```elixir
@type outside() :: (Feel.Runtime.Env.t() -&gt; {Feel.Value.t(), Feel.Runtime.Env.t()})
```

Runs the part in brackets once, outside the filter.

# `at`

```elixir
@spec at(Feel.Runtime.Env.t(), Feel.Value.t(), Feel.Value.t(), Feel.Diagnostic.span()) ::
  {Feel.Value.t(), Feel.Runtime.Env.t()}
```

`source[index]` as the backends run it: `index/2` settled at the span, with the conversion of a source that is not a
list reported (D-46).

    iex> env = Feel.Runtime.Env.new()
    iex> {value, env} = Feel.Runtime.Filter.at(env, "a", Decimal.new(1), {0, 4})
    iex> {value, Enum.map(Feel.Runtime.Env.diagnostics(env), &{&1.severity, &1.code, &1.span})}
    {"a", [{:info, :implicit_conversion, {0, 4}}]}
    iex> {value, env} = Feel.Runtime.Filter.at(Feel.Runtime.Env.new(), ["a"], Decimal.new(1), {0, 4})
    iex> {value, Feel.Runtime.Env.diagnostics(env)}
    {"a", []}

# `bind_item`

```elixir
@spec bind_item(Feel.bindings(), pos_integer(), Feel.Value.t()) :: Feel.bindings()
```

Binds the element of the filter of depth `depth` in a copy of the bindings. A filter inside a
filter has the depth one more, so both elements are there.

    iex> bindings = Feel.Runtime.Filter.bind_item(%{}, 1, "a")
    iex> Feel.Runtime.Filter.item(bindings, 1)
    "a"
    iex> Feel.Runtime.Filter.item(bindings, 2)
    nil

# `dynamic`

```elixir
@spec dynamic(
  Feel.Runtime.Env.t(),
  Feel.Value.t(),
  for_item(),
  outside(),
  Feel.Diagnostic.span(),
  boolean()
) :: {Feel.Value.t(), Feel.Runtime.Env.t()}
```

`source[brackets]` where it is not known whether the brackets hold an index or a predicate: the
part is run once for the first element (`null` for none), without anything it reports, and if it
gives a number it is an index, run outside the filter with `outside`; otherwise it is a predicate.

    iex> env = Feel.Runtime.Env.new()
    iex> list = [Decimal.new(10), Decimal.new(20)]
    iex> as_index = fn _item, env -> {Decimal.new(2), env} end
    iex> outside = fn env -> {Decimal.new(2), env} end
    iex> {value, _env} = Feel.Runtime.Filter.dynamic(env, list, as_index, outside, {0, 1})
    iex> value
    Decimal.new(20)
    iex> as_predicate = fn item, env -> {Decimal.compare(item, 10) == :gt, env} end
    iex> {kept, _env} = Feel.Runtime.Filter.dynamic(env, list, as_predicate, outside, {0, 1})
    iex> kept
    [Decimal.new(20)]

# `field`

```elixir
@spec field(Feel.Value.t(), Feel.Name.t()) :: Feel.Value.t()
```

The entry of an element that is known to have it; `null` for an element that is not a context
(there is none outside the filter).

    iex> Feel.Runtime.Filter.field(%{"a" => 1}, "a")
    1
    iex> Feel.Runtime.Filter.field(nil, "a")
    nil

# `index`

```elixir
@spec index(Feel.Value.t(), Feel.Value.t()) :: Feel.Runtime.Env.outcome()
```

`source[index]`: the outcome of taking the element.

    iex> list = [Decimal.new(10), Decimal.new(20), Decimal.new(30)]
    iex> Feel.Runtime.Filter.index(list, Decimal.new(1))
    {:ok, Decimal.new(10)}
    iex> Feel.Runtime.Filter.index(list, Decimal.new(-1))
    {:ok, Decimal.new(30)}
    iex> Feel.Runtime.Filter.index(list, Decimal.new(0))
    {:ok, nil}
    iex> Feel.Runtime.Filter.index(list, Decimal.new(4))
    {:ok, nil}
    iex> Feel.Runtime.Filter.index("a", Decimal.new(1))
    {:ok, "a"}
    iex> Feel.Runtime.Filter.index("a", Decimal.new(2))
    {:ok, nil}
    iex> Feel.Runtime.Filter.index(nil, Decimal.new(1))
    :missing
    iex> {:error, %Feel.Diagnostic{code: :type_mismatch}} = Feel.Runtime.Filter.index(list, "a")

# `item`

```elixir
@spec item(Feel.bindings(), pos_integer()) :: Feel.Value.t()
```

The element of the filter of that depth, or `null` where there is none (outside the filter).

# `items`

```elixir
@spec items(Feel.Value.t()) :: {:ok, [Feel.Value.t()]} | :null
```

The elements of the source of a filter: a list is itself, `null` is none, and any other value is its
own only element.

    iex> Feel.Runtime.Filter.items([1, 2])
    {:ok, [1, 2]}
    iex> Feel.Runtime.Filter.items("a")
    {:ok, ["a"]}
    iex> Feel.Runtime.Filter.items(nil)
    :null

# `member`

```elixir
@spec member([Feel.Value.t()], Feel.Name.t()) :: {:ok, Feel.Value.t()} | :error
```

The entry that a context has under a name, where the contexts are the elements of the filters around
(innermost first) and a value that is not a context has none.

    iex> Feel.Runtime.Filter.member([%{"a" => 1}, %{"b" => 2}], "b")
    {:ok, 2}
    iex> Feel.Runtime.Filter.member([%{"a" => 1}, "x", nil], "b")
    :error
    iex> Feel.Runtime.Filter.member([%{"a" => nil}], "a")
    {:ok, nil}

# `position`

```elixir
@spec position(Feel.Value.t()) ::
  {:ok, integer()} | :missing | {:error, Feel.Diagnostic.t()}
```

The position that an index says: a whole number, which is from the end when it is negative and no element when it is 0.
`null` is no position, and so is a number with a fraction and a value that is not a number, which are an error.

The elements of a list are found with it by `index/2`, and the results of a loop by `Feel.Runtime.Partial.at/4`, so
that they take the same index in the same way.

    iex> Feel.Runtime.Filter.position(Decimal.new("-2.0"))
    {:ok, -2}
    iex> Feel.Runtime.Filter.position(nil)
    :missing
    iex> {:error, %Feel.Diagnostic{code: :type_mismatch}} = Feel.Runtime.Filter.position(Decimal.new("1.5"))
    iex> {:error, %Feel.Diagnostic{code: :type_mismatch}} = Feel.Runtime.Filter.position(true)

# `predicate`

```elixir
@spec predicate(
  Feel.Runtime.Env.t(),
  Feel.Value.t(),
  for_item(),
  Feel.Diagnostic.span(),
  boolean()
) ::
  {Feel.Value.t(), Feel.Runtime.Env.t()}
```

`source[predicate]` for a predicate: the elements for which `for_item` gives `true`.

    iex> env = Feel.Runtime.Env.new()
    iex> keep_big = fn item, env -> {Decimal.compare(item, 1) == :gt, env} end
    iex> {kept, env} = Feel.Runtime.Filter.predicate(env, [Decimal.new(1), Decimal.new(2)], keep_big, {0, 1})
    iex> kept
    [Decimal.new(2)]
    iex> {nil, ^env} = Feel.Runtime.Filter.predicate(env, nil, keep_big, {0, 1})

---

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