1
use crate::shared::utils::{as_resolved_atom, to_aux_var_in};
2
use crate::types::matrix::MatrixComponents;
3
use crate::types::record::RecordComponents;
4
use crate::types::tuple::{TupleComponents, TuplePacked};
5
use conjure_cp::ast::{
6
    Atom, Expression as Expr, GroundDomain, Metadata, Moo, Name, SymbolTable, matrix,
7
};
8
use conjure_cp::representation::ReprRule;
9
use conjure_cp::rule_engine::{
10
    ApplicationError, ApplicationError::RuleNotApplicable, ApplicationResult, RuleEffect,
11
    register_rule,
12
};
13
use itertools::Itertools as _;
14

            
15
3535
fn lex_operand_elements(expr: &Expr) -> Result<Vec<Expr>, ApplicationError> {
16
3535
    let expr = match expr {
17
378
        Expr::Flatten(_, None, inner) => inner.as_ref(),
18
3157
        other => other,
19
    };
20
3535
    if let Some(elements) = expr.unwrap_list() {
21
2989
        return Ok(elements);
22
546
    }
23

            
24
    // Lex order depends only on the order of the elements, not on the index values, so any
25
    // (one-dimensional) index domain is fine. Elements of a multi-dimensional matrix are
26
    // themselves matrices, which `flatten_lex_element` rejects.
27
546
    let Some((elements, _)) = expr.clone().unwrap_matrix_unchecked() else {
28
210
        return Err(RuleNotApplicable);
29
    };
30
336
    Ok(elements)
31
3535
}
32

            
33
3535
fn lex_operand_to_atoms(
34
3535
    operand: &Moo<Expr>,
35
3535
    symbols: &mut SymbolTable,
36
3535
    tops: &mut Vec<Expr>,
37
3535
) -> Result<Vec<Atom>, ApplicationError> {
38
3535
    if let Some(atoms) = lex_represented_matrix_to_atoms(operand.as_ref(), symbols)? {
39
        return Ok(atoms);
40
3535
    }
41

            
42
3535
    let mut atoms = vec![];
43
3535
    for element in lex_operand_elements(operand.as_ref())? {
44
9471
        atoms.extend(flatten_lex_element(&element, symbols, tops)?);
45
    }
46
3325
    Ok(atoms)
47
3535
}
48

            
49
/// Expand one lex-operand element into the flat scalar atoms Minion's `FlatLexLt`/`FlatLexLeq`
50
/// need. A list element is not always already scalar -- e.g. a matrix-of-tuples operand (produced
51
/// by a set's own "compare through representation" ordering rule when its elements are tuples)
52
/// has tuple-typed elements, and treating one of those as a ready atom would leak its still-
53
/// abstract declaration name straight into the backend. Splicing a compound element's own fields
54
/// into the flat list in its place instead preserves lex semantics exactly: comparing
55
/// `[t1, t2]` against `[u1, u2]` lexicographically is equivalent to comparing
56
/// `[t1.f1, t1.f2, t2.f1, t2.f2]` against `[u1.f1, u1.f2, u2.f1, u2.f2]`, since a tuple's own
57
/// ordering is itself lexicographic over its fields. Recurses so a field that is itself a
58
/// tuple/record keeps unwinding.
59
9786
fn flatten_lex_element(
60
9786
    element: &Expr,
61
9786
    symbols: &mut SymbolTable,
62
9786
    tops: &mut Vec<Expr>,
63
9786
) -> Result<Vec<Atom>, ApplicationError> {
64
9786
    if let Some(atom) = as_resolved_atom(element) {
65
8995
        return Ok(vec![atom]);
66
791
    }
67

            
68
791
    if let Expr::Atomic(_, Atom::Reference(reference)) = element {
69
        // Tuple/record fields decompose into several sub-elements; a packed tuple is already a
70
        // single scalar integer that preserves lex order by construction (see
71
        // `types::tuple::packed::vertical::packed_cmp`), so it decomposes into just itself.
72
210
        let sub_elements = if let Some(repr) = reference.ptr().get_repr::<TupleComponents>() {
73
105
            Some(repr.field_exprs())
74
105
        } else if let Some(repr) = reference.ptr().get_repr::<RecordComponents>() {
75
            Some(repr.field_exprs())
76
        } else {
77
105
            reference
78
105
                .ptr()
79
105
                .get_repr::<TuplePacked>()
80
105
                .map(|repr| vec![repr.packed_expr()])
81
        };
82
210
        if let Some(sub_elements) = sub_elements {
83
210
            let mut flattened = Vec::with_capacity(sub_elements.len());
84
210
            for sub_element in &sub_elements {
85
315
                flattened.extend(flatten_lex_element(sub_element, symbols, tops)?);
86
            }
87
210
            return Ok(flattened);
88
        }
89
581
    }
90

            
91
581
    if let Some((reference, top)) = to_aux_var_in(element, symbols) {
92
581
        tops.push(top);
93
581
        return Ok(vec![Atom::Reference(reference)]);
94
    }
95

            
96
    Err(RuleNotApplicable)
97
9786
}
98

            
99
3535
fn lex_represented_matrix_to_atoms(
100
3535
    operand: &Expr,
101
3535
    symbols: &SymbolTable,
102
3535
) -> Result<Option<Vec<Atom>>, ApplicationError> {
103
3535
    let Expr::Atomic(_, Atom::Reference(declaration)) = operand else {
104
3332
        return Ok(None);
105
    };
106
203
    let Name::WithRepresentation(name, representations) = &declaration.name() as &Name else {
107
203
        return Ok(None);
108
    };
109
    if representations
110
        .first()
111
        .is_none_or(|id| *id != MatrixComponents::id())
112
    {
113
        return Ok(None);
114
    }
115

            
116
    let declaration = symbols.lookup(name.as_ref()).ok_or(RuleNotApplicable)?;
117
    let representation = symbols
118
        .get_representation(name.as_ref(), &[MatrixComponents::id()])
119
        .ok_or(RuleNotApplicable)?[0]
120
        .clone();
121
    let domain = declaration.resolved_domain().ok_or(RuleNotApplicable)?;
122
    let GroundDomain::Matrix(_, index_domains, _) = domain.as_ref() else {
123
        return Ok(None);
124
    };
125
    if index_domains.len() != 1 {
126
        return Ok(None);
127
    }
128

            
129
    let matrix_values = representation.expression_down(symbols)?;
130
    matrix::enumerate_indices(index_domains.clone())
131
        .map(|index| {
132
            matrix_values
133
                .get(&Name::Represented(Box::new((
134
                    name.as_ref().clone(),
135
                    MatrixComponents::id(),
136
                    index.iter().join("_").into(),
137
                ))))
138
                .cloned()
139
                .ok_or(RuleNotApplicable)?
140
                .try_into()
141
                .map_err(|_| RuleNotApplicable)
142
        })
143
        .collect::<Result<Vec<_>, _>>()
144
        .map(Some)
145
3535
}
146

            
147
#[register_rule("Minion", 2000, [LexLt, LexLeq])]
148
2798
fn flatten_lex_lt_leq(expr: &Expr, symbols: &SymbolTable) -> ApplicationResult {
149
2798
    let (lhs, rhs) = match expr {
150
1869
        Expr::LexLt(_, lhs, rhs) | Expr::LexLeq(_, lhs, rhs) => (lhs, rhs),
151
929
        _ => return Err(RuleNotApplicable),
152
    };
153

            
154
1869
    let mut symbols = symbols.clone();
155
1869
    let mut tops = vec![];
156
1869
    let mut lhs = lex_operand_to_atoms(lhs, &mut symbols, &mut tops)?;
157
1666
    let mut rhs = lex_operand_to_atoms(rhs, &mut symbols, &mut tops)?;
158
1659
    let new_expression = if lhs.len() == rhs.len() {
159
1617
        match expr {
160
889
            Expr::LexLt(..) => Expr::FlatLexLt(Metadata::new(), lhs, rhs),
161
728
            Expr::LexLeq(..) => Expr::FlatLexLeq(Metadata::new(), lhs, rhs),
162
            _ => unreachable!(),
163
        }
164
    } else {
165
42
        let first_longer = lhs.len() > rhs.len();
166
42
        let min_len = lhs.len().min(rhs.len());
167
42
        lhs.truncate(min_len);
168
42
        rhs.truncate(min_len);
169
42
        if first_longer {
170
28
            Expr::FlatLexLt(Metadata::new(), lhs, rhs)
171
        } else {
172
14
            Expr::FlatLexLeq(Metadata::new(), lhs, rhs)
173
        }
174
    };
175

            
176
1659
    if tops.is_empty() {
177
1512
        Ok(RuleEffect::pure(new_expression))
178
    } else {
179
147
        Ok(RuleEffect::new(new_expression, tops, symbols))
180
    }
181
2798
}