The names visible at one point of an expression, innermost first (DMN 1.6, section 10.3.2.11).
Evaluating an expression looks a name up in a stack of contexts. The first context that has the name wins, and nothing is merged. From the innermost out, the stack holds:
- The special contexts, innermost construct first:
itemand the keys of the element in a filter predicate; the iterator variables andpartialin afor; the variables ofsomeandevery; the parameters of a function being defined;?in a unary test. - The local context, when the expression is the value of an entry of a context being built: the other entries of that context.
- The enclosing contexts of that one, inner to outer.
- The global context: the names the expression is given, which come from
Feel.Scope. - The built-in context, last. It is not stored here, since the built-in
functions are a fixed table; a name that
resolve/2does not find may be a built-in, and the analyzer checks that after the stack.
Because lookup goes in this order, any construct may reuse a name that exists
further out, including a built-in's, and shadows it inside its extent. {sum: 1, ...} makes sum a number there, and a call sum(...) in it then finds a value
that is not a function.
Names in a stack are canonical (see Feel.Name).
Summary
Types
A binding: its kind and type, and for a local the id that its reads carry in their metadata (:local nodes tell which binding
they are of) and, if it is bound to a condition, the origin of that condition (Feel.Analysis.Narrowing.origin/0).
What a name is bound to.
Functions
Returns the number of contexts in the stack.
Starts a stack with the names of a scope as its global context.
Removes the innermost context. Raises when the stack is empty.
Pushes a context on the inside of the stack.
Looks a name up from the innermost context outwards.
Types
@type binding() :: %{ :kind => binding_kind(), :type => Feel.Type.t(), optional(:id) => pos_integer(), optional(:origin) => Feel.Analysis.Narrowing.origin() }
A binding: its kind and type, and for a local the id that its reads carry in their metadata (:local nodes tell which binding
they are of) and, if it is bound to a condition, the origin of that condition (Feel.Analysis.Narrowing.origin/0).
@type binding_kind() :: :var | :local | :item | :item_field
What a name is bound to.
:var- a name from the bindings.:local- a variable bound by a construct, such as an iterator variable, a parameter, or a context entry. The analyzer keeps the locals in a stack of their own, and their type is the inferred type of what they are bound to, aFeel.Type.Union.:item- the element in a filter predicate.:item_field- a key of that element, known to exist.
@type frame() :: %{kind: frame_kind(), names: %{optional(Feel.Name.t()) => binding()}}
@type frame_kind() :: :special | :local | :enclosing | :global
@type t() :: %Feel.Analysis.ScopeStack{depth: non_neg_integer(), frames: [frame()]}
Functions
@spec depth(t()) :: non_neg_integer()
Returns the number of contexts in the stack.
@spec new(Feel.Scope.t()) :: t()
Starts a stack with the names of a scope as its global context.
iex> stack = Feel.Analysis.ScopeStack.new(Feel.Scope.put_name(%Feel.Scope{}, "age", :number))
iex> Feel.Analysis.ScopeStack.resolve(stack, "age")
{:ok, %{kind: :var, type: :number}, :global}
Removes the innermost context. Raises when the stack is empty.
@spec push(t(), frame_kind(), %{optional(Feel.Name.t()) => binding()}) :: t()
Pushes a context on the inside of the stack.
iex> stack = Feel.Analysis.ScopeStack.new(%Feel.Scope{})
iex> stack = Feel.Analysis.ScopeStack.push(stack, :special, %{"item" => %{kind: :item, type: :number}})
iex> Feel.Analysis.ScopeStack.resolve(stack, "item")
{:ok, %{kind: :item, type: :number}, :special}
@spec resolve(t(), Feel.Name.t()) :: {:ok, binding(), frame_kind()} | :error
Looks a name up from the innermost context outwards.
Returns the binding and the kind of context it was found in, or :error when no
context has the name. The built-in functions are not searched; see the module
documentation.
iex> stack = Feel.Analysis.ScopeStack.new(Feel.Scope.put_name(%Feel.Scope{}, "x", :number))
iex> stack = Feel.Analysis.ScopeStack.push(stack, :special, %{"x" => %{kind: :local, type: :string}})
iex> Feel.Analysis.ScopeStack.resolve(stack, "x")
{:ok, %{kind: :local, type: :string}, :special}
iex> Feel.Analysis.ScopeStack.resolve(stack, "y")
:error