Rete.DSL.Vars (Rete v0.1.0)

Copy Markdown View Source

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

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}.

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 — 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]