1use std::collections::BTreeSet;
2use std::fmt::{self, Display, Formatter};
3use std::hash::Hash;
4use std::rc::Rc;
56use thiserror::Error;
78use crate::ast::Declaration;
9use crate::ast::{Expression, Name, SubModel, SymbolTable};
10use tree_morph::Rule as MorphRule;
1112#[derive(Debug, Error)]
13pub enum ApplicationError {
14#[error("Rule is not applicable")]
15RuleNotApplicable,
1617#[error("Could not calculate the expression domain")]
18DomainError,
19}
2021/// Represents the result of applying a rule to an expression within a model.
22///
23/// A `Reduction` encapsulates the changes made to a model during a rule application.
24/// It includes a new expression to replace the original one, an optional top-level constraint
25/// to be added to the model, and any updates to the model's symbol table.
26///
27/// This struct allows for representing side-effects of rule applications, ensuring that
28/// all modifications, including symbol table expansions and additional constraints, are
29/// accounted for and can be applied to the model consistently.
30///
31/// # Fields
32/// - `new_expression`: The updated [`Expression`] that replaces the original one after applying the rule.
33/// - `new_top`: An additional top-level [`Vec<Expression>`] constraint that should be added to the model. If no top-level
34/// constraint is needed, this field can be set to an empty vector [`Vec::new()`].
35/// - `symbols`: A [`SymbolTable`] containing any new symbol definitions or modifications to be added to the model's
36/// symbol table. If no symbols are modified, this field can be set to an empty symbol table.
37///
38/// # Usage
39/// A `Reduction` can be created using one of the provided constructors:
40/// - [`Reduction::new`]: Creates a reduction with a new expression, top-level constraint, and symbol modifications.
41/// - [`Reduction::pure`]: Creates a reduction with only a new expression and no side-effects on the symbol table or constraints.
42/// - [`Reduction::with_symbols`]: Creates a reduction with a new expression and symbol table modifications, but no top-level constraint.
43/// - [`Reduction::with_top`]: Creates a reduction with a new expression and a top-level constraint, but no symbol table modifications.
44///
45/// The `apply` method allows for applying the changes represented by the `Reduction` to a [`Model`].
46///
47/// # Example
48/// ```
49/// // Need to add an example
50/// ```
51///
52/// # See Also
53/// - [`ApplicationResult`]: Represents the result of applying a rule, which may either be a `Reduction` or an `ApplicationError`.
54/// - [`Model`]: The structure to which the `Reduction` changes are applied.
55#[non_exhaustive]
56#[derive(Clone, Debug)]
57pub struct Reduction {
58pub new_expression: Expression,
59pub new_top: Vec<Expression>,
60pub symbols: SymbolTable,
61}
6263/// The result of applying a rule to an expression.
64/// Contains either a set of reduction instructions or an error.
65pub type ApplicationResult = Result<Reduction, ApplicationError>;
6667impl Reduction {
68pub fn new(new_expression: Expression, new_top: Vec<Expression>, symbols: SymbolTable) -> Self {
69Self {
70 new_expression,
71 new_top,
72 symbols,
73 }
74 }
7576/// Represents a reduction with no side effects on the model.
77pub fn pure(new_expression: Expression) -> Self {
78Self {
79 new_expression,
80 new_top: Vec::new(),
81 symbols: SymbolTable::new(),
82 }
83 }
8485/// Represents a reduction that also modifies the symbol table.
86pub fn with_symbols(new_expression: Expression, symbols: SymbolTable) -> Self {
87Self {
88 new_expression,
89 new_top: Vec::new(),
90 symbols,
91 }
92 }
9394/// Represents a reduction that also adds a top-level constraint to the model.
95pub fn with_top(new_expression: Expression, new_top: Vec<Expression>) -> Self {
96Self {
97 new_expression,
98 new_top,
99 symbols: SymbolTable::new(),
100 }
101 }
102103/// Applies side-effects (e.g. symbol table updates)
104pub fn apply(self, model: &mut SubModel) {
105 model.symbols_mut().extend(self.symbols); // Add new assignments to the symbol table
106model.add_constraints(self.new_top.clone());
107 }
108109/// Gets symbols added by this reduction
110pub fn added_symbols(&self, initial_symbols: &SymbolTable) -> BTreeSet<Name> {
111let initial_symbols_set: BTreeSet<Name> = initial_symbols
112 .clone()
113 .into_iter_local()
114 .map(|x| x.0)
115 .collect();
116let new_symbols_set: BTreeSet<Name> = self
117.symbols
118 .clone()
119 .into_iter_local()
120 .map(|x| x.0)
121 .collect();
122123 new_symbols_set
124 .difference(&initial_symbols_set)
125 .cloned()
126 .collect()
127 }
128129/// Gets symbols changed by this reduction
130 ///
131 /// Returns a list of tuples of (name, domain before reduction, domain after reduction)
132pub fn changed_symbols(
133&self,
134 initial_symbols: &SymbolTable,
135 ) -> Vec<(Name, Rc<Declaration>, Rc<Declaration>)> {
136let mut changes: Vec<(Name, Rc<Declaration>, Rc<Declaration>)> = vec![];
137138for (var_name, initial_value) in initial_symbols.clone().into_iter_local() {
139let Some(new_value) = self.symbols.lookup(&var_name) else {
140continue;
141 };
142143if new_value != initial_value {
144 changes.push((var_name.clone(), initial_value.clone(), new_value.clone()));
145 }
146 }
147 changes
148 }
149}
150151/// The function type used in a [`Rule`].
152pub type RuleFn = fn(&Expression, &SymbolTable) -> ApplicationResult;
153154/**
155 * A rule with a name, application function, and rule sets.
156 *
157 * # Fields
158 * - `name` The name of the rule.
159 * - `application` The function to apply the rule.
160 * - `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.
161 */
162#[derive(Clone, Debug)]
163pub struct Rule<'a> {
164pub name: &'a str,
165pub application: RuleFn,
166pub rule_sets: &'a [(&'a str, u16)], // (name, priority). At runtime, we add the rule to rulesets
167}
168169impl<'a> Rule<'a> {
170pub const fn new(
171 name: &'a str,
172 application: RuleFn,
173 rule_sets: &'a [(&'static str, u16)],
174 ) -> Self {
175Self {
176 name,
177 application,
178 rule_sets,
179 }
180 }
181182pub fn apply(&self, expr: &Expression, symbols: &SymbolTable) -> ApplicationResult {
183 (self.application)(expr, symbols)
184 }
185}
186187impl Display for Rule<'_> {
188fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
189write!(f, "{}", self.name)
190 }
191}
192193impl PartialEq for Rule<'_> {
194fn eq(&self, other: &Self) -> bool {
195self.name == other.name
196 }
197}
198199impl Eq for Rule<'_> {}
200201impl Hash for Rule<'_> {
202fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
203self.name.hash(state);
204 }
205}
206207impl MorphRule<Expression, SymbolTable> for Rule<'_> {
208fn apply(
209&self,
210 commands: &mut tree_morph::Commands<Expression, SymbolTable>,
211 subtree: &Expression,
212 meta: &SymbolTable,
213 ) -> Option<Expression> {
214let reduction = self.apply(subtree, meta).ok()?;
215 commands.mut_meta(Box::new(|m: &mut SymbolTable| m.extend(reduction.symbols)));
216 commands.transform(Box::new(|m| m.extend_root(reduction.new_top)));
217Some(reduction.new_expression)
218 }
219}
220221impl MorphRule<Expression, SymbolTable> for &Rule<'_> {
222fn apply(
223&self,
224 commands: &mut tree_morph::Commands<Expression, SymbolTable>,
225 subtree: &Expression,
226 meta: &SymbolTable,
227 ) -> Option<Expression> {
228let reduction = Rule::apply(self, subtree, meta).ok()?;
229 commands.mut_meta(Box::new(|m: &mut SymbolTable| m.extend(reduction.symbols)));
230if !reduction.new_top.is_empty() {
231 commands.transform(Box::new(|m| m.extend_root(reduction.new_top)));
232 }
233Some(reduction.new_expression)
234 }
235}