use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use super::answer::{Answer, render_answer, render_terms};
use super::compile::{build, PredKey, Program, Query, TGoal};
use super::error::EvalError;
use super::store::{Addr, Cell, Mark, Store};
use crate::ast::RelOp;
#[derive(Debug, Clone, Copy)]
enum RtGoal {
Call(Addr),
Eq(Addr, Addr),
Dif(Addr, Addr),
Rel(RelOp, Addr, Addr),
}
type Goals = Option<Rc<GoalNode>>;
#[derive(Debug)]
struct GoalNode {
goal: RtGoal,
next: Goals,
}
fn cons(goal: RtGoal, next: Goals) -> Goals {
Some(Rc::new(GoalNode { goal, next }))
}
struct ChoicePoint {
rest: Goals,
call: Addr,
key: PredKey,
next_clause: usize,
mark: Mark,
}
pub struct Solutions<'p> {
program: &'p Program,
store: Store,
goals: Goals,
choice_points: Vec<ChoicePoint>,
query_vars: Vec<(String, Addr)>,
started: bool,
exhausted: bool,
error: Option<EvalError>,
interrupt: Option<Arc<AtomicBool>>,
interrupted: bool,
steps: u64,
}
impl<'p> Solutions<'p> {
pub(crate) fn new(program: &'p Program, query: &Query) -> Self {
let mut store = Store::new();
let initial_ok = program.post_initial(&mut store);
debug_assert!(initial_ok, "initial store validated at compile time");
store.set_baseline();
let mut vars = vec![None; query.var_names.len()];
let mut goals: Goals = None;
let mut ok = initial_ok;
let mut built = Vec::with_capacity(query.goals.len());
for g in &query.goals {
match instantiate_goal(&mut store, g, &mut vars) {
Some(rg) => built.push(rg),
None => {
ok = false;
break;
}
}
}
for rg in built.into_iter().rev() {
goals = cons(rg, goals);
}
let query_vars = query
.var_names
.iter()
.cloned()
.zip(vars.iter().map(|v| v.unwrap_or(usize::MAX)))
.filter(|(_, a)| *a != usize::MAX)
.collect();
let error = store.error.take();
Solutions {
program,
store,
goals,
choice_points: Vec::new(),
query_vars,
started: false,
exhausted: !ok,
error,
interrupt: None,
interrupted: false,
steps: 0,
}
}
pub fn error(&self) -> Option<&EvalError> {
self.error.as_ref()
}
pub fn with_interrupt(mut self, flag: Arc<AtomicBool>) -> Self {
self.interrupt = Some(flag);
self
}
pub fn interrupted(&self) -> bool {
self.interrupted
}
pub fn may_continue(&self) -> bool {
!self.exhausted && !self.choice_points.is_empty()
}
fn check_interrupt(&mut self) -> bool {
self.steps += 1;
if self.steps % 256 == 0 {
if let Some(flag) = &self.interrupt {
if flag.load(Ordering::Relaxed) {
self.interrupted = true;
return true;
}
}
}
false
}
fn try_clauses(&mut self, key: PredKey, from: usize, call: Addr, rest: Goals) -> bool {
let clauses = self.program.clauses(&key);
for j in from..clauses.len() {
let mark = self.store.mark();
let clause = &clauses[j];
let mut vars = vec![None; clause.nvars as usize];
let Some(head) = build(&mut self.store, &clause.head, &mut vars) else {
self.store.undo_to(&mark);
if self.store.error.is_some() {
return false;
}
continue;
};
if !self.store.post_eq(head, call) {
self.store.undo_to(&mark);
continue;
}
let mut body = Vec::with_capacity(clause.body.len());
let mut ok = true;
for g in &clause.body {
match instantiate_goal(&mut self.store, g, &mut vars) {
Some(rg) => body.push(rg),
None => {
ok = false;
break;
}
}
}
if !ok {
self.store.undo_to(&mark);
if self.store.error.is_some() {
return false;
}
continue;
}
if j + 1 < clauses.len() {
self.choice_points.push(ChoicePoint {
rest: rest.clone(),
call,
key,
next_clause: j + 1,
mark,
});
}
let mut goals = rest;
for rg in body.into_iter().rev() {
goals = cons(rg, goals);
}
self.goals = goals;
return true;
}
false
}
fn backtrack(&mut self) -> bool {
while let Some(cp) = self.choice_points.pop() {
self.store.undo_to(&cp.mark);
if self.try_clauses(cp.key, cp.next_clause, cp.call, cp.rest) {
return true;
}
if self.store.error.is_some() {
return false;
}
}
false
}
fn run(&mut self) -> bool {
loop {
if self.store.error.is_some() || self.check_interrupt() {
return false;
}
let Some(node) = self.goals.clone() else {
if self.store.finalize() {
return true;
}
if self.store.error.is_some() || self.store.nonlinear.is_some() || !self.backtrack() {
return false;
}
continue;
};
let rest = node.next.clone();
let ok = match node.goal {
RtGoal::Call(call) => {
let key = pred_key(&self.store, call);
self.try_clauses(key, 0, call, rest)
}
RtGoal::Eq(a, b) => {
self.goals = rest;
self.store.post_eq_goal(a, b)
}
RtGoal::Dif(a, b) => {
self.goals = rest;
self.store.post_dif_goal(a, b)
}
RtGoal::Rel(op, a, b) => {
self.goals = rest;
self.store.post_rel(op, a, b)
}
};
if !ok && (self.store.error.is_some() || !self.backtrack()) {
return false;
}
}
}
fn answer(&self) -> Answer {
render_answer(&self.program.symbols, &self.store, &self.query_vars)
}
fn take_error(&mut self) -> Option<EvalError> {
if let Some(e) = self.store.error.take() {
return Some(e);
}
let (a, b, op) = self.store.nonlinear.take()?;
let rendered = render_terms(&self.program.symbols, &self.store, &self.query_vars, &[a, b]);
Some(EvalError::NonLinear { constraint: format!("{} {op} {}", rendered[0], rendered[1]) })
}
}
impl Iterator for Solutions<'_> {
type Item = Answer;
fn next(&mut self) -> Option<Answer> {
if self.exhausted {
return None;
}
if self.started && !self.backtrack() {
self.exhausted = true;
self.error = self.take_error();
return None;
}
self.started = true;
if self.run() {
Some(self.answer())
} else {
self.exhausted = true;
self.error = self.take_error();
None
}
}
}
fn instantiate_goal(store: &mut Store, g: &TGoal, vars: &mut [Option<Addr>]) -> Option<RtGoal> {
Some(match g {
TGoal::Call(t) => RtGoal::Call(build(store, t, vars)?),
TGoal::Eq(a, b) => {
let a = build(store, a, vars)?;
let b = build(store, b, vars)?;
RtGoal::Eq(a, b)
}
TGoal::Dif(a, b) => {
let a = build(store, a, vars)?;
let b = build(store, b, vars)?;
RtGoal::Dif(a, b)
}
TGoal::Rel(op, a, b) => {
let a = build(store, a, vars)?;
let b = build(store, b, vars)?;
RtGoal::Rel(*op, a, b)
}
})
}
fn pred_key(store: &Store, call: Addr) -> PredKey {
match store.cell(store.deref(call)) {
Cell::Struct(f, args) => (*f, args.len()),
Cell::Const(c) => (*c, 0),
other => unreachable!("call goals are atoms, got {other:?}"),
}
}