tree_morph/
engine.rs

1//! Perform gradual rule-based transformations on trees.
2//!
3//! See the [`morph`](Engine::morph) for more information.
4
5use crate::events::EventHandlers;
6use crate::helpers::{SelectorFn, one_or_select};
7use crate::prelude::Rule;
8use crate::rule::apply_into_update;
9
10use paste::paste;
11use uniplate::{Uniplate, tagged_zipper::TaggedZipper};
12
13/// An engine for exhaustively transforming trees with user-defined rules.
14///
15/// See the [`morph`](Engine::morph) method for more information.
16pub struct Engine<T, M, R>
17where
18    T: Uniplate,
19    R: Rule<T, M>,
20{
21    pub(crate) event_handlers: EventHandlers<T, M>,
22
23    /// A collection of groups of equally-prioritised rules.
24    pub(crate) rule_groups: Vec<Vec<R>>,
25
26    pub(crate) selector: SelectorFn<T, M, R>,
27}
28
29impl<T, M, R> Engine<T, M, R>
30where
31    T: Uniplate,
32    R: Rule<T, M>,
33{
34    /// Exhaustively rewrites a tree using user-defined rule groups.
35    ///
36    /// Rewriting is complete when all rules have been attempted with no change. Rules may be organised
37    /// into groups to control the order in which they are attempted.
38    ///
39    /// # Rule Groups
40    /// If all rules are treated equally, those which apply higher in the tree will take precedence
41    /// because of the left-most outer-most traversal order of the engine.
42    ///
43    /// This can cause problems if a rule which should ideally be applied early (e.g. evaluating
44    /// constant expressions) is left until later.
45    ///
46    /// To solve this, rules are organised into different collections or "groups".
47    /// The engine will apply rules in an earlier group to the entire tree before trying later groups.
48    /// That is, no rule is attempted if a rule in an earlier group is applicable to any part of the
49    /// tree.
50    ///
51    /// # Selector Functions
52    ///
53    /// If multiple rules in the same group are applicable to an expression, the user-defined
54    /// selector function is used to choose one. This function is given an iterator over pairs of
55    /// rules and the engine-created [`Update`] values which contain their modifications to the tree.
56    ///
57    /// Some useful selector functions are available in the [`helpers`](crate::helpers) module. One
58    /// common function is [`select_first`](crate::helpers::select_first), which simply returns the
59    /// first applicable rule.
60    ///
61    /// # Event Handlers
62    ///
63    /// The engine provides events for more fine-tuned control over rewriting behaviour. Events have mutable
64    /// access to the current metadata.
65    ///
66    /// The engine will call the provided handlers for the "enter" and
67    /// "exits" events as it enters and exits subtrees while traversing, respectively.
68    ///
69    /// The "enter" event is triggered first on the root, and then whenever the engine moves down
70    /// into a subtree. As a result, when a node is passed to rules, all nodes above it will have
71    /// been passed to handlers for this event, in ascending order of their proximity to the root.
72    ///
73    /// The "exit" event is triggered when the engine leaves a subtree.
74    /// All nodes passed to "enter" event handlers (except the root) will also be passed to "exit"
75    /// event handlers in reverse order.
76    ///
77    /// In effect, before a node is passed to rules, all nodes in the path from the root (including the
78    /// current node) will have been passed to the "enter" event in order. No nodes are skipped.
79    ///
80    /// Event handling is useful when, for example, using a symbol table to keep track of variable definitions.
81    /// When entering a scope where a variable is defined, one can place the variable and its value into the table.
82    /// That stack can then be used for quick value lookup while inside the scope. When leaving the scope the
83    /// variable can be removed from the table.
84    ///
85    /// # Optimizations
86    ///
87    /// To optimize the morph function, we use a dirty/clean approach. Since whether a rule applies
88    /// is purely a function of a node and its children, rules should only be checked once unless a node
89    /// or one of its children has been changed.
90    ///
91    /// # Example
92    /// ```rust
93    /// use tree_morph::{prelude::*, helpers::select_panic};
94    /// use uniplate::Uniplate;
95    ///
96    ///
97    /// // A simple language of multiplied and squared constant expressions
98    /// #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
99    /// #[uniplate()]
100    /// enum Expr {
101    ///     Val(i32),
102    ///     Mul(Box<Expr>, Box<Expr>),
103    ///     Sqr(Box<Expr>),
104    /// }
105    ///
106    /// // a * b ~> (value of) a * b, where 'a' and 'b' are literal values
107    /// fn rule_eval_mul(cmds: &mut Commands<Expr, i32>, subtree: &Expr, meta: &i32) -> Option<Expr> {
108    ///     cmds.mut_meta(Box::new(|m: &mut i32| *m += 1));
109    ///
110    ///     if let Expr::Mul(a, b) = subtree {
111    ///         if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
112    ///             return Some(Expr::Val(a_v * b_v));
113    ///         }
114    ///     }
115    ///     None
116    /// }
117    ///
118    /// // e ^ 2 ~> e * e, where e is an expression
119    /// // If this rule is applied before the sub-expression is fully evaluated, duplicate work
120    /// // will be done on the resulting two identical sub-expressions.
121    /// fn rule_expand_sqr(cmds: &mut Commands<Expr, i32>, subtree: &Expr, meta: &i32) -> Option<Expr> {
122    ///     cmds.mut_meta(Box::new(|m: &mut i32| *m += 1));
123    ///
124    ///     if let Expr::Sqr(expr) = subtree {
125    ///         return Some(Expr::Mul(
126    ///             Box::new(*expr.clone()),
127    ///             Box::new(*expr.clone())
128    ///         ));
129    ///     }
130    ///     None
131    /// }
132    ///
133    /// // (1 * 2) ^ 2
134    /// let expr = Expr::Sqr(
135    ///     Box::new(Expr::Mul(
136    ///         Box::new(Expr::Val(1)),
137    ///         Box::new(Expr::Val(2))
138    ///     ))
139    /// );
140    ///
141    /// // Try with both rules in the same group, keeping track of the number of rule applications
142    /// let engine = EngineBuilder::new()
143    ///     .set_selector(select_panic)
144    ///     .add_rule_group(rule_fns![rule_eval_mul, rule_expand_sqr])
145    ///     .build();
146    /// let (result, num_applications) = engine.morph(expr.clone(), 0);
147    /// assert_eq!(result, Expr::Val(4));
148    /// assert_eq!(num_applications, 4); // The `Sqr` is expanded first, causing duplicate work
149    ///
150    /// // Move the evaluation rule to an earlier group
151    /// let engine = EngineBuilder::new()
152    ///     .set_selector(select_panic)
153    ///     .add_rule_group(rule_fns![rule_eval_mul])
154    ///     .add_rule_group(rule_fns![rule_expand_sqr])
155    ///     .build();
156    /// let (result, num_applications) = engine.morph(expr.clone(), 0);
157    /// assert_eq!(result, Expr::Val(4));
158    /// assert_eq!(num_applications, 3); // Now the sub-expression (1 * 2) is evaluated first
159    /// ```
160    pub fn morph(&self, tree: T, meta: M) -> (T, M)
161    where
162        T: Uniplate,
163        R: Rule<T, M>,
164    {
165        // Owns the tree/meta and is consumed to get them back at the end
166        let mut zipper = EngineZipper::new(tree, meta, &self.event_handlers);
167
168        'main: loop {
169            // Return here after every successful rule application
170
171            for (level, rules) in self.rule_groups.iter().enumerate() {
172                // Try each rule group in the whole tree
173
174                while zipper.go_next_dirty(level).is_some() {
175                    let subtree = zipper.inner.focus();
176
177                    // Choose one transformation from all applicable rules at this level
178                    let selected = {
179                        let applicable = &mut rules.iter().filter_map(|rule| {
180                            let update = apply_into_update(rule, subtree, &zipper.meta)?;
181                            Some((rule, update))
182                        });
183                        one_or_select(self.selector, subtree, applicable)
184                    };
185
186                    if let Some(mut update) = selected {
187                        // Replace the current subtree, invalidating subtree node states
188                        zipper.inner.replace_focus(update.new_subtree);
189
190                        // Mark all ancestors as dirty and move back to the root
191                        zipper.mark_dirty_to_root();
192
193                        let (new_tree, root_transformed) = update
194                            .commands
195                            .apply(zipper.inner.focus().clone(), &mut zipper.meta);
196
197                        if root_transformed {
198                            // This must unfortunately throw all node states away,
199                            // since the `transform` command may redefine the whole tree
200                            zipper.inner.replace_focus(new_tree);
201                        }
202
203                        continue 'main;
204                    } else {
205                        zipper.set_dirty_from(level + 1);
206                    }
207                }
208            }
209
210            // All rules have been tried with no more changes
211            break;
212        }
213
214        zipper.into()
215    }
216}
217
218#[derive(Debug, Clone)]
219struct EngineNodeState {
220    /// Rule groups with lower indices have already been applied without change.
221    /// For a level `n`, a state is 'dirty' if and only if `n >= dirty_from`.
222    dirty_from: usize,
223}
224
225impl EngineNodeState {
226    /// Marks the state as dirty for anything >= `level`.
227    fn set_dirty_from(&mut self, level: usize) {
228        self.dirty_from = level;
229    }
230
231    /// For a level `n`, a state is "dirty" if and only if `n >= dirty_from`.
232    /// That is, all rules groups before `n` have been applied without change.
233    fn is_dirty(&self, level: usize) -> bool {
234        level >= self.dirty_from
235    }
236}
237
238impl EngineNodeState {
239    fn new<T: Uniplate>(_: &T) -> Self {
240        Self { dirty_from: 0 }
241    }
242}
243
244macro_rules! movement_fns {
245    (
246        directions: [$($dir:ident),*]
247    ) => {
248        paste! {
249            $(fn [<go_ $dir>](&mut self) -> Option<()> {
250                self.inner.zipper().[<has_ $dir>]().then(|| {
251                    self.event_handlers
252                        .[<trigger_before_ $dir>](self.inner.focus(), &mut self.meta);
253                    self.inner.[<go_ $dir>]().expect("zipper movement failed despite check");
254                    self.event_handlers
255                        .[<trigger_after_ $dir>](self.inner.focus(), &mut self.meta);
256                })
257            })*
258        }
259    };
260}
261
262/// A Zipper with optimisations for tree transformation.
263struct EngineZipper<'events, T: Uniplate, M> {
264    inner: TaggedZipper<T, EngineNodeState, fn(&T) -> EngineNodeState>,
265    event_handlers: &'events EventHandlers<T, M>,
266    meta: M,
267}
268
269impl<'events, T: Uniplate, M> EngineZipper<'events, T, M> {
270    pub fn new(tree: T, meta: M, event_handlers: &'events EventHandlers<T, M>) -> Self {
271        EngineZipper {
272            inner: TaggedZipper::new(tree, EngineNodeState::new),
273            event_handlers,
274            meta,
275        }
276    }
277
278    /// Go to the next node in the tree which is dirty for the given level.
279    /// That node may be the current one if it is dirty.
280    /// If no such node exists, go to the root and return `None`.
281    pub fn go_next_dirty(&mut self, level: usize) -> Option<()> {
282        if self.inner.tag().is_dirty(level) {
283            return Some(());
284        }
285
286        self.go_down()
287            .and_then(|_| {
288                // go right until we find a dirty child, if it exists.
289                loop {
290                    if self.inner.tag().is_dirty(level) {
291                        return Some(());
292                    } else if self.go_right().is_none() {
293                        // all children are clean
294                        self.go_up();
295                        return None;
296                    }
297                }
298            })
299            .or_else(|| {
300                // Neither this node, nor any of its children are dirty
301                // Go right then up until we find a dirty node or reach the root
302                loop {
303                    if self.go_right().is_some() {
304                        if self.inner.tag().is_dirty(level) {
305                            return Some(());
306                        }
307                    } else if self.go_up().is_none() {
308                        // Reached the root without finding a dirty node
309                        return None;
310                    }
311                }
312            })
313    }
314
315    // We never move left in the tree
316    movement_fns! { directions: [up, down, right] }
317
318    /// Mark the current focus as visited at the given level.
319    /// Calling `go_next_dirty` with the same level will no longer yield this node.
320    pub fn set_dirty_from(&mut self, level: usize) {
321        self.inner.tag_mut().set_dirty_from(level);
322    }
323
324    /// Mark ancestors as dirty for all levels, and return to the root
325    pub fn mark_dirty_to_root(&mut self) {
326        while self.go_up().is_some() {
327            self.set_dirty_from(0);
328        }
329    }
330}
331
332impl<T: Uniplate, M> From<EngineZipper<'_, T, M>> for (T, M) {
333    fn from(val: EngineZipper<'_, T, M>) -> Self {
334        let meta = val.meta;
335        let tree = val.inner.rebuild_root();
336        (tree, meta)
337    }
338}