diff options
| author | Artur Malabarba | 2014-12-19 18:25:06 -0200 |
|---|---|---|
| committer | Artur Malabarba | 2014-12-19 18:30:26 -0200 |
| commit | f447d33fdb082ce8e5d336be6034df24339b4c45 (patch) | |
| tree | 7ad100c335eb0e84024c0aab266f674e2078ff47 | |
| parent | 948fa912de164a1374c87e9206cddca741b7fa33 (diff) | |
| download | emacs-f447d33fdb082ce8e5d336be6034df24339b4c45.tar.gz emacs-f447d33fdb082ce8e5d336be6034df24339b4c45.zip | |
* let-alist.el (let-alist): Enable access to deeper alists
Acces them by using extra dots inside the dotted symbols.
| -rw-r--r-- | lisp/ChangeLog | 5 | ||||
| -rw-r--r-- | lisp/let-alist.el | 71 | ||||
| -rw-r--r-- | test/ChangeLog | 1 | ||||
| -rw-r--r-- | test/automated/let-alist.el | 26 |
4 files changed, 85 insertions, 18 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 12530a997ba..b658cc1d0fa 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,8 @@ | |||
| 1 | 2014-12-19 Artur Malabarba <bruce.connor.am@gmail.com> | ||
| 2 | |||
| 3 | * let-alist.el (let-alist): Enable access to deeper alists by | ||
| 4 | using dots inside the dotted symbols. | ||
| 5 | |||
| 1 | 2014-12-19 Alan Mackenzie <acm@muc.de> | 6 | 2014-12-19 Alan Mackenzie <acm@muc.de> |
| 2 | 7 | ||
| 3 | Make C++11 uniform init syntax work. New keywords "final" and "override" | 8 | Make C++11 uniform init syntax work. New keywords "final" and "override" |
diff --git a/lisp/let-alist.el b/lisp/let-alist.el index 813b8417aaa..692beba16dd 100644 --- a/lisp/let-alist.el +++ b/lisp/let-alist.el | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | ;; Author: Artur Malabarba <bruce.connor.am@gmail.com> | 5 | ;; Author: Artur Malabarba <bruce.connor.am@gmail.com> |
| 6 | ;; Maintainer: Artur Malabarba <bruce.connor.am@gmail.com> | 6 | ;; Maintainer: Artur Malabarba <bruce.connor.am@gmail.com> |
| 7 | ;; Version: 1.0.1 | 7 | ;; Version: 1.0.2 |
| 8 | ;; Keywords: extensions lisp | 8 | ;; Keywords: extensions lisp |
| 9 | ;; Prefix: let-alist | 9 | ;; Prefix: let-alist |
| 10 | ;; Separator: - | 10 | ;; Separator: - |
| @@ -39,21 +39,25 @@ | |||
| 39 | ;; (let-alist alist | 39 | ;; (let-alist alist |
| 40 | ;; (if (and .title .body) | 40 | ;; (if (and .title .body) |
| 41 | ;; .body | 41 | ;; .body |
| 42 | ;; .site)) | 42 | ;; .site |
| 43 | ;; .site.contents)) | ||
| 43 | ;; | 44 | ;; |
| 44 | ;; expands to | 45 | ;; essentially expands to |
| 45 | ;; | 46 | ;; |
| 46 | ;; (let ((.title (cdr (assq 'title alist))) | 47 | ;; (let ((.title (cdr (assq 'title alist))) |
| 47 | ;; (.body (cdr (assq 'body alist))) | 48 | ;; (.body (cdr (assq 'body alist))) |
| 48 | ;; (.site (cdr (assq 'site alist)))) | 49 | ;; (.site (cdr (assq 'site alist))) |
| 50 | ;; (.site.contents (cdr (assq 'contents (cdr (assq 'site alist)))))) | ||
| 49 | ;; (if (and .title .body) | 51 | ;; (if (and .title .body) |
| 50 | ;; .body | 52 | ;; .body |
| 51 | ;; .site)) | 53 | ;; .site |
| 54 | ;; .site.contents)) | ||
| 55 | ;; | ||
| 56 | ;; If you nest `let-alist' invocations, the inner one can't access | ||
| 57 | ;; the variables of the outer one. You can, however, access alists | ||
| 58 | ;; inside the original alist by using dots inside the symbol, as | ||
| 59 | ;; displayed in the example above by the `.site.contents'. | ||
| 52 | ;; | 60 | ;; |
| 53 | ;; Note that only one level is supported. If you nest `let-alist' | ||
| 54 | ;; invocations, the inner one can't access the variables of the outer | ||
| 55 | ;; one. | ||
| 56 | |||
| 57 | ;;; Code: | 61 | ;;; Code: |
| 58 | 62 | ||
| 59 | 63 | ||
| @@ -72,6 +76,31 @@ symbol, and each cdr is the same symbol without the `.'." | |||
| 72 | (t (apply #'append | 76 | (t (apply #'append |
| 73 | (mapcar #'let-alist--deep-dot-search data))))) | 77 | (mapcar #'let-alist--deep-dot-search data))))) |
| 74 | 78 | ||
| 79 | (defun let-alist--access-sexp (symbol variable) | ||
| 80 | "Return a sexp used to acess SYMBOL inside VARIABLE." | ||
| 81 | (let* ((clean (let-alist--remove-dot symbol)) | ||
| 82 | (name (symbol-name clean))) | ||
| 83 | (if (string-match "\\`\\." name) | ||
| 84 | clean | ||
| 85 | (let-alist--list-to-sexp | ||
| 86 | (mapcar #'intern (nreverse (split-string name "\\."))) | ||
| 87 | variable)))) | ||
| 88 | |||
| 89 | (defun let-alist--list-to-sexp (list var) | ||
| 90 | "Turn symbols LIST into recursive calls to `cdr' `assq' on VAR." | ||
| 91 | `(cdr (assq ',(car list) | ||
| 92 | ,(if (cdr list) (let-alist--list-to-sexp (cdr list) var) | ||
| 93 | var)))) | ||
| 94 | |||
| 95 | (defun let-alist--remove-dot (symbol) | ||
| 96 | "Return SYMBOL, sans an initial dot." | ||
| 97 | (let ((name (symbol-name symbol))) | ||
| 98 | (if (string-match "\\`\\." name) | ||
| 99 | (intern (replace-match "" nil nil name)) | ||
| 100 | symbol))) | ||
| 101 | |||
| 102 | |||
| 103 | ;;; The actual macro. | ||
| 75 | ;;;###autoload | 104 | ;;;###autoload |
| 76 | (defmacro let-alist (alist &rest body) | 105 | (defmacro let-alist (alist &rest body) |
| 77 | "Let-bind dotted symbols to their cdrs in ALIST and execute BODY. | 106 | "Let-bind dotted symbols to their cdrs in ALIST and execute BODY. |
| @@ -83,20 +112,28 @@ For instance, the following code | |||
| 83 | (let-alist alist | 112 | (let-alist alist |
| 84 | (if (and .title .body) | 113 | (if (and .title .body) |
| 85 | .body | 114 | .body |
| 86 | .site)) | 115 | .site |
| 116 | .site.contents)) | ||
| 87 | 117 | ||
| 88 | expands to | 118 | essentially expands to |
| 89 | 119 | ||
| 90 | (let ((.title (cdr (assq 'title alist))) | 120 | (let ((.title (cdr (assq 'title alist))) |
| 91 | (.body (cdr (assq 'body alist))) | 121 | (.body (cdr (assq 'body alist))) |
| 92 | (.site (cdr (assq 'site alist)))) | 122 | (.site (cdr (assq 'site alist))) |
| 123 | (.site.contents (cdr (assq 'contents (cdr (assq 'site alist)))))) | ||
| 93 | (if (and .title .body) | 124 | (if (and .title .body) |
| 94 | .body | 125 | .body |
| 95 | .site))" | 126 | .site |
| 127 | .site.contents)) | ||
| 128 | |||
| 129 | If you nest `let-alist' invocations, the inner one can't access | ||
| 130 | the variables of the outer one. You can, however, access alists | ||
| 131 | inside the original alist by using dots inside the symbol, as | ||
| 132 | displayed in the example above." | ||
| 96 | (declare (indent 1) (debug t)) | 133 | (declare (indent 1) (debug t)) |
| 97 | (let ((var (gensym "let-alist"))) | 134 | (let ((var (gensym "alist"))) |
| 98 | `(let ((,var ,alist)) | 135 | `(let ((,var ,alist)) |
| 99 | (let ,(mapcar (lambda (x) `(,(car x) (cdr (assq ',(cdr x) ,var)))) | 136 | (let ,(mapcar (lambda (x) `(,(car x) ,(let-alist--access-sexp (car x) var))) |
| 100 | (delete-dups (let-alist--deep-dot-search body))) | 137 | (delete-dups (let-alist--deep-dot-search body))) |
| 101 | ,@body)))) | 138 | ,@body)))) |
| 102 | 139 | ||
diff --git a/test/ChangeLog b/test/ChangeLog index 80d2a40bc4c..7d23b3efe1c 100644 --- a/test/ChangeLog +++ b/test/ChangeLog | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | 2014-12-19 Artur Malabarba <bruce.connor.am@gmail.com> | 1 | 2014-12-19 Artur Malabarba <bruce.connor.am@gmail.com> |
| 2 | 2 | ||
| 3 | * automated/let-alist.el: require `cl-lib' | 3 | * automated/let-alist.el: require `cl-lib' |
| 4 | New tests for accessing alists inside alists. | ||
| 4 | 5 | ||
| 5 | 2014-12-18 Artur Malabarba <bruce.connor.am@gmail.com> | 6 | 2014-12-18 Artur Malabarba <bruce.connor.am@gmail.com> |
| 6 | 7 | ||
diff --git a/test/automated/let-alist.el b/test/automated/let-alist.el index a700a4773ff..391ccb44a8d 100644 --- a/test/automated/let-alist.el +++ b/test/automated/let-alist.el | |||
| @@ -33,7 +33,19 @@ | |||
| 33 | (cl-letf (((symbol-function #'gensym) (lambda (x) 'symbol))) | 33 | (cl-letf (((symbol-function #'gensym) (lambda (x) 'symbol))) |
| 34 | (macroexpand | 34 | (macroexpand |
| 35 | '(let-alist data (list .test-one .test-two | 35 | '(let-alist data (list .test-one .test-two |
| 36 | .test-two .test-two))))))) | 36 | .test-two .test-two)))))) |
| 37 | (should | ||
| 38 | (equal | ||
| 39 | (let ((.external "ext") | ||
| 40 | (.external.too "et")) | ||
| 41 | (let-alist '((test-two . 0) | ||
| 42 | (test-three . 1) | ||
| 43 | (sublist . ((foo . 2) | ||
| 44 | (bar . 3)))) | ||
| 45 | (list .test-one .test-two .test-three | ||
| 46 | .sublist.foo .sublist.bar | ||
| 47 | ..external ..external.too))) | ||
| 48 | (list nil 0 1 2 3 "ext" "et")))) | ||
| 37 | 49 | ||
| 38 | (defvar let-alist--test-counter 0 | 50 | (defvar let-alist--test-counter 0 |
| 39 | "Used to count number of times a function is called.") | 51 | "Used to count number of times a function is called.") |
| @@ -49,5 +61,17 @@ | |||
| 49 | (list .test-one .test-two .test-two .test-three .cl-incf)) | 61 | (list .test-one .test-two .test-two .test-three .cl-incf)) |
| 50 | '(nil 1 1 2 nil))))) | 62 | '(nil 1 1 2 nil))))) |
| 51 | 63 | ||
| 64 | (ert-deftest let-alist-remove-dot () | ||
| 65 | "Remove firt dot from symbol." | ||
| 66 | (should (equal (let-alist--remove-dot 'hi) 'hi)) | ||
| 67 | (should (equal (let-alist--remove-dot '.hi) 'hi)) | ||
| 68 | (should (equal (let-alist--remove-dot '..hi) '.hi))) | ||
| 69 | |||
| 70 | (ert-deftest let-alist-list-to-sexp () | ||
| 71 | "Check that multiple dots are handled correctly." | ||
| 72 | (should (= 1 (eval (let-alist--list-to-sexp '(a b c d) ''((d (c (b (a . 1))))))))) | ||
| 73 | (should (equal (let-alist--access-sexp '.foo.bar.baz 'var) | ||
| 74 | '(cdr (assq 'baz (cdr (assq 'bar (cdr (assq 'foo var)))))))) | ||
| 75 | (should (equal (let-alist--access-sexp '..foo.bar.baz 'var) '.foo.bar.baz))) | ||
| 52 | 76 | ||
| 53 | ;;; let-alist.el ends here | 77 | ;;; let-alist.el ends here |