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}