tree_morph/commands.rs
1//! Commands encode side-effects that will take place after a successful rule application.
2//!
3//! A rule may require some changes to the whole tree as part of its transformations, or a
4//! change to some associated data. These changes are applied after the node changed by the rule
5//! is updated.
6//!
7//! It may use [`Commands::transform`] to arbitrarily change entire tree after the rule has been
8//! applied. To change the global metadata in-place, it may use [`Commands::mut_meta`].
9
10use std::collections::VecDeque;
11use uniplate::Uniplate;
12
13enum Command<T: Uniplate, M> {
14 Transform(Box<dyn FnOnce(T) -> T + Send>),
15 MutMeta(Box<dyn FnOnce(&mut M) + Send>),
16}
17
18/// A queue of commands (side-effects) to be applied after a successful rule application.
19///
20/// A rule is given a mutable reference to a [`Commands`] and can use it to register side-effects.
21/// These side-effects are applied in order of registration **after** the rule itself updates
22/// a part of the tree.
23///
24/// # Application
25///
26/// A rule may not be applied due to different reasons, for example:
27/// - It does not return a new subtree (i.e. it returns `None`).
28/// - It returns a new subtree but the resulting [`Update`](crate::update::Update) is not chosen
29/// by the user-defined selector function. The function may select a different rule's update or
30/// no update at all.
31/// - It is part of a lower-priority rule group and a higher-priority rule is applied first.
32///
33/// In these cases, any side-effects which are registered by the rule are not applied and are
34/// dropped by the engine.
35///
36/// # Example
37/// ```rust
38/// use tree_morph::prelude::*;
39/// use uniplate::Uniplate;
40///
41/// #[derive(Debug, Clone, PartialEq, Eq, Uniplate)]
42/// #[uniplate()]
43/// enum Expr {
44/// A,
45/// B,
46/// C,
47/// }
48///
49/// fn rule(cmds: &mut Commands<Expr, bool>, subtree: &Expr, meta: &bool) -> Option<Expr> {
50/// cmds.transform(Box::new(|t| match t { // A pure transformation (no other side-effects)
51/// Expr::B => Expr::C,
52/// _ => t,
53/// }));
54/// cmds.mut_meta(Box::new(|m: &mut bool| *m = true)); // Set the metadata to 'true'
55///
56/// match subtree {
57/// Expr::A => Some(Expr::B),
58/// _ => None,
59/// }
60/// }
61///
62/// // Start with the expression 'A' and a metadata value of 'false'
63/// let mut engine = EngineBuilder::new()
64/// .add_rule_group(rule_fns![rule])
65/// .build();
66/// let (result, meta) = engine.morph(Expr::A, false);
67///
68/// // After applying the rule itself, the commands are applied in order
69/// assert_eq!(result, Expr::C);
70/// assert_eq!(meta, true);
71/// ```
72pub struct Commands<T: Uniplate, M> {
73 commands: VecDeque<Command<T, M>>,
74 has_transform: bool,
75}
76
77impl<T: Uniplate, M> Commands<T, M> {
78 pub(crate) fn new() -> Self {
79 Self {
80 commands: VecDeque::new(),
81 has_transform: false,
82 }
83 }
84
85 /// Registers a pure transformation of the whole tree.
86 ///
87 /// In this case, "pure" means that the transformation cannot register additional side-effects.
88 /// The transformation function is given ownership of the tree and should return the updated
89 /// tree.
90 ///
91 /// Side-effects are applied in order of registration after the rule is applied.
92 pub fn transform(&mut self, f: Box<dyn FnOnce(T) -> T + Send>) {
93 self.has_transform = true;
94 self.commands.push_back(Command::Transform(f));
95 }
96
97 /// Updates the global metadata in-place via a mutable reference.
98 ///
99 /// Side-effects are applied in order of registration after the rule is applied.
100 pub fn mut_meta(&mut self, f: Box<dyn FnOnce(&mut M) + Send>) {
101 self.commands.push_back(Command::MutMeta(f));
102 }
103
104 /// Removes all side-effects previously registered by the rule.
105 pub fn clear(&mut self) {
106 self.commands.clear();
107 }
108
109 /// Consumes and apply the side-effects currently in the queue.
110 pub(crate) fn apply(&mut self, mut tree: T, meta: &mut M) -> (T, bool) {
111 let mut transformed = false;
112 while let Some(cmd) = self.commands.pop_front() {
113 match cmd {
114 Command::Transform(f) => {
115 transformed = true;
116 tree = f(tree);
117 }
118 Command::MutMeta(f) => f(meta),
119 }
120 }
121 (tree, transformed)
122 }
123
124 pub(crate) fn has_transform(&self) -> bool {
125 self.has_transform
126 }
127}