1
use std::collections::HashSet;
2
use std::fmt;
3

            
4
use log::debug;
5

            
6
use merc_syntax::FixedPointOperator;
7
use merc_syntax::StateFrm;
8
use merc_syntax::StateVarDecl;
9
use merc_syntax::apply_statefrm;
10
use merc_syntax::visit_statefrm;
11

            
12
/// A fixpoint equation system representing a ranked set of fixpoint equations.
13
///
14
/// Each equation is of the shape `{mu, nu} X(args...) = rhs`. Where rhs
15
/// contains no further fixpoint equations.
16
pub struct ModalEquationSystem {
17
    equations: Vec<Equation>,
18
}
19

            
20
/// A single fixpoint equation of the shape `{mu, nu} X(args...) = rhs`.
21
#[derive(Clone)]
22
pub struct Equation {
23
    operator: FixedPointOperator,
24
    variable: StateVarDecl,
25
    rhs: StateFrm,
26
}
27

            
28
impl Equation {
29
    /// Returns the operator of the equation.
30
5
    pub fn operator(&self) -> FixedPointOperator {
31
5
        self.operator
32
5
    }
33

            
34
    /// Returns the variable declaration of the equation.
35
9
    pub fn variable(&self) -> &StateVarDecl {
36
9
        &self.variable
37
9
    }
38

            
39
    /// Returns the body of the equation.
40
14
    pub fn body(&self) -> &StateFrm {
41
14
        &self.rhs
42
14
    }
43
}
44

            
45
impl From<Equation> for StateFrm {
46
    fn from(val: Equation) -> Self {
47
        StateFrm::FixedPoint {
48
            operator: val.operator,
49
            variable: val.variable,
50
            body: Box::new(val.rhs),
51
        }
52
    }
53
}
54

            
55
impl ModalEquationSystem {
56
    /// Converts a plain state formula into a fixpoint equation system.
57
4
    pub fn new(formula: &StateFrm) -> Self {
58
4
        let mut equations = Vec::new();
59

            
60
        // Apply E to extract all equations from the formula
61
4
        apply_e(&mut equations, formula);
62

            
63
        // Check that there are no duplicate variable names
64
4
        let identifiers: HashSet<&String> = HashSet::from_iter(equations.iter().map(|eq| &eq.variable.identifier));
65
4
        assert_eq!(
66
4
            identifiers.len(),
67
4
            equations.len(),
68
            "Duplicate variable names found in fixpoint equation system"
69
        );
70

            
71
3
        debug_assert!(
72
3
            !equations.is_empty(),
73
            "At least one fixpoint equation expected in the equation system"
74
        );
75

            
76
3
        ModalEquationSystem { equations }
77
3
    }
78

            
79
    /// Returns the ith equation in the system.
80
5
    pub fn equation(&self, i: usize) -> &Equation {
81
5
        &self.equations[i]
82
5
    }
83

            
84
    /// The alternation depth is a complexity measure of the given formula.
85
    ///
86
    /// # Details
87
    ///
88
    /// The alternation depth of mu X . psi is defined as the maximum chain X <= X_1 <= ... <= X_n,
89
    /// where X <= Y iff X appears freely in the corresponding equation sigma Y . phi. And furthermore,
90
    /// X_0, X_2, ... are bound by mu and X_1, X_3, ... are bound by nu. Similarly, for nu X . psi. Note
91
    /// that the alternation depth of a formula with a rhs is always 1, since the chain cannot be extended.
92
9
    pub fn alternation_depth(&self, i: usize) -> usize {
93
9
        let equation = &self.equations[i];
94
9
        self.alternation_depth_rec(i, equation.body(), &equation.variable().identifier)
95
9
    }
96

            
97
    /// Finds an equation by its variable identifier.
98
18
    pub fn find_equation_by_identifier(&self, id: &str) -> Option<(usize, &Equation)> {
99
18
        self.equations
100
18
            .iter()
101
18
            .enumerate()
102
31
            .find(|(_, eq)| eq.variable.identifier == id)
103
18
    }
104

            
105
    /// Recursive helper function to compute the alternation depth of equation `i`.
106
67
    fn alternation_depth_rec(&self, i: usize, formula: &StateFrm, identifier: &String) -> usize {
107
67
        let equation = &self.equations[i];
108

            
109
67
        match formula {
110
26
            StateFrm::Id(id, _) => {
111
26
                if id == identifier {
112
12
                    1
113
                } else {
114
14
                    let (j, inner_equation) = self
115
14
                        .find_equation_by_identifier(id)
116
14
                        .expect("Equation not found for identifier");
117
14
                    if j > i {
118
4
                        let depth = self.alternation_depth_rec(j, &inner_equation.rhs, identifier);
119
4
                        depth
120
4
                            + (if inner_equation.operator != equation.operator {
121
4
                                1 // Alternation occurs.
122
                            } else {
123
                                0
124
                            })
125
                    } else {
126
                        // Only consider nested equations
127
10
                        0
128
                    }
129
                }
130
            }
131
15
            StateFrm::Binary { lhs, rhs, .. } => self
132
15
                .alternation_depth_rec(i, lhs, identifier)
133
15
                .max(self.alternation_depth_rec(i, rhs, identifier)),
134
24
            StateFrm::Modality { expr, .. } => self.alternation_depth_rec(i, expr, identifier),
135
2
            StateFrm::True | StateFrm::False => 0,
136
            _ => {
137
                unimplemented!("Cannot determine alternation depth of formula {}", formula)
138
            }
139
        }
140
67
    }
141
}
142

            
143
// E(nu X. f) = (nu X = RHS(f)) + E(f)
144
// E(mu X. f) = (mu X = RHS(f)) + E(f)
145
// E(g) = ... (traverse all the subformulas of g and apply E to them)
146
4
fn apply_e(equations: &mut Vec<Equation>, formula: &StateFrm) {
147
4
    debug!("Applying E to formula: {}", formula);
148

            
149
38
    visit_statefrm(formula, |formula| match formula {
150
        StateFrm::FixedPoint {
151
9
            operator,
152
9
            variable,
153
9
            body,
154
        } => {
155
9
            debug!("Adding equation for variable {}", variable.identifier);
156
            // Add the equation with the renamed variable (the span is the same as the original variable).
157
9
            equations.push(Equation {
158
9
                operator: *operator,
159
9
                variable: variable.clone(),
160
9
                rhs: rhs(body),
161
9
            });
162

            
163
9
            Ok(())
164
        }
165
29
        _ => Ok(()),
166
38
    })
167
4
    .expect("No error expected during fixpoint equation system construction");
168
4
}
169

            
170
/// Applies `RHS` to the given formula.
171
///
172
/// RHS(true) = true
173
/// RHS(false) = false
174
/// RHS(<a>f) = <a>RHS(f)
175
/// RHS([a]f) = [a]RHS(f)
176
/// RHS(f1 && f2) = RHS(f1) && RHS(f2)
177
/// RHS(f1 || f2) = RHS(f1) || RHS(f2)
178
/// RHS(X) = X
179
/// RHS(mu X. f) = X(args)
180
/// RHS(nu X. f) = X(args)
181
9
fn rhs(formula: &StateFrm) -> StateFrm {
182
34
    apply_statefrm(formula.clone(), |formula| match formula {
183
        // RHS(mu X. phi) = X(args)
184
5
        StateFrm::FixedPoint { variable, .. } => Ok(Some(StateFrm::Id(
185
5
            variable.identifier.clone(),
186
5
            variable.arguments.iter().map(|arg| arg.expr.clone()).collect(),
187
        ))),
188
29
        _ => Ok(None),
189
34
    })
190
9
    .expect("No error expected during RHS extraction")
191
9
}
192

            
193
impl fmt::Display for ModalEquationSystem {
194
2
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195
4
        for (i, equation) in self.equations.iter().enumerate() {
196
4
            writeln!(f, "{i}: {} {} = {}", equation.operator, equation.variable, equation.rhs)?;
197
        }
198
2
        Ok(())
199
2
    }
