changeset 5852:e9bb3688e654

Fix some bugs in #'substitute, #'nsubstitute. src/ChangeLog addition: 2015-03-04 Aidan Kehoe <kehoea@parhasard.net> * sequence.c (count_with_tail): Accept COUNT from #'substitute, #'nsubstitute too. * sequence.c (FdeleteX): Only remove COUNT from the arguments if FROM-END is non-nil. * sequence.c (Fnsubstitute): Remove COUNT from the arguments if specified and FROM-END is non-nil. * sequence.c (Fsubstitute): Remove COUNT from the arguments if specified and FROM-END is non-nil. Do this before calling count_with_tail(). When we encounter the cons return by count_with_tail(), use the replacement object. tests/ChangeLog addition: 2015-03-04 Aidan Kehoe <kehoea@parhasard.net> * automated/lisp-tests.el: Add some tests for #'substitute.
author Aidan Kehoe <kehoea@parhasard.net>
date Wed, 04 Mar 2015 15:54:00 +0000
parents eabf763bc6f9
children 1044acf60048
files src/ChangeLog src/sequence.c tests/ChangeLog tests/automated/lisp-tests.el
diffstat 4 files changed, 229 insertions(+), 78 deletions(-) [+]
line wrap: on
line diff
--- a/src/ChangeLog	Sat Feb 28 17:06:40 2015 -0800
+++ b/src/ChangeLog	Wed Mar 04 15:54:00 2015 +0000
@@ -1,3 +1,18 @@
+2015-03-04  Aidan Kehoe  <kehoea@parhasard.net>
+
+	* sequence.c (count_with_tail):
+	Accept COUNT from #'substitute, #'nsubstitute too.
+	* sequence.c (FdeleteX):
+	Only remove COUNT from the arguments if FROM-END is non-nil.
+	* sequence.c (Fnsubstitute):
+	Remove COUNT from the arguments if specified and FROM-END is
+	non-nil.
+	* sequence.c (Fsubstitute):
+	Remove COUNT from the arguments if specified and FROM-END is
+	non-nil. Do this before calling count_with_tail(). When we
+	encounter the cons return by count_with_tail(), use the
+	replacement object.
+
 2015-01-08  Stephen J. Turnbull  <stephen@xemacs.org>
 
 	Fix progress bar crashes.
--- a/src/sequence.c	Sat Feb 28 17:06:40 2015 -0800
+++ b/src/sequence.c	Wed Mar 04 15:54:00 2015 +0000
@@ -710,9 +710,6 @@
 
       /* Our callers should have filtered out non-positive COUNT. */
       assert (counting >= 0);
-      /* And we're not prepared to handle COUNT from any other caller at the
-	 moment. */
-      assert (EQ (caller, QremoveX)|| EQ (caller, QdeleteX));
     }
 
   check_test = get_check_test_function (item, &test, test_not, if_, if_not,
@@ -1878,7 +1875,7 @@
 
   PARSE_KEYWORDS (FdeleteX, nargs, args, 9,
 		  (test, if_not, if_, test_not, key, start, end, from_end,
-		   count), (start = Qzero, count = Qunbound));
+		   count), (start = Qzero));
 
   CHECK_SEQUENCE (sequence);
   CHECK_NATNUM (start);
@@ -1890,45 +1887,41 @@
       ending = BIGNUMP (end) ? 1 + MOST_POSITIVE_FIXNUM : XFIXNUM (end);
     }
 
