1
use std::collections::VecDeque;
2
use std::fmt::Debug;
3
use std::time::Instant;
4

            
5
use ahash::HashMap;
6
use log::debug;
7
use log::info;
8
use log::log_enabled;
9
use log::trace;
10
use log::warn;
11
use merc_aterm::Term;
12
use merc_data::DataExpression;
13
use merc_data::DataExpressionRef;
14
use merc_data::DataFunctionSymbol;
15
use merc_data::is_data_application;
16
use merc_data::is_data_function_symbol;
17
use merc_data::is_data_machine_number;
18
use merc_data::is_data_variable;
19
use rustc_hash::FxHashMap;
20
use smallvec::SmallVec;
21
use smallvec::smallvec;
22

            
23
use crate::rewrite_specification::RewriteSpecification;
24
use crate::rewrite_specification::Rule;
25
use crate::utilities::DataPosition;
26

            
27
use super::DotFormatter;
28
use super::MatchGoal;
29

            
30
/// The Set Automaton used to find all matching patterns in a term.
31
pub struct SetAutomaton<T> {
32
    states: Vec<State>,
33
    transitions: FxHashMap<(usize, usize), Transition<T>>,
34
}
35

            
36
/// A match announcement contains the rule that can be announced as a match at
37
/// the given position.
38
///
39
/// `symbols_seen` is internally used to keep track of how many symbols have
40
/// been observed so far. Since these symbols have a unique number this can be
41
/// used to speed up certain operations.
42
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
43
pub struct MatchAnnouncement {
44
    pub rule: Rule,
45
    pub position: DataPosition,
46
    pub symbols_seen: usize,
47
}
48

            
49
/// Represents a transition in the [SetAutomaton].
50
#[derive(Clone)]
51
pub struct Transition<T> {
52
    pub symbol: DataFunctionSymbol,
53
    pub announcements: SmallVec<[(MatchAnnouncement, T); 1]>,
54
    pub destinations: SmallVec<[(DataPosition, usize); 1]>,
55
}
56

            
57
/// Represents a match obligation in the [SetAutomaton].
58
#[derive(Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
59
pub struct MatchObligation {
60
    pub pattern: DataExpression,
61
    pub position: DataPosition,
62
}
63

            
64
impl MatchObligation {
65
    /// Returns the pattern of the match obligation
66
3232872
    pub fn new(pattern: DataExpression, position: DataPosition) -> Self {
67
3232872
        MatchObligation { pattern, position }
68
3232872
    }
69
}
70

            
71
/// Represents either the initial state or a set of match goals in the
72
/// [SetAutomaton].
73
///
74
/// This is only used during construction to avoid craating all the goals for
75
/// the initial state.
76
#[derive(Debug)]
77
enum GoalsOrInitial {
78
    InitialState,
79
    Goals(Vec<MatchGoal>),
80
}
81

            
82
impl<M> SetAutomaton<M> {
83
    /// Creates a new SetAutomaton from the given rewrite specification. If
84
    /// `apma` is true an Adaptive Pattern Matching Automaton is created,
85
    /// meaning that it only finds matches at the root position.
86
    ///
87
    /// The `annotate` function is used to create the annotation for each match
88
    /// announcement. This is used to accomondate different types of annotations
89
    /// for the different rewrite engines.
90
186
    pub fn new<F>(spec: &RewriteSpecification, annotate: F, apma: bool) -> SetAutomaton<M>
91
186
    where
92
186
        F: Fn(&Rule) -> M,
93
    {
94
186
        let start = Instant::now();
95

            
96
        // States are labelled s0, s1, s2, etcetera. state_counter keeps track of count.
97
186
        let mut state_counter: usize = 1;
98

            
99
        // Remove rules that we cannot deal with
100
186
        let supported_rules: Vec<Rule> = spec
101
186
            .rewrite_rules()
102
186
            .iter()
103
4710
            .filter(|rule| is_supported_rule(rule))
104
186
            .map(Rule::clone)
105
186
            .collect();
106

            
107
        // Find the indices of all the function symbols.
108
186
        let symbols = {
109
186
            let mut symbols = HashMap::default();
110

            
111
4710
            for rule in &supported_rules {
112
4710
                find_symbols(&rule.lhs.copy(), &mut symbols);
113
4710
                find_symbols(&rule.rhs.copy(), &mut symbols);
114

            
115
4710
                for cond in &rule.conditions {
116
486
                    find_symbols(&cond.lhs.copy(), &mut symbols);
117
486
                    find_symbols(&cond.rhs.copy(), &mut symbols);
118
486
                }
119
            }
120

            
121
186
            symbols
122
        };
123

            
124
3708
        for (index, (symbol, arity)) in symbols.iter().enumerate() {
125
3708
            trace!("{index}: {symbol} {arity}");
126
        }
127

            
128
        // The initial state has a match goals for each pattern. For each pattern l there is a match goal
129
        // with one obligation l@ε and announcement l@ε.
130
186
        let mut initial_match_goals = Vec::<MatchGoal>::new();
131
4710
        for rr in &supported_rules {
132
4710
            initial_match_goals.push(MatchGoal::new(
133
4710
                MatchAnnouncement {
134
4710
                    rule: (*rr).clone(),
135
4710
                    position: DataPosition::empty(),
136
4710
                    symbols_seen: 0,
137
4710
                },
138
4710
                vec![MatchObligation::new(rr.lhs.clone(), DataPosition::empty())],
139
4710
            ));
140
4710
        }
141

            
142
        // Match goals need to be sorted so that we can easily check whether a state with a certain
143
        // set of match goals already exists.
144
186
        initial_match_goals.sort();
145

            
146
        // Create the initial state
147
186
        let initial_state = State {
148
186
            label: DataPosition::empty(),
149
186
            match_goals: initial_match_goals.clone(),
150
186
        };
151

            
152
        // HashMap from goals to state number
153
186
        let mut map_goals_state = HashMap::default();
154

            
155
        // Queue of states that still need to be explored
156
186
        let mut queue = VecDeque::new();
157
186
        queue.push_back(0);
158

            
159
186
        map_goals_state.insert(initial_match_goals, 0);
160

            
161
186
        let mut states = vec![initial_state];
162
186
        let mut transitions = FxHashMap::default();
163

            
164
        // Pick a state to explore
165
3891
        while let Some(s_index) = queue.pop_front() {
166
146049
            for (symbol, arity) in &symbols {
167
146049
                let (mut announcements, pos_to_goals) =
168
146049
                    states
169
146049
                        .get(s_index)
170
146049
                        .unwrap()
171
146049
                        .derive_transition(symbol, *arity, &supported_rules, apma);
172

            
173
146049
                announcements.sort_by(|ma1, ma2| ma1.position.cmp(&ma2.position));
174

            
175
                // For the destinations we convert the match goal destinations to states
176
146049
                let mut destinations = smallvec![];
177

            
178
                // Loop over the hypertransitions
179
184419
                for (pos, goals_or_initial) in pos_to_goals {
180
                    // Match goals need to be sorted so that we can easily check whether a state with a certain
181
                    // set of match goals already exists.
182
184419
                    if let GoalsOrInitial::Goals(goals) = goals_or_initial {
183
                        // This code cannot be replaced by the entry since contains_key takes a reference.
184
                        #[allow(clippy::map_entry)]
185
142371
                        if map_goals_state.contains_key(&goals) {
186
                            // The destination state already exists
187
138852
                            destinations.push((pos, *map_goals_state.get(&goals).unwrap()))
188
3519
                        } else if !goals.is_empty() {
189
3519
                            // The destination state does not yet exist, create it
190
3519
                            let new_state = State::new(goals.clone());
191
3519
                            states.push(new_state);
192
3519
                            destinations.push((pos, state_counter));
193
3519
                            map_goals_state.insert(goals, state_counter);
194
3519
                            queue.push_back(state_counter);
195
3519
                            state_counter += 1;
196
3519
                        }
197
42048
                    } else {
198
42048
                        // The transition is to the initial state
199
42048
                        destinations.push((pos, 0));
200
42048
                    }
201
                }
202

            
203
                // Add the annotation for every match announcement.
204
146049
                let announcements = announcements
205
146049
                    .into_iter()
206
146049
                    .map(|ma| {
207
46692
                        let annotation = annotate(&ma.rule);
208
46692
                        (ma, annotation)
209
46692
                    })
210
146049
                    .collect();
211

            
212
                // Add the resulting outgoing transition to the state.
213
146049
                debug_assert!(
214
146049
                    !&transitions.contains_key(&(s_index, symbol.operation_id())),
215
                    "Set automaton should not contain duplicated transitions"
216
                );
217
146049
                transitions.insert(
218
146049
                    (s_index, symbol.operation_id()),
219
146049
                    Transition {
220
146049
                        symbol: symbol.clone(),
221
146049
                        announcements,
222
146049
                        destinations,
223
146049
                    },
224
                );
225
            }
226

            
227
3705
            debug!(
228
                "Queue size {}, currently {} states and {} transitions",
229
                queue.len(),
230
                states.len(),
231
                transitions.len()
232
            );
233
        }
234

            
235
        // Clear the match goals since they are only for debugging purposes.
236
186
        if !log_enabled!(log::Level::Debug) {
237
3705
            for state in &mut states {
238
3705
                state.match_goals.clear();
239
3705
            }
240
        }
241
186
        info!(
242
            "Created set automaton (states: {}, transitions: {}, apma: {}) in {} ms",
243
3
            states.len(),
244
3
            transitions.len(),
245
            apma,
246
3
            (Instant::now() - start).as_millis()
247
        );
248

            
249
186
        let result = SetAutomaton { states, transitions };
250
186
        debug!("{result:?}");
251

            
252
186
        result
253
186
    }
254

            
255
    /// Returns the number of states
256
    pub fn num_of_states(&self) -> usize {
257
        self.states.len()
258
    }
259

            
260
    /// Returns the number of transitions
261
    pub fn num_of_transitions(&self) -> usize {
262
        self.transitions.len()
263
    }
264

            
265
    /// Returns the states of the automaton
266
41433120
    pub fn states(&self) -> &[State] {
267
41433120
        &self.states
268
41433120
    }
269

            
270
    /// Returns the transition for the given state and function symbol operation
271
    /// id, if one exists.
272
37930182
    pub fn get_transition(&self, state: usize, operation_id: usize) -> Option<&Transition<M>> {
273
37930182
        self.transitions.get(&(state, operation_id))
274
37930182
    }
275

            
276
    /// Iterates over all transitions as `(state, operation_id, transition)` tuples.
277
3
    pub fn iter_transitions(&self) -> impl Iterator<Item = (usize, usize, &Transition<M>)> {
278
3
        self.transitions
279
3
            .iter()
280
60
            .map(|(&(state, operation_id), transition)| (state, operation_id, transition))
281
3
    }
282

            
283
    /// Provides a formatter for the .dot file format
284
    pub fn to_dot_graph(&self, show_backtransitions: bool, show_final: bool) -> DotFormatter<'_, M> {
285
        DotFormatter {
286
            automaton: self,
287
            show_backtransitions,
288
            show_final,
289
        }
290
    }
