add tests, update status.

This commit is contained in:
Mark Thom
2017-08-14 13:21:45 -06:00
parent a3fbf1bc73
commit 8d7d215234
9 changed files with 142 additions and 69 deletions

View File

@@ -16,7 +16,7 @@ argument indexing, and conjunctive queries.
Extend rusty-wam to include the following, among other features: Extend rusty-wam to include the following, among other features:
* call/N as a built-in meta-predicate (_done_). * call/N as a built-in meta-predicate (_done_).
* ISO Prolog compliant throw/catch (_done_). * ISO Prolog compliant throw/catch (_in progress_).
* Built-in and user-defined operators of all fixities, * Built-in and user-defined operators of all fixities,
with custom associativity and precedence. with custom associativity and precedence.
* Bignum and floating point arithmetic. * Bignum and floating point arithmetic.

View File

@@ -698,8 +698,32 @@ mod tests {
assert_eq!(submit(&mut wam, "?- catch(f(z), x, handle(y))."), true); assert_eq!(submit(&mut wam, "?- catch(f(z), x, handle(y))."), true);
assert_eq!(submit(&mut wam, "?- catch(f(z), x, handle(z))."), false); assert_eq!(submit(&mut wam, "?- catch(f(z), x, handle(z))."), false);
//TODO: write more tests: multi-layered throw/catch, catch submit(&mut wam, "f(X) :- throw(stuff). f(X) :- throw(other_stuff).");
// within catch, throw within catch, etc. submit(&mut wam, "handle(stuff). handle(other_stuff).");
// this should deterministically succeed with Exception = stuff.
assert_eq!(submit(&mut wam, "?- catch(f(X), Exception, handle(Exception))."), true);
assert_eq!(submit(&mut wam, "?- catch(f(X), Exception, handle(stuff))."), true);
assert_eq!(submit(&mut wam, "?- catch(f(X), Exception, handle(other_stuff))."), true);
assert_eq!(submit(&mut wam, "?- catch(f(X), Exception, handle(not_stuff))."), false);
submit(&mut wam, "f(success). f(X) :- catch(g(X), E, handle(E)).");
submit(&mut wam, "g(g_success). g(g_success_2). g(X) :- throw(X).");
submit(&mut wam, "handle(x). handle(y). handle(z).");
assert_eq!(submit(&mut wam, "?- catch(f(X), E, E)."), true);
assert_eq!(submit(&mut wam, "?- catch(f(fail), _, _)."), false);
assert_eq!(submit(&mut wam, "?- catch(f(x), _, _)."), true);
assert_eq!(submit(&mut wam, "?- catch(f(y), _, _)."), true);
assert_eq!(submit(&mut wam, "?- catch(f(z), _, _)."), true);
submit(&mut wam, "f(success). f(E) :- catch(g(E), E, handle(E)).");
submit(&mut wam, "g(g_success). g(g_success_2). g(X) :- throw(X).");
submit(&mut wam, "handle(x). handle(y). handle(z). handle(v) :- throw(X).");
//TODO: fix this test. record the ball properly. currently it
// is unwound when the heap is truncated.
assert_eq!(submit(&mut wam, "?- catch(f(X), E, E)."), true);
} }
} }

View File

