You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2435 lines
81 KiB
2435 lines
81 KiB
;;; dash.el --- A modern list library for Emacs -*- lexical-binding: t -*- |
|
|
|
;; Copyright (C) 2012-2015 Free Software Foundation, Inc. |
|
|
|
;; Author: Magnar Sveen <magnars@gmail.com> |
|
;; Version: 2.12.1 |
|
;; Package-Version: 20151021.113 |
|
;; Keywords: lists |
|
|
|
;; This program 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. |
|
|
|
;; This program 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: |
|
|
|
;; A modern list api for Emacs. |
|
;; |
|
;; See documentation on https://github.com/magnars/dash.el#functions |
|
;; |
|
;; **Please note** The lexical binding in this file is not utilised at the |
|
;; moment. We will take full advantage of lexical binding in an upcoming 3.0 |
|
;; release of Dash. In the meantime, we've added the pragma to avoid a bug that |
|
;; you can read more about in https://github.com/magnars/dash.el/issues/130. |
|
;; |
|
|
|
;;; Code: |
|
|
|
(defgroup dash () |
|
"Customize group for dash.el" |
|
:group 'lisp |
|
:prefix "dash-") |
|
|
|
(defun dash--enable-fontlock (symbol value) |
|
(when value |
|
(dash-enable-font-lock)) |
|
(set-default symbol value)) |
|
|
|
(defcustom dash-enable-fontlock nil |
|
"If non-nil, enable fontification of dash functions, macros and |
|
special values." |
|
:type 'boolean |
|
:set 'dash--enable-fontlock |
|
:group 'dash) |
|
|
|
(defmacro !cons (car cdr) |
|
"Destructive: Set CDR to the cons of CAR and CDR." |
|
`(setq ,cdr (cons ,car ,cdr))) |
|
|
|
(defmacro !cdr (list) |
|
"Destructive: Set LIST to the cdr of LIST." |
|
`(setq ,list (cdr ,list))) |
|
|
|
(defmacro --each (list &rest body) |
|
"Anaphoric form of `-each'." |
|
(declare (debug (form body)) |
|
(indent 1)) |
|
(let ((l (make-symbol "list"))) |
|
`(let ((,l ,list) |
|
(it-index 0)) |
|
(while ,l |
|
(let ((it (car ,l))) |
|
,@body) |
|
(setq it-index (1+ it-index)) |
|
(!cdr ,l))))) |
|
|
|
(defun -each (list fn) |
|
"Call FN with every item in LIST. Return nil, used for side-effects only." |
|
(--each list (funcall fn it))) |
|
|
|
(put '-each 'lisp-indent-function 1) |
|
|
|
(defmacro --each-while (list pred &rest body) |
|
"Anaphoric form of `-each-while'." |
|
(declare (debug (form form body)) |
|
(indent 2)) |
|
(let ((l (make-symbol "list")) |
|
(c (make-symbol "continue"))) |
|
`(let ((,l ,list) |
|
(,c t) |
|
(it-index 0)) |
|
(while (and ,l ,c) |
|
(let ((it (car ,l))) |
|
(if (not ,pred) (setq ,c nil) ,@body)) |
|
(setq it-index (1+ it-index)) |
|
(!cdr ,l))))) |
|
|
|
(defun -each-while (list pred fn) |
|
"Call FN with every item in LIST while (PRED item) is non-nil. |
|
Return nil, used for side-effects only." |
|
(--each-while list (funcall pred it) (funcall fn it))) |
|
|
|
(put '-each-while 'lisp-indent-function 2) |
|
|
|
(defmacro --dotimes (num &rest body) |
|
"Repeatedly executes BODY (presumably for side-effects) with `it` bound to integers from 0 through NUM-1." |
|
(declare (debug (form body)) |
|
(indent 1)) |
|
(let ((n (make-symbol "num"))) |
|
`(let ((,n ,num) |
|
(it 0)) |
|
(while (< it ,n) |
|
,@body |
|
(setq it (1+ it)))))) |
|
|
|
(defun -dotimes (num fn) |
|
"Repeatedly calls FN (presumably for side-effects) passing in integers from 0 through NUM-1." |
|
(--dotimes num (funcall fn it))) |
|
|
|
(put '-dotimes 'lisp-indent-function 1) |
|
|
|
(defun -map (fn list) |
|
"Return a new list consisting of the result of applying FN to the items in LIST." |
|
(mapcar fn list)) |
|
|
|
(defmacro --map (form list) |
|
"Anaphoric form of `-map'." |
|
(declare (debug (form form))) |
|
`(mapcar (lambda (it) ,form) ,list)) |
|
|
|
(defmacro --reduce-from (form initial-value list) |
|
"Anaphoric form of `-reduce-from'." |
|
(declare (debug (form form form))) |
|
`(let ((acc ,initial-value)) |
|
(--each ,list (setq acc ,form)) |
|
acc)) |
|
|
|
(defun -reduce-from (fn initial-value list) |
|
"Return the result of applying FN to INITIAL-VALUE and the |
|
first item in LIST, then applying FN to that result and the 2nd |
|
item, etc. If LIST contains no items, return INITIAL-VALUE and |
|
FN is not called. |
|
|
|
In the anaphoric form `--reduce-from', the accumulated value is |
|
exposed as `acc`. |
|
|
|
See also: `-reduce', `-reduce-r'" |
|
(--reduce-from (funcall fn acc it) initial-value list)) |
|
|
|
(defmacro --reduce (form list) |
|
"Anaphoric form of `-reduce'." |
|
(declare (debug (form form))) |
|
(let ((lv (make-symbol "list-value"))) |
|
`(let ((,lv ,list)) |
|
(if ,lv |
|
(--reduce-from ,form (car ,lv) (cdr ,lv)) |
|
(let (acc it) ,form))))) |
|
|
|
(defun -reduce (fn list) |
|
"Return the result of applying FN to the first 2 items in LIST, |
|
then applying FN to that result and the 3rd item, etc. If LIST |
|
contains no items, FN must accept no arguments as well, and |
|
reduce return the result of calling FN with no arguments. If |
|
LIST has only 1 item, it is returned and FN is not called. |
|
|
|
In the anaphoric form `--reduce', the accumulated value is |
|
exposed as `acc`. |
|
|
|
See also: `-reduce-from', `-reduce-r'" |
|
(if list |
|
(-reduce-from fn (car list) (cdr list)) |
|
(funcall fn))) |
|
|
|
(defun -reduce-r-from (fn initial-value list) |
|
"Replace conses with FN, nil with INITIAL-VALUE and evaluate |
|
the resulting expression. If LIST is empty, INITIAL-VALUE is |
|
returned and FN is not called. |
|
|
|
Note: this function works the same as `-reduce-from' but the |
|
operation associates from right instead of from left. |
|
|
|
See also: `-reduce-r', `-reduce'" |
|
(if (not list) initial-value |
|
(funcall fn (car list) (-reduce-r-from fn initial-value (cdr list))))) |
|
|
|
(defmacro --reduce-r-from (form initial-value list) |
|
"Anaphoric version of `-reduce-r-from'." |
|
(declare (debug (form form form))) |
|
`(-reduce-r-from (lambda (&optional it acc) ,form) ,initial-value ,list)) |
|
|
|
(defun -reduce-r (fn list) |
|
"Replace conses with FN and evaluate the resulting expression. |
|
The final nil is ignored. If LIST contains no items, FN must |
|
accept no arguments as well, and reduce return the result of |
|
calling FN with no arguments. If LIST has only 1 item, it is |
|
returned and FN is not called. |
|
|
|
The first argument of FN is the new item, the second is the |
|
accumulated value. |
|
|
|
Note: this function works the same as `-reduce' but the operation |
|
associates from right instead of from left. |
|
|
|
See also: `-reduce-r-from', `-reduce'" |
|
(cond |
|
((not list) (funcall fn)) |
|
((not (cdr list)) (car list)) |
|
(t (funcall fn (car list) (-reduce-r fn (cdr list)))))) |
|
|
|
(defmacro --reduce-r (form list) |
|
"Anaphoric version of `-reduce-r'." |
|
(declare (debug (form form))) |
|
`(-reduce-r (lambda (&optional it acc) ,form) ,list)) |
|
|
|
(defmacro --filter (form list) |
|
"Anaphoric form of `-filter'." |
|
(declare (debug (form form))) |
|
(let ((r (make-symbol "result"))) |
|
`(let (,r) |
|
(--each ,list (when ,form (!cons it ,r))) |
|
(nreverse ,r)))) |
|
|
|
(defun -filter (pred list) |
|
"Return a new list of the items in LIST for which PRED returns a non-nil value. |
|
|
|
Alias: `-select'" |
|
(--filter (funcall pred it) list)) |
|
|
|
(defalias '-select '-filter) |
|
(defalias '--select '--filter) |
|
|
|
(defmacro --remove (form list) |
|
"Anaphoric form of `-remove'." |
|
(declare (debug (form form))) |
|
`(--filter (not ,form) ,list)) |
|
|
|
(defun -remove (pred list) |
|
"Return a new list of the items in LIST for which PRED returns nil. |
|
|
|
Alias: `-reject'" |
|
(--remove (funcall pred it) list)) |
|
|
|
(defalias '-reject '-remove) |
|
(defalias '--reject '--remove) |
|
|
|
(defun -remove-first (pred list) |
|
"Return a new list with the first item matching PRED removed. |
|
|
|
Alias: `-reject-first' |
|
|
|
See also: `-remove', `-map-first'" |
|
(let (front) |
|
(while (and list (not (funcall pred (car list)))) |
|
(push (car list) front) |
|
(!cdr list)) |
|
(if list |
|
(-concat (nreverse front) (cdr list)) |
|
(nreverse front)))) |
|
|
|
(defmacro --remove-first (form list) |
|
"Anaphoric form of `-remove-first'." |
|
(declare (debug (form form))) |
|
`(-remove-first (lambda (it) ,form) ,list)) |
|
|
|
(defalias '-reject-first '-remove-first) |
|
(defalias '--reject-first '--remove-first) |
|
|
|
(defun -remove-last (pred list) |
|
"Return a new list with the last item matching PRED removed. |
|
|
|
Alias: `-reject-last' |
|
|
|
See also: `-remove', `-map-last'" |
|
(nreverse (-remove-first pred (nreverse list)))) |
|
|
|
(defmacro --remove-last (form list) |
|
"Anaphoric form of `-remove-last'." |
|
(declare (debug (form form))) |
|
`(-remove-last (lambda (it) ,form) ,list)) |
|
|
|
(defalias '-reject-last '-remove-last) |
|
(defalias '--reject-last '--remove-last) |
|
|
|
(defun -remove-item (item list) |
|
"Remove all occurences of ITEM from LIST. |
|
|
|
Comparison is done with `equal'." |
|
(--remove (equal it item) list)) |
|
|
|
(defmacro --keep (form list) |
|
"Anaphoric form of `-keep'." |
|
(declare (debug (form form))) |
|
(let ((r (make-symbol "result")) |
|
(m (make-symbol "mapped"))) |
|
`(let (,r) |
|
(--each ,list (let ((,m ,form)) (when ,m (!cons ,m ,r)))) |
|
(nreverse ,r)))) |
|
|
|
(defun -keep (fn list) |
|
"Return a new list of the non-nil results of applying FN to the items in LIST. |
|
|
|
If you want to select the original items satisfying a predicate use `-filter'." |
|
(--keep (funcall fn it) list)) |
|
|
|
(defun -non-nil (list) |
|
"Return all non-nil elements of LIST." |
|
(-remove 'null list)) |
|
|
|
(defmacro --map-indexed (form list) |
|
"Anaphoric form of `-map-indexed'." |
|
(declare (debug (form form))) |
|
(let ((r (make-symbol "result"))) |
|
`(let (,r) |
|
(--each ,list |
|
(!cons ,form ,r)) |
|
(nreverse ,r)))) |
|
|
|
(defun -map-indexed (fn list) |
|
"Return a new list consisting of the result of (FN index item) for each item in LIST. |
|
|
|
In the anaphoric form `--map-indexed', the index is exposed as `it-index`." |
|
(--map-indexed (funcall fn it-index it) list)) |
|
|
|
(defmacro --map-when (pred rep list) |
|
"Anaphoric form of `-map-when'." |
|
(declare (debug (form form form))) |
|
(let ((r (make-symbol "result"))) |
|
`(let (,r) |
|
(--each ,list (!cons (if ,pred ,rep it) ,r)) |
|
(nreverse ,r)))) |
|
|
|
(defun -map-when (pred rep list) |
|
"Return a new list where the elements in LIST that does not match the PRED function |
|
are unchanged, and where the elements in LIST that do match the PRED function are mapped |
|
through the REP function. |
|
|
|
Alias: `-replace-where' |
|
|
|
See also: `-update-at'" |
|
(--map-when (funcall pred it) (funcall rep it) list)) |
|
|
|
(defalias '-replace-where '-map-when) |
|
(defalias '--replace-where '--map-when) |
|
|
|
(defun -map-first (pred rep list) |
|
"Replace first item in LIST satisfying PRED with result of REP called on this item. |
|
|
|
See also: `-map-when', `-replace-first'" |
|
(let (front) |
|
(while (and list (not (funcall pred (car list)))) |
|
(push (car list) front) |
|
(!cdr list)) |
|
(if list |
|
(-concat (nreverse front) (cons (funcall rep (car list)) (cdr list))) |
|
(nreverse front)))) |
|
|
|
(defmacro --map-first (pred rep list) |
|
"Anaphoric form of `-map-first'." |
|
`(-map-first (lambda (it) ,pred) (lambda (it) (ignore it) ,rep) ,list)) |
|
|
|
(defun -map-last (pred rep list) |
|
"Replace first item in LIST satisfying PRED with result of REP called on this item. |
|
|
|
See also: `-map-when', `-replace-last'" |
|
(nreverse (-map-first pred rep (nreverse list)))) |
|
|
|
(defmacro --map-last (pred rep list) |
|
"Anaphoric form of `-map-last'." |
|
`(-map-last (lambda (it) ,pred) (lambda (it) (ignore it) ,rep) ,list)) |
|
|
|
(defun -replace (old new list) |
|
"Replace all OLD items in LIST with NEW. |
|
|
|
Elements are compared using `equal'. |
|
|
|
See also: `-replace-at'" |
|
(--map-when (equal it old) new list)) |
|
|
|
(defun -replace-first (old new list) |
|
"Replace the first occurence of OLD with NEW in LIST. |
|
|
|
Elements are compared using `equal'. |
|
|
|
See also: `-map-first'" |
|
(--map-first (equal old it) new list)) |
|
|
|
(defun -replace-last (old new list) |
|
"Replace the last occurence of OLD with NEW in LIST. |
|
|
|
Elements are compared using `equal'. |
|
|
|
See also: `-map-last'" |
|
(--map-last (equal old it) new list)) |
|
|
|
(defmacro --mapcat (form list) |
|
"Anaphoric form of `-mapcat'." |
|
(declare (debug (form form))) |
|
`(apply 'append (--map ,form ,list))) |
|
|
|
(defun -mapcat (fn list) |
|
"Return the concatenation of the result of mapping FN over LIST. |
|
Thus function FN should return a list." |
|
(--mapcat (funcall fn it) list)) |
|
|
|
(defun -flatten (l) |
|
"Take a nested list L and return its contents as a single, flat list. |
|
|
|
Note that because `nil' represents a list of zero elements (an |
|
empty list), any mention of nil in L will disappear after |
|
flattening. If you need to preserve nils, consider `-flatten-n' |
|
or map them to some unique symbol and then map them back. |
|
|
|
Conses of two atoms are considered \"terminals\", that is, they |
|
aren't flattened further. |
|
|
|
See also: `-flatten-n'" |
|
(if (and (listp l) (listp (cdr l))) |
|
(-mapcat '-flatten l) |
|
(list l))) |
|
|
|
(defmacro --iterate (form init n) |
|
"Anaphoric version of `-iterate'." |
|
(declare (debug (form form form))) |
|
`(-iterate (lambda (it) ,form) ,init ,n)) |
|
|
|
(defun -flatten-n (num list) |
|
"Flatten NUM levels of a nested LIST. |
|
|
|
See also: `-flatten'" |
|
(-last-item (--iterate (--mapcat (-list it) it) list (1+ num)))) |
|
|
|
(defun -concat (&rest lists) |
|
"Return a new list with the concatenation of the elements in the supplied LISTS." |
|
(apply 'append lists)) |
|
|
|
(defalias '-copy 'copy-sequence |
|
"Create a shallow copy of LIST.") |
|
|
|
(defun -splice (pred fun list) |
|
"Splice lists generated by FUN in place of elements matching PRED in LIST. |
|
|
|
FUN takes the element matching PRED as input. |
|
|
|
This function can be used as replacement for `,@' in case you |
|
need to splice several lists at marked positions (for example |
|
with keywords). |
|
|
|
See also: `-splice-list', `-insert-at'" |
|
(let (r) |
|
(--each list |
|
(if (funcall pred it) |
|
(let ((new (funcall fun it))) |
|
(--each new (!cons it r))) |
|
(!cons it r))) |
|
(nreverse r))) |
|
|
|
(defmacro --splice (pred form list) |
|
"Anaphoric form of `-splice'." |
|
`(-splice (lambda (it) ,pred) (lambda (it) ,form) ,list)) |
|
|
|
(defun -splice-list (pred new-list list) |
|
"Splice NEW-LIST in place of elements matching PRED in LIST. |
|
|
|
See also: `-splice', `-insert-at'" |
|
(-splice pred (lambda (_) new-list) list)) |
|
|
|
(defmacro --splice-list (pred new-list list) |
|
"Anaphoric form of `-splice-list'." |
|
`(-splice-list (lambda (it) ,pred) ,new-list ,list)) |
|
|
|
(defun -cons* (&rest args) |
|
"Make a new list from the elements of ARGS. |
|
|
|
The last 2 members of ARGS are used as the final cons of the |
|
result so if the final member of ARGS is not a list the result is |
|
a dotted list." |
|
(-reduce-r 'cons args)) |
|
|
|
(defun -snoc (list elem &rest elements) |
|
"Append ELEM to the end of the list. |
|
|
|
This is like `cons', but operates on the end of list. |
|
|
|
If ELEMENTS is non nil, append these to the list as well." |
|
(-concat list (list elem) elements)) |
|
|
|
(defmacro --first (form list) |
|
"Anaphoric form of `-first'." |
|
(declare (debug (form form))) |
|
(let ((n (make-symbol "needle"))) |
|
`(let (,n) |
|
(--each-while ,list (not ,n) |
|
(when ,form (setq ,n it))) |
|
,n))) |
|
|
|
(defun -first (pred list) |
|
"Return the first x in LIST where (PRED x) is non-nil, else nil. |
|
|
|
To get the first item in the list no questions asked, use `car'. |
|
|
|
Alias: `-find'" |
|
(--first (funcall pred it) list)) |
|
|
|
(defalias '-find '-first) |
|
(defalias '--find '--first) |
|
|
|
(defmacro --some (form list) |
|
"Anaphoric form of `-some'." |
|
(declare (debug (form form))) |
|
(let ((n (make-symbol "needle"))) |
|
`(let (,n) |
|
(--each-while ,list (not ,n) |
|
(setq ,n ,form)) |
|
,n))) |
|
|
|
(defun -some (pred list) |
|
"Return (PRED x) for the first LIST item where (PRED x) is non-nil, else nil. |
|
|
|
Alias: `-any'" |
|
(--some (funcall pred it) list)) |
|
|
|
(defalias '-any '-some) |
|
(defalias '--any '--some) |
|
|
|
(defmacro --last (form list) |
|
"Anaphoric form of `-last'." |
|
(declare (debug (form form))) |
|
(let ((n (make-symbol "needle"))) |
|
`(let (,n) |
|
(--each ,list |
|
(when ,form (setq ,n it))) |
|
,n))) |
|
|
|
(defun -last (pred list) |
|
"Return the last x in LIST where (PRED x) is non-nil, else nil." |
|
(--last (funcall pred it) list)) |
|
|
|
(defalias '-first-item 'car |
|
"Return the first item of LIST, or nil on an empty list.") |
|
|
|
(defun -last-item (list) |
|
"Return the last item of LIST, or nil on an empty list." |
|
(car (last list))) |
|
|
|
(defun -butlast (list) |
|
"Return a list of all items in list except for the last." |
|
(let (result) |
|
(while (cdr list) |
|
(!cons (car list) result) |
|
(!cdr list)) |
|
(nreverse result))) |
|
|
|
(defmacro --count (pred list) |
|
"Anaphoric form of `-count'." |
|
(declare (debug (form form))) |
|
(let ((r (make-symbol "result"))) |
|
`(let ((,r 0)) |
|
(--each ,list (when ,pred (setq ,r (1+ ,r)))) |
|
,r))) |
|
|
|
(defun -count (pred list) |
|
"Counts the number of items in LIST where (PRED item) is non-nil." |
|
(--count (funcall pred it) list)) |
|
|
|
(defun ---truthy? (val) |
|
(not (null val))) |
|
|
|
(defmacro --any? (form list) |
|
"Anaphoric form of `-any?'." |
|
(declare (debug (form form))) |
|
`(---truthy? (--first ,form ,list))) |
|
|
|
(defun -any? (pred list) |
|
"Return t if (PRED x) is non-nil for any x in LIST, else nil. |
|
|
|
Alias: `-any-p', `-some?', `-some-p'" |
|
(--any? (funcall pred it) list)) |
|
|
|
(defalias '-some? '-any?) |
|
(defalias '--some? '--any?) |
|
(defalias '-any-p '-any?) |
|
(defalias '--any-p '--any?) |
|
(defalias '-some-p '-any?) |
|
(defalias '--some-p '--any?) |
|
|
|
(defmacro --all? (form list) |
|
"Anaphoric form of `-all?'." |
|
(declare (debug (form form))) |
|
(let ((a (make-symbol "all"))) |
|
`(let ((,a t)) |
|
(--each-while ,list ,a (setq ,a ,form)) |
|
(---truthy? ,a)))) |
|
|
|
(defun -all? (pred list) |
|
"Return t if (PRED x) is non-nil for all x in LIST, else nil. |
|
|
|
Alias: `-all-p', `-every?', `-every-p'" |
|
(--all? (funcall pred it) list)) |
|
|
|
(defalias '-every? '-all?) |
|
(defalias '--every? '--all?) |
|
(defalias '-all-p '-all?) |
|
(defalias '--all-p '--all?) |
|
(defalias '-every-p '-all?) |
|
(defalias '--every-p '--all?) |
|
|
|
(defmacro --none? (form list) |
|
"Anaphoric form of `-none?'." |
|
(declare (debug (form form))) |
|
`(--all? (not ,form) ,list)) |
|
|
|
(defun -none? (pred list) |
|
"Return t if (PRED x) is nil for all x in LIST, else nil. |
|
|
|
Alias: `-none-p'" |
|
(--none? (funcall pred it) list)) |
|
|
|
(defalias '-none-p '-none?) |
|
(defalias '--none-p '--none?) |
|
|
|
(defmacro --only-some? (form list) |
|
"Anaphoric form of `-only-some?'." |
|
(declare (debug (form form))) |
|
(let ((y (make-symbol "yes")) |
|
(n (make-symbol "no"))) |
|
`(let (,y ,n) |
|
(--each-while ,list (not (and ,y ,n)) |
|
(if ,form (setq ,y t) (setq ,n t))) |
|
(---truthy? (and ,y ,n))))) |
|
|
|
(defun -only-some? (pred list) |
|
"Return `t` if at least one item of LIST matches PRED and at least one item of LIST does not match PRED. |
|
Return `nil` both if all items match the predicate or if none of the items match the predicate. |
|
|
|
Alias: `-only-some-p'" |
|
(--only-some? (funcall pred it) list)) |
|
|
|
(defalias '-only-some-p '-only-some?) |
|
(defalias '--only-some-p '--only-some?) |
|
|
|
(defun -slice (list from &optional to step) |
|
"Return copy of LIST, starting from index FROM to index TO. |
|
|
|
FROM or TO may be negative. These values are then interpreted |
|
modulo the length of the list. |
|
|
|
If STEP is a number, only each STEPth item in the resulting |
|
section is returned. Defaults to 1." |
|
(let ((length (length list)) |
|
(new-list nil)) |
|
;; to defaults to the end of the list |
|
(setq to (or to length)) |
|
(setq step (or step 1)) |
|
;; handle negative indices |
|
(when (< from 0) |
|
(setq from (mod from length))) |
|
(when (< to 0) |
|
(setq to (mod to length))) |
|
|
|
;; iterate through the list, keeping the elements we want |
|
(--each-while list (< it-index to) |
|
(when (and (>= it-index from) |
|
(= (mod (- from it-index) step) 0)) |
|
(push it new-list))) |
|
(nreverse new-list))) |
|
|
|
(defun -take (n list) |
|
"Return a new list of the first N items in LIST, or all items if there are fewer than N." |
|
(let (result) |
|
(--dotimes n |
|
(when list |
|
(!cons (car list) result) |
|
(!cdr list))) |
|
(nreverse result))) |
|
|
|
(defalias '-drop 'nthcdr "Return the tail of LIST without the first N items.") |
|
|
|
(defmacro --take-while (form list) |
|
"Anaphoric form of `-take-while'." |
|
(declare (debug (form form))) |
|
(let ((r (make-symbol "result"))) |
|
`(let (,r) |
|
(--each-while ,list ,form (!cons it ,r)) |
|
(nreverse ,r)))) |
|
|
|
(defun -take-while (pred list) |
|
"Return a new list of successive items from LIST while (PRED item) returns a non-nil value." |
|
(--take-while (funcall pred it) list)) |
|
|
|
(defmacro --drop-while (form list) |
|
"Anaphoric form of `-drop-while'." |
|
(declare (debug (form form))) |
|
(let ((l (make-symbol "list"))) |
|
`(let ((,l ,list)) |
|
(while (and ,l (let ((it (car ,l))) ,form)) |
|
(!cdr ,l)) |
|
,l))) |
|
|
|
(defun -drop-while (pred list) |
|
"Return the tail of LIST starting from the first item for which (PRED item) returns nil." |
|
(--drop-while (funcall pred it) list)) |
|
|
|
(defun -split-at (n list) |
|
"Return a list of ((-take N LIST) (-drop N LIST)), in no more than one pass through the list." |
|
(let (result) |
|
(--dotimes n |
|
(when list |
|
(!cons (car list) result) |
|
(!cdr list))) |
|
(list (nreverse result) list))) |
|
|
|
(defun -rotate (n list) |
|
"Rotate LIST N places to the right. With N negative, rotate to the left. |
|
The time complexity is O(n)." |
|
(if (> n 0) |
|
(append (last list n) (butlast list n)) |
|
(append (-drop (- n) list) (-take (- n) list)))) |
|
|
|
(defun -insert-at (n x list) |
|
"Return a list with X inserted into LIST at position N. |
|
|
|
See also: `-splice', `-splice-list'" |
|
(let ((split-list (-split-at n list))) |
|
(nconc (car split-list) (cons x (cadr split-list))))) |
|
|
|
(defun -replace-at (n x list) |
|
"Return a list with element at Nth position in LIST replaced with X. |
|
|
|
See also: `-replace'" |
|
(let ((split-list (-split-at n list))) |
|
(nconc (car split-list) (cons x (cdr (cadr split-list)))))) |
|
|
|
(defun -update-at (n func list) |
|
"Return a list with element at Nth position in LIST replaced with `(func (nth n list))`. |
|
|
|
See also: `-map-when'" |
|
(let ((split-list (-split-at n list))) |
|
(nconc (car split-list) (cons (funcall func (car (cadr split-list))) (cdr (cadr split-list)))))) |
|
|
|
(defmacro --update-at (n form list) |
|
"Anaphoric version of `-update-at'." |
|
(declare (debug (form form form))) |
|
`(-update-at ,n (lambda (it) ,form) ,list)) |
|
|
|
(defun -remove-at (n list) |
|
"Return a list with element at Nth position in LIST removed. |
|
|
|
See also: `-remove-at-indices', `-remove'" |
|
(-remove-at-indices (list n) list)) |
|
|
|
(defun -remove-at-indices (indices list) |
|
"Return a list whose elements are elements from LIST without |
|
elements selected as `(nth i list)` for all i |
|
from INDICES. |
|
|
|
See also: `-remove-at', `-remove'" |
|
(let* ((indices (-sort '< indices)) |
|
(diffs (cons (car indices) (-map '1- (-zip-with '- (cdr indices) indices)))) |
|
r) |
|
(--each diffs |
|
(let ((split (-split-at it list))) |
|
(!cons (car split) r) |
|
(setq list (cdr (cadr split))))) |
|
(!cons list r) |
|
(apply '-concat (nreverse r)))) |
|
|
|
(defmacro --split-with (pred list) |
|
"Anaphoric form of `-split-with'." |
|
(declare (debug (form form))) |
|
(let ((l (make-symbol "list")) |
|
(r (make-symbol "result")) |
|
(c (make-symbol "continue"))) |
|
`(let ((,l ,list) |
|
(,r nil) |
|
(,c t)) |
|
(while (and ,l ,c) |
|
(let ((it (car ,l))) |
|
(if (not ,pred) |
|
(setq ,c nil) |
|
(!cons it ,r) |
|
(!cdr ,l)))) |
|
(list (nreverse ,r) ,l)))) |
|
|
|
(defun -split-with (pred list) |
|
"Return a list of ((-take-while PRED LIST) (-drop-while PRED LIST)), in no more than one pass through the list." |
|
(--split-with (funcall pred it) list)) |
|
|
|
(defmacro -split-on (item list) |
|
"Split the LIST each time ITEM is found. |
|
|
|
Unlike `-partition-by', the ITEM is discarded from the results. |
|
Empty lists are also removed from the result. |
|
|
|
Comparison is done by `equal'. |
|
|
|
See also `-split-when'" |
|
(declare (debug (form form))) |
|
`(-split-when (lambda (it) (equal it ,item)) ,list)) |
|
|
|
(defmacro --split-when (form list) |
|
"Anaphoric version of `-split-when'." |
|
(declare (debug (form form))) |
|
`(-split-when (lambda (it) ,form) ,list)) |
|
|
|
(defun -split-when (fn list) |
|
"Split the LIST on each element where FN returns non-nil. |
|
|
|
Unlike `-partition-by', the \"matched\" element is discarded from |
|
the results. Empty lists are also removed from the result. |
|
|
|
This function can be thought of as a generalization of |
|
`split-string'." |
|
(let (r s) |
|
(while list |
|
(if (not (funcall fn (car list))) |
|
(push (car list) s) |
|
(when s (push (nreverse s) r)) |
|
(setq s nil)) |
|
(!cdr list)) |
|
(when s (push (nreverse s) r)) |
|
(nreverse r))) |
|
|
|
(defmacro --separate (form list) |
|
"Anaphoric form of `-separate'." |
|
(declare (debug (form form))) |
|
(let ((y (make-symbol "yes")) |
|
(n (make-symbol "no"))) |
|
`(let (,y ,n) |
|
(--each ,list (if ,form (!cons it ,y) (!cons it ,n))) |
|
(list (nreverse ,y) (nreverse ,n))))) |
|
|
|
(defun -separate (pred list) |
|
"Return a list of ((-filter PRED LIST) (-remove PRED LIST)), in one pass through the list." |
|
(--separate (funcall pred it) list)) |
|
|
|
(defun ---partition-all-in-steps-reversed (n step list) |
|
"Private: Used by -partition-all-in-steps and -partition-in-steps." |
|
(when (< step 1) |
|
(error "Step must be a positive number, or you're looking at some juicy infinite loops.")) |
|
(let ((result nil)) |
|
(while list |
|
(!cons (-take n list) result) |
|
(setq list (-drop step list))) |
|
result)) |
|
|
|
(defun -partition-all-in-steps (n step list) |
|
"Return a new list with the items in LIST grouped into N-sized sublists at offsets STEP apart. |
|
The last groups may contain less than N items." |
|
(nreverse (---partition-all-in-steps-reversed n step list))) |
|
|
|
(defun -partition-in-steps (n step list) |
|
"Return a new list with the items in LIST grouped into N-sized sublists at offsets STEP apart. |
|
If there are not enough items to make the last group N-sized, |
|
those items are discarded." |
|
(let ((result (---partition-all-in-steps-reversed n step list))) |
|
(while (and result (< (length (car result)) n)) |
|
(!cdr result)) |
|
(nreverse result))) |
|
|
|
(defun -partition-all (n list) |
|
"Return a new list with the items in LIST grouped into N-sized sublists. |
|
The last group may contain less than N items." |
|
(-partition-all-in-steps n n list)) |
|
|
|
(defun -partition (n list) |
|
"Return a new list with the items in LIST grouped into N-sized sublists. |
|
If there are not enough items to make the last group N-sized, |
|
those items are discarded." |
|
(-partition-in-steps n n list)) |
|
|
|
(defmacro --partition-by (form list) |
|
"Anaphoric form of `-partition-by'." |
|
(declare (debug (form form))) |
|
(let ((r (make-symbol "result")) |
|
(s (make-symbol "sublist")) |
|
(v (make-symbol "value")) |
|
(n (make-symbol "new-value")) |
|
(l (make-symbol "list"))) |
|
`(let ((,l ,list)) |
|
(when ,l |
|
(let* ((,r nil) |
|
(it (car ,l)) |
|
(,s (list it)) |
|
(,v ,form) |
|
(,l (cdr ,l))) |
|
(while ,l |
|
(let* ((it (car ,l)) |
|
(,n ,form)) |
|
(unless (equal ,v ,n) |
|
(!cons (nreverse ,s) ,r) |
|
(setq ,s nil) |
|
(setq ,v ,n)) |
|
(!cons it ,s) |
|
(!cdr ,l))) |
|
(!cons (nreverse ,s) ,r) |
|
(nreverse ,r)))))) |
|
|
|
(defun -partition-by (fn list) |
|
"Apply FN to each item in LIST, splitting it each time FN returns a new value." |
|
(--partition-by (funcall fn it) list)) |
|
|
|
(defmacro --partition-by-header (form list) |
|
"Anaphoric form of `-partition-by-header'." |
|
(declare (debug (form form))) |
|
(let ((r (make-symbol "result")) |
|
(s (make-symbol "sublist")) |
|
(h (make-symbol "header-value")) |
|
(b (make-symbol "seen-body?")) |
|
(n (make-symbol "new-value")) |
|
(l (make-symbol "list"))) |
|
`(let ((,l ,list)) |
|
(when ,l |
|
(let* ((,r nil) |
|
(it (car ,l)) |
|
(,s (list it)) |
|
(,h ,form) |
|
(,b nil) |
|
(,l (cdr ,l))) |
|
(while ,l |
|
(let* ((it (car ,l)) |
|
(,n ,form)) |
|
(if (equal ,h ,n) |
|
(when ,b |
|
(!cons (nreverse ,s) ,r) |
|
(setq ,s nil) |
|
(setq ,b nil)) |
|
(setq ,b t)) |
|
(!cons it ,s) |
|
(!cdr ,l))) |
|
(!cons (nreverse ,s) ,r) |
|
(nreverse ,r)))))) |
|
|
|
(defun -partition-by-header (fn list) |
|
"Apply FN to the first item in LIST. That is the header |
|
value. Apply FN to each item in LIST, splitting it each time FN |
|
returns the header value, but only after seeing at least one |
|
other value (the body)." |
|
(--partition-by-header (funcall fn it) list)) |
|
|
|
(defmacro --group-by (form list) |
|
"Anaphoric form of `-group-by'." |
|
(declare (debug t)) |
|
(let ((n (make-symbol "n")) |
|
(k (make-symbol "k")) |
|
(grp (make-symbol "grp"))) |
|
`(nreverse |
|
(-map |
|
(lambda (,n) |
|
(cons (car ,n) |
|
(nreverse (cdr ,n)))) |
|
(--reduce-from |
|
(let* ((,k (,@form)) |
|
(,grp (assoc ,k acc))) |
|
(if ,grp |
|
(setcdr ,grp (cons it (cdr ,grp))) |
|
(push |
|
(list ,k it) |
|
acc)) |
|
acc) |
|
nil ,list))))) |
|
|
|
(defun -group-by (fn list) |
|
"Separate LIST into an alist whose keys are FN applied to the |
|
elements of LIST. Keys are compared by `equal'." |
|
(--group-by (funcall fn it) list)) |
|
|
|
(defun -interpose (sep list) |
|
"Return a new list of all elements in LIST separated by SEP." |
|
(let (result) |
|
(when list |
|
(!cons (car list) result) |
|
(!cdr list)) |
|
(while list |
|
(setq result (cons (car list) (cons sep result))) |
|
(!cdr list)) |
|
(nreverse result))) |
|
|
|
(defun -interleave (&rest lists) |
|
"Return a new list of the first item in each list, then the second etc." |
|
(let (result) |
|
(while (-none? 'null lists) |
|
(--each lists (!cons (car it) result)) |
|
(setq lists (-map 'cdr lists))) |
|
(nreverse result))) |
|
|
|
(defmacro --zip-with (form list1 list2) |
|
"Anaphoric form of `-zip-with'. |
|
|
|
The elements in list1 is bound as `it`, the elements in list2 as `other`." |
|
(declare (debug (form form form))) |
|
(let ((r (make-symbol "result")) |
|
(l1 (make-symbol "list1")) |
|
(l2 (make-symbol "list2"))) |
|
`(let ((,r nil) |
|
(,l1 ,list1) |
|
(,l2 ,list2)) |
|
(while (and ,l1 ,l2) |
|
(let ((it (car ,l1)) |
|
(other (car ,l2))) |
|
(!cons ,form ,r) |
|
(!cdr ,l1) |
|
(!cdr ,l2))) |
|
(nreverse ,r)))) |
|
|
|
(defun -zip-with (fn list1 list2) |
|
"Zip the two lists LIST1 and LIST2 using a function FN. This |
|
function is applied pairwise taking as first argument element of |
|
LIST1 and as second argument element of LIST2 at corresponding |
|
position. |
|
|
|
The anaphoric form `--zip-with' binds the elements from LIST1 as `it`, |
|
and the elements from LIST2 as `other`." |
|
(--zip-with (funcall fn it other) list1 list2)) |
|
|
|
(defun -zip (&rest lists) |
|
"Zip LISTS together. Group the head of each list, followed by the |
|
second elements of each list, and so on. The lengths of the returned |
|
groupings are equal to the length of the shortest input list. |
|
|
|
If two lists are provided as arguments, return the groupings as a list |
|
of cons cells. Otherwise, return the groupings as a list of lists. |
|
|
|
Please note! This distinction is being removed in an upcoming 2.0 |
|
release of Dash. If you rely on this behavior, use -zip-pair instead." |
|
(let (results) |
|
(while (-none? 'null lists) |
|
(setq results (cons (mapcar 'car lists) results)) |
|
(setq lists (mapcar 'cdr lists))) |
|
(setq results (nreverse results)) |
|
(if (= (length lists) 2) |
|
;; to support backward compatability, return |
|
;; a cons cell if two lists were provided |
|
(--map (cons (car it) (cadr it)) results) |
|
results))) |
|
|
|
(defalias '-zip-pair '-zip) |
|
|
|
(defun -zip-fill (fill-value &rest lists) |
|
"Zip LISTS, with FILL-VALUE padded onto the shorter lists. The |
|
lengths of the returned groupings are equal to the length of the |
|
longest input list." |
|
(apply '-zip (apply '-pad (cons fill-value lists)))) |
|
|
|
(defun -cycle (list) |
|
"Return an infinite copy of LIST that will cycle through the |
|
elements and repeat from the beginning." |
|
(let ((newlist (-map 'identity list))) |
|
(nconc newlist newlist))) |
|
|
|
(defun -pad (fill-value &rest lists) |
|
"Appends FILL-VALUE to the end of each list in LISTS such that they |
|
will all have the same length." |
|
(let* ((annotations (-annotate 'length lists)) |
|
(n (-max (-map 'car annotations)))) |
|
(--map (append (cdr it) (-repeat (- n (car it)) fill-value)) annotations))) |
|
|
|
(defun -annotate (fn list) |
|
"Return a list of cons cells where each cell is FN applied to each |
|
element of LIST paired with the unmodified element of LIST." |
|
(-zip (-map fn list) list)) |
|
|
|
(defmacro --annotate (form list) |
|
"Anaphoric version of `-annotate'." |
|
(declare (debug (form form))) |
|
`(-annotate (lambda (it) ,form) ,list)) |
|
|
|
(defun dash--table-carry (lists restore-lists &optional re) |
|
"Helper for `-table' and `-table-flat'. |
|
|
|
If a list overflows, carry to the right and reset the list." |
|
(while (not (or (car lists) |
|
(equal lists '(nil)))) |
|
(setcar lists (car restore-lists)) |
|
(pop (cadr lists)) |
|
(!cdr lists) |
|
(!cdr restore-lists) |
|
(when re |
|
(push (nreverse (car re)) (cadr re)) |
|
(setcar re nil) |
|
(!cdr re)))) |
|
|
|
(defun -table (fn &rest lists) |
|
"Compute outer product of LISTS using function FN. |
|
|
|
The function FN should have the same arity as the number of |
|
supplied lists. |
|
|
|
The outer product is computed by applying fn to all possible |
|
combinations created by taking one element from each list in |
|
order. The dimension of the result is (length lists). |
|
|
|
See also: `-table-flat'" |
|
(let ((restore-lists (copy-sequence lists)) |
|
(last-list (last lists)) |
|
(re (make-list (length lists) nil))) |
|
(while (car last-list) |
|
(let ((item (apply fn (-map 'car lists)))) |
|
(push item (car re)) |
|
(setcar lists (cdar lists)) ;; silence byte compiler |
|
(dash--table-carry lists restore-lists re))) |
|
(nreverse (car (last re))))) |
|
|
|
(defun -table-flat (fn &rest lists) |
|
"Compute flat outer product of LISTS using function FN. |
|
|
|
The function FN should have the same arity as the number of |
|
supplied lists. |
|
|
|
The outer product is computed by applying fn to all possible |
|
combinations created by taking one element from each list in |
|
order. The results are flattened, ignoring the tensor structure |
|
of the result. This is equivalent to calling: |
|
|
|
(-flatten-n (1- (length lists)) (-table fn lists)) |
|
|
|
but the implementation here is much more efficient. |
|
|
|
See also: `-flatten-n', `-table'" |
|
(when lists ;Just in case. |
|
(let* ((list1 (pop lists)) |
|
(restore-lists (copy-sequence lists)) |
|
(last-list (last lists)) |
|
re) |
|
(while (car last-list) |
|
(let ((tail (-map #'car lists))) |
|
(dolist (head list1) |
|
(push (apply fn head tail) re))) |
|
(pop (car lists)) |
|
(dash--table-carry lists restore-lists)) |
|
(nreverse re)))) |
|
|
|
(defun -partial (fn &rest args) |
|
"Take a function FN and fewer than the normal arguments to FN, |
|
and return a fn that takes a variable number of additional ARGS. |
|
When called, the returned function calls FN with ARGS first and |
|
then additional args." |
|
(apply 'apply-partially fn args)) |
|
|
|
(defun -elem-index (elem list) |
|
"Return the index of the first element in the given LIST which |
|
is equal to the query element ELEM, or nil if there is no |
|
such element." |
|
(car (-elem-indices elem list))) |
|
|
|
(defun -elem-indices (elem list) |
|
"Return the indices of all elements in LIST equal to the query |
|
element ELEM, in ascending order." |
|
(-find-indices (-partial 'equal elem) list)) |
|
|
|
(defun -find-indices (pred list) |
|
"Return the indices of all elements in LIST satisfying the |
|
predicate PRED, in ascending order." |
|
(apply 'append (--map-indexed (when (funcall pred it) (list it-index)) list))) |
|
|
|
(defmacro --find-indices (form list) |
|
"Anaphoric version of `-find-indices'." |
|
(declare (debug (form form))) |
|
`(-find-indices (lambda (it) ,form) ,list)) |
|
|
|
(defun -find-index (pred list) |
|
"Take a predicate PRED and a LIST and return the index of the |
|
first element in the list satisfying the predicate, or nil if |
|
there is no such element." |
|
(car (-find-indices pred list))) |
|
|
|
(defmacro --find-index (form list) |
|
"Anaphoric version of `-find-index'." |
|
(declare (debug (form form))) |
|
`(-find-index (lambda (it) ,form) ,list)) |
|
|
|
(defun -find-last-index (pred list) |
|
"Take a predicate PRED and a LIST and return the index of the |
|
last element in the list satisfying the predicate, or nil if |
|
there is no such element." |
|
(-last-item (-find-indices pred list))) |
|
|
|
(defmacro --find-last-index (form list) |
|
"Anaphoric version of `-find-last-index'." |
|
`(-find-last-index (lambda (it) ,form) ,list)) |
|
|
|
(defun -select-by-indices (indices list) |
|
"Return a list whose elements are elements from LIST selected |
|
as `(nth i list)` for all i from INDICES." |
|
(let (r) |
|
(--each indices |
|
(!cons (nth it list) r)) |
|
(nreverse r))) |
|
|
|
(defmacro -> (x &optional form &rest more) |
|
"Thread the expr through the forms. Insert X as the second item |
|
in the first form, making a list of it if it is not a list |
|
already. If there are more forms, insert the first form as the |
|
second item in second form, etc." |
|
(cond |
|
((null form) x) |
|
((null more) (if (listp form) |
|
`(,(car form) ,x ,@(cdr form)) |
|
(list form x))) |
|
(:else `(-> (-> ,x ,form) ,@more)))) |
|
|
|
(defmacro ->> (x &optional form &rest more) |
|
"Thread the expr through the forms. Insert X as the last item |
|
in the first form, making a list of it if it is not a list |
|
already. If there are more forms, insert the first form as the |
|
last item in second form, etc." |
|
(cond |
|
((null form) x) |
|
((null more) (if (listp form) |
|
`(,@form ,x) |
|
(list form x))) |
|
(:else `(->> (->> ,x ,form) ,@more)))) |
|
|
|
(defmacro --> (x form &rest more) |
|
"Thread the expr through the forms. Insert X at the position |
|
signified by the token `it' in the first form. If there are more |
|
forms, insert the first form at the position signified by `it' in |
|
in second form, etc." |
|
(if (null more) |
|
(if (listp form) |
|
(--map-when (eq it 'it) x form) |
|
(list form x)) |
|
`(--> (--> ,x ,form) ,@more))) |
|
|
|
(defmacro -some-> (x &optional form &rest more) |
|
"When expr is non-nil, thread it through the first form (via `->'), |
|
and when that result is non-nil, through the next form, etc." |
|
(if (null form) x |
|
(let ((result (make-symbol "result"))) |
|
`(-some-> (-when-let (,result ,x) |
|
(-> ,result ,form)) |
|
,@more)))) |
|
|
|
(defmacro -some->> (x &optional form &rest more) |
|
"When expr is non-nil, thread it through the first form (via `->>'), |
|
and when that result is non-nil, through the next form, etc." |
|
(if (null form) x |
|
(let ((result (make-symbol "result"))) |
|
`(-some->> (-when-let (,result ,x) |
|
(->> ,result ,form)) |
|
,@more)))) |
|
|
|
(defmacro -some--> (x &optional form &rest more) |
|
"When expr in non-nil, thread it through the first form (via `-->'), |
|
and when that result is non-nil, through the next form, etc." |
|
(if (null form) x |
|
(let ((result (make-symbol "result"))) |
|
`(-some--> (-when-let (,result ,x) |
|
(--> ,result ,form)) |
|
,@more)))) |
|
|
|
(defun -grade-up (comparator list) |
|
"Grade elements of LIST using COMPARATOR relation, yielding a |
|
permutation vector such that applying this permutation to LIST |
|
sorts it in ascending order." |
|
;; ugly hack to "fix" lack of lexical scope |
|
(let ((comp `(lambda (it other) (funcall ',comparator (car it) (car other))))) |
|
(->> (--map-indexed (cons it it-index) list) |
|
(-sort comp) |
|
(-map 'cdr)))) |
|
|
|
(defun -grade-down (comparator list) |
|
"Grade elements of LIST using COMPARATOR relation, yielding a |
|
permutation vector such that applying this permutation to LIST |
|
sorts it in descending order." |
|
;; ugly hack to "fix" lack of lexical scope |
|
(let ((comp `(lambda (it other) (funcall ',comparator (car other) (car it))))) |
|
(->> (--map-indexed (cons it it-index) list) |
|
(-sort comp) |
|
(-map 'cdr)))) |
|
|
|
(defvar dash--source-counter 0 |
|
"Monotonic counter for generated symbols.") |
|
|
|
(defun dash--match-make-source-symbol () |
|
"Generate a new dash-source symbol. |
|
|
|
All returned symbols are guaranteed to be unique." |
|
(prog1 (make-symbol (format "--dash-source-%d--" dash--source-counter)) |
|
(setq dash--source-counter (1+ dash--source-counter)))) |
|
|
|
(defun dash--match-ignore-place-p (symbol) |
|
"Return non-nil if SYMBOL is a symbol and starts with _." |
|
(and (symbolp symbol) |
|
(eq (aref (symbol-name symbol) 0) ?_))) |
|
|
|
(defun dash--match-cons-skip-cdr (skip-cdr source) |
|
"Helper function generating idiomatic shifting code." |
|
(cond |
|
((= skip-cdr 0) |
|
`(pop ,source)) |
|
(t |
|
`(prog1 ,(dash--match-cons-get-car skip-cdr source) |
|
(setq ,source ,(dash--match-cons-get-cdr (1+ skip-cdr) source)))))) |
|
|
|
(defun dash--match-cons-get-car (skip-cdr source) |
|
"Helper function generating idiomatic code to get nth car." |
|
(cond |
|
((= skip-cdr 0) |
|
`(car ,source)) |
|
((= skip-cdr 1) |
|
`(cadr ,source)) |
|
(t |
|
`(nth ,skip-cdr ,source)))) |
|
|
|
(defun dash--match-cons-get-cdr (skip-cdr source) |
|
"Helper function generating idiomatic code to get nth cdr." |
|
(cond |
|
((= skip-cdr 0) |
|
source) |
|
((= skip-cdr 1) |
|
`(cdr ,source)) |
|
(t |
|
`(nthcdr ,skip-cdr ,source)))) |
|
|
|
(defun dash--match-cons (match-form source) |
|
"Setup a cons matching environment and call the real matcher." |
|
(let ((s (dash--match-make-source-symbol)) |
|
(n 0) |
|
(m match-form)) |
|
(while (and (consp m) |
|
(dash--match-ignore-place-p (car m))) |
|
(setq n (1+ n)) (!cdr m)) |
|
(cond |
|
;; when we only have one pattern in the list, we don't have to |
|
;; create a temporary binding (--dash-source--) for the source |
|
;; and just use the input directly |
|
((and (consp m) |
|
(not (cdr m))) |
|
(dash--match (car m) (dash--match-cons-get-car n source))) |
|
;; handle other special types |
|
((> n 0) |
|
(dash--match m (dash--match-cons-get-cdr n source))) |
|
;; this is the only entry-point for dash--match-cons-1, that's |
|
;; why we can't simply use the above branch, it would produce |
|
;; infinite recursion |
|
(t |
|
(cons (list s source) (dash--match-cons-1 match-form s)))))) |
|
|
|
(defun dash--match-cons-1 (match-form source &optional props) |
|
"Match MATCH-FORM against SOURCE. |
|
|
|
MATCH-FORM is a proper or improper list. Each element of |
|
MATCH-FORM is either a symbol, which gets bound to the respective |
|
value in source or another match form which gets destructured |
|
recursively. |
|
|
|
If the cdr of last cons cell in the list is `nil', matching stops |
|
there. |
|
|
|
SOURCE is a proper or improper list." |
|
(let ((skip-cdr (or (plist-get props :skip-cdr) 0))) |
|
(cond |
|
((consp match-form) |
|
(cond |
|
((cdr match-form) |
|
(cond |
|
((and (symbolp (car match-form)) |
|
(memq (car match-form) '(&keys &plist &alist &hash))) |
|
(dash--match-kv match-form (dash--match-cons-get-cdr skip-cdr source))) |
|
((dash--match-ignore-place-p (car match-form)) |
|
(dash--match-cons-1 (cdr match-form) source |
|
(plist-put props :skip-cdr (1+ skip-cdr)))) |
|
(t |
|
(-concat (dash--match (car match-form) (dash--match-cons-skip-cdr skip-cdr source)) |
|
(dash--match-cons-1 (cdr match-form) source))))) |
|
(t ;; Last matching place, no need for shift |
|
(dash--match (car match-form) (dash--match-cons-get-car skip-cdr source))))) |
|
((eq match-form nil) |
|
nil) |
|
(t ;; Handle improper lists. Last matching place, no need for shift |
|
(dash--match match-form (dash--match-cons-get-cdr skip-cdr source)))))) |
|
|
|
(defun dash--vector-tail (seq start) |
|
"Return the tail of SEQ starting at START." |
|
(cond |
|
((vectorp seq) |
|
(let* ((re-length (- (length seq) start)) |
|
(re (make-vector re-length 0))) |
|
(--dotimes re-length (aset re it (aref seq (+ it start)))) |
|
re)) |
|
((stringp seq) |
|
(substring seq start)))) |
|
|
|
(defun dash--match-vector (match-form source) |
|
"Setup a vector matching environment and call the real matcher." |
|
(let ((s (dash--match-make-source-symbol))) |
|
(cond |
|
;; don't bind `s' if we only have one sub-pattern |
|
((= (length match-form) 1) |
|
(dash--match (aref match-form 0) `(aref ,source 0))) |
|
;; if the source is a symbol, we don't need to re-bind it |
|
((symbolp source) |
|
(dash--match-vector-1 match-form source)) |
|
;; don't bind `s' if we only have one sub-pattern which is not ignored |
|
((let* ((ignored-places (mapcar 'dash--match-ignore-place-p match-form)) |
|
(ignored-places-n (length (-remove 'null ignored-places)))) |
|
(when (= ignored-places-n (1- (length match-form))) |
|
(let ((n (-find-index 'null ignored-places))) |
|
(dash--match (aref match-form n) `(aref ,source ,n)))))) |
|
(t |
|
(cons (list s source) (dash--match-vector-1 match-form s)))))) |
|
|
|
(defun dash--match-vector-1 (match-form source) |
|
"Match MATCH-FORM against SOURCE. |
|
|
|
MATCH-FORM is a vector. Each element of MATCH-FORM is either a |
|
symbol, which gets bound to the respective value in source or |
|
another match form which gets destructured recursively. |
|
|
|
If second-from-last place in MATCH-FORM is the symbol &rest, the |
|
next element of the MATCH-FORM is matched against the tail of |
|
SOURCE, starting at index of the &rest symbol. This is |
|
conceptually the same as the (head . tail) match for improper |
|
lists, where dot plays the role of &rest. |
|
|
|
SOURCE is a vector. |
|
|
|
If the MATCH-FORM vector is shorter than SOURCE vector, only |
|
the (length MATCH-FORM) places are bound, the rest of the SOURCE |
|
is discarded." |
|
(let ((i 0) |
|
(l (length match-form)) |
|
(re)) |
|
(while (< i l) |
|
(let ((m (aref match-form i))) |
|
(push (cond |
|
((and (symbolp m) |
|
(eq m '&rest)) |
|
(prog1 (dash--match |
|
(aref match-form (1+ i)) |
|
`(dash--vector-tail ,source ,i)) |
|
(setq i l))) |
|
((and (symbolp m) |
|
;; do not match symbols starting with _ |
|
(not (eq (aref (symbol-name m) 0) ?_))) |
|
(list (list m `(aref ,source ,i)))) |
|
((not (symbolp m)) |
|
(dash--match m `(aref ,source ,i)))) |
|
re) |
|
(setq i (1+ i)))) |
|
(-flatten-n 1 (nreverse re)))) |
|
|
|
(defun dash--match-kv (match-form source) |
|
"Setup a kv matching environment and call the real matcher. |
|
|
|
kv can be any key-value store, such as plist, alist or hash-table." |
|
(let ((s (dash--match-make-source-symbol))) |
|
(cond |
|
;; don't bind `s' if we only have one sub-pattern (&type key val) |
|
((= (length match-form) 3) |
|
(dash--match-kv-1 (cdr match-form) source (car match-form))) |
|
;; if the source is a symbol, we don't need to re-bind it |
|
((symbolp source) |
|
(dash--match-kv-1 (cdr match-form) source (car match-form))) |
|
(t |
|
(cons (list s source) (dash--match-kv-1 (cdr match-form) s (car match-form))))))) |
|
|
|
(defun dash--match-kv-1 (match-form source type) |
|
"Match MATCH-FORM against SOURCE of type TYPE. |
|
|
|
MATCH-FORM is a proper list of the form (key1 place1 ... keyN |
|
placeN). Each placeK is either a symbol, which gets bound to the |
|
value of keyK retrieved from the key-value store, or another |
|
match form which gets destructured recursively. |
|
|
|
SOURCE is a key-value store of type TYPE, which can be a plist, |
|
an alist or a hash table. |
|
|
|
TYPE is a token specifying the type of the key-value store. |
|
Valid values are &plist, &alist and &hash." |
|
(-flatten-n 1 (-map |
|
(lambda (kv) |
|
(let* ((k (car kv)) |
|
(v (cadr kv)) |
|
(getter (cond |
|
((or (eq type '&plist) (eq type '&keys)) |
|
`(plist-get ,source ,k)) |
|
((eq type '&alist) |
|
`(cdr (assoc ,k ,source))) |
|
((eq type '&hash) |
|
`(gethash ,k ,source))))) |
|
(cond |
|
((symbolp v) |
|
(list (list v getter))) |
|
(t (dash--match v getter))))) |
|
(-partition 2 match-form)))) |
|
|
|
(defun dash--match-symbol (match-form source) |
|
"Bind a symbol. |
|
|
|
This works just like `let', there is no destructuring." |
|
(list (list match-form source))) |
|
|
|
(defun dash--match (match-form source) |
|
"Match MATCH-FORM against SOURCE. |
|
|
|
This function tests the MATCH-FORM and dispatches to specific |
|
matchers based on the type of the expression. |
|
|
|
Key-value stores are disambiguated by placing a token &plist, |
|
&alist or &hash as a first item in the MATCH-FORM." |
|
(cond |
|
((symbolp match-form) |
|
(dash--match-symbol match-form source)) |
|
((consp match-form) |
|
(cond |
|
;; Handle the "x &as" bindings first. |
|
((and (consp (cdr match-form)) |
|
(symbolp (car match-form)) |
|
(eq '&as (cadr match-form))) |
|
(let ((s (car match-form))) |
|
(cons (list s source) |
|
(dash--match (cddr match-form) s)))) |
|
((memq (car match-form) '(&keys &plist &alist &hash)) |
|
(dash--match-kv match-form source)) |
|
(t (dash--match-cons match-form source)))) |
|
((vectorp match-form) |
|
;; We support the &as binding in vectors too |
|
(cond |
|
((and (> (length match-form) 2) |
|
(symbolp (aref match-form 0)) |
|
(eq '&as (aref match-form 1))) |
|
(let ((s (aref match-form 0))) |
|
(cons (list s source) |
|
(dash--match (dash--vector-tail match-form 2) s)))) |
|
(t (dash--match-vector match-form source)))))) |
|
|
|
(defmacro -let* (varlist &rest body) |
|
"Bind variables according to VARLIST then eval BODY. |
|
|
|
VARLIST is a list of lists of the form (PATTERN SOURCE). Each |
|
PATTERN is matched against the SOURCE structurally. SOURCE is |
|
only evaluated once for each PATTERN. |
|
|
|
Each SOURCE can refer to the symbols already bound by this |
|
VARLIST. This is useful if you want to destructure SOURCE |
|
recursively but also want to name the intermediate structures. |
|
|
|
See `-let' for the list of all possible patterns." |
|
(declare (debug ((&rest (sexp form)) body)) |
|
(indent 1)) |
|
(let ((bindings (--mapcat (dash--match (car it) (cadr it)) varlist))) |
|
`(let* ,bindings |
|
,@body))) |
|
|
|
(defmacro -let (varlist &rest body) |
|
"Bind variables according to VARLIST then eval BODY. |
|
|
|
VARLIST is a list of lists of the form (PATTERN SOURCE). Each |
|
PATTERN is matched against the SOURCE \"structurally\". SOURCE |
|
is only evaluated once for each PATTERN. Each PATTERN is matched |
|
recursively, and can therefore contain sub-patterns which are |
|
matched against corresponding sub-expressions of SOURCE. |
|
|
|
All the SOURCEs are evalled before any symbols are |
|
bound (i.e. \"in parallel\"). |
|
|
|
If VARLIST only contains one (PATTERN SOURCE) element, you can |
|
optionally specify it using a vector and discarding the |
|
outer-most parens. Thus |
|
|
|
(-let ((PATTERN SOURCE)) ..) |
|
|
|
becomes |
|
|
|
(-let [PATTERN SOURCE] ..). |
|
|
|
`-let' uses a convention of not binding places (symbols) starting |
|
with _ whenever it's possible. You can use this to skip over |
|
entries you don't care about. However, this is not *always* |
|
possible (as a result of implementation) and these symbols might |
|
get bound to undefined values. |
|
|
|
Following is the overview of supported patterns. Remember that |
|
patterns can be matched recursively, so every a, b, aK in the |
|
following can be a matching construct and not necessarily a |
|
symbol/variable. |
|
|
|
Symbol: |
|
|
|
a - bind the SOURCE to A. This is just like regular `let'. |
|
|
|
Conses and lists: |
|
|
|
(a) - bind `car' of cons/list to A |
|
|
|
(a . b) - bind car of cons to A and `cdr' to B |
|
|
|
(a b) - bind car of list to A and `cadr' to B |
|
|
|
(a1 a2 a3 ...) - bind 0th car of list to A1, 1st to A2, 2nd to A3 ... |
|
|
|
(a1 a2 a3 ... aN . rest) - as above, but bind the Nth cdr to REST. |
|
|
|
Vectors: |
|
|
|
[a] - bind 0th element of a non-list sequence to A (works with |
|
vectors, strings, bit arrays...) |
|
|
|
[a1 a2 a3 ...] - bind 0th element of non-list sequence to A0, 1st to |
|
A1, 2nd to A2, ... |
|
If the PATTERN is shorter than SOURCE, the values at |
|
places not in PATTERN are ignored. |
|
If the PATTERN is longer than SOURCE, an `error' is |
|
thrown. |
|
|
|
[a1 a2 a3 ... &rest rest] - as above, but bind the rest of |
|
the sequence to REST. This is |
|
conceptually the same as improper list |
|
matching (a1 a2 ... aN . rest) |
|
|
|
Key/value stores: |
|
|
|
(&plist key0 a0 ... keyN aN) - bind value mapped by keyK in the |
|
SOURCE plist to aK. If the |
|
value is not found, aK is nil. |
|
|
|
(&alist key0 a0 ... keyN aN) - bind value mapped by keyK in the |
|
SOURCE alist to aK. If the |
|
value is not found, aK is nil. |
|
|
|
(&hash key0 a0 ... keyN aN) - bind value mapped by keyK in the |
|
SOURCE hash table to aK. If the |
|
value is not found, aK is nil. |
|
|
|
Further, special keyword &keys supports \"inline\" matching of |
|
plist-like key-value pairs, similarly to &keys keyword of |
|
`cl-defun'. |
|
|
|
(a1 a2 ... aN &keys key1 b1 ... keyN bK) |
|
|
|
This binds N values from the list to a1 ... aN, then interprets |
|
the cdr as a plist (see key/value matching above). |
|
|
|
You can name the source using the syntax SYMBOL &as PATTERN. |
|
This syntax works with lists (proper or improper), vectors and |
|
all types of maps. |
|
|
|
(list &as a b c) (list 1 2 3) |
|
|
|
binds A to 1, B to 2, C to 3 and LIST to (1 2 3). |
|
|
|
Similarly: |
|
|
|
(bounds &as beg . end) (cons 1 2) |
|
|
|
binds BEG to 1, END to 2 and BOUNDS to (1 . 2). |
|
|
|
(items &as first . rest) (list 1 2 3) |
|
|
|
binds FIRST to 1, REST to (2 3) and ITEMS to (1 2 3) |
|
|
|
[vect &as _ b c] [1 2 3] |
|
|
|
binds B to 2, C to 3 and VECT to [1 2 3] (_ avoids binding as usual). |
|
|
|
(plist &as &plist :b b) (list :a 1 :b 2 :c 3) |
|
|
|
binds B to 2 and PLIST to (:a 1 :b 2 :c 3). Same for &alist and &hash. |
|
|
|
This is especially useful when we want to capture the result of a |
|
computation and destructure at the same time. Consider the |
|
form (function-returning-complex-structure) returning a list of |
|
two vectors with two items each. We want to capture this entire |
|
result and pass it to another computation, but at the same time |
|
we want to get the second item from each vector. We can achieve |
|
it with pattern |
|
|
|
(result &as [_ a] [_ b]) (function-returning-complex-structure) |
|
|
|
Note: Clojure programmers may know this feature as the \":as |
|
binding\". The difference is that we put the &as at the front |
|
because we need to support improper list binding." |
|
(declare (debug ([&or (&rest (sexp form)) |
|
(vector [&rest [sexp form]])] |
|
body)) |
|
(indent 1)) |
|
(if (vectorp varlist) |
|
`(let* ,(dash--match (aref varlist 0) (aref varlist 1)) |
|
,@body) |
|
(let* ((inputs (--map-indexed (list (make-symbol (format "input%d" it-index)) (cadr it)) varlist)) |
|
(new-varlist (--map (list (caar it) (cadr it)) (-zip varlist inputs)))) |
|
`(let ,inputs |
|
(-let* ,new-varlist ,@body))))) |
|
|
|
(defmacro -lambda (match-form &rest body) |
|
"Return a lambda which destructures its input as MATCH-FORM and executes BODY. |
|
|
|
Note that you have to enclose the MATCH-FORM in a pair of parens, |
|
such that: |
|
|
|
(-lambda (x) body) |
|
(-lambda (x y ...) body) |
|
|
|
has the usual semantics of `lambda'. Furthermore, these get |
|
translated into normal lambda, so there is no performance |
|
penalty. |
|
|
|
See `-let' for the description of destructuring mechanism." |
|
(declare (doc-string 2) (indent defun) |
|
(debug (&define sexp |
|
[&optional stringp] |
|
[&optional ("interactive" interactive)] |
|
def-body))) |
|
(cond |
|
((not (consp match-form)) |
|
(signal 'wrong-type-argument "match-form must be a list")) |
|
;; no destructuring, so just return regular lambda to make things faster |
|
((-all? 'symbolp match-form) |
|
`(lambda ,match-form ,@body)) |
|
(t |
|
(let* ((inputs (--map-indexed (list it (make-symbol (format "input%d" it-index))) match-form))) |
|
;; TODO: because inputs to the lambda are evaluated only once, |
|
;; -let* need not to create the extra bindings to ensure that. |
|
;; We should find a way to optimize that. Not critical however. |
|
`(lambda ,(--map (cadr it) inputs) |
|
(-let* ,inputs ,@body)))))) |
|
|
|
(defmacro -if-let* (vars-vals then &rest else) |
|
"If all VALS evaluate to true, bind them to their corresponding |
|
VARS and do THEN, otherwise do ELSE. VARS-VALS should be a list |
|
of (VAR VAL) pairs. |
|
|
|
Note: binding is done according to `-let*'. VALS are evaluated |
|
sequentially, and evaluation stops after the first nil VAL is |
|
encountered." |
|
(declare (debug ((&rest (sexp form)) form body)) |
|
(indent 2)) |
|
(->> vars-vals |
|
(--mapcat (dash--match (car it) (cadr it))) |
|
(--reduce-r-from |
|
(let ((var (car it)) |
|
(val (cadr it))) |
|
`(let ((,var ,val)) |
|
(if ,var ,acc ,@else))) |
|
then))) |
|
|
|
(defmacro -if-let (var-val then &rest else) |
|
"If VAL evaluates to non-nil, bind it to VAR and do THEN, |
|
otherwise do ELSE. VAR-VAL should be a (VAR VAL) pair. |
|
|
|
Note: binding is done according to `-let'." |
|
(declare (debug ((sexp form) form body)) |
|
(indent 2)) |
|
`(-if-let* (,var-val) ,then ,@else)) |
|
|
|
(defmacro --if-let (val then &rest else) |
|
"If VAL evaluates to non-nil, bind it to `it' and do THEN, |
|
otherwise do ELSE." |
|
(declare (debug (form form body)) |
|
(indent 2)) |
|
`(-if-let (it ,val) ,then ,@else)) |
|
|
|
(defmacro -when-let* (vars-vals &rest body) |
|
"If all VALS evaluate to true, bind them to their corresponding |
|
VARS and execute body. VARS-VALS should be a list of (VAR VAL) |
|
pairs. |
|
|
|
Note: binding is done according to `-let*'. VALS are evaluated |
|
sequentially, and evaluation stops after the first nil VAL is |
|
encountered." |
|
(declare (debug ((&rest (sexp form)) body)) |
|
(indent 1)) |
|
`(-if-let* ,vars-vals (progn ,@body))) |
|
|
|
(defmacro -when-let (var-val &rest body) |
|
"If VAL evaluates to non-nil, bind it to VAR and execute body. |
|
VAR-VAL should be a (VAR VAL) pair. |
|
|
|
Note: binding is done according to `-let'." |
|
(declare (debug ((sexp form) body)) |
|
(indent 1)) |
|
`(-if-let ,var-val (progn ,@body))) |
|
|
|
(defmacro --when-let (val &rest body) |
|
"If VAL evaluates to non-nil, bind it to `it' and execute |
|
body." |
|
(declare (debug (form body)) |
|
(indent 1)) |
|
`(--if-let ,val (progn ,@body))) |
|
|
|
(defvar -compare-fn nil |
|
"Tests for equality use this function or `equal' if this is nil. |
|
It should only be set using dynamic scope with a let, like: |
|
|
|
(let ((-compare-fn #'=)) (-union numbers1 numbers2 numbers3)") |
|
|
|
(defun -distinct (list) |
|
"Return a new list with all duplicates removed. |
|
The test for equality is done with `equal', |
|
or with `-compare-fn' if that's non-nil. |
|
|
|
Alias: `-uniq'" |
|
(let (result) |
|
(--each list (unless (-contains? result it) (!cons it result))) |
|
(nreverse result))) |
|
|
|
(defalias '-uniq '-distinct) |
|
|
|
(defun -union (list list2) |
|
"Return a new list containing the elements of LIST1 and elements of LIST2 that are not in LIST1. |
|
The test for equality is done with `equal', |
|
or with `-compare-fn' if that's non-nil." |
|
;; We fall back to iteration implementation if the comparison |
|
;; function isn't one of `eq', `eql' or `equal'. |
|
(let* ((result (reverse list)) |
|
;; TODO: get rid of this dynamic variable, pass it as an |
|
;; argument instead. |
|
(-compare-fn (if (bound-and-true-p -compare-fn) |
|
-compare-fn |
|
'equal))) |
|
(if (memq -compare-fn '(eq eql equal)) |
|
(let ((ht (make-hash-table :test -compare-fn))) |
|
(--each list (puthash it t ht)) |
|
(--each list2 (unless (gethash it ht) (!cons it result)))) |
|
(--each list2 (unless (-contains? result it) (!cons it result)))) |
|
(nreverse result))) |
|
|
|
(defun -intersection (list list2) |
|
"Return a new list containing only the elements that are members of both LIST and LIST2. |
|
The test for equality is done with `equal', |
|
or with `-compare-fn' if that's non-nil." |
|
(--filter (-contains? list2 it) list)) |
|
|
|
(defun -difference (list list2) |
|
"Return a new list with only the members of LIST that are not in LIST2. |
|
The test for equality is done with `equal', |
|
or with `-compare-fn' if that's non-nil." |
|
(--filter (not (-contains? list2 it)) list)) |
|
|
|
(defun -contains? (list element) |
|
"Return non-nil if LIST contains ELEMENT. |
|
|
|
The test for equality is done with `equal', or with `-compare-fn' |
|
if that's non-nil. |
|
|
|
Alias: `-contains-p'" |
|
(not |
|
(null |
|
(cond |
|
((null -compare-fn) (member element list)) |
|
((eq -compare-fn 'eq) (memq element list)) |
|
((eq -compare-fn 'eql) (memql element list)) |
|
(t |
|
(let ((lst list)) |
|
(while (and lst |
|
(not (funcall -compare-fn element (car lst)))) |
|
(setq lst (cdr lst))) |
|
lst)))))) |
|
|
|
(defalias '-contains-p '-contains?) |
|
|
|
(defun -same-items? (list list2) |
|
"Return true if LIST and LIST2 has the same items. |
|
|
|
The order of the elements in the lists does not matter. |
|
|
|
Alias: `-same-items-p'" |
|
(let ((length-a (length list)) |
|
(length-b (length list2))) |
|
(and |
|
(= length-a length-b) |
|
(= length-a (length (-intersection list list2)))))) |
|
|
|
(defalias '-same-items-p '-same-items?) |
|
|
|
(defun -is-prefix? (prefix list) |
|
"Return non-nil if PREFIX is prefix of LIST. |
|
|
|
Alias: `-is-prefix-p'" |
|
(--each-while list (equal (car prefix) it) |
|
(!cdr prefix)) |
|
(not prefix)) |
|
|
|
(defun -is-suffix? (suffix list) |
|
"Return non-nil if SUFFIX is suffix of LIST. |
|
|
|
Alias: `-is-suffix-p'" |
|
(-is-prefix? (reverse suffix) (reverse list))) |
|
|
|
(defun -is-infix? (infix list) |
|
"Return non-nil if INFIX is infix of LIST. |
|
|
|
This operation runs in O(n^2) time |
|
|
|
Alias: `-is-infix-p'" |
|
(let (done) |
|
(while (and (not done) list) |
|
(setq done (-is-prefix? infix list)) |
|
(!cdr list)) |
|
done)) |
|
|
|
(defalias '-is-prefix-p '-is-prefix?) |
|
(defalias '-is-suffix-p '-is-suffix?) |
|
(defalias '-is-infix-p '-is-infix?) |
|
|
|
(defun -sort (comparator list) |
|
"Sort LIST, stably, comparing elements using COMPARATOR. |
|
Return the sorted list. LIST is NOT modified by side effects. |
|
COMPARATOR is called with two elements of LIST, and should return non-nil |
|
if the first element should sort before the second." |
|
(sort (copy-sequence list) comparator)) |
|
|
|
(defmacro --sort (form list) |
|
"Anaphoric form of `-sort'." |
|
(declare (debug (form form))) |
|
`(-sort (lambda (it other) ,form) ,list)) |
|
|
|
(defun -list (&rest args) |
|
"Return a list with ARGS. |
|
|
|
If first item of ARGS is already a list, simply return ARGS. If |
|
not, return a list with ARGS as elements." |
|
(let ((arg (car args))) |
|
(if (listp arg) arg args))) |
|
|
|
(defun -repeat (n x) |
|
"Return a list with X repeated N times. |
|
Return nil if N is less than 1." |
|
(let (ret) |
|
(--dotimes n (!cons x ret)) |
|
ret)) |
|
|
|
(defun -sum (list) |
|
"Return the sum of LIST." |
|
(apply '+ list)) |
|
|
|
(defun -product (list) |
|
"Return the product of LIST." |
|
(apply '* list)) |
|
|
|
(defun -max (list) |
|
"Return the largest value from LIST of numbers or markers." |
|
(apply 'max list)) |
|
|
|
(defun -min (list) |
|
"Return the smallest value from LIST of numbers or markers." |
|
(apply 'min list)) |
|
|
|
(defun -max-by (comparator list) |
|
"Take a comparison function COMPARATOR and a LIST and return |
|
the greatest element of the list by the comparison function. |
|
|
|
See also combinator `-on' which can transform the values before |
|
comparing them." |
|
(--reduce (if (funcall comparator it acc) it acc) list)) |
|
|
|
(defun -min-by (comparator list) |
|
"Take a comparison function COMPARATOR and a LIST and return |
|
the least element of the list by the comparison function. |
|
|
|
See also combinator `-on' which can transform the values before |
|
comparing them." |
|
(--reduce (if (funcall comparator it acc) acc it) list)) |
|
|
|
(defmacro --max-by (form list) |
|
"Anaphoric version of `-max-by'. |
|
|
|
The items for the comparator form are exposed as \"it\" and \"other\"." |
|
(declare (debug (form form))) |
|
`(-max-by (lambda (it other) ,form) ,list)) |
|
|
|
(defmacro --min-by (form list) |
|
"Anaphoric version of `-min-by'. |
|
|
|
The items for the comparator form are exposed as \"it\" and \"other\"." |
|
(declare (debug (form form))) |
|
`(-min-by (lambda (it other) ,form) ,list)) |
|
|
|
(defun -iterate (fun init n) |
|
"Return a list of iterated applications of FUN to INIT. |
|
|
|
This means a list of form: |
|
|
|
(init (fun init) (fun (fun init)) ...) |
|
|
|
N is the length of the returned list." |
|
(if (= n 0) nil |
|
(let ((r (list init))) |
|
(--dotimes (1- n) |
|
(push (funcall fun (car r)) r)) |
|
(nreverse r)))) |
|
|
|
(defun -fix (fn list) |
|
"Compute the (least) fixpoint of FN with initial input LIST. |
|
|
|
FN is called at least once, results are compared with `equal'." |
|
(let ((re (funcall fn list))) |
|
(while (not (equal list re)) |
|
(setq list re) |
|
(setq re (funcall fn re))) |
|
re)) |
|
|
|
(defmacro --fix (form list) |
|
"Anaphoric form of `-fix'." |
|
`(-fix (lambda (it) ,form) ,list)) |
|
|
|
(defun -unfold (fun seed) |
|
"Build a list from SEED using FUN. |
|
|
|
This is \"dual\" operation to `-reduce-r': while -reduce-r |
|
consumes a list to produce a single value, `-unfold' takes a |
|
seed value and builds a (potentially infinite!) list. |
|
|
|
FUN should return `nil' to stop the generating process, or a |
|
cons (A . B), where A will be prepended to the result and B is |
|
the new seed." |
|
(let ((last (funcall fun seed)) r) |
|
(while last |
|
(push (car last) r) |
|
(setq last (funcall fun (cdr last)))) |
|
(nreverse r))) |
|
|
|
(defmacro --unfold (form seed) |
|
"Anaphoric version of `-unfold'." |
|
(declare (debug (form form))) |
|
`(-unfold (lambda (it) ,form) ,seed)) |
|
|
|
(defun -cons-pair? (con) |
|
"Return non-nil if CON is true cons pair. |
|
That is (A . B) where B is not a list." |
|
(and (listp con) |
|
(not (listp (cdr con))))) |
|
|
|
(defun -cons-to-list (con) |
|
"Convert a cons pair to a list with `car' and `cdr' of the pair respectively." |
|
(list (car con) (cdr con))) |
|
|
|
(defun -value-to-list (val) |
|
"Convert a value to a list. |
|
|
|
If the value is a cons pair, make a list with two elements, `car' |
|
and `cdr' of the pair respectively. |
|
|
|
If the value is anything else, wrap it in a list." |
|
(cond |
|
((-cons-pair? val) (-cons-to-list val)) |
|
(t (list val)))) |
|
|
|
(defun -tree-mapreduce-from (fn folder init-value tree) |
|
"Apply FN to each element of TREE, and make a list of the results. |
|
If elements of TREE are lists themselves, apply FN recursively to |
|
elements of these nested lists. |
|
|
|
Then reduce the resulting lists using FOLDER and initial value |
|
INIT-VALUE. See `-reduce-r-from'. |
|
|
|
This is the same as calling `-tree-reduce-from' after `-tree-map' |
|
but is twice as fast as it only traverse the structure once." |
|
(cond |
|
((not tree) nil) |
|
((-cons-pair? tree) (funcall fn tree)) |
|
((listp tree) |
|
(-reduce-r-from folder init-value (mapcar (lambda (x) (-tree-mapreduce-from fn folder init-value x)) tree))) |
|
(t (funcall fn tree)))) |
|
|
|
(defmacro --tree-mapreduce-from (form folder init-value tree) |
|
"Anaphoric form of `-tree-mapreduce-from'." |
|
(declare (debug (form form form form))) |
|
`(-tree-mapreduce-from (lambda (it) ,form) (lambda (it acc) ,folder) ,init-value ,tree)) |
|
|
|
(defun -tree-mapreduce (fn folder tree) |
|
"Apply FN to each element of TREE, and make a list of the results. |
|
If elements of TREE are lists themselves, apply FN recursively to |
|
elements of these nested lists. |
|
|
|
Then reduce the resulting lists using FOLDER and initial value |
|
INIT-VALUE. See `-reduce-r-from'. |
|
|
|
This is the same as calling `-tree-reduce' after `-tree-map' |
|
but is twice as fast as it only traverse the structure once." |
|
(cond |
|
((not tree) nil) |
|
((-cons-pair? tree) (funcall fn tree)) |
|
((listp tree) |
|
(-reduce-r folder (mapcar (lambda (x) (-tree-mapreduce fn folder x)) tree))) |
|
(t (funcall fn tree)))) |
|
|
|
(defmacro --tree-mapreduce (form folder tree) |
|
"Anaphoric form of `-tree-mapreduce'." |
|
(declare (debug (form form form))) |
|
`(-tree-mapreduce (lambda (it) ,form) (lambda (it acc) ,folder) ,tree)) |
|
|
|
(defun -tree-map (fn tree) |
|
"Apply FN to each element of TREE while preserving the tree structure." |
|
(cond |
|
((not tree) nil) |
|
((-cons-pair? tree) (funcall fn tree)) |
|
((listp tree) |
|
(mapcar (lambda (x) (-tree-map fn x)) tree)) |
|
(t (funcall fn tree)))) |
|
|
|
(defmacro --tree-map (form tree) |
|
"Anaphoric form of `-tree-map'." |
|
(declare (debug (form form))) |
|
`(-tree-map (lambda (it) ,form) ,tree)) |
|
|
|
(defun -tree-reduce-from (fn init-value tree) |
|
"Use FN to reduce elements of list TREE. |
|
If elements of TREE are lists themselves, apply the reduction recursively. |
|
|
|
FN is first applied to INIT-VALUE and first element of the list, |
|
then on this result and second element from the list etc. |
|
|
|
The initial value is ignored on cons pairs as they always contain |
|
two elements." |
|
(cond |
|
((not tree) nil) |
|
((-cons-pair? tree) tree) |
|
((listp tree) |
|
(-reduce-r-from fn init-value (mapcar (lambda (x) (-tree-reduce-from fn init-value x)) tree))) |
|
(t tree))) |
|
|
|
(defmacro --tree-reduce-from (form init-value tree) |
|
"Anaphoric form of `-tree-reduce-from'." |
|
(declare (debug (form form form))) |
|
`(-tree-reduce-from (lambda (it acc) ,form) ,init-value ,tree)) |
|
|
|
(defun -tree-reduce (fn tree) |
|
"Use FN to reduce elements of list TREE. |
|
If elements of TREE are lists themselves, apply the reduction recursively. |
|
|
|
FN is first applied to first element of the list and second |
|
element, then on this result and third element from the list etc. |
|
|
|
See `-reduce-r' for how exactly are lists of zero or one element handled." |
|
(cond |
|
((not tree) nil) |
|
((-cons-pair? tree) tree) |
|
((listp tree) |
|
(-reduce-r fn (mapcar (lambda (x) (-tree-reduce fn x)) tree))) |
|
(t tree))) |
|
|
|
(defmacro --tree-reduce (form tree) |
|
"Anaphoric form of `-tree-reduce'." |
|
(declare (debug (form form))) |
|
`(-tree-reduce (lambda (it acc) ,form) ,tree)) |
|
|
|
(defun -tree-map-nodes (pred fun tree) |
|
"Call FUN on each node of TREE that satisfies PRED. |
|
|
|
If PRED returns nil, continue descending down this node. If PRED |
|
returns non-nil, apply FUN to this node and do not descend |
|
further." |
|
(if (funcall pred tree) |
|
(funcall fun tree) |
|
(if (and (listp tree) |
|
(not (-cons-pair? tree))) |
|
(-map (lambda (x) (-tree-map-nodes pred fun x)) tree) |
|
tree))) |
|
|
|
(defmacro --tree-map-nodes (pred form tree) |
|
"Anaphoric form of `-tree-map-nodes'." |
|
`(-tree-map-nodes (lambda (it) ,pred) (lambda (it) ,form) ,tree)) |
|
|
|
(defun -tree-seq (branch children tree) |
|
"Return a sequence of the nodes in TREE, in depth-first search order. |
|
|
|
BRANCH is a predicate of one argument that returns non-nil if the |
|
passed argument is a branch, that is, a node that can have children. |
|
|
|
CHILDREN is a function of one argument that returns the children |
|
of the passed branch node. |
|
|
|
Non-branch nodes are simply copied." |
|
(cons tree |
|
(when (funcall branch tree) |
|
(-mapcat (lambda (x) (-tree-seq branch children x)) |
|
(funcall children tree))))) |
|
|
|
(defmacro --tree-seq (branch children tree) |
|
"Anaphoric form of `-tree-seq'." |
|
`(-tree-seq (lambda (it) ,branch) (lambda (it) ,children) ,tree)) |
|
|
|
(defun -clone (list) |
|
"Create a deep copy of LIST. |
|
The new list has the same elements and structure but all cons are |
|
replaced with new ones. This is useful when you need to clone a |
|
structure such as plist or alist." |
|
(-tree-map 'identity list)) |
|
|
|
(defun dash-enable-font-lock () |
|
"Add syntax highlighting to dash functions, macros and magic values." |
|
(eval-after-load "lisp-mode" |
|
'(progn |
|
(let ((new-keywords '( |
|
"-each" |
|
"--each" |
|
"-each-while" |
|
"--each-while" |
|
"-dotimes" |
|
"--dotimes" |
|
"-map" |
|
"--map" |
|
"-reduce-from" |
|
"--reduce-from" |
|
"-reduce" |
|
"--reduce" |
|
"-reduce-r-from" |
|
"--reduce-r-from" |
|
"-reduce-r" |
|
"--reduce-r" |
|
"-filter" |
|
"--filter" |
|
"-select" |
|
"--select" |
|
"-remove" |
|
"--remove" |
|
"-reject" |
|
"--reject" |
|
"-remove-first" |
|
"--remove-first" |
|
"-reject-first" |
|
"--reject-first" |
|
"-remove-last" |
|
"--remove-last" |
|
"-reject-last" |
|
"--reject-last" |
|
"-remove-item" |
|
"-non-nil" |
|
"-keep" |
|
"--keep" |
|
"-map-indexed" |
|
"--map-indexed" |
|
"-splice" |
|
"--splice" |
|
"-splice-list" |
|
"--splice-list" |
|
"-map-when" |
|
"--map-when" |
|
"-replace-where" |
|
"--replace-where" |
|
"-map-first" |
|
"--map-first" |
|
"-map-last" |
|
"--map-last" |
|
"-replace" |
|
"-replace-first" |
|
"-replace-last" |
|
"-flatten" |
|
"-flatten-n" |
|
"-concat" |
|
"-mapcat" |
|
"--mapcat" |
|
"-copy" |
|
"-cons*" |
|
"-snoc" |
|
"-first" |
|
"--first" |
|
"-find" |
|
"--find" |
|
"-some" |
|
"--some" |
|
"-any" |
|
"--any" |
|
"-last" |
|
"--last" |
|
"-first-item" |
|
"-last-item" |
|
"-butlast" |
|
"-count" |
|
"--count" |
|
"-any?" |
|
"--any?" |
|
"-some?" |
|
"--some?" |
|
"-any-p" |
|
"--any-p" |
|
"-some-p" |
|
"--some-p" |
|
"-all?" |
|
"--all?" |
|
"-every?" |
|
"--every?" |
|
"-all-p" |
|
"--all-p" |
|
"-every-p" |
|
"--every-p" |
|
"-none?" |
|
"--none?" |
|
"-none-p" |
|
"--none-p" |
|
"-only-some?" |
|
"--only-some?" |
|
"-only-some-p" |
|
"--only-some-p" |
|
"-slice" |
|
"-take" |
|
"-drop" |
|
"-take-while" |
|
"--take-while" |
|
"-drop-while" |
|
"--drop-while" |
|
"-split-at" |
|
"-rotate" |
|
"-insert-at" |
|
"-replace-at" |
|
"-update-at" |
|
"--update-at" |
|
"-remove-at" |
|
"-remove-at-indices" |
|
"-split-with" |
|
"--split-with" |
|
"-split-on" |
|
"-split-when" |
|
"--split-when" |
|
"-separate" |
|
"--separate" |
|
"-partition-all-in-steps" |
|
"-partition-in-steps" |
|
"-partition-all" |
|
"-partition" |
|
"-partition-by" |
|
"--partition-by" |
|
"-partition-by-header" |
|
"--partition-by-header" |
|
"-group-by" |
|
"--group-by" |
|
"-interpose" |
|
"-interleave" |
|
"-zip-with" |
|
"--zip-with" |
|
"-zip" |
|
"-zip-fill" |
|
"-cycle" |
|
"-pad" |
|
"-annotate" |
|
"--annotate" |
|
"-table" |
|
"-table-flat" |
|
"-partial" |
|
"-elem-index" |
|
"-elem-indices" |
|
"-find-indices" |
|
"--find-indices" |
|
"-find-index" |
|
"--find-index" |
|
"-find-last-index" |
|
"--find-last-index" |
|
"-select-by-indices" |
|
"-grade-up" |
|
"-grade-down" |
|
"->" |
|
"->>" |
|
"-->" |
|
"-when-let" |
|
"-when-let*" |
|
"--when-let" |
|
"-if-let" |
|
"-if-let*" |
|
"--if-let" |
|
"-let*" |
|
"-let" |
|
"-lambda" |
|
"-distinct" |
|
"-uniq" |
|
"-union" |
|
"-intersection" |
|
"-difference" |
|
"-contains?" |
|
"-contains-p" |
|
"-same-items?" |
|
"-same-items-p" |
|
"-is-prefix-p" |
|
"-is-prefix?" |
|
"-is-suffix-p" |
|
"-is-suffix?" |
|
"-is-infix-p" |
|
"-is-infix?" |
|
"-sort" |
|
"--sort" |
|
"-list" |
|
"-repeat" |
|
"-sum" |
|
"-product" |
|
"-max" |
|
"-min" |
|
"-max-by" |
|
"--max-by" |
|
"-min-by" |
|
"--min-by" |
|
"-iterate" |
|
"--iterate" |
|
"-fix" |
|
"--fix" |
|
"-unfold" |
|
"--unfold" |
|
"-cons-pair?" |
|
"-cons-to-list" |
|
"-value-to-list" |
|
"-tree-mapreduce-from" |
|
"--tree-mapreduce-from" |
|
"-tree-mapreduce" |
|
"--tree-mapreduce" |
|
"-tree-map" |
|
"--tree-map" |
|
"-tree-reduce-from" |
|
"--tree-reduce-from" |
|
"-tree-reduce" |
|
"--tree-reduce" |
|
"-tree-seq" |
|
"--tree-seq" |
|
"-tree-map-nodes" |
|
"--tree-map-nodes" |
|
"-clone" |
|
"-rpartial" |
|
"-juxt" |
|
"-applify" |
|
"-on" |
|
"-flip" |
|
"-const" |
|
"-cut" |
|
"-orfn" |
|
"-andfn" |
|
"-iteratefn" |
|
"-fixfn" |
|
"-prodfn" |
|
)) |
|
(special-variables '( |
|
"it" |
|
"it-index" |
|
"acc" |
|
"other" |
|
))) |
|
(font-lock-add-keywords 'emacs-lisp-mode `((,(concat "\\_<" (regexp-opt special-variables 'paren) "\\_>") |
|
1 font-lock-variable-name-face)) 'append) |
|
(font-lock-add-keywords 'emacs-lisp-mode `((,(concat "(\\s-*" (regexp-opt new-keywords 'paren) "\\_>") |
|
1 font-lock-keyword-face)) 'append)) |
|
(--each (buffer-list) |
|
(with-current-buffer it |
|
(when (and (eq major-mode 'emacs-lisp-mode) |
|
(boundp 'font-lock-mode) |
|
font-lock-mode) |
|
(font-lock-refresh-defaults))))))) |
|
|
|
(provide 'dash) |
|
;;; dash.el ends here
|
|
|