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
    context: Arc<RwLock<Context<'static>>>,
34
) -> Result<Model, EssenceParseError> {
35
    let mut cmd = std::process::Command::new("conjure");
36
    let output = match cmd
37
        .arg("pretty")
38
        .arg("--output-format=astjson")
39
        .arg(format!("{path}/{filename}.essence"))
40
        .output()
41
    {
42
        Ok(output) => output,
43
        Err(e) => return Err(EssenceParseError::ConjurePrettyError(e.to_string())),
44
    };
45

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

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

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

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

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

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

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

            
85
    solver.save_stats_to_context();
86

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

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

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