1
use crate::{Commands, Reduction, Rule};
2
use uniplate::Uniplate;
3

            
4
// TODO: (Felix) dirty/clean optimisation: replace tree with a custom tree structure,
5
//               which contains the original tree and adds metadata fields?
6

            
7
// TODO: (Felix) add logging via `log` crate; possibly need tree type to be Debug?
8
//               could be a crate feature?
9

            
10
// TODO: (Felix) add "control" rules; e.g. ignore a subtree to a certain depth?
11
//               test by ignoring everything once a metadata field is set? e.g. "reduce until contains X"
12

            
13
/// Exhaustively reduce a tree using a given transformation function.
14
///
15
/// The transformation function is called on each node in the tree (in left-most, outer-most order) along with
16
/// the metadata and a `Commands` object for side-effects.
17
///
18
/// - When the transformation function returns `Some(new_node)` for some node, that node is replaced with `new_node`.
19
///     Any side-effects are then applied at the root of the tree and the traversal begins again.
20
/// - Once the transformation function returns `None` for all nodes, the reduction is complete.
21
///
22
/// The `Commands` object is used to apply side-effects after a transformation is made.
23
/// This can be used to update metadata or perform arbitrary transformations on the entire tree,
24
/// which are reflected in the next traversal iteration.
25
///
26
/// # Parameters
27
/// - `transform`: A function which takes a mutable reference to a `Commands` object, a reference to the current node, and a reference to the metadata.
28
///               The function should return `Some(new_node)` if the node was transformed, or `None` otherwise.
29
/// - `tree`: The tree to reduce.
30
/// - `meta`: Metadata to be passed to the transformation function. This persists across all transformations.
31
///
32
/// # Returns
33
/// A tuple containing the reduced tree and the final metadata.
34
pub fn reduce<T, M, F>(transform: F, mut tree: T, mut meta: M) -> (T, M)
35
where
36
    T: Uniplate,
37
    F: Fn(&mut Commands<T, M>, &T, &M) -> Option<T>,
38
{
39
    // Apply the transformation to the tree until no more changes are made
40
    while let Some(mut reduction) = reduce_iteration(&transform, &tree, &meta) {
41
        // Apply reduction side-effects
42
        (tree, meta) = reduction.commands.apply(reduction.new_tree, meta);
43
    }
44
    (tree, meta)
45
}
46

            
47
fn reduce_iteration<T, M, F>(transform: &F, subtree: &T, meta: &M) -> Option<Reduction<T, M>>
48
where
49
    T: Uniplate,
50
    F: Fn(&mut Commands<T, M>, &T, &M) -> Option<T>,
51
{
52
    // Try to apply the transformation to the current node
53
    let reduction = Reduction::apply_transform(transform, subtree, meta);
54
    if reduction.is_some() {
55
        return reduction;
56
    }
57

            
58
    // Try to call the transformation on the children of the current node
59
    // If successful, return the new subtree
60
    let mut children = subtree.children();
61
    for c in children.iter_mut() {
62
        if let Some(reduction) = reduce_iteration(transform, c, meta) {
63
            *c = reduction.new_tree;
64
            return Some(Reduction {
65
                new_tree: subtree.with_children(children),
66
                ..reduction
67
            });
68
        }
69
    }
70

            
71
    None
72
}
73

            
74
/// Exhaustively reduce a tree by applying the given rules at each node.
75
///
76
/// Rules are applied in the order they are given. If multiple rules can be applied to a node,
77
/// the `select` function is used to choose which rule to apply.
78
///
79
/// `Reduction`s encapsulate the result of applying a rule at a given node, holding the resulting node
80
/// and any side-effects. An iterator of these objects (along with the rule they result from)
81
/// is given to the `select` function, and the one returned is applied to the tree as in the `reduce` function.
82
///
83
/// # Parameters
84
/// - `rules`: A slice of rules to apply to the tree.
85
/// - `select`: A function which takes the current node and an iterator of rule-`Reduction` pairs and returns the selected `Reduction`.
86
/// - `tree`: The tree to reduce.
87
/// - `meta`: Metadata to be passed to the transformation function. This persists across all transformations.
88
///
89
/// # Returns
90
/// A tuple containing the reduced tree and the final metadata.
91
pub fn reduce_with_rules<T, M, R, S>(rules: &[R], select: S, tree: T, meta: M) -> (T, M)
92
where
93
    T: Uniplate,
94
    R: Rule<T, M>,
95
    S: Fn(&T, &mut dyn Iterator<Item = (&R, Reduction<T, M>)>) -> Option<Reduction<T, M>>,
96
{
97
    reduce(
98
        |commands, subtree, meta| {
99
            let selection = select(
100
                subtree,
101
                &mut rules.iter().filter_map(|rule| {
102
                    Reduction::apply_transform(|c, t, m| rule.apply(c, t, m), subtree, meta)
103
                        .map(|r| (rule, r))
104
                }),
105
            );
106
            selection.map(|r| {
107
                // Ensure commands used by the engine are the ones resulting from this reduction
108
                *commands = r.commands;
109
                r.new_tree
110
            })
111
        },
112
        tree,
113
        meta,
114
    )
115
}