rust_rule_engine/backward/
grl_query.rs

1/// GRL Query Syntax Implementation
2/// 
3/// Provides parsing and execution of backward chaining queries defined in GRL syntax.
4/// 
5/// # Example
6/// ```grl
7/// query "CheckVIPStatus" {
8///     goal: User.IsVIP == true
9///     strategy: depth-first
10///     max-depth: 10
11///     on-success: {
12///         User.DiscountRate = 0.2;
13///         LogMessage("VIP confirmed");
14///     }
15/// }
16/// ```
17
18use crate::errors::RuleEngineError;
19use crate::{Facts, Value};
20use super::backward_engine::{BackwardEngine, BackwardConfig};
21use super::search::SearchStrategy;
22use super::query::{QueryResult, QueryStats, ProofTrace};
23
24use std::collections::HashMap;
25
26/// Search strategy option for queries
27#[derive(Debug, Clone, PartialEq)]
28pub enum GRLSearchStrategy {
29    DepthFirst,
30    BreadthFirst,
31    Iterative,
32}
33
34impl Default for GRLSearchStrategy {
35    fn default() -> Self {
36        GRLSearchStrategy::DepthFirst
37    }
38}
39
40/// Action to execute based on query result
41#[derive(Debug, Clone)]
42pub struct QueryAction {
43    /// Assignment: Variable = Value (as string to be parsed)
44    pub assignments: Vec<(String, String)>,
45    /// Function/method calls
46    pub calls: Vec<String>,
47}
48
49impl QueryAction {
50    pub fn new() -> Self {
51        QueryAction {
52            assignments: Vec::new(),
53            calls: Vec::new(),
54        }
55    }
56
57    /// Execute the action on the given facts
58    pub fn execute(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
59        // Execute assignments - for now just log them
60        for (var_name, value_str) in &self.assignments {
61            // Simple value parsing
62            let value = if value_str == "true" {
63                Value::Boolean(true)
64            } else if value_str == "false" {
65                Value::Boolean(false)
66            } else if let Ok(n) = value_str.parse::<f64>() {
67                Value::Number(n)
68            } else {
69                // Remove quotes if present
70                let cleaned = value_str.trim_matches('"');
71                Value::String(cleaned.to_string())
72            };
73            
74            facts.set(var_name, value);
75        }
76
77        // Execute function calls (log, etc.)
78        for call in &self.calls {
79            // Simple logging for now
80            if call.starts_with("LogMessage") {
81                println!("[Query Action] {}", call);
82            } else if call.starts_with("Request") {
83                println!("[Query Action] {}", call);
84            }
85        }
86
87        Ok(())
88    }
89}
90
91/// A GRL Query definition
92#[derive(Debug, Clone)]
93pub struct GRLQuery {
94    /// Query name
95    pub name: String,
96    
97    /// Goal pattern to prove (as string expression)
98    pub goal: String,
99    
100    /// Search strategy
101    pub strategy: GRLSearchStrategy,
102    
103    /// Maximum search depth
104    pub max_depth: usize,
105    
106    /// Maximum number of solutions
107    pub max_solutions: usize,
108    
109    /// Enable memoization
110    pub enable_memoization: bool,
111    
112    /// Action on success
113    pub on_success: Option<QueryAction>,
114    
115    /// Action on failure
116    pub on_failure: Option<QueryAction>,
117    
118    /// Action on missing facts
119    pub on_missing: Option<QueryAction>,
120    
121    /// Parameters for parameterized queries
122    pub params: HashMap<String, String>, // param_name -> type
123    
124    /// Conditional execution (as string condition)
125    pub when_condition: Option<String>,
126}
127
128impl GRLQuery {
129    /// Create a new query with defaults
130    pub fn new(name: String, goal: String) -> Self {
131        GRLQuery {
132            name,
133            goal,
134            strategy: GRLSearchStrategy::default(),
135            max_depth: 10,
136            max_solutions: 1,
137            enable_memoization: true,
138            on_success: None,
139            on_failure: None,
140            on_missing: None,
141            params: HashMap::new(),
142            when_condition: None,
143        }
144    }
145
146    /// Set search strategy
147    pub fn with_strategy(mut self, strategy: GRLSearchStrategy) -> Self {
148        self.strategy = strategy;
149        self
150    }
151
152    /// Set max depth
153    pub fn with_max_depth(mut self, max_depth: usize) -> Self {
154        self.max_depth = max_depth;
155        self
156    }
157
158    /// Set max solutions
159    pub fn with_max_solutions(mut self, max_solutions: usize) -> Self {
160        self.max_solutions = max_solutions;
161        self
162    }
163
164    /// Set memoization
165    pub fn with_memoization(mut self, enable: bool) -> Self {
166        self.enable_memoization = enable;
167        self
168    }
169
170    /// Add success action
171    pub fn with_on_success(mut self, action: QueryAction) -> Self {
172        self.on_success = Some(action);
173        self
174    }
175
176    /// Add failure action
177    pub fn with_on_failure(mut self, action: QueryAction) -> Self {
178        self.on_failure = Some(action);
179        self
180    }
181
182    /// Add missing facts action
183    pub fn with_on_missing(mut self, action: QueryAction) -> Self {
184        self.on_missing = Some(action);
185        self
186    }
187
188    /// Add parameter
189    pub fn with_param(mut self, name: String, type_name: String) -> Self {
190        self.params.insert(name, type_name);
191        self
192    }
193
194    /// Set conditional execution
195    pub fn with_when(mut self, condition: String) -> Self {
196        self.when_condition = Some(condition);
197        self
198    }
199
200    /// Check if query should execute based on when condition
201    pub fn should_execute(&self, _facts: &Facts) -> Result<bool, RuleEngineError> {
202        // TODO: Implement condition evaluation
203        // For now, always execute if no condition, or return true if condition exists
204        Ok(true)
205    }
206
207    /// Execute success actions
208    pub fn execute_success_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
209        if let Some(ref action) = self.on_success {
210            action.execute(facts)?;
211        }
212        Ok(())
213    }
214
215    /// Execute failure actions
216    pub fn execute_failure_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
217        if let Some(ref action) = self.on_failure {
218            action.execute(facts)?;
219        }
220        Ok(())
221    }
222
223    /// Execute missing facts actions
224    pub fn execute_missing_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
225        if let Some(ref action) = self.on_missing {
226            action.execute(facts)?;
227        }
228        Ok(())
229    }
230
231    /// Convert to BackwardConfig
232    pub fn to_config(&self) -> BackwardConfig {
233        let search_strategy = match self.strategy {
234            GRLSearchStrategy::DepthFirst => SearchStrategy::DepthFirst,
235            GRLSearchStrategy::BreadthFirst => SearchStrategy::BreadthFirst,
236            GRLSearchStrategy::Iterative => SearchStrategy::DepthFirst, // TODO: implement iterative
237        };
238
239        BackwardConfig {
240            strategy: search_strategy,
241            max_depth: self.max_depth,
242            enable_memoization: self.enable_memoization,
243            max_solutions: self.max_solutions,
244        }
245    }
246}
247
248/// Parser for GRL Query syntax
249pub struct GRLQueryParser;
250
251impl GRLQueryParser {
252    /// Parse a query from string
253    /// 
254    /// # Example
255    /// ```
256    /// let query_str = r#"
257    /// query "CheckVIP" {
258    ///     goal: User.IsVIP == true
259    ///     strategy: depth-first
260    /// }
261    /// "#;
262    /// let query = GRLQueryParser::parse(query_str)?;
263    /// ```
264    pub fn parse(input: &str) -> Result<GRLQuery, RuleEngineError> {
265        let input = input.trim();
266
267        // Extract query name
268        let name = Self::extract_query_name(input)?;
269
270        // Extract goal
271        let goal = Self::extract_goal(input)?;
272
273        // Create base query
274        let mut query = GRLQuery::new(name, goal);
275
276        // Parse optional attributes
277        if let Some(strategy) = Self::extract_strategy(input) {
278            query.strategy = strategy;
279        }
280
281        if let Some(max_depth) = Self::extract_max_depth(input) {
282            query.max_depth = max_depth;
283        }
284
285        if let Some(max_solutions) = Self::extract_max_solutions(input) {
286            query.max_solutions = max_solutions;
287        }
288
289        if let Some(enable_memo) = Self::extract_memoization(input) {
290            query.enable_memoization = enable_memo;
291        }
292
293        // Parse actions
294        if let Some(action) = Self::extract_on_success(input)? {
295            query.on_success = Some(action);
296        }
297
298        if let Some(action) = Self::extract_on_failure(input)? {
299            query.on_failure = Some(action);
300        }
301
302        if let Some(action) = Self::extract_on_missing(input)? {
303            query.on_missing = Some(action);
304        }
305
306        // Parse when condition
307        if let Some(condition) = Self::extract_when_condition(input)? {
308            query.when_condition = Some(condition);
309        }
310
311        Ok(query)
312    }
313
314    fn extract_query_name(input: &str) -> Result<String, RuleEngineError> {
315        let re = regex::Regex::new(r#"query\s+"([^"]+)"\s*\{"#).unwrap();
316        if let Some(caps) = re.captures(input) {
317            Ok(caps[1].to_string())
318        } else {
319            Err(RuleEngineError::ParseError {
320                message: "Invalid query syntax: missing query name".to_string(),
321            })
322        }
323    }
324
325    fn extract_goal(input: &str) -> Result<String, RuleEngineError> {
326        let re = regex::Regex::new(r"goal:\s*([^\n}]+)").unwrap();
327        if let Some(caps) = re.captures(input) {
328            let goal_str = caps[1].trim().to_string();
329            Ok(goal_str)
330        } else {
331            Err(RuleEngineError::ParseError {
332                message: "Invalid query syntax: missing goal".to_string(),
333            })
334        }
335    }
336
337    fn extract_strategy(input: &str) -> Option<GRLSearchStrategy> {
338        let re = regex::Regex::new(r"strategy:\s*([a-z-]+)").unwrap();
339        re.captures(input).and_then(|caps| {
340            match caps[1].trim() {
341                "depth-first" => Some(GRLSearchStrategy::DepthFirst),
342                "breadth-first" => Some(GRLSearchStrategy::BreadthFirst),
343                "iterative" => Some(GRLSearchStrategy::Iterative),
344                _ => None,
345            }
346        })
347    }
348
349    fn extract_max_depth(input: &str) -> Option<usize> {
350        let re = regex::Regex::new(r"max-depth:\s*(\d+)").unwrap();
351        re.captures(input)
352            .and_then(|caps| caps[1].parse().ok())
353    }
354
355    fn extract_max_solutions(input: &str) -> Option<usize> {
356        let re = regex::Regex::new(r"max-solutions:\s*(\d+)").unwrap();
357        re.captures(input)
358            .and_then(|caps| caps[1].parse().ok())
359    }
360
361    fn extract_memoization(input: &str) -> Option<bool> {
362        let re = regex::Regex::new(r"enable-memoization:\s*(true|false)").unwrap();
363        re.captures(input).and_then(|caps| {
364            match caps[1].trim() {
365                "true" => Some(true),
366                "false" => Some(false),
367                _ => None,
368            }
369        })
370    }
371
372    fn extract_on_success(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
373        Self::extract_action_block(input, "on-success")
374    }
375
376    fn extract_on_failure(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
377        Self::extract_action_block(input, "on-failure")
378    }
379
380    fn extract_on_missing(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
381        Self::extract_action_block(input, "on-missing")
382    }
383
384    fn extract_action_block(input: &str, action_name: &str) -> Result<Option<QueryAction>, RuleEngineError> {
385        let pattern = format!(r"{}:\s*\{{([^}}]+)\}}", action_name);
386        let re = regex::Regex::new(&pattern).unwrap();
387        
388        if let Some(caps) = re.captures(input) {
389            let block = caps[1].trim();
390            let mut action = QueryAction::new();
391
392            // Parse assignments: Variable = Value
393            let assign_re = regex::Regex::new(r"([A-Za-z_][A-Za-z0-9_.]*)\s*=\s*([^;]+);").unwrap();
394            for caps in assign_re.captures_iter(block) {
395                let var_name = caps[1].trim().to_string();
396                let value_str = caps[2].trim().to_string();
397                action.assignments.push((var_name, value_str));
398            }
399
400            // Parse function calls: Function(...)
401            let call_re = regex::Regex::new(r"([A-Za-z_][A-Za-z0-9_]*\([^)]*\));").unwrap();
402            for caps in call_re.captures_iter(block) {
403                action.calls.push(caps[1].trim().to_string());
404            }
405
406            Ok(Some(action))
407        } else {
408            Ok(None)
409        }
410    }
411
412    fn extract_when_condition(input: &str) -> Result<Option<String>, RuleEngineError> {
413        let re = regex::Regex::new(r"when:\s*([^\n}]+)").unwrap();
414        if let Some(caps) = re.captures(input) {
415            let condition_str = caps[1].trim().to_string();
416            Ok(Some(condition_str))
417        } else {
418            Ok(None)
419        }
420    }
421
422    /// Parse multiple queries from a file
423    pub fn parse_queries(input: &str) -> Result<Vec<GRLQuery>, RuleEngineError> {
424        let mut queries = Vec::new();
425        
426        // Find all query blocks - use simpler approach
427        // Split by "query" keyword and process each block
428        let parts: Vec<&str> = input.split("query").collect();
429        
430        for part in parts.iter().skip(1) { // Skip first empty part
431            let query_str = format!("query{}", part);
432            // Find the matching closing brace
433            if let Some(end_idx) = find_matching_brace(&query_str) {
434                let complete_query = &query_str[..end_idx];
435                if let Ok(query) = Self::parse(complete_query) {
436                    queries.push(query);
437                }
438            }
439        }
440
441        Ok(queries)
442    }
443}
444
445// Helper function to find matching closing brace
446fn find_matching_brace(input: &str) -> Option<usize> {
447    let mut depth = 0;
448    let mut in_string = false;
449    let mut escape_next = false;
450    
451    for (i, ch) in input.chars().enumerate() {
452        if escape_next {
453            escape_next = false;
454            continue;
455        }
456        
457        match ch {
458            '\\' => escape_next = true,
459            '"' => in_string = !in_string,
460            '{' if !in_string => depth += 1,
461            '}' if !in_string => {
462                depth -= 1;
463                if depth == 0 {
464                    return Some(i + 1);
465                }
466            }
467            _ => {}
468        }
469    }
470    
471    None
472}
473
474/// Executor for GRL queries
475pub struct GRLQueryExecutor;
476
477impl GRLQueryExecutor {
478    /// Execute a single query
479    pub fn execute(
480        query: &GRLQuery,
481        bc_engine: &mut BackwardEngine,
482        facts: &mut Facts,
483    ) -> Result<QueryResult, RuleEngineError> {
484        // Check when condition
485        if !query.should_execute(facts)? {
486            return Ok(QueryResult {
487                provable: false,
488                bindings: HashMap::new(),
489                proof_trace: ProofTrace { goal: String::new(), steps: Vec::new() },
490                missing_facts: Vec::new(),
491                stats: QueryStats::default(),
492            });
493        }
494
495        // Apply config
496        bc_engine.set_config(query.to_config());
497
498        // Parse compound goals (support && and !=)
499        let result = if query.goal.contains("&&") {
500            // Split on && and check all goals
501            Self::execute_compound_and_goal(&query.goal, bc_engine, facts)?
502        } else {
503            // Single goal
504            bc_engine.query(&query.goal, facts)?
505        };
506
507        // Execute appropriate actions
508        if result.provable {
509            query.execute_success_actions(facts)?;
510        } else if !result.missing_facts.is_empty() {
511            query.execute_missing_actions(facts)?;
512        } else {
513            query.execute_failure_actions(facts)?;
514        }
515
516        Ok(result)
517    }
518
519    /// Execute compound AND goal (all must be true)
520    fn execute_compound_and_goal(
521        goal_expr: &str,
522        bc_engine: &mut BackwardEngine,
523        facts: &mut Facts,
524    ) -> Result<QueryResult, RuleEngineError> {
525        let sub_goals: Vec<&str> = goal_expr.split("&&").map(|s| s.trim()).collect();
526        
527        let mut all_provable = true;
528        let mut combined_bindings = HashMap::new();
529        let mut all_missing = Vec::new();
530        let mut combined_stats = QueryStats::default();
531        
532        for (i, sub_goal) in sub_goals.iter().enumerate() {
533            // Handle != by using expression parser directly
534            let goal_satisfied = if sub_goal.contains("!=") {
535                // Parse and evaluate the expression directly
536                use crate::backward::expression::ExpressionParser;
537
538                match ExpressionParser::parse(sub_goal) {
539                    Ok(expr) => expr.is_satisfied(facts),
540                    Err(_) => false,
541                }
542            } else {
543                // Normal == comparison, use backward chaining
544                let result = bc_engine.query(sub_goal, facts)?;
545                result.provable
546            };
547
548            if !goal_satisfied {
549                all_provable = false;
550            }
551
552            // Note: For compound goals with !=, we don't track missing facts well yet
553            // This is a simplification for now
554        }
555        
556        Ok(QueryResult {
557            provable: all_provable,
558            bindings: combined_bindings,
559            proof_trace: ProofTrace { 
560                goal: goal_expr.to_string(), 
561                steps: Vec::new() 
562            },
563            missing_facts: all_missing,
564            stats: combined_stats,
565        })
566    }
567
568    /// Execute multiple queries
569    pub fn execute_queries(
570        queries: &[GRLQuery],
571        bc_engine: &mut BackwardEngine,
572        facts: &mut Facts,
573    ) -> Result<Vec<QueryResult>, RuleEngineError> {
574        let mut results = Vec::new();
575
576        for query in queries {
577            let result = Self::execute(query, bc_engine, facts)?;
578            results.push(result);
579        }
580
581        Ok(results)
582    }
583}
584
585#[cfg(test)]
586mod tests {
587    use super::*;
588
589    #[test]
590    fn test_parse_simple_query() {
591        let input = r#"
592        query "TestQuery" {
593            goal: User.IsVIP == true
594        }
595        "#;
596
597        let query = GRLQueryParser::parse(input).unwrap();
598        assert_eq!(query.name, "TestQuery");
599        assert_eq!(query.strategy, GRLSearchStrategy::DepthFirst);
600        assert_eq!(query.max_depth, 10);
601    }
602
603    #[test]
604    fn test_parse_query_with_strategy() {
605        let input = r#"
606        query "TestQuery" {
607            goal: User.IsVIP == true
608            strategy: breadth-first
609            max-depth: 5
610        }
611        "#;
612
613        let query = GRLQueryParser::parse(input).unwrap();
614        assert_eq!(query.strategy, GRLSearchStrategy::BreadthFirst);
615        assert_eq!(query.max_depth, 5);
616    }
617
618    #[test]
619    fn test_parse_query_with_actions() {
620        let input = r#"
621        query "TestQuery" {
622            goal: User.IsVIP == true
623            on-success: {
624                User.DiscountRate = 0.2;
625                LogMessage("VIP confirmed");
626            }
627        }
628        "#;
629
630        let query = GRLQueryParser::parse(input).unwrap();
631        assert!(query.on_success.is_some());
632        
633        let action = query.on_success.unwrap();
634        assert_eq!(action.assignments.len(), 1);
635        assert_eq!(action.calls.len(), 1);
636    }
637
638    #[test]
639    fn test_parse_query_with_when_condition() {
640        let input = r#"
641        query "TestQuery" {
642            goal: User.IsVIP == true
643            when: Environment.Mode == "Production"
644        }
645        "#;
646
647        let query = GRLQueryParser::parse(input).unwrap();
648        assert!(query.when_condition.is_some());
649    }
650
651    #[test]
652    fn test_parse_multiple_queries() {
653        let input = r#"
654        query "Query1" {
655            goal: A == true
656        }
657        
658        query "Query2" {
659            goal: B == true
660            strategy: breadth-first
661        }
662        "#;
663
664        let queries = GRLQueryParser::parse_queries(input).unwrap();
665        assert_eq!(queries.len(), 2);
666        assert_eq!(queries[0].name, "Query1");
667        assert_eq!(queries[1].name, "Query2");
668    }
669
670    #[test]
671    fn test_query_config_conversion() {
672        let query = GRLQuery::new(
673            "Test".to_string(),
674            "X == true".to_string(),
675        )
676        .with_strategy(GRLSearchStrategy::BreadthFirst)
677        .with_max_depth(15)
678        .with_memoization(false);
679
680        let config = query.to_config();
681        assert_eq!(config.max_depth, 15);
682        assert_eq!(config.enable_memoization, false);
683    }
684
685    #[test]
686    fn test_action_execution() {
687        let mut facts = Facts::new();
688
689        let mut action = QueryAction::new();
690        action.assignments.push((
691            "User.DiscountRate".to_string(),
692            "0.2".to_string(),
693        ));
694
695        action.execute(&mut facts).unwrap();
696
697        // Check that assignment was executed
698        let value = facts.get("User.DiscountRate");
699        assert!(value.is_some());
700    }
701}