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