1
use std::cmp::min;
2
use std::fmt;
3

            
4
use crate::utilities::ExplicitPosition;
5
use ahash::HashMap;
6
use ahash::HashMapExt;
7
use log::trace;
8
use smallvec::SmallVec;
9

            
10
use super::MatchAnnouncement;
11
use super::MatchObligation;
12

            
13
/// A match goal contains a number of obligations (positions that must still be
14
/// matched) and the corresponding rule that can be announced as being a match.
15
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
16
pub(crate) struct MatchGoal {
17
    pub obligations: Vec<MatchObligation>,
18
    pub announcement: MatchAnnouncement,
19
}
20

            
21
impl MatchGoal {
22
    /// Derive the greatest common prefix of the announcement and obligation positions
23
    /// of a list of match goals.
24
236080
    pub fn greatest_common_prefix(goals: &Vec<MatchGoal>) -> ExplicitPosition {
25
236080
        // gcp is empty if there are no match goals
26
236080
        if goals.is_empty() {
27
            return ExplicitPosition::empty_pos();
28
236080
        }
29
236080

            
30
236080
        // Initialise the prefix with the first match goal, can only shrink afterwards
31
236080
        let first_match_pos = &goals.first().unwrap().announcement.position;
32
236080
        let mut gcp_length = first_match_pos.len();
33
236080
        let prefix = &first_match_pos.clone();
34

            
35
13273150
        for g in goals {
36
            // Compare up to gcp_length or the length of the announcement position
37
13037070
            let compare_length = min(gcp_length, g.announcement.position.len());
38
13037070
            // gcp_length shrinks if they are not the same up to compare_length
39
13037070
            gcp_length = MatchGoal::common_prefix_length(
40
13037070
                &prefix.indices[0..compare_length],
41
13037070
                &g.announcement.position.indices[0..compare_length],
42
13037070
            );
43

            
44
26203525
            for mo in &g.obligations {
45
13166455
                // Compare up to gcp_length or the length of the match obligation position
46
13166455
                let compare_length = min(gcp_length, mo.position.len());
47
13166455
                // gcp_length shrinks if they are not the same up to compare_length
48
13166455
                gcp_length = MatchGoal::common_prefix_length(
49
13166455
                    &prefix.indices[0..compare_length],
50
13166455
                    &mo.position.indices[0..compare_length],
51
13166455
                );
52
13166455
            }
53
        }
54
        // The gcp is constructed by taking the first gcp_length indices of the first match goal prefix
55
236080
        let greatest_common_prefix = SmallVec::from_slice(&prefix.indices[0..gcp_length]);
56
236080
        ExplicitPosition {
57
236080
            indices: greatest_common_prefix,
58
236080
        }
59
236080
    }
60

            
61
    /// Removes the first len position indices of the match goal and obligation positions
62
236080
    pub fn remove_prefix(mut goals: Vec<MatchGoal>, len: usize) -> Vec<MatchGoal> {
63
13273150
        for goal in &mut goals {
64
            // update match announcement
65
13037070
            goal.announcement.position = ExplicitPosition {
66
13037070
                indices: SmallVec::from_slice(&goal.announcement.position.indices[len..]),
67
13037070
            };
68
13166455
            for mo_index in 0..goal.obligations.len() {
69
13166455
                let shortened = ExplicitPosition {
70
13166455
                    indices: SmallVec::from_slice(&goal.obligations.get(mo_index).unwrap().position.indices[len..]),
71
13166455
                };
72
13166455
                goal.obligations.get_mut(mo_index).unwrap().position = shortened;
73
13166455
            }
74
        }
75
236080
        goals
76
236080
    }
77

            
78
    /// Returns a Vec where each element is a partition containing the goals and
79
    /// the positions. This partitioning can be done in multiple ways, but
80
    /// currently match goals are equivalent when their match announcements have
81
    /// a comparable position.
82
174010
    pub fn partition(goals: Vec<MatchGoal>) -> Vec<(Vec<MatchGoal>, Vec<ExplicitPosition>)> {
83
174010
        let mut partitions = vec![];
84
174010

            
85
174010
        trace!("=== partition(match_goals = [ ===");
86
13211080
        for mg in &goals {
87
13037070
            trace!("\t {}", mg);
88
        }
89
174010
        trace!("]");
90

            
91
        // If one of the goals has a root position all goals are related.
92
12812780
        partitions = if goals.iter().any(|g| g.announcement.position.is_empty()) {
93
7735
            let mut all_positions = Vec::new();
94
306805
            for g in &goals {
95
299070
                if !all_positions.contains(&g.announcement.position) {
96
18405
                    all_positions.push(g.announcement.position.clone())
97
280665
                }
98
            }
99
7735
            partitions.push((goals, all_positions));
100
7735
            partitions
101
        } else {
102
            // Create a mapping from positions to goals, goals are represented with an index
103
            // on function parameter goals
104
166275
            let mut position_to_goals = HashMap::new();
105
12738000
            for (i, g) in goals.iter().enumerate() {
106
12738000
                if !position_to_goals.contains_key(&g.announcement.position) {
107
348370
                    position_to_goals.insert(g.announcement.position.clone(), vec![i]);
108
12389630
                } else {
109
12389630
                    let vec = position_to_goals.get_mut(&g.announcement.position).unwrap();
110
12389630
                    vec.push(i);
111
12389630
                }
112
            }
113

            
114
            // Sort the positions. They are now in depth first order.
115
166275
            let mut all_positions: Vec<ExplicitPosition> = position_to_goals.keys().cloned().collect();
116
166275
            all_positions.sort_unstable();
117
166275

            
118
166275
            // Compute the partitions, finished when all positions are processed
119
166275
            let mut p_index = 0; // position index
120
394620
            while p_index < all_positions.len() {
121
                // Start the partition with a position
122
228345
                let p = &all_positions[p_index];
123
228345
                let mut pos_in_partition = vec![p.clone()];
124
228345
                let mut goals_in_partition = vec![];
125
228345

            
126
228345
                // put the goals with position p in the partition
127
228345
                let g = position_to_goals.get(p).unwrap();
128
7965230
                for i in g {
129
7736885
                    goals_in_partition.push(goals[*i].clone());
130
7736885
                }
131

            
132
                // Go over the positions until we find a position that is not comparable to p
133
                // Because all_positions is sorted we know that once we find a position that is not comparable
134
                // all subsequent positions will also not be comparable.
135
                // Moreover, all positions in the partition are related to p. p is the highest in the partition.
136
228345
                p_index += 1;
137
348370
                while p_index < all_positions.len() && MatchGoal::pos_comparable(p, &all_positions[p_index]) {
138
120025
                    pos_in_partition.push(all_positions[p_index].clone());
139
120025
                    // Put the goals with position all_positions[p_index] in the partition
140
120025
                    let g = position_to_goals.get(&all_positions[p_index]).unwrap();
141
5121140
                    for i in g {
142
5001115
                        goals_in_partition.push(goals[*i].clone());
143
5001115
                    }
144
120025
                    p_index += 1;
145
                }
146

            
147
228345
                partitions.push((goals_in_partition, pos_in_partition));
148
            }
149

            
150
166275
            partitions
151
        };
152

            
153
410090
        for (goals, pos) in &partitions {
154
236080
            trace!("pos {{");
155
602855
            for mg in pos {
156
366775
                trace!("\t {}", mg);
157
            }
158
236080
            trace!("}} -> {{");
159
13273150
            for mg in goals {
160
13037070
                trace!("\t {}", mg);
161
            }
162
236080
            trace!("}}");
163
        }
164

            
165
174010
        partitions
166
174010
    }
167

            
168
    // Assumes two slices are of the same length and computes to what length they are equal
169
26203525
    fn common_prefix_length(pos1: &[usize], pos2: &[usize]) -> usize {
170
26203525
        debug_assert_eq!(pos1.len(), pos2.len(), "Given arrays should be of the same length.");
171

            
172
26203525
        let mut common_length = 0;
173
35608285
        for i in 0..pos1.len() {
174
35608285
            if pos1.get(i).unwrap() == pos2.get(i).unwrap() {
175
35608225
                common_length += 1;
176
35608225
            } else {
177
60
                break;
178
            }
179
        }
180
26203525
        common_length
181
26203525
    }
182

            
183
    /// Checks for two positions whether one is a subposition of the other.
184
    /// For example 2.2.3 and 2 are comparable. 2.2.3 and 1 are not.
185
615785
    pub fn pos_comparable(p1: &ExplicitPosition, p2: &ExplicitPosition) -> bool {
186
615785
        let mut index = 0;
187
        loop {
188
921535
            if p1.len() == index || p2.len() == index {
189
210315
                return true;
190
711220
            }
191
711220

            
192
711220
            if p1.indices[index] != p2.indices[index] {
193
405470
                return false;
194
305750
            }
195
305750
            index += 1;
196
        }
197
615785
    }
198
}
199

            
200
impl fmt::Display for MatchGoal {
201
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202
        let mut first = true;
203
        for obligation in &self.obligations {
204
            if !first {
205
                write!(f, ", ")?;
206
            }
207
            write!(f, "{}", obligation)?;
208
            first = false;
209
        }
210

            
211
        write!(f, " ↪ {}", self.announcement)
212
    }
213
}