clean up support for recursive calls, add support for (and protection of) built-in predicates.
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1,6 +1,6 @@
|
|||||||
[root]
|
[root]
|
||||||
name = "rusty-wam"
|
name = "rusty-wam"
|
||||||
version = "0.6.5"
|
version = "0.6.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lalrpop 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lalrpop 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lalrpop-util 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lalrpop-util 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "rusty-wam"
|
name = "rusty-wam"
|
||||||
version = "0.6.5"
|
version = "0.6.6"
|
||||||
authors = ["Mark Thom"]
|
authors = ["Mark Thom"]
|
||||||
|
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|||||||
@@ -292,6 +292,10 @@ impl IndexedChoiceInstruction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum BuiltInInstruction {
|
||||||
|
InternalCallN
|
||||||
|
}
|
||||||
|
|
||||||
pub enum ControlInstruction {
|
pub enum ControlInstruction {
|
||||||
Allocate(usize),
|
Allocate(usize),
|
||||||
Call(Atom, usize, usize),
|
Call(Atom, usize, usize),
|
||||||
@@ -359,6 +363,7 @@ pub type CompiledFact = Vec<FactInstruction>;
|
|||||||
pub type CompiledQuery = Vec<QueryInstruction>;
|
pub type CompiledQuery = Vec<QueryInstruction>;
|
||||||
|
|
||||||
pub enum Line {
|
pub enum Line {
|
||||||
|
BuiltIn(BuiltInInstruction),
|
||||||
Choice(ChoiceInstruction),
|
Choice(ChoiceInstruction),
|
||||||
Control(ControlInstruction),
|
Control(ControlInstruction),
|
||||||
Cut(CutInstruction),
|
Cut(CutInstruction),
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ pub struct CodeGenerator<'a, TermMarker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum EvalSession<'a> {
|
pub enum EvalSession<'a> {
|
||||||
EntryFailure,
|
EntryFailure(String),
|
||||||
EntrySuccess,
|
EntrySuccess,
|
||||||
InitialQuerySuccess(AllocVarDict<'a>, HeapVarDict<'a>),
|
InitialQuerySuccess(AllocVarDict<'a>, HeapVarDict<'a>),
|
||||||
QueryFailure,
|
QueryFailure,
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ fn is_consistent(predicate: &Vec<PredicateClause>) -> bool {
|
|||||||
pub fn print_code(code: &Code) {
|
pub fn print_code(code: &Code) {
|
||||||
for clause in code {
|
for clause in code {
|
||||||
match clause {
|
match clause {
|
||||||
|
&Line::BuiltIn(_) => {},
|
||||||
&Line::Fact(ref fact) =>
|
&Line::Fact(ref fact) =>
|
||||||
for fact_instr in fact {
|
for fact_instr in fact {
|
||||||
println!("{}", fact_instr);
|
println!("{}", fact_instr);
|
||||||
@@ -300,38 +301,30 @@ pub fn eval<'a, 'b: 'a>(wam: &'a mut Machine, tl: &'b TopLevel) -> EvalSession<'
|
|||||||
|
|
||||||
if is_consistent(clauses) {
|
if is_consistent(clauses) {
|
||||||
let compiled_pred = cg.compile_predicate(clauses);
|
let compiled_pred = cg.compile_predicate(clauses);
|
||||||
wam.add_predicate(clauses, compiled_pred);
|
wam.add_predicate(clauses, compiled_pred)
|
||||||
|
|
||||||
EvalSession::EntrySuccess
|
|
||||||
} else {
|
} else {
|
||||||
let msg = r"Error: predicate is inconsistent.
|
let msg = r"Error: predicate is inconsistent.
|
||||||
Each predicate must have the same name and arity.";
|
Each predicate must have the same name and arity.";
|
||||||
|
|
||||||
println!("{}", msg);
|
EvalSession::EntryFailure(String::from(msg))
|
||||||
EvalSession::EntryFailure
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
&TopLevel::Fact(ref fact) => {
|
&TopLevel::Fact(ref fact) => {
|
||||||
let mut cg = CodeGenerator::<DebrayAllocator>::new();
|
let mut cg = CodeGenerator::<DebrayAllocator>::new();
|
||||||
|
|
||||||
let compiled_fact = cg.compile_fact(fact);
|
let compiled_fact = cg.compile_fact(fact);
|
||||||
wam.add_fact(fact, compiled_fact);
|
wam.add_fact(fact, compiled_fact)
|
||||||
|
|
||||||
EvalSession::EntrySuccess
|
|
||||||
},
|
},
|
||||||
&TopLevel::Rule(ref rule) => {
|
&TopLevel::Rule(ref rule) => {
|
||||||
let mut cg = CodeGenerator::<DebrayAllocator>::new();
|
let mut cg = CodeGenerator::<DebrayAllocator>::new();
|
||||||
|
|
||||||
let compiled_rule = cg.compile_rule(rule);
|
let compiled_rule = cg.compile_rule(rule);
|
||||||
wam.add_rule(rule, compiled_rule);
|
wam.add_rule(rule, compiled_rule)
|
||||||
|
|
||||||
EvalSession::EntrySuccess
|
|
||||||
},
|
},
|
||||||
&TopLevel::Query(ref query) => {
|
&TopLevel::Query(ref query) => {
|
||||||
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -393,6 +386,7 @@ pub fn print(wam: &mut Machine, result: EvalSession) {
|
|||||||
write!(stdout(), ".\n").unwrap();
|
write!(stdout(), ".\n").unwrap();
|
||||||
},
|
},
|
||||||
EvalSession::QueryFailure => println!("false."),
|
EvalSession::QueryFailure => println!("false."),
|
||||||
|
EvalSession::EntryFailure(msg) => println!("{}", msg),
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,17 +15,13 @@ enum MachineMode {
|
|||||||
Write
|
Write
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: probably.. the wrong solution. should integrate deeply with the WAM.
|
struct MachineState {
|
||||||
//type SpecialHandler<'a> = fn(&'a mut MachineState, bool, usize);
|
|
||||||
|
|
||||||
struct MachineState { //<'a> {
|
|
||||||
h: usize,
|
h: usize,
|
||||||
s: usize,
|
s: usize,
|
||||||
p: CodePtr,
|
p: CodePtr,
|
||||||
b: usize,
|
b: usize,
|
||||||
b0: usize,
|
b0: usize,
|
||||||
e: usize,
|
e: usize,
|
||||||
//special_handlers: HashMap<&'a Atom, SpecialHandler<'a>>,
|
|
||||||
num_of_args: usize,
|
num_of_args: usize,
|
||||||
cp: CodePtr,
|
cp: CodePtr,
|
||||||
fail: bool,
|
fail: bool,
|
||||||
@@ -37,9 +33,18 @@ struct MachineState { //<'a> {
|
|||||||
trail: Vec<Ref>,
|
trail: Vec<Ref>,
|
||||||
tr: usize,
|
tr: usize,
|
||||||
hb: usize,
|
hb: usize,
|
||||||
|
lco: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type CodeDir = HashMap<(Atom, usize), usize>;
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
enum PredicateKeyType {
|
||||||
|
BuiltIn,
|
||||||
|
User
|
||||||
|
}
|
||||||
|
|
||||||
|
type PredicateKey = (Atom, usize); // name, arity, type.
|
||||||
|
|
||||||
|
type CodeDir = HashMap<PredicateKey, (PredicateKeyType, usize)>;
|
||||||
|
|
||||||
pub struct Machine {
|
pub struct Machine {
|
||||||
ms: MachineState,
|
ms: MachineState,
|
||||||
@@ -92,10 +97,19 @@ impl Index<CodePtr> for Machine {
|
|||||||
|
|
||||||
impl Machine {
|
impl Machine {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
let mut code_dir = HashMap::new();
|
||||||
|
let code = vec![Line::BuiltIn(BuiltInInstruction::InternalCallN)];
|
||||||
|
|
||||||
|
// there are 64 registers in the VM, so call/N is defined for all 0 <= N <= 63
|
||||||
|
// (an extra register is needed for the predicate name)
|
||||||
|
for arity in 0 .. 64 {
|
||||||
|
code_dir.insert((String::from("call"), arity), (PredicateKeyType::BuiltIn, 0));
|
||||||
|
}
|
||||||
|
|
||||||
Machine {
|
Machine {
|
||||||
ms: MachineState::new(),
|
ms: MachineState::new(),
|
||||||
code: Vec::new(),
|
code: code,
|
||||||
code_dir: HashMap::new(),
|
code_dir: code_dir,
|
||||||
cached_query: None
|
cached_query: None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +118,22 @@ impl Machine {
|
|||||||
self.ms.fail
|
self.ms.fail
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_fact(&mut self, fact: &Term, mut code: Code) {
|
fn add_user_code<'a>(&mut self, name: Atom, arity: usize, offset: usize) -> EvalSession<'a>
|
||||||
|
{
|
||||||
|
match self.code_dir.get(&(name.clone(), arity)) {
|
||||||
|
Some(&(PredicateKeyType::BuiltIn, _)) =>
|
||||||
|
return EvalSession::EntryFailure(format!("error: cannot replace built-in predicate {}/{}",
|
||||||
|
name,
|
||||||
|
arity)),
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.code_dir.insert((name, arity), (PredicateKeyType::User, offset));
|
||||||
|
EvalSession::EntrySuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_fact<'a>(&mut self, fact: &Term, mut code: Code) -> EvalSession<'a>
|
||||||
|
{
|
||||||
if let Some(name) = fact.name() {
|
if let Some(name) = fact.name() {
|
||||||
let p = self.code.len();
|
let p = self.code.len();
|
||||||
|
|
||||||
@@ -112,11 +141,14 @@ impl Machine {
|
|||||||
let arity = fact.arity();
|
let arity = fact.arity();
|
||||||
|
|
||||||
self.code.append(&mut code);
|
self.code.append(&mut code);
|
||||||
self.code_dir.insert((name, arity), p);
|
self.add_user_code(name, arity, p)
|
||||||
|
} else {
|
||||||
|
EvalSession::EntryFailure(format!("error: the fact has no name."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_rule(&mut self, rule: &Rule, mut code: Code) {
|
pub fn add_rule<'a>(&mut self, rule: &Rule, mut code: Code) -> EvalSession<'a>
|
||||||
|
{
|
||||||
if let Some(name) = rule.head.0.name() {
|
if let Some(name) = rule.head.0.name() {
|
||||||
let p = self.code.len();
|
let p = self.code.len();
|
||||||
|
|
||||||
@@ -124,11 +156,14 @@ impl Machine {
|
|||||||
let arity = rule.head.0.arity();
|
let arity = rule.head.0.arity();
|
||||||
|
|
||||||
self.code.append(&mut code);
|
self.code.append(&mut code);
|
||||||
self.code_dir.insert((name, arity), p);
|
self.add_user_code(name, arity, p)
|
||||||
|
} else {
|
||||||
|
EvalSession::EntryFailure(format!("error: the rule has no name."))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_predicate(&mut self, clauses: &Vec<PredicateClause>, mut code: Code)
|
pub fn add_predicate<'a>(&mut self, clauses: &Vec<PredicateClause>, mut code: Code)
|
||||||
|
-> EvalSession<'a>
|
||||||
{
|
{
|
||||||
let p = self.code.len();
|
let p = self.code.len();
|
||||||
|
|
||||||
@@ -136,7 +171,7 @@ impl Machine {
|
|||||||
let name = clauses.first().unwrap().name().clone();
|
let name = clauses.first().unwrap().name().clone();
|
||||||
|
|
||||||
self.code.append(&mut code);
|
self.code.append(&mut code);
|
||||||
self.code_dir.insert((name, arity), p);
|
self.add_user_code(name, arity, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cached_query_size(&self) -> usize {
|
fn cached_query_size(&self) -> usize {
|
||||||
@@ -162,6 +197,8 @@ impl Machine {
|
|||||||
};
|
};
|
||||||
|
|
||||||
match instr {
|
match instr {
|
||||||
|
&Line::BuiltIn(ref builtin_instr) =>
|
||||||
|
self.ms.execute_builtin_instr(&self.code_dir, builtin_instr),
|
||||||
&Line::Choice(ref choice_instr) =>
|
&Line::Choice(ref choice_instr) =>
|
||||||
self.ms.execute_choice_instr(choice_instr),
|
self.ms.execute_choice_instr(choice_instr),
|
||||||
&Line::Cut(ref cut_instr) =>
|
&Line::Cut(ref cut_instr) =>
|
||||||
@@ -412,7 +449,8 @@ impl MachineState {
|
|||||||
registers: vec![Addr::HeapCell(0); 64],
|
registers: vec![Addr::HeapCell(0); 64],
|
||||||
trail: Vec::new(),
|
trail: Vec::new(),
|
||||||
tr: 0,
|
tr: 0,
|
||||||
hb: 0
|
hb: 0,
|
||||||
|
lco: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,12 +993,21 @@ impl MachineState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_call_predicate(&mut self, code_dir: &CodeDir, name: Atom, arity: usize)
|
fn try_call_predicate(&mut self, code_dir: &CodeDir, name: Atom, arity: usize, lco: bool)
|
||||||
{
|
{
|
||||||
let compiled_tl_index = code_dir.get(&(name, arity)).map(|index| *index);
|
let compiled_tl_index = code_dir.get(&(name, arity)).map(|index| index.1);
|
||||||
|
|
||||||
match compiled_tl_index {
|
match compiled_tl_index {
|
||||||
|
Some(compiled_tl_index) if lco => {
|
||||||
|
self.lco = true;
|
||||||
|
|
||||||
|
self.num_of_args = arity;
|
||||||
|
self.b0 = self.b;
|
||||||
|
self.p = CodePtr::DirEntry(compiled_tl_index);
|
||||||
|
},
|
||||||
Some(compiled_tl_index) => {
|
Some(compiled_tl_index) => {
|
||||||
|
self.lco = false;
|
||||||
|
|
||||||
self.cp = self.p + 1;
|
self.cp = self.p + 1;
|
||||||
self.num_of_args = arity;
|
self.num_of_args = arity;
|
||||||
self.b0 = self.b;
|
self.b0 = self.b;
|
||||||
@@ -970,61 +1017,29 @@ impl MachineState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_execute_predicate(&mut self, code_dir: &CodeDir, name: Atom, arity: usize)
|
fn handle_internal_call_n(&mut self, code_dir: &CodeDir)
|
||||||
{
|
{
|
||||||
let compiled_tl_index = code_dir.get(&(name, arity)).map(|index| *index);
|
let arity = self.num_of_args + 1;
|
||||||
|
let lco = self.lco;
|
||||||
|
let pred = self.registers[1].clone();
|
||||||
|
|
||||||
match compiled_tl_index {
|
for i in 2 .. arity {
|
||||||
Some(compiled_tl_index) => {
|
|
||||||
self.num_of_args = arity;
|
|
||||||
self.b0 = self.b;
|
|
||||||
self.p = CodePtr::DirEntry(compiled_tl_index);
|
|
||||||
},
|
|
||||||
None => self.fail = true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dispatch_call_n(&mut self,
|
|
||||||
code_dir: &CodeDir,
|
|
||||||
name: Atom,
|
|
||||||
is_call: bool,
|
|
||||||
arity: &mut usize,
|
|
||||||
narity: usize)
|
|
||||||
-> bool
|
|
||||||
{
|
|
||||||
if name == "call" {
|
|
||||||
let new_pred = self.registers[1].clone();
|
|
||||||
|
|
||||||
for i in 2 .. *arity + narity {
|
|
||||||
self.registers[i-1] = self.registers[i].clone();
|
self.registers[i-1] = self.registers[i].clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.registers[*arity + narity - 1] = new_pred;
|
if arity > 1 {
|
||||||
|
self.registers[arity - 1] = pred;
|
||||||
if *arity + narity - 1 > 0 {
|
self.execute_call_n(code_dir, arity - 1, lco);
|
||||||
*arity = *arity + narity - 1;
|
|
||||||
return true;
|
|
||||||
} else {
|
} else {
|
||||||
self.fail = true;
|
self.fail = true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_call {
|
fn execute_call_n(&mut self, code_dir: &CodeDir, arity: usize, lco: bool)
|
||||||
self.try_call_predicate(code_dir, name, *arity + narity - 1);
|
|
||||||
} else {
|
|
||||||
self.try_execute_predicate(code_dir, name, *arity + narity - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_call_n(&mut self, code_dir: &CodeDir, is_call: bool, mut arity: usize)
|
|
||||||
{
|
{
|
||||||
loop {
|
|
||||||
let addr = self.deref(self.registers[arity].clone());
|
let addr = self.deref(self.registers[arity].clone());
|
||||||
|
|
||||||
match self.store(addr) {
|
let (name, narity) = match self.store(addr) {
|
||||||
Addr::Str(a) => {
|
Addr::Str(a) => {
|
||||||
let result = self.heap[a].clone();
|
let result = self.heap[a].clone();
|
||||||
|
|
||||||
@@ -1037,20 +1052,20 @@ impl MachineState {
|
|||||||
self.registers[i] = self.heap[a + i].as_addr(a + i);
|
self.registers[i] = self.heap[a + i].as_addr(a + i);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.dispatch_call_n(code_dir, name, is_call, &mut arity, narity) {
|
(name, narity)
|
||||||
continue;
|
} else {
|
||||||
}
|
self.fail = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Addr::Con(Constant::Atom(name)) =>
|
Addr::Con(Constant::Atom(name)) => (name, 0),
|
||||||
if self.dispatch_call_n(code_dir, name, is_call, &mut arity, 0) {
|
_ => {
|
||||||
continue;
|
self.fail = true;
|
||||||
},
|
return;
|
||||||
_ => self.fail = true
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
break;
|
self.try_call_predicate(code_dir, name, arity + narity - 1, lco);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute_ctrl_instr(&mut self, code_dir: &CodeDir, instr: &ControlInstruction)
|
fn execute_ctrl_instr(&mut self, code_dir: &CodeDir, instr: &ControlInstruction)
|
||||||
@@ -1065,9 +1080,9 @@ impl MachineState {
|
|||||||
self.p += 1;
|
self.p += 1;
|
||||||
},
|
},
|
||||||
&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, false),
|
||||||
&ControlInstruction::CallN(arity) =>
|
&ControlInstruction::CallN(arity) =>
|
||||||
self.execute_call_n(code_dir, true, arity),
|
self.execute_call_n(code_dir, arity, false),
|
||||||
&ControlInstruction::Deallocate => {
|
&ControlInstruction::Deallocate => {
|
||||||
let e = self.e;
|
let e = self.e;
|
||||||
|
|
||||||
@@ -1077,9 +1092,9 @@ impl MachineState {
|
|||||||
self.p += 1;
|
self.p += 1;
|
||||||
},
|
},
|
||||||
&ControlInstruction::Execute(ref name, arity) =>
|
&ControlInstruction::Execute(ref name, arity) =>
|
||||||
self.try_execute_predicate(code_dir, name.clone(), arity),
|
self.try_call_predicate(code_dir, name.clone(), arity, true),
|
||||||
&ControlInstruction::ExecuteN(arity) =>
|
&ControlInstruction::ExecuteN(arity) =>
|
||||||
self.execute_call_n(code_dir, false, arity),
|
self.execute_call_n(code_dir, arity, true),
|
||||||
&ControlInstruction::Proceed =>
|
&ControlInstruction::Proceed =>
|
||||||
self.p = self.cp,
|
self.p = self.cp,
|
||||||
};
|
};
|
||||||
@@ -1171,6 +1186,13 @@ impl MachineState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn execute_builtin_instr(&mut self, code_dir: &CodeDir, instr: &BuiltInInstruction)
|
||||||
|
{
|
||||||
|
match instr {
|
||||||
|
&BuiltInInstruction::InternalCallN => self.handle_internal_call_n(code_dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn execute_choice_instr(&mut self, instr: &ChoiceInstruction)
|
fn execute_choice_instr(&mut self, instr: &ChoiceInstruction)
|
||||||
{
|
{
|
||||||
match instr {
|
match instr {
|
||||||
|
|||||||
Reference in New Issue
Block a user