291
}
292

            
293
#[derive(Debug)]
294
pub struct Derivative {
295
    pub completed: Vec<MatchGoal>,
296
    pub unchanged: Vec<MatchGoal>,
297
    pub reduced: Vec<MatchGoal>,
298
}
299

            
300
pub struct State {
301
    label: DataPosition,
302
    match_goals: Vec<MatchGoal>,
303
}
304

            
305
impl State {
306
    /// Derive transitions from a state given a head symbol. The resulting transition is returned as a tuple
307
    /// The tuple consists of a vector of outputs and a set of destinations (which are sets of match goals).
308
    /// We don't use the struct Transition as it requires that the destination is a full state, with name.
309
    /// Since we don't yet know whether the state already exists we just return a set of match goals as 'state'.
310
    ///
311
    /// Parameter symbol is the symbol for which the transition is computed
312
146049
    fn derive_transition(
313
146049
        &self,
314
146049
        symbol: &DataFunctionSymbol,
315
146049
        arity: usize,
316
146049
        rewrite_rules: &[Rule],
317
146049
        apma: bool,
318
146049
    ) -> (Vec<MatchAnnouncement>, Vec<(DataPosition, GoalsOrInitial)>) {
319
        // Computes the derivative containing the goals that are completed, unchanged and reduced
320
146049
        let mut derivative = self.compute_derivative(symbol, arity);
321

            
322
        // The outputs/matching patterns of the transitions are those who are completed
323
146049
        let outputs = derivative.completed.into_iter().map(|x| x.announcement).collect();
324

            
325
        // The new match goals are the unchanged and reduced match goals.
326
146049
        let mut new_match_goals = derivative.unchanged;
327
146049
        new_match_goals.append(&mut derivative.reduced);
328

            
329
146049
        let mut destinations = vec![];
330
        // If we are building an APMA we do not deepen the position or create a hypertransitions
331
        // with multiple endpoints
332
146049
        if apma {
333
42747
            if !new_match_goals.is_empty() {
334
2415
                destinations.push((DataPosition::empty(), GoalsOrInitial::Goals(new_match_goals)));
335
40332
            }
336
        } else {
337
            // In case we are building a set automaton we partition the match goals
338
103302
            let partitioned = MatchGoal::partition(new_match_goals);
339

            
340
            // Get the greatest common prefix and shorten the positions
341
103302
            let mut positions_per_partition = vec![];
342
103302
            let mut gcp_length_per_partition = vec![];
343
139956
            for (p, pos) in partitioned {
344
139956
                positions_per_partition.push(pos);
345
139956
                let gcp = MatchGoal::greatest_common_prefix(&p);
346
139956
                let gcp_length = gcp.len();
347
139956
                gcp_length_per_partition.push(gcp_length);
348
139956
                let mut goals = MatchGoal::remove_prefix(p, gcp_length);
349
139956
                goals.sort_unstable();
350
139956
                destinations.push((gcp, GoalsOrInitial::Goals(goals)));
351
139956
            }
352

            
353
            // Handle fresh match goals, they are the positions Label(state).i
354
            // where i is between 1 and the arity of the function symbol of
355
            // the transition. Position 1 is the first argument.
356
103302
            for i in 1..arity + 1 {
357
95424
                let mut pos = self.label.clone();
358
95424
                pos.push(i);
359

            
360
                // Check if the fresh goals are related to one of the existing partitions
361
95424
                let mut partition_key = None;
362
158085
                'outer: for (i, part_pos) in positions_per_partition.iter().enumerate() {
363
238761
                    for p in part_pos {
364
238761
                        if MatchGoal::pos_comparable(p, &pos) {
365
53376
                            partition_key = Some(i);
366
53376
                            break 'outer;
367
185385
                        }
368
                    }
369
                }
370

            
371
95424
                if let Some(key) = partition_key {
372
                    // If the fresh goals fall in an existing partition
373
53376
                    let gcp_length = gcp_length_per_partition[key];
374
53376
                    let pos = DataPosition::new(&pos.indices()[gcp_length..]);
375

            
376
                    // Add the fresh goals to the partition
377
3228162
                    for rr in rewrite_rules {
378
3228162
                        if let GoalsOrInitial::Goals(goals) = &mut destinations[key].1 {
379
3228162
                            goals.push(MatchGoal {
380
3228162
                                obligations: vec![MatchObligation::new(rr.lhs.clone(), pos.clone())],
381
3228162
                                announcement: MatchAnnouncement {
382
3228162
                                    rule: (*rr).clone(),
383
3228162
                                    position: pos.clone(),
384
3228162
                                    symbols_seen: 0,
385
3228162
                                },
386
3228162
                            });
387
3228162
                        }
388
                    }
389
42048
                } else {
390
42048
                    // The transition is simply to the initial state
391
42048
                    // GoalsOrInitial::InitialState avoids unnecessary work of creating all these fresh goals
392
42048
                    destinations.push((pos, GoalsOrInitial::InitialState));
393
42048
                }
394
            }
395
        }
396

            
397
        // Sort the destination such that transitions which do not deepen the position are listed first
398
146049
        destinations.sort_unstable_by(|(pos1, _), (pos2, _)| pos1.cmp(pos2));
399
146049
        (outputs, destinations)
400
146049
    }
