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

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

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

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

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

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

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

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

            
68
24
        Ok(Reduction::pure(subject))
69
    } else {
70
24770
        Err(RuleNotApplicable)
71
    }
72
24944
}
73

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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