Skip to main content

tree_morph/
rule.rs

1//! Traits and types representing a transformation rule to a tree.
2//!
3//! See the [`Rule`] trait for more information.
4
5use std::{collections::HashMap, marker::PhantomData};
6
7use crate::prelude::{Commands, Update};
8use uniplate::Uniplate;
9
10/// Trait implemented by rules to transform parts of a tree.
11///
12/// Rules contain a method `apply` which accepts a [`Commands`] instance, a subtree, and
13/// global metadata. If the rule is applicable to the subtree, it should return `Some(<new_tree>)`,
14/// otherwise it should return `None`.
15///
16/// # Rule Application
17/// As the engine traverses the tree (in left-most, outer-most order), it will apply rules to each
18/// node. The `subtree` argument passed to the rule is the current node being visited.
19///
20/// If a rule is applicable to the given node/subtree (i.e. can transform it), then it should return
21/// the resulting new subtree, which will be inserted into the tree in place of the original node.
22///
23/// # Side-Effects
24///
25/// The [`Commands`] instance passed to the rule can be used to apply side-effects after the rule
26/// has been applied. This can be used to update global metadata, or to apply transformations to the
27/// entire tree.
28///
29/// # Global Metadata
30/// In contrast to the `subtree` argument given to rules, the `meta` argument is a
31/// reference to a global value which is available to all rules regardless of where in
32/// the tree they are applied. This user-defined value can be used to store information
33/// such as a symbol table, or the number of times a specific rule has been applied.
34///
35/// The global metadata may be mutated as a side-effect of applying a rule, using the
36/// [`Commands::mut_meta`] method.
37///
38/// # Provided Implementations
39/// This trait is automatically implemented by all types which implement
40/// `Fn(&mut Commands<T, M>, &T, &M) -> Option<T>` for types `T: Uniplate` and `M`. This allows
41/// function pointers and closures with the correct signatures to be used as rules directly.
42///
43/// # Example
44/// ```rust
45/// use tree_morph::prelude::*;
46/// use uniplate::Uniplate;
47///
48///
49/// #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
50/// #[uniplate()]
51/// enum Term {
52///     A,
53///     B,
54/// }
55///
56/// // Functions and closures automatically implement the Rule trait
57/// fn my_rule_fn(_: &mut Commands<Term, ()>, _: &Term, _: &()) -> Option<Term> {
58///     None // Never applicable
59/// }
60///
61/// let mut engine = EngineBuilder::new()
62///     .add_rule_group(rule_fns![my_rule_fn])
63///     .build();
64/// let (result, _) = engine.morph(Term::A, ());
65/// assert_eq!(result, Term::A);
66///
67///
68/// // Custom types can implement the Rule trait to allow more complex behaviour
69/// // Here a rule can be "toggled" to change whether it is applicable
70/// #[derive(Clone)]
71/// struct CustomRule(bool);
72///
73/// impl Rule<Term, ()> for CustomRule {
74///     fn apply(&self, _: &mut Commands<Term, ()>, t: &Term, _: &()) -> Option<Term> {
75///         if self.0 && matches!(t, Term::A) {
76///             Some(Term::B)
77///         } else {
78///             None
79///         }
80///     }
81/// }
82///
83/// let mut engine = EngineBuilder::new()
84///     .add_rule(CustomRule(false))
85///     .build();
86/// let (result, _) = engine.morph(Term::A, ());
87/// assert_eq!(result, Term::A);
88///
89/// let mut engine = EngineBuilder::new()
90///     .add_rule(CustomRule(true))
91///     .build();
92/// let (result, _) = engine.morph(Term::A, ());
93/// assert_eq!(result, Term::B);
94/// ```
95pub trait Rule<T: Uniplate, M> {
96    /// Applies the rule to the given subtree and returns the result if applicable.
97    ///
98    /// See the [Rule] trait documentation for more information.
99    fn apply(&self, commands: &mut Commands<T, M>, subtree: &T, meta: &M) -> Option<T>;
100
101    /// Return the name of the rule, will default to anonymous if not specified.
102    fn name(&self) -> &str {
103        "Anonymous Rule"
104    }
105
106    /// None -> Rule applies to all nodes
107    /// Some(ids) -> Rule only applies to nodes with these discriminant ids
108    fn applicable_to(&self) -> Option<Vec<usize>> {
109        None
110    }
111}
112
113// Allows the user to pass closures and function pointers directly as rules
114impl<T, M, F> Rule<T, M> for F
115where
116    T: Uniplate,
117    F: Fn(&mut Commands<T, M>, &T, &M) -> Option<T>,
118{
119    fn apply(&self, commands: &mut Commands<T, M>, subtree: &T, meta: &M) -> Option<T> {
120        (self)(commands, subtree, meta)
121    }
122}
123
124/// A helper method to get an [`Update`] directly from a rule.
125pub(crate) fn apply_into_update<T, M, R>(rule: &R, subtree: &T, meta: &M) -> Option<Update<T, M>>
126where
127    T: Uniplate,
128    R: Rule<T, M>,
129{
130    let mut commands = Commands::new();
131    let new_subtree = rule.apply(&mut commands, subtree, meta)?;
132    Some(Update::new(new_subtree, commands))
133}
134
135/// A uniform type for `fn` pointers and closures, which implements the [Rule] trait.
136///
137/// Casting an `fn` pointer or closure to this type allows it to be passed to the engine alongside
138/// other such types. This is necessary since no two `fn` pointers or closures have the same
139/// type, and thus cannot be stored in a single collection without casting.
140///
141/// See the [rule_fns!](crate::rule_fns) macro for a convenient way to do this.
142pub type RuleFn<T, M> = fn(&mut Commands<T, M>, &T, &M) -> Option<T>;
143
144/// A convenience macro to cast a list of `fn` pointers or closures to a uniform type.
145///
146/// Casting an `fn` pointer or closure to this type allows it to be passed to the engine alongside
147/// other such types. This is necessary since no two `fn` pointers or closures have the same
148/// type, and thus cannot be stored in a single collection without casting.
149///
150/// # Example
151/// ```rust
152/// use tree_morph::prelude::*;
153/// use uniplate::Uniplate;
154///
155///
156/// #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
157/// #[uniplate()]
158/// struct Foo;
159///
160/// fn rule_a(_: &mut Commands<Foo, ()>, _: &Foo, _: &()) -> Option<Foo> {
161///     None
162/// }
163///
164/// fn rule_b(_: &mut Commands<Foo, ()>, _: &Foo, _: &()) -> Option<Foo> {
165///     None
166/// }
167///
168/// let rules = vec![
169///     rule_fns![rule_a],
170///     vec![rule_a as RuleFn<_, _>], // Same as above
171///     rule_fns![rule_b, |_, _, _| None], // Closures and fn pointers can be mixed
172/// ];
173/// ```
174#[macro_export]
175macro_rules! rule_fns {
176    [$($x:expr),+ $(,)?] => {
177        vec![$( $x as ::tree_morph::prelude::RuleFn<_, _>, )*]
178    };
179}
180
181/// We can create a rule using this struct and pass it into our list of rules directly,
182/// For debugging and tracing, it is helpful to see rules by a meaningful name.
183/// or we can make use of the `named_rule` macro (see [`tree-morph-macros`]).
184///
185/// This struct and macro is for the short form way of defining named rules. You can change the name
186/// of the rule by implementing the `Rule` trait as well.
187///
188///  ```rust
189/// use tree_morph::prelude::*;
190/// use tree_morph_macros::named_rule;
191/// use uniplate::Uniplate;
192///
193/// #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
194/// #[uniplate()]
195/// enum Expr {
196///     # Add(Box<Expr>, Box<Expr>),
197///    // Snip
198/// }
199///
200/// struct Meta;
201///
202/// #[named_rule("CustomName")]
203/// fn my_rule(_: &mut Commands<Expr, Meta>, expr: &Expr, _: &Meta) -> Option<Expr> {
204///     /// rule implementation
205///     # None
206/// }
207/// ```
208/// This macro will return a helper function called `my_rule` which returns the NamedRule for us to
209/// use. We can add this to our list of rules with `vec![my_rule]`.
210///
211/// If a name is not specified, the functions name will be it's identifier.
212#[derive(Clone)]
213pub struct NamedRule<F: Clone> {
214    name: &'static str,
215    function: F,
216}
217
218impl<F: Clone> NamedRule<F> {
219    /// Create a Rule with a specified name.
220    pub const fn new(name: &'static str, function: F) -> Self {
221        Self { name, function }
222    }
223}
224
225impl<T, M, F> Rule<T, M> for NamedRule<F>
226where
227    T: Uniplate,
228    F: Fn(&mut Commands<T, M>, &T, &M) -> Option<T> + Clone,
229{
230    fn apply(&self, commands: &mut Commands<T, M>, subtree: &T, meta: &M) -> Option<T> {
231        (self.function)(commands, subtree, meta)
232    }
233
234    fn name(&self) -> &str {
235        self.name
236    }
237}
238
239/// TODO
240pub struct RuleGroups<T, M, R>
241where
242    T: Uniplate,
243    R: Rule<T, M> + Clone,
244{
245    /// Priority -> Rules
246    universal_rules: Vec<Vec<R>>,
247
248    /// Priority -> discriminant id -> Rules
249    filtered_rules: Option<Vec<HashMap<usize, Vec<R>>>>,
250
251    /// Function to compute a unique usize id for a node, used for prefiltering.
252    /// None means prefiltering is disabled.
253    pub discriminant_fn: Option<fn(&T) -> usize>,
254
255    _phantom: PhantomData<(T, M)>,
256}
257
258impl<T, M, R> RuleGroups<T, M, R>
259where
260    T: Uniplate,
261    R: Rule<T, M> + Clone,
262{
263    /// TODO
264    pub fn new(rule_group: Vec<Vec<R>>, discriminant_fn: Option<fn(&T) -> usize>) -> Self {
265        let Some(discriminant_fn) = discriminant_fn else {
266            return Self {
267                filtered_rules: None,
268                universal_rules: rule_group,
269                discriminant_fn: None,
270                _phantom: PhantomData,
271            };
272        };
273
274        let mut filtered_rules = Vec::with_capacity(rule_group.len());
275        let mut universal_rules = Vec::with_capacity(rule_group.len());
276
277        for rules in rule_group {
278            let mut filtered: HashMap<usize, Vec<R>> = HashMap::new();
279            let mut universal = Vec::new();
280
281            for rule in rules {
282                match rule.applicable_to() {
283                    None => universal.push(rule),
284                    Some(ids) => {
285                        for id in ids {
286                            filtered.entry(id).or_default().push(rule.clone());
287                        }
288                    }
289                };
290            }
291
292            filtered_rules.push(filtered);
293            universal_rules.push(universal);
294        }
295        Self {
296            universal_rules,
297            filtered_rules: Some(filtered_rules),
298            discriminant_fn: Some(discriminant_fn),
299            _phantom: PhantomData,
300        }
301    }
302
303    /// TODO
304    pub fn levels(&self) -> usize {
305        self.universal_rules.len()
306    }
307
308    /// TODO
309    pub fn get_rules(&self, level: usize, id: Option<usize>) -> RuleSet<'_, R> {
310        let filtered = id
311            .and_then(|id| {
312                self.filtered_rules
313                    .as_ref()
314                    .and_then(|filter_map| filter_map[level].get(&id))
315            })
316            .map(|f| f.as_slice())
317            .unwrap_or(&[]);
318        let universal = &self.universal_rules[level];
319        RuleSet {
320            universal,
321            filtered,
322        }
323    }
324
325    /// Returns the universal rule groups in priority order.
326    pub fn get_all_rules(&self) -> &[Vec<R>] {
327        self.universal_rules.as_slice()
328    }
329}
330
331/// A view into the rules applicable to a specific node, split into two slices.
332///
333/// `universal` contains rules that apply to all nodes; `filtered` contains rules that were
334/// prefiltered to this node's discriminant id. Both slices are empty-or-valid references into
335/// the engine's rule storage — no allocation is required to produce this type.
336pub struct RuleSet<'a, R> {
337    pub(crate) universal: &'a [R],
338    pub(crate) filtered: &'a [R],
339}
340
341impl<'a, R> RuleSet<'a, R> {
342    /// Total number of rules in this set.
343    pub fn len(&self) -> usize {
344        self.universal.len() + self.filtered.len()
345    }
346
347    /// Returns true if there are no rules in this set.
348    pub fn is_empty(&self) -> bool {
349        self.universal.is_empty() && self.filtered.is_empty()
350    }
351
352    /// Returns a Rayon parallel iterator over the rules in this set.
353    ///
354    /// Chains the universal and filtered slices without any intermediate allocation.
355    pub fn par_iter(&self) -> impl rayon::iter::ParallelIterator<Item = &'a R>
356    where
357        R: Sync,
358    {
359        use rayon::prelude::*;
360        self.universal.par_iter().chain(self.filtered.par_iter())
361    }
362}
363
364impl<'a, R> IntoIterator for RuleSet<'a, R> {
365    type Item = &'a R;
366    type IntoIter = std::iter::Chain<std::slice::Iter<'a, R>, std::slice::Iter<'a, R>>;
367
368    fn into_iter(self) -> Self::IntoIter {
369        self.universal.iter().chain(self.filtered.iter())
370    }
371}