Skip to main content

rust_rule_engine/rete/
pattern.rs

1//! Pattern Matching with Variable Binding (P3 Feature - Advanced)
2//!
3//! This module implements Drools-style pattern matching with:
4//! - Variable binding ($var)
5//! - Multi-object patterns
6//! - Join conditions between patterns
7//! - Field constraints with variables
8
9use super::facts::{FactValue, TypedFacts};
10use super::multifield::MultifieldOp;
11use super::working_memory::{FactHandle, WorkingMemory};
12use std::collections::HashMap;
13
14/// Variable name (e.g., "$name", "$age")
15pub type Variable = String;
16
17/// Pattern constraint with optional variable binding
18#[derive(Debug, Clone)]
19pub enum PatternConstraint {
20    /// Simple constraint: field op value
21    Simple {
22        field: String,
23        operator: String,
24        value: FactValue,
25    },
26    /// Binding constraint: field = $var (binds value to variable)
27    Binding { field: String, variable: Variable },
28    /// Variable constraint: field op $var (compare with bound variable)
29    Variable {
30        field: String,
31        operator: String,
32        variable: Variable,
33    },
34    /// Multi-field constraint: pattern matching on arrays/collections
35    ///
36    /// Examples:
37    /// - `Order.items $?all_items` - Collect all items (Collect)
38    /// - `Product.tags contains "electronics"` - Check containment (Contains)
39    /// - `Order.items count > 0` - Get array length (Count)
40    MultiField {
41        field: String,
42        variable: Option<Variable>, // $?var for multi-field binding
43        operator: MultifieldOp,
44        value: Option<FactValue>, // For operations like Contains
45    },
46}
47
48impl PatternConstraint {
49    /// Create simple constraint
50    pub fn simple(field: String, operator: String, value: FactValue) -> Self {
51        Self::Simple {
52            field,
53            operator,
54            value,
55        }
56    }
57
58    /// Create binding constraint
59    pub fn binding(field: String, variable: Variable) -> Self {
60        Self::Binding { field, variable }
61    }
62
63    /// Create variable constraint
64    pub fn variable(field: String, operator: String, variable: Variable) -> Self {
65        Self::Variable {
66            field,
67            operator,
68            variable,
69        }
70    }
71
72    /// Create multi-field constraint
73    pub fn multifield(
74        field: String,
75        operator: MultifieldOp,
76        variable: Option<Variable>,
77        value: Option<FactValue>,
78    ) -> Self {
79        Self::MultiField {
80            field,
81            operator,
82            variable,
83            value,
84        }
85    }
86
87    /// Evaluate constraint against facts and bindings
88    pub fn evaluate(
89        &self,
90        facts: &TypedFacts,
91        bindings: &HashMap<Variable, FactValue>,
92    ) -> Option<HashMap<Variable, FactValue>> {
93        match self {
94            PatternConstraint::Simple {
95                field,
96                operator,
97                value,
98            } => {
99                if facts.evaluate_condition(field, operator, value) {
100                    Some(HashMap::new())
101                } else {
102                    None
103                }
104            }
105            PatternConstraint::Binding { field, variable } => {
106                if let Some(fact_value) = facts.get(field) {
107                    let mut new_bindings = HashMap::new();
108                    new_bindings.insert(variable.clone(), fact_value.clone());
109                    Some(new_bindings)
110                } else {
111                    None
112                }
113            }
114            PatternConstraint::Variable {
115                field,
116                operator,
117                variable,
118            } => {
119                if let Some(bound_value) = bindings.get(variable) {
120                    if facts.evaluate_condition(field, operator, bound_value) {
121                        Some(HashMap::new())
122                    } else {
123                        None
124                    }
125                } else {
126                    None // Variable not bound yet
127                }
128            }
129            PatternConstraint::MultiField {
130                field,
131                operator,
132                variable,
133                value,
134            } => {
135                // Delegate to multifield evaluation helper
136                super::multifield::evaluate_multifield_pattern(
137                    facts,
138                    field,
139                    operator,
140                    variable.as_deref(),
141                    value.as_ref(),
142                    bindings,
143                )
144            }
145        }
146    }
147}
148
149/// A pattern matches facts of a specific type with constraints
150#[derive(Debug, Clone)]
151pub struct Pattern {
152    /// Fact type (e.g., "Person", "Order")
153    pub fact_type: String,
154    /// List of constraints
155    pub constraints: Vec<PatternConstraint>,
156    /// Optional pattern name for reference
157    pub name: Option<String>,
158}
159
160impl Pattern {
161    /// Create new pattern
162    pub fn new(fact_type: String) -> Self {
163        Self {
164            fact_type,
165            constraints: Vec::new(),
166            name: None,
167        }
168    }
169
170    /// Add constraint
171    pub fn with_constraint(mut self, constraint: PatternConstraint) -> Self {
172        self.constraints.push(constraint);
173        self
174    }
175
176    /// Set pattern name
177    pub fn with_name(mut self, name: String) -> Self {
178        self.name = Some(name);
179        self
180    }
181
182    /// Match this pattern against a fact
183    pub fn matches(
184        &self,
185        facts: &TypedFacts,
186        bindings: &HashMap<Variable, FactValue>,
187    ) -> Option<HashMap<Variable, FactValue>> {
188        let mut new_bindings = bindings.clone();
189
190        for constraint in &self.constraints {
191            let additional_bindings = constraint.evaluate(facts, &new_bindings)?;
192            new_bindings.extend(additional_bindings);
193        }
194
195        Some(new_bindings)
196    }
197
198    /// Match pattern against working memory
199    pub fn match_in_working_memory(
200        &self,
201        wm: &WorkingMemory,
202        bindings: &HashMap<Variable, FactValue>,
203    ) -> Vec<(FactHandle, HashMap<Variable, FactValue>)> {
204        let mut results = Vec::new();
205
206        for fact in wm.get_by_type(&self.fact_type) {
207            if let Some(new_bindings) = self.matches(&fact.data, bindings) {
208                results.push((fact.handle, new_bindings));
209            }
210        }
211
212        results
213    }
214}
215
216/// Multi-pattern rule with joins
217#[derive(Debug, Clone)]
218pub struct MultiPattern {
219    /// List of patterns that must all match
220    pub patterns: Vec<Pattern>,
221    /// Rule name
222    pub name: String,
223}
224
225impl MultiPattern {
226    /// Create new multi-pattern rule
227    pub fn new(name: String) -> Self {
228        Self {
229            patterns: Vec::new(),
230            name,
231        }
232    }
233
234    /// Add pattern
235    pub fn with_pattern(mut self, pattern: Pattern) -> Self {
236        self.patterns.push(pattern);
237        self
238    }
239
240    /// Match all patterns (with variable binding across patterns)
241    pub fn match_all(
242        &self,
243        wm: &WorkingMemory,
244    ) -> Vec<(Vec<FactHandle>, HashMap<Variable, FactValue>)> {
245        if self.patterns.is_empty() {
246            return Vec::new();
247        }
248
249        // Start with first pattern
250        let mut results = Vec::new();
251        let first_pattern = &self.patterns[0];
252        let empty_bindings = HashMap::new();
253
254        for (handle, bindings) in first_pattern.match_in_working_memory(wm, &empty_bindings) {
255            results.push((vec![handle], bindings));
256        }
257
258        // Join with remaining patterns
259        for pattern in &self.patterns[1..] {
260            let mut new_results = Vec::new();
261
262            for (handles, bindings) in results {
263                for (handle, new_bindings) in pattern.match_in_working_memory(wm, &bindings) {
264                    let mut combined_handles = handles.clone();
265                    combined_handles.push(handle);
266                    new_results.push((combined_handles, new_bindings));
267                }
268            }
269
270            results = new_results;
271
272            if results.is_empty() {
273                break; // No matches, stop early
274            }
275        }
276
277        results
278    }
279}
280
281/// Pattern builder for easier construction (Drools-style DSL)
282pub struct PatternBuilder {
283    pattern: Pattern,
284}
285
286impl PatternBuilder {
287    /// Start building pattern for a fact type
288    pub fn for_type(fact_type: impl Into<String>) -> Self {
289        Self {
290            pattern: Pattern::new(fact_type.into()),
291        }
292    }
293
294    /// Add simple constraint (field op value)
295    pub fn where_field(
296        mut self,
297        field: impl Into<String>,
298        operator: impl Into<String>,
299        value: FactValue,
300    ) -> Self {
301        self.pattern.constraints.push(PatternConstraint::Simple {
302            field: field.into(),
303            operator: operator.into(),
304            value,
305        });
306        self
307    }
308
309    /// Bind field to variable ($var)
310    pub fn bind(mut self, field: impl Into<String>, variable: impl Into<String>) -> Self {
311        self.pattern.constraints.push(PatternConstraint::Binding {
312            field: field.into(),
313            variable: variable.into(),
314        });
315        self
316    }
317
318    /// Compare field with variable ($var)
319    pub fn where_var(
320        mut self,
321        field: impl Into<String>,
322        operator: impl Into<String>,
323        variable: impl Into<String>,
324    ) -> Self {
325        self.pattern.constraints.push(PatternConstraint::Variable {
326            field: field.into(),
327            operator: operator.into(),
328            variable: variable.into(),
329        });
330        self
331    }
332
333    /// Set pattern name
334    pub fn named(mut self, name: impl Into<String>) -> Self {
335        self.pattern.name = Some(name.into());
336        self
337    }
338
339    /// Build the pattern
340    pub fn build(self) -> Pattern {
341        self.pattern
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348
349    #[test]
350    fn test_simple_pattern() {
351        let pattern = PatternBuilder::for_type("Person")
352            .where_field("age", ">", FactValue::Integer(18))
353            .where_field("status", "==", FactValue::String("active".to_string()))
354            .build();
355
356        let mut facts = TypedFacts::new();
357        facts.set("age", 25i64);
358        facts.set("status", "active");
359
360        let bindings = HashMap::new();
361        let result = pattern.matches(&facts, &bindings);
362        assert!(result.is_some());
363    }
364
365    #[test]
366    fn test_variable_binding() {
367        let pattern = PatternBuilder::for_type("Person")
368            .bind("name", "$personName")
369            .bind("age", "$personAge")
370            .build();
371
372        let mut facts = TypedFacts::new();
373        facts.set("name", "John");
374        facts.set("age", 25i64);
375
376        let bindings = HashMap::new();
377        let result = pattern.matches(&facts, &bindings).unwrap();
378
379        assert_eq!(result.get("$personName").unwrap().as_string(), "John");
380        assert_eq!(result.get("$personAge").unwrap().as_integer(), Some(25));
381    }
382
383    #[test]
384    fn test_variable_constraint() {
385        // First bind $minAge
386        let mut bindings = HashMap::new();
387        bindings.insert("$minAge".to_string(), FactValue::Integer(18));
388
389        // Then use $minAge in constraint
390        let pattern = PatternBuilder::for_type("Person")
391            .where_var("age", ">=", "$minAge")
392            .build();
393
394        let mut facts = TypedFacts::new();
395        facts.set("age", 25i64);
396
397        let result = pattern.matches(&facts, &bindings);
398        assert!(result.is_some());
399    }
400
401    #[test]
402    fn test_multi_pattern_join() {
403        let mut wm = WorkingMemory::new();
404
405        // Insert Person
406        let mut person = TypedFacts::new();
407        person.set("name", "John");
408        person.set("age", 25i64);
409        wm.insert("Person".to_string(), person);
410
411        // Insert Order for John
412        let mut order = TypedFacts::new();
413        order.set("customer", "John");
414        order.set("amount", 1000.0);
415        wm.insert("Order".to_string(), order);
416
417        // Multi-pattern: Person($name) AND Order(customer == $name)
418        let person_pattern = PatternBuilder::for_type("Person")
419            .bind("name", "$name")
420            .build();
421
422        let order_pattern = PatternBuilder::for_type("Order")
423            .where_var("customer", "==", "$name")
424            .build();
425
426        let multi = MultiPattern::new("PersonWithOrder".to_string())
427            .with_pattern(person_pattern)
428            .with_pattern(order_pattern);
429
430        let matches = multi.match_all(&wm);
431        assert_eq!(matches.len(), 1);
432
433        let (handles, bindings) = &matches[0];
434        assert_eq!(handles.len(), 2);
435        assert_eq!(bindings.get("$name").unwrap().as_string(), "John");
436    }
437}