1
#![forbid(unsafe_code)]
2

            
3
use merc_lts::LTS;
4
use merc_lts::StateIndex;
5

            
6
/// Returns true iff the given state diverges, i.e., it can perform an infinite
7
/// sequence of tau transitions.
8
///
9
/// Uses iterative DFS with 3-color marking to correctly detect cycles in any LTS,
10
/// including tau self-loops and graphs where the same state is reachable via
11
/// multiple paths (diamonds).
12
1046
pub fn diverges<L: LTS>(lts: &L, state: StateIndex) -> bool {
13
1046
    let mut color = vec![DfsColor::White; lts.num_of_states()];
14
    // Each stack entry is (node, backtrack): when backtrack is true the node is being finalised.
15
1046
    let mut stack = vec![(state, false)];
16

            
17
4463
    while let Some((current, backtrack)) = stack.pop() {
18
3421
        if backtrack {
19
            // All descendants have been processed; mark the node as fully done.
20
1705
            color[current] = DfsColor::Black;
21
1705
            continue;
22
1716
        }
23

            
24
1716
        match color[current] {
25
            DfsColor::Gray => return true, // Back-edge: current is still on the DFS path → cycle found.
26
            DfsColor::Black => continue,   // Already fully processed; no new information.
27
1716
            DfsColor::White => {}
28
        }
29

            
30
        // Mark gray (on the current DFS path) and schedule finalisation.
31
1716
        color[current] = DfsColor::Gray;
32
1716
        stack.push((current, true));
33

            
34
2170
        for transition in lts.outgoing_transitions(current) {
35
2170
            if lts.is_hidden_label(transition.label) {
36
676
                match color[transition.to] {
37
4
                    DfsColor::Gray => return true, // Back-edge (incl. self-loop): cycle found.
38
672
                    DfsColor::White => stack.push((transition.to, false)),
39
                    DfsColor::Black => {}
40
                }
41
1494
            }
42
        }
43
    }
44

            
45
    // All reachable tau-transitions explored without finding a back-edge.
46
1042
    false
47
1046
}
48

            
49
/// DFS node colour used by [`diverges`].
50
#[derive(Clone, Copy, Default, PartialEq, Eq)]
51
enum DfsColor {
52
    /// Not yet visited.
53
    #[default]
54
    White,
55
    /// On the current DFS path (in the recursion stack).
56
    Gray,
57
    /// All descendants fully processed.
58
    Black,
59
}