1 use crate::counter::Counter;
2 use crate::error::{OperationalError, PolarResult};
3 use crate::events::QueryEvent;
4 use crate::runnable::Runnable;
5 use crate::terms::{Operation, Operator, Pattern, Term, Value};
6 
path(x: &Term) -> Vec<Term>7 fn path(x: &Term) -> Vec<Term> {
8     match x.value() {
9         Value::Expression(Operation {
10             operator: Operator::Dot,
11             args,
12         }) => [vec![args[0].clone()], path(&args[1])].concat(),
13         _ => vec![x.clone()],
14     }
15 }
16 
17 #[derive(Clone)]
18 pub struct IsaConstraintCheck {
19     existing: Vec<Operation>,
20     proposed: Operation,
21     result: Option<bool>,
22     alternative_check: Option<QueryEvent>,
23     last_call_id: u64,
24 }
25 
26 impl IsaConstraintCheck {
new(existing: Vec<Operation>, proposed: Operation) -> Self27     pub fn new(existing: Vec<Operation>, proposed: Operation) -> Self {
28         Self {
29             existing,
30             proposed,
31             result: None,
32             alternative_check: None,
33             last_call_id: 0,
34         }
35     }
36 
37     /// Check if existing constraints are compatible with the proposed constraint.
38     ///
39     /// If either the existing or proposed constraint is not a type constraint or if they are
40     /// constraints for the same type, there's no external check required, and we return `None` to
41     /// indicate compatibility.
42     ///
43     /// Otherwise, we return a pair of `QueryEvent::ExternalIsSubclass`es to check whether the type
44     /// constraints are compatible. The constraints are compatible if either of their types is a
45     /// subclass of the other's.
46     ///
47     /// Returns:
48     /// - `None` if compatible.
49     /// - A pair of `QueryEvent::ExternalIsSubclass` checks if compatibility cannot be determined
50     /// locally.
check_constraint( &mut self, mut constraint: Operation, counter: &Counter, ) -> (Option<QueryEvent>, Option<QueryEvent>)51     fn check_constraint(
52         &mut self,
53         mut constraint: Operation,
54         counter: &Counter,
55     ) -> (Option<QueryEvent>, Option<QueryEvent>) {
56         // TODO(gj): check non-`Isa` constraints, e.g., `(Unify, partial, 1)` against `(Isa,
57         // partial, Integer)`.
58         if constraint.operator != Operator::Isa {
59             return (None, None);
60         }
61 
62         let constraint_path = path(&constraint.args[0]);
63         let proposed_path = path(&self.proposed.args[0]);
64 
65         // Not comparable b/c one of the matches statements has a LHS that isn't a variable or dot
66         // op.
67         if constraint_path.is_empty() || proposed_path.is_empty() {
68             return (None, None);
69         }
70 
71         // a.b.c vs. d
72         if constraint_path
73             .iter()
74             .zip(proposed_path.iter())
75             .any(|(a, b)| a != b)
76         {
77             return (None, None);
78         }
79 
80         let proposed = self.proposed.args.pop().unwrap();
81         let existing = constraint.args.pop().unwrap();
82 
83         // x matches A{} vs. x matches B{}
84         if constraint_path == proposed_path {
85             match (proposed.value(), existing.value()) {
86                 (
87                     Value::Pattern(Pattern::Instance(proposed)),
88                     Value::Pattern(Pattern::Instance(existing)),
89                 ) if proposed.tag != existing.tag => {
90                     let call_id = counter.next();
91                     self.last_call_id = call_id;
92 
93                     (
94                         Some(QueryEvent::ExternalIsSubclass {
95                             call_id,
96                             left_class_tag: proposed.tag.clone(),
97                             right_class_tag: existing.tag.clone(),
98                         }),
99                         Some(QueryEvent::ExternalIsSubclass {
100                             call_id,
101                             left_class_tag: existing.tag.clone(),
102                             right_class_tag: proposed.tag.clone(),
103                         }),
104                     )
105                 }
106                 _ => (None, None),
107             }
108         } else if constraint_path.len() < proposed_path.len() {
109             // Proposed path is a superset of existing path. Take the existing tag, the additional
110             // path segments from the proposed path, and the proposed tag.
111             //
112             // E.g., given `a.b matches B{}` and `a.b.c.d matches D{}`, we want to assemble an
113             // `ExternalIsaWithPath` of `B`, [c, d], and `D`.
114             match (proposed.value(), existing.value()) {
115                 (
116                     Value::Pattern(Pattern::Instance(proposed)),
117                     Value::Pattern(Pattern::Instance(existing)),
118                 ) => {
119                     let call_id = counter.next();
120                     self.last_call_id = call_id;
121                     (
122                         Some(QueryEvent::ExternalIsaWithPath {
123                             call_id,
124                             base_tag: existing.tag.clone(),
125                             path: proposed_path[constraint_path.len()..].to_vec(),
126                             class_tag: proposed.tag.clone(),
127                         }),
128                         None,
129                     )
130                 }
131                 _ => (None, None),
132             }
133         } else {
134             // Comparing existing `x.a.b matches B{}` vs. `proposed x.a matches A{}`.
135             (None, None)
136         }
137     }
138 }
139 
140 impl Runnable for IsaConstraintCheck {
run(&mut self, counter: Option<&mut Counter>) -> PolarResult<QueryEvent>141     fn run(&mut self, counter: Option<&mut Counter>) -> PolarResult<QueryEvent> {
142         if let Some(result) = self.result.take() {
143             if result {
144                 // If the primary check succeeds, there's no need to check the alternative.
145                 self.alternative_check = None;
146             } else if self.alternative_check.is_none() {
147                 // If both checks fail, we fail.
148                 return Ok(QueryEvent::Done { result: false });
149             }
150         }
151 
152         let counter = counter.expect("IsaConstraintCheck requires a Counter");
153         loop {
154             // If there's an alternative waiting to be checked, check it.
155             if let Some(alternative) = self.alternative_check.take() {
156                 return Ok(alternative);
157             } else if let Some(constraint) = self.existing.pop() {
158                 let (maybe_primary, maybe_alternative) =
159                     self.check_constraint(constraint, &counter);
160                 if let Some(alternative) = maybe_alternative {
161                     self.alternative_check = Some(alternative);
162                 }
163                 if let Some(primary) = maybe_primary {
164                     return Ok(primary);
165                 }
166             } else {
167                 return Ok(QueryEvent::Done { result: true });
168             }
169         }
170     }
171 
external_question_result(&mut self, call_id: u64, answer: bool) -> PolarResult<()>172     fn external_question_result(&mut self, call_id: u64, answer: bool) -> PolarResult<()> {
173         if call_id != self.last_call_id {
174             return Err(OperationalError::InvalidState(String::from("Unexpected call id")).into());
175         }
176 
177         self.result = Some(answer);
178         Ok(())
179     }
180 
clone_runnable(&self) -> Box<dyn Runnable>181     fn clone_runnable(&self) -> Box<dyn Runnable> {
182         Box::new(self.clone())
183     }
184 }
185