Feel.Analysis.References (feel v0.1.0)

Copy Markdown View Source

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 (and partial), some and every are 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
  • item is 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.

Functions

Returns the key of a context entry.

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.

Returns the names, of those given, that the expression reads.

Types

literal_reads()

@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

key(arg)

@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, ""})
""

of_contexts(ast)

@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"]}]

to(ast, names)

Returns the names, of those given, that the expression reads.