diff options
| author | Fabián Ezequiel Gallina | 2014-06-30 01:11:43 -0300 |
|---|---|---|
| committer | Fabián Ezequiel Gallina | 2014-06-30 01:11:43 -0300 |
| commit | c08f8be29f4f6d107da5cc38d614519df7a6ab11 (patch) | |
| tree | 1aa7c297a73c23b8c0dccc1242601c1eb02033dc /lisp | |
| parent | f8e16324a038417f0180b76c77c60313c880e74c (diff) | |
| download | emacs-c08f8be29f4f6d107da5cc38d614519df7a6ab11.tar.gz emacs-c08f8be29f4f6d107da5cc38d614519df7a6ab11.zip | |
New if-let, when-let, thread-first and thread-last macros.
* lisp/emacs-lisp/subr-x.el
(internal--listify, internal--check-binding)
(internal--build-binding-value-form, internal--build-binding)
(internal--build-bindings): New functions.
(internal--thread-argument, thread-first, thread-last)
(if-let, when-let): New macros.
* test/automated/subr-x-tests.el
(subr-x-test-if-let-single-binding-expansion)
(subr-x-test-if-let-single-symbol-expansion)
(subr-x-test-if-let-nil-related-expansion)
(subr-x-test-if-let-malformed-binding, subr-x-test-if-let-true)
(subr-x-test-if-let-false, subr-x-test-if-let-bound-references)
(subr-x-test-if-let-and-lazyness-is-preserved)
(subr-x-test-when-let-body-expansion)
(subr-x-test-when-let-single-binding-expansion)
(subr-x-test-when-let-single-symbol-expansion)
(subr-x-test-when-let-nil-related-expansion)
(subr-x-test-when-let-malformed-binding)
(subr-x-test-when-let-true, subr-x-test-when-let-false)
(subr-x-test-when-let-bound-references)
(subr-x-test-when-let-and-lazyness-is-preserved)
(subr-x-test-thread-first-no-forms)
(subr-x-test-thread-first-function-names-are-threaded)
(subr-x-test-thread-first-expansion)
(subr-x-test-thread-last-no-forms)
(subr-x-test-thread-last-function-names-are-threaded)
(subr-x-test-thread-last-expansion): New tests.
Diffstat (limited to 'lisp')
| -rw-r--r-- | lisp/ChangeLog | 11 | ||||
| -rw-r--r-- | lisp/emacs-lisp/subr-x.el | 107 |
2 files changed, 118 insertions, 0 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 3e9e6f3ee27..56e53ee673c 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,14 @@ | |||
| 1 | 2014-06-30 Fabián Ezequiel Gallina <fgallina@gnu.org> | ||
| 2 | |||
| 3 | New if-let, when-let, thread-first and thread-last macros. | ||
| 4 | |||
| 5 | * emacs-lisp/subr-x.el | ||
| 6 | (internal--listify, internal--check-binding) | ||
| 7 | (internal--build-binding-value-form, internal--build-binding) | ||
| 8 | (internal--build-bindings): New functions. | ||
| 9 | (internal--thread-argument, thread-first, thread-last) | ||
| 10 | (if-let, when-let): New macros. | ||
| 11 | |||
| 1 | 2014-06-30 Grégoire Jadi <daimrod@gmail.com> | 12 | 2014-06-30 Grégoire Jadi <daimrod@gmail.com> |
| 2 | 13 | ||
| 3 | * net/rcirc.el (rcirc-buffer-process): Restore previous | 14 | * net/rcirc.el (rcirc-buffer-process): Restore previous |
diff --git a/lisp/emacs-lisp/subr-x.el b/lisp/emacs-lisp/subr-x.el index 505a556b65f..60cd7b8995b 100644 --- a/lisp/emacs-lisp/subr-x.el +++ b/lisp/emacs-lisp/subr-x.el | |||
| @@ -32,6 +32,113 @@ | |||
| 32 | 32 | ||
| 33 | ;;; Code: | 33 | ;;; Code: |
| 34 | 34 | ||
| 35 | (require 'pcase) | ||
| 36 | |||
| 37 | |||
| 38 | (defmacro internal--thread-argument (first? &rest forms) | ||
| 39 | "Internal implementation for `thread-first' and `thread-last'. | ||
| 40 | When Argument FIRST? is non-nil argument is threaded first, else | ||
| 41 | last. FORMS are the expressions to be threaded." | ||
| 42 | (pcase forms | ||
| 43 | (`(,x (,f . ,args) . ,rest) | ||
| 44 | `(internal--thread-argument | ||
| 45 | ,first? ,(if first? `(,f ,x ,@args) `(,f ,@args ,x)) ,@rest)) | ||
| 46 | (`(,x ,f . ,rest) `(internal--thread-argument ,first? (,f ,x) ,@rest)) | ||
| 47 | (_ (car forms)))) | ||
| 48 | |||
| 49 | (defmacro thread-first (&rest forms) | ||
| 50 | "Thread FORMS elements as the first argument of their succesor. | ||
| 51 | Example: | ||
| 52 | (thread-first | ||
| 53 | 5 | ||
| 54 | (+ 20) | ||
| 55 | (/ 25) | ||
| 56 | - | ||
| 57 | (+ 40)) | ||
| 58 | Is equivalent to: | ||
| 59 | (+ (- (/ (+ 5 20) 25)) 40) | ||
| 60 | Note how the single `-' got converted into a list before | ||
| 61 | threading." | ||
| 62 | (declare (indent 1) | ||
| 63 | (debug (form &rest [&or symbolp (sexp &rest form)]))) | ||
| 64 | `(internal--thread-argument t ,@forms)) | ||
| 65 | |||
| 66 | (defmacro thread-last (&rest forms) | ||
| 67 | "Thread FORMS elements as the last argument of their succesor. | ||
| 68 | Example: | ||
| 69 | (thread-last | ||
| 70 | 5 | ||
| 71 | (+ 20) | ||
| 72 | (/ 25) | ||
| 73 | - | ||
| 74 | (+ 40)) | ||
| 75 | Is equivalent to: | ||
| 76 | (+ 40 (- (/ 25 (+ 20 5)))) | ||
| 77 | Note how the single `-' got converted into a list before | ||
| 78 | threading." | ||
| 79 | (declare (indent 1) (debug thread-first)) | ||
| 80 | `(internal--thread-argument nil ,@forms)) | ||
| 81 | |||
| 82 | (defsubst internal--listify (elt) | ||
| 83 | "Wrap ELT in a list if it is not one." | ||
| 84 | (if (not (listp elt)) | ||
| 85 | (list elt) | ||
| 86 | elt)) | ||
| 87 | |||
| 88 | (defsubst internal--check-binding (binding) | ||
| 89 | "Check BINDING is properly formed." | ||
| 90 | (when (> (length binding) 2) | ||
| 91 | (signal | ||
| 92 | 'error | ||
| 93 | (cons "`let' bindings can have only one value-form" binding))) | ||
| 94 | binding) | ||
| 95 | |||
| 96 | (defsubst internal--build-binding-value-form (binding prev-var) | ||
| 97 | "Build the conditional value form for BINDING using PREV-VAR." | ||
| 98 | `(,(car binding) (and ,prev-var ,(cadr binding)))) | ||
| 99 | |||
| 100 | (defun internal--build-binding (binding prev-var) | ||
| 101 | "Check and build a single BINDING with PREV-VAR." | ||
| 102 | (thread-first | ||
| 103 | binding | ||
| 104 | internal--listify | ||
| 105 | internal--check-binding | ||
| 106 | (internal--build-binding-value-form prev-var))) | ||
| 107 | |||
| 108 | (defun internal--build-bindings (bindings) | ||
| 109 | "Check and build conditional value forms for BINDINGS." | ||
| 110 | (let ((prev-var t)) | ||
| 111 | (mapcar (lambda (binding) | ||
| 112 | (let ((binding (internal--build-binding binding prev-var))) | ||
| 113 | (setq prev-var (car binding)) | ||
| 114 | binding)) | ||
| 115 | bindings))) | ||
| 116 | |||
| 117 | (defmacro if-let (bindings then &rest else) | ||
| 118 | "Process BINDINGS and if all values are non-nil eval THEN, else ELSE. | ||
| 119 | Argument BINDINGS is a list of tuples whose car is a symbol to be | ||
| 120 | bound and (optionally) used in THEN, and its cadr is a sexp to be | ||
| 121 | evaled to set symbol's value. In the special case you only want | ||
| 122 | to bind a single value, BINDINGS can just be a plain tuple." | ||
| 123 | (declare (indent 2) (debug ((&rest (symbolp form)) form body))) | ||
| 124 | (when (and (<= (length bindings) 2) | ||
| 125 | (not (listp (car bindings)))) | ||
| 126 | ;; Adjust the single binding case | ||
| 127 | (setq bindings (list bindings))) | ||
| 128 | `(let* ,(internal--build-bindings bindings) | ||
| 129 | (if ,(car (internal--listify (car (last bindings)))) | ||
| 130 | ,then | ||
| 131 | ,@else))) | ||
| 132 | |||
| 133 | (defmacro when-let (bindings &rest body) | ||
| 134 | "Process BINDINGS and if all values are non-nil eval BODY. | ||
| 135 | Argument BINDINGS is a list of tuples whose car is a symbol to be | ||
| 136 | bound and (optionally) used in BODY, and its cadr is a sexp to be | ||
| 137 | evaled to set symbol's value. In the special case you only want | ||
| 138 | to bind a single value, BINDINGS can just be a plain tuple." | ||
| 139 | (declare (indent 1) (debug if-let)) | ||
| 140 | (list 'if-let bindings (macroexp-progn body))) | ||
| 141 | |||
| 35 | (defsubst hash-table-keys (hash-table) | 142 | (defsubst hash-table-keys (hash-table) |
| 36 | "Return a list of keys in HASH-TABLE." | 143 | "Return a list of keys in HASH-TABLE." |
| 37 | (let ((keys '())) | 144 | (let ((keys '())) |