optimized up to chapter 6

This commit is contained in:
Mark Thom
2017-04-09 19:12:36 -06:00
parent d2cf8626a1
commit 19ee43e186
12 changed files with 1132 additions and 726 deletions

2
Cargo.lock generated
View File

@@ -1,6 +1,6 @@
[root]
name = "rusty-wam"
version = "0.5.11"
version = "0.5.12"
dependencies = [
"lalrpop 0.12.5 (registry+https://github.com/rust-lang/crates.io-index)",
"lalrpop-util 0.12.5 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@@ -1,6 +1,6 @@
[package]
name = "rusty-wam"
version = "0.5.11"
version = "0.5.12"
authors = ["Mark Thom"]
build = "build.rs"
@@ -12,5 +12,4 @@ version = "1.2.0"
version = "0.12.5"
[build-dependencies.lalrpop]
version = "0.12.5"
version = "0.12.5"

View File

@@ -8,11 +8,11 @@ pure Prolog.
## Progress
Pure Prolog is implemented as a simple REPL. "Pure Prolog" is Prolog
without cut, meta- or extra-logical operators, or side effects of any
kind. In terms of the tutorial pacing, the work has progressed to the
end of section 5.10, skipping past 5.4. Atoms and lists are the only
two data types currently supported.
Prolog is implemented as a simple REPL. It is without without meta- or
extra-logical operators, or side effects of any kind, with the lone
exception of cut. In terms of the tutorial pacing, the work has
progressed to the end of section 5.11, skipping past 5.4. Atoms and
lists are the only two data types currently supported.
While proper environment trimming code is emitted by the code
generator, it has no effect on the bytecode WAM, which lacks

View File

@@ -194,6 +194,87 @@ mod tests {
assert_eq!(submit(&mut wam, "?- p(X).").failed_query(), false);
}
#[test]
fn test_queries_on_cuts() {
let mut wam = Machine::new();
// test shallow cuts.
submit(&mut wam, "memberchk(X, [X|_]) :- !.
memberchk(X, [_|Xs]) :- !, memberchk(X, Xs).");
assert_eq!(submit(&mut wam, "?- memberchk(X, [a,b,c]).").failed_query(), false);
assert_eq!(submit(&mut wam, "?- memberchk([X,X], [a,b,c,[d,e],[d,d]]).").failed_query(), false);
assert_eq!(submit(&mut wam, "?- memberchk([X,X], [a,b,c,[D,d],[e,e]]).").failed_query(), false);
assert_eq!(submit(&mut wam, "?- memberchk([X,X], [a,b,c,[e,d],[f,e]]).").failed_query(), true);
assert_eq!(submit(&mut wam, "?- memberchk([X,X,Y], [a,b,c,[e,d],[f,e]]).").failed_query(), true);
assert_eq!(submit(&mut wam, "?- memberchk([X,X,Y], [a,b,c,[e,e,d],[f,e]]).").failed_query(), false);
// test deep cuts.
submit(&mut wam, "commit :- a, !.");
assert_eq!(submit(&mut wam, "?- commit.").failed_query(), true);
submit(&mut wam, "a.");
assert_eq!(submit(&mut wam, "?- commit.").failed_query(), false);
submit(&mut wam, "commit(X) :- a(X), !.");
assert_eq!(submit(&mut wam, "?- commit(X).").failed_query(), true);
submit(&mut wam, "a(x).");
assert_eq!(submit(&mut wam, "?- commit(X).").failed_query(), false);
submit(&mut wam, "a :- b, !, c. a :- d.");
assert_eq!(submit(&mut wam, "?- a.").failed_query(), true);
submit(&mut wam, "b.");
assert_eq!(submit(&mut wam, "?- a.").failed_query(), true);
submit(&mut wam, "d.");
// we've committed to the first clause since the query on b
// succeeds, so we expect failure here.
assert_eq!(submit(&mut wam, "?- a.").failed_query(), true);
submit(&mut wam, "c.");
assert_eq!(submit(&mut wam, "?- a.").failed_query(), false);
submit(&mut wam, "a(X) :- b, !, c(X). a(X) :- d(X).");
assert_eq!(submit(&mut wam, "?- a(X).").failed_query(), true);
submit(&mut wam, "c(c).");
submit(&mut wam, "d(d).");
assert_eq!(submit(&mut wam, "?- a(X).").failed_query(), false);
submit(&mut wam, "b.");
assert_eq!(submit(&mut wam, "?- a(X).").failed_query(), false);
wam.clear();
assert_eq!(submit(&mut wam, "?- c(X).").failed_query(), true);
submit(&mut wam, "a(X) :- b, c(X), !. a(X) :- d(X).");
submit(&mut wam, "b.");
assert_eq!(submit(&mut wam, "?- a(X).").failed_query(), true);
submit(&mut wam, "d(d).");
assert_eq!(submit(&mut wam, "?- a(X).").failed_query(), false);
submit(&mut wam, "c(c).");
assert_eq!(submit(&mut wam, "?- a(X).").failed_query(), false);
}
#[test]
fn test_queries_on_lists() {
let mut wam = Machine::new();

View File

@@ -5,6 +5,7 @@ use std::vec::Vec;
pub struct Frame {
pub global_index: usize,
pub b0: usize,
pub e: usize,
pub cp: CodePtr,
perms: Vec<Addr>
@@ -14,6 +15,7 @@ impl Frame {
fn new(global_index: usize, e: usize, cp: CodePtr, n: usize) -> Self {
Frame {
global_index: global_index,
b0: 0,
e: e,
cp: cp,
perms: vec![Addr::HeapCell(0); n]

View File

@@ -119,13 +119,27 @@ pub enum Term {
Var(Cell<VarReg>, Var)
}
pub struct Rule {
pub head: (Term, Term),
pub clauses: Vec<Term>
pub enum TermOrCut {
Cut,
Term(Term)
}
impl Rule {
pub fn last_clause(&self) -> &Term {
impl TermOrCut {
pub fn arity(&self) -> usize {
match self {
&TermOrCut::Term(ref term) => term.arity(),
_ => 0
}
}
}
pub struct Rule {
pub head: (Term, TermOrCut),
pub clauses: Vec<TermOrCut>
}
impl Rule {
pub fn last_clause(&self) -> &TermOrCut {
match self.clauses.last() {
None => &self.head.1,
Some(clause) => clause
@@ -141,12 +155,22 @@ pub enum TermRef<'a> {
Var(Level, &'a Cell<VarReg>, &'a Var)
}
pub enum ChoiceInstruction {
RetryMeElse(usize),
TrustMe,
pub enum ChoiceInstruction {
RetryMeElse(usize),
TrustMe,
TryMeElse(usize)
}
pub enum Terminal {
Terminal, Non
}
pub enum CutInstruction {
Cut(Terminal),
GetLevel,
NeckCut(Terminal)
}
pub enum IndexedChoiceInstruction {
Retry(usize),
Trust(usize),
@@ -223,6 +247,7 @@ pub type CompiledQuery = Vec<QueryInstruction>;
pub enum Line {
Choice(ChoiceInstruction),
Control(ControlInstruction),
Cut(CutInstruction),
Fact(CompiledFact),
Indexing(IndexingInstruction),
IndexedChoice(IndexedChoiceInstruction),
@@ -373,18 +398,18 @@ impl Term {
match self {
&Term::Clause(_, _, ref terms) =>
terms.first().map(|bt| bt.as_ref()),
_ => None
_ => None
}
}
pub fn is_clause(&self) -> bool {
if let &Term::Clause(_, _, _) = self {
true
} else {
false
}
}
}
pub fn subterms(&self) -> usize {
match self {
&Term::Clause(_, _, ref terms) => terms.len(),

View File

@@ -441,9 +441,9 @@ impl CodeOffsets {
IntIndex::External(o) => o + prelude_len + 1,
IntIndex::Fail => 0,
IntIndex::Internal(_) => prelude_len - lst_offset + 1
}
}
}
fn add_indices(self, code: &mut Code, mut code_body: Code)
{
if self.no_indices() {
@@ -470,11 +470,11 @@ impl CodeOffsets {
_ => {}
}
}
let str_loc = Self::switch_on_str_offset_from(str_loc, prelude.len(), con_loc);
let con_loc = Self::switch_on_con_offset_from(con_loc, prelude.len());
let lst_loc = Self::switch_on_lst_offset_from(lst_loc, prelude.len(), lst_offset);
let switch_instr = IndexingInstruction::SwitchOnTerm(prelude.len() + 1,
con_loc,
lst_loc,
@@ -752,21 +752,40 @@ impl<'a> CodeGenerator<'a> {
}
}
fn mark_perm_vars(rule: &'a Rule) -> VariableFixtures {
fn mark_perm_vars(rule: &'a Rule) -> (VariableFixtures, bool)
{
let &Rule { head: (ref p0, ref p1), ref clauses } = rule;
let mut vs = HashMap::new();
let iter = p0.breadth_first_iter().chain(p1.breadth_first_iter());
Self::mark_vars_in_term(iter, &mut vs, 0);
for (i, term) in clauses.iter().enumerate() {
Self::mark_vars_in_term(term.breadth_first_iter(), &mut vs, i + 1);
let mut vs = HashMap::new();
match p1 {
&TermOrCut::Cut => {
let iter = p0.breadth_first_iter();
Self::mark_vars_in_term(iter, &mut vs, 0);
},
&TermOrCut::Term(ref p1) => {
let iter = p0.breadth_first_iter().chain(p1.breadth_first_iter());
Self::mark_vars_in_term(iter, &mut vs, 0);
}
}
for (i, term) in clauses.iter().enumerate() {
if let &TermOrCut::Term(ref term) = term {
Self::mark_vars_in_term(term.breadth_first_iter(), &mut vs, i + 1)
}
}
let mut deep_cuts = false;
for term in clauses {
if let &TermOrCut::Cut = term {
deep_cuts = true;
break;
}
}
Self::set_perm_vals(&vs);
vs
(vs, deep_cuts)
}
fn add_conditional_call(compiled_query: &mut Code, term: &Term, pvs: usize)
@@ -787,8 +806,8 @@ impl<'a> CodeGenerator<'a> {
fn vars_above_threshold(vs: &VariableFixtures, index: usize) -> usize {
let mut var_count = 0;
for &(term_status, _) in vs.values() {
if let VarStatus::Permanent(i) = term_status {
for &(var_status, _) in vs.values() {
if let VarStatus::Permanent(i) = var_status {
if i > index {
var_count += 1;
}
@@ -803,8 +822,8 @@ impl<'a> CodeGenerator<'a> {
let mut dealloc_index = body.len() - 1;
match rule.last_clause() {
&Term::Clause(_, ref name, _)
| &Term::Constant(_, Constant::Atom(ref name)) => {
&TermOrCut::Term(Term::Clause(_, ref name, _))
| &TermOrCut::Term(Term::Constant(_, Constant::Atom(ref name))) => {
if let &mut Line::Control(ref mut ctrl) = body.last_mut().unwrap() {
*ctrl = ControlInstruction::Execute(name.clone(), last_arity);
}
@@ -863,8 +882,9 @@ impl<'a> CodeGenerator<'a> {
}
}
pub fn compile_rule(&mut self, rule: &'a Rule) -> Code {
let vs = Self::mark_perm_vars(&rule);
pub fn compile_rule(&mut self, rule: &'a Rule) -> Code
{
let (vs, deep_cuts) = Self::mark_perm_vars(&rule);
let &Rule { head: (ref p0, ref p1), ref clauses } = rule;
let perm_vars = Self::vars_above_threshold(&vs, 0);
@@ -872,23 +892,63 @@ impl<'a> CodeGenerator<'a> {
if clauses.len() > 0 {
body.push(Line::Control(ControlInstruction::Allocate(perm_vars)));
if deep_cuts {
body.push(Line::Cut(CutInstruction::GetLevel));
}
}
let iter = p0.breadth_first_iter().chain(p1.breadth_first_iter());
self.update_var_count(iter);
match p1 {
&TermOrCut::Cut => {
let iter = p0.breadth_first_iter();
self.update_var_count(iter);
},
&TermOrCut::Term(ref p1) => {
let iter = p0.breadth_first_iter().chain(p1.breadth_first_iter());
self.update_var_count(iter);
}
};
self.marker.advance(p0);
body.push(Line::Fact(self.compile_target(p0, false)));
self.marker.advance_at_head(p1);
body.push(Line::Query(self.compile_target(p1, false)));
if p0.is_clause() {
body.push(Line::Fact(self.compile_target(p0, false)));
}
Self::add_conditional_call(&mut body, p1, perm_vars);
match p1 {
&TermOrCut::Cut => {
let term = if clauses.is_empty() {
Terminal::Terminal
} else {
Terminal::Non
};
body.push(Line::Cut(CutInstruction::NeckCut(term)));
},
&TermOrCut::Term(ref p1) => {
self.marker.advance_at_head(p1);
if p1.is_clause() {
body.push(Line::Query(self.compile_target(p1, false)));
}
Self::add_conditional_call(&mut body, p1, perm_vars);
}
};
body = clauses.iter().enumerate()
.map(|(i, term)| {
let num_vars = Self::vars_above_threshold(&vs, i+1);
self.compile_internal_query(term, num_vars)
match term {
&TermOrCut::Cut if i + 1 < clauses.len() =>
vec![Line::Cut(CutInstruction::Cut(Terminal::Non))],
&TermOrCut::Cut =>
vec![Line::Cut(CutInstruction::Cut(Terminal::Terminal))],
&TermOrCut::Term(ref term) => {
let num_vars = Self::vars_above_threshold(&vs, i + 1);
self.compile_internal_query(term, num_vars)
}
}
})
.fold(body, |mut body, ref mut cqs| {
body.append(cqs);

View File

@@ -145,6 +145,19 @@ impl fmt::Display for IndexingInstruction {
}
}
impl fmt::Display for CutInstruction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
&CutInstruction::Cut(_) =>
write!(f, "cut"),
&CutInstruction::NeckCut(_) =>
write!(f, "neck_cut"),
&CutInstruction::GetLevel =>
write!(f, "get_level")
}
}
}
impl fmt::Display for Level {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
@@ -198,6 +211,8 @@ pub fn print_code(code: &Code) {
for fact_instr in fact {
println!("{}", fact_instr);
},
&Line::Cut(ref cut) =>
println!("{}", cut),
&Line::Choice(ref choice) =>
println!("{}", choice),
&Line::Control(ref control) =>

View File

@@ -19,6 +19,7 @@ struct MachineState {
s: usize,
p: CodePtr,
b: usize,
b0: usize,
e: usize,
num_of_args: usize,
cp: CodePtr,
@@ -82,7 +83,7 @@ impl Machine {
pub fn add_fact(&mut self, fact: &Term, mut code: Code) {
if let Some(name) = fact.name() {
let p = self.code.len();
let p = self.code.len();
let arity = fact.arity();
self.code.append(&mut code);
@@ -121,6 +122,8 @@ impl Machine {
match instr {
&Line::Choice(ref choice_instr) =>
self.ms.execute_choice_instr(choice_instr),
&Line::Cut(ref cut_instr) =>
self.ms.execute_cut_instr(cut_instr),
&Line::Control(ref control_instr) =>
self.ms.execute_ctrl_instr(&self.code_dir, control_instr),
&Line::Fact(ref fact) => {
@@ -132,7 +135,7 @@ impl Machine {
self.ms.execute_fact_instr(&fact_instr);
}
self.ms.p += 1;
},
},
&Line::Indexing(ref indexing_instr) =>
self.ms.execute_indexing_instr(&indexing_instr),
&Line::IndexedChoice(ref choice_instr) =>
@@ -146,21 +149,29 @@ impl Machine {
self.ms.execute_query_instr(&query_instr);
}
self.ms.p += 1;
}
}
}
if self.failed() {
let p = self.ms
.or_stack
.top()
.map(|fr| fr.bp)
.unwrap_or_default();
let b0 = self.ms
.or_stack
.top()
.map(|fr| fr.b0)
.unwrap_or(0);
let p = if self.ms.b > 0 {
let b = self.ms.b - 1;
self.ms.or_stack[b].bp
} else {
CodePtr::TopLevel
};
if let CodePtr::TopLevel = p {
return false;
} else {
self.ms.fail = false;
self.ms.p = p;
self.ms.b0 = b0;
}
}
@@ -267,7 +278,11 @@ impl Machine {
pub fn continue_query(&mut self) -> EvalResult
{
if !self.or_stack_is_empty() {
let b = self.ms.b;
if self.ms.b == 0 {
return EvalResult::QueryFailure;
}
let b = self.ms.b - 1;
self.ms.p = self.ms.or_stack[b].bp;
let succeeded = if let CodePtr::DirEntry(p) = self.ms.p {
@@ -286,6 +301,13 @@ impl Machine {
}
}
#[allow(dead_code)]
pub fn clear(&mut self) {
self.reset();
self.code.clear();
self.code_dir.clear();
}
pub fn reset(&mut self) {
self.ms.reset();
}
@@ -297,6 +319,7 @@ impl MachineState {
s: 0,
p: CodePtr::TopLevel,
b: 0,
b0: 0,
e: 0,
num_of_args: 0,
cp: CodePtr::TopLevel,
@@ -419,7 +442,12 @@ impl MachineState {
Ref::StackCell(fr, _) => {
let fr_gi = self.and_stack[fr].global_index;
let b_gi = if !self.or_stack.is_empty() {
self.or_stack[self.b].global_index
if self.b > 0 {
let b = self.b - 1;
self.or_stack[b].global_index
} else {
0
}
} else {
0
};
@@ -443,6 +471,48 @@ impl MachineState {
}
}
fn tidy_trail(&mut self) {
if self.b == 0 {
return;
}
let b = self.b - 1;
let mut i = self.or_stack[b].tr;
while i < self.tr {
let tr_i = self.trail[i];
let hb = self.hb;
match tr_i {
Ref::HeapCell(tr_i) =>
if tr_i < hb { //|| ((h < tr_i) && tr_i < b) {
i += 1;
} else {
let tr = self.tr;
let val = self.trail[tr - 1];
self.trail[i] = val;
},
Ref::StackCell(fr, _) => {
let b = self.b - 1;
let fr_gi = self.and_stack[fr].global_index;
let b_gi = if !self.or_stack.is_empty() {
self.or_stack[b].global_index
} else {
0
};
if fr_gi < b_gi {
i += 1;
} else {
let tr = self.tr;
let val = self.trail[tr - 1];
self.trail[i] = val;
}
}
};
}
}
fn execute_fact_instr(&mut self, instr: &FactInstruction) {
match instr {
&FactInstruction::GetConstant(_, ref constant, reg) => {
@@ -703,12 +773,12 @@ impl MachineState {
match offset {
0 => self.fail = true,
o => self.p += o
};
o => self.p += o
};
}
};
}
fn execute_query_instr(&mut self, instr: &QueryInstruction) {
match instr {
&QueryInstruction::PutConstant(_, ref constant, reg) =>
@@ -821,6 +891,7 @@ impl MachineState {
Some(compiled_tl_index) => {
self.cp = self.p + 1;
self.num_of_args = arity;
self.b0 = self.b;
self.p = CodePtr::DirEntry(compiled_tl_index);
},
None => self.fail = true
@@ -841,7 +912,8 @@ impl MachineState {
match compiled_tl_index {
Some(compiled_tl_index) => {
self.num_of_args = arity;
self.p = CodePtr::DirEntry(compiled_tl_index);
self.b0 = self.b;
self.p = CodePtr::DirEntry(compiled_tl_index);
},
None => self.fail = true
};
@@ -865,10 +937,11 @@ impl MachineState {
self.p + 1,
self.tr,
self.h,
self.b0,
self.num_of_args);
self.b = self.or_stack.len() - 1;
let b = self.b;
self.b = self.or_stack.len();
let b = self.b - 1;
for i in 1 .. n + 1 {
self.or_stack[b][i] = self.registers[i].clone();
@@ -878,7 +951,7 @@ impl MachineState {
self.p += l;
},
&IndexedChoiceInstruction::Retry(l) => {
let b = self.b;
let b = self.b - 1;
let n = self.or_stack[b].num_args();
for i in 1 .. n + 1 {
@@ -905,7 +978,7 @@ impl MachineState {
self.p += l;
},
&IndexedChoiceInstruction::Trust(l) => {
let b = self.b;
let b = self.b - 1;
let n = self.or_stack[b].num_args();
for i in 1 .. n + 1 {
@@ -935,10 +1008,10 @@ impl MachineState {
}
};
}
fn execute_choice_instr(&mut self, instr: &ChoiceInstruction)
{
match instr {
match instr {
&ChoiceInstruction::TryMeElse(offset) => {
let n = self.num_of_args;
let num_frames = self.num_frames();
@@ -950,10 +1023,11 @@ impl MachineState {
self.p + offset,
self.tr,
self.h,
self.b0,
self.num_of_args);
self.b = self.or_stack.len() - 1;
let b = self.b;
self.b = self.or_stack.len();
let b = self.b - 1;
for i in 1 .. n + 1 {
self.or_stack[b][i] = self.registers[i].clone();
@@ -961,9 +1035,9 @@ impl MachineState {
self.hb = self.h;
self.p += 1;
},
},
&ChoiceInstruction::RetryMeElse(offset) => {
let b = self.b;
let b = self.b - 1;
let n = self.or_stack[b].num_args();
for i in 1 .. n + 1 {
@@ -990,7 +1064,7 @@ impl MachineState {
self.p += 1;
},
&ChoiceInstruction::TrustMe => {
let b = self.b;
let b = self.b - 1;
let n = self.or_stack[b].num_args();
for i in 1 .. n + 1 {
@@ -1021,11 +1095,55 @@ impl MachineState {
}
}
fn execute_cut_instr(&mut self, instr: &CutInstruction) {
match instr {
&CutInstruction::Cut(ref term) => {
let b = self.b;
let e = self.e;
let b0 = self.and_stack[e].b0; // STACK[E+2+1]
if b > b0 {
self.b = b0;
self.tidy_trail();
}
if let &Terminal::Terminal = term {
self.p = CodePtr::TopLevel;
} else {
self.p += 1;
}
},
&CutInstruction::GetLevel => {
let b0 = self.b0;
let e = self.e;
self.and_stack[e].b0 = b0;
self.p += 1;
},
&CutInstruction::NeckCut(ref term) => {
let b = self.b;
let b0 = self.b0;
if b > b0 {
self.b = b0;
self.tidy_trail();
}
if let &Terminal::Terminal = term {
self.p = CodePtr::TopLevel;
} else {
self.p += 1;
}
}
}
}
fn reset(&mut self) {
self.h = 0;
self.hb = 0;
self.e = 0;
self.b = 0;
self.b0 = 0;
self.s = 0;
self.tr = 0;
self.p = CodePtr::TopLevel;
@@ -1039,5 +1157,5 @@ impl MachineState {
self.and_stack.clear();
self.or_stack.clear();
self.registers = vec![Addr::HeapCell(0); 64];
}
}
}

View File

@@ -7,10 +7,11 @@ pub struct Frame {
pub global_index: usize,
pub e: usize,
pub cp: CodePtr,
pub b: usize,
pub b: usize,
pub bp: CodePtr,
pub tr: usize,
pub h: usize,
pub b0: usize,
args: Vec<Addr>
}
@@ -22,6 +23,7 @@ impl Frame {
bp: CodePtr,
tr: usize,
h: usize,
b0: usize,
n: usize)
-> Self
{
@@ -33,6 +35,7 @@ impl Frame {
bp: bp,
tr: tr,
h: h,
b0: b0,
args: vec![Addr::HeapCell(0); n]
}
}
@@ -57,9 +60,10 @@ impl OrStack {
bp: CodePtr,
tr: usize,
h: usize,
b0: usize,
n: usize)
{
self.0.push(Frame::new(global_index, e, cp, b, bp, tr, h, n));
self.0.push(Frame::new(global_index, e, cp, b, bp, tr, h, b0, n));
}
pub fn len(&self) -> usize {

View File

@@ -57,19 +57,25 @@ PredicateClause : PredicateClause = {
};
Rule : Rule = {
<c:Clause> ":-" <h:Term> <cs: ("," <Term>)*> =>
<c:Clause> ":-" <h:TermOrCut> <cs: ("," <TermOrCut>)*> =>
Rule { head: (c, h), clauses: cs },
<a:Atom> ":-" <h:Term> <cs: ("," <Term>)*> =>
Rule { head: (Term::Constant(Cell::default(), Constant::Atom(a)), h),
<a:Atom> ":-" <h:TermOrCut> <cs: ("," <TermOrCut>)*> =>
Rule { head: (Term::Constant(Cell::default(), Constant::Atom(a)),
h),
clauses: cs }
};
TermOrCut : TermOrCut = {
"!" => TermOrCut::Cut,
<Term> => TermOrCut::Term(<>)
};
Term : Term = {
<Atom> => Term::Constant(Cell::default(), Constant::Atom(<>)),
<Clause> => <>,
<List> => <>,
<Var> => Term::Var(Cell::default(), <>),
"_" => Term::AnonVar
"_" => Term::AnonVar
};
Var : Var = {

File diff suppressed because it is too large Load Diff