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

            
5
use log::debug;
6
use log::info;
7

            
8
use log::warn;
9
use merc_collections::IndexedSet;
10
use merc_io::TimeProgress;
11
use merc_lts::LTS;
12
use merc_lts::LabelledTransitionSystem;
13
use merc_lts::StateIndex;
14
use merc_lts::Transition;
15
use merc_lts::TransitionLabel;
16
use merc_syntax::ActFrm;
17
use merc_syntax::ActFrmBinaryOp;
18
use merc_syntax::FixedPointOperator;
19
use merc_syntax::ModalityOperator;
20
use merc_syntax::MultiAction;
21
use merc_syntax::RegFrm;
22
use merc_syntax::StateFrm;
23
use merc_syntax::StateFrmOp;
24
use merc_syntax::StateVarDecl;
25
use merc_syntax::apply_statefrm;
26
use merc_syntax::visit_action_formula;
27
use merc_syntax::visit_regular_formula;
28
use merc_syntax::visit_statefrm;
29
use merc_utilities::MercError;
30

            
31
use crate::FreshStateVarGenerator;
32
use crate::ModalEquationSystem;
33
use crate::ParityGame;
34
use crate::Player;
35
use crate::Priority;
36
use crate::VertexIndex;
37
use crate::compute_reachable;
38

            
39
/// Translates a labelled transition system into a variability parity game.
40
515
pub fn translate(lts: &LabelledTransitionSystem<String>, formula: &StateFrm) -> Result<ParityGame, MercError> {
41
    // Parses all labels into MultiAction once
42
515
    let labels = lts
43
515
        .labels()
44
515
        .iter()
45
2589
        .map(|label| {
46
2589
            if label.is_tau_label() {
47
515
                Ok(MultiAction::tau())
48
            } else {
49
2074
                MultiAction::parse(label)
50
            }
51
2589
        })
52
515
        .collect::<Result<Vec<MultiAction>, MercError>>()?;
53

            
54
    // Warn about any labels that are used in the formula but do not correspond to any label in the LTS.
55
515
    warn_unknown_action_labels(formula, &labels);
56

            
57
515
    let mut identifier_generator = FreshStateVarGenerator::new(formula);
58
515
    let formula = translate_regular_formulas(formula.clone(), &mut identifier_generator);
59
515
    debug!("Translated regular formulas: {}", formula);
60

            
61
515
    let equation_system = ModalEquationSystem::new(&formula);
62
515
    debug!("{}", equation_system);
63

            
64
515
    let mut algorithm: Translation<'_, _, ()> = Translation::new(lts, &labels, &equation_system);
65
515
    algorithm.translate(lts.initial_state_index(), 0, |_| (), |_, _| Ok(()))?;
66

            
67
    // Construct the parity game from the collected vertices and edges, where the `()` edge label is ignored.
68
515
    let vertices = algorithm.vertices();
69
515
    let result = ParityGame::from_edges(
70
515
        VertexIndex::new(0),
71
515
        vertices.iter().map(|(player, _)| *player).collect(),
72
515
        vertices.iter().map(|(_, priority)| *priority).collect(),
73
        true,
74
31752
        || algorithm.edges.iter().map(|(s, _, t)| (*s, *t)),
75
    );
76

            
77
    // Check that all vertices are reachable from the initial vertex. After
78
    // totality it could be that the true or false nodes are not reachable.
79
515
    if cfg!(debug_assertions) {
80
515
        let (_, reachable_vertices) = compute_reachable(&result);
81
515
        debug_assert!(
82
16308
            reachable_vertices.iter().all(|v| v.is_some()),
83
            "Not all vertices are reachable from the initial vertex"
84
        );
85
    }
86

            
87
515
    Ok(result)
88
515
}
89

            
90
/// Produces a warning for each label that is used in the formula but does not correspond to any label in the LTS.
91
516
pub fn warn_unknown_action_labels(formula: &StateFrm, labels: &[MultiAction]) {
92
5878
    visit_statefrm::<(), _>(formula, |statefrm| {
93
5878
        if let StateFrm::Modality { formula, .. } = statefrm {
94
6486
            visit_regular_formula::<(), _>(formula, |regfrm| {
95
6486
                if let RegFrm::Action(act_frm) = regfrm {
96
4462
                    visit_action_formula::<(), _>(act_frm, |act_frm| {
97
4462
                        if let ActFrm::MultAct(action) = act_frm
98
4462
                            && !labels.contains(action)
99
                        {
100
3
                            warn!(
101
                                "Label {} in formula does not correspond to any label in the LTS",
102
                                action
103
                            );
104
4459
                        }
105

            
106
4462
                        Ok(ControlFlow::Continue(()))
107
4462
                    })?;
108
2024
                }
109

            
110
6486
                Ok(ControlFlow::Continue(()))
111
6486
            })?;
112
1416
        }
113

            
114
5878
        Ok(ControlFlow::Continue(()))
115
5878
    })
116
516
    .expect("Failed to visit state formula");
117
516
}
118

            
119
/// Translates regular formulas in modalities to fixpoint equations.
120
///
121
/// # Details
122
///
123
/// Applies these transformations:
124
///
125
/// ```plain
126
/// [a*]phi = nu I. [a]I && phi
127
/// [a+]phi = [a](nu I. [a]I && phi)
128
/// [a+b]phi = [a]phi || [b]phi
129
/// [a.b]phi = [a][b]phi
130
/// ```
131
///
132
/// and symmetrical for the diamond operator:
133
/// ```plain
134
/// <a*>phi = mu I. <a>I || phi
135
/// <a+>phi = <a>(mu I. <a>I || phi)
136
/// ```
137
2539
pub fn translate_regular_formulas(formula: StateFrm, identifier_generator: &mut FreshStateVarGenerator) -> StateFrm {
138
13964
    apply_statefrm(formula, |subformula| {
139
        if let StateFrm::Modality {
140
6483
            operator,
141
6483
            formula,
142
6483
            expr,
143
13964
        } = subformula
144
        {
145
6483
            return match formula {
146
4459
                merc_syntax::RegFrm::Action(_action_frm) => Ok(None),
147
2024
                merc_syntax::RegFrm::Iteration(reg_frm) => {
148
                    // Generate the I equation and replace the regular formula with it.
149
2024
                    let iteration_var = identifier_generator.generate("I");
150
2024
                    Ok(Some(translate_regular_formulas(
151
2024
                        convert_regular_iteration(*operator, reg_frm, iteration_var, operator, expr),
152
2024
                        identifier_generator,
153
2024
                    )))
154
                }
155
                merc_syntax::RegFrm::Plus(reg_frm) => {
156
                    // Generate the I equation and replace the regular formula with it.
157
                    let iteration_var = identifier_generator.generate("I");
158
                    Ok(Some(StateFrm::Modality {
159
                        operator: *operator,
160
                        formula: *reg_frm.clone(),
161
                        expr: Box::new(translate_regular_formulas(
162
                            convert_regular_iteration(*operator, reg_frm, iteration_var, operator, expr),
163
                            identifier_generator,
164
                        )),
165
                    }))
166
                }
167
                merc_syntax::RegFrm::Sequence { lhs, rhs } => Ok(Some(translate_regular_formulas(
168
                    StateFrm::Modality {
169
                        operator: *operator,
170
                        formula: *lhs.clone(),
171
                        expr: Box::new(StateFrm::Modality {
172
                            operator: *operator,
173
                            formula: *rhs.clone(),
174
                            expr: expr.clone(),
175
                        }),
176
                    },
177
                    identifier_generator,
178
                ))),
179
                merc_syntax::RegFrm::Choice { lhs, rhs } => Ok(Some(translate_regular_formulas(
180
                    StateFrm::Binary {
181
                        op: StateFrmOp::Disjunction,
182
                        lhs: Box::new(StateFrm::Modality {
183
                            operator: *operator,
184
                            formula: *lhs.clone(),
185
                            expr: expr.clone(),
186
                        }),
187
                        rhs: Box::new(StateFrm::Modality {
188
                            operator: *operator,
189
                            formula: *rhs.clone(),
190
                            expr: expr.clone(),
191
                        }),
192
                    },
193
                    identifier_generator,
194
                ))),
195
            };
196
7481
        }
197

            
198
7481
        Ok(None)
199
13964
    })
200
2539
    .expect("Failed to visit state formula")
201
2539
}
202

            
203
/// Convert an iteration regular formula to a fixpoint formula
204
///
205
/// # Details
206
///
207
/// The `modality` is the modality of the outer formula.
208
2024
fn convert_regular_iteration(
209
2024
    modality: ModalityOperator,
210
2024
    reg_frm: &RegFrm,
211
2024
    iteration_var: String,
212
2024
    operator: &ModalityOperator,
213
2024
    expr: &StateFrm,
214
2024
) -> StateFrm {
215
    StateFrm::FixedPoint {
216
2024
        operator: if modality == ModalityOperator::Box {
217
426
            FixedPointOperator::Greatest
218
        } else {
219
1598
            FixedPointOperator::Least
220
        },
221
2024
        variable: StateVarDecl::new(iteration_var.clone(), Vec::new()),
222
2024
        body: Box::new(StateFrm::Binary {
223
2024
            op: if modality == ModalityOperator::Box {
224
426
                StateFrmOp::Conjunction
225
            } else {
226
1598
                StateFrmOp::Disjunction
227
            },
228
2024
            lhs: Box::new(StateFrm::Modality {
229
2024
                operator: *operator,
230
2024
                formula: reg_frm.clone(),
231
2024
                expr: Box::new(StateFrm::Id(iteration_var, Vec::new())),
232
2024
            }),
233
2024
            rhs: Box::new(expr.clone()),
234
        }),
235
    }
236
2024
}
237

            
238
/// Is used to distinguish between StateFrm and Equation vertices in the vertex map.
239
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
240
enum Formula<'a> {
241
    StateFrm(&'a StateFrm),
242
    Equation(usize),
243
}
244

            
245
impl fmt::Display for Formula<'_> {
246
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247
        match self {
248
            Formula::StateFrm(statefrm) => write!(f, "{}", statefrm),
249
            Formula::Equation(i) => write!(f, "Equation({})", i),
250
        }
