aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/lispref/lists.texi16
-rw-r--r--etc/NEWS5
-rw-r--r--lisp/emacs-lisp/byte-opt.el3
-rw-r--r--lisp/emacs-lisp/cl-macs.el2
-rw-r--r--lisp/emacs-lisp/ert.el28
-rw-r--r--lisp/format.el12
-rw-r--r--lisp/org/ob-core.el5
-rw-r--r--lisp/subr.el6
-rw-r--r--test/lisp/emacs-lisp/ert-tests.el42
-rw-r--r--test/lisp/subr-tests.el18
10 files changed, 59 insertions, 78 deletions
diff --git a/doc/lispref/lists.texi b/doc/lispref/lists.texi
index 761750eb20c..57cefeac962 100644
--- a/doc/lispref/lists.texi
+++ b/doc/lispref/lists.texi
@@ -153,6 +153,22 @@ considered a list and @code{not} when it is considered a truth value
153@end example 153@end example
154@end defun 154@end defun
155 155
156@defun proper-list-p object
157This function returns the length of @var{object} if it is a proper
158list, @code{nil} otherwise (@pxref{Cons Cells}). In addition to
159satisfying @code{listp}, a proper list is neither circular nor dotted.
160
161@example
162@group
163(proper-list-p '(a b c))
164 @result{} 3
165@end group
166@group
167(proper-list-p '(a b . c))
168 @result{} nil
169@end group
170@end example
171@end defun
156 172
157@node List Elements 173@node List Elements
158@section Accessing Elements of Lists 174@section Accessing Elements of Lists
diff --git a/etc/NEWS b/etc/NEWS
index dae028be7b0..1a1e0d8b70e 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -703,6 +703,11 @@ manual for more details.
703 703
704* Lisp Changes in Emacs 27.1 704* Lisp Changes in Emacs 27.1
705 705
706+++
707** New function 'proper-list-p'.
708Given a proper list as argument, this predicate returns its length;
709otherwise, it returns nil.
710
706** define-minor-mode automatically documents the meaning of ARG 711** define-minor-mode automatically documents the meaning of ARG
707 712
708+++ 713+++
diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el
index 3bc4c438d6a..5c0b5e340bb 100644
--- a/lisp/emacs-lisp/byte-opt.el
+++ b/lisp/emacs-lisp/byte-opt.el
@@ -982,8 +982,7 @@
982 ;; (if <test> <then> nil) ==> (if <test> <then>) 982 ;; (if <test> <then> nil) ==> (if <test> <then>)
983 (let ((clause (nth 1 form))) 983 (let ((clause (nth 1 form)))
984 (cond ((and (eq (car-safe clause) 'progn) 984 (cond ((and (eq (car-safe clause) 'progn)
985 ;; `clause' is a proper list. 985 (proper-list-p clause))
986 (null (cdr (last clause))))
987 (if (null (cddr clause)) 986 (if (null (cddr clause))
988 ;; A trivial `progn'. 987 ;; A trivial `progn'.
989 (byte-optimize-if `(if ,(cadr clause) ,@(nthcdr 2 form))) 988 (byte-optimize-if `(if ,(cadr clause) ,@(nthcdr 2 form)))
diff --git a/lisp/emacs-lisp/cl-macs.el b/lisp/emacs-lisp/cl-macs.el
index b50961adac9..011965acb54 100644
--- a/lisp/emacs-lisp/cl-macs.el
+++ b/lisp/emacs-lisp/cl-macs.el
@@ -498,7 +498,7 @@ its argument list allows full Common Lisp conventions."
498 ;; `&aux' args aren't arguments, so let's just drop them from the 498 ;; `&aux' args aren't arguments, so let's just drop them from the
499 ;; usage info. 499 ;; usage info.
500 (setq arglist (cl-subseq arglist 0 aux)))) 500 (setq arglist (cl-subseq arglist 0 aux))))
501 (if (cdr-safe (last arglist)) ;Not a proper list. 501 (if (not (proper-list-p arglist))
502 (let* ((last (last arglist)) 502 (let* ((last (last arglist))
503 (tail (cdr last))) 503 (tail (cdr last)))
504 (unwind-protect 504 (unwind-protect
diff --git a/lisp/emacs-lisp/ert.el b/lisp/emacs-lisp/ert.el
index 32bb367cdb3..cad21044f15 100644
--- a/lisp/emacs-lisp/ert.el
+++ b/lisp/emacs-lisp/ert.el
@@ -472,18 +472,6 @@ Errors during evaluation are caught and handled like nil."
472;; buffer. Perhaps explanations should be reported through `ert-info' 472;; buffer. Perhaps explanations should be reported through `ert-info'
473;; rather than as part of the condition. 473;; rather than as part of the condition.
474 474
475(defun ert--proper-list-p (x)
476 "Return non-nil if X is a proper list, nil otherwise."
477 (cl-loop
478 for firstp = t then nil
479 for fast = x then (cddr fast)
480 for slow = x then (cdr slow) do
481 (when (null fast) (cl-return t))
482 (when (not (consp fast)) (cl-return nil))
483 (when (null (cdr fast)) (cl-return t))
484 (when (not (consp (cdr fast))) (cl-return nil))
485 (when (and (not firstp) (eq fast slow)) (cl-return nil))))
486
487(defun ert--explain-format-atom (x) 475(defun ert--explain-format-atom (x)
488 "Format the atom X for `ert--explain-equal'." 476 "Format the atom X for `ert--explain-equal'."
489 (pcase x 477 (pcase x
@@ -494,17 +482,17 @@ Errors during evaluation are caught and handled like nil."
494(defun ert--explain-equal-rec (a b) 482(defun ert--explain-equal-rec (a b)
495 "Return a programmer-readable explanation of why A and B are not `equal'. 483 "Return a programmer-readable explanation of why A and B are not `equal'.
496Returns nil if they are." 484Returns nil if they are."
497 (if (not (equal (type-of a) (type-of b))) 485 (if (not (eq (type-of a) (type-of b)))
498 `(different-types ,a ,b) 486 `(different-types ,a ,b)
499 (pcase-exhaustive a 487 (pcase-exhaustive a
500 ((pred consp) 488 ((pred consp)
501 (let ((a-proper-p (ert--proper-list-p a)) 489 (let ((a-length (proper-list-p a))
502 (b-proper-p (ert--proper-list-p b))) 490 (b-length (proper-list-p b)))
503 (if (not (eql (not a-proper-p) (not b-proper-p))) 491 (if (not (eq (not a-length) (not b-length)))
504 `(one-list-proper-one-improper ,a ,b) 492 `(one-list-proper-one-improper ,a ,b)
505 (if a-proper-p 493 (if a-length
506 (if (not (equal (length a) (length b))) 494 (if (/= a-length b-length)
507 `(proper-lists-of-different-length ,(length a) ,(length b) 495 `(proper-lists-of-different-length ,a-length ,b-length
508 ,a ,b 496 ,a ,b
509 first-mismatch-at 497 first-mismatch-at
510 ,(cl-mismatch a b :test 'equal)) 498 ,(cl-mismatch a b :test 'equal))
@@ -523,7 +511,7 @@ Returns nil if they are."
523 (cl-assert (equal a b) t) 511 (cl-assert (equal a b) t)
524 nil)))))))) 512 nil))))))))
525 ((pred arrayp) 513 ((pred arrayp)
526 (if (not (equal (length a) (length b))) 514 (if (/= (length a) (length b))
527 `(arrays-of-different-length ,(length a) ,(length b) 515 `(arrays-of-different-length ,(length a) ,(length b)
528 ,a ,b 516 ,a ,b
529 ,@(unless (char-table-p a) 517 ,@(unless (char-table-p a)
diff --git a/lisp/format.el b/lisp/format.el
index 2f198e3eb71..1222abbf658 100644
--- a/lisp/format.el
+++ b/lisp/format.el
@@ -539,14 +539,6 @@ Compare using `equal'."
539 (setq tail next))) 539 (setq tail next)))
540 (cons acopy bcopy))) 540 (cons acopy bcopy)))
541 541
542(defun format-proper-list-p (list)
543 "Return t if LIST is a proper list.
544A proper list is a list ending with a nil cdr, not with an atom "
545 (when (listp list)
546 (while (consp list)
547 (setq list (cdr list)))
548 (null list)))
549
550(defun format-reorder (items order) 542(defun format-reorder (items order)
551 "Arrange ITEMS to follow partial ORDER. 543 "Arrange ITEMS to follow partial ORDER.
552Elements of ITEMS equal to elements of ORDER will be rearranged 544Elements of ITEMS equal to elements of ORDER will be rearranged
@@ -1005,8 +997,8 @@ either strings, or lists of the form (PARAMETER VALUE)."
1005 ;; If either old or new is a list, have to treat both that way. 997 ;; If either old or new is a list, have to treat both that way.
1006 (if (and (or (listp old) (listp new)) 998 (if (and (or (listp old) (listp new))
1007 (not (get prop 'format-list-atomic-p))) 999 (not (get prop 'format-list-atomic-p)))
1008 (if (or (not (format-proper-list-p old)) 1000 (if (not (and (proper-list-p old)
1009 (not (format-proper-list-p new))) 1001 (proper-list-p new)))
1010 (format-annotate-atomic-property-change prop-alist old new) 1002 (format-annotate-atomic-property-change prop-alist old new)
1011 (let* ((old (if (listp old) old (list old))) 1003 (let* ((old (if (listp old) old (list old)))
1012 (new (if (listp new) new (list new))) 1004 (new (if (listp new) new (list new)))
diff --git a/lisp/org/ob-core.el b/lisp/org/ob-core.el
index 5d5faaa6fd0..a5449fe35e9 100644
--- a/lisp/org/ob-core.el
+++ b/lisp/org/ob-core.el
@@ -2310,10 +2310,9 @@ INFO may provide the values of these header arguments (in the
2310 (lambda (r) 2310 (lambda (r)
2311 ;; Non-nil when result R can be turned into 2311 ;; Non-nil when result R can be turned into
2312 ;; a table. 2312 ;; a table.
2313 (and (listp r) 2313 (and (proper-list-p r)
2314 (null (cdr (last r)))
2315 (cl-every 2314 (cl-every
2316 (lambda (e) (or (atom e) (null (cdr (last e))))) 2315 (lambda (e) (or (atom e) (proper-list-p e)))
2317 result))))) 2316 result)))))
2318 ;; insert results based on type 2317 ;; insert results based on type
2319 (cond 2318 (cond
diff --git a/lisp/subr.el b/lisp/subr.el
index ca184d8fc81..c1d90e3fb18 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -555,6 +555,12 @@ If N is omitted or nil, remove the last element."
555 (declare (compiler-macro (lambda (_) `(= 0 ,number)))) 555 (declare (compiler-macro (lambda (_) `(= 0 ,number))))
556 (= 0 number)) 556 (= 0 number))
557 557
558(defun proper-list-p (object)
559 "Return OBJECT's length if it is a proper list, nil otherwise.
560A proper list is neither circular nor dotted (i.e., its last cdr
561is nil)."
562 (and (listp object) (ignore-errors (length object))))
563
558(defun delete-dups (list) 564(defun delete-dups (list)
559 "Destructively remove `equal' duplicates from LIST. 565 "Destructively remove `equal' duplicates from LIST.
560Store the result in LIST and return it. LIST must be a proper list. 566Store the result in LIST and return it. LIST must be a proper list.
diff --git a/test/lisp/emacs-lisp/ert-tests.el b/test/lisp/emacs-lisp/ert-tests.el
index e92b4342748..cb957bd9fd6 100644
--- a/test/lisp/emacs-lisp/ert-tests.el
+++ b/test/lisp/emacs-lisp/ert-tests.el
@@ -496,48 +496,6 @@ This macro is used to test if macroexpansion in `should' works."
496 496
497 497
498;;; Tests for utility functions. 498;;; Tests for utility functions.
499(ert-deftest ert-test-proper-list-p ()
500 (should (ert--proper-list-p '()))
501 (should (ert--proper-list-p '(1)))
502 (should (ert--proper-list-p '(1 2)))
503 (should (ert--proper-list-p '(1 2 3)))
504 (should (ert--proper-list-p '(1 2 3 4)))
505 (should (not (ert--proper-list-p 'a)))
506 (should (not (ert--proper-list-p '(1 . a))))
507 (should (not (ert--proper-list-p '(1 2 . a))))
508 (should (not (ert--proper-list-p '(1 2 3 . a))))
509 (should (not (ert--proper-list-p '(1 2 3 4 . a))))
510 (let ((a (list 1)))
511 (setf (cdr (last a)) a)
512 (should (not (ert--proper-list-p a))))
513 (let ((a (list 1 2)))
514 (setf (cdr (last a)) a)
515 (should (not (ert--proper-list-p a))))
516 (let ((a (list 1 2 3)))
517 (setf (cdr (last a)) a)
518 (should (not (ert--proper-list-p a))))
519 (let ((a (list 1 2 3 4)))
520 (setf (cdr (last a)) a)
521 (should (not (ert--proper-list-p a))))
522 (let ((a (list 1 2)))
523 (setf (cdr (last a)) (cdr a))
524 (should (not (ert--proper-list-p a))))
525 (let ((a (list 1 2 3)))
526 (setf (cdr (last a)) (cdr a))
527 (should (not (ert--proper-list-p a))))
528 (let ((a (list 1 2 3 4)))
529 (setf (cdr (last a)) (cdr a))
530 (should (not (ert--proper-list-p a))))
531 (let ((a (list 1 2 3)))
532 (setf (cdr (last a)) (cddr a))
533 (should (not (ert--proper-list-p a))))
534 (let ((a (list 1 2 3 4)))
535 (setf (cdr (last a)) (cddr a))
536 (should (not (ert--proper-list-p a))))
537 (let ((a (list 1 2 3 4)))
538 (setf (cdr (last a)) (cl-cdddr a))
539 (should (not (ert--proper-list-p a)))))
540
541(ert-deftest ert-test-parse-keys-and-body () 499(ert-deftest ert-test-parse-keys-and-body ()
542 (should (equal (ert--parse-keys-and-body '(foo)) '(nil (foo)))) 500 (should (equal (ert--parse-keys-and-body '(foo)) '(nil (foo))))
543 (should (equal (ert--parse-keys-and-body '(:bar foo)) '((:bar foo) nil))) 501 (should (equal (ert--parse-keys-and-body '(:bar foo)) '((:bar foo) nil)))
diff --git a/test/lisp/subr-tests.el b/test/lisp/subr-tests.el
index 52b61d9fb97..86938d5dbe0 100644
--- a/test/lisp/subr-tests.el
+++ b/test/lisp/subr-tests.el
@@ -306,6 +306,24 @@ cf. Bug#25477."
306 (should (eq (string-to-char (symbol-name (gensym))) ?g)) 306 (should (eq (string-to-char (symbol-name (gensym))) ?g))
307 (should (eq (string-to-char (symbol-name (gensym "X"))) ?X))) 307 (should (eq (string-to-char (symbol-name (gensym "X"))) ?X)))
308 308
309(ert-deftest subr-tests--proper-list-p ()
310 "Test `proper-list-p' behavior."
311 (dotimes (length 4)
312 ;; Proper and dotted lists.
313 (let ((list (make-list length 0)))
314 (should (= (proper-list-p list) length))
315 (should (not (proper-list-p (nconc list 0)))))
316 ;; Circular lists.
317 (dotimes (n (1+ length))
318 (let ((circle (make-list (1+ length) 0)))
319 (should (not (proper-list-p (nconc circle (nthcdr n circle))))))))
320 ;; Atoms.
321 (should (not (proper-list-p 0)))
322 (should (not (proper-list-p "")))
323 (should (not (proper-list-p [])))
324 (should (not (proper-list-p (make-bool-vector 0 nil))))
325 (should (not (proper-list-p (make-symbol "a")))))
326
309(ert-deftest subr-tests--assq-delete-all () 327(ert-deftest subr-tests--assq-delete-all ()
310 "Test `assq-delete-all' behavior." 328 "Test `assq-delete-all' behavior."
311 (cl-flet ((new-list-fn 329 (cl-flet ((new-list-fn