diff options
| -rw-r--r-- | doc/lispref/lists.texi | 24 | ||||
| -rw-r--r-- | etc/NEWS | 3 | ||||
| -rw-r--r-- | lisp/emacs-lisp/gv.el | 6 | ||||
| -rw-r--r-- | lisp/emacs-lisp/map.el | 21 | ||||
| -rw-r--r-- | lisp/subr.el | 9 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/map-tests.el | 12 |
6 files changed, 52 insertions, 23 deletions
diff --git a/doc/lispref/lists.texi b/doc/lispref/lists.texi index 966d8f18b17..0c993806824 100644 --- a/doc/lispref/lists.texi +++ b/doc/lispref/lists.texi | |||
| @@ -1589,16 +1589,20 @@ keys may not be symbols: | |||
| 1589 | @end smallexample | 1589 | @end smallexample |
| 1590 | @end defun | 1590 | @end defun |
| 1591 | 1591 | ||
| 1592 | @defun alist-get key alist &optional default remove | 1592 | @defun alist-get key alist &optional default remove testfn |
| 1593 | This function is like @code{assq}, but instead of returning the entire | 1593 | This function is similar to @code{assq}. It finds the first |
| 1594 | association for @var{key} in @var{alist}, | 1594 | association @w{@code{(@var{key} . @var{value})}} by comparing |
| 1595 | @w{@code{(@var{key} . @var{value})}}, it returns just the @var{value}. | 1595 | @var{key} with @var{alist} elements, and, if found, returns the |
| 1596 | If @var{key} is not found in @var{alist}, it returns @var{default}. | 1596 | @var{value} of that association. If no association is found, the |
| 1597 | 1597 | function returns @var{default}. Comparison of @var{key} against | |
| 1598 | This is a generalized variable (@pxref{Generalized Variables}) that | 1598 | @var{alist} elements uses the function specified by @var{testfn}, |
| 1599 | can be used to change a value with @code{setf}. When using it to set | 1599 | defaulting to @code{eq}. |
| 1600 | a value, optional argument @var{remove} non-@code{nil} means to remove | 1600 | |
| 1601 | @var{key} from @var{alist} if the new value is @code{eql} to @var{default}. | 1601 | This is a generalized variable (@pxref{Generalized Variables}) |
| 1602 | that can be used to change a value with @code{setf}. When | ||
| 1603 | using it to set a value, optional argument @var{remove} non-@code{nil} | ||
| 1604 | means to remove @var{key}'s association from @var{alist} if the new | ||
| 1605 | value is @code{eql} to @var{default}. | ||
| 1602 | @end defun | 1606 | @end defun |
| 1603 | 1607 | ||
| 1604 | @defun rassq value alist | 1608 | @defun rassq value alist |
| @@ -1119,6 +1119,9 @@ break. | |||
| 1119 | 1119 | ||
| 1120 | * Lisp Changes in Emacs 26.1 | 1120 | * Lisp Changes in Emacs 26.1 |
| 1121 | 1121 | ||
| 1122 | +++ | ||
| 1123 | ** New optional argument TESTFN in 'alist-get', 'map-elt' and 'map-put'. | ||
| 1124 | |||
| 1122 | ** New function 'seq-set-equal-p' to check if SEQUENCE1 and SEQUENCE2 | 1125 | ** New function 'seq-set-equal-p' to check if SEQUENCE1 and SEQUENCE2 |
| 1123 | contain the same elements, regardless of the order. | 1126 | contain the same elements, regardless of the order. |
| 1124 | 1127 | ||
diff --git a/lisp/emacs-lisp/gv.el b/lisp/emacs-lisp/gv.el index c5c12a6414c..27376fc7f95 100644 --- a/lisp/emacs-lisp/gv.el +++ b/lisp/emacs-lisp/gv.el | |||
| @@ -377,10 +377,12 @@ The return value is the last VAL in the list. | |||
| 377 | `(with-current-buffer ,buf (set (make-local-variable ,var) ,v)))) | 377 | `(with-current-buffer ,buf (set (make-local-variable ,var) ,v)))) |
| 378 | 378 | ||
| 379 | (gv-define-expander alist-get | 379 | (gv-define-expander alist-get |
| 380 | (lambda (do key alist &optional default remove) | 380 | (lambda (do key alist &optional default remove testfn) |
| 381 | (macroexp-let2 macroexp-copyable-p k key | 381 | (macroexp-let2 macroexp-copyable-p k key |
| 382 | (gv-letplace (getter setter) alist | 382 | (gv-letplace (getter setter) alist |
| 383 | (macroexp-let2 nil p `(assq ,k ,getter) | 383 | (macroexp-let2 nil p `(if (and ,testfn (not (eq ,testfn 'eq))) |
| 384 | (assoc ,k ,getter ,testfn) | ||
| 385 | (assq ,k ,getter)) | ||
| 384 | (funcall do (if (null default) `(cdr ,p) | 386 | (funcall do (if (null default) `(cdr ,p) |
| 385 | `(if ,p (cdr ,p) ,default)) | 387 | `(if ,p (cdr ,p) ,default)) |
| 386 | (lambda (v) | 388 | (lambda (v) |
diff --git a/lisp/emacs-lisp/map.el b/lisp/emacs-lisp/map.el index a89457e877d..31ba075c40f 100644 --- a/lisp/emacs-lisp/map.el +++ b/lisp/emacs-lisp/map.el | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | ;; Author: Nicolas Petton <nicolas@petton.fr> | 5 | ;; Author: Nicolas Petton <nicolas@petton.fr> |
| 6 | ;; Keywords: convenience, map, hash-table, alist, array | 6 | ;; Keywords: convenience, map, hash-table, alist, array |
| 7 | ;; Version: 1.1 | 7 | ;; Version: 1.2 |
| 8 | ;; Package: map | 8 | ;; Package: map |
| 9 | 9 | ||
| 10 | ;; Maintainer: emacs-devel@gnu.org | 10 | ;; Maintainer: emacs-devel@gnu.org |
| @@ -93,11 +93,13 @@ Returns the result of evaluating the form associated with MAP-VAR's type." | |||
| 93 | ((arrayp ,map-var) ,(plist-get args :array)) | 93 | ((arrayp ,map-var) ,(plist-get args :array)) |
| 94 | (t (error "Unsupported map: %s" ,map-var))))) | 94 | (t (error "Unsupported map: %s" ,map-var))))) |
| 95 | 95 | ||
| 96 | (defun map-elt (map key &optional default) | 96 | (defun map-elt (map key &optional default testfn) |
| 97 | "Lookup KEY in MAP and return its associated value. | 97 | "Lookup KEY in MAP and return its associated value. |
| 98 | If KEY is not found, return DEFAULT which defaults to nil. | 98 | If KEY is not found, return DEFAULT which defaults to nil. |
| 99 | 99 | ||
| 100 | If MAP is a list, `eql' is used to lookup KEY. | 100 | If MAP is a list, `eql' is used to lookup KEY. Optional argument |
| 101 | TESTFN, if non-nil, means use its function definition instead of | ||
| 102 | `eql'. | ||
| 101 | 103 | ||
| 102 | MAP can be a list, hash-table or array." | 104 | MAP can be a list, hash-table or array." |
| 103 | (declare | 105 | (declare |
| @@ -106,30 +108,33 @@ MAP can be a list, hash-table or array." | |||
| 106 | (gv-letplace (mgetter msetter) `(gv-delay-error ,map) | 108 | (gv-letplace (mgetter msetter) `(gv-delay-error ,map) |
| 107 | (macroexp-let2* nil | 109 | (macroexp-let2* nil |
| 108 | ;; Eval them once and for all in the right order. | 110 | ;; Eval them once and for all in the right order. |
| 109 | ((key key) (default default)) | 111 | ((key key) (default default) (testfn testfn)) |
| 110 | `(if (listp ,mgetter) | 112 | `(if (listp ,mgetter) |
| 111 | ;; Special case the alist case, since it can't be handled by the | 113 | ;; Special case the alist case, since it can't be handled by the |
| 112 | ;; map--put function. | 114 | ;; map--put function. |
| 113 | ,(gv-get `(alist-get ,key (gv-synthetic-place | 115 | ,(gv-get `(alist-get ,key (gv-synthetic-place |
| 114 | ,mgetter ,msetter) | 116 | ,mgetter ,msetter) |
| 115 | ,default) | 117 | ,default nil ,testfn) |
| 116 | do) | 118 | do) |
| 117 | ,(funcall do `(map-elt ,mgetter ,key ,default) | 119 | ,(funcall do `(map-elt ,mgetter ,key ,default) |
| 118 | (lambda (v) `(map--put ,mgetter ,key ,v))))))))) | 120 | (lambda (v) `(map--put ,mgetter ,key ,v))))))))) |
| 119 | (map--dispatch map | 121 | (map--dispatch map |
| 120 | :list (alist-get key map default) | 122 | :list (alist-get key map default nil testfn) |
| 121 | :hash-table (gethash key map default) | 123 | :hash-table (gethash key map default) |
| 122 | :array (if (and (>= key 0) (< key (seq-length map))) | 124 | :array (if (and (>= key 0) (< key (seq-length map))) |
| 123 | (seq-elt map key) | 125 | (seq-elt map key) |
| 124 | default))) | 126 | default))) |
| 125 | 127 | ||
| 126 | (defmacro map-put (map key value) | 128 | (defmacro map-put (map key value &optional testfn) |
| 127 | "Associate KEY with VALUE in MAP and return VALUE. | 129 | "Associate KEY with VALUE in MAP and return VALUE. |
| 128 | If KEY is already present in MAP, replace the associated value | 130 | If KEY is already present in MAP, replace the associated value |
| 129 | with VALUE. | 131 | with VALUE. |
| 132 | When MAP is a list, test equality with TESTFN if non-nil, otherwise use `eql'. | ||
| 133 | TESTFN, if non-nil, means use its function definition instead of | ||
| 134 | `eql'. | ||
| 130 | 135 | ||
| 131 | MAP can be a list, hash-table or array." | 136 | MAP can be a list, hash-table or array." |
| 132 | `(setf (map-elt ,map ,key) ,value)) | 137 | `(setf (map-elt ,map ,key nil ,testfn) ,value)) |
| 133 | 138 | ||
| 134 | (defun map-delete (map key) | 139 | (defun map-delete (map key) |
| 135 | "Delete KEY from MAP and return MAP. | 140 | "Delete KEY from MAP and return MAP. |
diff --git a/lisp/subr.el b/lisp/subr.el index a9edff6166f..d9d918ed12d 100644 --- a/lisp/subr.el +++ b/lisp/subr.el | |||
| @@ -725,15 +725,18 @@ Elements of ALIST that are not conses are ignored." | |||
| 725 | (setq tail tail-cdr)))) | 725 | (setq tail tail-cdr)))) |
| 726 | alist) | 726 | alist) |
| 727 | 727 | ||
| 728 | (defun alist-get (key alist &optional default remove) | 728 | (defun alist-get (key alist &optional default remove testfn) |
| 729 | "Return the value associated with KEY in ALIST, using `assq'. | 729 | "Return the value associated with KEY in ALIST. |
| 730 | If KEY is not found in ALIST, return DEFAULT. | 730 | If KEY is not found in ALIST, return DEFAULT. |
| 731 | Use TESTFN to lookup in the alist if non-nil. Otherwise, use `assq'. | ||
| 731 | 732 | ||
| 732 | This is a generalized variable suitable for use with `setf'. | 733 | This is a generalized variable suitable for use with `setf'. |
| 733 | When using it to set a value, optional argument REMOVE non-nil | 734 | When using it to set a value, optional argument REMOVE non-nil |
| 734 | means to remove KEY from ALIST if the new value is `eql' to DEFAULT." | 735 | means to remove KEY from ALIST if the new value is `eql' to DEFAULT." |
| 735 | (ignore remove) ;;Silence byte-compiler. | 736 | (ignore remove) ;;Silence byte-compiler. |
| 736 | (let ((x (assq key alist))) | 737 | (let ((x (if (not testfn) |
| 738 | (assq key alist) | ||
| 739 | (assoc key alist testfn)))) | ||
| 737 | (if x (cdr x) default))) | 740 | (if x (cdr x) default))) |
| 738 | 741 | ||
| 739 | (defun remove (elt seq) | 742 | (defun remove (elt seq) |
diff --git a/test/lisp/emacs-lisp/map-tests.el b/test/lisp/emacs-lisp/map-tests.el index 07e85cc5391..15b0655040c 100644 --- a/test/lisp/emacs-lisp/map-tests.el +++ b/test/lisp/emacs-lisp/map-tests.el | |||
| @@ -63,6 +63,11 @@ Evaluate BODY for each created map. | |||
| 63 | (with-maps-do map | 63 | (with-maps-do map |
| 64 | (should (= 5 (map-elt map 7 5))))) | 64 | (should (= 5 (map-elt map 7 5))))) |
| 65 | 65 | ||
| 66 | (ert-deftest test-map-elt-testfn () | ||
| 67 | (let ((map (list (cons "a" 1) (cons "b" 2)))) | ||
| 68 | (should-not (map-elt map "a")) | ||
| 69 | (should (map-elt map "a" nil 'equal)))) | ||
| 70 | |||
| 66 | (ert-deftest test-map-elt-with-nil-value () | 71 | (ert-deftest test-map-elt-with-nil-value () |
| 67 | (should (null (map-elt '((a . 1) | 72 | (should (null (map-elt '((a . 1) |
| 68 | (b)) | 73 | (b)) |
| @@ -94,6 +99,13 @@ Evaluate BODY for each created map. | |||
| 94 | (should (eq (map-elt alist 2) | 99 | (should (eq (map-elt alist 2) |
| 95 | 'b)))) | 100 | 'b)))) |
| 96 | 101 | ||
| 102 | (ert-deftest test-map-put-testfn-alist () | ||
| 103 | (let ((alist (list (cons "a" 1) (cons "b" 2)))) | ||
| 104 | (map-put alist "a" 3 'equal) | ||
| 105 | (should-not (cddr alist)) | ||
| 106 | (map-put alist "a" 9) | ||
| 107 | (should (cddr alist)))) | ||
| 108 | |||
| 97 | (ert-deftest test-map-put-return-value () | 109 | (ert-deftest test-map-put-return-value () |
| 98 | (let ((ht (make-hash-table))) | 110 | (let ((ht (make-hash-table))) |
| 99 | (should (eq (map-put ht 'a 'hello) 'hello)))) | 111 | (should (eq (map-put ht 'a 'hello) 'hello)))) |