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