Feel.Runtime.Filter (feel v0.1.0)

Copy Markdown View Source

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

for_item()

@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.

outside()

@type outside() :: (Feel.Runtime.Env.t() -> {Feel.Value.t(), Feel.Runtime.Env.t()})

Runs the part in brackets once, outside the filter.

Functions

at(env, source, index, span)

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(bindings, depth, item)

@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(env, source, for_item, outside, span, strict \\ false)

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(item, name)

@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(source, index)

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(bindings, depth)

@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(list)

@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(list, name)

@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(number)

@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(env, source, for_item, span, strict \\ false)

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})