Skip to main content

rust_rule_engine/rete/
grl_loader.rs

1//! GRL to RETE Converter
2//!
3//! This module converts GRL (Grule Rule Language) rules into RETE-UL structures
4//! for efficient pattern matching and rule execution.
5
6#![allow(clippy::type_complexity)]
7#![allow(deprecated)]
8
9use crate::engine::rule::{Condition, ConditionGroup, Rule};
10use crate::errors::{Result, RuleEngineError};
11use crate::parser::GRLParser;
12use crate::rete::facts::{FactValue, TypedFacts};
13use crate::rete::propagation::IncrementalEngine;
14use crate::rete::{AlphaNode, ReteUlNode, TypedReteUlRule};
15use crate::types::{Operator, Value};
16use log::info;
17use std::fs;
18use std::path::Path;
19
20#[cfg(feature = "streaming")]
21use crate::rete::network::{StreamWindowSpec, StreamWindowTypeRete};
22
23/// GRL to RETE Loader
24/// Converts GRL rules into RETE-UL structures
25pub struct GrlReteLoader;
26
27impl GrlReteLoader {
28    /// Load rules from a GRL file into RETE engine
29    pub fn load_from_file<P: AsRef<Path>>(
30        path: P,
31        engine: &mut IncrementalEngine,
32    ) -> Result<usize> {
33        let grl_text =
34            fs::read_to_string(path.as_ref()).map_err(|e| RuleEngineError::ParseError {
35                message: format!("Failed to read GRL file: {}", e),
36            })?;
37
38        Self::load_from_string(&grl_text, engine)
39    }
40
41    /// Load rules from GRL string into RETE engine
42    pub fn load_from_string(grl_text: &str, engine: &mut IncrementalEngine) -> Result<usize> {
43        // Parse GRL rules
44        let rules = GRLParser::parse_rules(grl_text)?;
45
46        let mut loaded_count = 0;
47
48        for rule in rules {
49            // Convert GRL rule to RETE rule
50            let rete_rule = Self::convert_rule_to_rete(rule)?;
51
52            // Extract dependencies (fact types used in conditions)
53            let dependencies = Self::extract_dependencies(&rete_rule);
54
55            // Add to engine
56            engine.add_rule(rete_rule, dependencies);
57            loaded_count += 1;
58        }
59
60        Ok(loaded_count)
61    }
62
63    /// Convert GRL Rule to TypedReteUlRule
64    fn convert_rule_to_rete(rule: Rule) -> Result<TypedReteUlRule> {
65        // Convert ConditionGroup to ReteUlNode
66        let node = Self::convert_condition_group(&rule.conditions)?;
67
68        // Create RETE rule
69        let rete_rule = TypedReteUlRule {
70            name: rule.name.clone(),
71            node,
72            priority: rule.salience,
73            no_loop: rule.no_loop,
74            action: Self::create_action_closure(rule.actions),
75        };
76
77        Ok(rete_rule)
78    }
79
80    /// Convert ConditionGroup to ReteUlNode
81    fn convert_condition_group(group: &ConditionGroup) -> Result<ReteUlNode> {
82        match group {
83            ConditionGroup::Single(condition) => Self::convert_condition(condition),
84            ConditionGroup::Compound {
85                left,
86                operator,
87                right,
88            } => {
89                let left_node = Self::convert_condition_group(left)?;
90                let right_node = Self::convert_condition_group(right)?;
91
92                match operator {
93                    crate::types::LogicalOperator::And => {
94                        Ok(ReteUlNode::UlAnd(Box::new(left_node), Box::new(right_node)))
95                    }
96                    crate::types::LogicalOperator::Or => {
97                        Ok(ReteUlNode::UlOr(Box::new(left_node), Box::new(right_node)))
98                    }
99                    crate::types::LogicalOperator::Not => {
100                        // For NOT, we only use left node
101                        Ok(ReteUlNode::UlNot(Box::new(left_node)))
102                    }
103                }
104            }
105            ConditionGroup::Not(inner) => {
106                let inner_node = Self::convert_condition_group(inner)?;
107                Ok(ReteUlNode::UlNot(Box::new(inner_node)))
108            }
109            ConditionGroup::Exists(inner) => {
110                let inner_node = Self::convert_condition_group(inner)?;
111                Ok(ReteUlNode::UlExists(Box::new(inner_node)))
112            }
113            ConditionGroup::Forall(inner) => {
114                let inner_node = Self::convert_condition_group(inner)?;
115                Ok(ReteUlNode::UlForall(Box::new(inner_node)))
116            }
117            ConditionGroup::Accumulate {
118                result_var,
119                source_pattern,
120                extract_field,
121                source_conditions,
122                function,
123                function_arg,
124            } => Ok(ReteUlNode::UlAccumulate {
125                result_var: result_var.clone(),
126                source_pattern: source_pattern.clone(),
127                extract_field: extract_field.clone(),
128                source_conditions: source_conditions.clone(),
129                function: function.clone(),
130                function_arg: function_arg.clone(),
131            }),
132            #[cfg(feature = "streaming")]
133            ConditionGroup::StreamPattern {
134                var_name,
135                event_type,
136                stream_name,
137                window,
138            } => {
139                // Convert stream pattern to RETE UlStream node
140                Ok(ReteUlNode::UlStream {
141                    var_name: var_name.clone(),
142                    event_type: event_type.clone(),
143                    stream_name: stream_name.clone(),
144                    window: window.as_ref().map(|w| StreamWindowSpec {
145                        duration: w.duration,
146                        window_type: match &w.window_type {
147                            crate::engine::rule::StreamWindowType::Sliding => {
148                                StreamWindowTypeRete::Sliding
149                            }
150                            crate::engine::rule::StreamWindowType::Tumbling => {
151                                StreamWindowTypeRete::Tumbling
152                            }
153                            crate::engine::rule::StreamWindowType::Session { timeout } => {
154                                StreamWindowTypeRete::Session { timeout: *timeout }
155                            }
156                        },
157                    }),
158                })
159            }
160        }
161    }
162
163    /// Convert single Condition to ReteUlNode (AlphaNode or UlMultiField)
164    fn convert_condition(condition: &Condition) -> Result<ReteUlNode> {
165        use crate::engine::rule::ConditionExpression;
166
167        // Check if this is a multifield condition
168        match &condition.expression {
169            ConditionExpression::MultiField {
170                field,
171                operation,
172                variable: _,
173            } => {
174                // Convert to UlMultiField node
175                let operator_str = Self::operator_to_string(&condition.operator);
176                let value_str = if !matches!(condition.value, Value::Boolean(_)) {
177                    Some(Self::value_to_string(&condition.value))
178                } else {
179                    None
180                };
181
182                // Determine if this is a count operation with comparison
183                let (op, cmp_val) = if operation == "count" && operator_str != "==" {
184                    // Count with comparison: "count > 5"
185                    (Some(operator_str), value_str)
186                } else {
187                    // Other operations
188                    (None, value_str)
189                };
190
191                Ok(ReteUlNode::UlMultiField {
192                    field: field.clone(),
193                    operation: operation.clone(),
194                    value: if operation == "contains" {
195                        cmp_val.clone()
196                    } else {
197                        None
198                    },
199                    operator: op,
200                    compare_value: if operation == "count" { cmp_val } else { None },
201                })
202            }
203            ConditionExpression::FunctionCall { name, args } => {
204                Ok(ReteUlNode::UlFunctionCall {
205                    name:     name.clone(),
206                    args:     args.clone(),
207                    operator: Self::operator_to_string(&condition.operator),
208                    value:    Self::value_to_string(&condition.value),
209                })
210            }
211            _ => {
212                // Standard alpha node for regular conditions
213                let operator_str = Self::operator_to_string(&condition.operator);
214                let value_str = Self::value_to_string(&condition.value);
215
216                let alpha = AlphaNode {
217                    field: condition.field.clone(),
218                    operator: operator_str,
219                    value: value_str,
220                };
221
222                Ok(ReteUlNode::UlAlpha(alpha))
223            }
224        }
225    }
226
227    /// Convert Operator to string
228    fn operator_to_string(op: &Operator) -> String {
229        match op {
230            Operator::Equal => "==".to_string(),
231            Operator::NotEqual => "!=".to_string(),
232            Operator::GreaterThan => ">".to_string(),
233            Operator::GreaterThanOrEqual => ">=".to_string(),
234            Operator::LessThan => "<".to_string(),
235            Operator::LessThanOrEqual => "<=".to_string(),
236            Operator::Contains => "contains".to_string(),
237            Operator::NotContains => "!contains".to_string(),
238            Operator::StartsWith => "startsWith".to_string(),
239            Operator::EndsWith => "endsWith".to_string(),
240            Operator::Matches => "matches".to_string(),
241            Operator::In => "in".to_string(),
242        }
243    }
244
245    /// Convert Value to string for AlphaNode
246    fn value_to_string(value: &Value) -> String {
247        match value {
248            Value::Number(n) => n.to_string(),
249            Value::Integer(i) => i.to_string(),
250            Value::String(s) => s.clone(),
251            Value::Boolean(b) => b.to_string(),
252            Value::Null => "null".to_string(),
253            Value::Array(arr) => {
254                // Convert array to JSON-like string
255                let items: Vec<String> = arr.iter().map(Self::value_to_string).collect();
256                format!("[{}]", items.join(","))
257            }
258            Value::Object(_) => {
259                // For objects, we'll use a simplified representation
260                "object".to_string()
261            }
262            Value::Expression(expr) => {
263                // For expressions, return the expression string
264                expr.clone()
265            }
266        }
267    }
268
269    /// Create action closure from ActionType list
270    fn create_action_closure(
271        actions: Vec<crate::types::ActionType>,
272    ) -> std::sync::Arc<dyn Fn(&mut TypedFacts, &mut super::ActionResults) + Send + Sync> {
273        std::sync::Arc::new(
274            move |facts: &mut TypedFacts, results: &mut super::ActionResults| {
275                // Execute actions
276                for action in &actions {
277                    Self::execute_action(action, facts, results);
278                }
279            },
280        )
281    }
282
283    /// Execute a single action
284    fn execute_action(
285        action: &crate::types::ActionType,
286        facts: &mut TypedFacts,
287        results: &mut super::ActionResults,
288    ) {
289        use crate::types::ActionType;
290
291        match action {
292            ActionType::Set { field, value } => {
293                // Assignment action (from "field = value" syntax in GRL)
294                // Note: Set() function syntax is NOT supported.
295                // Use: Player.score = Player.score + 10;
296
297                // Check if value is an expression that needs evaluation
298                let evaluated_value = match value {
299                    Value::Expression(expr) => {
300                        // Evaluate expression with current facts
301                        Self::evaluate_expression_for_rete(expr, facts)
302                    }
303                    _ => value.clone(),
304                };
305
306                // Convert evaluated value to FactValue
307                let fact_value = Self::value_to_fact_value(&evaluated_value);
308                facts.set(field, fact_value);
309            }
310            ActionType::Log { message } => {
311                info!("📝 {}", message);
312            }
313            ActionType::MethodCall {
314                object,
315                method,
316                args,
317            } => {
318                // Method calls can be treated as function calls with object as first arg
319                let mut all_args = vec![object.clone()];
320                all_args.extend(args.iter().map(Self::value_to_string));
321
322                results.add(super::ActionResult::CallFunction {
323                    function_name: format!("{}.{}", object, method),
324                    args: all_args,
325                });
326                println!("� METHOD: {}.{}", object, method);
327            }
328            ActionType::Retract { object } => {
329                // Strip quotes from object name if present
330                let object_name = object.trim_matches('"');
331
332                // Try to get the handle for this fact type from metadata
333                if let Some(handle) = facts.get_fact_handle(object_name) {
334                    // Retract specific fact by handle
335                    results.add(super::ActionResult::Retract(handle));
336                    println!("🗑️ RETRACT: {} (handle: {:?})", object_name, handle);
337                } else {
338                    // Fallback: retract by type (first matching fact)
339                    results.add(super::ActionResult::RetractByType(object_name.to_string()));
340                    println!("🗑️ RETRACT: {} (by type, no handle found)", object_name);
341                }
342            }
343            ActionType::Custom {
344                action_type,
345                params,
346            } => {
347                // Treat custom actions as function calls
348                let args: Vec<String> = params.values().map(Self::value_to_string).collect();
349
350                results.add(super::ActionResult::CallFunction {
351                    function_name: action_type.clone(),
352                    args,
353                });
354                println!("🔧 CUSTOM CALL: {}", action_type);
355            }
356            ActionType::ActivateAgendaGroup { group } => {
357                // Queue agenda group activation
358                results.add(super::ActionResult::ActivateAgendaGroup(group.clone()));
359                println!("📋 ACTIVATE GROUP: {}", group);
360            }
361            ActionType::ScheduleRule {
362                rule_name,
363                delay_ms,
364            } => {
365                // Queue rule scheduling
366                results.add(super::ActionResult::ScheduleRule {
367                    rule_name: rule_name.clone(),
368                    delay_ms: *delay_ms,
369                });
370                println!("⏰ SCHEDULE: {} (delay: {}ms)", rule_name, delay_ms);
371            }
372            ActionType::CompleteWorkflow { workflow_name } => {
373                // Mark workflow as completed by setting a fact
374                let completion_key = format!("workflow.{}.completed", workflow_name);
375                facts.set(&completion_key, FactValue::Boolean(true));
376
377                let timestamp_key = format!("workflow.{}.completed_at", workflow_name);
378                facts.set(
379                    &timestamp_key,
380                    FactValue::Integer(chrono::Utc::now().timestamp()),
381                );
382
383                println!("✔️ WORKFLOW COMPLETED: {}", workflow_name);
384            }
385            ActionType::SetWorkflowData { key, value } => {
386                // Store workflow data as facts with "workflow.data." prefix
387                let data_key = format!("workflow.data.{}", key);
388                let fact_value = Self::value_to_fact_value(value);
389                facts.set(&data_key, fact_value);
390
391                println!("📊 WORKFLOW DATA SET: {} = {:?}", key, value);
392            }
393            ActionType::Append { field, value } => {
394                // Append to array field
395                // Get current array or create new one
396                let current_value = facts.get(field);
397
398                let mut array = match current_value {
399                    Some(FactValue::Array(arr)) => arr.clone(),
400                    Some(_) => {
401                        // Field exists but is not an array, create new array
402                        log::warn!("Field {} is not an array, creating new array", field);
403                        Vec::new()
404                    }
405                    None => {
406                        // Field doesn't exist, create new array
407                        Vec::new()
408                    }
409                };
410
411                // Evaluate value if it's an expression
412                let evaluated_value = match value {
413                    Value::Expression(expr) => Self::evaluate_expression_for_rete(expr, facts),
414                    _ => value.clone(),
415                };
416
417                // Convert to FactValue and append
418                let fact_value = Self::value_to_fact_value(&evaluated_value);
419                array.push(fact_value);
420
421                // Set the updated array
422                facts.set(field, FactValue::Array(array));
423
424                info!("➕ APPEND: {} += {:?}", field, evaluated_value);
425            }
426        }
427    }
428
429    /// Convert Value to FactValue
430    fn value_to_fact_value(value: &Value) -> FactValue {
431        match value {
432            Value::Number(n) => {
433                // Try integer first, fall back to float
434                if n.fract() == 0.0 {
435                    FactValue::Integer(*n as i64)
436                } else {
437                    FactValue::Float(*n)
438                }
439            }
440            Value::Integer(i) => FactValue::Integer(*i),
441            Value::String(s) => FactValue::String(s.clone()),
442            Value::Boolean(b) => FactValue::Boolean(*b),
443            Value::Null => FactValue::Null,
444            Value::Array(arr) => {
445                let fact_arr: Vec<FactValue> = arr.iter().map(Self::value_to_fact_value).collect();
446                FactValue::Array(fact_arr)
447            }
448            Value::Object(_) => {
449                // For now, treat objects as strings
450                FactValue::String("object".to_string())
451            }
452            Value::Expression(expr) => {
453                // For expressions, store as string - will be evaluated at runtime
454                FactValue::String(format!("[EXPR: {}]", expr))
455            }
456        }
457    }
458
459    /// Extract fact type dependencies from rule
460    fn extract_dependencies(rule: &TypedReteUlRule) -> Vec<String> {
461        let mut deps = Vec::new();
462        Self::extract_deps_from_node(&rule.node, &mut deps);
463
464        // Deduplicate
465        deps.sort();
466        deps.dedup();
467
468        deps
469    }
470
471    /// Recursively extract dependencies from ReteUlNode
472    fn extract_deps_from_node(node: &ReteUlNode, deps: &mut Vec<String>) {
473        match node {
474            ReteUlNode::UlAlpha(alpha) => {
475                // Extract fact type from field (e.g., "Person.age" -> "Person")
476                if let Some(dot_pos) = alpha.field.find('.') {
477                    let fact_type = alpha.field[..dot_pos].to_string();
478                    deps.push(fact_type);
479                }
480            }
481            ReteUlNode::UlMultiField { field, .. } => {
482                // Extract fact type from field (e.g., "Order.items" -> "Order")
483                if let Some(dot_pos) = field.find('.') {
484                    let fact_type = field[..dot_pos].to_string();
485                    deps.push(fact_type);
486                }
487            }
488            ReteUlNode::UlAnd(left, right) | ReteUlNode::UlOr(left, right) => {
489                Self::extract_deps_from_node(left, deps);
490                Self::extract_deps_from_node(right, deps);
491            }
492            ReteUlNode::UlNot(inner)
493            | ReteUlNode::UlExists(inner)
494            | ReteUlNode::UlForall(inner) => {
495                Self::extract_deps_from_node(inner, deps);
496            }
497            ReteUlNode::UlAccumulate { source_pattern, .. } => {
498                // Add source pattern as a dependency
499                deps.push(source_pattern.clone());
500            }
501            #[cfg(feature = "streaming")]
502            ReteUlNode::UlStream { stream_name, .. } => {
503                // Add stream name as a dependency
504                deps.push(stream_name.clone());
505            }
506            ReteUlNode::UlFunctionCall { args, .. } => {
507                for arg in args {
508                    if let Some(dot_pos) = arg.find('.') {
509                        deps.push(arg[..dot_pos].to_string());
510                    }
511                }
512            }
513            ReteUlNode::UlTerminal(_) => {}
514        }
515    }
516
517    /// Evaluate expression for RETE engine (converts TypedFacts to Facts temporarily)
518    fn evaluate_expression_for_rete(expr: &str, typed_facts: &TypedFacts) -> Value {
519        // Convert TypedFacts to Facts for expression evaluation
520        use crate::engine::facts::Facts;
521
522        let facts = Facts::new();
523
524        // Copy all facts from TypedFacts to Facts
525        // RETE stores facts as "quantity" while GRL uses "Order.quantity"
526        // We need to support both formats
527        for (key, value) in typed_facts.get_all() {
528            let converted_value = Self::fact_value_to_value(value);
529
530            // Store both with and without prefix
531            // E.g., "quantity" -> both "quantity" and "Order.quantity"
532            facts.set(key, converted_value.clone());
533
534            // Also try to add with "Order." prefix if not already present
535            if !key.contains('.') {
536                facts.set(&format!("Order.{}", key), converted_value);
537            }
538        }
539
540        // Evaluate expression
541        match crate::expression::evaluate_expression(expr, &facts) {
542            Ok(result) => result,
543            Err(_e) => {
544                // Silently fallback - this can happen with chained expressions in RETE
545                // due to working memory complexity
546                Value::String(expr.to_string())
547            }
548        }
549    }
550
551    /// Convert FactValue back to Value (reverse of value_to_fact_value)
552    fn fact_value_to_value(fact_value: &FactValue) -> Value {
553        match fact_value {
554            FactValue::String(s) => {
555                // Try to parse as number first
556                if let Ok(i) = s.parse::<i64>() {
557                    Value::Integer(i)
558                } else if let Ok(f) = s.parse::<f64>() {
559                    Value::Number(f)
560                } else if s == "true" {
561                    Value::Boolean(true)
562                } else if s == "false" {
563                    Value::Boolean(false)
564                } else {
565                    Value::String(s.clone())
566                }
567            }
568            FactValue::Integer(i) => Value::Integer(*i),
569            FactValue::Float(f) => Value::Number(*f),
570            FactValue::Boolean(b) => Value::Boolean(*b),
571            FactValue::Array(arr) => {
572                Value::Array(arr.iter().map(Self::fact_value_to_value).collect())
573            }
574            FactValue::Null => Value::Null,
575        }
576    }
577}
578
579#[cfg(test)]
580mod tests {
581    use super::*;
582
583    #[test]
584    fn test_convert_simple_rule() {
585        let grl = r#"
586        rule "TestRule" salience 10 no-loop {
587            when
588                Person.age > 18
589            then
590                Person.is_adult = true;
591        }
592        "#;
593
594        let rules = GRLParser::parse_rules(grl).unwrap();
595        assert_eq!(rules.len(), 1);
596
597        let rete_rule = GrlReteLoader::convert_rule_to_rete(rules[0].clone()).unwrap();
598        assert_eq!(rete_rule.name, "TestRule");
599        assert_eq!(rete_rule.priority, 10);
600        assert!(rete_rule.no_loop);
601    }
602
603    #[test]
604    fn test_extract_dependencies() {
605        let grl = r#"
606        rule "MultiTypeRule" {
607            when
608                Person.age > 18 && Order.amount > 1000
609            then
610                Person.premium = true;
611        }
612        "#;
613
614        let rules = GRLParser::parse_rules(grl).unwrap();
615        let rete_rule = GrlReteLoader::convert_rule_to_rete(rules[0].clone()).unwrap();
616        let deps = GrlReteLoader::extract_dependencies(&rete_rule);
617
618        assert_eq!(deps.len(), 2);
619        assert!(deps.contains(&"Person".to_string()));
620        assert!(deps.contains(&"Order".to_string()));
621    }
622
623    #[test]
624    fn test_load_from_string() {
625        let grl = r#"
626        rule "Rule1" {
627            when
628                Person.age > 18
629            then
630                Person.is_adult = true;
631        }
632
633        rule "Rule2" {
634            when
635                Order.amount > 1000
636            then
637                Order.high_value = true;
638        }
639        "#;
640
641        let mut engine = IncrementalEngine::new();
642        let count = GrlReteLoader::load_from_string(grl, &mut engine).unwrap();
643
644        assert_eq!(count, 2);
645    }
646}