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.
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).");
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);
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);
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);
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);
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);
}
}
pub enum QueryTerm {
CallN(Vec<Box<Term>>),
+ Catch(Vec<Box<Term>>),
Cut,
- Term(Term)
+ Term(Term),
+ Throw(Vec<Box<Term>>)
}
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)
}
}
}
#[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
}
| 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
}
}
}
#[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(),
}
}
-pub enum BuiltInInstruction {
+pub enum BuiltInInstruction {
CleanUpBlock,
CopyTerm,
Fail,
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
}
}
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.
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 {
}
fn perm_var_offset(&self) -> usize {
- self.has_deep_cut as usize + 2 * (self.has_catch as usize)
+ self.has_deep_cut as usize
}
}
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)
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));
let call = ControlInstruction::Call(atom.clone(), terms.len(), pvs);
compiled_query.push(Line::Control(call));
},
+ QueryTermRef::Throw(_) =>
+ compiled_query.push(Line::Control(ControlInstruction::Throw)),
_ => {}
}
}
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| {
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 {
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 =>
&ControlInstruction::Execute(ref name, arity) =>
write!(f, "execute {}/{}", name, arity),
&ControlInstruction::Proceed =>
- write!(f, "proceed")
+ write!(f, "proceed"),
+ &ControlInstruction::Throw =>
+ write!(f, "throw")
}
}
}
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
}
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())
}
}
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![] }
}
}
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))
{
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>
term_loc: GenContext::Head,
iter: inner_iter,
deep_cut_encountered: false,
- catch_encountered: false
}
}
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
}
}
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
}
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);
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);
},
&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);
},
&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);
+ }
};
}