rust_rule_engine/backward/
rule_executor.rs

1//! Rule execution for backward chaining
2//!
3//! This module provides proper condition evaluation and action execution for backward
4//! chaining queries. It integrates with the Truth Maintenance System (TMS) to support
5//! logical fact insertion and justification-based retraction.
6//!
7//! # Features
8//!
9//! - **Condition evaluation** - Evaluate rule conditions against current facts
10//! - **Action execution** - Execute rule actions (Set, MethodCall, Log, Retract)
11//! - **TMS integration** - Optional logical fact insertion with justifications
12//! - **Function support** - Built-in functions (len, isEmpty, exists, etc.)
13//! - **Type conversion** - Convert between Facts (string-based) and TypedFacts (RETE)
14//!
15//! # Architecture
16//!
17//! ```text
18//! ┌──────────────────┐
19//! │  RuleExecutor    │
20//! │                  │
21//! │  ┌────────────┐  │
22//! │  │ Condition  │──┼──> ConditionEvaluator
23//! │  │ Evaluator  │  │       │
24//! │  └────────────┘  │       ├─> Built-in functions
25//! │                  │       └─> Field comparison
26//! │  ┌────────────┐  │
27//! │  │   Action   │──┼──> Set, MethodCall, Log
28//! │  │ Executor   │  │       │
29//! │  └────────────┘  │       └─> TMS Inserter (optional)
30//! └──────────────────┘
31//! ```
32//!
33//! # Example: Basic Rule Execution
34//!
35//! ```rust
36//! use rust_rule_engine::backward::rule_executor::RuleExecutor;
37//! use rust_rule_engine::engine::rule::{Rule, Condition, ConditionGroup};
38//! use rust_rule_engine::types::{Operator, ActionType, Value};
39//! use rust_rule_engine::{KnowledgeBase, Facts};
40//!
41//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
42//! let kb = KnowledgeBase::new("test");
43//! let executor = RuleExecutor::new(kb);
44//!
45//! // Define a rule: If User.Age > 18, then User.IsAdult = true
46//! let conditions = ConditionGroup::Single(
47//!     Condition::new(
48//!         "User.Age".to_string(),
49//!         Operator::GreaterThan,
50//!         Value::Number(18.0),
51//!     )
52//! );
53//! let actions = vec![ActionType::Set {
54//!     field: "User.IsAdult".to_string(),
55//!     value: Value::Boolean(true),
56//! }];
57//! let rule = Rule::new("CheckAdult".to_string(), conditions, actions);
58//!
59//! // Execute rule
60//! let mut facts = Facts::new();
61//! facts.set("User.Age", Value::Number(25.0));
62//!
63//! let executed = executor.try_execute_rule(&rule, &mut facts)?;
64//! assert!(executed); // Rule should execute successfully
65//! assert_eq!(facts.get("User.IsAdult"), Some(Value::Boolean(true)));
66//! # Ok(())
67//! # }
68//! ```
69//!
70//! # Example: TMS Integration
71//!
72//! ```rust
73//! use rust_rule_engine::backward::rule_executor::RuleExecutor;
74//! use rust_rule_engine::rete::propagation::IncrementalEngine;
75//! use rust_rule_engine::{KnowledgeBase, Facts};
76//! use std::sync::{Arc, Mutex};
77//!
78//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
79//! let kb = KnowledgeBase::new("test");
80//! let rete_engine = Arc::new(Mutex::new(IncrementalEngine::new()));
81//!
82//! // Create TMS inserter callback
83//! let inserter = {
84//!     let eng = rete_engine.clone();
85//!     Arc::new(move |fact_type: String, data, rule_name: String, premises: Vec<String>| {
86//!         if let Ok(mut e) = eng.lock() {
87//!             let handles = e.resolve_premise_keys(premises);
88//!             let _ = e.insert_logical(fact_type, data, rule_name, handles);
89//!         }
90//!     })
91//! };
92//!
93//! let executor = RuleExecutor::new_with_inserter(kb, Some(inserter));
94//! // Now rule executions will insert facts logically with justifications
95//! # Ok(())
96//! # }
97//! ```
98//!
99//! # Supported Action Types
100//!
101//! - **Set** - Set a fact value: `field: value`
102//! - **MethodCall** - Call a method on an object: `object.method(args)`
103//! - **Log** - Log a message: `log("message")`
104//! - **Retract** - Retract a fact: `retract(field)`
105//!
106//! # Built-in Functions
107//!
108//! The executor supports these built-in functions for condition evaluation:
109//! - `len(field)` - Get string/array length
110//! - `isEmpty(field)` - Check if string/array is empty
111//! - `exists(field)` - Check if field exists
112//! - `count(field)` - Count array elements
113
114use crate::engine::rule::{Condition, ConditionGroup, Rule};
115use crate::engine::condition_evaluator::ConditionEvaluator;
116use crate::types::{ActionType, Value};
117use crate::{Facts, KnowledgeBase};
118use crate::errors::{Result, RuleEngineError};
119
120/// Rule executor for backward chaining
121pub struct RuleExecutor {
122    evaluator: ConditionEvaluator,
123    /// Optional TMS inserter callback: (fact_type, typed_data, source_rule, premise_keys)
124    /// premise_keys are strings in the format: "Type.field=value" which the inserter
125    /// can use to resolve to working-memory FactHandles.
126    tms_inserter: Option<std::sync::Arc<dyn Fn(String, crate::rete::TypedFacts, String, Vec<String>) + Send + Sync>>,
127}
128
129impl RuleExecutor {
130    /// Create a new rule executor
131    ///
132    /// Note: The knowledge_base parameter is kept for API compatibility but is not used.
133    /// Rule evaluation is done through the ConditionEvaluator.
134    pub fn new(_knowledge_base: KnowledgeBase) -> Self {
135        Self::new_with_inserter(_knowledge_base, None)
136    }
137
138    /// Create a new executor with optional TMS inserter callback
139    ///
140    /// Note: The knowledge_base parameter is kept for API compatibility but is not used.
141    /// Rule evaluation is done through the ConditionEvaluator.
142    pub fn new_with_inserter(
143        _knowledge_base: KnowledgeBase,
144        inserter: Option<std::sync::Arc<dyn Fn(String, crate::rete::TypedFacts, String, Vec<String>) + Send + Sync>>,
145    ) -> Self {
146        Self {
147            evaluator: ConditionEvaluator::with_builtin_functions(),
148            tms_inserter: inserter,
149        }
150    }
151
152    /// Check if rule conditions are satisfied and execute if they are
153    ///
154    /// Returns:
155    /// - Ok(true) if rule executed successfully
156    /// - Ok(false) if conditions not satisfied
157    /// - Err if execution failed
158    pub fn try_execute_rule(
159        &self,
160        rule: &Rule,
161        facts: &mut Facts,
162    ) -> Result<bool> {
163        // Check if all conditions are satisfied
164        if !self.evaluate_conditions(&rule.conditions, facts)? {
165            return Ok(false);
166        }
167
168        // Conditions satisfied - execute actions
169        self.execute_actions(rule, facts)?;
170
171        Ok(true)
172    }
173
174    /// Evaluate condition group
175    pub fn evaluate_conditions(
176        &self,
177        group: &ConditionGroup,
178        facts: &Facts,
179    ) -> Result<bool> {
180        // Delegate to shared evaluator
181        self.evaluator.evaluate_conditions(group, facts)
182    }
183
184    /// Evaluate a single condition
185    pub fn evaluate_condition(&self, condition: &Condition, facts: &Facts) -> Result<bool> {
186        // Delegate to shared evaluator
187        self.evaluator.evaluate_condition(condition, facts)
188    }
189
190    /// Execute rule actions
191    fn execute_actions(&self, rule: &Rule, facts: &mut Facts) -> Result<()> {
192        for action in &rule.actions {
193            self.execute_action(Some(rule), action, facts)?;
194        }
195
196        Ok(())
197    }
198
199    /// Execute a single action (has access to rule for TMS justifications)
200    fn execute_action(&self, rule: Option<&Rule>, action: &ActionType, facts: &mut Facts) -> Result<()> {
201        match action {
202            ActionType::Set { field, value } => {
203                // Evaluate value expression if needed
204                let evaluated_value = self.evaluate_value_expression(value, facts)?;
205
206                // If we have a TMS inserter and the field looks like "Type.field",
207                // attempt to create a TypedFacts wrapper and call the inserter as a logical assertion.
208                if let Some(inserter) = &self.tms_inserter {
209                    if let Some(dot_pos) = field.find('.') {
210                        let fact_type = field[..dot_pos].to_string();
211                        let field_name = field[dot_pos + 1..].to_string();
212
213                        // Build TypedFacts with this single field
214                        let mut typed = crate::rete::TypedFacts::new();
215                        // Map crate::types::Value -> rete::FactValue
216                        let fv = match &evaluated_value {
217                            crate::types::Value::String(s) => crate::rete::FactValue::String(s.clone()),
218                            crate::types::Value::Integer(i) => crate::rete::FactValue::Integer(*i),
219                            crate::types::Value::Number(n) => crate::rete::FactValue::Float(*n),
220                            crate::types::Value::Boolean(b) => crate::rete::FactValue::Boolean(*b),
221                            _ => crate::rete::FactValue::String(format!("{:?}", evaluated_value)),
222                        };
223
224                        typed.set(field_name, fv);
225
226                        // Build premise keys from the rule's conditions (best-effort):
227                        // format: "Type.field=value" so the RETE engine can map to handles.
228                        let premises = match rule {
229                            Some(r) => self.collect_premise_keys_from_rule(r, facts),
230                            None => Vec::new(),
231                        };
232
233                        // Call inserter with rule name (string-based premises)
234                        let source_name = rule.map(|r| r.name.clone()).unwrap_or_else(|| "<unknown>".to_string());
235                        (inserter)(fact_type, typed, source_name, premises);
236                        // Also apply to local Facts representation so backward search sees it
237                        facts.set(field, evaluated_value);
238                        return Ok(());
239                    }
240                }
241
242                // Fallback: just set into Facts
243                facts.set(field, evaluated_value);
244                Ok(())
245            }
246
247            ActionType::MethodCall { object, method, args } => {
248                // Execute method call
249                if let Some(obj_value) = facts.get(object) {
250                    let mut obj_value = obj_value.clone();
251                    // Evaluate arguments
252                    let mut arg_values = Vec::new();
253                    for arg in args {
254                        let val = self.evaluate_value_expression(arg, facts)?;
255                        arg_values.push(val);
256                    }
257
258                    // Call method
259                    let result = obj_value.call_method(method, arg_values)
260                        .map_err(|e| RuleEngineError::ExecutionError(e))?;
261
262                    // Update object
263                    facts.set(object, obj_value);
264
265                    // Store result if there's a return value
266                    if result != Value::Null {
267                        facts.set(&format!("{}._return", object), result);
268                    }
269
270                    Ok(())
271                } else {
272                    Err(RuleEngineError::ExecutionError(
273                        format!("Object not found: {}", object)
274                    ))
275                }
276            }
277
278            ActionType::Retract { object } => {
279                // Retract fact from working memory
280                // In backward chaining, we just remove the fact
281                facts.remove(object);
282                Ok(())
283            }
284
285            ActionType::Log { message } => {
286                // Just log for now
287                println!("[BC Action] {}", message);
288                Ok(())
289            }
290
291            ActionType::Custom { .. } => {
292                // Custom actions not supported in backward chaining yet
293                Ok(())
294            }
295
296            ActionType::ActivateAgendaGroup { .. } => {
297                // Agenda groups not supported in backward chaining
298                Ok(())
299            }
300
301            ActionType::ScheduleRule { .. } => {
302                // Rule scheduling not supported in backward chaining
303                Ok(())
304            }
305
306            ActionType::CompleteWorkflow { .. } => {
307                // Workflows not supported in backward chaining
308                Ok(())
309            }
310
311            ActionType::SetWorkflowData { .. } => {
312                // Workflow data not supported in backward chaining
313                Ok(())
314            }
315        }
316    }
317
318    /// Collect a best-effort list of premise keys from the rule's conditions.
319    /// Each entry has the format: "Type.field=value" when possible. This is
320    /// intentionally conservative: only field-based conditions with a dotted
321    /// "Type.field" expression are collected.
322    fn collect_premise_keys_from_rule(&self, rule: &Rule, facts: &Facts) -> Vec<String> {
323        use crate::engine::rule::{ConditionGroup, ConditionExpression};
324
325        let mut keys = Vec::new();
326
327        fn collect_from_group(group: &ConditionGroup, keys: &mut Vec<String>, facts: &Facts) {
328            match group {
329                ConditionGroup::Single(cond) => {
330                    if let ConditionExpression::Field(f) = &cond.expression {
331                        if let Some(dot_pos) = f.find('.') {
332                            let fact_type = &f[..dot_pos];
333                            let field_name = &f[dot_pos + 1..];
334
335                            // Try to get value from facts
336                            if let Some(val) = facts.get(f).or_else(|| facts.get_nested(f)) {
337                                let value_str = match val {
338                                    crate::types::Value::String(s) => s.clone(),
339                                    crate::types::Value::Integer(i) => i.to_string(),
340                                    crate::types::Value::Number(n) => n.to_string(),
341                                    crate::types::Value::Boolean(b) => b.to_string(),
342                                    _ => format!("{:?}", val),
343                                };
344
345                                keys.push(format!("{}.{}={}", fact_type, field_name, value_str));
346                            } else {
347                                // If we don't have a value at this time, still record the key without value
348                                keys.push(format!("{}.{}=", fact_type, field_name));
349                            }
350                        }
351                    }
352                }
353                ConditionGroup::Compound { left, right, .. } => {
354                    collect_from_group(left, keys, facts);
355                    collect_from_group(right, keys, facts);
356                }
357                // For other complex groups, skip
358                _ => {}
359            }
360        }
361
362        collect_from_group(&rule.conditions, &mut keys, facts);
363        keys
364    }
365
366    /// Evaluate value expression
367    fn evaluate_value_expression(&self, value: &Value, facts: &Facts) -> Result<Value> {
368        match value {
369            Value::Expression(expr) => {
370                // Try simple field lookup first
371                if let Some(val) = facts.get(expr).or_else(|| facts.get_nested(expr)) {
372                    return Ok(val);
373                }
374
375                // Try arithmetic expression evaluation
376                if let Some(result) = self.try_evaluate_arithmetic(expr, facts) {
377                    return Ok(result);
378                }
379
380                // Try to parse as literal
381                if expr == "true" {
382                    Ok(Value::Boolean(true))
383                } else if expr == "false" {
384                    Ok(Value::Boolean(false))
385                } else if expr == "null" {
386                    Ok(Value::Null)
387                } else if let Ok(n) = expr.parse::<f64>() {
388                    Ok(Value::Number(n))
389                } else if let Ok(i) = expr.parse::<i64>() {
390                    Ok(Value::Integer(i))
391                } else {
392                    // Try to parse as literal using simple parsing
393                    if expr == "true" {
394                        Ok(Value::Boolean(true))
395                    } else if expr == "false" {
396                        Ok(Value::Boolean(false))
397                    } else if expr == "null" {
398                        Ok(Value::Null)
399                    } else if let Ok(n) = expr.parse::<f64>() {
400                        Ok(Value::Number(n))
401                    } else if let Ok(i) = expr.parse::<i64>() {
402                        Ok(Value::Integer(i))
403                    } else {
404                        Ok(value.clone())
405                    }
406                }
407            }
408            _ => Ok(value.clone()),
409        }
410    }
411
412    /// Try to evaluate simple arithmetic expressions
413    /// Supports: +, -, *, /
414    fn try_evaluate_arithmetic(&self, expr: &str, facts: &Facts) -> Option<Value> {
415        // Check for division
416        if let Some(div_pos) = expr.find(" / ") {
417            let left = expr[..div_pos].trim();
418            let right = expr[div_pos + 3..].trim();
419
420            let left_val = self.get_numeric_value(left, facts)?;
421            let right_val = self.get_numeric_value(right, facts)?;
422
423            if right_val != 0.0 {
424                return Some(Value::Number(left_val / right_val));
425            }
426            return None;
427        }
428
429        // Check for multiplication
430        if let Some(mul_pos) = expr.find(" * ") {
431            let left = expr[..mul_pos].trim();
432            let right = expr[mul_pos + 3..].trim();
433
434            let left_val = self.get_numeric_value(left, facts)?;
435            let right_val = self.get_numeric_value(right, facts)?;
436
437            return Some(Value::Number(left_val * right_val));
438        }
439
440        // Check for addition
441        if let Some(add_pos) = expr.find(" + ") {
442            let left = expr[..add_pos].trim();
443            let right = expr[add_pos + 3..].trim();
444
445            let left_val = self.get_numeric_value(left, facts)?;
446            let right_val = self.get_numeric_value(right, facts)?;
447
448            return Some(Value::Number(left_val + right_val));
449        }
450
451        // Check for subtraction
452        if let Some(sub_pos) = expr.find(" - ") {
453            let left = expr[..sub_pos].trim();
454            let right = expr[sub_pos + 3..].trim();
455
456            let left_val = self.get_numeric_value(left, facts)?;
457            let right_val = self.get_numeric_value(right, facts)?;
458
459            return Some(Value::Number(left_val - right_val));
460        }
461
462        None
463    }
464
465    /// Get numeric value from field name or literal
466    fn get_numeric_value(&self, s: &str, facts: &Facts) -> Option<f64> {
467        // Try parsing as number first
468        if let Ok(n) = s.parse::<f64>() {
469            return Some(n);
470        }
471
472        // Try getting from facts
473        if let Some(val) = facts.get(s).or_else(|| facts.get_nested(s)) {
474            match val {
475                Value::Number(n) => Some(n),
476                Value::Integer(i) => Some(i as f64),
477                _ => None,
478            }
479        } else {
480            None
481        }
482    }
483}
484
485#[cfg(test)]
486mod tests {
487    use super::*;
488    use crate::types::Operator;
489
490    #[test]
491    fn test_evaluate_simple_condition() {
492        let kb = KnowledgeBase::new("test");
493        let executor = RuleExecutor::new(kb);
494
495        let mut facts = Facts::new();
496        facts.set("User.Age", Value::Number(25.0));
497
498        let condition = Condition::new(
499            "User.Age".to_string(),
500            Operator::GreaterThan,
501            Value::Number(18.0),
502        );
503
504        let result = executor.evaluate_condition(&condition, &facts).unwrap();
505        assert!(result);
506    }
507
508    #[test]
509    fn test_evaluate_function_call_len() {
510        let kb = KnowledgeBase::new("test");
511        let executor = RuleExecutor::new(kb);
512
513        let mut facts = Facts::new();
514        facts.set("User.Name", Value::String("John".to_string()));
515
516        let condition = Condition::with_function(
517            "len".to_string(),
518            vec!["User.Name".to_string()],
519            Operator::GreaterThan,
520            Value::Number(3.0),
521        );
522
523        let result = executor.evaluate_condition(&condition, &facts).unwrap();
524        assert!(result); // "John".len() = 4 > 3
525    }
526
527    #[test]
528    fn test_execute_set_action() {
529        let kb = KnowledgeBase::new("test");
530        let executor = RuleExecutor::new(kb);
531
532        let mut facts = Facts::new();
533
534        let action = ActionType::Set {
535            field: "User.IsVIP".to_string(),
536            value: Value::Boolean(true),
537        };
538
539    executor.execute_action(None, &action, &mut facts).unwrap();
540
541        assert_eq!(facts.get("User.IsVIP"), Some(Value::Boolean(true)));
542    }
543}