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:
:predicatewhen no number can come out of the brackets: a boolean,null, or anything that is not a number keeps no element;:indexwhen only a number (ornull) can, and the brackets do not read the element, so that their value is the same outside the filter, where an index is run;:dynamicotherwise, 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
Summary
Types
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.
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.
Functions
The type of the elements: of item in the brackets.
Whether an element can be a context (or anything at all), and so can have entries that a name in the brackets stands for.
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.
The reading of the brackets, from their type and whether they read the element. The three readings are in the module documentation.
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.
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).
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).
Whether the IR reads the element of the filter of this depth: item, an entry of it, or a name that
may be one.
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).
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.
Types
@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.
@type membership() :: :none | {:all, Feel.Type.Union.t()} | {:some, Feel.Type.Union.t()}
@type mode() :: :predicate | :index | :dynamic
@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.
Functions
@spec elements([part()]) :: Feel.Type.Union.t()
The type of the elements: of item in the brackets.
@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
@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.
@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.
@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>"
@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).
@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
@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
@spec result( [part()], mode(), (Feel.Type.Union.t() -> 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"
@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.