]> git.eshelyaron.com Git - emacs.git/commitdiff
Add predicate proper-list-p
authorBasil L. Contovounesios <contovob@tcd.ie>
Tue, 10 Jul 2018 01:46:33 +0000 (18:46 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Tue, 10 Jul 2018 02:00:43 +0000 (19:00 -0700)
For discussion, see emacs-devel thread starting at
https://lists.gnu.org/archive/html/emacs-devel/2018-04/msg00460.html.

* lisp/subr.el (proper-list-p): New function.
Implementation suggested by Paul Eggert <eggert@cs.ucla.edu> in
https://lists.gnu.org/archive/html/emacs-devel/2018-06/msg00138.html.
* doc/lispref/lists.texi (List Elements):
* etc/NEWS: Document proper-list-p.
* lisp/org/ob-core.el (org-babel-insert-result):
* lisp/emacs-lisp/byte-opt.el (byte-optimize-if):
* lisp/emacs-lisp/cl-macs.el (cl--make-usage-args): Use proper-list-p.
* lisp/emacs-lisp/ert.el (ert--proper-list-p): Remove.
Replaced by proper-list-p in lisp/subr.el.
(ert--explain-equal-rec): Use proper-list-length.
* lisp/format.el (format-proper-list-p): Remove.
Replaced by proper-list-p in lisp/subr.el.
(format-annotate-single-property-change): Use proper-list-p.
* test/lisp/emacs-lisp/ert-tests.el (ert-test-proper-list-p):
Move from here...
* test/lisp/subr-tests.el (subr-tests--proper-list-length):
...to here, mutatis mutandis.

doc/lispref/lists.texi
etc/NEWS
lisp/emacs-lisp/byte-opt.el
lisp/emacs-lisp/cl-macs.el
lisp/emacs-lisp/ert.el
lisp/format.el
lisp/org/ob-core.el
lisp/subr.el
test/lisp/emacs-lisp/ert-tests.el
test/lisp/subr-tests.el

index 761750eb20ca5e1afadc1dbbcfcde9fa333c16d7..57cefeac962c384ccf6e7d7bc2e069407682e8f2 100644 (file)
@@ -153,6 +153,22 @@ considered a list and @code{not} when it is considered a truth value
 @end example
 @end defun
 
+@defun proper-list-p object
+This function returns the length of @var{object} if it is a proper
+list, @code{nil} otherwise (@pxref{Cons Cells}).  In addition to
+satisfying @code{listp}, a proper list is neither circular nor dotted.
+
+@example
+@group
+(proper-list-p '(a b c))
+    @result{} 3
+@end group
+@group
+(proper-list-p '(a b . c))
+    @result{} nil
+@end group
+@end example
+@end defun
 
 @node List Elements
 @section Accessing Elements of Lists
index dae028be7b0ea49663856c0eddd3b823c42e0eec..1a1e0d8b70e88b210c443d9609dc113c98f58b8a 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -703,6 +703,11 @@ manual for more details.
 \f
 * Lisp Changes in Emacs 27.1
 
++++
+** New function 'proper-list-p'.
+Given a proper list as argument, this predicate returns its length;
+otherwise, it returns nil.
+
 ** define-minor-mode automatically documents the meaning of ARG
 
 +++
index 3bc4c438d6a08cfcc5a447e2be8be04333a1b4b6..5c0b5e340bb70cfcf72a0138d645fe4f1e7f3c35 100644 (file)
   ;; (if <test> <then> nil) ==> (if <test> <then>)
   (let ((clause (nth 1 form)))
     (cond ((and (eq (car-safe clause) 'progn)
-                ;; `clause' is a proper list.
-                (null (cdr (last clause))))
+                (proper-list-p clause))
            (if (null (cddr clause))
                ;; A trivial `progn'.
                (byte-optimize-if `(if ,(cadr clause) ,@(nthcdr 2 form)))
index b50961adac9a856e7c47b80a2e70ead83e49a94c..011965acb5446250887e3e3ed96a29e9bfba5d65 100644 (file)
@@ -498,7 +498,7 @@ its argument list allows full Common Lisp conventions."
       ;; `&aux' args aren't arguments, so let's just drop them from the
       ;; usage info.
       (setq arglist (cl-subseq arglist 0 aux))))
-  (if (cdr-safe (last arglist))         ;Not a proper list.
+  (if (not (proper-list-p arglist))
       (let* ((last (last arglist))
              (tail (cdr last)))
         (unwind-protect
index 32bb367cdb371128f90e92962cac217b55c194cc..cad21044f1590ced56ca42df0bfd7c6169d3696a 100644 (file)
@@ -472,18 +472,6 @@ Errors during evaluation are caught and handled like nil."
 ;; buffer.  Perhaps explanations should be reported through `ert-info'
 ;; rather than as part of the condition.
 
-(defun ert--proper-list-p (x)
-  "Return non-nil if X is a proper list, nil otherwise."
-  (cl-loop
-   for firstp = t then nil
-   for fast = x then (cddr fast)
-   for slow = x then (cdr slow) do
-   (when (null fast) (cl-return t))
-   (when (not (consp fast)) (cl-return nil))
-   (when (null (cdr fast)) (cl-return t))
-   (when (not (consp (cdr fast))) (cl-return nil))
-   (when (and (not firstp) (eq fast slow)) (cl-return nil))))
-
 (defun ert--explain-format-atom (x)
   "Format the atom X for `ert--explain-equal'."
   (pcase x
@@ -494,17 +482,17 @@ Errors during evaluation are caught and handled like nil."
 (defun ert--explain-equal-rec (a b)
   "Return a programmer-readable explanation of why A and B are not `equal'.
 Returns nil if they are."
-  (if (not (equal (type-of a) (type-of b)))
+  (if (not (eq (type-of a) (type-of b)))
       `(different-types ,a ,b)
     (pcase-exhaustive a
       ((pred consp)
-       (let ((a-proper-p (ert--proper-list-p a))
-             (b-proper-p (ert--proper-list-p b)))
-         (if (not (eql (not a-proper-p) (not b-proper-p)))
+       (let ((a-length (proper-list-p a))
+             (b-length (proper-list-p b)))
+         (if (not (eq (not a-length) (not b-length)))
              `(one-list-proper-one-improper ,a ,b)
-           (if a-proper-p
-               (if (not (equal (length a) (length b)))
-                   `(proper-lists-of-different-length ,(length a) ,(length b)
+           (if a-length
+               (if (/= a-length b-length)
+                   `(proper-lists-of-different-length ,a-length ,b-length
                                                       ,a ,b
                                                       first-mismatch-at
                                                       ,(cl-mismatch a b :test 'equal))
@@ -523,7 +511,7 @@ Returns nil if they are."
                      (cl-assert (equal a b) t)
                      nil))))))))
       ((pred arrayp)
-       (if (not (equal (length a) (length b)))
+       (if (/= (length a) (length b))
            `(arrays-of-different-length ,(length a) ,(length b)
                                         ,a ,b
                                         ,@(unless (char-table-p a)
index 2f198e3eb718830320bc2fa2ad5e70c73ce7935f..1222abbf658ddd0d3e1b86c673e0ab8f054ad610 100644 (file)
@@ -539,14 +539,6 @@ Compare using `equal'."
        (setq tail next)))
     (cons acopy bcopy)))
 
-(defun format-proper-list-p (list)
-  "Return t if LIST is a proper list.
-A proper list is a list ending with a nil cdr, not with an atom "
-  (when (listp list)
-    (while (consp list)
-      (setq list (cdr list)))
-    (null list)))
-
 (defun format-reorder (items order)
   "Arrange ITEMS to follow partial ORDER.
 Elements of ITEMS equal to elements of ORDER will be rearranged
@@ -1005,8 +997,8 @@ either strings, or lists of the form (PARAMETER VALUE)."
       ;; If either old or new is a list, have to treat both that way.
       (if (and (or (listp old) (listp new))
               (not (get prop 'format-list-atomic-p)))
-         (if (or (not (format-proper-list-p old))
-                 (not (format-proper-list-p new)))
+          (if (not (and (proper-list-p old)
+                        (proper-list-p new)))
              (format-annotate-atomic-property-change prop-alist old new)
            (let* ((old (if (listp old) old (list old)))
                   (new (if (listp new) new (list new)))
index 5d5faaa6fd03310d9461d6b0cadf686524c17cc2..a5449fe35e966afa71710ab9a262ed5450c07904 100644 (file)
@@ -2310,10 +2310,9 @@ INFO may provide the values of these header arguments (in the
                       (lambda (r)
                         ;; Non-nil when result R can be turned into
                         ;; a table.
-                        (and (listp r)
-                             (null (cdr (last r)))
+                         (and (proper-list-p r)
                              (cl-every
-                              (lambda (e) (or (atom e) (null (cdr (last e)))))
+                               (lambda (e) (or (atom e) (proper-list-p e)))
                               result)))))
                  ;; insert results based on type
                  (cond
index ca184d8fc81c9e0732fb99f28dd21b8cc0400b0c..c1d90e3fb18bfef9c14be0996bc8a39f5d874047 100644 (file)
@@ -555,6 +555,12 @@ If N is omitted or nil, remove the last element."
   (declare (compiler-macro (lambda (_) `(= 0 ,number))))
   (= 0 number))
 
+(defun proper-list-p (object)
+  "Return OBJECT's length if it is a proper list, nil otherwise.
+A proper list is neither circular nor dotted (i.e., its last cdr
+is nil)."
+  (and (listp object) (ignore-errors (length object))))
+
 (defun delete-dups (list)
   "Destructively remove `equal' duplicates from LIST.
 Store the result in LIST and return it.  LIST must be a proper list.
index e92b4342748d2dbc8fca91a7ec60a130d8732b07..cb957bd9fd697f22d81f22d858b92b8f3ed3fa69 100644 (file)
@@ -496,48 +496,6 @@ This macro is used to test if macroexpansion in `should' works."
 
 
 ;;; Tests for utility functions.
-(ert-deftest ert-test-proper-list-p ()
-  (should (ert--proper-list-p '()))
-  (should (ert--proper-list-p '(1)))
-  (should (ert--proper-list-p '(1 2)))
-  (should (ert--proper-list-p '(1 2 3)))
-  (should (ert--proper-list-p '(1 2 3 4)))
-  (should (not (ert--proper-list-p 'a)))
-  (should (not (ert--proper-list-p '(1 . a))))
-  (should (not (ert--proper-list-p '(1 2 . a))))
-  (should (not (ert--proper-list-p '(1 2 3 . a))))
-  (should (not (ert--proper-list-p '(1 2 3 4 . a))))
-  (let ((a (list 1)))
-    (setf (cdr (last a)) a)
-    (should (not (ert--proper-list-p a))))
-  (let ((a (list 1 2)))
-    (setf (cdr (last a)) a)
-    (should (not (ert--proper-list-p a))))
-  (let ((a (list 1 2 3)))
-    (setf (cdr (last a)) a)
-    (should (not (ert--proper-list-p a))))
-  (let ((a (list 1 2 3 4)))
-    (setf (cdr (last a)) a)
-    (should (not (ert--proper-list-p a))))
-  (let ((a (list 1 2)))
-    (setf (cdr (last a)) (cdr a))
-    (should (not (ert--proper-list-p a))))
-  (let ((a (list 1 2 3)))
-    (setf (cdr (last a)) (cdr a))
-    (should (not (ert--proper-list-p a))))
-  (let ((a (list 1 2 3 4)))
-    (setf (cdr (last a)) (cdr a))
-    (should (not (ert--proper-list-p a))))
-  (let ((a (list 1 2 3)))
-    (setf (cdr (last a)) (cddr a))
-    (should (not (ert--proper-list-p a))))
-  (let ((a (list 1 2 3 4)))
-    (setf (cdr (last a)) (cddr a))
-    (should (not (ert--proper-list-p a))))
-  (let ((a (list 1 2 3 4)))
-    (setf (cdr (last a)) (cl-cdddr a))
-    (should (not (ert--proper-list-p a)))))
-
 (ert-deftest ert-test-parse-keys-and-body ()
   (should (equal (ert--parse-keys-and-body '(foo)) '(nil (foo))))
   (should (equal (ert--parse-keys-and-body '(:bar foo)) '((:bar foo) nil)))
index 52b61d9fb974e4405d7a3d458a25269d06e5b642..86938d5dbe05ea78383f1efcedf46bc2af94358f 100644 (file)
@@ -306,6 +306,24 @@ cf. Bug#25477."
   (should (eq (string-to-char (symbol-name (gensym))) ?g))
   (should (eq (string-to-char (symbol-name (gensym "X"))) ?X)))
 
+(ert-deftest subr-tests--proper-list-p ()
+  "Test `proper-list-p' behavior."
+  (dotimes (length 4)
+    ;; Proper and dotted lists.
+    (let ((list (make-list length 0)))
+      (should (= (proper-list-p list) length))
+      (should (not (proper-list-p (nconc list 0)))))
+    ;; Circular lists.
+    (dotimes (n (1+ length))
+      (let ((circle (make-list (1+ length) 0)))
+        (should (not (proper-list-p (nconc circle (nthcdr n circle))))))))
+  ;; Atoms.
+  (should (not (proper-list-p 0)))
+  (should (not (proper-list-p "")))
+  (should (not (proper-list-p [])))
+  (should (not (proper-list-p (make-bool-vector 0 nil))))
+  (should (not (proper-list-p (make-symbol "a")))))
+
 (ert-deftest subr-tests--assq-delete-all ()
   "Test `assq-delete-all' behavior."
   (cl-flet ((new-list-fn