-  if (!UNBOUNDP (count))
-    {
-      if (!NILP (count))
-	{
-	  CHECK_INTEGER (count);
-          if (FIXNUMP (count))
-            {
-              counting = XFIXNUM (count);
-            }
+  if (!NILP (count))
+    {
+      CHECK_INTEGER (count);
+      if (FIXNUMP (count))
+        {
+          counting = XFIXNUM (count);
+        }
 #ifdef HAVE_BIGNUM
-          else
-            {
-              counting = bignum_sign (XBIGNUM_DATA (count)) > 0 ?
-                1 + MOST_POSITIVE_FIXNUM : MOST_NEGATIVE_FIXNUM - 1;
-            }
+      else
+        {
+          counting = bignum_sign (XBIGNUM_DATA (count)) > 0 ?
+            1 + MOST_POSITIVE_FIXNUM : MOST_NEGATIVE_FIXNUM - 1;
+        }
 #endif
-
-	  if (counting < 1)
-	    {
-	      return sequence;
-	    }
-
-          if (!NILP (from_end))
+      if (counting < 1)
+        {
+          return sequence;
+        }
+
+      if (!NILP (from_end))
+        {
+          /* Sigh, this is inelegant. Force count_with_tail () to ignore
+             the count keyword, so we get the actual number of matching
+             elements, and can start removing from the beginning for the
+             from-end case.  */
+          for (ii = XSUBR (GET_DEFUN_LISP_OBJECT (FdeleteX))->min_args;
+               ii < nargs; ii += 2)
             {
-              /* Sigh, this is inelegant. Force count_with_tail () to ignore
-                 the count keyword, so we get the actual number of matching
-                 elements, and can start removing from the beginning for the
-                 from-end case.  */
-              for (ii = XSUBR (GET_DEFUN_LISP_OBJECT (FdeleteX))->min_args;
-                   ii < nargs; ii += 2)
+              if (EQ (args[ii], Q_count))
                 {
-                  if (EQ (args[ii], Q_count))
-                    {
-                      args[ii + 1] = Qnil;
-                      break;
-                    }
+                  args[ii + 1] = Qnil;
+                  break;
                 }
-              ii = 0;
             }
+          ii = 0;
         }
     }
 
@@ -5797,6 +5790,20 @@
 	{
 	  return sequence;
 	}
+
+      if (!NILP (from_end))
+        {
+          for (ii = XSUBR (GET_DEFUN_LISP_OBJECT (Fnsubstitute))->min_args;
+               ii < nargs; ii += 2)
+            {
+              if (EQ (args[ii], Q_count))
+                {
+                  args[ii + 1] = Qnil;
+                  break;
+                }
+            }
+          ii = 0;
+        }
     }
 
   check_test = get_check_test_function (item, &test, test_not, if_, if_not,
@@ -6015,16 +6022,16 @@
 {
   Lisp_Object new_ = args[0], item = args[1], sequence = args[2], tail = Qnil;
   Lisp_Object result = Qnil, result_tail = Qnil;
-  Lisp_Object object, position0, matched_count;
+  Lisp_Object object, position0, matched;
   Elemcount starting = 0, ending = MOST_POSITIVE_FIXNUM, encountered = 0;
-  Elemcount ii = 0, counting = MOST_POSITIVE_FIXNUM, presenting = 0;
+  Elemcount ii = 0, counting = MOST_POSITIVE_FIXNUM, skipping = 0;
   Boolint test_not_unboundp = 1;
   check_test_func_t check_test = NULL;
   struct gcpro gcpro1;
 
   PARSE_KEYWORDS (Fsubstitute, nargs, args, 9,
 		  (test, if_, if_not, test_not, key, start, end, count,
-		   from_end), (start = Qzero, count = Qunbound));
+		   from_end), (start = Qzero));
 
   CHECK_SEQUENCE (sequence);
 
@@ -6040,30 +6047,6 @@
   check_test = get_check_test_function (item, &test, test_not, if_, if_not,
 					key, &test_not_unboundp);
 
-  if (!UNBOUNDP (count))
-    {
-      if (!NILP (count))
-	{
-          CHECK_INTEGER (count);
-          if (FIXNUMP (count))
-            {
-              counting = XFIXNUM (count);
-            }
-#ifdef HAVE_BIGNUM
-          else
-            {
-              counting = bignum_sign (XBIGNUM_DATA (count)) > 0 ?
-                1 + MOST_POSITIVE_FIXNUM : -1 + MOST_NEGATIVE_FIXNUM;
-            }
-#endif
-
-          if (counting <= 0)
-            {
-              return sequence;
-            }
-	}
-    }
-
   if (!CONSP (sequence))
     {
       position0 = position (&object, item, sequence, check_test,
@@ -6081,17 +6064,62 @@
 	}
     }
 
-  matched_count = count_with_tail (&tail, nargs - 1, args + 1, Qsubstitute);
-
-  if (ZEROP (matched_count))
+  if (!NILP (count))
+    {
+      CHECK_INTEGER (count);
+      if (FIXNUMP (count))
+        {
+          counting = XFIXNUM (count);
+        }
+#ifdef HAVE_BIGNUM
+      else
+        {
+          counting = bignum_sign (XBIGNUM_DATA (count)) > 0 ?
+            1 + MOST_POSITIVE_FIXNUM : -1 + MOST_NEGATIVE_FIXNUM;
+        }
+#endif
+
+      if (counting <= 0)
+        {
+          return sequence;
+        }
+
+      /* Sigh, this is inelegant. Force count_with_tail () to ignore the count
+         keyword, so we get the actual number of matching elements, and can
+         start removing from the beginning for the from-end case.  */
+      if (!NILP (from_end))
+        {
+          for (ii = XSUBR (GET_DEFUN_LISP_OBJECT (Fsubstitute))->min_args;
+               ii < nargs; ii += 2)
+            {
+              if (EQ (args[ii], Q_count))
+                {
+                  args[ii + 1] = Qnil;
+                  break;
+                }
+            }
+          ii = 0;
+        }
+    }
+
+  matched = count_with_tail (&tail, nargs - 1, args + 1, Qsubstitute);
+
+  if (ZEROP (matched))
     {
       return sequence;
     }
 
   if (!NILP (count) && !NILP (from_end))
     {
-      presenting = XFIXNUM (matched_count);
-      presenting = presenting <= counting ? 0 : presenting - counting;
+      Elemcount matching = XFIXNUM (matched);
+      if (matching > counting)
+        {
+          /* skipping is the number of elements to be skipped before we start
+             substituting. It is for those cases where both :count and
+             :from-end are specified, and the number of elements present is
+             greater than that limit specified with :count. */
+          skipping = matching - counting;
+        }
     }
 
   GCPRO1 (result);
@@ -6100,20 +6128,32 @@
       {
         if (EQ (tail, tailing))
           {
+            /* No need to do check_test, we're sure that this element matches
+               because its cons is what count_with_tail returned as the
+               tail. */
+            if (skipping ? encountered >= skipping : encountered < counting)
+              {
+                if (NILP (result))
+                  {
+                    result = Fcons (new_, XCDR (tail));
+                  }
+                else
+                  {
+                    XSETCDR (result_tail, Fcons (new_, XCDR (tail)));
+                  }
+              }
+            else
+              {
+                XSETCDR (result_tail, tail);
+              }
+
 	    XUNGCPRO (elt);
 	    UNGCPRO;
-
-            if (NILP (result))
-              {
-                return XCDR (tail);
-              }
-	  
-            XSETCDR (result_tail, XCDR (tail));
-	    return result;
+            return result;
           }
         else if (starting <= ii && ii < ending &&
                  (check_test (test, key, item, elt) == test_not_unboundp)
-                 && (presenting ? encountered++ >= presenting
+                 && (skipping ? encountered++ >= skipping
                      : encountered++ < counting))
           {
             if (NILP (result))
--- a/tests/ChangeLog	Sat Feb 28 17:06:40 2015 -0800
+++ b/tests/ChangeLog	Wed Mar 04 15:54:00 2015 +0000
@@ -1,3 +1,8 @@
+2015-03-04  Aidan Kehoe  <kehoea@parhasard.net>
+
+	* automated/lisp-tests.el:
+	Add some tests for #'substitute.
+
 2014-10-11  Stephen J. Turnbull  <stephen@xemacs.org>
 
 	* automated/keymap-tests.el:
--- a/tests/automated/lisp-tests.el	Sat Feb 28 17:06:40 2015 -0800
+++ b/tests/automated/lisp-tests.el	Wed Mar 04 15:54:00 2015 +0000
@@ -2988,6 +2988,97 @@
   (Check-Error wrong-number-of-arguments
                (funcall list-and-four 7 8 9 10)))
 
+;; Test #'substitute. Paul Dietz has much more comprehensive tests.
+
+(Assert (equal (substitute 'a 'b '(a b c d e f g)) '(a a c d e f g)))
+(Assert (equal (substitute 'a 'b '(a b c d e b f g) :from-end t :count 1)
+               '(a b c d e a f g)))
+
+(Assert (equal (let* ((nomodif '(a b c a b d a c b a e))
+                      (x (copy-list nomodif))
+                      (y (substitute 'z 'a x)))
+                 (and (equal nomodif x) y))
+               '(z b c z b d z c b z e)))
+(Assert (equal (let* ((nomodif '(a b c a b d a c b a e))
+                      (x (copy-list nomodif))
+                      (y (substitute 'z 'a x :count nil)))
+                 (and (equal nomodif x) y))
+               '(z b c z b d z c b z e)))
+(Assert (equal (let* ((nomodif '(a b c a b d a c b a e))
+                      (x (copy-list nomodif))
+                      (y (substitute 'z 'a x :key nil)))
+                 (and (equal nomodif x) y))
+               '(z b c z b d z c b z e)))
+(Assert (equal (let* ((nomodif '(a b c a b d a c b a e))
+                      (x (copy-list nomodif))
+                      (y (substitute 'z 'a x :count 100)))
+                 (and (equal nomodif x) y))
+               '(z b c z b d z c b z e)))
+(Assert (equal (let* ((nomodif '(a b c a b d a c b a e))
+                      (x (copy-list nomodif))
+                      (y (substitute 'z 'a x :count 0)))
+                 (and (equal nomodif x) y))
+               '(a b c a b d a c b a e)))
+(Assert (equal (let* ((nomodif '(a b c a b d a c b a e))
+                      (x (copy-list nomodif))
+                      (y (substitute 'z 'a x :count 1)))
+                 (and (equal nomodif x) y))
+               '(z b c a b d a c b a e)))
+(Assert (equal (let* ((nomodif '(a b c a b d a c b a e))
+                      (x (copy-list nomodif))
+                      (y (substitute 'z 'c x :count 1)))
+                 (and (equal nomodif x) y))
+               '(a b z a b d a c b a e)))
+(Assert (equal (let* ((nomodif '(a b c a b d a c b a e))
+                      (x (copy-list nomodif))
+                      (y (substitute 'z 'a x :from-end t)))
+                 (and (equal nomodif x) y))
+               '(z b c z b d z c b z e)))
+(Assert (equal (let* ((nomodif '(a b c a b d a c b a e))
+                      (x (copy-list nomodif))
+                      (y (substitute 'z 'a x :from-end t :count 1)))
+                 (and (equal nomodif x) y))
+               '(a b c a b d a c b z e)))
+(Assert (equal (let* ((nomodif '(a b c a b d a c b a e))
+                      (x (copy-list nomodif))
+                      (y (substitute 'z 'a x :from-end t :count 4)))
+                 (and (equal nomodif x) y))
+               '(z b c z b d z c b z e)))
+(Assert (equal (multiple-value-list
+                   (let* ((nomodif '(a b c a b d a c b a e))
+                          (x (copy-list nomodif)))
+                     (values
+                      (loop for i from 0 to 10
+                            collect (substitute 'z 'a x :start i))
+                      (equal nomodif x))))
+               '(((z b c z b d z c b z e) (a b c z b d z c b z e)
+                  (a b c z b d z c b z e) (a b c z b d z c b z e)
+                  (a b c a b d z c b z e) (a b c a b d z c b z e)
+                  (a b c a b d z c b z e) (a b c a b d a c b z e)
+                  (a b c a b d a c b z e) (a b c a b d a c b z e)
+                  (a b c a b d a c b a e))
+                 t)))
+(Assert (equal (multiple-value-list
+                   (let* ((nomodif '(a b c a b d a c b a e))
+                          (x (copy-list nomodif)))
+                     (values
+                      (loop for i from 0 to 10
+                            collect (substitute 'z 'a x :start i :end nil))
+                      (equal nomodif x))))
+               '(((z b c z b d z c b z e) (a b c z b d z c b z e)
+                  (a b c z b d z c b z e) (a b c z b d z c b z e)
+                  (a b c a b d z c b z e) (a b c a b d z c b z e)
+                  (a b c a b d z c b z e) (a b c a b d a c b z e)
+                  (a b c a b d a c b z e) (a b c a b d a c b z e)
+                  (a b c a b d a c b a e))
+                 t)))
+(Assert (equal
+         (let* ((nomodif '(1 2 3 2 6 1 2 4 1 3 2 7))
+                (x (copy-list nomodif))
+                (y (substitute 300 1 x :key #'1-)))
+           (and (equal nomodif x) y))
+         '(1 300 3 300 6 1 300 4 1 3 300 7)))
+
 ;; Test labels and inlining.
 (labels
     ((+ (&rest arguments)