mathhook_core/pattern/matching/
patterns.rs

1//! Pattern types and constraint definitions
2//!
3//! Provides the core pattern types for structural matching, including
4//! wildcards with constraints and exact matching patterns.
5
6use crate::core::Expression;
7
8/// Constraints for wildcard pattern matching
9///
10/// Provides fine-grained control over what expressions a wildcard can match.
11#[derive(Debug, Clone)]
12pub struct WildcardConstraints {
13    /// Expressions that cannot be matched (e.g., specific variables to exclude)
14    pub exclude: Vec<Expression>,
15
16    /// Predicates that must return true for a match to succeed
17    /// Common examples: is_integer, is_positive, is_polynomial_in(x)
18    pub properties: Vec<fn(&Expression) -> bool>,
19}
20
21impl PartialEq for WildcardConstraints {
22    fn eq(&self, other: &Self) -> bool {
23        self.exclude == other.exclude && self.properties.len() == other.properties.len()
24    }
25}
26
27impl WildcardConstraints {
28    /// Create constraints with excluded expressions
29    pub fn with_exclude(exclude: Vec<Expression>) -> Self {
30        Self {
31            exclude,
32            properties: Vec::new(),
33        }
34    }
35
36    /// Create constraints with property predicates
37    pub fn with_properties(properties: Vec<fn(&Expression) -> bool>) -> Self {
38        Self {
39            exclude: Vec::new(),
40            properties,
41        }
42    }
43
44    /// Check if an expression satisfies all constraints
45    pub fn is_satisfied_by(&self, expr: &Expression) -> bool {
46        for excluded in &self.exclude {
47            if expr == excluded {
48                return false;
49            }
50        }
51
52        for excluded in &self.exclude {
53            if contains_subexpression(expr, excluded) {
54                return false;
55            }
56        }
57
58        for property in &self.properties {
59            if !property(expr) {
60                return false;
61            }
62        }
63
64        true
65    }
66}
67
68/// Check if an expression contains a subexpression
69fn contains_subexpression(expr: &Expression, sub: &Expression) -> bool {
70    if expr == sub {
71        return true;
72    }
73
74    match expr {
75        Expression::Add(terms) | Expression::Mul(terms) | Expression::Set(terms) => {
76            terms.iter().any(|t| contains_subexpression(t, sub))
77        }
78        Expression::Pow(base, exp) => {
79            contains_subexpression(base, sub) || contains_subexpression(exp, sub)
80        }
81        Expression::Function { args, .. } => args.iter().any(|a| contains_subexpression(a, sub)),
82        Expression::Complex(data) => {
83            contains_subexpression(&data.real, sub) || contains_subexpression(&data.imag, sub)
84        }
85        _ => false,
86    }
87}
88
89/// A pattern that can match against expressions
90///
91/// Patterns support wildcards and structural matching to enable
92/// transformation rules and equation manipulation.
93#[derive(Debug, Clone, PartialEq)]
94pub enum Pattern {
95    /// Match any expression and bind to a name, optionally with constraints
96    Wildcard {
97        name: String,
98        constraints: Option<WildcardConstraints>,
99    },
100
101    /// Match a specific expression exactly
102    Exact(Expression),
103
104    /// Match addition with pattern terms (supports commutative matching)
105    Add(Vec<Pattern>),
106
107    /// Match multiplication with pattern factors (supports commutative matching)
108    Mul(Vec<Pattern>),
109
110    /// Match power with pattern base and exponent
111    Pow(Box<Pattern>, Box<Pattern>),
112
113    /// Match function call with pattern arguments
114    Function { name: String, args: Vec<Pattern> },
115}
116
117impl Pattern {
118    /// Create a simple wildcard pattern without constraints
119    pub fn wildcard(name: impl Into<String>) -> Self {
120        Pattern::Wildcard {
121            name: name.into(),
122            constraints: None,
123        }
124    }
125
126    /// Create a wildcard pattern with exclude constraints
127    pub fn wildcard_excluding(name: impl Into<String>, exclude: Vec<Expression>) -> Self {
128        Pattern::Wildcard {
129            name: name.into(),
130            constraints: Some(WildcardConstraints::with_exclude(exclude)),
131        }
132    }
133
134    /// Create a wildcard pattern with property constraints
135    pub fn wildcard_with_properties(
136        name: impl Into<String>,
137        properties: Vec<fn(&Expression) -> bool>,
138    ) -> Self {
139        Pattern::Wildcard {
140            name: name.into(),
141            constraints: Some(WildcardConstraints::with_properties(properties)),
142        }
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use crate::prelude::*;
150
151    #[test]
152    fn test_wildcard_constraints_exclude() {
153        let x = symbol!(x);
154        let constraints = WildcardConstraints::with_exclude(vec![Expression::symbol(x.clone())]);
155
156        assert!(!constraints.is_satisfied_by(&Expression::symbol(x.clone())));
157        assert!(constraints.is_satisfied_by(&Expression::integer(42)));
158    }
159
160    #[test]
161    fn test_wildcard_constraints_exclude_subexpression() {
162        let x = symbol!(x);
163        let constraints = WildcardConstraints::with_exclude(vec![Expression::symbol(x.clone())]);
164
165        let expr_with_x =
166            Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(1)]);
167        assert!(!constraints.is_satisfied_by(&expr_with_x));
168    }
169
170    #[test]
171    fn test_wildcard_constraints_properties() {
172        fn is_integer(expr: &Expression) -> bool {
173            matches!(expr, Expression::Number(_))
174        }
175
176        let constraints = WildcardConstraints::with_properties(vec![is_integer]);
177
178        assert!(constraints.is_satisfied_by(&Expression::integer(42)));
179
180        let x = symbol!(x);
181        assert!(!constraints.is_satisfied_by(&Expression::symbol(x)));
182    }
183
184    #[test]
185    fn test_pattern_wildcard_construction() {
186        let pattern = Pattern::wildcard("x");
187        match pattern {
188            Pattern::Wildcard { name, constraints } => {
189                assert_eq!(name.as_str(), "x");
190                assert!(constraints.is_none());
191            }
192            _ => panic!("Expected Wildcard pattern"),
193        }
194    }
195
196    #[test]
197    fn test_pattern_wildcard_excluding_construction() {
198        let x = symbol!(x);
199        let pattern = Pattern::wildcard_excluding("a", vec![Expression::symbol(x.clone())]);
200
201        match pattern {
202            Pattern::Wildcard { name, constraints } => {
203                assert_eq!(name.as_str(), "a");
204                assert!(constraints.is_some());
205            }
206            _ => panic!("Expected Wildcard pattern"),
207        }
208    }
209
210    #[test]
211    fn test_pattern_wildcard_with_properties_construction() {
212        fn is_integer(expr: &Expression) -> bool {
213            matches!(expr, Expression::Number(_))
214        }
215
216        let pattern = Pattern::wildcard_with_properties("n", vec![is_integer]);
217
218        match pattern {
219            Pattern::Wildcard { name, constraints } => {
220                assert_eq!(name.as_str(), "n");
221                assert!(constraints.is_some());
222            }
223            _ => panic!("Expected Wildcard pattern"),
224        }
225    }
226
227    #[test]
228    fn test_contains_subexpression_direct() {
229        let x = symbol!(x);
230        let expr = Expression::symbol(x.clone());
231        assert!(contains_subexpression(&expr, &expr));
232    }
233
234    #[test]
235    fn test_contains_subexpression_in_add() {
236        let x = symbol!(x);
237        let expr = Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(1)]);
238        assert!(contains_subexpression(
239            &expr,
240            &Expression::symbol(x.clone())
241        ));
242        assert!(contains_subexpression(&expr, &Expression::integer(1)));
243    }
244
245    #[test]
246    fn test_contains_subexpression_in_mul() {
247        let x = symbol!(x);
248        let expr = Expression::mul(vec![Expression::integer(2), Expression::symbol(x.clone())]);
249        assert!(contains_subexpression(
250            &expr,
251            &Expression::symbol(x.clone())
252        ));
253    }
254
255    #[test]
256    fn test_contains_subexpression_in_pow() {
257        let x = symbol!(x);
258        let expr = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
259        assert!(contains_subexpression(
260            &expr,
261            &Expression::symbol(x.clone())
262        ));
263        assert!(contains_subexpression(&expr, &Expression::integer(2)));
264    }
265
266    #[test]
267    fn test_contains_subexpression_in_function() {
268        let x = symbol!(x);
269        let expr = Expression::function("sin", vec![Expression::symbol(x.clone())]);
270        assert!(contains_subexpression(
271            &expr,
272            &Expression::symbol(x.clone())
273        ));
274    }
275
276    #[test]
277    fn test_contains_subexpression_not_found() {
278        let x = symbol!(x);
279        let y = symbol!(y);
280        let expr = Expression::symbol(x);
281        assert!(!contains_subexpression(&expr, &Expression::symbol(y)));
282    }
283}