401

            
402
    /// For a transition 'symbol' of state 'self' this function computes which match goals are
403
    /// completed, unchanged and reduced.
404
146049
    fn compute_derivative(&self, symbol: &DataFunctionSymbol, arity: usize) -> Derivative {
405
146049
        let mut result = Derivative {
406
146049
            completed: vec![],
407
146049
            unchanged: vec![],
408
146049
            reduced: vec![],
409
146049
        };
410

            
411
15414231
        for mg in &self.match_goals {
412
15414231
            debug_assert!(
413
15414231
                !mg.obligations.is_empty(),
414
                "The obligations should never be empty, should be completed then"
415
            );
416

            
417
            // Completed match goals
418
15414231
            if mg.obligations.len() == 1
419
15332931
                && mg.obligations.iter().any(|mo| {
420
15332931
                    mo.position == self.label
421
7695534
                        && mo.pattern.data_function_symbol() == symbol.copy()
422
145392
                        && mo.pattern.data_arguments().all(|x| is_data_variable(&x))
423
                    // Again skip the function symbol
424
15332931
                })
425
46692
            {
426
46692
                result.completed.push(mg.clone());
427
15367539
            } else if mg
428
15367539
                .obligations
429
15367539
                .iter()
430
15416772
                .any(|mo| mo.position == self.label && mo.pattern.data_function_symbol() != symbol.copy())
431
7593486
            {
432
7593486
                // Match goal is discarded since head symbol does not match.
433
7822083
            } else if mg.obligations.iter().all(|mo| mo.position != self.label) {
434
                // Unchanged match goals
435
7682907
                let mut mg = mg.clone();
436
7682907
                if mg.announcement.rule.lhs != mg.obligations.first().unwrap().pattern {
437
104607
                    mg.announcement.symbols_seen += 1;
438
7578300
                }
439

            
440
7682907
                result.unchanged.push(mg.clone());
441
            } else {
442
                // Reduce match obligations
443
91146
                let mut mg = mg.clone();
444
91146
                let mut new_obligations = vec![];
445

            
446
92349
                for mo in mg.obligations {
447
92349
                    if mo.pattern.data_function_symbol() == symbol.copy() && mo.position == self.label {
448
                        // Reduced match obligation
449
166530
                        for (index, t) in mo.pattern.data_arguments().enumerate() {
450
166530
                            assert!(
451
166530
                                index < arity,
452
                                "This pattern associates function symbol {:?} with different arities {} and {}",
453
                                symbol,
454
                                index + 1,
455
                                arity
456
                            );
457

            
458
166530
                            if !is_data_variable(&t) {
459
118764
                                let mut new_pos = mo.position.clone();
460
118764
                                new_pos.push(index + 1);
461
118764
                                new_obligations.push(MatchObligation {
462
118764
                                    pattern: t.protect(),
463
118764
                                    position: new_pos,
464
118764
                                });
465
118764
                            }
466
                        }
467
1203
                    } else {
468
1203
                        // remains unchanged
469
1203
                        new_obligations.push(mo.clone());
470
1203
                    }
471
                }
472

            
473
91146
                new_obligations.sort_unstable_by_key(|mo1| mo1.position.len());
474
91146
                mg.obligations = new_obligations;
475
91146
                mg.announcement.symbols_seen += 1;
476

            
477
91146
                result.reduced.push(mg);
478
            }
479
        }
480

            
481
146049
        trace!(
482
            "=== compute_derivative(symbol = {}, label = {}) ===",
483
            symbol, self.label
484
        );
485
146049
        trace!("Match goals: {{");
486
15414231
        for mg in &self.match_goals {
487
15414231
            trace!("\t {mg:?}");
488
        }
489

            
490
146049
        trace!("}}");
491
146049
        trace!("Completed: {{");
492
146049
        for mg in &result.completed {
493
46692
            trace!("\t {mg:?}");
494
        }
495

            
496
146049
        trace!("}}");
497
146049
        trace!("Unchanged: {{");
498
7682907
        for mg in &result.unchanged {
499
7682907
            trace!("\t {mg:?}");
500
        }
501

            
502
146049
        trace!("}}");
503
146049
        trace!("Reduced: {{");
504
146049
        for mg in &result.reduced {
505
91146
            trace!("\t {mg:?}");
506
        }
507
146049
        trace!("}}");
508

            
509
146049
        result
510
146049
    }
