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
72216
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
72216
    if let Expr::SafeIndex(_, subject, indices) = expr
25
4848
        && let Expr::Atomic(_, Atom::Reference(decl)) = &**subject
26
4506
        && let Name::WithRepresentation(name, reprs) = &decl.name() as &Name
27
    {
28
132
        if reprs.first().is_none_or(|x| x.as_str() != "record_to_atom") {
29
84
            return Err(RuleNotApplicable);
30
48
        }
31

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

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

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

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

            
52
48
        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
48
        let Some(index) = index.into_literal() else {
57
            return Err(RuleNotApplicable); // we don't support non-literal indices
58
        };
59

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

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

            
68
48
        Ok(Reduction::pure(subject))
69
    } else {
70
72084
        Err(RuleNotApplicable)
71
    }
72
72216
}
73

            
74
#[register_rule(("Bubble", 8000))]
75
621785
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
621785
    if let Expr::UnsafeIndex(_, subject, indices) = expr
79
11454
        && let Expr::Atomic(_, Atom::Reference(decl)) = &**subject
80
11430
        && let Name::WithRepresentation(_, reprs) = &decl.name() as &Name
81
    {
82
9438
        if reprs.first().is_none_or(|x| x.as_str() != "record_to_atom") {
83
9426
            return Err(RuleNotApplicable);
84
12
        }
85

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

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

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

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

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

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

            
110
        // find what numerical index in elems matches the entry name
111
18
        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
12
        let idx = Expr::Atomic(Metadata::new(), Atom::Literal(Literal::Int(idx as i32 + 1)));
117

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

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

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

            
156
// dealing with equality over 2 record variables
157
#[register_rule(("Base", 2000))]
158
72216
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
72216
    if let Expr::Eq(_, left, right) = expr
164
3688
        && let Expr::Atomic(_, Atom::Reference(decl)) = &**left
165
1870
        && let Name::WithRepresentation(_, reprs) = &decl.name() as &Name
166
30
        && let Expr::Atomic(_, Atom::Reference(decl2)) = &**right
167
12
        && let Name::WithRepresentation(_, reprs2) = &decl2.name() as &Name
168

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

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

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

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

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

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

            
217
6
        Ok(Reduction::pure(new_expr))
218
    } else {
219
72210
        Err(RuleNotApplicable)
220
    }
221
72216
}
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
72216
fn record_to_const(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
226
72216
    if let Expr::Eq(_, left, right) = expr
227
3688
        && let Expr::Atomic(_, Atom::Reference(decl)) = &**left
228
1870
        && let Name::WithRepresentation(_, reprs) = &decl.name() as &Name
229
30
        && reprs.first().is_none_or(|x| x.as_str() == "record_to_atom")
230
    {
231
12
        let domain = decl
232
12
            .resolved_domain()
233
12
            .ok_or(ApplicationError::DomainError)?;
234

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

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

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

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

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