diff --git a/Cargo.lock b/Cargo.lock index 6417785ca0..96fddbbcac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -323,6 +323,7 @@ name = "conjure_core" version = "0.0.1" dependencies = [ "anyhow", + "clap", "conjure_macros", "derivative", "derive_is_enum_variant", diff --git a/conjure_oxide/src/main.rs b/conjure_oxide/src/main.rs index d15f9f31ca..a74a25b95b 100644 --- a/conjure_oxide/src/main.rs +++ b/conjure_oxide/src/main.rs @@ -9,7 +9,6 @@ use std::process::exit; use anyhow::Result as AnyhowResult; use anyhow::{anyhow, bail}; use clap::{arg, command, Parser}; -use conjure_oxide::utils::json::sort_json_object; use schemars::schema_for; use serde_json::json; use serde_json::to_string_pretty; @@ -27,23 +26,46 @@ use conjure_oxide::SolverFamily; #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { - #[arg(long, value_name = "SOLVER")] - solver: Option, - #[arg( value_name = "INPUT_ESSENCE", - default_value = "./conjure_oxide/tests/integration/xyz/input.essence" + default_value = "./conjure_oxide/tests/integration/xyz/input.essence", + help = "The input Essence file" )] input_file: PathBuf, + #[arg( + long, + value_name = "EXTRA_RULE_SETS", + help = "Names of extra rule sets to enable" + )] + extra_rule_sets: Vec, + + #[arg( + long, + value_enum, + value_name = "SOLVER", + short = 's', + help = "Solver family use (Minion by default)" + )] + solver: Option, // ToDo this should probably set the solver adapter + // TODO: subcommands instead of these being a flag. - #[arg(long, default_value_t = false)] - /// Prints the schema for the info JSON then exits. + #[arg( + long, + default_value_t = false, + help = "Print the schema for the info JSON and exit" + )] print_info_schema: bool, - #[arg(long)] - /// Saves execution info as JSON to the given file-path. + #[arg(long, help = "Save execution info as JSON to the given file-path.")] info_json_path: Option, + + #[arg( + long, + short = 'o', + help = "Save solutions to a JSON file (prints to stdin by default)" + )] + output: Option, } #[allow(clippy::unwrap_used)] @@ -57,21 +79,34 @@ pub fn main() -> AnyhowResult<()> { return Ok(()); } - let target_family = SolverFamily::Minion; // ToDo get this from CLI input - let extra_rule_sets: Vec<&str> = vec!["Constant"]; // ToDo get this from CLI input - + let target_family = cli.solver.unwrap_or(SolverFamily::Minion); + let extra_rule_sets: Vec = cli.extra_rule_sets; + let out_file: Option = match &cli.output { + None => None, + Some(pth) => Some( + File::options() + .create(true) + .truncate(true) + .write(true) + .open(pth)?, + ), + }; #[allow(clippy::unwrap_used)] let log_file = File::options() .create(true) .append(true) - .open("conjure_oxide.log") - .unwrap(); + .open("conjure_oxide.log")?; Builder::new() .with_target_writer("info", new_writer(stdout())) .with_target_writer("file", new_writer(log_file)) .init(); + if target_family != SolverFamily::Minion { + log::error!("Only the Minion solver is currently supported!"); + exit(1); + } + let rule_sets = match resolve_rule_sets(target_family, &extra_rule_sets) { Ok(rs) => rs, Err(e) => { @@ -80,23 +115,30 @@ pub fn main() -> AnyhowResult<()> { } }; + let pretty_rule_sets = rule_sets + .iter() + .map(|rule_set| rule_set.name) + .collect::>() + .join(", "); + + println!("Enabled rule sets: [{}]", pretty_rule_sets); log::info!( - target: "info", + target: "file", "Rule sets: {}", - rule_sets.iter().map(|rule_set| rule_set.name).collect::>().join(", ") + pretty_rule_sets ); let rule_priorities = get_rule_priorities(&rule_sets)?; let rules_vec = get_rules_vec(&rule_priorities); - log::info!(target: "info", + log::info!(target: "file", "Rules and priorities: {}", rules_vec.iter() .map(|rule| format!("{}: {}", rule.name, rule_priorities.get(rule).unwrap_or(&0))) .collect::>() .join(", ")); - log::info!("Input file: {}", cli.input_file.display()); + log::info!(target: "file", "Input file: {}", cli.input_file.display()); let input_file: &str = cli.input_file.to_str().ok_or(anyhow!( "Given input_file could not be converted to a string" ))?; @@ -133,15 +175,31 @@ pub fn main() -> AnyhowResult<()> { let mut model = model_from_json(&astjson, context.clone())?; - log::info!("Initial model: {}", to_string_pretty(&json!(model))?); + log::info!(target: "file", "Initial model: {}", json!(model)); - log::info!("Rewriting model..."); + log::info!(target: "file", "Rewriting model..."); model = rewrite_model(&model, &rule_sets)?; - log::info!("Rewritten model: {}", to_string_pretty(&json!(model))?); + log::info!(target: "file", "Rewritten model: {}", json!(model)); + + let solutions = get_minion_solutions(model)?; // ToDo we need to properly set the solver adaptor here, not hard code minion + log::info!(target: "file", "Solutions: {}", minion_solutions_to_json(&solutions)); - let solutions = get_minion_solutions(model)?; - log::info!("Solutions: {}", minion_solutions_to_json(&solutions)); + let solutions_json = minion_solutions_to_json(&solutions); + let solutions_str = to_string_pretty(&solutions_json)?; + match out_file { + None => { + println!("Solutions:"); + println!("{}", solutions_str); + } + Some(mut outf) => { + outf.write_all(solutions_str.as_bytes())?; + println!( + "Solutions saved to {:?}", + &cli.output.unwrap().canonicalize()? + ) + } + } if let Some(path) = cli.info_json_path { #[allow(clippy::unwrap_used)] diff --git a/conjure_oxide/tests/generated_tests.rs b/conjure_oxide/tests/generated_tests.rs index 9d4361f08a..671c3a9120 100644 --- a/conjure_oxide/tests/generated_tests.rs +++ b/conjure_oxide/tests/generated_tests.rs @@ -54,7 +54,7 @@ fn integration_test(path: &str, essence_base: &str) -> Result<(), Box assert_eq!(model, expected_model); // Stage 2: Rewrite the model using the rule engine and check that the result is as expected - let rule_sets = resolve_rule_sets(SolverFamily::Minion, &vec!["Constant"])?; + let rule_sets = resolve_rule_sets(SolverFamily::Minion, &vec!["Constant".to_string()])?; let model = rewrite_model(&model, &rule_sets)?; if verbose { println!("Rewritten model: {:#?}", model) diff --git a/conjure_oxide/tests/rewrite_tests.rs b/conjure_oxide/tests/rewrite_tests.rs index 7f4a8d04b7..7e6206e27a 100644 --- a/conjure_oxide/tests/rewrite_tests.rs +++ b/conjure_oxide/tests/rewrite_tests.rs @@ -3,7 +3,6 @@ use std::collections::HashMap; use std::env; use std::process::exit; -use conjure_core::context::Context; use conjure_core::rules::eval_constant; use conjure_core::solver::SolverFamily; use conjure_oxide::{ @@ -829,7 +828,7 @@ fn rule_distribute_or_over_and() { fn rewrite_solve_xyz() { println!("Rules: {:?}", get_rules()); - let rule_sets = match resolve_rule_sets(SolverFamily::Minion, &vec!["Constant"]) { + let rule_sets = match resolve_rule_sets(SolverFamily::Minion, &vec!["Constant".to_string()]) { Ok(rs) => rs, Err(e) => { eprintln!("Error resolving rule sets: {}", e); @@ -868,7 +867,7 @@ fn rewrite_solve_xyz() { ], ); - let rule_sets = match resolve_rule_sets(SolverFamily::Minion, &vec!["Constant"]) { + let rule_sets = match resolve_rule_sets(SolverFamily::Minion, &vec!["Constant".to_string()]) { Ok(rs) => rs, Err(e) => { eprintln!("Error resolving rule sets: {}", e); diff --git a/crates/conjure_core/Cargo.toml b/crates/conjure_core/Cargo.toml index 67697bdaef..9b8086e8ba 100644 --- a/crates/conjure_core/Cargo.toml +++ b/crates/conjure_core/Cargo.toml @@ -24,6 +24,7 @@ regex = "1.10.4" walkdir = "2.5.0" derivative = "2.2.0" schemars = "0.8.16" +clap = { version = "4.5.4", features = ["derive"] } [lints] workspace = true diff --git a/crates/conjure_core/src/context.rs b/crates/conjure_core/src/context.rs index d3716eb919..44cd76280c 100644 --- a/crates/conjure_core/src/context.rs +++ b/crates/conjure_core/src/context.rs @@ -2,7 +2,7 @@ use std::fmt::{Debug, Formatter}; use std::sync::{Arc, RwLock}; use derivative::Derivative; -use schemars::{schema_for, JsonSchema}; +use schemars::JsonSchema; use serde::Serialize; use serde_with::skip_serializing_none; @@ -20,7 +20,7 @@ pub struct Context<'a> { pub file_name: Option, - pub extra_rule_set_names: Vec<&'a str>, + pub extra_rule_set_names: Vec, #[serde(skip)] pub rules: Vec<&'a Rule<'a>>, @@ -35,7 +35,7 @@ pub struct Context<'a> { impl<'a> Context<'a> { pub fn new( target_solver_family: SolverFamily, - extra_rule_set_names: Vec<&'a str>, + extra_rule_set_names: Vec, rules: Vec<&'a Rule<'a>>, rule_sets: Vec<&'a RuleSet<'a>>, ) -> Self { @@ -53,7 +53,7 @@ impl<'a> Context<'a> { impl Context<'static> { pub fn new_ptr( target_solver_family: SolverFamily, - extra_rule_set_names: Vec<&'static str>, + extra_rule_set_names: Vec, rules: Vec<&'static Rule<'static>>, rule_sets: Vec<&'static RuleSet<'static>>, ) -> Arc>> { @@ -69,7 +69,7 @@ impl Context<'static> { impl<'a> Debug for Context<'a> { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let target_solver_family: Option = self.target_solver_family; - let extra_rule_set_names: Vec<&str> = self.extra_rule_set_names.clone(); + let extra_rule_set_names: Vec = self.extra_rule_set_names.clone(); let rules: Vec<&str> = self.rules.iter().map(|r| r.name).collect(); let rule_sets: Vec<&str> = self.rule_sets.iter().map(|r| r.name).collect(); diff --git a/crates/conjure_core/src/parse/example_models.rs b/crates/conjure_core/src/parse/example_models.rs index 6d8eacd71e..439213badc 100644 --- a/crates/conjure_core/src/parse/example_models.rs +++ b/crates/conjure_core/src/parse/example_models.rs @@ -1,6 +1,5 @@ // example_models with get_example_model function -use std::default; use std::path::PathBuf; use project_root::get_project_root; diff --git a/crates/conjure_core/src/rule_engine/resolve_rules.rs b/crates/conjure_core/src/rule_engine/resolve_rules.rs index 91e2c2e62d..deb4d55a37 100644 --- a/crates/conjure_core/src/rule_engine/resolve_rules.rs +++ b/crates/conjure_core/src/rule_engine/resolve_rules.rs @@ -43,7 +43,7 @@ fn get_rule_set(rule_set_name: &str) -> Result<&'static RuleSet<'static>, Resolv /// #[allow(clippy::mutable_key_type)] // RuleSet is 'static so it's fine pub fn rule_sets_by_names<'a>( - rule_set_names: &Vec<&str>, + rule_set_names: &Vec, ) -> Result>, ResolveRulesError> { let mut rs_set: HashSet<&'static RuleSet<'static>> = HashSet::new(); @@ -69,7 +69,7 @@ pub fn rule_sets_by_names<'a>( #[allow(clippy::mutable_key_type)] // RuleSet is 'static so it's fine pub fn resolve_rule_sets<'a>( target_solver: SolverFamily, - extra_rs_names: &Vec<&str>, + extra_rs_names: &Vec, ) -> Result>, ResolveRulesError> { let mut ans = HashSet::new(); diff --git a/crates/conjure_core/src/solver/mod.rs b/crates/conjure_core/src/solver/mod.rs index c097336ab8..199c6b1fbd 100644 --- a/crates/conjure_core/src/solver/mod.rs +++ b/crates/conjure_core/src/solver/mod.rs @@ -32,7 +32,7 @@ //! //! // Define and rewrite a model for minion. //! let model = get_example_model("bool-03").unwrap(); -//! let rule_sets = resolve_rule_sets(SolverFamily::Minion, &vec!["Constant"]).unwrap(); +//! let rule_sets = resolve_rule_sets(SolverFamily::Minion, &vec!["Constant".to_string()]).unwrap(); //! let model = rewrite_model(&model,&rule_sets).unwrap(); //! //! @@ -128,7 +128,6 @@ pub mod states; Deserialize, JsonSchema, )] - pub enum SolverFamily { SAT, Minion,