1use crate::cell::*;
11#[cfg(test)]
12use crate::machine::ContFn;
13use crate::machine::{CpKind, 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 while m.cps.len() > floor {
76 let cp = m.cps.pop().unwrap();
77 m.rewind_to(cp.trail_mark, cp.heap_mark);
78 if cp.kind != CpKind::Catch {
79 continue;
80 }
81 let f = cp.env as usize;
84 let catcher = m.heap[f];
85 let recovery = m.heap[f + 1];
86 let err = m.error.take().unwrap();
87 let ball_w = crate::copyterm::restore_from_buf(m, &err.ball);
88 let tmark = m.trail.len();
89 if crate::unify::unify(m, catcher, ball_w) {
90 let kf: crate::machine::ContFn = unsafe { std::mem::transmute(m.heap[f + 2] as usize) };
91 m.k_fn = kf;
92 m.k_env = m.heap[f + 3];
93 m.qbarrier = m.heap[f + 4] as usize;
94 return Some(call_goal(m, recovery));
95 }
96 while m.trail.len() > tmark {
100 let idx = m.trail.pop().unwrap() as usize;
101 m.heap[idx] = make_ref(idx);
102 }
103 m.error = Some(err);
104 }
105 None
106}
107
108unsafe extern "C" fn capture_k(m: *mut Machine, _env: u64) -> i32 {
112 let m = unsafe { &mut *m };
113 m.solutions.push(render::capture_solution(m));
114 match m.solution_limit {
115 Some(limit) if m.solutions.len() >= limit => 1,
116 _ => 0,
117 }
118}
119
120pub fn call_goal(m: &mut Machine, goal: Word) -> i32 {
131 let _depth = crate::machine::MetacallDepthGuard::enter(m as *mut Machine);
134 if m.metacall_depth > m.metacall_depth_limit {
135 let ctx = format!(
136 "Maximum metacall recursion depth exceeded ({})",
137 m.metacall_depth_limit
138 );
139 crate::errors::resource(m, "metacall_depth", &ctx, true);
140 return 0;
141 }
142 call_goal_inner(m, goal)
143}
144
145fn call_goal_inner(m: &mut Machine, goal: Word) -> i32 {
146 let goal = m.deref(goal);
147 match tag_of(goal) {
148 TAG_ATOM => {
149 let name = m.atoms.resolve(atom_id(goal)).to_string();
150 if let Some(r) = crate::control::try_atom_builtin(m, &name) {
151 return r;
152 }
153 dispatch(m, atom_id(goal), 0, 0)
154 }
155 TAG_STR => {
156 let idx = payload(goal) as usize;
157 let (f, n) = unpack_functor(m.heap[idx]);
158 let name = m.atoms.resolve(f).to_string();
159 if let Some(r) = crate::control::try_builtin(m, &name, idx + 1, n) {
160 return r;
161 }
162 dispatch(m, f, n, idx + 1)
163 }
164 TAG_REF => {
165 crate::errors::instantiation(m, "Goal is an unbound variable");
166 0
167 }
168 _ => {
169 crate::errors::type_error(m, "callable", goal, "Goal is not callable");
170 0
171 }
172 }
173}
174
175pub(crate) fn resolve_simple(
184 m: &mut Machine,
185 functor: u32,
186 arity: u32,
187 args_idx: usize,
188) -> Option<crate::machine::ContFn> {
189 let f = m.registry_lookup(functor, arity)?;
190 debug_assert!(arity as usize <= MAX_ARGS);
191 for i in 0..arity as usize {
192 m.areg[i] = m.heap[args_idx + i];
193 }
194 Some(f)
195}
196
197fn dispatch(m: &mut Machine, functor: u32, arity: u32, args_idx: usize) -> i32 {
198 match resolve_simple(m, functor, arity, args_idx) {
199 Some(f) => unsafe { f(m as *mut Machine, 0) },
200 None => {
201 let name = m.atoms.resolve(functor).to_string();
202 crate::errors::existence_procedure(m, &name, arity);
205 0
206 }
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213 use crate::machine::RegistryEntry;
214 use plg_shared::StringInterner;
215
216 unsafe extern "C" fn p_entry(m: *mut Machine, _env: u64) -> i32 {
220 let mr = unsafe { &mut *m };
221 if !mr.step() {
222 return 0;
223 }
224 let f = mr.frame_alloc(3);
226 mr.heap[f] = mr.areg[0];
227 mr.heap[f + 1] = mr.k_fn as usize as u64;
228 mr.heap[f + 2] = mr.k_env;
229 mr.push_cp(p_clause2, f as u64);
230 unsafe { p_clause1(m, f as u64) }
231 }
232
233 unsafe extern "C" fn p_clause1(m: *mut Machine, env: u64) -> i32 {
234 let mr = unsafe { &mut *m };
235 let f = env as usize;
236 let atom_a = mr.atoms.lookup("a").unwrap();
237 if !crate::unify::unify(mr, mr.heap[f], make_atom(atom_a)) {
238 return 0;
239 }
240 let k: ContFn = unsafe { std::mem::transmute(mr.heap[f + 1] as usize) };
241 unsafe { k(m, mr.heap[f + 2]) }
242 }
243
244 unsafe extern "C" fn p_clause2(m: *mut Machine, env: u64) -> i32 {
245 let mr = unsafe { &mut *m };
246 let f = env as usize;
247 let atom_b = mr.atoms.lookup("b").unwrap();
248 if !crate::unify::unify(mr, mr.heap[f], make_atom(atom_b)) {
249 return 0;
250 }
251 let k: ContFn = unsafe { std::mem::transmute(mr.heap[f + 1] as usize) };
252 unsafe { k(m, mr.heap[f + 2]) }
253 }
254
255 fn machine_with_p() -> Box<Machine> {
256 let mut atoms = StringInterner::new();
257 let p = atoms.intern("p");
258 atoms.intern("a");
259 atoms.intern("b");
260 let registry = vec![RegistryEntry {
261 functor: p,
262 arity: 1,
263 f: p_entry,
264 }];
265 Machine::new(atoms, registry)
266 }
267
268 #[test]
269 fn enumerates_both_solutions_via_backtracking() {
270 let mut m = machine_with_p();
271 let goal = crate::query::parse_query(&mut m, "p(X)").unwrap();
272 assert!(matches!(solve(&mut m, goal), Outcome::Done));
273 assert!(m.error.is_none());
274 assert_eq!(m.solutions.len(), 2);
275 assert_eq!(m.solutions[0].bindings[0].text, "a");
276 assert_eq!(m.solutions[1].bindings[0].text, "b");
277 }
278
279 #[test]
280 fn ground_query_checks_membership() {
281 let mut m = machine_with_p();
282 let goal = crate::query::parse_query(&mut m, "p(b)").unwrap();
283 solve(&mut m, goal);
284 assert_eq!(m.solutions.len(), 1);
285
286 let mut m2 = machine_with_p();
287 let goal2 = crate::query::parse_query(&mut m2, "p(zzz)").unwrap();
288 solve(&mut m2, goal2);
289 assert_eq!(m2.solutions.len(), 0);
290 }
291
292 #[test]
293 fn limit_stops_enumeration() {
294 let mut m = machine_with_p();
295 m.solution_limit = Some(1);
296 let goal = crate::query::parse_query(&mut m, "p(X)").unwrap();
297 solve(&mut m, goal);
298 assert_eq!(m.solutions.len(), 1);
299 }
300
301 #[test]
302 fn conjunction_runs_both_goals() {
303 let mut m = machine_with_p();
304 let goal = crate::query::parse_query(&mut m, "p(X), p(Y)").unwrap();
305 solve(&mut m, goal);
306 assert_eq!(m.solutions.len(), 4);
308 }
309
310 #[test]
311 fn unknown_predicate_raises_existence_error() {
312 let mut m = machine_with_p();
313 let goal = crate::query::parse_query(&mut m, "nosuch(X)").unwrap();
314 assert!(matches!(solve(&mut m, goal), Outcome::Error));
315 let msg = &m.error.as_ref().unwrap().message;
316 assert_eq!(
317 msg,
318 "error(existence_error(procedure, /(nosuch, 1)), Undefined procedure: nosuch/1)"
319 );
320 }
321}