From abcb53699a6eaeab41d974960ffd7e8648528f14 Mon Sep 17 00:00:00 2001 From: Mark Thom Date: Sun, 2 Sep 2018 23:32:51 -0600 Subject: [PATCH] finalize support for partial strings --- README.md | 23 ++++- src/prolog/ast.rs | 28 +++--- src/prolog/codegen.rs | 23 +++-- src/prolog/compile.rs | 12 +-- src/prolog/heap_iter.rs | 6 +- src/prolog/heap_print.rs | 5 +- src/prolog/iterators.rs | 9 +- src/prolog/machine/machine_state.rs | 15 ++- src/prolog/machine/machine_state_impl.rs | 120 +++++++++++++++++------ src/prolog/machine/mod.rs | 2 +- src/prolog/macros.rs | 8 +- src/prolog/string_list.rs | 45 ++++++++- src/prolog/toplevel.rs | 76 ++++++++------ src/tests.rs | 51 +++++++++- 14 files changed, 315 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index 6bc255bc..f0307d7c 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Extend rusty-wam to include the following, among other features: * Default representation of strings as list of chars, using a packed internal representation (_done_). - A representation of 'partial strings' as difference lists - of characters (_in progress_). + of characters (_done_). * `term_expansion/2` and `goal_expansion/2` (_in progress_). * Definite Clause Grammars. * Attributed variables using the SICStus Prolog interface and @@ -148,6 +148,7 @@ The following predicates are built-in to rusty-wam. * `ground/1` * `integer/1` * `is_list/1` +* `is_partial_string/1` * `keysort/2` * `length/2` * `maplist/2..9` @@ -155,6 +156,7 @@ The following predicates are built-in to rusty-wam. * `memberchk/2` * `nonvar/1` * `once/1` +* `partial_string/2` * `rational/1` * `read/1` * `repeat/0` @@ -252,6 +254,25 @@ true. New operators can be defined using the `op` declaration. +### Partial strings + +rusty-wam has two specialized, non-ISO builtin predicates for handling +so-called "partial strings". Partial strings imitate difference lists +of characters, but are much more space efficient. This efficiency +comes at the cost of full generality -- you cannot unify the tail +variables of two distinct partial strings, because their buffers will +always be distinct. + +If `X` is a free variable, the query + +`?- partial_string("abc", X), X = [a, b, c | Y], is_partial_string(X), +is_partial_string(Y).` + +will succeed. Further, if `Y` a free variable, unifying `Y` against +another string, "def" in this case, produces the equations + +`X = [a, b, c, d, e, f], Y = [d, e, f].` + ### Modules rusty-wam has a simple predicate-based module system. It provides a diff --git a/src/prolog/ast.rs b/src/prolog/ast.rs index d006dd66..3a51a086 100644 --- a/src/prolog/ast.rs +++ b/src/prolog/ast.rs @@ -545,11 +545,7 @@ impl PartialEq for Constant { (&Constant::Atom(ref atom), &Constant::Char(c)) | (&Constant::Char(c), &Constant::Atom(ref atom)) => { let s = atom.as_str(); - if c.len_utf8() != s.len() || Some(c) != s.chars().next() { - false - } else { - true - } + c.len_utf8() == s.len() && Some(c) == s.chars().next() }, (&Constant::Atom(ref a1), &Constant::Atom(ref a2)) => a1.as_str() == a2.as_str(), @@ -597,7 +593,7 @@ pub enum Term { #[derive(Clone, PartialEq)] pub enum InlinedClauseType { - CompareNumber(CompareNumberQT, ArithmeticTerm, ArithmeticTerm), + CompareNumber(CompareNumberQT, ArithmeticTerm, ArithmeticTerm), IsAtom(RegType), IsAtomic(RegType), IsCompound(RegType), @@ -606,7 +602,8 @@ pub enum InlinedClauseType { IsString(RegType), IsFloat(RegType), IsNonVar(RegType), - IsVar(RegType), + IsPartialString(RegType), + IsVar(RegType) } impl InlinedClauseType { @@ -621,7 +618,8 @@ impl InlinedClauseType { &InlinedClauseType::IsString(..) => "string", &InlinedClauseType::IsFloat (..) => "float", &InlinedClauseType::IsNonVar(..) => "nonvar", - &InlinedClauseType::IsVar(..) => "var" + &InlinedClauseType::IsPartialString(..) => "partial_string", + &InlinedClauseType::IsVar(..) => "var", } } @@ -654,6 +652,7 @@ impl InlinedClauseType { ("float", 1) => Some(InlinedClauseType::IsFloat(r1)), ("nonvar", 1) => Some(InlinedClauseType::IsNonVar(r1)), ("var", 1) => Some(InlinedClauseType::IsVar(r1)), + ("is_partial_string", 1) => Some(InlinedClauseType::IsPartialString(r1)), _ => None } } @@ -846,7 +845,7 @@ impl SystemClauseType { #[derive(Clone, PartialEq)] pub enum BuiltInClauseType { AcyclicTerm, - Arg, + Arg, Compare, CompareTerm(CompareTermQT), CyclicTerm, @@ -858,6 +857,7 @@ pub enum BuiltInClauseType { Is(RegType, ArithmeticTerm), KeySort, NotEq, + PartialString, Read, Sort, } @@ -963,6 +963,7 @@ impl BuiltInClauseType { &BuiltInClauseType::NotEq => clause_name!("\\=="), &BuiltInClauseType::Read => clause_name!("read"), &BuiltInClauseType::Sort => clause_name!("sort"), + &BuiltInClauseType::PartialString => clause_name!("partial_string") } } @@ -983,6 +984,7 @@ impl BuiltInClauseType { &BuiltInClauseType::NotEq => 2, &BuiltInClauseType::Read => 1, &BuiltInClauseType::Sort => 2, + &BuiltInClauseType::PartialString => 1, } } @@ -1007,7 +1009,7 @@ impl BuiltInClauseType { ("keysort", 2) => Some(BuiltInClauseType::KeySort), ("\\==", 2) => Some(BuiltInClauseType::NotEq), ("sort", 2) => Some(BuiltInClauseType::Sort), - ("read", 1) => Some(BuiltInClauseType::Read), + ("read", 1) => Some(BuiltInClauseType::Read), _ => None } } @@ -1291,15 +1293,15 @@ impl Number { NumberPair::Float(n1, n2) => pow_float(n1.into_inner(), n2.into_inner()), NumberPair::Rational(r1, r2) => { - if let (Some(f1), Some(f2)) = (rational_to_f64(&r1), rational_to_f64(&r2)) { + if let (Some(f1), Some(f2)) = (rational_to_f64(&r1), rational_to_f64(&r2)) { if let Ok(result) = pow_float(f1, f2) { return Ok(result); } } - + let root = rational_pow((*r1).clone(), (*r2).clone())?; Ok(Number::Rational(Rc::new(root))) - } + } } } diff --git a/src/prolog/codegen.rs b/src/prolog/codegen.rs index 0bb4a74f..3c8d40a7 100644 --- a/src/prolog/codegen.rs +++ b/src/prolog/codegen.rs @@ -71,7 +71,7 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator *self.var_count.get(var).unwrap() } - fn mark_non_callable(&mut self, name: Rc, arity: usize, term_loc: GenContext, + fn mark_non_callable(&mut self, name: Rc, arity: usize, term_loc: GenContext, vr: &'a Cell, code: &mut Code) -> RegType { @@ -133,8 +133,7 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator }; } - fn compile_target(&mut self, iter: Iter, term_loc: GenContext, is_exposed: bool) - -> Vec + fn compile_target(&mut self, iter: Iter, term_loc: GenContext, is_exposed: bool) -> Vec where Target: CompilationTarget<'a>, Iter: Iterator> { let mut target = Vec::new(); @@ -385,6 +384,14 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator let r = self.mark_non_callable(name.clone(), 1, term_loc, vr, code); code.push(is_var!(r)); } + }, + &InlinedClauseType::IsPartialString(..) => + match terms[0].as_ref() { + &Term::Var(ref vr, ref name) => { + let r = self.mark_non_callable(name.clone(), 1, term_loc, vr, code); + code.push(is_partial_string!(r)); + }, + _ => code.push(fail!()) } } @@ -401,10 +408,8 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator code: &mut Code, is_exposed: bool) -> Result<(), ParserError> { - for (chunk_num, _, terms) in iter.rule_body_iter() - { - for (i, term) in terms.iter().enumerate() - { + for (chunk_num, _, terms) in iter.rule_body_iter() { + for (i, term) in terms.iter().enumerate() { let term_loc = if i + 1 < terms.len() { GenContext::Mid(chunk_num) } else { @@ -473,7 +478,7 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator code.push(fail!()); } } - }, + }, &QueryTerm::Clause(_, ClauseType::Inlined(ref ct), ref terms, _) => try!(self.compile_inlined(ct, terms, term_loc, code)), _ => { @@ -512,7 +517,7 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator // add a proceed to bookend any trailing cuts. match toc { &QueryTerm::BlockedCut | &QueryTerm::UnblockedCut(..) => code.push(proceed!()), - &QueryTerm::Clause(_, ClauseType::Inlined(..), ..) => code.push(proceed!()), + &QueryTerm::Clause(_, ClauseType::Inlined(..), ..) => code.push(proceed!()), _ => {} }; diff --git a/src/prolog/compile.rs b/src/prolog/compile.rs index 5d1f6f67..5c014117 100644 --- a/src/prolog/compile.rs +++ b/src/prolog/compile.rs @@ -41,12 +41,12 @@ pub fn parse_code(wam: &mut Machine, buffer: &str) -> Result, queue: Vec, flags: MachineFlag -> Result<(Code, AllocVarDict), ParserError> { // count backtracking inferences. - let mut cg = CodeGenerator::::new(false, flags); + let mut cg = CodeGenerator::::new(false, flags); let mut code = try!(cg.compile_query(&terms)); compile_appendix(&mut code, queue, false, flags)?; - + Ok((code, cg.take_vars())) } @@ -193,7 +193,7 @@ impl<'a> ListingCompiler<'a> { compile_appendix(&mut decl_code, Vec::from(queue), non_counted_bt, self.wam.machine_flags())?; - + let idx = code_dir.entry((name, arity)).or_insert(CodeIndex::default()); set_code_index!(idx, IndexPtr::Index(p), self.get_module_name()); @@ -252,7 +252,7 @@ fn compile_listing(wam: &mut Machine, src_str: &str, mut indices: MachineCodeInd while let Some(decl) = try_eval_session!(worker.consume(&mut indices)) { match decl { Declaration::NonCountedBacktracking(name, arity) => - compiler.add_non_counted_bt_flag(name, arity), + compiler.add_non_counted_bt_flag(name, arity), Declaration::Op(op_decl) => try_eval_session!(op_decl.submit(compiler.get_module_name(), &mut indices.op_dir)), Declaration::UseModule(name) => diff --git a/src/prolog/heap_iter.rs b/src/prolog/heap_iter.rs index e5f045ba..99206578 100644 --- a/src/prolog/heap_iter.rs +++ b/src/prolog/heap_iter.rs @@ -44,7 +44,7 @@ impl<'a> HCPreOrderIterator<'a> { if self.machine_st.machine_flags().double_quotes.is_chars() => { if let Some(c) = s.head() { let tail = s.tail(); - + self.state_stack.push(Addr::Con(Constant::String(tail))); self.state_stack.push(Addr::Con(Constant::Char(c))); } @@ -136,8 +136,7 @@ impl MachineState { HCPostOrderIterator::new(HCPreOrderIterator::new(self, a)) } - pub fn acyclic_pre_order_iter<'a>(&'a self, a: Addr) - -> HCAcyclicIterator> + pub fn acyclic_pre_order_iter<'a>(&'a self, a: Addr) -> HCAcyclicIterator> { HCAcyclicIterator::new(HCPreOrderIterator::new(self, a)) } @@ -182,6 +181,7 @@ where HCIter: Iterator + MutStackHCIterator if !self.seen.contains(&addr) { self.iter.stack().push(addr.clone()); self.seen.insert(addr); + break; } } diff --git a/src/prolog/heap_print.rs b/src/prolog/heap_print.rs index bd27208f..35e3c705 100644 --- a/src/prolog/heap_print.rs +++ b/src/prolog/heap_print.rs @@ -335,10 +335,13 @@ impl<'a, Formatter: HCValueFormatter, Outputter: HCValueOutputter> if self.machine_st.machine_flags().double_quotes.is_chars() { if !s.is_empty() { self.push_list(); + } else if s.is_expandable() { + if !self.at_cdr(" | _") { + self.outputter.push_char('_'); + } } else if !self.at_cdr("") { self.outputter.append("[]"); } - // self.expand_char_list(s); } else { // for now, == DoubleQuotes::Atom let borrowed_str = s.borrow(); diff --git a/src/prolog/iterators.rs b/src/prolog/iterators.rs index e6581b30..feb86038 100644 --- a/src/prolog/iterators.rs +++ b/src/prolog/iterators.rs @@ -51,8 +51,6 @@ impl<'a> QueryIterator<'a> { let state = TermIterState::Clause(Level::Root, 0, cell, ct.clone(), terms); QueryIterator { state_stack: vec![state] } }, - &QueryTerm::BlockedCut => - QueryIterator { state_stack: vec![] }, &QueryTerm::UnblockedCut(ref cell) => { let state = TermIterState::Var(Level::Root, cell, rc_atom!("!")); QueryIterator { state_stack: vec![state] } @@ -67,7 +65,9 @@ impl<'a> QueryIterator<'a> { }).collect(); QueryIterator { state_stack } - } + }, + &QueryTerm::BlockedCut => + QueryIterator { state_stack: vec![] }, } } } @@ -353,8 +353,7 @@ impl<'a> ChunkedIterator<'a> result.push(term), ChunkedTerm::BodyTerm(&QueryTerm::Clause(_, ClauseType::Inlined(_), ..)) => result.push(term), - ChunkedTerm::BodyTerm(&QueryTerm::Clause(_, ClauseType::CallN, ref subterms, _)) => - { + ChunkedTerm::BodyTerm(&QueryTerm::Clause(_, ClauseType::CallN, ref subterms, _)) => { result.push(term); arity = subterms.len() + 1; break; diff --git a/src/prolog/machine/machine_state.rs b/src/prolog/machine/machine_state.rs index 0ba8eb05..5b019146 100644 --- a/src/prolog/machine/machine_state.rs +++ b/src/prolog/machine/machine_state.rs @@ -553,7 +553,7 @@ pub(crate) trait CallPolicy: Any { code_dirs: CodeDirs) -> CallResult { - match ct { + match ct { &BuiltInClauseType::AcyclicTerm => { let addr = machine_st[temp_v!(1)].clone(); machine_st.fail = machine_st.is_cyclic_term(addr); @@ -657,6 +657,19 @@ pub(crate) trait CallPolicy: Any { return_from_clause!(machine_st.last_call, machine_st) }, + &BuiltInClauseType::PartialString => { + let a1 = machine_st[temp_v!(1)].clone(); + let a2 = machine_st[temp_v!(2)].clone(); + + if let Addr::Con(Constant::String(s)) = a1 { + s.set_expandable(); + machine_st.write_constant_to_var(a2, Constant::String(s)); + } else { + machine_st.fail = true; + } + + return_from_clause!(machine_st.last_call, machine_st) + }, &BuiltInClauseType::Sort => { machine_st.check_sort_errors()?; diff --git a/src/prolog/machine/machine_state_impl.rs b/src/prolog/machine/machine_state_impl.rs index 04037889..b9a411ae 100644 --- a/src/prolog/machine/machine_state_impl.rs +++ b/src/prolog/machine/machine_state_impl.rs @@ -9,6 +9,7 @@ use prolog::num::{Integer, Signed, ToPrimitive, Zero}; use prolog::num::bigint::{BigInt, BigUint}; use prolog::num::rational::Ratio; use prolog::or_stack::*; +use prolog::string_list::StringList; use std::cell::RefCell; use std::cmp::{max, Ordering}; @@ -190,21 +191,51 @@ impl MachineState { self.fail = true; }, - (Addr::Lis(a1), Addr::Con(Constant::String(ref s))) - | (Addr::Con(Constant::String(ref s)), Addr::Lis(a1)) - if self.flags.double_quotes.is_chars() => + (Addr::Lis(a1), Addr::Con(Constant::String(ref mut s))) + | (Addr::Con(Constant::String(ref mut s)), Addr::Lis(a1)) + if self.flags.double_quotes.is_chars() => { if let Some(c) = s.head() { pdl.push(Addr::Con(Constant::String(s.tail()))); pdl.push(Addr::HeapCell(a1 + 1)); pdl.push(Addr::Con(Constant::Char(c))); pdl.push(Addr::HeapCell(a1)); - } else { - self.fail = true; - }, + + continue; + } else if s.is_expandable() { + let mut stepper = |c| { + let new_s = s.push_char(c); + + pdl.push(Addr::HeapCell(a1 + 1)); + pdl.push(Addr::Con(Constant::String(new_s))); + }; + + match self.heap[a1].clone() { + HeapCellValue::Addr(Addr::Con(Constant::Char(c))) => { + stepper(c); + continue; + }, + HeapCellValue::Addr(Addr::Con(Constant::Atom(ref a))) => + if let Some(c) = a.as_str().chars().next() { + if c.len_utf8() == a.as_str().len() { + stepper(c); + continue; + } + }, + _ => {} + }; + } + + self.fail = true; + }, (Addr::Con(Constant::EmptyList), Addr::Con(Constant::String(ref s))) | (Addr::Con(Constant::String(ref s)), Addr::Con(Constant::EmptyList)) if self.flags.double_quotes.is_chars() => { + if s.is_expandable() && s.is_empty() { + s.set_non_expandable(); + continue; + } + self.fail = !s.is_empty(); }, (Addr::Lis(a1), Addr::Lis(a2)) => { @@ -214,23 +245,42 @@ impl MachineState { pdl.push(Addr::HeapCell(a1 + 1)); pdl.push(Addr::HeapCell(a2 + 1)); }, - (Addr::Con(Constant::String(ref s1)), Addr::Con(Constant::String(ref s2))) => { - if let Some(c1) = s1.head() { - if let Some(c2) = s2.head() { - if c1 == c2 { + (Addr::Con(Constant::String(ref mut s1)), Addr::Con(Constant::String(ref mut s2))) => { + let mut stepper = |s1: &mut StringList, s2: &mut StringList| -> bool { + if let Some(c1) = s1.head() { + if let Some(c2) = s2.head() { + if c1 == c2 { + pdl.push(Addr::Con(Constant::String(s1.tail()))); + pdl.push(Addr::Con(Constant::String(s2.tail()))); + + return true; + } + } else if s2.is_expandable() { + pdl.push(Addr::Con(Constant::String(s2.push_char(c1)))); pdl.push(Addr::Con(Constant::String(s1.tail()))); - pdl.push(Addr::Con(Constant::String(s2.tail()))); - continue; + return true; } + } else if s1.is_expandable() { + if let Some(c) = s2.head() { + pdl.push(Addr::Con(Constant::String(s1.push_char(c)))); + pdl.push(Addr::Con(Constant::String(s2.tail()))); + } else if !s2.is_expandable() { + s1.set_non_expandable(); + }/*else { + //TODO: unify the tails of s1 and s2? I guess? + }*/ + + return true; + } else if s2.head().is_none() { + s2.set_non_expandable(); + return true; } - } else { - if s2.head().is_none() { - continue; - } - } - self.fail = true; + false + }; + + self.fail = !(stepper(s1, s2) || stepper(s2, s1)); }, (Addr::Con(ref c1), Addr::Con(ref c2)) => if c1 != c2 { @@ -345,9 +395,7 @@ impl MachineState { } pub(super) fn write_constant_to_var(&mut self, addr: Addr, c: Constant) { - let addr = self.deref(addr); - - match self.store(addr) { + match self.store(self.deref(addr)) { Addr::HeapCell(hc) => { self.heap[hc] = HeapCellValue::Addr(Addr::Con(c.clone())); self.trail(Ref::HeapCell(hc)); @@ -356,12 +404,15 @@ impl MachineState { self.and_stack[fr][sc] = Addr::Con(c.clone()); self.trail(Ref::StackCell(fr, sc)); }, - Addr::Con(Constant::String(s)) => + Addr::Con(Constant::String(ref mut s)) => self.fail = match c { - Constant::EmptyList - if self.flags.double_quotes.is_chars() => + Constant::EmptyList if self.flags.double_quotes.is_chars() => !s.is_empty(), - Constant::String(s2) => s != s2, + Constant::String(ref s2) if s.is_empty() && s.is_expandable() => { + s.append(s2); + false + }, + Constant::String(s2) => *s != s2, _ => true }, Addr::Con(c1) => { @@ -691,7 +742,7 @@ impl MachineState { } pub(super) fn execute_arith_instr(&mut self, instr: &ArithmeticInstruction) { - match instr { + match instr { &ArithmeticInstruction::Add(ref a1, ref a2, t) => { let n1 = try_or_fail!(self, self.get_number(a1)); let n2 = try_or_fail!(self, self.get_number(a2)); @@ -826,12 +877,17 @@ impl MachineState { match addr { Addr::Con(Constant::String(ref s)) if self.flags.double_quotes.is_chars() => { - if let Some(c) = s.head() { - let h = self.heap.h; + let h = self.heap.h; + if let Some(c) = s.head() { self.heap.push(HeapCellValue::Addr(Addr::Con(Constant::Char(c)))); self.heap.push(HeapCellValue::Addr(Addr::Con(Constant::String(s.tail())))); + self.s = h; + self.mode = MachineMode::Read; + } else if s.is_expandable() { + self.heap.push(HeapCellValue::Addr(Addr::Con(Constant::String(s.clone())))); + self.s = h; self.mode = MachineMode::Read; } else { @@ -1588,6 +1644,14 @@ impl MachineState { _ => self.fail = true }; }, + &InlinedClauseType::IsPartialString(r1) => { + let d = self.store(self.deref(self[r1].clone())); + + match d { + Addr::Con(Constant::String(ref s)) if s.is_expandable() => self.p += 1, + _ => self.fail = true + }; + } } } diff --git a/src/prolog/machine/mod.rs b/src/prolog/machine/mod.rs index 73c10428..a5a8cd23 100644 --- a/src/prolog/machine/mod.rs +++ b/src/prolog/machine/mod.rs @@ -408,7 +408,7 @@ impl Machine { while self.ms.p < end_ptr { if let CodePtr::Local(LocalCodePtr::TopLevel(mut cn, p)) = self.ms.p { match &self[LocalCodePtr::TopLevel(cn, p)] { - &Line::Control(ref ctrl_instr) if ctrl_instr.is_jump_instr() => { + &Line::Control(ref ctrl_instr) if ctrl_instr.is_jump_instr() => { self.record_var_places(cn, alloc_locs, heap_locs); cn += 1; }, diff --git a/src/prolog/macros.rs b/src/prolog/macros.rs index ed608d9f..da53a665 100644 --- a/src/prolog/macros.rs +++ b/src/prolog/macros.rs @@ -73,7 +73,7 @@ macro_rules! functor { ); ($name:expr, $len:expr, [$($args:expr),*], $fix: expr) => ( vec![ HeapCellValue::NamedStr($len, clause_name!($name), Some($fix)), $($args),* ] - ); + ); } macro_rules! temp_v { @@ -143,6 +143,12 @@ macro_rules! is_var { ) } +macro_rules! is_partial_string { + ($r:expr) => ( + call_clause!(ClauseType::Inlined(InlinedClauseType::IsPartialString($r)), 1, 0) + ) +} + macro_rules! call_clause { ($ct:expr, $arity:expr, $pvs:expr) => ( Line::Control(ControlInstruction::CallClause($ct, $arity, $pvs, false, false)) diff --git a/src/prolog/string_list.rs b/src/prolog/string_list.rs index cbf39283..9a7262a4 100644 --- a/src/prolog/string_list.rs +++ b/src/prolog/string_list.rs @@ -37,13 +37,19 @@ impl PartialOrd for StringList { impl Ord for StringList { fn cmp(&self, other: &Self) -> Ordering { - self.body.cmp(&other.body) + if self.expandable.get() && !self.expandable.get() { + Ordering::Greater + } else if !self.expandable.get() && self.expandable.get() { + Ordering::Less + } else { + self.borrow()[self.cursor ..].cmp(&other.borrow()[other.cursor ..]) + } } } impl PartialEq for StringList { fn eq(&self, other: &Self) -> bool { - self.body == other.body && self.cursor == other.cursor && self.expandable == other.expandable + self.borrow()[self.cursor ..] == other.borrow()[other.cursor ..] && self.expandable == other.expandable } } @@ -61,6 +67,41 @@ impl StringList { } } + #[inline] + pub fn is_expandable(&self) -> bool { + self.expandable.get() + } + + #[inline] + pub fn set_expandable(&self) { + self.expandable.set(true); + } + + #[inline] + pub fn set_non_expandable(&self) { + self.expandable.set(false); + } + + #[inline] + pub fn push_char(&mut self, c: char) -> Self { + if self.expandable.get() { + self.body.0.borrow_mut().push(c); + + let mut new_string_list = self.clone(); + new_string_list.cursor += c.len_utf8(); + + new_string_list + } else { + self.clone() + } + } + + #[inline] + pub fn append(&mut self, s: &StringList) { + self.body.0.borrow_mut().extend(s.borrow()[s.cursor ..].chars()); + self.expandable.set(s.expandable.get()); + } + #[inline] pub fn cursor(&self) -> usize { self.cursor diff --git a/src/prolog/toplevel.rs b/src/prolog/toplevel.rs index 00fabb7d..49969e40 100644 --- a/src/prolog/toplevel.rs +++ b/src/prolog/toplevel.rs @@ -408,8 +408,7 @@ impl RelationWorker { self.fabricate_rule(fold_by_str(prec_seq, body_term, comma_sym)) } - fn to_query_term(&mut self, indices: &mut MachineCodeIndices, term: Term) - -> Result + fn to_query_term(&mut self, indices: &mut MachineCodeIndices, term: Term) -> Result { match term { Term::Constant(r, Constant::Atom(name)) => @@ -422,40 +421,53 @@ impl RelationWorker { Term::Var(_, ref v) if v.as_str() == "!" => Ok(QueryTerm::UnblockedCut(Cell::default())), Term::Clause(r, name, mut terms, fixity) => - if name.as_str() == ";" && 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 if name.as_str() == ":" && terms.len() == 2 { - let callee = *terms.pop().unwrap(); - let mod_name = *terms.pop().unwrap(); - - module_resolution_call(mod_name, callee) - } else if name.as_str() == "->" && 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 if name.as_str() == "$get_level" && terms.len() == 1 { - if let Term::Var(_, ref var) = *terms[0] { - Ok(QueryTerm::GetLevelAndUnify(Cell::default(), var.clone())) - } else { + match (name.as_str(), 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)) + }, + (":", 2) => { + let callee = *terms.pop().unwrap(); + let mod_name = *terms.pop().unwrap(); + + module_resolution_call(mod_name, callee) + }, + ("->", 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)) + }, + ("$get_level", 1) => + if let Term::Var(_, ref var) = *terms[0] { + Ok(QueryTerm::GetLevelAndUnify(Cell::default(), var.clone())) + } else { + Err(ParserError::InadmissibleQueryTerm) + }, + ("partial_string", 2) => { + if let Term::Constant(_, Constant::String(_)) = *terms[0].clone() { + if let Term::Var(..) = *terms[1].clone() { + let ct = ClauseType::BuiltIn(BuiltInClauseType::PartialString); + return Ok(QueryTerm::Clause(Cell::default(), ct, terms, false)); + } + } + Err(ParserError::InadmissibleQueryTerm) + }, + _ => { + let ct = indices.lookup(name, terms.len(), fixity); + Ok(QueryTerm::Clause(Cell::default(), ct, terms, false)) } - } else { - let ct = indices.lookup(name, terms.len(), fixity); - Ok(QueryTerm::Clause(Cell::default(), ct, terms, false)) }, Term::Var(..) => - Ok(QueryTerm::Clause(Cell::default(), ClauseType::CallN, vec![Box::new(term)], - false)), - _ => - Err(ParserError::InadmissibleQueryTerm) + Ok(QueryTerm::Clause(Cell::default(), ClauseType::CallN, vec![Box::new(term)], false)), + _ => Err(ParserError::InadmissibleQueryTerm) } } diff --git a/src/tests.rs b/src/tests.rs index 9356d75d..6d602137 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1020,7 +1020,7 @@ fn test_queries_on_arithmetic() assert_prolog_success!(&mut wam, "?- f(5, 33)."); assert_prolog_failure!(&mut wam, "?- f(5, 32)."); - // exponentiation. + // exponentiation. // the ~ operators tests whether |X - Y| <= 1/10000... // or whatever degree of approximation used by Newton's method in rational_pow. @@ -1078,8 +1078,8 @@ fn test_queries_on_arithmetic() assert_prolog_success!(&mut wam, "?- X is (1 rdiv 3) ^ 0.5, Y is X ^ 2, 1 rdiv 3 ~ Y."); assert_prolog_success!(&mut wam, "?- X is (-5) ^ (-1 rdiv 3), Y is X ^ 3, Y ~ -1 rdiv 5."); - assert_prolog_failure!(&mut wam, "?- X is (-5) ^ (-1 rdiv 3), Y is X ^ 3, Y ~ 1 rdiv 5."); - + assert_prolog_failure!(&mut wam, "?- X is (-5) ^ (-1 rdiv 3), Y is X ^ 3, Y ~ 1 rdiv 5."); + assert_prolog_success!(&mut wam, "?- X is (0 rdiv 5) ^ 5.", [["X = 0"]]); assert_prolog_success!(&mut wam, "?- X is (-0 rdiv 5) ^ 5.", @@ -1826,7 +1826,8 @@ fn test_queries_on_string_lists() [["X = [e, n]"]]); assert_prolog_success!(&mut wam, "?- \"koen\" = [k, o | X], X = \"en\".", [["X = [e, n]"]]); - assert_prolog_failure!(&mut wam, "?- \"koen\" = [k, o | X], X == \"en\"."); + assert_prolog_success!(&mut wam, "?- \"koen\" = [k, o | X], X == \"en\".", + [["X = [e, n]"]]); assert_prolog_success!(&mut wam, "?- \"koen\" = [k, o | X], X =@= \"en\".", [["X = [e, n]"]]); @@ -1843,7 +1844,8 @@ fn test_queries_on_string_lists() assert_prolog_success!(&mut wam, "?- matcher(\"abcdef\", X), X = [d,e,f|Y], Y == [], X = \"def\".", [["X = [d, e, f]", "Y = []"]]); - assert_prolog_failure!(&mut wam, "?- matcher(\"abcdef\", X), X = [d,e,f|Y], Y == [], X == \"def\"."); + assert_prolog_success!(&mut wam, "?- matcher(\"abcdef\", X), X = [d,e,f|Y], Y == [], X == \"def\".", + [["X = [d, e, f]", "Y = []"]]); assert_prolog_success!(&mut wam, "?- X = ['a', 'b', 'c' | \"def\"].", [["X = [a, b, c, d, e, f]"]]); @@ -1860,4 +1862,43 @@ fn test_queries_on_string_lists() assert_prolog_success!(&mut wam, "?- X = \"abc\", X = ['a' | Y], set_prolog_flag(double_quotes, atom).", [["X = \"abc\"", "Y = \"bc\""]]); + + // partial strings. + submit(&mut wam, "?- set_prolog_flag(double_quotes, chars)."); + + assert_prolog_failure!(&mut wam, "?- Y = 5, partial_string(\"abc\", Y)."); + assert_prolog_success!(&mut wam, "?- partial_string(\"abc\", X).", + [["X = [a, b, c | _]"]]); + + submit(&mut wam, "matcher([a, b, c | X], X)."); + + assert_prolog_success!(&mut wam, "?- partial_string(\"abc\", X), matcher(X, Y).", + [["X = [a, b, c | _]", "Y = _"]]); + assert_prolog_success!(&mut wam, "?- partial_string(\"abc\", X), matcher(X, Y), Y = \"def\".", + [["X = [a, b, c, d, e, f]", "Y = [d, e, f]"]]); + assert_prolog_success!(&mut wam, "?- partial_string(\"abc\", X), matcher(X, Y), \"def\" = Y.", + [["X = [a, b, c, d, e, f]", "Y = [d, e, f]"]]); + + assert_prolog_success!(&mut wam, "?- partial_string(\"abc\", X), matcher(X, Y), partial_string(\"def\", Y), + Y = \"defghijkl\".", + [["X = [a, b, c, d, e, f, g, h, i, j, k, l]", + "Y = [d, e, f, g, h, i, j, k, l]"]]); + assert_prolog_success!(&mut wam, "?- partial_string(\"abc\", X), matcher(X, Y), partial_string(\"def\", Y), + \"defghijkl\" = Y.", + [["X = [a, b, c, d, e, f, g, h, i, j, k, l]", + "Y = [d, e, f, g, h, i, j, k, l]"]]); + + assert_prolog_success!(&mut wam, "?- partial_string(\"abc\", X), matcher(X, Y), Y = [d, e, f | G].", + [["X = [a, b, c, d, e, f | _]", "Y = [d, e, f | _]", "G = _"]]); + assert_prolog_success!(&mut wam, "?- partial_string(\"abc\", X), matcher(X, Y), [d, e, f | G] = Y.", + [["X = [a, b, c, d, e, f | _]", "Y = [d, e, f | _]", "G = _"]]); + assert_prolog_success!(&mut wam, "?- partial_string(\"abc\", X), matcher(X, Y), Y = [d, e, f | G], + G = \"ghi\".", + [["X = [a, b, c, d, e, f, g, h, i]", "Y = [d, e, f, g, h, i]", "G = [g, h, i]"]]); + assert_prolog_success!(&mut wam, "?- partial_string(\"abc\", X), matcher(X, Y), Y = [d, e, f | G], + is_partial_string(Y), G = \"ghi\".", + [["X = [a, b, c, d, e, f, g, h, i]", "Y = [d, e, f, g, h, i]", "G = [g, h, i]"]]); + assert_prolog_success!(&mut wam, "?- partial_string(\"abc\", X), matcher(X, Y), Y = [d, e, f | G], + is_partial_string(Y), is_partial_string(G), G = \"ghi\".", + [["X = [a, b, c, d, e, f, g, h, i]", "Y = [d, e, f, g, h, i]", "G = [g, h, i]"]]); } -- 2.54.0