1 ;;; map.el --- Map manipulation functions -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2015 Free Software Foundation, Inc.
5 ;; Author: Nicolas Petton <nicolas@petton.fr>
6 ;; Keywords: convenience, map, hash-table, alist, array
10 ;; Maintainer: emacs-devel@gnu.org
12 ;; This file is part of GNU Emacs.
14 ;; GNU Emacs is free software: you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation, either version 3 of the License, or
17 ;; (at your option) any later version.
19 ;; GNU Emacs is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
29 ;; map.el provides map-manipulation functions that work on alists,
30 ;; hash-table and arrays. All functions are prefixed with "map-".
32 ;; Functions taking a predicate or iterating over a map using a
33 ;; function take the function as their first argument. All other
34 ;; functions take the map as their first argument.
37 ;; - Add support for char-tables
38 ;; - Maybe add support for gv?
39 ;; - See if we can integrate text-properties
40 ;; - A macro similar to let-alist but working on any type of map could
47 (pcase-defmacro map
(&rest args
)
48 "pcase pattern matching map elements.
49 Matches if the object is a map (list, hash-table or array), and
50 binds values from ARGS to their corresponding elements of the map.
52 ARGS can be a list elements of the form (KEY PAT), in which case
53 KEY in an unquoted form.
55 ARGS can also be a list of symbols, which stands for ('SYMBOL
58 ,@(map--make-pcase-bindings args
)))
60 (defmacro map-let
(keys map
&rest body
)
61 "Bind the variables in KEYS to the elements of MAP then evaluate BODY.
63 KEYS can be a list of symbols, in which case each element will be
64 bound to the looked up value in MAP.
66 KEYS can also be a list of (KEY VARNAME) pairs, in which case
67 KEY is an unquoted form.
69 MAP can be a list, hash-table or array."
70 (declare (indent 2) (debug t
))
71 `(pcase-let ((,(map--make-pcase-patterns keys
) ,map
))
75 (defmacro map--dispatch
(map-var &rest args
)
76 "Evaluate one of the forms specified by ARGS based on the type of MAP.
78 The following keyword types are meaningful: `:list',
79 `:hash-table' and `:array'.
81 An error is thrown if MAP is neither a list, hash-table nor array.
83 Return RESULT if non-nil or the result of evaluation of the form."
84 (declare (debug t
) (indent 1))
85 `(cond ((listp ,map-var
) ,(plist-get args
:list
))
86 ((hash-table-p ,map-var
) ,(plist-get args
:hash-table
))
87 ((arrayp ,map-var
) ,(plist-get args
:array
))
88 (t (error "Unsupported map: %s" ,map-var
)))))
90 (defun map-elt (map key
&optional default
)
91 "Perform a lookup in MAP of KEY and return its associated value.
92 If KEY is not found, return DEFAULT which defaults to nil.
94 If MAP is a list, `eql' is used to lookup KEY.
96 MAP can be a list, hash-table or array."
100 (gv-letplace (mgetter msetter
) `(gv-delay-error ,map
)
102 ;; Eval them once and for all in the right order.
103 ((key key
) (default default
))
104 `(if (listp ,mgetter
)
105 ;; Special case the alist case, since it can't be handled by the
106 ;; map--put function.
107 ,(gv-get `(alist-get ,key
(gv-synthetic-place
111 ,(funcall do
`(map-elt ,mgetter
,key
,default
)
112 (lambda (v) `(map--put ,mgetter
,key
,v
)))))))))
114 :list
(alist-get key map default
)
115 :hash-table
(gethash key map default
)
116 :array
(if (and (>= key
0) (< key
(seq-length map
)))
120 (defmacro map-put
(map key value
)
121 "In MAP, associate KEY with VALUE and return MAP.
122 If KEY is already present in MAP, replace the associated value
125 MAP can be a list, hash-table or array."
126 (macroexp-let2 nil map map
128 (setf (map-elt ,map
,key
) ,value
)
131 (defmacro map-delete
(map key
)
132 "In MAP, delete the key KEY if present and return MAP.
133 If MAP is an array, store nil at the index KEY.
135 MAP can be a list, hash-table or array."
137 (gv-letplace (mgetter msetter
) `(gv-delay-error ,map
)
138 (macroexp-let2 nil key key
139 `(if (not (listp ,mgetter
))
140 (map--delete ,mgetter
,key
)
141 ;; The alist case is special, since it can't be handled by the
142 ;; map--delete function.
143 (setf (alist-get ,key
(gv-synthetic-place ,mgetter
,msetter
)
148 (defun map-nested-elt (map keys
&optional default
)
149 "Traverse MAP using KEYS and return the looked up value or DEFAULT if nil.
151 Map can be a nested map composed of alists, hash-tables and arrays."
152 (or (seq-reduce (lambda (acc key
)
159 (defun map-keys (map)
160 "Return the list of keys in MAP.
162 MAP can be a list, hash-table or array."
163 (map-apply (lambda (key _
) key
) map
))
165 (defun map-values (map)
166 "Return the list of values in MAP.
168 MAP can be a list, hash-table or array."
169 (map-apply (lambda (_ value
) value
) map
))
171 (defun map-pairs (map)
172 "Return the elements of MAP as key/value association lists.
174 MAP can be a list, hash-table or array."
175 (map-apply #'cons map
))
177 (defun map-length (map)
178 "Return the length of MAP.
180 MAP can be a list, hash-table or array."
181 (length (map-keys map
)))
183 (defun map-copy (map)
184 "Return a copy of MAP.
186 MAP can be a list, hash-table or array."
189 :hash-table
(copy-hash-table map
)
190 :array
(seq-copy map
)))
192 (defun map-apply (function map
)
193 "Apply FUNCTION to each element of MAP and return the result as a list.
194 FUNCTION is called with two arguments, the key and the value.
196 MAP can be a list, hash-table or array."
197 (funcall (map--dispatch map
198 :list
#'map--apply-alist
199 :hash-table
#'map--apply-hash-table
200 :array
#'map--apply-array
)
204 (defun map-keys-apply (function map
)
205 "Return the result of applying FUNCTION to each key of MAP.
207 MAP can be a list, hash-table or array."
208 (map-apply (lambda (key _
)
209 (funcall function key
))
212 (defun map-values-apply (function map
)
213 "Return the result of applying FUNCTION to each value of MAP.
215 MAP can be a list, hash-table or array."
216 (map-apply (lambda (_ val
)
217 (funcall function val
))
220 (defun map-filter (pred map
)
221 "Return an alist of key/val pairs for which (PRED key val) is non-nil in MAP.
223 MAP can be a list, hash-table or array."
224 (delq nil
(map-apply (lambda (key val
)
225 (if (funcall pred key val
)
230 (defun map-remove (pred map
)
231 "Return an alist of the key/val pairs for which (PRED key val) is nil in MAP.
233 MAP can be a list, hash-table or array."
234 (map-filter (lambda (key val
) (not (funcall pred key val
)))
238 "Return non-nil if MAP is a map (list, hash-table or array)."
243 (defun map-empty-p (map)
244 "Return non-nil is MAP is empty.
246 MAP can be a list, hash-table or array."
249 :array
(seq-empty-p map
)
250 :hash-table
(zerop (hash-table-count map
))))
252 (defun map-contains-key (map key
&optional testfn
)
253 "Return non-nil if MAP contain the key KEY, nil otherwise.
254 Equality is defined by TESTFN if non-nil or by `equal' if nil.
256 MAP can be a list, hash-table or array."
257 (seq-contains (map-keys map
) key testfn
))
259 (defun map-some (pred map
)
260 "Return a non-nil if (PRED key val) is non-nil for any key/value pair in MAP.
262 MAP can be a list, hash-table or array."
264 (map-apply (lambda (key value
)
265 (let ((result (funcall pred key value
)))
267 (throw 'map--break result
))))
271 (defun map-every-p (pred map
)
272 "Return non-nil if (PRED key val) is non-nil for all elements of the map MAP.
274 MAP can be a list, hash-table or array."
276 (map-apply (lambda (key value
)
277 (or (funcall pred key value
)
278 (throw 'map--break nil
)))
282 (defun map-merge (type &rest maps
)
283 "Merge into a map of type TYPE all the key/value pairs in the maps MAPS.
285 MAP can be a list, hash-table or array."
288 (map-apply (lambda (key value
)
289 (setf (map-elt result key
) value
))
291 (map-into result type
)))
293 (defun map-into (map type
)
294 "Convert the map MAP into a map of type TYPE.
296 TYPE can be one of the following symbols: list or hash-table.
297 MAP can be a list, hash-table or array."
299 (`list
(map-pairs map
))
300 (`hash-table
(map--into-hash-table map
))
301 (_ (error "Not a map type name: %S" type
))))
303 (defun map--put (map key v
)
305 :list
(let ((p (assoc key map
)))
307 (error "No place to change the mapping for %S" key
)))
308 :hash-table
(puthash key v map
)
309 :array
(aset map key v
)))
311 (defun map--apply-alist (function map
)
312 "Private function used to apply FUNCTION over MAP, MAP being an alist."
313 (seq-map (lambda (pair)
319 (defun map--delete (map key
)
321 :list
(error "No place to remove the mapping for %S" key
)
322 :hash-table
(remhash key map
)
323 :array
(and (>= key
0)
324 (<= key
(seq-length map
))
328 (defun map--apply-hash-table (function map
)
329 "Private function used to apply FUNCTION over MAP, MAP being a hash-table."
331 (maphash (lambda (key value
)
332 (push (funcall function key value
) result
))
336 (defun map--apply-array (function map
)
337 "Private function used to apply FUNCTION over MAP, MAP being an array."
339 (seq-map (lambda (elt)
341 (funcall function index elt
)
342 (setq index
(1+ index
))))
345 (defun map--into-hash-table (map)
346 "Convert MAP into a hash-table."
347 (let ((ht (make-hash-table :size
(map-length map
)
349 (map-apply (lambda (key value
)
350 (setf (map-elt ht key
) value
))
354 (defun map--make-pcase-bindings (args)
355 "Return a list of pcase bindings from ARGS to the elements of a map."
356 (seq-map (lambda (elt)
358 `(app (pcase--flip map-elt
,(car elt
)) ,(cadr elt
))
359 `(app (pcase--flip map-elt
',elt
) ,elt
)))
362 (defun map--make-pcase-patterns (args)
363 "Return a list of `(map ...)' pcase patterns built from ARGS."
365 (seq-map (lambda (elt)
366 (if (and (consp elt
) (eq 'map
(car elt
)))
367 (map--make-pcase-patterns elt
)