use std::collections::HashMap;
use std::rc::Rc;
use super::answer::render_terms;
use super::compile::{Builtin, Program, TTerm, build_with};
use super::error::EvalError;
use super::machine::run_builtin;
use super::store::{Addr, Store};
use crate::ast::RelOp;
#[derive(Debug, Clone)]
pub(crate) enum Step {
Head { head: TTerm, call: TTerm },
Constraint {
op: ConstraintOp,
a: TTerm,
b: TTerm,
},
Builtin { builtin: Builtin, call: TTerm },
Tabled { call: TTerm, answer: TTerm },
Collected { into: TTerm, list: TTerm },
Fact(usize),
Answer,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ConstraintOp {
Eq,
Dif,
Rel(RelOp),
}
pub(crate) fn var_id(a: Addr) -> u32 {
u32::try_from(a).unwrap_or(u32::MAX)
}
pub(crate) fn close(tpl: &TTerm, frame: &[Addr]) -> TTerm {
enum Walk<'a> {
Enter(&'a TTerm),
Build(&'a TTerm),
}
let mut work = vec![Walk::Enter(tpl)];
let mut out: Vec<TTerm> = Vec::new();
while let Some(step) = work.pop() {
match step {
Walk::Enter(t) => {
work.push(Walk::Build(t));
match t {
TTerm::Var(_) | TTerm::Number(_) | TTerm::Const(_) => {}
TTerm::Compound(_, args) => work.extend(args.iter().rev().map(Walk::Enter)),
TTerm::Neg(x) | TTerm::Len(x) => work.push(Walk::Enter(x)),
TTerm::Arith(_, l, r) | TTerm::Concat(l, r) => {
work.push(Walk::Enter(r));
work.push(Walk::Enter(l));
}
}
}
Walk::Build(t) => {
let built = match t {
TTerm::Var(i) => TTerm::Var(var_id(frame[*i as usize])),
TTerm::Number(n) => TTerm::Number(n.clone()),
TTerm::Const(c) => TTerm::Const(*c),
TTerm::Compound(f, args) => {
let kids = out.split_off(out.len() - args.len());
TTerm::Compound(*f, kids)
}
TTerm::Neg(_) => TTerm::Neg(Box::new(out.pop().unwrap_or(TTerm::Var(0)))),
TTerm::Len(_) => TTerm::Len(Box::new(out.pop().unwrap_or(TTerm::Var(0)))),
TTerm::Arith(op, ..) => {
let r = out.pop().unwrap_or(TTerm::Var(0));
let l = out.pop().unwrap_or(TTerm::Var(0));
TTerm::Arith(*op, Box::new(l), Box::new(r))
}
TTerm::Concat(..) => {
let r = out.pop().unwrap_or(TTerm::Var(0));
let l = out.pop().unwrap_or(TTerm::Var(0));
TTerm::Concat(Box::new(l), Box::new(r))
}
};
out.push(built);
}
}
}
out.pop().unwrap_or(TTerm::Var(0))
}
struct Replay {
store: Store,
vars: HashMap<u32, Addr>,
}
impl Replay {
fn new(program: &Program) -> Self {
Replay {
store: Store::with_symbols(Rc::clone(&program.symbols)),
vars: HashMap::new(),
}
}
fn build(&mut self, t: &TTerm) -> Option<Addr> {
let vars = &mut self.vars;
build_with(&mut self.store, t, &mut |s, i| match vars.get(&i) {
Some(a) => *a,
None => {
let a = s.new_var();
vars.insert(i, a);
a
}
})
}
fn post(&mut self, program: &Program, step: &Step) -> Result<bool, EvalError> {
let ok = match step {
Step::Head { head, call, .. } => match (self.build(head), self.build(call)) {
(Some(h), Some(c)) => self.store.post_eq(h, c),
_ => false,
},
Step::Constraint { op, a, b } => match (self.build(a), self.build(b)) {
(Some(a), Some(b)) => match op {
ConstraintOp::Eq => self.store.post_eq_goal(a, b),
ConstraintOp::Dif => self.store.post_dif_goal(a, b),
ConstraintOp::Rel(op) => self.store.post_rel(*op, a, b),
},
_ => false,
},
Step::Builtin { builtin, call } => match self.build(call) {
Some(c) => run_builtin(&mut self.store, *builtin, c),
None => false,
},
Step::Tabled { call, answer } => match (self.build(call), self.build(answer)) {
(Some(c), Some(a)) => self.store.post_eq(c, a),
_ => false,
},
Step::Collected { into, list } => match (self.build(into), self.build(list)) {
(Some(v), Some(l)) => self.store.post_eq(v, l),
_ => false,
},
Step::Fact(idx) => program.post_fact(&mut self.store, *idx),
Step::Answer => self.store.finalize(),
};
self.check_error()?;
Ok(ok)
}
fn check_error(&mut self) -> Result<(), EvalError> {
if let Some(e) = self.store.error.take() {
return Err(e);
}
if let Some((a, b, op)) = self.store.nonlinear.take() {
let rendered = render_terms(&self.store, &[], &[a, b]);
return Err(EvalError::NonLinear {
constraint: format!("{} {op} {}", rendered[0], rendered[1]),
});
}
Ok(())
}
}
pub(crate) fn satisfiable(program: &Program, steps: &[&Step]) -> Result<bool, EvalError> {
let mut replay = Replay::new(program);
for step in steps {
if !replay.post(program, step)? {
return Ok(false);
}
}
let ok = replay.store.finalize();
replay.check_error()?;
Ok(ok)
}
pub(crate) fn minimal_conflict(
program: &Program,
candidates: &[Step],
failing: &Step,
) -> Result<Vec<usize>, EvalError> {
let mut qx = QuickXplain {
program,
candidates,
failing,
};
let all: Vec<usize> = (0..candidates.len()).collect();
let mut found = qx.explain(&[], true, &all)?;
found.sort_unstable();
Ok(found)
}
struct QuickXplain<'a> {
program: &'a Program,
candidates: &'a [Step],
failing: &'a Step,
}
impl QuickXplain<'_> {
fn satisfiable(&self, background: &[usize]) -> Result<bool, EvalError> {
let mut idx: Vec<usize> = background.to_vec();
idx.sort_unstable();
let mut steps: Vec<&Step> = idx.iter().map(|&i| &self.candidates[i]).collect();
steps.push(self.failing);
satisfiable(self.program, &steps)
}
fn explain(
&mut self,
background: &[usize],
check: bool,
c: &[usize],
) -> Result<Vec<usize>, EvalError> {
if check && !self.satisfiable(background)? {
return Ok(Vec::new());
}
if c.len() <= 1 {
return Ok(c.to_vec());
}
let (c1, c2) = c.split_at(c.len() / 2);
let mut b1 = background.to_vec();
b1.extend_from_slice(c1);
let d2 = self.explain(&b1, !c1.is_empty(), c2)?;
let mut b2 = background.to_vec();
b2.extend_from_slice(&d2);
let mut d1 = self.explain(&b2, !d2.is_empty(), c1)?;
d1.extend(d2);
Ok(d1)
}
}