rust_rule_engine/parser/
grl.rs

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