1
use std::collections::BTreeSet;
2
use std::fmt::{self, Debug, Display, Formatter};
3
use std::hash::Hash;
4
use std::sync::Arc;
5

            
6
use thiserror::Error;
7

            
8
use crate::Model;
9
use crate::ast::{
10
    DeclarationKind, DeclarationPtr, Expression, Metadata, Name, SatEncodingDecision, SymbolTable,
11
};
12

            
13
#[derive(Debug, Error)]
14
pub enum ApplicationError {
15
    #[error("Rule is not applicable")]
16
    RuleNotApplicable,
17

            
18
    #[error("Could not calculate the expression domain")]
19
    DomainError,
20
}
21

            
22
/// Represents the result of applying a rule to an expression within a model.
23
///
24
/// A `RuleEffect` encapsulates the changes made to a model during a rule application.
25
/// It includes a new expression to replace the original one, an optional top-level constraint
26
/// to be added to the model, and any updates to the model's symbol table.
27
///
28
/// This struct allows for representing side-effects of rule applications, ensuring that
29
/// all modifications, including symbol table expansions and additional constraints, are
30
/// accounted for and can be applied to the model consistently.
31
///
32
/// # Fields
33
/// - `new_expression`: The updated [`Expression`] that replaces the original one after applying the rule.
34
/// - `new_top`: An additional top-level [`Vec<Expression>`] constraint that should be added to the model. If no top-level
35
///   constraint is needed, this field can be set to an empty vector [`Vec::new()`].
36
/// - `symbols`: A [`SymbolTable`] containing any new symbol definitions or modifications to be added to the model's
37
///   symbol table. If no symbols are modified, this field can be set to an empty symbol table.
38
///
39
/// # Usage
40
/// A `RuleEffect` can be created using one of the provided constructors:
41
/// - [`RuleEffect::new`]: Creates an effect with a new expression, top-level constraint, and symbol modifications.
42
/// - [`RuleEffect::pure`]: Creates an effect with only a new expression and no side-effects on the symbol table or constraints.
43
/// - [`RuleEffect::with_symbols`]: Creates an effect with a new expression and symbol table modifications, but no top-level constraint.
44
/// - [`RuleEffect::with_top`]: Creates an effect with a new expression and a top-level constraint, but no symbol table modifications.
45
/// - [`RuleEffect::sat`]: Creates an effect with a new expression, SAT encoding decisions and symbol modifications, but no top-level constraints.
46
///
47
/// The `apply` method allows for applying the changes represented by the `RuleEffect` to a [`Model`].
48
///
49
/// # Example
50
/// ```
51
/// // Need to add an example
52
/// ```
53
///
54
/// # See Also
55
/// - [`ApplicationResult`]: Represents the result of applying a rule, which may either be a `RuleEffect` or an `ApplicationError`.
56
/// - [`Model`]: The structure to which the `RuleEffect` changes are applied.
57
#[non_exhaustive]
58
#[derive(Clone)]
59
pub struct RuleEffect {
60
    pub new_expression: Expression,
61
    pub new_top: Vec<Expression>,
62
    pub symbols: SymbolTable,
63
    pub new_sat_decisions: Vec<SatEncodingDecision>,
64
    /// Shared declarations to update if this effect is selected.
65
    pub(crate) declaration_updates: Vec<DeclarationUpdate>,
66
    materialise: Option<DeferredRuleEffect>,
67
    pub(crate) uniform_choices: crate::representation::uniform::Choices,
68
}
69

            
70
/// An in-place update to a shared declaration, applied only after its rule is selected.
71
#[derive(Clone, Debug)]
72
pub(crate) struct DeclarationUpdate {
73
    target: DeclarationPtr,
74
    replacement_kind: DeclarationKind,
75
}
76

            
77
impl DeclarationUpdate {
78
    /// Creates a deferred replacement for a shared declaration's kind.
79
10644
    fn new(target: DeclarationPtr, replacement_kind: DeclarationKind) -> Self {
80
10644
        Self {
81
10644
            target,
82
10644
            replacement_kind,
83
10644
        }
84
10644
    }
85

            
86
    /// Returns the name of the declaration that will be updated.
87
10992
    pub(crate) fn name(&self) -> Name {
88
10992
        self.target.name().clone()
89
10992
    }
90

            
91
    /// Commits the replacement while retaining the declaration's identity.
92
10528
    pub(crate) fn apply(mut self) {
93
10528
        let _ = self.target.replace_kind(self.replacement_kind);
94
10528
    }
95

            
96
    /// Creates an unshared declaration containing the pending replacement.
97
378
    fn preview(&self) -> DeclarationPtr {
98
378
        DeclarationPtr::new(self.name(), self.replacement_kind.clone())
99
378
    }
100
}
101

            
102
/// Deferred constructor for a concrete rule effect.
103
type DeferredRuleEffect = Arc<dyn Fn(&SymbolTable) -> RuleEffect + Send + Sync>;
104

            
105
/// The result of applying a rule to an expression.
106
/// Contains either a set of rule effects or an error.
107
pub type ApplicationResult = Result<RuleEffect, ApplicationError>;
108

            
109
impl Debug for RuleEffect {
110
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
111
        f.debug_struct("RuleEffect")
112
            .field("new_expression", &self.new_expression)
113
            .field("new_top", &self.new_top)
114
            .field("symbols", &self.symbols)
115
            .field("new_sat_decisions", &self.new_sat_decisions)
116
            .field("declaration_updates", &self.declaration_updates)
117
            .field("is_deferred", &self.materialise.is_some())
118
            .finish()
119
    }
