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

            
5
use log::debug;
6

            
7
use merc_syntax::FixedPointOperator;
8
use merc_syntax::StateFrm;
9
use merc_syntax::StateVarDecl;
10
use merc_syntax::apply_statefrm;
11
use merc_syntax::visit_statefrm;
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
10927
    pub fn operator(&self) -> FixedPointOperator {
32
10927
        self.operator
33
10927
    }
34

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

            
40
    /// Returns the body of the equation.
41
21858
    pub fn body(&self) -> &StateFrm {
42
21858
        &self.rhs
43
21858
    }
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
2365
    pub fn new(formula: &StateFrm) -> Self {
59
2365
        let mut equations = Vec::new();
60
2365
        let mut identifier_generator = FreshStateVarGenerator::new(formula);
61

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

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

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

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

            
81
2364
        ModalEquationSystem { equations }
82
2364
    }
83

            
84
    /// Returns the ith equation in the system.
85
10927
    pub fn equation(&self, i: usize) -> &Equation {
86
10927
        &self.equations[i]
87
10927
    }
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
10931
    pub fn alternation_depth(&self, i: usize) -> usize {
108
10931
        let equation = &self.equations[i];
109
10931
        self.alternation_depth_rec(i, equation.body(), &equation.variable().identifier)
110
10931
    }
111

            
112
    /// Finds an equation by its variable identifier.
113
    ///
114
    /// # Details
115
    ///
116
    /// This is a linear scan over the equations, and is also called from within
117
    /// [`Self::alternation_depth`]'s recursion. Equation systems correspond to a
118
    /// single (modal) formula and are therefore small, so an index map is not
119
    /// worth its maintenance cost; revisit if very large formulas become common.
120
38539
    pub fn find_equation_by_identifier(&self, id: &str) -> Option<(usize, &Equation)> {
121
38539
        self.equations
122
38539
            .iter()
123
38539
            .enumerate()
124
273845
            .find(|(_, eq)| eq.variable.identifier == id)
125
38539
    }
126

            
127
    /// Recursive helper function to compute the alternation depth of equation `i`.
128
126250
    fn alternation_depth_rec(&self, i: usize, formula: &StateFrm, identifier: &String) -> usize {
129
126250
        let equation = &self.equations[i];
130

            
131
126250
        match formula {
132
40473
            StateFrm::Id(id, _) => {
133
40473
                if id == identifier {
134
10506
                    1
135
                } else {
136
29967
                    let (j, inner_equation) = self
137
29967
                        .find_equation_by_identifier(id)
138
29967
                        .expect("Equation not found for identifier");
139
29967
                    if j > i {
140
14944
                        let depth = self.alternation_depth_rec(j, &inner_equation.rhs, identifier);
141
14944
                        depth
142
14944
                            + (if inner_equation.operator != equation.operator {
143
726
                                1 // Alternation occurs.
144
                            } else {
145
14218
                                0
146
                            })
147
                    } else {
148
                        // Only consider nested equations
149
15023
                        0
150
                    }
151
                }
152
            }
153
28272
            StateFrm::Binary { lhs, rhs, .. } => self
154
28272
                .alternation_depth_rec(i, lhs, identifier)
155
28272
                .max(self.alternation_depth_rec(i, rhs, identifier)),
156
43831
            StateFrm::Modality { expr, .. } => self.alternation_depth_rec(i, expr, identifier),
157
13674
            StateFrm::True | StateFrm::False => 0,
158
            _ => {
159
                unimplemented!("Cannot determine alternation depth of formula {}", formula)
160
            }
161
        }
162
126250
    }
