taco_parser/
tla.rs

1//! TLA+ Parser module
2//!
3//! This module implements the parse for TLA+ specification files. You can find
4//! more information on the supported syntax under `./docs/tla-to-ta.md`
5
6use std::collections::HashMap;
7
8use taco_display_utils::join_iterator;
9
10use log::{debug, warn};
11use pest::{
12    Parser, Span,
13    iterators::{Pair, Pairs},
14};
15use pest_derive::Parser;
16use taco_model_checker::eltl::ELTLSpecificationBuilder;
17
18use anyhow::{Context, Error, anyhow};
19use taco_threshold_automaton::{
20    ModifiableThresholdAutomaton, RuleDefinition, ThresholdAutomaton,
21    expressions::{
22        Atomic, BooleanConnective, BooleanExpression, ComparisonOp, IntegerExpression, IntegerOp,
23        IsDeclared, Location, Parameter, Variable,
24    },
25    general_threshold_automaton::{
26        self, GeneralThresholdAutomaton,
27        builder::{
28            GeneralThresholdAutomatonBuilder, InitializedGeneralThresholdAutomatonBuilder,
29            RuleBuilder,
30        },
31    },
32};
33
34use crate::{
35    ParseTA, ParseTAWithLTL,
36    tla::integer_exprs::{
37        parse_int_boolean_expr, parse_integer_const, parse_integer_update_expr,
38        parse_ltl_specification,
39    },
40};
41
42mod integer_exprs;
43
44/// Constant for representing set of processes
45const PROC_SET: &str = "Processes";
46/// Constant for specifying the number of correct processes
47const N_CORR_PROC: &str = "NbOfCorrProc";
48/// Variable that is used to specify the set of locations
49const LOCATIONS_SET: &str = "ProcessesLocations";
50
51// Location of the grammar file and generation of parser
52#[allow(missing_docs)]
53#[derive(Parser)]
54#[grammar = "./tla_format.pest"]
55struct PestTLAParser;
56
57impl ParseTA for TLAParser {
58    fn parse_ta(&self, input: &str) -> Result<GeneralThresholdAutomaton, Error> {
59        let (ta, _, _) = self.parse_ta_and_return_ltl_pairs(input)?;
60        Ok(ta)
61    }
62}
63
64impl ParseTAWithLTL for TLAParser {
65    fn parse_ta_and_spec(
66        &self,
67        input: &str,
68    ) -> Result<
69        (
70            GeneralThresholdAutomaton,
71            taco_model_checker::eltl::ELTLSpecification,
72        ),
73        Error,
74    > {
75        let (ta, pairs, ctx) = self.parse_ta_and_return_ltl_pairs(input)?;
76
77        let ltl_spec = self.parse_specification_and_add_expressions(
78            pairs,
79            ELTLSpecificationBuilder::new(&ta),
80            ctx,
81        )?;
82        let ltl_spec = ltl_spec.build();
83
84        Ok((ta, ltl_spec))
85    }
86}
87
88/// Helper context for storing information such as declared integer macros
89/// during parsing
90struct TLAParsingContext<'a> {
91    /// Integer expression that defines relation of the number of correct
92    /// processes to the other parameters of a threshold automaton
93    param_to_corr_procs: Option<IntegerExpression<Location>>,
94    /// Integers macros that have already been declared
95    int_macro: HashMap<String, Pair<'a, Rule>>,
96    /// Stores whether a `Spec` declaration has already been parsed
97    spec_declaration_parsed: bool,
98    /// Stores which rule ids appear inside the `Next` declaration
99    rule_ids_in_next: Vec<u32>,
100}
101impl TLAParsingContext<'_> {
102    pub fn new() -> Self {
103        TLAParsingContext {
104            param_to_corr_procs: None,
105            int_macro: HashMap::new(),
106            spec_declaration_parsed: false,
107            rule_ids_in_next: Vec::new(),
108        }
109    }
110}
111
112/// Parser for supported subset of TLA+
113///
114/// This parser is designed to parse TLA+ specifications that fall into the
115/// translatable fragment
116pub struct TLAParser {}
117
118impl Default for TLAParser {
119    fn default() -> Self {
120        Self::new()
121    }
122}
123
124impl TLAParser {
125    /// Create a new parser for TLA specifications
126    pub fn new() -> Self {
127        TLAParser {}
128    }
129
130    /// Parse specification list into ltl expressions
131    fn parse_specification_and_add_expressions<'a>(
132        &self,
133        pair: Vec<Pair<'a, Rule>>,
134        mut builder: ELTLSpecificationBuilder<'a, GeneralThresholdAutomaton>,
135        ctx: TLAParsingContext,
136    ) -> Result<ELTLSpecificationBuilder<'a, GeneralThresholdAutomaton>, anyhow::Error> {
137        let pairs = pair.into_iter();
138
139        let expressions = pairs
140            .map(|spec| parse_ltl_specification(spec, &builder, &ctx))
141            .collect::<Result<Vec<_>, Error>>()
142            .with_context(|| "Failed to parse specifications: ")?;
143
144        builder
145            .add_expressions(expressions)
146            .with_context(|| "Specification failed validation: ")?;
147        Ok(builder)
148    }
149
150    fn parse_ta_and_return_ltl_pairs<'a>(
151        &self,
152        input: &'a str,
153    ) -> Result<
154        (
155            GeneralThresholdAutomaton,
156            Vec<Pair<'a, Rule>>,
157            TLAParsingContext<'a>,
158        ),
159        Error,
160    > {
161        let mut ctx = TLAParsingContext::new();
162
163        let mut pairs = PestTLAParser::parse(Rule::tla_definition, input)?;
164
165        let mut pairs = pairs
166            .next()
167            .expect("Missing: variable declaration")
168            .into_inner();
169        let mut pair = pairs.next().expect("Missing: constant declaration");
170
171        pair = Self::validate_module_imports(&mut pairs, pair);
172
173        let builder = GeneralThresholdAutomatonBuilder::new("tla_ta");
174
175        let (mut pair, builder) = Self::parse_constant_declaration(&mut pairs, pair, builder)?;
176
177        // RC conditions can only be parsed once parameters, locations and variables have been declared
178        let mut assume_to_parse_later = Vec::new();
179        while pair.as_rule() == Rule::assume_declaration {
180            assume_to_parse_later.push(pair);
181            pair = pairs.next().expect("Missing: constant declaration");
182        }
183
184        let (pair, builder) = Self::parse_variable_declaration(&mut pairs, pair, builder)?;
185
186        let (pair, builder) = Self::parse_type_ok(&mut pairs, pair, builder)?;
187
188        let mut builder = builder;
189        for rc in assume_to_parse_later {
190            builder = Self::parse_assume_declaration(rc, builder, &mut ctx)?;
191        }
192
193        if ctx.param_to_corr_procs.is_none() {
194            return Err(anyhow!(
195                "Error after parsing all assumptions ('ASSSUME' statements): \
196Missing constraint on the number of correct processes. You need to specify the \
197relation of the number of correct processes '{N_CORR_PROC}' to the other \
198parameters (example: '{N_CORR_PROC} = N - F')."
199            ));
200        }
201
202        let (pair, builder) =
203            Self::parse_initial_constraint_declaration(&mut pairs, pair, builder, &mut ctx)?;
204        if pair.is_none() {
205            warn!("TLA file does not declare rules");
206            return Ok((builder.build(), Vec::new(), ctx));
207        }
208
209        let pair = Self::parse_int_macro_declarations(&mut pairs, Some(pair.unwrap()), &mut ctx);
210        if pair.is_none() {
211            warn!("TLA file does not declare rules");
212            return Ok((builder.build(), Vec::new(), ctx));
213        }
214
215        let (pair, builder) =
216            Self::parse_rule_declarations(&mut pairs, pair.unwrap(), builder, &mut ctx)?;
217
218        let pair = Self::parse_next_declaration(&mut pairs, pair, &mut ctx)?;
219
220        let pair = Self::validate_spec_declaration(&mut pairs, pair, &mut ctx, &builder)?;
221
222        let mut ltl_pairs = Vec::new();
223
224        let mut pair = pair;
225        while pair.is_some() {
226            let inner_pair = pair.unwrap();
227
228            if inner_pair.as_rule() != Rule::int_macro_declaration
229                && inner_pair.as_rule() != Rule::ltl_spec_declaration
230            {
231                pair = Some(inner_pair);
232                break;
233            }
234
235            if inner_pair.as_rule() == Rule::int_macro_declaration {
236                pair = Self::parse_int_macro_declarations(&mut pairs, Some(inner_pair), &mut ctx);
237                continue;
238            }
239
240            ltl_pairs.push(inner_pair);
241            pair = pairs.next();
242        }
243
244        let pair = Self::parse_next_declaration(&mut pairs, pair, &mut ctx)?;
245
246        let pair = Self::validate_spec_declaration(&mut pairs, pair, &mut ctx, &builder)?;
247
248        debug_assert!(
249            pair.unwrap().as_rule() == Rule::EOI,
250            "Expected no more tokens!"
251        );
252
253        if ctx.rule_ids_in_next.is_empty() {
254            warn!(
255                "TLA file does not declare 'Next' or the 'Next' declaration \
256does not specify any rules. This will break compatibility with TLC. Using all \
257declared rules!"
258            );
259        }
260
261        let mut ta = builder.build();
262
263        let rules_not_in_next = ta
264            .rules()
265            .filter(|r| !ctx.rule_ids_in_next.contains(&r.id()))
266            .cloned()
267            .collect::<Vec<_>>();
268        if !rules_not_in_next.is_empty() {
269            warn!(
270                "The next constraint excludes some rules from the threshold \
271automaton. The following rules will be not appear in the automaton: {}",
272                join_iterator(
273                    rules_not_in_next
274                        .iter()
275                        .map(|r| format!("'Rule{}'", r.id())),
276                    ", "
277                )
278            );
279            ta.retain_rules(|r| !rules_not_in_next.contains(r));
280        }
281
282        Ok((ta, ltl_pairs, ctx))
283    }
284
285    /// Validate the imports appearing in the TLA file
286    ///
287    /// This function will not fail, it will only produce warnings on TLC
288    /// compatibility
289    fn validate_module_imports<'a>(
290        pairs: &mut Pairs<'a, Rule>,
291        pair: Pair<'a, Rule>,
292    ) -> Pair<'a, Rule> {
293        if pair.as_rule() != Rule::module_declaration {
294            warn!(
295                "No module imports found. To maintain compatibility with TLC, \
296at least 'Integers' and 'FiniteSets' should be imported (use \
297'EXTENDS Integers, FiniteSets' at the beginning of the file)."
298            );
299            return pair;
300        }
301
302        let mut inner_pairs = pair.into_inner();
303        let ident_list = inner_pairs
304            .next()
305            .expect("Expected identifier list in EXTENDS");
306
307        let module_identifiers: Vec<String> = parse_identifier_list_to_t(ident_list);
308        debug!(
309            "TLA file imports modules: {}",
310            join_iterator(module_identifiers.iter(), ", ")
311        );
312
313        if !module_identifiers.contains(&"Integers".to_string()) {
314            warn!(
315                "Module import 'Integers' not found. This will break \
316compatibility with TLC."
317            );
318        }
319
320        if !module_identifiers.contains(&"FiniteSets".to_string()) {
321            warn!(
322                "Module  import 'FiniteSet' not found. This will break \
323compatibility with TLC."
324            );
325        }
326
327        pairs.next().expect("Missing: constant declaration")
328    }
329
330    /// Parses all constant declarations
331    ///
332    /// This block will parse all consecutive `constant_declaration`s pairs.
333    /// Every such block is `CONSTANT` declaration and declares the parameters
334    /// of a threshold automaton.
335    /// Additionally, the constants `NbOfCorrProcesses`  and `Processes` should
336    /// be declared.
337    fn parse_constant_declaration<'a>(
338        pairs: &mut Pairs<'a, Rule>,
339        pair: Pair<'a, Rule>,
340        builder: GeneralThresholdAutomatonBuilder,
341    ) -> Result<(Pair<'a, Rule>, GeneralThresholdAutomatonBuilder), anyhow::Error> {
342        debug_assert!(
343            pair.as_rule() == Rule::constant_declaration,
344            "Got rule {:?} for {}",
345            pair.as_rule(),
346            pair.as_str()
347        );
348
349        let mut found_params = Vec::new();
350        let mut pair = pair;
351        while pair.as_rule() == Rule::constant_declaration {
352            let mut inner_pairs = pair.into_inner();
353            let inner_pair = inner_pairs.next().unwrap();
354
355            debug_assert!(inner_pairs.next().is_none());
356
357            let params: Vec<Parameter> = parse_identifier_list_to_t(inner_pair);
358            found_params.extend(params);
359
360            pair = pairs.next().expect("Missing: variable declaration");
361        }
362
363        if !found_params.contains(&Parameter::new(PROC_SET)) {
364            return Err(anyhow!(
365                "Missing special constant '{PROC_SET}': No declaration for\
366constant '{PROC_SET}' which represents the set of processes was found. Please \
367add a declaration for constant '{PROC_SET}'."
368            ));
369        }
370
371        if !found_params.contains(&Parameter::new(N_CORR_PROC)) {
372            return Err(anyhow!(
373                "Missing special constant '{N_CORR_PROC}': No declaration for\
374constant '{N_CORR_PROC}' which represents the number of processes executing \
375correctly was found. Please add a declaration for constant '{N_CORR_PROC}'."
376            ));
377        }
378        debug!(
379            "TLA file declares constants: {}",
380            join_iterator(found_params.iter(), ", ")
381        );
382
383        // remove the special parameters before adding to the threshold automaton
384        found_params
385            .retain(|p| p != &Parameter::new(PROC_SET) && p != &Parameter::new(N_CORR_PROC));
386
387        let builder = builder.with_parameters(found_params).with_context(|| {
388            "Error while parsing 'CONSTANT' / 'CONSTANTS' declaration, i.e., \
389parameters for the threshold automaton: "
390        })?;
391
392        Ok((pair, builder))
393    }
394
395    /// Parse and validate an `ASSUME` block, i.e. a resilience condition
396    ///
397    /// This function parses one `ASSUME` definition, but there can be
398    /// potentially multiple ones
399    fn parse_assume_declaration<'a>(
400        pair: Pair<'a, Rule>,
401        builder: InitializedGeneralThresholdAutomatonBuilder,
402        ctx: &mut TLAParsingContext,
403    ) -> Result<InitializedGeneralThresholdAutomatonBuilder, anyhow::Error> {
404        debug_assert!(
405            pair.as_rule() == Rule::assume_declaration,
406            "Got rule {:?} for {}",
407            pair.as_rule(),
408            pair.as_str()
409        );
410        let err_context_msg = "Failed to parse 'ASSUME' / resilience condition: ";
411
412        let mut inner_pairs = pair.into_inner();
413        let inner_pair = inner_pairs.next().expect("Missing: condition for assume");
414        let inner_span = inner_pair.as_span();
415
416        debug_assert!(inner_pairs.next().is_none());
417
418        let rc_cond =
419            parse_int_boolean_expr(inner_pair, &builder, ctx).with_context(|| err_context_msg);
420
421        let mut rc_split = split_conjunct_bool_expr(rc_cond.unwrap());
422        validate_and_add_n_corr_proc_constr(ctx, &mut rc_split, inner_span)
423            .with_context(|| err_context_msg)?;
424
425        let builder = builder
426            .with_resilience_conditions(rc_split)
427            .with_context(|| err_context_msg)?;
428
429        Ok(builder)
430    }
431
432    /// Parse a variable declaration, i.e. the `VARIABLE` / `VARIABLES`
433    /// declaration of of a TLA file
434    ///
435    /// This declaration needs to declare all shared variables of the resulting
436    /// TA, as well as the variable 'ProcessesLocations' which is the function
437    /// from process identifiers to their current location.
438    fn parse_variable_declaration<'a>(
439        pairs: &mut Pairs<'a, Rule>,
440        pair: Pair<'a, Rule>,
441        builder: GeneralThresholdAutomatonBuilder,
442    ) -> Result<(Pair<'a, Rule>, GeneralThresholdAutomatonBuilder), anyhow::Error> {
443        if pair.as_rule() != Rule::variable_declaration {
444            warn!(
445                "Did not find any declared shared variables. Variables must be \
446declared using 'VARIABLE' / 'VARIABLES' declaration."
447            );
448            return Ok((pair, builder));
449        }
450
451        let mut inner_pairs = pair.into_inner();
452        let inner_pair = inner_pairs.next().unwrap();
453
454        let mut shared_vars: Vec<Variable> = parse_identifier_list_to_t(inner_pair);
455
456        if !shared_vars.contains(&Variable::new(LOCATIONS_SET)) {
457            return Err(anyhow!(
458                "Missing special variable '{LOCATIONS_SET}': No declaration for \
459variable '{LOCATIONS_SET}' which is used to declares the set of locations. \
460Please add a declaration for variable '{LOCATIONS_SET}'."
461            ));
462        }
463
464        debug!(
465            "TLA file declares constants: {}",
466            join_iterator(shared_vars.iter(), ", ")
467        );
468
469        shared_vars.retain(|v| v != &Variable::new(LOCATIONS_SET));
470
471        let builder = builder.with_variables(shared_vars)?;
472
473        let pair = pairs.next().expect("Missing: TypeOk");
474
475        Ok((pair, builder))
476    }
477
478    /// Parse the `TypeOk` block into the set of locations and validates that it
479    /// declares a type for all shared variables, that should be declared as
480    /// type 'Nat'.
481    fn parse_type_ok<'a>(
482        pairs: &mut Pairs<'a, Rule>,
483        pair: Pair<'a, Rule>,
484        builder: GeneralThresholdAutomatonBuilder,
485    ) -> Result<(Pair<'a, Rule>, InitializedGeneralThresholdAutomatonBuilder), anyhow::Error> {
486        debug_assert!(
487            pair.as_rule() == Rule::typeok_declaration,
488            "Got rule {:?} for {}",
489            pair.as_rule(),
490            pair.as_str()
491        );
492        let ctx_err_msg = "Error while parsing correctness \
493constraint 'TypeOk': ";
494
495        let mut variables_declared_nat = Vec::new();
496        let mut set_of_locs = Vec::new();
497
498        for set_expr in pair.into_inner() {
499            debug_assert!(set_expr.as_rule() == Rule::set_boolean_expr);
500
501            let mut inner_pairs = set_expr.into_inner();
502
503            let ident: Variable = parse_identifier_to_t(
504                &inner_pairs
505                    .next()
506                    .expect("Invalid set_boolean_expr: Missing identifier"),
507            );
508            let set_expr = inner_pairs
509                .next()
510                .expect("Invalid set_boolean_expr: Missing set");
511
512            // Parse special declaration of the type of ProcessesLocation to the
513            // set of locations of the threshold automaton
514            if ident == Variable::new(LOCATIONS_SET) {
515                if !set_of_locs.is_empty() {
516                    return new_parsing_error_with_ctx(
517                        "Second declaration of set of locations found: ",
518                        set_expr.as_span(),
519                        ctx_err_msg,
520                    );
521                }
522
523                set_of_locs = parse_set_of_locations(set_expr).with_context(|| ctx_err_msg)?;
524                continue;
525            }
526
527            if set_expr.as_rule() != Rule::nat {
528                return new_parsing_error_with_ctx(
529                    format!(
530                        "Shared variables are expected to be of type 'Nat' but \
531'{}'  is declared as type '{}'.",
532                        ident.name(),
533                        set_expr.as_str()
534                    ),
535                    set_expr.as_span(),
536                    ctx_err_msg,
537                );
538            }
539
540            variables_declared_nat.push((ident, set_expr));
541        }
542
543        let builder = builder.with_locations(set_of_locs)?;
544
545        let pair = pairs.next().expect("Missing: init constraint");
546        let builder = builder.initialize();
547
548        // Check that all variables which have a type declaration have been declared first
549        for (var, expr) in variables_declared_nat {
550            if !builder.has_variable(&var) {
551                return new_parsing_error_with_ctx(
552                    format!(
553                        "Type declaration for unknown variable '{}' found",
554                        var.name()
555                    ),
556                    expr.as_span(),
557                    ctx_err_msg,
558                );
559            }
560        }
561
562        Ok((pair, builder))
563    }
564
565    /// Parse the initial constraints of an `Init` block
566    fn parse_initial_constraint_declaration<'a>(
567        pairs: &mut Pairs<'a, Rule>,
568        pair: Pair<'a, Rule>,
569        builder: InitializedGeneralThresholdAutomatonBuilder,
570        ctx: &mut TLAParsingContext,
571    ) -> Result<
572        (
573            Option<Pair<'a, Rule>>,
574            InitializedGeneralThresholdAutomatonBuilder,
575        ),
576        anyhow::Error,
577    > {
578        if pair.as_rule() != Rule::init_constraint_declaration {
579            debug!("No initial constraints have been found in TLA file");
580            return Ok((Some(pair), builder));
581        }
582        let err_context_msg = "Error while parsing  initial constraints /
583'Init': ";
584
585        let inner_pairs = pair.into_inner();
586
587        let mut builder = builder;
588
589        for init_constr in inner_pairs {
590            debug_assert!(init_constr.as_rule() == Rule::init_constraint_expr);
591
592            let mut inner_pairs = init_constr.into_inner();
593            let inner_pair = inner_pairs.next().expect("Expected initial constraint");
594
595            if inner_pair.as_rule() == Rule::int_bool_expr {
596                let var_expr = parse_int_boolean_expr(inner_pair, &builder, ctx)
597                    .with_context(|| err_context_msg)?;
598                // Split first-level conjuncts for nicer representation
599                let split_var_expr = split_conjunct_bool_expr(var_expr);
600
601                builder = builder
602                    .with_initial_variable_constraints(split_var_expr)
603                    .with_context(|| err_context_msg)?;
604                continue;
605            }
606
607            if inner_pair.as_rule() == Rule::set_boolean_expr {
608                let loc_constr = parse_initial_loc_constr(inner_pair, &builder, ctx)?;
609                builder = builder
610                    .with_initial_location_constraints(loc_constr)
611                    .with_context(|| err_context_msg)?;
612                continue;
613            }
614
615            debug_assert!(false, "Expected location or variable expression")
616        }
617
618        let pair = pairs.next();
619        Ok((pair, builder))
620    }
621
622    /// Parse all consecutive declaration of integer macros
623    fn parse_int_macro_declarations<'a>(
624        pairs: &mut Pairs<'a, Rule>,
625        pair: Option<Pair<'a, Rule>>,
626        ctx: &mut TLAParsingContext<'a>,
627    ) -> Option<Pair<'a, Rule>> {
628        let mut opt_pair = pair;
629        while opt_pair.is_some() {
630            let pair = opt_pair.unwrap();
631            if pair.as_rule() != Rule::int_macro_declaration {
632                opt_pair = Some(pair);
633                break;
634            }
635
636            let mut inner_pairs = pair.into_inner();
637
638            let ident_pair = inner_pairs.next().expect("Missing: macro ident");
639            let ident: String = parse_identifier_to_t(&ident_pair);
640
641            let expr_pair = inner_pairs.next().expect("Missing: integer macro int expr");
642            debug_assert!(expr_pair.as_rule() == Rule::integer_expr);
643
644            debug!(
645                "TLA file declared integer macro: '{ident}' := '{}'",
646                expr_pair.as_str()
647            );
648
649            ctx.int_macro.insert(ident, expr_pair);
650            opt_pair = pairs.next()
651        }
652
653        opt_pair
654    }
655
656    /// Parse all consecutive declarations of rules
657    fn parse_rule_declarations<'a>(
658        pairs: &mut Pairs<'a, Rule>,
659        pair: Pair<'a, Rule>,
660        builder: InitializedGeneralThresholdAutomatonBuilder,
661        ctx: &mut TLAParsingContext<'a>,
662    ) -> Result<
663        (
664            Option<Pair<'a, Rule>>,
665            InitializedGeneralThresholdAutomatonBuilder,
666        ),
667        anyhow::Error,
668    > {
669        let mut opt_pair = Some(pair);
670        let mut builder = builder;
671
672        while opt_pair.is_some() {
673            let pair = opt_pair.unwrap();
674            if pair.as_rule() != Rule::rule_declaration {
675                opt_pair = Some(pair);
676                break;
677            }
678
679            let rule_span = pair.as_span();
680            let mut inner_pairs = pair.into_inner();
681
682            let id_pair = inner_pairs.next().expect("Missing: rule_ident");
683            let (id, proc_ident) = parse_rule_ident_to_id_and_proc_ident(id_pair);
684
685            let rule =
686                parse_rule_body_exprs(inner_pairs, rule_span, &builder, id, &proc_ident, ctx)?;
687            debug!("TLA file declared rule: {rule}");
688            builder = builder
689                .with_rule(rule)
690                .with_context(|| "Error while parsing rule: ")?;
691
692            opt_pair = pairs.next();
693        }
694
695        Ok((opt_pair, builder))
696    }
697
698    /// Parse a `Next` declaration and extract all appearing rules
699    fn parse_next_declaration<'a>(
700        pairs: &mut Pairs<'a, Rule>,
701        pair: Option<Pair<'a, Rule>>,
702        ctx: &mut TLAParsingContext,
703    ) -> Result<Option<Pair<'a, Rule>>, anyhow::Error> {
704        if pair.is_none() {
705            return Ok(pair);
706        }
707
708        let pair = pair.unwrap();
709        if pair.as_rule() != Rule::next_declaration {
710            return Ok(Some(pair));
711        }
712
713        if !ctx.rule_ids_in_next.is_empty() {
714            return new_parsing_error_with_ctx(
715                "Duplicate next declaration found.",
716                pair.as_span(),
717                "Error while parsing 'Next' specification",
718            );
719        }
720
721        let rules = parse_next_expr(pair)?;
722        ctx.rule_ids_in_next.extend(rules);
723
724        Ok(pairs.next())
725    }
726
727    /// Parse a `Spec` declaration and validate that it is consistent
728    fn validate_spec_declaration<'a>(
729        pairs: &mut Pairs<'a, Rule>,
730        pair: Option<Pair<'a, Rule>>,
731        ctx: &mut TLAParsingContext,
732        builder: &InitializedGeneralThresholdAutomatonBuilder,
733    ) -> Result<Option<Pair<'a, Rule>>, anyhow::Error> {
734        if pair.is_none() {
735            return Ok(None);
736        }
737
738        let pair = pair.unwrap();
739        let pair_span = pair.as_span();
740        if pair.as_rule() != Rule::spec_declaration {
741            return Ok(Some(pair));
742        }
743        let err_ctx_msg = "Error while parsing Spec declaration: ";
744
745        if ctx.spec_declaration_parsed {
746            return new_parsing_error_with_ctx(
747                "Duplicate definition of 'Spec'.",
748                pair_span,
749                err_ctx_msg,
750            );
751        }
752        ctx.spec_declaration_parsed = true;
753
754        let mut inner_pairs = pair.into_inner();
755        let vars_referenced: Vec<Variable> =
756            parse_identifier_list_to_t(inner_pairs.next().expect("Missing: Updated params"));
757
758        let proc_var = Variable::new(LOCATIONS_SET);
759
760        if let Some(var) = vars_referenced
761            .iter()
762            .find(|v| *v != &proc_var && !builder.is_declared(*v))
763        {
764            return new_parsing_error_with_ctx(
765                format!("Undeclared variable '{var}'"),
766                pair_span,
767                err_ctx_msg,
768            );
769        }
770
771        if let Some(var) = builder.variables().find(|v| !vars_referenced.contains(v)) {
772            return new_parsing_error_with_ctx(
773                format!("Next instantiation does not include variable '{var}'"),
774                pair_span,
775                err_ctx_msg,
776            );
777        }
778
779        if !vars_referenced.contains(&proc_var) {
780            return new_parsing_error_with_ctx(
781                format!(
782                    "Next instantiation does not include the location of processes represented by variable '{LOCATIONS_SET}'"
783                ),
784                pair_span,
785                err_ctx_msg,
786            );
787        }
788
789        Ok(pairs.next())
790    }
791}
792
793/// Validate and remove constraints on special parameter N_CORR_PROC
794///
795/// The function will parse the constraint into an integer expression and add it
796/// to the parsing context.
797fn validate_and_add_n_corr_proc_constr(
798    ctx: &mut TLAParsingContext,
799    rcs: &mut Vec<BooleanExpression<Parameter>>,
800    span: Span<'_>,
801) -> Result<(), anyhow::Error> {
802    let err_ctx_msg = "Error while validating assumptions on the number  correct processes";
803
804    for rc in rcs {
805        match rc {
806            BooleanExpression::ComparisonExpression(lhs, op, rhs) => {
807                let mut other_expr = None;
808                if lhs.as_ref() == &IntegerExpression::Atom(Parameter::new(N_CORR_PROC))
809                    || lhs.as_ref() == &IntegerExpression::Param(Parameter::new(N_CORR_PROC))
810                {
811                    other_expr = Some(*rhs.clone());
812                }
813
814                if rhs.as_ref() == &IntegerExpression::Atom(Parameter::new(N_CORR_PROC))
815                    || rhs.as_ref() == &IntegerExpression::Param(Parameter::new(N_CORR_PROC))
816                {
817                    other_expr = Some(*lhs.clone())
818                }
819                if other_expr.is_none() {
820                    continue;
821                }
822
823                let other_expr = other_expr.unwrap();
824
825                if other_expr.contains_atom_a(&Parameter::new(N_CORR_PROC))
826                    || op != &ComparisonOp::Eq
827                {
828                    return new_parsing_error_with_ctx(
829                        format!(
830                            "Invalid declaration of the number of correct \
831processes '{}{}{}' does not specify a concrete number of correct processes. \
832Expected constraint of the form '{N_CORR_PROC} == <EXPR>' where <EXPR> is an \
833integer expression over the parameters of the threshold automaton",
834                            lhs, op, rhs
835                        ),
836                        span,
837                        err_ctx_msg,
838                    );
839                }
840
841                if ctx.param_to_corr_procs.is_some() {
842                    return new_parsing_error_with_ctx(
843                        format!(
844                            "Found second constraint on the number of correct \
845processes: '{}{}{}'. There can only be one constraint on '{N_CORR_PROC}'!",
846                            lhs, op, rhs
847                        ),
848                        span,
849                        err_ctx_msg,
850                    );
851                }
852
853                debug!(
854                    "TLA file declares the number of correct processes as: {}",
855                    other_expr
856                );
857
858                ctx.param_to_corr_procs = Some(other_expr.into());
859                *rc = BooleanExpression::True;
860            }
861            BooleanExpression::BinaryExpression(_, BooleanConnective::And, _) => {
862                debug_assert!(
863                    false,
864                    "Boolean expression should have been split before validation!"
865                );
866            }
867            _ => {}
868        }
869    }
870
871    Ok(())
872}
873
874/// Parse a constraint over ` ProcessesLocations` into the set of initial
875/// locations
876///
877/// This function is meant to parse a constraint of the form
878/// `ProcessesLocations \in [Processes -> {"loc0", "loc1"}]`
879/// appearing in the initial constraints and derive the boolean expressions that
880/// are necessary to represent the initial constraints.
881fn parse_initial_loc_constr(
882    pair: Pair<'_, Rule>,
883    builder: &InitializedGeneralThresholdAutomatonBuilder,
884    ctx: &mut TLAParsingContext,
885) -> Result<Vec<BooleanExpression<Location>>, anyhow::Error> {
886    let err_ctx_msg = "Error while parsing set of locations: ";
887    let mut inner_pairs = pair.into_inner();
888
889    let ident_pair = &inner_pairs
890        .next()
891        .expect("Invalid set_bool_expr: Missing identifier");
892    let ident: Variable = parse_identifier_to_t(ident_pair);
893    if ident != Variable::new(LOCATIONS_SET) {
894        return new_parsing_error_with_ctx(
895            "Set constraint found that is not over the locations of processes",
896            ident_pair.as_span(),
897            err_ctx_msg,
898        );
899    }
900
901    let set_expr = inner_pairs
902        .next()
903        .expect("Invalid set_bool_expr: Missing set");
904
905    let init_locs = parse_set_of_locations(set_expr).with_context(|| err_ctx_msg)?;
906
907    debug!(
908        "TLA file declares the following locations as initial: {}",
909        join_iterator(init_locs.iter(), ", ")
910    );
911
912    // Add constraint for all locations that are not initial, setting them to 0
913    let not_init_locs = builder
914        .locations()
915        .filter(|l| !init_locs.contains(l))
916        .map(|l| {
917            BooleanExpression::ComparisonExpression(
918                Box::new(IntegerExpression::Atom(l.clone())),
919                ComparisonOp::Eq,
920                Box::new(IntegerExpression::Const(0)),
921            )
922        });
923
924    if init_locs.is_empty() {
925        warn!("No initial location found!");
926        return Ok(not_init_locs.collect());
927    }
928
929    // Sum over all initial locations
930    let first_loc = IntegerExpression::Atom(init_locs[0].clone());
931    let init_loc_sum = init_locs.iter().skip(1).fold(first_loc, |acc, l| {
932        IntegerExpression::BinaryExpr(
933            Box::new(acc),
934            IntegerOp::Add,
935            Box::new(IntegerExpression::Atom(l.clone())),
936        )
937    });
938
939    // constraint specifying that the correct number of processes starts in the initial location
940    let init_proc_constr = BooleanExpression::ComparisonExpression(
941        Box::new(init_loc_sum),
942        ComparisonOp::Eq,
943        Box::new(ctx.param_to_corr_procs.clone().unwrap()),
944    );
945
946    let mut loc_constr = vec![init_proc_constr];
947    loc_constr.extend(not_init_locs);
948
949    Ok(loc_constr)
950}
951
952/// Parse the location declaration in 'TypeOk' or in 'Init' into a set of
953/// locations
954fn parse_set_of_locations<'a>(pair: Pair<'a, Rule>) -> Result<Vec<Location>, anyhow::Error> {
955    let ctx_err_msg = "Error while parsing the list of locations: ";
956
957    if pair.as_rule() != Rule::map_definition {
958        return new_parsing_error_with_ctx(
959            format!(
960                "The type of '{LOCATIONS_SET}' is expected to be a map from the \
961set of processes '{PROC_SET}' to the set of locations, specified as a set of \
962strings"
963            ),
964            pair.as_span(),
965            ctx_err_msg,
966        );
967    }
968
969    let mut pairs: Pairs<'_, Rule> = pair.into_inner();
970
971    let ident_pair = pairs.next().expect("Missing: identifier");
972    let ident: String = parse_identifier_to_t(&ident_pair);
973    if ident != PROC_SET {
974        return new_parsing_error_with_ctx(
975            format!(
976                "The type of '{LOCATIONS_SET}' is expected to be a map from the \
977set of processes '{PROC_SET}' to the set of locations. Instead it is a map \
978from unknown set {ident}"
979            ),
980            ident_pair.as_span(),
981            ctx_err_msg,
982        );
983    }
984
985    let set_pair = pairs.next().expect("Missing: set definition");
986    if set_pair.as_rule() != Rule::set_definition_identifier_list {
987        return new_parsing_error_with_ctx(
988            format!(
989                "The type of '{LOCATIONS_SET}' is expected to be a map from \
990the set of processes '{PROC_SET}' to the set of locations, specified as pair \
991of strings. Location set has an unexpected form."
992            ),
993            set_pair.as_span(),
994            ctx_err_msg,
995        );
996    }
997
998    let mut inner_pairs = set_pair.into_inner();
999    let inner_pair = inner_pairs.next().expect("Location set cannot be empty");
1000
1001    let locations = parse_identifier_list_to_t(inner_pair);
1002
1003    debug!(
1004        "TLA file declared the following locations: {}",
1005        join_iterator(locations.iter(), ", ")
1006    );
1007
1008    Ok(locations)
1009}
1010
1011/// Parse all rule body expressions
1012///
1013/// Parses all pairs of type `rule_body_expr` into a consistent rule
1014fn parse_rule_body_exprs<'a>(
1015    pairs: Pairs<'a, Rule>,
1016    rule_span: Span<'a>,
1017    builder: &InitializedGeneralThresholdAutomatonBuilder,
1018    rule_id: u32,
1019    proc_ident: &String,
1020    ctx: &mut TLAParsingContext<'a>,
1021) -> Result<general_threshold_automaton::Rule, anyhow::Error> {
1022    let ctx_err_msg = format!("Error while parsing rule {rule_id}");
1023
1024    let pair_vec = pairs.collect::<Vec<_>>();
1025
1026    // Parse the source of the rule
1027    let src_pair = pair_vec
1028        .iter()
1029        .filter(|p| p.as_rule() == Rule::map_boolean_expr)
1030        .collect::<Vec<_>>();
1031    if src_pair.is_empty() {
1032        return new_parsing_error_with_ctx(
1033            "Missing: Definition of source location",
1034            rule_span,
1035            ctx_err_msg,
1036        );
1037    }
1038    if src_pair.len() > 1 {
1039        return new_parsing_error_with_ctx(
1040            format!(
1041                "Two declarations of target locations found in rule {rule_id}. Target constraints: {}",
1042                join_iterator(src_pair.iter().map(|p| p.as_str()), ",")
1043            ),
1044            rule_span,
1045            ctx_err_msg,
1046        );
1047    }
1048    let source = try_parse_map_entry_constr_loc(src_pair[0].clone(), proc_ident)
1049        .with_context(|| ctx_err_msg.clone())?;
1050
1051    // Parse the target of the rule
1052    let target_pair = pair_vec
1053        .iter()
1054        .filter(|p| p.as_rule() == Rule::map_update_expr)
1055        .collect::<Vec<_>>();
1056    if target_pair.is_empty() {
1057        return new_parsing_error_with_ctx(
1058            "Missing: Definition of target location",
1059            rule_span,
1060            ctx_err_msg,
1061        );
1062    }
1063    if target_pair.len() > 1 {
1064        return new_parsing_error_with_ctx(
1065            format!(
1066                "Two declarations of target locations found in rule {rule_id}. Target constraints: {}",
1067                join_iterator(target_pair.iter().map(|p| p.as_str()), ", ")
1068            ),
1069            rule_span,
1070            ctx_err_msg,
1071        );
1072    }
1073    let target = parse_map_update_to_target_location(target_pair[0].clone(), proc_ident)
1074        .with_context(|| ctx_err_msg.clone())?;
1075
1076    let mut rule_builder = RuleBuilder::new(rule_id, source, target);
1077
1078    // Parse the guard conditions of the rule
1079    let guard_conditions = pair_vec
1080        .iter()
1081        .filter(|p| p.as_rule() == Rule::int_bool_expr)
1082        .map(|p| parse_int_boolean_expr(p.clone(), builder, ctx))
1083        .collect::<Result<Vec<BooleanExpression<Variable>>, _>>()
1084        .with_context(|| ctx_err_msg.clone())?;
1085
1086    // Build the conjunction of the guards
1087    let mut first = BooleanExpression::True;
1088    if !guard_conditions.is_empty() {
1089        first = guard_conditions[0].clone();
1090    }
1091    let guard = guard_conditions.into_iter().skip(1).fold(first, |acc, x| {
1092        BooleanExpression::BinaryExpression(Box::new(acc), BooleanConnective::And, Box::new(x))
1093    });
1094    rule_builder = rule_builder.with_guard(guard);
1095
1096    // Parse actions
1097    let rule_updates = pair_vec
1098        .iter()
1099        .filter(|p| p.as_rule() == Rule::integer_update)
1100        .map(|p| parse_integer_update_expr(p.clone(), builder, ctx))
1101        .collect::<Result<Vec<_>, _>>()
1102        .with_context(|| ctx_err_msg.clone())?
1103        .into_iter()
1104        .flatten();
1105
1106    // add actions to the rule
1107    rule_builder = rule_builder.with_actions(rule_updates);
1108
1109    Ok(rule_builder.build())
1110}
1111
1112/// Parse a map update / map redefinition to the target location of a rule
1113///
1114/// Example of a map redefinition:
1115/// `ProcessesLocations' = [ProcessesLocations EXCEPT ![p] = "locEC"]`
1116fn parse_map_update_to_target_location(
1117    pair: Pair<'_, Rule>,
1118    proc_ident: &String,
1119) -> Result<Location, anyhow::Error> {
1120    debug_assert!(
1121        pair.as_rule() == Rule::map_update_expr,
1122        "Got rule {:?} for {}",
1123        pair.as_rule(),
1124        pair.as_str()
1125    );
1126
1127    let pair_span = pair.as_span();
1128    let mut inner_pairs = pair.into_inner();
1129
1130    let map_ident: String = parse_identifier_to_t(&inner_pairs.next().unwrap());
1131    if map_ident != LOCATIONS_SET {
1132        return new_parsing_error_with_ctx(
1133            format!(
1134                "Unknown map update. Only location map '{LOCATIONS_SET}' can be \
1135updated (Example: \
1136'{LOCATIONS_SET}' = [{LOCATIONS_SET} EXCEPT ![{proc_ident}] = \"loc0\"')."
1137            ),
1138            pair_span,
1139            "Error while parsing rule body: ",
1140        );
1141    }
1142
1143    let map_pair = inner_pairs.next().expect("Missing: map redefinition");
1144
1145    try_parse_map_redef_to_target_loc(map_pair, proc_ident)
1146}
1147
1148/// Helper function to [parse_map_update_to_target_location]
1149fn try_parse_map_redef_to_target_loc(
1150    pair: Pair<'_, Rule>,
1151    proc_ident: &String,
1152) -> Result<Location, anyhow::Error> {
1153    debug_assert!(
1154        pair.as_rule() == Rule::map_redefinition,
1155        "Got rule {:?} for {}",
1156        pair.as_rule(),
1157        pair.as_str()
1158    );
1159    let ctx_err_msg = "Error while parsing rule body: ";
1160
1161    let pair_span = pair.as_span();
1162    let mut inner_pairs = pair.into_inner();
1163
1164    let map_ident: String = parse_identifier_to_t(&inner_pairs.next().unwrap());
1165    if map_ident != LOCATIONS_SET {
1166        return new_parsing_error_with_ctx(
1167            format!(
1168                "Unknown map update. Only location map '{LOCATIONS_SET}' can be \
1169updated (Example: \
1170'{LOCATIONS_SET}' = [{LOCATIONS_SET} EXCEPT ![{proc_ident}] = \"loc0\"')."
1171            ),
1172            pair_span,
1173            ctx_err_msg,
1174        );
1175    }
1176
1177    let parsed_elem_name: String = parse_identifier_to_t(&inner_pairs.next().unwrap());
1178    if &parsed_elem_name != proc_ident {
1179        return new_parsing_error_with_ctx(
1180            format!(
1181                "Unknown element used in index of the map: got \
1182'{parsed_elem_name}', expected '{proc_ident}'"
1183            ),
1184            pair_span,
1185            ctx_err_msg,
1186        );
1187    }
1188
1189    let loc: Location = parse_identifier_to_t(&inner_pairs.next().unwrap());
1190    Ok(loc)
1191}
1192
1193/// Parses a `map_boolean_expr` into a [Location]
1194///
1195/// Example: `ProcessesLocations\[p\] = "loc1"`
1196fn try_parse_map_entry_constr_loc<T: Atomic + 'static>(
1197    pair: Pair<'_, Rule>,
1198    proc_ident: &String,
1199) -> Result<T, anyhow::Error> {
1200    debug_assert!(
1201        pair.as_rule() == Rule::map_boolean_expr,
1202        "Got rule {:?} for {}",
1203        pair.as_rule(),
1204        pair.as_str()
1205    );
1206    let ctx_err_msg = "Error while parsing rule body: ";
1207
1208    let mut inner_pairs = pair.into_inner();
1209
1210    let map_entry_pair = inner_pairs.next().expect("Missing: map entry");
1211    let map_entry_span = map_entry_pair.as_span();
1212    let (map_name, parsed_proc_name) =
1213        parse_map_entry_into_map_ident_and_proc_ident(map_entry_pair);
1214
1215    if map_name != LOCATIONS_SET {
1216        return new_parsing_error_with_ctx(
1217            format!(
1218                "Unknown map condition. Source location should be specified by \
1219constraining valuation of in '{LOCATIONS_SET}' (Example: \
1220'{LOCATIONS_SET}[{proc_ident}] = \"{proc_ident}\"')"
1221            ),
1222            map_entry_span,
1223            ctx_err_msg,
1224        );
1225    }
1226
1227    if proc_ident != &parsed_proc_name {
1228        return new_parsing_error_with_ctx(
1229            format!(
1230                "Unknown element used in index of the map: got \
1231'{parsed_proc_name}', expected '{proc_ident}'"
1232            ),
1233            map_entry_span,
1234            ctx_err_msg,
1235        );
1236    }
1237
1238    let loc = parse_identifier_to_t(&inner_pairs.next().expect("Missing: loc"));
1239
1240    Ok(loc)
1241}
1242
1243/// Parse all rule ids appearing in a `Next` expression
1244fn parse_next_expr(pair: Pair<'_, Rule>) -> Result<Vec<u32>, anyhow::Error> {
1245    debug_assert!(
1246        pair.as_rule() == Rule::next_declaration,
1247        "Got rule {:?} for {}",
1248        pair.as_rule(),
1249        pair.as_str()
1250    );
1251
1252    let mut inner_pairs = pair.into_inner();
1253
1254    let elem_ident_pair = inner_pairs.next().expect("Missing: process identifier");
1255    let proc_ident: String = parse_identifier_to_t(&elem_ident_pair);
1256
1257    let mut found_ids = Vec::new();
1258    // Parse rule ids specified in next
1259    for pair in inner_pairs {
1260        let pair_span = pair.as_span();
1261        let (id, parsed_proc_ident) = parse_rule_ident_to_id_and_proc_ident(pair);
1262
1263        if parsed_proc_ident != proc_ident {
1264            return new_parsing_error_with_ctx(
1265                format!(
1266                    "Unknown process identifier '{parsed_proc_ident}' (expected previously declared process dentifier '{proc_ident}'"
1267                ),
1268                pair_span,
1269                "Error while parsing next constraint: ",
1270            );
1271        }
1272
1273        found_ids.push(id);
1274    }
1275
1276    Ok(found_ids)
1277}
1278
1279/// Parses a `map_entry`  into the name of the map and the identifier of the
1280/// element used as an index in the map
1281fn parse_map_entry_into_map_ident_and_proc_ident(pair: Pair<'_, Rule>) -> (String, String) {
1282    debug_assert!(
1283        pair.as_rule() == Rule::map_entry,
1284        "Got rule {:?} for {}",
1285        pair.as_rule(),
1286        pair.as_str()
1287    );
1288    let mut inner_pairs = pair.into_inner();
1289
1290    let map_ident: String = parse_identifier_to_t(&inner_pairs.next().unwrap());
1291    let proc_ident: String = parse_identifier_to_t(&inner_pairs.next().unwrap());
1292
1293    (map_ident, proc_ident)
1294}
1295
1296/// Parse the rule id out of the rule name and collect the name of the identifier
1297/// used for specifying the process identifier
1298///
1299/// # Example
1300///
1301/// For example `Rule0(p)` will be parsed to the tuple (0, "p").
1302fn parse_rule_ident_to_id_and_proc_ident(pair: Pair<'_, Rule>) -> (u32, String) {
1303    debug_assert!(
1304        pair.as_rule() == Rule::rule_identifier,
1305        "Got rule {:?} for {}",
1306        pair.as_rule(),
1307        pair.as_str()
1308    );
1309    let mut inner_pairs = pair.into_inner();
1310
1311    let id_pair = inner_pairs.next().expect("Missing: rule id");
1312    let id = parse_integer_const(id_pair);
1313
1314    let proc_ident_pair = inner_pairs
1315        .next()
1316        .expect("Missing: rule process identifier");
1317    let proc_ident: String = parse_identifier_to_t(&proc_ident_pair);
1318
1319    (id, proc_ident)
1320}
1321
1322/// Split a conjunction of boolean expressions into a vector
1323///
1324/// This function splits only conjuncts on the top level !
1325/// It does not transform expressions into CNF
1326fn split_conjunct_bool_expr<T: Atomic>(bexpr: BooleanExpression<T>) -> Vec<BooleanExpression<T>> {
1327    match bexpr {
1328        BooleanExpression::BinaryExpression(lhs, BooleanConnective::And, rhs) => {
1329            let mut lhs = split_conjunct_bool_expr(*lhs);
1330            let rhs = split_conjunct_bool_expr(*rhs);
1331            lhs.extend(rhs);
1332            lhs
1333        }
1334        s => vec![s],
1335    }
1336}
1337
1338/// Parse a list of identifiers into a vector of objects of type T
1339#[inline(always)]
1340fn parse_identifier_list_to_t<T: for<'a> From<&'a str>>(pair: Pair<'_, Rule>) -> Vec<T> {
1341    debug_assert!(
1342        pair.as_rule() == Rule::identifier_list || pair.as_rule() == Rule::string_identifier_list,
1343        "Got rule {:?} for {}",
1344        pair.as_rule(),
1345        pair.as_str()
1346    );
1347
1348    pair.into_inner()
1349        .map(|id| parse_identifier_to_t(&id))
1350        .collect()
1351}
1352
1353/// Parse an identifier into an object of type T
1354#[inline(always)]
1355fn parse_identifier_to_t<T: for<'a> From<&'a str>>(pair: &Pair<'_, Rule>) -> T {
1356    debug_assert!(
1357        pair.as_rule() == Rule::identifier,
1358        "Got rule {:?} for {}",
1359        pair.as_rule(),
1360        pair.as_str()
1361    );
1362
1363    T::from(pair.as_str())
1364}
1365
1366/// Generate a new parsing error and add context
1367#[inline(always)]
1368fn new_parsing_error_with_ctx<T, S: Into<String>, SI: Into<String>>(
1369    err_msg: S,
1370    span: Span<'_>,
1371    ctx_msg: SI,
1372) -> Result<T, anyhow::Error> {
1373    let err_msg = err_msg.into();
1374    let parse_err: Box<pest::error::Error<Rule>> = Box::new(pest::error::Error::new_from_span(
1375        pest::error::ErrorVariant::CustomError { message: err_msg },
1376        span,
1377    ));
1378
1379    let ctx_msg = ctx_msg.into();
1380    Err(parse_err).with_context(|| ctx_msg)
1381}
1382
1383/// Generate a new parsing error
1384#[inline(always)]
1385fn new_parsing_error<S: Into<String>>(message: S, span: Span<'_>) -> Box<pest::error::Error<()>> {
1386    let message = message.into();
1387    Box::new(pest::error::Error::new_from_span(
1388        pest::error::ErrorVariant::CustomError { message },
1389        span,
1390    ))
1391}
1392
1393#[cfg(test)]
1394mod tests {
1395
1396    use pest::Parser;
1397
1398    use crate::{
1399        ParseTA, ParseTAWithLTL,
1400        bymc::ByMCParser,
1401        tla::{PestTLAParser, Rule, TLAParser},
1402    };
1403
1404    #[test]
1405    fn test_var_dec_parses() {
1406        let input =
1407            "VARIABLES ProcessesLocations, nsnt00, nsnt01, nsnt10,  nsnt11,  nsnt00plus01, nfaulty";
1408
1409        let _pairs = PestTLAParser::parse(Rule::variable_declaration, input);
1410    }
1411
1412    #[test]
1413    fn test_parse_min_working() {
1414        let input = "
1415-------------------------------- MODULE aba --------------------------------
1416CONSTANT Processes, NbOfCorrProc, n
1417
1418ASSUME NbOfCorrProc = 0
1419
1420VARIABLES ProcessesLocations
1421
1422TypeOk ==
1423/\\ ProcessesLocations \\in [Processes -> {\"loc\"}]
1424";
1425
1426        let parser = TLAParser::default();
1427        let res = parser.parse_ta_and_spec(input);
1428        let (got_ta, got_ltl) = res.unwrap();
1429        let got_ta_2 = TLAParser::default().parse_ta(input).unwrap();
1430
1431        let expected_ta = "\
1432ta tla_ta {
1433    parameters n;
1434
1435    assumptions (1) {
1436        true;
1437    }
1438
1439    locations (5) {
1440        loc: [0];
1441    }
1442
1443    inits (0) {
1444    }
1445
1446    rules (0) {
1447    }
1448
1449    spec(0) {
1450    }
1451        
1452}";
1453        let (expected_ta, expected_ltl) = ByMCParser::new().parse_ta_and_spec(expected_ta).unwrap();
1454
1455        assert_eq!(
1456            got_ta, expected_ta,
1457            "expected:\n{expected_ta}\n\ngot:\n{got_ta}"
1458        );
1459        assert_eq!(
1460            got_ta_2, expected_ta,
1461            "expected:\n{expected_ta}\n\ngot:\n{got_ta}"
1462        );
1463        assert_eq!(
1464            got_ltl, expected_ltl,
1465            "expected:\n{expected_ltl}\n\ngot:\n{got_ltl}"
1466        )
1467    }
1468
1469    #[test]
1470    fn test_parse_full_spec() {
1471        let input = "
1472-------------------------------- MODULE aba --------------------------------
1473
1474EXTENDS Integers, FiniteSets
1475
1476CONSTANT
1477Processes,
1478NbOfCorrProc,
1479N,
1480T,
1481F
1482
1483ASSUME
1484/\\ NbOfCorrProc = N - F
1485/\\ N > 3 * T
1486/\\ T >= F
1487/\\ T >= 1
1488
1489VARIABLES
1490ProcessesLocations,
1491nsntEC,
1492nsntRD
1493
1494
1495TypeOk ==
1496/\\ ProcessesLocations \\in [Processes -> {\"loc0\", \"loc1\", \"locEC\", \"locRD\", \"locAC\"}]
1497/\\ nsntEC \\in Nat
1498/\\ nsntRD \\in Nat 
1499
1500
1501
1502Init ==
1503/\\ ProcessesLocations \\in [Processes -> {\"loc0\", \"loc1\"}] 
1504/\\ nsntEC = 0
1505/\\ nsntRD = 0
1506
1507------------------------------------------------------------------------------------------
1508
1509Rule0(p) ==
1510                    /\\ ProcessesLocations[p] = \"loc1\"
1511                    /\\ ProcessesLocations\' = [ProcessesLocations EXCEPT ![p] = \"locEC\"]
1512                    /\\ nsntEC' = nsntEC + 1                   
1513                    /\\ UNCHANGED <<nsntRD>>
1514----------------------------------------------------------------------------------------
1515Rule1(p) ==
1516                     /\\ ProcessesLocations[p] = \"loc0\"
1517                     /\\ 2 * nsntEC >= N + T + 1 - 2 * F
1518                     /\\ ProcessesLocations' = [ProcessesLocations EXCEPT ![p] = \"locEC\"]
1519                     /\\ nsntEC' = nsntEC + 1                    
1520                     /\\ UNCHANGED <<nsntRD>>
1521
1522----------------------------------------------------------------------------------------
1523Rule2(p) ==
1524                    /\\ ProcessesLocations[p] = \"loc0\"
1525                    /\\ nsntRD >= T + 1 - F
1526                    /\\ ProcessesLocations' = [ProcessesLocations EXCEPT ![p] = \"locEC\"]
1527                    /\\ nsntEC' = nsntEC + 1
1528                    /\\ UNCHANGED <<nsntRD>>
1529                    
1530----------------------------------------------------------------------------------------
1531Rule3(p) ==
1532                     /\\ ProcessesLocations[p] = \"locEC\"
1533                     /\\ 2 * nsntEC >= N + T + 1 - 2 * F
1534                     /\\ ProcessesLocations' = [ProcessesLocations EXCEPT ![p] = \"locRD\"]
1535                     /\\ nsntRD' = nsntRD + 1
1536                     /\\ UNCHANGED << nsntEC>>
1537                     
1538----------------------------------------------------------------------------------------
1539Rule4(p) ==
1540                     /\\ ProcessesLocations[p] = \"locEC\"
1541                     /\\ nsntRD >= T + 1 - F
1542                     /\\ ProcessesLocations' = [ProcessesLocations EXCEPT ![p] = \"locRD\"]
1543                     /\\ nsntRD' = nsntRD + 1
1544                     /\\ UNCHANGED << nsntEC>>
1545------------------------------------------------------------------------
1546
1547Rule5(p) ==
1548                     /\\ ProcessesLocations[p] = \"locRD\"
1549                     /\\ 2 * nsntRD >= 2 * T + 1
1550                     /\\ ProcessesLocations' = [ProcessesLocations EXCEPT ![p] = \"locAC\"]
1551                     /\\ UNCHANGED <<nsntEC, nsntRD>>
1552----------------------------------------------------------------------------------------
1553Rule6(p) ==
1554                    /\\ ProcessesLocations[p] = \"loc0\"
1555                    /\\ 2 * nsntEC < N + T + 1
1556                    /\\ nsntRD < T + 1
1557                    /\\ ProcessesLocations' = [ProcessesLocations EXCEPT ![p] = \"loc0\"]
1558                    /\\ UNCHANGED <<nsntEC, nsntRD>>
1559----------------------------------------------------------------------------------------                    
1560                    
1561 Rule7(p) ==
1562                    /\\ ProcessesLocations[p] = \"locEC\"
1563                    /\\ 2 * nsntEC < N + T + 1
1564                    /\\ nsntRD < T + 1
1565                    /\\ ProcessesLocations' = [ProcessesLocations EXCEPT ![p] = \"locEC\"]
1566                    /\\ UNCHANGED << nsntEC, nsntRD>>
1567
1568----------------------------------------------------------------------------------------
1569Rule8(p) ==
1570                     /\\ ProcessesLocations[p] = \"locRD\"
1571                     /\\ nsntRD < 2 * T + 1
1572                     /\\ ProcessesLocations' = [ProcessesLocations EXCEPT ![p] = \"locRD\"]
1573                     /\\ UNCHANGED <<nsntEC, nsntRD>>
1574
1575
1576----------------------------------------------------------------------------------------
1577Rule9(p) ==
1578                    /\\ ProcessesLocations[p] = \"locAC\"
1579                    /\\ ProcessesLocations' = [ProcessesLocations EXCEPT ![p] = \"locAC\"]
1580                    /\\ UNCHANGED <<nsntEC, nsntRD>> 
1581
1582----------------------------------------------------------------------------------------
1583
1584NumInloc0 == Cardinality({p \\in Processes : ProcessesLocations[p] = \"loc0\"})
1585NumInloc1 == Cardinality({p \\in Processes : ProcessesLocations[p] = \"loc1\"})
1586NumInlocEC == Cardinality({p \\in Processes : ProcessesLocations[p] = \"locEC\"})
1587NumInlocAC == Cardinality({p \\in Processes : ProcessesLocations[p] = \"locAC\"})                    
1588NumInlocRD == Cardinality({p \\in Processes : ProcessesLocations[p] = \"locRD\"}) 
1589                                      
1590unforg == 
1591        NumInloc1 = 0 => [] (NumInlocAC = 0)
1592        
1593corr ==
1594  <>[](
1595    (((2 * nsntEC < N + T + 1) \\/ (NumInloc0 = 0))
1596     /\\ ((nsntRD < T + 1) \\/ (NumInloc0 = 0))
1597     /\\ ((2 * nsntEC < N + T + 1) \\/ (NumInlocEC = 0))
1598     /\\ ((nsntRD < T + 1) \\/ (NumInlocEC = 0))
1599     /\\ ((nsntRD < 2 * T + 1) \\/ (NumInlocRD = 0))
1600     /\\ (NumInloc1 = 0))
1601    => ((NumInloc0 = 0) => <> (NumInlocAC /= 0))
1602  )
1603  
1604 agreement ==
1605  <>[](
1606    (((2 * nsntEC < N + T + 1) \\/ (NumInloc0 = 0))
1607     /\\ ((nsntRD < T + 1) \\/ (NumInloc0 = 0))
1608     /\\ ((2 * nsntEC < N + T + 1) \\/ (NumInlocEC = 0))
1609     /\\ ((nsntRD < T + 1) \\/ (NumInlocEC = 0))
1610     /\\ ((nsntRD < 2 * T + 1) \\/ (NumInlocRD = 0))
1611     /\\ (NumInloc1 = 0))
1612    =>
1613    [](
1614      (NumInlocAC /= 0)
1615      => <>((NumInloc0 = 0) /\\ (NumInloc1 = 0) /\\ (NumInlocEC = 0) /\\ (NumInlocRD = 0))
1616    )
1617  )
1618  
1619        
1620----------------------------------------------------------------------------------------
1621Next == \\E p \\in Processes:
1622                            \\/ Rule0(p)
1623                            \\/ Rule1(p)
1624                            \\/ Rule2(p)
1625                            \\/ Rule3(p)
1626                            \\/ Rule4(p)
1627                            \\/ Rule5(p)
1628                            \\/ Rule6(p)
1629                            \\/ Rule7(p)
1630                            \\/ Rule8(p)
1631                            \\/ Rule9(p)
1632
1633
1634Spec == Init /\\ [][Next]_<< ProcessesLocations, nsntEC, nsntRD >>
1635=============================================================================
1636";
1637
1638        let parser = TLAParser::default();
1639        let res = parser.parse_ta_and_spec(input);
1640        let (got_ta, got_ltl) = res.unwrap();
1641        let got_ta_2 = TLAParser::default().parse_ta(input).unwrap();
1642
1643        let expected_ta = "\
1644ta tla_ta {
1645    shared nsntEC, nsntRD;
1646
1647    parameters N, T, F;
1648
1649    assumptions (4) {
1650        true;
1651        N > (3 * T);
1652        T >= F;
1653        T >= 1;
1654    }
1655
1656    locations (5) {
1657        loc0:[0];
1658        loc1:[1];
1659        locAC:[2];
1660        locEC:[3];
1661        locRD:[4];
1662    }
1663
1664    inits (6) {
1665        (loc0 + loc1) == (N - F);
1666        locAC == 0;
1667        locRD == 0;
1668        locEC == 0;
1669        nsntEC == 0;
1670        nsntRD == 0;
1671    }
1672
1673    rules (10) {
1674        0: loc1 -> locEC
1675            when ( true )
1676            do { nsntEC' == nsntEC + 1 };
1677        1: loc0 -> locEC
1678            when ( (2 * nsntEC) >= (((N + T) + 1) - (2 * F)) )
1679            do { nsntEC' == nsntEC + 1 };
1680        2: loc0 -> locEC
1681            when ( nsntRD >= ((T + 1) - F) )
1682            do { nsntEC' == nsntEC + 1 };
1683        3: locEC -> locRD
1684            when ( (2 * nsntEC) >= (((N + T) + 1) - (2 * F)) )
1685            do { nsntRD' == nsntRD + 1 };
1686        4: locEC -> locRD
1687            when ( nsntRD >= ((T + 1) - F) )
1688            do { nsntRD' == nsntRD + 1 };
1689        5: locRD -> locAC
1690            when ( (2 * nsntRD) >= ((2 * T) + 1) )
1691            do {  };
1692        6: loc0 -> loc0
1693            when ( ((2 * nsntEC) < ((N + T) + 1) && nsntRD < (T + 1)) )
1694            do {  };
1695        7: locEC -> locEC
1696            when ( ((2 * nsntEC) < ((N + T) + 1) && nsntRD < (T + 1)) )
1697            do {  };
1698        8: locRD -> locRD
1699            when ( nsntRD < ((2 * T) + 1) )
1700            do {  };
1701        9: locAC -> locAC
1702            when ( true )
1703            do {  };
1704    }
1705
1706    spec(3) {
1707    unforg: (loc1 == 0) -> ([](locAC == 0));
1708    corr: <>([](((((((((2 * nsntEC) < ((N + T) + 1)) || (loc0 == 0)) && ((nsntRD < (T + 1)) || (loc0 == 0))) && (((2 * nsntEC) < ((N + T) + 1)) || (locEC == 0))) && ((nsntRD < (T + 1)) || (locEC == 0))) && ((nsntRD < ((2 * T) + 1)) || (locRD == 0))) && (loc1 == 0)) -> ((loc0 == 0) -> (<>(locAC != 0)))));
1709    agreement: <>([](((((((((2 * nsntEC) < ((N + T) + 1)) || (loc0 == 0)) && ((nsntRD < (T + 1)) || (loc0 == 0))) && (((2 * nsntEC) < ((N + T) + 1)) || (locEC == 0))) && ((nsntRD < (T + 1)) || (locEC == 0))) && ((nsntRD < ((2 * T) + 1)) || (locRD == 0))) && (loc1 == 0)) -> ([]((locAC != 0) -> (<>((((loc0 == 0) && (loc1 == 0)) && (locEC == 0)) && (locRD == 0)))))));
1710    }
1711        
1712}";
1713        let (expected_ta, expected_ltl) = ByMCParser::new().parse_ta_and_spec(expected_ta).unwrap();
1714
1715        assert_eq!(
1716            got_ta, expected_ta,
1717            "expected:\n{expected_ta}\n\ngot:\n{got_ta}"
1718        );
1719        assert_eq!(
1720            got_ta_2, expected_ta,
1721            "expected:\n{expected_ta}\n\ngot:\n{got_ta}"
1722        );
1723        assert_eq!(
1724            got_ltl, expected_ltl,
1725            "expected:\n{expected_ltl}\n\ngot:\n{got_ltl}"
1726        )
1727    }
1728}