1
use conjure_cp::ast::Expression as Expr;
2
use conjure_cp::ast::Moo;
3
use conjure_cp::ast::SymbolTable;
4
use conjure_cp::ast::{AbstractLiteral, GroundDomain};
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
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
    if let Expr::SafeIndex(_, subject, indices) = expr
25
        && let Expr::Atomic(_, Atom::Reference(decl)) = &**subject
26
        && let Name::WithRepresentation(name, reprs) = &decl.name() as &Name
27
    {
28
        if reprs.first().is_none_or(|x| x.as_str() != "record_to_atom") {
29
            return Err(RuleNotApplicable);
30
        }
31

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
239
        let Expr::AbstractLiteral(_, AbstractLiteral::Record(entries2)) = &**right else {
240
            return Err(RuleNotApplicable);
241
        };
242

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

            
267
            equality_constraints.push(Expression::Eq(
268
                Metadata::new(),
269
                Moo::new(left_elem),
270
                Moo::new(right_elem),
271
            ));
272
        }
273
        let new_expr = Expression::And(
274
            Metadata::new(),
275
            Moo::new(into_matrix_expr!(equality_constraints)),
276
        );
277
        Ok(Reduction::pure(new_expr))
278
    } else {
279
        Err(RuleNotApplicable)
280
    }
281
}