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, [SafeIndex])]
21
168892
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
168892
    if let Expr::SafeIndex(_, subject, indices) = expr
25
8666
        && let Expr::Atomic(_, Atom::Reference(decl)) = &**subject
26
8096
        && let Name::WithRepresentation(name, reprs) = &decl.name() as &Name
27
    {
28
264
        if reprs.first().is_none_or(|x| x.as_str() != "record_to_atom") {
29
168
            return Err(RuleNotApplicable);
30
96
        }
31

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

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

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

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

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

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

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

            
68
96
        Ok(Reduction::pure(subject))
69
    } else {
70
168628
        Err(RuleNotApplicable)
71
    }
72
168892
}
73

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
217
12
        Ok(Reduction::pure(new_expr))
218
    } else {
219
168880
        Err(RuleNotApplicable)
220
    }
221
168892
}
222

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

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

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

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

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

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