1use super::backward_engine::{BackwardConfig, BackwardEngine};
87use super::query::{ProofTrace, QueryResult, QueryStats};
88use super::search::SearchStrategy;
89use crate::errors::RuleEngineError;
90use crate::{Facts, Value};
91
92use std::collections::HashMap;
93
94#[derive(Debug, Clone, PartialEq, Default)]
96pub enum GRLSearchStrategy {
97 #[default]
98 DepthFirst,
99 BreadthFirst,
100 Iterative,
101}
102
103#[derive(Debug, Clone)]
105pub struct QueryAction {
106 pub assignments: Vec<(String, String)>,
108 pub calls: Vec<String>,
110}
111
112impl Default for QueryAction {
113 fn default() -> Self {
114 Self::new()
115 }
116}
117
118impl QueryAction {
119 pub fn new() -> Self {
120 QueryAction {
121 assignments: Vec::new(),
122 calls: Vec::new(),
123 }
124 }
125
126 pub fn execute(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
128 for (var_name, value_str) in &self.assignments {
130 let value = if value_str == "true" {
132 Value::Boolean(true)
133 } else if value_str == "false" {
134 Value::Boolean(false)
135 } else if let Ok(n) = value_str.parse::<f64>() {
136 Value::Number(n)
137 } else {
138 let cleaned = value_str.trim_matches('"');
140 Value::String(cleaned.to_string())
141 };
142
143 facts.set(var_name, value);
144 }
145
146 for call in &self.calls {
148 self.execute_function_call(call)?;
149 }
150
151 Ok(())
152 }
153
154 fn execute_function_call(&self, call: &str) -> Result<(), RuleEngineError> {
156 let call = call.trim();
157
158 if let Some(open_paren) = call.find('(') {
160 let func_name = call[..open_paren].trim();
161
162 if let Some(close_paren) = call.rfind(')') {
164 let args_str = &call[open_paren + 1..close_paren];
165
166 match func_name {
167 "LogMessage" => {
168 let message = args_str.trim().trim_matches('"').trim_matches('\'');
170 println!("[LOG] {}", message);
171 }
172 "Request" => {
173 let message = args_str.trim().trim_matches('"').trim_matches('\'');
175 println!("[REQUEST] {}", message);
176 }
177 "Print" => {
178 let message = args_str.trim().trim_matches('"').trim_matches('\'');
180 println!("{}", message);
181 }
182 "Debug" => {
183 let message = args_str.trim().trim_matches('"').trim_matches('\'');
185 eprintln!("[DEBUG] {}", message);
186 }
187 other => {
188 eprintln!(
190 "[WARNING] Unknown function call in query action: {}({})",
191 other, args_str
192 );
193 }
194 }
195 } else {
196 return Err(RuleEngineError::ParseError {
197 message: format!("Malformed function call (missing closing paren): {}", call),
198 });
199 }
200 } else {
201 return Err(RuleEngineError::ParseError {
202 message: format!("Malformed function call (missing opening paren): {}", call),
203 });
204 }
205
206 Ok(())
207 }
208}
209
210#[derive(Debug, Clone)]
212pub struct GRLQuery {
213 pub name: String,
215
216 pub goal: String,
218
219 pub strategy: GRLSearchStrategy,
221
222 pub max_depth: usize,
224
225 pub max_solutions: usize,
227
228 pub enable_memoization: bool,
230
231 pub enable_optimization: bool,
233
234 pub on_success: Option<QueryAction>,
236
237 pub on_failure: Option<QueryAction>,
239
240 pub on_missing: Option<QueryAction>,
242
243 pub params: HashMap<String, String>, pub when_condition: Option<String>,
248}
249
250impl GRLQuery {
251 pub fn new(name: String, goal: String) -> Self {
253 GRLQuery {
254 name,
255 goal,
256 strategy: GRLSearchStrategy::default(),
257 max_depth: 10,
258 max_solutions: 1,
259 enable_memoization: true,
260 enable_optimization: true,
261 on_success: None,
262 on_failure: None,
263 on_missing: None,
264 params: HashMap::new(),
265 when_condition: None,
266 }
267 }
268
269 pub fn with_strategy(mut self, strategy: GRLSearchStrategy) -> Self {
271 self.strategy = strategy;
272 self
273 }
274
275 pub fn with_max_depth(mut self, max_depth: usize) -> Self {
277 self.max_depth = max_depth;
278 self
279 }
280
281 pub fn with_max_solutions(mut self, max_solutions: usize) -> Self {
283 self.max_solutions = max_solutions;
284 self
285 }
286
287 pub fn with_memoization(mut self, enable: bool) -> Self {
289 self.enable_memoization = enable;
290 self
291 }
292
293 pub fn with_optimization(mut self, enable: bool) -> Self {
295 self.enable_optimization = enable;
296 self
297 }
298
299 pub fn with_on_success(mut self, action: QueryAction) -> Self {
301 self.on_success = Some(action);
302 self
303 }
304
305 pub fn with_on_failure(mut self, action: QueryAction) -> Self {
307 self.on_failure = Some(action);
308 self
309 }
310
311 pub fn with_on_missing(mut self, action: QueryAction) -> Self {
313 self.on_missing = Some(action);
314 self
315 }
316
317 pub fn with_param(mut self, name: String, type_name: String) -> Self {
319 self.params.insert(name, type_name);
320 self
321 }
322
323 pub fn with_when(mut self, condition: String) -> Self {
325 self.when_condition = Some(condition);
326 self
327 }
328
329 pub fn should_execute(&self, _facts: &Facts) -> Result<bool, RuleEngineError> {
331 if self.when_condition.is_none() {
333 return Ok(true);
334 }
335
336 if let Some(ref cond_str) = self.when_condition {
338 use crate::backward::expression::ExpressionParser;
339
340 match ExpressionParser::parse(cond_str) {
341 Ok(expr) => Ok(expr.is_satisfied(_facts)),
342 Err(e) => Err(e),
343 }
344 } else {
345 Ok(true)
346 }
347 }
348
349 pub fn execute_success_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
351 if let Some(ref action) = self.on_success {
352 action.execute(facts)?;
353 }
354 Ok(())
355 }
356
357 pub fn execute_failure_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
359 if let Some(ref action) = self.on_failure {
360 action.execute(facts)?;
361 }
362 Ok(())
363 }
364
365 pub fn execute_missing_actions(&self, facts: &mut Facts) -> Result<(), RuleEngineError> {
367 if let Some(ref action) = self.on_missing {
368 action.execute(facts)?;
369 }
370 Ok(())
371 }
372
373 pub fn to_config(&self) -> BackwardConfig {
375 let search_strategy = match self.strategy {
376 GRLSearchStrategy::DepthFirst => SearchStrategy::DepthFirst,
377 GRLSearchStrategy::BreadthFirst => SearchStrategy::BreadthFirst,
378 GRLSearchStrategy::Iterative => SearchStrategy::Iterative,
379 };
380
381 BackwardConfig {
382 strategy: search_strategy,
383 max_depth: self.max_depth,
384 enable_memoization: self.enable_memoization,
385 max_solutions: self.max_solutions,
386 }
387 }
388}
389
390pub struct GRLQueryParser;
392
393impl GRLQueryParser {
394 pub fn parse(input: &str) -> Result<GRLQuery, RuleEngineError> {
407 let input = input.trim();
408
409 let name = Self::extract_query_name(input)?;
411
412 let goal = Self::extract_goal(input)?;
414
415 let mut query = GRLQuery::new(name, goal);
417
418 if let Some(strategy) = Self::extract_strategy(input) {
420 query.strategy = strategy;
421 }
422
423 if let Some(max_depth) = Self::extract_max_depth(input) {
424 query.max_depth = max_depth;
425 }
426
427 if let Some(max_solutions) = Self::extract_max_solutions(input) {
428 query.max_solutions = max_solutions;
429 }
430
431 if let Some(enable_memo) = Self::extract_memoization(input) {
432 query.enable_memoization = enable_memo;
433 }
434
435 if let Some(enable_opt) = Self::extract_optimization(input) {
436 query.enable_optimization = enable_opt;
437 }
438
439 if let Some(action) = Self::extract_on_success(input)? {
441 query.on_success = Some(action);
442 }
443
444 if let Some(action) = Self::extract_on_failure(input)? {
445 query.on_failure = Some(action);
446 }
447
448 if let Some(action) = Self::extract_on_missing(input)? {
449 query.on_missing = Some(action);
450 }
451
452 if let Some(condition) = Self::extract_when_condition(input)? {
454 query.when_condition = Some(condition);
455 }
456
457 Ok(query)
458 }
459
460 fn extract_query_name(input: &str) -> Result<String, RuleEngineError> {
461 let re = rexile::Pattern::new(r#"query\s+"([^"]+)"\s*\{"#).unwrap();
462 if let Some(caps) = re.captures(input) {
463 Ok(caps[1].to_string())
464 } else {
465 Err(RuleEngineError::ParseError {
466 message: "Invalid query syntax: missing query name".to_string(),
467 })
468 }
469 }
470
471 fn extract_goal(input: &str) -> Result<String, RuleEngineError> {
472 if let Some(goal_start) = input.find("goal:") {
474 let after_goal = &input[goal_start + 5..]; let goal_end = Self::find_goal_end(after_goal)?;
479 let goal_str = after_goal[..goal_end].trim().to_string();
480
481 if goal_str.is_empty() {
482 return Err(RuleEngineError::ParseError {
483 message: "Invalid query syntax: empty goal".to_string(),
484 });
485 }
486
487 Ok(goal_str)
488 } else {
489 Err(RuleEngineError::ParseError {
490 message: "Invalid query syntax: missing goal".to_string(),
491 })
492 }
493 }
494
495 fn find_goal_end(input: &str) -> Result<usize, RuleEngineError> {
496 let mut paren_depth = 0;
497 let mut in_string = false;
498 let mut escape_next = false;
499
500 for (i, ch) in input.chars().enumerate() {
501 if escape_next {
502 escape_next = false;
503 continue;
504 }
505
506 match ch {
507 '\\' if in_string => escape_next = true,
508 '"' => in_string = !in_string,
509 '(' if !in_string => paren_depth += 1,
510 ')' if !in_string => {
511 if paren_depth == 0 {
512 return Err(RuleEngineError::ParseError {
513 message: format!(
514 "Parse error: Unexpected closing parenthesis at position {}",
515 i
516 ),
517 });
518 }
519 paren_depth -= 1;
520 }
521 '\n' if !in_string && paren_depth == 0 => return Ok(i),
522 _ => {}
523 }
524 }
525
526 if in_string {
527 return Err(RuleEngineError::ParseError {
528 message: "Parse error: Unclosed string in goal".to_string(),
529 });
530 }
531
532 if paren_depth > 0 {
533 return Err(RuleEngineError::ParseError {
534 message: format!("Parse error: {} unclosed parentheses in goal", paren_depth),
535 });
536 }
537
538 Ok(input.len())
540 }
541
542 fn extract_strategy(input: &str) -> Option<GRLSearchStrategy> {
543 let re = rexile::Pattern::new(r"strategy:\s*([a-z-]+)").unwrap();
544 re.captures(input).and_then(|caps| match caps[1].trim() {
545 "depth-first" => Some(GRLSearchStrategy::DepthFirst),
546 "breadth-first" => Some(GRLSearchStrategy::BreadthFirst),
547 "iterative" => Some(GRLSearchStrategy::Iterative),
548 _ => None,
549 })
550 }
551
552 fn extract_max_depth(input: &str) -> Option<usize> {
553 let re = rexile::Pattern::new(r"max-depth:\s*(\d+)").unwrap();
554 re.captures(input).and_then(|caps| caps[1].parse().ok())
555 }
556
557 fn extract_max_solutions(input: &str) -> Option<usize> {
558 let re = rexile::Pattern::new(r"max-solutions:\s*(\d+)").unwrap();
559 re.captures(input).and_then(|caps| caps[1].parse().ok())
560 }
561
562 fn extract_memoization(input: &str) -> Option<bool> {
563 let re = rexile::Pattern::new(r"enable-memoization:\s*(true|false)").unwrap();
564 re.captures(input).and_then(|caps| match caps[1].trim() {
565 "true" => Some(true),
566 "false" => Some(false),
567 _ => None,
568 })
569 }
570
571 fn extract_optimization(input: &str) -> Option<bool> {
572 let re = rexile::Pattern::new(r"enable-optimization:\s*(true|false)").unwrap();
573 re.captures(input).and_then(|caps| match caps[1].trim() {
574 "true" => Some(true),
575 "false" => Some(false),
576 _ => None,
577 })
578 }
579
580 fn extract_on_success(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
581 Self::extract_action_block(input, "on-success")
582 }
583
584 fn extract_on_failure(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
585 Self::extract_action_block(input, "on-failure")
586 }
587
588 fn extract_on_missing(input: &str) -> Result<Option<QueryAction>, RuleEngineError> {
589 Self::extract_action_block(input, "on-missing")
590 }
591
592 fn extract_action_block(
593 input: &str,
594 action_name: &str,
595 ) -> Result<Option<QueryAction>, RuleEngineError> {
596 let pattern = format!(r"{}:\s*\{{([^}}]+)\}}", action_name);
597 let re = rexile::Pattern::new(&pattern).unwrap();
598
599 if let Some(caps) = re.captures(input) {
600 let block = caps[1].trim();
601 let mut action = QueryAction::new();
602
603 let assign_re =
605 rexile::Pattern::new(r"([A-Za-z_][A-Za-z0-9_.]*)\s*=\s*([^;]+);").unwrap();
606 for caps in assign_re.captures_iter(block) {
607 let var_name = caps[1].trim().to_string();
608 let value_str = caps[2].trim().to_string();
609 action.assignments.push((var_name, value_str));
610 }
611
612 let call_re = rexile::Pattern::new(r"([A-Za-z_][A-Za-z0-9_]*\([^)]*\));").unwrap();
614 for caps in call_re.captures_iter(block) {
615 action.calls.push(caps[1].trim().to_string());
616 }
617
618 Ok(Some(action))
619 } else {
620 Ok(None)
621 }
622 }
623
624 fn extract_when_condition(input: &str) -> Result<Option<String>, RuleEngineError> {
625 let re = rexile::Pattern::new(r"when:\s*([^\n}]+)").unwrap();
626 if let Some(caps) = re.captures(input) {
627 let condition_str = caps[1].trim().to_string();
628 Ok(Some(condition_str))
629 } else {
630 Ok(None)
631 }
632 }
633
634 pub fn parse_queries(input: &str) -> Result<Vec<GRLQuery>, RuleEngineError> {
636 let mut queries = Vec::new();
637
638 let parts: Vec<&str> = input.split("query").collect();
641
642 for part in parts.iter().skip(1) {
643 let query_str = format!("query{}", part);
645 if let Some(end_idx) = find_matching_brace(&query_str) {
647 let complete_query = &query_str[..end_idx];
648 if let Ok(query) = Self::parse(complete_query) {
649 queries.push(query);
650 }
651 }
652 }
653
654 Ok(queries)
655 }
656}
657
658fn find_matching_brace(input: &str) -> Option<usize> {
660 let mut depth = 0;
661 let mut in_string = false;
662 let mut escape_next = false;
663
664 for (i, ch) in input.chars().enumerate() {
665 if escape_next {
666 escape_next = false;
667 continue;
668 }
669
670 match ch {
671 '\\' => escape_next = true,
672 '"' => in_string = !in_string,
673 '{' if !in_string => depth += 1,
674 '}' if !in_string => {
675 depth -= 1;
676 if depth == 0 {
677 return Some(i + 1);
678 }
679 }
680 _ => {}
681 }
682 }
683
684 None
685}
686
687pub struct GRLQueryExecutor;
689
690impl GRLQueryExecutor {
691 pub fn execute(
693 query: &GRLQuery,
694 bc_engine: &mut BackwardEngine,
695 facts: &mut Facts,
696 ) -> Result<QueryResult, RuleEngineError> {
697 if !query.should_execute(facts)? {
699 return Ok(QueryResult {
700 provable: false,
701 bindings: HashMap::new(),
702 proof_trace: ProofTrace {
703 goal: String::new(),
704 steps: Vec::new(),
705 },
706 missing_facts: Vec::new(),
707 stats: QueryStats::default(),
708 solutions: Vec::new(),
709 });
710 }
711
712 bc_engine.set_config(query.to_config());
714
715 let result = if query.goal.contains("&&") && query.goal.contains("||") {
717 Self::execute_complex_goal(&query.goal, bc_engine, facts)?
720 } else if query.goal.contains("||") {
721 Self::execute_compound_or_goal(&query.goal, bc_engine, facts)?
723 } else if query.goal.contains("&&") {
724 Self::execute_compound_and_goal(&query.goal, bc_engine, facts)?
726 } else {
727 bc_engine.query(&query.goal, facts)?
729 };
730
731 if result.provable {
733 query.execute_success_actions(facts)?;
734 } else if !result.missing_facts.is_empty() {
735 query.execute_missing_actions(facts)?;
736 } else {
737 query.execute_failure_actions(facts)?;
738 }
739
740 Ok(result)
741 }
742
743 fn execute_compound_and_goal(
745 goal_expr: &str,
746 bc_engine: &mut BackwardEngine,
747 facts: &mut Facts,
748 ) -> Result<QueryResult, RuleEngineError> {
749 let sub_goals: Vec<&str> = goal_expr.split("&&").map(|s| s.trim()).collect();
750
751 let mut all_provable = true;
752 let combined_bindings = HashMap::new();
753 let all_missing = Vec::new();
754 let combined_stats = QueryStats::default();
755
756 for sub_goal in sub_goals.iter() {
757 let goal_satisfied = if sub_goal.contains("!=") {
759 use crate::backward::expression::ExpressionParser;
761
762 match ExpressionParser::parse(sub_goal) {
763 Ok(expr) => expr.is_satisfied(facts),
764 Err(_) => false,
765 }
766 } else {
767 let result = bc_engine.query(sub_goal, facts)?;
769 result.provable
770 };
771
772 if !goal_satisfied {
773 all_provable = false;
774 }
775
776 }
779
780 Ok(QueryResult {
781 provable: all_provable,
782 bindings: combined_bindings,
783 proof_trace: ProofTrace {
784 goal: goal_expr.to_string(),
785 steps: Vec::new(),
786 },
787 missing_facts: all_missing,
788 stats: combined_stats,
789 solutions: Vec::new(),
790 })
791 }
792
793 fn execute_compound_or_goal(
795 goal_expr: &str,
796 bc_engine: &mut BackwardEngine,
797 facts: &mut Facts,
798 ) -> Result<QueryResult, RuleEngineError> {
799 let sub_goals: Vec<&str> = goal_expr.split("||").map(|s| s.trim()).collect();
800
801 let mut any_provable = false;
802 let mut combined_bindings = HashMap::new();
803 let mut all_missing = Vec::new();
804 let mut combined_stats = QueryStats::default();
805 let mut all_solutions = Vec::new();
806
807 for sub_goal in sub_goals.iter() {
808 let (goal_satisfied, result_opt) = if sub_goal.contains("!=") {
810 use crate::backward::expression::ExpressionParser;
812
813 match ExpressionParser::parse(sub_goal) {
814 Ok(expr) => (expr.is_satisfied(facts), None),
815 Err(_) => (false, None),
816 }
817 } else {
818 let result = bc_engine.query(sub_goal, facts)?;
820 let provable = result.provable;
821 (provable, Some(result))
822 };
823
824 if goal_satisfied {
825 any_provable = true;
826
827 if let Some(result) = result_opt {
829 combined_bindings.extend(result.bindings);
830 all_missing.extend(result.missing_facts);
831 combined_stats.goals_explored += result.stats.goals_explored;
832 combined_stats.rules_evaluated += result.stats.rules_evaluated;
833 if let Some(dur) = result.stats.duration_ms {
834 combined_stats.duration_ms =
835 Some(combined_stats.duration_ms.unwrap_or(0) + dur);
836 }
837 all_solutions.extend(result.solutions);
838 }
839 }
840 }
841
842 Ok(QueryResult {
843 provable: any_provable,
844 bindings: combined_bindings,
845 proof_trace: ProofTrace {
846 goal: goal_expr.to_string(),
847 steps: Vec::new(),
848 },
849 missing_facts: all_missing,
850 stats: combined_stats,
851 solutions: all_solutions,
852 })
853 }
854
855 fn strip_outer_parens(expr: &str) -> &str {
857 let trimmed = expr.trim();
858 if trimmed.starts_with('(') && trimmed.ends_with(')') {
859 let inner = &trimmed[1..trimmed.len() - 1];
861 let mut depth = 0;
862 for ch in inner.chars() {
863 match ch {
864 '(' => depth += 1,
865 ')' => {
866 depth -= 1;
867 if depth < 0 {
868 return trimmed;
870 }
871 }
872 _ => {}
873 }
874 }
875 if depth == 0 {
876 return inner.trim();
878 }
879 }
880 trimmed
881 }
882
883 fn execute_complex_goal(
887 goal_expr: &str,
888 bc_engine: &mut BackwardEngine,
889 facts: &mut Facts,
890 ) -> Result<QueryResult, RuleEngineError> {
891 let cleaned_expr = Self::strip_outer_parens(goal_expr);
893
894 let or_parts: Vec<&str> = cleaned_expr.split("||").map(|s| s.trim()).collect();
896
897 let mut any_provable = false;
898 let mut combined_bindings = HashMap::new();
899 let mut all_missing = Vec::new();
900 let mut combined_stats = QueryStats::default();
901 let mut all_solutions = Vec::new();
902
903 for or_part in or_parts.iter() {
904 let cleaned_part = Self::strip_outer_parens(or_part);
906
907 let result = if cleaned_part.contains("&&") {
909 Self::execute_compound_and_goal(cleaned_part, bc_engine, facts)?
910 } else {
911 bc_engine.query(cleaned_part, facts)?
912 };
913
914 if result.provable {
915 any_provable = true;
916 combined_bindings.extend(result.bindings);
917 all_missing.extend(result.missing_facts);
918 combined_stats.goals_explored += result.stats.goals_explored;
919 combined_stats.rules_evaluated += result.stats.rules_evaluated;
920 if let Some(dur) = result.stats.duration_ms {
921 combined_stats.duration_ms =
922 Some(combined_stats.duration_ms.unwrap_or(0) + dur);
923 }
924 all_solutions.extend(result.solutions);
925 }
926 }
927
928 Ok(QueryResult {
929 provable: any_provable,
930 bindings: combined_bindings,
931 proof_trace: ProofTrace {
932 goal: goal_expr.to_string(),
933 steps: Vec::new(),
934 },
935 missing_facts: all_missing,
936 stats: combined_stats,
937 solutions: all_solutions,
938 })
939 }
940
941 pub fn execute_queries(
943 queries: &[GRLQuery],
944 bc_engine: &mut BackwardEngine,
945 facts: &mut Facts,
946 ) -> Result<Vec<QueryResult>, RuleEngineError> {
947 let mut results = Vec::new();
948
949 for query in queries {
950 let result = Self::execute(query, bc_engine, facts)?;
951 results.push(result);
952 }
953
954 Ok(results)
955 }
956}
957
958#[cfg(test)]
959mod tests {
960 use super::*;
961
962 #[test]
963 fn test_parse_simple_query() {
964 let input = r#"
965 query "TestQuery" {
966 goal: User.IsVIP == true
967 }
968 "#;
969
970 let query = GRLQueryParser::parse(input).unwrap();
971 assert_eq!(query.name, "TestQuery");
972 assert_eq!(query.strategy, GRLSearchStrategy::DepthFirst);
973 assert_eq!(query.max_depth, 10);
974 }
975
976 #[test]
977 fn test_parse_query_with_strategy() {
978 let input = r#"
979 query "TestQuery" {
980 goal: User.IsVIP == true
981 strategy: breadth-first
982 max-depth: 5
983 }
984 "#;
985
986 let query = GRLQueryParser::parse(input).unwrap();
987 assert_eq!(query.strategy, GRLSearchStrategy::BreadthFirst);
988 assert_eq!(query.max_depth, 5);
989 }
990
991 #[test]
992 fn test_parse_query_with_actions() {
993 let input = r#"
994 query "TestQuery" {
995 goal: User.IsVIP == true
996 on-success: {
997 User.DiscountRate = 0.2;
998 LogMessage("VIP confirmed");
999 }
1000 }
1001 "#;
1002
1003 let query = GRLQueryParser::parse(input).unwrap();
1004 assert!(query.on_success.is_some());
1005
1006 let action = query.on_success.unwrap();
1007 assert_eq!(action.assignments.len(), 1);
1008 assert_eq!(action.calls.len(), 1);
1009 }
1010
1011 #[test]
1012 fn test_parse_query_with_when_condition() {
1013 let input = r#"
1014 query "TestQuery" {
1015 goal: User.IsVIP == true
1016 when: Environment.Mode == "Production"
1017 }
1018 "#;
1019
1020 let query = GRLQueryParser::parse(input).unwrap();
1021 assert!(query.when_condition.is_some());
1022 }
1023
1024 #[test]
1025 fn test_parse_multiple_queries() {
1026 let input = r#"
1027 query "Query1" {
1028 goal: A == true
1029 }
1030
1031 query "Query2" {
1032 goal: B == true
1033 strategy: breadth-first
1034 }
1035 "#;
1036
1037 let queries = GRLQueryParser::parse_queries(input).unwrap();
1038 assert_eq!(queries.len(), 2);
1039 assert_eq!(queries[0].name, "Query1");
1040 assert_eq!(queries[1].name, "Query2");
1041 }
1042
1043 #[test]
1044 fn test_query_config_conversion() {
1045 let query = GRLQuery::new("Test".to_string(), "X == true".to_string())
1046 .with_strategy(GRLSearchStrategy::BreadthFirst)
1047 .with_max_depth(15)
1048 .with_memoization(false);
1049
1050 let config = query.to_config();
1051 assert_eq!(config.max_depth, 15);
1052 assert!(!config.enable_memoization);
1053 }
1054
1055 #[test]
1056 fn test_action_execution() {
1057 let mut facts = Facts::new();
1058
1059 let mut action = QueryAction::new();
1060 action
1061 .assignments
1062 .push(("User.DiscountRate".to_string(), "0.2".to_string()));
1063
1064 action.execute(&mut facts).unwrap();
1065
1066 let value = facts.get("User.DiscountRate");
1068 assert!(value.is_some());
1069 }
1070
1071 #[test]
1072 fn test_should_execute_no_condition() {
1073 let query = GRLQuery::new("Q".to_string(), "X == true".to_string());
1074 let facts = Facts::new();
1075 let res = query.should_execute(&facts).unwrap();
1077 assert!(res);
1078 }
1079
1080 #[test]
1081 fn test_should_execute_condition_true() {
1082 let facts = Facts::new();
1083 facts.set("Environment.Mode", Value::String("Production".to_string()));
1084
1085 let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
1086 .with_when("Environment.Mode == \"Production\"".to_string());
1087
1088 let res = query.should_execute(&facts).unwrap();
1089 assert!(res, "expected when condition to be satisfied");
1090 }
1091
1092 #[test]
1093 fn test_should_execute_condition_false() {
1094 let facts = Facts::new();
1095 facts.set("Environment.Mode", Value::String("Development".to_string()));
1096
1097 let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
1098 .with_when("Environment.Mode == \"Production\"".to_string());
1099
1100 let res = query.should_execute(&facts).unwrap();
1101 assert!(!res, "expected when condition to be unsatisfied");
1102 }
1103
1104 #[test]
1105 fn test_should_execute_parse_error_propagates() {
1106 let facts = Facts::new();
1107 let query = GRLQuery::new("Q".to_string(), "X == true".to_string())
1109 .with_when("Environment.Mode == \"Production".to_string());
1110
1111 let res = query.should_execute(&facts);
1112 assert!(res.is_err(), "expected parse error to propagate");
1113 }
1114
1115 #[test]
1116 fn test_parse_query_with_or_goal() {
1117 let input = r#"
1118 query "TestOR" {
1119 goal: User.IsVIP == true || User.TotalSpent > 10000
1120 }
1121 "#;
1122
1123 let query = GRLQueryParser::parse(input).unwrap();
1124 assert_eq!(query.name, "TestOR");
1125 assert!(query.goal.contains("||"));
1126 }
1127
1128 #[test]
1129 fn test_parse_query_with_complex_goal() {
1130 let input = r#"
1131 query "ComplexQuery" {
1132 goal: (User.IsVIP == true && User.Active == true) || User.TotalSpent > 10000
1133 }
1134 "#;
1135
1136 let query = GRLQueryParser::parse(input).unwrap();
1137 assert!(query.goal.contains("||"));
1138 assert!(query.goal.contains("&&"));
1139 }
1140
1141 #[test]
1142 fn test_parse_query_with_multiple_or_branches() {
1143 let input = r#"
1144 query "MultiOR" {
1145 goal: Employee.IsManager == true || Employee.IsSenior == true || Employee.IsDirector == true
1146 }
1147 "#;
1148
1149 let query = GRLQueryParser::parse(input).unwrap();
1150 let branches: Vec<&str> = query.goal.split("||").collect();
1151 assert_eq!(branches.len(), 3);
1152 }
1153
1154 #[test]
1155 fn test_parse_query_with_parentheses() {
1156 let input = r#"
1157 query "ParenQuery" {
1158 goal: (User.IsVIP == true && User.Active == true) || User.TotalSpent > 10000
1159 }
1160 "#;
1161
1162 let query = GRLQueryParser::parse(input).unwrap();
1163 assert!(query.goal.contains("("));
1164 assert!(query.goal.contains(")"));
1165 assert!(query.goal.contains("||"));
1166 assert!(query.goal.contains("&&"));
1167 }
1168
1169 #[test]
1170 fn test_parse_query_with_nested_parentheses() {
1171 let input = r#"
1172 query "NestedParen" {
1173 goal: ((A == true && B == true) || C == true) && D == true
1174 }
1175 "#;
1176
1177 let query = GRLQueryParser::parse(input).unwrap();
1178 assert_eq!(query.name, "NestedParen");
1179 assert!(query.goal.starts_with("(("));
1181 }
1182
1183 #[test]
1184 fn test_parse_query_unclosed_parenthesis() {
1185 let input = r#"
1186 query "BadParen" {
1187 goal: (User.IsVIP == true && User.Active == true
1188 }
1189 "#;
1190
1191 let result = GRLQueryParser::parse(input);
1192 assert!(result.is_err());
1193 if let Err(e) = result {
1194 let msg = format!("{:?}", e);
1195 assert!(msg.contains("unclosed parentheses") || msg.contains("parenthesis"));
1196 }
1197 }
1198}