]> Repositorios git - scryer-prolog.git/commitdiff
finalize support for partial strings
authorMark Thom <[email protected]>
Mon, 3 Sep 2018 05:32:51 +0000 (23:32 -0600)
committerMark Thom <[email protected]>
Mon, 3 Sep 2018 05:32:51 +0000 (23:32 -0600)
14 files changed:
README.md
src/prolog/ast.rs
src/prolog/codegen.rs
src/prolog/compile.rs
src/prolog/heap_iter.rs
src/prolog/heap_print.rs
src/prolog/iterators.rs
src/prolog/machine/machine_state.rs
src/prolog/machine/machine_state_impl.rs
src/prolog/machine/mod.rs
src/prolog/macros.rs
src/prolog/string_list.rs
src/prolog/toplevel.rs
src/tests.rs

index 6bc255bc68ad22e91f6a7c8210fc0e6aac50a4d1..f0307d7cbf3d3cb7f9659eb93567cdf04ae3f5ad 100644 (file)
--- 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
index d006dd66a592fe270366446f57b5703459f6a8a7..3a51a0867076ab7c898608c344c202f5d7216d51 100644 (file)
@@ -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)))
-            }                    
+            }
         }
     }
 
index 0bb4a74ff38204034b195dbed7dc05fec157f8a6..3c8d40a768eab50f687065a03fd9a933e50045a1 100644 (file)
@@ -71,7 +71,7 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator<TermMarker>
         *self.var_count.get(var).unwrap()
     }
 
-    fn mark_non_callable(&mut self, name: Rc<Atom>, arity: usize, term_loc: GenContext,
+    fn mark_non_callable(&mut self, name: Rc<Var>, arity: usize, term_loc: GenContext,
                          vr: &'a Cell<VarReg>, code: &mut Code)
                          -> RegType
     {
@@ -133,8 +133,7 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator<TermMarker>
         };
     }
 
-    fn compile_target<Target, Iter>(&mut self, iter: Iter, term_loc: GenContext, is_exposed: bool)
-                                    -> Vec<Target>
+    fn compile_target<Target, Iter>(&mut self, iter: Iter, term_loc: GenContext, is_exposed: bool) -> Vec<Target>
         where Target: CompilationTarget<'a>, Iter: Iterator<Item=TermRef<'a>>
     {
         let mut target = Vec::new();
@@ -385,6 +384,14 @@ impl<'a, TermMarker: Allocator<'a>> CodeGenerator<TermMarker>
                         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<TermMarker>
                    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<TermMarker>
                                 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<TermMarker>
         // 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!()),            
             _ => {}
         };
 
index 5d1f6f67b9dec52ce7b352e8de3a23d988a1b215..5c014117407174f049166b67d8c7989eb140a474 100644 (file)
@@ -41,12 +41,12 @@ pub fn parse_code(wam: &mut Machine, buffer: &str) -> Result<TopLevelPacket, Par
     let atom_tbl = wam.atom_tbl();
     let string_tbl = wam.string_tbl();
     let flags = wam.machine_flags();
-    
+
     let index = MachineCodeIndices {
         code_dir: &mut wam.code_dir,
         op_dir: &mut wam.op_dir,
     };
-        
+
     let mut worker = TopLevelWorker::new(buffer.as_bytes(), atom_tbl, string_tbl,
                                          flags, index);
     worker.parse_code()
@@ -102,11 +102,11 @@ fn compile_query(terms: Vec<QueryTerm>, queue: Vec<TopLevel>, flags: MachineFlag
                  -> Result<(Code, AllocVarDict), ParserError>
 {
     // count backtracking inferences.
-    let mut cg = CodeGenerator::<DebrayAllocator>::new(false, flags); 
+    let mut cg = CodeGenerator::<DebrayAllocator>::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) =>
index e5f045ba0f9813d6c0251a9b6de81d6192029012..992065781f4d2444699a6e5cdfd8b6cd86aeb288 100644 (file)
@@ -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<HCPreOrderIterator<'a>>
+    pub fn acyclic_pre_order_iter<'a>(&'a self, a: Addr) -> HCAcyclicIterator<HCPreOrderIterator<'a>>
     {
         HCAcyclicIterator::new(HCPreOrderIterator::new(self, a))
     }
@@ -182,6 +181,7 @@ where HCIter: Iterator<Item=HeapCellValue> + MutStackHCIterator
             if !self.seen.contains(&addr) {
                 self.iter.stack().push(addr.clone());
                 self.seen.insert(addr);
+
                 break;
             }
         }
index bd27208ffda07c7240d6323d61d703a88d7f7d66..35e3c705e4e24779bea4e97f4b0e78b8e2da1302 100644 (file)
@@ -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();
                     
index e6581b3004693aea2442e18bb97c9be8ada8d1df..feb8603800c5c729383e8390dd69c3ebab10e201 100644 (file)
@@ -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;
index 0ba8eb052aab82c574d0bd4a217909f139385c9d..5b01914601271e31dd3b9b21a3019811e0a24727 100644 (file)
@@ -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()?;
 
index 04037889e3fab9f033b8efd528a4ff500af36cbd..b9a411aebcb8684dda3961001874fd0570d803ce 100644 (file)
@@ -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
+                };
+            }
         }
     }
 
index 73c104280dcfc9d9379d263e94e17a824fde3fb8..a5a8cd23713c21b188c44cacd4bc80de2523afd8 100644 (file)
@@ -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;
                     },
index ed608d9f76c1314872c52841ac3ed7e5f27a6fd8..da53a665edb994a5137f0c0cbafa90b841cb6144 100644 (file)
@@ -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))
index cbf392832d0f6e0e93984a7e669e3d20e2ac88e6..9a7262a4ce2e4d7c69757f03f3660a56a30b605d 100644 (file)
@@ -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
index 00fabb7dcaf92e3628e85f31047d61f078f94466..49969e407db0ff2d30881583d93a3fd999ce7dfa 100644 (file)
@@ -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<QueryTerm, ParserError>
+    fn to_query_term(&mut self, indices: &mut MachineCodeIndices, term: Term) -> Result<QueryTerm, ParserError>
     {
         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)
         }
     }
 
index 9356d75dd5efd9454d603e9b7ad2e7d1cf46be58..6d60213747ea3f992e1433d22a0f1183e9026b19 100644 (file)
@@ -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]"]]);
 }