Skip to main content

tree_morph/
engine_builder.rs

1//! A builder type for constructing and configuring [`Engine`] instances.
2
3use crate::cache::{NoCache, RewriteCache};
4use crate::engine::Engine;
5use crate::events::EventHandlers;
6use crate::helpers::{SelectorFn, select_first};
7use crate::prelude::Rule;
8use crate::rule::RuleGroups;
9
10use paste::paste;
11use uniplate::Uniplate;
12
13/// A builder type for constructing and configuring [`Engine`] instances.
14pub struct EngineBuilder<T, M, R, C>
15where
16    T: Uniplate + Send + Sync,
17    R: Rule<T, M> + Clone,
18    C: RewriteCache<T>,
19{
20    event_handlers: EventHandlers<T, M, R>,
21
22    /// Groups of rules, each with a selector function.
23    rule_groups: Vec<Vec<R>>,
24
25    selector: SelectorFn<T, M, R>,
26
27    cache: C,
28
29    discriminant_fn: Option<fn(&T) -> usize>,
30
31    parallel: bool,
32
33    fixedpoint: bool,
34
35    down_predicate: fn(&T) -> bool,
36}
37
38macro_rules! add_handler_fns {
39    (
40        directions: [$($dir:ident),*]
41    ) => {
42        paste! {$(
43            /// Register an event handler to be called before moving $dir in the tree.
44            pub fn [<add_before_ $dir>](mut self, handler: fn(&T, &mut M)) -> Self {
45                self.event_handlers.[<add_before_ $dir>](handler);
46                self
47            }
48
49            /// Register an event handler to be called after moving $dir one node in the tree.
50            pub fn [<add_after_ $dir>](mut self, handler: fn(&T, &mut M)) -> Self {
51                self.event_handlers.[<add_after_ $dir>](handler);
52                self
53            }
54        )*}
55    };
56}
57
58impl<T, M, R> EngineBuilder<T, M, R, NoCache>
59where
60    T: Uniplate + Send + Sync,
61    R: Rule<T, M> + Clone,
62{
63    /// Creates a new builder instance with the default [`select_first`] selector.
64    pub fn new() -> Self {
65        EngineBuilder {
66            event_handlers: EventHandlers::new(),
67            rule_groups: Vec::new(),
68            selector: select_first,
69            cache: NoCache,
70            discriminant_fn: None,
71            parallel: false,
72            fixedpoint: false,
73            down_predicate: |_| true,
74        }
75    }
76}
77
78impl<T, M, R, C> EngineBuilder<T, M, R, C>
79where
80    T: Uniplate + Send + Sync,
81    R: Rule<T, M> + Clone,
82    C: RewriteCache<T>,
83{
84    /// Consumes the builder and returns the constructed [`Engine`] instance.
85    pub fn build(self) -> Engine<T, M, R, C> {
86        Engine {
87            event_handlers: self.event_handlers,
88            rule_groups: RuleGroups::new(self.rule_groups, self.discriminant_fn),
89            selector: self.selector,
90            cache: self.cache,
91            parallel: self.parallel,
92            fixedpoint: self.fixedpoint,
93            down_predicate: self.down_predicate,
94        }
95    }
96
97    /// Adds a collection of rules with the same priority.
98    ///
99    /// These rules will have a lower priority than previously added groups.
100    pub fn add_rule_group(mut self, rules: Vec<R>) -> Self {
101        self.rule_groups.push(rules);
102        self
103    }
104
105    /// Adds a single rule in a group by itself.
106    ///
107    /// This is a special case of [`add_rule_group`](EngineBuilder::add_rule_group).
108    pub fn add_rule(self, rule: R) -> Self {
109        self.add_rule_group(vec![rule])
110    }
111
112    /// Adds a collection of rule groups to the existing one.
113    ///
114    /// Rule groups maintain the same order and will be lower priority than existing groups.
115    pub fn append_rule_groups(mut self, groups: Vec<Vec<R>>) -> Self {
116        self.rule_groups.extend(groups);
117        self
118    }
119
120    add_handler_fns! {
121        directions: [up, down, right]
122    }
123
124    /// Register an event handler to be called before attempting a rule
125    pub fn add_before_rule(mut self, handler: fn(&T, &mut M, &R)) -> Self {
126        self.event_handlers.add_before_rule(handler);
127        self
128    }
129
130    /// Register an event handler to be called after attempting a rule
131    /// The boolean signifies whether the rule is applicable
132    pub fn add_after_rule(mut self, handler: fn(&T, &mut M, &R, bool)) -> Self {
133        self.event_handlers.add_after_rule(handler);
134        self
135    }
136
137    /// Register an event handler to be called after applying a rule
138    pub fn add_after_apply(mut self, handler: fn(&T, &mut M, &R)) -> Self {
139        self.event_handlers.add_after_apply(handler);
140        self
141    }
142
143    /// Register an event handler to be called on a cache hit
144    pub fn add_on_cache_hit(mut self, handler: fn(&T, &mut M)) -> Self {
145        self.event_handlers.add_on_cache_hit(handler);
146        self
147    }
148
149    /// Register an event handler to be called on a cache miss
150    pub fn add_on_cache_miss(mut self, handler: fn(&T, &mut M)) -> Self {
151        self.event_handlers.add_on_cache_miss(handler);
152        self
153    }
154
155    /// Sets the selector function to be used when multiple rules are applicable to the same node.
156    ///
157    /// See the [`morph`](Engine::morph) method of the Engine type for more information.
158    pub fn set_selector(mut self, selector: SelectorFn<T, M, R>) -> Self {
159        self.selector = selector;
160        self
161    }
162
163    /// Adds caching support to tree-morph.
164    ///
165    /// Recommended to use [`HashMapCache`] as it has a concrete
166    /// implementation
167    pub fn add_cacher<Cache: RewriteCache<T>>(
168        self,
169        cacher: Cache,
170    ) -> EngineBuilder<T, M, R, Cache> {
171        EngineBuilder {
172            event_handlers: self.event_handlers,
173            rule_groups: self.rule_groups,
174            selector: self.selector,
175            cache: cacher,
176            discriminant_fn: self.discriminant_fn,
177            parallel: self.parallel,
178            fixedpoint: self.fixedpoint,
179            down_predicate: self.down_predicate,
180        }
181    }
182
183    /// Enables or disables parallel rule checking via Rayon.
184    ///
185    /// When enabled, all rules are tested in parallel using `par_iter`.
186    /// Defaults to `false`.
187    pub fn set_parallel(mut self, parallel: bool) -> Self {
188        self.parallel = parallel;
189        self
190    }
191
192    /// Sets the discriminant function used for rule prefiltering.
193    ///
194    /// When `Some(f)` is provided, `f` is called on each node to compute a unique `usize` id,
195    /// which is used to skip rules that do not apply to that node type.
196    /// When `None`, prefiltering is disabled and all rules are tried on every node.
197    pub fn set_discriminant_fn(mut self, discriminant_fn: Option<fn(&T) -> usize>) -> Self {
198        self.discriminant_fn = discriminant_fn;
199        self
200    }
201
202    /// Fixed-point application: after a rule fires, re-apply rules to the
203    /// transformed node until no more rules match, before continuing traversal.
204    pub fn set_fixedpoint(mut self, fixedpoint: bool) -> Self {
205        self.fixedpoint = fixedpoint;
206        self
207    }
208
209    /// Adds a predicate that controls whether the engine descends into a node's children.
210    pub fn add_down_predicate(mut self, predicate: fn(&T) -> bool) -> Self {
211        self.down_predicate = predicate;
212        self
213    }
214}
215
216impl<T, M, R> Default for EngineBuilder<T, M, R, NoCache>
217where
218    T: Uniplate + Send + Sync,
219    R: Rule<T, M> + Clone,
220{
221    fn default() -> Self {
222        Self::new()
223    }
224}
225
226impl<T, M, R, C> From<EngineBuilder<T, M, R, C>> for Engine<T, M, R, C>
227where
228    T: Uniplate + Send + Sync,
229    R: Rule<T, M> + Clone,
230    C: RewriteCache<T>,
231{
232    fn from(val: EngineBuilder<T, M, R, C>) -> Self {
233        val.build()
234    }
235}