1
use conjure_cp::ast::Expression as Expr;
2
use conjure_cp::ast::GroundDomain;
3
use conjure_cp::ast::Moo;
4
use conjure_cp::ast::SymbolTable;
5
use conjure_cp::into_matrix_expr;
6
use conjure_cp::matrix_expr;
7
use conjure_cp::rule_engine::{
8
    ApplicationError::RuleNotApplicable, ApplicationResult, Reduction, register_rule,
9
};
10

            
11
use conjure_cp::ast::Atom;
12
use conjure_cp::ast::Expression;
13
use conjure_cp::ast::Literal;
14
use conjure_cp::ast::Metadata;
15
use conjure_cp::ast::Name;
16
use conjure_cp::rule_engine::ApplicationError;
17
use itertools::izip;
18

            
19
//takes a safe index expression and converts it to an atom via the representation rules
20
#[register_rule(("Base", 2000))]
21
105669
fn index_record_to_atom(expr: &Expr, symbols: &SymbolTable) -> ApplicationResult {
22
    // annoyingly, let chaining only works in if-lets, not let-elses,otherwise I could avoid the
23
    // indentation here!
24
105669
    if let Expr::SafeIndex(_, subject, indices) = expr
25
7272
        && let Expr::Atomic(_, Atom::Reference(decl)) = &**subject
26
6759
        && let Name::WithRepresentation(name, reprs) = &decl.name() as &Name
27
    {
28
198
        if reprs.first().is_none_or(|x| x.as_str() != "record_to_atom") {
29
126
            return Err(RuleNotApplicable);
30
72
        }
31

            
32
        // tuples are always one dimensional
33
72
        if indices.len() != 1 {
34
            return Err(RuleNotApplicable);
35
72
        }
36

            
37
72
        let repr = symbols
38
72
            .get_representation(name, &["record_to_atom"])
39
72
            .unwrap()[0]
40
72
            .clone();
41

            
42
72
        let Some(GroundDomain::Record(_)) = decl.resolved_domain().as_deref() else {
43
            return Err(RuleNotApplicable);
44
        };
45

            
46
72
        assert_eq!(
47
72
            indices.len(),
48
            1,
49
            "record indexing is always one dimensional"
50
        );
51

            
52
72
        let index = indices[0].clone();
53

            
54
        // during the conversion from unsafe index to safe index in bubbling
55
        // we convert the field name to a literal integer for direct access
56
72
        let Some(index) = index.into_literal() else {
57
            return Err(RuleNotApplicable); // we don't support non-literal indices
58
        };
59

            
60
72
        let indices_as_name = Name::Represented(Box::new((
61
72
            name.as_ref().clone(),
62
72
            "record_to_atom".into(),
63
72
            index.into(),
64
72
        )));
65

            
66
72
        let subject = repr.expression_down(symbols)?[&indices_as_name].clone();
67

            
68
72
        Ok(Reduction::pure(subject))
69
    } else {
70
105471
        Err(RuleNotApplicable)
71
    }
72
105669
}
73

            
74
#[register_rule(("Bubble", 8000))]
75
872982
fn record_index_to_bubble(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
76
    // annoyingly, let chaining only works in if-lets, not let-elses,otherwise I could avoid the
77
    // indentation here!
78
872982
    if let Expr::UnsafeIndex(_, subject, indices) = expr
79
16119
        && let Expr::Atomic(_, Atom::Reference(decl)) = &**subject
80
16083
        && let Name::WithRepresentation(_, reprs) = &decl.name() as &Name
81
    {
82
13095
        if reprs.first().is_none_or(|x| x.as_str() != "record_to_atom") {
83
13077
            return Err(RuleNotApplicable);
84
18
        }
85

            
86
18
        let domain = subject
87
18
            .domain_of()
88
18
            .ok_or(ApplicationError::DomainError)?
89
18
            .resolve()
90
18
            .ok_or(ApplicationError::DomainError)?;
91

            
92
18
        let GroundDomain::Record(elems) = domain.as_ref() else {
93
            return Err(RuleNotApplicable);
94
        };
95

            
96
18
        assert_eq!(
97
18
            indices.len(),
98
            1,
99
            "record indexing is always one dimensional"
100
        );
101

            
102
18
        let index = indices[0].clone();
103

            
104
18
        let Expr::Atomic(_, Atom::Reference(decl)) = index else {
105
            return Err(RuleNotApplicable);
106
        };
107

            
108
18
        let name: &Name = &decl.name();
109

            
110
        // find what numerical index in elems matches the entry name
111
27
        let Some(idx) = elems.iter().position(|x| &x.name == name) else {
112
            return Err(RuleNotApplicable);
113
        };
114

            
115
        // converting to an integer for direct access
116
18
        let idx = Expr::Atomic(Metadata::new(), Atom::Literal(Literal::Int(idx as i32 + 1)));
117

            
118
18
        let bubble_constraint = Moo::new(Expression::And(
119
18
            Metadata::new(),
120
18
            Moo::new(matrix_expr![
121
18
                Expression::Leq(
122
18
                    Metadata::new(),
123
18
                    Moo::new(idx.clone()),
124
18
                    Moo::new(Expression::Atomic(
125
18
                        Metadata::new(),
126
18
                        Atom::Literal(Literal::Int(elems.len() as i32))
127
18
                    ))
128
18
                ),
129
18
                Expression::Geq(
130
18
                    Metadata::new(),
131
18
                    Moo::new(idx.clone()),
132
18
                    Moo::new(Expression::Atomic(
133
18
                        Metadata::new(),
134
18
                        Atom::Literal(Literal::Int(1))
135
18
                    ))
136
18
                )
137
18
            ]),
138
18
        ));
139

            
140
18
        let new_expr = Moo::new(Expression::SafeIndex(
141
18
            Metadata::new(),
142
18
            subject.clone(),
143
18
            Vec::from([idx]),
144
18
        ));
145

            
146
18
        Ok(Reduction::pure(Expression::Bubble(
147
18
            Metadata::new(),
148
18
            new_expr,
149
18
            bubble_constraint,
150
18
        )))
151
    } else {
152
859887
        Err(RuleNotApplicable)
153
    }
154
872982
}
155

            
156
// dealing with equality over 2 record variables
157
#[register_rule(("Base", 2000))]
158
105669
fn record_equality(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
159
    // annoyingly, let chaining only works in if-lets, not let-elses, otherwise I could avoid the
160
    // indentation here!
161

            
162
    // check if both sides are record variables
163
105669
    if let Expr::Eq(_, left, right) = expr
164
5469
        && let Expr::Atomic(_, Atom::Reference(decl)) = &**left
165
2748
        && let Name::WithRepresentation(_, reprs) = &decl.name() as &Name
166
45
        && let Expr::Atomic(_, Atom::Reference(decl2)) = &**right
167
18
        && let Name::WithRepresentation(_, reprs2) = &decl2.name() as &Name
168

            
169
        // .. that have been represented with record_to_atom
170
18
        && reprs.first().is_none_or(|x| x.as_str() == "record_to_atom")
171
9
        && reprs2.first().is_none_or(|x| x.as_str() == "record_to_atom")
172
9
        && let Some(domain) = decl.resolved_domain()
173
9
        && let Some(domain2) = decl2.resolved_domain()
174

            
175
        // .. and have record variable domains
176
9
        && let GroundDomain::Record(entries) = domain.as_ref()
177
9
        && let GroundDomain::Record(entries2) = domain2.as_ref()
178

            
179
        // we only support equality over records of the same size
180
9
        && entries.len() == entries2.len()
181

            
182
        // assuming all record entry names must match for equality
183
18
        && izip!(entries,entries2).all(|(entry1,entry2)| entry1.name == entry2.name)
184
    {
185
9
        let mut equality_constraints = vec![];
186
        // unroll the equality into equality constraints for each field
187
18
        for i in 0..entries.len() {
188
18
            let left_elem = Expression::SafeIndex(
189
18
                Metadata::new(),
190
18
                Moo::clone(left),
191
18
                vec![Expression::Atomic(
192
18
                    Metadata::new(),
193
18
                    Atom::Literal(Literal::Int((i + 1) as i32)),
194
18
                )],
195
18
            );
196
18
            let right_elem = Expression::SafeIndex(
197
18
                Metadata::new(),
198
18
                Moo::clone(right),
199
18
                vec![Expression::Atomic(
200
18
                    Metadata::new(),
201
18
                    Atom::Literal(Literal::Int((i + 1) as i32)),
202
18
                )],
203
18
            );
204
18

            
205
18
            equality_constraints.push(Expression::Eq(
206
18
                Metadata::new(),
207
18
                Moo::new(left_elem),
208
18
                Moo::new(right_elem),
209
18
            ));
210
18
        }
211

            
212
9
        let new_expr = Expression::And(
213
9
            Metadata::new(),
214
9
            Moo::new(into_matrix_expr!(equality_constraints)),
215
9
        );
216

            
217
9
        Ok(Reduction::pure(new_expr))
218
    } else {
219
105660
        Err(RuleNotApplicable)
220
    }
221
105669
}
222

            
223
// dealing with equality where the left is a record variable, and the right is a constant record
224
#[register_rule(("Base", 2000))]
225
105669
fn record_to_const(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
226
105669
    if let Expr::Eq(_, left, right) = expr
227
5469
        && let Expr::Atomic(_, Atom::Reference(decl)) = &**left
228
2748
        && let Name::WithRepresentation(_, reprs) = &decl.name() as &Name
229
45
        && reprs.first().is_none_or(|x| x.as_str() == "record_to_atom")
230
    {
231
18
        let domain = decl
232
18
            .resolved_domain()
233
18
            .ok_or(ApplicationError::DomainError)?;
234

            
235
18
        let GroundDomain::Record(entries) = domain.as_ref() else {
236
            return Err(RuleNotApplicable);
237
        };
238

            
239
18
        let Some(rhs_record_names) = crate::utils::constant_record_names(right.as_ref()) else {
240
9
            return Err(RuleNotApplicable);
241
        };
242

            
243
9
        if entries.len() != rhs_record_names.len() {
244
            return Err(RuleNotApplicable);
245
9
        }
246

            
247
18
        for i in 0..entries.len() {
248
18
            if entries[i].name != rhs_record_names[i] {
249
                return Err(RuleNotApplicable);
250
18
            }
251
        }
252
9
        let mut equality_constraints = vec![];
253
18
        for i in 0..entries.len() {
254
18
            let left_elem = Expression::SafeIndex(
255
18
                Metadata::new(),
256
18
                Moo::clone(left),
257
18
                vec![Expression::Atomic(
258
18
                    Metadata::new(),
259
18
                    Atom::Literal(Literal::Int((i + 1) as i32)),
260
18
                )],
261
18
            );
262
18
            let right_elem = Expression::SafeIndex(
263
18
                Metadata::new(),
264
18
                Moo::clone(right),
265
18
                vec![Expression::Atomic(
266
18
                    Metadata::new(),
267
18
                    Atom::Literal(Literal::Int((i + 1) as i32)),
268
18
                )],
269
18
            );
270
18

            
271
18
            equality_constraints.push(Expression::Eq(
272
18
                Metadata::new(),
273
18
                Moo::new(left_elem),
274
18
                Moo::new(right_elem),
275
18
            ));
276
18
        }
277
9
        let new_expr = Expression::And(
278
9
            Metadata::new(),
279
9
            Moo::new(into_matrix_expr!(equality_constraints)),
280
9
        );
281
9
        Ok(Reduction::pure(new_expr))
282
    } else {
283
105651
        Err(RuleNotApplicable)
284
    }
285
105669
}