120
}
121

            
122
impl RuleEffect {
123
19524098
    pub fn new(new_expression: Expression, new_top: Vec<Expression>, symbols: SymbolTable) -> Self {
124
19524098
        Self {
125
19524098
            new_expression,
126
19524098
            new_top,
127
19524098
            symbols,
128
19524098
            new_sat_decisions: Vec::new(),
129
19524098
            declaration_updates: Vec::new(),
130
19524098
            materialise: None,
131
19524098
            uniform_choices: Default::default(),
132
19524098
        }
133
19524098
    }
134

            
135
    /// Represents an effect with no side effects on the model.
136
31405543
    pub fn pure(new_expression: Expression) -> Self {
137
31405543
        Self {
138
31405543
            new_expression,
139
31405543
            new_top: Vec::new(),
140
31405543
            symbols: SymbolTable::new(),
141
31405543
            new_sat_decisions: Vec::new(),
142
31405543
            declaration_updates: Vec::new(),
143
31405543
            materialise: None,
144
31405543
            uniform_choices: Default::default(),
145
31405543
        }
146
31405543
    }
147

            
148
    /// Represents an effect that also modifies the symbol table.
149
286127
    pub fn with_symbols(new_expression: Expression, symbols: SymbolTable) -> Self {
150
286127
        Self {
151
286127
            new_expression,
152
286127
            new_top: Vec::new(),
153
286127
            symbols,
154
286127
            new_sat_decisions: Vec::new(),
155
286127
            declaration_updates: Vec::new(),
156
286127
            materialise: None,
157
286127
            uniform_choices: Default::default(),
158
286127
        }
159
286127
    }
160

            
161
    /// Represents an effect that also adds a top-level constraint to the model.
162
    pub fn with_top(new_expression: Expression, new_top: Vec<Expression>) -> Self {
163
        Self {
164
            new_expression,
165
            new_top,
166
            symbols: SymbolTable::new(),
167
            new_sat_decisions: Vec::new(),
168
            declaration_updates: Vec::new(),
169
            materialise: None,
170
            uniform_choices: Default::default(),
171
        }
172
    }
173

            
174
    /// Represents an effect that adds semantic SAT encoding decisions.
175
26243833
    pub fn sat(
176
26243833
        new_expression: Expression,
177
26243833
        new_sat_decisions: Vec<SatEncodingDecision>,
178
26243833
        symbols: SymbolTable,
179
26243833
    ) -> Self {
180
26243833
        Self {
181
26243833
            new_expression,
182
26243833
            new_top: Vec::new(),
183
26243833
            symbols,
184
26243833
            new_sat_decisions,
185
26243833
            declaration_updates: Vec::new(),
186
26243833
            materialise: None,
187
26243833
            uniform_choices: Default::default(),
188
26243833
        }
189
26243833
    }
190

            
191
    /// Defers constructing a concrete effect until the rewriter chooses to apply this rule.
192
    ///
193
    /// This is intended for rule effects that allocate fresh names or otherwise depend on global
194
    /// model state. Applicability checks can return a deferred effect without consuming those
195
    /// effects; the rewriter calls [`RuleEffect::materialise`] only for the selected rule.
196
15515
    pub fn deferred(
197
15515
        materialise: impl Fn(&SymbolTable) -> RuleEffect + Send + Sync + 'static,
198
15515
    ) -> Self {
199
15515
        Self {
200
15515
            new_expression: Expression::Root(Metadata::new(), Vec::new()),
201
15515
            new_top: Vec::new(),
202
15515
            symbols: SymbolTable::new(),
203
15515
            new_sat_decisions: Vec::new(),
204
15515
            declaration_updates: Vec::new(),
205
15515
            materialise: Some(Arc::new(materialise)),
206
15515
            uniform_choices: Default::default(),
207
15515
        }
208
15515
    }
209

            
210
    /// Returns the concrete effect for the current symbol table.
211
    ///
212
    /// This consumes the selected effect: cloning a concrete effect can duplicate its expression,
213
    /// top-level constraints, SAT encoding decisions, and speculative symbol table. Before returning, the symbol
214
    /// snapshot is reduced to the bindings that the effect actually changes.
215
64768957
    pub fn materialise(mut self, symbols: &SymbolTable) -> Self {
216
64768957
        crate::representation::uniform::commit(std::mem::take(&mut self.uniform_choices));
217
64768957
        if let Some(materialise) = self.materialise.take() {
218
64135
            return materialise(symbols).materialise(symbols);
219
64704822
        }
220

            
221
64704822
        self.symbols.retain_local_changes_from(symbols);
222
64704822
        self
223
64768957
    }
224

            
225
5686
    pub(crate) fn is_deferred(&self) -> bool {
226
5686
        self.materialise.is_some()
227
5686
    }
228

            
229
    /// Adds declaration replacements that are committed only if this effect is selected.
230
2035
    pub fn with_declaration_updates(
231
2035
        mut self,
232
2035
        updates: impl IntoIterator<Item = (DeclarationPtr, DeclarationKind)>,
233
2035
    ) -> Self {
234
2035
        self.declaration_updates.extend(
235
2035
            updates
236
2035
                .into_iter()
237
2546
                .map(|(target, kind)| DeclarationUpdate::new(target, kind)),
238
        );
239
2035
        self
240
2035
    }
241

            
242
    /// Iterates over the names changed by deferred declaration updates.
243
64704909
    pub fn updated_declaration_names(&self) -> impl Iterator<Item = Name> + '_ {
244
64704909
        self.declaration_updates.iter().map(DeclarationUpdate::name)
245
64704909
    }
