From: Mark Thom Date: Mon, 14 Aug 2017 19:21:45 +0000 (-0600) Subject: add tests, update status. X-Git-Tag: v0.8.110~705 X-Git-Url: https://git.sagredo.dev/?a=commitdiff_plain;h=8d7d21523473673bdfe04a1c3338aa27a5966641;p=scryer-prolog.git add tests, update status. --- diff --git a/README.md b/README.md index 90a5b0e7..4e19b5ef 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/main.rs b/src/main.rs index 23d71338..9562f572 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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); } } diff --git a/src/prolog/ast.rs b/src/prolog/ast.rs index bf1a3fef..ebcff750 100644 --- a/src/prolog/ast.rs +++ b/src/prolog/ast.rs @@ -159,8 +159,10 @@ pub enum Term { pub enum QueryTerm { CallN(Vec>), + Catch(Vec>), Cut, - Term(Term) + Term(Term), + Throw(Vec>) } 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, &'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>), + Catch(&'a Vec>), Cut, Term(&'a Term), + Throw(&'a Vec>) } 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 } } diff --git a/src/prolog/builtins.rs b/src/prolog/builtins.rs index daa339f9..976263fb 100644 --- a/src/prolog/builtins.rs +++ b/src/prolog/builtins.rs @@ -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. diff --git a/src/prolog/codegen.rs b/src/prolog/codegen.rs index 71b9f435..bbbadbf1 100644 --- a/src/prolog/codegen.rs +++ b/src/prolog/codegen.rs @@ -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)), _ => {} } } diff --git a/src/prolog/fixtures.rs b/src/prolog/fixtures.rs index cc2412e4..a726835e 100644 --- a/src/prolog/fixtures.rs +++ b/src/prolog/fixtures.rs @@ -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 { diff --git a/src/prolog/io.rs b/src/prolog/io.rs index a45718e1..7ffbbf87 100644 --- a/src/prolog/io.rs +++ b/src/prolog/io.rs @@ -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>) -> QueryTerm { QueryTerm::CallN(new_terms) } +fn rewrite_catch(terms: &mut Vec>) -> QueryTerm { + let mut new_terms = Vec::with_capacity(0); + swap(&mut new_terms, terms); + + QueryTerm::Catch(new_terms) +} + +fn rewrite_throw(terms: &mut Vec>) -> 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>) -> Option { 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::::new(); let compiled_query = cg.compile_query(query); + print_code(&compiled_query); wam.submit_query(compiled_query, cg.take_vars()) } } diff --git a/src/prolog/iterators.rs b/src/prolog/iterators.rs index 8230fae9..78dd0320 100644 --- a/src/prolog/iterators.rs +++ b/src/prolog/iterators.rs @@ -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> + '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> + '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) -> 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); diff --git a/src/prolog/machine.rs b/src/prolog/machine.rs index 53cb6ab7..67c77469 100644 --- a/src/prolog/machine.rs +++ b/src/prolog/machine.rs @@ -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); + } }; }