1
use std::collections::HashSet;
2
use std::fmt;
3
use std::ops::ControlFlow;
4

            
5
use log::debug;
6

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

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

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

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

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

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

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

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

            
62
        // Ensure that the formula has an outermost fixpoint operator.
63
519
        let formula = add_placeholder_operator(formula.clone(), &mut identifier_generator);
64

            
65
        // Apply E to extract all equations from the formula
66
519
        apply_e(&mut equations, &formula);
67

            
68
        // Check that there are no duplicate variable names
69
519
        let identifiers: HashSet<&String> = HashSet::from_iter(equations.iter().map(|eq| &eq.variable.identifier));
70
519
        assert_eq!(
71
519
            identifiers.len(),
72
519
            equations.len(),
73
            "Duplicate variable names found in fixpoint equation system"
74
        );
75

            
76
518
        debug_assert!(
77
518
            !equations.is_empty(),
78
            "At least one fixpoint equation expected in the equation system"
79
        );
80

            
81
518
        ModalEquationSystem { equations }
82
518
    }
83

            
84
    /// Returns the ith equation in the system.
85
3052
    pub fn equation(&self, i: usize) -> &Equation {
86
3052
        &self.equations[i]
87
3052
    }
88

            
89
    /// Returns the number of equations in the system.
90
    pub fn len(&self) -> usize {
91
        self.equations.len()
92
    }
93

            
94
    /// Returns true if the system contains no equations.
95
    pub fn is_empty(&self) -> bool {
96
        self.equations.is_empty()
97
    }
98

            
99
    /// The alternation depth is a complexity measure of the given formula.
100
    ///
101
    /// # Details
102
    ///
103
    /// The alternation depth of mu X . psi is defined as the maximum chain X <= X_1 <= ... <= X_n,
104
    /// where X <= Y iff X appears freely in the corresponding equation sigma Y . phi. And furthermore,
105
    /// X_0, X_2, ... are bound by mu and X_1, X_3, ... are bound by nu. Similarly, for nu X . psi. Note
106
    /// that the alternation depth of a formula with a rhs is always 1, since the chain cannot be extended.
107
3056
    pub fn alternation_depth(&self, i: usize) -> usize {
108
3056
        let equation = &self.equations[i];
109
3056
        self.alternation_depth_rec(i, equation.body(), &equation.variable().identifier)
110
3056
    }
111

            
112
    /// Finds an equation by its variable identifier.
113
21050
    pub fn find_equation_by_identifier(&self, id: &str) -> Option<(usize, &Equation)> {
114
21050
        self.equations
115
21050
            .iter()
116
21050
            .enumerate()
117
144992
            .find(|(_, eq)| eq.variable.identifier == id)
118
21050
    }
119

            
120
    /// Recursive helper function to compute the alternation depth of equation `i`.
121
64365
    fn alternation_depth_rec(&self, i: usize, formula: &StateFrm, identifier: &String) -> usize {
122
64365
        let equation = &self.equations[i];
123

            
124
64365
        match formula {
125
21459
            StateFrm::Id(id, _) => {
126
21459
                if id == identifier {
127
2946
                    1
128
                } else {
129
18513
                    let (j, inner_equation) = self
130
18513
                        .find_equation_by_identifier(id)
131
18513
                        .expect("Equation not found for identifier");
132
18513
                    if j > i {
133
9253
                        let depth = self.alternation_depth_rec(j, &inner_equation.rhs, identifier);
134
9253
                        depth
135
9253
                            + (if inner_equation.operator != equation.operator {
136
254
                                1 // Alternation occurs.
137
                            } else {
138
8999
                                0
139
                            })
140
                    } else {
141
                        // Only consider nested equations
142
9260
                        0
143
                    }
144
                }
145
            }
146
14112
            StateFrm::Binary { lhs, rhs, .. } => self
147
14112
                .alternation_depth_rec(i, lhs, identifier)
148
14112
                .max(self.alternation_depth_rec(i, rhs, identifier)),
149
23832
            StateFrm::Modality { expr, .. } => self.alternation_depth_rec(i, expr, identifier),
150
4962
            StateFrm::True | StateFrm::False => 0,
151
            _ => {
152
                unimplemented!("Cannot determine alternation depth of formula {}", formula)
153
            }
154
        }
155
64365
    }