@@ -159,8 +159,10 @@ pub enum Term {
pub enum QueryTerm { pub enum QueryTerm {
CallN(Vec<Box<Term>>), CallN(Vec<Box<Term>>),
Catch(Vec<Box<Term>>),
Cut, Cut,
Term(Term) Term(Term),
Throw(Vec<Box<Term>>)
} }
impl QueryTerm { impl QueryTerm {
@@ -168,10 +170,14 @@ impl QueryTerm {
match self { match self {
&QueryTerm::CallN(ref terms) => &QueryTerm::CallN(ref terms) =>
QueryTermRef::CallN(terms), QueryTermRef::CallN(terms),
&QueryTerm::Catch(ref terms) =>
QueryTermRef::Catch(terms),
&QueryTerm::Cut => &QueryTerm::Cut =>
QueryTermRef::Cut, QueryTermRef::Cut,
&QueryTerm::Term(ref term) => &QueryTerm::Term(ref term) =>
QueryTermRef::Term(term), QueryTermRef::Term(term),
&QueryTerm::Throw(ref t) =>
QueryTermRef::Throw(t)
} }
} }
} }
@@ -184,14 +190,16 @@ pub struct Rule {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum ClauseType<'a> { pub enum ClauseType<'a> {
CallN, CallN,
Catch,
Deep(Level, &'a Cell<RegType>, &'a Atom), Deep(Level, &'a Cell<RegType>, &'a Atom),
Root Root,
Throw
} }
impl<'a> ClauseType<'a> { impl<'a> ClauseType<'a> {
pub fn level_of_subterms(self) -> Level { pub fn level_of_subterms(self) -> Level {
match self { match self {
ClauseType::CallN => Level::Shallow, ClauseType::CallN | ClauseType::Catch | ClauseType::Throw => Level::Shallow,
ClauseType::Deep(_, _, _) => Level::Deep, ClauseType::Deep(_, _, _) => Level::Deep,
ClauseType::Root => Level::Shallow ClauseType::Root => Level::Shallow
} }
@@ -216,7 +224,9 @@ impl<'a> TermRef<'a> {
| TermRef::Var(lvl, _, _) => lvl, | TermRef::Var(lvl, _, _) => lvl,
TermRef::Clause(ClauseType::Root, _) => Level::Shallow, TermRef::Clause(ClauseType::Root, _) => Level::Shallow,
TermRef::Clause(ClauseType::Deep(lvl, _, _), _) => lvl, TermRef::Clause(ClauseType::Deep(lvl, _, _), _) => lvl,
TermRef::Clause(ClauseType::CallN, _) => Level::Shallow TermRef::Clause(ClauseType::CallN, _) => Level::Shallow,
TermRef::Clause(ClauseType::Throw, _) => Level::Shallow,
TermRef::Clause(ClauseType::Catch, _) => Level::Shallow
} }
} }
} }
@@ -224,13 +234,17 @@ impl<'a> TermRef<'a> {
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum QueryTermRef<'a> { pub enum QueryTermRef<'a> {
CallN(&'a Vec<Box<Term>>), CallN(&'a Vec<Box<Term>>),
Catch(&'a Vec<Box<Term>>),
Cut, Cut,
Term(&'a Term), Term(&'a Term),
Throw(&'a Vec<Box<Term>>)
} }
impl<'a> QueryTermRef<'a> { impl<'a> QueryTermRef<'a> {
pub fn arity(self) -> usize { pub fn arity(self) -> usize {
match self { match self {
QueryTermRef::Catch(_) => 3,
QueryTermRef::Throw(_) => 1,
QueryTermRef::CallN(terms) => terms.len(), QueryTermRef::CallN(terms) => terms.len(),
QueryTermRef::Cut => 0, QueryTermRef::Cut => 0,
QueryTermRef::Term(term) => term.arity(), QueryTermRef::Term(term) => term.arity(),
@@ -297,19 +311,23 @@ pub enum ControlInstruction {
Allocate(usize), Allocate(usize),
Call(Atom, usize, usize), Call(Atom, usize, usize),
CallN(usize), CallN(usize),
Catch,
Deallocate, Deallocate,
Execute(Atom, usize), Execute(Atom, usize),
ExecuteN(usize), ExecuteN(usize),
Proceed Proceed,
Throw
} }
impl ControlInstruction { impl ControlInstruction {
pub fn is_jump_instr(&self) -> bool { pub fn is_jump_instr(&self) -> bool {
match self { match self {
&ControlInstruction::Call(_, _, _) => true, &ControlInstruction::Call(_, _, _) => true,
&ControlInstruction::Catch => true,
&ControlInstruction::Execute(_, _) => true, &ControlInstruction::Execute(_, _) => true,
&ControlInstruction::CallN(_) => true, &ControlInstruction::CallN(_) => true,
&ControlInstruction::ExecuteN(_) => true, &ControlInstruction::ExecuteN(_) => true,
&ControlInstruction::Throw => true,
_ => false _ => false
} }
} }

View File

@@ -24,20 +24,18 @@ pub enum EvalSession<'a> {
pub struct ConjunctInfo<'a> { pub struct ConjunctInfo<'a> {
pub perm_vs: VariableFixtures<'a>, pub perm_vs: VariableFixtures<'a>,
pub num_of_chunks: usize, pub num_of_chunks: usize,
pub has_deep_cut: bool, pub has_deep_cut: bool
pub has_catch: bool
} }
impl<'a> ConjunctInfo<'a> impl<'a> ConjunctInfo<'a>
{ {
fn new(perm_vs: VariableFixtures<'a>, num_of_chunks: usize, has_deep_cut: bool, has_catch: bool) fn new(perm_vs: VariableFixtures<'a>, num_of_chunks: usize, has_deep_cut: bool) -> Self
-> Self
{ {
ConjunctInfo { perm_vs, num_of_chunks, has_deep_cut, has_catch } ConjunctInfo { perm_vs, num_of_chunks, has_deep_cut }
} }
fn allocates(&self) -> bool { fn allocates(&self) -> bool {
self.perm_vs.size() > 0 || self.num_of_chunks > 1 || self.has_deep_cut || self.has_catch self.perm_vs.size() > 0 || self.num_of_chunks > 1 || self.has_deep_cut
} }
fn perm_vars(&self) -> usize { fn perm_vars(&self) -> usize {
@@ -45,7 +43,7 @@ impl<'a> ConjunctInfo<'a>
} }
fn perm_var_offset(&self) -> usize { fn perm_var_offset(&self) -> usize {
self.has_deep_cut as usize + 2 * (self.has_catch as usize) self.has_deep_cut as usize
} }
} }
@@ -197,14 +195,13 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator<'a, TermMarker>
let num_of_chunks = iter.chunk_num(); let num_of_chunks = iter.chunk_num();
let has_deep_cut = iter.encountered_deep_cut(); let has_deep_cut = iter.encountered_deep_cut();
let has_catch = iter.encountered_catch();
vs.populate_restricting_sets(); vs.populate_restricting_sets();
vs.set_perm_vals(has_deep_cut, has_catch); vs.set_perm_vals(has_deep_cut);
let vs = self.marker.drain_var_data(vs); let vs = self.marker.drain_var_data(vs);
ConjunctInfo::new(vs, num_of_chunks, has_deep_cut, has_catch) ConjunctInfo::new(vs, num_of_chunks, has_deep_cut)
} }
fn add_conditional_call(compiled_query: &mut Code, qt: QueryTermRef, pvs: usize) fn add_conditional_call(compiled_query: &mut Code, qt: QueryTermRef, pvs: usize)
@@ -214,6 +211,8 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator<'a, TermMarker>
let call = ControlInstruction::CallN(terms.len()); let call = ControlInstruction::CallN(terms.len());
compiled_query.push(Line::Control(call)); compiled_query.push(Line::Control(call));
}, },
QueryTermRef::Catch(_) =>
compiled_query.push(Line::Control(ControlInstruction::Catch)),
QueryTermRef::Term(&Term::Constant(_, Constant::Atom(ref atom))) => { QueryTermRef::Term(&Term::Constant(_, Constant::Atom(ref atom))) => {
let call = ControlInstruction::Call(atom.clone(), 0, pvs); let call = ControlInstruction::Call(atom.clone(), 0, pvs);
compiled_query.push(Line::Control(call)); compiled_query.push(Line::Control(call));
@@ -222,6 +221,8 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator<'a, TermMarker>
let call = ControlInstruction::Call(atom.clone(), terms.len(), pvs); let call = ControlInstruction::Call(atom.clone(), terms.len(), pvs);
compiled_query.push(Line::Control(call)); compiled_query.push(Line::Control(call));
}, },
QueryTermRef::Throw(_) =>
compiled_query.push(Line::Control(ControlInstruction::Throw)),
_ => {} _ => {}
} }
} }

