2014-12-14 13:15:50 +00:00
|
|
|
|
;;; let-alist.el --- Easily let-bind values of an assoc-list by their names -*- lexical-binding: t; -*-
|
2014-12-10 15:15:22 +00:00
|
|
|
|
|
2015-01-01 22:26:41 +00:00
|
|
|
|
;; Copyright (C) 2014-2015 Free Software Foundation, Inc.
|
2014-12-10 15:15:22 +00:00
|
|
|
|
|
|
|
|
|
;; Author: Artur Malabarba <bruce.connor.am@gmail.com>
|
|
|
|
|
;; Maintainer: Artur Malabarba <bruce.connor.am@gmail.com>
|
2014-12-22 12:36:30 +00:00
|
|
|
|
;; Version: 1.0.3
|
2014-12-10 15:15:22 +00:00
|
|
|
|
;; Keywords: extensions lisp
|
|
|
|
|
;; Prefix: let-alist
|
|
|
|
|
;; Separator: -
|
|
|
|
|
|
2014-12-10 21:18:46 +00:00
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
|
|
|
|
|
|
;; GNU Emacs is free software: you can redistribute it and/or modify
|
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
;; (at your option) any later version.
|
|
|
|
|
|
|
|
|
|
;; GNU Emacs is distributed in the hope that it will be useful,
|
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
|
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
2014-12-10 15:15:22 +00:00
|
|
|
|
;;; Commentary:
|
2014-12-10 21:18:46 +00:00
|
|
|
|
|
2014-12-10 15:15:22 +00:00
|
|
|
|
;; This package offers a single macro, `let-alist'. This macro takes a
|
|
|
|
|
;; first argument (whose value must be an alist) and a body.
|
|
|
|
|
;;
|
|
|
|
|
;; The macro expands to a let form containing body, where each dotted
|
|
|
|
|
;; symbol inside body is let-bound to their cdrs in the alist. Dotted
|
|
|
|
|
;; symbol is any symbol starting with a `.'. Only those present in
|
|
|
|
|
;; the body are let-bound and this search is done at compile time.
|
|
|
|
|
;;
|
|
|
|
|
;; For instance, the following code
|
|
|
|
|
;;
|
|
|
|
|
;; (let-alist alist
|
|
|
|
|
;; (if (and .title .body)
|
|
|
|
|
;; .body
|
2014-12-19 20:25:06 +00:00
|
|
|
|
;; .site
|
|
|
|
|
;; .site.contents))
|
2014-12-10 15:15:22 +00:00
|
|
|
|
;;
|
2014-12-19 20:25:06 +00:00
|
|
|
|
;; essentially expands to
|
2014-12-10 15:15:22 +00:00
|
|
|
|
;;
|
|
|
|
|
;; (let ((.title (cdr (assq 'title alist)))
|
2014-12-19 20:25:06 +00:00
|
|
|
|
;; (.body (cdr (assq 'body alist)))
|
|
|
|
|
;; (.site (cdr (assq 'site alist)))
|
|
|
|
|
;; (.site.contents (cdr (assq 'contents (cdr (assq 'site alist))))))
|
2014-12-10 15:15:22 +00:00
|
|
|
|
;; (if (and .title .body)
|
|
|
|
|
;; .body
|
2014-12-19 20:25:06 +00:00
|
|
|
|
;; .site
|
|
|
|
|
;; .site.contents))
|
|
|
|
|
;;
|
|
|
|
|
;; If you nest `let-alist' invocations, the inner one can't access
|
|
|
|
|
;; the variables of the outer one. You can, however, access alists
|
|
|
|
|
;; inside the original alist by using dots inside the symbol, as
|
|
|
|
|
;; displayed in the example above by the `.site.contents'.
|
2014-12-10 15:15:22 +00:00
|
|
|
|
;;
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(defun let-alist--deep-dot-search (data)
|
2014-12-10 21:18:46 +00:00
|
|
|
|
"Return alist of symbols inside DATA that start with a `.'.
|
2014-12-10 15:15:22 +00:00
|
|
|
|
Perform a deep search and return an alist where each car is the
|
|
|
|
|
symbol, and each cdr is the same symbol without the `.'."
|
|
|
|
|
(cond
|
|
|
|
|
((symbolp data)
|
|
|
|
|
(let ((name (symbol-name data)))
|
|
|
|
|
(when (string-match "\\`\\." name)
|
|
|
|
|
;; Return the cons cell inside a list, so it can be appended
|
|
|
|
|
;; with other results in the clause below.
|
|
|
|
|
(list (cons data (intern (replace-match "" nil nil name)))))))
|
|
|
|
|
((not (listp data)) nil)
|
|
|
|
|
(t (apply #'append
|
2014-12-18 20:59:23 +00:00
|
|
|
|
(mapcar #'let-alist--deep-dot-search data)))))
|
2014-12-10 15:15:22 +00:00
|
|
|
|
|
2014-12-19 20:25:06 +00:00
|
|
|
|
(defun let-alist--access-sexp (symbol variable)
|
2014-12-26 02:18:42 +00:00
|
|
|
|
"Return a sexp used to access SYMBOL inside VARIABLE."
|
2014-12-19 20:25:06 +00:00
|
|
|
|
(let* ((clean (let-alist--remove-dot symbol))
|
|
|
|
|
(name (symbol-name clean)))
|
|
|
|
|
(if (string-match "\\`\\." name)
|
|
|
|
|
clean
|
|
|
|
|
(let-alist--list-to-sexp
|
|
|
|
|
(mapcar #'intern (nreverse (split-string name "\\.")))
|
|
|
|
|
variable))))
|
|
|
|
|
|
|
|
|
|
(defun let-alist--list-to-sexp (list var)
|
|
|
|
|
"Turn symbols LIST into recursive calls to `cdr' `assq' on VAR."
|
|
|
|
|
`(cdr (assq ',(car list)
|
|
|
|
|
,(if (cdr list) (let-alist--list-to-sexp (cdr list) var)
|
|
|
|
|
var))))
|
|
|
|
|
|
|
|
|
|
(defun let-alist--remove-dot (symbol)
|
|
|
|
|
"Return SYMBOL, sans an initial dot."
|
|
|
|
|
(let ((name (symbol-name symbol)))
|
|
|
|
|
(if (string-match "\\`\\." name)
|
|
|
|
|
(intern (replace-match "" nil nil name))
|
|
|
|
|
symbol)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;; The actual macro.
|
2014-12-10 15:15:22 +00:00
|
|
|
|
;;;###autoload
|
|
|
|
|
(defmacro let-alist (alist &rest body)
|
|
|
|
|
"Let-bind dotted symbols to their cdrs in ALIST and execute BODY.
|
2014-12-10 21:18:46 +00:00
|
|
|
|
Dotted symbol is any symbol starting with a `.'. Only those present
|
|
|
|
|
in BODY are let-bound and this search is done at compile time.
|
2014-12-10 15:15:22 +00:00
|
|
|
|
|
|
|
|
|
For instance, the following code
|
|
|
|
|
|
|
|
|
|
(let-alist alist
|
|
|
|
|
(if (and .title .body)
|
|
|
|
|
.body
|
2014-12-19 20:25:06 +00:00
|
|
|
|
.site
|
|
|
|
|
.site.contents))
|
2014-12-10 15:15:22 +00:00
|
|
|
|
|
2014-12-19 20:25:06 +00:00
|
|
|
|
essentially expands to
|
2014-12-10 15:15:22 +00:00
|
|
|
|
|
|
|
|
|
(let ((.title (cdr (assq 'title alist)))
|
2014-12-19 20:25:06 +00:00
|
|
|
|
(.body (cdr (assq 'body alist)))
|
|
|
|
|
(.site (cdr (assq 'site alist)))
|
|
|
|
|
(.site.contents (cdr (assq 'contents (cdr (assq 'site alist))))))
|
2014-12-10 15:15:22 +00:00
|
|
|
|
(if (and .title .body)
|
|
|
|
|
.body
|
2014-12-19 20:25:06 +00:00
|
|
|
|
.site
|
|
|
|
|
.site.contents))
|
|
|
|
|
|
|
|
|
|
If you nest `let-alist' invocations, the inner one can't access
|
|
|
|
|
the variables of the outer one. You can, however, access alists
|
|
|
|
|
inside the original alist by using dots inside the symbol, as
|
|
|
|
|
displayed in the example above."
|
2014-12-10 15:15:22 +00:00
|
|
|
|
(declare (indent 1) (debug t))
|
2014-12-22 12:36:30 +00:00
|
|
|
|
(let ((var (make-symbol "alist")))
|
2014-12-18 20:59:23 +00:00
|
|
|
|
`(let ((,var ,alist))
|
2014-12-19 20:25:06 +00:00
|
|
|
|
(let ,(mapcar (lambda (x) `(,(car x) ,(let-alist--access-sexp (car x) var)))
|
2014-12-18 20:59:23 +00:00
|
|
|
|
(delete-dups (let-alist--deep-dot-search body)))
|
|
|
|
|
,@body))))
|
2014-12-10 15:15:22 +00:00
|
|
|
|
|
|
|
|
|
(provide 'let-alist)
|
|
|
|
|
|
|
|
|
|
;;; let-alist.el ends here
|