1
use std::fmt::{self, Display, Formatter};
2
use std::hash::Hash;
3

            
4
use thiserror::Error;
5

            
6
use crate::ast::{Expression, SymbolTable};
7
use crate::model::Model;
8

            
9
#[derive(Debug, Error)]
10
pub enum ApplicationError {
11
    #[error("Rule is not applicable")]
12
    RuleNotApplicable,
13

            
14
    #[error("Could not calculate the expression domain")]
15
    DomainError,
16
}
17

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

            
60
/// The result of applying a rule to an expression.
61
/// Contains either a set of reduction instructions or an error.
62
pub type ApplicationResult = Result<Reduction, ApplicationError>;
63

            
64
impl Reduction {
65
    pub fn new(new_expression: Expression, new_top: Vec<Expression>, symbols: SymbolTable) -> Self {
66
        Self {
67
            new_expression,
68
            new_top,
69
            symbols,
70
        }
71
    }
72

            
73
    /// Represents a reduction with no side effects on the model.
74
18
    pub fn pure(new_expression: Expression) -> Self {
75
18
        Self {
76
18
            new_expression,
77
18
            new_top: vec![],
78
18
            symbols: SymbolTable::new(),
79
18
        }
80
18
    }
81

            
82
    /// Represents a reduction that also modifies the symbol table.
83
    pub fn with_symbols(new_expression: Expression, symbols: SymbolTable) -> Self {
84
        Self {
85
            new_expression,
86
            new_top: vec![],
87
            symbols,
88
        }
89
    }
90

            
91
    /// Represents a reduction that also adds a top-level constraint to the model.
92
    pub fn with_top(new_expression: Expression, new_top: Vec<Expression>) -> Self {
93
        Self {
94
            new_expression,
95
            new_top,
96
            symbols: SymbolTable::new(),
97
        }
98
    }
99

            
100
    // Apply side-effects (e.g. symbol table updates)
101
9
    pub fn apply(self, model: &mut Model, constraint_idx: usize) {
102
9
        model.variables.extend(self.symbols); // Add new assignments to the symbol table
103
9
        model.constraints.extend(self.new_top.clone());
104
9
        model.constraints[constraint_idx] = self.new_expression.clone();
105
9
    }
106
}
107

            
108
/**
109
 * A rule with a name, application function, and rule sets.
110
 *
111
 * # Fields
112
 * - `name` The name of the rule.
113
 * - `application` The function to apply the rule.
114
 * - `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.
115
 */
116
#[derive(Clone, Debug)]
117
pub struct Rule<'a> {
118
    pub name: &'a str,
119
    pub application: fn(&Expression, &Model) -> ApplicationResult,
120
    pub rule_sets: &'a [(&'a str, u16)], // (name, priority). At runtime, we add the rule to rulesets
121
}
122

            
123
impl<'a> Rule<'a> {
124
    pub const fn new(
125
        name: &'a str,
126
        application: fn(&Expression, &Model) -> ApplicationResult,
127
        rule_sets: &'a [(&'static str, u16)],
128
    ) -> Self {
129
        Self {
130
            name,
131
            application,
132
            rule_sets,
133
        }
134
    }
135

            
136
720
    pub fn apply(&self, expr: &Expression, mdl: &Model) -> ApplicationResult {
137
720
        (self.application)(expr, mdl)
138
720
    }
139
}
140

            
141
impl Display for Rule<'_> {
142
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
143
        write!(f, "{}", self.name)
144
    }
145
}
146

            
147
impl PartialEq for Rule<'_> {
148
13023
    fn eq(&self, other: &Self) -> bool {
149
13023
        self.name == other.name
150
13023
    }
151
}
152

            
153
impl Eq for Rule<'_> {}
154

            
155
impl Hash for Rule<'_> {
156
19602
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
157
19602
        self.name.hash(state);
158
19602
    }
159
}