;;; anaphora.el --- anaphoric macros providing implicit temp variables -*- lexical-binding: t -*-
|
;;
|
;; This code is in the public domain.
|
;;
|
;; Author: Roland Walker <walker@pobox.com>
|
;; Homepage: http://github.com/rolandwalker/anaphora
|
;; URL: http://raw.githubusercontent.com/rolandwalker/anaphora/master/anaphora.el
|
;; Package-Version: 20180618.2200
|
;; Version: 1.0.4
|
;; Last-Updated: 18 Jun 2018
|
;; EmacsWiki: Anaphora
|
;; Keywords: extensions
|
;;
|
;;; Commentary:
|
;;
|
;; Quickstart
|
;;
|
;; (require 'anaphora)
|
;;
|
;; (awhen (big-long-calculation)
|
;; (foo it) ; `it' is provided as
|
;; (bar it)) ; a temporary variable
|
;;
|
;; ;; anonymous function to compute factorial using `self'
|
;; (alambda (x) (if (= x 0) 1 (* x (self (1- x)))))
|
;;
|
;; ;; to fontify `it' and `self'
|
;; (with-eval-after-load "lisp-mode"
|
;; (anaphora-install-font-lock-keywords))
|
;;
|
;; Explanation
|
;;
|
;; Anaphoric expressions implicitly create one or more temporary
|
;; variables which can be referred to during the expression. This
|
;; technique can improve clarity in certain cases. It also enables
|
;; recursion for anonymous functions.
|
;;
|
;; To use anaphora, place the anaphora.el library somewhere
|
;; Emacs can find it, and add the following to your ~/.emacs file:
|
;;
|
;; (require 'anaphora)
|
;;
|
;; The following macros are made available
|
;;
|
;; `aand'
|
;; `ablock'
|
;; `acase'
|
;; `acond'
|
;; `aecase'
|
;; `aetypecase'
|
;; `aif'
|
;; `alambda'
|
;; `alet'
|
;; `aprog1'
|
;; `aprog2'
|
;; `atypecase'
|
;; `awhen'
|
;; `awhile'
|
;; `a+'
|
;; `a-'
|
;; `a*'
|
;; `a/'
|
;;
|
;; See Also
|
;;
|
;; M-x customize-group RET anaphora RET
|
;; http://en.wikipedia.org/wiki/On_Lisp
|
;; http://en.wikipedia.org/wiki/Anaphoric_macro
|
;;
|
;; Notes
|
;;
|
;; Partially based on examples from the book "On Lisp", by Paul
|
;; Graham.
|
;;
|
;; Compatibility and Requirements
|
;;
|
;; GNU Emacs version 26.1 : yes
|
;; GNU Emacs version 25.x : yes
|
;; GNU Emacs version 24.x : yes
|
;; GNU Emacs version 23.x : yes
|
;; GNU Emacs version 22.x : yes
|
;; GNU Emacs version 21.x and lower : unknown
|
;;
|
;; Bugs
|
;;
|
;; TODO
|
;;
|
;; better face for it and self
|
;;
|
;;; License
|
;;
|
;; All code contributed by the author to this library is placed in the
|
;; public domain. It is the author's belief that the portions adapted
|
;; from examples in "On Lisp" are in the public domain.
|
;;
|
;; Regardless of the copyright status of individual functions, all
|
;; code herein is free software, and is provided without any express
|
;; or implied warranties.
|
;;
|
;;; Code:
|
;;
|
|
;;; requirements
|
|
;; for declare, labels, do, block, case, ecase, typecase, etypecase
|
(require 'cl-lib)
|
|
;;; customizable variables
|
|
;;;###autoload
|
(defgroup anaphora nil
|
"Anaphoric macros providing implicit temp variables"
|
:version "1.0.4"
|
:link '(emacs-commentary-link :tag "Commentary" "anaphora")
|
:link '(url-link :tag "GitHub" "http://github.com/rolandwalker/anaphora")
|
:link '(url-link :tag "EmacsWiki" "http://emacswiki.org/emacs/Anaphora")
|
:prefix "anaphora-"
|
:group 'extensions)
|
|
;;;###autoload
|
(defcustom anaphora-use-long-names-only nil
|
"Use only long names such as `anaphoric-if' instead of traditional `aif'."
|
:type 'boolean
|
:group 'anaphora)
|
|
;;; font-lock
|
|
(defun anaphora-install-font-lock-keywords nil
|
"Fontify keywords `it' and `self'."
|
(font-lock-add-keywords 'emacs-lisp-mode `((,(concat "\\<" (regexp-opt '("it" "self") 'paren) "\\>")
|
1 font-lock-variable-name-face)) 'append))
|
|
;;; aliases
|
|
;;;###autoload
|
(progn
|
(defun anaphora--install-traditional-aliases (&optional arg)
|
"Install traditional short aliases for anaphoric macros.
|
|
With negative numeric ARG, remove traditional aliases."
|
(let ((syms '(
|
(if . t)
|
(prog1 . t)
|
(prog2 . t)
|
(when . when)
|
(while . t)
|
(and . t)
|
(cond . cond)
|
(lambda . lambda)
|
(block . block)
|
(case . case)
|
(ecase . ecase)
|
(typecase . typecase)
|
(etypecase . etypecase)
|
(let . let)
|
(+ . t)
|
(- . t)
|
(* . t)
|
(/ . t)
|
)))
|
(cond
|
((and (numberp arg)
|
(< arg 0))
|
(dolist (cell syms)
|
(when (ignore-errors
|
(eq (symbol-function (intern-soft (format "a%s" (car cell))))
|
(intern-soft (format "anaphoric-%s" (car cell)))))
|
(fmakunbound (intern (format "a%s" (car cell)))))))
|
(t
|
(dolist (cell syms)
|
(let* ((builtin (car cell))
|
(traditional (intern (format "a%s" builtin)))
|
(long (intern (format "anaphoric-%s" builtin))))
|
(defalias traditional long)
|
(put traditional 'lisp-indent-function
|
(get builtin 'lisp-indent-function))
|
(put traditional 'edebug-form-spec (cdr cell)))))))))
|
|
;;;###autoload
|
(unless anaphora-use-long-names-only
|
(anaphora--install-traditional-aliases))
|
|
;;; macros
|
|
;;;###autoload
|
(defmacro anaphoric-if (cond then &rest else)
|
"Like `if', but the result of evaluating COND is bound to `it'.
|
|
The variable `it' is available within THEN and ELSE.
|
|
COND, THEN, and ELSE are otherwise as documented for `if'."
|
(declare (debug t)
|
(indent 2))
|
`(let ((it ,cond))
|
(if it ,then ,@else)))
|
|
;;;###autoload
|
(defmacro anaphoric-prog1 (first &rest body)
|
"Like `prog1', but the result of evaluating FIRST is bound to `it'.
|
|
The variable `it' is available within BODY.
|
|
FIRST and BODY are otherwise as documented for `prog1'."
|
(declare (debug t)
|
(indent 1))
|
`(let ((it ,first))
|
(progn ,@body)
|
it))
|
|
;;;###autoload
|
(defmacro anaphoric-prog2 (form1 form2 &rest body)
|
"Like `prog2', but the result of evaluating FORM2 is bound to `it'.
|
|
The variable `it' is available within BODY.
|
|
FORM1, FORM2, and BODY are otherwise as documented for `prog2'."
|
(declare (debug t)
|
(indent 2))
|
`(progn
|
,form1
|
(let ((it ,form2))
|
(progn ,@body)
|
it)))
|
|
;;;###autoload
|
(defmacro anaphoric-when (cond &rest body)
|
"Like `when', but the result of evaluating COND is bound to `it'.
|
|
The variable `it' is available within BODY.
|
|
COND and BODY are otherwise as documented for `when'."
|
(declare (debug when)
|
(indent 1))
|
`(anaphoric-if ,cond
|
(progn ,@body)))
|
|
;;;###autoload
|
(defmacro anaphoric-while (test &rest body)
|
"Like `while', but the result of evaluating TEST is bound to `it'.
|
|
The variable `it' is available within BODY.
|
|
TEST and BODY are otherwise as documented for `while'."
|
(declare (debug t)
|
(indent 1))
|
`(do ((it ,test ,test))
|
((not it))
|
,@body))
|
|
;;;###autoload
|
(defmacro anaphoric-and (&rest conditions)
|
"Like `and', but the result of the previous condition is bound to `it'.
|
|
The variable `it' is available within all CONDITIONS after the
|
initial one.
|
|
CONDITIONS are otherwise as documented for `and'.
|
|
Note that some implementations of this macro bind only the first
|
condition to `it', rather than each successive condition."
|
(declare (debug t))
|
(cond
|
((null conditions)
|
t)
|
((null (cdr conditions))
|
(car conditions))
|
(t
|
`(anaphoric-if ,(car conditions) (anaphoric-and ,@(cdr conditions))))))
|
|
;;;###autoload
|
(defmacro anaphoric-cond (&rest clauses)
|
"Like `cond', but the result of each condition is bound to `it'.
|
|
The variable `it' is available within the remainder of each of CLAUSES.
|
|
CLAUSES are otherwise as documented for `cond'."
|
(declare (debug cond))
|
(if (null clauses)
|
nil
|
(let ((cl1 (car clauses))
|
(sym (gensym)))
|
`(let ((,sym ,(car cl1)))
|
(if ,sym
|
(if (null ',(cdr cl1))
|
,sym
|
(let ((it ,sym)) ,@(cdr cl1)))
|
(anaphoric-cond ,@(cdr clauses)))))))
|
|
;;;###autoload
|
(defmacro anaphoric-lambda (args &rest body)
|
"Like `lambda', but the function may refer to itself as `self'.
|
|
ARGS and BODY are otherwise as documented for `lambda'."
|
(declare (debug lambda)
|
(indent defun))
|
`(cl-labels ((self ,args ,@body))
|
#'self))
|
|
;;;###autoload
|
(defmacro anaphoric-block (name &rest body)
|
"Like `block', but the result of the previous expression is bound to `it'.
|
|
The variable `it' is available within all expressions of BODY
|
except the initial one.
|
|
NAME and BODY are otherwise as documented for `block'."
|
(declare (debug block)
|
(indent 1))
|
`(cl-block ,name
|
,(funcall (anaphoric-lambda (body)
|
(cl-case (length body)
|
(0 nil)
|
(1 (car body))
|
(t `(let ((it ,(car body)))
|
,(self (cdr body))))))
|
body)))
|
|
;;;###autoload
|
(defmacro anaphoric-case (expr &rest clauses)
|
"Like `case', but the result of evaluating EXPR is bound to `it'.
|
|
The variable `it' is available within CLAUSES.
|
|
EXPR and CLAUSES are otherwise as documented for `case'."
|
(declare (debug case)
|
(indent 1))
|
`(let ((it ,expr))
|
(cl-case it ,@clauses)))
|
|
;;;###autoload
|
(defmacro anaphoric-ecase (expr &rest clauses)
|
"Like `ecase', but the result of evaluating EXPR is bound to `it'.
|
|
The variable `it' is available within CLAUSES.
|
|
EXPR and CLAUSES are otherwise as documented for `ecase'."
|
(declare (debug ecase)
|
(indent 1))
|
`(let ((it ,expr))
|
(cl-ecase it ,@clauses)))
|
|
;;;###autoload
|
(defmacro anaphoric-typecase (expr &rest clauses)
|
"Like `typecase', but the result of evaluating EXPR is bound to `it'.
|
|
The variable `it' is available within CLAUSES.
|
|
EXPR and CLAUSES are otherwise as documented for `typecase'."
|
(declare (debug typecase)
|
(indent 1))
|
`(let ((it ,expr))
|
(cl-typecase it ,@clauses)))
|
|
;;;###autoload
|
(defmacro anaphoric-etypecase (expr &rest clauses)
|
"Like `etypecase', but result of evaluating EXPR is bound to `it'.
|
|
The variable `it' is available within CLAUSES.
|
|
EXPR and CLAUSES are otherwise as documented for `etypecase'."
|
(declare (debug etypecase)
|
(indent 1))
|
`(let ((it ,expr))
|
(cl-etypecase it ,@clauses)))
|
|
;;;###autoload
|
(defmacro anaphoric-let (form &rest body)
|
"Like `let', but the result of evaluating FORM is bound to `it'.
|
|
FORM and BODY are otherwise as documented for `let'."
|
(declare (debug let)
|
(indent 1))
|
`(let ((it ,form))
|
(progn ,@body)))
|
|
;;;###autoload
|
(defmacro anaphoric-+ (&rest numbers-or-markers)
|
"Like `+', but the result of evaluating the previous expression is bound to `it'.
|
|
The variable `it' is available within all expressions after the
|
initial one.
|
|
NUMBERS-OR-MARKERS are otherwise as documented for `+'."
|
(declare (debug t))
|
(cond
|
((null numbers-or-markers)
|
0)
|
(t
|
`(let ((it ,(car numbers-or-markers)))
|
(+ it (anaphoric-+ ,@(cdr numbers-or-markers)))))))
|
|
;;;###autoload
|
(defmacro anaphoric-- (&optional number-or-marker &rest numbers-or-markers)
|
"Like `-', but the result of evaluating the previous expression is bound to `it'.
|
|
The variable `it' is available within all expressions after the
|
initial one.
|
|
NUMBER-OR-MARKER and NUMBERS-OR-MARKERS are otherwise as
|
documented for `-'."
|
(declare (debug t))
|
(cond
|
((null number-or-marker)
|
0)
|
((null numbers-or-markers)
|
`(- ,number-or-marker))
|
(t
|
`(let ((it ,(car numbers-or-markers)))
|
(- ,number-or-marker (+ it (anaphoric-+ ,@(cdr numbers-or-markers))))))))
|
|
;;;###autoload
|
(defmacro anaphoric-* (&rest numbers-or-markers)
|
"Like `*', but the result of evaluating the previous expression is bound to `it'.
|
|
The variable `it' is available within all expressions after the
|
initial one.
|
|
NUMBERS-OR-MARKERS are otherwise as documented for `*'."
|
(declare (debug t))
|
(cond
|
((null numbers-or-markers)
|
1)
|
(t
|
`(let ((it ,(car numbers-or-markers)))
|
(* it (anaphoric-* ,@(cdr numbers-or-markers)))))))
|
|
;;;###autoload
|
(defmacro anaphoric-/ (dividend divisor &rest divisors)
|
"Like `/', but the result of evaluating the previous divisor is bound to `it'.
|
|
The variable `it' is available within all expressions after the
|
first divisor.
|
|
DIVIDEND, DIVISOR, and DIVISORS are otherwise as documented for `/'."
|
(declare (debug t))
|
(cond
|
((null divisors)
|
`(/ ,dividend ,divisor))
|
(t
|
`(let ((it ,divisor))
|
(/ ,dividend (* it (anaphoric-* ,@divisors)))))))
|
|
(provide 'anaphora)
|
|
;;
|
;; Emacs
|
;;
|
;; Local Variables:
|
;; indent-tabs-mode: nil
|
;; mangle-whitespace: t
|
;; require-final-newline: t
|
;; coding: utf-8
|
;; byte-compile-warnings: (not cl-functions redefine)
|
;; End:
|
;;
|
;; LocalWords: Anaphora EXPR awhen COND ARGS alambda ecase typecase
|
;; LocalWords: etypecase aprog aand acond ablock acase aecase alet
|
;; LocalWords: atypecase aetypecase
|
;;
|
|
;;; anaphora.el ends here
|