rust_rule_engine/backward/
goal.rs

1//! Goal system for backward chaining
2
3use super::expression::Expression;
4use super::unification::Bindings;
5use std::collections::HashMap;
6
7/// Represents a goal to prove/achieve in backward chaining
8#[derive(Debug, Clone)]
9pub struct Goal {
10    /// Target pattern to prove (e.g., "User.IsVIP == true")
11    pub pattern: String,
12
13    /// Parsed expression AST (if available)
14    pub expression: Option<Expression>,
15
16    /// Current status of this goal
17    pub status: GoalStatus,
18
19    /// Sub-goals that need to be proven first
20    pub sub_goals: Vec<Goal>,
21
22    /// Rules that can potentially derive this goal
23    pub candidate_rules: Vec<String>,
24
25    /// Variable bindings accumulated during proof
26    pub bindings: Bindings,
27
28    /// Depth of this goal in the search tree
29    pub depth: usize,
30
31    /// Whether this is a negated goal (NOT keyword)
32    /// When true, the goal succeeds if it CANNOT be proven (closed-world assumption)
33    pub is_negated: bool,
34}
35
36/// Status of a goal in the proof process
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum GoalStatus {
39    /// Goal has not been attempted yet
40    Pending,
41
42    /// Goal is currently being proven (to detect cycles)
43    InProgress,
44
45    /// Goal has been successfully proven
46    Proven,
47
48    /// Goal cannot be proven with available rules/facts
49    Unprovable,
50}
51
52impl Goal {
53    /// Create a new goal with the given pattern
54    pub fn new(pattern: String) -> Self {
55        Self {
56            pattern,
57            expression: None,
58            status: GoalStatus::Pending,
59            sub_goals: Vec::new(),
60            candidate_rules: Vec::new(),
61            bindings: Bindings::new(),
62            depth: 0,
63            is_negated: false,
64        }
65    }
66
67    /// Create a new goal with parsed expression
68    pub fn with_expression(pattern: String, expression: Expression) -> Self {
69        Self {
70            pattern,
71            expression: Some(expression),
72            status: GoalStatus::Pending,
73            sub_goals: Vec::new(),
74            candidate_rules: Vec::new(),
75            bindings: Bindings::new(),
76            depth: 0,
77            is_negated: false,
78        }
79    }
80
81    /// Create a negated goal (NOT goal)
82    pub fn negated(pattern: String) -> Self {
83        Self {
84            pattern,
85            expression: None,
86            status: GoalStatus::Pending,
87            sub_goals: Vec::new(),
88            candidate_rules: Vec::new(),
89            bindings: Bindings::new(),
90            depth: 0,
91            is_negated: true,
92        }
93    }
94
95    /// Create a negated goal with expression
96    pub fn negated_with_expression(pattern: String, expression: Expression) -> Self {
97        Self {
98            pattern,
99            expression: Some(expression),
100            status: GoalStatus::Pending,
101            sub_goals: Vec::new(),
102            candidate_rules: Vec::new(),
103            bindings: Bindings::new(),
104            depth: 0,
105            is_negated: true,
106        }
107    }
108
109    /// Check if this goal is proven
110    pub fn is_proven(&self) -> bool {
111        self.status == GoalStatus::Proven
112    }
113
114    /// Check if this goal is unprovable
115    pub fn is_unprovable(&self) -> bool {
116        self.status == GoalStatus::Unprovable
117    }
118
119    /// Check if all sub-goals are proven
120    pub fn all_subgoals_proven(&self) -> bool {
121        self.sub_goals.iter().all(|g| g.is_proven())
122    }
123
124    /// Add a sub-goal
125    pub fn add_subgoal(&mut self, goal: Goal) {
126        self.sub_goals.push(goal);
127    }
128
129    /// Add a candidate rule that can derive this goal
130    pub fn add_candidate_rule(&mut self, rule_name: String) {
131        if !self.candidate_rules.contains(&rule_name) {
132            self.candidate_rules.push(rule_name);
133        }
134    }
135}
136
137/// Manager for goal-driven reasoning
138#[derive(Debug)]
139pub struct GoalManager {
140    /// Active goals being pursued
141    goals: Vec<Goal>,
142
143    /// Maximum depth for goal search (prevent infinite recursion)
144    max_depth: usize,
145
146    /// Cache of proven goals (memoization)
147    proven_cache: HashMap<String, bool>,
148}
149
150impl GoalManager {
151    /// Create a new goal manager
152    pub fn new(max_depth: usize) -> Self {
153        Self {
154            goals: Vec::new(),
155            max_depth,
156            proven_cache: HashMap::new(),
157        }
158    }
159
160    /// Add a new goal to pursue
161    pub fn add_goal(&mut self, goal: Goal) {
162        self.goals.push(goal);
163    }
164
165    /// Get the next pending goal to work on
166    pub fn next_pending(&mut self) -> Option<&mut Goal> {
167        self.goals
168            .iter_mut()
169            .find(|g| g.status == GoalStatus::Pending)
170    }
171
172    /// Check if a goal pattern has been proven before (memoization)
173    pub fn is_cached(&self, pattern: &str) -> Option<bool> {
174        self.proven_cache.get(pattern).copied()
175    }
176
177    /// Cache the result of proving a goal
178    pub fn cache_result(&mut self, pattern: String, proven: bool) {
179        self.proven_cache.insert(pattern, proven);
180    }
181
182    /// Check if we've exceeded maximum depth
183    pub fn is_too_deep(&self, depth: usize) -> bool {
184        depth > self.max_depth
185    }
186
187    /// Get all goals
188    pub fn goals(&self) -> &[Goal] {
189        &self.goals
190    }
191
192    /// Clear all goals and cache
193    pub fn clear(&mut self) {
194        self.goals.clear();
195        self.proven_cache.clear();
196    }
197}
198
199impl Default for GoalManager {
200    fn default() -> Self {
201        Self::new(10) // Default max depth of 10
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208    use crate::Value;
209
210    #[test]
211    fn test_goal_creation() {
212        let goal = Goal::new("User.IsVIP == true".to_string());
213        assert_eq!(goal.status, GoalStatus::Pending);
214        assert_eq!(goal.depth, 0);
215        assert!(goal.sub_goals.is_empty());
216    }
217
218    #[test]
219    fn test_goal_status_checks() {
220        let mut goal = Goal::new("test".to_string());
221        assert!(!goal.is_proven());
222
223        goal.status = GoalStatus::Proven;
224        assert!(goal.is_proven());
225
226        goal.status = GoalStatus::Unprovable;
227        assert!(goal.is_unprovable());
228    }
229
230    #[test]
231    fn test_subgoal_management() {
232        let mut parent = Goal::new("parent".to_string());
233        let mut child1 = Goal::new("child1".to_string());
234        let mut child2 = Goal::new("child2".to_string());
235
236        child1.status = GoalStatus::Proven;
237        child2.status = GoalStatus::Proven;
238
239        parent.add_subgoal(child1);
240        parent.add_subgoal(child2);
241
242        assert_eq!(parent.sub_goals.len(), 2);
243        assert!(parent.all_subgoals_proven());
244    }
245
246    #[test]
247    fn test_goal_manager() {
248        let mut manager = GoalManager::new(5);
249
250        let goal1 = Goal::new("goal1".to_string());
251        let goal2 = Goal::new("goal2".to_string());
252
253        manager.add_goal(goal1);
254        manager.add_goal(goal2);
255
256        assert_eq!(manager.goals().len(), 2);
257
258        // Test caching
259        assert!(manager.is_cached("goal1").is_none());
260        manager.cache_result("goal1".to_string(), true);
261        assert_eq!(manager.is_cached("goal1"), Some(true));
262
263        // Test depth check
264        assert!(!manager.is_too_deep(3));
265        assert!(manager.is_too_deep(10));
266    }
267
268    #[test]
269    fn test_candidate_rules() {
270        let mut goal = Goal::new("test".to_string());
271
272        goal.add_candidate_rule("Rule1".to_string());
273        goal.add_candidate_rule("Rule2".to_string());
274        goal.add_candidate_rule("Rule1".to_string()); // Duplicate
275
276        assert_eq!(goal.candidate_rules.len(), 2);
277    }
278
279    #[test]
280    fn test_goal_with_expression() {
281        use super::super::expression::Expression;
282        use crate::types::{Operator, Value};
283
284        let expr = Expression::Comparison {
285            left: Box::new(Expression::Field("User.IsVIP".to_string())),
286            operator: Operator::Equal,
287            right: Box::new(Expression::Literal(Value::Boolean(true))),
288        };
289
290        let goal = Goal::with_expression("User.IsVIP == true".to_string(), expr);
291        assert!(goal.expression.is_some());
292        assert_eq!(goal.pattern, "User.IsVIP == true");
293        assert_eq!(goal.status, GoalStatus::Pending);
294    }
295
296    #[test]
297    fn test_subgoals_not_all_proven() {
298        let mut parent = Goal::new("parent".to_string());
299        let mut child1 = Goal::new("child1".to_string());
300        let mut child2 = Goal::new("child2".to_string());
301
302        child1.status = GoalStatus::Proven;
303        child2.status = GoalStatus::Pending; // Not proven yet
304
305        parent.add_subgoal(child1);
306        parent.add_subgoal(child2);
307
308        assert_eq!(parent.sub_goals.len(), 2);
309        assert!(!parent.all_subgoals_proven());
310    }
311
312    #[test]
313    fn test_goal_manager_next_pending() {
314        let mut manager = GoalManager::new(5);
315
316        let mut goal1 = Goal::new("goal1".to_string());
317        goal1.status = GoalStatus::Proven; // Already proven
318
319        let goal2 = Goal::new("goal2".to_string()); // Pending
320
321        let mut goal3 = Goal::new("goal3".to_string());
322        goal3.status = GoalStatus::InProgress;
323
324        manager.add_goal(goal1);
325        manager.add_goal(goal2);
326        manager.add_goal(goal3);
327
328        // Should return goal2 as it's the only pending one
329        let next = manager.next_pending();
330        assert!(next.is_some());
331        assert_eq!(next.unwrap().pattern, "goal2");
332    }
333
334    #[test]
335    fn test_goal_manager_clear() {
336        let mut manager = GoalManager::new(5);
337
338        manager.add_goal(Goal::new("goal1".to_string()));
339        manager.add_goal(Goal::new("goal2".to_string()));
340        manager.cache_result("goal1".to_string(), true);
341
342        assert_eq!(manager.goals().len(), 2);
343        assert!(manager.is_cached("goal1").is_some());
344
345        manager.clear();
346
347        assert_eq!(manager.goals().len(), 0);
348        assert!(manager.is_cached("goal1").is_none());
349    }
350
351    #[test]
352    fn test_goal_bindings() {
353        let mut goal = Goal::new("User.?X == true".to_string());
354
355        // Test that bindings start empty
356        assert!(goal.bindings.get("X").is_none());
357
358        // Add a binding
359        goal.bindings
360            .bind("X".to_string(), Value::String("IsVIP".to_string()))
361            .ok();
362
363        // Verify binding was added
364        assert!(goal.bindings.get("X").is_some());
365        assert_eq!(
366            goal.bindings.get("X"),
367            Some(&Value::String("IsVIP".to_string()))
368        );
369    }
370
371    #[test]
372    fn test_goal_depth() {
373        let mut goal = Goal::new("test".to_string());
374        assert_eq!(goal.depth, 0);
375
376        goal.depth = 5;
377        assert_eq!(goal.depth, 5);
378    }
379
380    #[test]
381    fn test_goal_manager_default() {
382        let manager = GoalManager::default();
383        assert_eq!(manager.max_depth, 10);
384        assert_eq!(manager.goals().len(), 0);
385    }
386
387    #[test]
388    fn test_negated_goal() {
389        let goal = Goal::negated("User.IsBanned == true".to_string());
390        assert_eq!(goal.pattern, "User.IsBanned == true");
391        assert!(goal.is_negated);
392        assert_eq!(goal.status, GoalStatus::Pending);
393    }
394
395    #[test]
396    fn test_negated_goal_with_expression() {
397        use super::super::expression::Expression;
398        use crate::types::{Operator, Value};
399
400        let expr = Expression::Comparison {
401            left: Box::new(Expression::Field("User.IsBanned".to_string())),
402            operator: Operator::Equal,
403            right: Box::new(Expression::Literal(Value::Boolean(true))),
404        };
405
406        let goal = Goal::negated_with_expression("User.IsBanned == true".to_string(), expr);
407        assert!(goal.expression.is_some());
408        assert!(goal.is_negated);
409        assert_eq!(goal.pattern, "User.IsBanned == true");
410    }
411
412    #[test]
413    fn test_normal_goal_not_negated() {
414        let goal = Goal::new("test".to_string());
415        assert!(!goal.is_negated);
416
417        let expr = Expression::Field("test".to_string());
418        let goal_with_expr = Goal::with_expression("test".to_string(), expr);
419        assert!(!goal_with_expr.is_negated);
420    }
421}