Behaviour for defining expression rewrite rules.
A rewrite rule defines how to transform boolean expressions during traversal. Rules can be composed together or applied individually depending on their exclusivity requirements.
Summary
Callbacks
Returns whether this rule is exclusive.
Returns whether this rule may need reapplication.
Returns the traversal type for this rule.
Transforms an expression without an accumulator.
Transforms an expression with an accumulator.
Functions
Applies a list of rewrite rules to an expression.
Types
@type acc_id(variable, acc) :: t() | Crux.Expression.walker(variable, acc)
@type acc_map(variable, acc) :: %{required(acc_id(variable, acc)) => acc}
@type rule_spec(variable, acc) :: t() | Crux.Expression.walker_stateless(variable) | {Crux.Expression.walker_stateful(variable, acc), acc} | {Crux.Expression.walker_stateful(variable, acc), acc, rule_options()}
@type t() :: module()
Callbacks
@callback exclusive?() :: boolean()
Returns whether this rule is exclusive.
An exclusive rule cannot be composed with other rules and must be applied in isolation. Non-exclusive rules can be composed together for efficiency.
@callback needs_reapplication?() :: boolean()
Returns whether this rule may need reapplication.
Rules that return true may be applied multiple times until a fixpoint
is reached (no further changes occur). Rules that return false are
applied only once.
@callback type() :: :prewalk | :postwalk
Returns the traversal type for this rule.
Must return either :prewalk or :postwalk to indicate when during
tree traversal this rule should be applied.
@callback walk(Crux.Expression.t(variable)) :: Crux.Expression.t(variable) when variable: term()
Transforms an expression without an accumulator.
This is the core transformation logic for the rule.
@callback walk_stateful(Crux.Expression.t(variable), acc) :: {Crux.Expression.t(variable), acc} when variable: term(), acc: term()
Transforms an expression with an accumulator.
This callback is optional - if not implemented, the default implementation
will call walk/1 and return {result, acc}.
Functions
@spec apply(Crux.Expression.t(variable), [rule_spec(variable, acc)]) :: {Crux.Expression.t(variable), acc_map(variable, acc)} when variable: term(), acc: term()
Applies a list of rewrite rules to an expression.
Rules are processed in chunks based on their exclusivity and type while preserving the order they appear in the list. Adjacent rules with the same exclusivity and type are grouped together for efficiency.
Rule Processing
- Rules are chunked by adjacency of exclusivity and type
- Exclusive rules always get their own chunk
- Each chunk is applied sequentially to the result of the previous chunk
- If any rule in a chunk has
needs_reapplication?() == true, the chunk is reapplied until no changes occur (fixpoint)
Examples
iex> import Crux.Expression, only: [b: 1]
...> expr = b(not not :a)
...>
...> {result, _acc_map} =
...> RewriteRule.apply(expr, [
...> Crux.Expression.RewriteRule.NegationLaw
...> ])
...>
...> result
:a