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