156
}
157

            
158
/// If the given formula has no outermost fixpoint operator, adds a placeholder
159
/// fixpoint operator around it.
160
519
fn add_placeholder_operator(formula: StateFrm, identifier_generator: &mut FreshStateVarGenerator) -> StateFrm {
161
519
    if matches!(formula, StateFrm::FixedPoint { .. }) {
162
        // The outer operator is already a fixpoint
163
405
        formula
164
    } else {
165
        // Introduce a placeholder.
166
114
        StateFrm::FixedPoint {
167
114
            operator: FixedPointOperator::Least,
168
114
            variable: StateVarDecl::new(identifier_generator.generate("X"), Vec::new()),
169
114
            body: Box::new(formula),
170
114
        }
171
    }
172
519
}
173

            
174
/// Applies `E` to the given formula, adding equations to the given vector.
175
///
176
/// E(nu X. f) = (nu X = RHS(f)) + E(f)
177
/// E(mu X. f) = (mu X = RHS(f)) + E(f)
178
/// E(g) = ... (traverse all the subformulas of g and apply E to them)
179
519
fn apply_e(equations: &mut Vec<Equation>, formula: &StateFrm) {
180
519
    debug!("Applying E to formula: {}", formula);
181

            
182
12092
    visit_statefrm::<(), _>(formula, |formula| match formula {
183
        StateFrm::FixedPoint {
184
2149
            operator,
185
2149
            variable,
186
2149
            body,
187
        } => {
188
2149
            debug!("Adding equation for variable {}", variable.identifier);
189
            // Add the equation with the renamed variable (the span is the same as the original variable).
190
2149
            equations.push(Equation {
191
2149
                operator: *operator,
192
2149
                variable: variable.clone(),
193
2149
                rhs: rhs(body),
194
2149
            });
195

            
196
2149
            Ok(ControlFlow::Continue(()))
197
        }
198
9943
        _ => Ok(ControlFlow::Continue(())),
199
12092
    })
200
519
    .expect("No error expected during fixpoint equation system construction");
201
519
}
202

            
203
/// Applies `RHS` to the given formula.
204
///
205
/// RHS(true) = true
206
/// RHS(false) = false
207
/// RHS(<a>f) = <a>RHS(f)
208
/// RHS([a]f) = [a]RHS(f)
209
/// RHS(f1 && f2) = RHS(f1) && RHS(f2)
210
/// RHS(f1 || f2) = RHS(f1) || RHS(f2)
211
/// RHS(X) = X
212
/// RHS(mu X. f) = X(args)
213
/// RHS(nu X. f) = X(args)
214
2149
fn rhs(formula: &StateFrm) -> StateFrm {
215
11573
    apply_statefrm(formula.clone(), |formula| match formula {
216
        // RHS(mu X. phi) = X(args)
217
1630
        StateFrm::FixedPoint { variable, .. } => Ok(Some(StateFrm::Id(
218
1630
            variable.identifier.clone(),
219
1630
            variable.arguments.iter().map(|arg| arg.expr.clone()).collect(),
220
        ))),
221
9943
        _ => Ok(None),
222
11573
    })
223
2149
    .expect("No error expected during RHS extraction")
224
2149
}
225

            
226
/// A generator for fresh state variable names.
227
pub struct FreshStateVarGenerator {
228
    used: HashSet<String>,
229
}
230

            
231
impl FreshStateVarGenerator {
232
    /// Creates a new fresh state variable generator.
233
    ///
234
    /// # Details
235
    ///
236
    /// Traverses the given formula to collect all used variable names.
237
1034
    pub fn new(formula: &StateFrm) -> Self {
238
1034
        let mut used = HashSet::new();
239
17846
        visit_statefrm::<(), _>(formula, |subformula| {
240
17846
            if let StateFrm::FixedPoint { variable, .. } = subformula {
241
2037
                used.insert(variable.identifier.clone());
242
15809
            }
243

            
244
17846
            Ok(ControlFlow::Continue(()))
245
17846
        })
246
1034
        .expect("No error expected during visiting");
247

            
248
1034
        FreshStateVarGenerator { used }
249
1034
    }
250

            
251
    /// Generates a fresh state variable name based on the given base.
252
2138
    pub fn generate(&mut self, base: &str) -> String {
253
2138
        let mut index = 0;
254
        loop {
255
8258
            let candidate = format!("{}{}", base, index);
256
8258
            if !self.used.contains(&candidate) {
257
2138
                self.used.insert(candidate.clone());
258
2138
                return candidate;
259
6120
            }
260
6120
            index += 1;
261
        }
262
2138
    }
263
}
264

            
265
impl fmt::Display for ModalEquationSystem {
266
2
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
267
4
        for (i, equation) in self.equations.iter().enumerate() {
268
4
            write!(f, "{i}: {} {} = {}", equation.operator, equation.variable, equation.rhs)?;
269
4
            if i + 1 < self.equations.len() {
270
2
                writeln!(f)?;
271
2
            }
272
        }
273
2
        Ok(())
274
2
    }
275
}
276

            
277
#[cfg(test)]
278
mod tests {
279
    use merc_macros::merc_test;
280
    use merc_syntax::UntypedStateFrmSpec;
281

            
282
    use super::*;
283

            
284
    #[merc_test]
285
    fn test_fixpoint_equation_system_construction() {
286
        let formula = UntypedStateFrmSpec::parse("mu X. [a]X && nu Y. <b>true")
287
            .unwrap()
288
            .formula;
289
        let fes = ModalEquationSystem::new(&formula);
290

            
291
        println!("{}", fes);
292

            
293
        assert_eq!(fes.equations.len(), 2);
294
        assert_eq!(fes.alternation_depth(0), 1);
295
        assert_eq!(fes.alternation_depth(1), 0);
296
    }
297

            
298
    #[merc_test]
299
    fn test_fixpoint_equation_system_example() {
300
        let formula = UntypedStateFrmSpec::parse(include_str!("../../../examples/vpg/running_example.mcf"))
301
            .unwrap()
302
            .formula;
303
        let fes = ModalEquationSystem::new(&formula);
304

            
305
        println!("{}", fes);
306

            
307
        assert_eq!(fes.equations.len(), 2);
308
        assert_eq!(fes.alternation_depth(0), 2);
309
        assert_eq!(fes.alternation_depth(1), 1);
310
    }
311

            
312
    #[merc_test]
313
    #[should_panic(expected = "Duplicate variable names found in fixpoint equation system")]
314
    fn test_fixpoint_equation_system_duplicates() {
315
        let formula = UntypedStateFrmSpec::parse("mu X. [a]X && (nu Y. <b>true) && (nu Y . <c>X)")
316
            .unwrap()
317
            .formula;
318
        let fes = ModalEquationSystem::new(&formula);
319

            
320
        println!("{}", fes);
321

            
322
        assert_eq!(fes.equations.len(), 3);
323
    }
324
}