200
}
201

            
202
#[cfg(test)]
203
mod tests {
204
    use merc_macros::merc_test;
205
    use merc_syntax::UntypedStateFrmSpec;
206

            
207
    use super::*;
208

            
209
    #[merc_test]
210
    fn test_fixpoint_equation_system_construction() {
211
        let formula = UntypedStateFrmSpec::parse("mu X. [a]X && nu Y. <b>true")
212
            .unwrap()
213
            .formula;
214
        let fes = ModalEquationSystem::new(&formula);
215

            
216
        println!("{}", fes);
217

            
218
        assert_eq!(fes.equations.len(), 2);
219
        assert_eq!(fes.alternation_depth(0), 1);
220
        assert_eq!(fes.alternation_depth(1), 0);
221
    }
222

            
223
    #[merc_test]
224
    fn test_fixpoint_equation_system_example() {
225
        let formula = UntypedStateFrmSpec::parse(include_str!("../../../examples/vpg/running_example.mcf"))
226
            .unwrap()
227
            .formula;
228
        let fes = ModalEquationSystem::new(&formula);
229

            
230
        println!("{}", fes);
231

            
232
        assert_eq!(fes.equations.len(), 2);
233
        assert_eq!(fes.alternation_depth(0), 2);
234
        assert_eq!(fes.alternation_depth(1), 1);
235
    }
236

            
237
    #[merc_test]
238
    #[should_panic(expected = "Duplicate variable names found in fixpoint equation system")]
239
    fn test_fixpoint_equation_system_duplicates() {
240
        let formula = UntypedStateFrmSpec::parse("mu X. [a]X && (nu Y. <b>true) && (nu Y . <c>X)")
241
            .unwrap()
242
            .formula;
243
        let fes = ModalEquationSystem::new(&formula);
244

            
245
        println!("{}", fes);
246

            
247
        assert_eq!(fes.equations.len(), 3);
248
    }
249
}