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:
* 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,
with custom associativity and precedence.
* Bignum and floating point arithmetic.

View File

@@ -504,15 +504,15 @@ mod tests {
assert_eq!(submit(&mut wam, "?- member(X, [a,b,c,d]), !, member(X, [a,d])."), true);
assert_eq!(submit(&mut wam, "?- member(X, [a,b,c,d]), !, member(X, [e])."), false);
assert_eq!(submit(&mut wam, "?- member([X,X],[a,b,c,[d,d],[e,d]]),
member(X, [a,b,c,d,e,f,g]),
member(X, [a,b,c,d,e,f,g]),
member(Y, [X, a, b, c, d])."),
true);
assert_eq!(submit(&mut wam, "?- member([X,X],[a,b,c,[d,d],[e,d]]),
member(X, [a,b,c,d,e,f,g]),
member(X, [a,b,c,d,e,f,g]),
!,
member(Y, [X, a, b, c, d])."),
true);
submit(&mut wam, "p(a, [f(g(X))]).");
submit(&mut wam, "q(Y, c).");
@@ -545,11 +545,11 @@ mod tests {
fn test_queries_on_call_n()
{
let mut wam = Machine::new();
submit(&mut wam, "maplist(Pred, []).
maplist(Pred, [X|Xs]) :- call(Pred, X), maplist(Pred, Xs).");
submit(&mut wam, "f(a). f(b). f(c).");
assert_eq!(submit(&mut wam, "?- maplist(f, [X,Y,Z])."), true);
assert_eq!(submit(&mut wam, "?- maplist(f, [a,Y,Z])."), true);
assert_eq!(submit(&mut wam, "?- maplist(f, [X,a,b])."), true);
@@ -580,7 +580,7 @@ mod tests {
assert_eq!(submit(&mut wam, "?- f(p, x, y)."), true);
assert_eq!(submit(&mut wam, "?- f(p, X, z)."), false);
assert_eq!(submit(&mut wam, "?- f(p, z, Y)."), false);
assert_eq!(submit(&mut wam, "?- call(p, X)."), true);
assert_eq!(submit(&mut wam, "?- call(p, x)."), true);
assert_eq!(submit(&mut wam, "?- call(p, y)."), true);
@@ -639,7 +639,7 @@ mod tests {
assert_eq!(submit(&mut wam, "?- call(david_lynch, kyle(Film), _)."), false);
submit(&mut wam, "call_mult(P, X) :- call(call(P), X).");
assert_eq!(submit(&mut wam, "?- call_mult(p(X), Y)."), true);
assert_eq!(submit(&mut wam, "?- call_mult(p(X), X)."), true);
assert_eq!(submit(&mut wam, "?- call_mult(p(one), X)."), true);
@@ -656,10 +656,10 @@ mod tests {
assert_eq!(submit(&mut wam, "?- call(call(p), X, Y), call(call(call(p(X))), Y)."), true);
assert_eq!(submit(&mut wam, "?- call(call(p), X, Y), call(call(call(p(X))), X, Y)."), false);
assert_eq!(submit(&mut wam, "?- call(call(p), X, Y), call(call(call(p(X))), X)."), true);
submit(&mut wam, "f(call(f, undefined)). f(undefined).");
submit(&mut wam, "call_var(P) :- P.");
assert_eq!(submit(&mut wam, "?- f(X), call_var(X)."), true);
assert_eq!(submit(&mut wam, "?- f(call(f, Q)), call_var(call(f, Q))."), true);
assert_eq!(submit(&mut wam, "?- call_var(call(undefined, Q))."), false);
@@ -681,25 +681,49 @@ mod tests {
submit(&mut wam, "handle(stuff).");
assert_eq!(submit(&mut wam, "?- catch(f(X), Exception, handle(Exception))."), true);
submit(&mut wam, "f(a). f(X) :- g(X).");
submit(&mut wam, "g(x). g(y). g(z).");
submit(&mut wam, "handle(x). handle(y).");
assert_eq!(submit(&mut wam, "?- catch(f(X), X, handle(X))."), true);
assert_eq!(submit(&mut wam, "?- catch(f(a), _, handle(X))."), true);
assert_eq!(submit(&mut wam, "?- catch(f(b), _, handle(X))."), false);
submit(&mut wam, "g(x). g(X) :- throw(x).");
assert_eq!(submit(&mut wam, "?- catch(f(X), x, handle(X))."), true);
assert_eq!(submit(&mut wam, "?- catch(f(X), x, handle(z))."), true);
assert_eq!(submit(&mut wam, "?- catch(f(z), x, handle(x))."), 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);
submit(&mut wam, "f(X) :- throw(stuff). f(X) :- throw(other_stuff).");
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);
//TODO: write more tests: multi-layered throw/catch, catch
// within catch, throw within catch, etc.
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 {
CallN(Vec<Box<Term>>),
Catch(Vec<Box<Term>>),
Cut,
Term(Term)
Term(Term),
Throw(Vec<Box<Term>>)
}
impl QueryTerm {
@@ -168,10 +170,14 @@ impl QueryTerm {
match self {
&QueryTerm::CallN(ref terms) =>
QueryTermRef::CallN(terms),
&QueryTerm::Catch(ref terms) =>
QueryTermRef::Catch(terms),
&QueryTerm::Cut =>
QueryTermRef::Cut,
&QueryTerm::Term(ref term) =>
QueryTermRef::Term(term),
&QueryTerm::Throw(ref t) =>
QueryTermRef::Throw(t)
}
}
}
@@ -184,14 +190,16 @@ pub struct Rule {
#[derive(Clone, Copy)]
pub enum ClauseType<'a> {
CallN,
Catch,
Deep(Level, &'a Cell<RegType>, &'a Atom),
Root
Root,
Throw
}
impl<'a> ClauseType<'a> {
pub fn level_of_subterms(self) -> Level {
match self {
ClauseType::CallN => Level::Shallow,
ClauseType::CallN | ClauseType::Catch | ClauseType::Throw => Level::Shallow,
ClauseType::Deep(_, _, _) => Level::Deep,
ClauseType::Root => Level::Shallow
}
@@ -216,7 +224,9 @@ impl<'a> TermRef<'a> {
| TermRef::Var(lvl, _, _) => lvl,
TermRef::Clause(ClauseType::Root, _) => Level::Shallow,
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)]
pub enum QueryTermRef<'a> {
CallN(&'a Vec<Box<Term>>),
Catch(&'a Vec<Box<Term>>),
Cut,
Term(&'a Term),
Throw(&'a Vec<Box<Term>>)
}
impl<'a> QueryTermRef<'a> {
pub fn arity(self) -> usize {
match self {
QueryTermRef::Catch(_) => 3,
QueryTermRef::Throw(_) => 1,
QueryTermRef::CallN(terms) => terms.len(),
QueryTermRef::Cut => 0,
QueryTermRef::Term(term) => term.arity(),
@@ -276,7 +290,7 @@ impl IndexedChoiceInstruction {
}
}
pub enum BuiltInInstruction {
pub enum BuiltInInstruction {
CleanUpBlock,
CopyTerm,
Fail,
@@ -297,19 +311,23 @@ pub enum ControlInstruction {
Allocate(usize),
Call(Atom, usize, usize),
CallN(usize),
Catch,
Deallocate,
Execute(Atom, usize),
ExecuteN(usize),
Proceed
Proceed,
Throw
}
impl ControlInstruction {
pub fn is_jump_instr(&self) -> bool {
match self {
&ControlInstruction::Call(_, _, _) => true,
&ControlInstruction::Execute(_, _) => true,
&ControlInstruction::Call(_, _, _) => true,
&ControlInstruction::Catch => true,
&ControlInstruction::Execute(_, _) => true,
&ControlInstruction::CallN(_) => true,
&ControlInstruction::ExecuteN(_) => true,
&ControlInstruction::Throw => true,
_ => false
}
}

View File

@@ -87,7 +87,7 @@ fn get_builtins() -> Code {
deallocate!(),
goto!(58, 0), // goto false.
set_ball!(), // throw/1, 56.
unwind_stack!(),
unwind_stack!(),
fail!(), // false/0, 58.
proceed!(),
try_me_else!(7), // not/1, 60.

View File

@@ -24,20 +24,18 @@ pub enum EvalSession<'a> {
pub struct ConjunctInfo<'a> {
pub perm_vs: VariableFixtures<'a>,
pub num_of_chunks: usize,
pub has_deep_cut: bool,
pub has_catch: bool
pub has_deep_cut: bool
}
impl<'a> ConjunctInfo<'a>
{
fn new(perm_vs: VariableFixtures<'a>, num_of_chunks: usize, has_deep_cut: bool, has_catch: bool)
-> Self
fn new(perm_vs: VariableFixtures<'a>, num_of_chunks: usize, has_deep_cut: bool) -> 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 {
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 {
@@ -45,7 +43,7 @@ impl<'a> ConjunctInfo<'a>
}
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 has_deep_cut = iter.encountered_deep_cut();
let has_catch = iter.encountered_catch();
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);
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)
@@ -214,6 +211,8 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator<'a, TermMarker>
let call = ControlInstruction::CallN(terms.len());
compiled_query.push(Line::Control(call));
},
QueryTermRef::Catch(_) =>
compiled_query.push(Line::Control(ControlInstruction::Catch)),
QueryTermRef::Term(&Term::Constant(_, Constant::Atom(ref atom))) => {
let call = ControlInstruction::Call(atom.clone(), 0, pvs);
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);
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()
}
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()
.filter_map(|ref v| {
@@ -231,7 +231,7 @@ impl<'a> VariableFixtures<'a>
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 cell in cells {

View File

@@ -104,6 +104,8 @@ impl fmt::Display for ControlInstruction {
write!(f, "call {}/{}, {}", name, arity, pvs),
&ControlInstruction::CallN(arity) =>
write!(f, "call_N {}", arity),
&ControlInstruction::Catch =>
write!(f, "catch"),
&ControlInstruction::ExecuteN(arity) =>
write!(f, "execute_N {}", arity),
&ControlInstruction::Deallocate =>
@@ -111,7 +113,9 @@ impl fmt::Display for ControlInstruction {
&ControlInstruction::Execute(ref name, arity) =>
write!(f, "execute {}/{}", name, arity),
&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)
}
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>
{
if name == "call" {
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 {
None
}
@@ -370,6 +392,7 @@ Each predicate must have the same name and arity.";
let mut cg = CodeGenerator::<DebrayAllocator>::new();
let compiled_query = cg.compile_query(query);
print_code(&compiled_query);
wam.submit_query(compiled_query, cg.take_vars())
}
}

View File

@@ -72,11 +72,19 @@ impl<'a> QueryIterator<'a> {
fn new(term: QueryTermRef<'a>) -> Self {
match term {
QueryTermRef::CallN(child_terms) => {
let state = IteratorState::Clause(1, ClauseType::CallN, child_terms);
QueryTermRef::CallN(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] }
},
QueryTermRef::Term(term) => Self::from_term(term),
QueryTermRef::Throw(term) => {
let state = IteratorState::Clause(0, ClauseType::Throw, term);
QueryIterator { state_stack: vec![state] }
},
QueryTermRef::Term(term) => Self::from_term(term),
_ => QueryIterator { state_stack: vec![] }
}
}
@@ -101,7 +109,7 @@ impl<'a> Iterator for QueryIterator<'a> {
match ct {
ClauseType::CallN =>
self.push_subterm(Level::Shallow, child_terms[0].as_ref()),
ClauseType::Root =>
ClauseType::Root | ClauseType::Throw | ClauseType::Catch =>
return None,
ClauseType::Deep(_, _, _) =>
return Some(TermRef::Clause(ct, child_terms))
@@ -211,8 +219,7 @@ pub struct ChunkedIterator<'a>
{
term_loc: GenContext,
iter: Box<Iterator<Item=QueryTermRef<'a>> + 'a>,
deep_cut_encountered: bool,
catch_encountered: bool
deep_cut_encountered: bool
}
impl<'a> ChunkedIterator<'a>
@@ -226,7 +233,6 @@ impl<'a> ChunkedIterator<'a>
term_loc: GenContext::Head,
iter: inner_iter,
deep_cut_encountered: false,
catch_encountered: false
}
}
@@ -237,49 +243,34 @@ impl<'a> ChunkedIterator<'a>
ChunkedIterator {
term_loc: GenContext::Last(0),
iter: Box::new(iter),
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))
deep_cut_encountered: false
}
}
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()));
ChunkedIterator {
term_loc: GenContext::Last(0),
iter: Box::new(iter),
deep_cut_encountered: false,
catch_encountered: false
deep_cut_encountered: false
}
}
pub fn from_rule(rule: &'a Rule) -> Self
{
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())));
ChunkedIterator {
term_loc: GenContext::Head,
iter: Box::new(iter),
deep_cut_encountered: false,
catch_encountered: false
deep_cut_encountered: false
}
}
@@ -287,10 +278,6 @@ impl<'a> ChunkedIterator<'a>
self.deep_cut_encountered
}
pub fn encountered_catch(&self) -> bool {
self.catch_encountered
}
pub fn at_rule_head(&self) -> bool {
self.term_loc == GenContext::Head
}
@@ -307,6 +294,9 @@ impl<'a> ChunkedIterator<'a>
while let Some(term) = item {
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) => {
if let GenContext::Head = self.term_loc {
result.push(term);
@@ -325,6 +315,11 @@ impl<'a> ChunkedIterator<'a>
arity = child_terms.len() + 1;
break;
},
QueryTermRef::Catch(child_terms) | QueryTermRef::Throw(child_terms) => {
result.push(term);
arity = child_terms.len();
break;
},
QueryTermRef::Cut => {
result.push(term);

View File

@@ -1286,6 +1286,12 @@ impl MachineState {
},
&ControlInstruction::Call(ref name, 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) =>
if let Some((name, arity)) = self.setup_call_n(arity) {
self.try_call_predicate(code_dir, name, arity);
@@ -1306,6 +1312,12 @@ impl MachineState {
},
&ControlInstruction::Proceed =>
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);
}
};
}