2015-04-18 16:22:16 +02:00
|
|
|
;;; map.el --- Map manipulation functions -*- lexical-binding: t; -*-
|
|
|
|
|
|
|
|
;; Copyright (C) 2015 Free Software Foundation, Inc.
|
|
|
|
|
|
|
|
;; Author: Nicolas Petton <nicolas@petton.fr>
|
|
|
|
;; Keywords: convenience, map, hash-table, alist, array
|
|
|
|
;; Version: 1.0
|
|
|
|
;; Package: map
|
|
|
|
|
|
|
|
;; Maintainer: emacs-devel@gnu.org
|
|
|
|
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
|
|
|
|
;; GNU Emacs is free software: you can redistribute it and/or modify
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
;; map.el provides map-manipulation functions that work on alists,
|
|
|
|
;; hash-table and arrays. All functions are prefixed with "map-".
|
|
|
|
;;
|
|
|
|
;; Functions taking a predicate or iterating over a map using a
|
|
|
|
;; function take the function as their first argument. All other
|
|
|
|
;; functions take the map as their first argument.
|
|
|
|
|
|
|
|
;; TODO:
|
|
|
|
;; - Add support for char-tables
|
|
|
|
;; - Maybe add support for gv?
|
|
|
|
;; - See if we can integrate text-properties
|
|
|
|
;; - A macro similar to let-alist but working on any type of map could
|
|
|
|
;; be really useful
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
(require 'seq)
|
|
|
|
|
2015-06-02 22:13:38 +02:00
|
|
|
(pcase-defmacro map (&rest args)
|
2015-10-26 22:23:02 +01:00
|
|
|
"Build a `pcase' pattern matching map elements.
|
2015-10-14 09:37:59 +02:00
|
|
|
|
2015-10-26 22:23:02 +01:00
|
|
|
The `pcase' pattern will match each element of PATTERN against
|
|
|
|
the corresponding elements of the map.
|
2015-10-14 09:37:59 +02:00
|
|
|
|
2015-10-26 22:23:02 +01:00
|
|
|
Extra elements of the map are ignored if fewer ARGS are
|
2015-10-14 09:37:59 +02:00
|
|
|
given, and the match does not fail.
|
2015-06-02 22:13:38 +02:00
|
|
|
|
2015-10-14 09:37:59 +02:00
|
|
|
ARGS can be a list of the form (KEY PAT), in which case KEY in an
|
|
|
|
unquoted form.
|
2015-06-15 12:43:09 +02:00
|
|
|
|
|
|
|
ARGS can also be a list of symbols, which stands for ('SYMBOL
|
|
|
|
SYMBOL)."
|
2015-06-02 22:13:38 +02:00
|
|
|
`(and (pred map-p)
|
|
|
|
,@(map--make-pcase-bindings args)))
|
|
|
|
|
2015-06-06 01:54:11 +02:00
|
|
|
(defmacro map-let (keys map &rest body)
|
|
|
|
"Bind the variables in KEYS to the elements of MAP then evaluate BODY.
|
2015-06-02 22:13:38 +02:00
|
|
|
|
2015-06-06 01:54:11 +02:00
|
|
|
KEYS can be a list of symbols, in which case each element will be
|
|
|
|
bound to the looked up value in MAP.
|
|
|
|
|
|
|
|
KEYS can also be a list of (KEY VARNAME) pairs, in which case
|
2015-06-15 12:43:09 +02:00
|
|
|
KEY is an unquoted form.
|
2015-06-06 01:54:11 +02:00
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-06-02 22:13:38 +02:00
|
|
|
(declare (indent 2) (debug t))
|
2015-06-06 01:54:11 +02:00
|
|
|
`(pcase-let ((,(map--make-pcase-patterns keys) ,map))
|
2015-06-02 22:13:38 +02:00
|
|
|
,@body))
|
|
|
|
|
2015-07-09 19:43:41 +02:00
|
|
|
(eval-when-compile
|
|
|
|
(defmacro map--dispatch (map-var &rest args)
|
|
|
|
"Evaluate one of the forms specified by ARGS based on the type of MAP.
|
2015-06-05 16:30:39 -04:00
|
|
|
|
|
|
|
The following keyword types are meaningful: `:list',
|
2015-06-06 01:25:04 +02:00
|
|
|
`:hash-table' and `:array'.
|
2015-06-05 16:30:39 -04:00
|
|
|
|
|
|
|
An error is thrown if MAP is neither a list, hash-table nor array.
|
|
|
|
|
2015-07-09 19:43:41 +02:00
|
|
|
Return RESULT if non-nil or the result of evaluation of the form."
|
|
|
|
(declare (debug t) (indent 1))
|
|
|
|
`(cond ((listp ,map-var) ,(plist-get args :list))
|
|
|
|
((hash-table-p ,map-var) ,(plist-get args :hash-table))
|
|
|
|
((arrayp ,map-var) ,(plist-get args :array))
|
|
|
|
(t (error "Unsupported map: %s" ,map-var)))))
|
2015-06-05 16:30:39 -04:00
|
|
|
|
2015-04-18 16:22:16 +02:00
|
|
|
(defun map-elt (map key &optional default)
|
2015-10-26 22:23:02 +01:00
|
|
|
"Lookup KEY in MAP and return its associated value.
|
2015-04-18 16:22:16 +02:00
|
|
|
If KEY is not found, return DEFAULT which defaults to nil.
|
|
|
|
|
2015-06-21 20:25:28 +02:00
|
|
|
If MAP is a list, `eql' is used to lookup KEY.
|
2015-05-16 11:30:12 +02:00
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-07-09 19:43:41 +02:00
|
|
|
(declare
|
|
|
|
(gv-expander
|
|
|
|
(lambda (do)
|
|
|
|
(gv-letplace (mgetter msetter) `(gv-delay-error ,map)
|
|
|
|
(macroexp-let2* nil
|
|
|
|
;; Eval them once and for all in the right order.
|
|
|
|
((key key) (default default))
|
|
|
|
`(if (listp ,mgetter)
|
|
|
|
;; Special case the alist case, since it can't be handled by the
|
|
|
|
;; map--put function.
|
|
|
|
,(gv-get `(alist-get ,key (gv-synthetic-place
|
|
|
|
,mgetter ,msetter)
|
|
|
|
,default)
|
|
|
|
do)
|
|
|
|
,(funcall do `(map-elt ,mgetter ,key ,default)
|
|
|
|
(lambda (v) `(map--put ,mgetter ,key ,v)))))))))
|
2015-04-18 16:22:16 +02:00
|
|
|
(map--dispatch map
|
2015-06-21 20:25:28 +02:00
|
|
|
:list (alist-get key map default)
|
2015-04-18 16:22:16 +02:00
|
|
|
:hash-table (gethash key map default)
|
2015-07-09 19:43:41 +02:00
|
|
|
:array (if (and (>= key 0) (< key (seq-length map)))
|
|
|
|
(seq-elt map key)
|
|
|
|
default)))
|
2015-04-18 16:22:16 +02:00
|
|
|
|
2015-06-21 23:44:50 +02:00
|
|
|
(defmacro map-put (map key value)
|
2015-10-26 22:23:02 +01:00
|
|
|
"Associate KEY with VALUE in MAP and return MAP.
|
2015-04-24 19:15:23 +02:00
|
|
|
If KEY is already present in MAP, replace the associated value
|
2015-05-16 11:30:12 +02:00
|
|
|
with VALUE.
|
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-07-09 19:43:41 +02:00
|
|
|
(macroexp-let2 nil map map
|
2015-06-21 23:44:50 +02:00
|
|
|
`(progn
|
2015-07-09 19:43:41 +02:00
|
|
|
(setf (map-elt ,map ,key) ,value)
|
|
|
|
,map)))
|
2015-04-18 16:22:16 +02:00
|
|
|
|
|
|
|
(defmacro map-delete (map key)
|
2015-10-26 22:23:02 +01:00
|
|
|
"Delete KEY from MAP and return MAP.
|
|
|
|
No error is signaled if KEY is not a key of MAP. If MAP is an
|
|
|
|
array, store nil at the index KEY.
|
2015-05-16 11:30:12 +02:00
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 16:22:16 +02:00
|
|
|
(declare (debug t))
|
2015-07-09 19:43:41 +02:00
|
|
|
(gv-letplace (mgetter msetter) `(gv-delay-error ,map)
|
|
|
|
(macroexp-let2 nil key key
|
|
|
|
`(if (not (listp ,mgetter))
|
|
|
|
(map--delete ,mgetter ,key)
|
|
|
|
;; The alist case is special, since it can't be handled by the
|
|
|
|
;; map--delete function.
|
|
|
|
(setf (alist-get ,key (gv-synthetic-place ,mgetter ,msetter)
|
|
|
|
nil t)
|
|
|
|
nil)
|
|
|
|
,mgetter))))
|
2015-04-18 16:22:16 +02:00
|
|
|
|
|
|
|
(defun map-nested-elt (map keys &optional default)
|
2015-04-29 19:01:40 +02:00
|
|
|
"Traverse MAP using KEYS and return the looked up value or DEFAULT if nil.
|
2015-05-16 11:30:12 +02:00
|
|
|
|
2015-04-18 16:22:16 +02:00
|
|
|
Map can be a nested map composed of alists, hash-tables and arrays."
|
|
|
|
(or (seq-reduce (lambda (acc key)
|
|
|
|
(when (map-p acc)
|
|
|
|
(map-elt acc key)))
|
|
|
|
keys
|
|
|
|
map)
|
|
|
|
default))
|
|
|
|
|
|
|
|
(defun map-keys (map)
|
2015-05-16 11:30:12 +02:00
|
|
|
"Return the list of keys in MAP.
|
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 20:04:17 +02:00
|
|
|
(map-apply (lambda (key _) key) map))
|
2015-04-18 16:22:16 +02:00
|
|
|
|
|
|
|
(defun map-values (map)
|
2015-05-16 11:30:12 +02:00
|
|
|
"Return the list of values in MAP.
|
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 20:04:17 +02:00
|
|
|
(map-apply (lambda (_ value) value) map))
|
2015-04-18 16:22:16 +02:00
|
|
|
|
|
|
|
(defun map-pairs (map)
|
2015-05-16 11:30:12 +02:00
|
|
|
"Return the elements of MAP as key/value association lists.
|
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-25 17:27:13 +01:00
|
|
|
(map-apply #'cons map))
|
2015-04-18 16:22:16 +02:00
|
|
|
|
|
|
|
(defun map-length (map)
|
2015-05-16 11:30:12 +02:00
|
|
|
"Return the length of MAP.
|
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 16:22:16 +02:00
|
|
|
(length (map-keys map)))
|
|
|
|
|
|
|
|
(defun map-copy (map)
|
2015-05-16 11:30:12 +02:00
|
|
|
"Return a copy of MAP.
|
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 16:22:16 +02:00
|
|
|
(map--dispatch map
|
|
|
|
:list (seq-copy map)
|
|
|
|
:hash-table (copy-hash-table map)
|
|
|
|
:array (seq-copy map)))
|
|
|
|
|
|
|
|
(defun map-apply (function map)
|
2015-04-29 19:01:40 +02:00
|
|
|
"Apply FUNCTION to each element of MAP and return the result as a list.
|
2015-05-16 11:30:12 +02:00
|
|
|
FUNCTION is called with two arguments, the key and the value.
|
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 16:22:16 +02:00
|
|
|
(funcall (map--dispatch map
|
|
|
|
:list #'map--apply-alist
|
|
|
|
:hash-table #'map--apply-hash-table
|
|
|
|
:array #'map--apply-array)
|
|
|
|
function
|
|
|
|
map))
|
|
|
|
|
|
|
|
(defun map-keys-apply (function map)
|
2015-05-16 11:30:12 +02:00
|
|
|
"Return the result of applying FUNCTION to each key of MAP.
|
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 20:04:17 +02:00
|
|
|
(map-apply (lambda (key _)
|
2015-04-18 16:22:16 +02:00
|
|
|
(funcall function key))
|
|
|
|
map))
|
|
|
|
|
|
|
|
(defun map-values-apply (function map)
|
2015-05-16 11:30:12 +02:00
|
|
|
"Return the result of applying FUNCTION to each value of MAP.
|
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 20:04:17 +02:00
|
|
|
(map-apply (lambda (_ val)
|
2015-04-18 16:22:16 +02:00
|
|
|
(funcall function val))
|
|
|
|
map))
|
|
|
|
|
|
|
|
(defun map-filter (pred map)
|
2015-06-16 23:04:03 +02:00
|
|
|
"Return an alist of key/val pairs for which (PRED key val) is non-nil in MAP.
|
2015-05-16 11:30:12 +02:00
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 16:22:16 +02:00
|
|
|
(delq nil (map-apply (lambda (key val)
|
|
|
|
(if (funcall pred key val)
|
|
|
|
(cons key val)
|
|
|
|
nil))
|
|
|
|
map)))
|
|
|
|
|
|
|
|
(defun map-remove (pred map)
|
2015-05-16 11:30:12 +02:00
|
|
|
"Return an alist of the key/val pairs for which (PRED key val) is nil in MAP.
|
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 16:22:16 +02:00
|
|
|
(map-filter (lambda (key val) (not (funcall pred key val)))
|
|
|
|
map))
|
|
|
|
|
|
|
|
(defun map-p (map)
|
|
|
|
"Return non-nil if MAP is a map (list, hash-table or array)."
|
|
|
|
(or (listp map)
|
|
|
|
(hash-table-p map)
|
|
|
|
(arrayp map)))
|
|
|
|
|
|
|
|
(defun map-empty-p (map)
|
2015-10-26 22:23:02 +01:00
|
|
|
"Return non-nil if MAP is empty.
|
2015-05-16 11:30:12 +02:00
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-29 19:01:56 +02:00
|
|
|
(map--dispatch map
|
|
|
|
:list (null map)
|
|
|
|
:array (seq-empty-p map)
|
|
|
|
:hash-table (zerop (hash-table-count map))))
|
2015-04-18 16:22:16 +02:00
|
|
|
|
2015-09-06 00:45:48 +02:00
|
|
|
(defun map-contains-key (map key &optional testfn)
|
2015-10-26 22:23:02 +01:00
|
|
|
"Return non-nil if MAP contain KEY, nil otherwise.
|
2015-05-16 11:30:12 +02:00
|
|
|
Equality is defined by TESTFN if non-nil or by `equal' if nil.
|
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-09-06 00:45:48 +02:00
|
|
|
(seq-contains (map-keys map) key testfn))
|
2015-04-18 16:22:16 +02:00
|
|
|
|
2015-09-06 00:45:48 +02:00
|
|
|
(defun map-some (pred map)
|
|
|
|
"Return a non-nil if (PRED key val) is non-nil for any key/value pair in MAP.
|
2015-05-16 11:30:12 +02:00
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 16:22:16 +02:00
|
|
|
(catch 'map--break
|
|
|
|
(map-apply (lambda (key value)
|
2015-09-06 00:51:35 +02:00
|
|
|
(let ((result (funcall pred key value)))
|
|
|
|
(when result
|
|
|
|
(throw 'map--break result))))
|
2015-04-18 16:22:16 +02:00
|
|
|
map)
|
|
|
|
nil))
|
|
|
|
|
|
|
|
(defun map-every-p (pred map)
|
2015-05-16 11:30:12 +02:00
|
|
|
"Return non-nil if (PRED key val) is non-nil for all elements of the map MAP.
|
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 16:22:16 +02:00
|
|
|
(catch 'map--break
|
|
|
|
(map-apply (lambda (key value)
|
|
|
|
(or (funcall pred key value)
|
|
|
|
(throw 'map--break nil)))
|
|
|
|
map)
|
|
|
|
t))
|
|
|
|
|
|
|
|
(defun map-merge (type &rest maps)
|
2015-10-26 22:23:02 +01:00
|
|
|
"Merge into a map of type TYPE all the key/value pairs in MAPS.
|
2015-05-16 11:30:12 +02:00
|
|
|
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 16:22:16 +02:00
|
|
|
(let (result)
|
|
|
|
(while maps
|
|
|
|
(map-apply (lambda (key value)
|
2015-07-09 19:43:41 +02:00
|
|
|
(setf (map-elt result key) value))
|
2015-04-18 16:22:16 +02:00
|
|
|
(pop maps)))
|
|
|
|
(map-into result type)))
|
|
|
|
|
|
|
|
(defun map-into (map type)
|
|
|
|
"Convert the map MAP into a map of type TYPE.
|
2015-05-16 11:30:12 +02:00
|
|
|
|
|
|
|
TYPE can be one of the following symbols: list or hash-table.
|
|
|
|
MAP can be a list, hash-table or array."
|
2015-04-18 16:22:16 +02:00
|
|
|
(pcase type
|
|
|
|
(`list (map-pairs map))
|
2015-04-18 16:35:43 +02:00
|
|
|
(`hash-table (map--into-hash-table map))
|
2015-06-16 23:04:03 +02:00
|
|
|
(_ (error "Not a map type name: %S" type))))
|
2015-04-18 16:22:16 +02:00
|
|
|
|
2015-07-09 19:43:41 +02:00
|
|
|
(defun map--put (map key v)
|
|
|
|
(map--dispatch map
|
|
|
|
:list (let ((p (assoc key map)))
|
|
|
|
(if p (setcdr p v)
|
|
|
|
(error "No place to change the mapping for %S" key)))
|
|
|
|
:hash-table (puthash key v map)
|
|
|
|
:array (aset map key v)))
|
|
|
|
|
2015-04-18 16:22:16 +02:00
|
|
|
(defun map--apply-alist (function map)
|
|
|
|
"Private function used to apply FUNCTION over MAP, MAP being an alist."
|
|
|
|
(seq-map (lambda (pair)
|
|
|
|
(funcall function
|
|
|
|
(car pair)
|
|
|
|
(cdr pair)))
|
|
|
|
map))
|
|
|
|
|
2015-07-09 19:43:41 +02:00
|
|
|
(defun map--delete (map key)
|
|
|
|
(map--dispatch map
|
|
|
|
:list (error "No place to remove the mapping for %S" key)
|
|
|
|
:hash-table (remhash key map)
|
|
|
|
:array (and (>= key 0)
|
|
|
|
(<= key (seq-length map))
|
|
|
|
(aset map key nil)))
|
|
|
|
map)
|
|
|
|
|
2015-04-18 16:22:16 +02:00
|
|
|
(defun map--apply-hash-table (function map)
|
|
|
|
"Private function used to apply FUNCTION over MAP, MAP being a hash-table."
|
|
|
|
(let (result)
|
|
|
|
(maphash (lambda (key value)
|
|
|
|
(push (funcall function key value) result))
|
|
|
|
map)
|
|
|
|
(nreverse result)))
|
|
|
|
|
|
|
|
(defun map--apply-array (function map)
|
|
|
|
"Private function used to apply FUNCTION over MAP, MAP being an array."
|
2015-06-06 01:09:40 +02:00
|
|
|
(let ((index 0))
|
|
|
|
(seq-map (lambda (elt)
|
|
|
|
(prog1
|
|
|
|
(funcall function index elt)
|
|
|
|
(setq index (1+ index))))
|
|
|
|
map)))
|
2015-04-18 16:22:16 +02:00
|
|
|
|
|
|
|
(defun map--into-hash-table (map)
|
|
|
|
"Convert MAP into a hash-table."
|
|
|
|
(let ((ht (make-hash-table :size (map-length map)
|
|
|
|
:test 'equal)))
|
|
|
|
(map-apply (lambda (key value)
|
2015-07-09 19:43:41 +02:00
|
|
|
(setf (map-elt ht key) value))
|
2015-04-18 16:22:16 +02:00
|
|
|
map)
|
|
|
|
ht))
|
|
|
|
|
2015-06-02 22:13:38 +02:00
|
|
|
(defun map--make-pcase-bindings (args)
|
|
|
|
"Return a list of pcase bindings from ARGS to the elements of a map."
|
|
|
|
(seq-map (lambda (elt)
|
|
|
|
(if (consp elt)
|
2015-06-06 01:50:32 +02:00
|
|
|
`(app (pcase--flip map-elt ,(car elt)) ,(cadr elt))
|
2015-06-02 22:13:38 +02:00
|
|
|
`(app (pcase--flip map-elt ',elt) ,elt)))
|
|
|
|
args))
|
|
|
|
|
|
|
|
(defun map--make-pcase-patterns (args)
|
|
|
|
"Return a list of `(map ...)' pcase patterns built from ARGS."
|
|
|
|
(cons 'map
|
|
|
|
(seq-map (lambda (elt)
|
|
|
|
(if (and (consp elt) (eq 'map (car elt)))
|
|
|
|
(map--make-pcase-patterns elt)
|
|
|
|
elt))
|
|
|
|
args)))
|
|
|
|
|
2015-04-18 16:22:16 +02:00
|
|
|
(provide 'map)
|
|
|
|
;;; map.el ends here
|