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

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

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

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

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

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

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

            
81
1220
        ModalEquationSystem { equations }
82
1220
    }
83

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

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

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

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

            
158
/// If the given formula has no outermost fixpoint operator, adds a placeholder
159
/// fixpoint operator around it.
160
1221
fn add_placeholder_operator(formula: StateFrm, identifier_generator: &mut FreshStateVarGenerator) -> StateFrm {
161
1221
    if matches!(formula, StateFrm::FixedPoint { .. }) {
162
        // The outer operator is already a fixpoint
163
1025
        formula
164
    } else {
165
        // Introduce a placeholder.
166
196
        StateFrm::FixedPoint {
167
196
            operator: FixedPointOperator::Least,
168
196
            variable: StateVarDecl::new(identifier_generator.generate("X"), Vec::new()),
169
196
            body: Box::new(formula),
170
196
        }
171
    }
172
1221
}
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
1221
fn apply_e(equations: &mut Vec<Equation>, formula: &StateFrm) {
180
1221
    debug!("Applying E to formula: {}", formula);
181

            
182
16065
    visit_statefrm::<(), _>(formula, |formula| match formula {
183
        StateFrm::FixedPoint {
184
2887
            operator,
185
2887
            variable,
186
2887
            body,
187
        } => {
188
2887
            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
2887
            equations.push(Equation {
191
2887
                operator: *operator,
192
2887
                variable: variable.clone(),
193
2887
                rhs: rhs(body),
194
2887
            });
195

            
196
2887
            Ok(ControlFlow::Continue(()))
197
        }
198
13178
        _ => Ok(ControlFlow::Continue(())),
199
16065
    })
200
1221
    .expect("No error expected during fixpoint equation system construction");
201
1221
}
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
2887
fn rhs(formula: &StateFrm) -> StateFrm {
215
14844
    apply_statefrm(formula.clone(), |formula| match formula {
216
        // RHS(mu X. phi) = X(args)
217
1666
        StateFrm::FixedPoint { variable, .. } => Ok(Some(StateFrm::Id(
218
1666
            variable.identifier.clone(),
219
1666
            variable.arguments.iter().map(|arg| arg.expr.clone()).collect(),
220
        ))),
221
13178
        _ => Ok(None),
222
14844
    })
223
2887
    .expect("No error expected during RHS extraction")
224
2887
}
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
2438
    pub fn new(formula: &StateFrm) -> Self {
238
2438
        let mut used = HashSet::new();
239
23672
        visit_statefrm::<(), _>(formula, |subformula| {
240
23672
            if let StateFrm::FixedPoint { variable, .. } = subformula {
241
2697
                used.insert(variable.identifier.clone());
242
20975
            }
243

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

            
248
2438
        FreshStateVarGenerator { used }
249
2438
    }
250

            
251
    /// Generates a fresh state variable name based on the given base.
252
2872
    pub fn generate(&mut self, base: &str) -> String {
253
2872
        let mut index = 0;
254
        loop {
255
6802
            let candidate = format!("{}{}", base, index);
256
6802
            if !self.used.contains(&candidate) {
257
2872
                self.used.insert(candidate.clone());
258
2872
                return candidate;
259
3930
            }
260
3930
            index += 1;
261
        }
262
2872
    }
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::ModalEquationSystem;
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
}