use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::rc::Rc;
use crate::Span;
use crate::ast::{self, ArithOp, Clause, Expr, Goal, RelOp};
use crate::number::Number;
use super::error::EvalError;
use super::explain::{self, Step};
use super::machine::Solutions;
use super::store::{Addr, Store};
use super::symbol::{LIST_LABEL, Symbol, Symbols};
use super::table::Tables;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum TTerm {
Var(u32),
Number(Number),
Const(Symbol),
Compound(Symbol, Vec<TTerm>),
Neg(Box<TTerm>),
Arith(ArithOp, Box<TTerm>, Box<TTerm>),
Len(Box<TTerm>),
Concat(Box<TTerm>, Box<TTerm>),
}
impl Drop for TTerm {
fn drop(&mut self) {
let mut work: Vec<TTerm> = Vec::new();
self.take_children(&mut work);
while let Some(mut t) = work.pop() {
t.take_children(&mut work);
}
}
}
impl TTerm {
fn take_children(&mut self, out: &mut Vec<TTerm>) {
match self {
TTerm::Var(_) | TTerm::Number(_) | TTerm::Const(_) => {}
TTerm::Compound(_, args) => out.append(args),
TTerm::Neg(x) | TTerm::Len(x) => out.push(std::mem::replace(x, TTerm::Var(0))),
TTerm::Arith(_, l, r) | TTerm::Concat(l, r) => {
out.push(std::mem::replace(l, TTerm::Var(0)));
out.push(std::mem::replace(r, TTerm::Var(0)));
}
}
}
}
fn unbalanced() -> EvalError {
EvalError::InternalInvariant {
what: "term lowering walk out of step".to_string(),
}
}
#[derive(Debug, Clone)]
pub(crate) enum TGoal {
Call(TTerm),
Eq(TTerm, TTerm),
Dif(TTerm, TTerm),
Rel(RelOp, TTerm, TTerm),
Or(Vec<TGoal>, Vec<TGoal>),
IfThenElse {
cond: Vec<TGoal>,
then: Vec<TGoal>,
els: Option<Vec<TGoal>>,
check: CondCheck,
},
Not {
cond: Vec<TGoal>,
check: CondCheck,
},
Collect {
template: TTerm,
body: Vec<TGoal>,
into: u32,
check: CondCheck,
},
}
#[derive(Debug, Clone, Default)]
pub(crate) struct CondCheck {
pub external: Vec<(u32, String)>,
pub construct: String,
pub clause: String,
}
fn has_control(goals: &[TGoal]) -> bool {
goals.iter().any(|g| {
matches!(
g,
TGoal::Or(..) | TGoal::IfThenElse { .. } | TGoal::Not { .. } | TGoal::Collect { .. }
)
})
}
pub(crate) fn term_vars(root: &TTerm, out: &mut BTreeSet<u32>) {
let mut work = vec![root];
while let Some(t) = work.pop() {
match t {
TTerm::Var(i) => {
out.insert(*i);
}
TTerm::Number(_) | TTerm::Const(_) => {}
TTerm::Compound(_, args) => work.extend(args.iter()),
TTerm::Neg(x) | TTerm::Len(x) => work.push(x),
TTerm::Arith(_, l, r) | TTerm::Concat(l, r) => {
work.push(l);
work.push(r);
}
}
}
}
fn goals_vars(goals: &[TGoal], out: &mut BTreeSet<u32>) {
for g in goals {
match g {
TGoal::Call(t) => term_vars(t, out),
TGoal::Eq(a, b) | TGoal::Dif(a, b) | TGoal::Rel(_, a, b) => {
term_vars(a, out);
term_vars(b, out);
}
TGoal::Or(a, b) => {
goals_vars(a, out);
goals_vars(b, out);
}
TGoal::IfThenElse {
cond, then, els, ..
} => {
goals_vars(cond, out);
goals_vars(then, out);
if let Some(e) = els {
goals_vars(e, out);
}
}
TGoal::Not { cond, .. } => goals_vars(cond, out),
TGoal::Collect {
template,
body,
into,
..
} => {
term_vars(template, out);
goals_vars(body, out);
out.insert(*into);
}
}
}
}
fn resolve_conditions(
goals: &mut [TGoal],
outside: &BTreeSet<u32>,
names: &[String],
clause: &str,
) {
let per_goal: Vec<BTreeSet<u32>> = goals
.iter()
.map(|g| {
let mut s = BTreeSet::new();
goals_vars(std::slice::from_ref(g), &mut s);
s
})
.collect();
for (i, g) in goals.iter_mut().enumerate() {
if matches!(
g,
TGoal::Call(_) | TGoal::Eq(..) | TGoal::Dif(..) | TGoal::Rel(..)
) {
continue;
}
let mut around: BTreeSet<u32> = outside.clone();
for (j, s) in per_goal.iter().enumerate() {
if j != i {
around.extend(s.iter().copied());
}
}
match g {
TGoal::Or(a, b) => {
let mut va = BTreeSet::new();
goals_vars(a, &mut va);
let mut vb = BTreeSet::new();
goals_vars(b, &mut vb);
let mut oa = around.clone();
oa.extend(vb.iter().copied());
resolve_conditions(a, &oa, names, clause);
let mut ob = around;
ob.extend(va.iter().copied());
resolve_conditions(b, &ob, names, clause);
}
TGoal::IfThenElse {
cond,
then,
els,
check,
} => {
let mut vc = BTreeSet::new();
goals_vars(cond, &mut vc);
let mut vt = BTreeSet::new();
goals_vars(then, &mut vt);
let mut ve = BTreeSet::new();
if let Some(e) = els {
goals_vars(e, &mut ve);
}
check.external = vc
.iter()
.filter(|v| around.contains(v))
.map(|v| (*v, names[*v as usize].clone()))
.collect();
check.clause = clause.to_string();
let mut oc = around.clone();
oc.extend(vt.iter().copied());
oc.extend(ve.iter().copied());
resolve_conditions(cond, &oc, names, clause);
let mut ot = around.clone();
ot.extend(vc.iter().copied());
ot.extend(ve.iter().copied());
resolve_conditions(then, &ot, names, clause);
if let Some(e) = els {
let mut oe = around;
oe.extend(vc.iter().copied());
oe.extend(vt.iter().copied());
resolve_conditions(e, &oe, names, clause);
}
}
TGoal::Not { cond, check } => {
let mut vc = BTreeSet::new();
goals_vars(cond, &mut vc);
check.external = vc
.iter()
.filter(|v| around.contains(v))
.map(|v| (*v, names[*v as usize].clone()))
.collect();
check.clause = clause.to_string();
resolve_conditions(cond, &around, names, clause);
}
TGoal::Collect {
template,
body,
check,
..
} => {
let mut vt = BTreeSet::new();
term_vars(template, &mut vt);
let mut vb = BTreeSet::new();
goals_vars(body, &mut vb);
check.external = vt
.union(&vb)
.filter(|v| around.contains(v))
.map(|v| (*v, names[*v as usize].clone()))
.collect();
check.clause = clause.to_string();
let mut ob = around;
ob.extend(vt.iter().copied());
resolve_conditions(body, &ob, names, clause);
}
TGoal::Call(_) | TGoal::Eq(..) | TGoal::Dif(..) | TGoal::Rel(..) => {}
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct TClause {
pub head: TTerm,
pub body: Vec<TGoal>,
pub nvars: u32,
pub span: Option<Span>,
pub text: String,
}
#[derive(Debug, Clone)]
pub(crate) struct Fact {
pub goals: Vec<TGoal>,
pub nvars: u32,
pub span: Option<Span>,
pub text: String,
}
pub(crate) type PredKey = (Symbol, usize);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum ArgKey {
Const(Symbol),
Num(Number),
Struct(Symbol, usize),
}
#[derive(Debug, Clone, Default)]
pub(crate) struct Pred {
pub clauses: Vec<TClause>,
all: Vec<usize>,
by_key: HashMap<ArgKey, Vec<usize>>,
var_only: Vec<usize>,
}
impl Pred {
fn push(&mut self, clause: TClause) {
let pos = self.clauses.len();
let key = match &clause.head {
TTerm::Compound(_, args) => args.first().and_then(arg_key),
_ => None,
};
self.all.push(pos);
match key {
Some(k) => self.by_key.entry(k).or_default().push(pos),
None => {
self.var_only.push(pos);
for list in self.by_key.values_mut() {
list.push(pos);
}
}
}
self.clauses.push(clause);
}
fn finish(&mut self) {
for list in self.by_key.values_mut() {
list.extend(self.var_only.iter().copied());
list.sort_unstable();
list.dedup();
}
}
fn candidates(&self, key: Option<&ArgKey>) -> &[usize] {
match key {
None => &self.all,
Some(k) => self
.by_key
.get(k)
.map(Vec::as_slice)
.unwrap_or(&self.var_only),
}
}
}
fn arg_key(t: &TTerm) -> Option<ArgKey> {
match t {
TTerm::Const(c) => Some(ArgKey::Const(*c)),
TTerm::Number(n) => Some(ArgKey::Num(n.clone())),
TTerm::Compound(f, args) => Some(ArgKey::Struct(*f, args.len())),
TTerm::Var(_) | TTerm::Neg(_) | TTerm::Arith(..) | TTerm::Len(_) | TTerm::Concat(..) => {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Builtin {
AtomChars,
Number,
Atom,
List,
Sum,
}
const BUILTINS: [(&str, usize, Builtin); 5] = [
("atom_chars", 2, Builtin::AtomChars),
("number", 1, Builtin::Number),
("atom", 1, Builtin::Atom),
("list", 1, Builtin::List),
("sum", 2, Builtin::Sum),
];
#[derive(Debug, Clone)]
pub struct Query {
pub(crate) goals: Vec<TGoal>,
pub(crate) var_names: Vec<String>,
text: String,
}
impl Query {
pub fn text(&self) -> &str {
&self.text
}
}
#[derive(Debug, Clone)]
pub struct Program {
pub(crate) symbols: Rc<RefCell<Symbols>>,
preds: HashMap<PredKey, Pred>,
pub(crate) builtins: HashMap<PredKey, Builtin>,
pub(crate) initial: Vec<Fact>,
queries: Vec<Query>,
pub(crate) tabled: HashSet<PredKey>,
pub(crate) world: Store,
pub(crate) tables: RefCell<Tables>,
}
#[derive(Default)]
struct VarMap {
names: Vec<String>,
index: HashMap<String, u32>,
}
pub(crate) const ANONYMOUS: &str = "_";
impl VarMap {
fn fresh(&mut self) -> u32 {
self.get(ANONYMOUS)
}
fn get(&mut self, name: &str) -> u32 {
if name != ANONYMOUS {
if let Some(&i) = self.index.get(name) {
return i;
}
}
let i = self.names.len() as u32;
self.names.push(name.to_string());
if name != ANONYMOUS {
self.index.insert(name.to_string(), i);
}
i
}
}
struct Lowerer<'a> {
symbols: &'a mut Symbols,
vars: VarMap,
collects: Vec<TGoal>,
}
enum Walk<'a, N> {
Enter(&'a N),
Build(&'a N),
}
fn take_last<T>(out: &mut Vec<T>, k: usize) -> Vec<T> {
out.split_off(out.len() - k)
}
fn take_two<T>(out: &mut Vec<T>) -> Option<(T, T)> {
let r = out.pop()?;
let l = out.pop()?;
Some((l, r))
}
impl Lowerer<'_> {
fn term(&mut self, root: &Expr) -> Result<TTerm, EvalError> {
let mut work = vec![Walk::Enter(root)];
let mut out: Vec<TTerm> = Vec::new();
while let Some(step) = work.pop() {
match step {
Walk::Enter(e) => {
work.push(Walk::Build(e));
match e {
Expr::Var(_) | Expr::Number(_) | Expr::Ident(_) => {}
Expr::Atom(a) => work.extend(a.args.iter().rev().map(Walk::Enter)),
Expr::Neg(x) | Expr::Len(x) => work.push(Walk::Enter(x)),
Expr::Binary { left, right, .. } | Expr::Concat(left, right) => {
work.push(Walk::Enter(right));
work.push(Walk::Enter(left));
}
Expr::List { items, tail } => {
work.extend(tail.iter().map(|t| Walk::Enter(t.as_ref())));
work.extend(items.iter().rev().map(Walk::Enter));
}
Expr::Comprehension { .. } => {}
}
}
Walk::Build(e) => {
let built = match e {
Expr::Var(v) => TTerm::Var(self.vars.get(v)),
Expr::Number(n) => TTerm::Number(n.clone()),
Expr::Ident(i) => TTerm::Const(self.symbols.intern(i)),
Expr::Atom(a) => {
let args = take_last(&mut out, a.args.len());
TTerm::Compound(self.symbols.intern(&a.name), args)
}
Expr::Neg(_) => TTerm::Neg(Box::new(out.pop().ok_or_else(unbalanced)?)),
Expr::Len(_) => TTerm::Len(Box::new(out.pop().ok_or_else(unbalanced)?)),
Expr::Binary { op, .. } => {
let (l, r) = take_two(&mut out).ok_or_else(unbalanced)?;
TTerm::Arith(*op, Box::new(l), Box::new(r))
}
Expr::Concat(..) => {
let (l, r) = take_two(&mut out).ok_or_else(unbalanced)?;
TTerm::Concat(Box::new(l), Box::new(r))
}
Expr::Comprehension { template, body } => {
let outer = std::mem::take(&mut self.collects);
let template = self.term(template)?;
let in_template = std::mem::take(&mut self.collects);
self.collects = outer;
let mut body = self.body(body)?;
body.extend(in_template);
let into = self.vars.fresh();
self.collects.push(TGoal::Collect {
template,
body,
into,
check: CondCheck {
construct: e.to_string(),
..CondCheck::default()
},
});
TTerm::Var(into)
}
Expr::List { items, tail } => {
let nil = self.symbols.intern(LIST_LABEL);
let mut t = match tail {
Some(_) => out.pop().ok_or_else(unbalanced)?,
None => TTerm::Const(nil),
};
for h in take_last(&mut out, items.len()).into_iter().rev() {
t = TTerm::Compound(nil, vec![h, t]);
}
t
}
};
out.push(built);
}
}
}
out.pop().ok_or_else(unbalanced)
}
fn atom(&mut self, a: &ast::Atom) -> Result<TTerm, EvalError> {
let f = self.symbols.intern(&a.name);
let mut args = Vec::with_capacity(a.args.len());
for e in &a.args {
args.push(self.term(e)?);
}
Ok(TTerm::Compound(f, args))
}
fn constraint(&mut self, c: &ast::Constraint) -> Result<TGoal, EvalError> {
let l = self.term(&c.left)?;
let r = self.term(&c.right)?;
Ok(match c.op {
RelOp::Eq => TGoal::Eq(l, r),
RelOp::Neq => TGoal::Dif(l, r),
op => TGoal::Rel(op, l, r),
})
}
fn body(&mut self, goals: &[Goal]) -> Result<Vec<TGoal>, EvalError> {
let saved = std::mem::take(&mut self.collects);
let result = self.body_goals(goals);
self.collects = saved;
result
}
fn body_goals(&mut self, goals: &[Goal]) -> Result<Vec<TGoal>, EvalError> {
let mut out = Vec::new();
for g in goals {
match g {
Goal::Atom(a) => {
let call = TGoal::Call(self.atom(a)?);
out.append(&mut self.collects);
out.push(call);
}
Goal::Constraint(ce) => {
for c in &ce.terms {
let goal = self.constraint(c)?;
out.append(&mut self.collects);
out.push(goal);
}
}
Goal::Or(a, b) => out.push(TGoal::Or(self.body(a)?, self.body(b)?)),
Goal::IfThenElse { cond, then, els } => out.push(TGoal::IfThenElse {
cond: self.body(cond)?,
then: self.body(then)?,
els: match els {
Some(e) => Some(self.body(e)?),
None => None,
},
check: CondCheck {
construct: g.to_string(),
..CondCheck::default()
},
}),
Goal::Not(inner) => out.push(TGoal::Not {
cond: self.body(inner)?,
check: CondCheck {
construct: g.to_string(),
..CondCheck::default()
},
}),
}
}
Ok(out)
}
fn no_collects(&self, span: Option<Span>) -> Result<(), EvalError> {
if self.collects.is_empty() {
Ok(())
} else {
Err(EvalError::ComprehensionInHead { span })
}
}
fn finish_body(&self, body: &mut [TGoal], head: Option<&TTerm>, clause: &Clause) {
if !has_control(body) {
return;
}
let mut outside = BTreeSet::new();
if let Some(h) = head {
term_vars(h, &mut outside);
}
let text = clause.to_string();
resolve_conditions(body, &outside, &self.vars.names, &text);
}
}
impl Program {
pub fn compile(clauses: &[Clause]) -> Result<Program, EvalError> {
Self::compile_iter(clauses.iter().map(|c| (c, None)))
}
pub fn compile_spanned(clauses: &[(Clause, Span)]) -> Result<Program, EvalError> {
Self::compile_iter(clauses.iter().map(|(c, s)| (c, Some(*s))))
}
fn compile_iter<'a>(
clauses: impl Iterator<Item = (&'a Clause, Option<Span>)>,
) -> Result<Program, EvalError> {
let mut symbols = Symbols::default();
let builtins: HashMap<PredKey, Builtin> = BUILTINS
.iter()
.map(|(name, arity, b)| ((symbols.intern(name), *arity), *b))
.collect();
let mut preds: HashMap<PredKey, Pred> = HashMap::new();
let mut initial = Vec::new();
let mut queries = Vec::new();
let mut tabled: HashSet<PredKey> = HashSet::new();
for (clause, span) in clauses {
if let Clause::Fact(head)
| Clause::Rule { head, .. }
| Clause::ConstraintRule { head, .. }
| Clause::Implication { head, .. } = clause
{
if head.name == LIST_LABEL {
return Err(EvalError::ReservedName {
name: head.name.clone(),
span,
});
}
if BUILTINS
.iter()
.any(|(n, a, _)| *n == head.name && *a == head.args.len())
{
return Err(EvalError::ReservedBuiltin {
name: head.name.clone(),
arity: head.args.len(),
span,
});
}
}
let mut lw = Lowerer {
symbols: &mut symbols,
vars: VarMap::default(),
collects: Vec::new(),
};
match clause {
Clause::Fact(atom) => {
let head = lw.atom(atom)?;
lw.no_collects(span)?;
let nvars = lw.vars.names.len() as u32;
push_clause(
&mut preds,
TClause {
head,
body: vec![],
nvars,
span,
text: clause.to_string(),
},
);
}
Clause::Rule { head, body } | Clause::ConstraintRule { head, body } => {
let head = lw.atom(head)?;
lw.no_collects(span)?;
let mut body = lw.body(body)?;
lw.finish_body(&mut body, Some(&head), clause);
let nvars = lw.vars.names.len() as u32;
push_clause(
&mut preds,
TClause {
head,
body,
nvars,
span,
text: clause.to_string(),
},
);
}
Clause::Implication { constraint, head } => {
let head = lw.atom(head)?;
lw.no_collects(span)?;
let as_body = vec![Goal::Constraint(constraint.clone())];
let mut body = lw.body(&as_body)?;
lw.finish_body(&mut body, Some(&head), clause);
let nvars = lw.vars.names.len() as u32;
push_clause(
&mut preds,
TClause {
head,
body,
nvars,
span,
text: clause.to_string(),
},
);
}
Clause::ConstraintFact(ce) => {
let goals = ce
.terms
.iter()
.map(|c| lw.constraint(c))
.collect::<Result<Vec<_>, _>>()?;
lw.no_collects(span)?;
initial.push(Fact {
goals,
nvars: lw.vars.names.len() as u32,
span,
text: clause.to_string(),
});
}
Clause::Query(body) => {
let mut goals = lw.body(body)?;
lw.finish_body(&mut goals, None, clause);
queries.push(Query {
goals,
var_names: lw.vars.names.clone(),
text: clause.to_string(),
});
}
Clause::Directive { name, indicators } => {
let bad = |message: String| EvalError::BadDirective { message, span };
if name != "table" {
return Err(bad(format!("unknown directive `{name}`")));
}
for ind in indicators {
let arity = if ind.arity.is_integer() && !ind.arity.is_negative() {
ind.arity.numer().to_string().parse::<usize>().ok()
} else {
None
};
let Some(arity) = arity else {
return Err(bad(format!(
"the arity of `{ind}` must be a non-negative integer"
)));
};
if ind.name == LIST_LABEL
|| BUILTINS
.iter()
.any(|(n, a, _)| *n == ind.name && *a == arity)
{
return Err(bad(format!("`{ind}` is a built-in and cannot be tabled")));
}
tabled.insert((lw.symbols.intern(&ind.name), arity));
}
}
}
}
for pred in preds.values_mut() {
pred.finish();
}
let mut program = Program {
symbols: Rc::new(RefCell::new(symbols)),
preds,
builtins,
initial,
queries,
tabled,
world: Store::default(),
tables: RefCell::new(Tables::default()),
};
let mut store = Store::with_symbols(Rc::clone(&program.symbols));
if let Some(k) = program.post_initial(&mut store) {
let candidates: Vec<Step> = (0..k).map(Step::Fact).collect();
let conflict = match explain::minimal_conflict(&program, &candidates, &Step::Fact(k)) {
Ok(mut set) => {
set.push(k);
set.iter()
.map(|&i| {
let f = &program.initial[i];
(f.span, f.text.clone())
})
.collect()
}
Err(_) => Vec::new(),
};
return Err(EvalError::InitialStoreUnsatisfiable { conflict });
}
store.set_baseline();
program.world = store;
Ok(program)
}
pub fn queries(&self) -> &[Query] {
&self.queries
}
pub fn solve<'p>(&'p self, query: &'p Query) -> Solutions<'p> {
Solutions::new(self, query)
}
pub(crate) fn clauses(&self, key: &PredKey) -> &[TClause] {
self.preds
.get(key)
.map(|p| p.clauses.as_slice())
.unwrap_or(&[])
}
pub(crate) fn candidates(&self, key: &PredKey, first: Option<&ArgKey>) -> &[usize] {
self.preds
.get(key)
.map(|p| p.candidates(first))
.unwrap_or(&[])
}
pub(crate) fn post_initial(&self, store: &mut Store) -> Option<usize> {
(0..self.initial.len()).find(|&i| !self.post_fact(store, i))
}
pub(crate) fn post_fact(&self, store: &mut Store, idx: usize) -> bool {
let fact = &self.initial[idx];
let mut vars = vec![None; fact.nvars as usize];
for g in &fact.goals {
let ok = match g {
TGoal::Eq(a, b) => match (build(store, a, &mut vars), build(store, b, &mut vars)) {
(Some(a), Some(b)) => store.post_eq_goal(a, b),
_ => false,
},
TGoal::Dif(a, b) => {
match (build(store, a, &mut vars), build(store, b, &mut vars)) {
(Some(a), Some(b)) => store.post_dif_goal(a, b),
_ => false,
}
}
TGoal::Rel(op, a, b) => {
match (build(store, a, &mut vars), build(store, b, &mut vars)) {
(Some(a), Some(b)) => store.post_rel(*op, a, b),
_ => false,
}
}
TGoal::Call(_)
| TGoal::Or(..)
| TGoal::IfThenElse { .. }
| TGoal::Not { .. }
| TGoal::Collect { .. } => {
unreachable!("constraint facts hold constraints only")
}
};
if !ok {
return false;
}
}
true
}
}
fn push_clause(preds: &mut HashMap<PredKey, Pred>, clause: TClause) {
let key = match &clause.head {
TTerm::Compound(f, args) => (*f, args.len()),
TTerm::Const(c) => (*c, 0),
_ => unreachable!("clause heads are atoms"),
};
preds.entry(key).or_default().push(clause);
}
pub(crate) fn build_with(
store: &mut Store,
root: &TTerm,
var: &mut dyn FnMut(&mut Store, u32) -> Addr,
) -> Option<Addr> {
let mut work = vec![Walk::Enter(root)];
let mut out: Vec<Addr> = 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) => var(store, *i),
TTerm::Number(n) => store.new_num(n.clone()),
TTerm::Const(c) => store.new_const(*c),
TTerm::Compound(f, args) => {
let addrs = take_last(&mut out, args.len());
store.new_struct(*f, addrs)
}
TTerm::Neg(_) => store.post_neg(out.pop()?)?,
TTerm::Len(_) => store.post_len(out.pop()?)?,
TTerm::Arith(op, ..) => {
let (a, b) = take_two(&mut out)?;
store.post_arith(*op, a, b)?
}
TTerm::Concat(..) => {
let (x, y) = take_two(&mut out)?;
store.post_concat(x, y)?
}
};
out.push(built);
}
}
}
out.pop()
}
pub(crate) fn lazy_var(store: &mut Store, vars: &mut [Option<Addr>], i: u32) -> Addr {
let slot = &mut vars[i as usize];
match slot {
Some(a) => *a,
None => {
let a = store.new_var();
*slot = Some(a);
a
}
}
}
pub(crate) fn build(store: &mut Store, t: &TTerm, vars: &mut [Option<Addr>]) -> Option<Addr> {
build_with(store, t, &mut |store, i| lazy_var(store, vars, i))
}