1
#![allow(unreachable_patterns)]
2
#![allow(unsafe_op_in_unsafe_fn)]
3

            
4
use std::{
5
    collections::HashMap,
6
    ffi::{CStr, CString, c_char, c_void},
7
};
8

            
9
use anyhow::anyhow;
10

            
11
use crate::{
12
    ast::Tuple,
13
    ffi::{self},
14
};
15
use crate::{
16
    ast::{Constant, Constraint, Model, Var, VarDomain, VarName},
17
    error::{MinionError, RuntimeError},
18
    scoped_ptr::Scoped,
19
};
20

            
21
unsafe fn take_minion_ffi_error() -> String {
22
    let error_ptr = ffi::minion_get_last_error();
23
    if error_ptr.is_null() {
24
        return "unknown Minion FFI error".to_string();
25
    }
26

            
27
    let message = CStr::from_ptr(error_ptr).to_string_lossy().into_owned();
28
    ffi::minion_clear_last_error();
29
    if message.is_empty() {
30
        "unknown Minion FFI error".to_string()
31
    } else {
32
        message
33
    }
34
}
35

            
36
220972
unsafe fn new_var_ffi_checked(
37
220972
    instance: *mut ffi::ProbSpec_CSPInstance,
38
220972
    name: *const c_char,
39
220972
    vartype_raw: ffi::VariableType,
40
220972
    domain_low: i32,
41
220972
    domain_high: i32,
42
220972
) -> Result<(), MinionError> {
43
220972
    if ffi::newVar_ffi_safe(
44
220972
        instance,
45
220972
        name as *mut c_char,
46
220972
        vartype_raw,
47
220972
        domain_low,
48
220972
        domain_high,
49
    ) {
50
220972
        Ok(())
51
    } else {
52
        Err(MinionError::Other(anyhow!(take_minion_ffi_error())))
53
    }
54
220972
}
55

            
56
658656
unsafe fn get_var_by_name_checked(
57
658656
    instance: *mut ffi::ProbSpec_CSPInstance,
58
658656
    name: *const c_char,
59
658656
) -> Result<ffi::ProbSpec_Var, MinionError> {
60
658656
    let mut var = ffi::ProbSpec_Var {
61
658656
        type_m: ffi::VariableType_VAR_CONSTANT,
62
658656
        pos_m: 0,
63
658656
    };
64

            
65
658656
    if ffi::getVarByName_safe(instance, name as *mut c_char, &mut var) {
66
658656
        Ok(var)
67
    } else {
68
        Err(MinionError::Other(anyhow!(take_minion_ffi_error())))
69
    }
70
658656
}
71

            
72
/// The callback type used by [`run_minion`].
73
///
74
/// Called by Minion whenever a solution is found. The input is
75
/// a `HashMap` of all named variables along with their value.
76
///
77
/// Return `true` to continue searching, `false` to stop.
78
///
79
/// Since this is a boxed closure, it can capture state from its environment,
80
/// eliminating the need for global or thread-local state in callers.
81
///
82
/// # Examples
83
///
84
/// ```
85
///   use minion_sys::ast::*;
86
///   use minion_sys::run_minion;
87
///   use std::collections::HashMap;
88
///
89
///   let mut all_solutions: Vec<HashMap<VarName,Constant>> = vec![];
90
///
91
///   let callback = Box::new(|solutions: HashMap<VarName,Constant>| -> bool {
92
///       all_solutions.push(solutions);
93
///       true
94
///   });
95
///
96
///   // Build and run the model.
97
///   let mut model = Model::new();
98
///
99
///   // ... omitted for brevity ...
100
/// # model
101
/// #     .named_variables
102
/// #     .add_var("x".to_owned(), VarDomain::Bound(1, 3));
103
/// # model
104
/// #     .named_variables
105
/// #     .add_var("y".to_owned(), VarDomain::Bound(2, 4));
106
/// # model
107
/// #     .named_variables
108
/// #     .add_var("z".to_owned(), VarDomain::Bound(1, 5));
109
/// #
110
/// # let leq = Constraint::SumLeq(
111
/// #     vec![
112
/// #         Var::NameRef("x".to_owned()),
113
/// #         Var::NameRef("y".to_owned()),
114
/// #         Var::NameRef("z".to_owned()),
115
/// #     ],
116
/// #     Var::ConstantAsVar(4),
117
/// # );
118
/// #
119
/// # let geq = Constraint::SumGeq(
120
/// #     vec![
121
/// #         Var::NameRef("x".to_owned()),
122
/// #         Var::NameRef("y".to_owned()),
123
/// #         Var::NameRef("z".to_owned()),
124
/// #     ],
125
/// #     Var::ConstantAsVar(4),
126
/// # );
127
/// #
128
/// # let ineq = Constraint::Ineq(
129
/// #     Var::NameRef("x".to_owned()),
130
/// #     Var::NameRef("y".to_owned()),
131
/// #     Constant::Integer(-1),
132
/// # );
133
/// #
134
/// # model.constraints.push(leq);
135
/// # model.constraints.push(geq);
136
/// # model.constraints.push(ineq);
137
///
138
///   let _solver_ctx = run_minion(model, callback).expect("Error occurred");
139
///
140
///   let solution_set_1 = &all_solutions[0];
141
///   let x1 = solution_set_1.get("x").unwrap();
142
///   let y1 = solution_set_1.get("y").unwrap();
143
///   let z1 = solution_set_1.get("z").unwrap();
144
/// #
145
/// # assert_eq!(all_solutions.len(),1);
146
/// # assert_eq!(*x1,Constant::Integer(1));
147
/// # assert_eq!(*y1,Constant::Integer(2));
148
/// # assert_eq!(*z1,Constant::Integer(1));
149
/// ```
150
pub type Callback<'a> = Box<dyn FnMut(HashMap<VarName, Constant>) -> bool + 'a>;
151

            
152
/// State passed through the C callback's `void* userdata` pointer.
153
///
154
/// This replaces the old thread-local approach — all callback state is now
155
/// passed explicitly through the FFI userdata mechanism.
156
struct CallbackState<'a> {
157
    callback: Callback<'a>,
