Which of some names an expression reads, found in its syntax tree.
The entries of a context literal can refer to each other, in any order, and an entry has to be
evaluated after the ones it refers to (DMN 1.6, section 10.3.2.6; D-58). So the analyzer has to know what
each entry reads before it analyzes any of them, and it cannot learn that by analyzing them, for an
entry's type needs the types of the entries it reads. This module reads it off the tree, in one walk
that is linear in the size of the entry (to/2), or for all the context literals of an expression
in one walk of it (of_contexts/1).
A read is a name node whose canonical form (Feel.Name) is one of the names asked about, unless the
expression binds the name itself where it is read:
- the keys of a context literal inside the expression are bound in all of its entries,
- the variables of a
for(andpartial),someandeveryare bound in the part they scope: in the body, and in the domains after them (D-86), - the parameters of a function are bound in its body, and
itemis bound in a filter's predicate.
A name that is a key of the inner context is not a read of an outer entry of the same key, as
in {x: {x: 1, y: x}.y}. Where the walk cannot be sure it counts a read: a name in a filter's
predicate may be a key of the elements and not a name in scope (A4-08). Missing a read would evaluate an
entry before one it needs, while counting one that is not only makes the order more careful.
iex> {:ok, ast} = Feel.parse("{a: 1, b: a + 1, c: {a: 2, d: a}.d + b}")
iex> {:context, _meta, entries} = ast
iex> names = MapSet.new(["a", "b", "c"])
iex> for {key, value} <- entries, do: {Feel.Analysis.References.key(key), Feel.Analysis.References.to(value, names) |> Enum.sort()}
[{"a", []}, {"b", ["a"]}, {"c", ["b"]}]
Summary
Types
What the entries of one context literal read of its keys: the entries of the literal, and for each entry that reads any the keys, by the position of the entry.
Types
@type literal_reads() :: {[{Feel.AST.key(), Feel.AST.t()}], %{required(non_neg_integer()) => MapSet.t(Feel.Name.t())}}
What the entries of one context literal read of its keys: the entries of the literal, and for each entry that reads any the keys, by the position of the entry.
Functions
@spec key(Feel.AST.key()) :: String.t()
Returns the key of a context entry.
A key that is a name is its canonical form. A key that is a string is the string, in the canonical form
of a name when it is one (D-05): "first name" and first name are the same key. Any other string is the key
as it is written (D-213), and it is decided by Feel.Syntax.Lexer.name_tokens/1, as the parser decides which
names an entry binds.
iex> Feel.Analysis.References.key({:name, ["first", "name"]})
"first name"
iex> Feel.Analysis.References.key({:string, "first name"})
"first name"
iex> Feel.Analysis.References.key({:string, "first name!"})
"first name!"
iex> Feel.Analysis.References.key({:string, ""})
""
@spec of_contexts(Feel.AST.t()) :: %{ required(Feel.Diagnostic.span()) => literal_reads() }
Returns, for every context literal in the expression, by its span, what to/2 gives for each of its entries and the
keys of the literal: the keys that the entry reads.
The analyzer asks it for the literals one inside another, and to/2 for each walks everything below the literal, so
the nodes of the innermost one are walked once for each level around them. This is one walk of the tree: a name read
is for the innermost construct that binds it, and it counts if that is a context literal, for the entry that it is in.
iex> {:ok, ast} = Feel.parse("{a: 1, b: {a: 2, c: a + b}, d: a}")
iex> {:context, %{span: span}, _entries} = ast
iex> {_entries, reads} = Feel.Analysis.References.of_contexts(ast)[span]
iex> reads |> Enum.map(fn {index, names} -> {index, Enum.sort(names)} end) |> Enum.sort()
[{1, ["b"]}, {2, ["a"]}]
@spec to(Feel.AST.t(), MapSet.t(Feel.Name.t())) :: MapSet.t(Feel.Name.t())
Returns the names, of those given, that the expression reads.