From: Mark Thom Date: Sat, 14 Apr 2018 00:44:23 +0000 (-0600) Subject: update/extension to lexer, bug fixes to module importing X-Git-Tag: v0.8.110~494 X-Git-Url: https://git.sagredo.dev/?a=commitdiff_plain;h=ff6e58e987c01edbd7684bf947f512f393dfd0d3;p=scryer-prolog.git update/extension to lexer, bug fixes to module importing --- diff --git a/Cargo.lock b/Cargo.lock index 2083ddcf..fd2b7642 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,11 +1,3 @@ -[[package]] -name = "aho-corasick" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "bitflags" version = "0.7.0" @@ -32,29 +24,11 @@ dependencies = [ "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "lazy_static" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "lazy_static" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "libc" version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "memchr" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "num" version = "0.1.41" @@ -152,23 +126,6 @@ dependencies = [ "redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "regex" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "regex-syntax" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "rustc-serialize" version = "0.3.24" @@ -176,13 +133,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "rusty-wam" -version = "0.7.6" +version = "0.7.7" dependencies = [ "downcast 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "ordered-float 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -196,15 +151,6 @@ dependencies = [ "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "thread_local" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "unreachable" version = "0.1.1" @@ -213,34 +159,17 @@ dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "unreachable" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "utf8-ranges" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] -"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" "checksum downcast 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf6d87c251688702a52991eaebd07c13eabba9cc0b7f8c31ef94dc881be706e8" "checksum fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f6c0581a4e363262e52b87f59ee2afe3415361c6ec35e665924eb08afe8ff159" "checksum fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43f3795b4bae048dc6123a6b972cadde2e676f9ded08aef6bb77f5f157684a82" -"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" -"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" "checksum libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)" = "36fbc8a8929c632868295d0178dd8f63fc423fd7537ad0738372bd010b3ac9b0" -"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" "checksum num 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cc4083e14b542ea3eb9b5f33ff48bd373a92d78687e74f4cc0a30caeb754f0ca" "checksum num-bigint 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "bdc1494b5912f088f260b775799468d9b9209ac60885d8186a547a0476289e23" "checksum num-complex 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "58de7b4bf7cf5dbecb635a5797d489864eadd03b107930cbccf9e0fd7428b47c" @@ -252,12 +181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rand 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "6475140dfd8655aeb72e1fd4b7a1cc1c202be65d71669476e392fe62532b9edd" "checksum redox_syscall 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ab105df655884ede59d45b7070c8a65002d921461ee813a024558ca16030eea0" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -"checksum regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ac6ab4e9218ade5b423358bbd2567d1617418403c7a512603630181813316322" -"checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" -"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" -"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" diff --git a/Cargo.toml b/Cargo.toml index bf3be3bc..3e19297a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,10 @@ [package] name = "rusty-wam" -version = "0.7.6" +version = "0.7.7" authors = ["Mark Thom"] [dependencies] -lazy_static = "0.2" num = "0.1" -regex = "0.2.1" ordered-float = "0.5.0" downcast = "0.9.1" diff --git a/README.md b/README.md index 3c74cb18..c1722354 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # rusty-wam -rusty-wam aims to become to Prolog what GHC is to Haskell: an open +rusty-wam aims to become to ISO Prolog what GHC is to Haskell: an open source industrial strength production environment that is also a testbed for bleeding edge research in logic and constraint programming, which is itself written in a high-level language. diff --git a/src/main.rs b/src/main.rs index cde9536e..da05f866 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -#[macro_use] extern crate lazy_static; #[macro_use] extern crate downcast; extern crate termion; diff --git a/src/prolog/ast.rs b/src/prolog/ast.rs index fcb3356e..c2b3d6c6 100644 --- a/src/prolog/ast.rs +++ b/src/prolog/ast.rs @@ -10,14 +10,11 @@ use std::collections::{BTreeSet, HashMap, VecDeque}; use std::fmt; use std::hash::{Hash, Hasher}; use std::io::Error as IOError; -use std::num::{ParseFloatError}; use std::ops::{Add, AddAssign, Div, Index, IndexMut, Sub, Mul, Neg}; use std::rc::Rc; use std::str::Utf8Error; use std::vec::Vec; -pub const LEXER_BUF_SIZE: usize = 4096; - pub type Atom = String; pub type Var = String; @@ -461,17 +458,13 @@ pub enum ArithmeticError { UninstantiatedVar } -/* 'TokenTooLong' is hard to detect reliably if we don't process the -input one character at a time. It would be easy to detect if the regex -library supported matching on iterator inputs, but it currently does -not. This is fine, mostly; the typical Prolog program will not contain -tokens exceeding 4096 chars in length. */ - #[derive(Debug)] pub enum ParserError { Arithmetic(ArithmeticError), + BackQuotedString, BuiltInArityMismatch(&'static str), + UnexpectedChar(char), UnexpectedEOF, FailedMatch(String), IO(IOError), @@ -479,14 +472,14 @@ pub enum ParserError InadmissibleFact, InadmissibleQueryTerm, IncompleteReduction, - InconsistentEntry, // was InconsistentDeclaration. + InconsistentEntry, InvalidModuleDecl, InvalidModuleExport, InvalidRuleHead, InvalidUseModuleDecl, + MissingQuote, ParseBigInt, - ParseFloat(ParseFloatError), - // TokenTooLong, + ParseFloat, Utf8Conversion(Utf8Error) } @@ -508,12 +501,6 @@ impl From for ParserError { } } -impl From for ParserError { - fn from(err: ParseFloatError) -> ParserError { - ParserError::ParseFloat(err) - } -} - #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub enum Fixity { In, Post, Pre @@ -522,6 +509,7 @@ pub enum Fixity { #[derive(Clone, Eq, Hash, PartialEq)] pub enum Constant { Atom(ClauseName), + Char(char), Number(Number), String(Rc), Usize(usize), @@ -549,12 +537,14 @@ impl fmt::Display for Constant { match self { &Constant::Atom(ref atom) => write!(f, "{}", atom), + &Constant::Char(c) => + write!(f, "#\\{}", c), &Constant::EmptyList => write!(f, "[]"), &Constant::Number(ref n) => write!(f, "{}", n), &Constant::String(ref s) => - write!(f, "{}", s), + write!(f, "\"{}\"", s), &Constant::Usize(integer) => write!(f, "u{}", integer) } diff --git a/src/prolog/io.rs b/src/prolog/io.rs index 010306f0..85e5330c 100644 --- a/src/prolog/io.rs +++ b/src/prolog/io.rs @@ -4,7 +4,7 @@ use prolog::codegen::*; use prolog::debray_allocator::*; use prolog::heap_print::*; use prolog::machine::*; -use prolog::parser::toplevel::*; +use prolog::toplevel::*; use termion::raw::IntoRawMode; use termion::input::TermRead; @@ -13,7 +13,6 @@ use termion::event::Key; use std::io::{Write, stdin, stdout}; use std::fmt; - impl fmt::Display for IndexPtr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -472,8 +471,7 @@ impl TLInfo for DeclInfo { let (name, arity) = (self.name.clone(), self.arity); if entry.0.get() == IndexPtr::Undefined { - if &name == n1 && arity == a1 { - // *entry = default(); // implement logical view update semantics. + if &name == n1 && arity == a1 { entry.0.set(IndexPtr::Index(code_size)); } } @@ -673,14 +671,18 @@ pub fn compile_listing(wam: &mut Machine, src_str: &str) -> EvalSession }); let module_name = get_module_name(&module); + let decl_info = DeclInfo { name, arity: decl.arity(), + module_name: module_name.clone() }; - let decl_info = DeclInfo { name, arity: decl.arity(), module_name }; - decl_info.label_clauses(p, &mut code_dir, &mut decl_code); + { + let index = code_dir.entry((decl_info.name.clone(), decl_info.arity)) + .or_insert(CodeIndex::default()); + index.0.set(IndexPtr::Index(p)); + } + + decl_info.label_clauses(p, &mut code_dir, &mut decl_code); code.extend(decl_code.into_iter()); - - let index = CodeIndex::default(); - code_dir.insert((decl_info.name.clone(), decl_info.arity), index); } } } diff --git a/src/prolog/mod.rs b/src/prolog/mod.rs index a5b6d87b..954417f9 100644 --- a/src/prolog/mod.rs +++ b/src/prolog/mod.rs @@ -8,6 +8,7 @@ pub mod macros; pub mod ast; #[macro_use] pub mod allocator; +pub mod toplevel; pub mod arithmetic; pub mod builtins; pub mod codegen; diff --git a/src/prolog/parser b/src/prolog/parser index 1d06c7d2..c413cc46 160000 --- a/src/prolog/parser +++ b/src/prolog/parser @@ -1 +1 @@ -Subproject commit 1d06c7d2e51315560751c653522979caa37be227 +Subproject commit c413cc4651052990a518b9f358c21add95556ddd diff --git a/src/prolog/toplevel.rs b/src/prolog/toplevel.rs new file mode 100644 index 00000000..a16d314b --- /dev/null +++ b/src/prolog/toplevel.rs @@ -0,0 +1,629 @@ +use prolog::ast::*; +use prolog::num::*; +use prolog::parser::parser::*; +use prolog::tabled_rc::*; + +use std::collections::{HashSet, VecDeque}; +use std::cell::Cell; +use std::io::Read; +use std::mem; +use std::rc::Rc; + +fn setup_fact(term: Term) -> Result +{ + match term { + Term::Clause(..) | Term::Constant(_, Constant::Atom(_)) => + Ok(term), + _ => + Err(ParserError::InadmissibleFact) + } +} + +fn setup_op_decl(mut terms: Vec>) -> Result +{ + let name = match *terms.pop().unwrap() { + Term::Constant(_, Constant::Atom(name)) => name, + _ => return Err(ParserError::InconsistentEntry) + }; + + let spec = match *terms.pop().unwrap() { + Term::Constant(_, Constant::Atom(name)) => name, + _ => return Err(ParserError::InconsistentEntry) + }; + + let prec = match *terms.pop().unwrap() { + Term::Constant(_, Constant::Number(Number::Integer(bi))) => + match bi.to_usize() { + Some(n) if n <= 1200 => n, + _ => return Err(ParserError::InconsistentEntry) + }, + _ => return Err(ParserError::InconsistentEntry) + }; + + match spec.as_str() { + "xfx" => Ok(OpDecl(prec, XFX, name)), + "xfy" => Ok(OpDecl(prec, XFY, name)), + "yfx" => Ok(OpDecl(prec, YFX, name)), + "fx" => Ok(OpDecl(prec, FX, name)), + "fy" => Ok(OpDecl(prec, FY, name)), + "xf" => Ok(OpDecl(prec, XF, name)), + "yf" => Ok(OpDecl(prec, YF, name)), + _ => Err(ParserError::InconsistentEntry) + } +} + +fn setup_predicate_export(mut term: Term) -> Result +{ + match term { + Term::Clause(_, ref name, ref mut terms, Some(Fixity::In)) + if name.as_str() == "/" && terms.len() == 2 => { + let arity = *terms.pop().unwrap(); + let name = *terms.pop().unwrap(); + + let arity = arity.to_constant().and_then(|c| c.to_integer()) + .and_then(|n| if !n.is_negative() { n.to_usize() } else { None }) + .ok_or(ParserError::InvalidModuleExport)?; + + let name = name.to_constant().and_then(|c| c.to_atom()) + .ok_or(ParserError::InvalidModuleExport)?; + + Ok((name, arity)) + }, + _ => Err(ParserError::InvalidModuleExport) + } +} + +fn setup_module_decl(mut terms: Vec>) -> Result +{ + let mut export_list = *terms.pop().unwrap(); + let name = terms.pop().unwrap().to_constant().and_then(|c| c.to_atom()) + .ok_or(ParserError::InvalidModuleDecl)?; + + let mut exports = Vec::new(); + + while let Term::Cons(_, t1, t2) = export_list { + exports.push(setup_predicate_export(*t1)?); + export_list = *t2; + } + + if export_list.to_constant() != Some(Constant::EmptyList) { + Err(ParserError::InvalidModuleDecl) + } else { + Ok(ModuleDecl { name, exports }) + } +} + +fn setup_use_module_decl(mut terms: Vec>) -> Result +{ + match *terms.pop().unwrap() { + Term::Clause(_, ref name, ref mut terms, None) + if name.as_str() == "library" && terms.len() == 1 => { + terms.pop().unwrap().to_constant() + .and_then(|c| c.to_atom()) + .ok_or(ParserError::InvalidUseModuleDecl) + }, + _ => Err(ParserError::InvalidUseModuleDecl) + } +} + +type UseModuleExport = (ClauseName, Vec); + +fn setup_qualified_import(mut terms: Vec>) -> Result +{ + let mut export_list = *terms.pop().unwrap(); + let name = match *terms.pop().unwrap() { + Term::Clause(_, ref name, ref mut terms, None) + if name.as_str() == "library" && terms.len() == 1 => { + terms.pop().unwrap().to_constant() + .and_then(|c| c.to_atom()) + .ok_or(ParserError::InvalidUseModuleDecl) + }, + _ => Err(ParserError::InvalidUseModuleDecl) + }?; + + let mut exports = Vec::new(); + + while let Term::Cons(_, t1, t2) = export_list { + exports.push(setup_predicate_export(*t1)?); + export_list = *t2; + } + + if export_list.to_constant() != Some(Constant::EmptyList) { + Err(ParserError::InvalidModuleDecl) + } else { + Ok((name, exports)) + } +} + +fn setup_declaration(term: Term) -> Result +{ + match term { + Term::Clause(_, name, terms, _) => + if name.as_str() == "op" && terms.len() == 3 { + Ok(Declaration::Op(setup_op_decl(terms)?)) + } else if name.as_str() == "module" && terms.len() == 2 { + Ok(Declaration::Module(setup_module_decl(terms)?)) + } else if name.as_str() == "use_module" && terms.len() == 1 { + Ok(Declaration::UseModule(setup_use_module_decl(terms)?)) + } else if name.as_str() == "use_module" && terms.len() == 2 { + let (name, exports) = setup_qualified_import(terms)?; + Ok(Declaration::UseQualifiedModule(name, exports)) + } else { + Err(ParserError::InconsistentEntry) + }, + _ => return Err(ParserError::InconsistentEntry) + } +} + +fn is_consistent(tl: &TopLevel, clauses: &Vec) -> bool +{ + match clauses.first() { + Some(ref cl) => tl.name() == cl.name() && tl.arity() == cl.arity(), + None => true + } +} + +pub fn deque_to_packet(head: TopLevel, deque: VecDeque) -> TopLevelPacket +{ + match head { + TopLevel::Query(query) => TopLevelPacket::Query(query, Vec::from(deque)), + tl => TopLevelPacket::Decl(tl, Vec::from(deque)) + } +} + +pub fn merge_clauses(tls: &mut VecDeque) -> Result +{ + let mut clauses: Vec = vec![]; + + while let Some(tl) = tls.pop_front() { + match tl { + TopLevel::Query(_) if clauses.is_empty() && tls.is_empty() => + return Ok(tl), + TopLevel::Declaration(_) if clauses.is_empty() => + return Ok(tl), + TopLevel::Query(_) => + return Err(ParserError::InconsistentEntry), + TopLevel::Fact(_) if is_consistent(&tl, &clauses) => + if let TopLevel::Fact(fact) = tl { + let clause = PredicateClause::Fact(fact); + clauses.push(clause); + }, + TopLevel::Rule(_) if is_consistent(&tl, &clauses) => + if let TopLevel::Rule(rule) = tl { + let clause = PredicateClause::Rule(rule); + clauses.push(clause); + }, + TopLevel::Predicate(_) if is_consistent(&tl, &clauses) => + if let TopLevel::Predicate(pred) = tl { + clauses.extend(pred.clauses().into_iter()) + }, + _ => { + tls.push_front(tl); + break; + } + } + } + + if clauses.is_empty() { + Err(ParserError::InconsistentEntry) + } else { + Ok(TopLevel::Predicate(Predicate(clauses))) + } +} + +fn unfold_by_str_once(term: &mut Term, s: &str) -> Option<(Term, Term)> +{ + if let &mut Term::Clause(_, ref name, ref mut subterms, _) = term { + if name.as_str() == s && subterms.len() == 2 { + let snd = *subterms.pop().unwrap(); + let fst = *subterms.pop().unwrap(); + + return Some((fst, snd)); + } + } + + None +} + +fn unfold_by_str(mut term: Term, s: &str) -> Vec +{ + let mut terms = vec![]; + + while let Some((fst, snd)) = unfold_by_str_once(&mut term, s) { + terms.push(fst); + term = snd; + } + + terms.push(term); + terms +} + +fn fold_by_str(mut terms: Vec, mut term: Term, sym: ClauseName) -> Term +{ + while let Some(prec) = terms.pop() { + term = Term::Clause(Cell::default(), sym.clone(), + vec![Box::new(prec), Box::new(term)], + None); + } + + term +} + +fn mark_cut_variables_as(terms: &mut Vec, name: ClauseName) { + for term in terms.iter_mut() { + match term { + &mut Term::Constant(_, Constant::Atom(ref mut var)) if var.as_str() == "!" => + *var = name.clone(), + _ => {} + } + } +} + +fn mark_cut_variable(term: &mut Term) -> bool { + let cut_var_found = match term { + &mut Term::Constant(_, Constant::Atom(ref var)) if var.as_str() == "!" => true, + _ => false + }; + + if cut_var_found { + *term = Term::Var(Cell::default(), rc_atom!("!")); + true + } else { + false + } +} + +fn mark_cut_variables(terms: &mut Vec) -> bool { + let mut found_cut_var = false; + + for item in terms.iter_mut() { + found_cut_var = mark_cut_variable(item); + } + + found_cut_var +} + +pub enum TopLevelPacket { + Query(Vec, Vec), + Decl(TopLevel, Vec) +} + +struct RelationWorker { + queue: VecDeque> +} + +impl RelationWorker { + fn new() -> Self { + RelationWorker { queue: VecDeque::new() } + } + + fn compute_head(&self, term: &Term) -> Vec + { + let mut vars = HashSet::new(); + + for term in term.post_order_iter() { + if let TermRef::Var(_, _, v) = term { + vars.insert(v.clone()); + } + } + + vars.insert(rc_atom!("!")); + vars.into_iter() + .map(|v| Term::Var(Cell::default(), v)) + .collect() + } + + fn fabricate_rule_body(&self, vars: &Vec, body_term: Term) -> Term + { + let vars_of_head = vars.iter().cloned().map(Box::new).collect(); + let head_term = Term::Clause(Cell::default(), clause_name!(""), vars_of_head, None); + + let rule = vec![Box::new(head_term), Box::new(body_term)]; + let turnstile = clause_name!(":-"); + + Term::Clause(Cell::default(), turnstile, rule, None) + } + + // the terms form the body of the rule. We create a head, by + // gathering variables from the body of terms and recording them + // in the head clause. + fn fabricate_rule(&self, body_term: Term) -> (JumpStub, VecDeque) + { + // collect the vars of body_term into a head, return the num_vars + // (the arity) as well. + let vars = self.compute_head(&body_term); + let rule = self.fabricate_rule_body(&vars, body_term); + + (vars, VecDeque::from(vec![rule])) + } + + fn fabricate_disjunct(&self, body_term: Term) -> (JumpStub, VecDeque) + { + let mut cut_var_found = false; + + let mut vars = self.compute_head(&body_term); + let clauses: Vec<_> = unfold_by_str(body_term, ";").into_iter() + .map(|term| { + let mut subterms = unfold_by_str(term, ","); + cut_var_found = mark_cut_variables(&mut subterms); + + let term = subterms.pop().unwrap(); + fold_by_str(subterms, term, clause_name!(",")) + }).collect(); + + if cut_var_found { + vars.push(Term::Var(Cell::default(), rc_atom!("!"))); + } + + let results = clauses.into_iter() + .map(|clause| self.fabricate_rule_body(&vars, clause)) + .collect(); + + (vars, results) + } + + fn fabricate_if_then(&self, prec: Term, conq: Term) -> (JumpStub, VecDeque) + { + let mut prec_seq = unfold_by_str(prec, ","); + let comma_sym = clause_name!(","); + let cut_sym = atom!("!"); + + prec_seq.push(Term::Constant(Cell::default(), cut_sym)); + + mark_cut_variables_as(&mut prec_seq, clause_name!("blocked_!")); + + let mut conq_seq = unfold_by_str(conq, ","); + + mark_cut_variables(&mut conq_seq); + prec_seq.extend(conq_seq.into_iter()); + + let back_term = Box::new(prec_seq.pop().unwrap()); + let front_term = Box::new(prec_seq.pop().unwrap()); + + let body_term = Term::Clause(Cell::default(), comma_sym.clone(), + vec![front_term, back_term], None); + + self.fabricate_rule(fold_by_str(prec_seq, body_term, comma_sym)) + } + + fn to_query_term(&mut self, term: Term) -> Result + { + match term { + Term::Constant(r, Constant::Atom(name)) => + if name.as_str() == "!" || name.as_str() == "blocked_!" { + Ok(QueryTerm::BlockedCut) + } else { + Ok(QueryTerm::Clause(r, ClauseType::Named(name, CodeIndex::default()), + vec![])) + }, + Term::Var(_, ref v) if v.as_str() == "!" => + Ok(QueryTerm::UnblockedCut(Cell::default())), + Term::Clause(r, name, mut terms, fixity) => + if let Some(inlined_ct) = InlinedClauseType::from(name.as_str(), terms.len()) { + Ok(QueryTerm::Clause(r, ClauseType::Inlined(inlined_ct), terms)) + } else if name.as_str() == ";" { + if terms.len() == 2 { + let term = Term::Clause(r, name.clone(), terms, fixity); + let (stub, clauses) = self.fabricate_disjunct(term); + + self.queue.push_back(clauses); + Ok(QueryTerm::Jump(stub)) + } else { + Err(ParserError::BuiltInArityMismatch(";")) + } + } else if name.as_str() == "->" && terms.len() == 2 { + if terms.len() == 2 { + let conq = *terms.pop().unwrap(); + let prec = *terms.pop().unwrap(); + let (stub, clauses) = self.fabricate_if_then(prec, conq); + + self.queue.push_back(clauses); + Ok(QueryTerm::Jump(stub)) + } else { + Err(ParserError::BuiltInArityMismatch("->")) + } + } else { + Ok(QueryTerm::Clause(Cell::default(), + ClauseType::from(name, terms.len(), fixity), + terms)) + }, + Term::Var(_, _) => + Ok(QueryTerm::Clause(Cell::default(), ClauseType::CallN, vec![Box::new(term)])), + _ => + Err(ParserError::InadmissibleQueryTerm) + } + } + + // never blocks cuts in the consequent. + fn prepend_if_then(&self, prec: Term, conq: Term, queue: &mut VecDeque>, + blocks_cuts: bool) + { + let cut_symb = atom!("blocked_!"); + let mut terms_seq = unfold_by_str(prec, ","); + + terms_seq.push(Term::Constant(Cell::default(), cut_symb)); + + let mut conq_seq = unfold_by_str(conq, ","); + + if !blocks_cuts { + for item in conq_seq.iter_mut() { + mark_cut_variable(item); + } + } + + terms_seq.append(&mut conq_seq); + + while let Some(term) = terms_seq.pop() { + queue.push_front(Box::new(term)); + } + } + + fn setup_query(&mut self, terms: Vec>, blocks_cuts: bool) + -> Result, ParserError> + { + let mut query_terms = vec![]; + let mut work_queue = VecDeque::from(terms); + + while let Some(term) = work_queue.pop_front() { + let mut term = *term; + + // a (->) clause makes up the entire query. That's what the test confirms. + if query_terms.is_empty() && work_queue.is_empty() { + // check for ->, inline it if found. + if let &mut Term::Clause(_, ref name, ref mut subterms, _) = &mut term { + if name.as_str() == "->" && subterms.len() == 2 { + let conq = *subterms.pop().unwrap(); + let prec = *subterms.pop().unwrap(); + + self.prepend_if_then(prec, conq, &mut work_queue, blocks_cuts); + continue; + } + } + } + + for mut subterm in unfold_by_str(term, ",") { + if !blocks_cuts { + mark_cut_variable(&mut subterm); + } + + query_terms.push(try!(self.to_query_term(subterm))); + } + } + + Ok(query_terms) + } + + fn setup_rule(&mut self, mut terms: Vec>, blocks_cuts: bool) + -> Result + { + let post_head_terms = terms.drain(1..).collect(); + let mut query_terms = try!(self.setup_query(post_head_terms, blocks_cuts)); + let clauses = query_terms.drain(1 ..).collect(); + let qt = query_terms.pop().unwrap(); + + match *terms.pop().unwrap() { + Term::Clause(_, name, terms, _) => + Ok(Rule { head: (name, terms, qt), clauses }), + Term::Constant(_, Constant::Atom(name)) => + Ok(Rule { head: (name, vec![], qt), clauses }), + _ => Err(ParserError::InvalidRuleHead) + } + } + + + pub fn try_term_to_tl(&mut self, term: Term, blocks_cuts: bool) -> Result + { + match term { + Term::Clause(r, name, mut terms, fixity) => + if name.as_str() == "?-" { + Ok(TopLevel::Query(try!(self.setup_query(terms, blocks_cuts)))) + } else if name.as_str() == ":-" && terms.len() > 1 { + Ok(TopLevel::Rule(try!(self.setup_rule(terms, blocks_cuts)))) + } else if name.as_str() == ":-" && terms.len() == 1 { + let term = *terms.pop().unwrap(); + Ok(TopLevel::Declaration(try!(setup_declaration(term)))) + } else { + Ok(TopLevel::Fact(try!(setup_fact(Term::Clause(r, name, terms, fixity))))) + }, + term => Ok(TopLevel::Fact(try!(setup_fact(term)))) + } + } + + fn try_terms_to_tls(&mut self, terms: Iter, blocks_cuts: bool) + -> Result, ParserError> + where Iter: IntoIterator + { + let mut results = VecDeque::new(); + + for term in terms.into_iter() { + results.push_back(self.try_term_to_tl(term, blocks_cuts)?); + } + + Ok(results) + } + + fn parse_queue(&mut self) -> Result, ParserError> + { + let mut queue = VecDeque::new(); + + while let Some(terms) = self.queue.pop_front() { + let clauses = merge_clauses(&mut self.try_terms_to_tls(terms, false)?)?; + queue.push_back(clauses); + } + + Ok(queue) + } + + fn absorb(&mut self, other: RelationWorker) { + self.queue.extend(other.queue.into_iter()); + } +} + +pub struct TopLevelWorker where R: Read { + pub parser: Parser +} + +impl TopLevelWorker { + pub fn new(inner: R, atom_tbl: TabledData) -> Self { + TopLevelWorker { parser: Parser::new(inner, atom_tbl) } + } + + pub fn parse_batch(&mut self, op_dir: &mut OpDir) -> Result, EvalError> + { + let mut preds = vec![]; + let mut mod_name = clause_name!("user"); + let mut results = vec![]; + let mut rel_worker = RelationWorker::new(); + + fn append_preds(preds: &mut Vec) -> TopLevel { + let preds = mem::replace(preds, vec![]); + TopLevel::Predicate(Predicate(preds)) + } + + while !self.parser.eof() { + self.parser.reset(); // empty the parser stack of token descriptions. + let term = self.parser.read_term(&op_dir)?; + + let mut new_rel_worker = RelationWorker::new(); + let tl = new_rel_worker.try_term_to_tl(term, true)?; + + if !is_consistent(&tl, &preds) { + results.push(deque_to_packet(append_preds(&mut preds), rel_worker.parse_queue()?)); + } + + rel_worker.absorb(new_rel_worker); + + match tl { + TopLevel::Declaration(Declaration::Op(op_decl)) => { + op_decl.submit(mod_name.clone(), op_dir)?; + }, + TopLevel::Declaration(Declaration::Module(actual_mod)) => { + mod_name = actual_mod.name.clone(); + let tl = TopLevel::Declaration(Declaration::Module(actual_mod)); + results.push(TopLevelPacket::Decl(tl, vec![])); + }, + tl => preds.extend(tl.as_predicate().ok().unwrap().clauses().into_iter()) + }; + } + + results.push(deque_to_packet(append_preds(&mut preds), rel_worker.parse_queue()?)); + Ok(results) + } + + pub fn parse_code(&mut self, op_dir: &OpDir) -> Result + { + let mut rel_worker = RelationWorker::new(); + + let terms = self.parser.read(op_dir)?; + let mut tls = rel_worker.try_terms_to_tls(terms, true)?; + let results = rel_worker.parse_queue()?; + + let tl = merge_clauses(&mut tls)?; + + if tls.is_empty() { + Ok(deque_to_packet(tl, results)) + } else { + Err(ParserError::InconsistentEntry) + } + } +}