From bfd0aa4a686ba0957de364ccbb753717d934efb8 Mon Sep 17 00:00:00 2001 From: Stefan Ellmauthaler <71695780+ellmau@users.noreply.github.com> Date: Tue, 11 Jan 2022 16:24:18 +0100 Subject: [PATCH] Implement non-deprecated cli with structopts and adf-state-safer (#14) Rework of the CLI methods. Added import/export functionality for ADFs in BDD representation (via JSON serialisation) --- Cargo.lock | 66 +++++++++++++++- Cargo.toml | 1 + src/datatypes/adf.rs | 2 +- src/main.rs | 179 +++++++++++++++++++++++++------------------ tests/cli.rs | 64 ++++++++++++++++ 5 files changed, 237 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9287a32..449d92c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "adf_bdd" -version = "0.1.2" +version = "0.1.3" dependencies = [ "assert_cmd", "assert_fs", @@ -18,6 +18,7 @@ dependencies = [ "quickcheck_macros", "serde", "serde_json", + "structopt", "test-log", ] @@ -229,6 +230,15 @@ dependencies = [ "walkdir", ] +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.19" @@ -387,6 +397,30 @@ dependencies = [ "termtree", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.32" @@ -560,6 +594,30 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +[[package]] +name = "structopt" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "syn" version = "1.0.81" @@ -629,6 +687,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "unicode-segmentation" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" + [[package]] name = "unicode-width" version = "0.1.9" diff --git a/Cargo.toml b/Cargo.toml index 3cf81f8..5f294ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ build = "build.rs" [dependencies] clap = "2.33.*" +structopt = "0.3.25" log = { version = "0.4", features = [ "max_level_trace", "release_max_level_info" ] } env_logger = "0.9" nom = "7.1.0" diff --git a/src/datatypes/adf.rs b/src/datatypes/adf.rs index bf7e30e..eb67006 100644 --- a/src/datatypes/adf.rs +++ b/src/datatypes/adf.rs @@ -5,7 +5,7 @@ use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc}; use super::{Term, Var}; -#[derive(Serialize, Deserialize,Debug)] +#[derive(Serialize, Deserialize, Debug)] pub(crate) struct VarContainer { names: Rc>>, mapping: Rc>>, diff --git a/src/main.rs b/src/main.rs index 9224ba3..e71d46d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,88 +3,121 @@ pub mod datatypes; pub mod obdd; pub mod parser; -use std::str::FromStr; +use std::{fs::File, path::PathBuf}; use adf::Adf; -use clap::{clap_app, crate_authors, crate_description, crate_name, crate_version}; +use clap::{crate_authors, crate_description, crate_name, crate_version}; use parser::AdfParser; -fn main() { - let matches = clap_app!(myapp => - (version: crate_version!()) - (author: crate_authors!()) - (name: crate_name!()) - (about: crate_description!()) - //(@arg fast: -f --fast "fast algorithm instead of the direct fixpoint-computation") - (@arg verbose: -v +multiple "Sets log verbosity") - (@arg INPUT: +required "Input file") - (@group sorting => - (@arg sort_lex: --lx "Sorts variables in a lexicographic manner") - (@arg sort_alphan: --an "Sorts variables in an alphanumeric manner") - ) - (@arg grounded: --grd "Compute the grounded model") - (@arg stable: --stm "Compute the stable models") - ) - .get_matches_safe() - .unwrap_or_else(|e| match e.kind { - clap::ErrorKind::HelpDisplayed => { - e.exit(); - } - clap::ErrorKind::VersionDisplayed => { - e.exit(); - } - _ => { - eprintln!("{} Version {{{}}}", crate_name!(), crate_version!()); - e.exit(); - } - }); - let filter_level = match matches.occurrences_of("verbose") { - 1 => log::LevelFilter::Info, - 2 => log::LevelFilter::Debug, - 3 => log::LevelFilter::Trace, - _ => { - match std::env::vars().find_map(|(var, val)| { - if var.eq("RUST_LOG") { - Some(log::LevelFilter::from_str(val.as_str())) +use structopt::StructOpt; + +#[derive(StructOpt, Debug)] +#[structopt(name = crate_name!(), about = crate_description!(), author = crate_authors!(), version = crate_version!())] +struct App { + /// Input filename + #[structopt(parse(from_os_str))] + input: PathBuf, + /// Sets the verbosity to 'warn', 'info', 'debug' or 'trace' if -v and -q are not use + #[structopt(long = "rust_log", env)] + rust_log: Option, + /// Sets log verbosity (multiple times means more verbose) + #[structopt(short, parse(from_occurrences), group = "verbosity")] + verbose: u8, + /// Sets log verbosity to only errors + #[structopt(short, group = "verbosity")] + quiet: bool, + /// Sorts variables in an lexicographic manner + #[structopt(long = "lx", group = "sorting")] + sort_lex: bool, + /// Sorts variables in an alphanumeric manner + #[structopt(long = "an", group = "sorting")] + sort_alphan: bool, + /// Compute the grounded model + #[structopt(long = "grd")] + grounded: bool, + /// Compute the stable models + #[structopt(long = "stm")] + stable: bool, + /// Import an adf- bdd state instead of an adf + #[structopt(long)] + import: bool, + /// Export the adf-bdd state after parsing and BDD instantiation to the given filename + #[structopt(long)] + export: Option, +} + +impl App { + fn run(&self) { + let filter_level = match self.verbose { + 1 => log::LevelFilter::Info, + 2 => log::LevelFilter::Debug, + 3 => log::LevelFilter::Trace, + _ => { + if self.quiet { + log::LevelFilter::Error + } else if let Some(rust_log) = self.rust_log.clone() { + match rust_log.as_str() { + "error" => log::LevelFilter::Error, + "info" => log::LevelFilter::Info, + "debug" => log::LevelFilter::Debug, + "trace" => log::LevelFilter::Trace, + _ => log::LevelFilter::Warn, + } } else { - None + log::LevelFilter::Warn } - }) { - Some(v) => v.unwrap_or(log::LevelFilter::Error), - None => log::LevelFilter::Error, + } + }; + env_logger::builder().filter_level(filter_level).init(); + log::info!("Version: {}", crate_version!()); + let input = std::fs::read_to_string(self.input.clone()).expect("Error Reading File"); + let mut adf = if self.import { + serde_json::from_str(&input).unwrap() + } else { + let parser = AdfParser::default(); + parser.parse()(&input).unwrap(); + log::info!("[Done] parsing"); + if self.sort_lex { + parser.varsort_lexi(); + } + if self.sort_alphan { + parser.varsort_alphanum(); + } + Adf::from_parser(&parser) + }; + if let Some(export) = &self.export { + if export.exists() { + log::error!( + "Cannot write JSON file <{}>, as it already exists", + export.to_string_lossy() + ); + } else { + let export_file = match File::create(&export) { + Err(reason) => { + panic!("couldn't create {}: {}", export.to_string_lossy(), reason) + } + Ok(file) => file, + }; + serde_json::to_writer(export_file, &adf).unwrap_or_else(|_| { + panic!("Writing JSON file {} failed", export.to_string_lossy()) + }); } } - }; - env_logger::builder().filter_level(filter_level).init(); - log::info!("Version: {}", crate_version!()); - - let input = std::fs::read_to_string( - matches - .value_of("INPUT") - .expect("Input Filename should be given"), - ) - .expect("Error Reading File"); - let parser = AdfParser::default(); - parser.parse()(&input).unwrap(); - log::info!("[Done] parsing"); - - if matches.is_present("sort_lex") { - parser.varsort_lexi(); - } - if matches.is_present("sort_alphan") { - parser.varsort_alphanum(); - } - - let mut adf = Adf::from_parser(&parser); - if matches.is_present("grounded") { - let grounded = adf.grounded(); - println!("{}", adf.print_interpretation(&grounded)); - } - if matches.is_present("stable") { - let stable = adf.stable(1); - for model in stable { - println!("{}", adf.print_interpretation(&model)); + if self.grounded { + let grounded = adf.grounded(); + println!("{}", adf.print_interpretation(&grounded)); + } + if self.stable { + let stable = adf.stable(0); + for model in stable { + println!("{}", adf.print_interpretation(&model)); + } } } } + +fn main() { + let app = App::from_args(); + app.run(); +} diff --git a/tests/cli.rs b/tests/cli.rs index c9c49a4..a660261 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -51,6 +51,12 @@ fn runs() -> Result<(), Box> { "u(7) F(4) u(8) u(3) F(5) u(9) u(10) u(1) u(6) u(2)", )); + cmd = Command::cargo_bin("adf_bdd")?; + cmd.arg(file.path()).arg("-q").arg("--grd"); + cmd.assert().success().stdout(predicate::str::contains( + "u(7) F(4) u(8) u(3) F(5) u(9) u(10) u(1) u(6) u(2)", + )); + cmd = Command::cargo_bin("adf_bdd")?; cmd.arg(file.path()).arg("--lx").arg("-v").arg("--grd"); cmd.assert().success().stdout(predicate::str::contains( @@ -62,5 +68,63 @@ fn runs() -> Result<(), Box> { cmd.assert().success().stdout(predicate::str::contains( "u(1) u(2) u(3) F(4) F(5) u(6) u(7) u(8) u(9) u(10) \n\n", )); + + cmd = Command::cargo_bin("adf_bdd")?; + cmd.env_clear(); + cmd.arg(file.path()).arg("--an").arg("--grd"); + cmd.assert().success().stdout(predicate::str::contains( + "u(1) u(2) u(3) F(4) F(5) u(6) u(7) u(8) u(9) u(10) \n\n", + )); + + cmd = Command::cargo_bin("adf_bdd")?; + cmd.arg(file.path()) + .arg("--an") + .arg("--grd") + .arg("--rust_log") + .arg("trace"); + cmd.assert().success().stdout(predicate::str::contains( + "u(1) u(2) u(3) F(4) F(5) u(6) u(7) u(8) u(9) u(10) \n\n", + )); + + cmd = Command::cargo_bin("adf_bdd")?; + cmd.arg(file.path()) + .arg("--an") + .arg("--grd") + .arg("--rust_log") + .arg("warn"); + cmd.assert().success().stdout(predicate::str::contains( + "u(1) u(2) u(3) F(4) F(5) u(6) u(7) u(8) u(9) u(10) \n\n", + )); + + let tempdir = assert_fs::TempDir::new()?; + + cmd = Command::cargo_bin("adf_bdd")?; + cmd.arg(file.path()) + .arg("--an") + .arg("--grd") + .arg("--export") + .arg(tempdir.path().with_file_name("test.json")); + cmd.assert().success().stdout(predicate::str::contains( + "u(1) u(2) u(3) F(4) F(5) u(6) u(7) u(8) u(9) u(10) \n\n", + )); + + cmd = Command::cargo_bin("adf_bdd")?; + cmd.arg(file.path()) + .arg("--an") + .arg("--grd") + .arg("--export") + .arg(tempdir.path().with_file_name("test.json")); + cmd.assert().success().stdout(predicate::str::contains( + "u(1) u(2) u(3) F(4) F(5) u(6) u(7) u(8) u(9) u(10) \n\n", + )); + + cmd = Command::cargo_bin("adf_bdd")?; + cmd.arg(tempdir.path().with_file_name("test.json")) + .arg("--an") + .arg("--grd") + .arg("--import"); + cmd.assert().success().stdout(predicate::str::contains( + "u(1) u(2) u(3) F(4) F(5) u(6) u(7) u(8) u(9) u(10) \n\n", + )); Ok(()) }