Scope aware variable analysis for DSL fragments.
Internal. Two questions with different answers:
pattern_vars/1— what does this pattern bind? The variables a condition contributes to the token.read_vars/1— what does this expression read from its enclosing scope? Decides whether a guard is local to its condition or needs a join filter.
Both are scope aware, which a plain Macro.prewalk/3 is not. A traversal collecting
every {name, meta, nil} node would report the parameter of fn v -> v > 0 end, a
comprehension generator, a case clause head and the binary in <<rest::binary>> as
bound by the rule. A spurious name is not in the condition's own bindings, so the guard
would be judged non-local, lifted into a join filter and destructured from a token that
can never carry it. The rule would then never fire.
Summary
Functions
Whether a name is explicitly discarded, _ or _-prefixed.
pattern_vars/1 as a sorted list of names.
The variables a pattern binds, as %{name => variable_ast}.
read_vars/1 as a sorted list of names.
The variables an expression reads from its enclosing scope, as a MapSet.
Types
Functions
Whether a name is explicitly discarded, _ or _-prefixed.
pattern_vars/1 as a sorted list of names.
The variables a pattern binds, as %{name => variable_ast}.
Not bindings, and therefore excluded:
^pinned— a match against an existing value@attr— a compile time constant_and_-prefixed names — explicitly discarded by the author- the modifier side of
::in a bitstring —binaryin<<rest::binary>>is a type, not a variable - map and struct keys — a pattern key is a literal or a pin, never a binder
Examples
iex> Rete.DSL.Vars.pattern_vars(quote(do: {:order, id, _ignored})) |> Map.keys()
[:id]
iex> Rete.DSL.Vars.pattern_vars(quote(do: <<a::8, rest::binary>>)) |> Map.keys() |> Enum.sort()
[:a, :rest]
read_vars/1 as a sorted list of names.
The variables an expression reads from its enclosing scope, as a MapSet.
Excluded: ^pinned and @attr (compile time constants), the anonymous _,
and anything bound by a construct inside the expression — fn parameters,
for/with generators, case/receive/try clause heads, and = earlier
in the same block.
_-prefixed names are deliberately kept. _t in amt > _t genuinely is a
read of _t; treating it as local would inline it into the alpha function,
where it is not in scope.
Examples
iex> Rete.DSL.Vars.read_vars(quote(do: amt > t)) |> Enum.sort()
[:amt, :t]
iex> Rete.DSL.Vars.read_vars(quote(do: Enum.all?(xs, fn v -> v > 0 end))) |> Enum.sort()
[:xs]