use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use super::answer::{
Answer, Failure, FailureTree, ProofNode, Reason, Resolution, render_answer, render_template,
render_terms,
};
use super::compile::{
ANONYMOUS, ArgKey, Builtin, CondCheck, PredKey, Program, Query, TGoal, TTerm, build_with,
term_vars,
};
use super::error::EvalError;
use super::explain::{self, ConstraintOp, Step};
use super::store::{Addr, Cell, Mark, Store, TypeKind};
use super::symbol::LIST;
use super::table::TableState;
use crate::ast::RelOp;
#[derive(Debug, Clone, Copy)]
enum RtGoal {
Call(Addr),
Eq(Addr, Addr),
Dif(Addr, Addr),
Rel(RelOp, Addr, Addr),
}
type Frame = Rc<[Addr]>;
type Under = Option<usize>;
#[derive(Debug, Clone)]
struct CallRef<'p> {
addr: Addr,
tpl: Tpl<'p>,
frame: Frame,
}
#[derive(Debug, Clone)]
enum Tpl<'p> {
Prog(&'p TTerm),
Key(Rc<TTerm>),
}
impl Tpl<'_> {
fn get(&self) -> &TTerm {
match self {
Tpl::Prog(t) => t,
Tpl::Key(t) => t,
}
}
}
#[derive(Debug, Clone)]
enum Goal<'p> {
Pending {
frame: Frame,
goal: &'p TGoal,
under: Under,
},
Commit {
height: usize,
then: Then<'p>,
text: &'p str,
under: Under,
proof_at_open: usize,
},
CollectOne {
frame: Frame,
template: &'p TTerm,
construct: &'p str,
acc: Rc<RefCell<Vec<TTerm>>>,
},
Resolve { call: CallRef<'p>, under: Under },
TableOne { key: Rc<TTerm>, call: Addr },
}
#[derive(Debug, Clone)]
enum Then<'p> {
Goals {
frame: Frame,
goals: &'p [TGoal],
under: Under,
},
Fail,
}
type Goals<'p> = Option<Rc<GoalNode<'p>>>;
#[derive(Debug)]
struct GoalNode<'p> {
goal: Goal<'p>,
next: Goals<'p>,
}
fn cons<'p>(goal: Goal<'p>, next: Goals<'p>) -> Goals<'p> {
Some(Rc::new(GoalNode { goal, next }))
}
fn prepend<'p>(goals: &'p [TGoal], frame: &Frame, under: Under, rest: Goals<'p>) -> Goals<'p> {
let mut out = rest;
for g in goals.iter().rev() {
out = cons(
Goal::Pending {
frame: frame.clone(),
goal: g,
under,
},
out,
);
}
out
}
struct ChoicePoint<'p> {
retry: Retry<'p>,
mark: Mark,
proof_len: usize,
}
enum Retry<'p> {
Clauses {
rest: Goals<'p>,
call: CallRef<'p>,
key: PredKey,
candidates: &'p [usize],
next: usize,
under: Under,
},
Goals {
goals: Goals<'p>,
note: Option<(Under, &'p str)>,
},
FailNote { under: Under, text: &'p str },
Finish {
rest: Goals<'p>,
frame: Frame,
into: u32,
acc: Rc<RefCell<Vec<TTerm>>>,
under: Under,
text: &'p str,
},
TableFinish {
key: Rc<TTerm>,
rest: Goals<'p>,
call: CallRef<'p>,
under: Under,
},
Answers {
key: Rc<TTerm>,
next: usize,
rest: Goals<'p>,
call: CallRef<'p>,
under: Under,
},
}
struct EvalFrame {
parked: Store,
abstract_call: Addr,
abstract_vars: Frame,
leader_min: usize,
looping: bool,
total_at_start: usize,
dependents: Vec<Rc<TTerm>>,
proof_start: usize,
}
struct ProofRec<'p> {
parent: Under,
kind: ProofKind<'p>,
}
enum ProofKind<'p> {
Clause {
key: PredKey,
idx: usize,
frame: Frame,
src: CallRef<'p>,
},
Constraint { goal: &'p TGoal, frame: Frame },
Builtin {
call: Addr,
builtin: Builtin,
tpl: &'p TTerm,
frame: Frame,
},
Committed { text: &'p str },
NoSolution { text: &'p str },
Collected { text: &'p str, into: Addr },
Tabled {
key: Rc<TTerm>,
idx: usize,
src: CallRef<'p>,
},
Failed(Failed<'p>),
}
struct Failed<'p> {
goal: FailedGoal<'p>,
detail: FailDetail,
}
#[derive(Clone)]
enum FailedGoal<'p> {
Call(Addr),
Constraint {
goal: &'p TGoal,
frame: Frame,
},
Text(&'p str),
Answer,
}
enum FailDetail {
Unsatisfiable {
conflict: Vec<Origin>,
note: Option<String>,
},
NoClause {
clauses: usize,
candidates: usize,
},
HasSolution(Vec<ProofNode>),
NoTableAnswer(usize),
NoSolution,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Origin {
Fact(usize),
Record(usize),
}
const FAILURE_BUDGET: usize = 64;
pub struct Solutions<'p> {
program: &'p Program,
store: Store,
goals: Goals<'p>,
choice_points: Vec<ChoicePoint<'p>>,
query_vars: Vec<(String, Addr)>,
started: bool,
exhausted: bool,
error: Option<EvalError>,
interrupt: Option<Arc<AtomicBool>>,
interrupted: bool,
steps: u64,
step_limit: Option<u64>,
proof: Option<Vec<ProofRec<'p>>>,
eval_stack: Vec<EvalFrame>,
failures: Vec<Vec<ProofNode>>,
omitted: usize,
record_failures: bool,
}
impl<'p> Solutions<'p> {
pub(crate) fn new(program: &'p Program, query: &'p Query) -> Self {
let mut store = program.world.clone();
let frame: Frame = (0..query.var_names.len())
.map(|_| store.new_var())
.collect();
let goals = prepend(&query.goals, &frame, None, None);
let query_vars = query
.var_names
.iter()
.cloned()
.zip(frame.iter().copied())
.filter(|(name, _)| name != ANONYMOUS)
.collect();
Solutions {
program,
store,
goals,
choice_points: Vec::new(),
query_vars,
started: false,
exhausted: false,
error: None,
interrupt: None,
interrupted: false,
steps: 0,
step_limit: None,
proof: None,
eval_stack: Vec::new(),
failures: Vec::new(),
omitted: 0,
record_failures: false,
}
}
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 with_step_limit(mut self, n: u64) -> Self {
self.step_limit = Some(n);
self
}
pub fn steps(&self) -> u64 {
self.steps
}
pub fn with_explanations(mut self) -> Self {
self.proof = Some(Vec::new());
self.record_failures = true;
self
}
pub fn interrupted(&self) -> bool {
self.interrupted
}
pub fn may_continue(&self) -> bool {
!self.exhausted
&& self
.choice_points
.iter()
.any(|cp| !matches!(cp.retry, Retry::FailNote { .. }))
}
pub fn failure(&self) -> Option<FailureTree> {
if self.record_failures && self.exhausted && self.error.is_none() && !self.interrupted {
Some(FailureTree {
branches: self.failures.clone(),
omitted: self.omitted,
})
} else {
None
}
}
fn account_step(&mut self) -> bool {
self.steps += 1;
if self.step_limit.is_some_and(|limit| self.steps > limit) {
let goal = self.describe_next_goal();
self.store.error = Some(EvalError::StepLimit {
steps: self.steps - 1,
goal,
});
return true;
}
if self.steps % 256 == 0 {
if let Some(flag) = &self.interrupt {
if flag.load(Ordering::Relaxed) {
self.interrupted = true;
return true;
}
}
}
false
}
fn describe_next_goal(&mut self) -> String {
let Some(node) = self.goals.clone() else {
return "an answer".to_string();
};
match &node.goal {
Goal::Pending {
frame,
goal: TGoal::Call(t),
..
} => {
let frame = frame.clone();
match build_with(&mut self.store, t, &mut |_, i| frame[i as usize]) {
Some(a) => format!("`{}`", self.render_one(a)),
None => "a call".to_string(),
}
}
Goal::Pending { .. } => "a constraint or control goal".to_string(),
Goal::Resolve { call, .. } => format!("`{}`", self.render_one(call.addr)),
Goal::Commit { .. } => "a condition's commit".to_string(),
Goal::CollectOne { .. } => "a collected solution".to_string(),
Goal::TableOne { .. } => "a table answer".to_string(),
}
}
fn proof_len(&self) -> usize {
self.proof.as_ref().map_or(0, Vec::len)
}
fn record(&mut self, parent: Under, kind: ProofKind<'p>) -> Under {
let arena = self.proof.as_mut()?;
arena.push(ProofRec { parent, kind });
Some(arena.len() - 1)
}
fn push_choice_point(&mut self, retry: Retry<'p>, mark: Mark) {
let proof_len = self.proof_len();
self.choice_points.push(ChoicePoint {
retry,
mark,
proof_len,
});
}
fn try_clauses(
&mut self,
key: PredKey,
candidates: &'p [usize],
from: usize,
call: CallRef<'p>,
rest: Goals<'p>,
under: Under,
) -> bool {
let program: &'p Program = self.program;
let clauses = program.clauses(&key);
for j in from..candidates.len() {
let mark = self.store.mark();
let clause = &clauses[candidates[j]];
let frame: Frame = (0..clause.nvars).map(|_| self.store.new_var()).collect();
let head = build_with(&mut self.store, &clause.head, &mut |_, i| frame[i as usize]);
let Some(head) = head else {
self.store.undo_to(&mark);
if self.store.error.is_some() {
return false;
}
continue;
};
if !self.store.post_eq(head, call.addr) {
self.store.undo_to(&mark);
continue;
}
if j + 1 < candidates.len() {
self.push_choice_point(
Retry::Clauses {
rest: rest.clone(),
call: call.clone(),
key,
candidates,
next: j + 1,
under,
},
mark,
);
}
let node = self.record(
under,
ProofKind::Clause {
key,
idx: candidates[j],
frame: frame.clone(),
src: call,
},
);
self.goals = prepend(&clause.body, &frame, node, rest);
return true;
}
false
}
fn backtrack(&mut self) -> bool {
while let Some(cp) = self.choice_points.pop() {
self.store.undo_to(&cp.mark);
if let Some(arena) = self.proof.as_mut() {
arena.truncate(cp.proof_len);
}
match cp.retry {
Retry::Clauses {
rest,
call,
key,
candidates,
next,
under,
} => {
if self.try_clauses(key, candidates, next, call, rest, under) {
return true;
}
if self.store.error.is_some() {
return false;
}
}
Retry::Goals { goals, note } => {
if let Some((under, text)) = note {
self.record(under, ProofKind::NoSolution { text });
}
self.goals = goals;
return true;
}
Retry::FailNote { under, text } => {
self.fail_unchecked(under, FailedGoal::Text(text), |_| FailDetail::NoSolution);
}
Retry::Finish {
rest,
frame,
into,
acc,
under,
text,
} => {
let mut list = TTerm::Const(LIST);
for item in acc.borrow().iter().rev() {
list = TTerm::Compound(LIST, vec![item.clone(), list]);
}
let Some(addr) = build_with(&mut self.store, &list, &mut |s, _| s.new_var())
else {
continue;
};
let target = frame[into as usize];
if !self.store.post_eq(target, addr) {
continue;
}
self.record(under, ProofKind::Collected { text, into: target });
self.goals = rest;
return true;
}
Retry::TableFinish {
key,
rest,
call,
under,
} => {
if self.finish_evaluation(key, call, rest, under) {
return true;
}
if self.store.error.is_some() {
return false;
}
}
Retry::Answers {
key,
next,
rest,
call,
under,
} => {
if self.consume(key, next, call, rest, under) {
return true;
}
if self.store.error.is_some() {
return false;
}
}
}
}
false
}
fn names(&self) -> &[(String, Addr)] {
if self.eval_stack.is_empty() {
&self.query_vars
} else {
&[]
}
}
fn render_one(&self, a: Addr) -> String {
render_terms(&self.store, self.names(), &[a]).remove(0)
}
fn pred_name(&self, key: PredKey) -> String {
format!(
"{}/{}",
crate::ast::display_name(self.store.symbols.borrow().name(key.0)),
key.1
)
}
fn tabled_call(&mut self, call: CallRef<'p>, rest: Goals<'p>, under: Under) -> bool {
let Some(vkey) = self.store.variant_key(call.addr) else {
self.store.error = Some(EvalError::CyclicTabledCall {
call: self.render_one(call.addr),
});
return false;
};
let vkey = Rc::new(vkey);
let state = self.program.tables.borrow().state(&vkey);
match state {
Some(TableState::Complete) => self.consume_or_fail(vkey, call, rest, under),
Some(TableState::Evaluating(d)) => {
let top = self.eval_stack.len();
for f in &mut self.eval_stack[d + 1..top] {
f.leader_min = f.leader_min.min(d);
}
self.eval_stack[d].looping = true;
self.consume(vkey, 0, call, rest, under)
}
Some(TableState::Incomplete) | None => self.evaluate(vkey, call, rest, under),
}
}
fn evaluate(
&mut self,
vkey: Rc<TTerm>,
call: CallRef<'p>,
rest: Goals<'p>,
under: Under,
) -> bool {
let depth = self.eval_stack.len();
let total = {
let mut tables = self.program.tables.borrow_mut();
tables.begin(&vkey, depth);
tables.total
};
let world = self.program.world.clone();
let parked = std::mem::replace(&mut self.store, world);
let mut vars: Vec<Option<Addr>> = Vec::new();
let built = build_with(&mut self.store, &vkey, &mut |s, i| {
let i = i as usize;
if vars.len() <= i {
vars.resize(i + 1, None);
}
match vars[i] {
Some(a) => a,
None => {
let a = s.new_var();
vars[i] = Some(a);
a
}
}
});
let Some(abstract_call) = built else {
self.store = parked;
self.store.error = Some(EvalError::InternalInvariant {
what: "a table key did not instantiate".to_string(),
});
return false;
};
let abstract_vars: Frame = vars.into_iter().flatten().collect();
let abstract_ref = CallRef {
addr: abstract_call,
tpl: Tpl::Key(vkey.clone()),
frame: abstract_vars.clone(),
};
self.eval_stack.push(EvalFrame {
parked,
abstract_call,
abstract_vars,
leader_min: depth,
looping: false,
total_at_start: total,
dependents: Vec::new(),
proof_start: self.proof_len(),
});
self.start_pass(vkey, call, rest, under, abstract_ref);
true
}
fn start_pass(
&mut self,
vkey: Rc<TTerm>,
call: CallRef<'p>,
rest: Goals<'p>,
under: Under,
abstract_call: CallRef<'p>,
) {
let mark = self.store.mark();
self.push_choice_point(
Retry::TableFinish {
key: vkey.clone(),
rest,
call,
under,
},
mark,
);
let addr = abstract_call.addr;
self.goals = cons(
Goal::Resolve {
call: abstract_call,
under: None,
},
cons(
Goal::TableOne {
key: vkey,
call: addr,
},
None,
),
);
}
fn finish_evaluation(
&mut self,
key: Rc<TTerm>,
call: CallRef<'p>,
rest: Goals<'p>,
under: Under,
) -> bool {
let Some(frame) = self.eval_stack.pop() else {
self.store.error = Some(EvalError::InternalInvariant {
what: "a table evaluation finished with no frame".to_string(),
});
return false;
};
let depth = self.eval_stack.len();
let total = self.program.tables.borrow().total;
if frame.looping && frame.leader_min == depth && total > frame.total_at_start {
let abstract_call = CallRef {
addr: frame.abstract_call,
tpl: Tpl::Key(key.clone()),
frame: frame.abstract_vars.clone(),
};
self.eval_stack.push(EvalFrame {
looping: false,
total_at_start: total,
..frame
});
self.start_pass(key, call, rest, under, abstract_call);
return true;
}
let world = std::mem::replace(&mut self.store, frame.parked);
drop(world);
{
let mut tables = self.program.tables.borrow_mut();
if frame.leader_min < depth {
tables.set_state(&key, TableState::Incomplete);
let leader = &mut self.eval_stack[frame.leader_min];
leader.dependents.push(key.clone());
leader.dependents.extend(frame.dependents);
} else {
tables.set_state(&key, TableState::Complete);
for d in &frame.dependents {
tables.set_state(d, TableState::Complete);
}
}
}
self.consume_or_fail(key, call, rest, under)
}
fn consume(
&mut self,
key: Rc<TTerm>,
from: usize,
call: CallRef<'p>,
rest: Goals<'p>,
under: Under,
) -> bool {
let (len, complete) = {
let tables = self.program.tables.borrow();
(
tables.len(&key),
tables.state(&key) == Some(TableState::Complete),
)
};
if !complete {
if let Some(construct) = enclosing_sub_search(&rest) {
let predicate = self.pred_name(pred_key(&self.store, call.addr));
self.store.error = Some(EvalError::IncompleteTable {
predicate,
construct,
});
return false;
}
}
for i in from..len {
let answer = self
.program
.tables
.borrow()
.answer(&key, i)
.map(|(a, _)| a.clone());
let Some(answer) = answer else {
break;
};
let mark = self.store.mark();
let built = build_with(&mut self.store, &answer, &mut |s, _| s.new_var());
let Some(built) = built else {
self.store.undo_to(&mark);
continue;
};
if !self.store.post_eq(call.addr, built) {
self.store.undo_to(&mark);
continue;
}
if i + 1 < len || !complete {
self.push_choice_point(
Retry::Answers {
key: key.clone(),
next: i + 1,
rest: rest.clone(),
call: call.clone(),
under,
},
mark,
);
}
self.record(
under,
ProofKind::Tabled {
key,
idx: i,
src: call,
},
);
self.goals = rest;
return true;
}
false
}
fn residual_of_solution(&self) -> Option<String> {
if !self.store.has_residual() {
return None;
}
let frame = self.eval_stack.last()?;
let names: Vec<(String, Addr)> = frame
.abstract_vars
.iter()
.enumerate()
.map(|(i, a)| (format!("_T{i}"), *a))
.collect();
let (solution, _) = render_answer(&self.store, &names, &[]);
if solution.disequations.is_empty() && solution.constraints.is_empty() {
return None;
}
let mut world = self.program.world.clone();
world.touch_like(&self.store);
let (base, _) = render_answer(&world, &[], &[]);
let residual: Vec<String> = solution
.disequations
.iter()
.chain(solution.constraints.iter())
.filter(|c| !base.disequations.contains(c) && !base.constraints.contains(c))
.cloned()
.collect();
if residual.is_empty() {
None
} else {
Some(residual.join(", "))
}
}
fn abandon_evaluations(&mut self) {
if self.eval_stack.is_empty() {
return;
}
let outer = self.eval_stack.drain(..).next().map(|f| f.parked);
if let Some(store) = outer {
self.store = store;
}
self.program.tables.borrow_mut().abandon();
}
fn check_condition(&mut self, frame: &Frame, check: &CondCheck) -> bool {
for (i, name) in &check.external {
if !self.store.is_ground(frame[*i as usize]) {
self.store.error = Some(EvalError::NonGroundCondition {
construct: check.construct.clone(),
variable: name.clone(),
clause: check.clause.clone(),
});
return false;
}
}
true
}
fn open_construct(
&mut self,
frame: &Frame,
goal: &'p TGoal,
rest: Goals<'p>,
under: Under,
) -> bool {
match goal {
TGoal::Or(a, b) => {
let mark = self.store.mark();
self.push_choice_point(
Retry::Goals {
goals: prepend(b, frame, under, rest.clone()),
note: None,
},
mark,
);
self.goals = prepend(a, frame, under, rest);
}
TGoal::IfThenElse {
cond,
then,
els,
check,
} => {
if !self.check_condition(frame, check) {
return false;
}
let height = self.choice_points.len();
if let Some(e) = els {
let mark = self.store.mark();
self.push_choice_point(
Retry::Goals {
goals: prepend(e, frame, under, rest.clone()),
note: Some((under, check.construct.as_str())),
},
mark,
);
} else if self.recording_failures(&rest) {
let mark = self.store.mark();
self.push_choice_point(
Retry::FailNote {
under,
text: check.construct.as_str(),
},
mark,
);
}
let proof_at_open = self.proof_len();
let node = self.record(
under,
ProofKind::Committed {
text: check.construct.as_str(),
},
);
let commit = Goal::Commit {
height,
then: Then::Goals {
frame: frame.clone(),
goals: then,
under,
},
text: check.construct.as_str(),
under,
proof_at_open,
};
self.goals = prepend(cond, frame, node, cons(commit, rest));
}
TGoal::Not { cond, check } => {
if !self.check_condition(frame, check) {
return false;
}
let height = self.choice_points.len();
let mark = self.store.mark();
self.push_choice_point(
Retry::Goals {
goals: rest.clone(),
note: Some((under, check.construct.as_str())),
},
mark,
);
let commit = Goal::Commit {
height,
then: Then::Fail,
text: check.construct.as_str(),
under,
proof_at_open: self.proof_len(),
};
self.goals = prepend(cond, frame, under, cons(commit, rest));
}
TGoal::Collect {
template,
body,
into,
check,
} => {
if !self.check_condition(frame, check) {
return false;
}
let acc: Rc<RefCell<Vec<TTerm>>> = Rc::new(RefCell::new(Vec::new()));
let mark = self.store.mark();
self.push_choice_point(
Retry::Finish {
rest: rest.clone(),
frame: frame.clone(),
into: *into,
acc: acc.clone(),
under,
text: check.construct.as_str(),
},
mark,
);
let one = Goal::CollectOne {
frame: frame.clone(),
template,
construct: check.construct.as_str(),
acc,
};
self.goals = prepend(body, frame, under, cons(one, rest));
}
TGoal::Call(_) | TGoal::Eq(..) | TGoal::Dif(..) | TGoal::Rel(..) => {
unreachable!("not a control construct")
}
}
true
}
fn run(&mut self) -> bool {
loop {
if self.store.error.is_some() || self.account_step() {
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() {
return false;
}
self.fail(None, &None, FailedGoal::Answer, |s| {
s.conflict_for(Step::Answer)
});
if !self.backtrack() {
return false;
}
continue;
};
let rest = node.next.clone();
let (goal, under, tgoal, frame) = match &node.goal {
Goal::Commit {
height,
then,
text,
under: cunder,
proof_at_open,
} => {
if !self.store.finalize() {
if self.store.error.is_some()
|| self.store.nonlinear.is_some()
|| !self.backtrack()
{
return false;
}
continue;
}
self.choice_points.truncate(*height);
match then {
Then::Fail => {
let (cunder, at, text) = (*cunder, *proof_at_open, *text);
self.fail(cunder, &rest, FailedGoal::Text(text), |s| {
let solution = s.render_proof(at).1;
if let Some(arena) = s.proof.as_mut() {
arena.truncate(at);
}
FailDetail::HasSolution(solution)
});
if !self.backtrack() {
return false;
}
}
Then::Goals {
frame,
goals,
under,
} => self.goals = prepend(goals, frame, *under, rest),
}
continue;
}
Goal::CollectOne {
frame,
template,
construct,
acc,
} => {
if !self.store.finalize() {
if self.store.error.is_some()
|| self.store.nonlinear.is_some()
|| !self.backtrack()
{
return false;
}
continue;
}
let frame = frame.clone();
let built =
build_with(&mut self.store, template, &mut |_, i| frame[i as usize]);
let Some(inst) = built else {
if self.store.error.is_some() || !self.backtrack() {
return false;
}
continue;
};
match self.store.snapshot(inst) {
Some(copy) => {
acc.borrow_mut().push(copy);
if !self.backtrack() {
return false;
}
continue;
}
None => {
let instance = self.render_one(inst);
self.store.error = Some(EvalError::NonGroundSolution {
construct: construct.to_string(),
instance,
});
return false;
}
}
}
Goal::TableOne { key, call } => {
if !self.store.finalize() {
if self.store.error.is_some()
|| self.store.nonlinear.is_some()
|| !self.backtrack()
{
return false;
}
continue;
}
let (key, call) = (key.clone(), *call);
match self.store.snapshot(call) {
Some(answer) => {
if let Some(residual) = self.residual_of_solution() {
let predicate = self.pred_name(pred_key(&self.store, call));
let instance = self.render_one(call);
self.store.error = Some(EvalError::ConstrainedTabledAnswer {
predicate,
instance,
residual,
});
return false;
}
let proof = if self.proof.is_some() {
let start = self.eval_stack.last().map_or(0, |f| f.proof_start);
Some(self.render_proof(start).1)
} else {
None
};
self.program.tables.borrow_mut().add(&key, answer, proof);
if !self.backtrack() {
return false;
}
continue;
}
None => {
let predicate = self.pred_name(pred_key(&self.store, call));
let instance = self.render_one(call);
self.store.error = Some(EvalError::NonGroundTabledAnswer {
predicate,
instance,
});
return false;
}
}
}
Goal::Resolve { call, under } => {
let (call, under) = (call.clone(), *under);
let key = pred_key(&self.store, call.addr);
let first = first_arg_key(&self.store, call.addr);
let candidates = self.program.candidates(&key, first.as_ref());
let ok = self.try_clauses(key, candidates, 0, call, rest, under);
if !ok && (self.store.error.is_some() || !self.backtrack()) {
return false;
}
continue;
}
Goal::Pending {
frame,
goal:
goal @ (TGoal::Or(..)
| TGoal::IfThenElse { .. }
| TGoal::Not { .. }
| TGoal::Collect { .. }),
under,
} => {
if !self.open_construct(frame, goal, rest, *under) {
return false;
}
continue;
}
Goal::Pending { frame, goal, under } => {
let frame = frame.clone();
let built =
instantiate_goal(&mut self.store, goal, &mut |_, i| frame[i as usize]);
match built {
Some(g) => (g, *under, *goal, frame),
None => {
if self.store.error.is_some() || !self.backtrack() {
return false;
}
continue;
}
}
}
};
let ok = match goal {
RtGoal::Call(call) => {
let key = pred_key(&self.store, call);
let tpl = match tgoal {
TGoal::Call(t) => t,
_ => unreachable!("a call is instantiated from a call template"),
};
match self.program.builtins.get(&key).copied() {
Some(b) => {
self.goals = rest.clone();
let ok = run_builtin(&mut self.store, b, call);
if ok {
self.record(
under,
ProofKind::Builtin {
call,
builtin: b,
tpl,
frame: frame.clone(),
},
);
} else if self.store.error.is_none() {
self.fail(under, &rest, FailedGoal::Call(call), |s| {
s.conflict_for(Step::Builtin {
builtin: b,
call: explain::close(tpl, &frame),
})
});
}
ok
}
None => {
let src = CallRef {
addr: call,
tpl: Tpl::Prog(tpl),
frame: frame.clone(),
};
if self.program.tabled.contains(&key) {
self.tabled_call(src, rest.clone(), under)
} else {
let first = first_arg_key(&self.store, call);
let candidates = self.program.candidates(&key, first.as_ref());
let ok =
self.try_clauses(key, candidates, 0, src, rest.clone(), under);
if !ok && self.store.error.is_none() {
let clauses = self.program.clauses(&key).len();
let n = candidates.len();
self.fail(under, &rest, FailedGoal::Call(call), |_| {
FailDetail::NoClause {
clauses,
candidates: n,
}
});
}
ok
}
}
}
}
RtGoal::Eq(a, b) => {
self.goals = rest.clone();
self.constraint(under, (tgoal, frame.clone()), |s| s.post_eq_goal(a, b))
}
RtGoal::Dif(a, b) => {
self.goals = rest.clone();
self.constraint(under, (tgoal, frame.clone()), |s| s.post_dif_goal(a, b))
}
RtGoal::Rel(op, a, b) => {
self.goals = rest.clone();
self.constraint(under, (tgoal, frame.clone()), |s| s.post_rel(op, a, b))
}
};
if !ok && self.store.error.is_none() {
if let Some(step) = constraint_step(tgoal, &frame) {
let failed = match goal {
RtGoal::Eq(..) | RtGoal::Dif(..) | RtGoal::Rel(..) => {
FailedGoal::Constraint {
goal: tgoal,
frame: frame.clone(),
}
}
RtGoal::Call(call) => FailedGoal::Call(call),
};
self.fail(under, &rest, failed, |s| s.conflict_for(step));
}
}
if !ok && (self.store.error.is_some() || !self.backtrack()) {
return false;
}
}
}
fn constraint(
&mut self,
under: Under,
src: (&'p TGoal, Frame),
post: impl FnOnce(&mut Store) -> bool,
) -> bool {
let ok = post(&mut self.store);
if ok {
let (goal, frame) = src;
self.record(under, ProofKind::Constraint { goal, frame });
}
ok
}
fn render_goal(
&self,
goal: &TGoal,
frame: &[Addr],
rendered: &mut impl Iterator<Item = String>,
) -> String {
let Some((op, l, r)) = goal_sides(goal) else {
return String::new();
};
let vars: HashMap<u32, String> = goal_var_addrs(goal, frame)
.into_iter()
.map(|(i, _)| (i, rendered.next().unwrap_or_default()))
.collect();
format!(
"{{ {} {op} {} }}",
render_template(&self.store, l, &vars),
render_template(&self.store, r, &vars)
)
}
fn recording_failures(&self, rest: &Goals<'p>) -> bool {
self.record_failures
&& self.proof.is_some()
&& self.eval_stack.is_empty()
&& enclosing_sub_search(rest).is_none()
}
fn fail(
&mut self,
under: Under,
rest: &Goals<'p>,
goal: FailedGoal<'p>,
detail: impl FnOnce(&mut Self) -> FailDetail,
) {
if self.recording_failures(rest) {
self.fail_unchecked(under, goal, detail);
}
}
fn fail_unchecked(
&mut self,
under: Under,
goal: FailedGoal<'p>,
detail: impl FnOnce(&mut Self) -> FailDetail,
) {
if self.failures.len() >= FAILURE_BUDGET {
self.omitted += 1;
return;
}
let detail = detail(self);
let len = self.proof_len();
self.record(under, ProofKind::Failed(Failed { goal, detail }));
let (_, roots) = self.render_proof(0);
self.failures.push(roots);
if let Some(arena) = self.proof.as_mut() {
arena.truncate(len);
}
}
fn conflict_for(&mut self, failing: Step) -> FailDetail {
let (steps, origins) = self.derivation_steps();
match explain::minimal_conflict(self.program, &steps, &failing) {
Ok(idx) => FailDetail::Unsatisfiable {
conflict: idx.iter().map(|&i| origins[i]).collect(),
note: None,
},
Err(e) => FailDetail::Unsatisfiable {
conflict: origins,
note: Some(e.to_string()),
},
}
}
fn consume_or_fail(
&mut self,
key: Rc<TTerm>,
call: CallRef<'p>,
rest: Goals<'p>,
under: Under,
) -> bool {
let ok = self.consume(key.clone(), 0, call.clone(), rest.clone(), under);
if !ok && self.store.error.is_none() {
let answers = self.program.tables.borrow().len(&key);
self.fail(under, &rest, FailedGoal::Call(call.addr), |_| {
FailDetail::NoTableAnswer(answers)
});
}
ok
}
pub(crate) fn derivation_steps(&mut self) -> (Vec<Step>, Vec<Origin>) {
let facts = self.program.initial.len();
let mut steps: Vec<Step> = (0..facts).map(Step::Fact).collect();
let mut origins: Vec<Origin> = (0..facts).map(Origin::Fact).collect();
let Some(arena) = &self.proof else {
return (steps, origins);
};
let program = self.program;
for (i, rec) in arena.iter().enumerate() {
let step = match &rec.kind {
ProofKind::Clause {
key,
idx,
frame,
src,
} => {
let c = &program.clauses(key)[*idx];
Some(Step::Head {
head: explain::close(&c.head, frame),
call: explain::close(src.tpl.get(), &src.frame),
})
}
ProofKind::Constraint { goal, frame, .. } => constraint_step(goal, frame),
ProofKind::Builtin {
builtin,
tpl,
frame,
..
} => Some(Step::Builtin {
builtin: *builtin,
call: explain::close(tpl, frame),
}),
ProofKind::Tabled { key, idx, src } => program
.tables
.borrow()
.answer(key, *idx)
.map(|(a, _)| Step::Tabled {
call: explain::close(src.tpl.get(), &src.frame),
answer: a.clone(),
}),
ProofKind::Collected { into, .. } => {
self.store.snapshot(*into).map(|list| Step::Collected {
into: TTerm::Var(explain::var_id(*into)),
list,
})
}
ProofKind::Committed { .. }
| ProofKind::NoSolution { .. }
| ProofKind::Failed(_) => None,
};
if let Some(step) = step {
steps.push(step);
origins.push(Origin::Record(i));
}
}
(steps, origins)
}
fn answer(&self) -> Answer {
if self.proof.is_none() {
return render_answer(&self.store, &self.query_vars, &[]).0;
}
let (mut answer, roots) = self.render_proof(0);
answer.explanation = Some(roots);
answer
}
fn render_proof(&self, from: usize) -> (Answer, Vec<ProofNode>) {
let arena: &[ProofRec<'p>] = match &self.proof {
Some(a) => &a[from..],
None => &[],
};
let mut addrs = Vec::new();
for rec in arena {
match &rec.kind {
ProofKind::Clause { src, .. } | ProofKind::Tabled { src, .. } => {
addrs.push(src.addr)
}
ProofKind::Builtin { call, .. } => addrs.push(*call),
ProofKind::Constraint { goal, frame } => {
addrs.extend(goal_var_addrs(goal, frame).into_iter().map(|(_, a)| a));
}
ProofKind::Collected { into, .. } => addrs.push(*into),
ProofKind::Committed { .. } | ProofKind::NoSolution { .. } => {}
ProofKind::Failed(f) => match &f.goal {
FailedGoal::Call(a) => addrs.push(*a),
FailedGoal::Constraint { goal, frame } => {
addrs.extend(goal_var_addrs(goal, frame).into_iter().map(|(_, a)| a));
}
FailedGoal::Text(_) | FailedGoal::Answer => {}
},
}
}
let (answer, rendered) = render_answer(&self.store, self.names(), &addrs);
let mut rendered = rendered.into_iter();
let cited = |i: usize| -> Option<(Option<crate::Span>, &str)> {
let ProofKind::Clause { key, idx, .. } = &arena[i.checked_sub(from)?].kind else {
return None;
};
let c = &self.program.clauses(key)[*idx];
Some((c.span, c.text.as_str()))
};
let mut nodes: Vec<ProofNode> = Vec::with_capacity(arena.len());
let mut failed: Vec<(usize, &FailDetail)> = Vec::new();
for rec in arena.iter() {
let mut subgoals = Vec::new();
let (goal, by, cite, clause) = match &rec.kind {
ProofKind::Clause { key, idx, .. } => {
let c = &self.program.clauses(key)[*idx];
let by = if c.body.is_empty() {
Resolution::Fact
} else {
Resolution::Rule
};
(
rendered.next().unwrap_or_default(),
by,
c.span,
Some(c.text.clone()),
)
}
ProofKind::Constraint { goal, frame } => {
let text = self.render_goal(goal, frame, &mut rendered);
let cite = rec.parent.and_then(cited).and_then(|(s, _)| s);
(text, Resolution::Constraint, cite, None)
}
ProofKind::Builtin { .. } => (
rendered.next().unwrap_or_default(),
Resolution::Builtin,
None,
None,
),
ProofKind::Committed { text } => {
(text.to_string(), Resolution::Committed, None, None)
}
ProofKind::NoSolution { text } => {
(text.to_string(), Resolution::NoSolution, None, None)
}
ProofKind::Collected { text, .. } => {
let list = rendered.next().unwrap_or_default();
(
format!("{text} = {list}"),
Resolution::Collected,
None,
None,
)
}
ProofKind::Tabled { key, idx, .. } => {
if let Some((_, Some(proof))) = self.program.tables.borrow().answer(key, *idx) {
subgoals = proof.clone();
}
(
rendered.next().unwrap_or_default(),
Resolution::Tabled,
None,
None,
)
}
ProofKind::Failed(f) => {
let goal = match &f.goal {
FailedGoal::Call(_) => rendered.next().unwrap_or_default(),
FailedGoal::Constraint { goal, frame } => {
self.render_goal(goal, frame, &mut rendered)
}
FailedGoal::Text(t) => t.to_string(),
FailedGoal::Answer => "answer".to_string(),
};
if let FailDetail::HasSolution(solution) = &f.detail {
subgoals = solution.clone();
}
failed.push((nodes.len(), &f.detail));
(goal, Resolution::Failed, None, None)
}
};
nodes.push(ProofNode {
goal,
by,
line: cite.map(|s| s.line),
column: cite.map(|s| s.column),
clause,
subgoals,
failure: None,
});
}
for (i, detail) in failed {
let mut failure = empty_failure(Reason::NoSolution);
match detail {
FailDetail::Unsatisfiable { conflict, note } => {
failure.reason = Reason::Unsatisfiable;
failure.note = note.clone();
failure.conflict = conflict
.iter()
.filter_map(|o| match *o {
Origin::Record(r) => r.checked_sub(from).map(|k| shallow(&nodes[k])),
Origin::Fact(j) => Some(fact_node(self.program, j)),
})
.collect();
}
FailDetail::NoClause {
clauses,
candidates,
} => {
failure.reason = Reason::NoClause;
failure.clauses = Some(*clauses);
failure.candidates = Some(*candidates);
}
FailDetail::HasSolution(_) => failure.reason = Reason::HasSolution,
FailDetail::NoTableAnswer(n) => {
failure.reason = Reason::NoTableAnswer;
failure.answers = Some(*n);
}
FailDetail::NoSolution => {}
}
nodes[i].failure = Some(Box::new(failure));
}
let mut roots = Vec::new();
let mut slots: Vec<Option<ProofNode>> = nodes.into_iter().map(Some).collect();
for i in (0..arena.len()).rev() {
let node = slots[i].take().expect("each node moves once");
match arena[i].parent {
Some(p) if p >= from => slots[p - from]
.as_mut()
.expect("parents precede children")
.subgoals
.insert(0, node),
_ => roots.push(node),
}
}
roots.reverse();
(answer, roots)
}
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.store, self.names(), &[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();
self.abandon_evaluations();
return None;
}
self.started = true;
if self.run() {
self.record_failures = false;
self.failures.clear();
self.omitted = 0;
Some(self.answer())
} else {
self.exhausted = true;
self.error = self.take_error();
self.abandon_evaluations();
None
}
}
}
fn instantiate_goal(
store: &mut Store,
g: &TGoal,
var: &mut dyn FnMut(&mut Store, u32) -> Addr,
) -> Option<RtGoal> {
Some(match g {
TGoal::Call(t) => RtGoal::Call(build_with(store, t, var)?),
TGoal::Eq(a, b) => {
let a = build_with(store, a, var)?;
let b = build_with(store, b, var)?;
RtGoal::Eq(a, b)
}
TGoal::Dif(a, b) => {
let a = build_with(store, a, var)?;
let b = build_with(store, b, var)?;
RtGoal::Dif(a, b)
}
TGoal::Rel(op, a, b) => {
let a = build_with(store, a, var)?;
let b = build_with(store, b, var)?;
RtGoal::Rel(*op, a, b)
}
TGoal::Or(..) | TGoal::IfThenElse { .. } | TGoal::Not { .. } | TGoal::Collect { .. } => {
unreachable!("control constructs are opened by the machine, not instantiated")
}
})
}
fn enclosing_sub_search(rest: &Goals<'_>) -> Option<String> {
let mut cur = rest.clone();
while let Some(node) = cur {
match &node.goal {
Goal::Commit { text, .. } => return Some(text.to_string()),
Goal::CollectOne { construct, .. } => return Some(construct.to_string()),
_ => {}
}
cur = node.next.clone();
}
None
}
fn first_arg_key(store: &Store, call: Addr) -> Option<ArgKey> {
let Cell::Struct(_, args) = store.cell(store.deref(call)) else {
return None;
};
let a = store.deref(*args.first()?);
match store.cell(a) {
Cell::Const(c) => Some(ArgKey::Const(*c)),
Cell::Num(n) => Some(ArgKey::Num(n.clone())),
Cell::Struct(f, xs) => Some(ArgKey::Struct(*f, xs.len())),
Cell::Var(_) => None,
}
}
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:?}"),
}
}
pub(super) fn run_builtin(store: &mut Store, b: Builtin, call: Addr) -> bool {
let d = store.deref(call);
let Cell::Struct(_, args) = store.cell(d).clone() else {
return false;
};
match b {
Builtin::AtomChars => store.post_atom_chars(args[0], args[1]),
Builtin::Number => store.post_number(args[0]),
Builtin::Atom => store.post_type(args[0], TypeKind::Atom),
Builtin::List => store.post_type(args[0], TypeKind::List),
Builtin::Sum => store.post_sum(args[0], args[1]),
}
}
fn rel_text(op: RelOp) -> &'static str {
match op {
RelOp::Eq => "=",
RelOp::Neq => "!=",
RelOp::Lt => "<",
RelOp::Gt => ">",
RelOp::Le => "<=",
RelOp::Ge => ">=",
}
}
fn constraint_step(goal: &TGoal, frame: &[Addr]) -> Option<Step> {
let (op, a, b) = match goal {
TGoal::Eq(a, b) => (ConstraintOp::Eq, a, b),
TGoal::Dif(a, b) => (ConstraintOp::Dif, a, b),
TGoal::Rel(op, a, b) => (ConstraintOp::Rel(*op), a, b),
_ => return None,
};
Some(Step::Constraint {
op,
a: explain::close(a, frame),
b: explain::close(b, frame),
})
}
fn empty_failure(reason: Reason) -> Failure {
Failure {
reason,
conflict: Vec::new(),
note: None,
clauses: None,
candidates: None,
answers: None,
}
}
fn shallow(n: &ProofNode) -> ProofNode {
ProofNode {
goal: n.goal.clone(),
by: n.by,
line: n.line,
column: n.column,
clause: n.clause.clone(),
subgoals: Vec::new(),
failure: None,
}
}
fn fact_node(program: &Program, j: usize) -> ProofNode {
let f = &program.initial[j];
ProofNode {
goal: f.text.trim_end_matches('.').to_string(),
by: Resolution::Fact,
line: f.span.map(|s| s.line),
column: f.span.map(|s| s.column),
clause: Some(f.text.clone()),
subgoals: Vec::new(),
failure: None,
}
}
fn goal_sides(goal: &TGoal) -> Option<(&'static str, &TTerm, &TTerm)> {
match goal {
TGoal::Eq(a, b) => Some(("=", a, b)),
TGoal::Dif(a, b) => Some(("!=", a, b)),
TGoal::Rel(op, a, b) => Some((rel_text(*op), a, b)),
_ => None,
}
}
fn goal_var_addrs(goal: &TGoal, frame: &[Addr]) -> Vec<(u32, Addr)> {
let Some((_, l, r)) = goal_sides(goal) else {
return Vec::new();
};
let mut vars = std::collections::BTreeSet::new();
term_vars(l, &mut vars);
term_vars(r, &mut vars);
vars.into_iter().map(|i| (i, frame[i as usize])).collect()
}