diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/flat.svg b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/flat.svg
new file mode 100644
index 000000000..5d16fe0c0
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/flat.svg
@@ -0,0 +1,23 @@
+
+ coverage: 72%
+
+
+
+
+
+
+
+
+
+
+
+
+
+ coverage
+ coverage
+ 72%
+ 72%
+
+
+
\ No newline at end of file
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/flat_square.svg b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/flat_square.svg
new file mode 100644
index 000000000..05e4ffb5c
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/flat_square.svg
@@ -0,0 +1,13 @@
+
+ coverage: 72%
+
+
+
+
+
+ coverage
+ 72%
+
+
+
\ No newline at end of file
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/for_the_badge.svg b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/for_the_badge.svg
new file mode 100644
index 000000000..82deedd46
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/for_the_badge.svg
@@ -0,0 +1,13 @@
+
+ COVERAGE: 72%
+
+
+
+
+
+ COVERAGE
+ 72%
+
+
+
\ No newline at end of file
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/plastic.svg b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/plastic.svg
new file mode 100644
index 000000000..8cc11d9bb
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/plastic.svg
@@ -0,0 +1,25 @@
+
+ coverage: 72%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ coverage
+ coverage
+ 72%
+ 72%
+
+
+
\ No newline at end of file
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/social.svg b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/social.svg
new file mode 100644
index 000000000..4c18f142e
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/badges/social.svg
@@ -0,0 +1,27 @@
+
+ Coverage: 72%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Coverage
+ Coverage
+ 72%
+ 72%
+
+
+
\ No newline at end of file
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/find_conjure.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/find_conjure.rs.html
new file mode 100644
index 000000000..9b1dcf38d
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/find_conjure.rs.html
@@ -0,0 +1,1049 @@
+
+
+
+
+ Grcov report - find_conjure.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 16.67 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use anyhow::{anyhow, bail, Result};
+
+
+
+
+
+
+
+
use versions::Versioning;
+
+
+
+
+
+
+
+
const CONJURE_MIN_VERSION: &str = "2.5.1";
+
+
+
+
+
+
+
+
const CORRECT_FIRST_LINE: &str = "Conjure: The Automated Constraint Modelling Tool";
+
+
+
+
+
+
+
+
/// Checks if the conjure executable is present in PATH and if it is the correct version.
+
+
+
+
+
+
+
+
/// Returns () on success and an error on failure.
+
+
+
+
+ 4
+
+
+
pub fn conjure_executable() -> Result<()> {
+
+
+
+
+ 4
+
+
+
let mut cmd = std::process::Command::new("conjure");
+
+
+
+
+ 4
+
+
+
let output = cmd.arg("--version").output()?;
+
+
+
+
+ 4
+
+
+
let stdout = String::from_utf8(output.stdout)?;
+
+
+
+
+ 4
+
+
+
let stderr = String::from_utf8(output.stderr)?;
+
+
+
+
+ 4
+
+
+
if !stderr.is_empty() {
+
+
+
+
+
+
+
+
bail!("'conjure' results in error: ".to_string() + &stderr);
+
+
+
+
+ 4
+
+
+
.ok_or(anyhow!("Could not read stdout"))?;
+
+
+
+
+ 4
+
+
+
if first != CORRECT_FIRST_LINE {
+
+
+
+
+
+
+
+
let path = std::env::var("PATH")?;
+
+
+
+
+
+
+
+
let paths = std::env::split_paths(&path);
+
+
+
+
+
+
+
+
let num_conjures = paths.filter(|path| path.join("conjure").exists()).count();
+
+
+
+
+
+
+
+
if num_conjures > 1 {
+
+
+
+
+
+
+
+
"Conjure may be present in PATH after a conflicting name. \
+
+
+
+
+
+
+
+
Make sure to prepend the correct path to Conjure to PATH."
+
+
+
+
+
+
+
+
bail!("The correct Conjure executable is not present in PATH.")
+
+
+
+
+ 4
+
+
+
let version_line = stdout
+
+
+
+
+ 4
+
+
+
.ok_or(anyhow!("Could not read Conjure's stdout"))?;
+
+
+
+
+ 4
+
+
+
let version = match version_line.strip_prefix("Release version ") {
+
+
+
+
+
+
+
+
None => match version_line.strip_prefix("Conjure v") {
+
+
+
+
+
+
+
+
// New format: Conjure v2.5.1 (Repository version ...)
+
+
+
+
+
+
+
+
Some(v) => v.split_whitespace().next().ok_or(anyhow!(
+
+
+
+
+
+
+
+
"Could not read Conjure's version from: {}",
+
+
+
+
+
+
+
+
"Could not read Conjure's version from: {}",
+
+
+
+
+ 4
+
+
+
if Versioning::new(version) < Versioning::new(CONJURE_MIN_VERSION) {
+
+
+
+
+
+
+
+
"Conjure version is too old (< {}): {}",
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/index.html
new file mode 100644
index 000000000..f11a57966
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/index.html
@@ -0,0 +1,74 @@
+
+
+
+
+ Grcov report - conjure_oxide/src
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 16.67 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ find_conjure.rs
+
+
+
+ 47.92%
+
+
+
+ 47.92%
+
+
+ 23 / 48
+
+
+ 16.67%
+ 1 / 6
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/conjure.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/conjure.rs.html
new file mode 100644
index 000000000..ea35b5440
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/conjure.rs.html
@@ -0,0 +1,1689 @@
+
+
+
+
+ Grcov report - conjure.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 22.22 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::collections::HashMap;
+
+
+
+
+
+
+
+
use std::sync::{Arc, Mutex, RwLock};
+
+
+
+
+
+
+
+
use conjure_core::context::Context;
+
+
+
+
+
+
+
+
use serde_json::{Map, Value as JsonValue};
+
+
+
+
+
+
+
+
use thiserror::Error as ThisError;
+
+
+
+
+
+
+
+
use crate::ast::{Constant, Name};
+
+
+
+
+
+
+
+
use crate::model_from_json;
+
+
+
+
+
+
+
+
use crate::solver::adaptors::Minion;
+
+
+
+
+
+
+
+
use crate::solver::Solver;
+
+
+
+
+
+
+
+
use crate::utils::json::sort_json_object;
+
+
+
+
+
+
+
+
use crate::Error as ParseErr;
+
+
+
+
+
+
+
+
#[derive(Debug, ThisError)]
+
+
+
+
+
+
+
+
pub enum EssenceParseError {
+
+
+
+
+
+
+
+
#[error("Error running conjure pretty: {0}")]
+
+
+
+
+
+
+
+
ConjurePrettyError(String),
+
+
+
+
+
+
+
+
#[error("Error parsing essence file: {0}")]
+
+
+
+
+
+
+
+
ParseError(ParseErr),
+
+
+
+
+
+
+
+
impl From<ParseErr> for EssenceParseError {
+
+
+
+
+
+
+
+
fn from(e: ParseErr) -> Self {
+
+
+
+
+
+
+
+
EssenceParseError::ParseError(e)
+
+
+
+
+ 36
+
+
+
pub fn parse_essence_file(
+
+
+
+
+ 36
+
+
+
context: Arc<RwLock<Context<'static>>>,
+
+
+
+
+ 36
+
+
+
) -> Result<Model, EssenceParseError> {
+
+
+
+
+ 36
+
+
+
let mut cmd = std::process::Command::new("conjure");
+
+
+
+
+ 36
+
+
+
let output = match cmd
+
+
+
+
+ 36
+
+
+
.arg("--output-format=astjson")
+
+
+
+
+ 36
+
+
+
.arg(format!("{path}/{filename}.essence"))
+
+
+
+
+ 36
+
+
+
Ok(output) => output,
+
+
+
+
+
+
+
+
Err(e) => return Err(EssenceParseError::ConjurePrettyError(e.to_string())),
+
+
+
+
+ 36
+
+
+
if !output.status.success() {
+
+
+
+
+
+
+
+
let stderr_string = String::from_utf8(output.stderr)
+
+
+
+
+
+
+
+
.unwrap_or("stderr is not a valid UTF-8 string".to_string());
+
+
+
+
+
+
+
+
return Err(EssenceParseError::ConjurePrettyError(stderr_string));
+
+
+
+
+ 36
+
+
+
let astjson = match String::from_utf8(output.stdout) {
+
+
+
+
+ 36
+
+
+
Ok(astjson) => astjson,
+
+
+
+
+
+
+
+
return Err(EssenceParseError::ConjurePrettyError(format!(
+
+
+
+
+
+
+
+
"Error parsing output from conjure: {:#?}",
+
+
+
+
+ 36
+
+
+
let parsed_model = model_from_json(&astjson, context)?;
+
+
+
+
+ 36
+
+
+
pub fn get_minion_solutions(model: Model) -> Result<Vec<HashMap<Name, Constant>>, anyhow::Error> {
+
+
+
+
+ 36
+
+
+
let solver = Solver::new(Minion::new());
+
+
+
+
+ 36
+
+
+
println!("Building Minion model...");
+
+
+
+
+ 36
+
+
+
let solver = solver.load_model(model)?;
+
+
+
+
+ 36
+
+
+
println!("Running Minion...");
+
+
+
+
+ 36
+
+
+
let all_solutions_ref = Arc::new(Mutex::<Vec<HashMap<Name, Constant>>>::new(vec![]));
+
+
+
+
+ 36
+
+
+
let all_solutions_ref_2 = all_solutions_ref.clone();
+
+
+
+
+ 36
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+ 140
+
+
+
solver.solve(Box::new(move |sols| {
+
+
+
+
+ 140
+
+
+
let mut all_solutions = (*all_solutions_ref_2).lock().unwrap();
+
+
+
+
+ 140
+
+
+
(*all_solutions).push(sols);
+
+
+
+
+
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+ 36
+
+
+
let sols = (*all_solutions_ref).lock().unwrap();
+
+
+
+
+ 36
+
+
+
pub fn minion_solutions_to_json(solutions: &Vec<HashMap<Name, Constant>>) -> JsonValue {
+
+
+
+
+ 36
+
+
+
let mut json_solutions = Vec::new();
+
+
+
+
+ 176
+
+
+
for solution in solutions {
+
+
+
+
+ 140
+
+
+
let mut json_solution = Map::new();
+
+
+
+
+ 520
+
+
+
for (var_name, constant) in solution {
+
+
+
+
+ 380
+
+
+
let serialized_constant = match constant {
+
+
+
+
+ 380
+
+
+
Constant::Int(i) => JsonValue::Number((*i).into()),
+
+
+
+
+
+
+
+
Constant::Bool(b) => JsonValue::Bool(*b),
+
+
+
+
+ 380
+
+
+
json_solution.insert(var_name.to_string(), serialized_constant);
+
+
+
+
+ 140
+
+
+
json_solutions.push(JsonValue::Object(json_solution));
+
+
+
+
+ 36
+
+
+
let ans = JsonValue::Array(json_solutions);
+
+
+
+
+ 36
+
+
+
sort_json_object(&ans, true)
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/index.html
new file mode 100644
index 000000000..4b40ea87b
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/index.html
@@ -0,0 +1,146 @@
+
+
+
+
+ Grcov report - conjure_oxide/src/utils
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ conjure.rs
+
+
+
+ 77.78%
+
+
+
+ 77.78%
+
+
+ 49 / 63
+
+
+ 22.22%
+ 4 / 18
+
+
+
+
+
+ json.rs
+
+
+
+ 80.77%
+
+
+
+ 80.77%
+
+
+ 42 / 52
+
+
+ 33.33%
+ 6 / 18
+
+
+
+
+
+ misc.rs
+
+
+
+ 0%
+
+
+
+ 0%
+
+
+ 0 / 7
+
+
+ 0%
+ 0 / 3
+
+
+
+
+
+ testing.rs
+
+
+
+ 54.55%
+
+
+
+ 54.55%
+
+
+ 60 / 110
+
+
+ 23.81%
+ 5 / 21
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/json.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/json.rs.html
new file mode 100644
index 000000000..57bacf205
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/json.rs.html
@@ -0,0 +1,1305 @@
+
+
+
+
+ Grcov report - json.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 33.33 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use serde_json::Value;
+
+
+
+
+
+
+
+
/// Compare two JSON values.
+
+
+
+
+
+
+
+
/// If the values are String, Number, or Bool, they are compared directly.
+
+
+
+
+
+
+
+
/// If the values are arrays, they are compared element-wise.
+
+
+
+
+
+
+
+
/// Otherwise, they are compared as strings.
+
+
+
+
+ 460
+
+
+
fn json_value_cmp(a: &Value, b: &Value) -> std::cmp::Ordering {
+
+
+
+
+
+
+
+
(Value::Null, Value::Null) => std::cmp::Ordering::Equal,
+
+
+
+
+
+
+
+
(Value::Bool(a), Value::Bool(b)) => a.cmp(b),
+
+
+
+
+
+
+
+
(Value::String(a), Value::String(b)) => a.cmp(b),
+
+
+
+
+
+
+
+
(Value::Number(a), Value::Number(b)) => {
+
+
+
+
+
+
+
+
let af = a.as_f64().unwrap_or_default();
+
+
+
+
+
+
+
+
let bf = b.as_f64().unwrap_or_default();
+
+
+
+
+ 104
+
+
+
(Value::Array(a), Value::Array(b)) => {
+
+
+
+
+ 104
+
+
+
for (a, b) in a.iter().zip(b.iter()) {
+
+
+
+
+ 104
+
+
+
let cmp = json_value_cmp(a, b);
+
+
+
+
+ 104
+
+
+
if cmp != std::cmp::Ordering::Equal {
+
+
+
+
+
+
+
+
std::cmp::Ordering::Equal
+
+
+
+
+ 356
+
+
+
_ => a.to_string().cmp(&b.to_string()),
+
+
+
+
+
+
+
+
/// Sort the "variables" field by name.
+
+
+
+
+
+
+
+
/// We have to do this separately because that field is not a JSON object, instead it's an array of tuples.
+
+
+
+
+ 72
+
+
+
pub fn sort_json_variables(value: &Value) -> Value {
+
+
+
+
+ 72
+
+
+
Value::Array(vars) => {
+
+
+
+
+ 72
+
+
+
let mut vars_sorted = vars.clone();
+
+
+
+
+ 72
+
+
+
vars_sorted.sort_by(json_value_cmp);
+
+
+
+
+ 72
+
+
+
Value::Array(vars_sorted)
+
+
+
+
+
+
+
+
/// Recursively sorts the keys of all JSON objects within the provided JSON value.
+
+
+
+
+
+
+
+
/// serde_json will output JSON objects in an arbitrary key order.
+
+
+
+
+
+
+
+
/// this is normally fine, except in our use case we wouldn't want to update the expected output again and again.
+
+
+
+
+
+
+
+
/// so a consistent (sorted) ordering of the keys is desirable.
+
+
+
+
+ 7860
+
+
+
pub fn sort_json_object(value: &Value, sort_arrays: bool) -> Value {
+
+
+
+
+ 3572
+
+
+
Value::Object(obj) => {
+
+
+
+
+ 3572
+
+
+
let mut ordered: Vec<(String, Value)> = obj
+
+
+
+
+ 4196
+
+
+
if k == "variables" {
+
+
+
+
+ 72
+
+
+
(k.clone(), sort_json_variables(v))
+
+
+
+
+ 4124
+
+
+
(k.clone(), sort_json_object(v, sort_arrays))
+
+
+
+
+ 3572
+
+
+
ordered.sort_by(|a, b| a.0.cmp(&b.0));
+
+
+
+
+ 3572
+
+
+
Value::Object(ordered.into_iter().collect())
+
+
+
+
+ 1428
+
+
+
Value::Array(arr) => {
+
+
+
+
+ 1428
+
+
+
let mut arr: Vec<Value> = arr
+
+
+
+
+ 3592
+
+
+
.map(|val| sort_json_object(val, sort_arrays))
+
+
+
+
+ 72
+
+
+
arr.sort_by(json_value_cmp);
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/misc.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/misc.rs.html
new file mode 100644
index 000000000..b138c78f3
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/misc.rs.html
@@ -0,0 +1,217 @@
+
+
+
+
+ Grcov report - misc.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::collections::HashSet;
+
+
+
+
+
+
+
+
pub fn to_set<T: Eq + Hash + Debug + Clone>(a: &Vec<T>) -> HashSet<T> {
+
+
+
+
+
+
+
+
let mut a_set: HashSet<T> = HashSet::new();
+
+
+
+
+
+
+
+
a_set.insert(el.clone());
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/testing.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/testing.rs.html
new file mode 100644
index 000000000..86528c6fa
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/src/utils/testing.rs.html
@@ -0,0 +1,2681 @@
+
+
+
+
+ Grcov report - testing.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 23.81 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::collections::{HashMap, HashSet};
+
+
+
+
+
+
+
+
use serde_json::{Error as JsonError, Value as JsonValue};
+
+
+
+
+
+
+
+
use conjure_core::error::Error;
+
+
+
+
+
+
+
+
use crate::ast::Name::UserName;
+
+
+
+
+
+
+
+
use crate::ast::{Constant, Name};
+
+
+
+
+
+
+
+
use crate::utils::conjure::minion_solutions_to_json;
+
+
+
+
+
+
+
+
use crate::utils::json::sort_json_object;
+
+
+
+
+
+
+
+
use crate::utils::misc::to_set;
+
+
+
+
+
+
+
+
use crate::Model as ConjureModel;
+
+
+
+
+
+
+
+
pub fn assert_eq_any_order<T: Eq + Hash + Debug + Clone>(a: &Vec<Vec<T>>, b: &Vec<Vec<T>>) {
+
+
+
+
+
+
+
+
assert_eq!(a.len(), b.len());
+
+
+
+
+
+
+
+
let mut a_rows: Vec<HashSet<T>> = Vec::new();
+
+
+
+
+
+
+
+
let hash_row = to_set(row);
+
+
+
+
+
+
+
+
a_rows.push(hash_row);
+
+
+
+
+
+
+
+
let mut b_rows: Vec<HashSet<T>> = Vec::new();
+
+
+
+
+
+
+
+
let hash_row = to_set(row);
+
+
+
+
+
+
+
+
b_rows.push(hash_row);
+
+
+
+
+
+
+
+
println!("{:?},{:?}", a_rows, b_rows);
+
+
+
+
+
+
+
+
assert!(b_rows.contains(&row));
+
+
+
+
+ 72
+
+
+
pub fn serialise_model(model: &ConjureModel) -> Result<String, JsonError> {
+
+
+
+
+
+
+
+
// A consistent sorting of the keys of json objects
+
+
+
+
+
+
+
+
// only required for the generated version
+
+
+
+
+
+
+
+
// since the expected version will already be sorted
+
+
+
+
+ 72
+
+
+
let generated_json = sort_json_object(&serde_json::to_value(model.clone())?, false);
+
+
+
+
+
+
+
+
// serialise to string
+
+
+
+
+ 72
+
+
+
let generated_json_str = serde_json::to_string_pretty(&generated_json)?;
+
+
+
+
+ 72
+
+
+
Ok(generated_json_str)
+
+
+
+
+ 72
+
+
+
pub fn save_model_json(
+
+
+
+
+ 72
+
+
+
model: &ConjureModel,
+
+
+
+
+ 72
+
+
+
) -> Result<(), std::io::Error> {
+
+
+
+
+ 72
+
+
+
let generated_json_str = serialise_model(model)?;
+
+
+
+
+ 72
+
+
+
File::create(format!(
+
+
+
+
+ 72
+
+
+
"{path}/{test_name}.generated-{test_stage}.serialised.json"
+
+
+
+
+ 72
+
+
+
.write_all(generated_json_str.as_bytes())?;
+
+
+
+
+
+
+
+
format!("{path}/{test_name}.generated-{test_stage}.serialised.json"),
+
+
+
+
+
+
+
+
format!("{path}/{test_name}.expected-{test_stage}.serialised.json"),
+
+
+
+
+ 72
+
+
+
pub fn read_model_json(
+
+
+
+
+ 72
+
+
+
) -> Result<ConjureModel, std::io::Error> {
+
+
+
+
+ 72
+
+
+
let expected_json_str = std::fs::read_to_string(format!(
+
+
+
+
+ 72
+
+
+
"{path}/{test_name}.{prefix}-{test_stage}.serialised.json"
+
+
+
+
+ 72
+
+
+
let expected_model: ConjureModel = serde_json::from_str(&expected_json_str)?;
+
+
+
+
+
+
+
+
pub fn minion_solutions_from_json(
+
+
+
+
+
+
+
+
) -> Result<Vec<HashMap<Name, Constant>>, anyhow::Error> {
+
+
+
+
+
+
+
+
let json: JsonValue = serde_json::from_str(serialized)?;
+
+
+
+
+
+
+
+
let json_array = json
+
+
+
+
+
+
+
+
.ok_or(Error::Parse("Invalid JSON".to_owned()))?;
+
+
+
+
+
+
+
+
let mut solutions = Vec::new();
+
+
+
+
+
+
+
+
for solution in json_array {
+
+
+
+
+
+
+
+
let mut sol = HashMap::new();
+
+
+
+
+
+
+
+
let solution = solution
+
+
+
+
+
+
+
+
.ok_or(Error::Parse("Invalid JSON".to_owned()))?;
+
+
+
+
+
+
+
+
for (var_name, constant) in solution {
+
+
+
+
+
+
+
+
let constant = match constant {
+
+
+
+
+
+
+
+
JsonValue::Number(n) => {
+
+
+
+
+
+
+
+
.ok_or(Error::Parse("Invalid integer".to_owned()))?;
+
+
+
+
+
+
+
+
Constant::Int(n as i32)
+
+
+
+
+
+
+
+
JsonValue::Bool(b) => Constant::Bool(*b),
+
+
+
+
+
+
+
+
_ => return Err(Error::Parse("Invalid constant".to_owned()).into()),
+
+
+
+
+
+
+
+
sol.insert(UserName(var_name.into()), constant);
+
+
+
+
+ 36
+
+
+
pub fn save_minion_solutions_json(
+
+
+
+
+ 36
+
+
+
solutions: &Vec<HashMap<Name, Constant>>,
+
+
+
+
+ 36
+
+
+
) -> Result<JsonValue, std::io::Error> {
+
+
+
+
+ 36
+
+
+
let json_solutions = minion_solutions_to_json(solutions);
+
+
+
+
+ 36
+
+
+
let generated_json_str = serde_json::to_string_pretty(&json_solutions)?;
+
+
+
+
+ 36
+
+
+
File::create(format!(
+
+
+
+
+ 36
+
+
+
"{path}/{test_name}.generated-minion.solutions.json"
+
+
+
+
+ 36
+
+
+
.write_all(generated_json_str.as_bytes())?;
+
+
+
+
+
+
+
+
format!("{path}/{test_name}.generated-minion.solutions.json"),
+
+
+
+
+
+
+
+
format!("{path}/{test_name}.expected-minion.solutions.json"),
+
+
+
+
+ 36
+
+
+
pub fn read_minion_solutions_json(
+
+
+
+
+ 36
+
+
+
) -> Result<JsonValue, anyhow::Error> {
+
+
+
+
+ 36
+
+
+
let expected_json_str =
+
+
+
+
+ 36
+
+
+
std::fs::read_to_string(format!("{path}/{test_name}.{prefix}-minion.solutions.json"))?;
+
+
+
+
+ 36
+
+
+
let expected_solutions: JsonValue =
+
+
+
+
+ 36
+
+
+
sort_json_object(&serde_json::from_str(&expected_json_str)?, true);
+
+
+
+
+ 36
+
+
+
Ok(expected_solutions)
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/tests/generated_tests.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/tests/generated_tests.rs.html
new file mode 100644
index 000000000..af25d14ce
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/tests/generated_tests.rs.html
@@ -0,0 +1,1465 @@
+
+
+
+
+ Grcov report - generated_tests.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::error::Error;
+
+
+
+
+
+
+
+
use std::sync::RwLock;
+
+
+
+
+
+
+
+
use conjure_core::context::Context;
+
+
+
+
+
+
+
+
use conjure_oxide::rule_engine::resolve_rule_sets;
+
+
+
+
+
+
+
+
use conjure_oxide::rule_engine::rewrite_model;
+
+
+
+
+
+
+
+
use conjure_oxide::utils::conjure::{get_minion_solutions, parse_essence_file};
+
+
+
+
+
+
+
+
use conjure_oxide::utils::testing::{
+
+
+
+
+
+
+
+
read_minion_solutions_json, read_model_json, save_minion_solutions_json, save_model_json,
+
+
+
+
+
+
+
+
use conjure_oxide::SolverFamily;
+
+
+
+
+
+
+
+
let file_path = Path::new("/path/to/your/file.txt");
+
+
+
+
+
+
+
+
let base_name = file_path.file_stem().and_then(|stem| stem.to_str());
+
+
+
+
+
+
+
+
Some(name) => println!("Base name: {}", name),
+
+
+
+
+
+
+
+
None => println!("Could not extract the base name"),
+
+
+
+
+ 9
+
+
+
fn integration_test(path: &str, essence_base: &str) -> Result<(), Box<dyn Error>> {
+
+
+
+
+ 9
+
+
+
let context: Arc<RwLock<Context<'static>>> = Default::default();
+
+
+
+
+ 9
+
+
+
let accept = env::var("ACCEPT").unwrap_or("false".to_string()) == "true";
+
+
+
+
+ 9
+
+
+
let verbose = env::var("VERBOSE").unwrap_or("false".to_string()) == "true";
+
+
+
+
+
+
+
+
"Running integration test for {}/{}, ACCEPT={}",
+
+
+
+
+
+
+
+
path, essence_base, accept
+
+
+
+
+
+
+
+
// Stage 1: Read the essence file and check that the model is parsed correctly
+
+
+
+
+ 9
+
+
+
let model = parse_essence_file(path, essence_base, context)?;
+
+
+
+
+
+
+
+
println!("Parsed model: {:#?}", model)
+
+
+
+
+ 9
+
+
+
save_model_json(&model, path, essence_base, "parse", accept)?;
+
+
+
+
+ 9
+
+
+
let expected_model = read_model_json(path, essence_base, "expected", "parse")?;
+
+
+
+
+
+
+
+
println!("Expected model: {:#?}", expected_model)
+
+
+
+
+ 9
+
+
+
assert_eq!(model, expected_model);
+
+
+
+
+
+
+
+
// Stage 2: Rewrite the model using the rule engine and check that the result is as expected
+
+
+
+
+ 9
+
+
+
let rule_sets = resolve_rule_sets(SolverFamily::Minion, &vec!["Constant"])?;
+
+
+
+
+ 9
+
+
+
let model = rewrite_model(&model, &rule_sets)?;
+
+
+
+
+
+
+
+
println!("Rewritten model: {:#?}", model)
+
+
+
+
+ 9
+
+
+
save_model_json(&model, path, essence_base, "rewrite", accept)?;
+
+
+
+
+ 9
+
+
+
let expected_model = read_model_json(path, essence_base, "expected", "rewrite")?;
+
+
+
+
+
+
+
+
println!("Expected model: {:#?}", expected_model)
+
+
+
+
+ 9
+
+
+
assert_eq!(model, expected_model);
+
+
+
+
+
+
+
+
// Stage 3: Run the model through the Minion solver and check that the solutions are as expected
+
+
+
+
+ 9
+
+
+
let solutions = get_minion_solutions(model)?;
+
+
+
+
+ 9
+
+
+
let solutions_json = save_minion_solutions_json(&solutions, path, essence_base, accept)?;
+
+
+
+
+
+
+
+
println!("Minion solutions: {:#?}", solutions_json)
+
+
+
+
+ 9
+
+
+
let expected_solutions_json = read_minion_solutions_json(path, essence_base, "expected")?;
+
+
+
+
+
+
+
+
println!("Expected solutions: {:#?}", expected_solutions_json)
+
+
+
+
+ 9
+
+
+
assert_eq!(solutions_json, expected_solutions_json);
+
+
+
+
+ 1
+
+
+
fn assert_conjure_present() {
+
+
+
+
+ 1
+
+
+
conjure_oxide::find_conjure::conjure_executable().unwrap();
+
+
+
+
+
+
+
+
include!(concat!(env!("OUT_DIR"), "/gen_tests.rs"));
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/tests/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/tests/index.html
new file mode 100644
index 000000000..e2c3fb4cb
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/tests/index.html
@@ -0,0 +1,122 @@
+
+
+
+
+ Grcov report - conjure_oxide/tests
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 95.74 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ generated_tests.rs
+
+
+
+ 67.27%
+
+
+
+ 67.27%
+
+
+ 37 / 55
+
+
+ 50%
+ 2 / 4
+
+
+
+
+
+ model_tests.rs
+
+
+
+ 100%
+
+
+
+ 100%
+
+
+ 20 / 20
+
+
+ 100%
+ 1 / 1
+
+
+
+
+
+ rewrite_tests.rs
+
+
+
+ 96.69%
+
+
+
+ 96.69%
+
+
+ 906 / 937
+
+
+ 100%
+ 42 / 42
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/tests/model_tests.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/tests/model_tests.rs.html
new file mode 100644
index 000000000..045069614
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/tests/model_tests.rs.html
@@ -0,0 +1,537 @@
+
+
+
+
+ Grcov report - model_tests.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
// Tests for various functionalities of the Model
+
+
+
+
+
+
+
+
use std::collections::HashMap;
+
+
+
+
+
+
+
+
use conjure_core::context::Context;
+
+
+
+
+
+
+
+
use conjure_core::metadata::Metadata;
+
+
+
+
+
+
+
+
use conjure_core::model::Model;
+
+
+
+
+
+
+
+
use conjure_oxide::ast::*;
+
+
+
+
+ 1
+
+
+
let a = Name::UserName(String::from("a"));
+
+
+
+
+ 1
+
+
+
let d1 = Domain::IntDomain(vec![Range::Bounded(1, 3)]);
+
+
+
+
+ 1
+
+
+
let d2 = Domain::IntDomain(vec![Range::Bounded(1, 2)]);
+
+
+
+
+ 1
+
+
+
let mut variables = HashMap::new();
+
+
+
+
+ 1
+
+
+
variables.insert(a.clone(), DecisionVariable { domain: d1.clone() });
+
+
+
+
+ 1
+
+
+
let mut m = Model::new(
+
+
+
+
+ 1
+
+
+
Expression::And(Metadata::new(), Vec::new()),
+
+
+
+
+ 1
+
+
+
assert_eq!(m.variables.get(&a).unwrap().domain, d1);
+
+
+
+
+ 1
+
+
+
m.update_domain(&a, d2.clone());
+
+
+
+
+ 1
+
+
+
assert_eq!(m.variables.get(&a).unwrap().domain, d2);
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/tests/rewrite_tests.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/tests/rewrite_tests.rs.html
new file mode 100644
index 000000000..f7b6ddd47
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/conjure_oxide/tests/rewrite_tests.rs.html
@@ -0,0 +1,18233 @@
+
+
+
+
+ Grcov report - rewrite_tests.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::collections::HashMap;
+
+
+
+
+
+
+
+
use std::process::exit;
+
+
+
+
+
+
+
+
use conjure_core::context::Context;
+
+
+
+
+
+
+
+
use conjure_core::rules::eval_constant;
+
+
+
+
+
+
+
+
use conjure_core::solver::SolverFamily;
+
+
+
+
+
+
+
+
get_rule_by_name, get_rules,
+
+
+
+
+
+
+
+
rule_engine::{resolve_rule_sets, rewrite_model},
+
+
+
+
+
+
+
+
solver::{adaptors, Solver, SolverAdaptor as _},
+
+
+
+
+
+
+
+
Metadata, Model, Rule,
+
+
+
+
+
+
+
+
use uniplate::uniplate::Uniplate;
+
+
+
+
+ 1
+
+
+
let rules = get_rules();
+
+
+
+
+ 1
+
+
+
assert!(!rules.is_empty());
+
+
+
+
+ 1
+
+
+
fn sum_of_constants() {
+
+
+
+
+ 1
+
+
+
let valid_sum_expression = Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(1)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(2)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(3)),
+
+
+
+
+ 1
+
+
+
let invalid_sum_expression = Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(1)),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
match evaluate_sum_of_constants(&valid_sum_expression) {
+
+
+
+
+ 1
+
+
+
Some(result) => assert_eq!(result, 6),
+
+
+
+
+ 1
+
+
+
if evaluate_sum_of_constants(&invalid_sum_expression).is_some() {
+
+
+
+
+ 4
+
+
+
fn evaluate_sum_of_constants(expr: &Expression) -> Option<i32> {
+
+
+
+
+ 4
+
+
+
Expression::Sum(_metadata, expressions) => {
+
+
+
+
+ 12
+
+
+
for e in expressions {
+
+
+
+
+ 8
+
+
+
Expression::Constant(_, Constant::Int(value)) => {
+
+
+
+
+ 1
+
+
+
fn recursive_sum_of_constants() {
+
+
+
+
+ 1
+
+
+
let complex_expression = Expression::Eq(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(1)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(2)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(1)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(2)),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Int(3))),
+
+
+
+
+ 1
+
+
+
let correct_simplified_expression = Expression::Eq(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(1)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(2)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(3)),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Int(3))),
+
+
+
+
+ 1
+
+
+
let simplified_expression = simplify_expression(complex_expression.clone());
+
+
+
+
+ 1
+
+
+
assert_eq!(simplified_expression, correct_simplified_expression);
+
+
+
+
+ 7
+
+
+
fn simplify_expression(expr: Expression) -> Expression {
+
+
+
+
+ 2
+
+
+
Expression::Sum(_metadata, expressions) => {
+
+
+
+
+ 1
+
+
+
if let Some(result) =
+
+
+
+
+ 2
+
+
+
evaluate_sum_of_constants(&Expression::Sum(Metadata::new(), expressions.clone()))
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(result))
+
+
+
+
+ 1
+
+
+
expressions.into_iter().map(simplify_expression).collect(),
+
+
+
+
+ 1
+
+
+
Expression::Eq(_metadata, left, right) => Expression::Eq(
+
+
+
+
+ 1
+
+
+
Box::new(simplify_expression(*left)),
+
+
+
+
+ 1
+
+
+
Box::new(simplify_expression(*right)),
+
+
+
+
+
+
+
+
Expression::Geq(_metadata, left, right) => Expression::Geq(
+
+
+
+
+
+
+
+
Box::new(simplify_expression(*left)),
+
+
+
+
+
+
+
+
Box::new(simplify_expression(*right)),
+
+
+
+
+ 1
+
+
+
fn rule_sum_constants() {
+
+
+
+
+ 1
+
+
+
let sum_constants = get_rule_by_name("sum_constants").unwrap();
+
+
+
+
+ 1
+
+
+
let unwrap_sum = get_rule_by_name("unwrap_sum").unwrap();
+
+
+
+
+ 1
+
+
+
let mut expr = Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(1)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(2)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(3)),
+
+
+
+
+ 1
+
+
+
.apply(&expr, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
.apply(&expr, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(6))
+
+
+
+
+ 1
+
+
+
fn rule_sum_mixed() {
+
+
+
+
+ 1
+
+
+
let sum_constants = get_rule_by_name("sum_constants").unwrap();
+
+
+
+
+ 1
+
+
+
let mut expr = Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(1)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(2)),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
.apply(&expr, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(3)),
+
+
+
+
+ 1
+
+
+
let flatten_sum_geq = get_rule_by_name("flatten_sum_geq").unwrap();
+
+
+
+
+ 1
+
+
+
let mut expr = Expression::Geq(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(1)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(2)),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Int(3))),
+
+
+
+
+ 1
+
+
+
expr = flatten_sum_geq
+
+
+
+
+ 1
+
+
+
.apply(&expr, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(1)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(2)),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Int(3)))
+
+
+
+
+
+
+
+
/// Reduce and solve:
+
+
+
+
+
+
+
+
/// find a,b,c : int(1..3)
+
+
+
+
+
+
+
+
/// such that a + b + c <= 2 + 3 - 1
+
+
+
+
+ 1
+
+
+
fn reduce_solve_xyz() {
+
+
+
+
+ 1
+
+
+
println!("Rules: {:?}", get_rules());
+
+
+
+
+ 1
+
+
+
let sum_constants = get_rule_by_name("sum_constants").unwrap();
+
+
+
+
+ 1
+
+
+
let unwrap_sum = get_rule_by_name("unwrap_sum").unwrap();
+
+
+
+
+ 1
+
+
+
let lt_to_ineq = get_rule_by_name("lt_to_ineq").unwrap();
+
+
+
+
+ 1
+
+
+
let sum_leq_to_sumleq = get_rule_by_name("sum_leq_to_sumleq").unwrap();
+
+
+
+
+ 1
+
+
+
let mut expr1 = Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(2)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(3)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(-1)),
+
+
+
+
+ 1
+
+
+
expr1 = sum_constants
+
+
+
+
+ 1
+
+
+
.apply(&expr1, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
.apply(&expr1, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(4))
+
+
+
+
+ 1
+
+
+
expr1 = Expression::Leq(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("b"))),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("c"))),
+
+
+
+
+ 1
+
+
+
expr1 = sum_leq_to_sumleq
+
+
+
+
+ 1
+
+
+
.apply(&expr1, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("b"))),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("c"))),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Int(4)))
+
+
+
+
+ 1
+
+
+
let mut expr2 = Expression::Lt(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("a")),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("b")),
+
+
+
+
+ 1
+
+
+
.apply(&expr2, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("a"))
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("b"))
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Int(-1)))
+
+
+
+
+ 1
+
+
+
let mut model = Model::new(
+
+
+
+
+ 1
+
+
+
Expression::And(Metadata::new(), vec![expr1, expr2]),
+
+
+
+
+ 1
+
+
+
model.variables.insert(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("a")),
+
+
+
+
+ 1
+
+
+
domain: Domain::IntDomain(vec![Range::Bounded(1, 3)]),
+
+
+
+
+ 1
+
+
+
model.variables.insert(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("b")),
+
+
+
+
+ 1
+
+
+
domain: Domain::IntDomain(vec![Range::Bounded(1, 3)]),
+
+
+
+
+ 1
+
+
+
model.variables.insert(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("c")),
+
+
+
+
+ 1
+
+
+
domain: Domain::IntDomain(vec![Range::Bounded(1, 3)]),
+
+
+
+
+ 1
+
+
+
let solver: Solver<adaptors::Minion> = Solver::new(adaptors::Minion::new());
+
+
+
+
+ 1
+
+
+
let solver = solver.load_model(model).unwrap();
+
+
+
+
+ 1
+
+
+
solver.solve(Box::new(|_| true)).unwrap();
+
+
+
+
+ 1
+
+
+
fn rule_remove_double_negation() {
+
+
+
+
+ 1
+
+
+
let remove_double_negation = get_rule_by_name("remove_double_negation").unwrap();
+
+
+
+
+ 1
+
+
+
let mut expr = Expression::Not(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Not(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Bool(true))),
+
+
+
+
+ 1
+
+
+
expr = remove_double_negation
+
+
+
+
+ 1
+
+
+
.apply(&expr, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true))
+
+
+
+
+ 1
+
+
+
fn rule_unwrap_nested_or() {
+
+
+
+
+ 1
+
+
+
let unwrap_nested_or = get_rule_by_name("unwrap_nested_or").unwrap();
+
+
+
+
+ 1
+
+
+
let mut expr = Expression::Or(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
expr = unwrap_nested_or
+
+
+
+
+ 1
+
+
+
.apply(&expr, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
fn rule_unwrap_nested_and() {
+
+
+
+
+ 1
+
+
+
let unwrap_nested_and = get_rule_by_name("unwrap_nested_and").unwrap();
+
+
+
+
+ 1
+
+
+
let mut expr = Expression::And(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
expr = unwrap_nested_and
+
+
+
+
+ 1
+
+
+
.apply(&expr, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
fn unwrap_nested_or_not_changed() {
+
+
+
+
+ 1
+
+
+
let unwrap_nested_or = get_rule_by_name("unwrap_nested_or").unwrap();
+
+
+
+
+ 1
+
+
+
let expr = Expression::Or(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false)),
+
+
+
+
+ 1
+
+
+
let result = unwrap_nested_or.apply(&expr, &Model::new_empty(Default::default()));
+
+
+
+
+ 1
+
+
+
assert!(result.is_err());
+
+
+
+
+ 1
+
+
+
fn unwrap_nested_and_not_changed() {
+
+
+
+
+ 1
+
+
+
let unwrap_nested_and = get_rule_by_name("unwrap_nested_and").unwrap();
+
+
+
+
+ 1
+
+
+
let expr = Expression::And(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false)),
+
+
+
+
+ 1
+
+
+
let result = unwrap_nested_and.apply(&expr, &Model::new_empty(Default::default()));
+
+
+
+
+ 1
+
+
+
assert!(result.is_err());
+
+
+
+
+ 1
+
+
+
fn remove_trivial_and_or() {
+
+
+
+
+ 1
+
+
+
let remove_trivial_and = get_rule_by_name("remove_trivial_and").unwrap();
+
+
+
+
+ 1
+
+
+
let remove_trivial_or = get_rule_by_name("remove_trivial_or").unwrap();
+
+
+
+
+ 1
+
+
+
let mut expr_and = Expression::And(
+
+
+
+
+ 1
+
+
+
vec![Expression::Constant(Metadata::new(), Constant::Bool(true))],
+
+
+
+
+ 1
+
+
+
let mut expr_or = Expression::Or(
+
+
+
+
+ 1
+
+
+
vec![Expression::Constant(Metadata::new(), Constant::Bool(false))],
+
+
+
+
+ 1
+
+
+
expr_and = remove_trivial_and
+
+
+
+
+ 1
+
+
+
.apply(&expr_and, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
expr_or = remove_trivial_or
+
+
+
+
+ 1
+
+
+
.apply(&expr_or, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true))
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false))
+
+
+
+
+ 1
+
+
+
fn rule_remove_constants_from_or() {
+
+
+
+
+ 1
+
+
+
let remove_constants_from_or = get_rule_by_name("remove_constants_from_or").unwrap();
+
+
+
+
+ 1
+
+
+
let mut expr = Expression::Or(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false)),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
expr = remove_constants_from_or
+
+
+
+
+ 1
+
+
+
.apply(&expr, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true))
+
+
+
+
+ 1
+
+
+
fn rule_remove_constants_from_and() {
+
+
+
+
+ 1
+
+
+
let remove_constants_from_and = get_rule_by_name("remove_constants_from_and").unwrap();
+
+
+
+
+ 1
+
+
+
let mut expr = Expression::And(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false)),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
expr = remove_constants_from_and
+
+
+
+
+ 1
+
+
+
.apply(&expr, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false))
+
+
+
+
+ 1
+
+
+
fn remove_constants_from_or_not_changed() {
+
+
+
+
+ 1
+
+
+
let remove_constants_from_or = get_rule_by_name("remove_constants_from_or").unwrap();
+
+
+
+
+ 1
+
+
+
let expr = Expression::Or(
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("b"))),
+
+
+
+
+ 1
+
+
+
let result = remove_constants_from_or.apply(&expr, &Model::new_empty(Default::default()));
+
+
+
+
+ 1
+
+
+
assert!(result.is_err());
+
+
+
+
+ 1
+
+
+
fn remove_constants_from_and_not_changed() {
+
+
+
+
+ 1
+
+
+
let remove_constants_from_and = get_rule_by_name("remove_constants_from_and").unwrap();
+
+
+
+
+ 1
+
+
+
let expr = Expression::And(
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("b"))),
+
+
+
+
+ 1
+
+
+
let result = remove_constants_from_and.apply(&expr, &Model::new_empty(Default::default()));
+
+
+
+
+ 1
+
+
+
assert!(result.is_err());
+
+
+
+
+ 1
+
+
+
fn rule_distribute_not_over_and() {
+
+
+
+
+ 1
+
+
+
let distribute_not_over_and = get_rule_by_name("distribute_not_over_and").unwrap();
+
+
+
+
+ 1
+
+
+
let mut expr = Expression::Not(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::And(
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("b"))),
+
+
+
+
+ 1
+
+
+
expr = distribute_not_over_and
+
+
+
+
+ 1
+
+
+
.apply(&expr, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("a"))
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("b"))
+
+
+
+
+ 1
+
+
+
fn rule_distribute_not_over_or() {
+
+
+
+
+ 1
+
+
+
let distribute_not_over_or = get_rule_by_name("distribute_not_over_or").unwrap();
+
+
+
+
+ 1
+
+
+
let mut expr = Expression::Not(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Or(
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("b"))),
+
+
+
+
+ 1
+
+
+
expr = distribute_not_over_or
+
+
+
+
+ 1
+
+
+
.apply(&expr, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("a"))
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("b"))
+
+
+
+
+ 1
+
+
+
fn rule_distribute_not_over_and_not_changed() {
+
+
+
+
+ 1
+
+
+
let distribute_not_over_and = get_rule_by_name("distribute_not_over_and").unwrap();
+
+
+
+
+ 1
+
+
+
let expr = Expression::Not(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("a")),
+
+
+
+
+ 1
+
+
+
let result = distribute_not_over_and.apply(&expr, &Model::new_empty(Default::default()));
+
+
+
+
+ 1
+
+
+
assert!(result.is_err());
+
+
+
+
+ 1
+
+
+
fn rule_distribute_not_over_or_not_changed() {
+
+
+
+
+ 1
+
+
+
let distribute_not_over_or = get_rule_by_name("distribute_not_over_or").unwrap();
+
+
+
+
+ 1
+
+
+
let expr = Expression::Not(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("a")),
+
+
+
+
+ 1
+
+
+
let result = distribute_not_over_or.apply(&expr, &Model::new_empty(Default::default()));
+
+
+
+
+ 1
+
+
+
assert!(result.is_err());
+
+
+
+
+ 1
+
+
+
fn rule_distribute_or_over_and() {
+
+
+
+
+ 1
+
+
+
let distribute_or_over_and = get_rule_by_name("distribute_or_over_and").unwrap();
+
+
+
+
+ 1
+
+
+
let expr = Expression::Or(
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::MachineName(1)),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::MachineName(2)),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::MachineName(3)),
+
+
+
+
+ 1
+
+
+
let red = distribute_or_over_and
+
+
+
+
+ 1
+
+
+
.apply(&expr, &Model::new_empty(Default::default()))
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::MachineName(3)),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::MachineName(1)),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::MachineName(3)),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::MachineName(2)),
+
+
+
+
+
+
+
+
// fn rule_ensure_div() {
+
+
+
+
+
+
+
+
// let ensure_div = get_rule_by_name("ensure_div").unwrap();
+
+
+
+
+
+
+
+
// let expr = Expression::Div(
+
+
+
+
+
+
+
+
// Box::new(Expression::Reference(
+
+
+
+
+
+
+
+
// Name::UserName("a".to_string()),
+
+
+
+
+
+
+
+
// Box::new(Expression::Reference(
+
+
+
+
+
+
+
+
// Name::UserName("b".to_string()),
+
+
+
+
+
+
+
+
// let red = ensure_div.apply(&expr, &Model::new_empty(Default::default())).unwrap();
+
+
+
+
+
+
+
+
// red.new_expression,
+
+
+
+
+
+
+
+
// Expression::SafeDiv(
+
+
+
+
+
+
+
+
// Box::new(Expression::Reference(
+
+
+
+
+
+
+
+
// Name::UserName("a".to_string())
+
+
+
+
+
+
+
+
// Box::new(Expression::Reference(
+
+
+
+
+
+
+
+
// Name::UserName("b".to_string())
+
+
+
+
+
+
+
+
// Box::new(Expression::Reference(
+
+
+
+
+
+
+
+
// Name::UserName("b".to_string())
+
+
+
+
+
+
+
+
// Box::new(Expression::Constant(Metadata::new(), Constant::Int(0)))
+
+
+
+
+
+
+
+
/// Reduce and solve:
+
+
+
+
+
+
+
+
/// find a,b,c : int(1..3)
+
+
+
+
+
+
+
+
/// such that a + b + c = 4
+
+
+
+
+
+
+
+
/// This test uses the rewrite function to simplify the expression instead
+
+
+
+
+
+
+
+
/// of applying the rules manually.
+
+
+
+
+ 1
+
+
+
fn rewrite_solve_xyz() {
+
+
+
+
+ 1
+
+
+
println!("Rules: {:?}", get_rules());
+
+
+
+
+ 1
+
+
+
let rule_sets = match resolve_rule_sets(SolverFamily::Minion, &vec!["Constant"]) {
+
+
+
+
+
+
+
+
eprintln!("Error resolving rule sets: {}", e);
+
+
+
+
+ 1
+
+
+
println!("Rule sets: {:?}", rule_sets);
+
+
+
+
+ 1
+
+
+
// Create variables and domains
+
+
+
+
+ 1
+
+
+
let variable_a = Name::UserName(String::from("a"));
+
+
+
+
+ 1
+
+
+
let variable_b = Name::UserName(String::from("b"));
+
+
+
+
+ 1
+
+
+
let variable_c = Name::UserName(String::from("c"));
+
+
+
+
+ 1
+
+
+
let domain = Domain::IntDomain(vec![Range::Bounded(1, 3)]);
+
+
+
+
+ 1
+
+
+
// Construct nested expression
+
+
+
+
+ 1
+
+
+
let nested_expr = Expression::And(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), variable_a.clone()),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), variable_b.clone()),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), variable_c.clone()),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Int(4))),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(Metadata::new(), variable_a.clone())),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(Metadata::new(), variable_b.clone())),
+
+
+
+
+ 1
+
+
+
let rule_sets = match resolve_rule_sets(SolverFamily::Minion, &vec!["Constant"]) {
+
+
+
+
+
+
+
+
eprintln!("Error resolving rule sets: {}", e);
+
+
+
+
+
+
+
+
// Apply rewrite function to the nested expression
+
+
+
+
+ 1
+
+
+
let rewritten_expr = rewrite_model(
+
+
+
+
+ 1
+
+
+
&Model::new(HashMap::new(), nested_expr, Default::default()),
+
+
+
+
+ 1
+
+
+
// Check if the expression is in its simplest form
+
+
+
+
+ 1
+
+
+
let expr = rewritten_expr.clone();
+
+
+
+
+ 1
+
+
+
assert!(is_simple(&expr));
+
+
+
+
+
+
+
+
// Create model with variables and constraints
+
+
+
+
+ 1
+
+
+
let mut model = Model::new(HashMap::new(), rewritten_expr, Default::default());
+
+
+
+
+ 1
+
+
+
// Insert variables and domains
+
+
+
+
+ 1
+
+
+
model.variables.insert(
+
+
+
+
+ 1
+
+
+
domain: domain.clone(),
+
+
+
+
+ 1
+
+
+
model.variables.insert(
+
+
+
+
+ 1
+
+
+
domain: domain.clone(),
+
+
+
+
+ 1
+
+
+
model.variables.insert(
+
+
+
+
+ 1
+
+
+
domain: domain.clone(),
+
+
+
+
+ 1
+
+
+
let solver: Solver<adaptors::Minion> = Solver::new(adaptors::Minion::new());
+
+
+
+
+ 1
+
+
+
let solver = solver.load_model(model).unwrap();
+
+
+
+
+ 1
+
+
+
solver.solve(Box::new(|_| true)).unwrap();
+
+
+
+
+
+
+
+
struct RuleResult<'a> {
+
+
+
+
+
+
+
+
new_expression: Expression,
+
+
+
+
+
+
+
+
/// - True if `expression` is in its simplest form.
+
+
+
+
+
+
+
+
/// - False otherwise.
+
+
+
+
+ 1
+
+
+
pub fn is_simple(expression: &Expression) -> bool {
+
+
+
+
+ 1
+
+
+
let rules = get_rules();
+
+
+
+
+ 1
+
+
+
let mut new = expression.clone();
+
+
+
+
+ 1
+
+
+
while let Some(step) = is_simple_iteration(&new, &rules) {
+
+
+
+
+
+
+
+
/// - Some(<new_expression>) after applying the first applicable rule to `expr` or a sub-expression.
+
+
+
+
+
+
+
+
/// - None if no rule is applicable to the expression or any sub-expression.
+
+
+
+
+ 15
+
+
+
fn is_simple_iteration<'a>(
+
+
+
+
+ 15
+
+
+
expression: &'a Expression,
+
+
+
+
+ 15
+
+
+
rules: &'a Vec<&'a Rule<'a>>,
+
+
+
+
+ 15
+
+
+
) -> Option<Expression> {
+
+
+
+
+ 15
+
+
+
let rule_results = apply_all_rules(expression, rules);
+
+
+
+
+ 15
+
+
+
if let Some(new) = choose_rewrite(&rule_results) {
+
+
+
+
+ 15
+
+
+
let mut sub = expression.children();
+
+
+
+
+ 15
+
+
+
for i in 0..sub.len() {
+
+
+
+
+ 14
+
+
+
if let Some(new) = is_simple_iteration(&sub[i], rules) {
+
+
+
+
+
+
+
+
if let Ok(res) = expression.with_children(sub.clone()) {
+
+
+
+
+ 15
+
+
+
None // No rules applicable to this branch of the expression
+
+
+
+
+
+
+
+
/// - A list of RuleResults after applying all rules to `expression`.
+
+
+
+
+
+
+
+
/// - An empty list if no rules are applicable.
+
+
+
+
+ 15
+
+
+
fn apply_all_rules<'a>(
+
+
+
+
+ 15
+
+
+
expression: &'a Expression,
+
+
+
+
+ 15
+
+
+
rules: &'a Vec<&'a Rule<'a>>,
+
+
+
+
+ 15
+
+
+
) -> Vec<RuleResult<'a>> {
+
+
+
+
+ 15
+
+
+
let mut results = Vec::new();
+
+
+
+
+ 420
+
+
+
match rule.apply(expression, &Model::new_empty(Default::default())) {
+
+
+
+
+
+
+
+
results.push(RuleResult {
+
+
+
+
+
+
+
+
new_expression: red.new_expression,
+
+
+
+
+
+
+
+
/// - Some(<new_expression>) after applying the first rule in `results`.
+
+
+
+
+
+
+
+
/// - None if `results` is empty.
+
+
+
+
+ 15
+
+
+
fn choose_rewrite(results: &[RuleResult]) -> Option<Expression> {
+
+
+
+
+ 15
+
+
+
if results.is_empty() {
+
+
+
+
+
+
+
+
// Return the first result for now
+
+
+
+
+
+
+
+
// println!("Applying rule: {:?}", results[0].rule);
+
+
+
+
+
+
+
+
Some(results[0].new_expression.clone())
+
+
+
+
+ 1
+
+
+
fn eval_const_int() {
+
+
+
+
+ 1
+
+
+
let expr = Expression::Constant(Metadata::new(), Constant::Int(1));
+
+
+
+
+ 1
+
+
+
let result = eval_constant(&expr);
+
+
+
+
+ 1
+
+
+
assert_eq!(result, Some(Constant::Int(1)));
+
+
+
+
+ 1
+
+
+
fn eval_const_bool() {
+
+
+
+
+ 1
+
+
+
let expr = Expression::Constant(Metadata::new(), Constant::Bool(true));
+
+
+
+
+ 1
+
+
+
let result = eval_constant(&expr);
+
+
+
+
+ 1
+
+
+
assert_eq!(result, Some(Constant::Bool(true)));
+
+
+
+
+ 1
+
+
+
fn eval_const_and() {
+
+
+
+
+ 1
+
+
+
let expr = Expression::And(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false)),
+
+
+
+
+ 1
+
+
+
let result = eval_constant(&expr);
+
+
+
+
+ 1
+
+
+
assert_eq!(result, Some(Constant::Bool(false)));
+
+
+
+
+ 1
+
+
+
fn eval_const_ref() {
+
+
+
+
+ 1
+
+
+
let expr = Expression::Reference(Metadata::new(), Name::UserName(String::from("a")));
+
+
+
+
+ 1
+
+
+
let result = eval_constant(&expr);
+
+
+
+
+ 1
+
+
+
assert_eq!(result, None);
+
+
+
+
+ 1
+
+
+
fn eval_const_nested_ref() {
+
+
+
+
+ 1
+
+
+
let expr = Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(1)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("a"))),
+
+
+
+
+ 1
+
+
+
let result = eval_constant(&expr);
+
+
+
+
+ 1
+
+
+
assert_eq!(result, None);
+
+
+
+
+ 1
+
+
+
fn eval_const_eq_int() {
+
+
+
+
+ 1
+
+
+
let expr = Expression::Eq(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Int(1))),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Int(1))),
+
+
+
+
+ 1
+
+
+
let result = eval_constant(&expr);
+
+
+
+
+ 1
+
+
+
assert_eq!(result, Some(Constant::Bool(true)));
+
+
+
+
+ 1
+
+
+
fn eval_const_eq_bool() {
+
+
+
+
+ 1
+
+
+
let expr = Expression::Eq(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Bool(true))),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Bool(true))),
+
+
+
+
+ 1
+
+
+
let result = eval_constant(&expr);
+
+
+
+
+ 1
+
+
+
assert_eq!(result, Some(Constant::Bool(true)));
+
+
+
+
+ 1
+
+
+
fn eval_const_eq_mixed() {
+
+
+
+
+ 1
+
+
+
let expr = Expression::Eq(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Int(1))),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Bool(true))),
+
+
+
+
+ 1
+
+
+
let result = eval_constant(&expr);
+
+
+
+
+ 1
+
+
+
assert_eq!(result, None);
+
+
+
+
+ 1
+
+
+
fn eval_const_sum_mixed() {
+
+
+
+
+ 1
+
+
+
let expr = Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Int(1)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(true)),
+
+
+
+
+ 1
+
+
+
let result = eval_constant(&expr);
+
+
+
+
+ 1
+
+
+
assert_eq!(result, None);
+
+
+
+
+ 1
+
+
+
fn eval_const_sum_xyz() {
+
+
+
+
+ 1
+
+
+
let expr = Expression::And(
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Sum(
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("x"))),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("y"))),
+
+
+
+
+ 1
+
+
+
Expression::Reference(Metadata::new(), Name::UserName(String::from("z"))),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Constant(Metadata::new(), Constant::Int(4))),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("x")),
+
+
+
+
+ 1
+
+
+
Box::new(Expression::Reference(
+
+
+
+
+ 1
+
+
+
Name::UserName(String::from("y")),
+
+
+
+
+ 1
+
+
+
let result = eval_constant(&expr);
+
+
+
+
+ 1
+
+
+
assert_eq!(result, None);
+
+
+
+
+ 1
+
+
+
let expr = Expression::Or(
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false)),
+
+
+
+
+ 1
+
+
+
Expression::Constant(Metadata::new(), Constant::Bool(false)),
+
+
+
+
+ 1
+
+
+
let result = eval_constant(&expr);
+
+
+
+
+ 1
+
+
+
assert_eq!(result, Some(Constant::Bool(false)));
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/coverage.json b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/coverage.json
new file mode 100644
index 000000000..441a89966
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/coverage.json
@@ -0,0 +1 @@
+{"schemaVersion":1,"label":"coverage","message":"72.59%","color":"red"}
\ No newline at end of file
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/constants.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/constants.rs.html
new file mode 100644
index 000000000..53db4f7d0
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/constants.rs.html
@@ -0,0 +1,665 @@
+
+
+
+
+ Grcov report - constants.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 13.16 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::fmt::{Display, Formatter};
+
+
+
+
+
+
+
+
use serde::{Deserialize, Serialize};
+
+
+
+
+ 552
+
+
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+
+
+
+
+
+
+
+
impl TryFrom<Constant> for i32 {
+
+
+
+
+
+
+
+
type Error = &'static str;
+
+
+
+
+ 1110
+
+
+
fn try_from(value: Constant) -> Result<Self, Self::Error> {
+
+
+
+
+ 1065
+
+
+
Constant::Int(i) => Ok(i),
+
+
+
+
+ 45
+
+
+
_ => Err("Cannot convert non-i32 Constant to i32"),
+
+
+
+
+
+
+
+
impl TryFrom<Constant> for bool {
+
+
+
+
+
+
+
+
type Error = &'static str;
+
+
+
+
+ 120
+
+
+
fn try_from(value: Constant) -> Result<Self, Self::Error> {
+
+
+
+
+ 105
+
+
+
Constant::Bool(b) => Ok(b),
+
+
+
+
+ 15
+
+
+
_ => Err("Cannot convert non-bool Constant to bool"),
+
+
+
+
+
+
+
+
impl Display for Constant {
+
+
+
+
+
+
+
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+
+
+
+
+
+
+
+
Constant::Int(i) => write!(f, "Int({})", i),
+
+
+
+
+
+
+
+
Constant::Bool(b) => write!(f, "Bool({})", b),
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/domains.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/domains.rs.html
new file mode 100644
index 000000000..f772a32a9
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/domains.rs.html
@@ -0,0 +1,1353 @@
+
+
+
+
+ Grcov report - domains.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 11.69 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use serde::{Deserialize, Serialize};
+
+
+
+
+ 372
+
+
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+
+
+
+
+ 328
+
+
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+
+
+
+
+
+
+
+
IntDomain(Vec<Range<i32>>),
+
+
+
+
+
+
+
+
/// Returns the minimum i32 value a variable of the domain can take, if it is an i32 domain.
+
+
+
+
+
+
+
+
pub fn min_i32(&self) -> Option<i32> {
+
+
+
+
+
+
+
+
Domain::BoolDomain => Some(0),
+
+
+
+
+
+
+
+
Domain::IntDomain(ranges) => {
+
+
+
+
+
+
+
+
if ranges.is_empty() {
+
+
+
+
+
+
+
+
let mut min = i32::MAX;
+
+
+
+
+
+
+
+
Range::Single(i) => min = min.min(*i),
+
+
+
+
+
+
+
+
Range::Bounded(i, _) => min = min.min(*i),
+
+
+
+
+
+
+
+
/// Returns the maximum i32 value a variable of the domain can take, if it is an i32 domain.
+
+
+
+
+
+
+
+
pub fn max_i32(&self) -> Option<i32> {
+
+
+
+
+
+
+
+
Domain::BoolDomain => Some(1),
+
+
+
+
+
+
+
+
Domain::IntDomain(ranges) => {
+
+
+
+
+
+
+
+
if ranges.is_empty() {
+
+
+
+
+
+
+
+
let mut max = i32::MIN;
+
+
+
+
+
+
+
+
Range::Single(i) => max = max.max(*i),
+
+
+
+
+
+
+
+
Range::Bounded(_, i) => max = max.max(*i),
+
+
+
+
+
+
+
+
/// Returns the minimum and maximum integer values a variable of the domain can take, if it is an integer domain.
+
+
+
+
+ 150
+
+
+
pub fn min_max_i32(&self) -> Option<(i32, i32)> {
+
+
+
+
+
+
+
+
Domain::BoolDomain => Some((0, 1)),
+
+
+
+
+ 150
+
+
+
Domain::IntDomain(ranges) => {
+
+
+
+
+ 150
+
+
+
if ranges.is_empty() {
+
+
+
+
+ 150
+
+
+
let mut min = i32::MAX;
+
+
+
+
+ 150
+
+
+
let mut max = i32::MIN;
+
+
+
+
+
+
+
+
Range::Single(i) => {
+
+
+
+
+ 150
+
+
+
Range::Bounded(i, j) => {
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/expressions.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/expressions.rs.html
new file mode 100644
index 000000000..4e4c0fb51
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/expressions.rs.html
@@ -0,0 +1,3513 @@
+
+
+
+
+ Grcov report - expressions.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 11.24 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::fmt::{Display, Formatter};
+
+
+
+
+
+
+
+
use derive_is_enum_variant::is_enum_variant;
+
+
+
+
+
+
+
+
use serde::{Deserialize, Serialize};
+
+
+
+
+
+
+
+
use enum_compatability_macro::document_compatibility;
+
+
+
+
+
+
+
+
use uniplate::uniplate::Uniplate;
+
+
+
+
+
+
+
+
use uniplate_derive::Uniplate;
+
+
+
+
+
+
+
+
use crate::ast::constants::Constant;
+
+
+
+
+
+
+
+
use crate::ast::symbol_table::{Name, SymbolTable};
+
+
+
+
+
+
+
+
use crate::metadata::Metadata;
+
+
+
+
+
+
+
+
#[document_compatibility]
+
+
+
+
+ 73920
+
+
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, is_enum_variant, Uniplate)]
+
+
+
+
+
+
+
+
pub enum Expression {
+
+
+
+
+
+
+
+
* Represents an empty expression
+
+
+
+
+
+
+
+
* NB: we only expect this at the top level of a model (if there is no constraints)
+
+
+
+
+
+
+
+
#[compatible(Minion, JsonInput)]
+
+
+
+
+
+
+
+
Constant(Metadata, Constant),
+
+
+
+
+
+
+
+
#[compatible(Minion, JsonInput, SAT)]
+
+
+
+
+
+
+
+
Reference(Metadata, Name),
+
+
+
+
+
+
+
+
#[compatible(Minion, JsonInput)]
+
+
+
+
+
+
+
+
Sum(Metadata, Vec<Expression>),
+
+
+
+
+
+
+
+
// /// Division after preventing division by zero, usually with a top-level constraint
+
+
+
+
+
+
+
+
// #[compatible(Minion)]
+
+
+
+
+
+
+
+
// SafeDiv(Metadata, Box<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
// /// Division with a possibly undefined value (division by 0)
+
+
+
+
+
+
+
+
// #[compatible(Minion, JsonInput)]
+
+
+
+
+
+
+
+
// Div(Metadata, Box<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
#[compatible(JsonInput)]
+
+
+
+
+
+
+
+
Min(Metadata, Vec<Expression>),
+
+
+
+
+
+
+
+
#[compatible(JsonInput, SAT)]
+
+
+
+
+
+
+
+
Not(Metadata, Box<Expression>),
+
+
+
+
+
+
+
+
#[compatible(JsonInput, SAT)]
+
+
+
+
+
+
+
+
Or(Metadata, Vec<Expression>),
+
+
+
+
+
+
+
+
#[compatible(JsonInput, SAT)]
+
+
+
+
+
+
+
+
And(Metadata, Vec<Expression>),
+
+
+
+
+
+
+
+
#[compatible(JsonInput)]
+
+
+
+
+
+
+
+
Eq(Metadata, Box<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
#[compatible(JsonInput)]
+
+
+
+
+
+
+
+
Neq(Metadata, Box<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
#[compatible(JsonInput)]
+
+
+
+
+
+
+
+
Geq(Metadata, Box<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
#[compatible(JsonInput)]
+
+
+
+
+
+
+
+
Leq(Metadata, Box<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
#[compatible(JsonInput)]
+
+
+
+
+
+
+
+
Gt(Metadata, Box<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
#[compatible(JsonInput)]
+
+
+
+
+
+
+
+
Lt(Metadata, Box<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
* Note: this is an intermediary step that's used in the process of converting from conjure model to minion.
+
+
+
+
+
+
+
+
* This is NOT a valid expression in either Essence or minion.
+
+
+
+
+
+
+
+
* ToDo: This is a stop gap solution. Eventually it may be better to have multiple constraints instead? (gs248)
+
+
+
+
+
+
+
+
SumEq(Metadata, Vec<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
// Flattened Constraints
+
+
+
+
+
+
+
+
#[compatible(Minion)]
+
+
+
+
+
+
+
+
SumGeq(Metadata, Vec<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
#[compatible(Minion)]
+
+
+
+
+
+
+
+
SumLeq(Metadata, Vec<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
#[compatible(Minion)]
+
+
+
+
+
+
+
+
Ineq(Metadata, Box<Expression>, Box<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
// #[compatible(Minion)]
+
+
+
+
+
+
+
+
// DivEq(Metadata, Box<Expression>, Box<Expression>, Box<Expression>),
+
+
+
+
+
+
+
+
#[compatible(Minion)]
+
+
+
+
+
+
+
+
AllDiff(Metadata, Vec<Expression>),
+
+
+
+
+ 225
+
+
+
pub fn bounds(&self, vars: &SymbolTable) -> Option<(i32, i32)> {
+
+
+
+
+ 150
+
+
+
Expression::Reference(_, name) => vars.get(name).and_then(|v| v.domain.min_max_i32()),
+
+
+
+
+
+
+
+
Expression::Constant(_, Constant::Int(i)) => Some((*i, *i)),
+
+
+
+
+
+
+
+
Expression::Sum(_, exprs) => {
+
+
+
+
+
+
+
+
if exprs.is_empty() {
+
+
+
+
+
+
+
+
let (mut min, mut max) = (0, 0);
+
+
+
+
+
+
+
+
if let Some((e_min, e_max)) = e.bounds(vars) {
+
+
+
+
+ 75
+
+
+
Expression::Min(_, exprs) => {
+
+
+
+
+ 75
+
+
+
if exprs.is_empty() {
+
+
+
+
+ 150
+
+
+
.map(|e| e.bounds(vars))
+
+
+
+
+ 75
+
+
+
.collect::<Option<Vec<(i32, i32)>>>()?;
+
+
+
+
+ 150
+
+
+
bounds.iter().map(|(min, _)| *min).min()?,
+
+
+
+
+ 150
+
+
+
bounds.iter().map(|(_, max)| *max).min()?,
+
+
+
+
+
+
+
+
fn display_expressions(expressions: &[Expression]) -> String {
+
+
+
+
+
+
+
+
if expressions.len() <= 3 {
+
+
+
+
+
+
+
+
.map(|e| e.to_string())
+
+
+
+
+
+
+
+
.collect::<Vec<String>>()
+
+
+
+
+
+
+
+
expressions[expressions.len() - 1]
+
+
+
+
+
+
+
+
impl Display for Expression {
+
+
+
+
+
+
+
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+
+
+
+
+
+
+
+
Expression::Constant(metadata, c) => write!(f, "Constant({}, {})", metadata, c),
+
+
+
+
+
+
+
+
Expression::Reference(metadata, name) => write!(f, "Reference({}, {})", metadata, name),
+
+
+
+
+
+
+
+
Expression::Nothing => write!(f, "Nothing"),
+
+
+
+
+
+
+
+
Expression::Sum(metadata, expressions) => {
+
+
+
+
+
+
+
+
write!(f, "Sum({}, {})", metadata, display_expressions(expressions))
+
+
+
+
+
+
+
+
Expression::Not(metadata, expr_box) => {
+
+
+
+
+
+
+
+
write!(f, "Not({}, {})", metadata, expr_box.clone())
+
+
+
+
+
+
+
+
Expression::Or(metadata, expressions) => {
+
+
+
+
+
+
+
+
write!(f, "Not({}, {})", metadata, display_expressions(expressions))
+
+
+
+
+
+
+
+
Expression::And(metadata, expressions) => {
+
+
+
+
+
+
+
+
write!(f, "And({}, {})", metadata, display_expressions(expressions))
+
+
+
+
+
+
+
+
Expression::Eq(metadata, box1, box2) => {
+
+
+
+
+
+
+
+
write!(f, "Eq({}, {}, {})", metadata, box1.clone(), box2.clone())
+
+
+
+
+
+
+
+
Expression::Neq(metadata, box1, box2) => {
+
+
+
+
+
+
+
+
write!(f, "Neq({}, {}, {})", metadata, box1.clone(), box2.clone())
+
+
+
+
+
+
+
+
Expression::Geq(metadata, box1, box2) => {
+
+
+
+
+
+
+
+
write!(f, "Geq({}, {}, {})", metadata, box1.clone(), box2.clone())
+
+
+
+
+
+
+
+
Expression::Leq(metadata, box1, box2) => {
+
+
+
+
+
+
+
+
write!(f, "Leq({}, {}, {})", metadata, box1.clone(), box2.clone())
+
+
+
+
+
+
+
+
Expression::Gt(metadata, box1, box2) => {
+
+
+
+
+
+
+
+
write!(f, "Gt({}, {}, {})", metadata, box1.clone(), box2.clone())
+
+
+
+
+
+
+
+
Expression::Lt(metadata, box1, box2) => {
+
+
+
+
+
+
+
+
write!(f, "Lt({}, {}, {})", metadata, box1.clone(), box2.clone())
+
+
+
+
+
+
+
+
Expression::SumGeq(metadata, box1, box2) => {
+
+
+
+
+
+
+
+
"SumGeq({}, {}. {})",
+
+
+
+
+
+
+
+
display_expressions(box1),
+
+
+
+
+
+
+
+
Expression::SumLeq(metadata, box1, box2) => {
+
+
+
+
+
+
+
+
"SumLeq({}, {}, {})",
+
+
+
+
+
+
+
+
display_expressions(box1),
+
+
+
+
+
+
+
+
Expression::Ineq(metadata, box1, box2, box3) => write!(
+
+
+
+
+
+
+
+
"Ineq({}, {}, {}, {})",
+
+
+
+
+
+
+
+
#[allow(unreachable_patterns)]
+
+
+
+
+
+
+
+
_ => write!(f, "Expression::Unknown"),
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/index.html
new file mode 100644
index 000000000..a8c5a7131
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/index.html
@@ -0,0 +1,170 @@
+
+
+
+
+ Grcov report - crates/conjure_core/src/ast
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 11.47 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ constants.rs
+
+
+
+ 68.75%
+
+
+
+ 68.75%
+
+
+ 11 / 16
+
+
+ 13.16%
+ 5 / 38
+
+
+
+
+
+ domains.rs
+
+
+
+ 33.33%
+
+
+
+ 33.33%
+
+
+ 17 / 51
+
+
+ 11.69%
+ 9 / 77
+
+
+
+
+
+ expressions.rs
+
+
+
+ 13.54%
+
+
+
+ 13.54%
+
+
+ 13 / 96
+
+
+ 11.24%
+ 38 / 338
+
+
+
+
+
+ symbol_table.rs
+
+
+
+ 100%
+
+
+
+ 100%
+
+
+ 6 / 6
+
+
+ 12.5%
+ 4 / 32
+
+
+
+
+
+ variables.rs
+
+
+
+ 21.05%
+
+
+
+ 21.05%
+
+
+ 4 / 19
+
+
+ 10.53%
+ 4 / 38
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/symbol_table.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/symbol_table.rs.html
new file mode 100644
index 000000000..209c20338
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/symbol_table.rs.html
@@ -0,0 +1,409 @@
+
+
+
+
+ Grcov report - symbol_table.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::collections::HashMap;
+
+
+
+
+
+
+
+
use std::fmt::Display;
+
+
+
+
+
+
+
+
use serde::{Deserialize, Serialize};
+
+
+
+
+
+
+
+
use crate::ast::variables::DecisionVariable;
+
+
+
+
+ 1384
+
+
+
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
+
+
+
+
+
+
+
+
impl Display for Name {
+
+
+
+
+ 1425
+
+
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+
+
+
+
+ 1050
+
+
+
Name::UserName(s) => write!(f, "UserName({})", s),
+
+
+
+
+ 375
+
+
+
Name::MachineName(i) => write!(f, "MachineName({})", i),
+
+
+
+
+
+
+
+
pub type SymbolTable = HashMap<Name, DecisionVariable>;
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/variables.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/variables.rs.html
new file mode 100644
index 000000000..4e13a0673
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/ast/variables.rs.html
@@ -0,0 +1,665 @@
+
+
+
+
+ Grcov report - variables.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 10.53 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::fmt::Display;
+
+
+
+
+
+
+
+
use serde::{Deserialize, Serialize};
+
+
+
+
+
+
+
+
use crate::ast::domains::{Domain, Range};
+
+
+
+
+ 328
+
+
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+
+
+
+
+
+
+
+
pub struct DecisionVariable {
+
+
+
+
+
+
+
+
impl DecisionVariable {
+
+
+
+
+ 75
+
+
+
pub fn new(domain: Domain) -> DecisionVariable {
+
+
+
+
+ 75
+
+
+
DecisionVariable { domain }
+
+
+
+
+
+
+
+
impl Display for DecisionVariable {
+
+
+
+
+
+
+
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+
+
+
+
+
+
+
+
Domain::BoolDomain => write!(f, "bool"),
+
+
+
+
+
+
+
+
Domain::IntDomain(ranges) => {
+
+
+
+
+
+
+
+
let mut first = true;
+
+
+
+
+
+
+
+
Range::Single(i) => write!(f, "{}", i)?,
+
+
+
+
+
+
+
+
Range::Bounded(i, j) => write!(f, "{}..{}", i, j)?,
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/context.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/context.rs.html
new file mode 100644
index 000000000..2bf3997c5
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/context.rs.html
@@ -0,0 +1,1769 @@
+
+
+
+
+ Grcov report - context.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::fmt::{Debug, Formatter};
+
+
+
+
+
+
+
+
use std::sync::{Arc, RwLock};
+
+
+
+
+
+
+
+
use crate::rule_engine::{Rule, RuleSet};
+
+
+
+
+
+
+
+
use crate::solver::SolverFamily;
+
+
+
+
+
+
+
+
use crate::stats::Stats;
+
+
+
+
+
+
+
+
pub struct Context<'a> {
+
+
+
+
+
+
+
+
pub target_solver_family: Arc<RwLock<Option<SolverFamily>>>,
+
+
+
+
+
+
+
+
pub extra_rule_set_names: Arc<RwLock<Vec<&'a str>>>,
+
+
+
+
+
+
+
+
pub rules: Arc<RwLock<Vec<&'a Rule<'a>>>>,
+
+
+
+
+
+
+
+
pub rule_sets: Arc<RwLock<Vec<&'a RuleSet<'a>>>>,
+
+
+
+
+
+
+
+
impl<'a> Context<'a> {
+
+
+
+
+
+
+
+
target_solver_family: SolverFamily,
+
+
+
+
+
+
+
+
extra_rule_set_names: Vec<&'a str>,
+
+
+
+
+
+
+
+
rules: Vec<&'a Rule<'a>>,
+
+
+
+
+
+
+
+
rule_sets: Vec<&'a RuleSet<'a>>,
+
+
+
+
+
+
+
+
target_solver_family: Arc::new(RwLock::new(Some(target_solver_family))),
+
+
+
+
+
+
+
+
extra_rule_set_names: Arc::new(RwLock::new(extra_rule_set_names)),
+
+
+
+
+
+
+
+
rules: Arc::new(RwLock::new(rules)),
+
+
+
+
+
+
+
+
rule_sets: Arc::new(RwLock::new(rule_sets)),
+
+
+
+
+
+
+
+
stats: Default::default(),
+
+
+
+
+
+
+
+
impl Context<'static> {
+
+
+
+
+
+
+
+
target_solver_family: SolverFamily,
+
+
+
+
+
+
+
+
extra_rule_set_names: Vec<&'static str>,
+
+
+
+
+
+
+
+
rules: Vec<&'static Rule<'static>>,
+
+
+
+
+
+
+
+
rule_sets: Vec<&'static RuleSet<'static>>,
+
+
+
+
+
+
+
+
) -> Arc<RwLock<Context<'static>>> {
+
+
+
+
+
+
+
+
Arc::new(RwLock::new(Context::new(
+
+
+
+
+
+
+
+
target_solver_family,
+
+
+
+
+
+
+
+
extra_rule_set_names,
+
+
+
+
+
+
+
+
impl<'a> Debug for Context<'a> {
+
+
+
+
+
+
+
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+
+
+
+
+
+
+
+
let target_solver_family: Option<SolverFamily> = *self.target_solver_family.read().unwrap();
+
+
+
+
+
+
+
+
let extra_rule_set_names: Vec<&str> = self.extra_rule_set_names.read().unwrap().clone();
+
+
+
+
+
+
+
+
let rules: Vec<&str> = self.rules.read().unwrap().iter().map(|r| r.name).collect();
+
+
+
+
+
+
+
+
let rule_sets: Vec<&str> = self
+
+
+
+
+
+
+
+
\ttarget_solver_family: {:?}\n\
+
+
+
+
+
+
+
+
\textra_rule_set_names: {:?}\n\
+
+
+
+
+
+
+
+
target_solver_family, extra_rule_set_names, rules, rule_sets
+
+
+
+
+
+
+
+
impl<'a> Default for Context<'a> {
+
+
+
+
+ 7170
+
+
+
fn default() -> Self {
+
+
+
+
+ 7170
+
+
+
target_solver_family: Arc::new(RwLock::new(None)),
+
+
+
+
+ 7170
+
+
+
extra_rule_set_names: Arc::new(RwLock::new(Vec::new())),
+
+
+
+
+ 7170
+
+
+
rules: Arc::new(RwLock::new(Vec::new())),
+
+
+
+
+ 7170
+
+
+
rule_sets: Arc::new(RwLock::new(Vec::new())),
+
+
+
+
+ 7170
+
+
+
stats: Default::default(),
+
+
+
+
+
+
+
+
impl PartialEq for Context<'_> {
+
+
+
+
+
+
+
+
#[allow(clippy::unwrap_used)] // A poisoned RWLock is probably panic worthy
+
+
+
+
+
+
+
+
fn eq(&self, other: &Self) -> bool {
+
+
+
+
+
+
+
+
self.target_solver_family
+
+
+
+
+
+
+
+
.eq(&*other.target_solver_family.read().unwrap())
+
+
+
+
+
+
+
+
.extra_rule_set_names
+
+
+
+
+
+
+
+
.eq(&*other.extra_rule_set_names.read().unwrap())
+
+
+
+
+
+
+
+
&& self.rules.read().unwrap().eq(&*other.rules.read().unwrap())
+
+
+
+
+
+
+
+
.eq(&*other.rule_sets.read().unwrap())
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/error.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/error.rs.html
new file mode 100644
index 000000000..f61b1a85a
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/error.rs.html
@@ -0,0 +1,377 @@
+
+
+
+
+ Grcov report - error.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
//! Top-level error types for Conjure-Oxide.
+
+
+
+
+
+
+
+
use serde_json::Error as JsonError;
+
+
+
+
+
+
+
+
use thiserror::Error;
+
+
+
+
+
+
+
+
pub type Result<T> = std::result::Result<T, Error>;
+
+
+
+
+
+
+
+
#[derive(Debug, Error)]
+
+
+
+
+
+
+
+
#[error("JSON error: {0}")]
+
+
+
+
+
+
+
+
JSON(#[from] JsonError),
+
+
+
+
+
+
+
+
#[error("Error parsing model: {0}")]
+
+
+
+
+
+
+
+
#[error("{0} is not yet implemented.")]
+
+
+
+
+
+
+
+
NotImplemented(String),
+
+
+
+
+
+
+
+
#[error(transparent)]
+
+
+
+
+
+
+
+
Other(#[from] anyhow::Error),
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/index.html
new file mode 100644
index 000000000..833444ca8
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/index.html
@@ -0,0 +1,146 @@
+
+
+
+
+ Grcov report - crates/conjure_core/src
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 11.52 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ context.rs
+
+
+
+ 11.84%
+
+
+
+ 11.84%
+
+
+ 9 / 76
+
+
+ 4.76%
+ 1 / 21
+
+
+
+
+
+ error.rs
+
+
+
+ 0%
+
+
+
+ 0%
+
+
+ 0 / 1
+
+
+ 0%
+ 0 / 12
+
+
+
+
+
+ metadata.rs
+
+
+
+ 40%
+
+
+
+ 40%
+
+
+ 4 / 10
+
+
+ 9.76%
+ 4 / 41
+
+
+
+
+
+ model.rs
+
+
+
+ 77.42%
+
+
+
+ 77.42%
+
+
+ 48 / 62
+
+
+ 15.38%
+ 14 / 91
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/metadata.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/metadata.rs.html
new file mode 100644
index 000000000..96c46340c
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/metadata.rs.html
@@ -0,0 +1,665 @@
+
+
+
+
+ Grcov report - metadata.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::fmt::{Debug, Display};
+
+
+
+
+
+
+
+
use serde::{Deserialize, Serialize};
+
+
+
+
+ 2416
+
+
+
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
+
+
+
+
+
+
+
+
pub struct Metadata {
+
+
+
+
+
+
+
+
pub dirtyclean: bool,
+
+
+
+
+
+
+
+
impl Default for Metadata {
+
+
+
+
+
+
+
+
fn default() -> Self {
+
+
+
+
+ 6150
+
+
+
pub fn new() -> Metadata {
+
+
+
+
+ 6150
+
+
+
Metadata { dirtyclean: false }
+
+
+
+
+
+
+
+
impl Display for Metadata {
+
+
+
+
+
+
+
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+
+
+
+
+
+
+
+
write!(f, "Metadata")
+
+
+
+
+
+
+
+
// impl<T> Display for Metadata<T> where T: for<'a> MetadataKind<'a> {
+
+
+
+
+
+
+
+
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+
+
+
+
+
+
+
+
// write!(f, "Metadata")
+
+
+
+
+
+
+
+
// impl<T> Metadata<T> where T: for<'a> MetadataKind<'a> {
+
+
+
+
+
+
+
+
// fn new(a: T) -> Metadata<T> {
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/model.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/model.rs.html
new file mode 100644
index 000000000..fbec89391
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/model.rs.html
@@ -0,0 +1,1657 @@
+
+
+
+
+ Grcov report - model.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 15.38 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::cell::RefCell;
+
+
+
+
+
+
+
+
use std::sync::{Arc, RwLock};
+
+
+
+
+
+
+
+
use derivative::Derivative;
+
+
+
+
+
+
+
+
use serde::{Deserialize, Serialize};
+
+
+
+
+
+
+
+
use serde_with::serde_as;
+
+
+
+
+
+
+
+
use crate::ast::{DecisionVariable, Domain, Expression, Name, SymbolTable};
+
+
+
+
+
+
+
+
use crate::context::Context;
+
+
+
+
+
+
+
+
use crate::metadata::Metadata;
+
+
+
+
+ 558
+
+
+
#[derive(Derivative, Clone, Debug, Serialize, Deserialize)]
+
+
+
+
+
+
+
+
#[derivative(PartialEq, Eq)]
+
+
+
+
+
+
+
+
#[serde_as(as = "Vec<(_, _)>")]
+
+
+
+
+
+
+
+
pub variables: SymbolTable,
+
+
+
+
+
+
+
+
pub constraints: Expression,
+
+
+
+
+
+
+
+
#[derivative(PartialEq = "ignore")]
+
+
+
+
+
+
+
+
pub context: Arc<RwLock<Context<'static>>>,
+
+
+
+
+
+
+
+
next_var: RefCell<i32>,
+
+
+
+
+ 6900
+
+
+
variables: SymbolTable,
+
+
+
+
+ 6900
+
+
+
constraints: Expression,
+
+
+
+
+ 6900
+
+
+
context: Arc<RwLock<Context<'static>>>,
+
+
+
+
+ 6900
+
+
+
next_var: RefCell::new(0),
+
+
+
+
+ 6840
+
+
+
pub fn new_empty(context: Arc<RwLock<Context<'static>>>) -> Model {
+
+
+
+
+ 6840
+
+
+
Model::new(Default::default(), Expression::Nothing, context)
+
+
+
+
+
+
+
+
// Function to update a DecisionVariable based on its Name
+
+
+
+
+ 15
+
+
+
pub fn update_domain(&mut self, name: &Name, new_domain: Domain) {
+
+
+
+
+ 15
+
+
+
if let Some(decision_var) = self.variables.get_mut(name) {
+
+
+
+
+ 15
+
+
+
decision_var.domain = new_domain;
+
+
+
+
+
+
+
+
pub fn get_domain(&self, name: &Name) -> Option<&Domain> {
+
+
+
+
+
+
+
+
self.variables.get(name).map(|v| &v.domain)
+
+
+
+
+
+
+
+
// Function to add a new DecisionVariable to the Model
+
+
+
+
+ 375
+
+
+
pub fn add_variable(&mut self, name: Name, decision_var: DecisionVariable) {
+
+
+
+
+ 375
+
+
+
self.variables.insert(name, decision_var);
+
+
+
+
+ 360
+
+
+
pub fn get_constraints_vec(&self) -> Vec<Expression> {
+
+
+
+
+ 360
+
+
+
match &self.constraints {
+
+
+
+
+ 120
+
+
+
Expression::And(_, constraints) => constraints.clone(),
+
+
+
+
+ 180
+
+
+
Expression::Nothing => vec![],
+
+
+
+
+ 60
+
+
+
_ => vec![self.constraints.clone()],
+
+
+
+
+ 180
+
+
+
pub fn set_constraints(&mut self, constraints: Vec<Expression>) {
+
+
+
+
+ 180
+
+
+
if constraints.is_empty() {
+
+
+
+
+
+
+
+
self.constraints = Expression::Nothing;
+
+
+
+
+ 180
+
+
+
} else if constraints.len() == 1 {
+
+
+
+
+ 150
+
+
+
self.constraints = constraints[0].clone();
+
+
+
+
+ 30
+
+
+
self.constraints = Expression::And(Metadata::new(), constraints);
+
+
+
+
+
+
+
+
pub fn set_context(&mut self, context: Arc<RwLock<Context<'static>>>) {
+
+
+
+
+
+
+
+
self.context = context;
+
+
+
+
+
+
+
+
pub fn add_constraint(&mut self, expression: Expression) {
+
+
+
+
+
+
+
+
// ToDo (gs248) - there is no checking whatsoever
+
+
+
+
+
+
+
+
// We need to properly validate the expression but this is just for testing
+
+
+
+
+
+
+
+
let mut constraints = self.get_constraints_vec();
+
+
+
+
+
+
+
+
constraints.push(expression);
+
+
+
+
+
+
+
+
self.set_constraints(constraints);
+
+
+
+
+ 180
+
+
+
pub fn add_constraints(&mut self, expressions: Vec<Expression>) {
+
+
+
+
+ 180
+
+
+
let mut constraints = self.get_constraints_vec();
+
+
+
+
+ 180
+
+
+
constraints.extend(expressions);
+
+
+
+
+ 180
+
+
+
self.set_constraints(constraints);
+
+
+
+
+
+
+
+
/// Returns an arbitrary variable name that is not in the model.
+
+
+
+
+ 75
+
+
+
pub fn gensym(&self) -> Name {
+
+
+
+
+ 75
+
+
+
let num = *self.next_var.borrow();
+
+
+
+
+ 75
+
+
+
*(self.next_var.borrow_mut()) += 1;
+
+
+
+
+ 75
+
+
+
Name::MachineName(num) // incremented when inserted
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/parse/example_models.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/parse/example_models.rs.html
new file mode 100644
index 000000000..8e0953488
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/parse/example_models.rs.html
@@ -0,0 +1,1769 @@
+
+
+
+
+ Grcov report - example_models.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 33.33 %
+
+
+
+
+
+
+
+
+
+
+
+
+
// example_models with get_example_model function
+
+
+
+
+
+
+
+
use std::path::PathBuf;
+
+
+
+
+
+
+
+
use project_root::get_project_root;
+
+
+
+
+
+
+
+
use walkdir::WalkDir;
+
+
+
+
+
+
+
+
use crate::parse::model_from_json;
+
+
+
+
+
+
+
+
/// Searches recursively in `../tests/integration` folder for an `.essence` file matching the given
+
+
+
+
+
+
+
+
/// filename, then uses conjure to process it into astjson, and returns the parsed model.
+
+
+
+
+
+
+
+
/// * `filename` - A string slice that holds filename without extension
+
+
+
+
+
+
+
+
/// Function returns a `Result<Value, anyhow::Error>`, where `Value` is the parsed model.
+
+
+
+
+ 45
+
+
+
pub fn get_example_model(filename: &str) -> Result<Model, anyhow::Error> {
+
+
+
+
+
+
+
+
// define relative path -> integration tests dir
+
+
+
+
+ 45
+
+
+
let base_dir = get_project_root()?;
+
+
+
+
+ 45
+
+
+
let mut essence_path = PathBuf::new();
+
+
+
+
+
+
+
+
// walk through directory tree recursively starting at base
+
+
+
+
+ 245325
+
+
+
for entry in WalkDir::new(base_dir).into_iter().filter_map(|e| e.ok()) {
+
+
+
+
+ 245325
+
+
+
let path = entry.path();
+
+
+
+
+ 207555
+
+
+
&& path.extension().map_or(false, |e| e == "essence")
+
+
+
+
+ 225
+
+
+
&& path.file_stem() == Some(std::ffi::OsStr::new(filename))
+
+
+
+
+ 30
+
+
+
essence_path = path.to_path_buf();
+
+
+
+
+
+
+
+
//println!("PATH TO FILE: {}", essence_path.display());
+
+
+
+
+
+
+
+
// return error if file not found
+
+
+
+
+ 45
+
+
+
if essence_path.as_os_str().is_empty() {
+
+
+
+
+ 15
+
+
+
return Err(anyhow::Error::new(std::io::Error::new(
+
+
+
+
+ 15
+
+
+
std::io::ErrorKind::NotFound,
+
+
+
+
+ 15
+
+
+
"ERROR: File not found in any subdirectory",
+
+
+
+
+ 30
+
+
+
// let path = PathBuf::from(format!("../tests/integration/basic/comprehension{}.essence", filename));
+
+
+
+
+ 30
+
+
+
let mut cmd = std::process::Command::new("conjure");
+
+
+
+
+ 30
+
+
+
.arg("--output-format=astjson")
+
+
+
+
+
+
+
+
// convert Conjure's stdout from bytes to string
+
+
+
+
+ 30
+
+
+
let astjson = String::from_utf8(output.stdout)?;
+
+
+
+
+
+
+
+
//println!("ASTJSON: {}", astjson);
+
+
+
+
+
+
+
+
// parse AST JSON from desired Model format
+
+
+
+
+ 30
+
+
+
let generated_mdl = model_from_json(&astjson, Default::default())?;
+
+
+
+
+
+
+
+
/// Searches for an `.essence` file at the given filepath,
+
+
+
+
+
+
+
+
/// then uses conjure to process it into astjson, and returns the parsed model.
+
+
+
+
+
+
+
+
/// * `filepath` - A string slice that holds the full file path
+
+
+
+
+
+
+
+
/// Function returns a `Result<Value, anyhow::Error>`, where `Value` is the parsed model
+
+
+
+
+ 30
+
+
+
pub fn get_example_model_by_path(filepath: &str) -> Result<Model, anyhow::Error> {
+
+
+
+
+ 30
+
+
+
let essence_path = PathBuf::from(filepath);
+
+
+
+
+ 30
+
+
+
// return error if file not found
+
+
+
+
+ 30
+
+
+
if essence_path.as_os_str().is_empty() {
+
+
+
+
+ 15
+
+
+
return Err(anyhow::Error::new(std::io::Error::new(
+
+
+
+
+ 15
+
+
+
std::io::ErrorKind::NotFound,
+
+
+
+
+ 15
+
+
+
"ERROR: File not found in any subdirectory",
+
+
+
+
+ 15
+
+
+
// println!("PATH TO FILE: {}", essence_path.display());
+
+
+
+
+ 15
+
+
+
// Command execution using 'conjure' CLI tool with provided path
+
+
+
+
+ 15
+
+
+
let mut cmd = std::process::Command::new("conjure");
+
+
+
+
+ 15
+
+
+
.arg("--output-format=astjson")
+
+
+
+
+
+
+
+
// convert Conjure's stdout from bytes to string
+
+
+
+
+ 15
+
+
+
let astjson = String::from_utf8(output.stdout)?;
+
+
+
+
+
+
+
+
// println!("ASTJSON: {}", astjson);
+
+
+
+
+
+
+
+
// parse AST JSON into the desired Model format
+
+
+
+
+ 15
+
+
+
let generated_model = model_from_json(&astjson, Default::default())?;
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/parse/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/parse/index.html
new file mode 100644
index 000000000..f6609351f
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/parse/index.html
@@ -0,0 +1,98 @@
+
+
+
+
+ Grcov report - crates/conjure_core/src/parse
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 28.33 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ example_models.rs
+
+
+
+ 100%
+
+
+
+ 100%
+
+
+ 53 / 53
+
+
+ 33.33%
+ 4 / 12
+
+
+
+
+
+ parse_model.rs
+
+
+
+ 84.34%
+
+
+
+ 84.34%
+
+
+ 210 / 249
+
+
+ 27.08%
+ 13 / 48
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/parse/parse_model.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/parse/parse_model.rs.html
new file mode 100644
index 000000000..9efc04df1
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/parse/parse_model.rs.html
@@ -0,0 +1,5065 @@
+
+
+
+
+ Grcov report - parse_model.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 27.08 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::collections::HashMap;
+
+
+
+
+
+
+
+
use std::sync::{Arc, RwLock};
+
+
+
+
+
+
+
+
use serde_json::Value;
+
+
+
+
+
+
+
+
use serde_json::Value as JsonValue;
+
+
+
+
+
+
+
+
use crate::ast::{Constant, DecisionVariable, Domain, Expression, Name, Range};
+
+
+
+
+
+
+
+
use crate::context::Context;
+
+
+
+
+
+
+
+
use crate::error::{Error, Result};
+
+
+
+
+
+
+
+
use crate::metadata::Metadata;
+
+
+
+
+ 180
+
+
+
pub fn model_from_json(str: &str, context: Arc<RwLock<Context<'static>>>) -> Result<Model> {
+
+
+
+
+ 180
+
+
+
let mut m = Model::new_empty(context);
+
+
+
+
+ 180
+
+
+
let v: JsonValue = serde_json::from_str(str)?;
+
+
+
+
+ 180
+
+
+
let statements = v["mStatements"]
+
+
+
+
+ 180
+
+
+
.ok_or(Error::Parse("mStatements is not an array".to_owned()))?;
+
+
+
+
+ 735
+
+
+
for statement in statements {
+
+
+
+
+ 555
+
+
+
let entry = statement
+
+
+
+
+ 555
+
+
+
.ok_or(Error::Parse("mStatements contains a non-object".to_owned()))?
+
+
+
+
+ 555
+
+
+
"mStatements contains an empty object".to_owned(),
+
+
+
+
+ 555
+
+
+
match entry.0.as_str() {
+
+
+
+
+ 375
+
+
+
let (name, var) = parse_variable(entry.1)?;
+
+
+
+
+ 375
+
+
+
m.add_variable(name, var);
+
+
+
+
+ 180
+
+
+
let constraints_arr = match entry.1.as_array() {
+
+
+
+
+
+
+
+
return Err(Error::Parse("SuchThat is not a vector".to_owned()));
+
+
+
+
+ 180
+
+
+
let constraints: Vec<Expression> =
+
+
+
+
+ 180
+
+
+
constraints_arr.iter().flat_map(parse_expression).collect();
+
+
+
+
+ 180
+
+
+
m.add_constraints(constraints);
+
+
+
+
+
+
+
+
// println!("Nb constraints {}", m.constraints.len());
+
+
+
+
+
+
+
+
otherwise => panic!("Unhandled Statement {:#?}", otherwise),
+
+
+
+
+ 375
+
+
+
fn parse_variable(v: &JsonValue) -> Result<(Name, DecisionVariable)> {
+
+
+
+
+ 375
+
+
+
.ok_or(Error::Parse("Declaration is not an object".to_owned()))?["FindOrGiven"]
+
+
+
+
+ 375
+
+
+
.ok_or(Error::Parse("FindOrGiven is not an array".to_owned()))?;
+
+
+
+
+ 375
+
+
+
.ok_or(Error::Parse("FindOrGiven[1] is not an object".to_owned()))?["Name"]
+
+
+
+
+ 375
+
+
+
"FindOrGiven[1].Name is not a string".to_owned(),
+
+
+
+
+ 375
+
+
+
let name = Name::UserName(name.to_owned());
+
+
+
+
+ 375
+
+
+
.ok_or(Error::Parse("FindOrGiven[2] is not an object".to_owned()))?
+
+
+
+
+ 375
+
+
+
.ok_or(Error::Parse("FindOrGiven[2] is an empty object".to_owned()))?;
+
+
+
+
+ 375
+
+
+
let domain = match domain.0.as_str() {
+
+
+
+
+ 375
+
+
+
"DomainInt" => Ok(parse_int_domain(domain.1)?),
+
+
+
+
+ 105
+
+
+
"DomainBool" => Ok(Domain::BoolDomain),
+
+
+
+
+
+
+
+
_ => Err(Error::Parse(
+
+
+
+
+
+
+
+
"FindOrGiven[2] is an unknown object".to_owned(),
+
+
+
+
+ 375
+
+
+
Ok((name, DecisionVariable { domain }))
+
+
+
+
+ 270
+
+
+
fn parse_int_domain(v: &JsonValue) -> Result<Domain> {
+
+
+
+
+ 270
+
+
+
let mut ranges = Vec::new();
+
+
+
+
+ 270
+
+
+
.ok_or(Error::Parse("DomainInt is not an array".to_owned()))?[1]
+
+
+
+
+ 270
+
+
+
.ok_or(Error::Parse("DomainInt[1] is not an array".to_owned()))?;
+
+
+
+
+ 270
+
+
+
"DomainInt[1] contains a non-object".to_owned(),
+
+
+
+
+ 270
+
+
+
"DomainInt[1] contains an empty object".to_owned(),
+
+
+
+
+ 270
+
+
+
match range.0.as_str() {
+
+
+
+
+ 270
+
+
+
.ok_or(Error::Parse("RangeBounded is not an array".to_owned()))?;
+
+
+
+
+ 270
+
+
+
let mut nums = Vec::new();
+
+
+
+
+ 540
+
+
+
for item in arr.iter() {
+
+
+
+
+ 540
+
+
+
let num = item["Constant"]["ConstantInt"][1]
+
+
+
+
+ 540
+
+
+
"Could not parse int domain constant".to_owned(),
+
+
+
+
+ 540
+
+
+
let num32 = i32::try_from(num).map_err(|_| {
+
+
+
+
+
+
+
+
Error::Parse("Could not parse int domain constant".to_owned())
+
+
+
+
+ 270
+
+
+
ranges.push(Range::Bounded(nums[0], nums[1]));
+
+
+
+
+
+
+
+
let num = &range.1["Constant"]["ConstantInt"][1]
+
+
+
+
+
+
+
+
"Could not parse int domain constant".to_owned(),
+
+
+
+
+
+
+
+
let num32 = i32::try_from(*num)
+
+
+
+
+
+
+
+
.map_err(|_| Error::Parse("Could not parse int domain constant".to_owned()))?;
+
+
+
+
+
+
+
+
ranges.push(Range::Single(num32));
+
+
+
+
+
+
+
+
return Err(Error::Parse(
+
+
+
+
+
+
+
+
"DomainInt[1] contains an unknown object".to_owned(),
+
+
+
+
+ 270
+
+
+
Ok(Domain::IntDomain(ranges))
+
+
+
+
+
+
+
+
// this needs an explicit type signature to force the closures to have the same type
+
+
+
+
+
+
+
+
type BinOp = Box<dyn Fn(Metadata, Box<Expression>, Box<Expression>) -> Expression>;
+
+
+
+
+
+
+
+
type UnaryOp = Box<dyn Fn(Metadata, Box<Expression>) -> Expression>;
+
+
+
+
+
+
+
+
type VecOp = Box<dyn Fn(Metadata, Vec<Expression>) -> Expression>;
+
+
+
+
+ 840
+
+
+
fn parse_expression(obj: &JsonValue) -> Option<Expression> {
+
+
+
+
+ 840
+
+
+
let binary_operators: HashMap<&str, BinOp> = [
+
+
+
+
+ 840
+
+
+
Box::new(Expression::Eq) as Box<dyn Fn(_, _, _) -> _>,
+
+
+
+
+ 840
+
+
+
Box::new(Expression::Neq) as Box<dyn Fn(_, _, _) -> _>,
+
+
+
+
+ 840
+
+
+
Box::new(Expression::Geq) as Box<dyn Fn(_, _, _) -> _>,
+
+
+
+
+ 840
+
+
+
Box::new(Expression::Leq) as Box<dyn Fn(_, _, _) -> _>,
+
+
+
+
+ 840
+
+
+
Box::new(Expression::Gt) as Box<dyn Fn(_, _, _) -> _>,
+
+
+
+
+ 840
+
+
+
Box::new(Expression::Lt) as Box<dyn Fn(_, _, _) -> _>,
+
+
+
+
+ 840
+
+
+
Box::new(Expression::Gt) as Box<dyn Fn(_, _, _) -> _>,
+
+
+
+
+ 840
+
+
+
Box::new(Expression::Lt) as Box<dyn Fn(_, _, _) -> _>,
+
+
+
+
+ 840
+
+
+
// Box::new(Expression::Div) as Box<dyn Fn(_, _, _) -> _>,
+
+
+
+
+ 840
+
+
+
let unary_operators: HashMap<&str, UnaryOp> = [(
+
+
+
+
+ 840
+
+
+
Box::new(Expression::Not) as Box<dyn Fn(_, _) -> _>,
+
+
+
+
+ 840
+
+
+
let vec_operators: HashMap<&str, VecOp> = [
+
+
+
+
+ 840
+
+
+
Box::new(Expression::Sum) as Box<dyn Fn(_, _) -> _>,
+
+
+
+
+ 840
+
+
+
Box::new(Expression::And) as Box<dyn Fn(_, _) -> _>,
+
+
+
+
+ 840
+
+
+
("MkOpOr", Box::new(Expression::Or) as Box<dyn Fn(_, _) -> _>),
+
+
+
+
+ 840
+
+
+
Box::new(Expression::Min) as Box<dyn Fn(_, _) -> _>,
+
+
+
+
+ 1500
+
+
+
let mut binary_operator_names = binary_operators.iter().map(|x| x.0);
+
+
+
+
+ 840
+
+
+
let mut unary_operator_names = unary_operators.iter().map(|x| x.0);
+
+
+
+
+ 840
+
+
+
let mut vec_operator_names = vec_operators.iter().map(|x| x.0);
+
+
+
+
+ 840
+
+
+
Value::Object(op) if op.contains_key("Op") => match &op["Op"] {
+
+
+
+
+ 1500
+
+
+
Value::Object(bin_op) if binary_operator_names.any(|key| bin_op.contains_key(*key)) => {
+
+
+
+
+ 180
+
+
+
parse_bin_op(bin_op, binary_operators)
+
+
+
+
+ 150
+
+
+
Value::Object(un_op) if unary_operator_names.any(|key| un_op.contains_key(*key)) => {
+
+
+
+
+
+
+
+
parse_unary_op(un_op, unary_operators)
+
+
+
+
+ 330
+
+
+
Value::Object(vec_op) if vec_operator_names.any(|key| vec_op.contains_key(*key)) => {
+
+
+
+
+ 150
+
+
+
parse_vec_op(vec_op, vec_operators)
+
+
+
+
+
+
+
+
otherwise => panic!("Unhandled Op {:#?}", otherwise),
+
+
+
+
+ 510
+
+
+
Value::Object(refe) if refe.contains_key("Reference") => {
+
+
+
+
+ 390
+
+
+
let name = refe["Reference"].as_array()?[0].as_object()?["Name"].as_str()?;
+
+
+
+
+ 390
+
+
+
Some(Expression::Reference(
+
+
+
+
+ 390
+
+
+
Name::UserName(name.to_string()),
+
+
+
+
+ 120
+
+
+
Value::Object(constant) if constant.contains_key("Constant") => parse_constant(constant),
+
+
+
+
+
+
+
+
otherwise => panic!("Unhandled Expression {:#?}", otherwise),
+
+
+
+
+ 180
+
+
+
bin_op: &serde_json::Map<String, Value>,
+
+
+
+
+ 180
+
+
+
binary_operators: HashMap<&str, BinOp>,
+
+
+
+
+ 180
+
+
+
) -> Option<Expression> {
+
+
+
+
+
+
+
+
// we know there is a single key value pair in this object
+
+
+
+
+
+
+
+
// extract the value, ignore the key
+
+
+
+
+ 180
+
+
+
let (key, value) = bin_op.into_iter().next()?;
+
+
+
+
+ 180
+
+
+
let constructor = binary_operators.get(key.as_str())?;
+
+
+
+
+ 180
+
+
+
Value::Array(bin_op_args) if bin_op_args.len() == 2 => {
+
+
+
+
+ 180
+
+
+
let arg1 = parse_expression(&bin_op_args[0])?;
+
+
+
+
+ 180
+
+
+
let arg2 = parse_expression(&bin_op_args[1])?;
+
+
+
+
+ 180
+
+
+
Some(constructor(Metadata::new(), Box::new(arg1), Box::new(arg2)))
+
+
+
+
+
+
+
+
otherwise => panic!("Unhandled parse_bin_op {:#?}", otherwise),
+
+
+
+
+
+
+
+
un_op: &serde_json::Map<String, Value>,
+
+
+
+
+
+
+
+
unary_operators: HashMap<&str, UnaryOp>,
+
+
+
+
+
+
+
+
) -> Option<Expression> {
+
+
+
+
+
+
+
+
let (key, value) = un_op.into_iter().next()?;
+
+
+
+
+
+
+
+
let constructor = unary_operators.get(key.as_str())?;
+
+
+
+
+
+
+
+
let arg = parse_expression(value)?;
+
+
+
+
+
+
+
+
Some(constructor(Metadata::new(), Box::new(arg)))
+
+
+
+
+ 150
+
+
+
vec_op: &serde_json::Map<String, Value>,
+
+
+
+
+ 150
+
+
+
vec_operators: HashMap<&str, VecOp>,
+
+
+
+
+ 150
+
+
+
) -> Option<Expression> {
+
+
+
+
+ 150
+
+
+
let (key, value) = vec_op.into_iter().next()?;
+
+
+
+
+ 150
+
+
+
let constructor = vec_operators.get(key.as_str())?;
+
+
+
+
+ 150
+
+
+
let args_parsed: Vec<Option<Expression>> = value["AbstractLiteral"]["AbsLitMatrix"][1]
+
+
+
+
+ 150
+
+
+
.map(parse_expression)
+
+
+
+
+ 150
+
+
+
let number_of_args = args_parsed.len();
+
+
+
+
+ 150
+
+
+
let valid_args: Vec<Expression> = args_parsed.into_iter().flatten().collect();
+
+
+
+
+ 150
+
+
+
if number_of_args != valid_args.len() {
+
+
+
+
+ 150
+
+
+
Some(constructor(Metadata::new(), valid_args))
+
+
+
+
+ 120
+
+
+
fn parse_constant(constant: &serde_json::Map<String, Value>) -> Option<Expression> {
+
+
+
+
+ 120
+
+
+
match &constant["Constant"] {
+
+
+
+
+ 120
+
+
+
Value::Object(int) if int.contains_key("ConstantInt") => {
+
+
+
+
+ 120
+
+
+
let int_32: i32 = match int["ConstantInt"].as_array()?[1].as_i64()?.try_into() {
+
+
+
+
+
+
+
+
"Could not convert integer constant to i32: {:#?}",
+
+
+
+
+ 120
+
+
+
Some(Expression::Constant(Metadata::new(), Constant::Int(int_32)))
+
+
+
+
+
+
+
+
otherwise => panic!("Unhandled parse_constant {:#?}", otherwise),
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/index.html
new file mode 100644
index 000000000..d1fe40e5e
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/index.html
@@ -0,0 +1,170 @@
+
+
+
+
+ Grcov report - crates/conjure_core/src/rule_engine
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 24.05 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ mod.rs
+
+
+
+ 87.95%
+
+
+
+ 87.95%
+
+
+ 73 / 83
+
+
+ 28.3%
+ 15 / 53
+
+
+
+
+
+ resolve_rules.rs
+
+
+
+ 87.14%
+
+
+
+ 87.14%
+
+
+ 61 / 70
+
+
+ 29.17%
+ 7 / 24
+
+
+
+
+
+ rewrite.rs
+
+
+
+ 85.71%
+
+
+
+ 85.71%
+
+
+ 54 / 63
+
+
+ 22.22%
+ 4 / 18
+
+
+
+
+
+ rule.rs
+
+
+
+ 50.75%
+
+
+
+ 50.75%
+
+
+ 34 / 67
+
+
+ 18.18%
+ 6 / 33
+
+
+
+
+
+ rule_set.rs
+
+
+
+ 59.18%
+
+
+
+ 59.18%
+
+
+ 58 / 98
+
+
+ 20%
+ 6 / 30
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/mod.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/mod.rs.html
new file mode 100644
index 000000000..9d727fbf5
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/mod.rs.html
@@ -0,0 +1,3657 @@
+
+
+
+
+ Grcov report - mod.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
pub use linkme::distributed_slice;
+
+
+
+
+
+
+
+
/// This procedural macro registers a decorated function with `conjure_rules`' global registry, and
+
+
+
+
+
+
+
+
/// adds the rule to one or more `RuleSet`'s.
+
+
+
+
+
+
+
+
/// It may be used in any downstream crate.
+
+
+
+
+
+
+
+
/// For more information on linker magic, see the [`linkme`](https://docs.rs/linkme/latest/linkme/) crate.
+
+
+
+
+
+
+
+
/// **IMPORTANT**: Since the resulting rule may not be explicitly referenced, it may be removed by the compiler's dead code elimination.
+
+
+
+
+
+
+
+
/// To prevent this, you must ensure that either:
+
+
+
+
+
+
+
+
/// 1. codegen-units is set to 1, i.e. in Cargo.toml:
+
+
+
+
+
+
+
+
/// [profile.release]
+
+
+
+
+
+
+
+
/// codegen-units = 1
+
+
+
+
+
+
+
+
/// 2. The function is included somewhere else in the code
+
+
+
+
+
+
+
+
/// Functions must have the signature `fn(&Expr) -> ApplicationResult`.
+
+
+
+
+
+
+
+
/// The created rule will have the same name as the function.
+
+
+
+
+
+
+
+
/// Intermediary static variables are created to allow for the decentralized registry, with the prefix `CONJURE_GEN_`.
+
+
+
+
+
+
+
+
/// Please ensure that other variable names in the same scope do not conflict with these.
+
+
+
+
+
+
+
+
/// This macro must decorate a function with the given signature.
+
+
+
+
+
+
+
+
/// As arguments, it excepts a tuple of 2-tuples in the format:
+
+
+
+
+
+
+
+
/// `((<RuleSet name>, <Priority in RuleSet>), ...)`
+
+
+
+
+ 1
+
+
+
/// use conjure_core::ast::Expression;
+
+
+
+
+ 1
+
+
+
/// use conjure_core::model::Model;
+
+
+
+
+ 1
+
+
+
/// use conjure_core::rule_engine::{ApplicationError, ApplicationResult, Reduction};
+
+
+
+
+ 1
+
+
+
/// use conjure_core::rule_engine::register_rule;
+
+
+
+
+ 1
+
+
+
/// #[register_rule(("RuleSetName", 10))]
+
+
+
+
+ 1
+
+
+
/// fn identity(expr: &Expression, mdl: &Model) -> ApplicationResult {
+
+
+
+
+ 1
+
+
+
/// Ok(Reduction::pure(expr.clone()))
+
+
+
+
+ 1
+
+
+
pub use conjure_rules_proc_macro::register_rule;
+
+
+
+
+
+
+
+
/// This procedural macro registers a rule set with the global registry.
+
+
+
+
+
+
+
+
/// It may be used in any downstream crate.
+
+
+
+
+
+
+
+
/// For more information on linker magic, see the [`linkme`](https://docs.rs/linkme/latest/linkme/) crate.
+
+
+
+
+
+
+
+
/// This macro uses the following syntax:
+
+
+
+
+
+
+
+
/// register_rule_set!(<RuleSet name>, <RuleSet order>, (<DependencyRuleSet1>, <DependencyRuleSet2>, ...));
+
+
+
+
+ 1
+
+
+
/// use conjure_core::rule_engine::register_rule_set;
+
+
+
+
+ 1
+
+
+
/// register_rule_set!("MyRuleSet", 10, ("DependencyRuleSet", "AnotherRuleSet"));
+
+
+
+
+
+
+
+
pub use conjure_rules_proc_macro::register_rule_set;
+
+
+
+
+
+
+
+
pub use resolve_rules::{get_rule_priorities, get_rules_vec, resolve_rule_sets};
+
+
+
+
+
+
+
+
pub use rewrite::{rewrite_model, RewriteError};
+
+
+
+
+
+
+
+
pub use rule::{ApplicationError, ApplicationResult, Reduction, Rule};
+
+
+
+
+
+
+
+
pub use rule_set::RuleSet;
+
+
+
+
+
+
+
+
use crate::solver::SolverFamily;
+
+
+
+
+
+
+
+
pub static RULES_DISTRIBUTED_SLICE: [Rule<'static>];
+
+
+
+
+
+
+
+
pub static RULE_SETS_DISTRIBUTED_SLICE: [RuleSet<'static>];
+
+
+
+
+
+
+
+
pub mod _dependencies {
+
+
+
+
+
+
+
+
pub use linkme::distributed_slice;
+
+
+
+
+
+
+
+
/// Returns a copied `Vec` of all rules registered with the `register_rule` macro.
+
+
+
+
+
+
+
+
/// Rules are not guaranteed to be in any particular order.
+
+
+
+
+
+
+
+
/// # use conjure_core::rule_engine::{ApplicationResult, Reduction, get_rules};
+
+
+
+
+
+
+
+
/// # use conjure_core::ast::Expression;
+
+
+
+
+
+
+
+
/// # use conjure_core::model::Model;
+
+
+
+
+
+
+
+
/// # use conjure_core::rule_engine::register_rule;
+
+
+
+
+
+
+
+
/// fn identity(expr: &Expression, mdl: &Model) -> ApplicationResult {
+
+
+
+
+
+
+
+
/// Ok(Reduction::pure(expr.clone()))
+
+
+
+
+ 1
+
+
+
/// println!("Rules: {:?}", get_rules());
+
+
+
+
+
+
+
+
/// This will print (if no other rules are registered):
+
+
+
+
+
+
+
+
/// Rules: [Rule { name: "identity", application: MEM }]
+
+
+
+
+
+
+
+
/// Where `MEM` is the memory address of the `identity` function.
+
+
+
+
+ 615
+
+
+
pub fn get_rules() -> Vec<&'static Rule<'static>> {
+
+
+
+
+ 615
+
+
+
RULES_DISTRIBUTED_SLICE.iter().collect()
+
+
+
+
+
+
+
+
/// Get a rule by name.
+
+
+
+
+
+
+
+
/// Returns the rule with the given name or None if it doesn't exist.
+
+
+
+
+
+
+
+
/// use conjure_core::rule_engine::register_rule;
+
+
+
+
+
+
+
+
/// use conjure_core::rule_engine::{Rule, ApplicationResult, Reduction, get_rule_by_name};
+
+
+
+
+
+
+
+
/// use conjure_core::ast::Expression;
+
+
+
+
+
+
+
+
/// use conjure_core::model::Model;
+
+
+
+
+
+
+
+
/// fn identity(expr: &Expression, mdl: &Model) -> ApplicationResult {
+
+
+
+
+
+
+
+
/// Ok(Reduction::pure(expr.clone()))
+
+
+
+
+ 1
+
+
+
/// println!("Rule: {:?}", get_rule_by_name("identity"));
+
+
+
+
+
+
+
+
/// Rule: Some(Rule { name: "identity", application: MEM })
+
+
+
+
+ 375
+
+
+
pub fn get_rule_by_name(name: &str) -> Option<&'static Rule<'static>> {
+
+
+
+
+ 6570
+
+
+
get_rules().iter().find(|rule| rule.name == name).cloned()
+
+
+
+
+
+
+
+
/// Get all rule sets
+
+
+
+
+
+
+
+
/// Returns a `Vec` of static references to all rule sets registered with the `register_rule_set` macro.
+
+
+
+
+
+
+
+
/// Rule sets are not guaranteed to be in any particular order.
+
+
+
+
+ 1
+
+
+
/// use conjure_core::rule_engine::register_rule_set;
+
+
+
+
+ 1
+
+
+
/// use conjure_core::rule_engine::get_rule_sets;
+
+
+
+
+ 1
+
+
+
/// register_rule_set!("MyRuleSet", 10, ("AnotherRuleSet"));
+
+
+
+
+ 1
+
+
+
/// register_rule_set!("AnotherRuleSet", 5, ());
+
+
+
+
+ 1
+
+
+
/// println!("Rule sets: {:?}", get_rule_sets());
+
+
+
+
+
+
+
+
/// This will print (if no other rule sets are registered):
+
+
+
+
+
+
+
+
/// RuleSet { name: "MyRuleSet", order: 10, rules: OnceLock { state: Uninitialized }, dependencies: ["AnotherRuleSet"] },
+
+
+
+
+
+
+
+
/// RuleSet { name: "AnotherRuleSet", order: 5, rules: OnceLock { state: Uninitialized }, dependencies: [] }
+
+
+
+
+ 465
+
+
+
pub fn get_rule_sets() -> Vec<&'static RuleSet<'static>> {
+
+
+
+
+ 465
+
+
+
RULE_SETS_DISTRIBUTED_SLICE.iter().collect()
+
+
+
+
+
+
+
+
/// Get a rule set by name.
+
+
+
+
+
+
+
+
/// Returns the rule set with the given name or None if it doesn't exist.
+
+
+
+
+ 1
+
+
+
/// use conjure_core::rule_engine::register_rule_set;
+
+
+
+
+ 1
+
+
+
/// use conjure_core::rule_engine::get_rule_set_by_name;
+
+
+
+
+ 1
+
+
+
/// register_rule_set!("MyRuleSet", 10, ("DependencyRuleSet", "AnotherRuleSet"));
+
+
+
+
+ 1
+
+
+
/// println!("Rule set: {:?}", get_rule_set_by_name("MyRuleSet"));
+
+
+
+
+
+
+
+
/// Rule set: Some(RuleSet { name: "MyRuleSet", order: 10, rules: OnceLock { state: Uninitialized }, dependencies: ["DependencyRuleSet", "AnotherRuleSet"] })
+
+
+
+
+ 255
+
+
+
pub fn get_rule_set_by_name(name: &str) -> Option<&'static RuleSet<'static>> {
+
+
+
+
+ 615
+
+
+
.find(|rule_set| rule_set.name == name)
+
+
+
+
+
+
+
+
/// Get all rule sets for a given solver family.
+
+
+
+
+
+
+
+
/// Returns a `Vec` of static references to all rule sets that are applicable to the given solver family.
+
+
+
+
+ 1
+
+
+
/// use conjure_core::solver::SolverFamily;
+
+
+
+
+ 1
+
+
+
/// use conjure_core::rule_engine::get_rule_sets_for_solver_family;
+
+
+
+
+ 1
+
+
+
/// let rule_sets = get_rule_sets_for_solver_family(SolverFamily::SAT);
+
+
+
+
+ 1
+
+
+
/// assert_eq!(rule_sets.len(), 1);
+
+
+
+
+ 1
+
+
+
/// assert_eq!(rule_sets[0].name, "CNF");
+
+
+
+
+ 183
+
+
+
pub fn get_rule_sets_for_solver_family(
+
+
+
+
+ 195
+
+
+
solver_family: SolverFamily,
+
+
+
+
+ 195
+
+
+
) -> Vec<&'static RuleSet<'static>> {
+
+
+
+
+ 780
+
+
+
.any(|family| family.eq(&solver_family))
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/resolve_rules.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/resolve_rules.rs.html
new file mode 100644
index 000000000..225ce82a9
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/resolve_rules.rs.html
@@ -0,0 +1,2505 @@
+
+
+
+
+ Grcov report - resolve_rules.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 29.17 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::collections::{HashMap, HashSet};
+
+
+
+
+
+
+
+
use std::fmt::Display;
+
+
+
+
+
+
+
+
use thiserror::Error;
+
+
+
+
+
+
+
+
use crate::rule_engine::{get_rule_set_by_name, get_rule_sets_for_solver_family, Rule, RuleSet};
+
+
+
+
+
+
+
+
use crate::solver::SolverFamily;
+
+
+
+
+
+
+
+
#[derive(Debug, Error)]
+
+
+
+
+
+
+
+
pub enum ResolveRulesError {
+
+
+
+
+
+
+
+
impl Display for ResolveRulesError {
+
+
+
+
+
+
+
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+
+
+
+
+
+
+
+
ResolveRulesError::RuleSetNotFound => write!(f, "Rule set not found."),
+
+
+
+
+
+
+
+
/// Helper function to get a rule set by name, or return an error if it doesn't exist.
+
+
+
+
+
+
+
+
/// - `rule_set_name` The name of the rule set to get.
+
+
+
+
+
+
+
+
/// - The rule set with the given name or `RuleSetError::RuleSetNotFound` if it doesn't exist.
+
+
+
+
+ 180
+
+
+
fn get_rule_set(rule_set_name: &str) -> Result<&'static RuleSet<'static>, ResolveRulesError> {
+
+
+
+
+ 180
+
+
+
match get_rule_set_by_name(rule_set_name) {
+
+
+
+
+ 180
+
+
+
Some(rule_set) => Ok(rule_set),
+
+
+
+
+
+
+
+
None => Err(ResolveRulesError::RuleSetNotFound),
+
+
+
+
+
+
+
+
/// Resolve a list of rule sets (and dependencies) by their names
+
+
+
+
+
+
+
+
/// - `rule_set_names` The names of the rule sets to resolve.
+
+
+
+
+
+
+
+
/// - A list of the given rule sets and all of their dependencies, or error
+
+
+
+
+
+
+
+
#[allow(clippy::mutable_key_type)] // RuleSet is 'static so it's fine
+
+
+
+
+ 180
+
+
+
pub fn rule_sets_by_names<'a>(
+
+
+
+
+ 180
+
+
+
rule_set_names: &Vec<&str>,
+
+
+
+
+ 180
+
+
+
) -> Result<HashSet<&'a RuleSet<'static>>, ResolveRulesError> {
+
+
+
+
+ 180
+
+
+
let mut rs_set: HashSet<&'static RuleSet<'static>> = HashSet::new();
+
+
+
+
+ 360
+
+
+
for rule_set_name in rule_set_names {
+
+
+
+
+ 180
+
+
+
let rule_set = get_rule_set(rule_set_name)?;
+
+
+
+
+ 180
+
+
+
let new_dependencies = rule_set.get_dependencies();
+
+
+
+
+ 180
+
+
+
rs_set.insert(rule_set);
+
+
+
+
+ 180
+
+
+
rs_set.extend(new_dependencies);
+
+
+
+
+
+
+
+
/// Resolves the final set of rule sets to apply based on target solver and extra rule set names.
+
+
+
+
+
+
+
+
/// - `target_solver` The solver to resolve the rule sets for.
+
+
+
+
+
+
+
+
/// - `extra_rs_names` The names of the extra rule sets to use
+
+
+
+
+
+
+
+
/// - A vector of rule sets to apply.
+
+
+
+
+
+
+
+
#[allow(clippy::mutable_key_type)] // RuleSet is 'static so it's fine
+
+
+
+
+ 180
+
+
+
pub fn resolve_rule_sets<'a>(
+
+
+
+
+ 180
+
+
+
target_solver: SolverFamily,
+
+
+
+
+ 180
+
+
+
extra_rs_names: &Vec<&str>,
+
+
+
+
+ 180
+
+
+
) -> Result<Vec<&'a RuleSet<'static>>, ResolveRulesError> {
+
+
+
+
+ 180
+
+
+
let mut ans = HashSet::new();
+
+
+
+
+ 180
+
+
+
for rs in get_rule_sets_for_solver_family(target_solver) {
+
+
+
+
+ 180
+
+
+
ans.extend(rs.with_dependencies());
+
+
+
+
+ 180
+
+
+
ans.extend(rule_sets_by_names(extra_rs_names)?);
+
+
+
+
+ 180
+
+
+
Ok(ans.iter().cloned().collect())
+
+
+
+
+
+
+
+
/// Convert a list of rule sets into a final map of rules to their priorities.
+
+
+
+
+
+
+
+
/// - `rule_sets` The rule sets to get the rules from.
+
+
+
+
+
+
+
+
/// - A map of rules to their priorities.
+
+
+
+
+ 165
+
+
+
pub fn get_rule_priorities<'a>(
+
+
+
+
+ 165
+
+
+
rule_sets: &Vec<&'a RuleSet<'a>>,
+
+
+
+
+ 165
+
+
+
) -> Result<HashMap<&'a Rule<'a>, u8>, ResolveRulesError> {
+
+
+
+
+ 165
+
+
+
let mut rule_priorities: HashMap<&'a Rule<'a>, (&'a RuleSet<'a>, u8)> = HashMap::new();
+
+
+
+
+ 660
+
+
+
for rs in rule_sets {
+
+
+
+
+ 4290
+
+
+
for (rule, priority) in rs.get_rules() {
+
+
+
+
+ 4290
+
+
+
if let Some((old_rs, _)) = rule_priorities.get(rule) {
+
+
+
+
+
+
+
+
if rs.order >= old_rs.order {
+
+
+
+
+
+
+
+
rule_priorities.insert(rule, (&rs, *priority));
+
+
+
+
+ 4290
+
+
+
rule_priorities.insert(rule, (&rs, *priority));
+
+
+
+
+ 165
+
+
+
let mut ans: HashMap<&'a Rule<'a>, u8> = HashMap::new();
+
+
+
+
+ 4455
+
+
+
for (rule, (_, priority)) in rule_priorities {
+
+
+
+
+ 4290
+
+
+
ans.insert(rule, priority);
+
+
+
+
+
+
+
+
/// Compare two rules by their priorities and names.
+
+
+
+
+
+
+
+
/// Takes the rules and a map of rules to their priorities.
+
+
+
+
+
+
+
+
/// If rules are not in the map, they are assumed to have priority 0.
+
+
+
+
+
+
+
+
/// If the rules have the same priority, they are compared by their names.
+
+
+
+
+
+
+
+
/// - `a` first rule to compare.
+
+
+
+
+
+
+
+
/// - `b` second rule to compare.
+
+
+
+
+
+
+
+
/// - `rule_priorities` The priorities of the rules.
+
+
+
+
+
+
+
+
/// - The ordering of the two rules.
+
+
+
+
+ 18315
+
+
+
rule_priorities: &HashMap<&'a Rule<'a>, u8>,
+
+
+
+
+ 18315
+
+
+
) -> std::cmp::Ordering {
+
+
+
+
+ 18315
+
+
+
let a_priority = *rule_priorities.get(a).unwrap_or(&0);
+
+
+
+
+ 18315
+
+
+
let b_priority = *rule_priorities.get(b).unwrap_or(&0);
+
+
+
+
+ 18315
+
+
+
if a_priority == b_priority {
+
+
+
+
+ 16125
+
+
+
return a.name.cmp(b.name);
+
+
+
+
+ 2190
+
+
+
b_priority.cmp(&a_priority)
+
+
+
+
+
+
+
+
/// Get a final ordering of rules based on their priorities and names.
+
+
+
+
+
+
+
+
/// - `rule_priorities` The priorities of the rules.
+
+
+
+
+
+
+
+
/// - A list of rules sorted by their priorities and names.
+
+
+
+
+ 165
+
+
+
pub fn get_rules_vec<'a>(rule_priorities: &HashMap<&'a Rule<'a>, u8>) -> Vec<&'a Rule<'a>> {
+
+
+
+
+ 165
+
+
+
let mut rules: Vec<&'a Rule<'a>> = rule_priorities.keys().copied().collect();
+
+
+
+
+ 18315
+
+
+
rules.sort_by(|a, b| rule_cmp(a, b, rule_priorities));
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/rewrite.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/rewrite.rs.html
new file mode 100644
index 000000000..90371461d
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/rewrite.rs.html
@@ -0,0 +1,2009 @@
+
+
+
+
+ Grcov report - rewrite.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 22.22 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::fmt::Display;
+
+
+
+
+
+
+
+
use thiserror::Error;
+
+
+
+
+
+
+
+
use uniplate::uniplate::Uniplate;
+
+
+
+
+
+
+
+
use crate::rule_engine::{Reduction, Rule, RuleSet};
+
+
+
+
+
+
+
+
rule_engine::resolve_rules::{
+
+
+
+
+
+
+
+
get_rule_priorities, get_rules_vec, ResolveRulesError as ResolveError,
+
+
+
+
+
+
+
+
struct RuleResult<'a> {
+
+
+
+
+
+
+
+
reduction: Reduction,
+
+
+
+
+
+
+
+
#[derive(Debug, Error)]
+
+
+
+
+
+
+
+
pub enum RewriteError {
+
+
+
+
+
+
+
+
ResolveRulesError(ResolveError),
+
+
+
+
+
+
+
+
impl Display for RewriteError {
+
+
+
+
+
+
+
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+
+
+
+
+
+
+
+
RewriteError::ResolveRulesError(e) => write!(f, "Error resolving rules: {}", e),
+
+
+
+
+
+
+
+
impl From<ResolveError> for RewriteError {
+
+
+
+
+
+
+
+
fn from(error: ResolveError) -> Self {
+
+
+
+
+
+
+
+
RewriteError::ResolveRulesError(error)
+
+
+
+
+
+
+
+
/// Rewrites the model by applying the rules to all constraints.
+
+
+
+
+
+
+
+
/// Any side-effects such as symbol table updates and top-level constraints are applied to the returned model.
+
+
+
+
+
+
+
+
/// A copy of the model after all, if any, possible rules are applied to its constraints.
+
+
+
+
+ 165
+
+
+
pub fn rewrite_model<'a>(
+
+
+
+
+ 165
+
+
+
rule_sets: &Vec<&'a RuleSet<'a>>,
+
+
+
+
+ 165
+
+
+
) -> Result<Model, RewriteError> {
+
+
+
+
+ 165
+
+
+
let rule_priorities = get_rule_priorities(rule_sets)?;
+
+
+
+
+ 165
+
+
+
let rules = get_rules_vec(&rule_priorities);
+
+
+
+
+ 165
+
+
+
let mut new_model = model.clone();
+
+
+
+
+ 1755
+
+
+
while let Some(step) = rewrite_iteration(&new_model.constraints, &new_model, &rules) {
+
+
+
+
+ 1590
+
+
+
step.apply(&mut new_model); // Apply side-effects (e.g. symbol table updates)
+
+
+
+
+
+
+
+
/// - Some(<new_expression>) after applying the first applicable rule to `expr` or a sub-expression.
+
+
+
+
+
+
+
+
/// - None if no rule is applicable to the expression or any sub-expression.
+
+
+
+
+ 29385
+
+
+
fn rewrite_iteration<'a>(
+
+
+
+
+ 29385
+
+
+
expression: &'a Expression,
+
+
+
+
+ 29385
+
+
+
rules: &'a Vec<&'a Rule<'a>>,
+
+
+
+
+ 29385
+
+
+
) -> Option<Reduction> {
+
+
+
+
+ 29385
+
+
+
let rule_results = apply_all_rules(expression, model, rules);
+
+
+
+
+ 29385
+
+
+
if let Some(new) = choose_rewrite(&rule_results) {
+
+
+
+
+ 27795
+
+
+
let mut sub = expression.children();
+
+
+
+
+ 27795
+
+
+
for i in 0..sub.len() {
+
+
+
+
+ 27630
+
+
+
if let Some(red) = rewrite_iteration(&sub[i], model, rules) {
+
+
+
+
+ 1770
+
+
+
sub[i] = red.new_expression;
+
+
+
+
+ 1770
+
+
+
if let Ok(res) = expression.with_children(sub.clone()) {
+
+
+
+
+ 1770
+
+
+
return Some(Reduction::new(res, red.new_top, red.symbols));
+
+
+
+
+ 26025
+
+
+
None // No rules applicable to this branch of the expression
+
+
+
+
+
+
+
+
/// - A list of RuleResults after applying all rules to `expression`.
+
+
+
+
+
+
+
+
/// - An empty list if no rules are applicable.
+
+
+
+
+ 29385
+
+
+
fn apply_all_rules<'a>(
+
+
+
+
+ 29385
+
+
+
expression: &'a Expression,
+
+
+
+
+ 29385
+
+
+
rules: &'a Vec<&'a Rule<'a>>,
+
+
+
+
+ 29385
+
+
+
) -> Vec<RuleResult<'a>> {
+
+
+
+
+ 29385
+
+
+
let mut results = Vec::new();
+
+
+
+
+ 764010
+
+
+
match rule.apply(expression, model) {
+
+
+
+
+ 1620
+
+
+
results.push(RuleResult {
+
+
+
+
+ 1620
+
+
+
log::trace!(target: "file", "Rule applied: {:?}", rule);
+
+
+
+
+ 762390
+
+
+
log::trace!(target: "file", "Rule attempted but not applied: {:?}", rule);
+
+
+
+
+
+
+
+
/// - Some(<reduction>) after applying the first rule in `results`.
+
+
+
+
+
+
+
+
/// - None if `results` is empty.
+
+
+
+
+ 29385
+
+
+
fn choose_rewrite(results: &[RuleResult]) -> Option<Reduction> {
+
+
+
+
+ 29385
+
+
+
if results.is_empty() {
+
+
+
+
+ 1590
+
+
+
// Return the first result for now
+
+
+
+
+ 1590
+
+
+
Some(results[0].reduction.clone())
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/rule.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/rule.rs.html
new file mode 100644
index 000000000..bbd8db47c
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/rule.rs.html
@@ -0,0 +1,2313 @@
+
+
+
+
+ Grcov report - rule.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 18.18 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::fmt::{self, Display, Formatter};
+
+
+
+
+
+
+
+
use thiserror::Error;
+
+
+
+
+
+
+
+
use crate::ast::{Expression, SymbolTable};
+
+
+
+
+
+
+
+
use crate::metadata::Metadata;
+
+
+
+
+
+
+
+
use crate::model::Model;
+
+
+
+
+
+
+
+
#[derive(Debug, Error)]
+
+
+
+
+
+
+
+
pub enum ApplicationError {
+
+
+
+
+
+
+
+
#[error("Rule is not applicable")]
+
+
+
+
+
+
+
+
#[error("Could not find the min/max bounds for the expression")]
+
+
+
+
+
+
+
+
/// The result of applying a rule to an expression.
+
+
+
+
+
+
+
+
/// Contains an expression to replace the original, a top-level constraint to add to the top of the constraint AST, and an expansion to the model symbol table.
+
+
+
+
+
+
+
+
#[derive(Clone, Debug)]
+
+
+
+
+
+
+
+
pub struct Reduction {
+
+
+
+
+
+
+
+
pub new_expression: Expression,
+
+
+
+
+
+
+
+
pub new_top: Expression,
+
+
+
+
+
+
+
+
pub symbols: SymbolTable,
+
+
+
+
+
+
+
+
/// The result of applying a rule to an expression.
+
+
+
+
+
+
+
+
/// Contains either a set of reduction instructions or an error.
+
+
+
+
+
+
+
+
pub type ApplicationResult = Result<Reduction, ApplicationError>;
+
+
+
+
+ 1845
+
+
+
pub fn new(new_expression: Expression, new_top: Expression, symbols: SymbolTable) -> Self {
+
+
+
+
+
+
+
+
/// Represents a reduction with no side effects on the model.
+
+
+
+
+ 1815
+
+
+
pub fn pure(new_expression: Expression) -> Self {
+
+
+
+
+ 1815
+
+
+
new_top: Expression::Nothing,
+
+
+
+
+ 1815
+
+
+
symbols: SymbolTable::new(),
+
+
+
+
+
+
+
+
/// Represents a reduction that also modifies the symbol table.
+
+
+
+
+
+
+
+
pub fn with_symbols(new_expression: Expression, symbols: SymbolTable) -> Self {
+
+
+
+
+
+
+
+
new_top: Expression::Nothing,
+
+
+
+
+
+
+
+
/// Represents a reduction that also adds a top-level constraint to the model.
+
+
+
+
+
+
+
+
pub fn with_top(new_expression: Expression, new_top: Expression) -> Self {
+
+
+
+
+
+
+
+
symbols: SymbolTable::new(),
+
+
+
+
+
+
+
+
// Apply side-effects (e.g. symbol table updates
+
+
+
+
+ 1590
+
+
+
pub fn apply(self, model: &mut Model) {
+
+
+
+
+ 1590
+
+
+
model.variables.extend(self.symbols); // Add new assignments to the symbol table
+
+
+
+
+ 1590
+
+
+
if self.new_top.is_nothing() {
+
+
+
+
+ 1515
+
+
+
model.constraints = self.new_expression.clone();
+
+
+
+
+ 75
+
+
+
model.constraints = match self.new_expression {
+
+
+
+
+
+
+
+
Expression::And(metadata, mut exprs) => {
+
+
+
+
+
+
+
+
// Avoid creating a nested conjunction
+
+
+
+
+
+
+
+
exprs.push(self.new_top.clone());
+
+
+
+
+
+
+
+
Expression::And(metadata.clone(), exprs)
+
+
+
+
+ 75
+
+
+
_ => Expression::And(
+
+
+
+
+ 75
+
+
+
vec![self.new_expression.clone(), self.new_top],
+
+
+
+
+
+
+
+
* A rule with a name, application function, and rule sets.
+
+
+
+
+
+
+
+
* - `name` The name of the rule.
+
+
+
+
+
+
+
+
* - `application` The function to apply the rule.
+
+
+
+
+
+
+
+
* - `rule_sets` A list of rule set names and priorities that this rule is a part of. This is used to populate rulesets at runtime.
+
+
+
+
+
+
+
+
#[derive(Clone, Debug)]
+
+
+
+
+
+
+
+
pub struct Rule<'a> {
+
+
+
+
+
+
+
+
pub application: fn(&Expression, &Model) -> ApplicationResult,
+
+
+
+
+
+
+
+
pub rule_sets: &'a [(&'a str, u8)], // (name, priority). At runtime, we add the rule to rulesets
+
+
+
+
+
+
+
+
application: fn(&Expression, &Model) -> ApplicationResult,
+
+
+
+
+
+
+
+
rule_sets: &'a [(&'static str, u8)],
+
+
+
+
+ 770670
+
+
+
pub fn apply(&self, expr: &Expression, mdl: &Model) -> ApplicationResult {
+
+
+
+
+ 770670
+
+
+
(self.application)(expr, mdl)
+
+
+
+
+
+
+
+
impl<'a> Display for Rule<'a> {
+
+
+
+
+
+
+
+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+
+
+
+
+
+
+
+
write!(f, "{}", self.name)
+
+
+
+
+
+
+
+
impl<'a> PartialEq for Rule<'a> {
+
+
+
+
+ 38010
+
+
+
fn eq(&self, other: &Self) -> bool {
+
+
+
+
+ 38010
+
+
+
self.name == other.name
+
+
+
+
+
+
+
+
impl<'a> Eq for Rule<'a> {}
+
+
+
+
+
+
+
+
impl<'a> Hash for Rule<'a> {
+
+
+
+
+ 60840
+
+
+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+
+
+
+
+ 60840
+
+
+
self.name.hash(state);
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/rule_set.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/rule_set.rs.html
new file mode 100644
index 000000000..4e1ecc277
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rule_engine/rule_set.rs.html
@@ -0,0 +1,2809 @@
+
+
+
+
+ Grcov report - rule_set.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::collections::{HashMap, HashSet};
+
+
+
+
+
+
+
+
use std::fmt::{Display, Formatter};
+
+
+
+
+
+
+
+
use std::sync::OnceLock;
+
+
+
+
+
+
+
+
use crate::rule_engine::{get_rule_set_by_name, get_rules, Rule};
+
+
+
+
+
+
+
+
use crate::solver::SolverFamily;
+
+
+
+
+
+
+
+
/// A set of rules with a name, priority, and dependencies.
+
+
+
+
+
+
+
+
#[derive(Clone, Debug)]
+
+
+
+
+
+
+
+
pub struct RuleSet<'a> {
+
+
+
+
+
+
+
+
/// The name of the rule set.
+
+
+
+
+
+
+
+
/// Order of the RuleSet. Used to establish a consistent order of operations when resolving rules.
+
+
+
+
+
+
+
+
/// If two RuleSets overlap (contain the same rule but with different priorities), the RuleSet with the higher order will be used as the source of truth.
+
+
+
+
+
+
+
+
/// A map of rules to their priorities. This will be lazily initialized at runtime.
+
+
+
+
+
+
+
+
rules: OnceLock<HashMap<&'a Rule<'a>, u8>>,
+
+
+
+
+
+
+
+
/// The names of the rule sets that this rule set depends on.
+
+
+
+
+
+
+
+
dependency_rs_names: &'a [&'a str],
+
+
+
+
+
+
+
+
dependencies: OnceLock<HashSet<&'a RuleSet<'a>>>,
+
+
+
+
+
+
+
+
/// The solver families that this rule set applies to.
+
+
+
+
+
+
+
+
pub solver_families: &'a [SolverFamily],
+
+
+
+
+
+
+
+
impl<'a> RuleSet<'a> {
+
+
+
+
+
+
+
+
dependencies: &'a [&'a str],
+
+
+
+
+
+
+
+
solver_families: &'a [SolverFamily],
+
+
+
+
+
+
+
+
dependency_rs_names: dependencies,
+
+
+
+
+
+
+
+
rules: OnceLock::new(),
+
+
+
+
+
+
+
+
dependencies: OnceLock::new(),
+
+
+
+
+
+
+
+
/// Get the rules of this rule set, evaluating them lazily if necessary
+
+
+
+
+
+
+
+
/// Returns a `&HashMap<&Rule, u8>` where the key is the rule and the value is the priority of the rule.
+
+
+
+
+ 495
+
+
+
pub fn get_rules(&self) -> &HashMap<&'a Rule<'a>, u8> {
+
+
+
+
+ 495
+
+
+
match self.rules.get() {
+
+
+
+
+ 165
+
+
+
let rules = self.resolve_rules();
+
+
+
+
+ 165
+
+
+
let _ = self.rules.set(rules); // Try to set the rules, but ignore if it fails.
+
+
+
+
+ 165
+
+
+
// At this point, the rules cell is guaranteed to be set, so we can unwrap safely.
+
+
+
+
+ 165
+
+
+
// see: https://doc.rust-lang.org/stable/std/sync/struct.OnceLock.html#method.set
+
+
+
+
+ 165
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+ 165
+
+
+
self.rules.get().unwrap()
+
+
+
+
+ 330
+
+
+
Some(rules) => rules,
+
+
+
+
+
+
+
+
/// Get the dependencies of this rule set, evaluating them lazily if necessary
+
+
+
+
+
+
+
+
/// Returns a `&HashSet<&RuleSet>` of the rule sets that this rule set depends on.
+
+
+
+
+
+
+
+
#[allow(clippy::mutable_key_type)] // RuleSet is 'static so it's fine
+
+
+
+
+ 360
+
+
+
pub fn get_dependencies(&self) -> &HashSet<&'static RuleSet> {
+
+
+
+
+ 360
+
+
+
match self.dependencies.get() {
+
+
+
+
+ 120
+
+
+
let dependencies = self.resolve_dependencies();
+
+
+
+
+ 120
+
+
+
let _ = self.dependencies.set(dependencies); // Try to set the dependencies, but ignore if it fails.
+
+
+
+
+ 120
+
+
+
// At this point, the dependencies cell is guaranteed to be set, so we can unwrap safely.
+
+
+
+
+ 120
+
+
+
// see: https://doc.rust-lang.org/stable/std/sync/struct.OnceLock.html#method.set
+
+
+
+
+ 120
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+ 120
+
+
+
self.dependencies.get().unwrap()
+
+
+
+
+ 240
+
+
+
Some(dependencies) => dependencies,
+
+
+
+
+
+
+
+
/// Get the dependencies of this rule set, including itself
+
+
+
+
+
+
+
+
#[allow(clippy::mutable_key_type)] // RuleSet is 'static so it's fine
+
+
+
+
+ 180
+
+
+
pub fn with_dependencies(&self) -> HashSet<&'static RuleSet> {
+
+
+
+
+ 180
+
+
+
let mut deps = self.get_dependencies().clone();
+
+
+
+
+
+
+
+
/// Resolve the rules of this rule set ("reverse the arrows")
+
+
+
+
+ 165
+
+
+
fn resolve_rules(&self) -> HashMap<&'a Rule<'a>, u8> {
+
+
+
+
+ 165
+
+
+
let mut rules = HashMap::new();
+
+
+
+
+ 4785
+
+
+
for rule in get_rules() {
+
+
+
+
+ 4620
+
+
+
let mut found = false;
+
+
+
+
+ 4620
+
+
+
let mut priority: u8 = 0;
+
+
+
+
+ 7695
+
+
+
for (name, p) in rule.rule_sets {
+
+
+
+
+ 4620
+
+
+
if *name == self.name {
+
+
+
+
+ 1545
+
+
+
rules.insert(rule, priority);
+
+
+
+
+
+
+
+
/// Recursively resolve the dependencies of this rule set.
+
+
+
+
+
+
+
+
#[allow(clippy::mutable_key_type)] // RuleSet is 'static so it's fine
+
+
+
+
+ 180
+
+
+
fn resolve_dependencies(&self) -> HashSet<&'static RuleSet> {
+
+
+
+
+ 180
+
+
+
let mut dependencies = HashSet::new();
+
+
+
+
+ 240
+
+
+
for dep in self.dependency_rs_names {
+
+
+
+
+ 60
+
+
+
match get_rule_set_by_name(dep) {
+
+
+
+
+
+
+
+
"Rule set {} depends on non-existent rule set {}",
+
+
+
+
+ 60
+
+
+
if !dependencies.contains(rule_set) {
+
+
+
+
+ 60
+
+
+
dependencies.insert(rule_set);
+
+
+
+
+ 60
+
+
+
dependencies.extend(rule_set.resolve_dependencies());
+
+
+
+
+
+
+
+
impl<'a> PartialEq for RuleSet<'a> {
+
+
+
+
+
+
+
+
fn eq(&self, other: &Self) -> bool {
+
+
+
+
+
+
+
+
self.name == other.name
+
+
+
+
+
+
+
+
impl<'a> Eq for RuleSet<'a> {}
+
+
+
+
+
+
+
+
impl<'a> Hash for RuleSet<'a> {
+
+
+
+
+ 960
+
+
+
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+
+
+
+
+ 960
+
+
+
self.name.hash(state);
+
+
+
+
+
+
+
+
impl<'a> Display for RuleSet<'a> {
+
+
+
+
+
+
+
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+
+
+
+
+
+
+
+
let n_rules = self.get_rules().len();
+
+
+
+
+
+
+
+
let solver_families = self
+
+
+
+
+
+
+
+
.map(|f| f.to_string())
+
+
+
+
+
+
+
+
.collect::<Vec<String>>();
+
+
+
+
+
+
+
+
\tsolver_families: {:?}\n\
+
+
+
+
+
+
+
+
self.name, self.order, n_rules, solver_families
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/base.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/base.rs.html
new file mode 100644
index 000000000..abba4eba1
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/base.rs.html
@@ -0,0 +1,8425 @@
+
+
+
+
+ Grcov report - base.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 15.42 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use conjure_core::ast::{
+
+
+
+
+
+
+
+
Constant as Const, DecisionVariable, Domain, Expression as Expr, Range, SymbolTable,
+
+
+
+
+
+
+
+
use conjure_core::metadata::Metadata;
+
+
+
+
+
+
+
+
use conjure_core::rule_engine::{
+
+
+
+
+
+
+
+
register_rule, register_rule_set, ApplicationError, ApplicationResult, Reduction,
+
+
+
+
+
+
+
+
use conjure_core::Model;
+
+
+
+
+
+
+
+
use uniplate::uniplate::Uniplate;
+
+
+
+
+
+
+
+
/*****************************************************************************/
+
+
+
+
+
+
+
+
/* This file contains basic rules for simplifying expressions */
+
+
+
+
+
+
+
+
/*****************************************************************************/
+
+
+
+
+
+
+
+
register_rule_set!("Base", 150, ());
+
+
+
+
+
+
+
+
* Remove nothing's from expressions:
+
+
+
+
+
+
+
+
* and([a, nothing, b]) = and([a, b])
+
+
+
+
+
+
+
+
* sum([a, nothing, b]) = sum([a, b])
+
+
+
+
+
+
+
+
* sum_leq([a, nothing, b], c) = sum_leq([a, b], c)
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29610
+
+
+
fn remove_nothings(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 29610
+
+
+
fn remove_nothings(exprs: Vec<Expr>) -> Result<Vec<Expr>, ApplicationError> {
+
+
+
+
+ 29610
+
+
+
let mut changed = false;
+
+
+
+
+ 29610
+
+
+
let mut new_exprs = Vec::new();
+
+
+
+
+ 33165
+
+
+
_ => new_exprs.push(e),
+
+
+
+
+ 29610
+
+
+
Err(ApplicationError::RuleNotApplicable)
+
+
+
+
+
+
+
+
fn get_lhs_rhs(sub: Vec<Expr>) -> (Vec<Expr>, Box<Expr>) {
+
+
+
+
+
+
+
+
return (Vec::new(), Box::new(Expr::Nothing));
+
+
+
+
+
+
+
+
let lhs = sub[..(sub.len() - 1)].to_vec();
+
+
+
+
+
+
+
+
let rhs = Box::new(sub[sub.len() - 1].clone());
+
+
+
+
+ 29610
+
+
+
let new_sub = remove_nothings(expr.children())?;
+
+
+
+
+
+
+
+
Expr::And(md, _) => Ok(Reduction::pure(Expr::And(md.clone(), new_sub))),
+
+
+
+
+
+
+
+
Expr::Or(md, _) => Ok(Reduction::pure(Expr::Or(md.clone(), new_sub))),
+
+
+
+
+
+
+
+
Expr::Sum(md, _) => Ok(Reduction::pure(Expr::Sum(md.clone(), new_sub))),
+
+
+
+
+
+
+
+
Expr::SumEq(md, _, _) => {
+
+
+
+
+
+
+
+
let (lhs, rhs) = get_lhs_rhs(new_sub);
+
+
+
+
+
+
+
+
Ok(Reduction::pure(Expr::SumEq(md.clone(), lhs, rhs)))
+
+
+
+
+
+
+
+
Expr::SumLeq(md, _lhs, _rhs) => {
+
+
+
+
+
+
+
+
let (lhs, rhs) = get_lhs_rhs(new_sub);
+
+
+
+
+
+
+
+
Ok(Reduction::pure(Expr::SumLeq(md.clone(), lhs, rhs)))
+
+
+
+
+
+
+
+
Expr::SumGeq(md, _lhs, _rhs) => {
+
+
+
+
+
+
+
+
let (lhs, rhs) = get_lhs_rhs(new_sub);
+
+
+
+
+
+
+
+
Ok(Reduction::pure(Expr::SumGeq(md.clone(), lhs, rhs)))
+
+
+
+
+
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Remove empty expressions:
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29610
+
+
+
fn empty_to_nothing(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+
+
+
+
Expr::Nothing | Expr::Reference(_, _) | Expr::Constant(_, _) => {
+
+
+
+
+ 18630
+
+
+
Err(ApplicationError::RuleNotApplicable)
+
+
+
+
+ 10980
+
+
+
if expr.children().is_empty() {
+
+
+
+
+
+
+
+
Ok(Reduction::pure(Expr::Nothing))
+
+
+
+
+ 10980
+
+
+
Err(ApplicationError::RuleNotApplicable)
+
+
+
+
+
+
+
+
* Evaluate sum of constants:
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29655
+
+
+
fn sum_constants(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 60
+
+
+
Expr::Sum(_, exprs) => {
+
+
+
+
+ 60
+
+
+
let mut new_exprs = Vec::new();
+
+
+
+
+ 60
+
+
+
let mut changed = false;
+
+
+
+
+ 120
+
+
+
Expr::Constant(_metadata, Const::Int(i)) => {
+
+
+
+
+ 45
+
+
+
_ => new_exprs.push(e.clone()),
+
+
+
+
+ 15
+
+
+
return Err(ApplicationError::RuleNotApplicable);
+
+
+
+
+ 45
+
+
+
// TODO (kf77): Get existing metadata instead of creating a new one
+
+
+
+
+ 45
+
+
+
new_exprs.push(Expr::Constant(Metadata::new(), Const::Int(sum)));
+
+
+
+
+ 45
+
+
+
Ok(Reduction::pure(Expr::Sum(Metadata::new(), new_exprs))) // Let other rules handle only one Expr being contained in the sum
+
+
+
+
+ 29595
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Unwrap trivial sums:
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29640
+
+
+
fn unwrap_sum(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 45
+
+
+
Expr::Sum(_, exprs) if (exprs.len() == 1) => Ok(Reduction::pure(exprs[0].clone())),
+
+
+
+
+ 29610
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Flatten nested sums:
+
+
+
+
+ 1
+
+
+
* sum(sum(a, b), c) = sum(a, b, c)
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29610
+
+
+
pub fn flatten_nested_sum(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 15
+
+
+
Expr::Sum(metadata, exprs) => {
+
+
+
+
+ 15
+
+
+
let mut new_exprs = Vec::new();
+
+
+
+
+ 15
+
+
+
let mut changed = false;
+
+
+
+
+ 15
+
+
+
Expr::Sum(_, sub_exprs) => {
+
+
+
+
+ 30
+
+
+
new_exprs.push(e.clone());
+
+
+
+
+ 15
+
+
+
_ => new_exprs.push(e.clone()),
+
+
+
+
+
+
+
+
return Err(ApplicationError::RuleNotApplicable);
+
+
+
+
+ 15
+
+
+
Ok(Reduction::pure(Expr::Sum(metadata.clone(), new_exprs)))
+
+
+
+
+ 29595
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+ 1
+
+
+
* or(or(a, b), c) = or(a, b, c)
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29640
+
+
+
fn unwrap_nested_or(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 2205
+
+
+
Expr::Or(metadata, exprs) => {
+
+
+
+
+ 2205
+
+
+
let mut new_exprs = Vec::new();
+
+
+
+
+ 2205
+
+
+
let mut changed = false;
+
+
+
+
+ 15
+
+
+
Expr::Or(_, exprs) => {
+
+
+
+
+ 30
+
+
+
new_exprs.push(e.clone());
+
+
+
+
+ 4395
+
+
+
_ => new_exprs.push(e.clone()),
+
+
+
+
+ 2190
+
+
+
return Err(ApplicationError::RuleNotApplicable);
+
+
+
+
+ 15
+
+
+
Ok(Reduction::pure(Expr::Or(metadata.clone(), new_exprs)))
+
+
+
+
+ 27435
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Unwrap nested `and`
+
+
+
+
+ 1
+
+
+
* and(and(a, b), c) = and(a, b, c)
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29640
+
+
+
fn unwrap_nested_and(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 1560
+
+
+
Expr::And(metadata, exprs) => {
+
+
+
+
+ 1560
+
+
+
let mut new_exprs = Vec::new();
+
+
+
+
+ 1560
+
+
+
let mut changed = false;
+
+
+
+
+ 345
+
+
+
Expr::And(_, exprs) => {
+
+
+
+
+ 765
+
+
+
new_exprs.push(e.clone());
+
+
+
+
+ 7575
+
+
+
_ => new_exprs.push(e.clone()),
+
+
+
+
+ 1215
+
+
+
return Err(ApplicationError::RuleNotApplicable);
+
+
+
+
+ 345
+
+
+
Ok(Reduction::pure(Expr::And(metadata.clone(), new_exprs)))
+
+
+
+
+ 28080
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Remove double negation:
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29625
+
+
+
fn remove_double_negation(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 15
+
+
+
Expr::Not(_, contents) => match contents.as_ref() {
+
+
+
+
+ 15
+
+
+
Expr::Not(_, expr_box) => Ok(Reduction::pure(*expr_box.clone())),
+
+
+
+
+
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+ 29610
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Remove trivial `and` (only one element):
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29625
+
+
+
fn remove_trivial_and(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 1545
+
+
+
Expr::And(_, exprs) => {
+
+
+
+
+ 1545
+
+
+
if exprs.len() == 1 {
+
+
+
+
+ 15
+
+
+
return Ok(Reduction::pure(exprs[0].clone()));
+
+
+
+
+ 1530
+
+
+
Err(ApplicationError::RuleNotApplicable)
+
+
+
+
+ 28080
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Remove trivial `or` (only one element):
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29625
+
+
+
fn remove_trivial_or(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 2190
+
+
+
Expr::Or(_, exprs) => {
+
+
+
+
+ 2190
+
+
+
if exprs.len() == 1 {
+
+
+
+
+ 15
+
+
+
return Ok(Reduction::pure(exprs[0].clone()));
+
+
+
+
+ 2175
+
+
+
Err(ApplicationError::RuleNotApplicable)
+
+
+
+
+ 27435
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Remove constant bools from or expressions
+
+
+
+
+
+
+
+
* or([true, a]) = true
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29640
+
+
+
fn remove_constants_from_or(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 2205
+
+
+
Expr::Or(metadata, exprs) => {
+
+
+
+
+ 2205
+
+
+
let mut new_exprs = Vec::new();
+
+
+
+
+ 2205
+
+
+
let mut changed = false;
+
+
+
+
+ 15
+
+
+
Expr::Constant(metadata, Const::Bool(val)) => {
+
+
+
+
+
+
+
+
// If we find a true, the whole expression is true
+
+
+
+
+ 15
+
+
+
return Ok(Reduction::pure(Expr::Constant(
+
+
+
+
+
+
+
+
// If we find a false, we can ignore it
+
+
+
+
+ 4380
+
+
+
_ => new_exprs.push(e.clone()),
+
+
+
+
+ 2190
+
+
+
return Err(ApplicationError::RuleNotApplicable);
+
+
+
+
+
+
+
+
Ok(Reduction::pure(Expr::Or(metadata.clone(), new_exprs)))
+
+
+
+
+ 27435
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Remove constant bools from and expressions
+
+
+
+
+ 1
+
+
+
* and([false, a]) = false
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29640
+
+
+
fn remove_constants_from_and(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 1560
+
+
+
Expr::And(metadata, exprs) => {
+
+
+
+
+ 1560
+
+
+
let mut new_exprs = Vec::new();
+
+
+
+
+ 1560
+
+
+
let mut changed = false;
+
+
+
+
+ 30
+
+
+
Expr::Constant(metadata, Const::Bool(val)) => {
+
+
+
+
+
+
+
+
// If we find a false, the whole expression is false
+
+
+
+
+ 15
+
+
+
return Ok(Reduction::pure(Expr::Constant(
+
+
+
+
+ 15
+
+
+
// If we find a true, we can ignore it
+
+
+
+
+ 7890
+
+
+
_ => new_exprs.push(e.clone()),
+
+
+
+
+ 1545
+
+
+
return Err(ApplicationError::RuleNotApplicable);
+
+
+
+
+
+
+
+
Ok(Reduction::pure(Expr::And(metadata.clone(), new_exprs)))
+
+
+
+
+ 28080
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Evaluate Not expressions with constant bools
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29610
+
+
+
fn evaluate_constant_not(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+
+
+
+
Expr::Not(_, contents) => match contents.as_ref() {
+
+
+
+
+
+
+
+
Expr::Constant(metadata, Const::Bool(val)) => Ok(Reduction::pure(Expr::Constant(
+
+
+
+
+
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+ 29610
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
// /** Turn a Div into a SafeDiv and post a global constraint to avoid undefined. */
+
+
+
+
+
+
+
+
// #[register_rule(("Base", 100))]
+
+
+
+
+
+
+
+
// fn ensure_div(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+
+
+
+
// Expr::Div(metadata, a, b) => Ok(Reduction::with_top(
+
+
+
+
+
+
+
+
// Expr::SafeDiv(metadata.clone(), a.clone(), b.clone()),
+
+
+
+
+
+
+
+
// Box::new(Expr::Constant(Metadata::new(), Const::Int(0))),
+
+
+
+
+
+
+
+
// _ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Turn a Min into a new variable and post a global constraint to ensure the new variable is the minimum.
+
+
+
+
+ 1
+
+
+
* min([a, b]) ~> c ; c <= a & c <= b & (c = a | c = b)
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29610
+
+
+
fn min_to_var(expr: &Expr, mdl: &Model) -> ApplicationResult {
+
+
+
+
+ 75
+
+
+
Expr::Min(metadata, exprs) => {
+
+
+
+
+ 75
+
+
+
let new_name = mdl.gensym();
+
+
+
+
+ 75
+
+
+
let mut new_top = Vec::new(); // the new variable must be less than or equal to all the other variables
+
+
+
+
+ 75
+
+
+
let mut disjunction = Vec::new(); // the new variable must be equal to one of the variables
+
+
+
+
+ 150
+
+
+
new_top.push(Expr::Leq(
+
+
+
+
+ 150
+
+
+
Box::new(Expr::Reference(Metadata::new(), new_name.clone())),
+
+
+
+
+ 150
+
+
+
disjunction.push(Expr::And(
+
+
+
+
+ 150
+
+
+
// TODO: change to an Eq once we figure out how to apply them later
+
+
+
+
+ 150
+
+
+
Box::new(Expr::Reference(Metadata::new(), new_name.clone())),
+
+
+
+
+ 150
+
+
+
Box::new(Expr::Reference(Metadata::new(), new_name.clone())),
+
+
+
+
+ 75
+
+
+
new_top.push(Expr::Or(Metadata::new(), disjunction));
+
+
+
+
+ 75
+
+
+
let mut new_vars = SymbolTable::new();
+
+
+
+
+ 75
+
+
+
.bounds(&mdl.variables)
+
+
+
+
+ 75
+
+
+
.ok_or(ApplicationError::BoundError)?;
+
+
+
+
+ 75
+
+
+
DecisionVariable::new(Domain::IntDomain(vec![Range::Bounded(bound.0, bound.1)])),
+
+
+
+
+ 75
+
+
+
Expr::Reference(Metadata::new(), new_name),
+
+
+
+
+ 75
+
+
+
Expr::And(metadata.clone(), new_top),
+
+
+
+
+ 29535
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Apply the Distributive Law to expressions like `Or([..., And(a, b)])`
+
+
+
+
+ 1
+
+
+
* or(and(a, b), c) = and(or(a, c), or(b, c))
+
+
+
+
+
+
+
+
#[register_rule(("Base", 100))]
+
+
+
+
+ 29625
+
+
+
fn distribute_or_over_and(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 29625
+
+
+
fn find_and(exprs: &[Expr]) -> Option<usize> {
+
+
+
+
+ 29625
+
+
+
// ToDo: may be better to move this to some kind of utils module?
+
+
+
+
+ 29625
+
+
+
for (i, e) in exprs.iter().enumerate() {
+
+
+
+
+ 29625
+
+
+
if let Expr::And(_, _) = e {
+
+
+
+
+ 2190
+
+
+
Expr::Or(_, exprs) => match find_and(exprs) {
+
+
+
+
+ 240
+
+
+
let mut rest = exprs.clone();
+
+
+
+
+ 240
+
+
+
let and_expr = rest.remove(idx);
+
+
+
+
+ 240
+
+
+
Expr::And(metadata, and_exprs) => {
+
+
+
+
+ 240
+
+
+
let mut new_and_contents = Vec::new();
+
+
+
+
+
+
+
+
// ToDo: Cloning everything may be a bit inefficient - discuss
+
+
+
+
+ 480
+
+
+
let mut new_or_contents = rest.clone();
+
+
+
+
+ 480
+
+
+
new_or_contents.push(e.clone());
+
+
+
+
+ 480
+
+
+
new_and_contents.push(Expr::Or(metadata.clone(), new_or_contents))
+
+
+
+
+ 240
+
+
+
Ok(Reduction::pure(Expr::And(
+
+
+
+
+
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+ 1950
+
+
+
None => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+ 27435
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/cnf.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/cnf.rs.html
new file mode 100644
index 000000000..e32cd7940
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/cnf.rs.html
@@ -0,0 +1,1001 @@
+
+
+
+
+ Grcov report - cnf.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 13.89 %
+
+
+
+
+
+
+
+
+
+
+
+
+
/***********************************************************************************/
+
+
+
+
+
+
+
+
/* This file contains rules for converting logic expressions to CNF */
+
+
+
+
+
+
+
+
/***********************************************************************************/
+
+
+
+
+
+
+
+
use conjure_core::ast::Expression as Expr;
+
+
+
+
+
+
+
+
use conjure_core::rule_engine::{
+
+
+
+
+
+
+
+
register_rule, register_rule_set, ApplicationError, ApplicationResult, Reduction,
+
+
+
+
+
+
+
+
use conjure_core::solver::SolverFamily;
+
+
+
+
+
+
+
+
use conjure_core::Model;
+
+
+
+
+
+
+
+
register_rule_set!("CNF", 100, ("Base"), (SolverFamily::SAT));
+
+
+
+
+
+
+
+
* Distribute `not` over `and` (De Morgan's Law):
+
+
+
+
+ 1
+
+
+
* not(and(a, b)) = or(not a, not b)
+
+
+
+
+
+
+
+
#[register_rule(("CNF", 100))]
+
+
+
+
+ 255
+
+
+
fn distribute_not_over_and(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 30
+
+
+
Expr::Not(_, contents) => match contents.as_ref() {
+
+
+
+
+ 15
+
+
+
Expr::And(metadata, exprs) => {
+
+
+
+
+ 15
+
+
+
let mut new_exprs = Vec::new();
+
+
+
+
+ 30
+
+
+
new_exprs.push(Expr::Not(metadata.clone(), Box::new(e.clone())));
+
+
+
+
+ 15
+
+
+
Ok(Reduction::pure(Expr::Or(metadata.clone(), new_exprs)))
+
+
+
+
+ 15
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+ 225
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Distribute `not` over `or` (De Morgan's Law):
+
+
+
+
+ 1
+
+
+
* not(or(a, b)) = and(not a, not b)
+
+
+
+
+
+
+
+
#[register_rule(("CNF", 100))]
+
+
+
+
+ 255
+
+
+
fn distribute_not_over_or(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 30
+
+
+
Expr::Not(_, contents) => match contents.as_ref() {
+
+
+
+
+ 15
+
+
+
Expr::Or(metadata, exprs) => {
+
+
+
+
+ 15
+
+
+
let mut new_exprs = Vec::new();
+
+
+
+
+ 30
+
+
+
new_exprs.push(Expr::Not(metadata.clone(), Box::new(e.clone())));
+
+
+
+
+ 15
+
+
+
Ok(Reduction::pure(Expr::And(metadata.clone(), new_exprs)))
+
+
+
+
+ 15
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+ 225
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/constant.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/constant.rs.html
new file mode 100644
index 000000000..b3cde9f04
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/constant.rs.html
@@ -0,0 +1,2041 @@
+
+
+
+
+ Grcov report - constant.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 16.22 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use conjure_core::ast::{Constant as Const, Expression as Expr};
+
+
+
+
+
+
+
+
use conjure_core::metadata::Metadata;
+
+
+
+
+
+
+
+
use conjure_core::rule_engine::{
+
+
+
+
+
+
+
+
register_rule, register_rule_set, ApplicationError, ApplicationResult, Reduction,
+
+
+
+
+
+
+
+
use conjure_core::Model;
+
+
+
+
+
+
+
+
register_rule_set!("Constant", 255, ());
+
+
+
+
+
+
+
+
#[register_rule(("Constant", 255))]
+
+
+
+
+ 29610
+
+
+
fn apply_eval_constant(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 29610
+
+
+
if expr.is_constant() {
+
+
+
+
+ 7200
+
+
+
return Err(ApplicationError::RuleNotApplicable);
+
+
+
+
+ 22410
+
+
+
.map(|c| Reduction::pure(Expr::Constant(Metadata::new(), c)))
+
+
+
+
+ 22410
+
+
+
.ok_or(ApplicationError::RuleNotApplicable)
+
+
+
+
+
+
+
+
/// Simplify an expression to a constant if possible
+
+
+
+
+
+
+
+
/// `None` if the expression cannot be simplified to a constant (e.g. if it contains a variable)
+
+
+
+
+
+
+
+
/// `Some(Const)` if the expression can be simplified to a constant
+
+
+
+
+ 39255
+
+
+
pub fn eval_constant(expr: &Expr) -> Option<Const> {
+
+
+
+
+ 1260
+
+
+
Expr::Constant(_, c) => Some(c.clone()),
+
+
+
+
+ 22440
+
+
+
Expr::Reference(_, _) => None,
+
+
+
+
+ 150
+
+
+
Expr::Eq(_, a, b) => bin_op::<i32, bool>(|a, b| a == b, a, b)
+
+
+
+
+ 150
+
+
+
.or_else(|| bin_op::<bool, bool>(|a, b| a == b, a, b))
+
+
+
+
+ 30
+
+
+
Expr::Neq(_, a, b) => bin_op::<i32, bool>(|a, b| a != b, a, b).map(Const::Bool),
+
+
+
+
+ 15
+
+
+
Expr::Lt(_, a, b) => bin_op::<i32, bool>(|a, b| a < b, a, b).map(Const::Bool),
+
+
+
+
+
+
+
+
Expr::Gt(_, a, b) => bin_op::<i32, bool>(|a, b| a > b, a, b).map(Const::Bool),
+
+
+
+
+ 870
+
+
+
Expr::Leq(_, a, b) => bin_op::<i32, bool>(|a, b| a <= b, a, b).map(Const::Bool),
+
+
+
+
+ 495
+
+
+
Expr::Geq(_, a, b) => bin_op::<i32, bool>(|a, b| a >= b, a, b).map(Const::Bool),
+
+
+
+
+
+
+
+
Expr::Not(_, expr) => un_op::<bool, bool>(|e| !e, expr).map(Const::Bool),
+
+
+
+
+ 1830
+
+
+
Expr::And(_, exprs) => {
+
+
+
+
+ 1830
+
+
+
vec_op::<bool, bool>(|e| e.iter().all(|&e| e), exprs).map(Const::Bool)
+
+
+
+
+ 2190
+
+
+
Expr::Or(_, exprs) => {
+
+
+
+
+ 2190
+
+
+
vec_op::<bool, bool>(|e| e.iter().any(|&e| e), exprs).map(Const::Bool)
+
+
+
+
+ 330
+
+
+
Expr::Sum(_, exprs) => vec_op::<i32, i32>(|e| e.iter().sum(), exprs).map(Const::Int),
+
+
+
+
+ 9045
+
+
+
Expr::Ineq(_, a, b, c) => {
+
+
+
+
+ 9045
+
+
+
tern_op::<i32, bool>(|a, b, c| a <= (b + c), a, b, c).map(Const::Bool)
+
+
+
+
+ 180
+
+
+
Expr::SumGeq(_, exprs, a) => {
+
+
+
+
+ 180
+
+
+
flat_op::<i32, bool>(|e, a| e.iter().sum::<i32>() >= a, exprs, a).map(Const::Bool)
+
+
+
+
+ 75
+
+
+
Expr::SumLeq(_, exprs, a) => {
+
+
+
+
+ 75
+
+
+
flat_op::<i32, bool>(|e, a| e.iter().sum::<i32>() <= a, exprs, a).map(Const::Bool)
+
+
+
+
+
+
+
+
// Expr::Div(_, a, b) => bin_op::<i32, i32>(|a, b| a / b, a, b).map(Const::Int),
+
+
+
+
+
+
+
+
// Expr::SafeDiv(_, a, b) => bin_op::<i32, i32>(|a, b| a / b, a, b).map(Const::Int),
+
+
+
+
+ 225
+
+
+
Expr::Min(_, exprs) => {
+
+
+
+
+ 225
+
+
+
opt_vec_op::<i32, i32>(|e| e.iter().min().copied(), exprs).map(Const::Int)
+
+
+
+
+ 120
+
+
+
println!("WARNING: Unimplemented constant eval: {:?}", expr);
+
+
+
+
+
+
+
+
fn un_op<T, A>(f: fn(T) -> A, a: &Expr) -> Option<A>
+
+
+
+
+
+
+
+
let a = unwrap_expr::<T>(a)?;
+
+
+
+
+ 1695
+
+
+
fn bin_op<T, A>(f: fn(T, T) -> A, a: &Expr, b: &Expr) -> Option<A>
+
+
+
+
+ 1695
+
+
+
let a = unwrap_expr::<T>(a)?;
+
+
+
+
+ 45
+
+
+
let b = unwrap_expr::<T>(b)?;
+
+
+
+
+ 9045
+
+
+
fn tern_op<T, A>(f: fn(T, T, T) -> A, a: &Expr, b: &Expr, c: &Expr) -> Option<A>
+
+
+
+
+ 9045
+
+
+
let a = unwrap_expr::<T>(a)?;
+
+
+
+
+ 990
+
+
+
let b = unwrap_expr::<T>(b)?;
+
+
+
+
+
+
+
+
let c = unwrap_expr::<T>(c)?;
+
+
+
+
+ 4350
+
+
+
fn vec_op<T, A>(f: fn(Vec<T>) -> A, a: &[Expr]) -> Option<A>
+
+
+
+
+ 4350
+
+
+
let a = a.iter().map(unwrap_expr).collect::<Option<Vec<T>>>()?;
+
+
+
+
+ 225
+
+
+
fn opt_vec_op<T, A>(f: fn(Vec<T>) -> Option<A>, a: &[Expr]) -> Option<A>
+
+
+
+
+ 225
+
+
+
let a = a.iter().map(unwrap_expr).collect::<Option<Vec<T>>>()?;
+
+
+
+
+ 255
+
+
+
fn flat_op<T, A>(f: fn(Vec<T>, T) -> A, a: &[Expr], b: &Expr) -> Option<A>
+
+
+
+
+ 255
+
+
+
let a = a.iter().map(unwrap_expr).collect::<Option<Vec<T>>>()?;
+
+
+
+
+
+
+
+
let b = unwrap_expr::<T>(b)?;
+
+
+
+
+ 16680
+
+
+
fn unwrap_expr<T: TryFrom<Const>>(expr: &Expr) -> Option<T> {
+
+
+
+
+ 16680
+
+
+
let c = eval_constant(expr)?;
+
+
+
+
+ 1230
+
+
+
TryInto::<T>::try_into(c).ok()
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/index.html
new file mode 100644
index 000000000..46298ac45
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/index.html
@@ -0,0 +1,146 @@
+
+
+
+
+ Grcov report - crates/conjure_core/src/rules
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 15.42 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ base.rs
+
+
+
+ 82.72%
+
+
+
+ 82.72%
+
+
+ 292 / 353
+
+
+ 15.42%
+ 33 / 214
+
+
+
+
+
+ cnf.rs
+
+
+
+ 90.91%
+
+
+
+ 90.91%
+
+
+ 30 / 33
+
+
+ 13.89%
+ 5 / 36
+
+
+
+
+
+ constant.rs
+
+
+
+ 82.02%
+
+
+
+ 82.02%
+
+
+ 73 / 89
+
+
+ 16.22%
+ 18 / 111
+
+
+
+
+
+ minion.rs
+
+
+
+ 86.5%
+
+
+
+ 86.5%
+
+
+ 141 / 163
+
+
+ 15.17%
+ 22 / 145
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/minion.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/minion.rs.html
new file mode 100644
index 000000000..c7c716840
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/rules/minion.rs.html
@@ -0,0 +1,4793 @@
+
+
+
+
+ Grcov report - minion.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 15.17 %
+
+
+
+
+
+
+
+
+
+
+
+
+
/************************************************************************/
+
+
+
+
+
+
+
+
/* Rules for translating to Minion-supported constraints */
+
+
+
+
+
+
+
+
/************************************************************************/
+
+
+
+
+
+
+
+
use crate::ast::{Constant as Const, Expression as Expr};
+
+
+
+
+
+
+
+
use crate::metadata::Metadata;
+
+
+
+
+
+
+
+
use crate::rule_engine::{
+
+
+
+
+
+
+
+
register_rule, register_rule_set, ApplicationError, ApplicationResult, Reduction,
+
+
+
+
+
+
+
+
use crate::solver::SolverFamily;
+
+
+
+
+
+
+
+
register_rule_set!("Minion", 100, ("Base"), (SolverFamily::Minion));
+
+
+
+
+ 120
+
+
+
fn is_nested_sum(exprs: &Vec<Expr>) -> bool {
+
+
+
+
+ 285
+
+
+
if let Expr::Sum(_, _) = e {
+
+
+
+
+
+
+
+
* Helper function to get the vector of expressions from a sum (or error if it's a nested sum - we need to flatten it first)
+
+
+
+
+ 975
+
+
+
fn sum_to_vector(expr: &Expr) -> Result<Vec<Expr>, ApplicationError> {
+
+
+
+
+ 120
+
+
+
Expr::Sum(_, exprs) => {
+
+
+
+
+ 120
+
+
+
if is_nested_sum(exprs) {
+
+
+
+
+ 30
+
+
+
Err(ApplicationError::RuleNotApplicable)
+
+
+
+
+ 855
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
// * Convert an Eq to a conjunction of Geq and Leq:
+
+
+
+
+
+
+
+
// * a = b => a >= b && a <= b
+
+
+
+
+
+
+
+
// #[register_rule(("Minion", 100))]
+
+
+
+
+
+
+
+
// fn eq_to_minion(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+
+
+
+
// Expr::Eq(metadata, a, b) => Ok(Reduction::pure(Expr::And(
+
+
+
+
+
+
+
+
// Expr::Geq(metadata.clone(), a.clone(), b.clone()),
+
+
+
+
+
+
+
+
// Expr::Leq(metadata.clone(), a.clone(), b.clone()),
+
+
+
+
+
+
+
+
// _ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Convert a Geq to a SumGeq if the left hand side is a sum:
+
+
+
+
+ 1
+
+
+
* sum([a, b, c]) >= d => sum_geq([a, b, c], d)
+
+
+
+
+
+
+
+
#[register_rule(("Minion", 100))]
+
+
+
+
+ 29625
+
+
+
fn flatten_sum_geq(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 360
+
+
+
Expr::Geq(metadata, a, b) => {
+
+
+
+
+ 360
+
+
+
let exprs = sum_to_vector(a)?;
+
+
+
+
+ 15
+
+
+
Ok(Reduction::pure(Expr::SumGeq(
+
+
+
+
+ 29265
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Convert a Leq to a SumLeq if the left hand side is a sum:
+
+
+
+
+ 1
+
+
+
* sum([a, b, c]) <= d => sum_leq([a, b, c], d)
+
+
+
+
+
+
+
+
#[register_rule(("Minion", 100))]
+
+
+
+
+ 29625
+
+
+
fn sum_leq_to_sumleq(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 510
+
+
+
Expr::Leq(metadata, a, b) => {
+
+
+
+
+ 510
+
+
+
let exprs = sum_to_vector(a)?;
+
+
+
+
+ 15
+
+
+
Ok(Reduction::pure(Expr::SumLeq(
+
+
+
+
+ 29115
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Convert a 'Eq(Sum([...]))' to a SumEq
+
+
+
+
+ 1
+
+
+
* eq(sum([a, b]), c) => sumeq([a, b], c)
+
+
+
+
+
+
+
+
#[register_rule(("Minion", 100))]
+
+
+
+
+ 29610
+
+
+
fn sum_eq_to_sumeq(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 45
+
+
+
Expr::Eq(metadata, a, b) => {
+
+
+
+
+ 45
+
+
+
let exprs = sum_to_vector(a)?;
+
+
+
+
+ 30
+
+
+
Ok(Reduction::pure(Expr::SumEq(
+
+
+
+
+ 29565
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Convert a `SumEq` to an `And(SumGeq, SumLeq)`
+
+
+
+
+
+
+
+
* This is a workaround for Minion not having support for a flat "equals" operation on sums
+
+
+
+
+
+
+
+
* sumeq([a, b], c) -> watched_and({
+
+
+
+
+
+
+
+
* ((a + b) >= c) && ((a + b) <= c)
+
+
+
+
+
+
+
+
#[register_rule(("Minion", 100))]
+
+
+
+
+ 29610
+
+
+
fn sumeq_to_minion(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 30
+
+
+
Expr::SumEq(metadata, exprs, eq_to) => Ok(Reduction::pure(Expr::And(
+
+
+
+
+ 30
+
+
+
Expr::SumGeq(metadata.clone(), exprs.clone(), Box::from(*eq_to.clone())),
+
+
+
+
+ 30
+
+
+
Expr::SumLeq(metadata.clone(), exprs.clone(), Box::from(*eq_to.clone())),
+
+
+
+
+ 29580
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Convert a Lt to an Ineq:
+
+
+
+
+ 1
+
+
+
* a < b => a - b < -1
+
+
+
+
+
+
+
+
#[register_rule(("Minion", 100))]
+
+
+
+
+ 29625
+
+
+
fn lt_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 30
+
+
+
Expr::Lt(metadata, a, b) => Ok(Reduction::pure(Expr::Ineq(
+
+
+
+
+ 30
+
+
+
Box::new(Expr::Constant(Metadata::new(), Const::Int(-1))),
+
+
+
+
+ 29595
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Convert a Gt to an Ineq:
+
+
+
+
+ 1
+
+
+
* a > b => b - a < -1
+
+
+
+
+
+
+
+
#[register_rule(("Minion", 100))]
+
+
+
+
+ 29610
+
+
+
fn gt_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+
+
+
+
Expr::Gt(metadata, a, b) => Ok(Reduction::pure(Expr::Ineq(
+
+
+
+
+
+
+
+
Box::new(Expr::Constant(Metadata::new(), Const::Int(-1))),
+
+
+
+
+ 29610
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Convert a Geq to an Ineq:
+
+
+
+
+ 1
+
+
+
* a >= b => b - a < 0
+
+
+
+
+
+
+
+
#[register_rule(("Minion", 100))]
+
+
+
+
+ 29610
+
+
+
fn geq_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 345
+
+
+
Expr::Geq(metadata, a, b) => Ok(Reduction::pure(Expr::Ineq(
+
+
+
+
+ 345
+
+
+
Box::new(Expr::Constant(Metadata::new(), Const::Int(0))),
+
+
+
+
+ 29265
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
* Convert a Leq to an Ineq:
+
+
+
+
+ 1
+
+
+
* a <= b => a - b < 0
+
+
+
+
+
+
+
+
#[register_rule(("Minion", 100))]
+
+
+
+
+ 29610
+
+
+
fn leq_to_ineq(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 495
+
+
+
Expr::Leq(metadata, a, b) => Ok(Reduction::pure(Expr::Ineq(
+
+
+
+
+ 495
+
+
+
Box::new(Expr::Constant(Metadata::new(), Const::Int(0))),
+
+
+
+
+ 29115
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
// #[register_rule(("Minion", 100))]
+
+
+
+
+
+
+
+
// fn safediv_eq_to_diveq(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+
+
+
+
// Expr::Eq(metadata, a, b) => {
+
+
+
+
+
+
+
+
// if let Expr::SafeDiv(_, x, y) = a.as_ref() {
+
+
+
+
+
+
+
+
// if !(b.is_reference() || b.is_constant()) {
+
+
+
+
+
+
+
+
// return Err(ApplicationError::RuleNotApplicable);
+
+
+
+
+
+
+
+
// Ok(Reduction::pure(Expr::DivEq(
+
+
+
+
+
+
+
+
// } else if let Expr::SafeDiv(_, x, y) = b.as_ref() {
+
+
+
+
+
+
+
+
// if !(a.is_reference() || a.is_constant()) {
+
+
+
+
+
+
+
+
// return Err(ApplicationError::RuleNotApplicable);
+
+
+
+
+
+
+
+
// Ok(Reduction::pure(Expr::DivEq(
+
+
+
+
+
+
+
+
// Err(ApplicationError::RuleNotApplicable)
+
+
+
+
+
+
+
+
// _ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
#[register_rule(("Minion", 100))]
+
+
+
+
+ 29610
+
+
+
fn neq_to_alldiff(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 30
+
+
+
Expr::Neq(metadata, a, b) => Ok(Reduction::pure(Expr::AllDiff(
+
+
+
+
+ 30
+
+
+
vec![*a.clone(), *b.clone()],
+
+
+
+
+ 29580
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
+
+
#[register_rule(("Minion", 99))]
+
+
+
+
+ 29610
+
+
+
fn eq_to_leq_geq(expr: &Expr, _: &Model) -> ApplicationResult {
+
+
+
+
+ 45
+
+
+
Expr::Eq(metadata, a, b) => {
+
+
+
+
+ 45
+
+
+
if let Ok(exprs) = sum_to_vector(a) {
+
+
+
+
+ 30
+
+
+
Ok(Reduction::pure(Expr::SumEq(
+
+
+
+
+ 15
+
+
+
} else if let Ok(exprs) = sum_to_vector(b) {
+
+
+
+
+
+
+
+
Ok(Reduction::pure(Expr::SumEq(
+
+
+
+
+ 15
+
+
+
Err(ApplicationError::RuleNotApplicable)
+
+
+
+
+ 29565
+
+
+
_ => Err(ApplicationError::RuleNotApplicable),
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/adaptors/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/adaptors/index.html
new file mode 100644
index 000000000..84cd4f67f
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/adaptors/index.html
@@ -0,0 +1,122 @@
+
+
+
+
+ Grcov report - crates/conjure_core/src/solver/adaptors
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 11.73 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ kissat.rs
+
+
+
+ 0%
+
+
+
+ 0%
+
+
+ 0 / 30
+
+
+ 0%
+ 0 / 18
+
+
+
+
+
+ minion.rs
+
+
+
+ 83.68%
+
+
+
+ 83.68%
+
+
+ 200 / 239
+
+
+ 24.36%
+ 19 / 78
+
+
+
+
+
+ sat_common.rs
+
+
+
+ 0%
+
+
+
+ 0%
+
+
+ 0 / 158
+
+
+ 0%
+ 0 / 66
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/adaptors/kissat.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/adaptors/kissat.rs.html
new file mode 100644
index 000000000..562bc5327
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/adaptors/kissat.rs.html
@@ -0,0 +1,1049 @@
+
+
+
+
+ Grcov report - kissat.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use crate::solver::{SolveSuccess, SolverCallback, SolverFamily, SolverMutCallback};
+
+
+
+
+
+
+
+
use crate::Model as ConjureModel;
+
+
+
+
+
+
+
+
use super::super::model_modifier::NotModifiable;
+
+
+
+
+
+
+
+
use super::super::private;
+
+
+
+
+
+
+
+
use super::super::SearchComplete::*;
+
+
+
+
+
+
+
+
use super::super::SearchIncomplete::*;
+
+
+
+
+
+
+
+
use super::super::SearchStatus::*;
+
+
+
+
+
+
+
+
use super::super::SolverAdaptor;
+
+
+
+
+
+
+
+
use super::super::SolverError;
+
+
+
+
+
+
+
+
use super::super::SolverError::*;
+
+
+
+
+
+
+
+
use super::super::SolverError::*;
+
+
+
+
+
+
+
+
use super::sat_common::CNFModel;
+
+
+
+
+
+
+
+
/// A [SolverAdaptor] for interacting with the Kissat SAT solver.
+
+
+
+
+
+
+
+
__non_constructable: private::Internal,
+
+
+
+
+
+
+
+
model: Option<CNFModel>,
+
+
+
+
+
+
+
+
impl private::Sealed for Kissat {}
+
+
+
+
+
+
+
+
pub fn new() -> Self {
+
+
+
+
+
+
+
+
__non_constructable: private::Internal,
+
+
+
+
+
+
+
+
impl Default for Kissat {
+
+
+
+
+
+
+
+
fn default() -> Self {
+
+
+
+
+
+
+
+
impl SolverAdaptor for Kissat {
+
+
+
+
+
+
+
+
callback: SolverCallback,
+
+
+
+
+
+
+
+
_: private::Internal,
+
+
+
+
+
+
+
+
) -> Result<SolveSuccess, SolverError> {
+
+
+
+
+
+
+
+
Err(OpNotImplemented("solve(): todo!".to_owned()))
+
+
+
+
+
+
+
+
callback: SolverMutCallback,
+
+
+
+
+
+
+
+
_: private::Internal,
+
+
+
+
+
+
+
+
) -> Result<SolveSuccess, SolverError> {
+
+
+
+
+
+
+
+
Err(OpNotSupported("solve_mut".to_owned()))
+
+
+
+
+
+
+
+
fn load_model(&mut self, model: ConjureModel, _: private::Internal) -> Result<(), SolverError> {
+
+
+
+
+
+
+
+
self.model = Some(CNFModel::from_conjure(model)?);
+
+
+
+
+
+
+
+
fn get_family(&self) -> SolverFamily {
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/adaptors/minion.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/adaptors/minion.rs.html
new file mode 100644
index 000000000..5cff4ad53
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/adaptors/minion.rs.html
@@ -0,0 +1,5561 @@
+
+
+
+
+ Grcov report - minion.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 24.36 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::collections::HashMap;
+
+
+
+
+
+
+
+
use std::sync::{Mutex, OnceLock};
+
+
+
+
+
+
+
+
use minion_ast::Model as MinionModel;
+
+
+
+
+
+
+
+
use minion_rs::ast as minion_ast;
+
+
+
+
+
+
+
+
use minion_rs::error::MinionError;
+
+
+
+
+
+
+
+
use minion_rs::run_minion;
+
+
+
+
+
+
+
+
use crate::ast as conjure_ast;
+
+
+
+
+
+
+
+
use crate::solver::SolverCallback;
+
+
+
+
+
+
+
+
use crate::solver::SolverFamily;
+
+
+
+
+
+
+
+
use crate::solver::SolverMutCallback;
+
+
+
+
+
+
+
+
use crate::Model as ConjureModel;
+
+
+
+
+
+
+
+
use super::super::model_modifier::NotModifiable;
+
+
+
+
+
+
+
+
use super::super::private;
+
+
+
+
+
+
+
+
use super::super::SearchComplete::*;
+
+
+
+
+
+
+
+
use super::super::SearchIncomplete::*;
+
+
+
+
+
+
+
+
use super::super::SearchStatus::*;
+
+
+
+
+
+
+
+
use super::super::SolveSuccess;
+
+
+
+
+
+
+
+
use super::super::SolverAdaptor;
+
+
+
+
+
+
+
+
use super::super::SolverError;
+
+
+
+
+
+
+
+
use super::super::SolverError::*;
+
+
+
+
+
+
+
+
/// A [SolverAdaptor] for interacting with Minion.
+
+
+
+
+
+
+
+
/// This adaptor uses the `minion_rs` crate to talk to Minion over FFI.
+
+
+
+
+
+
+
+
__non_constructable: private::Internal,
+
+
+
+
+
+
+
+
model: Option<MinionModel>,
+
+
+
+
+
+
+
+
static MINION_LOCK: Mutex<()> = Mutex::new(());
+
+
+
+
+
+
+
+
static USER_CALLBACK: OnceLock<Mutex<SolverCallback>> = OnceLock::new();
+
+
+
+
+
+
+
+
static ANY_SOLUTIONS: Mutex<bool> = Mutex::new(false);
+
+
+
+
+
+
+
+
static USER_TERMINATED: Mutex<bool> = Mutex::new(false);
+
+
+
+
+
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+ 585
+
+
+
fn minion_rs_callback(solutions: HashMap<minion_ast::VarName, minion_ast::Constant>) -> bool {
+
+
+
+
+ 585
+
+
+
*(ANY_SOLUTIONS.lock().unwrap()) = true;
+
+
+
+
+ 585
+
+
+
let callback = USER_CALLBACK
+
+
+
+
+ 585
+
+
+
.get_or_init(|| Mutex::new(Box::new(|x| true)))
+
+
+
+
+ 585
+
+
+
let mut conjure_solutions: HashMap<conjure_ast::Name, conjure_ast::Constant> = HashMap::new();
+
+
+
+
+ 1575
+
+
+
for (minion_name, minion_const) in solutions.into_iter() {
+
+
+
+
+ 1575
+
+
+
let conjure_const = match minion_const {
+
+
+
+
+
+
+
+
minion_ast::Constant::Bool(x) => conjure_ast::Constant::Bool(x),
+
+
+
+
+ 1575
+
+
+
minion_ast::Constant::Integer(x) => conjure_ast::Constant::Int(x),
+
+
+
+
+ 1575
+
+
+
let machine_name_re = Regex::new(r"__conjure_machine_name_([0-9]+)").unwrap();
+
+
+
+
+ 1575
+
+
+
let conjure_name = if let Some(caps) = machine_name_re.captures(&minion_name) {
+
+
+
+
+ 375
+
+
+
conjure_ast::Name::MachineName(caps[1].parse::<i32>().unwrap())
+
+
+
+
+ 1200
+
+
+
conjure_ast::Name::UserName(minion_name)
+
+
+
+
+ 1575
+
+
+
conjure_solutions.insert(conjure_name, conjure_const);
+
+
+
+
+ 585
+
+
+
let continue_search = (**callback)(conjure_solutions);
+
+
+
+
+ 585
+
+
+
if !continue_search {
+
+
+
+
+
+
+
+
*(USER_TERMINATED.lock().unwrap()) = true;
+
+
+
+
+
+
+
+
impl private::Sealed for Minion {}
+
+
+
+
+ 180
+
+
+
pub fn new() -> Minion {
+
+
+
+
+ 180
+
+
+
__non_constructable: private::Internal,
+
+
+
+
+
+
+
+
impl Default for Minion {
+
+
+
+
+
+
+
+
fn default() -> Self {
+
+
+
+
+
+
+
+
impl SolverAdaptor for Minion {
+
+
+
+
+
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+ 180
+
+
+
callback: SolverCallback,
+
+
+
+
+ 180
+
+
+
_: private::Internal,
+
+
+
+
+ 180
+
+
+
) -> Result<SolveSuccess, SolverError> {
+
+
+
+
+ 180
+
+
+
// our minion callback is global state, so single threading the adaptor as a whole is
+
+
+
+
+ 180
+
+
+
// probably a good move...
+
+
+
+
+ 180
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+ 180
+
+
+
let mut minion_lock = MINION_LOCK.lock().unwrap();
+
+
+
+
+ 180
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+ 180
+
+
+
let mut user_callback = USER_CALLBACK
+
+
+
+
+ 180
+
+
+
.get_or_init(|| Mutex::new(Box::new(|x| true)))
+
+
+
+
+ 180
+
+
+
*user_callback = callback;
+
+
+
+
+ 180
+
+
+
drop(user_callback); // release mutex. REQUIRED so that run_minion can use the
+
+
+
+
+ 180
+
+
+
// user callback and not deadlock.
+
+
+
+
+ 180
+
+
+
self.model.clone().expect("STATE MACHINE ERR"),
+
+
+
+
+ 180
+
+
+
.map_err(|err| match err {
+
+
+
+
+
+
+
+
MinionError::RuntimeError(x) => Runtime(format!("{:#?}", x)),
+
+
+
+
+
+
+
+
MinionError::Other(x) => Runtime(format!("{:#?}", x)),
+
+
+
+
+
+
+
+
MinionError::NotImplemented(x) => RuntimeNotImplemented(x),
+
+
+
+
+
+
+
+
x => Runtime(format!("unknown minion_rs error: {:#?}", x)),
+
+
+
+
+ 180
+
+
+
let mut status = Complete(HasSolutions);
+
+
+
+
+ 180
+
+
+
if *(USER_TERMINATED.lock()).unwrap() {
+
+
+
+
+
+
+
+
status = Incomplete(UserTerminated);
+
+
+
+
+ 180
+
+
+
} else if *(ANY_SOLUTIONS.lock()).unwrap() {
+
+
+
+
+ 180
+
+
+
status = Complete(NoSolutions);
+
+
+
+
+ 180
+
+
+
stats: Default::default(),
+
+
+
+
+
+
+
+
callback: SolverMutCallback,
+
+
+
+
+
+
+
+
_: private::Internal,
+
+
+
+
+
+
+
+
) -> Result<SolveSuccess, SolverError> {
+
+
+
+
+
+
+
+
Err(OpNotImplemented("solve_mut".into()))
+
+
+
+
+ 180
+
+
+
fn load_model(&mut self, model: ConjureModel, _: private::Internal) -> Result<(), SolverError> {
+
+
+
+
+ 180
+
+
+
let mut minion_model = MinionModel::new();
+
+
+
+
+ 180
+
+
+
parse_vars(&model, &mut minion_model)?;
+
+
+
+
+ 180
+
+
+
parse_exprs(&model, &mut minion_model)?;
+
+
+
+
+ 180
+
+
+
self.model = Some(minion_model);
+
+
+
+
+
+
+
+
fn get_family(&self) -> SolverFamily {
+
+
+
+
+ 180
+
+
+
conjure_model: &ConjureModel,
+
+
+
+
+ 180
+
+
+
minion_model: &mut MinionModel,
+
+
+
+
+ 180
+
+
+
) -> Result<(), SolverError> {
+
+
+
+
+
+
+
+
// TODO (niklasdewally): remove unused vars?
+
+
+
+
+
+
+
+
// TODO (niklasdewally): ensure all vars references are used.
+
+
+
+
+ 465
+
+
+
for (name, variable) in conjure_model.variables.iter() {
+
+
+
+
+ 465
+
+
+
parse_var(name, variable, minion_model)?;
+
+
+
+
+ 465
+
+
+
name: &conjure_ast::Name,
+
+
+
+
+ 465
+
+
+
var: &conjure_ast::DecisionVariable,
+
+
+
+
+ 465
+
+
+
minion_model: &mut MinionModel,
+
+
+
+
+ 465
+
+
+
) -> Result<(), SolverError> {
+
+
+
+
+ 360
+
+
+
conjure_ast::Domain::IntDomain(ranges) => _parse_intdomain_var(name, ranges, minion_model),
+
+
+
+
+ 105
+
+
+
conjure_ast::Domain::BoolDomain => _parse_booldomain_var(name, minion_model),
+
+
+
+
+
+
+
+
x => Err(ModelFeatureNotSupported(format!("{:?}", x))),
+
+
+
+
+ 360
+
+
+
fn _parse_intdomain_var(
+
+
+
+
+ 360
+
+
+
name: &conjure_ast::Name,
+
+
+
+
+ 360
+
+
+
ranges: &[conjure_ast::Range<i32>],
+
+
+
+
+ 360
+
+
+
minion_model: &mut MinionModel,
+
+
+
+
+ 360
+
+
+
) -> Result<(), SolverError> {
+
+
+
+
+ 360
+
+
+
let str_name = _name_to_string(name.to_owned());
+
+
+
+
+ 360
+
+
+
if ranges.len() != 1 {
+
+
+
+
+
+
+
+
return Err(ModelFeatureNotImplemented(format!(
+
+
+
+
+
+
+
+
"variable {:?} has {:?} ranges. Multiple ranges / SparseBound is not yet supported.",
+
+
+
+
+ 360
+
+
+
let range = ranges.first().ok_or(ModelInvalid(format!(
+
+
+
+
+ 360
+
+
+
"variable {:?} has no range",
+
+
+
+
+ 360
+
+
+
let (low, high) = match range {
+
+
+
+
+ 360
+
+
+
conjure_ast::Range::Bounded(x, y) => Ok((x.to_owned(), y.to_owned())),
+
+
+
+
+
+
+
+
conjure_ast::Range::Single(x) => Ok((x.to_owned(), x.to_owned())),
+
+
+
+
+
+
+
+
#[allow(unreachable_patterns)]
+
+
+
+
+
+
+
+
x => Err(ModelFeatureNotSupported(format!("{:?}", x))),
+
+
+
+
+ 360
+
+
+
minion_ast::VarDomain::Bound(low, high),
+
+
+
+
+ 105
+
+
+
fn _parse_booldomain_var(
+
+
+
+
+ 105
+
+
+
name: &conjure_ast::Name,
+
+
+
+
+ 105
+
+
+
minion_model: &mut MinionModel,
+
+
+
+
+ 105
+
+
+
) -> Result<(), SolverError> {
+
+
+
+
+ 105
+
+
+
let str_name = _name_to_string(name.to_owned());
+
+
+
+
+ 105
+
+
+
minion_ast::VarDomain::Bool,
+
+
+
+
+ 465
+
+
+
name: minion_ast::VarName,
+
+
+
+
+ 465
+
+
+
domain: minion_ast::VarDomain,
+
+
+
+
+ 465
+
+
+
minion_model: &mut MinionModel,
+
+
+
+
+ 465
+
+
+
) -> Result<(), SolverError> {
+
+
+
+
+ 465
+
+
+
.add_var(name.clone(), domain)
+
+
+
+
+ 465
+
+
+
.ok_or(ModelInvalid(format!(
+
+
+
+
+ 465
+
+
+
"variable {:?} is defined twice",
+
+
+
+
+ 180
+
+
+
conjure_model: &ConjureModel,
+
+
+
+
+ 180
+
+
+
minion_model: &mut MinionModel,
+
+
+
+
+ 180
+
+
+
) -> Result<(), SolverError> {
+
+
+
+
+ 675
+
+
+
for expr in conjure_model.get_constraints_vec().iter() {
+
+
+
+
+ 675
+
+
+
parse_expr(expr.to_owned(), minion_model)?;
+
+
+
+
+ 675
+
+
+
expr: conjure_ast::Expression,
+
+
+
+
+ 675
+
+
+
minion_model: &mut MinionModel,
+
+
+
+
+ 675
+
+
+
) -> Result<(), SolverError> {
+
+
+
+
+ 675
+
+
+
minion_model.constraints.push(read_expr(expr)?);
+
+
+
+
+ 1275
+
+
+
fn read_expr(expr: conjure_ast::Expression) -> Result<minion_ast::Constraint, SolverError> {
+
+
+
+
+ 45
+
+
+
conjure_ast::Expression::SumLeq(_metadata, lhs, rhs) => Ok(minion_ast::Constraint::SumLeq(
+
+
+
+
+ 30
+
+
+
conjure_ast::Expression::SumGeq(_metadata, lhs, rhs) => Ok(minion_ast::Constraint::SumGeq(
+
+
+
+
+ 870
+
+
+
conjure_ast::Expression::Ineq(_metadata, a, b, c) => Ok(minion_ast::Constraint::Ineq(
+
+
+
+
+ 870
+
+
+
minion_ast::Constant::Integer(read_const(*c)?),
+
+
+
+
+
+
+
+
conjure_ast::Expression::Neq(_metadata, a, b) => Ok(minion_ast::Constraint::AllDiff(vec![
+
+
+
+
+
+
+
+
// conjure_ast::Expression::DivEq(_metadata, a, b, c) => {
+
+
+
+
+
+
+
+
// minion_model.constraints.push(minion_ast::Constraint::Div(
+
+
+
+
+
+
+
+
// (read_var(*a)?, read_var(*b)?),
+
+
+
+
+ 30
+
+
+
conjure_ast::Expression::AllDiff(_metadata, vars) => {
+
+
+
+
+ 30
+
+
+
Ok(minion_ast::Constraint::AllDiff(read_vars(vars)?))
+
+
+
+
+ 300
+
+
+
conjure_ast::Expression::Or(_metadata, exprs) => Ok(minion_ast::Constraint::WatchedOr(
+
+
+
+
+ 600
+
+
+
.map(|x| read_expr(x.to_owned()))
+
+
+
+
+ 300
+
+
+
.collect::<Result<Vec<minion_ast::Constraint>, SolverError>>()?,
+
+
+
+
+
+
+
+
x => Err(ModelFeatureNotSupported(format!("{:?}", x))),
+
+
+
+
+ 105
+
+
+
fn read_vars(exprs: Vec<conjure_ast::Expression>) -> Result<Vec<minion_ast::Var>, SolverError> {
+
+
+
+
+ 105
+
+
+
let mut minion_vars: Vec<minion_ast::Var> = vec![];
+
+
+
+
+ 285
+
+
+
let minion_var = read_var(expr)?;
+
+
+
+
+ 285
+
+
+
minion_vars.push(minion_var);
+
+
+
+
+ 2100
+
+
+
fn read_var(e: conjure_ast::Expression) -> Result<minion_ast::Var, SolverError> {
+
+
+
+
+ 2100
+
+
+
// a minion var is either a reference or a "var as const"
+
+
+
+
+ 2100
+
+
+
match _read_ref(e.clone()) {
+
+
+
+
+ 1950
+
+
+
Ok(name) => Ok(minion_ast::Var::NameRef(name)),
+
+
+
+
+ 150
+
+
+
Err(_) => match read_const(e) {
+
+
+
+
+ 150
+
+
+
Ok(n) => Ok(minion_ast::Var::ConstantAsVar(n)),
+
+
+
+
+ 2100
+
+
+
fn _read_ref(e: conjure_ast::Expression) -> Result<String, SolverError> {
+
+
+
+
+ 1950
+
+
+
conjure_ast::Expression::Reference(_metadata, n) => Ok(n),
+
+
+
+
+ 150
+
+
+
x => Err(ModelInvalid(format!(
+
+
+
+
+ 150
+
+
+
"expected a reference, but got `{0:?}`",
+
+
+
+
+ 1950
+
+
+
let str_name = _name_to_string(name);
+
+
+
+
+ 1020
+
+
+
fn read_const(e: conjure_ast::Expression) -> Result<i32, SolverError> {
+
+
+
+
+ 1020
+
+
+
conjure_ast::Expression::Constant(_, conjure_ast::Constant::Int(n)) => Ok(n),
+
+
+
+
+
+
+
+
x => Err(ModelInvalid(format!(
+
+
+
+
+
+
+
+
"expected a constant, but got `{0:?}`",
+
+
+
+
+ 2415
+
+
+
fn _name_to_string(name: conjure_ast::Name) -> String {
+
+
+
+
+ 1515
+
+
+
conjure_ast::Name::UserName(x) => x,
+
+
+
+
+ 900
+
+
+
conjure_ast::Name::MachineName(x) => format!("__conjure_machine_name_{}", x),
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/adaptors/sat_common.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/adaptors/sat_common.rs.html
new file mode 100644
index 000000000..d0e3f35d3
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/adaptors/sat_common.rs.html
@@ -0,0 +1,5225 @@
+
+
+
+
+ Grcov report - sat_common.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
//! Common code for SAT adaptors.
+
+
+
+
+
+
+
+
//! Primarily, this is CNF related code.
+
+
+
+
+
+
+
+
use std::collections::HashMap;
+
+
+
+
+
+
+
+
use thiserror::Error;
+
+
+
+
+
+
+
+
ast as conjure_ast, solver::SolverError, solver::SolverError::*, Model as ConjureModel,
+
+
+
+
+
+
+
+
// (nd60, march 24) - i basically copied all this from @gskorokod's SAT implemention for the old
+
+
+
+
+
+
+
+
use crate::metadata::Metadata;
+
+
+
+
+
+
+
+
/// A representation of a model in CNF.
+
+
+
+
+
+
+
+
/// Expects Model to be in the Conjunctive Normal Form:
+
+
+
+
+
+
+
+
/// - All variables must be boolean
+
+
+
+
+
+
+
+
/// - Expressions must be `Reference`, `Not(Reference)`, or `Or(Reference1, Not(Reference2), ...)`
+
+
+
+
+
+
+
+
/// - The top level And() may contain nested Or()s. Any other nested expressions are not allowed.
+
+
+
+
+
+
+
+
#[derive(Debug, Clone)]
+
+
+
+
+
+
+
+
pub struct CNFModel {
+
+
+
+
+
+
+
+
pub clauses: Vec<Vec<i32>>,
+
+
+
+
+
+
+
+
variables: HashMap<conjure_ast::Name, i32>,
+
+
+
+
+
+
+
+
pub fn new() -> CNFModel {
+
+
+
+
+
+
+
+
variables: HashMap::new(),
+
+
+
+
+
+
+
+
pub fn from_conjure(conjure_model: ConjureModel) -> Result<CNFModel, SolverError> {
+
+
+
+
+
+
+
+
let mut ans: CNFModel = CNFModel::new();
+
+
+
+
+
+
+
+
for var in conjure_model.variables.keys() {
+
+
+
+
+
+
+
+
// Check that domain has the correct type
+
+
+
+
+
+
+
+
let decision_var = match conjure_model.variables.get(var) {
+
+
+
+
+
+
+
+
return Err(ModelInvalid(format!("variable {:?} not found", var)));
+
+
+
+
+
+
+
+
if decision_var.domain != conjure_ast::Domain::BoolDomain {
+
+
+
+
+
+
+
+
return Err(ModelFeatureNotSupported(format!(
+
+
+
+
+
+
+
+
"variable {:?} is not BoolDomain",
+
+
+
+
+
+
+
+
ans.add_variable(var);
+
+
+
+
+
+
+
+
for expr in conjure_model.get_constraints_vec() {
+
+
+
+
+
+
+
+
match ans.add_expression(&expr) {
+
+
+
+
+
+
+
+
let message = format!("{:?}", error);
+
+
+
+
+
+
+
+
return Err(ModelFeatureNotSupported(message));
+
+
+
+
+
+
+
+
/// Gets all the Conjure variables in the CNF.
+
+
+
+
+
+
+
+
#[allow(dead_code)] // It will be used once we actually run kissat
+
+
+
+
+
+
+
+
pub fn get_variables(&self) -> Vec<&conjure_ast::Name> {
+
+
+
+
+
+
+
+
let mut ans: Vec<&conjure_ast::Name> = Vec::new();
+
+
+
+
+
+
+
+
for key in self.variables.keys() {
+
+
+
+
+
+
+
+
/// Gets the index of a Conjure variable.
+
+
+
+
+
+
+
+
pub fn get_index(&self, var: &conjure_ast::Name) -> Option<i32> {
+
+
+
+
+
+
+
+
return self.variables.get(var).copied();
+
+
+
+
+
+
+
+
/// Gets a Conjure variable by index.
+
+
+
+
+
+
+
+
pub fn get_name(&self, ind: i32) -> Option<&conjure_ast::Name> {
+
+
+
+
+
+
+
+
for key in self.variables.keys() {
+
+
+
+
+
+
+
+
let idx = self.get_index(key)?;
+
+
+
+
+
+
+
+
/// Adds a new Conjure variable to the CNF representation.
+
+
+
+
+
+
+
+
pub fn add_variable(&mut self, var: &conjure_ast::Name) {
+
+
+
+
+
+
+
+
self.variables.insert(var.clone(), self.next_ind);
+
+
+
+
+
+
+
+
* Check if a Conjure variable or index is present in the CNF
+
+
+
+
+
+
+
+
pub fn has_variable<T: HasVariable>(&self, value: T) -> bool {
+
+
+
+
+
+
+
+
value.has_variable(self)
+
+
+
+
+
+
+
+
* Add a new clause to the CNF. Must be a vector of indices in CNF format
+
+
+
+
+
+
+
+
pub fn add_clause(&mut self, vec: &Vec<i32>) -> Result<(), CNFError> {
+
+
+
+
+
+
+
+
if !self.has_variable(idx.abs()) {
+
+
+
+
+
+
+
+
return Err(CNFError::ClauseIndexNotFound(*idx));
+
+
+
+
+
+
+
+
self.clauses.push(vec.clone());
+
+
+
+
+
+
+
+
* Add a new Conjure expression to the CNF. Must be a logical expression in CNF form
+
+
+
+
+
+
+
+
pub fn add_expression(&mut self, expr: &conjure_ast::Expression) -> Result<(), CNFError> {
+
+
+
+
+
+
+
+
for row in self.handle_expression(expr)? {
+
+
+
+
+
+
+
+
self.add_clause(&row)?;
+
+
+
+
+
+
+
+
* Convert the CNF to a Conjure expression
+
+
+
+
+
+
+
+
#[allow(dead_code)] // It will be used once we actually run kissat
+
+
+
+
+
+
+
+
pub fn as_expression(&self) -> Result<conjure_ast::Expression, CNFError> {
+
+
+
+
+
+
+
+
let mut expr_clauses: Vec<conjure_ast::Expression> = Vec::new();
+
+
+
+
+
+
+
+
for clause in &self.clauses {
+
+
+
+
+
+
+
+
expr_clauses.push(self.clause_to_expression(clause)?);
+
+
+
+
+
+
+
+
Ok(conjure_ast::Expression::And(Metadata::new(), expr_clauses))
+
+
+
+
+
+
+
+
* Convert a single clause to a Conjure expression
+
+
+
+
+
+
+
+
fn clause_to_expression(&self, clause: &Vec<i32>) -> Result<conjure_ast::Expression, CNFError> {
+
+
+
+
+
+
+
+
let mut ans: Vec<conjure_ast::Expression> = Vec::new();
+
+
+
+
+
+
+
+
match self.get_name(idx.abs()) {
+
+
+
+
+
+
+
+
None => return Err(CNFError::ClauseIndexNotFound(*idx)),
+
+
+
+
+
+
+
+
ans.push(conjure_ast::Expression::Reference(
+
+
+
+
+
+
+
+
let expression: conjure_ast::Expression =
+
+
+
+
+
+
+
+
conjure_ast::Expression::Reference(Metadata::new(), name.clone());
+
+
+
+
+
+
+
+
ans.push(conjure_ast::Expression::Not(
+
+
+
+
+
+
+
+
Box::from(expression),
+
+
+
+
+
+
+
+
Ok(conjure_ast::Expression::Or(Metadata::new(), ans))
+
+
+
+
+
+
+
+
* Get the index for a Conjure Reference or return an error
+
+
+
+
+
+
+
+
* @see conjure_ast::Expression::Reference
+
+
+
+
+
+
+
+
fn get_reference_index(&self, name: &conjure_ast::Name) -> Result<i32, CNFError> {
+
+
+
+
+
+
+
+
match self.get_index(name) {
+
+
+
+
+
+
+
+
None => Err(CNFError::VariableNameNotFound(name.clone())),
+
+
+
+
+
+
+
+
Some(ind) => Ok(ind),
+
+
+
+
+
+
+
+
* Convert the contents of a single Reference to a row of the CNF format
+
+
+
+
+
+
+
+
* @see get_reference_index
+
+
+
+
+
+
+
+
* @see conjure_ast::Expression::Reference
+
+
+
+
+
+
+
+
fn handle_reference(&self, name: &conjure_ast::Name) -> Result<Vec<i32>, CNFError> {
+
+
+
+
+
+
+
+
Ok(vec![self.get_reference_index(name)?])
+
+
+
+
+
+
+
+
* Convert the contents of a single Not() to CNF
+
+
+
+
+
+
+
+
fn handle_not(&self, expr: &conjure_ast::Expression) -> Result<Vec<i32>, CNFError> {
+
+
+
+
+
+
+
+
// Expression inside the Not()
+
+
+
+
+
+
+
+
conjure_ast::Expression::Reference(_metadata, name) => {
+
+
+
+
+
+
+
+
Ok(vec![-self.get_reference_index(name)?])
+
+
+
+
+
+
+
+
_ => Err(CNFError::UnexpectedExpressionInsideNot(expr.clone())),
+
+
+
+
+
+
+
+
* Convert the contents of a single Or() to a row of the CNF format
+
+
+
+
+
+
+
+
fn handle_or(&self, expressions: &Vec<conjure_ast::Expression>) -> Result<Vec<i32>, CNFError> {
+
+
+
+
+
+
+
+
let mut ans: Vec<i32> = Vec::new();
+
+
+
+
+
+
+
+
for expr in expressions {
+
+
+
+
+
+
+
+
let ret = self.handle_flat_expression(expr)?;
+
+
+
+
+
+
+
+
* Convert a single Reference, `Not` or `Or` into a clause of the CNF format
+
+
+
+
+
+
+
+
fn handle_flat_expression(
+
+
+
+
+
+
+
+
expression: &conjure_ast::Expression,
+
+
+
+
+
+
+
+
) -> Result<Vec<i32>, CNFError> {
+
+
+
+
+
+
+
+
conjure_ast::Expression::Reference(_metadata, name) => self.handle_reference(name),
+
+
+
+
+
+
+
+
conjure_ast::Expression::Not(_metadata, var_box) => self.handle_not(var_box),
+
+
+
+
+
+
+
+
conjure_ast::Expression::Or(_metadata, expressions) => self.handle_or(expressions),
+
+
+
+
+
+
+
+
_ => Err(CNFError::UnexpectedExpression(expression.clone())),
+
+
+
+
+
+
+
+
* Convert a single And() into a vector of clauses in the CNF format
+
+
+
+
+
+
+
+
expressions: &Vec<conjure_ast::Expression>,
+
+
+
+
+
+
+
+
) -> Result<Vec<Vec<i32>>, CNFError> {
+
+
+
+
+
+
+
+
let mut ans: Vec<Vec<i32>> = Vec::new();
+
+
+
+
+
+
+
+
for expression in expressions {
+
+
+
+
+
+
+
+
conjure_ast::Expression::And(_metadata, _expressions) => {
+
+
+
+
+
+
+
+
return Err(CNFError::NestedAnd(expression.clone()));
+
+
+
+
+
+
+
+
ans.push(self.handle_flat_expression(expression)?);
+
+
+
+
+
+
+
+
* Convert a single Conjure expression into a vector of clauses of the CNF format
+
+
+
+
+
+
+
+
fn handle_expression(
+
+
+
+
+
+
+
+
expression: &conjure_ast::Expression,
+
+
+
+
+
+
+
+
) -> Result<Vec<Vec<i32>>, CNFError> {
+
+
+
+
+
+
+
+
conjure_ast::Expression::And(_metadata, expressions) => self.handle_and(expressions),
+
+
+
+
+
+
+
+
_ => Ok(vec![self.handle_flat_expression(expression)?]),
+
+
+
+
+
+
+
+
impl Default for CNFModel {
+
+
+
+
+
+
+
+
fn default() -> Self {
+
+
+
+
+
+
+
+
#[derive(Error, Debug)]
+
+
+
+
+
+
+
+
#[error("Variable with name `{0}` not found")]
+
+
+
+
+
+
+
+
VariableNameNotFound(conjure_ast::Name),
+
+
+
+
+
+
+
+
#[error("Clause with index `{0}` not found")]
+
+
+
+
+
+
+
+
ClauseIndexNotFound(i32),
+
+
+
+
+
+
+
+
#[error("Unexpected Expression `{0}` inside Not(). Only Not(Reference) allowed!")]
+
+
+
+
+
+
+
+
UnexpectedExpressionInsideNot(conjure_ast::Expression),
+
+
+
+
+
+
+
+
"Unexpected Expression `{0}` found. Only Reference, Not(Reference) and Or(...) allowed!"
+
+
+
+
+
+
+
+
UnexpectedExpression(conjure_ast::Expression),
+
+
+
+
+
+
+
+
#[error("Unexpected nested And: {0}")]
+
+
+
+
+
+
+
+
NestedAnd(conjure_ast::Expression),
+
+
+
+
+
+
+
+
/// Helper trait for checking if a variable is present in the CNF polymorphically (i32 or conjure_ast::Name)
+
+
+
+
+
+
+
+
pub trait HasVariable {
+
+
+
+
+
+
+
+
fn has_variable(self, cnf: &CNFModel) -> bool;
+
+
+
+
+
+
+
+
impl HasVariable for i32 {
+
+
+
+
+
+
+
+
fn has_variable(self, cnf: &CNFModel) -> bool {
+
+
+
+
+
+
+
+
return cnf.get_name(self).is_some();
+
+
+
+
+
+
+
+
impl HasVariable for &conjure_ast::Name {
+
+
+
+
+
+
+
+
fn has_variable(self, cnf: &CNFModel) -> bool {
+
+
+
+
+
+
+
+
cnf.get_index(self).is_some()
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/index.html
new file mode 100644
index 000000000..e9998f798
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/index.html
@@ -0,0 +1,98 @@
+
+
+
+
+ Grcov report - crates/conjure_core/src/solver
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 10.19 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ mod.rs
+
+
+
+ 64.39%
+
+
+
+ 64.39%
+
+
+ 85 / 132
+
+
+ 10.78%
+ 11 / 102
+
+
+
+
+
+ model_modifier.rs
+
+
+
+ 0%
+
+
+
+ 0%
+
+
+ 0 / 6
+
+
+ 0%
+ 0 / 6
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/mod.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/mod.rs.html
new file mode 100644
index 000000000..08672ba59
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/mod.rs.html
@@ -0,0 +1,6265 @@
+
+
+
+
+ Grcov report - mod.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 10.78 %
+
+
+
+
+
+
+
+
+
+
+
+
+
//! A high-level API for interacting with constraints solvers.
+
+
+
+
+
+
+
+
//! This module provides a consistent, solver-independent API for interacting with constraints
+
+
+
+
+
+
+
+
//! solvers. It also provides incremental solving support, and the returning of run stats from
+
+
+
+
+
+
+
+
//! - [Solver<Adaptor>] provides the API for interacting with constraints solvers.
+
+
+
+
+
+
+
+
//! - The [SolverAdaptor] trait controls how solving actually occurs and handles translation
+
+
+
+
+
+
+
+
//! between the [Solver] type and a specific solver.
+
+
+
+
+
+
+
+
//! - [adaptors] contains all implemented solver adaptors.
+
+
+
+
+
+
+
+
//! - The [model_modifier] submodule defines types to help with incremental solving / changing a
+
+
+
+
+
+
+
+
//! model during search. The entrypoint for incremental solving is the [Solver<A,ModelLoaded>::solve_mut]
+
+
+
+
+
+
+
+
//! ## A Successful Minion Model
+
+
+
+
+ 1
+
+
+
//! use std::sync::{Arc,Mutex};
+
+
+
+
+ 1
+
+
+
//! use conjure_core::parse::get_example_model;
+
+
+
+
+ 1
+
+
+
//! use conjure_core::rule_engine::resolve_rule_sets;
+
+
+
+
+ 1
+
+
+
//! use conjure_core::rule_engine::rewrite_model;
+
+
+
+
+ 1
+
+
+
//! use conjure_core::solver::{adaptors, Solver, SolverAdaptor};
+
+
+
+
+ 1
+
+
+
//! use conjure_core::solver::states::ModelLoaded;
+
+
+
+
+ 1
+
+
+
//! use conjure_core::solver::SolverFamily;
+
+
+
+
+ 1
+
+
+
//! // Define and rewrite a model for minion.
+
+
+
+
+ 1
+
+
+
//! let model = get_example_model("bool-03").unwrap();
+
+
+
+
+ 1
+
+
+
//! let rule_sets = resolve_rule_sets(SolverFamily::Minion, &vec!["Constant"]).unwrap();
+
+
+
+
+ 1
+
+
+
//! let model = rewrite_model(&model,&rule_sets).unwrap();
+
+
+
+
+ 1
+
+
+
//! // Solve using Minion.
+
+
+
+
+ 1
+
+
+
//! let solver = Solver::new(adaptors::Minion::new());
+
+
+
+
+ 1
+
+
+
//! let solver: Solver<adaptors::Minion,ModelLoaded> = solver.load_model(model).unwrap();
+
+
+
+
+ 1
+
+
+
//! // In this example, we will count solutions.
+
+
+
+
+ 1
+
+
+
//! // The solver interface is designed to allow adaptors to use multiple-threads / processes if
+
+
+
+
+ 1
+
+
+
//! // necessary. Therefore, the callback type requires all variables inside it to have a static
+
+
+
+
+ 1
+
+
+
//! // lifetime and to implement Send (i.e. the variable can be safely shared between theads).
+
+
+
+
+ 1
+
+
+
//! // We use Arc<Mutex<T>> to create multiple references to a threadsafe mutable
+
+
+
+
+ 1
+
+
+
//! // variable of type T.
+
+
+
+
+ 1
+
+
+
//! // Using the move |x| ... closure syntax, we move one of these references into the closure.
+
+
+
+
+ 1
+
+
+
//! // Note that a normal closure borrow variables from the parent so is not
+
+
+
+
+ 1
+
+
+
//! let counter_ref = Arc::new(Mutex::new(0));
+
+
+
+
+ 1
+
+
+
//! let counter_ref_2 = counter_ref.clone();
+
+
+
+
+ 1
+
+
+
//! solver.solve(Box::new(move |_| {
+
+
+
+
+ 2
+
+
+
//! let mut counter = (*counter_ref_2).lock().unwrap();
+
+
+
+
+ 1
+
+
+
//! let mut counter = (*counter_ref).lock().unwrap();
+
+
+
+
+ 1
+
+
+
//! assert_eq!(*counter,2);
+
+
+
+
+
+
+
+
// # Implementing Solver interfaces
+
+
+
+
+
+
+
+
// Solver interfaces can only be implemented inside this module, due to the SolverAdaptor crate
+
+
+
+
+
+
+
+
// To add support for a solver, implement the `SolverAdaptor` trait in a submodule.
+
+
+
+
+
+
+
+
// If incremental solving support is required, also implement a new `ModelModifier`. If this is not
+
+
+
+
+
+
+
+
// required, all `ModelModifier` instances required by the SolverAdaptor trait can be replaced with
+
+
+
+
+
+
+
+
// For more details, see the docstrings for SolverAdaptor, ModelModifier, and NotModifiable.
+
+
+
+
+
+
+
+
#![allow(clippy::manual_non_exhaustive)]
+
+
+
+
+
+
+
+
use std::cell::OnceCell;
+
+
+
+
+
+
+
+
use std::collections::HashMap;
+
+
+
+
+
+
+
+
use std::error::Error;
+
+
+
+
+
+
+
+
use std::fmt::{Debug, Display};
+
+
+
+
+
+
+
+
use std::sync::{Arc, RwLock};
+
+
+
+
+
+
+
+
use std::time::Instant;
+
+
+
+
+
+
+
+
use serde::{Deserialize, Serialize};
+
+
+
+
+
+
+
+
use strum_macros::{Display, EnumIter, EnumString};
+
+
+
+
+
+
+
+
use thiserror::Error;
+
+
+
+
+
+
+
+
use crate::ast::{Constant, Name};
+
+
+
+
+
+
+
+
use crate::context::Context;
+
+
+
+
+
+
+
+
use crate::stats::SolverStats;
+
+
+
+
+
+
+
+
use self::model_modifier::ModelModifier;
+
+
+
+
+
+
+
+
pub mod model_modifier;
+
+
+
+
+
+
+
+
Debug, EnumString, EnumIter, Display, PartialEq, Eq, Hash, Clone, Copy, Serialize, Deserialize,
+
+
+
+
+
+
+
+
pub enum SolverFamily {
+
+
+
+
+
+
+
+
/// The type for user-defined callbacks for use with [Solver].
+
+
+
+
+
+
+
+
/// Note that this enforces thread safety
+
+
+
+
+
+
+
+
pub type SolverCallback = Box<dyn Fn(HashMap<Name, Constant>) -> bool + Send>;
+
+
+
+
+
+
+
+
pub type SolverMutCallback =
+
+
+
+
+
+
+
+
Box<dyn Fn(HashMap<Name, Constant>, Box<dyn ModelModifier>) -> bool + Send>;
+
+
+
+
+
+
+
+
/// A common interface for calling underlying solver APIs inside a [`Solver`].
+
+
+
+
+
+
+
+
/// Implementations of this trait aren't directly callable and should be used through [`Solver`] .
+
+
+
+
+
+
+
+
/// The below documentation lists the formal requirements that all implementations of
+
+
+
+
+
+
+
+
/// [`SolverAdaptor`] should follow - **see the top level module documentation and [`Solver`] for
+
+
+
+
+
+
+
+
/// The [`SolverAdaptor`] trait **must** only be implemented inside a submodule of this one,
+
+
+
+
+
+
+
+
/// and **should** only be called through [`Solver`].
+
+
+
+
+
+
+
+
/// The `private::Sealed` trait and `private::Internal` type enforce these requirements by only
+
+
+
+
+
+
+
+
/// allowing trait implementations and calling of methods of SolverAdaptor to occur inside this
+
+
+
+
+
+
+
+
/// Multiple instances of [`Solver`] can be run in parallel across multiple threads.
+
+
+
+
+
+
+
+
/// [`Solver`] provides no concurrency control or thread-safety; therefore, adaptors **must**
+
+
+
+
+
+
+
+
/// ensure that multiple instances of themselves can be ran in parallel. This applies to all
+
+
+
+
+
+
+
+
/// stages of solving including having two active `solve()` calls happening at a time, loading
+
+
+
+
+
+
+
+
/// a model while another is mid-solve, loading two models at once, etc.
+
+
+
+
+
+
+
+
/// A [SolverAdaptor] **may** use whatever threading or process model it likes underneath the hood,
+
+
+
+
+
+
+
+
/// as long as it obeys the above.
+
+
+
+
+
+
+
+
/// Method calls **should** block instead of erroring where possible.
+
+
+
+
+
+
+
+
/// Underlying solvers that only have one instance per process (such as Minion) **should** block
+
+
+
+
+
+
+
+
/// (eg. using a [`Mutex<()>`](`std::sync::Mutex`)) to run calls to
+
+
+
+
+
+
+
+
/// [`Solver<A,ModelLoaded>::solve()`] and [`Solver<A,ModelLoaded>::solve_mut()`] sequentially.
+
+
+
+
+
+
+
+
pub trait SolverAdaptor: private::Sealed {
+
+
+
+
+
+
+
+
/// Runs the solver on the given model.
+
+
+
+
+
+
+
+
/// Implementations of this function **must** call the user provided callback whenever a solution
+
+
+
+
+
+
+
+
/// is found. If the user callback returns `true`, search should continue, if the user callback
+
+
+
+
+
+
+
+
/// returns `false`, search should terminate.
+
+
+
+
+
+
+
+
/// If the solver terminates without crashing a [SolveSuccess] struct **must** returned. The
+
+
+
+
+
+
+
+
/// value of [SearchStatus] can be used to denote whether the underlying solver completed its
+
+
+
+
+
+
+
+
/// search or not. The latter case covers most non-crashing "failure" cases including user
+
+
+
+
+
+
+
+
/// termination, timeouts, etc.
+
+
+
+
+
+
+
+
/// To help populate [SearchStatus], it may be helpful to implement counters that track if the
+
+
+
+
+
+
+
+
/// user callback has been called yet, and its return value. This information makes it is
+
+
+
+
+
+
+
+
/// possible to distinguish between the most common search statuses:
+
+
+
+
+
+
+
+
/// [SearchComplete::HasSolutions], [SearchComplete::NoSolutions], and
+
+
+
+
+
+
+
+
/// [SearchIncomplete::UserTerminated].
+
+
+
+
+
+
+
+
callback: SolverCallback,
+
+
+
+
+
+
+
+
_: private::Internal,
+
+
+
+
+
+
+
+
) -> Result<SolveSuccess, SolverError>;
+
+
+
+
+
+
+
+
/// Runs the solver on the given model, allowing modification of the model through a
+
+
+
+
+
+
+
+
/// [`ModelModifier`].
+
+
+
+
+
+
+
+
/// Implementations of this function **must** return [`OpNotSupported`](`ModificationFailure::OpNotSupported`)
+
+
+
+
+
+
+
+
/// if modifying the model mid-search is not supported.
+
+
+
+
+
+
+
+
/// Otherwise, this should work in the same way as [`solve`](SolverAdaptor::solve).
+
+
+
+
+
+
+
+
callback: SolverMutCallback,
+
+
+
+
+
+
+
+
_: private::Internal,
+
+
+
+
+
+
+
+
) -> Result<SolveSuccess, SolverError>;
+
+
+
+
+
+
+
+
fn load_model(&mut self, model: Model, _: private::Internal) -> Result<(), SolverError>;
+
+
+
+
+ 45
+
+
+
fn init_solver(&mut self, _: private::Internal) {}
+
+
+
+
+
+
+
+
/// Get the solver family that this solver adaptor belongs to
+
+
+
+
+
+
+
+
fn get_family(&self) -> SolverFamily;
+
+
+
+
+
+
+
+
/// An abstract representation of a constraints solver.
+
+
+
+
+
+
+
+
/// [Solver] provides a common interface for interacting with a constraint solver. It also
+
+
+
+
+
+
+
+
/// abstracts over solver-specific datatypes, handling the translation to/from [conjure_core::ast]
+
+
+
+
+
+
+
+
/// types for a model and its solutions.
+
+
+
+
+
+
+
+
/// Details of how a model is solved is specified by the [SolverAdaptor]. This includes: the
+
+
+
+
+
+
+
+
/// underlying solver used, the translation of the model to a solver compatible form, how solutions
+
+
+
+
+
+
+
+
/// are translated back to [conjure_core::ast] types, and how incremental solving is implemented.
+
+
+
+
+
+
+
+
/// As such, there may be multiple [SolverAdaptor] implementations for a single underlying solver:
+
+
+
+
+
+
+
+
/// e.g. one adaptor may give solutions in a representation close to the solvers, while another may
+
+
+
+
+
+
+
+
/// attempt to rewrite it back into Essence.
+
+
+
+
+
+
+
+
pub struct Solver<A: SolverAdaptor, State: SolverState = Init> {
+
+
+
+
+
+
+
+
context: Option<Arc<RwLock<Context<'static>>>>,
+
+
+
+
+
+
+
+
impl<Adaptor: SolverAdaptor> Solver<Adaptor> {
+
+
+
+
+ 45
+
+
+
pub fn new(solver_adaptor: Adaptor) -> Solver<Adaptor> {
+
+
+
+
+ 45
+
+
+
let mut solver = Solver {
+
+
+
+
+ 45
+
+
+
adaptor: solver_adaptor,
+
+
+
+
+ 45
+
+
+
solver.adaptor.init_solver(private::Internal);
+
+
+
+
+
+
+
+
pub fn get_family(&self) -> SolverFamily {
+
+
+
+
+
+
+
+
self.adaptor.get_family()
+
+
+
+
+
+
+
+
impl<A: SolverAdaptor> Solver<A, Init> {
+
+
+
+
+ 45
+
+
+
pub fn load_model(mut self, model: Model) -> Result<Solver<A, ModelLoaded>, SolverError> {
+
+
+
+
+ 45
+
+
+
let solver_model = &mut self.adaptor.load_model(model.clone(), private::Internal)?;
+
+
+
+
+ 45
+
+
+
adaptor: self.adaptor,
+
+
+
+
+ 45
+
+
+
context: Some(model.context.clone()),
+
+
+
+
+
+
+
+
impl<A: SolverAdaptor> Solver<A, ModelLoaded> {
+
+
+
+
+ 45
+
+
+
callback: SolverCallback,
+
+
+
+
+ 45
+
+
+
) -> Result<Solver<A, ExecutionSuccess>, SolverError> {
+
+
+
+
+ 45
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+ 45
+
+
+
let start_time = Instant::now();
+
+
+
+
+ 45
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+ 45
+
+
+
let result = self.adaptor.solve(callback, private::Internal);
+
+
+
+
+ 45
+
+
+
let duration = start_time.elapsed();
+
+
+
+
+ 45
+
+
+
adaptor: self.adaptor,
+
+
+
+
+ 45
+
+
+
state: ExecutionSuccess {
+
+
+
+
+ 45
+
+
+
stats: x.stats.with_timings(duration.as_secs_f64()),
+
+
+
+
+ 45
+
+
+
_sealed: private::Internal,
+
+
+
+
+ 45
+
+
+
context: self.context,
+
+
+
+
+
+
+
+
callback: SolverMutCallback,
+
+
+
+
+
+
+
+
) -> Result<Solver<A, ExecutionSuccess>, SolverError> {
+
+
+
+
+
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+
+
+
+
let start_time = Instant::now();
+
+
+
+
+
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+
+
+
+
let result = self.adaptor.solve_mut(callback, private::Internal);
+
+
+
+
+
+
+
+
let duration = start_time.elapsed();
+
+
+
+
+
+
+
+
adaptor: self.adaptor,
+
+
+
+
+
+
+
+
state: ExecutionSuccess {
+
+
+
+
+
+
+
+
stats: x.stats.with_timings(duration.as_secs_f64()),
+
+
+
+
+
+
+
+
_sealed: private::Internal,
+
+
+
+
+
+
+
+
context: self.context,
+
+
+
+
+
+
+
+
impl<A: SolverAdaptor> Solver<A, ExecutionSuccess> {
+
+
+
+
+
+
+
+
pub fn stats(&self) -> SolverStats {
+
+
+
+
+
+
+
+
self.state.stats.clone()
+
+
+
+
+
+
+
+
// Saves this solvers stats to the global context as a "solver run"
+
+
+
+
+
+
+
+
pub fn save_stats_to_context(&self) {
+
+
+
+
+
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+
+
+
+
#[allow(clippy::expect_used)]
+
+
+
+
+
+
+
+
.add_solver_run(self.stats());
+
+
+
+
+
+
+
+
pub fn wall_time_s(&self) -> f64 {
+
+
+
+
+
+
+
+
self.stats().conjure_solver_wall_time_s
+
+
+
+
+
+
+
+
/// Errors returned by [Solver] on failure.
+
+
+
+
+
+
+
+
#[derive(Debug, Error, Clone)]
+
+
+
+
+
+
+
+
pub enum SolverError {
+
+
+
+
+
+
+
+
#[error("operation not implemented yet: {0}")]
+
+
+
+
+
+
+
+
OpNotImplemented(String),
+
+
+
+
+
+
+
+
#[error("operation not supported: {0}")]
+
+
+
+
+
+
+
+
OpNotSupported(String),
+
+
+
+
+
+
+
+
#[error("model feature not supported: {0}")]
+
+
+
+
+
+
+
+
ModelFeatureNotSupported(String),
+
+
+
+
+
+
+
+
#[error("model feature not implemented yet: {0}")]
+
+
+
+
+
+
+
+
ModelFeatureNotImplemented(String),
+
+
+
+
+
+
+
+
// use for semantics / type errors, use the above for syntax
+
+
+
+
+
+
+
+
#[error("model invalid: {0}")]
+
+
+
+
+
+
+
+
ModelInvalid(String),
+
+
+
+
+
+
+
+
#[error("error during solver execution: not implemented: {0}")]
+
+
+
+
+
+
+
+
RuntimeNotImplemented(String),
+
+
+
+
+
+
+
+
#[error("error during solver execution: {0}")]
+
+
+
+
+
+
+
+
/// Returned from [SolverAdaptor] when solving is successful.
+
+
+
+
+
+
+
+
pub struct SolveSuccess {
+
+
+
+
+
+
+
+
status: SearchStatus,
+
+
+
+
+
+
+
+
pub enum SearchStatus {
+
+
+
+
+
+
+
+
/// The search was complete (i.e. the solver found all possible solutions)
+
+
+
+
+
+
+
+
Complete(SearchComplete),
+
+
+
+
+
+
+
+
/// The search was incomplete (i.e. it was terminated before all solutions were found)
+
+
+
+
+
+
+
+
Incomplete(SearchIncomplete),
+
+
+
+
+
+
+
+
pub enum SearchIncomplete {
+
+
+
+
+
+
+
+
/// This variant should not be matched - it exists to simulate non-exhaustiveness of this enum.
+
+
+
+
+
+
+
+
pub enum SearchComplete {
+
+
+
+
+
+
+
+
/// This variant should not be matched - it exists to simulate non-exhaustiveness of this enum.
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/model_modifier.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/model_modifier.rs.html
new file mode 100644
index 000000000..654128fb2
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/solver/model_modifier.rs.html
@@ -0,0 +1,905 @@
+
+
+
+
+ Grcov report - model_modifier.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
//! Modifying a model during search.
+
+
+
+
+
+
+
+
//! Incremental solving can be triggered for a solverthrough the
+
+
+
+
+
+
+
+
//! [`Solver::solve_mut`] method.
+
+
+
+
+
+
+
+
//! This gives access to a [`ModelModifier`] in the solution retrieval callback.
+
+
+
+
+
+
+
+
use crate::ast::{Domain, Expression, Name};
+
+
+
+
+
+
+
+
/// A ModelModifier provides an interface to modify a model during solving.
+
+
+
+
+
+
+
+
/// Modifications are defined in terms of Conjure AST nodes, so must be translated to a solver
+
+
+
+
+
+
+
+
/// specfic form before use.
+
+
+
+
+
+
+
+
/// It is implementation defined whether these constraints can be given at high level and passed
+
+
+
+
+
+
+
+
/// through the rewriter, or only low-level solver constraints are supported.
+
+
+
+
+
+
+
+
/// See also: [`Solver::solve_mut`].
+
+
+
+
+
+
+
+
pub trait ModelModifier: private::Sealed {
+
+
+
+
+
+
+
+
fn add_constraint(&self, constraint: Expression) -> Result<(), ModificationFailure> {
+
+
+
+
+
+
+
+
Err(ModificationFailure::OpNotSupported)
+
+
+
+
+
+
+
+
fn add_variable(&self, name: Name, domain: Domain) -> Result<(), ModificationFailure> {
+
+
+
+
+
+
+
+
Err(ModificationFailure::OpNotSupported)
+
+
+
+
+
+
+
+
/// A [`ModelModifier`] for a solver that does not support incremental solving. Returns
+
+
+
+
+
+
+
+
/// [`OperationNotSupported`](`ModificationFailure::OperationNotSupported`) for all operations.
+
+
+
+
+
+
+
+
pub struct NotModifiable;
+
+
+
+
+
+
+
+
impl private::Sealed for NotModifiable {}
+
+
+
+
+
+
+
+
impl ModelModifier for NotModifiable {}
+
+
+
+
+
+
+
+
/// The requested modification to the model has failed.
+
+
+
+
+
+
+
+
pub enum ModificationFailure {
+
+
+
+
+
+
+
+
/// The desired operation is not supported for this solver adaptor.
+
+
+
+
+
+
+
+
/// The desired operation is supported by this solver adaptor, but has not been
+
+
+
+
+
+
+
+
// The arguments given to the operation are invalid.
+
+
+
+
+
+
+
+
ArgsInvalid(anyhow::Error),
+
+
+
+
+
+
+
+
/// An unspecified error has occurred.
+
+
+
+
+
+
+
+
Error(anyhow::Error),
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/stats/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/stats/index.html
new file mode 100644
index 000000000..45a018780
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/stats/index.html
@@ -0,0 +1,98 @@
+
+
+
+
+ Grcov report - crates/conjure_core/src/stats
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 11.11 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ mod.rs
+
+
+
+ 0%
+
+
+
+ 0%
+
+
+ 0 / 6
+
+
+ 0%
+ 0 / 6
+
+
+
+
+
+ solver_stats.rs
+
+
+
+ 100%
+
+
+
+ 100%
+
+
+ 6 / 6
+
+
+ 33.33%
+ 1 / 3
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/stats/mod.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/stats/mod.rs.html
new file mode 100644
index 000000000..9e1072347
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/stats/mod.rs.html
@@ -0,0 +1,377 @@
+
+
+
+
+ Grcov report - mod.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use serde::Serialize;
+
+
+
+
+
+
+
+
pub use solver_stats::SolverStats;
+
+
+
+
+
+
+
+
#[derive(Default, Serialize, Clone)]
+
+
+
+
+
+
+
+
pub solve_wall_time_s: Option<f64>,
+
+
+
+
+
+
+
+
pub solver_runs: Vec<SolverStats>,
+
+
+
+
+
+
+
+
pub fn new() -> Stats {
+
+
+
+
+
+
+
+
pub fn add_solver_run(&mut self, solver_stats: SolverStats) {
+
+
+
+
+
+
+
+
self.solver_runs.push(solver_stats);
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/stats/solver_stats.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/stats/solver_stats.rs.html
new file mode 100644
index 000000000..650aba137
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_core/src/stats/solver_stats.rs.html
@@ -0,0 +1,521 @@
+
+
+
+
+ Grcov report - solver_stats.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 33.33 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use serde::Serialize;
+
+
+
+
+
+
+
+
use crate::solver::SolverFamily;
+
+
+
+
+
+
+
+
#[derive(Default, Serialize, Clone)]
+
+
+
+
+
+
+
+
pub struct SolverStats {
+
+
+
+
+
+
+
+
// Wall time as measured by Conjure Oxide.
+
+
+
+
+
+
+
+
// This is set by Solver, not SolverAdaptor
+
+
+
+
+
+
+
+
pub conjure_solver_wall_time_s: f64,
+
+
+
+
+
+
+
+
pub solver_family: Option<SolverFamily>,
+
+
+
+
+
+
+
+
// NOTE (niklasdewally): these fields are copied from the list in Savile Row
+
+
+
+
+
+
+
+
pub nodes: Option<u64>,
+
+
+
+
+
+
+
+
pub satisfiable: Option<bool>,
+
+
+
+
+
+
+
+
pub sat_vars: Option<u64>,
+
+
+
+
+
+
+
+
pub sat_clauses: Option<u64>,
+
+
+
+
+
+
+
+
// If the given stats object exists, add the wall time value.
+
+
+
+
+
+
+
+
// Otherwise create a new stats object containing the wall time value.
+
+
+
+
+ 180
+
+
+
pub fn with_timings(self, wall_time_s: f64) -> SolverStats {
+
+
+
+
+ 180
+
+
+
conjure_solver_wall_time_s: wall_time_s,
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_macros/src/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_macros/src/index.html
new file mode 100644
index 000000000..2ab94e77a
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_macros/src/index.html
@@ -0,0 +1,74 @@
+
+
+
+
+ Grcov report - crates/conjure_macros/src
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 55.88 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ lib.rs
+
+
+
+ 94.96%
+
+
+
+ 94.96%
+
+
+ 113 / 119
+
+
+ 55.88%
+ 19 / 34
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_macros/src/lib.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_macros/src/lib.rs.html
new file mode 100644
index 000000000..e9d750ba0
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/conjure_macros/src/lib.rs.html
@@ -0,0 +1,2937 @@
+
+
+
+
+ Grcov report - lib.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 55.88 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use proc_macro::TokenStream;
+
+
+
+
+
+
+
+
use proc_macro2::Span;
+
+
+
+
+
+
+
+
use syn::punctuated::Punctuated;
+
+
+
+
+
+
+
+
use syn::token::Comma;
+
+
+
+
+
+
+
+
parenthesized, parse::Parse, parse::ParseStream, parse_macro_input, Ident, ItemFn, LitInt,
+
+
+
+
+
+
+
+
LitStr, Path, Result,
+
+
+
+
+
+
+
+
struct RuleSetAndPriority {
+
+
+
+
+
+
+
+
impl Parse for RuleSetAndPriority {
+
+
+
+
+ 169
+
+
+
fn parse(input: ParseStream) -> Result<Self> {
+
+
+
+
+ 169
+
+
+
parenthesized!(content in input);
+
+
+
+
+ 169
+
+
+
let rule_set: LitStr = content.parse()?;
+
+
+
+
+ 169
+
+
+
let _: Comma = content.parse()?;
+
+
+
+
+ 169
+
+
+
let priority: LitInt = content.parse()?;
+
+
+
+
+ 169
+
+
+
Ok(RuleSetAndPriority { rule_set, priority })
+
+
+
+
+
+
+
+
struct RegisterRuleArgs {
+
+
+
+
+
+
+
+
pub rule_sets: Vec<RuleSetAndPriority>,
+
+
+
+
+
+
+
+
impl Parse for RegisterRuleArgs {
+
+
+
+
+ 171
+
+
+
fn parse(input: ParseStream) -> Result<Self> {
+
+
+
+
+ 171
+
+
+
let rule_sets = Punctuated::<RuleSetAndPriority, Comma>::parse_terminated(input)?;
+
+
+
+
+ 171
+
+
+
Ok(RegisterRuleArgs {
+
+
+
+
+ 171
+
+
+
rule_sets: rule_sets.into_iter().collect(),
+
+
+
+
+
+
+
+
* Register a rule with the given rule sets and priorities.
+
+
+
+
+
+
+
+
#[proc_macro_attribute]
+
+
+
+
+ 171
+
+
+
pub fn register_rule(arg_tokens: TokenStream, item: TokenStream) -> TokenStream {
+
+
+
+
+ 171
+
+
+
let func = parse_macro_input!(item as ItemFn);
+
+
+
+
+ 171
+
+
+
let rule_ident = &func.sig.ident;
+
+
+
+
+ 171
+
+
+
let static_name = format!("CONJURE_GEN_RULE_{}", rule_ident).to_uppercase();
+
+
+
+
+ 171
+
+
+
let static_ident = Ident::new(&static_name, rule_ident.span());
+
+
+
+
+ 171
+
+
+
let args = parse_macro_input!(arg_tokens as RegisterRuleArgs);
+
+
+
+
+ 169
+
+
+
let rule_set_name = &rule_set.rule_set;
+
+
+
+
+ 169
+
+
+
let priority = &rule_set.priority;
+
+
+
+
+ 169
+
+
+
(#rule_set_name, #priority as u8)
+
+
+
+
+ 171
+
+
+
.collect::<Vec<_>>();
+
+
+
+
+ 171
+
+
+
let expanded = quote! {
+
+
+
+
+ 171
+
+
+
use ::conjure_core::rule_engine::_dependencies::*; // ToDo idk if we need to explicitly do that?
+
+
+
+
+ 171
+
+
+
#[::conjure_core::rule_engine::_dependencies::distributed_slice(::conjure_core::rule_engine::RULES_DISTRIBUTED_SLICE)]
+
+
+
+
+ 171
+
+
+
pub static #static_ident: ::conjure_core::rule_engine::Rule<'static> = ::conjure_core::rule_engine::Rule {
+
+
+
+
+ 171
+
+
+
name: stringify!(#rule_ident),
+
+
+
+
+ 171
+
+
+
application: #rule_ident,
+
+
+
+
+ 171
+
+
+
rule_sets: &[#(#rule_sets),*],
+
+
+
+
+ 171
+
+
+
TokenStream::from(expanded)
+
+
+
+
+ 40
+
+
+
fn parse_parenthesized<T: Parse>(input: ParseStream) -> Result<Vec<T>> {
+
+
+
+
+ 40
+
+
+
parenthesized!(content in input);
+
+
+
+
+ 40
+
+
+
let mut paths = Vec::new();
+
+
+
+
+ 42
+
+
+
while !content.is_empty() {
+
+
+
+
+ 29
+
+
+
let path = content.parse()?;
+
+
+
+
+ 29
+
+
+
if content.is_empty() {
+
+
+
+
+ 2
+
+
+
content.parse::<Comma>()?;
+
+
+
+
+
+
+
+
dependencies: Vec<LitStr>,
+
+
+
+
+
+
+
+
solver_families: Vec<Path>,
+
+
+
+
+
+
+
+
impl Parse for RuleSetArgs {
+
+
+
+
+ 28
+
+
+
fn parse(input: ParseStream) -> Result<Self> {
+
+
+
+
+ 28
+
+
+
let name = input.parse()?;
+
+
+
+
+ 28
+
+
+
input.parse::<Comma>()?;
+
+
+
+
+ 28
+
+
+
let priority = input.parse()?;
+
+
+
+
+ 28
+
+
+
if input.is_empty() {
+
+
+
+
+
+
+
+
dependencies: Vec::new(),
+
+
+
+
+
+
+
+
solver_families: Vec::new(),
+
+
+
+
+ 28
+
+
+
input.parse::<Comma>()?;
+
+
+
+
+ 28
+
+
+
let dependencies = parse_parenthesized::<LitStr>(input)?;
+
+
+
+
+ 28
+
+
+
if input.is_empty() {
+
+
+
+
+ 16
+
+
+
solver_families: Vec::new(),
+
+
+
+
+ 12
+
+
+
input.parse::<Comma>()?;
+
+
+
+
+ 12
+
+
+
let solver_families = parse_parenthesized::<Path>(input)?;
+
+
+
+
+
+
+
+
* Register a rule set with the given name, priority, and dependencies.
+
+
+
+
+ 1
+
+
+
* use conjure_macros::register_rule_set;
+
+
+
+
+ 1
+
+
+
* register_rule_set!("MyRuleSet", 10, ("DependencyRuleSet", "AnotherRuleSet"));
+
+
+
+
+ 28
+
+
+
pub fn register_rule_set(args: TokenStream) -> TokenStream {
+
+
+
+
+ 28
+
+
+
} = parse_macro_input!(args as RuleSetArgs);
+
+
+
+
+ 28
+
+
+
let static_name = format!("CONJURE_GEN_RULE_SET_{}", name.value()).to_uppercase();
+
+
+
+
+ 28
+
+
+
let static_ident = Ident::new(&static_name, Span::call_site());
+
+
+
+
+ 28
+
+
+
let dependencies = quote! {
+
+
+
+
+ 28
+
+
+
let solver_families = quote! {
+
+
+
+
+ 28
+
+
+
#(#solver_families),*
+
+
+
+
+ 28
+
+
+
let expanded = quote! {
+
+
+
+
+ 28
+
+
+
use ::conjure_core::rule_engine::_dependencies::*; // ToDo idk if we need to explicitly do that?
+
+
+
+
+ 28
+
+
+
#[::conjure_core::rule_engine::_dependencies::distributed_slice(::conjure_core::rule_engine::RULE_SETS_DISTRIBUTED_SLICE)]
+
+
+
+
+ 28
+
+
+
pub static #static_ident: ::conjure_core::rule_engine::RuleSet<'static> = ::conjure_core::rule_engine::RuleSet::new(#name, #priority, &[#dependencies], &[#solver_families]);
+
+
+
+
+ 28
+
+
+
TokenStream::from(expanded)
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/enum_compatability_macro/src/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/enum_compatability_macro/src/index.html
new file mode 100644
index 000000000..d1727210b
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/enum_compatability_macro/src/index.html
@@ -0,0 +1,74 @@
+
+
+
+
+ Grcov report - crates/enum_compatability_macro/src
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ lib.rs
+
+
+
+ 100%
+
+
+
+ 100%
+
+
+ 115 / 115
+
+
+ 60%
+ 12 / 20
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/enum_compatability_macro/src/lib.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/enum_compatability_macro/src/lib.rs.html
new file mode 100644
index 000000000..51e5c0556
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/enum_compatability_macro/src/lib.rs.html
@@ -0,0 +1,3193 @@
+
+
+
+
+ Grcov report - lib.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
//! A macro to document enum variants with the things that they are compatible with.
+
+
+
+
+
+
+
+
//! As well as documenting each variant, this macro also generates lists of all compatible variants
+
+
+
+
+
+
+
+
//! for each "thing".
+
+
+
+
+
+
+
+
//! This macro is used in Conjure-Oxide, a constraint modelling tool with support for multiple
+
+
+
+
+
+
+
+
//! backend solvers (e.g. Minion, SAT).
+
+
+
+
+
+
+
+
//! The Conjure-Oxide AST is used as the singular representation for constraints models throughout
+
+
+
+
+
+
+
+
//! its crate. A consequence of this is that the AST must contain all possible supported
+
+
+
+
+
+
+
+
//! expressions for all solvers, as well as the high level Essence language it takes as input.
+
+
+
+
+
+
+
+
//! Therefore, only a small subset of AST nodes are useful for a particular solver.
+
+
+
+
+
+
+
+
//! The documentation this generates helps rewrite rule implementers determine which AST nodes are
+
+
+
+
+
+
+
+
//! used for which backends by grouping AST nodes per solver.
+
+
+
+
+
+
+
+
#![allow(clippy::unwrap_used)]
+
+
+
+
+
+
+
+
#![allow(unstable_name_collisions)]
+
+
+
+
+
+
+
+
use proc_macro::TokenStream;
+
+
+
+
+
+
+
+
use std::collections::HashMap;
+
+
+
+
+
+
+
+
use itertools::Itertools;
+
+
+
+
+
+
+
+
parse_macro_input, parse_quote, punctuated::Punctuated, visit_mut::VisitMut, Attribute,
+
+
+
+
+
+
+
+
ItemEnum, Meta, Token, Variant,
+
+
+
+
+
+
+
+
// A nice S.O answer that helped write the syn code :)
+
+
+
+
+
+
+
+
// https://stackoverflow.com/a/65182902
+
+
+
+
+
+
+
+
struct RemoveCompatibleAttrs;
+
+
+
+
+
+
+
+
impl VisitMut for RemoveCompatibleAttrs {
+
+
+
+
+ 120
+
+
+
fn visit_variant_mut(&mut self, i: &mut Variant) {
+
+
+
+
+ 120
+
+
+
// 1. generate docstring for variant
+
+
+
+
+ 120
+
+
+
// Supported by: minion, sat ...
+
+
+
+
+ 120
+
+
+
// 2. delete #[compatible] attributes
+
+
+
+
+ 120
+
+
+
let mut solvers: Vec<String> = vec![];
+
+
+
+
+ 120
+
+
+
for attr in i.attrs.iter() {
+
+
+
+
+ 116
+
+
+
if !attr.path().is_ident("compatible") {
+
+
+
+
+ 110
+
+
+
.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
+
+
+
+
+ 153
+
+
+
let ident = arg.path().require_ident().unwrap();
+
+
+
+
+ 153
+
+
+
let solver_name = ident.to_string();
+
+
+
+
+ 153
+
+
+
solvers.push(solver_name);
+
+
+
+
+ 120
+
+
+
if !solvers.is_empty() {
+
+
+
+
+ 108
+
+
+
let solver_list: String = solvers.into_iter().intersperse(", ".into()).collect();
+
+
+
+
+ 108
+
+
+
let doc_string: String = format!("**Supported by:** {}.\n", solver_list);
+
+
+
+
+ 108
+
+
+
let doc_attr: Attribute = parse_quote!(#[doc = #doc_string]);
+
+
+
+
+ 108
+
+
+
i.attrs.push(doc_attr);
+
+
+
+
+ 224
+
+
+
i.attrs.retain(|attr| !attr.path().is_ident("compatible"));
+
+
+
+
+
+
+
+
/// A macro to document enum variants by the things that they are compatible with.
+
+
+
+
+ 1
+
+
+
/// use enum_compatability_macro::document_compatibility;
+
+
+
+
+ 1
+
+
+
/// #[document_compatibility]
+
+
+
+
+ 1
+
+
+
/// pub enum Expression {
+
+
+
+
+ 1
+
+
+
/// #[compatible(Minion)]
+
+
+
+
+ 1
+
+
+
/// ConstantInt(i32),
+
+
+
+
+ 1
+
+
+
/// #[compatible(Chuffed)]
+
+
+
+
+ 1
+
+
+
/// #[compatible(Minion)]
+
+
+
+
+ 1
+
+
+
/// Sum(Vec<Expression>)
+
+
+
+
+
+
+
+
/// The Expression type will have the following lists appended to its documentation:
+
+
+
+
+
+
+
+
/// ## Supported by `minion`
+
+
+
+
+
+
+
+
/// Sum(Vec<Expression>)
+
+
+
+
+
+
+
+
/// ## Supported by `chuffed`
+
+
+
+
+
+
+
+
/// Sum(Vec<Expression>)
+
+
+
+
+
+
+
+
/// Two equivalent syntaxes exist for specifying supported solvers:
+
+
+
+
+ 1
+
+
+
///# use enum_compatability_macro::document_compatibility;
+
+
+
+
+ 1
+
+
+
///# #[document_compatibility]
+
+
+
+
+ 1
+
+
+
///# pub enum Expression {
+
+
+
+
+ 1
+
+
+
///# #[compatible(Minion)]
+
+
+
+
+ 1
+
+
+
///# ConstantInt(i32),
+
+
+
+
+ 1
+
+
+
/// #[compatible(Chuffed)]
+
+
+
+
+ 1
+
+
+
/// #[compatible(Minion)]
+
+
+
+
+ 1
+
+
+
/// Sum(Vec<Expression>)
+
+
+
+
+ 1
+
+
+
///# use enum_compatability_macro::document_compatibility;
+
+
+
+
+ 1
+
+
+
///# #[document_compatibility]
+
+
+
+
+ 1
+
+
+
///# pub enum Expression {
+
+
+
+
+ 1
+
+
+
///# #[compatible(Minion)]
+
+
+
+
+ 1
+
+
+
///# ConstantInt(i32),
+
+
+
+
+ 1
+
+
+
/// #[compatible(Minion,Chuffed)]
+
+
+
+
+ 1
+
+
+
/// Sum(Vec<Expression>)
+
+
+
+
+
+
+
+
#[proc_macro_attribute]
+
+
+
+
+ 9
+
+
+
pub fn document_compatibility(_attr: TokenStream, input: TokenStream) -> TokenStream {
+
+
+
+
+
+
+
+
// Parse the input tokens into a syntax tree
+
+
+
+
+ 9
+
+
+
let mut input = parse_macro_input!(input as ItemEnum);
+
+
+
+
+ 9
+
+
+
let mut nodes_supported_by_solver: HashMap<String, Vec<syn::Ident>> = HashMap::new();
+
+
+
+
+
+
+
+
// process each item inside the enum.
+
+
+
+
+ 120
+
+
+
for variant in input.variants.iter() {
+
+
+
+
+ 120
+
+
+
let variant_ident = variant.ident.clone();
+
+
+
+
+ 120
+
+
+
for attr in variant.attrs.iter() {
+
+
+
+
+ 116
+
+
+
if !attr.path().is_ident("compatible") {
+
+
+
+
+ 110
+
+
+
.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
+
+
+
+
+ 153
+
+
+
let ident = arg.path().require_ident().unwrap();
+
+
+
+
+ 153
+
+
+
let solver_name = ident.to_string();
+
+
+
+
+ 153
+
+
+
match nodes_supported_by_solver.get_mut(&solver_name) {
+
+
+
+
+ 24
+
+
+
nodes_supported_by_solver.insert(solver_name, vec![variant_ident.clone()]);
+
+
+
+
+ 129
+
+
+
a.push(variant_ident.clone());
+
+
+
+
+
+
+
+
// we must remove all references to #[compatible] before we finish expanding the macro,
+
+
+
+
+
+
+
+
// as it does not exist outside of the context of this macro.
+
+
+
+
+ 9
+
+
+
RemoveCompatibleAttrs.visit_item_enum_mut(&mut input);
+
+
+
+
+ 9
+
+
+
// Build the doc string.
+
+
+
+
+ 9
+
+
+
// Note that quote wants us to build the doc message first, as it cannot interpolate doc
+
+
+
+
+ 9
+
+
+
// https://docs.rs/quote/latest/quote/macro.quote.html#interpolating-text-inside-of-doc-comments
+
+
+
+
+ 9
+
+
+
let mut doc_msg: String = "# Compatability\n".into();
+
+
+
+
+ 24
+
+
+
for solver in nodes_supported_by_solver.keys() {
+
+
+
+
+ 24
+
+
+
doc_msg.push_str(&format!("## {}\n", solver));
+
+
+
+
+
+
+
+
// list all the ast nodes for this solver
+
+
+
+
+ 153
+
+
+
for node in nodes_supported_by_solver
+
+
+
+
+ 153
+
+
+
.map(|x| x.to_string())
+
+
+
+
+ 153
+
+
+
doc_msg.push_str(&format!("* [`{}`]({}::{})\n", node, input.ident, node));
+
+
+
+
+ 9
+
+
+
input.attrs.push(parse_quote!(#[doc = #doc_msg]));
+
+
+
+
+ 9
+
+
+
let expanded = quote! {
+
+
+
+
+ 9
+
+
+
TokenStream::from(expanded)
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/biplate.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/biplate.rs.html
new file mode 100644
index 000000000..ec07c1b69
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/biplate.rs.html
@@ -0,0 +1,2361 @@
+
+
+
+
+ Grcov report - biplate.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 23.81 %
+
+
+
+
+
+
+
+
+
+
+
+
+
//#![cfg(feature = "unstable")]
+
+
+
+
+
+
+
+
#![allow(clippy::type_complexity)]
+
+
+
+
+
+
+
+
pub trait Biplate<To>
+
+
+
+
+
+
+
+
Self: Sized + Clone + Eq + Uniplate + 'static,
+
+
+
+
+
+
+
+
To: Sized + Clone + Eq + Uniplate + 'static,
+
+
+
+
+
+
+
+
/// Returns all the top most children of type to within from.
+
+
+
+
+
+
+
+
/// If from == to then this function should return the root as the single child.
+
+
+
+
+
+
+
+
fn biplate(&self) -> (Tree<To>, Box<dyn Fn(Tree<To>) -> Self>);
+
+
+
+
+
+
+
+
/// Like descend but with more general types.
+
+
+
+
+
+
+
+
/// If from == to then this function does not descend. Therefore, when writing definitions it
+
+
+
+
+
+
+
+
/// is highly unlikely that this function should be used in the recursive case. A common
+
+
+
+
+
+
+
+
/// pattern is to first match the types using descendBi, then continue the recursion with
+
+
+
+
+
+
+
+
fn descend_bi(&self, op: Arc<dyn Fn(To) -> To>) -> Self {
+
+
+
+
+
+
+
+
let (children, ctx) = self.biplate();
+
+
+
+
+
+
+
+
ctx(children.map(op))
+
+
+
+
+
+
+
+
// NOTE (niklasdewally): Uniplate does something different here, and I don't know why. In
+
+
+
+
+
+
+
+
// particular, it doesn't use structure (its version of tree.list()) at all here, and uses some
+
+
+
+
+
+
+
+
// builder thing I don't understand. My children_bi and universe_bi work though, so this might
+
+
+
+
+
+
+
+
// just be performance and Haskell related.
+
+
+
+
+
+
+
+
// https://github.com/ndmitchell/uniplate/blob/66a2c55a7de0f5d8b0e437479719469244e00fa4/Data/Generics/Uniplate/Internal/OperationsInc.hs#L189
+
+
+
+
+ 1
+
+
+
fn universe_bi(&self) -> im::Vector<To> {
+
+
+
+
+ 4
+
+
+
.flat_map(|child| child.universe())
+
+
+
+
+
+
+
+
/// Returns the children of a type. If to == from then it returns the original element (in contrast to children).
+
+
+
+
+ 2
+
+
+
fn children_bi(&self) -> im::Vector<To> {
+
+
+
+
+ 2
+
+
+
self.biplate().0.list().0
+
+
+
+
+
+
+
+
fn transform_bi(&self, op: Arc<dyn Fn(To) -> To>) -> Self {
+
+
+
+
+
+
+
+
let (children, ctx) = self.biplate();
+
+
+
+
+
+
+
+
ctx(children.map(Arc::new(move |child| child.transform(op.clone()))))
+
+
+
+
+
+
+
+
Self: Sized + Clone + Eq + 'static,
+
+
+
+
+
+
+
+
fn uniplate(&self) -> (Tree<Self>, Box<dyn Fn(Tree<Self>) -> Self>);
+
+
+
+
+
+
+
+
fn descend(&self, op: Arc<dyn Fn(Self) -> Self>) -> Self {
+
+
+
+
+
+
+
+
let (children, ctx) = self.uniplate();
+
+
+
+
+
+
+
+
ctx(children.map(op))
+
+
+
+
+
+
+
+
/// Gest all children of a node, including itself and all children.
+
+
+
+
+ 8
+
+
+
fn universe(&self) -> im::Vector<Self> {
+
+
+
+
+ 8
+
+
+
let mut results = vector![self.clone()];
+
+
+
+
+ 8
+
+
+
for child in self.children() {
+
+
+
+
+ 4
+
+
+
results.append(child.universe());
+
+
+
+
+
+
+
+
/// Gets the direct children (maximal substructures) of a node.
+
+
+
+
+ 8
+
+
+
fn children(&self) -> im::Vector<Self> {
+
+
+
+
+ 8
+
+
+
let (children, _) = self.uniplate();
+
+
+
+
+ 8
+
+
+
children.list().0.clone()
+
+
+
+
+
+
+
+
/// Reconstructs the node with the given children.
+
+
+
+
+
+
+
+
/// If there are a different number of children given as there were originally returned by
+
+
+
+
+
+
+
+
fn with_children(&self, children: im::Vector<Self>) -> Self {
+
+
+
+
+
+
+
+
// 1. Turn old tree into list.
+
+
+
+
+
+
+
+
// 2. Check lists are same size.
+
+
+
+
+
+
+
+
// 3. Use the reconstruction function given by old_children.list() to
+
+
+
+
+
+
+
+
// create a tree with the same structure but the new lists' elements .
+
+
+
+
+
+
+
+
let (old_children, ctx) = self.uniplate();
+
+
+
+
+
+
+
+
let (old_children_lst, rebuild) = old_children.list();
+
+
+
+
+
+
+
+
if old_children_lst.len() != children.len() {
+
+
+
+
+
+
+
+
panic!("with_children() given an unexpected amount of children");
+
+
+
+
+
+
+
+
ctx(rebuild(children))
+
+
+
+
+
+
+
+
/// Applies the given rule to all nodes bottom up.
+
+
+
+
+
+
+
+
fn transform(&self, f: Arc<dyn Fn(Self) -> Self>) -> Self {
+
+
+
+
+
+
+
+
let (children, ctx) = self.uniplate();
+
+
+
+
+
+
+
+
let f2 = f.clone(); // make another pointer to f for map.
+
+
+
+
+
+
+
+
children.map(Arc::new(move |child| child.transform(f2.clone())))
+
+
+
+
+
+
+
+
/// Rewrites by applying a rule everywhere it can.
+
+
+
+
+
+
+
+
fn rewrite(&self, f: Arc<dyn Fn(Self) -> Option<Self>>) -> Self {
+
+
+
+
+
+
+
+
let (children, ctx) = self.uniplate();
+
+
+
+
+
+
+
+
let f2 = f.clone(); // make another pointer to f for map.
+
+
+
+
+
+
+
+
let new_children = children.map(Arc::new(move |child| child.rewrite(f2.clone())));
+
+
+
+
+
+
+
+
match f(ctx(new_children.clone())) {
+
+
+
+
+
+
+
+
None => ctx(new_children),
+
+
+
+
+
+
+
+
/// Performs a fold-like computation on each value.
+
+
+
+
+
+
+
+
/// Working from the bottom up, this applies the given callback function to each nested
+
+
+
+
+
+
+
+
/// Unlike [`transform`](Uniplate::transform), this returns an arbitrary type, and is not
+
+
+
+
+
+
+
+
/// limited to T -> T transformations. In other words, it can transform a type into a new
+
+
+
+
+
+
+
+
/// The meaning of the callback function is the following:
+
+
+
+
+
+
+
+
/// f(element_to_fold, folded_children) -> folded_element
+
+
+
+
+
+
+
+
fn cata<T>(&self, op: Arc<dyn Fn(Self, Vec<T>) -> T>) -> T {
+
+
+
+
+
+
+
+
let children = self.children();
+
+
+
+
+
+
+
+
children.into_iter().map(|c| c.cata(op.clone())).collect(),
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/index.html
new file mode 100644
index 000000000..2788b92f9
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/index.html
@@ -0,0 +1,146 @@
+
+
+
+
+ Grcov report - crates/uniplate/src
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 48.53 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ biplate.rs
+
+
+
+ 28.99%
+
+
+
+ 28.99%
+
+
+ 20 / 69
+
+
+ 23.81%
+ 5 / 21
+
+
+
+
+
+ lib.rs
+
+
+
+ 93.62%
+
+
+
+ 93.62%
+
+
+ 44 / 47
+
+
+ 88.89%
+ 8 / 9
+
+
+
+
+
+ tree.rs
+
+
+
+ 96.25%
+
+
+
+ 96.25%
+
+
+ 77 / 80
+
+
+ 78.95%
+ 15 / 19
+
+
+
+
+
+ uniplate.rs
+
+
+
+ 26.98%
+
+
+
+ 26.98%
+
+
+ 17 / 63
+
+
+ 26.32%
+ 5 / 19
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/lib.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/lib.rs.html
new file mode 100644
index 000000000..a6922d444
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/lib.rs.html
@@ -0,0 +1,1881 @@
+
+
+
+
+ Grcov report - lib.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 88.89 %
+
+
+
+
+
+
+
+
+
+
+
+
+
//! A port of Haskell's [Uniplate](https://hackage.haskell.org/package/uniplate) in Rust.
+
+
+
+
+
+
+
+
//! ## A Calculator Input Language
+
+
+
+
+
+
+
+
//! Consider the AST of a calculator input language:
+
+
+
+
+ 1
+
+
+
//! Add(Box<AST>,Box<AST>),
+
+
+
+
+ 1
+
+
+
//! Sub(Box<AST>,Box<AST>),
+
+
+
+
+ 1
+
+
+
//! Div(Box<AST>,Box<AST>),
+
+
+
+
+ 1
+
+
+
//! Mul(Box<AST>,Box<AST>)
+
+
+
+
+
+
+
+
//! Using uniplate, one can implement a single function for this AST that can be used in a whole
+
+
+
+
+
+
+
+
//! range of traversals.
+
+
+
+
+
+
+
+
//! While this does not seem helpful in this toy example, the benefits amplify when the number of
+
+
+
+
+
+
+
+
//! enum variants increase, and the different types contained in their fields increase.
+
+
+
+
+
+
+
+
//! The below example implements [`Uniplate`](uniplate::Uniplate) for this language AST, and uses uniplate methods to
+
+
+
+
+
+
+
+
//! evaluate the encoded equation.
+
+
+
+
+
+
+
+
//! use uniplate::uniplate::{Uniplate, UniplateError};
+
+
+
+
+
+
+
+
//! #[derive(Clone,Eq,PartialEq,Debug)]
+
+
+
+
+
+
+
+
//! Add(Box<AST>,Box<AST>),
+
+
+
+
+
+
+
+
//! Sub(Box<AST>,Box<AST>),
+
+
+
+
+
+
+
+
//! Div(Box<AST>,Box<AST>),
+
+
+
+
+
+
+
+
//! Mul(Box<AST>,Box<AST>)
+
+
+
+
+
+
+
+
//! // In the future would be automatically derived.
+
+
+
+
+
+
+
+
//! impl Uniplate for AST {
+
+
+
+
+
+
+
+
//! fn uniplate(&self) -> (Vec<AST>, Box<dyn Fn(Vec<AST>) -> Result<AST, UniplateError> +'_>) {
+
+
+
+
+ 9
+
+
+
//! let context: Box<dyn Fn(Vec<AST>) -> Result<AST, UniplateError>> = match self {
+
+
+
+
+ 9
+
+
+
//! AST::Int(i) => Box::new(|_| Ok(AST::Int(*i))),
+
+
+
+
+ 5
+
+
+
//! AST::Add(_, _) => Box::new(|exprs: Vec<AST>| Ok(AST::Add(Box::new(exprs[0].clone()),Box::new(exprs[1].clone())))),
+
+
+
+
+ 2
+
+
+
//! AST::Sub(_, _) => Box::new(|exprs: Vec<AST>| Ok(AST::Sub(Box::new(exprs[0].clone()),Box::new(exprs[1].clone())))),
+
+
+
+
+
+
+
+
//! AST::Div(_, _) => Box::new(|exprs: Vec<AST>| Ok(AST::Div(Box::new(exprs[0].clone()),Box::new(exprs[1].clone())))),
+
+
+
+
+ 1
+
+
+
//! AST::Mul(_, _) => Box::new(|exprs: Vec<AST>| Ok(AST::Mul(Box::new(exprs[0].clone()),Box::new(exprs[1].clone()))))
+
+
+
+
+
+
+
+
//! let children: Vec<AST> = match self {
+
+
+
+
+ 9
+
+
+
//! AST::Add(a,b) => vec![*a.clone(),*b.clone()],
+
+
+
+
+ 2
+
+
+
//! AST::Sub(a,b) => vec![*a.clone(),*b.clone()],
+
+
+
+
+
+
+
+
//! AST::Div(a,b) => vec![*a.clone(),*b.clone()],
+
+
+
+
+ 1
+
+
+
//! AST::Mul(a,b) => vec![*a.clone(),*b.clone()],
+
+
+
+
+
+
+
+
//! (children,context)
+
+
+
+
+
+
+
+
//! pub fn my_rule(e: AST) -> AST{
+
+
+
+
+ 9
+
+
+
//! AST::Int(a) => AST::Int(a),
+
+
+
+
+ 5
+
+
+
//! AST::Add(a,b) => {match (&*a,&*b) { (AST::Int(a), AST::Int(b)) => AST::Int(a+b), _ => AST::Add(a,b) }}
+
+
+
+
+ 2
+
+
+
//! AST::Sub(a,b) => {match (&*a,&*b) { (AST::Int(a), AST::Int(b)) => AST::Int(a-b), _ => AST::Sub(a,b) }}
+
+
+
+
+
+
+
+
//! AST::Mul(a,b) => {match (&*a,&*b) { (AST::Int(a), AST::Int(b)) => AST::Int(a*b), _ => AST::Mul(a,b) }}
+
+
+
+
+ 1
+
+
+
//! AST::Div(a,b) => {match (&*a,&*b) { (AST::Int(a), AST::Int(b)) => AST::Int(a/b), _ => AST::Div(a,b) }}
+
+
+
+
+ 1
+
+
+
//! let ast = AST::Add(
+
+
+
+
+ 1
+
+
+
//! Box::new(AST::Int(1)),
+
+
+
+
+ 1
+
+
+
//! Box::new(AST::Mul(
+
+
+
+
+ 1
+
+
+
//! Box::new(AST::Int(2)),
+
+
+
+
+ 1
+
+
+
//! Box::new(AST::Div(
+
+
+
+
+ 1
+
+
+
//! Box::new(AST::Add(Box::new(AST::Int(1)),Box::new(AST::Int(2)))),
+
+
+
+
+ 1
+
+
+
//! Box::new(AST::Int(3))
+
+
+
+
+ 1
+
+
+
//! let new_ast = ast.transform(my_rule);
+
+
+
+
+ 1
+
+
+
//! assert!(new_ast.is_ok());
+
+
+
+
+ 1
+
+
+
//! println!("{:?}",new_ast);
+
+
+
+
+ 1
+
+
+
//! assert_eq!(new_ast.unwrap(), AST::Int(3));
+
+
+
+
+
+
+
+
//! ....MORE DOCS TO COME....
+
+
+
+
+
+
+
+
//! # Acknowledgements / Related Work
+
+
+
+
+
+
+
+
//! *This crate implements programming constructs from the following Haskell libraries and
+
+
+
+
+
+
+
+
//! * [Uniplate](https://hackage.haskell.org/package/uniplate).
+
+
+
+
+
+
+
+
//! * Neil Mitchell and Colin Runciman. 2007. Uniform boilerplate and list processing. In
+
+
+
+
+
+
+
+
//! Proceedings of the ACM SIGPLAN workshop on Haskell workshop (Haskell '07). Association for
+
+
+
+
+
+
+
+
//! Computing Machinery, New York, NY, USA, 49–60. <https://doi.org/10.1145/1291201.1291208>
+
+
+
+
+
+
+
+
//! [(free copy)](https://www.cs.york.ac.uk/plasma/publications/pdf/MitchellRuncimanHW07.pdf)
+
+
+
+
+
+
+
+
//! * Huet G. The Zipper. Journal of Functional Programming. 1997;7(5):549–54. <https://doi.org/10.1017/S0956796897002864>
+
+
+
+
+
+
+
+
//! [(free copy)](https://www.cambridge.org/core/services/aop-cambridge-core/content/view/0C058890B8A9B588F26E6D68CF0CE204/S0956796897002864a.pdf/zipper.pdf)
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/test_common/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/test_common/index.html
new file mode 100644
index 000000000..88335b74f
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/test_common/index.html
@@ -0,0 +1,74 @@
+
+
+
+
+ Grcov report - crates/uniplate/src/test_common
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ paper.rs
+
+
+
+ 0%
+
+
+
+ 0%
+
+
+ 0 / 35
+
+
+ 0%
+ 0 / 12
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/test_common/paper.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/test_common/paper.rs.html
new file mode 100644
index 000000000..6fee48ab8
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/test_common/paper.rs.html
@@ -0,0 +1,1033 @@
+
+
+
+
+ Grcov report - paper.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use proptest::prelude::*;
+
+
+
+
+
+
+
+
// Examples found in the Uniplate paper.
+
+
+
+
+
+
+
+
// Stmt and Expr to demonstrate and test multitype traversals.
+
+
+
+
+
+
+
+
#[derive(Eq, PartialEq, Clone, Debug)]
+
+
+
+
+
+
+
+
Assign(String, Expr),
+
+
+
+
+
+
+
+
If(Expr, Box<Stmt>, Box<Stmt>),
+
+
+
+
+
+
+
+
While(Expr, Box<Stmt>),
+
+
+
+
+
+
+
+
#[derive(Eq, PartialEq, Clone, Debug)]
+
+
+
+
+
+
+
+
Add(Box<Expr>, Box<Expr>),
+
+
+
+
+
+
+
+
Sub(Box<Expr>, Box<Expr>),
+
+
+
+
+
+
+
+
Mul(Box<Expr>, Box<Expr>),
+
+
+
+
+
+
+
+
Div(Box<Expr>, Box<Expr>),
+
+
+
+
+
+
+
+
pub fn proptest_exprs() -> impl Strategy<Value = Expr> {
+
+
+
+
+
+
+
+
let leafs = prop_oneof![any::<i32>().prop_map(Val), any::<String>().prop_map(Var),];
+
+
+
+
+
+
+
+
leafs.prop_recursive(10, 512, 2, |inner| {
+
+
+
+
+
+
+
+
prop::collection::vec(inner.clone(), 2..2)
+
+
+
+
+
+
+
+
.prop_map(|elems| Add(Box::new(elems[0].clone()), Box::new(elems[1].clone()))),
+
+
+
+
+
+
+
+
prop::collection::vec(inner.clone(), 2..2)
+
+
+
+
+
+
+
+
.prop_map(|elems| Sub(Box::new(elems[0].clone()), Box::new(elems[1].clone()))),
+
+
+
+
+
+
+
+
prop::collection::vec(inner.clone(), 2..2)
+
+
+
+
+
+
+
+
.prop_map(|elems| Mul(Box::new(elems[0].clone()), Box::new(elems[1].clone()))),
+
+
+
+
+
+
+
+
prop::collection::vec(inner.clone(), 2..2)
+
+
+
+
+
+
+
+
.prop_map(|elems| Div(Box::new(elems[0].clone()), Box::new(elems[1].clone()))),
+
+
+
+
+
+
+
+
inner.prop_map(|inner| Neg(Box::new(inner.clone())))
+
+
+
+
+
+
+
+
pub fn proptest_stmts() -> impl Strategy<Value = Stmt> {
+
+
+
+
+
+
+
+
let leafs = prop_oneof![(".*", proptest_exprs()).prop_map(|(a, b)| Assign(a, b)),];
+
+
+
+
+
+
+
+
leafs.prop_recursive(10, 512, 50, |inner| {
+
+
+
+
+
+
+
+
(proptest_exprs(), prop::collection::vec(inner.clone(), 2..2)).prop_map(
+
+
+
+
+
+
+
+
move |(expr, stmts)| If(
+
+
+
+
+
+
+
+
Box::new(stmts[0].clone()),
+
+
+
+
+
+
+
+
Box::new(stmts[1].clone())
+
+
+
+
+
+
+
+
(proptest_exprs(), inner.clone())
+
+
+
+
+
+
+
+
.prop_map(move |(expr, stmt)| While(expr, Box::new(stmt))),
+
+
+
+
+
+
+
+
prop::collection::vec(inner.clone(), 0..50).prop_map(Sequence)
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/tree.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/tree.rs.html
new file mode 100644
index 000000000..c04fd2cd5
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/tree.rs.html
@@ -0,0 +1,2409 @@
+
+
+
+
+ Grcov report - tree.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 78.95 %
+
+
+
+
+
+
+
+
+
+
+
+
+
//#![cfg(feature = "unstable")]
+
+
+
+
+
+
+
+
use proptest::prelude::*;
+
+
+
+
+
+
+
+
#[derive(Clone, Debug, Eq, PartialEq)]
+
+
+
+
+
+
+
+
pub enum Tree<T: Sized + Clone + Eq> {
+
+
+
+
+
+
+
+
Many(im::Vector<Tree<T>>),
+
+
+
+
+
+
+
+
// NOTE (niklasdewally): This converts the entire tree into a list. Therefore this is only really
+
+
+
+
+
+
+
+
// worth it when we use all the children returned. This is what we use this for inside Uniplate.
+
+
+
+
+
+
+
+
// Because of this, I think a .iter() / IntoIterator for Tree<&T> is a bad idea.
+
+
+
+
+
+
+
+
impl<T: Sized + Clone + Eq + 'static> IntoIterator for Tree<T> {
+
+
+
+
+
+
+
+
type IntoIter = im::vector::ConsumingIter<T>;
+
+
+
+
+
+
+
+
fn into_iter(self) -> Self::IntoIter {
+
+
+
+
+
+
+
+
self.list().0.into_iter()
+
+
+
+
+
+
+
+
impl<T: Sized + Clone + Eq + 'static> Tree<T> {
+
+
+
+
+
+
+
+
/// Returns the tree as a list alongside a function to reconstruct the tree from a list.
+
+
+
+
+
+
+
+
/// This preserves the structure of the tree.
+
+
+
+
+
+
+
+
#[allow(clippy::type_complexity)]
+
+
+
+
+ 785
+
+
+
pub fn list(self) -> (im::Vector<T>, Box<dyn Fn(im::Vector<T>) -> Tree<T>>) {
+
+
+
+
+ 785
+
+
+
// inspired by the Uniplate Haskell equivalent Data.Generics.Str::strStructure
+
+
+
+
+ 785
+
+
+
// https://github.com/ndmitchell/uniplate/blob/master/Data/Generics/Str.hs#L85
+
+
+
+
+ 29809
+
+
+
fn flatten<T: Sized + Clone + Eq>(t: Tree<T>, xs: im::Vector<T>) -> im::Vector<T> {
+
+
+
+
+ 13521
+
+
+
let mut xs1 = xs.clone();
+
+
+
+
+ 29024
+
+
+
(Many(ts), xs) => ts.into_iter().fold(xs, |xs, t| flatten(t, xs)),
+
+
+
+
+ 785
+
+
+
// Iterate over both the old tree and the new list.
+
+
+
+
+ 785
+
+
+
// We use the node types of the old tree to know what node types to use for the new tree.
+
+
+
+
+ 10499
+
+
+
fn recons<T: Sized + Clone + Eq>(
+
+
+
+
+ 10489
+
+
+
) -> (Tree<T>, im::Vector<T>) {
+
+
+
+
+ 10489
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+ 10489
+
+
+
match (old_tree, xs) {
+
+
+
+
+ 4664
+
+
+
(Zero, xs) => (Zero, xs),
+
+
+
+
+ 4760
+
+
+
let mut xs1 = xs.clone();
+
+
+
+
+ 4760
+
+
+
(One(xs1.pop_front().unwrap()), xs1)
+
+
+
+
+ 10231
+
+
+
let (ts1, xs1) = ts.into_iter().fold((vector![], xs), |(ts1, xs), t| {
+
+
+
+
+ 10231
+
+
+
let (t1, xs1) = recons(t, xs);
+
+
+
+
+ 10231
+
+
+
let mut ts2 = ts1.clone();
+
+
+
+
+ 785
+
+
+
flatten(self.clone(), vector![]),
+
+
+
+
+ 785
+
+
+
Box::new(move |xs| recons(self.clone(), xs).0),
+
+
+
+
+
+
+
+
// Perform a map over all elements in the tree.
+
+
+
+
+ 9634
+
+
+
pub fn map(self, op: Arc<dyn Fn(T) -> T>) -> Tree<T> {
+
+
+
+
+ 4372
+
+
+
One(t) => One(op(t)),
+
+
+
+
+ 9376
+
+
+
Many(ts) => Many(ts.into_iter().map(|t| t.map(op.clone())).collect()),
+
+
+
+
+
+
+
+
// Used by proptest for generating test instances of Tree<i32>.
+
+
+
+
+ 2
+
+
+
fn proptest_integer_trees() -> impl Strategy<Value = Tree<i32>> {
+
+
+
+
+ 2
+
+
+
// https://proptest-rs.github.io/proptest/proptest/tutorial/enums.html
+
+
+
+
+ 2
+
+
+
// https://proptest-rs.github.io/proptest/proptest/tutorial/recursive.html
+
+
+
+
+ 2
+
+
+
let leaf = prop_oneof![Just(Tree::Zero), any::<i32>().prop_map(Tree::One),];
+
+
+
+
+ 2
+
+
+
512, // Shoot for maximum size of 512 nodes
+
+
+
+
+ 2
+
+
+
20, // We put up to 20 items per collection
+
+
+
+
+ 5160
+
+
+
|inner| im::proptest::vector(inner.clone(), 0..20).prop_map(Tree::Many),
+
+
+
+
+
+
+
+
// Is tree.recons() isomorphic?
+
+
+
+
+
+
+
+
fn list_is_isomorphic(tree in proptest_integer_trees()) {
+
+
+
+
+
+
+
+
let (children,func) = tree.clone().list();
+
+
+
+
+
+
+
+
let new_tree = func(children);
+
+
+
+
+
+
+
+
prop_assert_eq!(new_tree,tree);
+
+
+
+
+
+
+
+
fn map_add(tree in proptest_integer_trees(), diff in -100i32..100i32) {
+
+
+
+
+ 4372
+
+
+
let new_tree = tree.clone().map(Arc::new(move |a| a+diff));
+
+
+
+
+
+
+
+
let (old_children,_) = tree.list();
+
+
+
+
+
+
+
+
let (new_children,_) = new_tree.list();
+
+
+
+
+
+
+
+
for (old,new) in zip(old_children,new_children) {
+
+
+
+
+
+
+
+
prop_assert_eq!(old+diff,new);
+
+
+
+
+ 1
+
+
+
fn list_preserves_ordering() {
+
+
+
+
+ 1
+
+
+
let my_tree: Tree<i32> = Many(vector![
+
+
+
+
+ 1
+
+
+
Many(vector![One(0), Zero]),
+
+
+
+
+ 1
+
+
+
Many(vector![Many(vector![Zero, One(1), One(2)])]),
+
+
+
+
+ 1
+
+
+
let flat = my_tree.list().0;
+
+
+
+
+ 5
+
+
+
assert_eq!(flat[i], i.try_into().unwrap());
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/uniplate.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/uniplate.rs.html
new file mode 100644
index 000000000..9419752e5
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/src/uniplate.rs.html
@@ -0,0 +1,2409 @@
+
+
+
+
+ Grcov report - uniplate.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 26.32 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use thiserror::Error;
+
+
+
+
+
+
+
+
#[derive(Debug, PartialEq, Eq, Error)]
+
+
+
+
+
+
+
+
pub enum UniplateError {
+
+
+
+
+
+
+
+
#[error("Could not reconstruct node because wrong number of children was provided. Expected {0} children, got {1}.")]
+
+
+
+
+
+
+
+
WrongNumberOfChildren(usize, usize),
+
+
+
+
+
+
+
+
Self: Sized + Clone + Eq,
+
+
+
+
+
+
+
+
/// The `uniplate` function. Takes a node and produces a tuple of `(children, context)`, where:
+
+
+
+
+
+
+
+
/// - children is a list of the node's direct descendants of the same type
+
+
+
+
+
+
+
+
/// - context is a function to reconstruct the original node with a new list of children
+
+
+
+
+
+
+
+
/// The number of children passed to context must be the same as the number of children in
+
+
+
+
+
+
+
+
/// the original node.
+
+
+
+
+
+
+
+
/// If the number of children given is different, context returns `UniplateError::NotEnoughChildren`
+
+
+
+
+
+
+
+
#[allow(clippy::type_complexity)]
+
+
+
+
+
+
+
+
Box<dyn Fn(Vec<Self>) -> Result<Self, UniplateError> + '_>,
+
+
+
+
+
+
+
+
/// Get all children of a node, including itself and all children.
+
+
+
+
+
+
+
+
fn universe(&self) -> Vec<Self> {
+
+
+
+
+
+
+
+
let mut results = vec![self.clone()];
+
+
+
+
+
+
+
+
for child in self.children() {
+
+
+
+
+
+
+
+
results.append(&mut child.universe());
+
+
+
+
+
+
+
+
/// Get the DIRECT children of a node.
+
+
+
+
+ 70395
+
+
+
fn children(&self) -> Vec<Self> {
+
+
+
+
+
+
+
+
/// Reconstruct this node with the given children
+
+
+
+
+
+
+
+
/// - children - a vector of the same type and same size as self.children()
+
+
+
+
+ 1770
+
+
+
fn with_children(&self, children: Vec<Self>) -> Result<Self, UniplateError> {
+
+
+
+
+ 1770
+
+
+
let context = self.uniplate().1;
+
+
+
+
+
+
+
+
/// Apply the given rule to all nodes bottom up.
+
+
+
+
+ 9
+
+
+
fn transform(&self, f: fn(Self) -> Self) -> Result<Self, UniplateError> {
+
+
+
+
+ 9
+
+
+
let (children, context) = self.uniplate();
+
+
+
+
+ 9
+
+
+
let mut new_children: Vec<Self> = Vec::new();
+
+
+
+
+ 8
+
+
+
let new_ch = ch.transform(f)?;
+
+
+
+
+ 8
+
+
+
new_children.push(new_ch);
+
+
+
+
+ 9
+
+
+
let transformed = context(new_children)?;
+
+
+
+
+
+
+
+
/// Rewrite by applying a rule everywhere you can.
+
+
+
+
+
+
+
+
fn rewrite(&self, f: fn(Self) -> Option<Self>) -> Result<Self, UniplateError> {
+
+
+
+
+
+
+
+
let (children, context) = self.uniplate();
+
+
+
+
+
+
+
+
let mut new_children: Vec<Self> = Vec::new();
+
+
+
+
+
+
+
+
let new_ch = ch.rewrite(f)?;
+
+
+
+
+
+
+
+
new_children.push(new_ch);
+
+
+
+
+
+
+
+
let node: Self = context(new_children)?;
+
+
+
+
+
+
+
+
Ok(f(node.clone()).unwrap_or(node))
+
+
+
+
+
+
+
+
/// Perform a transformation on all the immediate children, then combine them back.
+
+
+
+
+
+
+
+
/// This operation allows additional information to be passed downwards, and can be used to provide a top-down transformation.
+
+
+
+
+
+
+
+
fn descend(&self, f: fn(Self) -> Self) -> Result<Self, UniplateError> {
+
+
+
+
+
+
+
+
let (children, context) = self.uniplate();
+
+
+
+
+
+
+
+
let children: Vec<Self> = children.into_iter().map(f).collect();
+
+
+
+
+
+
+
+
/// Perform a fold-like computation on each value.
+
+
+
+
+
+
+
+
/// Working from the bottom up, this applies the given callback function to each nested
+
+
+
+
+
+
+
+
/// Unlike [`transform`](Uniplate::transform), this returns an arbitrary type, and is not
+
+
+
+
+
+
+
+
/// limited to T -> T transformations. In other words, it can transform a type into a new
+
+
+
+
+
+
+
+
/// The meaning of the callback function is the following:
+
+
+
+
+
+
+
+
/// f(element_to_fold, folded_children) -> folded_element
+
+
+
+
+
+
+
+
fn fold<T>(&self, op: fn(Self, Vec<T>) -> T) -> T {
+
+
+
+
+
+
+
+
self.children().into_iter().map(|c| c.fold(op)).collect(),
+
+
+
+
+
+
+
+
/// Get the nth one holed context.
+
+
+
+
+
+
+
+
/// A uniplate context for type T has holes where all the nested T's should be.
+
+
+
+
+
+
+
+
/// This is encoded as a function Vec<T> -> T.
+
+
+
+
+
+
+
+
/// On the other hand, the nth one-holed context has only one hole where the nth nested
+
+
+
+
+
+
+
+
/// instance of T would be.
+
+
+
+
+
+
+
+
/// Eg. for some type:
+
+
+
+
+
+
+
+
/// F(A,Expr,A,Expr,A),
+
+
+
+
+
+
+
+
/// The 1st one-holed context of `F` (using 0-indexing) would be:
+
+
+
+
+
+
+
+
/// |HOLE| F(a,b,c,HOLE,e)
+
+
+
+
+
+
+
+
/// Used primarily in the implementation of Zippers.
+
+
+
+
+
+
+
+
fn one_holed_context(&self, n: usize) -> Option<Box<dyn Fn(Self) -> Self + '_>> {
+
+
+
+
+
+
+
+
let (children, context) = self.uniplate();
+
+
+
+
+
+
+
+
let number_of_elems = children.len();
+
+
+
+
+
+
+
+
if n >= number_of_elems {
+
+
+
+
+
+
+
+
Some(Box::new(move |x| {
+
+
+
+
+
+
+
+
let mut children = children.clone();
+
+
+
+
+
+
+
+
#[allow(clippy::unwrap_used)]
+
+
+
+
+
+
+
+
// We are directly replacing a child so there can't be an error
+
+
+
+
+
+
+
+
context(children).unwrap()
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/tests/expr_stmt_manual.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/tests/expr_stmt_manual.rs.html
new file mode 100644
index 000000000..934c7fe6a
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/tests/expr_stmt_manual.rs.html
@@ -0,0 +1,7225 @@
+
+
+
+
+ Grcov report - expr_stmt_manual.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 17.65 %
+
+
+
+
+
+
+
+
+
+
+
+
+
// Expr and Stmt from the paper, manually derived.
+
+
+
+
+
+
+
+
//use uniplate::test_common::paper::*;
+
+
+
+
+
+
+
+
use uniplate::biplate::*;
+
+
+
+
+
+
+
+
// Stmt and Expr to demonstrate and test multitype traversals.
+
+
+
+
+
+
+
+
#[derive(Eq, PartialEq, Clone, Debug)]
+
+
+
+
+
+
+
+
Assign(String, Expr),
+
+
+
+
+
+
+
+
If(Expr, Box<Stmt>, Box<Stmt>),
+
+
+
+
+
+
+
+
While(Expr, Box<Stmt>),
+
+
+
+
+
+
+
+
#[derive(Eq, PartialEq, Clone, Debug)]
+
+
+
+
+
+
+
+
Add(Box<Expr>, Box<Expr>),
+
+
+
+
+
+
+
+
Sub(Box<Expr>, Box<Expr>),
+
+
+
+
+
+
+
+
Mul(Box<Expr>, Box<Expr>),
+
+
+
+
+
+
+
+
Div(Box<Expr>, Box<Expr>),
+
+
+
+
+
+
+
+
impl Uniplate for Expr {
+
+
+
+
+ 8
+
+
+
fn uniplate(&self) -> (Tree<Self>, Box<dyn Fn(Tree<Self>) -> Self>) {
+
+
+
+
+ 2
+
+
+
// Field 0 - Box<Expr>
+
+
+
+
+ 2
+
+
+
let (f0_tree, f0_ctx) = <Expr as Biplate<Expr>>::biplate(&*f0);
+
+
+
+
+ 2
+
+
+
// Field 1 - Box<Expr>
+
+
+
+
+ 2
+
+
+
let (f1_tree, f1_ctx) = <Expr as Biplate<Expr>>::biplate(&*f1);
+
+
+
+
+ 2
+
+
+
let tree = Many(vector![f0_tree, f1_tree]);
+
+
+
+
+ 2
+
+
+
let ctx = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(ts) = new_tree else { panic!() };
+
+
+
+
+
+
+
+
assert_eq!(ts.len(), 2);
+
+
+
+
+
+
+
+
Box::new(f0_ctx(ts[0].clone())),
+
+
+
+
+
+
+
+
Box::new(f1_ctx(ts[1].clone())),
+
+
+
+
+
+
+
+
// Field 0 - Box<Expr>
+
+
+
+
+
+
+
+
let (f0_tree, f0_ctx) = <Expr as Biplate<Expr>>::biplate(&*f0);
+
+
+
+
+
+
+
+
// Field 1 - Box<Expr>
+
+
+
+
+
+
+
+
let (f1_tree, f1_ctx) = <Expr as Biplate<Expr>>::biplate(&*f1);
+
+
+
+
+
+
+
+
let tree = Many(vector![f0_tree, f1_tree]);
+
+
+
+
+
+
+
+
let ctx = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(ts) = new_tree else { panic!() };
+
+
+
+
+
+
+
+
assert_eq!(ts.len(), 2);
+
+
+
+
+
+
+
+
Box::new(f0_ctx(ts[0].clone())),
+
+
+
+
+
+
+
+
Box::new(f1_ctx(ts[1].clone())),
+
+
+
+
+
+
+
+
// Field 0 - Box<Expr>
+
+
+
+
+
+
+
+
let (f0_tree, f0_ctx) = <Expr as Biplate<Expr>>::biplate(&*f0);
+
+
+
+
+
+
+
+
// Field 1 - Box<Expr>
+
+
+
+
+
+
+
+
let (f1_tree, f1_ctx) = <Expr as Biplate<Expr>>::biplate(&*f1);
+
+
+
+
+
+
+
+
let tree = Many(vector![f0_tree, f1_tree]);
+
+
+
+
+
+
+
+
let ctx = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(ts) = new_tree else { panic!() };
+
+
+
+
+
+
+
+
assert_eq!(ts.len(), 2);
+
+
+
+
+
+
+
+
Box::new(f0_ctx(ts[0].clone())),
+
+
+
+
+
+
+
+
Box::new(f1_ctx(ts[1].clone())),
+
+
+
+
+
+
+
+
// Field 0 - Box<Expr>
+
+
+
+
+
+
+
+
let (f0_tree, f0_ctx) = <Expr as Biplate<Expr>>::biplate(&*f0);
+
+
+
+
+
+
+
+
// Field 1 - Box<Expr>
+
+
+
+
+
+
+
+
let (f1_tree, f1_ctx) = <Expr as Biplate<Expr>>::biplate(&*f1);
+
+
+
+
+
+
+
+
let tree = Many(vector![f0_tree, f1_tree]);
+
+
+
+
+
+
+
+
let ctx = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(ts) = new_tree else { panic!() };
+
+
+
+
+
+
+
+
assert_eq!(ts.len(), 2);
+
+
+
+
+
+
+
+
Box::new(f0_ctx(ts[0].clone())),
+
+
+
+
+
+
+
+
Box::new(f1_ctx(ts[1].clone())),
+
+
+
+
+
+
+
+
let Zero = x else { panic!() };
+
+
+
+
+
+
+
+
let Zero = x else { panic!() };
+
+
+
+
+
+
+
+
let Zero = x else { panic!() };
+
+
+
+
+
+
+
+
impl Biplate<Stmt> for Expr {
+
+
+
+
+
+
+
+
fn biplate(&self) -> (Tree<Stmt>, Box<dyn Fn(Tree<Stmt>) -> Expr>) {
+
+
+
+
+
+
+
+
// Optimisation - in derivation, build index of types that lead to eachother.
+
+
+
+
+
+
+
+
// Walk this graph to generate all "reachable types from expr"
+
+
+
+
+
+
+
+
// Stmt is not reachable so just return 0.
+
+
+
+
+
+
+
+
// Paper does this with the combinators!
+
+
+
+
+
+
+
+
// We may also need this to know what Biplates to derive!
+
+
+
+
+
+
+
+
let expr = self.clone();
+
+
+
+
+
+
+
+
Box::new(move |stmt| {
+
+
+
+
+
+
+
+
let Zero = stmt else { panic!() };
+
+
+
+
+
+
+
+
//this is the most interesting example!
+
+
+
+
+
+
+
+
impl Biplate<Expr> for Stmt {
+
+
+
+
+ 12
+
+
+
fn biplate(&self) -> (Tree<Expr>, Box<dyn Fn(Tree<Expr>) -> Stmt>) {
+
+
+
+
+ 4
+
+
+
// Field 0 - non recursive (String)
+
+
+
+
+ 4
+
+
+
let (f0_tree, f0_ctx) = (
+
+
+
+
+ 4
+
+
+
Box::new(move |_: Tree<Expr>| f0.clone()),
+
+
+
+
+ 4
+
+
+
//field 1 - Expr - target type
+
+
+
+
+ 4
+
+
+
let (f1_tree, f1_ctx) = <Expr as Biplate<Expr>>::biplate(&f1);
+
+
+
+
+ 4
+
+
+
let tree = Tree::<Expr>::Many(vector![f0_tree, f1_tree]);
+
+
+
+
+ 4
+
+
+
let ctx = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(ts) = new_tree else { panic!() };
+
+
+
+
+
+
+
+
assert_eq!(ts.len(), 2);
+
+
+
+
+
+
+
+
Assign(f0_ctx(ts[0].clone()), f1_ctx(ts[1].clone()))
+
+
+
+
+ 4
+
+
+
// Field 0 - Vec<Stmt>
+
+
+
+
+ 4
+
+
+
// Get trees and contexts for each element.
+
+
+
+
+ 4
+
+
+
let (f0_elems, f0_ctxs): (Vec<Tree<Expr>>, Vec<Box<dyn Fn(Tree<Expr>) -> Stmt>>) =
+
+
+
+
+ 4
+
+
+
.map(|stmt| <Stmt as Biplate<Expr>>::biplate(&stmt))
+
+
+
+
+ 4
+
+
+
let f0_tree = Many(f0_elems.into());
+
+
+
+
+ 4
+
+
+
let f0_ctx: Box<dyn Fn(Tree<Expr>) -> Vec<Stmt>> = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(elem_ts) = new_tree else {
+
+
+
+
+
+
+
+
zip(&f0_ctxs, elem_ts).map(|(ctx, t)| (**ctx)(t)).collect()
+
+
+
+
+ 4
+
+
+
let tree = Many(vector![f0_tree]);
+
+
+
+
+ 4
+
+
+
let ctx = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(ts) = new_tree else {
+
+
+
+
+
+
+
+
assert_eq!(ts.len(), 1);
+
+
+
+
+
+
+
+
Sequence(f0_ctx(ts[0].clone()))
+
+
+
+
+ 2
+
+
+
let (f0_tree, f0_ctx) = <Expr as Biplate<Expr>>::biplate(&f0);
+
+
+
+
+ 2
+
+
+
// Field 1 - Box::(stmt)
+
+
+
+
+ 2
+
+
+
let (f1_tree, f1_ctx) = <Stmt as Biplate<Expr>>::biplate(&*f1);
+
+
+
+
+ 2
+
+
+
//Field 2 - Box::(Stmt)
+
+
+
+
+ 2
+
+
+
let (f2_tree, f2_ctx) = <Stmt as Biplate<Expr>>::biplate(&*f2);
+
+
+
+
+ 2
+
+
+
let tree = Many(vector![f0_tree, f1_tree, f2_tree]);
+
+
+
+
+ 2
+
+
+
let ctx = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(ts) = new_tree else { panic!() };
+
+
+
+
+
+
+
+
assert_eq!(ts.len(), 3);
+
+
+
+
+
+
+
+
f0_ctx(ts[0].clone()),
+
+
+
+
+
+
+
+
Box::new(f1_ctx(ts[1].clone())),
+
+
+
+
+
+
+
+
Box::new(f2_ctx(ts[2].clone())),
+
+
+
+
+ 2
+
+
+
let (f0_tree, f0_ctx) = <Expr as Biplate<Expr>>::biplate(&f0);
+
+
+
+
+ 2
+
+
+
//Field 1 - Box::(Stmt)
+
+
+
+
+ 2
+
+
+
let (f1_tree, f1_ctx) = <Stmt as Biplate<Expr>>::biplate(&*f1);
+
+
+
+
+ 2
+
+
+
let tree = Many(vector![f0_tree, f1_tree]);
+
+
+
+
+ 2
+
+
+
let ctx = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(ts) = new_tree else { panic!() };
+
+
+
+
+
+
+
+
assert_eq!(ts.len(), 2);
+
+
+
+
+
+
+
+
While(f0_ctx(ts[0].clone()), Box::new(f1_ctx(ts[1].clone())))
+
+
+
+
+
+
+
+
impl Biplate<Expr> for Expr {
+
+
+
+
+ 12
+
+
+
fn biplate(&self) -> (Tree<Expr>, Box<dyn Fn(Tree<Expr>) -> Self>) {
+
+
+
+
+
+
+
+
let One(stmt) = t else { panic!() };
+
+
+
+
+
+
+
+
impl Biplate<Stmt> for Stmt {
+
+
+
+
+
+
+
+
fn biplate(&self) -> (Tree<Stmt>, Box<dyn Fn(Tree<Stmt>) -> Self>) {
+
+
+
+
+
+
+
+
let One(stmt) = t else { panic!() };
+
+
+
+
+
+
+
+
impl Uniplate for Stmt {
+
+
+
+
+
+
+
+
fn uniplate(&self) -> (Tree<Stmt>, Box<dyn Fn(Tree<Stmt>) -> Stmt>) {
+
+
+
+
+
+
+
+
// Field 0 - non recursive (String)
+
+
+
+
+
+
+
+
let (f0_tree, f0_ctx) =
+
+
+
+
+
+
+
+
(Tree::<Stmt>::Zero, Box::new(move |_: Tree<Stmt>| s.clone()));
+
+
+
+
+
+
+
+
// Field 1- ADT (Expr)
+
+
+
+
+
+
+
+
let (f1_tree, f1_ctx) = <Expr as Biplate<Stmt>>::biplate(&expr);
+
+
+
+
+
+
+
+
// we know there is no path Expr -> Stmt, so we could just inline the zero
+
+
+
+
+
+
+
+
// defintion (see Biplate<Stmt> for Expr comments)
+
+
+
+
+
+
+
+
// let (f1_tree,f1_ctx) (Zero, Box::new(move |stmt| {let Zero = stmt else {panic!()}; f1.clone()}));
+
+
+
+
+
+
+
+
let tree = Many(vector![f0_tree, f1_tree]);
+
+
+
+
+
+
+
+
let ctx = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(ts) = new_tree else { panic!() };
+
+
+
+
+
+
+
+
assert_eq!(ts.len(), 2);
+
+
+
+
+
+
+
+
Assign(f0_ctx(ts[0].clone()), f1_ctx(ts[1].clone()))
+
+
+
+
+
+
+
+
// Field 0 - Vec<Stmt>
+
+
+
+
+
+
+
+
// Special case for iterables / lists?
+
+
+
+
+
+
+
+
// Get trees and contexts for each element.
+
+
+
+
+
+
+
+
let (f0_elems, f0_ctxs): (Vec<Tree<Stmt>>, Vec<Box<dyn Fn(Tree<Stmt>) -> Stmt>>) =
+
+
+
+
+
+
+
+
.map(|stmt| <Stmt as Biplate<Stmt>>::biplate(&stmt))
+
+
+
+
+
+
+
+
let f0_tree = Many(f0_elems.into());
+
+
+
+
+
+
+
+
let f0_ctx: Box<dyn Fn(Tree<Stmt>) -> Vec<Stmt>> = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(elem_ts) = new_tree else {
+
+
+
+
+
+
+
+
zip(&f0_ctxs, elem_ts).map(|(ctx, t)| (**ctx)(t)).collect()
+
+
+
+
+
+
+
+
let tree = Many(vector![f0_tree]);
+
+
+
+
+
+
+
+
let ctx = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(ts) = new_tree else {
+
+
+
+
+
+
+
+
assert_eq!(ts.len(), 1);
+
+
+
+
+
+
+
+
Sequence(f0_ctx(ts[0].clone()))
+
+
+
+
+
+
+
+
let (f0_tree, f0_ctx) = <Expr as Biplate<Stmt>>::biplate(&f0);
+
+
+
+
+
+
+
+
//field 1 - box::(stmt)
+
+
+
+
+
+
+
+
// Treat T, Box<T> as a special case as defining Uniplate and Biplate for Box<T> is
+
+
+
+
+
+
+
+
// a lot of moving things from stack to heap and back for no reason.
+
+
+
+
+
+
+
+
let (f1_tree, f1_ctx) = <Stmt as Biplate<Stmt>>::biplate(&*f1);
+
+
+
+
+
+
+
+
//Field 2 - Box::(Stmt)
+
+
+
+
+
+
+
+
let (f2_tree, f2_ctx) = <Stmt as Biplate<Stmt>>::biplate(&*f2);
+
+
+
+
+
+
+
+
let tree = Many(vector![f0_tree, f1_tree, f2_tree]);
+
+
+
+
+
+
+
+
let ctx = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(ts) = new_tree else { panic!() };
+
+
+
+
+
+
+
+
assert_eq!(ts.len(), 3);
+
+
+
+
+
+
+
+
f0_ctx(ts[0].clone()),
+
+
+
+
+
+
+
+
Box::new(f1_ctx(ts[1].clone())),
+
+
+
+
+
+
+
+
Box::new(f2_ctx(ts[2].clone())),
+
+
+
+
+
+
+
+
let (f0_tree, f0_ctx) = <Expr as Biplate<Stmt>>::biplate(&f0);
+
+
+
+
+
+
+
+
//Field 1 - Box::(Stmt)
+
+
+
+
+
+
+
+
let (f1_tree, f1_ctx) = <Stmt as Biplate<Stmt>>::biplate(&*f1);
+
+
+
+
+
+
+
+
let tree = Many(vector![f0_tree, f1_tree]);
+
+
+
+
+
+
+
+
let ctx = Box::new(move |new_tree| {
+
+
+
+
+
+
+
+
let Many(ts) = new_tree else { panic!() };
+
+
+
+
+
+
+
+
assert_eq!(ts.len(), 2);
+
+
+
+
+
+
+
+
While(f0_ctx(ts[0].clone()), Box::new(f1_ctx(ts[1].clone())))
+
+
+
+
+ 1
+
+
+
fn children_bi_multitype() {
+
+
+
+
+ 1
+
+
+
let my_stmt = Sequence(vec![
+
+
+
+
+ 1
+
+
+
Add(Box::new(Var("x".to_owned())), Box::new(Val(10))),
+
+
+
+
+ 1
+
+
+
Var("x".to_string()),
+
+
+
+
+ 1
+
+
+
Add(Box::new(Var("x".to_owned())), Box::new(Val(10))),
+
+
+
+
+ 1
+
+
+
Box::new(Sequence(vec![])),
+
+
+
+
+ 1
+
+
+
let expected_expr_children = 4;
+
+
+
+
+ 1
+
+
+
let children = <Stmt as Biplate<Expr>>::children_bi(&my_stmt);
+
+
+
+
+ 1
+
+
+
assert_eq!(expected_expr_children, children.len());
+
+
+
+
+ 1
+
+
+
println!("{:?}", children);
+
+
+
+
+ 1
+
+
+
let Val(_) = children[0] else { panic!() };
+
+
+
+
+ 1
+
+
+
let Add(_, _) = children[1] else { panic!() };
+
+
+
+
+ 1
+
+
+
let Var(_) = children[2] else { panic!() };
+
+
+
+
+ 1
+
+
+
let Add(_, _) = children[3] else { panic!() };
+
+
+
+
+ 1
+
+
+
fn universe_bi_multitype() {
+
+
+
+
+ 1
+
+
+
let my_stmt = Sequence(vec![
+
+
+
+
+ 1
+
+
+
Add(Box::new(Var("x".to_owned())), Box::new(Val(10))),
+
+
+
+
+ 1
+
+
+
Var("x".to_string()),
+
+
+
+
+ 1
+
+
+
Add(Box::new(Var("x".to_owned())), Box::new(Val(10))),
+
+
+
+
+ 1
+
+
+
Box::new(Sequence(vec![])),
+
+
+
+
+ 1
+
+
+
let expected_expr_universe = 8;
+
+
+
+
+ 1
+
+
+
let children = <Stmt as Biplate<Expr>>::universe_bi(&my_stmt);
+
+
+
+
+ 1
+
+
+
assert_eq!(expected_expr_universe, children.len());
+
+
+
+
+ 1
+
+
+
println!("{:?}", children);
+
+
+
+
+ 1
+
+
+
let Val(_) = children[0] else { panic!() };
+
+
+
+
+ 1
+
+
+
let Add(_, _) = children[1] else { panic!() };
+
+
+
+
+ 1
+
+
+
let Var(_) = children[2] else { panic!() };
+
+
+
+
+ 1
+
+
+
let Val(_) = children[3] else { panic!() };
+
+
+
+
+ 1
+
+
+
let Var(_) = children[4] else { panic!() };
+
+
+
+
+ 1
+
+
+
let Add(_, _) = children[5] else { panic!() };
+
+
+
+
+ 1
+
+
+
let Var(_) = children[6] else { panic!() };
+
+
+
+
+ 1
+
+
+
let Val(_) = children[7] else { panic!() };
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/tests/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/tests/index.html
new file mode 100644
index 000000000..2367e32f5
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate/tests/index.html
@@ -0,0 +1,74 @@
+
+
+
+
+ Grcov report - crates/uniplate/tests
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 17.65 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ expr_stmt_manual.rs
+
+
+
+ 43.48%
+
+
+
+ 43.48%
+
+
+ 160 / 368
+
+
+ 17.65%
+ 6 / 34
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/index.html
new file mode 100644
index 000000000..8df26792f
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/index.html
@@ -0,0 +1,74 @@
+
+
+
+
+ Grcov report - crates/uniplate_derive/src
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 51.61 %
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ lib.rs
+
+
+
+ 96.9%
+
+
+
+ 96.9%
+
+
+ 125 / 129
+
+
+ 51.61%
+ 16 / 31
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/lib.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/lib.rs.html
new file mode 100644
index 000000000..a622fb6e0
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/lib.rs.html
@@ -0,0 +1,3081 @@
+
+
+
+
+ Grcov report - lib.rs
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 51.61 %
+
+
+
+
+
+
+
+
+
+
+
+
+
use proc_macro::{self, TokenStream};
+
+
+
+
+
+
+
+
use proc_macro2::TokenStream as TokenStream2;
+
+
+
+
+
+
+
+
use quote::{format_ident, quote};
+
+
+
+
+
+
+
+
use syn::{parse_macro_input, Data, DataEnum, DeriveInput, Ident, Variant};
+
+
+
+
+
+
+
+
use crate::utils::generate::{generate_field_clones, generate_field_fills, generate_field_idents};
+
+
+
+
+
+
+
+
/// Generate the full match pattern for a variant
+
+
+
+
+ 252
+
+
+
fn generate_match_pattern(variant: &Variant, root_ident: &Ident) -> TokenStream2 {
+
+
+
+
+ 252
+
+
+
let field_idents = generate_field_idents(&variant.fields);
+
+
+
+
+ 252
+
+
+
let variant_ident = &variant.ident;
+
+
+
+
+ 252
+
+
+
if field_idents.is_empty() {
+
+
+
+
+ 12
+
+
+
#root_ident::#variant_ident
+
+
+
+
+ 240
+
+
+
#root_ident::#variant_ident(#(#field_idents,)*)
+
+
+
+
+
+
+
+
/// Generate the code to get the children of a variant
+
+
+
+
+ 126
+
+
+
fn generate_variant_children_match_arm(variant: &Variant, root_ident: &Ident) -> TokenStream2 {
+
+
+
+
+ 126
+
+
+
let field_clones = generate_field_clones(&variant.fields, root_ident);
+
+
+
+
+ 126
+
+
+
let match_pattern = generate_match_pattern(variant, root_ident);
+
+
+
+
+ 126
+
+
+
let clones = if field_clones.is_empty() {
+
+
+
+
+ 106
+
+
+
vec![#(#field_clones,)*].iter().flatten().cloned().collect::<Vec<_>>()
+
+
+
+
+ 126
+
+
+
let mach_arm = quote! {
+
+
+
+
+
+
+
+
/// Generate an implementation of `context` for a variant
+
+
+
+
+ 126
+
+
+
fn generate_variant_context_match_arm(variant: &Variant, root_ident: &Ident) -> TokenStream2 {
+
+
+
+
+ 126
+
+
+
let variant_ident = &variant.ident;
+
+
+
+
+ 126
+
+
+
let children_ident = Ident::new("children", variant_ident.span());
+
+
+
+
+ 126
+
+
+
let field_fills = generate_field_fills(&variant.fields, root_ident, &children_ident);
+
+
+
+
+ 126
+
+
+
let error_ident = format_ident!("UniplateError{}", root_ident);
+
+
+
+
+ 126
+
+
+
let match_pattern = generate_match_pattern(variant, root_ident);
+
+
+
+
+ 126
+
+
+
if field_fills.is_empty() {
+
+
+
+
+ 6
+
+
+
Box::new(|_| Ok(#root_ident::#variant_ident))
+
+
+
+
+ 120
+
+
+
Box::new(|children| {
+
+
+
+
+ 120
+
+
+
if (children.len() != self.children().len()) {
+
+
+
+
+ 120
+
+
+
return Err(#error_ident::WrongNumberOfChildren(self.children().len(), children.len()));
+
+
+
+
+ 120
+
+
+
let mut #children_ident = children.clone();
+
+
+
+
+ 120
+
+
+
Ok(#root_ident::#variant_ident(#(#field_fills,)*))
+
+
+
+
+
+
+
+
/// Derive the `Uniplate` trait for an arbitrary type
+
+
+
+
+
+
+
+
/// This is alpha code. It is not yet stable and some features are missing.
+
+
+
+
+
+
+
+
/// - Deriving `Uniplate` for enum types
+
+
+
+
+
+
+
+
/// - `Box<T>` and `Vec<T>` fields, including nested vectors
+
+
+
+
+
+
+
+
/// - Tuple fields, including nested tuples - e.g. `(Vec<T>, (Box<T>, i32))`
+
+
+
+
+
+
+
+
/// ## What does not work?
+
+
+
+
+
+
+
+
/// - Multiple type arguments - e.g. `MyType<T, R>`
+
+
+
+
+
+
+
+
/// - Any complex type arguments, e.g. `MyType<T: MyTrait1 + MyTrait2>`
+
+
+
+
+
+
+
+
/// - Any collection type other than `Vec`
+
+
+
+
+
+
+
+
/// - Any box type other than `Box`
+
+
+
+
+
+
+
+
/// This macro is intended to replace a hand-coded implementation of the `Uniplate` trait.
+
+
+
+
+ 1
+
+
+
/// use uniplate_derive::Uniplate;
+
+
+
+
+ 1
+
+
+
/// use uniplate::uniplate::Uniplate;
+
+
+
+
+ 1
+
+
+
/// #[derive(PartialEq, Eq, Debug, Clone, Uniplate)]
+
+
+
+
+ 1
+
+
+
/// let a = MyEnum::A(Box::new(MyEnum::C(42)));
+
+
+
+
+ 1
+
+
+
/// let (children, context) = a.uniplate();
+
+
+
+
+ 1
+
+
+
/// assert_eq!(children, vec![MyEnum::C(42)]);
+
+
+
+
+ 1
+
+
+
/// assert_eq!(context(vec![MyEnum::C(42)]).unwrap(), a);
+
+
+
+
+
+
+
+
#[proc_macro_derive(Uniplate)]
+
+
+
+
+ 8
+
+
+
pub fn derive(macro_input: TokenStream) -> TokenStream {
+
+
+
+
+ 8
+
+
+
let input = parse_macro_input!(macro_input as DeriveInput);
+
+
+
+
+ 8
+
+
+
let root_ident = &input.ident;
+
+
+
+
+ 8
+
+
+
let data = &input.data;
+
+
+
+
+ 8
+
+
+
let children_impl: TokenStream2 = match data {
+
+
+
+
+
+
+
+
Data::Struct(_) => unimplemented!("Structs currently not supported"), // ToDo support structs
+
+
+
+
+
+
+
+
Data::Union(_) => unimplemented!("Unions currently not supported"), // ToDo support unions
+
+
+
+
+ 8
+
+
+
Data::Enum(DataEnum { variants, .. }) => {
+
+
+
+
+ 8
+
+
+
let match_arms: Vec<TokenStream2> = variants
+
+
+
+
+ 126
+
+
+
.map(|vt| generate_variant_children_match_arm(vt, root_ident))
+
+
+
+
+ 8
+
+
+
.collect::<Vec<_>>();
+
+
+
+
+ 8
+
+
+
let match_statement = quote! {
+
+
+
+
+ 8
+
+
+
let context_impl = match data {
+
+
+
+
+
+
+
+
Data::Struct(_) => unimplemented!("Structs currently not supported"),
+
+
+
+
+
+
+
+
Data::Union(_) => unimplemented!("Unions currently not supported"),
+
+
+
+
+ 8
+
+
+
Data::Enum(DataEnum { variants, .. }) => {
+
+
+
+
+ 8
+
+
+
let match_arms: Vec<TokenStream2> = variants
+
+
+
+
+ 126
+
+
+
.map(|vt| generate_variant_context_match_arm(vt, root_ident))
+
+
+
+
+ 8
+
+
+
.collect::<Vec<_>>();
+
+
+
+
+ 8
+
+
+
let match_statement = quote! {
+
+
+
+
+ 8
+
+
+
let error_ident = format_ident!("UniplateError{}", root_ident);
+
+
+
+
+ 8
+
+
+
let output = quote! {
+
+
+
+
+ 8
+
+
+
use uniplate::uniplate::UniplateError as #error_ident;
+
+
+
+
+ 8
+
+
+
impl Uniplate for #root_ident {
+
+
+
+
+ 8
+
+
+
#[allow(unused_variables)]
+
+
+
+
+ 8
+
+
+
fn uniplate(&self) -> (Vec<#root_ident>, Box<dyn Fn(Vec<#root_ident>) -> Result<#root_ident, #error_ident> + '_>) {
+
+
+
+
+ 8
+
+
+
let context: Box<dyn Fn(Vec<#root_ident>) -> Result<#root_ident, #error_ident>> = #context_impl;
+
+
+
+
+ 8
+
+
+
let children: Vec<#root_ident> = #children_impl;
+
+
+
+
+ 8
+
+
+
// println!("Final macro output:\n{}", output.to_string());
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/utils/generate.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/utils/generate.rs.html
new file mode 100644
index 000000000..95e8cd59c
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/utils/generate.rs.html
@@ -0,0 +1,3001 @@
+
+
+
+
+ Grcov report - generate.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use proc_macro2::{Ident, Literal, TokenStream as TokenStream2};
+
+
+
+
+
+
+
+
use quote::{quote, ToTokens};
+
+
+
+
+
+
+
+
use syn::spanned::Spanned;
+
+
+
+
+
+
+
+
use syn::{Field, Fields};
+
+
+
+
+
+
+
+
use crate::utils::parse::{check_field_type, parse_field_type, UniplateField};
+
+
+
+
+
+
+
+
/// Generate the code to fill a field in a variant
+
+
+
+
+ 481
+
+
+
field_ident: &TokenStream2,
+
+
+
+
+ 481
+
+
+
if check_field_type(ft, root_ident) {
+
+
+
+
+
+
+
+
// If the field or at least one of its children is a type we want to fill
+
+
+
+
+
+
+
+
UniplateField::Identifier(_) => {
+
+
+
+
+ 176
+
+
+
#exprs_ident.remove(0) // If it is an identifier, take the next child from the list
+
+
+
+
+ 123
+
+
+
UniplateField::Box(_, subfield) => {
+
+
+
+
+ 123
+
+
+
let sf = subfield.as_ref();
+
+
+
+
+ 123
+
+
+
let sf_fill = get_fill(sf, exprs_ident, field_ident, root_ident);
+
+
+
+
+ 123
+
+
+
Box::new(#sf_fill) // If it is a box, generate the fill for the inner type and box it
+
+
+
+
+ 54
+
+
+
UniplateField::Vector(_, subfield) => {
+
+
+
+
+ 54
+
+
+
let sf = subfield.as_ref();
+
+
+
+
+ 54
+
+
+
let sf_fill = get_fill(sf, exprs_ident, field_ident, root_ident);
+
+
+
+
+ 54
+
+
+
return quote! { // The size is not known at compile time, so generate a loop to fill the vector (using the appropriate fill for the inner type)
+
+
+
+
+ 54
+
+
+
let mut elems: Vec<_> = Vec::new();
+
+
+
+
+ 54
+
+
+
for i in 0..#field_ident.len() { // The length of vectors must not change, so we can use the length of the field to determine how many children to take
+
+
+
+
+ 3
+
+
+
UniplateField::Tuple(_, sfs) => {
+
+
+
+
+ 3
+
+
+
let mut sf_fills: Vec<TokenStream2> = Vec::new();
+
+
+
+
+ 6
+
+
+
for (i, sf) in sfs.iter().enumerate() {
+
+
+
+
+ 6
+
+
+
// Recursively generate the fill for each field in the tuple
+
+
+
+
+ 6
+
+
+
let i_literal = Literal::usize_unsuffixed(i);
+
+
+
+
+ 6
+
+
+
let sf_ident = quote! {
+
+
+
+
+ 6
+
+
+
#field_ident.#i_literal
+
+
+
+
+ 6
+
+
+
sf_fills.push(get_fill(sf, exprs_ident, &sf_ident, root_ident));
+
+
+
+
+ 3
+
+
+
(#(#sf_fills,)*) // Wrap the fills in a tuple
+
+
+
+
+
+
+
+
UniplateField::Array(_, _, _) => {
+
+
+
+
+
+
+
+
unimplemented!("Arrays not currently supported") // ToDo support arrays
+
+
+
+
+
+
+
+
UniplateField::Unknown(_) => {}
+
+
+
+
+ 125
+
+
+
#field_ident.clone() // If the field is not a type we want to fill, just keep it
+
+
+
+
+
+
+
+
/// Generate the code to clone a field in a variant
+
+
+
+
+ 481
+
+
+
field_ident: TokenStream2,
+
+
+
+
+ 481
+
+
+
) -> Option<TokenStream2> {
+
+
+
+
+ 481
+
+
+
if check_field_type(ft, root_ident) {
+
+
+
+
+
+
+
+
// If the field or at least one of its children is a type we want to clone
+
+
+
+
+
+
+
+
UniplateField::Identifier(_) => {
+
+
+
+
+ 176
+
+
+
vec![#field_ident.clone()] // If it is an identifier, clone it. We still need to wrap it in a vec to use .flatten() on the final list.
+
+
+
+
+ 123
+
+
+
UniplateField::Box(_, inner) => {
+
+
+
+
+ 123
+
+
+
let sf = inner.as_ref();
+
+
+
+
+ 123
+
+
+
let box_clone = quote! { // Generate the prefix for getting the inner type out of the box
+
+
+
+
+ 123
+
+
+
#field_ident.as_ref().clone()
+
+
+
+
+ 123
+
+
+
return get_clone(sf, box_clone, root_ident); // Then generate the clone for the inner type
+
+
+
+
+ 54
+
+
+
UniplateField::Vector(_, inner) => {
+
+
+
+
+ 54
+
+
+
let sf = inner.as_ref();
+
+
+
+
+ 54
+
+
+
let sf_ident = Ident::new("sf", sf.span()).into_token_stream(); // Identity for the subfields
+
+
+
+
+ 54
+
+
+
let sf_clone = get_clone(sf, sf_ident, root_ident); // Clone for the subfields
+
+
+
+
+ 54
+
+
+
#field_ident.iter().flat_map(|sf| #sf_clone).collect::<Vec<_>>() // If it is a vector, generate the clone for the inner type and flatten the list
+
+
+
+
+ 3
+
+
+
UniplateField::Tuple(_, sfs) => {
+
+
+
+
+ 3
+
+
+
let mut sf_clones: Vec<TokenStream2> = Vec::new();
+
+
+
+
+ 6
+
+
+
for (i, sf) in sfs.iter().enumerate() {
+
+
+
+
+
+
+
+
// Recursively generate the clone for each field in the tuple
+
+
+
+
+ 6
+
+
+
let i_literal = Literal::usize_unsuffixed(i);
+
+
+
+
+ 6
+
+
+
let sf_ident = quote! {
+
+
+
+
+ 6
+
+
+
#field_ident.#i_literal
+
+
+
+
+ 6
+
+
+
let sf_clone = get_clone(sf, sf_ident, root_ident);
+
+
+
+
+ 5
+
+
+
Some(sfc) => sf_clones.push(sfc),
+
+
+
+
+ 3
+
+
+
return Some(quote! { // Clone the subfields into a vec and flatten
+
+
+
+
+ 3
+
+
+
vec![#(#sf_clones,)*].iter().flatten().cloned().collect::<Vec<_>>()
+
+
+
+
+
+
+
+
UniplateField::Array(_, _, _) => {
+
+
+
+
+
+
+
+
// ToDo support arrays
+
+
+
+
+
+
+
+
unimplemented!("Arrays not currently supported")
+
+
+
+
+
+
+
+
UniplateField::Unknown(_) => {} // Ignore unknown types
+
+
+
+
+ 125
+
+
+
None // If the field is not a type we want to clone, return None
+
+
+
+
+
+
+
+
/// Helper function to get the name of a field - if it has no name, use `field{idx}`
+
+
+
+
+ 1192
+
+
+
fn get_field_name(field: &Field, idx: usize) -> String {
+
+
+
+
+ 1192
+
+
+
None => format!("field{}", idx),
+
+
+
+
+
+
+
+
Some(ident) => ident.to_string(),
+
+
+
+
+
+
+
+
/// Generate the code to match the fields of a variant
+
+
+
+
+ 252
+
+
+
pub fn generate_field_idents(fields: &Fields) -> Vec<TokenStream2> {
+
+
+
+
+ 596
+
+
+
.map(|(idx, field)| {
+
+
+
+
+ 596
+
+
+
let field_name = get_field_name(field, idx);
+
+
+
+
+ 596
+
+
+
Ident::new(&field_name, field.ident.span()).into_token_stream()
+
+
+
+
+
+
+
+
/// Generate the code to clone the fields of a variant
+
+
+
+
+ 126
+
+
+
pub fn generate_field_clones(fields: &Fields, root_ident: &Ident) -> Vec<TokenStream2> {
+
+
+
+
+ 298
+
+
+
.filter_map(|(idx, field)| {
+
+
+
+
+ 298
+
+
+
let field_name = get_field_name(field, idx);
+
+
+
+
+ 298
+
+
+
let field_type = parse_field_type(&field.ty);
+
+
+
+
+ 298
+
+
+
let field_ident = Ident::new(&field_name, field.ident.span()).into_token_stream();
+
+
+
+
+ 298
+
+
+
get_clone(&field_type, field_ident, root_ident)
+
+
+
+
+
+
+
+
/// Generate the code to fill the fields of a variant
+
+
+
+
+ 126
+
+
+
pub fn generate_field_fills(
+
+
+
+
+ 126
+
+
+
) -> Vec<TokenStream2> {
+
+
+
+
+ 298
+
+
+
.map(|(idx, field)| {
+
+
+
+
+ 298
+
+
+
let field_name = get_field_name(field, idx);
+
+
+
+
+ 298
+
+
+
let field_type = parse_field_type(&field.ty);
+
+
+
+
+ 298
+
+
+
let field_ident = Ident::new(&field_name, field.ident.span()).into_token_stream();
+
+
+
+
+ 298
+
+
+
get_fill(&field_type, exprs_ident, &field_ident, root_ident)
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/utils/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/utils/index.html
new file mode 100644
index 000000000..bb41115d4
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/utils/index.html
@@ -0,0 +1,98 @@
+
+
+
+
+ Grcov report - crates/uniplate_derive/src/utils
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ generate.rs
+
+
+
+ 96.38%
+
+
+
+ 96.38%
+
+
+ 133 / 138
+
+
+ 50%
+ 18 / 36
+
+
+
+
+
+ parse.rs
+
+
+
+ 73.42%
+
+
+
+ 73.42%
+
+
+ 58 / 79
+
+
+ 50%
+ 10 / 20
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/utils/parse.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/utils/parse.rs.html
new file mode 100644
index 000000000..c9a153fef
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/src/utils/parse.rs.html
@@ -0,0 +1,2169 @@
+
+
+
+
+ Grcov report - parse.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use proc_macro2::{Ident, Span};
+
+
+
+
+
+
+
+
use syn::spanned::Spanned;
+
+
+
+
+
+
+
+
use syn::{Expr, GenericArgument, PathArguments, PathSegment, Type};
+
+
+
+
+
+
+
+
/// Represents an error produced during parsing a type argument (e.g. `::<T>`)
+
+
+
+
+
+
+
+
pub enum ParseTypeArgumentError {
+
+
+
+
+
+
+
+
MultipleTypeArguments,
+
+
+
+
+
+
+
+
TypeArgumentNotAType,
+
+
+
+
+
+
+
+
TypeArgumentValueNotPath,
+
+
+
+
+
+
+
+
TypeArgumentEmptyPath,
+
+
+
+
+
+
+
+
/// Represents a field in a tree-like structure. Used for deriving the uniplate implementation.
+
+
+
+
+
+
+
+
pub enum UniplateField {
+
+
+
+
+
+
+
+
/// Any other valid identifier
+
+
+
+
+
+
+
+
/// A field consisting of a Box<T>
+
+
+
+
+
+
+
+
Box(Span, Box<UniplateField>),
+
+
+
+
+
+
+
+
/// A field consisting of a Vec<T>
+
+
+
+
+
+
+
+
Vector(Span, Box<UniplateField>),
+
+
+
+
+
+
+
+
/// A tuple of multiple fields (e.g. `(Box<T>, i32)`)
+
+
+
+
+
+
+
+
Tuple(Span, Vec<UniplateField>),
+
+
+
+
+
+
+
+
/// An array field. ToDo: currently not supported.
+
+
+
+
+
+
+
+
Array(Span, Box<UniplateField>, Expr),
+
+
+
+
+
+
+
+
/// A field that could not be parsed
+
+
+
+
+
+
+
+
/// Get the span corresponding to this field
+
+
+
+
+ 54
+
+
+
pub fn span(&self) -> Span {
+
+
+
+
+ 53
+
+
+
UniplateField::Identifier(idnt) => idnt.span(),
+
+
+
+
+
+
+
+
UniplateField::Box(spn, _) => *spn,
+
+
+
+
+ 1
+
+
+
UniplateField::Vector(spn, _) => *spn,
+
+
+
+
+
+
+
+
UniplateField::Tuple(spn, _) => *spn,
+
+
+
+
+
+
+
+
UniplateField::Array(spn, _, _) => *spn,
+
+
+
+
+
+
+
+
UniplateField::Unknown(spn) => *spn,
+
+
+
+
+
+
+
+
/// Parse a type argument from a path segment (e.g. `T` from `Box<T>`)
+
+
+
+
+ 354
+
+
+
fn parse_type_argument(seg_args: &PathArguments) -> Result<&PathSegment, ParseTypeArgumentError> {
+
+
+
+
+ 354
+
+
+
PathArguments::AngleBracketed(type_args) => {
+
+
+
+
+ 354
+
+
+
if type_args.args.len() > 1 {
+
+
+
+
+
+
+
+
// ToDo: discuss - can and should we support multiple type arguments?
+
+
+
+
+
+
+
+
return Err(ParseTypeArgumentError::MultipleTypeArguments);
+
+
+
+
+ 354
+
+
+
match type_args.args.last() {
+
+
+
+
+
+
+
+
None => Err(ParseTypeArgumentError::EmptyTypeArguments),
+
+
+
+
+ 354
+
+
+
Some(arg) => match arg {
+
+
+
+
+ 354
+
+
+
GenericArgument::Type(tp) => match tp {
+
+
+
+
+ 354
+
+
+
Type::Path(pth) => match pth.path.segments.last() {
+
+
+
+
+ 354
+
+
+
Some(seg) => Ok(seg),
+
+
+
+
+
+
+
+
None => Err(ParseTypeArgumentError::TypeArgumentEmptyPath),
+
+
+
+
+
+
+
+
_ => Err(ParseTypeArgumentError::TypeArgumentValueNotPath),
+
+
+
+
+
+
+
+
_ => Err(ParseTypeArgumentError::TypeArgumentNotAType),
+
+
+
+
+
+
+
+
_ => Err(ParseTypeArgumentError::NoTypeArguments),
+
+
+
+
+
+
+
+
/// Parse a field type into a `UniplateField`
+
+
+
+
+ 608
+
+
+
pub fn parse_field_type(field_type: &Type) -> UniplateField {
+
+
+
+
+ 608
+
+
+
/// Helper function to parse a path segment into a `UniplateField`
+
+
+
+
+ 956
+
+
+
fn parse_type(seg: &PathSegment) -> UniplateField {
+
+
+
+
+ 956
+
+
+
let ident = &seg.ident;
+
+
+
+
+ 956
+
+
+
let span = ident.span();
+
+
+
+
+ 956
+
+
+
let args = &seg.arguments;
+
+
+
+
+ 956
+
+
+
let box_ident = &Ident::new("Box", span);
+
+
+
+
+ 956
+
+
+
let vec_ident = &Ident::new("Vec", span); // ToDo: support other collection types
+
+
+
+
+ 956
+
+
+
if ident.eq(box_ident) {
+
+
+
+
+ 608
+
+
+
match parse_type_argument(args) {
+
+
+
+
+ 608
+
+
+
Ok(inner_seg) => UniplateField::Box(seg.span(), Box::new(parse_type(inner_seg))),
+
+
+
+
+ 608
+
+
+
Err(_) => UniplateField::Unknown(ident.span()),
+
+
+
+
+ 710
+
+
+
} else if ident.eq(vec_ident) {
+
+
+
+
+ 608
+
+
+
match parse_type_argument(args) {
+
+
+
+
+ 608
+
+
+
Ok(inner_seg) => UniplateField::Vector(seg.span(), Box::new(parse_type(inner_seg))),
+
+
+
+
+ 608
+
+
+
Err(_) => UniplateField::Unknown(ident.span()),
+
+
+
+
+ 608
+
+
+
UniplateField::Identifier(ident.clone())
+
+
+
+
+ 602
+
+
+
Type::Path(path) => match path.path.segments.last() {
+
+
+
+
+
+
+
+
None => UniplateField::Unknown(path.span()),
+
+
+
+
+ 602
+
+
+
Some(seg) => parse_type(seg),
+
+
+
+
+ 6
+
+
+
Type::Tuple(tpl) => {
+
+
+
+
+ 6
+
+
+
UniplateField::Tuple(tpl.span(), tpl.elems.iter().map(parse_field_type).collect())
+
+
+
+
+
+
+
+
Type::Array(arr) => UniplateField::Array(
+
+
+
+
+
+
+
+
Box::new(parse_field_type(arr.elem.as_ref())),
+
+
+
+
+
+
+
+
_ => UniplateField::Unknown(field_type.span()), // ToDo discuss - Can we support any of: BareFn, Group, ImplTrait, Infer, Macro, Never, Paren, Ptr, Reference, TraitObject, Verbatim
+
+
+
+
+
+
+
+
/// Check if a field type is equal to a given identifier. Used to check if a field is an instance of the root type.
+
+
+
+
+ 1330
+
+
+
pub fn check_field_type(ft: &UniplateField, root_ident: &Ident) -> bool {
+
+
+
+
+ 962
+
+
+
UniplateField::Identifier(ident) => ident.eq(root_ident),
+
+
+
+
+ 252
+
+
+
UniplateField::Box(_, subfield) => check_field_type(subfield.as_ref(), root_ident),
+
+
+
+
+ 110
+
+
+
UniplateField::Vector(_, subfield) => check_field_type(subfield.as_ref(), root_ident),
+
+
+
+
+ 6
+
+
+
UniplateField::Tuple(_, subfields) => {
+
+
+
+
+ 6
+
+
+
for sft in subfields {
+
+
+
+
+ 6
+
+
+
if check_field_type(sft, root_ident) {
+
+
+
+
+
+
+
+
UniplateField::Array(_, arr_type, _) => check_field_type(arr_type.as_ref(), root_ident),
+
+
+
+
+
+
+
+
UniplateField::Unknown(_) => false,
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/tests/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/tests/index.html
new file mode 100644
index 000000000..3639695a3
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/tests/index.html
@@ -0,0 +1,74 @@
+
+
+
+
+ Grcov report - crates/uniplate_derive/tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ File
+ Line Coverage
+ Functions
+
+
+
+
+
+ macro_tests.rs
+
+
+
+ 100%
+
+
+
+ 100%
+
+
+ 188 / 188
+
+
+ 100%
+ 36 / 36
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/tests/macro_tests.rs.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/tests/macro_tests.rs.html
new file mode 100644
index 000000000..f33b93bd8
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/crates/uniplate_derive/tests/macro_tests.rs.html
@@ -0,0 +1,3945 @@
+
+
+
+
+ Grcov report - macro_tests.rs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use uniplate::uniplate::Uniplate;
+
+
+
+
+
+
+
+
use uniplate_derive::Uniplate;
+
+
+
+
+ 47
+
+
+
#[derive(Clone, Debug, PartialEq, Eq, Uniplate)]
+
+
+
+
+
+
+
+
D(bool, Box<TestEnum>),
+
+
+
+
+
+
+
+
E(Box<TestEnum>, Box<TestEnum>),
+
+
+
+
+
+
+
+
F((Box<TestEnum>, Box<TestEnum>)),
+
+
+
+
+
+
+
+
G((Box<TestEnum>, (Box<TestEnum>, i32))),
+
+
+
+
+
+
+
+
H(Vec<Vec<TestEnum>>),
+
+
+
+
+
+
+
+
I(Vec<TestEnum>, i32, Vec<TestEnum>),
+
+
+
+
+ 1
+
+
+
fn increase_number_of_children() {
+
+
+
+
+ 1
+
+
+
let c = TestEnum::C(vec![TestEnum::A(42)]);
+
+
+
+
+ 1
+
+
+
let context = c.uniplate().1;
+
+
+
+
+ 1
+
+
+
context(vec![TestEnum::A(42), TestEnum::A(42)]),
+
+
+
+
+ 1
+
+
+
Err(uniplate::uniplate::UniplateError::WrongNumberOfChildren(
+
+
+
+
+ 1
+
+
+
fn decrease_number_of_children() {
+
+
+
+
+ 1
+
+
+
let c = TestEnum::C(vec![TestEnum::A(42)]);
+
+
+
+
+ 1
+
+
+
let context = c.uniplate().1;
+
+
+
+
+ 1
+
+
+
Err(uniplate::uniplate::UniplateError::WrongNumberOfChildren(
+
+
+
+
+ 1
+
+
+
fn derive_context_empty() {
+
+
+
+
+ 1
+
+
+
let a = TestEnum::A(42);
+
+
+
+
+ 1
+
+
+
let context = a.uniplate().1;
+
+
+
+
+ 1
+
+
+
assert_eq!(context(vec![]).unwrap(), a)
+
+
+
+
+ 1
+
+
+
fn derive_context_box() {
+
+
+
+
+ 1
+
+
+
let a = TestEnum::A(42);
+
+
+
+
+ 1
+
+
+
let b = TestEnum::B(Box::new(a.clone()));
+
+
+
+
+ 1
+
+
+
let context = b.uniplate().1;
+
+
+
+
+ 1
+
+
+
assert_eq!(context(vec![a.clone()]).unwrap(), b);
+
+
+
+
+ 1
+
+
+
fn derive_context_vec() {
+
+
+
+
+ 1
+
+
+
let a = TestEnum::A(1);
+
+
+
+
+ 1
+
+
+
let b = TestEnum::B(Box::new(TestEnum::A(2)));
+
+
+
+
+ 1
+
+
+
let c = TestEnum::C(vec![a.clone(), b.clone()]);
+
+
+
+
+ 1
+
+
+
let context = c.uniplate().1;
+
+
+
+
+ 1
+
+
+
assert_eq!(context(vec![a.clone(), b.clone()]).unwrap(), c);
+
+
+
+
+ 1
+
+
+
fn derive_context_two() {
+
+
+
+
+ 1
+
+
+
let d = TestEnum::D(true, Box::new(TestEnum::A(42)));
+
+
+
+
+ 1
+
+
+
let context = d.uniplate().1;
+
+
+
+
+ 1
+
+
+
assert_eq!(context(vec![TestEnum::A(42)]).unwrap(), d);
+
+
+
+
+ 1
+
+
+
fn derive_context_tuple() {
+
+
+
+
+ 1
+
+
+
let e = TestEnum::F((Box::new(TestEnum::A(1)), Box::new(TestEnum::A(2))));
+
+
+
+
+ 1
+
+
+
let context = e.uniplate().1;
+
+
+
+
+ 1
+
+
+
assert_eq!(context(vec![TestEnum::A(1), TestEnum::A(2)]).unwrap(), e);
+
+
+
+
+ 1
+
+
+
fn derive_context_different_variants() {
+
+
+
+
+ 1
+
+
+
Box::new(TestEnum::A(1)),
+
+
+
+
+ 1
+
+
+
Box::new(TestEnum::B(Box::new(TestEnum::A(2)))),
+
+
+
+
+ 1
+
+
+
let context = f.uniplate().1;
+
+
+
+
+ 1
+
+
+
context(vec![TestEnum::A(1), TestEnum::B(Box::new(TestEnum::A(2)))]).unwrap(),
+
+
+
+
+ 1
+
+
+
fn derive_context_nested_tuples() {
+
+
+
+
+ 1
+
+
+
let g = TestEnum::G((Box::new(TestEnum::A(1)), (Box::new(TestEnum::A(2)), 42)));
+
+
+
+
+ 1
+
+
+
let context = g.uniplate().1;
+
+
+
+
+ 1
+
+
+
assert_eq!(context(vec![TestEnum::A(1), TestEnum::A(2)]).unwrap(), g);
+
+
+
+
+ 1
+
+
+
fn derive_context_nested_vectors() {
+
+
+
+
+ 1
+
+
+
let h = TestEnum::H(vec![
+
+
+
+
+ 1
+
+
+
vec![TestEnum::A(1), TestEnum::A(2)],
+
+
+
+
+ 1
+
+
+
vec![TestEnum::A(3), TestEnum::A(4)],
+
+
+
+
+ 1
+
+
+
let context = h.uniplate().1;
+
+
+
+
+ 1
+
+
+
fn derive_context_multiple_vecs() {
+
+
+
+
+ 1
+
+
+
vec![TestEnum::A(1), TestEnum::A(2)],
+
+
+
+
+ 1
+
+
+
vec![TestEnum::A(3), TestEnum::A(4)],
+
+
+
+
+ 1
+
+
+
let context = i.uniplate().1;
+
+
+
+
+ 1
+
+
+
fn box_change_child() {
+
+
+
+
+ 1
+
+
+
let b = TestEnum::B(Box::new(TestEnum::A(1)));
+
+
+
+
+ 1
+
+
+
let context = b.uniplate().1;
+
+
+
+
+ 1
+
+
+
context(vec![TestEnum::C(vec![TestEnum::A(41), TestEnum::A(42)])]).unwrap(),
+
+
+
+
+ 1
+
+
+
TestEnum::B(Box::new(TestEnum::C(vec![
+
+
+
+
+ 1
+
+
+
fn derive_children_empty() {
+
+
+
+
+ 1
+
+
+
let a = TestEnum::A(42);
+
+
+
+
+ 1
+
+
+
let children = a.uniplate().0;
+
+
+
+
+ 1
+
+
+
assert_eq!(children, vec![]);
+
+
+
+
+ 1
+
+
+
fn derive_children_box() {
+
+
+
+
+ 1
+
+
+
let b = TestEnum::B(Box::new(TestEnum::A(42)));
+
+
+
+
+ 1
+
+
+
let children = b.uniplate().0;
+
+
+
+
+ 1
+
+
+
assert_eq!(children, vec![TestEnum::A(42)]);
+
+
+
+
+ 1
+
+
+
fn derive_children_vec() {
+
+
+
+
+ 1
+
+
+
let c = TestEnum::C(vec![TestEnum::A(1), TestEnum::B(Box::new(TestEnum::A(2)))]);
+
+
+
+
+ 1
+
+
+
let children = c.uniplate().0;
+
+
+
+
+ 1
+
+
+
vec![TestEnum::A(1), TestEnum::B(Box::new(TestEnum::A(2))),]
+
+
+
+
+ 1
+
+
+
fn derive_children_two() {
+
+
+
+
+ 1
+
+
+
let d = TestEnum::D(true, Box::new(TestEnum::A(42)));
+
+
+
+
+ 1
+
+
+
let children = d.uniplate().0;
+
+
+
+
+ 1
+
+
+
assert_eq!(children, vec![TestEnum::A(42)]);
+
+
+
+
+ 1
+
+
+
fn derive_children_tuple() {
+
+
+
+
+ 1
+
+
+
let e = TestEnum::F((Box::new(TestEnum::A(1)), Box::new(TestEnum::A(2))));
+
+
+
+
+ 1
+
+
+
let children = e.uniplate().0;
+
+
+
+
+ 1
+
+
+
assert_eq!(children, vec![TestEnum::A(1), TestEnum::A(2),]);
+
+
+
+
+ 1
+
+
+
fn derive_children_different_variants() {
+
+
+
+
+ 1
+
+
+
Box::new(TestEnum::A(1)),
+
+
+
+
+ 1
+
+
+
Box::new(TestEnum::B(Box::new(TestEnum::A(2)))),
+
+
+
+
+ 1
+
+
+
let children = f.uniplate().0;
+
+
+
+
+ 1
+
+
+
vec![TestEnum::A(1), TestEnum::B(Box::new(TestEnum::A(2)))]
+
+
+
+
+ 1
+
+
+
fn derive_children_nested_tuples() {
+
+
+
+
+ 1
+
+
+
let g = TestEnum::G((Box::new(TestEnum::A(1)), (Box::new(TestEnum::A(2)), 42)));
+
+
+
+
+ 1
+
+
+
let children = g.uniplate().0;
+
+
+
+
+ 1
+
+
+
assert_eq!(children, vec![TestEnum::A(1), TestEnum::A(2)])
+
+
+
+
+ 1
+
+
+
fn derive_children_nested_vectors() {
+
+
+
+
+ 1
+
+
+
let h = TestEnum::H(vec![
+
+
+
+
+ 1
+
+
+
vec![TestEnum::A(1), TestEnum::A(2)],
+
+
+
+
+ 1
+
+
+
vec![TestEnum::A(3), TestEnum::A(4)],
+
+
+
+
+ 1
+
+
+
let children = h.uniplate().0;
+
+
+
+
+ 1
+
+
+
fn derive_children_multiple_vecs() {
+
+
+
+
+ 1
+
+
+
vec![TestEnum::A(1), TestEnum::A(2)],
+
+
+
+
+ 1
+
+
+
vec![TestEnum::A(3), TestEnum::A(4)],
+
+
+
+
+ 1
+
+
+
let children = i.uniplate().0;
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/doc-coverage.json b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/doc-coverage.json
new file mode 100644
index 000000000..866019cdd
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/doc-coverage.json
@@ -0,0 +1,9 @@
+{"crates/enum_compatability_macro/src/lib.rs":{"total":2,"with_docs":2,"total_examples":2,"with_examples":1}}
+{"crates/conjure_macros/src/lib.rs":{"total":3,"with_docs":2,"total_examples":3,"with_examples":1}}
+{"crates/uniplate_derive/src/lib.rs":{"total":2,"with_docs":1,"total_examples":2,"with_examples":1}}
+{"conjure_oxide/src/find_conjure.rs":{"total":2,"with_docs":1,"total_examples":2,"with_examples":0},"conjure_oxide/src/lib.rs":{"total":1,"with_docs":0,"total_examples":1,"with_examples":0},"conjure_oxide/src/utils/conjure.rs":{"total":7,"with_docs":0,"total_examples":5,"with_examples":0},"conjure_oxide/src/utils/json.rs":{"total":3,"with_docs":2,"total_examples":3,"with_examples":0},"conjure_oxide/src/utils/misc.rs":{"total":2,"with_docs":0,"total_examples":2,"with_examples":0},"conjure_oxide/src/utils/mod.rs":{"total":1,"with_docs":0,"total_examples":1,"with_examples":0},"conjure_oxide/src/utils/testing.rs":{"total":8,"with_docs":0,"total_examples":8,"with_examples":0}}
+{"solvers/kissat/src/lib.rs":{"total":1,"with_docs":0,"total_examples":1,"with_examples":0}}
+{"/home/runner/work/conjure-oxide/conjure-oxide/target/debug/build/chuffed_rs-deaded5605d26364/out/chuffed_bindings.rs":{"total":207,"with_docs":0,"total_examples":104,"with_examples":0},"solvers/chuffed/src/lib.rs":{"total":10,"with_docs":0,"total_examples":10,"with_examples":0}}
+{"crates/uniplate/src/biplate.rs":{"total":16,"with_docs":9,"total_examples":16,"with_examples":0},"crates/uniplate/src/lib.rs":{"total":1,"with_docs":1,"total_examples":1,"with_examples":1},"crates/uniplate/src/uniplate.rs":{"total":13,"with_docs":9,"total_examples":12,"with_examples":0}}
+{"solvers/minion/src/ast.rs":{"total":98,"with_docs":11,"total_examples":12,"with_examples":0},"solvers/minion/src/error.rs":{"total":8,"with_docs":8,"total_examples":3,"with_examples":0},"solvers/minion/src/lib.rs":{"total":1,"with_docs":1,"total_examples":1,"with_examples":1}}
+{"crates/conjure_core/src/ast/mod.rs":{"total":1,"with_docs":0,"total_examples":1,"with_examples":0},"crates/conjure_core/src/context.rs":{"total":9,"with_docs":0,"total_examples":4,"with_examples":0},"crates/conjure_core/src/error.rs":{"total":7,"with_docs":1,"total_examples":2,"with_examples":0},"crates/conjure_core/src/lib.rs":{"total":1,"with_docs":0,"total_examples":1,"with_examples":0},"crates/conjure_core/src/metadata.rs":{"total":4,"with_docs":0,"total_examples":3,"with_examples":0},"crates/conjure_core/src/model.rs":{"total":16,"with_docs":1,"total_examples":13,"with_examples":0},"crates/conjure_core/src/parse/mod.rs":{"total":1,"with_docs":0,"total_examples":1,"with_examples":0},"crates/conjure_core/src/rule_engine/mod.rs":{"total":7,"with_docs":5,"total_examples":7,"with_examples":5},"crates/conjure_core/src/rules/mod.rs":{"total":1,"with_docs":0,"total_examples":1,"with_examples":0},"crates/conjure_core/src/solver/adaptors/mod.rs":{"total":1,"with_docs":1,"total_examples":1,"with_examples":0},"crates/conjure_core/src/solver/mod.rs":{"total":40,"with_docs":12,"total_examples":22,"with_examples":1},"crates/conjure_core/src/solver/model_modifier.rs":{"total":10,"with_docs":7,"total_examples":6,"with_examples":0},"crates/conjure_core/src/solver/states.rs":{"total":11,"with_docs":7,"total_examples":6,"with_examples":0},"crates/conjure_core/src/stats/mod.rs":{"total":6,"with_docs":0,"total_examples":4,"with_examples":0}}
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/doc-coverage.txt b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/doc-coverage.txt
new file mode 100644
index 000000000..2c13ce8a5
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/doc-coverage.txt
@@ -0,0 +1,104 @@
++-------------------------------------+------------+------------+------------+------------+
+| File | Documented | Percentage | Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+
+| crates/conjure_macros/src/lib.rs | 2 | 66.7% | 1 | 33.3% |
++-------------------------------------+------------+------------+------------+------------+
+| Total | 2 | 66.7% | 1 | 33.3% |
++-------------------------------------+------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+
+| File | Documented | Percentage | Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+
+| solvers/kissat/src/lib.rs | 0 | 0.0% | 0 | 0.0% |
++-------------------------------------+------------+------------+------------+------------+
+| Total | 0 | 0.0% | 0 | 0.0% |
++-------------------------------------+------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+
+| File | Documented | Percentage | Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+
+| ...m_compatability_macro/src/lib.rs | 2 | 100.0% | 1 | 50.0% |
++-------------------------------------+------------+------------+------------+------------+
+| Total | 2 | 100.0% | 1 | 50.0% |
++-------------------------------------+------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+
+| File | Documented | Percentage | Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+
+| crates/uniplate_derive/src/lib.rs | 1 | 50.0% | 1 | 50.0% |
++-------------------------------------+------------+------------+------------+------------+
+| Total | 1 | 50.0% | 1 | 50.0% |
++-------------------------------------+------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+
+| File | Documented | Percentage | Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+
+| crates/uniplate/src/biplate.rs | 9 | 56.2% | 0 | 0.0% |
+| crates/uniplate/src/lib.rs | 1 | 100.0% | 1 | 100.0% |
+| crates/uniplate/src/tree.rs | 0 | 0.0% | 0 | 0.0% |
+| crates/uniplate/src/uniplate.rs | 9 | 69.2% | 0 | 0.0% |
++-------------------------------------+------------+------------+------------+------------+
+| Total | 19 | 55.9% | 1 | 3.4% |
++-------------------------------------+------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+
+| File | Documented | Percentage | Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+
+| solvers/minion/src/ast.rs | 11 | 11.2% | 0 | 0.0% |
+| solvers/minion/src/error.rs | 8 | 100.0% | 0 | 0.0% |
+| solvers/minion/src/lib.rs | 1 | 100.0% | 1 | 100.0% |
+| solvers/minion/src/run.rs | 2 | 100.0% | 1 | 100.0% |
++-------------------------------------+------------+------------+------------+------------+
+| Total | 22 | 20.2% | 2 | 11.8% |
++-------------------------------------+------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+
+| File | Documented | Percentage | Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+
+| ...onjure_core/src/ast/constants.rs | 0 | 0.0% | 0 | 0.0% |
+| .../conjure_core/src/ast/domains.rs | 0 | 0.0% | 0 | 0.0% |
+| ...jure_core/src/ast/expressions.rs | 19 | 95.0% | 0 | 0.0% |
+| crates/conjure_core/src/ast/mod.rs | 0 | 0.0% | 0 | 0.0% |
+| ...ure_core/src/ast/symbol_table.rs | 0 | 0.0% | 0 | 0.0% |
+| ...onjure_core/src/ast/variables.rs | 0 | 0.0% | 0 | 0.0% |
+| crates/conjure_core/src/context.rs | 0 | 0.0% | 0 | 0.0% |
+| crates/conjure_core/src/error.rs | 1 | 14.3% | 0 | 0.0% |
+| crates/conjure_core/src/lib.rs | 0 | 0.0% | 0 | 0.0% |
+| crates/conjure_core/src/metadata.rs | 0 | 0.0% | 0 | 0.0% |
+| crates/conjure_core/src/model.rs | 1 | 6.2% | 0 | 0.0% |
+| ...core/src/parse/example_models.rs | 2 | 100.0% | 0 | 0.0% |
+| ...es/conjure_core/src/parse/mod.rs | 0 | 0.0% | 0 | 0.0% |
+| ...re_core/src/parse/parse_model.rs | 0 | 0.0% | 0 | 0.0% |
+| ...jure_core/src/rule_engine/mod.rs | 5 | 71.4% | 5 | 71.4% |
+| ...src/rule_engine/resolve_rules.rs | 3 | 100.0% | 0 | 0.0% |
+| ..._core/src/rule_engine/rewrite.rs | 1 | 33.3% | 0 | 0.0% |
+| ...ure_core/src/rule_engine/rule.rs | 3 | 25.0% | 0 | 0.0% |
+| ...core/src/rule_engine/rule_set.rs | 4 | 100.0% | 0 | 0.0% |
+| ...njure_core/src/rules/constant.rs | 1 | 100.0% | 0 | 0.0% |
+| ...es/conjure_core/src/rules/mod.rs | 0 | 0.0% | 0 | 0.0% |
+| ...re/src/solver/adaptors/kissat.rs | 1 | 100.0% | 0 | 0.0% |
+| ...re/src/solver/adaptors/minion.rs | 1 | 100.0% | 0 | 0.0% |
+| ..._core/src/solver/adaptors/mod.rs | 1 | 100.0% | 0 | 0.0% |
+| ...s/conjure_core/src/solver/mod.rs | 12 | 30.0% | 1 | 4.5% |
+| ...ore/src/solver/model_modifier.rs | 7 | 70.0% | 0 | 0.0% |
+| ...onjure_core/src/solver/states.rs | 7 | 63.6% | 0 | 0.0% |
+| ...es/conjure_core/src/stats/mod.rs | 0 | 0.0% | 0 | 0.0% |
+| ...e_core/src/stats/solver_stats.rs | 0 | 0.0% | 0 | 0.0% |
++-------------------------------------+------------+------------+------------+------------+
+| Total | 69 | 37.3% | 6 | 8.3% |
++-------------------------------------+------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+
+| File | Documented | Percentage | Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+
+| conjure_oxide/src/find_conjure.rs | 1 | 50.0% | 0 | 0.0% |
+| conjure_oxide/src/lib.rs | 0 | 0.0% | 0 | 0.0% |
+| conjure_oxide/src/utils/conjure.rs | 0 | 0.0% | 0 | 0.0% |
+| conjure_oxide/src/utils/json.rs | 2 | 66.7% | 0 | 0.0% |
+| conjure_oxide/src/utils/misc.rs | 0 | 0.0% | 0 | 0.0% |
+| conjure_oxide/src/utils/mod.rs | 0 | 0.0% | 0 | 0.0% |
+| conjure_oxide/src/utils/testing.rs | 0 | 0.0% | 0 | 0.0% |
++-------------------------------------+------------+------------+------------+------------+
+| Total | 3 | 12.5% | 0 | 0.0% |
++-------------------------------------+------------+------------+------------+------------+
++-------------------------------------+------------+------------+------------+------------+
+| File | Documented | Percentage | Examples | Percentage |
++-------------------------------------+------------+------------+------------+------------+
+| ...05d26364/out/chuffed_bindings.rs | 0 | 0.0% | 0 | 0.0% |
+| solvers/chuffed/src/lib.rs | 0 | 0.0% | 0 | 0.0% |
++-------------------------------------+------------+------------+------------+------------+
+| Total | 0 | 0.0% | 0 | 0.0% |
++-------------------------------------+------------+------------+------------+------------+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/index.html b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/index.html
new file mode 100644
index 000000000..6fa706f07
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/index.html
@@ -0,0 +1,626 @@
+
+
+
+
+ Grcov report - top_level
+
+
+
+
+
+
+
+
+
+
+
+
Functions
+
+ 22.15 %
+
+
+
+
+
+
+
+
+
+
diff --git a/coverage/cbe674125fb51538c59e622d46e339e36df0a433/lcov.info b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/lcov.info
new file mode 100644
index 000000000..eec11220d
--- /dev/null
+++ b/coverage/cbe674125fb51538c59e622d46e339e36df0a433/lcov.info
@@ -0,0 +1,10876 @@
+TN:
+SF:conjure_oxide/tests/model_tests.rs
+FN:11,model_tests::modify_domain
+FNDA:1,model_tests::modify_domain
+FNF:1
+FNH:1
+BRF:0
+BRH:0
+DA:11,1
+DA:12,1
+DA:13,1
+DA:14,1
+DA:15,1
+DA:16,1
+DA:17,1
+DA:18,1
+DA:19,1
+DA:20,1
+DA:21,1
+DA:22,1
+DA:23,1
+DA:24,1
+DA:25,1
+DA:26,1
+DA:28,1
+DA:29,1
+DA:30,1
+DA:31,1
+LF:20
+LH:20
+end_of_record
+SF:crates/uniplate_derive/src/utils/parse.rs
+FN:117,uniplate_derive::utils::parse::check_field_type
+FN:74,uniplate_derive::utils::parse::parse_field_type
+FN:76,uniplate_derive::utils::parse::parse_field_type::parse_type
+FN:47,uniplate_derive::utils::parse::parse_type_argument
+FN:117,uniplate_derive::utils::parse::check_field_type
+FN:47,uniplate_derive::utils::parse::parse_type_argument
+FN:117,uniplate_derive::utils::parse::check_field_type
+FN:117,uniplate_derive::utils::parse::check_field_type
+FN:76,uniplate_derive::utils::parse::parse_field_type::parse_type
+FN:47,uniplate_derive::utils::parse::parse_type_argument
+FN:47,uniplate_derive::utils::parse::parse_type_argument
+FN:74,uniplate_derive::utils::parse::parse_field_type
+FN:34,::span
+FN:34,::span
+FN:74,uniplate_derive::utils::parse::parse_field_type
+FN:76,uniplate_derive::utils::parse::parse_field_type::parse_type
+FN:34,::span
+FN:34,::span
+FN:76,uniplate_derive::utils::parse::parse_field_type::parse_type
+FN:74,uniplate_derive::utils::parse::parse_field_type
+FNDA:0,uniplate_derive::utils::parse::check_field_type
+FNDA:0,uniplate_derive::utils::parse::parse_field_type
+FNDA:0,uniplate_derive::utils::parse::parse_field_type::parse_type
+FNDA:1,uniplate_derive::utils::parse::parse_type_argument
+FNDA:1,uniplate_derive::utils::parse::check_field_type
+FNDA:0,uniplate_derive::utils::parse::parse_type_argument
+FNDA:0,uniplate_derive::utils::parse::check_field_type
+FNDA:1,uniplate_derive::utils::parse::check_field_type
+FNDA:1,uniplate_derive::utils::parse::parse_field_type::parse_type
+FNDA:1,uniplate_derive::utils::parse::parse_type_argument
+FNDA:0,uniplate_derive::utils::parse::parse_type_argument
+FNDA:0,uniplate_derive::utils::parse::parse_field_type
+FNDA:1,::span
+FNDA:1,::span
+FNDA:1,uniplate_derive::utils::parse::parse_field_type
+FNDA:0,uniplate_derive::utils::parse::parse_field_type::parse_type
+FNDA:0,::span
+FNDA:0,::span
+FNDA:1,uniplate_derive::utils::parse::parse_field_type::parse_type
+FNDA:1,uniplate_derive::utils::parse::parse_field_type
+FNF:20
+FNH:10
+BRF:0
+BRH:0
+DA:34,54
+DA:35,54
+DA:36,53
+DA:37,0
+DA:38,1
+DA:39,0
+DA:40,0
+DA:41,0
+DA:43,54
+DA:47,354
+DA:48,354
+DA:49,354
+DA:50,354
+DA:52,0
+DA:53,354
+DA:54,354
+DA:55,354
+DA:56,0
+DA:57,354
+DA:58,354
+DA:59,354
+DA:60,354
+DA:61,0
+DA:63,0
+DA:65,0
+DA:69,0
+DA:71,354
+DA:74,608
+DA:75,608
+DA:76,956
+DA:77,956
+DA:78,956
+DA:79,956
+DA:80,956
+DA:81,956
+DA:82,956
+DA:83,956
+DA:84,956
+DA:85,608
+DA:86,608
+DA:87,608
+DA:88,608
+DA:89,710
+DA:90,608
+DA:91,608
+DA:92,608
+DA:93,608
+DA:94,608
+DA:95,608
+DA:96,608
+DA:97,956
+DA:98,608
+DA:99,608
+DA:100,602
+DA:101,0
+DA:102,602
+DA:104,6
+DA:105,6
+DA:107,0
+DA:108,0
+DA:109,0
+DA:110,0
+DA:111,0
+DA:112,0
+DA:114,608
+DA:117,1330
+DA:118,1330
+DA:119,962
+DA:120,252
+DA:121,110
+DA:122,6
+DA:123,6
+DA:124,6
+DA:125,6
+DA:126,0
+DA:128,0
+DA:130,0
+DA:131,0
+DA:133,1330
+LF:79
+LH:58
+end_of_record
+SF:crates/conjure_core/src/parse/example_models.rs
+FN:31,conjure_core::parse::example_models::get_example_model::{closure#1}
+FN:78,conjure_core::parse::example_models::get_example_model_by_path
+FN:28,conjure_core::parse::example_models::get_example_model::{closure#0}
+FN:28,conjure_core::parse::example_models::get_example_model::{closure#0}
+FN:31,conjure_core::parse::example_models::get_example_model::{closure#1}
+FN:22,conjure_core::parse::example_models::get_example_model
+FN:28,conjure_core::parse::example_models::get_example_model::{closure#0}
+FN:22,conjure_core::parse::example_models::get_example_model
+FN:78,conjure_core::parse::example_models::get_example_model_by_path
+FN:78,conjure_core::parse::example_models::get_example_model_by_path
+FN:22,conjure_core::parse::example_models::get_example_model
+FN:31,conjure_core::parse::example_models::get_example_model::{closure#1}
+FNDA:0,conjure_core::parse::example_models::get_example_model::{closure#1}
+FNDA:0,conjure_core::parse::example_models::get_example_model_by_path
+FNDA:1,conjure_core::parse::example_models::get_example_model::{closure#0}
+FNDA:0,conjure_core::parse::example_models::get_example_model::{closure#0}
+FNDA:0,conjure_core::parse::example_models::get_example_model::{closure#1}
+FNDA:0,conjure_core::parse::example_models::get_example_model
+FNDA:0,conjure_core::parse::example_models::get_example_model::{closure#0}
+FNDA:0,conjure_core::parse::example_models::get_example_model
+FNDA:0,conjure_core::parse::example_models::get_example_model_by_path
+FNDA:1,conjure_core::parse::example_models::get_example_model_by_path
+FNDA:1,conjure_core::parse::example_models::get_example_model
+FNDA:1,conjure_core::parse::example_models::get_example_model::{closure#1}
+FNF:12
+FNH:4
+BRF:0
+BRH:0
+DA:22,45
+DA:24,45
+DA:25,45
+DA:28,245325
+DA:29,245325
+DA:30,245325
+DA:31,207555
+DA:32,225
+DA:34,30
+DA:35,30
+DA:36,245295
+DA:42,45
+DA:43,15
+DA:44,15
+DA:45,15
+DA:46,15
+DA:47,30
+DA:48,30
+DA:49,30
+DA:50,30
+DA:51,30
+DA:52,30
+DA:53,30
+DA:54,30
+DA:55,30
+DA:58,30
+DA:63,30
+DA:65,30
+DA:66,45
+DA:78,30
+DA:79,30
+DA:80,30
+DA:81,30
+DA:82,30
+DA:83,15
+DA:84,15
+DA:85,15
+DA:86,15
+DA:87,15
+DA:88,15
+DA:89,15
+DA:90,15
+DA:91,15
+DA:92,15
+DA:93,15
+DA:94,15
+DA:95,15
+DA:96,15
+DA:97,15
+DA:100,15
+DA:105,15
+DA:107,15
+DA:108,30
+LF:53
+LH:53
+end_of_record
+SF:conjure_oxide/tests/generated_tests.rs
+FN:26,generated_tests::integration_test
+FN:85,generated_tests::assert_conjure_present
+FN:16,generated_tests::main
+FN:18,generated_tests::main::{closure#0}
+FNDA:1,generated_tests::integration_test
+FNDA:1,generated_tests::assert_conjure_present
+FNDA:0,generated_tests::main
+FNDA:0,generated_tests::main::{closure#0}
+FNF:4
+FNH:2
+BRF:0
+BRH:0
+DA:16,0
+DA:17,0
+DA:18,0
+DA:19,0
+DA:20,0
+DA:21,0
+DA:22,0
+DA:24,0
+DA:26,9
+DA:27,9
+DA:28,9
+DA:29,9
+DA:30,9
+DA:31,9
+DA:32,0
+DA:33,0
+DA:34,0
+DA:35,0
+DA:36,9
+DA:39,9
+DA:40,9
+DA:41,0
+DA:42,9
+DA:44,9
+DA:45,9
+DA:46,9
+DA:47,0
+DA:48,9
+DA:50,9
+DA:53,9
+DA:54,9
+DA:55,9
+DA:56,0
+DA:57,9
+DA:59,9
+DA:60,9
+DA:61,9
+DA:62,0
+DA:63,9
+DA:65,9
+DA:68,9
+DA:69,9
+DA:70,9
+DA:71,0
+DA:72,9
+DA:74,9
+DA:75,9
+DA:76,0
+DA:77,9
+DA:79,9
+DA:81,9
+DA:82,9
+DA:85,1
+DA:86,1
+DA:87,1
+LF:55
+LH:37
+end_of_record
+SF:conjure_oxide/src/utils/conjure.rs
+FN:16,::fmt
+FN:16,::fmt
+FN:88,conjure_oxide::utils::conjure::minion_solutions_to_json
+FN:30,conjure_oxide::utils::conjure::parse_essence_file
+FN:30,conjure_oxide::utils::conjure::parse_essence_file
+FN:77,conjure_oxide::utils::conjure::get_minion_solutions::{closure#0}
+FN:16,::fmt
+FN:25,>::from
+FN:77,conjure_oxide::utils::conjure::get_minion_solutions::{closure#0}
+FN:25,>::from
+FN:30,conjure_oxide::utils::conjure::parse_essence_file
+FN:25,>::from
+FN:66,conjure_oxide::utils::conjure::get_minion_solutions
+FN:88,conjure_oxide::utils::conjure::minion_solutions_to_json
+FN:88,conjure_oxide::utils::conjure::minion_solutions_to_json
+FN:66,conjure_oxide::utils::conjure::get_minion_solutions
+FN:66,conjure_oxide::utils::conjure::get_minion_solutions
+FN:77,conjure_oxide::utils::conjure::get_minion_solutions::{closure#0}
+FNDA:0,::fmt
+FNDA:0,::fmt
+FNDA:1,conjure_oxide::utils::conjure::minion_solutions_to_json
+FNDA:0,conjure_oxide::utils::conjure::parse_essence_file
+FNDA:1,conjure_oxide::utils::conjure::parse_essence_file
+FNDA:0,conjure_oxide::utils::conjure::get_minion_solutions::{closure#0}
+FNDA:0,::fmt
+FNDA:0,>::from
+FNDA:1,conjure_oxide::utils::conjure::get_minion_solutions::{closure#0}
+FNDA:0,>::from
+FNDA:0,conjure_oxide::utils::conjure::parse_essence_file
+FNDA:0,>::from
+FNDA:1,conjure_oxide::utils::conjure::get_minion_solutions
+FNDA:0,conjure_oxide::utils::conjure::minion_solutions_to_json
+FNDA:0,conjure_oxide::utils::conjure::minion_solutions_to_json
+FNDA:0,conjure_oxide::utils::conjure::get_minion_solutions
+FNDA:0,conjure_oxide::utils::conjure::get_minion_solutions
+FNDA:0,conjure_oxide::utils::conjure::get_minion_solutions::{closure#0}
+FNF:18
+FNH:4
+BRF:0
+BRH:0
+DA:16,0
+DA:25,0
+DA:26,0
+DA:27,0
+DA:30,36
+DA:31,36
+DA:32,36
+DA:33,36
+DA:34,36
+DA:35,36
+DA:36,36
+DA:37,36
+DA:38,36
+DA:39,36
+DA:40,36
+DA:42,36
+DA:43,0
+DA:46,36
+DA:47,0
+DA:48,0
+DA:49,0
+DA:50,36
+DA:52,36
+DA:53,36
+DA:54,0
+DA:55,0
+DA:56,0
+DA:57,0
+DA:58,0
+DA:62,36
+DA:63,36
+DA:64,36
+DA:66,36
+DA:67,36
+DA:68,36
+DA:69,36
+DA:70,36
+DA:72,36
+DA:73,36
+DA:74,36
+DA:75,36
+DA:76,36
+DA:77,140
+DA:78,140
+DA:79,140
+DA:80,140
+DA:81,140
+DA:84,36
+DA:85,36
+DA:86,36
+DA:88,36
+DA:89,36
+DA:90,176
+DA:91,140
+DA:92,520
+DA:93,380
+DA:94,380
+DA:95,0
+DA:97,380
+DA:99,140
+DA:101,36
+DA:102,36
+DA:103,36
+LF:63
+LH:49
+end_of_record
+SF:solvers/chuffed/tests/dummy_test.rs
+FN:2,dummy_test::dummy
+FNDA:1,dummy_test::dummy
+FNF:1
+FNH:1
+BRF:0
+BRH:0
+DA:2,1
+DA:3,1
+DA:4,1
+LF:3
+LH:3
+end_of_record
+SF:solvers/minion/tests/test_watchedor_reifyimply_1.rs
+FN:29,test_watchedor_reifyimply_1::test_watchedor_reifyimply_1
+FN:57,test_watchedor_reifyimply_1::callback
+FNDA:1,test_watchedor_reifyimply_1::test_watchedor_reifyimply_1
+FNDA:1,test_watchedor_reifyimply_1::callback
+FNF:2
+FNH:2
+BRF:0
+BRH:0
+DA:29,1
+DA:30,1
+DA:31,1
+DA:32,1
+DA:33,1
+DA:34,1
+DA:35,1
+DA:36,1
+DA:37,1
+DA:38,1
+DA:39,1
+DA:40,1
+DA:41,1
+DA:42,1
+DA:43,1
+DA:44,1
+DA:45,1
+DA:46,1
+DA:47,1
+DA:48,1
+DA:49,1
+DA:51,1
+DA:52,1
+DA:53,1
+DA:54,1
+DA:57,7
+DA:58,7
+DA:59,7
+DA:60,7
+DA:61,7
+DA:62,7
+LF:31
+LH:31
+end_of_record
+SF:crates/conjure_core/src/rule_engine/resolve_rules.rs
+FN:45,conjure_core::rule_engine::resolve_rules::rule_sets_by_names
+FN:70,conjure_core::rule_engine::resolve_rules::resolve_rule_sets
+FN:90,conjure_core::rule_engine::resolve_rules::get_rule_priorities
+FN:70,conjure_core::rule_engine::resolve_rules::resolve_rule_sets
+FN:15,::fmt
+FN:90,conjure_core::rule_engine::resolve_rules::get_rule_priorities
+FN:150,conjure_core::rule_engine::resolve_rules::get_rules_vec
+FN:29,conjure_core::rule_engine::resolve_rules::get_rule_set
+FN:45,conjure_core::rule_engine::resolve_rules::rule_sets_by_names
+FN:150,conjure_core::rule_engine::resolve_rules::get_rules_vec
+FN:29,conjure_core::rule_engine::resolve_rules::get_rule_set
+FN:15,::fmt
+FN:70,conjure_core::rule_engine::resolve_rules::resolve_rule_sets
+FN:150,conjure_core::rule_engine::resolve_rules::get_rules_vec
+FN:45,conjure_core::rule_engine::resolve_rules::rule_sets_by_names
+FN:90,conjure_core::rule_engine::resolve_rules::get_rule_priorities
+FN:128,conjure_core::rule_engine::resolve_rules::rule_cmp
+FN:128,conjure_core::rule_engine::resolve_rules::rule_cmp
+FN:128,conjure_core::rule_engine::resolve_rules::rule_cmp
+FN:152,conjure_core::rule_engine::resolve_rules::get_rules_vec::{closure#0}
+FN:15,::fmt
+FN:152,conjure_core::rule_engine::resolve_rules::get_rules_vec::{closure#0}
+FN:29,conjure_core::rule_engine::resolve_rules::get_rule_set
+FN:152,conjure_core::rule_engine::resolve_rules::get_rules_vec::{closure#0}
+FNDA:1,conjure_core::rule_engine::resolve_rules::rule_sets_by_names
+FNDA:0,conjure_core::rule_engine::resolve_rules::resolve_rule_sets
+FNDA:0,conjure_core::rule_engine::resolve_rules::get_rule_priorities
+FNDA:1,conjure_core::rule_engine::resolve_rules::resolve_rule_sets
+FNDA:0,::fmt
+FNDA:1,conjure_core::rule_engine::resolve_rules::get_rule_priorities
+FNDA:0,conjure_core::rule_engine::resolve_rules::get_rules_vec
+FNDA:0,conjure_core::rule_engine::resolve_rules::get_rule_set
+FNDA:0,conjure_core::rule_engine::resolve_rules::rule_sets_by_names
+FNDA:0,conjure_core::rule_engine::resolve_rules::get_rules_vec
+FNDA:0,conjure_core::rule_engine::resolve_rules::get_rule_set
+FNDA:0,::fmt
+FNDA:0,conjure_core::rule_engine::resolve_rules::resolve_rule_sets
+FNDA:1,conjure_core::rule_engine::resolve_rules::get_rules_vec
+FNDA:0,conjure_core::rule_engine::resolve_rules::rule_sets_by_names
+FNDA:0,conjure_core::rule_engine::resolve_rules::get_rule_priorities
+FNDA:0,conjure_core::rule_engine::resolve_rules::rule_cmp
+FNDA:1,conjure_core::rule_engine::resolve_rules::rule_cmp
+FNDA:0,conjure_core::rule_engine::resolve_rules::rule_cmp
+FNDA:0,conjure_core::rule_engine::resolve_rules::get_rules_vec::{closure#0}
+FNDA:0,::fmt
+FNDA:1,conjure_core::rule_engine::resolve_rules::get_rules_vec::{closure#0}
+FNDA:1,conjure_core::rule_engine::resolve_rules::get_rule_set
+FNDA:0,conjure_core::rule_engine::resolve_rules::get_rules_vec::{closure#0}
+FNF:24
+FNH:7
+BRF:0
+BRH:0
+DA:15,0
+DA:16,0
+DA:17,0
+DA:18,0
+DA:19,0
+DA:29,180
+DA:30,180
+DA:31,180
+DA:32,0
+DA:34,180
+DA:45,180
+DA:46,180
+DA:47,180
+DA:48,180
+DA:50,360
+DA:51,180
+DA:52,180
+DA:53,180
+DA:54,180
+DA:57,180
+DA:58,180
+DA:70,180
+DA:71,180
+DA:72,180
+DA:73,180
+DA:74,180
+DA:76,180
+DA:77,180
+DA:78,180
+DA:80,180
+DA:81,180
+DA:82,180
+DA:90,165
+DA:91,165
+DA:92,165
+DA:93,165
+DA:95,660
+DA:96,4290
+DA:97,4290
+DA:98,0
+DA:99,0
+DA:100,0
+DA:101,4290
+DA:102,4290
+DA:103,4290
+DA:107,165
+DA:108,4455
+DA:109,4290
+DA:110,4290
+DA:112,165
+DA:113,165
+DA:128,18315
+DA:129,18315
+DA:130,18315
+DA:131,18315
+DA:132,18315
+DA:133,18315
+DA:134,18315
+DA:135,18315
+DA:136,18315
+DA:137,16125
+DA:138,2190
+DA:139,2190
+DA:140,2190
+DA:141,18315
+DA:150,165
+DA:151,165
+DA:152,18315
+DA:153,165
+DA:154,165
+LF:70
+LH:61
+end_of_record
+SF:conjure_oxide/src/utils/json.rs
+FN:32,conjure_oxide::utils::json::sort_json_variables
+FN:68,conjure_oxide::utils::json::sort_json_object::{closure#2}
+FN:68,conjure_oxide::utils::json::sort_json_object::{closure#2}
+FN:48,conjure_oxide::utils::json::sort_json_object
+FN:53,conjure_oxide::utils::json::sort_json_object::{closure#0}
+FN:53,conjure_oxide::utils::json::sort_json_object::{closure#0}
+FN:32,conjure_oxide::utils::json::sort_json_variables
+FN:62,conjure_oxide::utils::json::sort_json_object::{closure#1}
+FN:7,conjure_oxide::utils::json::json_value_cmp
+FN:48,conjure_oxide::utils::json::sort_json_object
+FN:7,conjure_oxide::utils::json::json_value_cmp
+FN:7,conjure_oxide::utils::json::json_value_cmp
+FN:68,conjure_oxide::utils::json::sort_json_object::{closure#2}
+FN:62,conjure_oxide::utils::json::sort_json_object::{closure#1}
+FN:62,conjure_oxide::utils::json::sort_json_object::{closure#1}
+FN:48,conjure_oxide::utils::json::sort_json_object
+FN:32,conjure_oxide::utils::json::sort_json_variables
+FN:53,conjure_oxide::utils::json::sort_json_object::{closure#0}
+FNDA:0,conjure_oxide::utils::json::sort_json_variables
+FNDA:1,conjure_oxide::utils::json::sort_json_object::{closure#2}
+FNDA:0,conjure_oxide::utils::json::sort_json_object::{closure#2}
+FNDA:0,conjure_oxide::utils::json::sort_json_object
+FNDA:0,conjure_oxide::utils::json::sort_json_object::{closure#0}
+FNDA:0,conjure_oxide::utils::json::sort_json_object::{closure#0}
+FNDA:0,conjure_oxide::utils::json::sort_json_variables
+FNDA:0,conjure_oxide::utils::json::sort_json_object::{closure#1}
+FNDA:1,conjure_oxide::utils::json::json_value_cmp
+FNDA:1,conjure_oxide::utils::json::sort_json_object
+FNDA:0,conjure_oxide::utils::json::json_value_cmp
+FNDA:0,conjure_oxide::utils::json::json_value_cmp
+FNDA:0,conjure_oxide::utils::json::sort_json_object::{closure#2}
+FNDA:0,conjure_oxide::utils::json::sort_json_object::{closure#1}
+FNDA:1,conjure_oxide::utils::json::sort_json_object::{closure#1}
+FNDA:0,conjure_oxide::utils::json::sort_json_object
+FNDA:1,conjure_oxide::utils::json::sort_json_variables
+FNDA:1,conjure_oxide::utils::json::sort_json_object::{closure#0}
+FNF:18
+FNH:6
+BRF:0
+BRH:0
+DA:7,460
+DA:8,460
+DA:9,0
+DA:10,0
+DA:11,0
+DA:12,0
+DA:13,0
+DA:14,0
+DA:15,0
+DA:17,104
+DA:18,104
+DA:19,104
+DA:20,104
+DA:21,104
+DA:22,0
+DA:24,0
+DA:26,356
+DA:28,460
+DA:32,72
+DA:33,72
+DA:34,72
+DA:35,72
+DA:36,72
+DA:37,72
+DA:39,0
+DA:41,72
+DA:48,7860
+DA:49,7860
+DA:50,3572
+DA:51,3572
+DA:52,3572
+DA:53,4196
+DA:54,4196
+DA:55,72
+DA:57,4124
+DA:59,4196
+DA:60,3572
+DA:61,3572
+DA:62,3572
+DA:63,3572
+DA:65,1428
+DA:66,1428
+DA:67,1428
+DA:68,3592
+DA:69,1428
+DA:70,1428
+DA:71,1428
+DA:72,72
+DA:73,1356
+DA:75,1428
+DA:77,2860
+DA:79,7860
+LF:52
+LH:42
+end_of_record
+SF:crates/conjure_core/src/metadata.rs
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::expecting
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::expecting
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::visit_map::>
+FN:23,::fmt
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:11,::default
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:5,<::deserialize::__Field as serde::de::Deserialize>::deserialize::>
+FN:11,::default
+FN:11,::default
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::visit_map::<_>
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_u64::<_>
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_str::<_>
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_bytes::<_>
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::expecting
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::visit_map::<_>
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_u64::<_>
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_str::
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::expecting
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::visit_map::<_>
+FN:5,<::deserialize::__Field as serde::de::Deserialize>::deserialize::<_>
+FN:5,<::deserialize::__Field as serde::de::Deserialize>::deserialize::<_>
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_str::
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::expecting
+FN:5,<::deserialize::__Field as serde::de::Deserialize>::deserialize::<_>
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_str::<_>
+FN:23,::fmt
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_bytes::<_>
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_u64::<_>
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_str::<_>
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::expecting
+FN:23,::fmt
+FN:17,::new
+FN:5,<::deserialize::__Field as serde::de::Deserialize>::deserialize::>
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::visit_map::>
+FN:5,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_bytes::<_>
+FN:5,<::deserialize::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:17,::new
+FN:17,::new
+FNDA:0,<::deserialize::__Visitor as serde::de::Visitor>::expecting
+FNDA:0,<::deserialize::__Visitor as serde::de::Visitor>::expecting
+FNDA:1,<::deserialize::__Visitor as serde::de::Visitor>::visit_map::>
+FNDA:0,::fmt
+FNDA:0,<::deserialize::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FNDA:0,::default
+FNDA:0,<::deserialize::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FNDA:0,<::deserialize::__Field as serde::de::Deserialize>::deserialize::>
+FNDA:0,::default
+FNDA:0,::default
+FNDA:0,<::deserialize::__Visitor as serde::de::Visitor>::visit_map::<_>
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_u64::<_>
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_str::<_>
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_bytes::<_>
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::expecting
+FNDA:0,<::deserialize::__Visitor as serde::de::Visitor>::visit_map::<_>
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_u64::<_>
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_str::
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::expecting
+FNDA:0,<::deserialize::__Visitor as serde::de::Visitor>::visit_map::<_>
+FNDA:0,<::deserialize::__Field as serde::de::Deserialize>::deserialize::<_>
+FNDA:0,<::deserialize::__Field as serde::de::Deserialize>::deserialize::<_>
+FNDA:1,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_str::
+FNDA:0,<::deserialize::__Visitor as serde::de::Visitor>::expecting
+FNDA:0,<::deserialize::__Field as serde::de::Deserialize>::deserialize::<_>
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_str::<_>
+FNDA:0,::fmt
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_bytes::<_>
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_u64::<_>
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_str::<_>
+FNDA:0,<::deserialize::__Visitor as serde::de::Visitor>::visit_seq::>
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::expecting
+FNDA:0,::fmt
+FNDA:0,::new
+FNDA:1,<::deserialize::__Field as serde::de::Deserialize>::deserialize::>
+FNDA:0,<::deserialize::__Visitor as serde::de::Visitor>::visit_seq::>
+FNDA:0,<::deserialize::__Visitor as serde::de::Visitor>::visit_map::>
+FNDA:0,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_bytes::<_>
+FNDA:0,<::deserialize::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FNDA:1,::new
+FNDA:0,::new
+FNF:41
+FNH:4
+BRF:0
+BRH:0
+DA:5,2416
+DA:11,0
+DA:12,0
+DA:13,0
+DA:17,6150
+DA:18,6150
+DA:19,6150
+DA:23,0
+DA:24,0
+DA:25,0
+LF:10
+LH:4
+end_of_record
+SF:solvers/chuffed/tests/chuffed_basic_run.rs
+FN:11,chuffed_basic_run::post_constraints
+FN:42,chuffed_basic_run::run_basic_problem
+FN:35,callback
+FNDA:0,chuffed_basic_run::post_constraints
+FNDA:1,chuffed_basic_run::run_basic_problem
+FNDA:0,callback
+FNF:3
+FNH:1
+BRF:0
+BRH:0
+DA:11,0
+DA:12,0
+DA:13,0
+DA:14,0
+DA:15,0
+DA:16,0
+DA:17,0
+DA:18,0
+DA:19,0
+DA:20,0
+DA:21,0
+DA:22,0
+DA:23,0
+DA:24,0
+DA:25,0
+DA:26,0
+DA:27,0
+DA:28,0
+DA:29,0
+DA:30,0
+DA:31,0
+DA:35,0
+DA:36,0
+DA:37,0
+DA:42,1
+DA:43,1
+DA:44,1
+DA:45,1
+DA:46,1
+DA:47,1
+DA:48,0
+DA:49,0
+DA:50,0
+DA:51,0
+DA:52,0
+DA:53,0
+DA:54,0
+DA:55,0
+DA:56,0
+DA:57,0
+DA:58,0
+DA:59,0
+DA:60,0
+DA:61,0
+DA:62,0
+DA:63,0
+DA:64,0
+DA:65,0
+DA:67,1
+LF:49
+LH:7
+end_of_record
+SF:crates/conjure_core/src/ast/expressions.rs
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,::uniplate::{closure#9}
+FN:15,::uniplate::{closure#7}
+FN:15,::uniplate::{closure#25}
+FN:15,::uniplate::{closure#19}
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,::is_neq
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,::is_reference
+FN:15,::uniplate::{closure#21}
+FN:15,::is_sum_leq
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,::is_or
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,::is_eq
+FN:15,<::deserialize::__Visitor as serde::de::Visitor>::expecting
+FN:15,::is_sum_leq
+FN:15,::is_sum_geq
+FN:15,::uniplate::{closure#12}
+FN:15,::uniplate::{closure#26}
+FN:95,::bounds
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:124,::bounds::{closure#3}
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:97,::bounds::{closure#0}
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,::uniplate::{closure#8}
+FN:15,::uniplate::{closure#10}
+FN:15,<::deserialize::__Field as serde::de::Deserialize>::deserialize::<_>
+FN:15,::uniplate::{closure#13}
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,::uniplate::{closure#15}
+FN:15,::is_geq
+FN:15,::is_all_diff
+FN:15,::is_constant
+FN:15,::uniplate::{closure#0}
+FN:15,::is_min
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,::uniplate::{closure#5}
+FN:15,::is_sum
+FN:15,::is_leq
+FN:15,::is_all_diff
+FN:15,::is_sum_geq
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::>
+FN:97,::bounds::{closure#0}
+FN:15,<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::<_>
+FN:15,<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::>
+FN:15,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_bytes::<_>
+FN:15,::is_nothing
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:15,<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::<_>
+FN:15,<::deserialize::__FieldVisitor as serde::de::Visitor>::expecting
+FN:15,::uniplate::{closure#20}
+FN:15,::is_and
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:15,::is_nothing
+FN:15,::uniplate::{closure#22}
+FN:97,::bounds::{closure#0}
+FN:15,::is_sum_eq
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:15,::uniplate::{closure#25}
+FN:15,::uniplate::{closure#23}
+FN:15,::uniplate::{closure#6}
+FN:15,::uniplate::{closure#8}
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,::uniplate::{closure#14}
+FN:15,::uniplate::{closure#16}
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,::is_lt
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:15,::uniplate
+FN:15,::is_constant
+FN:15,::is_gt
+FN:15,::is_or
+FN:15,::uniplate::{closure#18}
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,::is_sum_eq
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,<::deserialize::__FieldVisitor as serde::de::Visitor>::expecting
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,::uniplate::{closure#18}
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,::is_gt
+FN:15,::uniplate::{closure#5}
+FN:15,::uniplate::{closure#3}
+FN:15,<::deserialize::__Field as serde::de::Deserialize>::deserialize::<&mut serde_json::de::Deserializer>
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,::is_eq
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_str::<_>
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:95,::bounds
+FN:15,<::deserialize::__FieldVisitor as serde::de::Visitor>::visit_bytes::<_>
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:15,::uniplate
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::expecting
+FN:15,::is_and
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:123,::bounds::{closure#2}
+FN:132,conjure_core::ast::expressions::display_expressions
+FN:15,::uniplate::{closure#24}
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::>
+FN:15,::uniplate::{closure#1}
+FN:15,<<::deserialize::__Visitor as serde::de::Visitor>::visit_enum::__Visitor as serde::de::Visitor>::visit_seq::<_>
+FN:15,<