1use crate::errors::RuleEngineError;
87use crate::{Facts, Value};
88use super::backward_engine::{BackwardEngine, BackwardConfig};
89use super::search::SearchStrategy;
90use super::query::{QueryResult, QueryStats, ProofTrace};
91
92use std::collections::HashMap;
93
94#[derive(Debug, Clone, PartialEq)]
96pub enum GRLSearchStrategy {
97 DepthFirst,
98 BreadthFirst,
99 Iterative,
100}
101
102impl Default for GRLSearchStrategy {
103 fn default() -> Self {
104 GRLSearchStrategy::DepthFirst
105 }
106}
107
108#[derive(Debug, Clone)]
110pub struct QueryAction {
111 pub assignments: Vec<(String, String)>,
113 pub calls: Vec<String>,
115}
116
117impl QueryAction {
118 pub fn new() -> Self {
119 QueryAction {
120 assignments: Vec::new(),
121 calls: Vec::new(),
122 }
123 }
124
125 pub fn execute(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
127 for (var_name, value_str) in &self.assignments {
129 let value = if value_str == "true" {
131 Value::Boolean(true)
132 } else if value_str == "false" {
133 Value::Boolean(false)
134 } else if let Ok(n) = value_str.parse::<f64>() {
135 Value::Number(n)
136 } else {
137 let cleaned = value_str.trim_matches('"');
139 Value::String(cleaned.to_string())
140 };
141
142 facts.set(var_name, value);
143 }
144
145 for call in &self.calls {
147 self.execute_function_call(call)?;
148 }
149
150 Ok(())
151 }
152
153 fn execute_function_call(&self, call: &str) -> Result<(), RuleEngineError> {
155 let call = call.trim();
156
157 if let Some(open_paren) = call.find('(') {
159 let func_name = call[..open_paren].trim();
160
161 if let Some(close_paren) = call.rfind(')') {
163 let args_str = &call[open_paren + 1..close_paren];
164
165 match func_name {
166 "LogMessage" => {
167 let message = args_str.trim().trim_matches('"').trim_matches('\'');
169 println!("[LOG] {}", message);
170 }
171 "Request" => {
172 let message = args_str.trim().trim_matches('"').trim_matches('\'');
174 println!("[REQUEST] {}", message);
175 }
176 "Print" => {
177 let message = args_str.trim().trim_matches('"').trim_matches('\'');
179 println!("{}", message);
180 }
181 "Debug" => {
182 let message = args_str.trim().trim_matches('"').trim_matches('\'');
184 eprintln!("[DEBUG] {}", message);
185 }
186 other => {
187 eprintln!("[WARNING] Unknown function call in query action: {}({})", other, args_str);
189 }
190 }
191 } else {
192 return Err(RuleEngineError::ParseError {
193 message: format!("Malformed function call (missing closing paren): {}", call),
194 });
195 }
196 } else {
197 return Err(RuleEngineError::ParseError {
198 message: format!("Malformed function call (missing opening paren): {}", call),
199 });
200 }
201
202 Ok(())
203 }
204}
205
206#[derive(Debug, Clone)]
208pub struct GRLQuery {
209 pub name: String,
211
212 pub goal: String,
214
215 pub strategy: GRLSearchStrategy,
217
218 pub max_depth: usize,
220
221 pub max_solutions: usize,
223
224 pub enable_memoization: bool,
226
227 pub on_success: Option<QueryAction>,
229
230 pub on_failure: Option<QueryAction>,
232
233 pub on_missing: Option<QueryAction>,
235
236 pub params: HashMap<String, String>, pub when_condition: Option<String>,
241}
242
243impl GRLQuery {
244 pub fn new(name: String, goal: String) -> Self {
246 GRLQuery {
247 name,
248 goal,
249 strategy: GRLSearchStrategy::default(),
250 max_depth: 10,
251 max_solutions: 1,
252 enable_memoization: true,
253 on_success: None,
254 on_failure: None,
255 on_missing: None,
256 params: HashMap::new(),
257 when_condition: None,
258 }
259 }
260
261 pub fn with_strategy(mut self, strategy: GRLSearchStrategy) -> Self {
263 self.strategy = strategy;
264 self
265 }
266
267 pub fn with_max_depth(mut self, max_depth: usize) -> Self {
269 self.max_depth = max_depth;
270 self
271 }
272
273 pub fn with_max_solutions(mut self, max_solutions: usize) -> Self {
275 self.max_solutions = max_solutions;
276 self
277 }
278
279 pub fn with_memoization(mut self, enable: bool) -> Self {
281 self.enable_memoization = enable;
282 self
283 }
284
285 pub fn with_on_success(mut self, action: QueryAction) -> Self {
287 self.on_success = Some(action);
288 self
289 }
290
291 pub fn with_on_failure(mut self, action: QueryAction) -> Self {
293 self.on_failure = Some(action);
294 self
295 }
296
297 pub fn with_on_missing(mut self, action: QueryAction) -> Self {
299 self.on_missing = Some(action);
300 self
301 }
302
303 pub fn with_param(mut self, name: String, type_name: String) -> Self {
305 self.params.insert(name, type_name);
306 self
307 }
308
309 pub fn with_when(mut self, condition: String) -> Self {
311 self.when_condition = Some(condition);
312 self
313 }
314
315 pub fn should_execute(&self, _facts: &Facts) -> Result<bool, RuleEngineError> {
317 if self.when_condition.is_none() {
319 return Ok(true);
320 }
321
322 if let Some(ref cond_str) = self.when_condition {
324 use crate::backward::expression::ExpressionParser;
325
326 match ExpressionParser::parse(cond_str) {
327 Ok(expr) => Ok(expr.is_satisfied(_facts)),
328 Err(e) => Err(e),
329 }
330 } else {
331 Ok(true)
332 }
333 }
334
335 pub fn execute_success_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
337 if let Some(ref action) = self.on_success {
338 action.execute(facts)?;
339 }
340 Ok(())
341 }
342
343 pub fn execute_failure_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
345 if let Some(ref action) = self.on_failure {
346 action.execute(facts)?;
347 }
348 Ok(())
349 }
350
351 pub fn execute_missing_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
353 if let Some(ref action) = self.on_missing {
354 action.execute(facts)?;
355 }
356 Ok(())
357 }
358
359 pub fn to_config(&self) -> BackwardConfig {
361 let search_strategy = match self.strategy {
362 GRLSearchStrategy::DepthFirst => SearchStrategy::DepthFirst,
363 GRLSearchStrategy::BreadthFirst => SearchStrategy::BreadthFirst,
364 GRLSearchStrategy::Iterative => SearchStrategy::Iterative,
365 };
366
367 BackwardConfig {
368 strategy: search_strategy,
369 max_depth: self.max_depth,
370 enable_memoization: self.enable_memoization,
371 max_solutions: self.max_solutions,
372 }
373 }
374}
375
376pub struct GRLQueryParser;
378
379impl GRLQueryParser {
380 pub fn parse(input: &str) -> Result<GRLQuery, RuleEngineError> {
393 let input = input.trim();
394
395 let name = Self::extract_query_name(input)?;
397
398 let goal = Self::extract_goal(input)?;
400
401 let mut query = GRLQuery::new(name, goal);
403
404 if let Some(strategy) = Self::extract_strategy(input) {
406 query.strategy = strategy;
407 }
408
409 if let Some(max_depth) = Self::extract_max_depth(input) {
410 query.max_depth = max_depth;
411 }
412
413 if let Some(max_solutions) = Self::extract_max_solutions(input) {
414 query.max_solutions = max_solutions;
415 }
416
417 if let Some(enable_memo) = Self::extract_memoization(input) {
418 query.enable_memoization = enable_memo;
419 }
420
421 if let Some(action) = Self::extract_on_success(input)? {
423 query.on_success = Some(action);
424 }
425
426 if let Some(action) = Self::extract_on_failure(input)? {
427 query.on_failure = Some(action);
428 }
429
430 if let Some(action) = Self::extract_on_missing(input)? {
431 query.on_missing = Some(action);
432 }
433
434 if let Some(condition) = Self::extract_when_condition(input)? {
436 query.when_condition = Some(condition);
437 }
438
439 Ok(query)
440 }
441
442 fn extract_query_name(input: &str) -> Result<String, RuleEngineError> {
443 let re = regex::Regex::new(r#"query\s+"([^"]+)"\s*\{"#).unwrap();
444 if let Some(caps) = re.captures(input) {
445 Ok(caps[1].to_string())
446 } else {
447 Err(RuleEngineError::ParseError {
448 message: "Invalid query syntax: missing query name".to_string(),
449 })
450 }
451 }
452
453 fn extract_goal(input: &str) -> Result<String, RuleEngineError> {
454 let re = regex::Regex::new(r"goal:\s*([^\n}]+)").unwrap();
455 if let Some(caps) = re.captures(input) {
456 let goal_str = caps[1].trim().to_string();
457 Ok(goal_str)
458 } else {
459 Err(RuleEngineError::ParseError {
460 message: "Invalid query syntax: missing goal".to_string(),
461 })
462 }
463 }
464
465 fn extract_strategy(input: &str) -> Option<GRLSearchStrategy> {
466 let re = regex::Regex::new(r"strategy:\s*([a-z-]+)").unwrap();
467 re.captures(input).and_then(|caps| {
468 match caps[1].trim() {
469 "depth-first" => Some(GRLSearchStrategy::DepthFirst),
470 "breadth-first" => Some(GRLSearchStrategy::BreadthFirst),
471 "iterative" => Some(GRLSearchStrategy::Iterative),
472 _ => None,
473 }
474 })
475 }
476
477 fn extract_max_depth(input: &str) -> Option<usize> {
478 let re = regex::Regex::new(r"max-depth:\s*(\d+)").unwrap();
479 re.captures(input)
480 .and_then(|caps| caps[1].parse().ok())
481 }
482
483 fn extract_max_solutions(input: &str) -> Option<usize> {
484 let re = regex::Regex::new(r"max-solutions:\s*(\d+)").unwrap();
485 re.captures(input)
486 .and_then(|caps| caps[1].parse().ok())
487 }
488
489 fn extract_memoization(input: &str) -> Option<bool> {
490 let re = regex::Regex::new(r"enable-memoization:\s*(true|false)").unwrap();
491 re.captures(input).and_then(|caps| {
492 match caps[1].trim() {
493 "true" => Some(true),
494 "false" => Some(false),
495 _ => None,
496 }
497 })
498 }
499
500 fn extract_on_success(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
501 Self::extract_action_block(input, "on-success")
502 }
503
504 fn extract_on_failure(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
505 Self::extract_action_block(input, "on-failure")
506 }
507
508 fn extract_on_missing(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
509 Self::extract_action_block(input, "on-missing")
510 }
511
512 fn extract_action_block(input: &str, action_name: &str) -> Result<Option<QueryAction>, RuleEngineError> {
513 let pattern = format!(r"{}:\s*\{{([^}}]+)\}}", action_name);
514 let re = regex::Regex::new(&pattern).unwrap();
515
516 if let Some(caps) = re.captures(input) {
517 let block = caps[1].trim();
518 let mut action = QueryAction::new();
519
520 let assign_re = regex::Regex::new(r"([A-Za-z_][A-Za-z0-9_.]*)\s*=\s*([^;]+);").unwrap();
522 for caps in assign_re.captures_iter(block) {
523 let var_name = caps[1].trim().to_string();
524 let value_str = caps[2].trim().to_string();
525 action.assignments.push((var_name, value_str));
526 }
527
528 let call_re = regex::Regex::new(r"([A-Za-z_][A-Za-z0-9_]*\([^)]*\));").unwrap();
530 for caps in call_re.captures_iter(block) {
531 action.calls.push(caps[1].trim().to_string());
532 }
533
534 Ok(Some(action))
535 } else {
536 Ok(None)
537 }
538 }
539
540 fn extract_when_condition(input: &str) -> Result<Option<String>, RuleEngineError> {
541 let re = regex::Regex::new(r"when:\s*([^\n}]+)").unwrap();
542 if let Some(caps) = re.captures(input) {
543 let condition_str = caps[1].trim().to_string();
544 Ok(Some(condition_str))
545 } else {
546 Ok(None)
547 }
548 }
549
550 pub fn parse_queries(input: &str) -> Result<Vec<GRLQuery>, RuleEngineError> {
552 let mut queries = Vec::new();
553
554 let parts: Vec<&str> = input.split("query").collect();
557
558 for part in parts.iter().skip(1) { let query_str = format!("query{}", part);
560 if let Some(end_idx) = find_matching_brace(&query_str) {
562 let complete_query = &query_str[..end_idx];
563 if let Ok(query) = Self::parse(complete_query) {
564 queries.push(query);
565 }
566 }
567 }
568
569 Ok(queries)
570 }
571}
572
573fn find_matching_brace(input: &str) -> Option<usize> {
575 let mut depth = 0;
576 let mut in_string = false;
577 let mut escape_next = false;
578
579 for (i, ch) in input.chars().enumerate() {
580 if escape_next {
581 escape_next = false;
582 continue;
583 }
584
585 match ch {
586 '\\' => escape_next = true,
587 '"' => in_string = !in_string,
588 '{' if !in_string => depth += 1,
589 '}' if !in_string => {
590 depth -= 1;
591 if depth == 0 {
592 return Some(i + 1);
593 }
594 }
595 _ => {}
596 }
597 }
598
599 None
600}
601
602pub struct GRLQueryExecutor;
604
605impl GRLQueryExecutor {
606 pub fn execute(
608 query: &GRLQuery,
609 bc_engine: &mut BackwardEngine,
610 facts: &mut Facts,
611 ) -> Result<QueryResult, RuleEngineError> {
612 if !query.should_execute(facts)? {
614 return Ok(QueryResult {
615 provable: false,
616 bindings: HashMap::new(),
617 proof_trace: ProofTrace { goal: String::new(), steps: Vec::new() },
618 missing_facts: Vec::new(),
619 stats: QueryStats::default(),
620 });
621 }
622
623 bc_engine.set_config(query.to_config());
625
626 let result = if query.goal.contains("&&") {
628 Self::execute_compound_and_goal(&query.goal, bc_engine, facts)?
630 } else {
631 bc_engine.query(&query.goal, facts)?
633 };
634
635 if result.provable {
637 query.execute_success_actions(facts)?;
638 } else if !result.missing_facts.is_empty() {
639 query.execute_missing_actions(facts)?;
640 } else {
641 query.execute_failure_actions(facts)?;
642 }
643
644 Ok(result)
645 }
646
647 fn execute_compound_and_goal(
649 goal_expr: &str,
650 bc_engine: &mut BackwardEngine,
651 facts: &mut Facts,
652 ) -> Result<QueryResult, RuleEngineError> {
653 let sub_goals: Vec<&str> = goal_expr.split("&&").map(|s| s.trim()).collect();
654
655 let mut all_provable = true;
656 let mut combined_bindings = HashMap::new();
657 let mut all_missing = Vec::new();
658 let mut combined_stats = QueryStats::default();
659
660 for (i, sub_goal) in sub_goals.iter().enumerate() {
661 let goal_satisfied = if sub_goal.contains("!=") {
663 use crate::backward::expression::ExpressionParser;
665
666 match ExpressionParser::parse(sub_goal) {
667 Ok(expr) => expr.is_satisfied(facts),
668 Err(_) => false,
669 }
670 } else {
671 let result = bc_engine.query(sub_goal, facts)?;
673 result.provable
674 };
675
676 if !goal_satisfied {
677 all_provable = false;
678 }
679
680 }
683
684 Ok(QueryResult {
685 provable: all_provable,
686 bindings: combined_bindings,
687 proof_trace: ProofTrace {
688 goal: goal_expr.to_string(),
689 steps: Vec::new()
690 },
691 missing_facts: all_missing,
692 stats: combined_stats,
693 })
694 }
695
696 pub fn execute_queries(
698 queries: &[GRLQuery],
699 bc_engine: &mut BackwardEngine,
700 facts: &mut Facts,
701 ) -> Result<Vec<QueryResult>, RuleEngineError> {
702 let mut results = Vec::new();
703
704 for query in queries {
705 let result = Self::execute(query, bc_engine, facts)?;
706 results.push(result);
707 }
708
709 Ok(results)
710 }
711}
712
713#[cfg(test)]
714mod tests {
715 use super::*;
716
717 #[test]
718 fn test_parse_simple_query() {
719 let input = r#"
720 query "TestQuery" {
721 goal: User.IsVIP == true
722 }
723 "#;
724
725 let query = GRLQueryParser::parse(input).unwrap();
726 assert_eq!(query.name, "TestQuery");
727 assert_eq!(query.strategy, GRLSearchStrategy::DepthFirst);
728 assert_eq!(query.max_depth, 10);
729 }
730
731 #[test]
732 fn test_parse_query_with_strategy() {
733 let input = r#"
734 query "TestQuery" {
735 goal: User.IsVIP == true
736 strategy: breadth-first
737 max-depth: 5
738 }
739 "#;
740
741 let query = GRLQueryParser::parse(input).unwrap();
742 assert_eq!(query.strategy, GRLSearchStrategy::BreadthFirst);
743 assert_eq!(query.max_depth, 5);
744 }
745
746 #[test]
747 fn test_parse_query_with_actions() {
748 let input = r#"
749 query "TestQuery" {
750 goal: User.IsVIP == true
751 on-success: {
752 User.DiscountRate = 0.2;
753 LogMessage("VIP confirmed");
754 }
755 }
756 "#;
757
758 let query = GRLQueryParser::parse(input).unwrap();
759 assert!(query.on_success.is_some());
760
761 let action = query.on_success.unwrap();
762 assert_eq!(action.assignments.len(), 1);
763 assert_eq!(action.calls.len(), 1);
764 }
765
766 #[test]
767 fn test_parse_query_with_when_condition() {
768 let input = r#"
769 query "TestQuery" {
770 goal: User.IsVIP == true
771 when: Environment.Mode == "Production"
772 }
773 "#;
774
775 let query = GRLQueryParser::parse(input).unwrap();
776 assert!(query.when_condition.is_some());
777 }
778
779 #[test]
780 fn test_parse_multiple_queries() {
781 let input = r#"
782 query "Query1" {
783 goal: A == true
784 }
785
786 query "Query2" {
787 goal: B == true
788 strategy: breadth-first
789 }
790 "#;
791
792 let queries = GRLQueryParser::parse_queries(input).unwrap();
793 assert_eq!(queries.len(), 2);
794 assert_eq!(queries[0].name, "Query1");
795 assert_eq!(queries[1].name, "Query2");
796 }
797
798 #[test]
799 fn test_query_config_conversion() {
800 let query = GRLQuery::new(
801 "Test".to_string(),
802 "X == true".to_string(),
803 )
804 .with_strategy(GRLSearchStrategy::BreadthFirst)
805 .with_max_depth(15)
806 .with_memoization(false);
807
808 let config = query.to_config();
809 assert_eq!(config.max_depth, 15);
810 assert_eq!(config.enable_memoization, false);
811 }
812
813 #[test]
814 fn test_action_execution() {
815 let mut facts = Facts::new();
816
817 let mut action = QueryAction::new();
818 action.assignments.push((
819 "User.DiscountRate".to_string(),
820 "0.2".to_string(),
821 ));
822
823 action.execute(&mut facts).unwrap();
824
825 let value = facts.get("User.DiscountRate");
827 assert!(value.is_some());
828 }
829
830 #[test]
831 fn test_should_execute_no_condition() {
832 let query = GRLQuery::new("Q".to_string(), "X == true".to_string());
833 let facts = Facts::new();
834 let res = query.should_execute(&facts).unwrap();
836 assert!(res);
837 }
838
839 #[test]
840 fn test_should_execute_condition_true() {
841 let mut facts = Facts::new();
842 facts.set("Environment.Mode", Value::String("Production".to_string()));
843
844 let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
845 .with_when("Environment.Mode == \"Production\"".to_string());
846
847 let res = query.should_execute(&facts).unwrap();
848 assert!(res, "expected when condition to be satisfied");
849 }
850
851 #[test]
852 fn test_should_execute_condition_false() {
853 let mut facts = Facts::new();
854 facts.set("Environment.Mode", Value::String("Development".to_string()));
855
856 let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
857 .with_when("Environment.Mode == \"Production\"".to_string());
858
859 let res = query.should_execute(&facts).unwrap();
860 assert!(!res, "expected when condition to be unsatisfied");
861 }
862
863 #[test]
864 fn test_should_execute_parse_error_propagates() {
865 let facts = Facts::new();
866 let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
868 .with_when("Environment.Mode == \"Production".to_string());
869
870 let res = query.should_execute(&facts);
871 assert!(res.is_err(), "expected parse error to propagate");
872 }
873}