246

            
247
    /// Applies pending declaration replacements to a detached symbol-table preview.
248
41006
    pub(crate) fn preview_declaration_updates(&self, symbols: &mut SymbolTable) {
249
41006
        for update in &self.declaration_updates {
250
378
            symbols.update_insert(update.preview());
251
        }
252
41006
    }
253

            
254
    /// Applies side-effects (e.g. symbol table updates)
255
1
    pub fn apply(mut self, model: &mut Model) {
256
1
        crate::representation::uniform::commit(std::mem::take(&mut self.uniform_choices));
257
        debug_assert!(
258
1
            self.materialise.is_none(),
259
            "deferred rule effects must be materialised before being applied"
260
        );
261
1
        for update in self.declaration_updates {
262
1
            update.apply();
263
        }
264
1
        model.symbols_mut().extend(self.symbols); // Add new assignments to the symbol table
265
1
        model.add_constraints(self.new_top);
266
1
        model.add_sat_decisions(self.new_sat_decisions);
267
1
    }
268

            
269
    /// Gets symbols added by this effect.
270
    ///
271
    /// Walks this effect's own symbols rather than diffing two whole tables. The rewriter asks
272
    /// this once per applied rule, and most effects are pure, so diffing made every rewrite cost
273
    /// a clone and a sort of the entire model symbol table.
