rust_rule_engine/backward/
goal.rs

1//! Goal system for backward chaining
2
3use crate::types::Value;
4use std::collections::HashMap;
5use super::expression::Expression;
6use super::unification::Bindings;
7
8/// Represents a goal to prove/achieve in backward chaining
9#[derive(Debug, Clone)]
10pub struct Goal {
11    /// Target pattern to prove (e.g., "User.IsVIP == true")
12    pub pattern: String,
13
14    /// Parsed expression AST (if available)
15    pub expression: Option<Expression>,
16
17    /// Current status of this goal
18    pub status: GoalStatus,
19
20    /// Sub-goals that need to be proven first
21    pub sub_goals: Vec<Goal>,
22
23    /// Rules that can potentially derive this goal
24    pub candidate_rules: Vec<String>,
25
26    /// Variable bindings accumulated during proof
27    pub bindings: Bindings,
28
29    /// Depth of this goal in the search tree
30    pub depth: usize,
31}
32
33/// Status of a goal in the proof process
34#[derive(Debug, Clone, PartialEq, Eq)]
35pub enum GoalStatus {
36    /// Goal has not been attempted yet
37    Pending,
38    
39    /// Goal is currently being proven (to detect cycles)
40    InProgress,
41    
42    /// Goal has been successfully proven
43    Proven,
44    
45    /// Goal cannot be proven with available rules/facts
46    Unprovable,
47}
48
49impl Goal {
50    /// Create a new goal with the given pattern
51    pub fn new(pattern: String) -> Self {
52        Self {
53            pattern,
54            expression: None,
55            status: GoalStatus::Pending,
56            sub_goals: Vec::new(),
57            candidate_rules: Vec::new(),
58            bindings: Bindings::new(),
59            depth: 0,
60        }
61    }
62
63    /// Create a new goal with parsed expression
64    pub fn with_expression(pattern: String, expression: Expression) -> Self {
65        Self {
66            pattern,
67            expression: Some(expression),
68            status: GoalStatus::Pending,
69            sub_goals: Vec::new(),
70            candidate_rules: Vec::new(),
71            bindings: Bindings::new(),
72            depth: 0,
73        }
74    }
75    
76    /// Check if this goal is proven
77    pub fn is_proven(&self) -> bool {
78        self.status == GoalStatus::Proven
79    }
80    
81    /// Check if this goal is unprovable
82    pub fn is_unprovable(&self) -> bool {
83        self.status == GoalStatus::Unprovable
84    }
85    
86    /// Check if all sub-goals are proven
87    pub fn all_subgoals_proven(&self) -> bool {
88        self.sub_goals.iter().all(|g| g.is_proven())
89    }
90    
91    /// Add a sub-goal
92    pub fn add_subgoal(&mut self, goal: Goal) {
93        self.sub_goals.push(goal);
94    }
95    
96    /// Add a candidate rule that can derive this goal
97    pub fn add_candidate_rule(&mut self, rule_name: String) {
98        if !self.candidate_rules.contains(&rule_name) {
99            self.candidate_rules.push(rule_name);
100        }
101    }
102}
103
104/// Manager for goal-driven reasoning
105#[derive(Debug)]
106pub struct GoalManager {
107    /// Active goals being pursued
108    goals: Vec<Goal>,
109    
110    /// Maximum depth for goal search (prevent infinite recursion)
111    max_depth: usize,
112    
113    /// Cache of proven goals (memoization)
114    proven_cache: HashMap<String, bool>,
115}
116
117impl GoalManager {
118    /// Create a new goal manager
119    pub fn new(max_depth: usize) -> Self {
120        Self {
121            goals: Vec::new(),
122            max_depth,
123            proven_cache: HashMap::new(),
124        }
125    }
126    
127    /// Add a new goal to pursue
128    pub fn add_goal(&mut self, goal: Goal) {
129        self.goals.push(goal);
130    }
131    
132    /// Get the next pending goal to work on
133    pub fn next_pending(&mut self) -> Option<&mut Goal> {
134        self.goals.iter_mut()
135            .find(|g| g.status == GoalStatus::Pending)
136    }
137    
138    /// Check if a goal pattern has been proven before (memoization)
139    pub fn is_cached(&self, pattern: &str) -> Option<bool> {
140        self.proven_cache.get(pattern).copied()
141    }
142    
143    /// Cache the result of proving a goal
144    pub fn cache_result(&mut self, pattern: String, proven: bool) {
145        self.proven_cache.insert(pattern, proven);
146    }
147    
148    /// Check if we've exceeded maximum depth
149    pub fn is_too_deep(&self, depth: usize) -> bool {
150        depth > self.max_depth
151    }
152    
153    /// Get all goals
154    pub fn goals(&self) -> &[Goal] {
155        &self.goals
156    }
157    
158    /// Clear all goals and cache
159    pub fn clear(&mut self) {
160        self.goals.clear();
161        self.proven_cache.clear();
162    }
163}
164
165impl Default for GoalManager {
166    fn default() -> Self {
167        Self::new(10) // Default max depth of 10
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    
175    #[test]
176    fn test_goal_creation() {
177        let goal = Goal::new("User.IsVIP == true".to_string());
178        assert_eq!(goal.status, GoalStatus::Pending);
179        assert_eq!(goal.depth, 0);
180        assert!(goal.sub_goals.is_empty());
181    }
182    
183    #[test]
184    fn test_goal_status_checks() {
185        let mut goal = Goal::new("test".to_string());
186        assert!(!goal.is_proven());
187        
188        goal.status = GoalStatus::Proven;
189        assert!(goal.is_proven());
190        
191        goal.status = GoalStatus::Unprovable;
192        assert!(goal.is_unprovable());
193    }
194    
195    #[test]
196    fn test_subgoal_management() {
197        let mut parent = Goal::new("parent".to_string());
198        let mut child1 = Goal::new("child1".to_string());
199        let mut child2 = Goal::new("child2".to_string());
200        
201        child1.status = GoalStatus::Proven;
202        child2.status = GoalStatus::Proven;
203        
204        parent.add_subgoal(child1);
205        parent.add_subgoal(child2);
206        
207        assert_eq!(parent.sub_goals.len(), 2);
208        assert!(parent.all_subgoals_proven());
209    }
210    
211    #[test]
212    fn test_goal_manager() {
213        let mut manager = GoalManager::new(5);
214        
215        let goal1 = Goal::new("goal1".to_string());
216        let goal2 = Goal::new("goal2".to_string());
217        
218        manager.add_goal(goal1);
219        manager.add_goal(goal2);
220        
221        assert_eq!(manager.goals().len(), 2);
222        
223        // Test caching
224        assert!(manager.is_cached("goal1").is_none());
225        manager.cache_result("goal1".to_string(), true);
226        assert_eq!(manager.is_cached("goal1"), Some(true));
227        
228        // Test depth check
229        assert!(!manager.is_too_deep(3));
230        assert!(manager.is_too_deep(10));
231    }
232    
233    #[test]
234    fn test_candidate_rules() {
235        let mut goal = Goal::new("test".to_string());
236        
237        goal.add_candidate_rule("Rule1".to_string());
238        goal.add_candidate_rule("Rule2".to_string());
239        goal.add_candidate_rule("Rule1".to_string()); // Duplicate
240        
241        assert_eq!(goal.candidate_rules.len(), 2);
242    }
243}