rust_rule_engine/parser/
grl.rs

1use crate::engine::module::{ExportItem, ExportList, ImportType, ItemType, ModuleManager};
2use crate::engine::rule::{Condition, ConditionGroup, Rule};
3use crate::errors::{Result, RuleEngineError};
4use crate::types::{ActionType, Operator, Value};
5use chrono::{DateTime, Utc};
6use regex::Regex;
7use std::collections::HashMap;
8use std::sync::OnceLock;
9
10// Stream syntax parser module
11#[cfg(feature = "streaming")]
12pub mod stream_syntax;
13
14// Cached main regexes - compiled once at startup
15static RULE_REGEX: OnceLock<Regex> = OnceLock::new();
16static RULE_SPLIT_REGEX: OnceLock<Regex> = OnceLock::new();
17static DEFMODULE_REGEX: OnceLock<Regex> = OnceLock::new();
18static DEFMODULE_SPLIT_REGEX: OnceLock<Regex> = OnceLock::new();
19static WHEN_THEN_REGEX: OnceLock<Regex> = OnceLock::new();
20static SALIENCE_REGEX: OnceLock<Regex> = OnceLock::new();
21static TEST_CONDITION_REGEX: OnceLock<Regex> = OnceLock::new();
22static TYPED_TEST_CONDITION_REGEX: OnceLock<Regex> = OnceLock::new();
23static FUNCTION_CALL_REGEX: OnceLock<Regex> = OnceLock::new();
24static CONDITION_REGEX: OnceLock<Regex> = OnceLock::new();
25static METHOD_CALL_REGEX: OnceLock<Regex> = OnceLock::new();
26static FUNCTION_BINDING_REGEX: OnceLock<Regex> = OnceLock::new();
27static MULTIFIELD_COLLECT_REGEX: OnceLock<Regex> = OnceLock::new();
28static MULTIFIELD_COUNT_REGEX: OnceLock<Regex> = OnceLock::new();
29static MULTIFIELD_FIRST_REGEX: OnceLock<Regex> = OnceLock::new();
30static MULTIFIELD_LAST_REGEX: OnceLock<Regex> = OnceLock::new();
31static MULTIFIELD_EMPTY_REGEX: OnceLock<Regex> = OnceLock::new();
32static MULTIFIELD_NOT_EMPTY_REGEX: OnceLock<Regex> = OnceLock::new();
33static SIMPLE_CONDITION_REGEX: OnceLock<Regex> = OnceLock::new();
34
35// Helper functions to get or initialize regexes
36fn rule_regex() -> &'static Regex {
37    RULE_REGEX.get_or_init(|| {
38        Regex::new(r#"rule\s+(?:"([^"]+)"|([a-zA-Z_]\w*))\s*([^{]*)\{(.+)\}"#)
39            .expect("Invalid rule regex pattern")
40    })
41}
42
43fn rule_split_regex() -> &'static Regex {
44    RULE_SPLIT_REGEX.get_or_init(|| {
45        Regex::new(r#"(?s)rule\s+(?:"[^"]+"|[a-zA-Z_]\w*).*?\}"#)
46            .expect("Invalid rule split regex pattern")
47    })
48}
49
50fn defmodule_regex() -> &'static Regex {
51    DEFMODULE_REGEX.get_or_init(|| {
52        Regex::new(r#"defmodule\s+([A-Z_]\w*)\s*\{([^}]*)\}"#)
53            .expect("Invalid defmodule regex pattern")
54    })
55}
56
57fn defmodule_split_regex() -> &'static Regex {
58    DEFMODULE_SPLIT_REGEX.get_or_init(|| {
59        Regex::new(r#"(?s)defmodule\s+[A-Z_]\w*\s*\{[^}]*\}"#)
60            .expect("Invalid defmodule split regex pattern")
61    })
62}
63
64fn when_then_regex() -> &'static Regex {
65    WHEN_THEN_REGEX.get_or_init(|| {
66        Regex::new(r"when\s+(.+?)\s+then\s+(.+)").expect("Invalid when-then regex pattern")
67    })
68}
69
70fn salience_regex() -> &'static Regex {
71    SALIENCE_REGEX
72        .get_or_init(|| Regex::new(r"salience\s+(\d+)").expect("Invalid salience regex pattern"))
73}
74
75fn test_condition_regex() -> &'static Regex {
76    TEST_CONDITION_REGEX.get_or_init(|| {
77        Regex::new(r#"^test\s*\(\s*([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*\)$"#)
78            .expect("Invalid test condition regex")
79    })
80}
81
82fn typed_test_condition_regex() -> &'static Regex {
83    TYPED_TEST_CONDITION_REGEX.get_or_init(|| {
84        Regex::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#)
85            .expect("Invalid typed test condition regex")
86    })
87}
88
89fn function_call_regex() -> &'static Regex {
90    FUNCTION_CALL_REGEX.get_or_init(|| {
91        Regex::new(r#"([a-zA-Z_]\w*)\s*\(([^)]*)\)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#)
92            .expect("Invalid function call regex")
93    })
94}
95
96fn condition_regex() -> &'static Regex {
97    CONDITION_REGEX.get_or_init(|| {
98        Regex::new(r#"([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*(?:\s*[+\-*/%]\s*[a-zA-Z0-9_\.]+)*)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#)
99            .expect("Invalid condition regex")
100    })
101}
102
103fn method_call_regex() -> &'static Regex {
104    METHOD_CALL_REGEX.get_or_init(|| {
105        Regex::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#).expect("Invalid method call regex")
106    })
107}
108
109fn function_binding_regex() -> &'static Regex {
110    FUNCTION_BINDING_REGEX.get_or_init(|| {
111        Regex::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#).expect("Invalid function binding regex")
112    })
113}
114
115fn multifield_collect_regex() -> &'static Regex {
116    MULTIFIELD_COLLECT_REGEX.get_or_init(|| {
117        Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+(\$\?[a-zA-Z_]\w*)$"#)
118            .expect("Invalid multifield collect regex")
119    })
120}
121
122fn multifield_count_regex() -> &'static Regex {
123    MULTIFIELD_COUNT_REGEX.get_or_init(|| {
124        Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+count\s*(>=|<=|==|!=|>|<)\s*(.+)$"#)
125            .expect("Invalid multifield count regex")
126    })
127}
128
129fn multifield_first_regex() -> &'static Regex {
130    MULTIFIELD_FIRST_REGEX.get_or_init(|| {
131        Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+first(?:\s+(\$[a-zA-Z_]\w*))?$"#)
132            .expect("Invalid multifield first regex")
133    })
134}
135
136fn multifield_last_regex() -> &'static Regex {
137    MULTIFIELD_LAST_REGEX.get_or_init(|| {
138        Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+last(?:\s+(\$[a-zA-Z_]\w*))?$"#)
139            .expect("Invalid multifield last regex")
140    })
141}
142
143fn multifield_empty_regex() -> &'static Regex {
144    MULTIFIELD_EMPTY_REGEX.get_or_init(|| {
145        Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+empty$"#)
146            .expect("Invalid multifield empty regex")
147    })
148}
149
150fn multifield_not_empty_regex() -> &'static Regex {
151    MULTIFIELD_NOT_EMPTY_REGEX.get_or_init(|| {
152        Regex::new(r#"^([a-zA-Z_]\w*\.[a-zA-Z_]\w*)\s+not_empty$"#)
153            .expect("Invalid multifield not_empty regex")
154    })
155}
156
157fn simple_condition_regex() -> &'static Regex {
158    SIMPLE_CONDITION_REGEX.get_or_init(|| {
159        Regex::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#).expect("Invalid simple condition regex")
160    })
161}
162
163/// GRL (Grule Rule Language) Parser
164/// Parses Grule-like syntax into Rule objects
165pub struct GRLParser;
166
167/// Parsed rule attributes from GRL header
168#[derive(Debug, Default)]
169struct RuleAttributes {
170    pub no_loop: bool,
171    pub lock_on_active: bool,
172    pub agenda_group: Option<String>,
173    pub activation_group: Option<String>,
174    pub date_effective: Option<DateTime<Utc>>,
175    pub date_expires: Option<DateTime<Utc>>,
176}
177
178/// Result from parsing GRL with modules
179#[derive(Debug, Clone)]
180pub struct ParsedGRL {
181    /// Parsed rules
182    pub rules: Vec<Rule>,
183    /// Module manager with configured modules
184    pub module_manager: ModuleManager,
185    /// Map of rule name to module name
186    pub rule_modules: HashMap<String, String>,
187}
188
189impl Default for ParsedGRL {
190    fn default() -> Self {
191        Self::new()
192    }
193}
194
195impl ParsedGRL {
196    pub fn new() -> Self {
197        Self {
198            rules: Vec::new(),
199            module_manager: ModuleManager::new(),
200            rule_modules: HashMap::new(),
201        }
202    }
203}
204
205impl GRLParser {
206    /// Parse a single rule from GRL syntax
207    ///
208    /// Example GRL syntax:
209    /// ```grl
210    /// rule CheckAge "Age verification rule" salience 10 {
211    ///     when
212    ///         User.Age >= 18 && User.Country == "US"
213    ///     then
214    ///         User.IsAdult = true;
215    ///         Retract("User");
216    /// }
217    /// ```
218    pub fn parse_rule(grl_text: &str) -> Result<Rule> {
219        let mut parser = GRLParser;
220        parser.parse_single_rule(grl_text)
221    }
222
223    /// Parse multiple rules from GRL text
224    pub fn parse_rules(grl_text: &str) -> Result<Vec<Rule>> {
225        let mut parser = GRLParser;
226        parser.parse_multiple_rules(grl_text)
227    }
228
229    /// Parse GRL text with module support
230    ///
231    /// Example:
232    /// ```grl
233    /// defmodule SENSORS {
234    ///   export: all
235    /// }
236    ///
237    /// defmodule CONTROL {
238    ///   import: SENSORS (rules * (templates temperature))
239    /// }
240    ///
241    /// rule "CheckTemp" {
242    ///   when temperature.value > 28
243    ///   then println("Hot");
244    /// }
245    /// ```
246    pub fn parse_with_modules(grl_text: &str) -> Result<ParsedGRL> {
247        let mut parser = GRLParser;
248        parser.parse_grl_with_modules(grl_text)
249    }
250
251    fn parse_grl_with_modules(&mut self, grl_text: &str) -> Result<ParsedGRL> {
252        let mut result = ParsedGRL::new();
253
254        // First, parse and register all modules
255        for module_match in defmodule_split_regex().find_iter(grl_text) {
256            let module_def = module_match.as_str();
257            self.parse_and_register_module(module_def, &mut result.module_manager)?;
258        }
259
260        // Remove all defmodule blocks from text before parsing rules
261        let rules_text = defmodule_split_regex().replace_all(grl_text, "");
262
263        // Then parse all rules from cleaned text
264        let rules = self.parse_multiple_rules(&rules_text)?;
265
266        // Try to assign rules to modules based on comments
267        for rule in rules {
268            let module_name = self.extract_module_from_context(grl_text, &rule.name);
269            result
270                .rule_modules
271                .insert(rule.name.clone(), module_name.clone());
272
273            // Add rule to module in manager
274            if let Ok(module) = result.module_manager.get_module_mut(&module_name) {
275                module.add_rule(&rule.name);
276            }
277
278            result.rules.push(rule);
279        }
280
281        Ok(result)
282    }
283
284    fn parse_and_register_module(
285        &self,
286        module_def: &str,
287        manager: &mut ModuleManager,
288    ) -> Result<()> {
289        // Parse: defmodule MODULE_NAME { export: all/none, import: ... }
290        if let Some(captures) = defmodule_regex().captures(module_def) {
291            let module_name = captures.get(1).unwrap().as_str().to_string();
292            let module_body = captures.get(2).unwrap().as_str();
293
294            // Create module (ignore if already exists)
295            let _ = manager.create_module(&module_name);
296            let module = manager.get_module_mut(&module_name)?;
297
298            // Parse export directive
299            if let Some(export_type) = self.extract_directive(module_body, "export:") {
300                let exports = if export_type.trim() == "all" {
301                    ExportList::All
302                } else if export_type.trim() == "none" {
303                    ExportList::None
304                } else {
305                    // Parse pattern-based exports
306                    ExportList::Specific(vec![ExportItem {
307                        item_type: ItemType::All,
308                        pattern: export_type.trim().to_string(),
309                    }])
310                };
311                module.set_exports(exports);
312            }
313
314            // Parse import directives
315            let import_lines: Vec<&str> = module_body
316                .lines()
317                .filter(|line| line.trim().starts_with("import:"))
318                .collect();
319
320            for import_line in import_lines {
321                if let Some(import_spec) = self.extract_directive(import_line, "import:") {
322                    // Parse: "MODULE_A (rules * (templates foo))"
323                    self.parse_import_spec(&module_name, &import_spec, manager)?;
324                }
325            }
326        }
327
328        Ok(())
329    }
330
331    fn extract_directive(&self, text: &str, directive: &str) -> Option<String> {
332        if let Some(pos) = text.find(directive) {
333            let after_directive = &text[pos + directive.len()..];
334
335            // Find the end of the directive (next directive, or end of block)
336            let end = after_directive
337                .find("import:")
338                .or_else(|| after_directive.find("export:"))
339                .unwrap_or(after_directive.len());
340
341            Some(after_directive[..end].trim().to_string())
342        } else {
343            None
344        }
345    }
346
347    fn parse_import_spec(
348        &self,
349        importing_module: &str,
350        spec: &str,
351        manager: &mut ModuleManager,
352    ) -> Result<()> {
353        // Parse: "SENSORS (rules * (templates temperature))"
354        let parts: Vec<&str> = spec.splitn(2, '(').collect();
355        if parts.is_empty() {
356            return Ok(());
357        }
358
359        let source_module = parts[0].trim().to_string();
360        let rest = if parts.len() > 1 { parts[1] } else { "" };
361
362        // Check if we're importing rules or templates
363        if rest.contains("rules") {
364            manager.import_from(importing_module, &source_module, ImportType::AllRules, "*")?;
365        }
366
367        if rest.contains("templates") {
368            manager.import_from(
369                importing_module,
370                &source_module,
371                ImportType::AllTemplates,
372                "*",
373            )?;
374        }
375
376        Ok(())
377    }
378
379    fn extract_module_from_context(&self, grl_text: &str, rule_name: &str) -> String {
380        // Look backward from rule to find the module comment
381        if let Some(rule_pos) = grl_text
382            .find(&format!("rule \"{}\"", rule_name))
383            .or_else(|| grl_text.find(&format!("rule {}", rule_name)))
384        {
385            // Look backward for ;; MODULE: comment
386            let before = &grl_text[..rule_pos];
387            if let Some(module_pos) = before.rfind(";; MODULE:") {
388                let after_module_marker = &before[module_pos + 10..];
389                if let Some(end_of_line) = after_module_marker.find('\n') {
390                    let module_line = &after_module_marker[..end_of_line].trim();
391                    // Extract module name from "SENSORS - Temperature Monitoring"
392                    if let Some(first_word) = module_line.split_whitespace().next() {
393                        return first_word.to_string();
394                    }
395                }
396            }
397        }
398
399        // Default to MAIN
400        "MAIN".to_string()
401    }
402
403    fn parse_single_rule(&mut self, grl_text: &str) -> Result<Rule> {
404        let cleaned = self.clean_text(grl_text);
405
406        // Extract rule components using cached regex
407        let captures =
408            rule_regex()
409                .captures(&cleaned)
410                .ok_or_else(|| RuleEngineError::ParseError {
411                    message: format!("Invalid GRL rule format. Input: {}", cleaned),
412                })?;
413
414        // Rule name can be either quoted (group 1) or unquoted (group 2)
415        let rule_name = if let Some(quoted_name) = captures.get(1) {
416            quoted_name.as_str().to_string()
417        } else if let Some(unquoted_name) = captures.get(2) {
418            unquoted_name.as_str().to_string()
419        } else {
420            return Err(RuleEngineError::ParseError {
421                message: "Could not extract rule name".to_string(),
422            });
423        };
424
425        // Attributes section (group 3)
426        let attributes_section = captures.get(3).map(|m| m.as_str()).unwrap_or("");
427
428        // Rule body (group 4)
429        let rule_body = captures.get(4).unwrap().as_str();
430
431        // Parse salience from attributes section
432        let salience = self.extract_salience(attributes_section)?;
433
434        // Parse when and then sections using cached regex
435        let when_then_captures =
436            when_then_regex()
437                .captures(rule_body)
438                .ok_or_else(|| RuleEngineError::ParseError {
439                    message: "Missing when or then clause".to_string(),
440                })?;
441
442        let when_clause = when_then_captures.get(1).unwrap().as_str().trim();
443        let then_clause = when_then_captures.get(2).unwrap().as_str().trim();
444
445        // Parse conditions and actions
446        let conditions = self.parse_when_clause(when_clause)?;
447        let actions = self.parse_then_clause(then_clause)?;
448
449        // Parse all attributes from rule header
450        let attributes = self.parse_rule_attributes(attributes_section)?;
451
452        // Build rule
453        let mut rule = Rule::new(rule_name, conditions, actions);
454        rule = rule.with_priority(salience);
455
456        // Apply parsed attributes
457        if attributes.no_loop {
458            rule = rule.with_no_loop(true);
459        }
460        if attributes.lock_on_active {
461            rule = rule.with_lock_on_active(true);
462        }
463        if let Some(agenda_group) = attributes.agenda_group {
464            rule = rule.with_agenda_group(agenda_group);
465        }
466        if let Some(activation_group) = attributes.activation_group {
467            rule = rule.with_activation_group(activation_group);
468        }
469        if let Some(date_effective) = attributes.date_effective {
470            rule = rule.with_date_effective(date_effective);
471        }
472        if let Some(date_expires) = attributes.date_expires {
473            rule = rule.with_date_expires(date_expires);
474        }
475
476        Ok(rule)
477    }
478
479    fn parse_multiple_rules(&mut self, grl_text: &str) -> Result<Vec<Rule>> {
480        // Split by rule boundaries - support both quoted and unquoted rule names
481        // Use DOTALL flag to match newlines in rule body
482        let mut rules = Vec::new();
483
484        for rule_match in rule_split_regex().find_iter(grl_text) {
485            let rule_text = rule_match.as_str();
486            let rule = self.parse_single_rule(rule_text)?;
487            rules.push(rule);
488        }
489
490        Ok(rules)
491    }
492
493    /// Parse rule attributes from the rule header
494    fn parse_rule_attributes(&self, rule_header: &str) -> Result<RuleAttributes> {
495        let mut attributes = RuleAttributes::default();
496
497        // Extract the attributes section (after rule name/description, before opening brace)
498        // This ensures we don't match keywords inside description strings
499        // Strategy: Find all quoted strings and remove them, then check for attributes
500        let mut attrs_section = rule_header.to_string();
501
502        // Remove all quoted strings (descriptions) to avoid false matches
503        let quoted_regex = Regex::new(r#""[^"]*""#).map_err(|e| RuleEngineError::ParseError {
504            message: format!("Invalid quoted string regex: {}", e),
505        })?;
506        attrs_section = quoted_regex.replace_all(&attrs_section, "").to_string();
507
508        // Also remove the "rule" keyword and rule name (if unquoted)
509        if let Some(rule_pos) = attrs_section.find("rule") {
510            // Find the next space or attribute keyword after "rule"
511            let after_rule = &attrs_section[rule_pos + 4..];
512            if let Some(first_keyword) = after_rule
513                .find("salience")
514                .or_else(|| after_rule.find("no-loop"))
515                .or_else(|| after_rule.find("lock-on-active"))
516                .or_else(|| after_rule.find("agenda-group"))
517                .or_else(|| after_rule.find("activation-group"))
518                .or_else(|| after_rule.find("date-effective"))
519                .or_else(|| after_rule.find("date-expires"))
520            {
521                attrs_section = after_rule[first_keyword..].to_string();
522            }
523        }
524
525        // Now check for boolean attributes using word boundaries
526        let no_loop_regex =
527            Regex::new(r"\bno-loop\b").map_err(|e| RuleEngineError::ParseError {
528                message: format!("Invalid no-loop regex: {}", e),
529            })?;
530        let lock_on_active_regex =
531            Regex::new(r"\block-on-active\b").map_err(|e| RuleEngineError::ParseError {
532                message: format!("Invalid lock-on-active regex: {}", e),
533            })?;
534
535        if no_loop_regex.is_match(&attrs_section) {
536            attributes.no_loop = true;
537        }
538        if lock_on_active_regex.is_match(&attrs_section) {
539            attributes.lock_on_active = true;
540        }
541
542        // Parse agenda-group attribute
543        if let Some(agenda_group) = self.extract_quoted_attribute(rule_header, "agenda-group")? {
544            attributes.agenda_group = Some(agenda_group);
545        }
546
547        // Parse activation-group attribute
548        if let Some(activation_group) =
549            self.extract_quoted_attribute(rule_header, "activation-group")?
550        {
551            attributes.activation_group = Some(activation_group);
552        }
553
554        // Parse date-effective attribute
555        if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-effective")? {
556            attributes.date_effective = Some(self.parse_date_string(&date_str)?);
557        }
558
559        // Parse date-expires attribute
560        if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-expires")? {
561            attributes.date_expires = Some(self.parse_date_string(&date_str)?);
562        }
563
564        Ok(attributes)
565    }
566
567    /// Extract quoted attribute value from rule header
568    fn extract_quoted_attribute(&self, header: &str, attribute: &str) -> Result<Option<String>> {
569        let pattern = format!(r#"{}\s+"([^"]+)""#, attribute);
570        let regex = Regex::new(&pattern).map_err(|e| RuleEngineError::ParseError {
571            message: format!("Invalid attribute regex for {}: {}", attribute, e),
572        })?;
573
574        if let Some(captures) = regex.captures(header) {
575            if let Some(value) = captures.get(1) {
576                return Ok(Some(value.as_str().to_string()));
577            }
578        }
579
580        Ok(None)
581    }
582
583    /// Parse date string in various formats
584    fn parse_date_string(&self, date_str: &str) -> Result<DateTime<Utc>> {
585        // Try ISO 8601 format first
586        if let Ok(date) = DateTime::parse_from_rfc3339(date_str) {
587            return Ok(date.with_timezone(&Utc));
588        }
589
590        // Try simple date formats
591        let formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%d-%b-%Y", "%d-%m-%Y"];
592
593        for format in &formats {
594            if let Ok(naive_date) = chrono::NaiveDateTime::parse_from_str(date_str, format) {
595                return Ok(naive_date.and_utc());
596            }
597            if let Ok(naive_date) = chrono::NaiveDate::parse_from_str(date_str, format) {
598                return Ok(naive_date.and_hms_opt(0, 0, 0).unwrap().and_utc());
599            }
600        }
601
602        Err(RuleEngineError::ParseError {
603            message: format!("Unable to parse date: {}", date_str),
604        })
605    }
606
607    /// Extract salience value from attributes section
608    fn extract_salience(&self, attributes_section: &str) -> Result<i32> {
609        if let Some(captures) = salience_regex().captures(attributes_section) {
610            if let Some(salience_match) = captures.get(1) {
611                return salience_match.as_str().parse::<i32>().map_err(|e| {
612                    RuleEngineError::ParseError {
613                        message: format!("Invalid salience value: {}", e),
614                    }
615                });
616            }
617        }
618
619        Ok(0) // Default salience
620    }
621
622    fn clean_text(&self, text: &str) -> String {
623        text.lines()
624            .map(|line| line.trim())
625            .filter(|line| !line.is_empty() && !line.starts_with("//"))
626            .collect::<Vec<_>>()
627            .join(" ")
628    }
629
630    fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
631        // Handle logical operators with proper parentheses support
632        let trimmed = when_clause.trim();
633
634        // Strip outer parentheses if they exist
635        let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
636            // Check if these are the outermost parentheses
637            let inner = &trimmed[1..trimmed.len() - 1];
638            if self.is_balanced_parentheses(inner) {
639                inner
640            } else {
641                trimmed
642            }
643        } else {
644            trimmed
645        };
646
647        // Parse OR at the top level (lowest precedence)
648        if let Some(parts) = self.split_logical_operator(clause, "||") {
649            return self.parse_or_parts(parts);
650        }
651
652        // Parse AND (higher precedence)
653        if let Some(parts) = self.split_logical_operator(clause, "&&") {
654            return self.parse_and_parts(parts);
655        }
656
657        // Handle NOT condition
658        if clause.trim_start().starts_with("!") {
659            return self.parse_not_condition(clause);
660        }
661
662        // Handle EXISTS condition
663        if clause.trim_start().starts_with("exists(") {
664            return self.parse_exists_condition(clause);
665        }
666
667        // Handle FORALL condition
668        if clause.trim_start().starts_with("forall(") {
669            return self.parse_forall_condition(clause);
670        }
671
672        // Handle ACCUMULATE condition
673        if clause.trim_start().starts_with("accumulate(") {
674            return self.parse_accumulate_condition(clause);
675        }
676
677        // Single condition
678        self.parse_single_condition(clause)
679    }
680
681    fn is_balanced_parentheses(&self, text: &str) -> bool {
682        let mut count = 0;
683        for ch in text.chars() {
684            match ch {
685                '(' => count += 1,
686                ')' => {
687                    count -= 1;
688                    if count < 0 {
689                        return false;
690                    }
691                }
692                _ => {}
693            }
694        }
695        count == 0
696    }
697
698    fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
699        let mut parts = Vec::new();
700        let mut current_part = String::new();
701        let mut paren_count = 0;
702        let mut chars = clause.chars().peekable();
703
704        while let Some(ch) = chars.next() {
705            match ch {
706                '(' => {
707                    paren_count += 1;
708                    current_part.push(ch);
709                }
710                ')' => {
711                    paren_count -= 1;
712                    current_part.push(ch);
713                }
714                '&' if operator == "&&" && paren_count == 0 => {
715                    if chars.peek() == Some(&'&') {
716                        chars.next(); // consume second &
717                        parts.push(current_part.trim().to_string());
718                        current_part.clear();
719                    } else {
720                        current_part.push(ch);
721                    }
722                }
723                '|' if operator == "||" && paren_count == 0 => {
724                    if chars.peek() == Some(&'|') {
725                        chars.next(); // consume second |
726                        parts.push(current_part.trim().to_string());
727                        current_part.clear();
728                    } else {
729                        current_part.push(ch);
730                    }
731                }
732                _ => {
733                    current_part.push(ch);
734                }
735            }
736        }
737
738        if !current_part.trim().is_empty() {
739            parts.push(current_part.trim().to_string());
740        }
741
742        if parts.len() > 1 {
743            Some(parts)
744        } else {
745            None
746        }
747    }
748
749    fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
750        let mut conditions = Vec::new();
751        for part in parts {
752            let condition = self.parse_when_clause(&part)?;
753            conditions.push(condition);
754        }
755
756        if conditions.is_empty() {
757            return Err(RuleEngineError::ParseError {
758                message: "No conditions found in OR".to_string(),
759            });
760        }
761
762        let mut iter = conditions.into_iter();
763        let mut result = iter.next().unwrap();
764        for condition in iter {
765            result = ConditionGroup::or(result, condition);
766        }
767
768        Ok(result)
769    }
770
771    fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
772        let mut conditions = Vec::new();
773        for part in parts {
774            let condition = self.parse_when_clause(&part)?;
775            conditions.push(condition);
776        }
777
778        if conditions.is_empty() {
779            return Err(RuleEngineError::ParseError {
780                message: "No conditions found in AND".to_string(),
781            });
782        }
783
784        let mut iter = conditions.into_iter();
785        let mut result = iter.next().unwrap();
786        for condition in iter {
787            result = ConditionGroup::and(result, condition);
788        }
789
790        Ok(result)
791    }
792
793    fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
794        let inner_clause = clause.strip_prefix("!").unwrap().trim();
795        let inner_condition = self.parse_when_clause(inner_clause)?;
796        Ok(ConditionGroup::not(inner_condition))
797    }
798
799    fn parse_exists_condition(&self, clause: &str) -> Result<ConditionGroup> {
800        let clause = clause.trim_start();
801        if !clause.starts_with("exists(") || !clause.ends_with(")") {
802            return Err(RuleEngineError::ParseError {
803                message: "Invalid exists syntax. Expected: exists(condition)".to_string(),
804            });
805        }
806
807        // Extract content between parentheses
808        let inner_clause = &clause[7..clause.len() - 1]; // Remove "exists(" and ")"
809        let inner_condition = self.parse_when_clause(inner_clause)?;
810        Ok(ConditionGroup::exists(inner_condition))
811    }
812
813    fn parse_forall_condition(&self, clause: &str) -> Result<ConditionGroup> {
814        let clause = clause.trim_start();
815        if !clause.starts_with("forall(") || !clause.ends_with(")") {
816            return Err(RuleEngineError::ParseError {
817                message: "Invalid forall syntax. Expected: forall(condition)".to_string(),
818            });
819        }
820
821        // Extract content between parentheses
822        let inner_clause = &clause[7..clause.len() - 1]; // Remove "forall(" and ")"
823        let inner_condition = self.parse_when_clause(inner_clause)?;
824        Ok(ConditionGroup::forall(inner_condition))
825    }
826
827    fn parse_accumulate_condition(&self, clause: &str) -> Result<ConditionGroup> {
828        let clause = clause.trim_start();
829        if !clause.starts_with("accumulate(") || !clause.ends_with(")") {
830            return Err(RuleEngineError::ParseError {
831                message: "Invalid accumulate syntax. Expected: accumulate(pattern, function)"
832                    .to_string(),
833            });
834        }
835
836        // Extract content between parentheses
837        let inner = &clause[11..clause.len() - 1]; // Remove "accumulate(" and ")"
838
839        // Split by comma at the top level (not inside parentheses)
840        let parts = self.split_accumulate_parts(inner)?;
841
842        if parts.len() != 2 {
843            return Err(RuleEngineError::ParseError {
844                message: format!(
845                    "Invalid accumulate syntax. Expected 2 parts (pattern, function), got {}",
846                    parts.len()
847                ),
848            });
849        }
850
851        let pattern_part = parts[0].trim();
852        let function_part = parts[1].trim();
853
854        // Parse the pattern: Order($amount: amount, status == "completed")
855        let (source_pattern, extract_field, source_conditions) =
856            self.parse_accumulate_pattern(pattern_part)?;
857
858        // Parse the function: sum($amount)
859        let (function, function_arg) = self.parse_accumulate_function(function_part)?;
860
861        // For now, we'll create a placeholder result variable
862        // In a full implementation, this would be extracted from the parent context
863        // e.g., from "$total: accumulate(...)"
864        let result_var = "$result".to_string();
865
866        Ok(ConditionGroup::accumulate(
867            result_var,
868            source_pattern,
869            extract_field,
870            source_conditions,
871            function,
872            function_arg,
873        ))
874    }
875
876    fn split_accumulate_parts(&self, content: &str) -> Result<Vec<String>> {
877        let mut parts = Vec::new();
878        let mut current = String::new();
879        let mut paren_depth = 0;
880
881        for ch in content.chars() {
882            match ch {
883                '(' => {
884                    paren_depth += 1;
885                    current.push(ch);
886                }
887                ')' => {
888                    paren_depth -= 1;
889                    current.push(ch);
890                }
891                ',' if paren_depth == 0 => {
892                    parts.push(current.trim().to_string());
893                    current.clear();
894                }
895                _ => {
896                    current.push(ch);
897                }
898            }
899        }
900
901        if !current.trim().is_empty() {
902            parts.push(current.trim().to_string());
903        }
904
905        Ok(parts)
906    }
907
908    fn parse_accumulate_pattern(&self, pattern: &str) -> Result<(String, String, Vec<String>)> {
909        // Pattern format: Order($amount: amount, status == "completed", category == "electronics")
910        // We need to extract:
911        // - source_pattern: "Order"
912        // - extract_field: "amount" (from $amount: amount)
913        // - source_conditions: ["status == \"completed\"", "category == \"electronics\""]
914
915        let pattern = pattern.trim();
916
917        // Find the opening parenthesis to get the pattern type
918        let paren_pos = pattern
919            .find('(')
920            .ok_or_else(|| RuleEngineError::ParseError {
921                message: format!("Invalid accumulate pattern: missing '(' in '{}'", pattern),
922            })?;
923
924        let source_pattern = pattern[..paren_pos].trim().to_string();
925
926        // Extract content between parentheses
927        if !pattern.ends_with(')') {
928            return Err(RuleEngineError::ParseError {
929                message: format!("Invalid accumulate pattern: missing ')' in '{}'", pattern),
930            });
931        }
932
933        let inner = &pattern[paren_pos + 1..pattern.len() - 1];
934
935        // Split by comma (respecting nested parentheses and quotes)
936        let parts = self.split_pattern_parts(inner)?;
937
938        let mut extract_field = String::new();
939        let mut source_conditions = Vec::new();
940
941        for part in parts {
942            let part = part.trim();
943
944            // Check if this is a variable binding: $var: field
945            if part.contains(':') && part.starts_with('$') {
946                let colon_pos = part.find(':').unwrap();
947                let _var_name = part[..colon_pos].trim();
948                let field_name = part[colon_pos + 1..].trim();
949                extract_field = field_name.to_string();
950            } else if part.contains("==")
951                || part.contains("!=")
952                || part.contains(">=")
953                || part.contains("<=")
954                || part.contains('>')
955                || part.contains('<')
956            {
957                // This is a condition
958                source_conditions.push(part.to_string());
959            }
960        }
961
962        Ok((source_pattern, extract_field, source_conditions))
963    }
964
965    fn split_pattern_parts(&self, content: &str) -> Result<Vec<String>> {
966        let mut parts = Vec::new();
967        let mut current = String::new();
968        let mut paren_depth = 0;
969        let mut in_quotes = false;
970        let mut quote_char = ' ';
971
972        for ch in content.chars() {
973            match ch {
974                '"' | '\'' if !in_quotes => {
975                    in_quotes = true;
976                    quote_char = ch;
977                    current.push(ch);
978                }
979                '"' | '\'' if in_quotes && ch == quote_char => {
980                    in_quotes = false;
981                    current.push(ch);
982                }
983                '(' if !in_quotes => {
984                    paren_depth += 1;
985                    current.push(ch);
986                }
987                ')' if !in_quotes => {
988                    paren_depth -= 1;
989                    current.push(ch);
990                }
991                ',' if !in_quotes && paren_depth == 0 => {
992                    parts.push(current.trim().to_string());
993                    current.clear();
994                }
995                _ => {
996                    current.push(ch);
997                }
998            }
999        }
1000
1001        if !current.trim().is_empty() {
1002            parts.push(current.trim().to_string());
1003        }
1004
1005        Ok(parts)
1006    }
1007
1008    fn parse_accumulate_function(&self, function_str: &str) -> Result<(String, String)> {
1009        // Function format: sum($amount) or count() or average($price)
1010
1011        let function_str = function_str.trim();
1012
1013        let paren_pos = function_str
1014            .find('(')
1015            .ok_or_else(|| RuleEngineError::ParseError {
1016                message: format!(
1017                    "Invalid accumulate function: missing '(' in '{}'",
1018                    function_str
1019                ),
1020            })?;
1021
1022        let function_name = function_str[..paren_pos].trim().to_string();
1023
1024        if !function_str.ends_with(')') {
1025            return Err(RuleEngineError::ParseError {
1026                message: format!(
1027                    "Invalid accumulate function: missing ')' in '{}'",
1028                    function_str
1029                ),
1030            });
1031        }
1032
1033        let args = &function_str[paren_pos + 1..function_str.len() - 1];
1034        let function_arg = args.trim().to_string();
1035
1036        Ok((function_name, function_arg))
1037    }
1038
1039    fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
1040        // Remove outer parentheses if they exist (handle new syntax like "(user.age >= 18)")
1041        let trimmed_clause = clause.trim();
1042        let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
1043            trimmed_clause[1..trimmed_clause.len() - 1].trim()
1044        } else {
1045            trimmed_clause
1046        };
1047
1048        // === STREAM PATTERNS ===
1049        // Check for stream pattern syntax: "var: Type from stream(...)"
1050        #[cfg(feature = "streaming")]
1051        if clause_to_parse.contains("from stream(") {
1052            return self.parse_stream_pattern_condition(clause_to_parse);
1053        }
1054
1055        // === MULTI-FIELD PATTERNS ===
1056        // Handle multi-field patterns before other patterns
1057        // These must be checked first to avoid conflict with standard patterns
1058
1059        // Pattern 1: Field.array $?var (Collect operation with variable binding)
1060        // Example: Order.items $?all_items
1061        if let Some(captures) = multifield_collect_regex().captures(clause_to_parse) {
1062            let field = captures.get(1).unwrap().as_str().to_string();
1063            let variable = captures.get(2).unwrap().as_str().to_string();
1064
1065            // Create a multifield Collect condition
1066            // Note: This will need to be handled by the engine
1067            let condition = Condition::with_multifield_collect(field, variable);
1068            return Ok(ConditionGroup::single(condition));
1069        }
1070
1071        // Pattern 2: Field.array contains "value"
1072        // Example: Product.tags contains "electronics"
1073        // This is already handled by the standard regex, but we need to distinguish array contains
1074
1075        // Pattern 3: Field.array count operator value
1076        // Example: Order.items count > 0, Order.items count >= 5
1077        if let Some(captures) = multifield_count_regex().captures(clause_to_parse) {
1078            let field = captures.get(1).unwrap().as_str().to_string();
1079            let operator_str = captures.get(2).unwrap().as_str();
1080            let value_str = captures.get(3).unwrap().as_str().trim();
1081
1082            let operator = Operator::from_str(operator_str).ok_or_else(|| {
1083                RuleEngineError::InvalidOperator {
1084                    operator: operator_str.to_string(),
1085                }
1086            })?;
1087
1088            let value = self.parse_value(value_str)?;
1089
1090            let condition = Condition::with_multifield_count(field, operator, value);
1091            return Ok(ConditionGroup::single(condition));
1092        }
1093
1094        // Pattern 4: Field.array first [optional: $var or operator value]
1095        // Example: Queue.tasks first, Queue.tasks first $first_task
1096        if let Some(captures) = multifield_first_regex().captures(clause_to_parse) {
1097            let field = captures.get(1).unwrap().as_str().to_string();
1098            let variable = captures.get(2).map(|m| m.as_str().to_string());
1099
1100            let condition = Condition::with_multifield_first(field, variable);
1101            return Ok(ConditionGroup::single(condition));
1102        }
1103
1104        // Pattern 5: Field.array last [optional: $var]
1105        // Example: Queue.tasks last, Queue.tasks last $last_task
1106        if let Some(captures) = multifield_last_regex().captures(clause_to_parse) {
1107            let field = captures.get(1).unwrap().as_str().to_string();
1108            let variable = captures.get(2).map(|m| m.as_str().to_string());
1109
1110            let condition = Condition::with_multifield_last(field, variable);
1111            return Ok(ConditionGroup::single(condition));
1112        }
1113
1114        // Pattern 6: Field.array empty
1115        // Example: ShoppingCart.items empty
1116        if let Some(captures) = multifield_empty_regex().captures(clause_to_parse) {
1117            let field = captures.get(1).unwrap().as_str().to_string();
1118
1119            let condition = Condition::with_multifield_empty(field);
1120            return Ok(ConditionGroup::single(condition));
1121        }
1122
1123        // Pattern 7: Field.array not_empty
1124        // Example: ShoppingCart.items not_empty
1125        if let Some(captures) = multifield_not_empty_regex().captures(clause_to_parse) {
1126            let field = captures.get(1).unwrap().as_str().to_string();
1127
1128            let condition = Condition::with_multifield_not_empty(field);
1129            return Ok(ConditionGroup::single(condition));
1130        }
1131
1132        // === END MULTI-FIELD PATTERNS ===
1133
1134        // Handle Test CE: test(functionName(args...))
1135        // This is a CLIPS-inspired feature for arbitrary boolean expressions
1136        if let Some(captures) = test_condition_regex().captures(clause_to_parse) {
1137            let function_name = captures.get(1).unwrap().as_str().to_string();
1138            let args_str = captures.get(2).unwrap().as_str();
1139
1140            // Parse arguments
1141            let args: Vec<String> = if args_str.trim().is_empty() {
1142                Vec::new()
1143            } else {
1144                args_str
1145                    .split(',')
1146                    .map(|arg| arg.trim().to_string())
1147                    .collect()
1148            };
1149
1150            let condition = Condition::with_test(function_name, args);
1151            return Ok(ConditionGroup::single(condition));
1152        }
1153
1154        // Handle typed object conditions like: $TestCar : TestCarClass( speedUp == true && speed < maxSpeed )
1155        if let Some(captures) = typed_test_condition_regex().captures(clause_to_parse) {
1156            let _object_name = captures.get(1).unwrap().as_str();
1157            let _object_type = captures.get(2).unwrap().as_str();
1158            let conditions_str = captures.get(3).unwrap().as_str();
1159
1160            // Parse conditions inside parentheses
1161            return self.parse_conditions_within_object(conditions_str);
1162        }
1163
1164        // Try to parse function call pattern: functionName(arg1, arg2, ...) operator value
1165        if let Some(captures) = function_call_regex().captures(clause_to_parse) {
1166            let function_name = captures.get(1).unwrap().as_str().to_string();
1167            let args_str = captures.get(2).unwrap().as_str();
1168            let operator_str = captures.get(3).unwrap().as_str();
1169            let value_str = captures.get(4).unwrap().as_str().trim();
1170
1171            // Parse arguments
1172            let args: Vec<String> = if args_str.trim().is_empty() {
1173                Vec::new()
1174            } else {
1175                args_str
1176                    .split(',')
1177                    .map(|arg| arg.trim().to_string())
1178                    .collect()
1179            };
1180
1181            let operator = Operator::from_str(operator_str).ok_or_else(|| {
1182                RuleEngineError::InvalidOperator {
1183                    operator: operator_str.to_string(),
1184                }
1185            })?;
1186
1187            let value = self.parse_value(value_str)?;
1188
1189            let condition = Condition::with_function(function_name, args, operator, value);
1190            return Ok(ConditionGroup::single(condition));
1191        }
1192
1193        // Parse expressions like: User.Age >= 18, Product.Price < 100.0, user.age >= 18, etc.
1194        // Support both PascalCase (User.Age) and lowercase (user.age) field naming
1195        // Also support arithmetic expressions like: User.Age % 3 == 0, User.Price * 2 > 100
1196        let captures = condition_regex().captures(clause_to_parse).ok_or_else(|| {
1197            RuleEngineError::ParseError {
1198                message: format!("Invalid condition format: {}", clause_to_parse),
1199            }
1200        })?;
1201
1202        let left_side = captures.get(1).unwrap().as_str().trim().to_string();
1203        let operator_str = captures.get(2).unwrap().as_str();
1204        let value_str = captures.get(3).unwrap().as_str().trim();
1205
1206        let operator =
1207            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1208                operator: operator_str.to_string(),
1209            })?;
1210
1211        let value = self.parse_value(value_str)?;
1212
1213        // Check if left_side contains arithmetic operators - if yes, it's an expression
1214        if left_side.contains('+')
1215            || left_side.contains('-')
1216            || left_side.contains('*')
1217            || left_side.contains('/')
1218            || left_side.contains('%')
1219        {
1220            // This is an arithmetic expression - use Test CE
1221            // Format: test(left_side operator value)
1222            let test_expr = format!("{} {} {}", left_side, operator_str, value_str);
1223            let condition = Condition::with_test(test_expr, vec![]);
1224            Ok(ConditionGroup::single(condition))
1225        } else {
1226            // Simple field reference
1227            let condition = Condition::new(left_side, operator, value);
1228            Ok(ConditionGroup::single(condition))
1229        }
1230    }
1231
1232    fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
1233        // Parse conditions like: speedUp == true && speed < maxSpeed
1234        let parts: Vec<&str> = conditions_str.split("&&").collect();
1235
1236        let mut conditions = Vec::new();
1237        for part in parts {
1238            let trimmed = part.trim();
1239            let condition = self.parse_simple_condition(trimmed)?;
1240            conditions.push(condition);
1241        }
1242
1243        // Combine with AND
1244        if conditions.is_empty() {
1245            return Err(RuleEngineError::ParseError {
1246                message: "No conditions found".to_string(),
1247            });
1248        }
1249
1250        let mut iter = conditions.into_iter();
1251        let mut result = iter.next().unwrap();
1252        for condition in iter {
1253            result = ConditionGroup::and(result, condition);
1254        }
1255
1256        Ok(result)
1257    }
1258
1259    fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
1260        // Parse simple condition like: speedUp == true or speed < maxSpeed
1261        let captures = simple_condition_regex().captures(clause).ok_or_else(|| {
1262            RuleEngineError::ParseError {
1263                message: format!("Invalid simple condition format: {}", clause),
1264            }
1265        })?;
1266
1267        let field = captures.get(1).unwrap().as_str().to_string();
1268        let operator_str = captures.get(2).unwrap().as_str();
1269        let value_str = captures.get(3).unwrap().as_str().trim();
1270
1271        let operator =
1272            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
1273                operator: operator_str.to_string(),
1274            })?;
1275
1276        let value = self.parse_value(value_str)?;
1277
1278        let condition = Condition::new(field, operator, value);
1279        Ok(ConditionGroup::single(condition))
1280    }
1281
1282    fn parse_value(&self, value_str: &str) -> Result<Value> {
1283        let trimmed = value_str.trim();
1284
1285        // String literal
1286        if (trimmed.starts_with('"') && trimmed.ends_with('"'))
1287            || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
1288        {
1289            let unquoted = &trimmed[1..trimmed.len() - 1];
1290            return Ok(Value::String(unquoted.to_string()));
1291        }
1292
1293        // Boolean
1294        if trimmed.eq_ignore_ascii_case("true") {
1295            return Ok(Value::Boolean(true));
1296        }
1297        if trimmed.eq_ignore_ascii_case("false") {
1298            return Ok(Value::Boolean(false));
1299        }
1300
1301        // Null
1302        if trimmed.eq_ignore_ascii_case("null") {
1303            return Ok(Value::Null);
1304        }
1305
1306        // Number (try integer first, then float)
1307        if let Ok(int_val) = trimmed.parse::<i64>() {
1308            return Ok(Value::Integer(int_val));
1309        }
1310
1311        if let Ok(float_val) = trimmed.parse::<f64>() {
1312            return Ok(Value::Number(float_val));
1313        }
1314
1315        // Expression with arithmetic operators (e.g., "Order.quantity * Order.price")
1316        // Detect: contains operators AND (contains field reference OR multiple tokens)
1317        if self.is_expression(trimmed) {
1318            return Ok(Value::Expression(trimmed.to_string()));
1319        }
1320
1321        // Field reference (like User.Name)
1322        if trimmed.contains('.') {
1323            return Ok(Value::String(trimmed.to_string()));
1324        }
1325
1326        // Variable reference (identifier without quotes or dots)
1327        // This handles cases like: order_qty = moq
1328        // where 'moq' should be evaluated as a variable reference at runtime
1329        if self.is_identifier(trimmed) {
1330            return Ok(Value::Expression(trimmed.to_string()));
1331        }
1332
1333        // Default to string
1334        Ok(Value::String(trimmed.to_string()))
1335    }
1336
1337    /// Check if a string is a valid identifier (variable name)
1338    /// Valid identifiers: alphanumeric + underscore, starts with letter or underscore
1339    fn is_identifier(&self, s: &str) -> bool {
1340        if s.is_empty() {
1341            return false;
1342        }
1343
1344        // First character must be letter or underscore
1345        let first_char = s.chars().next().unwrap();
1346        if !first_char.is_alphabetic() && first_char != '_' {
1347            return false;
1348        }
1349
1350        // Rest must be alphanumeric or underscore
1351        s.chars().all(|c| c.is_alphanumeric() || c == '_')
1352    }
1353
1354    /// Check if a string is an arithmetic expression
1355    fn is_expression(&self, s: &str) -> bool {
1356        // Check for arithmetic operators
1357        let has_operator = s.contains('+')
1358            || s.contains('-')
1359            || s.contains('*')
1360            || s.contains('/')
1361            || s.contains('%');
1362
1363        // Check for field references (contains .)
1364        let has_field_ref = s.contains('.');
1365
1366        // Check for multiple tokens (spaces between operands/operators)
1367        let has_spaces = s.contains(' ');
1368
1369        // Expression if: has operator AND (has field reference OR has spaces)
1370        has_operator && (has_field_ref || has_spaces)
1371    }
1372
1373    fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
1374        let statements: Vec<&str> = then_clause
1375            .split(';')
1376            .map(|s| s.trim())
1377            .filter(|s| !s.is_empty())
1378            .collect();
1379
1380        let mut actions = Vec::new();
1381
1382        for statement in statements {
1383            let action = self.parse_action_statement(statement)?;
1384            actions.push(action);
1385        }
1386
1387        Ok(actions)
1388    }
1389
1390    fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
1391        let trimmed = statement.trim();
1392
1393        // Method call: $Object.method(args)
1394        if let Some(captures) = method_call_regex().captures(trimmed) {
1395            let object = captures.get(1).unwrap().as_str().to_string();
1396            let method = captures.get(2).unwrap().as_str().to_string();
1397            let args_str = captures.get(3).unwrap().as_str();
1398
1399            let args = if args_str.trim().is_empty() {
1400                Vec::new()
1401            } else {
1402                self.parse_method_args(args_str)?
1403            };
1404
1405            return Ok(ActionType::MethodCall {
1406                object,
1407                method,
1408                args,
1409            });
1410        }
1411
1412        // Check for compound assignment operators first (+=, -=, etc.)
1413        if let Some(plus_eq_pos) = trimmed.find("+=") {
1414            // Append operator: Field += Value
1415            let field = trimmed[..plus_eq_pos].trim().to_string();
1416            let value_str = trimmed[plus_eq_pos + 2..].trim();
1417            let value = self.parse_value(value_str)?;
1418
1419            return Ok(ActionType::Append { field, value });
1420        }
1421
1422        // Assignment: Field = Value
1423        if let Some(eq_pos) = trimmed.find('=') {
1424            let field = trimmed[..eq_pos].trim().to_string();
1425            let value_str = trimmed[eq_pos + 1..].trim();
1426            let value = self.parse_value(value_str)?;
1427
1428            return Ok(ActionType::Set { field, value });
1429        }
1430
1431        // Function calls: update($Object), retract($Object), etc.
1432        if let Some(captures) = function_binding_regex().captures(trimmed) {
1433            let function_name = captures.get(1).unwrap().as_str();
1434            let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
1435
1436            match function_name.to_lowercase().as_str() {
1437                "retract" => {
1438                    // Extract object name from $Object
1439                    let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
1440                        stripped.to_string()
1441                    } else {
1442                        args_str.to_string()
1443                    };
1444                    Ok(ActionType::Retract {
1445                        object: object_name,
1446                    })
1447                }
1448                "log" => {
1449                    let message = if args_str.is_empty() {
1450                        "Log message".to_string()
1451                    } else {
1452                        let value = self.parse_value(args_str.trim())?;
1453                        value.to_string()
1454                    };
1455                    Ok(ActionType::Log { message })
1456                }
1457                "activateagendagroup" | "activate_agenda_group" => {
1458                    let agenda_group = if args_str.is_empty() {
1459                        return Err(RuleEngineError::ParseError {
1460                            message: "ActivateAgendaGroup requires agenda group name".to_string(),
1461                        });
1462                    } else {
1463                        let value = self.parse_value(args_str.trim())?;
1464                        match value {
1465                            Value::String(s) => s,
1466                            _ => value.to_string(),
1467                        }
1468                    };
1469                    Ok(ActionType::ActivateAgendaGroup {
1470                        group: agenda_group,
1471                    })
1472                }
1473                "schedulerule" | "schedule_rule" => {
1474                    // Parse delay and target rule: ScheduleRule(5000, "next-rule")
1475                    let parts: Vec<&str> = args_str.split(',').collect();
1476                    if parts.len() != 2 {
1477                        return Err(RuleEngineError::ParseError {
1478                            message: "ScheduleRule requires delay_ms and rule_name".to_string(),
1479                        });
1480                    }
1481
1482                    let delay_ms = self.parse_value(parts[0].trim())?;
1483                    let rule_name = self.parse_value(parts[1].trim())?;
1484
1485                    let delay_ms = match delay_ms {
1486                        Value::Integer(i) => i as u64,
1487                        Value::Number(f) => f as u64,
1488                        _ => {
1489                            return Err(RuleEngineError::ParseError {
1490                                message: "ScheduleRule delay_ms must be a number".to_string(),
1491                            })
1492                        }
1493                    };
1494
1495                    let rule_name = match rule_name {
1496                        Value::String(s) => s,
1497                        _ => rule_name.to_string(),
1498                    };
1499
1500                    Ok(ActionType::ScheduleRule {
1501                        delay_ms,
1502                        rule_name,
1503                    })
1504                }
1505                "completeworkflow" | "complete_workflow" => {
1506                    let workflow_id = if args_str.is_empty() {
1507                        return Err(RuleEngineError::ParseError {
1508                            message: "CompleteWorkflow requires workflow_id".to_string(),
1509                        });
1510                    } else {
1511                        let value = self.parse_value(args_str.trim())?;
1512                        match value {
1513                            Value::String(s) => s,
1514                            _ => value.to_string(),
1515                        }
1516                    };
1517                    Ok(ActionType::CompleteWorkflow {
1518                        workflow_name: workflow_id,
1519                    })
1520                }
1521                "setworkflowdata" | "set_workflow_data" => {
1522                    // Parse key=value: SetWorkflowData("key=value")
1523                    let data_str = args_str.trim();
1524
1525                    // Simple key=value parsing
1526                    let (key, value) = if let Some(eq_pos) = data_str.find('=') {
1527                        let key = data_str[..eq_pos].trim().trim_matches('"');
1528                        let value_str = data_str[eq_pos + 1..].trim();
1529                        let value = self.parse_value(value_str)?;
1530                        (key.to_string(), value)
1531                    } else {
1532                        return Err(RuleEngineError::ParseError {
1533                            message: "SetWorkflowData data must be in key=value format".to_string(),
1534                        });
1535                    };
1536
1537                    Ok(ActionType::SetWorkflowData { key, value })
1538                }
1539                _ => {
1540                    // All other functions become custom actions
1541                    let params = if args_str.is_empty() {
1542                        HashMap::new()
1543                    } else {
1544                        self.parse_function_args_as_params(args_str)?
1545                    };
1546
1547                    Ok(ActionType::Custom {
1548                        action_type: function_name.to_string(),
1549                        params,
1550                    })
1551                }
1552            }
1553        } else {
1554            // Custom statement
1555            Ok(ActionType::Custom {
1556                action_type: "statement".to_string(),
1557                params: {
1558                    let mut params = HashMap::new();
1559                    params.insert("statement".to_string(), Value::String(trimmed.to_string()));
1560                    params
1561                },
1562            })
1563        }
1564    }
1565
1566    fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
1567        if args_str.trim().is_empty() {
1568            return Ok(Vec::new());
1569        }
1570
1571        // Handle expressions like: $TestCar.Speed + $TestCar.SpeedIncrement
1572        let mut args = Vec::new();
1573        let parts: Vec<&str> = args_str.split(',').collect();
1574
1575        for part in parts {
1576            let trimmed = part.trim();
1577
1578            // Handle arithmetic expressions
1579            if trimmed.contains('+')
1580                || trimmed.contains('-')
1581                || trimmed.contains('*')
1582                || trimmed.contains('/')
1583            {
1584                // For now, store as string - the engine will evaluate
1585                args.push(Value::String(trimmed.to_string()));
1586            } else {
1587                args.push(self.parse_value(trimmed)?);
1588            }
1589        }
1590
1591        Ok(args)
1592    }
1593
1594    /// Parse function arguments as parameters for custom actions
1595    fn parse_function_args_as_params(&self, args_str: &str) -> Result<HashMap<String, Value>> {
1596        let mut params = HashMap::new();
1597
1598        if args_str.trim().is_empty() {
1599            return Ok(params);
1600        }
1601
1602        // Parse positional parameters as numbered args
1603        let parts: Vec<&str> = args_str.split(',').collect();
1604        for (i, part) in parts.iter().enumerate() {
1605            let trimmed = part.trim();
1606            let value = self.parse_value(trimmed)?;
1607
1608            // Use simple numeric indexing - engine will resolve references dynamically
1609            params.insert(i.to_string(), value);
1610        }
1611
1612        Ok(params)
1613    }
1614
1615    /// Parse stream pattern condition
1616    /// Example: "login: LoginEvent from stream(\"logins\") over window(10 min, sliding)"
1617    #[cfg(feature = "streaming")]
1618    fn parse_stream_pattern_condition(&self, clause: &str) -> Result<ConditionGroup> {
1619        use crate::engine::rule::{StreamWindow, StreamWindowType};
1620        use crate::parser::grl::stream_syntax::parse_stream_pattern;
1621
1622        // Parse using nom parser
1623        let parse_result =
1624            parse_stream_pattern(clause).map_err(|e| RuleEngineError::ParseError {
1625                message: format!("Failed to parse stream pattern: {:?}", e),
1626            })?;
1627
1628        let (_, pattern) = parse_result;
1629
1630        // Convert WindowType from parser to StreamWindowType
1631        let window = pattern.source.window.map(|w| StreamWindow {
1632            duration: w.duration,
1633            window_type: match w.window_type {
1634                crate::parser::grl::stream_syntax::WindowType::Sliding => StreamWindowType::Sliding,
1635                crate::parser::grl::stream_syntax::WindowType::Tumbling => {
1636                    StreamWindowType::Tumbling
1637                }
1638                crate::parser::grl::stream_syntax::WindowType::Session { timeout } => {
1639                    StreamWindowType::Session { timeout }
1640                }
1641            },
1642        });
1643
1644        Ok(ConditionGroup::stream_pattern(
1645            pattern.var_name,
1646            pattern.event_type,
1647            pattern.source.stream_name,
1648            window,
1649        ))
1650    }
1651}
1652
1653#[cfg(test)]
1654mod tests {
1655    use super::GRLParser;
1656
1657    #[test]
1658    fn test_parse_simple_rule() {
1659        let grl = r#"
1660        rule "CheckAge" salience 10 {
1661            when
1662                User.Age >= 18
1663            then
1664                log("User is adult");
1665        }
1666        "#;
1667
1668        let rules = GRLParser::parse_rules(grl).unwrap();
1669        assert_eq!(rules.len(), 1);
1670        let rule = &rules[0];
1671        assert_eq!(rule.name, "CheckAge");
1672        assert_eq!(rule.salience, 10);
1673        assert_eq!(rule.actions.len(), 1);
1674    }
1675
1676    #[test]
1677    fn test_parse_complex_condition() {
1678        let grl = r#"
1679        rule "ComplexRule" {
1680            when
1681                User.Age >= 18 && User.Country == "US"
1682            then
1683                User.Qualified = true;
1684        }
1685        "#;
1686
1687        let rules = GRLParser::parse_rules(grl).unwrap();
1688        assert_eq!(rules.len(), 1);
1689        let rule = &rules[0];
1690        assert_eq!(rule.name, "ComplexRule");
1691    }
1692
1693    #[test]
1694    fn test_parse_new_syntax_with_parentheses() {
1695        let grl = r#"
1696        rule "Default Rule" salience 10 {
1697            when
1698                (user.age >= 18)
1699            then
1700                set(user.status, "approved");
1701        }
1702        "#;
1703
1704        let rules = GRLParser::parse_rules(grl).unwrap();
1705        assert_eq!(rules.len(), 1);
1706        let rule = &rules[0];
1707        assert_eq!(rule.name, "Default Rule");
1708        assert_eq!(rule.salience, 10);
1709        assert_eq!(rule.actions.len(), 1);
1710
1711        // Check that the action is parsed as a Custom action (set is now custom)
1712        match &rule.actions[0] {
1713            crate::types::ActionType::Custom {
1714                action_type,
1715                params,
1716            } => {
1717                assert_eq!(action_type, "set");
1718                assert_eq!(
1719                    params.get("0"),
1720                    Some(&crate::types::Value::String("user.status".to_string()))
1721                );
1722                assert_eq!(
1723                    params.get("1"),
1724                    Some(&crate::types::Value::String("approved".to_string()))
1725                );
1726            }
1727            _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1728        }
1729    }
1730
1731    #[test]
1732    fn test_parse_complex_nested_conditions() {
1733        let grl = r#"
1734        rule "Complex Business Rule" salience 10 {
1735            when
1736                (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
1737            then
1738                apply_discount(20000);
1739        }
1740        "#;
1741
1742        let rules = GRLParser::parse_rules(grl).unwrap();
1743        assert_eq!(rules.len(), 1);
1744        let rule = &rules[0];
1745        assert_eq!(rule.name, "Complex Business Rule");
1746        assert_eq!(rule.salience, 10);
1747        assert_eq!(rule.actions.len(), 1);
1748
1749        // Check that the action is parsed as a Custom action (apply_discount is now custom)
1750        match &rule.actions[0] {
1751            crate::types::ActionType::Custom {
1752                action_type,
1753                params,
1754            } => {
1755                assert_eq!(action_type, "apply_discount");
1756                assert_eq!(params.get("0"), Some(&crate::types::Value::Integer(20000)));
1757            }
1758            _ => panic!("Expected Custom action, got: {:?}", rule.actions[0]),
1759        }
1760    }
1761
1762    #[test]
1763    fn test_parse_no_loop_attribute() {
1764        let grl = r#"
1765        rule "NoLoopRule" no-loop salience 15 {
1766            when
1767                User.Score < 100
1768            then
1769                set(User.Score, User.Score + 10);
1770        }
1771        "#;
1772
1773        let rules = GRLParser::parse_rules(grl).unwrap();
1774        assert_eq!(rules.len(), 1);
1775        let rule = &rules[0];
1776        assert_eq!(rule.name, "NoLoopRule");
1777        assert_eq!(rule.salience, 15);
1778        assert!(rule.no_loop, "Rule should have no-loop=true");
1779    }
1780
1781    #[test]
1782    fn test_parse_no_loop_different_positions() {
1783        // Test no-loop before salience
1784        let grl1 = r#"
1785        rule "Rule1" no-loop salience 10 {
1786            when User.Age >= 18
1787            then log("adult");
1788        }
1789        "#;
1790
1791        // Test no-loop after salience
1792        let grl2 = r#"
1793        rule "Rule2" salience 10 no-loop {
1794            when User.Age >= 18
1795            then log("adult");
1796        }
1797        "#;
1798
1799        let rules1 = GRLParser::parse_rules(grl1).unwrap();
1800        let rules2 = GRLParser::parse_rules(grl2).unwrap();
1801
1802        assert_eq!(rules1.len(), 1);
1803        assert_eq!(rules2.len(), 1);
1804
1805        assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
1806        assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
1807
1808        assert_eq!(rules1[0].salience, 10);
1809        assert_eq!(rules2[0].salience, 10);
1810    }
1811
1812    #[test]
1813    fn test_parse_without_no_loop() {
1814        let grl = r#"
1815        rule "RegularRule" salience 5 {
1816            when
1817                User.Active == true
1818            then
1819                log("active user");
1820        }
1821        "#;
1822
1823        let rules = GRLParser::parse_rules(grl).unwrap();
1824        assert_eq!(rules.len(), 1);
1825        let rule = &rules[0];
1826        assert_eq!(rule.name, "RegularRule");
1827        assert!(!rule.no_loop, "Rule should have no-loop=false by default");
1828    }
1829
1830    #[test]
1831    fn test_parse_exists_pattern() {
1832        let grl = r#"
1833        rule "ExistsRule" salience 20 {
1834            when
1835                exists(Customer.tier == "VIP")
1836            then
1837                System.premiumActive = true;
1838        }
1839        "#;
1840
1841        let rules = GRLParser::parse_rules(grl).unwrap();
1842        assert_eq!(rules.len(), 1);
1843        let rule = &rules[0];
1844        assert_eq!(rule.name, "ExistsRule");
1845        assert_eq!(rule.salience, 20);
1846
1847        // Check that condition is EXISTS pattern
1848        match &rule.conditions {
1849            crate::engine::rule::ConditionGroup::Exists(_) => {
1850                // Test passes
1851            }
1852            _ => panic!(
1853                "Expected EXISTS condition group, got: {:?}",
1854                rule.conditions
1855            ),
1856        }
1857    }
1858
1859    #[test]
1860    fn test_parse_forall_pattern() {
1861        let grl = r#"
1862        rule "ForallRule" salience 15 {
1863            when
1864                forall(Order.status == "processed")
1865            then
1866                Shipping.enabled = true;
1867        }
1868        "#;
1869
1870        let rules = GRLParser::parse_rules(grl).unwrap();
1871        assert_eq!(rules.len(), 1);
1872        let rule = &rules[0];
1873        assert_eq!(rule.name, "ForallRule");
1874
1875        // Check that condition is FORALL pattern
1876        match &rule.conditions {
1877            crate::engine::rule::ConditionGroup::Forall(_) => {
1878                // Test passes
1879            }
1880            _ => panic!(
1881                "Expected FORALL condition group, got: {:?}",
1882                rule.conditions
1883            ),
1884        }
1885    }
1886
1887    #[test]
1888    fn test_parse_combined_patterns() {
1889        let grl = r#"
1890        rule "CombinedRule" salience 25 {
1891            when
1892                exists(Customer.tier == "VIP") && !exists(Alert.priority == "high")
1893            then
1894                System.vipMode = true;
1895        }
1896        "#;
1897
1898        let rules = GRLParser::parse_rules(grl).unwrap();
1899        assert_eq!(rules.len(), 1);
1900        let rule = &rules[0];
1901        assert_eq!(rule.name, "CombinedRule");
1902
1903        // Check that condition is AND with EXISTS and NOT(EXISTS) patterns
1904        match &rule.conditions {
1905            crate::engine::rule::ConditionGroup::Compound {
1906                left,
1907                operator,
1908                right,
1909            } => {
1910                assert_eq!(*operator, crate::types::LogicalOperator::And);
1911
1912                // Left should be EXISTS
1913                match left.as_ref() {
1914                    crate::engine::rule::ConditionGroup::Exists(_) => {
1915                        // Expected
1916                    }
1917                    _ => panic!("Expected EXISTS in left side, got: {:?}", left),
1918                }
1919
1920                // Right should be NOT(EXISTS)
1921                match right.as_ref() {
1922                    crate::engine::rule::ConditionGroup::Not(inner) => {
1923                        match inner.as_ref() {
1924                            crate::engine::rule::ConditionGroup::Exists(_) => {
1925                                // Expected
1926                            }
1927                            _ => panic!("Expected EXISTS inside NOT, got: {:?}", inner),
1928                        }
1929                    }
1930                    _ => panic!("Expected NOT in right side, got: {:?}", right),
1931                }
1932            }
1933            _ => panic!("Expected compound condition, got: {:?}", rule.conditions),
1934        }
1935    }
1936}