274
65466075
    pub fn added_symbols(&self, initial_symbols: &SymbolTable) -> BTreeSet<Name> {
275
65466075
        self.symbols
276
65466075
            .iter_local()
277
623611290
            .filter(|(name, _)| initial_symbols.lookup_local(name).is_none())
278
623611290
            .map(|(name, _)| name.clone())
279
            .collect()
280
65466075
    }
281

            
282
    /// Gets symbols changed by this effect.
283
    ///
284
    /// Returns a list of tuples of (name, domain before effect, domain after effect), ordered by
285
    /// name.
286
    ///
287
    /// Walks this effect's symbols for the same reason as [`RuleEffect::added_symbols`]: a symbol
288
    /// can only have changed if this effect carries its new value.
289
64704822
    pub fn changed_symbols(
290
64704822
        &self,
291
64704822
        initial_symbols: &SymbolTable,
292
64704822
    ) -> Vec<(Name, DeclarationPtr, DeclarationPtr)> {
293
64704822
        let mut changes: Vec<(Name, DeclarationPtr, DeclarationPtr)> = self
294
64704822
            .symbols
295
64704822
            .iter_local()
296
623464612
            .filter_map(|(name, new_value)| {
297
623464612
                let initial_value = initial_symbols.lookup_local(name)?;
298
                (!new_value.content_eq(&initial_value))
299
                    .then(|| (name.clone(), initial_value.clone(), new_value.clone()))
300
623464612
            })
301
            .collect();
302
64704822
        changes.sort_by(|(lhs, ..), (rhs, ..)| lhs.cmp(rhs));
303
64704822
        changes
304
64704822
    }
305
}
306

            
307
/// The function type used in a [`Rule`].
308
pub type RuleFn = fn(&Expression, &SymbolTable) -> ApplicationResult;
309

            
310
/// Atomic expression subvariants that can be used as rule prefilters.
311
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
312
pub enum AtomKind {
313
    /// An `Atomic` expression containing a literal.
314
    Literal,
315
    /// An `Atomic` expression containing a reference.
316
    Reference,
317
}
318

            
319
/// A complete rule prefilter alternative.
320
///
321
/// A rule matches when any of its prefilter alternatives matches the focused expression. This keeps
322
/// compound filters such as `And / Comprehension` paired instead of forming a cross product with
323
/// other alternatives in the same rule declaration.
324
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
325
pub enum RulePrefilter {
326
    /// Focused expression must have this `Expression` variant.
327
    Variant(usize),
328
    /// Focused expression must have an immediate child with this `Expression` variant.
329
    Child { child: usize },
330
    /// Focused expression must have this variant and an immediate child with `child`'s variant.
331
    VariantChild { variant: usize, child: usize },
332
    /// Focused expression must be an `Atomic` expression with this atomic subvariant.
333
    Atom(AtomKind),
334
    /// Focused expression must have an immediate `Atomic` child with this atomic subvariant.
335
    ChildAtom(AtomKind),
336
}
337

            
338
/// State changes that can invalidate a failed rule application.
339
///
340
/// Most rules may become applicable when either their focused expression or the symbol table
341
/// changes. A small number of Root rules use the expression only as an entry point and decide
342
/// applicability entirely from declarations. Remembering their failed applications until the
343
/// symbol table changes avoids repeatedly scanning every declaration after unrelated expression
344
/// rewrites.
345
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
346
pub enum RuleFailureInvalidation {
347
    /// Reconsider a failed application after any relevant expression or symbol-table change.
348
    #[default]
349
    ExpressionOrSymbols,
350
    /// Reconsider a failed application only after the symbol table changes.
351
    SymbolsOnly,
352
}
353

            
354
/**
355
 * A rule with a name, application function, and rule sets.
356
 *
357
 * # Fields
358
 * - `name` The name of the rule.
359
 * - `application` The function to apply the rule.
360
 * - `rule_sets` A list of rule set names and priorities that this rule is a part of. This is used to populate rulesets at runtime.
361
 */
