diff --git a/crates/conjure-cp-cli/src/pretty.rs b/crates/conjure-cp-cli/src/pretty.rs index 08dc5fa5b6..b9f5715b4f 100644 --- a/crates/conjure-cp-cli/src/pretty.rs +++ b/crates/conjure-cp-cli/src/pretty.rs @@ -3,7 +3,7 @@ use std::{path::PathBuf, sync::Arc}; use anyhow::anyhow; use clap::ValueHint; -use conjure_cp_cli::utils::testing::serialize_model; +use conjure_cp_cli::utils::testing::{serialize_domains, serialize_model}; use crate::cli::{GlobalArgs, LOGGING_HELP_HEADING}; use crate::solve::{init_context, parse}; @@ -28,10 +28,11 @@ pub fn run_pretty_command(global_args: GlobalArgs, pretty_args: Args) -> anyhow: // Running the correct method to acquire pretty string let output = match pretty_args.output_format.as_str() { "ast-json" => serialize_model(&model), + "expression-domains" => serialize_domains(&model), // "add_new_flag" => method(), _ => { return Err(anyhow!( - "Unknown output format {}; supports [ast-json]", + "Unknown output format {}; supports [ast-json, expression-domains]", &pretty_args.output_format )); } diff --git a/crates/conjure-cp-cli/src/utils/testing.rs b/crates/conjure-cp-cli/src/utils/testing.rs index bf09b9c759..352d394812 100644 --- a/crates/conjure-cp-cli/src/utils/testing.rs +++ b/crates/conjure-cp-cli/src/utils/testing.rs @@ -13,7 +13,7 @@ use std::io::{BufRead, BufReader, Write}; use std::sync::{Arc, RwLock}; use uniplate::Uniplate; -use conjure_cp::ast::{AbstractLiteral, GroundDomain, Moo, SerdeModel}; +use conjure_cp::ast::{AbstractLiteral, ExprInfo, GroundDomain, Moo, SerdeModel}; use conjure_cp::context::Context; use serde_json::{Error as JsonError, Value as JsonValue}; @@ -115,6 +115,11 @@ pub fn serialize_model(model: &ConjureModel) -> Result { serde_json::to_string_pretty(&sorted_json) } +pub fn serialize_domains(model: &ConjureModel) -> Result { + let exprs: Vec = model.constraints().iter().map(ExprInfo::create).collect(); + serde_json::to_string_pretty(&exprs) +} + pub fn save_model_json( model: &ConjureModel, path: &str, diff --git a/crates/conjure-cp-core/src/ast/model.rs b/crates/conjure-cp-core/src/ast/model.rs index 8c265d9534..95a73ec2ce 100644 --- a/crates/conjure-cp-core/src/ast/model.rs +++ b/crates/conjure-cp-core/src/ast/model.rs @@ -3,6 +3,7 @@ use std::fmt::{Debug, Display}; use std::hash::Hash; use std::sync::{Arc, RwLock}; +use crate::ast::Domain; use crate::context::Context; use crate::{bug, into_matrix_expr}; use derivative::Derivative; @@ -549,3 +550,26 @@ impl SerdeModel { model.collect_stable_id_mapping() } } + +/// A struct for the information about expressions +#[serde_as] +#[derive(Serialize, Deserialize)] +pub struct ExprInfo { + pretty: String, + domain: Option>, + children: Vec, +} + +impl ExprInfo { + pub fn create(expr: &Expression) -> ExprInfo { + let pretty = expr.to_string(); + let domain = expr.domain_of(); + let children = expr.children().iter().map(Self::create).collect(); + + ExprInfo { + pretty, + domain, + children, + } + } +} diff --git a/tests-integration/tests/domain_tightening/README.md b/tests-integration/tests/domain_tightening/README.md new file mode 100644 index 0000000000..91b1e4eb7e --- /dev/null +++ b/tests-integration/tests/domain_tightening/README.md @@ -0,0 +1,13 @@ +# Domain Tightening Tests +These tests are designed to check whether or not the pruning stage reduces the domains of expressions without making them invalid. + +They will work by running the same underlying functions used in the `pretty <> --output-format=expression-domains` command. + +The general steps of the algorithm (TBC) will likely involve: +1. Parse essence file +2. Get the domains of the expressions + +We can then add subsequent steps that will run some ac-style algorithm and recheck the domains. + +## How to Run +cargo test tests_domain_tightening \ No newline at end of file diff --git a/tests-integration/tests/domain_tightening_tests.rs b/tests-integration/tests/domain_tightening_tests.rs new file mode 100644 index 0000000000..ed4200ad86 --- /dev/null +++ b/tests-integration/tests/domain_tightening_tests.rs @@ -0,0 +1,114 @@ +use std::{ + env, + error::Error, + fs, + sync::{Arc, RwLock}, +}; + +use conjure_cp::parse::tree_sitter::{parse_essence_file, parse_essence_file_native}; +use conjure_cp::settings::Parser; +use conjure_cp::{ + Model, ast::ExprInfo, context::Context, parse::tree_sitter::errors::ParseErrorCollection, +}; +use tests_integration::TestConfig; + +/// Parser function used by expression domain tests. +type ParseFn = fn(&str, Arc>>) -> Result>; + +/// Runs a test for one model using each configured parser +fn expression_domain_test( + path: &str, + filename: &str, + extension: &str, +) -> Result<(), Box> { + let accept = env::var("ACCEPT").unwrap_or("false".to_string()) == "true"; + + let file_config: TestConfig = + if let Ok(config_contents) = fs::read_to_string(format!("{path}/config.toml")) { + toml::from_str(&config_contents).unwrap() + } else { + Default::default() + }; + + if accept { + clean_test_dir_for_accept(path)?; + } + + let parsers = file_config + .configured_parsers() + .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?; + + for parser in parsers { + let case_name = parser.to_string(); + let parse = match parser { + Parser::TreeSitter => parse_essence_file_native, + Parser::ViaConjure => parse_essence_file, + }; + expression_domain_test_inner(path, filename, &case_name, extension, parse)?; + } + + Ok(()) +} + +fn expression_domain_test_inner( + path: &str, + input_filename: &str, + case_name: &str, + extension: &str, + parse: ParseFn, +) -> Result<(), Box> { + unimplemented!() +} + +fn clean_test_dir_for_accept(path: &str) -> Result<(), std::io::Error> { + for entry in std::fs::read_dir(path)? { + let entry = entry?; + let file_name = entry.file_name(); + let file_name = file_name.to_string_lossy(); + let entry_path = entry.path(); + + if entry_path.is_dir() { + continue; + } + + let keep = if file_name == "config.toml" { + true + } else { + let is_model_file = entry_path + .extension() + .and_then(|ext| ext.to_str()) + .is_some_and(|ext| ext == "essence"); + let is_generated_or_expected = + file_name.contains(".generated") || file_name.contains(".expected"); + is_model_file && !is_generated_or_expected + }; + + if keep { + continue; + } + + std::fs::remove_file(entry_path)?; + } + + Ok(()) +} + +/// Returns the saved expression JSON path +fn expression_domains_json_path(path: &str, case_name: &str, file_type: &str) -> String { + format!("{path}/{case_name}.{file_type}.serialised.json") +} + +/// Reads and initialises a saved expression domains snapshot. +fn read_expression_domains_json( + context: &Arc>>, + path: &str, + case_name: &str, + file_type: &str, +) -> Result, std::io::Error> { + let serialised = fs::read_to_string(expression_domains_json_path(path, case_name, file_type))?; + let exprs: Vec = serde_json::from_str(&serialised).map_err(std::io::Error::other)?; + + Ok(exprs) +} + +// include!(concat!(env!("OUT_DIR"), "/gen_tests_domain_tightening.rs"));