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}