1
use std::collections::HashMap;
2
use std::sync::{Arc, Mutex, RwLock};
3

            
4
use conjure_core::context::Context;
5
use serde_json::{Map, Value as JsonValue};
6
use thiserror::Error as ThisError;
7

            
8
use crate::ast::{Constant, Name};
9
use crate::model_from_json;
10
use crate::solver::adaptors::Minion;
11
use crate::solver::Solver;
12
use crate::utils::json::sort_json_object;
13
use crate::Error as ParseErr;
14
use crate::Model;
15

            
16
#[derive(Debug, ThisError)]
17
pub enum EssenceParseError {
18
    #[error("Error running conjure pretty: {0}")]
19
    ConjurePrettyError(String),
20
    #[error("Error parsing essence file: {0}")]
21
    ParseError(ParseErr),
22
}
23

            
24
impl From<ParseErr> for EssenceParseError {
25
    fn from(e: ParseErr) -> Self {
26
        EssenceParseError::ParseError(e)
27
    }
28
}
29

            
30
pub fn parse_essence_file(
31
    path: &str,
32
    filename: &str,
33
    extension: &str,
34
    context: Arc<RwLock<Context<'static>>>,
35
) -> Result<Model, EssenceParseError> {
36
    let mut cmd = std::process::Command::new("conjure");
37
    let output = match cmd
38
        .arg("pretty")
39
        .arg("--output-format=astjson")
40
        .arg(format!("{path}/{filename}.{extension}"))
41
        .output()
42
    {
43
        Ok(output) => output,
44
        Err(e) => return Err(EssenceParseError::ConjurePrettyError(e.to_string())),
45
    };
46

            
47
    if !output.status.success() {
48
        let stderr_string = String::from_utf8(output.stderr)
49
            .unwrap_or("stderr is not a valid UTF-8 string".to_string());
50
        return Err(EssenceParseError::ConjurePrettyError(stderr_string));
51
    }
52

            
53
    let astjson = match String::from_utf8(output.stdout) {
54
        Ok(astjson) => astjson,
55
        Err(e) => {
56
            return Err(EssenceParseError::ConjurePrettyError(format!(
57
                "Error parsing output from conjure: {:#?}",
58
                e
59
            )))
60
        }
61
    };
62

            
63
    let parsed_model = model_from_json(&astjson, context)?;
64
    Ok(parsed_model)
65
}
66

            
67
pub fn get_minion_solutions(model: Model) -> Result<Vec<HashMap<Name, Constant>>, anyhow::Error> {
68
    let solver = Solver::new(Minion::new());
69

            
70
    println!("Building Minion model...");
71
    let solver = solver.load_model(model)?;
72

            
73
    println!("Running Minion...");
74

            
75
    let all_solutions_ref = Arc::new(Mutex::<Vec<HashMap<Name, Constant>>>::new(vec![]));
76
    let all_solutions_ref_2 = all_solutions_ref.clone();
77
    #[allow(clippy::unwrap_used)]
78
    let solver = solver
79
        .solve(Box::new(move |sols| {
80
            let mut all_solutions = (*all_solutions_ref_2).lock().unwrap();
81
            (*all_solutions).push(sols);
82
            true
83
        }))
84
        .unwrap();
85

            
86
    solver.save_stats_to_context();
87

            
88
    #[allow(clippy::unwrap_used)]
89
    let sols = (*all_solutions_ref).lock().unwrap();
90

            
91
    Ok((*sols).clone())
92
}
93

            
94
pub fn minion_solutions_to_json(solutions: &Vec<HashMap<Name, Constant>>) -> JsonValue {
95
    let mut json_solutions = Vec::new();
96
    for solution in solutions {
97
        let mut json_solution = Map::new();
98
        for (var_name, constant) in solution {
99
            let serialized_constant = match constant {
100
                Constant::Int(i) => JsonValue::Number((*i).into()),
101
                Constant::Bool(b) => JsonValue::Bool(*b),
102
            };
103
            json_solution.insert(var_name.to_string(), serialized_constant);
104
        }
105
        json_solutions.push(JsonValue::Object(json_solution));
106
    }
107
    let ans = JsonValue::Array(json_solutions);
108
    sort_json_object(&ans, true)
109
}