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

The types of `source[brackets]` (DMN 1.6, Table 69; D-71).

The analyzer calls this module for the parts of a filter that are about types: what the elements of
the source are, whether a name in the brackets can be an entry of an element, which of the three
readings of the brackets applies, and what the result is. The run is `Feel.Runtime.Filter`.

## The elements

The source is taken one alternative of its type at a time (`parts/1`):

| The alternative       | Its elements                                               |
| --------------------- | ---------------------------------------------------------- |
| `list<E>`             | `E`                                                        |
| `Null`                | none: the result is `null`                                 |
| `Any`, a named type   | `Any \| Null`, a list or not                               |
| anything else         | itself: a value that is not a list is a list of one        |

`elements/1` is their union, which is the type of `item` in the brackets.

## The names in the brackets

A name in the brackets is looked up in the elements first: `item`, and, if the element is a context,
its entries (section 10.3.2.11.4). `membership/2` says for a name whether every element has it
(`{:all, type}`, an entry that is known to be there), none can (`:none`, so the name is the one of the
scope), or some do (`{:some, type}`, so it is found at run time and falls back to the scope). A context
type of the specification is open, so an entry that it does not list may be there; a context literal and a
constant are closed (D-65), and an element that is no context has none. `item` is the same: it is the
element, unless a context element has an entry called `item`, which wins (0069 #026).

## The reading of the brackets

Whether they hold an index or a predicate is circular in the specification (notes 05, E40): it depends on
the type of the brackets, which are run for each element. `mode/2` decides from the type, which is
known as soon as the names are:

  * `:predicate` when no number can come out of the brackets: a boolean, `null`, or anything that is
    not a number keeps no element;
  * `:index` when only a number (or `null`) can, and the brackets do not read the element, so that
    their value is the same outside the filter, where an index is run;
  * `:dynamic` otherwise, when the value decides (`Feel.Runtime.Filter.dynamic/5`).

## The result

`result/3` is the union of what the alternatives of the source give. A predicate gives a list of the
elements that it keeps, so a list type whose elements are the type of the elements **narrowed by what
the predicate says when it is true** (`narrow/3`, D-194): after `item > 5`, the numbers above 5; after
`x < 2` for the entry `x` of a context element, the contexts whose `x` is below 2, with that `x`. A predicate that
can never be true keeps nothing, and the list is `list<Null>`, as `[]` is (Table 43). An index gives an
element or `null`, which is `null` for a position that is not there; the first and the last element of a list that has
one are there (D-152).

    iex> parts = Feel.Analysis.Filters.parts([{:list, [:number, :null]}, :null])
    iex> Feel.Analysis.Filters.elements(parts)
    [:number, :null]
    iex> Feel.Analysis.Filters.elements(Feel.Analysis.Filters.parts([:string]))
    [:string]
    iex> Feel.Analysis.Filters.membership([{:closed_context, %{"a" => [:number]}}], "a")
    {:all, [:number]}
    iex> Feel.Analysis.Filters.membership([{:context, %{}}], "a")
    {:some, [:any, :null]}
    iex> Feel.Analysis.Filters.membership([:number], "a")
    :none
    iex> Feel.Analysis.Filters.mode([:boolean, :null], false)
    :predicate
    iex> Feel.Analysis.Filters.mode([:number], false)
    :index
    iex> Feel.Analysis.Filters.mode([:any, :null], false)
    :dynamic

# `frame`

```elixir
@type frame() :: %{
  depth: pos_integer(),
  elements: Feel.Type.Union.t(),
  positive: Feel.Type.Union.t(),
  locals: non_neg_integer()
}
```

A filter whose brackets are being analyzed: its depth (the number of filters around it, itself
included), the type of its elements, that type as a reader that looks into it sees it (`positive`: read once, for the
lookup of every name in the brackets), and the number of contexts of the enclosing context literals that were there when
it began, which tells where it comes among them.

# `membership`

```elixir
@type membership() ::
  :none | {:all, Feel.Type.Union.t()} | {:some, Feel.Type.Union.t()}
```

# `mode`

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

# `part`

```elixir
@type part() ::
  {:list, Feel.Type.Union.t()}
  | {:non_empty, Feel.Type.Union.t()}
  | {:single, Feel.Type.Union.t()}
  | :null
  | :unknown
```

One alternative of the type of the source of a filter: a list, a list that has an element, a value that is no list (the
list of itself), `null`, or anything.

# `elements`

```elixir
@spec elements([part()]) :: Feel.Type.Union.t()
```

The type of the elements: of `item` in the brackets.

# `entries?`

```elixir
@spec entries?(Feel.Type.Union.t()) :: boolean()
```

Whether an element can be a context (or anything at all), and so can have entries that a name in the
brackets stands for.

    iex> Feel.Analysis.Filters.entries?([{:closed_context, %{}}, :null])
    true
    iex> Feel.Analysis.Filters.entries?([:any, :null])
    true
    iex> Feel.Analysis.Filters.entries?([:number, {:list, [:number]}])
    false

# `membership`

```elixir
@spec membership(Feel.Type.Union.t(), Feel.Name.t()) :: membership()
```

Whether the elements have an entry of this name: all of them do, with the type its values have; none
can; or some can, with the type the entry has where it is there.

A list of lists and the like have none. An element that is `null` has none.
Discovery approximates each inspected union locally: an excluded context can still supply
the entry. Returned field unions retain their own exclusions.

# `mode`

```elixir
@spec mode(Feel.Type.Union.t(), boolean()) :: mode()
```

The reading of the brackets, from their type and whether they read the element. The three readings
are in the module documentation.

# `narrow`

```elixir
@spec narrow(Feel.Type.Union.t(), map(), pos_integer()) :: Feel.Type.Union.t()
```

The elements that are left when the predicate is true, by its facts (`Feel.Analysis.Narrowing`) about
the element of the filter of this depth: the element itself (`item > 5`) and the entries of a context
element (`x < 2`, `item.x < 2`, `item.a.b = 1`). It is `[]` when none can be.

An alternative of a context type is kept with the type of the entry narrowed, and goes if nothing is left
of the entry: with `[{a: 1}, {a: 2}][a >= 2]` the contexts with `a` 1 go. A selection from what is no context
is `null`, so an element that is `null` or a scalar goes if the fact cannot hold for `null`. What can have
entries that the type does not tell (an open context, `Any`) stays.

    iex> elements = [{:closed_context, %{"a" => [{:number, Feel.Type.NumberSet.point(Decimal.new(1))}]}},
    ...>             {:closed_context, %{"a" => [{:number, Feel.Type.NumberSet.point(Decimal.new(2))}]}}]
    iex> {:ok, ast} = Feel.parse("[{a: 1}, {a: 2}][a >= 2]")
    iex> {:ok, %{ir: ir}} = Feel.Analysis.Analyzer.analyze(ast, [])
    iex> {:filter, _meta, :predicate, _source, predicate} = ir
    iex> facts = Feel.Analysis.Narrowing.facts(predicate, :then)
    iex> Feel.Analysis.Filters.narrow(elements, facts, 1) |> Feel.Type.Union.format()
    "context<a: number = 2>"

# `parts`

```elixir
@spec parts(Feel.Type.Union.t()) :: [part()]
```

The alternatives of the type of the source of a filter, by what their elements are. The type is the exact one, in which a
list that has an element is a list type less the empty list (`Feel.Type.Union.non_empty/1`, D-152); the others that are
less some of a kind are the kind (`Feel.Type.Union.positive/1`).

# `position`

```elixir
@spec position(Feel.Type.Union.t()) :: 1 | 0 | -1 | nil
```

The one index that brackets of this type are, if they are one: the number `1`, `-1` or `0`, and nothing else (a `null` is no
index, and gives `null`).

    iex> Feel.Analysis.Filters.position([{:number, Feel.Type.NumberSet.point(Decimal.new(-1))}])
    -1
    iex> Feel.Analysis.Filters.position([{:number, Feel.Type.NumberSet.point(Decimal.new(0))}])
    0
    iex> Feel.Analysis.Filters.position([{:number, Feel.Type.NumberSet.point(Decimal.new(2))}])
    nil
    iex> Feel.Analysis.Filters.position([{:number, Feel.Type.NumberSet.point(Decimal.new(1))}, :null])
    nil

# `reads?`

```elixir
@spec reads?(Feel.IR.t(), pos_integer()) :: boolean()
```

Whether the IR reads the element of the filter of this depth: `item`, an entry of it, or a name that
may be one.

    iex> Feel.Analysis.Filters.reads?({:item, %{span: {0, 4}, type: [:number], depth: 1}}, 1)
    true
    iex> Feel.Analysis.Filters.reads?({:item, %{span: {0, 4}, type: [:number], depth: 2}}, 1)
    false
    iex> Feel.Analysis.Filters.reads?({:const, %{span: {0, 1}, type: [:null]}, nil}, 1)
    false

# `result`

```elixir
@spec result(
  [part()],
  mode(),
  (Feel.Type.Union.t() -&gt; Feel.Type.Union.t()),
  1 | 0 | -1 | nil
) ::
  Feel.Type.Union.t()
```

The type of `source[brackets]`, from the parts of the source's type, the reading, and `narrow`: a
function from the type of the elements of a part to the type of the elements that the predicate keeps
(it gives `[]` for none).

`position` is the one index that the brackets can be (`position/1`), or `nil`: `1` and `-1` are an element of a list that
has one, and `1` is the value that is not a list, so there the index is not `null`; `0` is no position, so it is `null` for
every source (D-170).

    iex> parts = Feel.Analysis.Filters.parts(Feel.Type.Union.non_empty({:list, [:number]}))
    iex> narrow = fn elements -> elements end
    iex> Feel.Type.Union.format(Feel.Analysis.Filters.result(parts, :index, narrow))
    "number | Null"
    iex> Feel.Type.Union.format(Feel.Analysis.Filters.result(parts, :index, narrow, -1))
    "number"
    iex> Feel.Type.Union.format(Feel.Analysis.Filters.result(parts, :index, narrow, 1))
    "number"
    iex> Feel.Type.Union.format(Feel.Analysis.Filters.result(parts, :index, narrow, 0))
    "Null"

# `search_order`

```elixir
@spec search_order([frame()], Feel.Analysis.ScopeStack.t()) :: [
  filter: frame(),
  local: Feel.Analysis.ScopeStack.frame()
]
```

The order in which the contexts that bind names are looked in, innermost first: the filters
(`frame/0`) and the contexts of the context literals (`Feel.Analysis.ScopeStack` frames) are in one
stack in the specification, and they are kept apart in the analysis, so this puts them together by
where each filter began.

---

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