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.
Summary
Types
Runs the part in brackets for one element, in the environment.
Runs the part in brackets once, outside the filter.
Functions
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).
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.
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.
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).
source[index]: the outcome of taking the element.
The element of the filter of that depth, or null where there is none (outside the filter).
The elements of the source of a filter: a list is itself, null is none, and any other value is its
own only element.
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.
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.
source[predicate] for a predicate: the elements for which for_item gives true.
Types
@type for_item() :: (Feel.Value.t(), Feel.Runtime.Env.t() -> {Feel.Value.t(), Feel.Runtime.Env.t()})
Runs the part in brackets for one element, in the environment.
@type outside() :: (Feel.Runtime.Env.t() -> {Feel.Value.t(), Feel.Runtime.Env.t()})
Runs the part in brackets once, outside the filter.
Functions
@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", []}
@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
@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)]
@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
@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")
@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).
@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
@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}
@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)
@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})