Scope-aware variable analysis for DSL fragments.
Internal. This module answers two questions, and they have different answers:
pattern_vars/1— what does this pattern bind? These are the variables a condition contributes to the token.read_vars/1— what does this expression read from its enclosing scope? This decides whether a guard is local to its condition, or needs a join filter.
Both functions are scope-aware, unlike a plain Macro.prewalk/3. A traversal that
collects 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 all bound by the rule. A spurious name like that 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}.
These are not bindings, and this excludes them:
^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]