511

            
512
    /// Create a state from a set of match goals
513
3519
    fn new(goals: Vec<MatchGoal>) -> State {
514
        // The label of the state is taken from a match obligation of a root match goal.
515
3519
        let mut label: Option<DataPosition> = None;
516

            
517
        // Go through all match goals until a root match goal is found
518
309573
        for goal in &goals {
519
309573
            if goal.announcement.position.is_empty() {
520
                // Find the shortest match obligation position.
521
                // This design decision was taken as it presumably has two advantages.
522
                // 1. Patterns that overlap will be more quickly distinguished, potentially decreasing
523
                // the size of the automaton.
524
                // 2. The average lookahead may be shorter.
525
8979
                if label.is_none() {
526
3519
                    label = Some(goal.obligations.first().unwrap().position.clone());
527
5460
                }
528

            
529
10218
                for obligation in &goal.obligations {
530
10218
                    if let Some(l) = &label
531
10218
                        && &obligation.position < l
532
30
                    {
533
30
                        label = Some(obligation.position.clone());
534
10188
                    }
535
                }
536
300594
            }
537
        }
538

            
539
3519
        State {
540
3519
            label: label.unwrap(),
541
3519
            match_goals: goals,
542
3519
        }
543
3519
    }
544

            
545
    /// Returns the label of the state
