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::cache::{CacheResult, RewriteCache};
6use crate::engine_zipper::{EngineZipper, NaiveZipper};
7use crate::events::EventHandlers;
8use crate::helpers::{SelectorFn, one_or_select};
9use crate::prelude::Rule;
10use crate::rule::{RuleGroups, RuleSet, apply_into_update};
11use crate::update::Update;
12
13use rayon::prelude::*;
14use tracing::{debug, error, info, instrument, trace};
15use uniplate::Uniplate;
16
17/// An engine for exhaustively transforming trees with user-defined rules.
18///
19/// See the [`morph`](Engine::morph) method for more information.
20pub struct Engine<T, M, R, C>
21where
22 T: Uniplate + Send + Sync,
23 R: Rule<T, M> + Clone,
24 C: RewriteCache<T>,
25{
26 pub(crate) event_handlers: EventHandlers<T, M, R>,
27
28 /// A collection of groups of equally-prioritised rules.
29 pub(crate) rule_groups: RuleGroups<T, M, R>,
30
31 pub(crate) selector: SelectorFn<T, M, R>,
32
33 pub(crate) cache: C,
34
35 /// Whether to use parallel (Rayon) iteration when checking rules.
36 pub(crate) parallel: bool,
37
38 pub(crate) fixedpoint: bool,
39
40 pub(crate) down_predicate: fn(&T) -> bool,
41}
42
43impl<T, M, R, C> Engine<T, M, R, C>
44where
45 T: Uniplate + Send + Sync,
46 R: Rule<T, M> + Clone + Sync,
47 M: Sync,
48 C: RewriteCache<T>,
49{
50 #[instrument(skip(selector, parallel, subtree, meta, rules, event_handlers))]
51 fn select_rule<'a>(
52 selector: SelectorFn<T, M, R>,
53 parallel: bool,
54 subtree: &T,
55 meta: &mut M,
56 rules: RuleSet<'a, R>,
57 event_handlers: &EventHandlers<T, M, R>,
58 ) -> Option<(&'a R, Update<T, M>)> {
59 trace!("Beginning Rule Checks");
60 if parallel {
61 let applicable: Vec<(&'a R, Update<T, M>)> = rules
62 .par_iter()
63 .filter_map(|rule| {
64 let update = apply_into_update(rule, subtree, meta)?;
65 Some((rule, update))
66 })
67 .collect();
68 trace!("Finished Rule Checks");
69 one_or_select(selector, subtree, &mut applicable.into_iter())
70 } else {
71 let applicable = &mut rules.into_iter().filter_map(|rule| {
72 event_handlers.trigger_before_rule(subtree, meta, rule);
73 let result = apply_into_update(rule, subtree, meta);
74 let is_applicable = result.is_some();
75 event_handlers.trigger_after_rule(subtree, meta, rule, is_applicable);
76 let update = result?;
77 Some((rule, update))
78 });
79 trace!("Finished Rule Checks");
80 one_or_select(selector, subtree, applicable)
81 }
82 }
83
84 fn apply_rule(
85 zipper: &mut EngineZipper<T, M, R, C>,
86 event_handlers: &EventHandlers<T, M, R>,
87 application: (&R, Update<T, M>),
88 level: usize,
89 ) {
90 let (rule, mut update) = application;
91 debug!("Applying Rule '{}'", rule.name());
92
93 let cache_active = zipper.cache.is_active();
94 let original = cache_active.then(|| zipper.focus().clone());
95
96 // Clear any stale cached hashes in the replacement subtree
97 zipper.cache.invalidate_subtree(&update.new_subtree);
98
99 let replacement = cache_active.then(|| update.new_subtree.clone());
100
101 // Replace the current subtree, invalidating subtree node states
102 zipper.replace_focus(update.new_subtree);
103
104 // Mark all ancestors as dirty, insert ancestor mappings, and move back to the root
105 zipper.mark_dirty_to_root(level);
106
107 let (focus, meta) = zipper.focus_and_meta();
108 let (new_tree, root_transformed) = update.commands.apply(focus.clone(), meta);
109
110 if root_transformed {
111 debug!("Root transformed, clearing state.");
112 // This must unfortunately throw all node states away,
113 // since the `transform` command may redefine the whole tree
114 zipper.replace_focus(new_tree);
115 } else if let (Some(orig), Some(repl)) = (original, replacement) {
116 if orig != repl {
117 zipper.cache.insert(&orig, Some(repl), level);
118 } else {
119 error!("Original Subtree is the same as Replacement Tree");
120 }
121 }
122
123 let (focus, meta) = zipper.focus_and_meta();
124 event_handlers.trigger_on_apply(focus, meta, rule);
125 }
126
127 fn apply_rule_fixedpoint(
128 zipper: &mut EngineZipper<T, M, R, C>,
129 event_handlers: &EventHandlers<T, M, R>,
130 rule_groups: &RuleGroups<T, M, R>,
131 selector: SelectorFn<T, M, R>,
132 parallel: bool,
133 application: (&R, Update<T, M>),
134 level: usize,
135 ) {
136 // Apply the initial rule
137 let (rule, mut update) = application;
138 debug!("Applying Rules in Fixed Point Mode");
139
140 if update.has_transform() {
141 trace!("Rule has Transform. Stopping Fixed Point");
142 Self::apply_rule(zipper, event_handlers, (rule, update), level);
143 return;
144 }
145
146 // No transform — apply locally
147 let cache_active = zipper.cache.is_active();
148 let original = cache_active.then(|| zipper.focus().clone());
149 zipper.cache.invalidate_subtree(&update.new_subtree);
150 let replacement = cache_active.then(|| update.new_subtree.clone());
151 zipper.replace_focus(update.new_subtree);
152
153 {
154 let (focus, meta) = zipper.focus_and_meta();
155 update.commands.apply(focus.clone(), meta);
156 }
157
158 if let (Some(orig), Some(repl)) = (original, replacement) {
159 if orig != repl {
160 zipper.cache.insert(&orig, Some(repl), level);
161 } else {
162 error!("Original Subtree is the same as Replacement Tree");
163 }
164 }
165
166 let (focus, meta) = zipper.focus_and_meta();
167 event_handlers.trigger_on_apply(focus, meta, rule);
168
169 let mut try_level = 0;
170 while try_level <= level {
171 let subtree = zipper.focus();
172 let id = rule_groups.discriminant_fn.map(|f| f(subtree));
173 let rules = rule_groups.get_rules(try_level, id);
174
175 let (subtree, meta) = zipper.focus_and_meta();
176 match Self::select_rule(selector, parallel, subtree, meta, rules, event_handlers) {
177 Some((rule, mut update)) => {
178 if update.has_transform() {
179 trace!("Rule has Transform. Stopping Fixed Point");
180 Self::apply_rule(zipper, event_handlers, (rule, update), level);
181 return;
182 }
183
184 debug!("Applying Rule '{}' till Fixed Point (local)", rule.name());
185 let cache_active = zipper.cache.is_active();
186 let original = cache_active.then(|| zipper.focus().clone());
187 zipper.cache.invalidate_subtree(&update.new_subtree);
188 let replacement = cache_active.then(|| update.new_subtree.clone());
189 zipper.replace_focus(update.new_subtree);
190
191 {
192 let (focus, meta) = zipper.focus_and_meta();
193 update.commands.apply(focus.clone(), meta);
194 }
195
196 if let (Some(orig), Some(repl)) = (original, replacement) {
197 if orig != repl {
198 zipper.cache.insert(&orig, Some(repl), level);
199 } else {
200 error!("Original Subtree is the same as Replacement Tree");
201 }
202 }
203
204 let (focus, meta) = zipper.focus_and_meta();
205 event_handlers.trigger_on_apply(focus, meta, rule);
206
207 try_level = 0;
208 }
209 None => {
210 try_level += 1;
211 }
212 }
213 }
214
215 // Node stabilized — mark pass-through and go to root
216 zipper.set_pass_through(level);
217 zipper.mark_dirty_to_root(level);
218 }
219
220 fn apply_rule_naive_fixedpoint(
221 zipper: &mut NaiveZipper<T, M, R, C>,
222 event_handlers: &EventHandlers<T, M, R>,
223 rule_groups: &RuleGroups<T, M, R>,
224 selector: SelectorFn<T, M, R>,
225 parallel: bool,
226 application: (&R, Update<T, M>),
227 level: usize,
228 ) -> bool {
229 let (rule, mut update) = application;
230 debug!("Applying Rule '{}' in Fast Mode (naive)", rule.name());
231
232 if update.has_transform() {
233 trace!("Rule has Transform. Stopping Fixed Point");
234 Self::apply_rule_naive(zipper, event_handlers, (rule, update), level);
235 return false;
236 }
237
238 // No transform — apply locally (no root traversal)
239 let cache_active = zipper.cache.is_active();
240 let original = cache_active.then(|| zipper.focus().clone());
241 zipper.cache.invalidate_subtree(&update.new_subtree);
242 let replacement = cache_active.then(|| update.new_subtree.clone());
243 zipper.replace_focus(update.new_subtree);
244
245 {
246 let (focus, meta) = zipper.focus_and_meta();
247 update.commands.apply(focus.clone(), meta);
248 }
249
250 if let (Some(orig), Some(repl)) = (original, replacement) {
251 if orig != repl {
252 zipper.cache.insert(&orig, Some(repl), level);
253 } else {
254 error!("Original Subtree is the same as Replacement Tree");
255 }
256 }
257
258 let (focus, meta) = zipper.focus_and_meta();
259 event_handlers.trigger_on_apply(focus, meta, rule);
260
261 // Try rules from level 0 up to current level
262 let mut try_level = 0;
263 while try_level <= level {
264 let subtree = zipper.focus();
265 let id = rule_groups.discriminant_fn.map(|f| f(subtree));
266 let rules = rule_groups.get_rules(try_level, id);
267
268 let (subtree, meta) = zipper.focus_and_meta();
269 match Self::select_rule(selector, parallel, subtree, meta, rules, event_handlers) {
270 Some((rule, mut update)) => {
271 if update.has_transform() {
272 trace!("Rule has Transform. Stopping Fixed Point");
273 Self::apply_rule_naive(zipper, event_handlers, (rule, update), level);
274 return false;
275 }
276
277 debug!(
278 "Applying Rule '{}' in Fast Mode (naive, local)",
279 rule.name()
280 );
281 let cache_active = zipper.cache.is_active();
282 let original = cache_active.then(|| zipper.focus().clone());
283 zipper.cache.invalidate_subtree(&update.new_subtree);
284 let replacement = cache_active.then(|| update.new_subtree.clone());
285 zipper.replace_focus(update.new_subtree);
286
287 {
288 let (focus, meta) = zipper.focus_and_meta();
289 update.commands.apply(focus.clone(), meta);
290 }
291
292 if let (Some(orig), Some(repl)) = (original, replacement) {
293 if orig != repl {
294 zipper.cache.insert(&orig, Some(repl), level);
295 } else {
296 error!("Original Subtree is the same as Replacement Tree");
297 }
298 }
299
300 let (focus, meta) = zipper.focus_and_meta();
301 event_handlers.trigger_on_apply(focus, meta, rule);
302
303 try_level = 0;
304 }
305 None => {
306 try_level += 1;
307 }
308 }
309 }
310
311 // Node stabilized — need to go to root and rebuild ancestors
312 zipper.map_ancestors_to_root(level);
313 true
314 }
315
316 fn apply_rule_naive(
317 zipper: &mut NaiveZipper<T, M, R, C>,
318 event_handlers: &EventHandlers<T, M, R>,
319 application: (&R, Update<T, M>),
320 level: usize,
321 ) {
322 let (rule, mut update) = application;
323 debug!("Applying Rule '{}'", rule.name());
324
325 let cache_active = zipper.cache.is_active();
326 let original = cache_active.then(|| zipper.focus().clone());
327 zipper.cache.invalidate_subtree(&update.new_subtree);
328 let replacement = cache_active.then(|| update.new_subtree.clone());
329
330 zipper.replace_focus(update.new_subtree);
331 zipper.map_ancestors_to_root(level);
332
333 let (focus, meta) = zipper.focus_and_meta();
334 let (new_tree, root_transformed) = update.commands.apply(focus.clone(), meta);
335
336 if root_transformed {
337 trace!("Root transformed.");
338 zipper.replace_focus(new_tree);
339 } else if let (Some(orig), Some(repl)) = (original, replacement) {
340 if orig != repl {
341 zipper.cache.insert(&orig, Some(repl), level);
342 } else {
343 error!("Original Subtree is the same as Replacement Tree");
344 }
345 }
346
347 let (focus, meta) = zipper.focus_and_meta();
348 event_handlers.trigger_on_apply(focus, meta, rule);
349 }
350
351 /// Exhaustively rewrites a tree using user-defined rule groups.
352 ///
353 /// Rewriting is complete when all rules have been attempted with no change. Rules may be organised
354 /// into groups to control the order in which they are attempted.
355 ///
356 /// # Rule Groups
357 /// If all rules are treated equally, those which apply higher in the tree will take precedence
358 /// because of the left-most outer-most traversal order of the engine.
359 ///
360 /// This can cause problems if a rule which should ideally be applied early (e.g. evaluating
361 /// constant expressions) is left until later.
362 ///
363 /// To solve this, rules are organised into different collections or "groups".
364 /// The engine will apply rules in an earlier group to the entire tree before trying later groups.
365 /// That is, no rule is attempted if a rule in an earlier group is applicable to any part of the
366 /// tree.
367 ///
368 /// # Selector Functions
369 ///
370 /// If multiple rules in the same group are applicable to an expression, the user-defined
371 /// selector function is used to choose one. This function is given an iterator over pairs of
372 /// rules and the engine-created [`Update`] values which contain their modifications to the tree.
373 ///
374 /// Some useful selector functions are available in the [`helpers`](crate::helpers) module. One
375 /// common function is [`select_first`](crate::helpers::select_first), which simply returns the
376 /// first applicable rule.
377 ///
378 /// # Event Handlers
379 ///
380 /// The engine provides events for more fine-tuned control over rewriting behaviour. Events have mutable
381 /// access to the current metadata.
382 ///
383 /// The engine will call the provided handlers for the "enter" and
384 /// "exits" events as it enters and exits subtrees while traversing, respectively.
385 ///
386 /// The "enter" event is triggered first on the root, and then whenever the engine moves down
387 /// into a subtree. As a result, when a node is passed to rules, all nodes above it will have
388 /// been passed to handlers for this event, in ascending order of their proximity to the root.
389 ///
390 /// The "exit" event is triggered when the engine leaves a subtree.
391 /// All nodes passed to "enter" event handlers (except the root) will also be passed to "exit"
392 /// event handlers in reverse order.
393 ///
394 /// In effect, before a node is passed to rules, all nodes in the path from the root (including the
395 /// current node) will have been passed to the "enter" event in order. No nodes are skipped.
396 ///
397 /// Event handling is useful when, for example, using a symbol table to keep track of variable definitions.
398 /// When entering a scope where a variable is defined, one can place the variable and its value into the table.
399 /// That stack can then be used for quick value lookup while inside the scope. When leaving the scope the
400 /// variable can be removed from the table.
401 ///
402 /// # Optimizations
403 ///
404 /// To optimize the morph function, we use a dirty/clean approach. Since whether a rule applies
405 /// is purely a function of a node and its children, rules should only be checked once unless a node
406 /// or one of its children has been changed.
407 ///
408 /// # Example
409 /// ```rust
410 /// use tree_morph::{prelude::*, helpers::select_panic};
411 /// use uniplate::Uniplate;
412 ///
413 ///
414 /// // A simple language of multiplied and squared constant expressions
415 /// #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
416 /// #[uniplate()]
417 /// enum Expr {
418 /// Val(i32),
419 /// Mul(Box<Expr>, Box<Expr>),
420 /// Sqr(Box<Expr>),
421 /// }
422 ///
423 /// // a * b ~> (value of) a * b, where 'a' and 'b' are literal values
424 /// fn rule_eval_mul(cmds: &mut Commands<Expr, i32>, subtree: &Expr, meta: &i32) -> Option<Expr> {
425 /// cmds.mut_meta(Box::new(|m: &mut i32| *m += 1));
426 ///
427 /// if let Expr::Mul(a, b) = subtree {
428 /// if let (Expr::Val(a_v), Expr::Val(b_v)) = (a.as_ref(), b.as_ref()) {
429 /// return Some(Expr::Val(a_v * b_v));
430 /// }
431 /// }
432 /// None
433 /// }
434 ///
435 /// // e ^ 2 ~> e * e, where e is an expression
436 /// // If this rule is applied before the sub-expression is fully evaluated, duplicate work
437 /// // will be done on the resulting two identical sub-expressions.
438 /// fn rule_expand_sqr(cmds: &mut Commands<Expr, i32>, subtree: &Expr, meta: &i32) -> Option<Expr> {
439 /// cmds.mut_meta(Box::new(|m: &mut i32| *m += 1));
440 ///
441 /// if let Expr::Sqr(expr) = subtree {
442 /// return Some(Expr::Mul(
443 /// Box::new(*expr.clone()),
444 /// Box::new(*expr.clone())
445 /// ));
446 /// }
447 /// None
448 /// }
449 ///
450 /// // (1 * 2) ^ 2
451 /// let expr = Expr::Sqr(
452 /// Box::new(Expr::Mul(
453 /// Box::new(Expr::Val(1)),
454 /// Box::new(Expr::Val(2))
455 /// ))
456 /// );
457 ///
458 /// // Try with both rules in the same group, keeping track of the number of rule applications
459 /// let mut engine = EngineBuilder::new()
460 /// .set_selector(select_panic)
461 /// .add_rule_group(rule_fns![rule_eval_mul, rule_expand_sqr])
462 /// .build();
463 /// let (result, num_applications) = engine.morph(expr.clone(), 0);
464 /// assert_eq!(result, Expr::Val(4));
465 /// assert_eq!(num_applications, 4); // The `Sqr` is expanded first, causing duplicate work
466 ///
467 /// // Move the evaluation rule to an earlier group
468 /// let mut engine = EngineBuilder::new()
469 /// .set_selector(select_panic)
470 /// .add_rule_group(rule_fns![rule_eval_mul])
471 /// .add_rule_group(rule_fns![rule_expand_sqr])
472 /// .build();
473 /// let (result, num_applications) = engine.morph(expr.clone(), 0);
474 /// assert_eq!(result, Expr::Val(4));
475 /// assert_eq!(num_applications, 3); // Now the sub-expression (1 * 2) is evaluated first
476 /// ```
477 // #[instrument(skip(self, tree, meta))]
478 pub fn morph(&mut self, tree: T, meta: M) -> (T, M)
479 where
480 T: Uniplate + Send + Sync,
481 R: Rule<T, M>,
482 {
483 // Owns the tree/meta and is consumed to get them back at the end
484 let mut zipper = EngineZipper::new(
485 tree,
486 meta,
487 self.down_predicate,
488 &self.event_handlers,
489 &mut self.cache,
490 );
491 info!("Beginning Morph");
492
493 'main: loop {
494 // Return here after every successful rule application
495 for level in 0..self.rule_groups.levels() {
496 while zipper.go_next_dirty(level).is_some() {
497 trace!("Got Dirty, Level {}", level);
498
499 {
500 let subtree = zipper.focus();
501 let id = self.rule_groups.discriminant_fn.map(|f| f(subtree));
502 let rules = self.rule_groups.get_rules(level, id);
503
504 debug!("Checking Level {} with {} Rules", level, rules.len());
505 match zipper.cache.get(subtree, level) {
506 CacheResult::Terminal(clean_level) => {
507 debug!(
508 "Cache Hit - Nothing Applicable (clean through level {})",
509 clean_level
510 );
511 zipper.trigger_cache_hit();
512 zipper.set_dirty_from(clean_level + 1);
513 continue;
514 }
515 CacheResult::Rewrite(cached) => {
516 debug!("Cache Hit");
517 zipper.trigger_cache_hit();
518 zipper.replace_focus(cached);
519 zipper.mark_dirty_to_root(level);
520 continue 'main;
521 }
522 _ => {
523 zipper.trigger_cache_miss();
524 }
525 };
526 }
527
528 // Choose one transformation from all applicable rules at this level
529 let (subtree, meta) = zipper.focus_and_meta();
530 let id = self.rule_groups.discriminant_fn.map(|f| f(subtree));
531
532 let rules = self.rule_groups.get_rules(level, id);
533 match Self::select_rule(
534 self.selector,
535 self.parallel,
536 subtree,
537 meta,
538 rules,
539 &self.event_handlers,
540 ) {
541 Some(selected) => {
542 if self.fixedpoint {
543 Self::apply_rule_fixedpoint(
544 &mut zipper,
545 &self.event_handlers,
546 &self.rule_groups,
547 self.selector,
548 self.parallel,
549 selected,
550 level,
551 );
552 } else {
553 Self::apply_rule(
554 &mut zipper,
555 &self.event_handlers,
556 selected,
557 level,
558 );
559 }
560 continue 'main;
561 }
562 None => {
563 trace!("Nothing Applicable");
564 if zipper.cache.is_active() {
565 let subtree = zipper.focus().clone();
566 zipper.cache.insert(&subtree, None, level);
567 }
568 zipper.set_dirty_from(level + 1);
569 }
570 }
571 }
572 }
573
574 // All rules have been tried with no more changes
575 break;
576 }
577
578 info!("Finished Morph");
579 zipper.into()
580 }
581
582 /// Exhaustively rewrites a tree using user-defined rule groups.
583 ///
584 /// It is not recommended to use this function besides testing and for correctness.
585 pub fn morph_naive(&mut self, tree: T, meta: M) -> (T, M)
586 where
587 T: Uniplate,
588 R: Rule<T, M>,
589 {
590 let mut zipper = NaiveZipper::new(
591 tree,
592 meta,
593 self.down_predicate,
594 &self.event_handlers,
595 &mut self.cache,
596 );
597 info!("Beginning Naive Morph");
598
599 'main: loop {
600 for level in 0..self.rule_groups.levels() {
601 loop {
602 {
603 let subtree = zipper.focus();
604 match zipper.cache.get(subtree, level) {
605 CacheResult::Terminal(_clean_level) => {
606 debug!("Cache Hit - Nothing Applicable");
607 zipper.trigger_cache_hit();
608 if zipper.get_next().is_none() {
609 break;
610 }
611 continue;
612 }
613 CacheResult::Rewrite(cached) => {
614 debug!("Cache Hit");
615 zipper.trigger_cache_hit();
616 zipper.replace_focus(cached);
617 zipper.map_ancestors_to_root(level);
618 continue 'main;
619 }
620 _ => {
621 zipper.trigger_cache_miss();
622 }
623 };
624 }
625
626 let (subtree, meta) = zipper.focus_and_meta();
627 let id = self.rule_groups.discriminant_fn.map(|f| f(subtree));
628 let rules = self.rule_groups.get_rules(level, id);
629 debug!("Checking Level {} with {} Rules", level, rules.len());
630 // Choose one transformation from all applicable rules at this level
631 let selected = Self::select_rule(
632 self.selector,
633 self.parallel,
634 subtree,
635 meta,
636 rules,
637 &self.event_handlers,
638 );
639
640 if let Some(selected) = selected {
641 if self.fixedpoint {
642 let stabilized = Self::apply_rule_naive_fixedpoint(
643 &mut zipper,
644 &self.event_handlers,
645 &self.rule_groups,
646 self.selector,
647 self.parallel,
648 selected,
649 level,
650 );
651 if !stabilized {
652 continue 'main;
653 }
654 // Stabilized — continue 'main to restart from root
655 continue 'main;
656 } else {
657 Self::apply_rule_naive(
658 &mut zipper,
659 &self.event_handlers,
660 selected,
661 level,
662 );
663 }
664 continue 'main;
665 } else {
666 debug!("Nothing Applicable");
667 if zipper.cache.is_active() {
668 let subtree = zipper.focus().clone();
669 zipper.cache.insert(&subtree, None, level);
670 }
671 }
672
673 if zipper.get_next().is_none() {
674 break;
675 }
676 }
677 }
678
679 break;
680 }
681
682 info!("Finished Naive Morph");
683 zipper.into_parts()
684 }
685}