158
    print_vars: Vec<VarName>,
159
}
160

            
161
/// Opaque handle to a Minion solver context.
162
///
163
/// Holds solver state (including run statistics) after a solve completes.
164
/// Query results (e.g. via [`SolverContext::get_from_table`]) before dropping.
165
pub struct SolverContext {
166
    ctx: *mut ffi::MinionContext,
167
}
168

            
169
// Safety: MinionContext is an independent solver instance. It is safe to send
170
// between threads as long as it is not used concurrently (which we ensure by
171
// only accessing it through &mut self or after the solve completes).
172
unsafe impl Send for SolverContext {}
173

            
174
impl SolverContext {
175
    /// Gets a value from Minion's TableOut (where it stores run statistics).
176
36480
    pub fn get_from_table(&self, key: String) -> Option<String> {
177
        unsafe {
178
            #[allow(clippy::expect_used)]
179
36480
            let c_string = CString::new(key).expect("");
180
36480
            let key_ptr = c_string.into_raw();
181
36480
            let val_ptr: *mut c_char = ffi::TableOut_get(self.ctx, key_ptr);
182

            
183
36480
            drop(CString::from_raw(key_ptr));
184

            
185
36480
            if val_ptr.is_null() {
186
                None
187
            } else {
188
                #[allow(clippy::unwrap_used)]
189
36480
                let res = CStr::from_ptr(val_ptr).to_str().unwrap().to_owned();
190
36480
                libc::free(val_ptr as _);
191
36480
                Some(res)
192
            }
193
        }
194
36480
    }
195
}
196

            
197
impl Drop for SolverContext {
198
36536
    fn drop(&mut self) {
199
36536
        unsafe {
200
36536
            ffi::minion_freeContext(self.ctx);
201
36536
        }
202
36536
    }
203
}
204

            
205
/// The C callback passed to `runMinion`. Receives the active context and our
206
/// `CallbackState` via the userdata pointer.
207
999010
unsafe extern "C" fn run_callback(ctx: *mut ffi::MinionContext, userdata: *mut c_void) -> bool {
208
    // Safety: userdata is a pointer to our CallbackState, set by run_minion
209
    // and valid for the duration of the runMinion call.
210
999010
    let state = unsafe { &mut *(userdata as *mut CallbackState<'_>) };
211

            
212
999010
    if state.print_vars.is_empty() {
213
908
        return true;
214
998102
    }
215

            
216
    // Build solutions HashMap by reading variable values from Minion.
217
998102
    let mut solutions: HashMap<VarName, Constant> = HashMap::new();
218
7397152
    for (i, var) in state.print_vars.iter().enumerate() {
219
7397152
        let solution_int: i32 = unsafe { ffi::printMatrix_getValue(ctx, i as _) };
220
7397152
        let solution: Constant = Constant::Integer(solution_int);
221
7397152
        solutions.insert(var.to_string(), solution);
222
7397152
    }
223

            
224
998102
    (state.callback)(solutions)
225
999010
}
226

            
227
/// Run Minion on the given [Model].
228
///
229
/// The given [callback](Callback) is ran whenever a new solution set is found.
230
///
231
/// Returns a [`SolverContext`] on success, which can be used to query run
232
/// statistics via [`SolverContext::get_from_table`].
233
#[allow(clippy::unwrap_used)]
234
36536
pub fn run_minion(model: Model, callback: Callback<'_>) -> Result<SolverContext, MinionError> {
235
36536
    let mut state = CallbackState {
236
36536
        callback,
237
36536
        print_vars: vec![],
238
36536
    };
239

            
240
    unsafe {
241
36536
        let ctx = ffi::minion_newContext();
242
36536
        let search_opts = ffi::searchOptions_new();
243
36536
        let search_method = ffi::searchMethod_new();
244
36536
        let search_instance = ffi::instance_new();
245

            
246
        // Use Minion as a quiet library by default. Low-level FFI callers that
247
        // want native solver output can opt out by configuring SearchOptions
248
        // themselves instead of going through this wrapper.
249
36536
        (*search_opts).silent = true;
250
36536
        (*search_opts).print_solution = false;
251

            
252
36536
        convert_model_to_raw(search_instance, &model, &mut state.print_vars)?;
253

            
254
36536
        let userdata = &mut state as *mut CallbackState<'_> as *mut c_void;
255
36536
        let res = ffi::runMinion(
256
36536
            ctx,
257
36536
            search_opts,
258
36536
            search_method,
259
36536
            search_instance,
260
36536
            Some(run_callback),
261
36536
            userdata,
262
        );
263

            
264
36536
        ffi::searchMethod_free(search_method);
265
36536
        ffi::searchOptions_free(search_opts);
266
36536
        ffi::instance_free(search_instance);
267

            
268
36536
        match res {
269
36536
            0 => Ok(SolverContext { ctx }),
270
            x => {
271
                ffi::minion_freeContext(ctx);
272
                Err(MinionError::from(RuntimeError::from(x)))
273
            }
274
        }
275
    }
276
36536
}
277

            
278
36536
unsafe fn convert_model_to_raw(
279
36536
    instance: *mut ffi::ProbSpec_CSPInstance,
280
36536
    model: &Model,
281
36536
    print_vars: &mut Vec<VarName>,
282
36536
) -> Result<(), MinionError> {
283
    /*******************************/
284
    /*        Add variables        */
285
    /*******************************/
286

            
287
    /*
288
     * Add variables to:
289
     * 1. symbol table
290
     * 2. print matrix
291
     * 3. search vars
292
     *
293
     * These are all done in the order saved in the SymbolTable.
294
     */
295

            
296
36536
    let search_vars = Scoped::new(ffi::vec_var_new(), |x| ffi::vec_var_free(x as _));
297

            
298
    // initialise all variables, and add all variables to the print order
299
220972
    for var_name in model.named_variables.get_variable_order() {
300
220972
        let c_str = CString::new(var_name.clone()).map_err(|_| {
301
            anyhow!(
302
                "Variable name {:?} contains a null character.",
303
                var_name.clone()
304
            )
305
        })?;
306

            
307
220972
        let vartype = model
308
220972
            .named_variables
309
220972
            .get_vartype(var_name.clone())
310
220972
            .ok_or(anyhow!("Could not get var type for {:?}", var_name.clone()))?;
311

            
312
220972
        let (vartype_raw, domain_low, domain_high) = match vartype {
313
19074
            VarDomain::Bound(a, b) => Ok((ffi::VariableType_VAR_BOUND, a, b)),
314
178868
            VarDomain::Discrete(a, b) => Ok((ffi::VariableType_VAR_DISCRETE, a, b)),
315
23030
            VarDomain::Bool => Ok((ffi::VariableType_VAR_BOOL, 0, 1)), // TODO: will this work?
316
            x => Err(MinionError::NotImplemented(format!("{x:?}"))),
317
        }?;
318

            
319
220972
        new_var_ffi_checked(
320
220972
            instance,
321
220972
            c_str.as_ptr(),
322
220972
            vartype_raw,
323
220972
            domain_low,
324
220972
            domain_high,
325
        )?;
326

            
327
220972
        let var = get_var_by_name_checked(instance, c_str.as_ptr())?;
328

            
329
220972
        ffi::printMatrix_addVar(instance, var);
330

            
331
        // Remember the order for the callback function.
332
220972
        print_vars.push(var_name.clone());
333
    }
334

            
335
    // only add search variables to search order
336
89910
    for search_var_name in model.named_variables.get_search_variable_order() {
337
89910
        let c_str = CString::new(search_var_name.clone()).map_err(|_| {
338
            anyhow!(
339
                "Variable name {:?} contains a null character.",
340
                search_var_name.clone()
341
            )
342
        })?;
343
89910
        let var = get_var_by_name_checked(instance, c_str.as_ptr())?;
344
89910
        ffi::vec_var_push_back(search_vars.ptr, var);
345
    }
346

            
347
36536
    let search_order = Scoped::new(
348
36536
        ffi::searchOrder_new(search_vars.ptr, ffi::VarOrderEnum_ORDER_STATIC, false),
349
36536
        |x| ffi::searchOrder_free(x as _),
350
    );
351

            
352
36536
    ffi::instance_addSearchOrder(instance, search_order.ptr);
353

            
354
    /*********************************/
355
    /*        Add constraints        */
356
    /*********************************/
357

            
358
138648
    for constraint in &model.constraints {
359
        // 1. get constraint type and create C++ constraint object
360
        // 2. run through arguments and add them to the constraint
361
        // 3. add constraint to instance
362

            
363
138648
        let constraint_type = get_constraint_type(constraint)?;
364
138648
        let raw_constraint = Scoped::new(ffi::constraint_new(constraint_type), |x| {
365
138648
            ffi::constraint_free(x as _)
366
138648
        });
367

            
368
138648
        constraint_add_args(instance, raw_constraint.ptr, constraint)?;
369
138648
        ffi::instance_addConstraint(instance, raw_constraint.ptr);
370
    }
371

            
372
36536
    Ok(())
373
36536
}
374

            
375
177004
unsafe fn get_constraint_type(constraint: &Constraint) -> Result<u32, MinionError> {
376
177004
    match constraint {
377
20126
        Constraint::SumGeq(_, _) => Ok(ffi::ConstraintType_CT_GEQSUM),
378
19010
        Constraint::SumLeq(_, _) => Ok(ffi::ConstraintType_CT_LEQSUM),
379
23762
        Constraint::Ineq(_, _, _) => Ok(ffi::ConstraintType_CT_INEQ),
380
19666
        Constraint::Eq(_, _) => Ok(ffi::ConstraintType_CT_EQ),
381
        Constraint::Difference(_, _) => Ok(ffi::ConstraintType_CT_DIFFERENCE),
382
        Constraint::Div(_, _) => Ok(ffi::ConstraintType_CT_DIV),
383
3752
        Constraint::DivUndefZero(_, _) => Ok(ffi::ConstraintType_CT_DIV_UNDEFZERO),
384
        Constraint::Modulo(_, _) => Ok(ffi::ConstraintType_CT_MODULO),
385
1232
        Constraint::ModuloUndefZero(_, _) => Ok(ffi::ConstraintType_CT_MODULO_UNDEFZERO),
386
950
        Constraint::Pow(_, _) => Ok(ffi::ConstraintType_CT_POW),
387
1624
        Constraint::Product(_, _) => Ok(ffi::ConstraintType_CT_PRODUCT2),
388
6048
        Constraint::WeightedSumGeq(_, _, _) => Ok(ffi::ConstraintType_CT_WEIGHTGEQSUM),
389
6384
        Constraint::WeightedSumLeq(_, _, _) => Ok(ffi::ConstraintType_CT_WEIGHTLEQSUM),
390
        Constraint::CheckAssign(_) => Ok(ffi::ConstraintType_CT_CHECK_ASSIGN),
391
        Constraint::CheckGsa(_) => Ok(ffi::ConstraintType_CT_CHECK_GSA),
392
        Constraint::ForwardChecking(_) => Ok(ffi::ConstraintType_CT_FORWARD_CHECKING),
393
10392
        Constraint::Reify(_, _) => Ok(ffi::ConstraintType_CT_REIFY),
394
3684
        Constraint::ReifyImply(_, _) => Ok(ffi::ConstraintType_CT_REIFYIMPLY),
395
        Constraint::ReifyImplyQuick(_, _) => Ok(ffi::ConstraintType_CT_REIFYIMPLY_QUICK),
396
2072
        Constraint::WatchedAnd(_) => Ok(ffi::ConstraintType_CT_WATCHED_NEW_AND),
397
8918
        Constraint::WatchedOr(_) => Ok(ffi::ConstraintType_CT_WATCHED_NEW_OR),
398
        Constraint::GacAllDiff(_) => Ok(ffi::ConstraintType_CT_GACALLDIFF),
399
2482
        Constraint::AllDiff(_) => Ok(ffi::ConstraintType_CT_ALLDIFF),
400
        Constraint::AllDiffMatrix(_, _) => Ok(ffi::ConstraintType_CT_ALLDIFFMATRIX),
401
        Constraint::WatchSumGeq(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_GEQSUM),
402
        Constraint::WatchSumLeq(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_LEQSUM),
403
        Constraint::OccurrenceGeq(_, _, _) => Ok(ffi::ConstraintType_CT_GEQ_OCCURRENCE),
404
        Constraint::OccurrenceLeq(_, _, _) => Ok(ffi::ConstraintType_CT_LEQ_OCCURRENCE),
405
        Constraint::Occurrence(_, _, _) => Ok(ffi::ConstraintType_CT_OCCURRENCE),
406
        Constraint::LitSumGeq(_, _, _) => Ok(ffi::ConstraintType_CT_WATCHED_LITSUM),
407
        Constraint::Gcc(_, _, _) => Ok(ffi::ConstraintType_CT_GCC),
408
        Constraint::GccWeak(_, _, _) => Ok(ffi::ConstraintType_CT_GCCWEAK),
409
        Constraint::LexLeqRv(_, _) => Ok(ffi::ConstraintType_CT_GACLEXLEQ),
410
336
        Constraint::LexLeq(_, _) => Ok(ffi::ConstraintType_CT_LEXLEQ),
411
224
        Constraint::LexLess(_, _) => Ok(ffi::ConstraintType_CT_LEXLESS),
412
        Constraint::LexLeqQuick(_, _) => Ok(ffi::ConstraintType_CT_QUICK_LEXLEQ),
413
        Constraint::LexLessQuick(_, _) => Ok(ffi::ConstraintType_CT_QUICK_LEXLEQ),
414
        Constraint::WatchVecNeq(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_VECNEQ),
415
        Constraint::WatchVecExistsLess(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_VEC_OR_LESS),
416
        Constraint::Hamming(_, _, _) => Ok(ffi::ConstraintType_CT_WATCHED_HAMMING),
417
        Constraint::NotHamming(_, _, _) => Ok(ffi::ConstraintType_CT_WATCHED_NOT_HAMMING),
418
        Constraint::FrameUpdate(_, _, _, _, _) => Ok(ffi::ConstraintType_CT_FRAMEUPDATE),
419
84
        Constraint::NegativeTable(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_NEGATIVE_TABLE),
420
364
        Constraint::Table(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_TABLE),
421
        Constraint::GacSchema(_, _) => Ok(ffi::ConstraintType_CT_GACSCHEMA),
422
        Constraint::LightTable(_, _) => Ok(ffi::ConstraintType_CT_LIGHTTABLE),
423
        Constraint::Mddc(_, _) => Ok(ffi::ConstraintType_CT_MDDC),
424
        Constraint::NegativeMddc(_, _) => Ok(ffi::ConstraintType_CT_NEGATIVEMDDC),
425
        Constraint::Str2Plus(_, _) => Ok(ffi::ConstraintType_CT_STR),
426
        Constraint::Max(_, _) => Ok(ffi::ConstraintType_CT_MAX),
427
        Constraint::Min(_, _) => Ok(ffi::ConstraintType_CT_MIN),
428
        Constraint::NvalueGeq(_, _) => Ok(ffi::ConstraintType_CT_GEQNVALUE),
429
        Constraint::NvalueLeq(_, _) => Ok(ffi::ConstraintType_CT_LEQNVALUE),
430
        Constraint::Element(_, _, _) => Ok(ffi::ConstraintType_CT_ELEMENT),
431
10474
        Constraint::ElementOne(_, _, _) => Ok(ffi::ConstraintType_CT_ELEMENT_ONE),
432
        Constraint::ElementUndefZero(_, _, _) => Ok(ffi::ConstraintType_CT_ELEMENT_UNDEFZERO),
433
        Constraint::WatchElement(_, _, _) => Ok(ffi::ConstraintType_CT_WATCHED_ELEMENT),
434
        Constraint::WatchElementOne(_, _, _) => Ok(ffi::ConstraintType_CT_WATCHED_ELEMENT_ONE),
435
        Constraint::WatchElementOneUndefZero(_, _, _) => {
436
            Ok(ffi::ConstraintType_CT_WATCHED_ELEMENT_ONE_UNDEFZERO)
437
        }
438
        Constraint::WatchElementUndefZero(_, _, _) => {
439
            Ok(ffi::ConstraintType_CT_WATCHED_ELEMENT_UNDEFZERO)
440
        }
441
14514
        Constraint::WLiteral(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_LIT),
442
        Constraint::WNotLiteral(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_NOTLIT),
443
448
        Constraint::WInIntervalSet(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_ININTERVALSET),
444
        Constraint::WInRange(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_INRANGE),
445
2014
        Constraint::WInset(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_INSET),
446
        Constraint::WNotInRange(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_NOT_INRANGE),
447
        Constraint::WNotInset(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_NOT_INSET),
448
1120
        Constraint::Abs(_, _) => Ok(ffi::ConstraintType_CT_ABS),
449
10600
        Constraint::DisEq(_, _) => Ok(ffi::ConstraintType_CT_DISEQ),
450
672
        Constraint::MinusEq(_, _) => Ok(ffi::ConstraintType_CT_MINUSEQ),
451
        Constraint::GacEq(_, _) => Ok(ffi::ConstraintType_CT_GACEQ),
452
        Constraint::WatchLess(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_LESS),
453
        Constraint::WatchNeq(_, _) => Ok(ffi::ConstraintType_CT_WATCHED_NEQ),
454
4988
        Constraint::True => Ok(ffi::ConstraintType_CT_TRUE),
455
1064
        Constraint::False => Ok(ffi::ConstraintType_CT_FALSE),
456

            
457
        #[allow(unreachable_patterns)]
458
        x => Err(MinionError::NotImplemented(format!(
459
            "Constraint not implemented {x:?}",
460
        ))),
461
    }
462
177004
}
463

            
464
177004
unsafe fn constraint_add_args(
465
177004
    i: *mut ffi::ProbSpec_CSPInstance,
466
177004
    r_constr: *mut ffi::ProbSpec_ConstraintBlob,
467
177004
    constr: &Constraint,
468
177004
) -> Result<(), MinionError> {
469
177004
    match constr {
470
20126
        Constraint::SumGeq(lhs_vars, rhs_var) => {
471
20126
            read_list(i, r_constr, lhs_vars)?;
472
20126
            read_var(i, r_constr, rhs_var)?;
473
20126
            Ok(())
474
        }
475
19010
        Constraint::SumLeq(lhs_vars, rhs_var) => {
476
19010
            read_list(i, r_constr, lhs_vars)?;
477
19010
            read_var(i, r_constr, rhs_var)?;
478
19010
            Ok(())
479
        }
480
23762
        Constraint::Ineq(var1, var2, c) => {
481
23762
            read_var(i, r_constr, var1)?;
482
23762
            read_var(i, r_constr, var2)?;
483
23762
            read_constant(r_constr, c)?;
484
23762
            Ok(())
485
        }
486
19666
        Constraint::Eq(var1, var2) => {
487
19666
            read_var(i, r_constr, var1)?;
488
19666
            read_var(i, r_constr, var2)?;
489
19666
            Ok(())
490
        }
491
        Constraint::Difference((a, b), c) => {
492
            read_2_vars(i, r_constr, a, b)?;
493
            read_var(i, r_constr, c)?;
494
            Ok(())
495
        }
496
        Constraint::Div((a, b), c) => {
497
            read_2_vars(i, r_constr, a, b)?;
498
            read_var(i, r_constr, c)?;
499
            Ok(())
500
        }
501
3752
        Constraint::DivUndefZero((a, b), c) => {
502
3752
            read_2_vars(i, r_constr, a, b)?;
503
3752
            read_var(i, r_constr, c)?;
504
3752
            Ok(())
505
        }
506
        Constraint::Modulo((a, b), c) => {
507
            read_2_vars(i, r_constr, a, b)?;
508
            read_var(i, r_constr, c)?;
509
            Ok(())
510
        }
511
1232
        Constraint::ModuloUndefZero((a, b), c) => {
512
1232
            read_2_vars(i, r_constr, a, b)?;
513
1232
            read_var(i, r_constr, c)?;
514
1232
            Ok(())
515
        }
516
950
        Constraint::Pow((a, b), c) => {
517
950
            read_2_vars(i, r_constr, a, b)?;
518
950
            read_var(i, r_constr, c)?;
519
950
            Ok(())
520
        }
521
1624
        Constraint::Product((a, b), c) => {
522
1624
            read_2_vars(i, r_constr, a, b)?;
523
1624
            read_var(i, r_constr, c)?;
524
1624
            Ok(())
525
        }
526
6048
        Constraint::WeightedSumGeq(a, b, c) => {
527
6048
            read_constant_list(r_constr, a)?;
528
6048
            read_list(i, r_constr, b)?;
529
6048
            read_var(i, r_constr, c)?;
530
6048
            Ok(())
531
        }
532
6384
        Constraint::WeightedSumLeq(a, b, c) => {
533
6384
            read_constant_list(r_constr, a)?;
534
6384
            read_list(i, r_constr, b)?;
535
6384
            read_var(i, r_constr, c)?;
536
6384
            Ok(())
537
        }
538
        Constraint::CheckAssign(a) => {
539
            read_constraint(i, r_constr, (**a).clone())?;
540
            Ok(())
541
        }
542
        Constraint::CheckGsa(a) => {
543
            read_constraint(i, r_constr, (**a).clone())?;
544
            Ok(())
545
        }
546
        Constraint::ForwardChecking(a) => {
547
            read_constraint(i, r_constr, (**a).clone())?;
548
            Ok(())
549
        }
550
10392
        Constraint::Reify(a, b) => {
551
10392
            read_constraint(i, r_constr, (**a).clone())?;
552
10392
            read_var(i, r_constr, b)?;
553
10392
            Ok(())
554
        }
555
3684
        Constraint::ReifyImply(a, b) => {
556
3684
            read_constraint(i, r_constr, (**a).clone())?;
557
3684
            read_var(i, r_constr, b)?;
558
3684
            Ok(())
559
        }
560
        Constraint::ReifyImplyQuick(a, b) => {
561
            read_constraint(i, r_constr, (**a).clone())?;
562
            read_var(i, r_constr, b)?;
563
            Ok(())
564
        }
565
2072
        Constraint::WatchedAnd(a) => {
566
2072
            read_constraint_list(i, r_constr, a)?;
567
2072
            Ok(())
568
        }
569
8918
        Constraint::WatchedOr(a) => {
570
8918
            read_constraint_list(i, r_constr, a)?;
571
8918
            Ok(())
572
        }
573
        Constraint::GacAllDiff(a) => {
574
            read_list(i, r_constr, a)?;
575
            Ok(())
576
        }
577
2482
        Constraint::AllDiff(a) => {
578
2482
            read_list(i, r_constr, a)?;
579
2482
            Ok(())
580
        }
581
        Constraint::AllDiffMatrix(a, b) => {
582
            read_list(i, r_constr, a)?;
583
            read_constant(r_constr, b)?;
584
            Ok(())
585
        }
586
        Constraint::WatchSumGeq(a, b) => {
587
            read_list(i, r_constr, a)?;
588
            read_constant(r_constr, b)?;
589
            Ok(())
590
        }
591
        Constraint::WatchSumLeq(a, b) => {
592
            read_list(i, r_constr, a)?;
593
            read_constant(r_constr, b)?;
594
            Ok(())
595
        }
596
        Constraint::OccurrenceGeq(a, b, c) => {
597
            read_list(i, r_constr, a)?;
598
            read_constant(r_constr, b)?;
599
            read_constant(r_constr, c)?;
600
            Ok(())
601
        }
602
        Constraint::OccurrenceLeq(a, b, c) => {
603
            read_list(i, r_constr, a)?;
604
            read_constant(r_constr, b)?;
605
            read_constant(r_constr, c)?;
606
            Ok(())
607
        }
608
        Constraint::Occurrence(a, b, c) => {
609
            read_list(i, r_constr, a)?;
610
            read_constant(r_constr, b)?;
611
            read_var(i, r_constr, c)?;
612
            Ok(())
613
        }
614
224
        Constraint::LexLess(a, b) => {
615
224
            read_list(i, r_constr, a)?;
616
224
            read_list(i, r_constr, b)?;
617
224
            Ok(())
618
        }
619
336
        Constraint::LexLeq(a, b) => {
620
336
            read_list(i, r_constr, a)?;
621
336
            read_list(i, r_constr, b)?;
622
336
            Ok(())
623
        }
624
        //Constraint::LitSumGeq(_, _, _) => todo!(),
625
        //Constraint::Gcc(_, _, _) => todo!(),
626
        //Constraint::GccWeak(_, _, _) => todo!(),
627
        //Constraint::LexLeqRv(_, _) => todo!(),
628
        //Constraint::LexLeqQuick(_, _) => todo!(),
629
        //Constraint::LexLessQuick(_, _) => todo!(),
630
        //Constraint::WatchVecNeq(_, _) => todo!(),
631
        //Constraint::WatchVecExistsLess(_, _) => todo!(),
632
        //Constraint::Hamming(_, _, _) => todo!(),
633
        //Constraint::NotHamming(_, _, _) => todo!(),
634
        //Constraint::FrameUpdate(_, _, _, _, _) => todo!(),
635
364
        Constraint::NegativeTable(vars, tuple_list) | Constraint::Table(vars, tuple_list) => {
636
448
            read_list(i, r_constr, vars)?;
637
448
            read_tuple_list(r_constr, tuple_list)?;
638
448
            Ok(())
639
        }
640
        //Constraint::GacSchema(_, _) => todo!(),
641
        //Constraint::LightTable(_, _) => todo!(),
642
        //Constraint::Mddc(_, _) => todo!(),
643
        //Constraint::NegativeMddc(_, _) => todo!(),
644
        //Constraint::Str2Plus(_, _) => todo!(),
645
        //Constraint::Max(_, _) => todo!(),
646
        //Constraint::Min(_, _) => todo!(),
647
        //Constraint::NvalueGeq(_, _) => todo!(),
648
        //Constraint::NvalueLeq(_, _) => todo!(),
649
        //Constraint::Element(_, _, _) => todo!(),
650
        //Constraint::ElementUndefZero(_, _, _) => todo!(),
651
        //Constraint::WatchElement(_, _, _) => todo!(),
652
        //Constraint::WatchElementOne(_, _, _) => todo!(),
653
10474
        Constraint::ElementOne(vec, j, e) => {
654
10474
            read_list(i, r_constr, vec)?;
655
10474
            read_var(i, r_constr, j)?;
656
10474
            read_var(i, r_constr, e)?;
657
10474
            Ok(())
658
        }
659
        //Constraint::WatchElementOneUndefZero(_, _, _) => todo!(),
660
        //Constraint::WatchElementUndefZero(_, _, _) => todo!(),
661
14514
        Constraint::WLiteral(a, b) => {
662
14514
            read_var(i, r_constr, a)?;
663
14514
            read_constant(r_constr, b)?;
664
14514
            Ok(())
665
        }
666
        //Constraint::WNotLiteral(_, _) => todo!(),
667
448
        Constraint::WInIntervalSet(var, consts) => {
668
448
            read_var(i, r_constr, var)?;
669
448
            read_constant_list(r_constr, consts)?;
670
448
            Ok(())
671
        }
672
        //Constraint::WInRange(_, _) => todo!(),
673
2014
        Constraint::WInset(a, b) => {
674
2014
            read_var(i, r_constr, a)?;
675
2014
            read_constant_list(r_constr, b)?;
676
2014
            Ok(())
677
        }
678
        //Constraint::WNotInRange(_, _) => todo!(),
679
        //Constraint::WNotInset(_, _) => todo!(),
680
1120
        Constraint::Abs(a, b) => {
681
1120
            read_var(i, r_constr, a)?;
682
1120
            read_var(i, r_constr, b)?;
683
1120
            Ok(())
684
        }
685
10600
        Constraint::DisEq(a, b) => {
686
10600
            read_var(i, r_constr, a)?;
687
10600
            read_var(i, r_constr, b)?;
688
10600
            Ok(())
689
        }
690
672
        Constraint::MinusEq(a, b) => {
691
672
            read_var(i, r_constr, a)?;
692
672
            read_var(i, r_constr, b)?;
693
672
            Ok(())
694
        }
695
        //Constraint::GacEq(_, _) => todo!(),
696
        //Constraint::WatchLess(_, _) => todo!(),
697
        // TODO: ensure that this is a bool?
698
        Constraint::WatchNeq(a, b) => {
699
            read_var(i, r_constr, a)?;
700
            read_var(i, r_constr, b)?;
701
            Ok(())
702
        }
703

            
704
4988
        Constraint::True => Ok(()),
705
1064
        Constraint::False => Ok(()),
706
        #[allow(unreachable_patterns)]
707
        x => Err(MinionError::NotImplemented(format!("{x:?}"))),
708
    }
709
177004
}
710

            
711
// DO NOT call manually - this assumes that all needed vars are already in the symbol table.
712
// TODO not happy with this just assuming the name is in the symbol table
713
66092
unsafe fn read_list(
714
66092
    instance: *mut ffi::ProbSpec_CSPInstance,
715
66092
    raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
716
66092
    vars: &Vec<Var>,
717
66092
) -> Result<(), MinionError> {
718
66092
    let raw_vars = Scoped::new(ffi::vec_var_new(), |x| ffi::vec_var_free(x as _));
719
196856
    for var in vars {
720
196856
        let raw_var = match var {
721
157460
            Var::NameRef(name) => {
722
157460
                let c_str = CString::new(name.clone()).map_err(|_| {
723
                    anyhow!(
724
                        "Variable name {:?} contains a null character.",
725
                        name.clone()
726
                    )
727
                })?;
728
157460
                get_var_by_name_checked(instance, c_str.as_ptr())?
729
            }
730
39396
            Var::ConstantAsVar(n) => ffi::constantAsVar(*n),
731
        };
732

            
733
196856
        ffi::vec_var_push_back(raw_vars.ptr, raw_var);
734
    }
735

            
736
66092
    ffi::constraint_addList(raw_constraint, raw_vars.ptr);
737

            
738
66092
    Ok(())
739
66092
}
740

            
741
222766
unsafe fn read_var(
742
222766
    instance: *mut ffi::ProbSpec_CSPInstance,
743
222766
    raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
744
222766
    var: &Var,
745
222766
) -> Result<(), MinionError> {
746
222766
    let raw_vars = Scoped::new(ffi::vec_var_new(), |x| ffi::vec_var_free(x as _));
747
222766
    let raw_var = match var {
748
176764
        Var::NameRef(name) => {
749
176764
            let c_str = CString::new(name.clone()).map_err(|_| {
750
                anyhow!(
751
                    "Variable name {:?} contains a null character.",
752
                    name.clone()
753
                )
754
            })?;
755
176764
            get_var_by_name_checked(instance, c_str.as_ptr())?
756
        }
757
46002
        Var::ConstantAsVar(n) => ffi::constantAsVar(*n),
758
    };
759

            
760
222766
    ffi::vec_var_push_back(raw_vars.ptr, raw_var);
761
222766
    ffi::constraint_addList(raw_constraint, raw_vars.ptr);
762

            
763
222766
    Ok(())
764
222766
}
765

            
766
7558
unsafe fn read_2_vars(
767
7558
    instance: *mut ffi::ProbSpec_CSPInstance,
768
7558
    raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
769
7558
    var1: &Var,
770
7558
    var2: &Var,
771
7558
) -> Result<(), MinionError> {
772
7558
    let mut raw_var = match var1 {
773
7334
        Var::NameRef(name) => {
774
7334
            let c_str = CString::new(name.clone()).map_err(|_| {
775
                anyhow!(
776
                    "Variable name {:?} contains a null character.",
777
                    name.clone()
778
                )
779
            })?;
780
7334
            get_var_by_name_checked(instance, c_str.as_ptr())?
781
        }
782
224
        Var::ConstantAsVar(n) => ffi::constantAsVar(*n),
783
    };
784
7558
    let mut raw_var2 = match var2 {
785
6216
        Var::NameRef(name) => {
786
6216
            let c_str = CString::new(name.clone()).map_err(|_| {
787
                anyhow!(
788
                    "Variable name {:?} contains a null character.",
789
                    name.clone()
790
                )
791
            })?;
792
6216
            get_var_by_name_checked(instance, c_str.as_ptr())?
793
        }
794
1342
        Var::ConstantAsVar(n) => ffi::constantAsVar(*n),
795
    };
796
    // todo: does this move or copy? I am confus!
797
    // TODO need to mkae the semantics of move vs copy / ownership clear in libminion!!
798
    // This shouldve leaked everywhere by now but i think libminion copies stuff??
799
7558
    ffi::constraint_addTwoVars(raw_constraint, &mut raw_var, &mut raw_var2);
800
7558
    Ok(())
801
7558
}
802

            
803
38276
unsafe fn read_constant(
804
38276
    raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
805
38276
    constant: &Constant,
806
38276
) -> Result<(), MinionError> {
807
38276
    let val: i32 = match constant {
808
38276
        Constant::Integer(n) => Ok(*n),
809
        Constant::Bool(true) => Ok(1),
810
        Constant::Bool(false) => Ok(0),
811
        x => Err(MinionError::NotImplemented(format!("{x:?}"))),
812
    }?;
813

            
814
38276
    ffi::constraint_addConstant(raw_constraint, val);
815

            
816
38276
    Ok(())
817
38276
}
818

            
819
14894
unsafe fn read_constant_list(
820
14894
    raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
821
14894
    constants: &[Constant],
822
14894
) -> Result<(), MinionError> {
823
14894
    let raw_consts = Scoped::new(ffi::vec_int_new(), |x| ffi::vec_int_free(x as _));
824

            
825
56752
    for constant in constants.iter() {
826
56752
        let val = match constant {
827
56696
            Constant::Integer(n) => Ok(*n),
828
28
            Constant::Bool(true) => Ok(1),
829
28
            Constant::Bool(false) => Ok(0),
830
            #[allow(unreachable_patterns)] // TODO: can there be other types?
831
            x => Err(MinionError::NotImplemented(format!("{x:?}"))),
832
        }?;
833

            
834
56752
        ffi::vec_int_push_back(raw_consts.ptr, val);
835
    }
836

            
837
14894
    ffi::constraint_addConstantList(raw_constraint, raw_consts.ptr);
838
14894
    Ok(())
839
14894
}
840

            
841
//TODO: check if the inner constraint is listed in the model or not?
842
//Does this matter?
843
// TODO: type-check inner constraints vars and tuples and so on?
844
14076
unsafe fn read_constraint(
845
14076
    instance: *mut ffi::ProbSpec_CSPInstance,
846
14076
    raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
847
14076
    inner_constraint: Constraint,
848
14076
) -> Result<(), MinionError> {
849
14076
    let constraint_type = get_constraint_type(&inner_constraint)?;
850
14076
    let raw_inner_constraint = Scoped::new(ffi::constraint_new(constraint_type), |x| {
851
14076
        ffi::constraint_free(x as _)
852
14076
    });
853

            
854
14076
    constraint_add_args(instance, raw_inner_constraint.ptr, &inner_constraint)?;
855

            
856
14076
    ffi::constraint_addConstraint(raw_constraint, raw_inner_constraint.ptr);
857
14076
    Ok(())
858
14076
}
859

            
860
10990
unsafe fn read_constraint_list(
861
10990
    instance: *mut ffi::ProbSpec_CSPInstance,
862
10990
    raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
863
10990
    inner_constraints: &[Constraint],
864
10990
) -> Result<(), MinionError> {
865
10990
    let raw_inners = Scoped::new(ffi::vec_constraints_new(), |x| {
866
10990
        ffi::vec_constraints_free(x as _)
867
10990
    });
868
24280
    for inner_constraint in inner_constraints.iter() {
869
24280
        let constraint_type = get_constraint_type(inner_constraint)?;
870
24280
        let raw_inner_constraint = Scoped::new(ffi::constraint_new(constraint_type), |x| {
871
24280
            ffi::constraint_free(x as _)
872
24280
        });
873

            
874
24280
        constraint_add_args(instance, raw_inner_constraint.ptr, inner_constraint)?;
875
24280
        ffi::vec_constraints_push_back(raw_inners.ptr, raw_inner_constraint.ptr);
876
    }
877

            
878
10990
    ffi::constraint_addConstraintList(raw_constraint, raw_inners.ptr);
879
10990
    Ok(())
880
10990
}
881

            
882
448
unsafe fn read_tuple_list(
883
448
    raw_constraint: *mut ffi::ProbSpec_ConstraintBlob,
884
448
    tuples: &Vec<Tuple>,
885
448
) -> Result<(), MinionError> {
886
    // a tuple list is just a vec<vec<int>>, where each inner vec is a tuple
887
448
    let raw_tuples = Scoped::new(ffi::vec_vec_int_new(), |x| ffi::vec_vec_int_free(x as _));
888
1148
    for tuple in tuples {
889
1148
        let raw_tuple = Scoped::new(ffi::vec_int_new(), |x| ffi::vec_int_free(x as _));
890
2996
        for constant in tuple.iter() {
891
2996
            let val = match constant {
892
2996
                Constant::Integer(n) => Ok(*n),
893
                Constant::Bool(true) => Ok(1),
894
                Constant::Bool(false) => Ok(0),
895
                #[allow(unreachable_patterns)] // TODO: can there be other types?
896
                x => Err(MinionError::NotImplemented(format!("{:?}", x))),
897
            }?;
898

            
899
2996
            ffi::vec_int_push_back(raw_tuple.ptr, val);
900
        }
901

            
902
1148
        ffi::vec_vec_int_push_back_ptr(raw_tuples.ptr, raw_tuple.ptr);
903
    }
904

            
905
    // `constraint_setTuples` transfers ownership of `TupleList` into Minion via shared_ptr.
906
    // Do not wrap this pointer in `Scoped` or it will be freed too early.
907
448
    let raw_tuple_list = ffi::tupleList_new(raw_tuples.ptr);
908
448
    ffi::constraint_setTuples(raw_constraint, raw_tuple_list);
909

            
910
448
    Ok(())
911
448
}