use crate::cell::*;
#[cfg(test)]
use crate::machine::ContFn;
use crate::machine::{MAX_ARGS, Machine};
use crate::render;
pub enum Outcome {
Done,
Error,
}
pub fn solve(m: &mut Machine, goal: Word) -> Outcome {
m.k_fn = capture_k;
m.k_env = 0;
m.qbarrier = 0;
let r = call_goal(m, goal);
drive(m, 0, r);
if m.error.is_some() {
Outcome::Error
} else {
Outcome::Done
}
}
pub fn drive(m: &mut Machine, floor: usize, mut r: i32) -> i32 {
loop {
if m.error.is_some() {
if m.error.as_ref().is_some_and(|e| e.uncatchable) {
return 0;
}
match unwind_to_catch(m, floor) {
Some(r2) => {
r = r2;
continue;
}
None => return 0, }
}
if r == 1 {
return 1; }
if m.cps.len() <= floor {
return 0; }
let cp = m.cps.pop().unwrap();
m.rewind_to(cp.trail_mark, cp.heap_mark);
r = unsafe { (cp.retry)(m as *mut Machine, cp.env) };
}
}
fn unwind_to_catch(m: &mut Machine, floor: usize) -> Option<i32> {
use crate::machine::CpKind;
while m.cps.len() > floor {
let cp = m.cps.pop().unwrap();
m.rewind_to(cp.trail_mark, cp.heap_mark);
if cp.kind != CpKind::Catch {
continue;
}
let f = cp.env as usize;
let catcher = m.heap[f];
let recovery = m.heap[f + 1];
let err = m.error.take().unwrap();
let ball_w = crate::copyterm::restore_from_buf(m, &err.ball);
let tmark = m.trail.len();
if crate::unify::unify(m, catcher, ball_w) {
let kf: crate::machine::ContFn = unsafe { std::mem::transmute(m.heap[f + 2] as usize) };
m.k_fn = kf;
m.k_env = m.heap[f + 3];
m.qbarrier = m.heap[f + 4] as usize;
return Some(call_goal(m, recovery));
}
while m.trail.len() > tmark {
let idx = m.trail.pop().unwrap() as usize;
m.heap[idx] = make_ref(idx);
}
m.error = Some(err);
}
None
}
unsafe extern "C" fn capture_k(m: *mut Machine, _env: u64) -> i32 {
let m = unsafe { &mut *m };
m.solutions.push(render::capture_solution(m));
match m.solution_limit {
Some(limit) if m.solutions.len() >= limit => 1,
_ => 0,
}
}
pub fn call_goal(m: &mut Machine, goal: Word) -> i32 {
let goal = m.deref(goal);
match tag_of(goal) {
TAG_ATOM => {
let name = m.atoms.resolve(atom_id(goal)).to_string();
if let Some(r) = crate::control::try_atom_builtin(m, &name) {
return r;
}
dispatch(m, atom_id(goal), 0, 0)
}
TAG_STR => {
let idx = payload(goal) as usize;
let (f, n) = unpack_functor(m.heap[idx]);
let name = m.atoms.resolve(f).to_string();
if let Some(r) = crate::control::try_builtin(m, &name, idx + 1, n) {
return r;
}
dispatch(m, f, n, idx + 1)
}
TAG_REF => {
crate::errors::instantiation(m, "Goal is an unbound variable");
0
}
_ => {
crate::errors::type_error(m, "callable", goal, "Goal is not callable");
0
}
}
}
fn dispatch(m: &mut Machine, functor: u32, arity: u32, args_idx: usize) -> i32 {
let Some(f) = m.registry_lookup(functor, arity) else {
let name = m.atoms.resolve(functor).to_string();
crate::errors::existence_procedure(m, &name, arity);
return 0;
};
debug_assert!(arity as usize <= MAX_ARGS);
for i in 0..arity as usize {
m.areg[i] = m.heap[args_idx + i];
}
unsafe { f(m as *mut Machine, 0) }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::machine::RegistryEntry;
use plg_shared::StringInterner;
unsafe extern "C" fn p_entry(m: *mut Machine, _env: u64) -> i32 {
let mr = unsafe { &mut *m };
if !mr.step() {
return 0;
}
let f = mr.frame_alloc(3);
mr.heap[f] = mr.areg[0];
mr.heap[f + 1] = mr.k_fn as usize as u64;
mr.heap[f + 2] = mr.k_env;
mr.push_cp(p_clause2, f as u64);
unsafe { p_clause1(m, f as u64) }
}
unsafe extern "C" fn p_clause1(m: *mut Machine, env: u64) -> i32 {
let mr = unsafe { &mut *m };
let f = env as usize;
let atom_a = mr.atoms.lookup("a").unwrap();
if !crate::unify::unify(mr, mr.heap[f], make_atom(atom_a)) {
return 0;
}
let k: ContFn = unsafe { std::mem::transmute(mr.heap[f + 1] as usize) };
unsafe { k(m, mr.heap[f + 2]) }
}
unsafe extern "C" fn p_clause2(m: *mut Machine, env: u64) -> i32 {
let mr = unsafe { &mut *m };
let f = env as usize;
let atom_b = mr.atoms.lookup("b").unwrap();
if !crate::unify::unify(mr, mr.heap[f], make_atom(atom_b)) {
return 0;
}
let k: ContFn = unsafe { std::mem::transmute(mr.heap[f + 1] as usize) };
unsafe { k(m, mr.heap[f + 2]) }
}
fn machine_with_p() -> Box<Machine> {
let mut atoms = StringInterner::new();
let p = atoms.intern("p");
atoms.intern("a");
atoms.intern("b");
let registry = vec![RegistryEntry {
functor: p,
arity: 1,
f: p_entry,
}];
Machine::new(atoms, registry)
}
#[test]
fn enumerates_both_solutions_via_backtracking() {
let mut m = machine_with_p();
let goal = crate::query::parse_query(&mut m, "p(X)").unwrap();
assert!(matches!(solve(&mut m, goal), Outcome::Done));
assert!(m.error.is_none());
assert_eq!(m.solutions.len(), 2);
assert_eq!(m.solutions[0].bindings[0].2, "a");
assert_eq!(m.solutions[1].bindings[0].2, "b");
}
#[test]
fn ground_query_checks_membership() {
let mut m = machine_with_p();
let goal = crate::query::parse_query(&mut m, "p(b)").unwrap();
solve(&mut m, goal);
assert_eq!(m.solutions.len(), 1);
let mut m2 = machine_with_p();
let goal2 = crate::query::parse_query(&mut m2, "p(zzz)").unwrap();
solve(&mut m2, goal2);
assert_eq!(m2.solutions.len(), 0);
}
#[test]
fn limit_stops_enumeration() {
let mut m = machine_with_p();
m.solution_limit = Some(1);
let goal = crate::query::parse_query(&mut m, "p(X)").unwrap();
solve(&mut m, goal);
assert_eq!(m.solutions.len(), 1);
}
#[test]
fn conjunction_runs_both_goals() {
let mut m = machine_with_p();
let goal = crate::query::parse_query(&mut m, "p(X), p(Y)").unwrap();
solve(&mut m, goal);
assert_eq!(m.solutions.len(), 4);
}
#[test]
fn unknown_predicate_raises_existence_error() {
let mut m = machine_with_p();
let goal = crate::query::parse_query(&mut m, "nosuch(X)").unwrap();
assert!(matches!(solve(&mut m, goal), Outcome::Error));
let msg = &m.error.as_ref().unwrap().message;
assert_eq!(
msg,
"error(existence_error(procedure, /(nosuch, 1)), Undefined procedure: nosuch/1)"
);
}
}