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
362
    pub fn operator(&self) -> FixedPointOperator {
32
362
        self.operator
33
362
    }
34

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

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

            
61
361
        let mut identifier_generator = FreshStateVarGenerator::new(formula);
62

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

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

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

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

            
82
360
        ModalEquationSystem { equations }
83
360
    }
84

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

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

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

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

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

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

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

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

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

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

            
197
367
            Ok(ControlFlow::Continue(()))
198
        }
199
781
        _ => Ok(ControlFlow::Continue(())),
200
1148
    })
201
361
    .expect("No error expected during fixpoint equation system construction");
202
361
}
203

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

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

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

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

            
248
361
        FreshStateVarGenerator {
249
361
            used,
250
361
        }
251
361
    }
252

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

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

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

            
281
    use super::*;
282

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

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

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

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

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

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

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

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

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