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}