1
use crate::{helpers::one_or_select, 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 transform a tree with the given list of functions.
14
///
15
/// Each transform function is applied to every node before the next function is tried.
16
/// When any change is made, the tree is updated and side-effects are applied before the process
17
/// restarts with the first transform function.
18
///
19
/// Once the last transform function makes no changes, this function returns the updated tree and metadata.
20
15
pub fn reduce<T, M, F>(transforms: &[F], mut tree: T, mut meta: M) -> (T, M)
21
15
where
22
15
    T: Uniplate,
23
15
    F: Fn(&mut Commands<T, M>, &T, &M) -> Option<T>,
24
15
{
25
15
    let mut new_tree = tree;
26
    'main: loop {
27
40
        tree = new_tree;
28
41
        for transform in transforms.iter() {
29
            // Try each transform on the entire tree before moving to the next
30
67
            for (node, ctx) in tree.contexts() {
31
67
                let red_opt = Reduction::apply_transform(transform, &node, &meta);
32

            
33
67
                if let Some(mut red) = red_opt {
34
25
                    (new_tree, meta) = red.commands.apply(ctx(red.new_tree), meta);
35
25

            
36
25
                    // Restart with the first transform every time a change is made
37
25
                    continue 'main;
38
42
                }
39
            }
40
        }
41
        // All transforms were attempted without change
42
15
        break;
43
15
    }
44
15
    (tree, meta)
45
15
}
46

            
47
/// Exhaustively transform a tree with the given list of rules.
48
///
49
/// If multiple rules apply to a node, the `select` function is used to choose which one to apply.
50
///
51
/// This is a special case of [`reduce_with_rule_groups`] with a single rule group.
52
6
pub fn reduce_with_rules<T, M, R, S>(rules: &[R], select: S, tree: T, meta: M) -> (T, M)
53
6
where
54
6
    T: Uniplate,
55
6
    R: Rule<T, M>,
56
6
    S: Fn(&T, &mut dyn Iterator<Item = (&R, Reduction<T, M>)>) -> Option<Reduction<T, M>>,
57
6
{
58
6
    reduce_with_rule_groups(&[rules], select, tree, meta)
59
6
}
60

            
61
/// Exhaustively transform a tree with the given list of rule groups.
62
/// A 'rule group' represents a higher-priority set of rules which are applied to the entire tree before subsequent groups.
63
///
64
/// If multiple rules apply to a node, the `select` function is used to choose which one to apply.
65
///
66
/// This is an abstraction over [`reduce`], where each transform function attempts a rule group on each node.
67
8
pub fn reduce_with_rule_groups<T, M, R, S>(groups: &[&[R]], select: S, tree: T, meta: M) -> (T, M)
68
8
where
69
8
    T: Uniplate,
70
8
    R: Rule<T, M>,
71
8
    S: Fn(&T, &mut dyn Iterator<Item = (&R, Reduction<T, M>)>) -> Option<Reduction<T, M>>,
72
8
{
73
8
    let transforms: Vec<_> = groups
74
8
        .iter()
75
9
        .map(|group| {
76
46
            |commands: &mut Commands<T, M>, subtree: &T, meta: &M| {
77
201
                let applicable = &mut group.iter().filter_map(|rule| {
78
201
                    Reduction::apply_transform(|c, t, m| rule.apply(c, t, m), subtree, meta)
79
201
                        .map(|r| (rule, r))
80
201
                });
81
46
                let selection = one_or_select(&select, subtree, applicable);
82
46
                selection.map(|r| {
83
17
                    // Ensure commands used by the engine are the ones resulting from this reduction
84
17
                    *commands = r.commands;
85
17
                    r.new_tree
86
46
                })
87
46
            }
88
9
        })
89
8
        .collect();
90
8
    reduce(&transforms, tree, meta)
91
8
}