251
    }
252
}
253

            
254
/// Local struct to keep track of the translation state. The generic bound `E` is the
255
/// type of the edge labels.
256
///
257
/// Implements the translation from (s, Ψ) pairs to VPG vertices and edges.
258
/// However, to avoid the complication of merging sub-results we immediately
259
/// store the vertices and edges into mutable vectors. Furthermore, to avoid
260
/// stack overflows we use a breadth-first search approach with a queue. This
261
/// means that during queuing we immediately assign a fresh index to each (s, Ψ)
262
/// pair (if it does not yet exist) and then queue it to assign its actual
263
/// values later on.
264
pub(crate) struct Translation<'a, L, E> {
265
    vertex_map: IndexedSet<(StateIndex, Formula<'a>)>,
266
    vertices: Vec<Option<(Player, Priority)>>,
267
    edges: Vec<(VertexIndex, E, VertexIndex)>,
268

            
269
    /// Temporary storage used to check for duplicated outgoing edges.
270
    seen_modality_targets: HashMap<VertexIndex, usize>,
271

            
272
    /// Used for the breadth first search.
273
    queue: Vec<(StateIndex, Formula<'a>, VertexIndex)>,
274

            
275
    /// The parsed labels of the LTS.
276
    parsed_labels: &'a Vec<MultiAction>,
277

            
278
    /// The labelled transition system being translated.
279
    lts: &'a L,
280

            
281
    /// A reference to the modal equation system being translated.
282
    equation_system: &'a ModalEquationSystem,
283

            
284
    /// Use to print progress information.
285
    progress: TimeProgress<usize>,
286
}
287

            
288
impl<'a, L: LTS, E> Translation<'a, L, E> {
289
    /// Creates a new translation instance.
290
516
    pub fn new(lts: &'a L, parsed_labels: &'a Vec<MultiAction>, equation_system: &'a ModalEquationSystem) -> Self {
291
516
        let progress: TimeProgress<usize> = TimeProgress::new(
292
            |num_of_vertices: usize| {
293
                info!("Translated {} vertices...", num_of_vertices);
294
            },
295
            1,
296
        );
297

            
298
516
        Self {
299
516
            vertex_map: IndexedSet::new(),
300
516
            vertices: Vec::new(),
301
516
            edges: Vec::new(),
302
516
            seen_modality_targets: HashMap::new(),
303
516
            queue: Vec::new(),
304
516
            lts,
305
516
            parsed_labels,
306
516
            equation_system,
307
516
            progress,
308
516
        }
309
516
    }
310

            
311
    /// Perform the translation for the given `initial_state` and `initial_equation_index`.
312
    ///
313
    /// The `labelling` function is used to compute the edge label for the
314
    /// outgoing edges of this vertex. The argument is the transition
315
    /// corresponding to the modality, or None if there is no modality (e.g.,
316
    /// for conjunctions).
317
516
    pub fn translate<F, C>(
318
516
        &mut self,
319
516
        initial_state: StateIndex,
320
516
        initial_equation_index: usize,
321
516
        labelling: F,
322
516
        combine_labelling: C,
323
516
    ) -> Result<(), MercError>
324
516
    where
325
516
        F: Fn(Option<Transition>) -> E,
326
516
        C: Fn(&mut E, E) -> Result<(), MercError>,
327
    {
328
        // We store (state, formula, N) into the queue, where N is the vertex number assigned to this pair. This means
329
        // that during the traversal we can assume this N to exist.
330
516
        self.queue = vec![(
331
516
            initial_state,
332
516
            Formula::Equation(initial_equation_index),
333
516
            VertexIndex::new(0),
334
516
        )];
335
516
        self.vertex_map
336
516
            .insert((initial_state, Formula::Equation(initial_equation_index)));
337
516
        self.vertices.push(None); // Placeholder for the initial vertex
338

            
339
16847
        while let Some((s, formula, vertex_index)) = self.queue.pop() {
340
16331
            debug!("Translating vertex {}: (s={}, formula={})", vertex_index, s, formula);
341
16331
            self.progress.print(self.vertices.len());
342
16331
            match formula {
343
13279
                Formula::StateFrm(f) => {
344
13279
                    self.translate_vertex(s, f, vertex_index, &labelling, &combine_labelling)?;
345
                }
346
3052
                Formula::Equation(i) => {
347
3052
                    self.translate_equation(s, i, vertex_index, &labelling);
348
3052
                }
349
            }
350
        }
351

            
352
516
        Ok(())
353
516
    }
354

            
355
    /// Returns the collected vertices with placeholders removed.
356
516
    pub fn vertices(&self) -> Vec<(Player, Priority)> {
357
516
        debug_assert!(
358
16331
            self.vertices.iter().all(|v| v.is_some()),
359
            "All vertices should be assigned before retrieving the vertices"
360
        );
361

            
362
516
        self.vertices
363
516
            .iter()
364
16331
            .filter_map(|vertex| vertex.as_ref().copied())
365
516
            .collect()
366
516
    }
367

            
368
    /// Returns the collected edges, where the edge label is ignored.
369
2
    pub fn edges(&self) -> &Vec<(VertexIndex, E, VertexIndex)> {
370
2
        &self.edges
371
2
    }
372

            
373
    /// Translate a single vertex (s, Ψ) into the variability parity game vertex
374
    /// and its outgoing edges.
375
13279
    fn translate_vertex<F, C>(
376
13279
        &mut self,
377
13279
        s: StateIndex,
378
13279
        formula: &'a StateFrm,
379
13279
        vertex_index: VertexIndex,
380
13279
        labelling: &F,
381
13279
        combine_labelling: &C,
382
13279
    ) -> Result<(), MercError>
383
13279
    where
384
13279
        F: Fn(Option<Transition>) -> E,
385
13279
        C: Fn(&mut E, E) -> Result<(), MercError>,
386
    {
387
13279
        match formula {
388
329
            StateFrm::True => {
389
329
                // (s, true) → odd, 0
390
329
                self.set_vertex(vertex_index, Player::Odd, Priority::new(0));
391
329
            }
392
217
            StateFrm::False => {
393
217
                // (s, false) → even, 0
394
217
                self.set_vertex(vertex_index, Player::Even, Priority::new(0));
395
217
            }
396
3634
            StateFrm::Binary { op, lhs, rhs } => {
397
3634
                match op {
398
1311
                    StateFrmOp::Conjunction => {
399
1311
                        // (s, Ψ_1 ∧ Ψ_2) →_P odd, (s, Ψ_1) and (s, Ψ_2), 0
400
1311
                        self.set_vertex(vertex_index, Player::Odd, Priority::new(0));
401
1311
                        let s_psi_1 = self.queue_vertex(s, Formula::StateFrm(lhs));
402
1311
                        let s_psi_2 = self.queue_vertex(s, Formula::StateFrm(rhs));
403
1311

            
404
1311
                        self.edges.push((vertex_index, labelling(None), s_psi_1));
405
1311
                        self.edges.push((vertex_index, labelling(None), s_psi_2));
406
1311
                    }
407
2323
                    StateFrmOp::Disjunction => {
408
2323
                        // (s, Ψ_1 ∨ Ψ_2) →_P even, (s, Ψ_1) and (s, Ψ_2), 0
409
2323
                        self.set_vertex(vertex_index, Player::Even, Priority::new(0));
410
2323
                        let s_psi_1 = self.queue_vertex(s, Formula::StateFrm(lhs));
411
2323
                        let s_psi_2 = self.queue_vertex(s, Formula::StateFrm(rhs));
412
2323

            
413
2323
                        self.edges.push((vertex_index, labelling(None), s_psi_1));
414
2323
                        self.edges.push((vertex_index, labelling(None), s_psi_2));
415
2323
                    }
416
                    _ => {
417
                        unimplemented!("Cannot translate binary operator in {}", formula);
418
                    }
419
                }
420
            }
421
2537
            StateFrm::Id(identifier, _args) => {
422
2537
                let (i, _equation) = self
423
2537
                    .equation_system
424
2537
                    .find_equation_by_identifier(identifier)
425
2537
                    .expect("Variable must correspond to an equation");
426
2537

            
427
2537
                self.set_vertex(vertex_index, Player::Odd, Priority::new(0)); // The priority and owner do not matter here
428
2537
                let equation_vertex = self.queue_vertex(s, Formula::Equation(i));
429
2537
                self.edges.push((vertex_index, labelling(None), equation_vertex));
430
2537
            }
431
            StateFrm::Modality {
432
6562
                operator,
433
6562
                formula,
434
6562
                expr,
435
            } => {
436
6562
                self.translate_modality_vertex(s, *operator, formula, expr, vertex_index, labelling, combine_labelling)?;
437
            }
438
            _ => {
439
                unimplemented!("Cannot translate formula {}", formula);
440
            }
441
        }
442

            
443
13279
        Ok(())
444
13279
    }
445

            
446
    /// Translates a modality vertex (s, [a]Ψ) or (s, <a>Ψ) into the variability parity game vertex and its outgoing edges.
447
    /// 
448
    /// # Details
449
    /// 
450
    /// Applies the following transformations:
451
    /// 
452
    /// > (s, [a] Ψ) → odd, (s', Ψ) for all s' with s -a-> s', 0
453
    /// > (s, <a> Ψ) → even, (s', Ψ) for all s' with s -a-> s', 0
454
6562
    fn translate_modality_vertex<F, C>(
455
6562
        &mut self,
456
6562
        s: StateIndex,
457
6562
        operator: ModalityOperator,
458
6562
        formula: &'a RegFrm,
459
6562
        expr: &'a StateFrm,
460
6562
        vertex_index: VertexIndex,
461
6562
        labelling: &F,
462
6562
        combine_labelling: &C,
463
6562
    ) -> Result<(), MercError>
464
6562
    where
465
6562
        F: Fn(Option<Transition>) -> E,
466
6562
        C: Fn(&mut E, E) -> Result<(), MercError>,
467
    {
468
6562
        let player = match operator {
469
1788
            ModalityOperator::Box => Player::Odd,
470
4774
            ModalityOperator::Diamond => Player::Even,
471
        };
472

            
473
6562
        self.set_vertex(vertex_index, player, Priority::new(0));
474
6562
        self.seen_modality_targets.clear();
475

            
476
9016
        for transition in self.lts.outgoing_transitions(s) {
477
9016
            let action = &self.parsed_labels[*transition.label];
478

            
479
9016
            if match_regular_formula(formula, action) {
480
3043
                let s_prime_psi = self.queue_vertex(transition.to, Formula::StateFrm(expr));
481
3043
                let label = labelling(Some(transition));
482

            
483
3043
                if let Some(edge_index) = self.seen_modality_targets.get(&s_prime_psi).copied() {
484
                    combine_labelling(&mut self.edges[edge_index].1, label)?;
485
3043
                } else {
486
3043
                    let edge_index = self.edges.len();
487
3043
                    self.edges.push((vertex_index, label, s_prime_psi));
488
3043
                    self.seen_modality_targets.insert(s_prime_psi, edge_index);
489
3043
                }
490
5973
            }
491
        }
492

            
493
6562
        Ok(())
494
6562
    }
495

            
496
    /// Applies the translation to the given (s, equation) vertex.
497
3052
    fn translate_equation<F>(&mut self, s: StateIndex, equation_index: usize, vertex_index: VertexIndex, labelling: &F)
498
3052
    where
499
3052
        F: Fn(Option<Transition>) -> E,
500
    {
501
3052
        let equation = self.equation_system.equation(equation_index);
502
3052
        match equation.operator() {
503
2441
            FixedPointOperator::Least => {
504
2441
                // (s, μ X. Ψ) →_P odd, (s, Ψ[x := μ X. Ψ]), 2 * floor(AD(Ψ)/2) + 1. In Rust division is already floor.
505
2441
                self.set_vertex(
506
2441
                    vertex_index,
507
2441
                    Player::Odd,
508
2441
                    Priority::new(2 * (self.equation_system.alternation_depth(equation_index) / 2) + 1),
509
2441
                );
510
2441
                let s_psi = self.queue_vertex(s, Formula::StateFrm(equation.body()));
511
2441
                self.edges.push((vertex_index, labelling(None), s_psi));
512
2441
            }
513
611
            FixedPointOperator::Greatest => {
514
611
                // (s, ν X. Ψ) →_P even, (s, Ψ[x := ν X. Ψ]), 2 * floor(AD(Ψ)/2). In Rust division is already floor.
515
611
                self.set_vertex(
516
611
                    vertex_index,
517
611
                    Player::Even,
518
611
                    Priority::new(2 * (self.equation_system.alternation_depth(equation_index) / 2)),
519
611
                );
520
611
                let s_psi = self.queue_vertex(s, Formula::StateFrm(equation.body()));
521
611
                self.edges.push((vertex_index, labelling(None), s_psi));
522
611
            }
523
        }
524
3052
    }
525

            
526
    /// Assigns a concrete vertex value exactly once.
527
16331
    fn set_vertex(&mut self, vertex_index: VertexIndex, player: Player, priority: Priority) {
528
16331
        debug_assert!(
529
16331
            self.vertices[vertex_index].is_none(),
530
            "Vertex {vertex_index} was assigned more than once"
531
        );
532
16331
        self.vertices[vertex_index] = Some((player, priority));
533
16331
    }
534

            
535
    /// Queues a new pair to be translated, returning its vertex index.
536
15900
    fn queue_vertex(&mut self, s: StateIndex, formula: Formula<'a>) -> VertexIndex {
537
15900
        let (index, inserted) = self.vertex_map.insert((s, formula.clone()));
538
15900
        let vertex_index = VertexIndex::new(*index);
539

            
540
15900
        if inserted {
541
15815
            // New vertex, assign placeholder values
542
15815
            self.vertices.resize(*vertex_index + 1, None);
543
15815
            self.queue.push((s, formula, vertex_index));
544
15815
        }
545

            
546
15900
        vertex_index
547
15900
    }
548
}
549

            
550
/// Returns true iff the given action matches the regular formula.
551
9016
fn match_regular_formula(formula: &RegFrm, action: &MultiAction) -> bool {
552
9016
    match formula {
553
9016
        RegFrm::Action(action_formula) => match_action_formula(action_formula, action),
554
        RegFrm::Choice { lhs, rhs } => match_regular_formula(lhs, action) || match_regular_formula(rhs, action),
555
        _ => {
556
            unimplemented!("Cannot translate regular formula {}", formula);
557
        }
558
    }
559
9016
}
560

            
561
/// Returns true iff the given action matches the action formula.
562
9016
fn match_action_formula(formula: &ActFrm, action: &MultiAction) -> bool {
563
9016
    match formula {
564
        ActFrm::True => true,
565
        ActFrm::False => false,
566
9016
        ActFrm::MultAct(expected_action) => expected_action == action,
567
        ActFrm::Binary { op, lhs, rhs } => match op {
568
            ActFrmBinaryOp::Union => match_action_formula(lhs, action) || match_action_formula(rhs, action),
569
            ActFrmBinaryOp::Intersect => match_action_formula(lhs, action) && match_action_formula(rhs, action),
570
            _ => {
571
                unimplemented!("Cannot translate binary operator {}", formula);
572
            }
573
        },
574
        ActFrm::Negation(expr) => !match_action_formula(expr, action),
575
        _ => {
576
            unimplemented!("Cannot translate action formula {}", formula);
577
        }
578
    }
579
9016
}
580

            
581
#[cfg(test)]
582
mod tests {
583
    use merc_lts::read_aut;
584
    use merc_macros::merc_test;
585
    use merc_syntax::UntypedStateFrmSpec;
586

            
587
    use crate::PG;
588

            
589
    use super::*;
590

            
591
    #[merc_test]
592
    #[cfg_attr(miri, ignore)] // Oxidd does not work with miri
593
    fn test_running_example() {
594
        let lts = read_aut(include_bytes!("../../../examples/lts/abp.aut") as &[u8], Vec::new()).unwrap();
595
        let formula = UntypedStateFrmSpec::parse(include_str!("../../../examples/vpg/running_example.mcf")).unwrap();
596

            
597
        let pg: ParityGame = translate(&lts, &formula.formula).unwrap();
598

            
599
        assert!(pg.is_total(), "The translated parity game should be total");
600
        assert!(
601
8
            compute_reachable(&pg).1.iter().all(|v| v.is_some()),
602
            "All vertices should be reachable from the initial vertex"
603
        );
604
    }
605
}