rust_rule_engine/parser/
grl.rs

1use crate::engine::rule::{Condition, ConditionGroup, Rule};
2use crate::errors::{Result, RuleEngineError};
3use crate::types::{ActionType, Operator, Value};
4use chrono::{DateTime, Utc};
5use regex::Regex;
6use std::collections::HashMap;
7
8/// GRL (Grule Rule Language) Parser
9/// Parses Grule-like syntax into Rule objects
10pub struct GRLParser;
11
12/// Parsed rule attributes from GRL header
13#[derive(Debug, Default)]
14struct RuleAttributes {
15    pub no_loop: bool,
16    pub lock_on_active: bool,
17    pub agenda_group: Option<String>,
18    pub activation_group: Option<String>,
19    pub date_effective: Option<DateTime<Utc>>,
20    pub date_expires: Option<DateTime<Utc>>,
21}
22
23impl GRLParser {
24    /// Parse a single rule from GRL syntax
25    ///
26    /// Example GRL syntax:
27    /// ```grl
28    /// rule CheckAge "Age verification rule" salience 10 {
29    ///     when
30    ///         User.Age >= 18 && User.Country == "US"
31    ///     then
32    ///         User.IsAdult = true;
33    ///         Retract("User");
34    /// }
35    /// ```
36    pub fn parse_rule(grl_text: &str) -> Result<Rule> {
37        let mut parser = GRLParser;
38        parser.parse_single_rule(grl_text)
39    }
40
41    /// Parse multiple rules from GRL text
42    pub fn parse_rules(grl_text: &str) -> Result<Vec<Rule>> {
43        let mut parser = GRLParser;
44        parser.parse_multiple_rules(grl_text)
45    }
46
47    fn parse_single_rule(&mut self, grl_text: &str) -> Result<Rule> {
48        let cleaned = self.clean_text(grl_text);
49
50        // Extract rule components using regex - support various attributes
51        let rule_regex = Regex::new(r#"rule\s+(?:"([^"]+)"|([a-zA-Z_]\w*))\s*([^{]*)\{(.+)\}"#)
52            .map_err(|e| RuleEngineError::ParseError {
53                message: format!("Invalid rule regex: {}", e),
54            })?;
55
56        let captures =
57            rule_regex
58                .captures(&cleaned)
59                .ok_or_else(|| RuleEngineError::ParseError {
60                    message: format!("Invalid GRL rule format. Input: {}", cleaned),
61                })?;
62
63        // Rule name can be either quoted (group 1) or unquoted (group 2)
64        let rule_name = if let Some(quoted_name) = captures.get(1) {
65            quoted_name.as_str().to_string()
66        } else if let Some(unquoted_name) = captures.get(2) {
67            unquoted_name.as_str().to_string()
68        } else {
69            return Err(RuleEngineError::ParseError {
70                message: "Could not extract rule name".to_string(),
71            });
72        };
73
74        // Attributes section (group 3)
75        let attributes_section = captures.get(3).map(|m| m.as_str()).unwrap_or("");
76
77        // Rule body (group 4)
78        let rule_body = captures.get(4).unwrap().as_str();
79
80        // Parse salience from attributes section
81        let salience = self.extract_salience(attributes_section)?;
82
83        // Parse when and then sections
84        let when_then_regex =
85            Regex::new(r"when\s+(.+?)\s+then\s+(.+)").map_err(|e| RuleEngineError::ParseError {
86                message: format!("Invalid when-then regex: {}", e),
87            })?;
88
89        let when_then_captures =
90            when_then_regex
91                .captures(rule_body)
92                .ok_or_else(|| RuleEngineError::ParseError {
93                    message: "Missing when or then clause".to_string(),
94                })?;
95
96        let when_clause = when_then_captures.get(1).unwrap().as_str().trim();
97        let then_clause = when_then_captures.get(2).unwrap().as_str().trim();
98
99        // Parse conditions and actions
100        let conditions = self.parse_when_clause(when_clause)?;
101        let actions = self.parse_then_clause(then_clause)?;
102
103        // Parse all attributes from rule header
104        let attributes = self.parse_rule_attributes(attributes_section)?;
105
106        // Build rule
107        let mut rule = Rule::new(rule_name, conditions, actions);
108        rule = rule.with_priority(salience);
109
110        // Apply parsed attributes
111        if attributes.no_loop {
112            rule = rule.with_no_loop(true);
113        }
114        if attributes.lock_on_active {
115            rule = rule.with_lock_on_active(true);
116        }
117        if let Some(agenda_group) = attributes.agenda_group {
118            rule = rule.with_agenda_group(agenda_group);
119        }
120        if let Some(activation_group) = attributes.activation_group {
121            rule = rule.with_activation_group(activation_group);
122        }
123        if let Some(date_effective) = attributes.date_effective {
124            rule = rule.with_date_effective(date_effective);
125        }
126        if let Some(date_expires) = attributes.date_expires {
127            rule = rule.with_date_expires(date_expires);
128        }
129
130        Ok(rule)
131    }
132
133    fn parse_multiple_rules(&mut self, grl_text: &str) -> Result<Vec<Rule>> {
134        // Split by rule boundaries - support both quoted and unquoted rule names
135        // Use DOTALL flag to match newlines in rule body
136        let rule_regex =
137            Regex::new(r#"(?s)rule\s+(?:"[^"]+"|[a-zA-Z_]\w*).*?\}"#).map_err(|e| {
138                RuleEngineError::ParseError {
139                    message: format!("Rule splitting regex error: {}", e),
140                }
141            })?;
142
143        let mut rules = Vec::new();
144
145        for rule_match in rule_regex.find_iter(grl_text) {
146            let rule_text = rule_match.as_str();
147            let rule = self.parse_single_rule(rule_text)?;
148            rules.push(rule);
149        }
150
151        Ok(rules)
152    }
153
154    /// Parse rule attributes from the rule header
155    fn parse_rule_attributes(&self, rule_header: &str) -> Result<RuleAttributes> {
156        let mut attributes = RuleAttributes::default();
157
158        // Check for simple boolean attributes
159        if rule_header.contains("no-loop") {
160            attributes.no_loop = true;
161        }
162        if rule_header.contains("lock-on-active") {
163            attributes.lock_on_active = true;
164        }
165
166        // Parse agenda-group attribute
167        if let Some(agenda_group) = self.extract_quoted_attribute(rule_header, "agenda-group")? {
168            attributes.agenda_group = Some(agenda_group);
169        }
170
171        // Parse activation-group attribute
172        if let Some(activation_group) =
173            self.extract_quoted_attribute(rule_header, "activation-group")?
174        {
175            attributes.activation_group = Some(activation_group);
176        }
177
178        // Parse date-effective attribute
179        if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-effective")? {
180            attributes.date_effective = Some(self.parse_date_string(&date_str)?);
181        }
182
183        // Parse date-expires attribute
184        if let Some(date_str) = self.extract_quoted_attribute(rule_header, "date-expires")? {
185            attributes.date_expires = Some(self.parse_date_string(&date_str)?);
186        }
187
188        Ok(attributes)
189    }
190
191    /// Extract quoted attribute value from rule header
192    fn extract_quoted_attribute(&self, header: &str, attribute: &str) -> Result<Option<String>> {
193        let pattern = format!(r#"{}\s+"([^"]+)""#, attribute);
194        let regex = Regex::new(&pattern).map_err(|e| RuleEngineError::ParseError {
195            message: format!("Invalid attribute regex for {}: {}", attribute, e),
196        })?;
197
198        if let Some(captures) = regex.captures(header) {
199            if let Some(value) = captures.get(1) {
200                return Ok(Some(value.as_str().to_string()));
201            }
202        }
203
204        Ok(None)
205    }
206
207    /// Parse date string in various formats
208    fn parse_date_string(&self, date_str: &str) -> Result<DateTime<Utc>> {
209        // Try ISO 8601 format first
210        if let Ok(date) = DateTime::parse_from_rfc3339(date_str) {
211            return Ok(date.with_timezone(&Utc));
212        }
213
214        // Try simple date formats
215        let formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%d-%b-%Y", "%d-%m-%Y"];
216
217        for format in &formats {
218            if let Ok(naive_date) = chrono::NaiveDateTime::parse_from_str(date_str, format) {
219                return Ok(naive_date.and_utc());
220            }
221            if let Ok(naive_date) = chrono::NaiveDate::parse_from_str(date_str, format) {
222                return Ok(naive_date.and_hms_opt(0, 0, 0).unwrap().and_utc());
223            }
224        }
225
226        Err(RuleEngineError::ParseError {
227            message: format!("Unable to parse date: {}", date_str),
228        })
229    }
230
231    /// Extract salience value from attributes section
232    fn extract_salience(&self, attributes_section: &str) -> Result<i32> {
233        let salience_regex =
234            Regex::new(r"salience\s+(\d+)").map_err(|e| RuleEngineError::ParseError {
235                message: format!("Invalid salience regex: {}", e),
236            })?;
237
238        if let Some(captures) = salience_regex.captures(attributes_section) {
239            if let Some(salience_match) = captures.get(1) {
240                return salience_match.as_str().parse::<i32>().map_err(|e| {
241                    RuleEngineError::ParseError {
242                        message: format!("Invalid salience value: {}", e),
243                    }
244                });
245            }
246        }
247
248        Ok(0) // Default salience
249    }
250
251    fn clean_text(&self, text: &str) -> String {
252        text.lines()
253            .map(|line| line.trim())
254            .filter(|line| !line.is_empty() && !line.starts_with("//"))
255            .collect::<Vec<_>>()
256            .join(" ")
257    }
258
259    fn parse_when_clause(&self, when_clause: &str) -> Result<ConditionGroup> {
260        // Handle logical operators with proper parentheses support
261        let trimmed = when_clause.trim();
262
263        // Strip outer parentheses if they exist
264        let clause = if trimmed.starts_with('(') && trimmed.ends_with(')') {
265            // Check if these are the outermost parentheses
266            let inner = &trimmed[1..trimmed.len() - 1];
267            if self.is_balanced_parentheses(inner) {
268                inner
269            } else {
270                trimmed
271            }
272        } else {
273            trimmed
274        };
275
276        // Parse OR at the top level (lowest precedence)
277        if let Some(parts) = self.split_logical_operator(clause, "||") {
278            return self.parse_or_parts(parts);
279        }
280
281        // Parse AND (higher precedence)
282        if let Some(parts) = self.split_logical_operator(clause, "&&") {
283            return self.parse_and_parts(parts);
284        }
285
286        // Handle NOT condition
287        if clause.trim_start().starts_with("!") {
288            return self.parse_not_condition(clause);
289        }
290
291        // Single condition
292        self.parse_single_condition(clause)
293    }
294
295    fn is_balanced_parentheses(&self, text: &str) -> bool {
296        let mut count = 0;
297        for ch in text.chars() {
298            match ch {
299                '(' => count += 1,
300                ')' => {
301                    count -= 1;
302                    if count < 0 {
303                        return false;
304                    }
305                }
306                _ => {}
307            }
308        }
309        count == 0
310    }
311
312    fn split_logical_operator(&self, clause: &str, operator: &str) -> Option<Vec<String>> {
313        let mut parts = Vec::new();
314        let mut current_part = String::new();
315        let mut paren_count = 0;
316        let mut chars = clause.chars().peekable();
317
318        while let Some(ch) = chars.next() {
319            match ch {
320                '(' => {
321                    paren_count += 1;
322                    current_part.push(ch);
323                }
324                ')' => {
325                    paren_count -= 1;
326                    current_part.push(ch);
327                }
328                '&' if operator == "&&" && paren_count == 0 => {
329                    if chars.peek() == Some(&'&') {
330                        chars.next(); // consume second &
331                        parts.push(current_part.trim().to_string());
332                        current_part.clear();
333                    } else {
334                        current_part.push(ch);
335                    }
336                }
337                '|' if operator == "||" && paren_count == 0 => {
338                    if chars.peek() == Some(&'|') {
339                        chars.next(); // consume second |
340                        parts.push(current_part.trim().to_string());
341                        current_part.clear();
342                    } else {
343                        current_part.push(ch);
344                    }
345                }
346                _ => {
347                    current_part.push(ch);
348                }
349            }
350        }
351
352        if !current_part.trim().is_empty() {
353            parts.push(current_part.trim().to_string());
354        }
355
356        if parts.len() > 1 {
357            Some(parts)
358        } else {
359            None
360        }
361    }
362
363    fn parse_or_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
364        let mut conditions = Vec::new();
365        for part in parts {
366            let condition = self.parse_when_clause(&part)?;
367            conditions.push(condition);
368        }
369
370        if conditions.is_empty() {
371            return Err(RuleEngineError::ParseError {
372                message: "No conditions found in OR".to_string(),
373            });
374        }
375
376        let mut iter = conditions.into_iter();
377        let mut result = iter.next().unwrap();
378        for condition in iter {
379            result = ConditionGroup::or(result, condition);
380        }
381
382        Ok(result)
383    }
384
385    fn parse_and_parts(&self, parts: Vec<String>) -> Result<ConditionGroup> {
386        let mut conditions = Vec::new();
387        for part in parts {
388            let condition = self.parse_when_clause(&part)?;
389            conditions.push(condition);
390        }
391
392        if conditions.is_empty() {
393            return Err(RuleEngineError::ParseError {
394                message: "No conditions found in AND".to_string(),
395            });
396        }
397
398        let mut iter = conditions.into_iter();
399        let mut result = iter.next().unwrap();
400        for condition in iter {
401            result = ConditionGroup::and(result, condition);
402        }
403
404        Ok(result)
405    }
406
407    fn parse_not_condition(&self, clause: &str) -> Result<ConditionGroup> {
408        let inner_clause = clause.strip_prefix("!").unwrap().trim();
409        let inner_condition = self.parse_when_clause(inner_clause)?;
410        Ok(ConditionGroup::not(inner_condition))
411    }
412
413    fn parse_single_condition(&self, clause: &str) -> Result<ConditionGroup> {
414        // Remove outer parentheses if they exist (handle new syntax like "(user.age >= 18)")
415        let trimmed_clause = clause.trim();
416        let clause_to_parse = if trimmed_clause.starts_with('(') && trimmed_clause.ends_with(')') {
417            trimmed_clause[1..trimmed_clause.len() - 1].trim()
418        } else {
419            trimmed_clause
420        };
421
422        // Handle typed object conditions like: $TestCar : TestCarClass( speedUp == true && speed < maxSpeed )
423        let typed_object_regex =
424            Regex::new(r#"\$(\w+)\s*:\s*(\w+)\s*\(\s*(.+?)\s*\)"#).map_err(|e| {
425                RuleEngineError::ParseError {
426                    message: format!("Typed object regex error: {}", e),
427                }
428            })?;
429
430        if let Some(captures) = typed_object_regex.captures(clause_to_parse) {
431            let _object_name = captures.get(1).unwrap().as_str();
432            let _object_type = captures.get(2).unwrap().as_str();
433            let conditions_str = captures.get(3).unwrap().as_str();
434
435            // Parse conditions inside parentheses
436            return self.parse_conditions_within_object(conditions_str);
437        }
438
439        // Parse expressions like: User.Age >= 18, Product.Price < 100.0, user.age >= 18, etc.
440        // Support both PascalCase (User.Age) and lowercase (user.age) field naming
441        let condition_regex = Regex::new(
442            r#"([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*(>=|<=|==|!=|>|<|contains|matches)\s*(.+)"#,
443        )
444        .map_err(|e| RuleEngineError::ParseError {
445            message: format!("Condition regex error: {}", e),
446        })?;
447
448        let captures = condition_regex.captures(clause_to_parse).ok_or_else(|| {
449            RuleEngineError::ParseError {
450                message: format!("Invalid condition format: {}", clause_to_parse),
451            }
452        })?;
453
454        let field = captures.get(1).unwrap().as_str().to_string();
455        let operator_str = captures.get(2).unwrap().as_str();
456        let value_str = captures.get(3).unwrap().as_str().trim();
457
458        let operator =
459            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
460                operator: operator_str.to_string(),
461            })?;
462
463        let value = self.parse_value(value_str)?;
464
465        let condition = Condition::new(field, operator, value);
466        Ok(ConditionGroup::single(condition))
467    }
468
469    fn parse_conditions_within_object(&self, conditions_str: &str) -> Result<ConditionGroup> {
470        // Parse conditions like: speedUp == true && speed < maxSpeed
471        let parts: Vec<&str> = conditions_str.split("&&").collect();
472
473        let mut conditions = Vec::new();
474        for part in parts {
475            let trimmed = part.trim();
476            let condition = self.parse_simple_condition(trimmed)?;
477            conditions.push(condition);
478        }
479
480        // Combine with AND
481        if conditions.is_empty() {
482            return Err(RuleEngineError::ParseError {
483                message: "No conditions found".to_string(),
484            });
485        }
486
487        let mut iter = conditions.into_iter();
488        let mut result = iter.next().unwrap();
489        for condition in iter {
490            result = ConditionGroup::and(result, condition);
491        }
492
493        Ok(result)
494    }
495
496    fn parse_simple_condition(&self, clause: &str) -> Result<ConditionGroup> {
497        // Parse simple condition like: speedUp == true or speed < maxSpeed
498        let condition_regex = Regex::new(r#"(\w+)\s*(>=|<=|==|!=|>|<)\s*(.+)"#).map_err(|e| {
499            RuleEngineError::ParseError {
500                message: format!("Simple condition regex error: {}", e),
501            }
502        })?;
503
504        let captures =
505            condition_regex
506                .captures(clause)
507                .ok_or_else(|| RuleEngineError::ParseError {
508                    message: format!("Invalid simple condition format: {}", clause),
509                })?;
510
511        let field = captures.get(1).unwrap().as_str().to_string();
512        let operator_str = captures.get(2).unwrap().as_str();
513        let value_str = captures.get(3).unwrap().as_str().trim();
514
515        let operator =
516            Operator::from_str(operator_str).ok_or_else(|| RuleEngineError::InvalidOperator {
517                operator: operator_str.to_string(),
518            })?;
519
520        let value = self.parse_value(value_str)?;
521
522        let condition = Condition::new(field, operator, value);
523        Ok(ConditionGroup::single(condition))
524    }
525
526    fn parse_value(&self, value_str: &str) -> Result<Value> {
527        let trimmed = value_str.trim();
528
529        // String literal
530        if (trimmed.starts_with('"') && trimmed.ends_with('"'))
531            || (trimmed.starts_with('\'') && trimmed.ends_with('\''))
532        {
533            let unquoted = &trimmed[1..trimmed.len() - 1];
534            return Ok(Value::String(unquoted.to_string()));
535        }
536
537        // Boolean
538        if trimmed.eq_ignore_ascii_case("true") {
539            return Ok(Value::Boolean(true));
540        }
541        if trimmed.eq_ignore_ascii_case("false") {
542            return Ok(Value::Boolean(false));
543        }
544
545        // Null
546        if trimmed.eq_ignore_ascii_case("null") {
547            return Ok(Value::Null);
548        }
549
550        // Number (try integer first, then float)
551        if let Ok(int_val) = trimmed.parse::<i64>() {
552            return Ok(Value::Integer(int_val));
553        }
554
555        if let Ok(float_val) = trimmed.parse::<f64>() {
556            return Ok(Value::Number(float_val));
557        }
558
559        // Field reference (like User.Name)
560        if trimmed.contains('.') {
561            return Ok(Value::String(trimmed.to_string()));
562        }
563
564        // Default to string
565        Ok(Value::String(trimmed.to_string()))
566    }
567
568    fn parse_then_clause(&self, then_clause: &str) -> Result<Vec<ActionType>> {
569        let statements: Vec<&str> = then_clause
570            .split(';')
571            .map(|s| s.trim())
572            .filter(|s| !s.is_empty())
573            .collect();
574
575        let mut actions = Vec::new();
576
577        for statement in statements {
578            let action = self.parse_action_statement(statement)?;
579            actions.push(action);
580        }
581
582        Ok(actions)
583    }
584
585    fn parse_action_statement(&self, statement: &str) -> Result<ActionType> {
586        let trimmed = statement.trim();
587
588        // Method call: $Object.method(args)
589        let method_regex = Regex::new(r#"\$(\w+)\.(\w+)\s*\(([^)]*)\)"#).map_err(|e| {
590            RuleEngineError::ParseError {
591                message: format!("Method regex error: {}", e),
592            }
593        })?;
594
595        if let Some(captures) = method_regex.captures(trimmed) {
596            let object = captures.get(1).unwrap().as_str().to_string();
597            let method = captures.get(2).unwrap().as_str().to_string();
598            let args_str = captures.get(3).unwrap().as_str();
599
600            let args = if args_str.trim().is_empty() {
601                Vec::new()
602            } else {
603                self.parse_method_args(args_str)?
604            };
605
606            return Ok(ActionType::MethodCall {
607                object,
608                method,
609                args,
610            });
611        }
612
613        // Assignment: Field = Value
614        if let Some(eq_pos) = trimmed.find('=') {
615            let field = trimmed[..eq_pos].trim().to_string();
616            let value_str = trimmed[eq_pos + 1..].trim();
617            let value = self.parse_value(value_str)?;
618
619            return Ok(ActionType::Set { field, value });
620        }
621
622        // Function calls: update($Object), retract($Object), etc.
623        let func_regex =
624            Regex::new(r#"(\w+)\s*\(\s*(.+?)?\s*\)"#).map_err(|e| RuleEngineError::ParseError {
625                message: format!("Function regex error: {}", e),
626            })?;
627
628        if let Some(captures) = func_regex.captures(trimmed) {
629            let function_name = captures.get(1).unwrap().as_str();
630            let args_str = captures.get(2).map(|m| m.as_str()).unwrap_or("");
631
632            match function_name.to_lowercase().as_str() {
633                "update" => {
634                    // Extract object name from $Object
635                    let object_name = if let Some(stripped) = args_str.strip_prefix('$') {
636                        stripped.to_string()
637                    } else {
638                        args_str.to_string()
639                    };
640                    Ok(ActionType::Update {
641                        object: object_name,
642                    })
643                }
644                "set" => {
645                    // Handle set(field, value) format
646                    let args = if args_str.is_empty() {
647                        Vec::new()
648                    } else {
649                        args_str
650                            .split(',')
651                            .map(|arg| self.parse_value(arg.trim()))
652                            .collect::<Result<Vec<_>>>()?
653                    };
654
655                    if args.len() >= 2 {
656                        let field = args[0].to_string();
657                        let value = args[1].clone();
658                        Ok(ActionType::Set { field, value })
659                    } else if args.len() == 1 {
660                        // set(field) - set to true by default
661                        Ok(ActionType::Set {
662                            field: args[0].to_string(),
663                            value: Value::Boolean(true),
664                        })
665                    } else {
666                        Ok(ActionType::Custom {
667                            action_type: "set".to_string(),
668                            params: {
669                                let mut params = HashMap::new();
670                                params.insert(
671                                    "args".to_string(),
672                                    Value::String(args_str.to_string()),
673                                );
674                                params
675                            },
676                        })
677                    }
678                }
679                "add" => {
680                    // Handle add(value) format
681                    let value = if args_str.is_empty() {
682                        Value::Integer(1) // Default increment
683                    } else {
684                        self.parse_value(args_str.trim())?
685                    };
686                    Ok(ActionType::Custom {
687                        action_type: "add".to_string(),
688                        params: {
689                            let mut params = HashMap::new();
690                            params.insert("value".to_string(), value);
691                            params
692                        },
693                    })
694                }
695                "log" => {
696                    let message = if args_str.is_empty() {
697                        "Log message".to_string()
698                    } else {
699                        let value = self.parse_value(args_str.trim())?;
700                        value.to_string()
701                    };
702                    Ok(ActionType::Log { message })
703                }
704                _ => {
705                    let args = if args_str.is_empty() {
706                        Vec::new()
707                    } else {
708                        args_str
709                            .split(',')
710                            .map(|arg| self.parse_value(arg.trim()))
711                            .collect::<Result<Vec<_>>>()?
712                    };
713                    Ok(ActionType::Call {
714                        function: function_name.to_string(),
715                        args,
716                    })
717                }
718            }
719        } else {
720            // Custom statement
721            Ok(ActionType::Custom {
722                action_type: "statement".to_string(),
723                params: {
724                    let mut params = HashMap::new();
725                    params.insert("statement".to_string(), Value::String(trimmed.to_string()));
726                    params
727                },
728            })
729        }
730    }
731
732    fn parse_method_args(&self, args_str: &str) -> Result<Vec<Value>> {
733        if args_str.trim().is_empty() {
734            return Ok(Vec::new());
735        }
736
737        // Handle expressions like: $TestCar.Speed + $TestCar.SpeedIncrement
738        let mut args = Vec::new();
739        let parts: Vec<&str> = args_str.split(',').collect();
740
741        for part in parts {
742            let trimmed = part.trim();
743
744            // Handle arithmetic expressions
745            if trimmed.contains('+')
746                || trimmed.contains('-')
747                || trimmed.contains('*')
748                || trimmed.contains('/')
749            {
750                // For now, store as string - the engine will evaluate
751                args.push(Value::String(trimmed.to_string()));
752            } else {
753                args.push(self.parse_value(trimmed)?);
754            }
755        }
756
757        Ok(args)
758    }
759}
760
761#[cfg(test)]
762mod tests {
763    use super::GRLParser;
764
765    #[test]
766    fn test_parse_simple_rule() {
767        let grl = r#"
768        rule "CheckAge" salience 10 {
769            when
770                User.Age >= 18
771            then
772                log("User is adult");
773        }
774        "#;
775
776        let rules = GRLParser::parse_rules(grl).unwrap();
777        assert_eq!(rules.len(), 1);
778        let rule = &rules[0];
779        assert_eq!(rule.name, "CheckAge");
780        assert_eq!(rule.salience, 10);
781        assert_eq!(rule.actions.len(), 1);
782    }
783
784    #[test]
785    fn test_parse_complex_condition() {
786        let grl = r#"
787        rule "ComplexRule" {
788            when
789                User.Age >= 18 && User.Country == "US"
790            then
791                User.Qualified = true;
792        }
793        "#;
794
795        let rules = GRLParser::parse_rules(grl).unwrap();
796        assert_eq!(rules.len(), 1);
797        let rule = &rules[0];
798        assert_eq!(rule.name, "ComplexRule");
799    }
800
801    #[test]
802    fn test_parse_new_syntax_with_parentheses() {
803        let grl = r#"
804        rule "Default Rule" salience 10 {
805            when
806                (user.age >= 18)
807            then
808                set(user.status, "approved");
809        }
810        "#;
811
812        let rules = GRLParser::parse_rules(grl).unwrap();
813        assert_eq!(rules.len(), 1);
814        let rule = &rules[0];
815        assert_eq!(rule.name, "Default Rule");
816        assert_eq!(rule.salience, 10);
817        assert_eq!(rule.actions.len(), 1);
818
819        // Check that the action is parsed as a Set action
820        match &rule.actions[0] {
821            crate::types::ActionType::Set { field, value } => {
822                assert_eq!(field, "user.status");
823                assert_eq!(value, &crate::types::Value::String("approved".to_string()));
824            }
825            _ => panic!("Expected Set action, got: {:?}", rule.actions[0]),
826        }
827    }
828
829    #[test]
830    fn test_parse_complex_nested_conditions() {
831        let grl = r#"
832        rule "Complex Business Rule" salience 10 {
833            when
834                (((user.vipStatus == true) && (order.amount > 500)) || ((date.isHoliday == true) && (order.hasCoupon == true)))
835            then
836                apply_discount(20000);
837        }
838        "#;
839
840        let rules = GRLParser::parse_rules(grl).unwrap();
841        assert_eq!(rules.len(), 1);
842        let rule = &rules[0];
843        assert_eq!(rule.name, "Complex Business Rule");
844        assert_eq!(rule.salience, 10);
845        assert_eq!(rule.actions.len(), 1);
846
847        // Check that the action is parsed as a function call
848        match &rule.actions[0] {
849            crate::types::ActionType::Call { function, args } => {
850                assert_eq!(function, "apply_discount");
851                assert_eq!(args.len(), 1);
852                assert_eq!(args[0], crate::types::Value::Integer(20000));
853            }
854            _ => panic!("Expected Call action, got: {:?}", rule.actions[0]),
855        }
856    }
857
858    #[test]
859    fn test_parse_no_loop_attribute() {
860        let grl = r#"
861        rule "NoLoopRule" no-loop salience 15 {
862            when
863                User.Score < 100
864            then
865                set(User.Score, User.Score + 10);
866        }
867        "#;
868
869        let rules = GRLParser::parse_rules(grl).unwrap();
870        assert_eq!(rules.len(), 1);
871        let rule = &rules[0];
872        assert_eq!(rule.name, "NoLoopRule");
873        assert_eq!(rule.salience, 15);
874        assert!(rule.no_loop, "Rule should have no-loop=true");
875    }
876
877    #[test]
878    fn test_parse_no_loop_different_positions() {
879        // Test no-loop before salience
880        let grl1 = r#"
881        rule "Rule1" no-loop salience 10 {
882            when User.Age >= 18
883            then log("adult");
884        }
885        "#;
886
887        // Test no-loop after salience
888        let grl2 = r#"
889        rule "Rule2" salience 10 no-loop {
890            when User.Age >= 18
891            then log("adult");
892        }
893        "#;
894
895        let rules1 = GRLParser::parse_rules(grl1).unwrap();
896        let rules2 = GRLParser::parse_rules(grl2).unwrap();
897
898        assert_eq!(rules1.len(), 1);
899        assert_eq!(rules2.len(), 1);
900
901        assert!(rules1[0].no_loop, "Rule1 should have no-loop=true");
902        assert!(rules2[0].no_loop, "Rule2 should have no-loop=true");
903
904        assert_eq!(rules1[0].salience, 10);
905        assert_eq!(rules2[0].salience, 10);
906    }
907
908    #[test]
909    fn test_parse_without_no_loop() {
910        let grl = r#"
911        rule "RegularRule" salience 5 {
912            when
913                User.Active == true
914            then
915                log("active user");
916        }
917        "#;
918
919        let rules = GRLParser::parse_rules(grl).unwrap();
920        assert_eq!(rules.len(), 1);
921        let rule = &rules[0];
922        assert_eq!(rule.name, "RegularRule");
923        assert!(!rule.no_loop, "Rule should have no-loop=false by default");
924    }
925}