1use crate::cell::*;
11#[cfg(test)]
12use crate::machine::ContFn;
13use crate::machine::{MAX_ARGS, Machine};
14use crate::render;
15
16pub enum Outcome {
17 Done,
20 Error,
21}
22
23pub fn solve(m: &mut Machine, goal: Word) -> Outcome {
25 m.k_fn = capture_k;
26 m.k_env = 0;
27 m.qbarrier = 0;
28 let r = call_goal(m, goal);
29 drive(m, 0, r);
30 if m.error.is_some() {
31 Outcome::Error
32 } else {
33 Outcome::Done
34 }
35}
36
37pub fn drive(m: &mut Machine, floor: usize, mut r: i32) -> i32 {
44 loop {
45 if m.error.is_some() {
46 if m.error.as_ref().is_some_and(|e| e.uncatchable) {
47 return 0;
48 }
49 match unwind_to_catch(m, floor) {
50 Some(r2) => {
51 r = r2;
52 continue;
53 }
54 None => return 0, }
56 }
57 if r == 1 {
58 return 1; }
60 if m.cps.len() <= floor {
61 return 0; }
63 let cp = m.cps.pop().unwrap();
64 m.rewind_to(cp.trail_mark, cp.heap_mark);
65 r = unsafe { (cp.retry)(m as *mut Machine, cp.env) };
66 }
67}
68
69fn unwind_to_catch(m: &mut Machine, floor: usize) -> Option<i32> {
75 use crate::machine::CpKind;
76 while m.cps.len() > floor {
77 let cp = m.cps.pop().unwrap();
78 m.rewind_to(cp.trail_mark, cp.heap_mark);
79 if cp.kind != CpKind::Catch {
80 continue;
81 }
82 let f = cp.env as usize;
85 let catcher = m.heap[f];
86 let recovery = m.heap[f + 1];
87 let err = m.error.take().unwrap();
88 let ball_w = crate::copyterm::restore_from_buf(m, &err.ball);
89 let tmark = m.trail.len();
90 if crate::unify::unify(m, catcher, ball_w) {
91 let kf: crate::machine::ContFn = unsafe { std::mem::transmute(m.heap[f + 2] as usize) };
92 m.k_fn = kf;
93 m.k_env = m.heap[f + 3];
94 m.qbarrier = m.heap[f + 4] as usize;
95 return Some(call_goal(m, recovery));
96 }
97 while m.trail.len() > tmark {
101 let idx = m.trail.pop().unwrap() as usize;
102 m.heap[idx] = make_ref(idx);
103 }
104 m.error = Some(err);
105 }
106 None
107}
108
109unsafe extern "C" fn capture_k(m: *mut Machine, _env: u64) -> i32 {
113 let m = unsafe { &mut *m };
114 m.solutions.push(render::capture_solution(m));
115 match m.solution_limit {
116 Some(limit) if m.solutions.len() >= limit => 1,
117 _ => 0,
118 }
119}
120
121pub fn call_goal(m: &mut Machine, goal: Word) -> i32 {
126 let goal = m.deref(goal);
127 match tag_of(goal) {
128 TAG_ATOM => {
129 let name = m.atoms.resolve(atom_id(goal)).to_string();
130 if let Some(r) = crate::control::try_atom_builtin(m, &name) {
131 return r;
132 }
133 dispatch(m, atom_id(goal), 0, 0)
134 }
135 TAG_STR => {
136 let idx = payload(goal) as usize;
137 let (f, n) = unpack_functor(m.heap[idx]);
138 let name = m.atoms.resolve(f).to_string();
139 if let Some(r) = crate::control::try_builtin(m, &name, idx + 1, n) {
140 return r;
141 }
142 dispatch(m, f, n, idx + 1)
143 }
144 TAG_REF => {
145 crate::errors::instantiation(m, "Goal is an unbound variable");
146 0
147 }
148 _ => {
149 crate::errors::type_error(m, "callable", goal, "Goal is not callable");
150 0
151 }
152 }
153}
154
155fn dispatch(m: &mut Machine, functor: u32, arity: u32, args_idx: usize) -> i32 {
156 let Some(f) = m.registry_lookup(functor, arity) else {
157 let name = m.atoms.resolve(functor).to_string();
158 crate::errors::existence_procedure(m, &name, arity);
161 return 0;
162 };
163 debug_assert!(arity as usize <= MAX_ARGS);
164 for i in 0..arity as usize {
165 m.areg[i] = m.heap[args_idx + i];
166 }
167 unsafe { f(m as *mut Machine, 0) }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use crate::machine::RegistryEntry;
174 use plg_shared::StringInterner;
175
176 unsafe extern "C" fn p_entry(m: *mut Machine, _env: u64) -> i32 {
180 let mr = unsafe { &mut *m };
181 if !mr.step() {
182 return 0;
183 }
184 let f = mr.frame_alloc(3);
186 mr.heap[f] = mr.areg[0];
187 mr.heap[f + 1] = mr.k_fn as usize as u64;
188 mr.heap[f + 2] = mr.k_env;
189 mr.push_cp(p_clause2, f as u64);
190 unsafe { p_clause1(m, f as u64) }
191 }
192
193 unsafe extern "C" fn p_clause1(m: *mut Machine, env: u64) -> i32 {
194 let mr = unsafe { &mut *m };
195 let f = env as usize;
196 let atom_a = mr.atoms.lookup("a").unwrap();
197 if !crate::unify::unify(mr, mr.heap[f], make_atom(atom_a)) {
198 return 0;
199 }
200 let k: ContFn = unsafe { std::mem::transmute(mr.heap[f + 1] as usize) };
201 unsafe { k(m, mr.heap[f + 2]) }
202 }
203
204 unsafe extern "C" fn p_clause2(m: *mut Machine, env: u64) -> i32 {
205 let mr = unsafe { &mut *m };
206 let f = env as usize;
207 let atom_b = mr.atoms.lookup("b").unwrap();
208 if !crate::unify::unify(mr, mr.heap[f], make_atom(atom_b)) {
209 return 0;
210 }
211 let k: ContFn = unsafe { std::mem::transmute(mr.heap[f + 1] as usize) };
212 unsafe { k(m, mr.heap[f + 2]) }
213 }
214
215 fn machine_with_p() -> Box<Machine> {
216 let mut atoms = StringInterner::new();
217 let p = atoms.intern("p");
218 atoms.intern("a");
219 atoms.intern("b");
220 let registry = vec![RegistryEntry {
221 functor: p,
222 arity: 1,
223 f: p_entry,
224 }];
225 Machine::new(atoms, registry)
226 }
227
228 #[test]
229 fn enumerates_both_solutions_via_backtracking() {
230 let mut m = machine_with_p();
231 let goal = crate::query::parse_query(&mut m, "p(X)").unwrap();
232 assert!(matches!(solve(&mut m, goal), Outcome::Done));
233 assert!(m.error.is_none());
234 assert_eq!(m.solutions.len(), 2);
235 assert_eq!(m.solutions[0].bindings[0].2, "a");
236 assert_eq!(m.solutions[1].bindings[0].2, "b");
237 }
238
239 #[test]
240 fn ground_query_checks_membership() {
241 let mut m = machine_with_p();
242 let goal = crate::query::parse_query(&mut m, "p(b)").unwrap();
243 solve(&mut m, goal);
244 assert_eq!(m.solutions.len(), 1);
245
246 let mut m2 = machine_with_p();
247 let goal2 = crate::query::parse_query(&mut m2, "p(zzz)").unwrap();
248 solve(&mut m2, goal2);
249 assert_eq!(m2.solutions.len(), 0);
250 }
251
252 #[test]
253 fn limit_stops_enumeration() {
254 let mut m = machine_with_p();
255 m.solution_limit = Some(1);
256 let goal = crate::query::parse_query(&mut m, "p(X)").unwrap();
257 solve(&mut m, goal);
258 assert_eq!(m.solutions.len(), 1);
259 }
260
261 #[test]
262 fn conjunction_runs_both_goals() {
263 let mut m = machine_with_p();
264 let goal = crate::query::parse_query(&mut m, "p(X), p(Y)").unwrap();
265 solve(&mut m, goal);
266 assert_eq!(m.solutions.len(), 4);
268 }
269
270 #[test]
271 fn unknown_predicate_raises_existence_error() {
272 let mut m = machine_with_p();
273 let goal = crate::query::parse_query(&mut m, "nosuch(X)").unwrap();
274 assert!(matches!(solve(&mut m, goal), Outcome::Error));
275 let msg = &m.error.as_ref().unwrap().message;
276 assert_eq!(
277 msg,
278 "error(existence_error(procedure, /(nosuch, 1)), Undefined procedure: nosuch/1)"
279 );
280 }
281}