1use std::result::Result::Ok;
9use std::{fmt::Display, fs, io, path::PathBuf, process::exit};
10
11use anyhow::{Context, anyhow};
12
13use clap::{Args, Parser, Subcommand, ValueEnum};
14use taco_bdd::BDDManagerConfig;
15
16use log::{LevelFilter, error, info};
17use log4rs::{
18 Config,
19 append::console::ConsoleAppender,
20 config::{Appender, Root},
21 encode::pattern::PatternEncoder,
22};
23use taco_acs_model_checker::ACSModelChecker;
24use taco_display_utils::join_iterator;
25use taco_model_checker::preprocessing::ExistingPreprocessors;
26use taco_model_checker::{
27 ModelChecker,
28 eltl::{ELTLExpression, ELTLSpecification},
29 preprocessing::{self},
30};
31use taco_model_checker::{ModelCheckerContext, ModelCheckerResult, TargetSpec};
32use taco_parser::{ParseTAWithLTL, bymc::ByMCParser, tla::TLAParser};
33use taco_smt_encoder::{ProvidesSMTSolverBuilder, SMTSolverBuilderCfg};
34
35use taco_smt_model_checker::{SMTModelChecker, SMTModelCheckerOptions};
36use taco_threshold_automaton::{
37 ModifiableThresholdAutomaton, ThresholdAutomaton,
38 expressions::{Atomic, BooleanExpression, ComparisonOp, IntegerExpression, Location, Variable},
39 general_threshold_automaton::GeneralThresholdAutomaton,
40};
41use taco_zcs_model_checker::{ZCSModelChecker, ZCSModelCheckerHeuristics};
42
43use crate::cli::output_formats::{into_bymc, into_tla};
44use crate::{cli, taco_config::TACOConfig};
45
46mod output_formats;
47
48pub const DEFAULT_PREPROCESSORS: [ExistingPreprocessors; 5] = [
50 ExistingPreprocessors::DropSelfLoops,
51 ExistingPreprocessors::DropUnreachableLocations,
52 ExistingPreprocessors::RemoveUnusedVariables,
53 ExistingPreprocessors::CheckInitCondSatSMT,
54 ExistingPreprocessors::ReplaceTrivialGuardsSMT,
55];
56
57#[derive(Parser, Debug)]
75#[command(version, name = "TACO CLI", about, long_about)]
76pub(crate) struct Cli {
77 #[command(flatten)]
78 pub(crate) log_config: LoggerConfig,
79 #[command(subcommand)]
80 pub(crate) command: Commands,
81}
82
83#[derive(Subcommand, Debug)]
84pub enum Commands {
85 Check {
87 #[command(flatten)]
88 input: SpecFileInput,
89
90 #[arg(short, long, value_name = "CONFIG_FILE")]
92 config_file: Option<PathBuf>,
93
94 #[command(subcommand)]
96 model_checker: Option<ModelCheckerOption>,
97
98 #[arg(short, long, value_name = "BDD")]
100 bdd: Option<BDDManagerOption>,
101
102 #[arg(short, long, value_name = "SMT_SOLVER")]
104 smt_solver: Option<SMTSolverDefaultOptions>,
105
106 #[arg(short = 'o', long, default_value_t = false)]
109 compact_out: bool,
110
111 #[arg(short, long, default_value_t = false)]
114 abort_on_violation: bool,
115
116 #[arg(long, value_enum, default_value_t = CheckMode::Normal)]
118 mode: CheckMode,
119 },
120 #[cfg(feature = "dot")]
121 Visualize {
123 #[command(flatten)]
124 input: SpecFileInput,
125
126 #[command(flatten)]
127 output: VisualizationOutput,
128 },
129 Translate {
131 #[command(flatten)]
132 input: SpecFileInput,
133
134 #[command(flatten)]
135 output: TranslationOutput,
136 },
137}
138
139#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
140pub enum CheckMode {
141 Normal,
144 Coverability,
146 Reachability,
148}
149
150#[derive(Args, Debug)]
151pub(crate) struct SpecFileInput {
152 input_file: PathBuf,
154
155 #[arg(short, long, value_name = "INPUT_FORMAT")]
158 format: Option<SpecFileFormat>,
159}
160
161#[allow(clippy::upper_case_acronyms)]
162#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
163enum SpecFileFormat {
164 BYMC,
166 TLA,
168}
169
170impl Display for SpecFileFormat {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 match self {
173 SpecFileFormat::BYMC => write!(f, "ByMC"),
174 SpecFileFormat::TLA => write!(f, "TLA+"),
175 }
176 }
177}
178
179#[derive(Subcommand, Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
180pub(crate) enum ModelCheckerOption {
181 Zcs {
183 #[arg(long, value_name = "HEURISTICS")]
185 heuristic: Option<SymbolicModelCheckerHeuristics>,
186 },
187 Smt {
189 #[arg(short, long, value_name = "PARALLEL", default_value_t = false)]
191 parallel: bool,
192 },
193 Acs,
195}
196
197#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
198pub(crate) enum SymbolicModelCheckerHeuristics {
199 ResetHeuristics,
204 DecrementAndIncrementHeuristics,
209 CanonicalHeuristics,
213 EmptyErrorGraphHeuristic,
218}
219
220#[allow(clippy::upper_case_acronyms)]
221#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
222pub(crate) enum BDDManagerOption {
223 #[cfg(feature = "cudd")]
225 CUDD,
226 #[cfg(feature = "oxidd")]
228 OXIDD,
229}
230
231#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
232pub enum SMTSolverDefaultOptions {
234 Z3,
236 CVC5,
238}
239
240#[derive(Debug, Args)]
241pub struct VisualizationOutput {
242 output_file: PathBuf,
244 #[arg(short, long, value_name = "OUT_FORMAT", default_value = "dot")]
250 output_format: OutputFormat,
251}
252
253#[allow(clippy::upper_case_acronyms)]
254#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
255pub enum OutputFormat {
256 DOT,
258 SVG,
260 PNG,
262}
263
264#[derive(Debug, Args)]
265pub struct TranslationOutput {
266 output_file: PathBuf,
268 #[arg(short, long, value_name = "OUT_FORMAT", default_value = "bymc")]
270 output_format: SpecFileFormat,
271}
272
273#[derive(Debug, Args)]
274pub(crate) struct LoggerConfig {
275 #[arg(long)]
278 logger_config_file: Option<String>,
279
280 #[arg(short, long, default_value_t = false)]
283 debug: bool,
284}
285
286pub(crate) fn initialize_logger(cfg: LoggerConfig) -> Result<(), anyhow::Error> {
292 if let Some(f) = cfg.logger_config_file {
293 log4rs::init_file(f, Default::default())
295 .with_context(|| "Failed to read logger config file")?;
296 return Ok(());
297 }
298
299 let p_encoder = match cfg.debug {
300 true => PatternEncoder::new("{d(%Y-%m-%d %H:%M:%S)} - {h({l})} - [{f}:{L} - {M}] - {m}{n}"),
301 false => PatternEncoder::new("{d(%H:%M:%S)} - {h({l})} - {m}{n}"),
302 };
303
304 let stdout = ConsoleAppender::builder()
306 .encoder(Box::new(p_encoder))
307 .build();
308
309 let mut level = LevelFilter::Info;
310 if cfg.debug {
311 level = LevelFilter::Debug;
312 }
313
314 let log_config = Config::builder()
315 .appender(Appender::builder().build("stdout", Box::new(stdout)))
316 .build(Root::builder().appender("stdout").build(level))
317 .expect("Failed to initialize logger");
318
319 log4rs::init_config(log_config).expect("Failed to initialize console logger");
320 Ok(())
321}
322
323pub(crate) fn get_smt_solver(smt_config: SMTSolverDefaultOptions) -> SMTSolverBuilderCfg {
325 match smt_config {
326 SMTSolverDefaultOptions::Z3 => SMTSolverBuilderCfg::new_z3(),
327 SMTSolverDefaultOptions::CVC5 => SMTSolverBuilderCfg::new_cvc5(),
328 }
329}
330
331pub(crate) fn get_bdd_manager_cfg(bdd: BDDManagerOption) -> BDDManagerConfig {
333 match bdd {
334 #[cfg(feature = "cudd")]
335 BDDManagerOption::CUDD => BDDManagerConfig::new_cudd(),
336 #[cfg(feature = "oxidd")]
337 BDDManagerOption::OXIDD => BDDManagerConfig::new_oxidd(),
338 }
339}
340
341fn get_zcs_model_checker_heuristic(
343 heuristic: Option<SymbolicModelCheckerHeuristics>,
344) -> Option<ZCSModelCheckerHeuristics> {
345 heuristic.map(|h| match h {
346 SymbolicModelCheckerHeuristics::ResetHeuristics => {
347 ZCSModelCheckerHeuristics::ResetHeuristics
348 }
349 SymbolicModelCheckerHeuristics::DecrementAndIncrementHeuristics => {
350 ZCSModelCheckerHeuristics::DecrementAndIncrementHeuristics
351 }
352 SymbolicModelCheckerHeuristics::CanonicalHeuristics => {
353 ZCSModelCheckerHeuristics::CanonicalHeuristics
354 }
355 SymbolicModelCheckerHeuristics::EmptyErrorGraphHeuristic => {
356 ZCSModelCheckerHeuristics::EmptyErrorGraphHeuristics
357 }
358 })
359}
360
361fn get_preprocessors<S, C>(
364 cfg: &TACOConfig,
365) -> Vec<Box<dyn preprocessing::Preprocessor<GeneralThresholdAutomaton, S, C>>>
366where
367 S: TargetSpec,
368 C: ModelCheckerContext + ProvidesSMTSolverBuilder,
369{
370 if cfg.get_preprocessors_cfg().is_some() {
371 info!(
372 "Configured TACO to use preprocessors: {}",
373 join_iterator(cfg.get_preprocessors_cfg().clone().unwrap().iter(), ", ")
374 );
375 }
376
377 cfg.get_preprocessors_cfg()
378 .clone()
379 .unwrap_or(DEFAULT_PREPROCESSORS.to_vec())
380 .into_iter()
381 .map(|mc| mc.into())
382 .collect()
383}
384
385pub(crate) fn initialize_zcs_model_checker(
388 mc_cfg: Option<SymbolicModelCheckerHeuristics>,
389 ta: GeneralThresholdAutomaton,
390 spec: ELTLSpecification,
391 cfg: TACOConfig,
392) -> ZCSModelChecker {
393 let opt = get_zcs_model_checker_heuristic(mc_cfg);
394
395 let preprocessors = get_preprocessors(&cfg);
396
397 let mc = ZCSModelChecker::new(
398 Some(cfg.get_bdd_smt_config()),
399 opt,
400 preprocessors,
401 ta,
402 spec.into_iter(),
403 );
404
405 if let Err(err) = mc {
406 error!("Error during initialization of the model checker: {}", err);
407 exit(1);
408 }
409
410 mc.unwrap()
411}
412
413pub(crate) fn initialize_smt_model_checker(
414 ta: GeneralThresholdAutomaton,
415 spec: ELTLSpecification,
416 cfg: TACOConfig,
417 parallel: bool,
418) -> SMTModelChecker {
419 let opts = SMTModelCheckerOptions::new(parallel);
420
421 let preprocessors = get_preprocessors(&cfg);
422
423 let mc = SMTModelChecker::new(
424 cfg.get_smt_solver_builder_cfg(),
425 opts,
426 preprocessors,
427 ta,
428 spec.into_iter(),
429 );
430
431 if let Err(err) = mc {
432 error!("Error during initialization of the model checker: {}", err);
433 exit(1);
434 }
435
436 mc.unwrap()
437}
438
439pub(crate) fn initialize_cs_model_checker(
440 ta: GeneralThresholdAutomaton,
441 spec: ELTLSpecification,
442 cfg: TACOConfig,
443) -> ACSModelChecker {
444 let preprocessors = get_preprocessors(&cfg);
445
446 let mode = None;
447
448 let mc = ACSModelChecker::new(
449 cfg.get_smt_solver_builder_cfg(),
450 mode,
451 preprocessors,
452 ta,
453 spec.into_iter(),
454 );
455
456 if let Err(err) = mc {
457 error!("Error during initialization of the model checker: {}", err);
458 exit(1);
459 }
460
461 mc.unwrap()
462}
463
464pub(crate) fn parse_input_file(
470 cfg: SpecFileInput,
471) -> Result<(GeneralThresholdAutomaton, ELTLSpecification), anyhow::Error> {
472 let mut format = cfg.format;
473
474 if format.is_none()
475 && let Some(ext) = cfg.input_file.extension()
476 {
477 let ext = ext.to_str().unwrap();
478 if ext == "ta" || ext == "eta" {
479 format = Some(SpecFileFormat::BYMC);
480 }
481 if ext == "tla" {
482 format = Some(SpecFileFormat::TLA);
483 }
484 }
485
486 let f =
487 fs::read_to_string(cfg.input_file).with_context(|| "Unable to read specification file")?;
488
489 match format {
490 None | Some(SpecFileFormat::BYMC) => ByMCParser::new().parse_ta_and_spec(&f),
491 Some(SpecFileFormat::TLA) => TLAParser::new().parse_ta_and_spec(&f),
492 }
493}
494
495#[cfg(feature = "dot")]
500pub(crate) fn visualize_ta(
501 ta: &GeneralThresholdAutomaton,
502 cfg: VisualizationOutput,
503) -> Result<(), anyhow::Error> {
504 use std::{
505 borrow::BorrowMut,
506 process::{Command, Stdio},
507 };
508
509 use anyhow::Context;
510 use taco_threshold_automaton::dot::ToDOT;
511 let out_str = ta.get_dot_graph();
512
513 let out_arg = match cfg.output_format {
514 OutputFormat::DOT => {
515 fs::write(cfg.output_file, out_str).with_context(|| "Failed to write output file")?;
516 return Ok(());
517 }
518 OutputFormat::SVG => "-Tsvg",
519 OutputFormat::PNG => "-Tpng",
520 };
521
522 if Command::new("dot")
523 .arg("--version")
524 .stdout(Stdio::null())
525 .stderr(Stdio::null())
526 .spawn()
527 .is_err()
528 {
529 return Err(anyhow!("Graphviz is not installed on the system"));
530 }
531
532 let mut echo_cmd = Command::new("echo")
533 .arg(out_str)
534 .stdout(Stdio::piped())
535 .spawn()
536 .unwrap();
537
538 let _ = echo_cmd.borrow_mut().wait();
539
540 let _ = Command::new("dot")
541 .stdin(echo_cmd.stdout.unwrap())
542 .arg(out_arg)
543 .arg("-o")
544 .arg(cfg.output_file)
545 .output()
546 .with_context(|| "Failed to execute graphviz")?;
547
548 Ok(())
549}
550
551fn model_check_ta(
552 ta: GeneralThresholdAutomaton,
553 spec: ELTLSpecification,
554 model_checker: Option<ModelCheckerOption>,
555 config: TACOConfig,
556 abort_on_violation: bool,
557) -> ModelCheckerResult {
558 match model_checker.unwrap_or_else(|| {
559 if ta.has_resets() || ta.has_decrements() {
560 return ModelCheckerOption::Zcs { heuristic: None };
561 }
562
563 ModelCheckerOption::Smt { parallel: false }
564 }) {
565 cli::ModelCheckerOption::Zcs { heuristic } => {
566 let res = initialize_zcs_model_checker(heuristic, ta, spec, config)
567 .verify(abort_on_violation);
568
569 if let Err(err) = res {
570 error!(
571 "An error occurred during the model checking with the symbolic model checker: {}",
572 err
573 );
574 exit(1);
575 }
576
577 res.unwrap()
578 }
579 cli::ModelCheckerOption::Smt { parallel } => {
580 let res =
581 initialize_smt_model_checker(ta, spec, config, parallel).verify(abort_on_violation);
582 if let Err(err) = res {
583 error!(
584 "An error occurred during the model checking with the SMT model checker: {}",
585 err
586 );
587 exit(1)
588 }
589
590 res.unwrap()
591 }
592 cli::ModelCheckerOption::Acs => {
593 let res = initialize_cs_model_checker(ta, spec, config).verify(abort_on_violation);
594
595 if let Err(err) = res {
596 error!(
597 "An error occurred during the model checking with the CS model checker: {}",
598 err
599 );
600 exit(1)
601 }
602
603 res.unwrap()
604 }
605 }
606}
607
608pub fn display_result(
609 ta: GeneralThresholdAutomaton,
610 spec: ELTLSpecification,
611 model_checker: Option<ModelCheckerOption>,
612 config: TACOConfig,
613 abort_on_violation: bool,
614 mode: CheckMode,
615 compact_out: bool,
616) {
617 let res = model_check_ta(ta, spec, model_checker, config, abort_on_violation);
618
619 match mode {
620 cli::CheckMode::Coverability | cli::CheckMode::Reachability => match res {
623 taco_model_checker::ModelCheckerResult::SAFE => {
624 info!(
625 "The provided configuration is not reachable from the initial configuration. Coverability/Reachability property not satisfied."
626 )
627 }
628 taco_model_checker::ModelCheckerResult::UNSAFE(violations) => {
629 for (property, error_path) in violations {
630 if compact_out {
631 info!(
632 "Path towards the desired configuration of our property: '{property}': {}",
633 error_path.display_compact()
634 );
635 } else {
636 info!(
637 "Path towards the desired configuration of our property: '{property}': {error_path}\n"
638 )
639 }
640 }
641 }
642 taco_model_checker::ModelCheckerResult::UNKNOWN(unknown) => {
643 for property in unknown {
644 info!(
645 "The configured model checker could not determine whether the threshold automaton satisfies property: {property}"
646 );
647 }
648 }
649 },
650 _ => match res {
651 taco_model_checker::ModelCheckerResult::SAFE => {
652 info!("Threshold automaton satisfies all properties. The protocol is safe.")
653 }
654 taco_model_checker::ModelCheckerResult::UNSAFE(violations) => {
655 for (property, error_path) in violations {
656 if compact_out {
657 info!(
658 "Counter example to property '{property}': {}",
659 error_path.display_compact()
660 );
661 } else {
662 info!("Counter example to property '{property}': {error_path}\n")
663 }
664 }
665 info!("The protocol is unsafe.")
666 }
667 taco_model_checker::ModelCheckerResult::UNKNOWN(unknown) => {
668 for property in unknown {
669 info!(
670 "The configured model checker could not determine whether the threshold automaton satisfies property: {property}"
671 );
672 }
673 }
674 },
675 }
676}
677
678pub fn parse_coverability(ta: &mut GeneralThresholdAutomaton) -> String {
679 let mut loc_name = String::new();
680 loop {
681 println!(
682 "Please input the name of the location that should be covered in your coverability check:"
683 );
684 loc_name.clear();
685 io::stdin().read_line(&mut loc_name).unwrap();
686
687 if ta
689 .locations()
690 .any(|l| l.name() == Location::new(loc_name.trim()).name())
691 {
692 break;
693 }
694 println!(
695 "Location '{}' does not exist in the threshold automaton!",
696 loc_name.trim()
697 );
698 let all_locations: Vec<&str> = ta.locations().map(|l| l.name()).collect();
699 println!("Possible locations are : {}", all_locations.join(", "))
700 }
701 parse_initial_configuration(ta);
703
704 println!(
705 "Checking whether location '{}' can be covered in the threshold automaton.",
706 loc_name
707 );
708 loc_name
709}
710
711pub fn create_coverability_expression(loc_name: String) -> ELTLExpression {
718 ELTLExpression::Globally(Box::new(ELTLExpression::LocationExpr(
719 Box::new(IntegerExpression::Atom(Location::new(loc_name.trim()))),
720 ComparisonOp::Eq,
721 Box::new(IntegerExpression::Const(0)),
722 )))
723}
724
725pub fn parse_reachability(ta: &mut GeneralThresholdAutomaton) -> (Vec<String>, Vec<String>) {
726 let mut populated_locs: Vec<String> = Vec::new();
728 loop {
729 println!(
730 "Input the name of a location that should be covered by processes or press enter when you have input all of them."
731 );
732 let mut loc_name = String::new();
733 io::stdin().read_line(&mut loc_name).unwrap();
734
735 if populated_locs.is_empty() && loc_name.trim().is_empty() {
737 println!("Please input at least one location that should be populated.");
738 continue;
739 }
740 if loc_name.trim().is_empty() {
742 break;
743 }
744
745 if populated_locs.contains(&loc_name.trim().to_string()) {
747 println!("You have already specified this location.");
748 continue;
749 }
750 if !ta
752 .locations()
753 .any(|l| l.name() == Location::new(loc_name.trim()).name())
754 {
755 println!(
756 "Location '{}' does not exist in the threshold automaton!",
757 loc_name.trim()
758 );
759 let all_locations: Vec<&str> = ta.locations().map(|l| l.name()).collect();
760 println!("Possible locations are : {}", all_locations.join(", "));
761 continue;
762 }
763
764 populated_locs.push(loc_name.trim().to_string());
765 }
766
767 let unpopulated_locs: Vec<String> = ta
769 .locations()
770 .filter(|l| !populated_locs.contains(&l.name().to_string()))
771 .map(|name| name.to_string())
772 .collect();
773
774 parse_initial_configuration(ta);
776
777 println!(
778 "Checking whether the specified configuration can be reached in the threshold automaton. The configuration looks as follows: locations to be populated: {}, and locations to be unpopulated: {}",
779 populated_locs.join(", "),
780 unpopulated_locs.join(", ")
781 );
782 (populated_locs, unpopulated_locs)
783}
784
785pub fn create_reachability_expression(
793 populated_locs: Vec<String>,
794 unpopulated_locs: Vec<String>,
795) -> ELTLExpression {
796 let populated_expression = populated_locs
797 .iter()
798 .map(|v| {
799 Box::new(ELTLExpression::LocationExpr(
800 Box::new(IntegerExpression::Atom(Location::new(v))),
801 ComparisonOp::Eq,
802 Box::new(IntegerExpression::Const(0)),
803 ))
804 })
805 .reduce(|acc, val| Box::new(ELTLExpression::And(acc, val)))
806 .unwrap();
807
808 let unpopulated_expression = unpopulated_locs
809 .iter()
810 .map(|v| {
811 Box::new(ELTLExpression::LocationExpr(
812 Box::new(IntegerExpression::Atom(Location::new(v))),
813 ComparisonOp::Gt,
814 Box::new(IntegerExpression::Const(0)),
815 ))
816 })
817 .reduce(|acc, val| Box::new(ELTLExpression::And(acc, val)))
818 .unwrap();
819
820 ELTLExpression::Globally(Box::new(ELTLExpression::Or(
821 populated_expression,
822 unpopulated_expression,
823 )))
824}
825
826fn parse_initial_configuration(ta: &mut GeneralThresholdAutomaton) {
827 println!(
828 "Do you want to manually input an initial configuration? [y/n] If not, the initial locations and the initial variable valuations specified in the provided .ta file will be used."
829 );
830 let mut input = String::new();
831 io::stdin().read_line(&mut input).unwrap();
832 let input = input.trim().to_lowercase();
833
834 if !(input == "y" || input == "yes") {
835 println!(
836 "Using the initial locations and initial variable constraints provided in the .ta file."
837 );
838 return;
839 }
840
841 println!(
842 "Please input the number of processes in each location for your initial configuration:"
843 );
844 let mut location_constraints = Vec::new();
845 for loc in ta.locations() {
846 let procs_in_loc = loop {
847 println!("{}:", loc.name());
848 let mut num = String::new();
849 io::stdin().read_line(&mut num).unwrap();
850 match num.trim().parse::<u32>() {
851 Ok(num) => break num,
852 _ => println!("Invalid input. Please enter a number greater or equal to 0."),
853 }
854 };
855 location_constraints.push(BooleanExpression::ComparisonExpression(
856 Box::new(IntegerExpression::Atom(Location::new(loc.name()))),
857 ComparisonOp::Eq,
858 Box::new(IntegerExpression::Const(procs_in_loc)),
859 ));
860 }
861 ta.replace_initial_location_constraints(location_constraints);
862
863 println!("Please input the initial variable valuations:");
864 let mut variable_constraints = Vec::new();
865 for var in ta.variables() {
866 let var_value = loop {
867 println!("{}:", var.name());
868 let mut num = String::new();
869 io::stdin().read_line(&mut num).unwrap();
870 match num.trim().parse::<u32>() {
871 Ok(num) => break num,
872 _ => println!("Invalid input. Please enter a number greater or equal to 0."),
873 }
874 };
875 variable_constraints.push(BooleanExpression::ComparisonExpression(
876 Box::new(IntegerExpression::Atom(Variable::new(var.name()))),
877 ComparisonOp::Eq,
878 Box::new(IntegerExpression::Const(var_value)),
879 ));
880 }
881 ta.replace_initial_variable_constraints(variable_constraints);
882}
883
884pub(crate) fn translate_ta(
887 ta: &GeneralThresholdAutomaton,
888 spec: &ELTLSpecification,
889 out: TranslationOutput,
890) -> Result<(), anyhow::Error> {
891 let translated_str: String = match out.output_format {
892 SpecFileFormat::BYMC => into_bymc(ta, spec),
893 SpecFileFormat::TLA => into_tla(ta, spec),
894 };
895
896 fs::write(out.output_file, translated_str).with_context(|| "Failed to write output file")?;
897 info!("Finished writing output file");
898
899 Ok(())
900}