1
use conjure_cp::{
2
    ast::{
3
        Atom, DeclarationKind, DeclarationPtr, DomainPtr, Expression, Literal, Metadata, Name,
4
        SymbolTable,
5
        comprehension::{Comprehension, ComprehensionQualifier},
6
        eval_constant,
7
    },
8
    bug,
9
    solver::SolverError,
10
};
11
use uniplate::Biplate as _;
12

            
13
use super::via_solver_common::{lift_machine_references_into_parent_scope, simplify_expression};
14

            
15
/// Expands the comprehension without calling an external solver.
16
///
17
/// Algorithm:
18
/// 1. Recurse qualifiers left-to-right.
19
/// 2. For each generator value, temporarily bind the quantified declaration to a
20
///    `TemporaryValueLetting` and recurse.
21
/// 3. For each condition, evaluate and recurse only if true.
22
/// 4. At the leaf, evaluate the return expression under the active bindings.
23
396
pub fn expand_native(
24
396
    comprehension: Comprehension,
25
396
    parent_symbols: &mut SymbolTable,
26
396
) -> Result<Vec<Expression>, SolverError> {
27
396
    let mut expanded = Vec::new();
28
396
    expand_qualifiers(&comprehension, 0, &mut expanded, parent_symbols)?;
29
396
    Ok(expanded)
30
396
}
31

            
32
16450
fn expand_qualifiers(
33
16450
    comprehension: &Comprehension,
34
16450
    qualifier_index: usize,
35
16450
    expanded: &mut Vec<Expression>,
36
16450
    parent_symbols: &mut SymbolTable,
37
16450
) -> Result<(), SolverError> {
38
16450
    if qualifier_index == comprehension.qualifiers.len() {
39
13698
        let child_symbols = comprehension.symbols().clone();
40
13698
        let return_expression =
41
13698
            concretise_resolved_reference_atoms(comprehension.return_expression.clone());
42
13698
        let return_expression = simplify_expression(return_expression);
43
13698
        let return_expression = lift_machine_references_into_parent_scope(
44
13698
            return_expression,
45
13698
            &child_symbols,
46
13698
            parent_symbols,
47
        );
48
13698
        expanded.push(return_expression);
49
13698
        return Ok(());
50
2752
    }
51

            
52
2752
    match &comprehension.qualifiers[qualifier_index] {
53
2106
        ComprehensionQualifier::Generator { ptr } => {
54
2106
            let name = ptr.name().clone();
55
2106
            let domain = ptr.domain().expect("generator declaration has domain");
56
2106
            let values = resolve_generator_values(&name, &domain)?;
57

            
58
15454
            for literal in values {
59
15454
                with_temporary_quantified_binding(ptr, &literal, || {
60
15454
                    expand_qualifiers(comprehension, qualifier_index + 1, expanded, parent_symbols)
61
15454
                })?;
62
            }
63
        }
64
646
        ComprehensionQualifier::Condition(condition) => {
65
646
            if evaluate_guard(condition)? {
66
600
                expand_qualifiers(comprehension, qualifier_index + 1, expanded, parent_symbols)?;
67
46
            }
68
        }
69
        ComprehensionQualifier::ExpressionGenerator { .. } => {
70
            bug!(
71
                "Comprehension expander should not be called on comprehensions containing ExpressionGenerator"
72
            );
73
        }
74
    }
75

            
76
2752
    Ok(())
77
16450
}
78

            
79
2106
fn resolve_generator_values(name: &Name, domain: &DomainPtr) -> Result<Vec<Literal>, SolverError> {
80
2106
    let resolved = domain.resolve().ok_or_else(|| {
81
        SolverError::ModelFeatureNotSupported(format!(
82
            "quantified variable '{name}' has unresolved domain after assigning previous generators: {domain}"
83
        ))
84
    })?;
85

            
86
2106
    resolved.values().map(|iter| iter.collect()).map_err(|err| {
87
        SolverError::ModelFeatureNotSupported(format!(
88
            "quantified variable '{name}' has non-enumerable domain: {err}"
89
        ))
90
    })
91
2106
}
92

            
93
15454
fn with_temporary_quantified_binding<T>(
94
15454
    quantified: &DeclarationPtr,
95
15454
    value: &Literal,
96
15454
    f: impl FnOnce() -> Result<T, SolverError>,
97
15454
) -> Result<T, SolverError> {
98
15454
    let mut targets = vec![quantified.clone()];
99
15454
    if let DeclarationKind::Quantified(inner) = &*quantified.kind()
100
15454
        && let Some(generator) = inner.generator()
101
    {
102
        targets.push(generator.clone());
103
15454
    }
104

            
105
15454
    let mut originals = Vec::with_capacity(targets.len());
106
15454
    for mut target in targets {
107
15454
        let old_kind = target.replace_kind(DeclarationKind::TemporaryValueLetting(
108
15454
            Expression::Atomic(Metadata::new(), Atom::Literal(value.clone())),
109
15454
        ));
110
15454
        originals.push((target, old_kind));
111
15454
    }
112

            
113
15454
    let result = f();
114

            
115
15454
    for (mut target, old_kind) in originals.into_iter().rev() {
116
15454
        let _ = target.replace_kind(old_kind);
117
15454
    }
118

            
119
15454
    result
120
15454
}
121

            
122
646
fn evaluate_guard(guard: &Expression) -> Result<bool, SolverError> {
123
646
    let simplified = simplify_expression(guard.clone());
124
646
    match eval_constant(&simplified) {
125
646
        Some(Literal::Bool(value)) => Ok(value),
126
        Some(other) => Err(SolverError::ModelInvalid(format!(
127
            "native comprehension guard must evaluate to Bool, got {other}: {guard}"
128
        ))),
129
        None => Err(SolverError::ModelInvalid(format!(
130
            "native comprehension expansion could not evaluate guard: {guard}"
131
        ))),
132
    }
133
646
}
134

            
135
13698
fn concretise_resolved_reference_atoms(expr: Expression) -> Expression {
136
280050
    expr.transform_bi(&|atom: Atom| match atom {
137
240691
        Atom::Reference(reference) => reference
138
240691
            .resolve_constant()
139
240691
            .map_or_else(|| Atom::Reference(reference), Atom::Literal),
140
39359
        other => other,
141
280050
    })
142
13698
}