View File

@@ -218,7 +218,7 @@ impl<'a> VariableFixtures<'a>
self.0.len() self.0.len()
} }
pub fn set_perm_vals(&self, has_deep_cuts: bool, has_catch: bool) pub fn set_perm_vals(&self, has_deep_cuts: bool)
{ {
let mut values_vec : Vec<_> = self.values() let mut values_vec : Vec<_> = self.values()
.filter_map(|ref v| { .filter_map(|ref v| {
@@ -231,7 +231,7 @@ impl<'a> VariableFixtures<'a>
values_vec.sort_by_key(|ref v| v.0); values_vec.sort_by_key(|ref v| v.0);
let offset = has_deep_cuts as usize + 2 * has_catch as usize; let offset = has_deep_cuts as usize;
for (i, (_, cells)) in values_vec.into_iter().rev().enumerate() { for (i, (_, cells)) in values_vec.into_iter().rev().enumerate() {
for cell in cells { for cell in cells {

View File

@@ -104,6 +104,8 @@ impl fmt::Display for ControlInstruction {
write!(f, "call {}/{}, {}", name, arity, pvs), write!(f, "call {}/{}, {}", name, arity, pvs),
&ControlInstruction::CallN(arity) => &ControlInstruction::CallN(arity) =>
write!(f, "call_N {}", arity), write!(f, "call_N {}", arity),
&ControlInstruction::Catch =>
write!(f, "catch"),
&ControlInstruction::ExecuteN(arity) => &ControlInstruction::ExecuteN(arity) =>
write!(f, "execute_N {}", arity), write!(f, "execute_N {}", arity),
&ControlInstruction::Deallocate => &ControlInstruction::Deallocate =>
@@ -111,7 +113,9 @@ impl fmt::Display for ControlInstruction {
&ControlInstruction::Execute(ref name, arity) => &ControlInstruction::Execute(ref name, arity) =>
write!(f, "execute {}/{}", name, arity), write!(f, "execute {}/{}", name, arity),
&ControlInstruction::Proceed => &ControlInstruction::Proceed =>
write!(f, "proceed") write!(f, "proceed"),
&ControlInstruction::Throw =>
write!(f, "throw")
} }
} }
} }
@@ -239,10 +243,28 @@ fn rewrite_call_n(terms: &mut Vec<Box<Term>>) -> QueryTerm {
QueryTerm::CallN(new_terms) QueryTerm::CallN(new_terms)
} }
fn rewrite_catch(terms: &mut Vec<Box<Term>>) -> QueryTerm {
let mut new_terms = Vec::with_capacity(0);
swap(&mut new_terms, terms);
QueryTerm::Catch(new_terms)
}
fn rewrite_throw(terms: &mut Vec<Box<Term>>) -> QueryTerm {
let mut new_terms = Vec::with_capacity(0);
swap(&mut new_terms, terms);
QueryTerm::Throw(new_terms)
}
fn rewrite_clause(name: &Atom, terms: &mut Vec<Box<Term>>) -> Option<QueryTerm> fn rewrite_clause(name: &Atom, terms: &mut Vec<Box<Term>>) -> Option<QueryTerm>
{ {
if name == "call" { if name == "call" {
Some(rewrite_call_n(terms)) Some(rewrite_call_n(terms))
} else if name == "catch" && terms.len() == 3 {
Some(rewrite_catch(terms))
} else if name == "throw" && terms.len() == 1 {
Some(rewrite_throw(terms))
} else { } else {
None None
} }
@@ -370,6 +392,7 @@ Each predicate must have the same name and arity.";
let mut cg = CodeGenerator::<DebrayAllocator>::new(); let mut cg = CodeGenerator::<DebrayAllocator>::new();
let compiled_query = cg.compile_query(query); let compiled_query = cg.compile_query(query);
print_code(&compiled_query);
wam.submit_query(compiled_query, cg.take_vars()) wam.submit_query(compiled_query, cg.take_vars())
} }
} }

View File

@@ -72,11 +72,19 @@ impl<'a> QueryIterator<'a> {
fn new(term: QueryTermRef<'a>) -> Self { fn new(term: QueryTermRef<'a>) -> Self {
match term { match term {
QueryTermRef::CallN(child_terms) => { QueryTermRef::CallN(terms) => {
let state = IteratorState::Clause(1, ClauseType::CallN, child_terms); let state = IteratorState::Clause(1, ClauseType::CallN, terms);
QueryIterator { state_stack: vec![state] }
},
QueryTermRef::Catch(terms) => {
let state = IteratorState::Clause(0, ClauseType::Catch, terms);
QueryIterator { state_stack: vec![state] } QueryIterator { state_stack: vec![state] }
}, },
QueryTermRef::Term(term) => Self::from_term(term), QueryTermRef::Term(term) => Self::from_term(term),
QueryTermRef::Throw(term) => {
let state = IteratorState::Clause(0, ClauseType::Throw, term);
QueryIterator { state_stack: vec![state] }
},
_ => QueryIterator { state_stack: vec![] } _ => QueryIterator { state_stack: vec![] }
} }
} }
@@ -101,7 +109,7 @@ impl<'a> Iterator for QueryIterator<'a> {
match ct { match ct {
ClauseType::CallN => ClauseType::CallN =>
self.push_subterm(Level::Shallow, child_terms[0].as_ref()), self.push_subterm(Level::Shallow, child_terms[0].as_ref()),
ClauseType::Root => ClauseType::Root | ClauseType::Throw | ClauseType::Catch =>
return None, return None,
ClauseType::Deep(_, _, _) => ClauseType::Deep(_, _, _) =>
return Some(TermRef::Clause(ct, child_terms)) return Some(TermRef::Clause(ct, child_terms))
@@ -211,8 +219,7 @@ pub struct ChunkedIterator<'a>
{ {
term_loc: GenContext, term_loc: GenContext,
iter: Box<Iterator<Item=QueryTermRef<'a>> + 'a>, iter: Box<Iterator<Item=QueryTermRef<'a>> + 'a>,
deep_cut_encountered: bool, deep_cut_encountered: bool
catch_encountered: bool
} }
impl<'a> ChunkedIterator<'a> impl<'a> ChunkedIterator<'a>
@@ -226,7 +233,6 @@ impl<'a> ChunkedIterator<'a>
term_loc: GenContext::Head, term_loc: GenContext::Head,
iter: inner_iter, iter: inner_iter,
deep_cut_encountered: false, deep_cut_encountered: false,
catch_encountered: false
} }
} }
@@ -237,49 +243,34 @@ impl<'a> ChunkedIterator<'a>
ChunkedIterator { ChunkedIterator {
term_loc: GenContext::Last(0), term_loc: GenContext::Last(0),
iter: Box::new(iter), iter: Box::new(iter),
deep_cut_encountered: false, deep_cut_encountered: false
catch_encountered: false
}
}
fn iterate_over_query_term(p1: &'a QueryTerm) -> Box<Iterator<Item=QueryTermRef<'a>> + 'a>
{
match p1 {
&QueryTerm::CallN(ref child_terms) =>
Box::new(once(QueryTermRef::CallN(child_terms))),
&QueryTerm::Term(ref p1) =>
Box::new(once(QueryTermRef::Term(p1))),
&QueryTerm::Cut =>
Box::new(once(QueryTermRef::Cut))
} }
} }
pub fn from_rule_body(p1: &'a QueryTerm, clauses: &'a Vec<QueryTerm>) -> Self pub fn from_rule_body(p1: &'a QueryTerm, clauses: &'a Vec<QueryTerm>) -> Self
{ {
let inner_iter = Self::iterate_over_query_term(p1); let inner_iter = Box::new(once(p1.to_ref()));
let iter = inner_iter.chain(clauses.iter().map(|c| c.to_ref())); let iter = inner_iter.chain(clauses.iter().map(|c| c.to_ref()));
ChunkedIterator { ChunkedIterator {
term_loc: GenContext::Last(0), term_loc: GenContext::Last(0),
iter: Box::new(iter), iter: Box::new(iter),
deep_cut_encountered: false, deep_cut_encountered: false
catch_encountered: false
} }
} }
pub fn from_rule(rule: &'a Rule) -> Self pub fn from_rule(rule: &'a Rule) -> Self
{ {
let &Rule { head: (ref p0, ref p1), ref clauses } = rule; let &Rule { head: (ref p0, ref p1), ref clauses } = rule;
let iter = once(QueryTermRef::Term(p0));
let inner_iter = Self::iterate_over_query_term(p1); let iter = once(QueryTermRef::Term(p0));
let inner_iter = Box::new(once(p1.to_ref()));
let iter = iter.chain(inner_iter.chain(clauses.iter().map(|c| c.to_ref()))); let iter = iter.chain(inner_iter.chain(clauses.iter().map(|c| c.to_ref())));
ChunkedIterator { ChunkedIterator {
term_loc: GenContext::Head, term_loc: GenContext::Head,
iter: Box::new(iter), iter: Box::new(iter),
deep_cut_encountered: false, deep_cut_encountered: false
catch_encountered: false
} }
} }
@@ -287,10 +278,6 @@ impl<'a> ChunkedIterator<'a>
self.deep_cut_encountered self.deep_cut_encountered
} }
pub fn encountered_catch(&self) -> bool {
self.catch_encountered
}
pub fn at_rule_head(&self) -> bool { pub fn at_rule_head(&self) -> bool {
self.term_loc == GenContext::Head self.term_loc == GenContext::Head
} }
@@ -307,6 +294,9 @@ impl<'a> ChunkedIterator<'a>
while let Some(term) = item { while let Some(term) = item {
match term { match term {
//TODO: This can refer to the term at the head of a
// goal, not technically a QueryTerm (ie. a term in a
// query). Think of a better name.
QueryTermRef::Term(inner_term) => { QueryTermRef::Term(inner_term) => {
if let GenContext::Head = self.term_loc { if let GenContext::Head = self.term_loc {
result.push(term); result.push(term);
@@ -325,6 +315,11 @@ impl<'a> ChunkedIterator<'a>
arity = child_terms.len() + 1; arity = child_terms.len() + 1;
break; break;
}, },
QueryTermRef::Catch(child_terms) | QueryTermRef::Throw(child_terms) => {
result.push(term);
arity = child_terms.len();
break;
},
QueryTermRef::Cut => { QueryTermRef::Cut => {
result.push(term); result.push(term);

View File

@@ -1286,6 +1286,12 @@ impl MachineState {
}, },
&ControlInstruction::Call(ref name, arity, _) => &ControlInstruction::Call(ref name, arity, _) =>
self.try_call_predicate(code_dir, name.clone(), arity), self.try_call_predicate(code_dir, name.clone(), arity),
&ControlInstruction::Catch => {
self.cp = self.p + 1;
self.num_of_args = 3;
self.b0 = self.b;
self.p = CodePtr::DirEntry(5);
},
&ControlInstruction::CallN(arity) => &ControlInstruction::CallN(arity) =>
if let Some((name, arity)) = self.setup_call_n(arity) { if let Some((name, arity)) = self.setup_call_n(arity) {
self.try_call_predicate(code_dir, name, arity); self.try_call_predicate(code_dir, name, arity);
@@ -1306,6 +1312,12 @@ impl MachineState {
}, },
&ControlInstruction::Proceed => &ControlInstruction::Proceed =>
self.p = self.cp, self.p = self.cp,
&ControlInstruction::Throw => {
self.cp = self.p + 1;
self.num_of_args = 1;
self.b0 = self.b;
self.p = CodePtr::DirEntry(56);
}
}; };
} }