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
6use crate::engine::rule::{Condition, ConditionGroup, Rule};
7use crate::parser::GRLParser;
8use crate::rete::{AlphaNode, ReteUlNode, TypedReteUlRule};
9use crate::rete::facts::{TypedFacts, FactValue};
10use crate::rete::propagation::IncrementalEngine;
11use crate::types::{Operator, Value};
12use crate::errors::{Result, RuleEngineError};
13use std::fs;
14use std::path::Path;
15
16/// GRL to RETE Loader
17/// Converts GRL rules into RETE-UL structures
18pub struct GrlReteLoader;
19
20impl GrlReteLoader {
21    /// Load rules from a GRL file into RETE engine
22    pub fn load_from_file<P: AsRef<Path>>(
23        path: P,
24        engine: &mut IncrementalEngine,
25    ) -> Result<usize> {
26        let grl_text = fs::read_to_string(path.as_ref()).map_err(|e| {
27            RuleEngineError::ParseError {
28                message: format!("Failed to read GRL file: {}", e),
29            }
30        })?;
31
32        Self::load_from_string(&grl_text, engine)
33    }
34
35    /// Load rules from GRL string into RETE engine
36    pub fn load_from_string(
37        grl_text: &str,
38        engine: &mut IncrementalEngine,
39    ) -> Result<usize> {
40        // Parse GRL rules
41        let rules = GRLParser::parse_rules(grl_text)?;
42
43        let mut loaded_count = 0;
44
45        for rule in rules {
46            // Convert GRL rule to RETE rule
47            let rete_rule = Self::convert_rule_to_rete(rule)?;
48
49            // Extract dependencies (fact types used in conditions)
50            let dependencies = Self::extract_dependencies(&rete_rule);
51
52            // Add to engine
53            engine.add_rule(rete_rule, dependencies);
54            loaded_count += 1;
55        }
56
57        Ok(loaded_count)
58    }
59
60    /// Convert GRL Rule to TypedReteUlRule
61    fn convert_rule_to_rete(rule: Rule) -> Result<TypedReteUlRule> {
62        // Convert ConditionGroup to ReteUlNode
63        let node = Self::convert_condition_group(&rule.conditions)?;
64
65        // Create RETE rule
66        let rete_rule = TypedReteUlRule {
67            name: rule.name.clone(),
68            node,
69            priority: rule.salience,
70            no_loop: rule.no_loop,
71            action: Self::create_action_closure(rule.actions),
72        };
73
74        Ok(rete_rule)
75    }
76
77    /// Convert ConditionGroup to ReteUlNode
78    fn convert_condition_group(group: &ConditionGroup) -> Result<ReteUlNode> {
79        match group {
80            ConditionGroup::Single(condition) => {
81                Self::convert_condition(condition)
82            }
83            ConditionGroup::Compound { left, operator, right } => {
84                let left_node = Self::convert_condition_group(left)?;
85                let right_node = Self::convert_condition_group(right)?;
86
87                match operator {
88                    crate::types::LogicalOperator::And => {
89                        Ok(ReteUlNode::UlAnd(Box::new(left_node), Box::new(right_node)))
90                    }
91                    crate::types::LogicalOperator::Or => {
92                        Ok(ReteUlNode::UlOr(Box::new(left_node), Box::new(right_node)))
93                    }
94                    crate::types::LogicalOperator::Not => {
95                        // For NOT, we only use left node
96                        Ok(ReteUlNode::UlNot(Box::new(left_node)))
97                    }
98                }
99            }
100            ConditionGroup::Not(inner) => {
101                let inner_node = Self::convert_condition_group(inner)?;
102                Ok(ReteUlNode::UlNot(Box::new(inner_node)))
103            }
104            ConditionGroup::Exists(inner) => {
105                let inner_node = Self::convert_condition_group(inner)?;
106                Ok(ReteUlNode::UlExists(Box::new(inner_node)))
107            }
108            ConditionGroup::Forall(inner) => {
109                let inner_node = Self::convert_condition_group(inner)?;
110                Ok(ReteUlNode::UlForall(Box::new(inner_node)))
111            }
112            ConditionGroup::Accumulate {
113                result_var,
114                source_pattern,
115                extract_field,
116                source_conditions,
117                function,
118                function_arg,
119            } => {
120                Ok(ReteUlNode::UlAccumulate {
121                    result_var: result_var.clone(),
122                    source_pattern: source_pattern.clone(),
123                    extract_field: extract_field.clone(),
124                    source_conditions: source_conditions.clone(),
125                    function: function.clone(),
126                    function_arg: function_arg.clone(),
127                })
128            }
129        }
130    }
131
132    /// Convert single Condition to ReteUlNode (AlphaNode)
133    fn convert_condition(condition: &Condition) -> Result<ReteUlNode> {
134        let operator_str = Self::operator_to_string(&condition.operator);
135        let value_str = Self::value_to_string(&condition.value);
136
137        let alpha = AlphaNode {
138            field: condition.field.clone(),
139            operator: operator_str,
140            value: value_str,
141        };
142
143        Ok(ReteUlNode::UlAlpha(alpha))
144    }
145
146    /// Convert Operator to string
147    fn operator_to_string(op: &Operator) -> String {
148        match op {
149            Operator::Equal => "==".to_string(),
150            Operator::NotEqual => "!=".to_string(),
151            Operator::GreaterThan => ">".to_string(),
152            Operator::GreaterThanOrEqual => ">=".to_string(),
153            Operator::LessThan => "<".to_string(),
154            Operator::LessThanOrEqual => "<=".to_string(),
155            Operator::Contains => "contains".to_string(),
156            Operator::NotContains => "!contains".to_string(),
157            Operator::StartsWith => "startsWith".to_string(),
158            Operator::EndsWith => "endsWith".to_string(),
159            Operator::Matches => "matches".to_string(),
160        }
161    }
162
163    /// Convert Value to string for AlphaNode
164    fn value_to_string(value: &Value) -> String {
165        match value {
166            Value::Number(n) => n.to_string(),
167            Value::Integer(i) => i.to_string(),
168            Value::String(s) => s.clone(),
169            Value::Boolean(b) => b.to_string(),
170            Value::Null => "null".to_string(),
171            Value::Array(arr) => {
172                // Convert array to JSON-like string
173                let items: Vec<String> = arr.iter()
174                    .map(|v| Self::value_to_string(v))
175                    .collect();
176                format!("[{}]", items.join(","))
177            }
178            Value::Object(_) => {
179                // For objects, we'll use a simplified representation
180                "object".to_string()
181            }
182        }
183    }
184
185    /// Create action closure from ActionType list
186    fn create_action_closure(
187        actions: Vec<crate::types::ActionType>,
188    ) -> Box<dyn FnMut(&mut TypedFacts)> {
189        Box::new(move |facts: &mut TypedFacts| {
190            // Execute actions
191            for action in &actions {
192                Self::execute_action(action, facts);
193            }
194        })
195    }
196
197    /// Execute a single action
198    fn execute_action(action: &crate::types::ActionType, facts: &mut TypedFacts) {
199        use crate::types::ActionType;
200
201        match action {
202            ActionType::Set { field, value } => {
203                // Convert Value to FactValue
204                let fact_value = Self::value_to_fact_value(value);
205                facts.set(field, fact_value);
206            }
207            ActionType::Log { message } => {
208                println!("📝 LOG: {}", message);
209            }
210            ActionType::Call { function, args: _ } => {
211                // For function calls, we'll just log them
212                println!("🔧 CALL: {}", function);
213            }
214            ActionType::MethodCall { object, method, args: _ } => {
215                println!("📞 METHOD: {}.{}", object, method);
216            }
217            ActionType::Update { object } => {
218                println!("🔄 UPDATE: {}", object);
219            }
220            ActionType::Retract { object } => {
221                println!("🗑️ RETRACT: {}", object);
222                // Mark fact as retracted - actual retraction will be handled by engine
223                facts.set(format!("_retract_{}", object), FactValue::Boolean(true));
224            }
225            ActionType::Custom { action_type, params: _ } => {
226                println!("⚙️ CUSTOM: {}", action_type);
227            }
228            ActionType::ActivateAgendaGroup { group } => {
229                println!("📋 ACTIVATE GROUP: {}", group);
230            }
231            ActionType::ScheduleRule { rule_name, delay_ms } => {
232                println!("⏰ SCHEDULE: {} (delay: {}ms)", rule_name, delay_ms);
233            }
234            ActionType::CompleteWorkflow { workflow_name } => {
235                println!("✔️ COMPLETE WORKFLOW: {}", workflow_name);
236            }
237            ActionType::SetWorkflowData { key, value: _ } => {
238                println!("📊 SET WORKFLOW DATA: {}", key);
239            }
240        }
241    }
242
243    /// Convert Value to FactValue
244    fn value_to_fact_value(value: &Value) -> FactValue {
245        match value {
246            Value::Number(n) => {
247                // Try integer first, fall back to float
248                if n.fract() == 0.0 {
249                    FactValue::Integer(*n as i64)
250                } else {
251                    FactValue::Float(*n)
252                }
253            }
254            Value::Integer(i) => FactValue::Integer(*i),
255            Value::String(s) => FactValue::String(s.clone()),
256            Value::Boolean(b) => FactValue::Boolean(*b),
257            Value::Null => FactValue::Null,
258            Value::Array(arr) => {
259                let fact_arr: Vec<FactValue> = arr.iter()
260                    .map(Self::value_to_fact_value)
261                    .collect();
262                FactValue::Array(fact_arr)
263            }
264            Value::Object(_) => {
265                // For now, treat objects as strings
266                FactValue::String("object".to_string())
267            }
268        }
269    }
270
271    /// Extract fact type dependencies from rule
272    fn extract_dependencies(rule: &TypedReteUlRule) -> Vec<String> {
273        let mut deps = Vec::new();
274        Self::extract_deps_from_node(&rule.node, &mut deps);
275
276        // Deduplicate
277        deps.sort();
278        deps.dedup();
279
280        deps
281    }
282
283    /// Recursively extract dependencies from ReteUlNode
284    fn extract_deps_from_node(node: &ReteUlNode, deps: &mut Vec<String>) {
285        match node {
286            ReteUlNode::UlAlpha(alpha) => {
287                // Extract fact type from field (e.g., "Person.age" -> "Person")
288                if let Some(dot_pos) = alpha.field.find('.') {
289                    let fact_type = alpha.field[..dot_pos].to_string();
290                    deps.push(fact_type);
291                }
292            }
293            ReteUlNode::UlAnd(left, right) | ReteUlNode::UlOr(left, right) => {
294                Self::extract_deps_from_node(left, deps);
295                Self::extract_deps_from_node(right, deps);
296            }
297            ReteUlNode::UlNot(inner)
298            | ReteUlNode::UlExists(inner)
299            | ReteUlNode::UlForall(inner) => {
300                Self::extract_deps_from_node(inner, deps);
301            }
302            ReteUlNode::UlAccumulate { source_pattern, .. } => {
303                // Add source pattern as a dependency
304                deps.push(source_pattern.clone());
305            }
306            ReteUlNode::UlTerminal(_) => {
307                // Terminal nodes don't have dependencies
308            }
309        }
310    }
311}
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316
317    #[test]
318    fn test_convert_simple_rule() {
319        let grl = r#"
320        rule "TestRule" salience 10 no-loop {
321            when
322                Person.age > 18
323            then
324                Person.is_adult = true;
325        }
326        "#;
327
328        let rules = GRLParser::parse_rules(grl).unwrap();
329        assert_eq!(rules.len(), 1);
330
331        let rete_rule = GrlReteLoader::convert_rule_to_rete(rules[0].clone()).unwrap();
332        assert_eq!(rete_rule.name, "TestRule");
333        assert_eq!(rete_rule.priority, 10);
334        assert!(rete_rule.no_loop);
335    }
336
337    #[test]
338    fn test_extract_dependencies() {
339        let grl = r#"
340        rule "MultiTypeRule" {
341            when
342                Person.age > 18 && Order.amount > 1000
343            then
344                Person.premium = true;
345        }
346        "#;
347
348        let rules = GRLParser::parse_rules(grl).unwrap();
349        let rete_rule = GrlReteLoader::convert_rule_to_rete(rules[0].clone()).unwrap();
350        let deps = GrlReteLoader::extract_dependencies(&rete_rule);
351
352        assert_eq!(deps.len(), 2);
353        assert!(deps.contains(&"Person".to_string()));
354        assert!(deps.contains(&"Order".to_string()));
355    }
356
357    #[test]
358    fn test_load_from_string() {
359        let grl = r#"
360        rule "Rule1" {
361            when
362                Person.age > 18
363            then
364                Person.is_adult = true;
365        }
366
367        rule "Rule2" {
368            when
369                Order.amount > 1000
370            then
371                Order.high_value = true;
372        }
373        "#;
374
375        let mut engine = IncrementalEngine::new();
376        let count = GrlReteLoader::load_from_string(grl, &mut engine).unwrap();
377
378        assert_eq!(count, 2);
379    }
380}