546
41433150
    pub fn label(&self) -> &DataPosition {
547
41433150
        &self.label
548
41433150
    }
549

            
550
    /// Returns the match goals of the state
551
12
    pub fn match_goals(&self) -> &[MatchGoal] {
552
12
        &self.match_goals
553
12
    }
554
}
555

            
556
/// Adds the given function symbol to the indexed symbols. Errors when a
557
/// function symbol is overloaded with different arities.
558
23358
fn add_symbol(function_symbol: DataFunctionSymbol, arity: usize, symbols: &mut HashMap<DataFunctionSymbol, usize>) {
559
23358
    if let Some(x) = symbols.get(&function_symbol) {
560
19650
        assert_eq!(
561
            *x, arity,
562
            "Function symbol {function_symbol} occurs with different arities",
563
        );
564
3708
    } else {
565
3708
        symbols.insert(function_symbol, arity);
566
3708
    }
567
23358
}
568

            
569
/// Returns false iff this is a higher order term, of the shape t(t_0, ..., t_n), or an unknown term.
570
10392
fn is_supported_term(t: &DataExpression) -> bool {
571
153657
    for subterm in t.iter() {
572
153657
        if is_data_application(&subterm) && !is_data_function_symbol(&subterm.arg(0)) {
573
            warn!("{} is higher order", &subterm);
574
            return false;
575
153657
        }
576
    }
577

            
578
10392
    true
579
10392
}
580

            
581
/// Checks whether the set automaton can use this rule, no higher order rules or binders.
582
4710
pub fn is_supported_rule(rule: &Rule) -> bool {
583
    // There should be no terms of the shape t(t0,...,t_n)
584
4710
    if !is_supported_term(&rule.rhs) || !is_supported_term(&rule.lhs) {
585
        return false;
586
4710
    }
587

            
588
4710
    for cond in &rule.conditions {
589
486
        if !is_supported_term(&cond.rhs) || !is_supported_term(&cond.lhs) {
590
            return false;
591
486
        }
592
    }
593

            
594
4710
    true
595
4710
}
596

            
597
/// Finds all data symbols in the term and adds them to the symbol index.
598
34458
fn find_symbols(t: &DataExpressionRef<'_>, symbols: &mut HashMap<DataFunctionSymbol, usize>) {
599
34458
    if is_data_function_symbol(t) {
600
7533
        add_symbol(t.protect().into(), 0, symbols);
601
26925
    } else if is_data_application(t) {
602
        // REC specifications should never contain this so it can be a debug error.
603
15825
        assert!(
604
15825
            is_data_function_symbol(&t.data_function_symbol()),
605
            "Error in term {t}, higher order term rewrite systems are not supported"
606
        );
607

            
608
15825
        add_symbol(t.data_function_symbol().protect(), t.data_arguments().len(), symbols);
609
24066
        for arg in t.data_arguments() {
610
24066
            find_symbols(&arg, symbols);
611
24066
        }
612
11100
    } else if is_data_machine_number(t) {
613
        // Ignore machine numbers during matching?
614
11100
    } else if !is_data_variable(t) {
615
        panic!("Unexpected term {t:?}");
616
11100
    }
617
34458
}