163
}
164

            
165
/// If the given formula has no outermost fixpoint operator, adds a placeholder
166
/// fixpoint operator around it.
167
2365
fn add_placeholder_operator(formula: StateFrm, identifier_generator: &mut FreshStateVarGenerator) -> StateFrm {
168
2365
    if matches!(formula, StateFrm::FixedPoint { .. }) {
169
        // The outer operator is already a fixpoint
170
1937
        formula
171
    } else {
172
        // Introduce a placeholder.
173
428
        StateFrm::FixedPoint {
174
428
            operator: FixedPointOperator::Least,
175
428
            variable: StateVarDecl::new(identifier_generator.generate("X"), Vec::new()),
176
428
            body: Box::new(formula),
177
428
        }
178
    }
179
2365
}
180

            
181
/// Applies `E` to the given formula, adding equations to the given vector.
182
///
183
/// E(nu X. f) = (nu X = RHS(f)) + E(f)
184
/// E(mu X. f) = (mu X = RHS(f)) + E(f)
185
/// E(g) = ... (traverse all the subformulas of g and apply E to them)
186
2365
fn apply_e(equations: &mut Vec<Equation>, formula: &StateFrm) {
187
2365
    debug!("Applying E to formula: {}", formula);
188

            
189
29817
    visit_statefrm::<(), _>(formula, |formula| match formula {
190
        StateFrm::FixedPoint {
191
5435
            operator,
192
5435
            variable,
193
5435
            body,
194
        } => {
195
5435
            debug!("Adding equation for variable {}", variable.identifier);
196
            // Add the equation with the renamed variable (the span is the same as the original variable).
197
5435
            equations.push(Equation {
198
5435
                operator: *operator,
199
5435
                variable: variable.clone(),
200
5435
                rhs: rhs(body),
201
5435
            });
202

            
203
5435
            Ok(ControlFlow::Continue(()))
204
        }
205
24382
        _ => Ok(ControlFlow::Continue(())),
206
29817
    })
207
2365
    .expect("No error expected during fixpoint equation system construction");
208
2365
}
209

            
210
/// Applies `RHS` to the given formula.
211
///
212
/// RHS(true) = true
213
/// RHS(false) = false
214
/// RHS(<a>f) = <a>RHS(f)
215
/// RHS([a]f) = [a]RHS(f)
216
/// RHS(f1 && f2) = RHS(f1) && RHS(f2)
217
/// RHS(f1 || f2) = RHS(f1) || RHS(f2)
218
/// RHS(X) = X
219
/// RHS(mu X. f) = X(args)
220
/// RHS(nu X. f) = X(args)
221
5435
fn rhs(formula: &StateFrm) -> StateFrm {
222
27452
    apply_statefrm(formula.clone(), |formula| match formula {
223
        // RHS(mu X. phi) = X(args)
224
3070
        StateFrm::FixedPoint { variable, .. } => Ok(Some(StateFrm::Id(
225
3070
            variable.identifier.clone(),
226
3070
            variable.arguments.iter().map(|arg| arg.expr.clone()).collect(),
227
        ))),
228
24382
        _ => Ok(None),
229
27452
    })
230
5435
    .expect("No error expected during RHS extraction")
231
5435
}
232

            
233
/// A generator for fresh state variable names.
234
pub struct FreshStateVarGenerator {
235
    used: HashSet<String>,
236
}
237

            
238
impl FreshStateVarGenerator {
239
    /// Creates a new fresh state variable generator.
240
    ///
241
    /// # Details
242
    ///
243
    /// Traverses the given formula to collect all used variable names.
244
4726
    pub fn new(formula: &StateFrm) -> Self {
245
4726
        let mut used = HashSet::new();
246
43764
        visit_statefrm::<(), _>(formula, |subformula| {
247
43764
            if let StateFrm::FixedPoint { variable, .. } = subformula {
248
5013
                used.insert(variable.identifier.clone());
249
38751
            }
250

            
251
43764
            Ok(ControlFlow::Continue(()))
252
43764
        })
253
4726
        .expect("No error expected during visiting");
254

            
255
4726
        FreshStateVarGenerator { used }
256
4726
    }
257

            
258
    /// Generates a fresh state variable name based on the given base.
259
5420
    pub fn generate(&mut self, base: &str) -> String {
260
5420
        let mut index = 0;
261
        loop {
262
12680
            let candidate = format!("{}{}", base, index);
263
12680
            if !self.used.contains(&candidate) {
264
5420
                self.used.insert(candidate.clone());
265
5420
                return candidate;
266
7260
            }
267
7260
            index += 1;
268
        }
269
5420
    }
270
}
271

            
272
impl fmt::Display for ModalEquationSystem {
273
2
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274
4
        for (i, equation) in self.equations.iter().enumerate() {
275
4
            write!(f, "{i}: {} {} = {}", equation.operator, equation.variable, equation.rhs)?;
276
4
            if i + 1 < self.equations.len() {
277
2
                writeln!(f)?;
278
2
            }
279
        }
280
2
        Ok(())
281
2
    }
282
}
283

            
284
#[cfg(test)]
285
mod tests {
286
    use merc_macros::merc_test;
287
    use merc_syntax::UntypedStateFrmSpec;
288

            
289
    use super::ModalEquationSystem;
290

            
291
    #[merc_test]
292
    fn test_fixpoint_equation_system_construction() {
293
        let formula = UntypedStateFrmSpec::parse("mu X. [a]X && nu Y. <b>true")
294
            .unwrap()
295
            .formula;
296
        let fes = ModalEquationSystem::new(&formula);
297

            
298
        println!("{}", fes);
299

            
300
        assert_eq!(fes.equations.len(), 2);
301
        assert_eq!(fes.alternation_depth(0), 1);
302
        assert_eq!(fes.alternation_depth(1), 0);
303
    }
304

            
305
    #[merc_test]
306
    fn test_fixpoint_equation_system_example() {
307
        let formula = UntypedStateFrmSpec::parse(include_str!("../../../examples/vpg/running_example.mcf"))
308
            .unwrap()
309
            .formula;
310
        let fes = ModalEquationSystem::new(&formula);
311

            
312
        println!("{}", fes);
313

            
314
        assert_eq!(fes.equations.len(), 2);
315
        assert_eq!(fes.alternation_depth(0), 2);
316
        assert_eq!(fes.alternation_depth(1), 1);
317
    }
318

            
319
    #[merc_test]
320
    #[should_panic(expected = "Duplicate variable names found in fixpoint equation system")]
321
    fn test_fixpoint_equation_system_duplicates() {
322
        let formula = UntypedStateFrmSpec::parse("mu X. [a]X && (nu Y. <b>true) && (nu Y . <c>X)")
323
            .unwrap()
324
            .formula;
325
        let fes = ModalEquationSystem::new(&formula);
326

            
327
        println!("{}", fes);
328

            
329
        assert_eq!(fes.equations.len(), 3);
330
    }
331
}