Rete.DSL.Vars (Rete v0.2.0)

Copy Markdown View Source

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

Types

A variable name.

A set of variable names that are bound in the current scope.

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

name()

@type name() :: atom()

A variable name.

scope()

@type scope() :: MapSet.t(name())

A set of variable names that are bound in the current scope.

Functions

discarded?(name)

@spec discarded?(name()) :: boolean()

Whether a name is explicitly discarded, _ or _-prefixed.

pattern_var_names(ast)

@spec pattern_var_names(Macro.t()) :: [name()]

pattern_vars/1 as a sorted list of names.

pattern_vars(ast)

@spec pattern_vars(Macro.t()) :: %{required(name()) => Macro.t()}

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 — binary in <<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_var_names(ast)

@spec read_var_names(Macro.t() | nil) :: [name()]

read_vars/1 as a sorted list of names.

read_vars(ast)

@spec read_vars(Macro.t() | nil) :: scope()

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]