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