1
#![forbid(unsafe_code)]
2

            
3
use std::fmt;
4

            
5
use itertools::Itertools;
6
use merc_data::DataVariable;
7
use merc_data::DataVariableRef;
8
use merc_data::is_data_variable;
9

            
10
use crate::Rule;
11
use crate::utilities::DataPosition;
12
use crate::utilities::DataPositionIndexed;
13
use crate::utilities::DataPositionIterator;
14

            
15
/// An equivalence class is a variable with (multiple) positions. This is
16
/// necessary for non-linear patterns.
17
///
18
/// # Example
19
/// Suppose we have a pattern f(x,x), where x is a variable. Then it will have
20
/// one equivalence class storing "x" and the positions 1 and 2. The function
21
/// equivalences_hold checks whether the term has the same term on those
22
/// positions. For example, it will returns false on the term f(a, b) and true
23
/// on the term f(a, a).
24
#[derive(Hash, Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
25
pub struct EquivalenceClass {
26
    pub variable: DataVariable,
27
    pub positions: Vec<DataPosition>,
28
}
29

            
30
/// Derives the positions in a pattern with same variable (for non-linear patters)
31
46693
pub fn derive_equivalence_classes(rule: &Rule) -> Vec<EquivalenceClass> {
32
46693
    let mut var_equivalences = vec![];
33

            
34
98695
    for (term, pos) in DataPositionIterator::new(rule.lhs.copy()) {
35
98695
        if is_data_variable(&term) {
36
43304
            // Register the position of the variable
37
43304
            update_equivalences(&mut var_equivalences, &DataVariableRef::from(term), pos);
38
55391
        }
39
    }
40

            
41
    // Discard variables that only occur once
42
46693
    var_equivalences.retain(|x| x.positions.len() > 1);
43
46693
    var_equivalences
44
46693
}
45

            
46
/// Checks if the equivalence classes hold for the given term.
47
3394756
pub fn check_equivalence_classes<'a, T, P>(term: &'a P, eqs: &[EquivalenceClass]) -> bool
48
3394756
where
49
3394756
    P: DataPositionIndexed<'a, Target<'a> = T> + 'a,
50
3394756
    T: PartialEq,
51
{
52
3394756
    eqs.iter().all(|ec| {
53
7
        debug_assert!(
54
7
            ec.positions.len() >= 2,
55
            "An equivalence class must contain at least two positions"
56
        );
57

            
58
        // The term at the first position must be equivalent to all other positions.
59
7
        let mut iter_pos = ec.positions.iter();
60
7
        let first = term.get_data_position(iter_pos.next().unwrap());
61
7
        iter_pos.all(|other_pos| first == term.get_data_position(other_pos))
62
7
    })
63
3394756
}
64

            
65
/// Adds the position of a variable to the equivalence classes
66
43304
fn update_equivalences(ve: &mut Vec<EquivalenceClass>, variable: &DataVariableRef<'_>, pos: DataPosition) {
67
    // Check if the variable was seen before
68
43304
    if ve.iter().any(|ec| ec.variable.copy() == *variable) {
69
7
        for ec in ve.iter_mut() {
70
            // Find the equivalence class and add the position
71
7
            if ec.variable.copy() == *variable && !ec.positions.iter().any(|x| x == &pos) {
72
7
                ec.positions.push(pos);
73
7
                break;
74
            }
75
        }
76
43297
    } else {
77
43297
        // If the variable was not found at another position add a new equivalence class
78
43297
        ve.push(EquivalenceClass {
79
43297
            variable: variable.protect(),
80
43297
            positions: vec![pos],
81
43297
        });
82
43297
    }
83
43304
}
84

            
85
impl fmt::Display for EquivalenceClass {
86
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87
        write!(f, "{}{{ {} }}", self.variable, self.positions.iter().format(", "))
88
    }
89
}
90

            
91
#[cfg(test)]
92
mod tests {
93
    use merc_data::DataExpression;
94
    use merc_data::DataVariable;
95

            
96
    use crate::test_utility::create_rewrite_rule;
97

            
98
    use super::DataPosition;
99
    use super::EquivalenceClass;
100
    use super::check_equivalence_classes;
101
    use super::derive_equivalence_classes;
102

            
103
    #[test]
104
1
    fn test_derive_equivalence_classes() {
105
1
        let eq: Vec<EquivalenceClass> =
106
1
            derive_equivalence_classes(&create_rewrite_rule("f(x, h(x))", "result", &["x"]).unwrap());
107

            
108
1
        assert_eq!(
109
            eq,
110
1
            vec![EquivalenceClass {
111
1
                variable: DataVariable::new("x"),
112
1
                positions: vec![DataPosition::new(&[1]), DataPosition::new(&[2, 1])]
113
1
            },],
114
            "The resulting config stack is not as expected"
115
        );
116

            
117
        // Check the equivalence class for an example
118
1
        let expression = DataExpression::from_string("f(a(b), h(a(b)))").unwrap();
119

            
120
1
        assert!(
121
1
            check_equivalence_classes(&expression, &eq),
122
            "The equivalence classes are not checked correctly, equivalences: {:?} and term {}",
123
            &eq,
124
            &expression
125
        );
126
1
    }
127
}