mathhook_core/pattern/matching/
patterns.rs1use crate::core::Expression;
7
8#[derive(Debug, Clone)]
12pub struct WildcardConstraints {
13 pub exclude: Vec<Expression>,
15
16 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 pub fn with_exclude(exclude: Vec<Expression>) -> Self {
30 Self {
31 exclude,
32 properties: Vec::new(),
33 }
34 }
35
36 pub fn with_properties(properties: Vec<fn(&Expression) -> bool>) -> Self {
38 Self {
39 exclude: Vec::new(),
40 properties,
41 }
42 }
43
44 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
68fn 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#[derive(Debug, Clone, PartialEq)]
94pub enum Pattern {
95 Wildcard {
97 name: String,
98 constraints: Option<WildcardConstraints>,
99 },
100
101 Exact(Expression),
103
104 Add(Vec<Pattern>),
106
107 Mul(Vec<Pattern>),
109
110 Pow(Box<Pattern>, Box<Pattern>),
112
113 Function { name: String, args: Vec<Pattern> },
115}
116
117impl Pattern {
118 pub fn wildcard(name: impl Into<String>) -> Self {
120 Pattern::Wildcard {
121 name: name.into(),
122 constraints: None,
123 }
124 }
125
126 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 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}