362
#[derive(Clone, Debug)]
363
pub struct Rule<'a> {
364
    pub name: &'a str,
365
    pub application: RuleFn,
366
    pub rule_sets: &'a [(&'a str, u16)], // (name, priority). At runtime, we add the rule to rulesets
367
    /// Complete prefilter alternatives this rule applies to, or `None` for universal rules.
368
    pub prefilters: Option<&'static [RulePrefilter]>,
369
    /// Which state changes can make a failed application become applicable.
370
    pub failure_invalidation: RuleFailureInvalidation,
371
}
372

            
373
impl<'a> Rule<'a> {
374
    pub const fn new(
375
        name: &'a str,
376
        application: RuleFn,
377
        rule_sets: &'a [(&'static str, u16)],
378
    ) -> Self {
379
        Self {
380
            name,
381
            application,
382
            rule_sets,
383
            prefilters: None,
384
            failure_invalidation: RuleFailureInvalidation::ExpressionOrSymbols,
385
        }
386
    }
387

            
388
2686283788
    pub fn apply(&self, expr: &Expression, symbols: &SymbolTable) -> ApplicationResult {
389
2686283788
        crate::representation::uniform::attempt(|| (self.application)(expr, symbols))
390
2686283788
    }
391
}
392

            
393
impl Display for Rule<'_> {
394
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
395
        write!(f, "{}", self.name)
396
    }
397
}
398

            
399
impl PartialEq for Rule<'_> {
400
371849
    fn eq(&self, other: &Self) -> bool {
401
371849
        self.name == other.name
402
371849
    }
403
}
404

            
405
impl Eq for Rule<'_> {}
406

            
407
impl Hash for Rule<'_> {
408
11719097
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
409
11719097
        self.name.hash(state);
410
11719097
    }
411
}
412

            
413
#[cfg(test)]
414
mod tests {
415
    use super::*;
416
    use crate::ast::{DecisionVariable, Domain, Range, Reference};
417
    use crate::rule_engine::rewriter_common::{
418
        snapshot_symbols_after_effect, snapshot_variable_declarations,
419
    };
420

            
421
    #[test]
422
1
    fn declaration_updates_are_applied_only_when_effect_is_committed() {
423
1
        let old_domain = Domain::int(vec![Range::Bounded(1, 3)]);
424
1
        let new_domain = Domain::int(vec![Range::Bounded(1, 2)]);
425
1
        let mut model = Model::new(Default::default());
426
1
        let declaration = DeclarationPtr::new_find(Name::user("x"), old_domain.clone());
427
1
        let reference = Reference::new(declaration.clone());
428
1
        model.symbols_mut().insert(declaration.clone()).unwrap();
429

            
430
1
        let effect = RuleEffect::pure(Expression::from(1)).with_declaration_updates([(
431
1
            declaration,
432
1
            DeclarationKind::Find(DecisionVariable::new(new_domain.clone())),
433
1
        )]);
434

            
435
1
        let before = snapshot_variable_declarations(&model.symbols());
436
1
        let after = snapshot_symbols_after_effect(&model.symbols(), &effect);
437
1
        assert_eq!(before.get(&Name::user("x")).unwrap(), "find x: int(1..3)");
438
1
        assert_eq!(after.get(&Name::user("x")).unwrap(), "find x: int(1..2)");
439
1
        assert_eq!(reference.domain(), Some(old_domain));
440
1
        effect.apply(&mut model);
441
1
        assert_eq!(reference.domain(), Some(new_domain));
442
1
    }
443
}