[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/macrostep 704f164 091/110: Merge branch 'generic'
From: |
ELPA Syncer |
Subject: |
[nongnu] elpa/macrostep 704f164 091/110: Merge branch 'generic' |
Date: |
Sat, 7 Aug 2021 09:18:09 -0400 (EDT) |
branch: elpa/macrostep
commit 704f1645dd3c7611562326cefff668d204bc1858
Merge: 8f74a4b 2c5bda2
Author: joddie <jonxfield@gmail.com>
Commit: joddie <jonxfield@gmail.com>
Merge branch 'generic'
---
Makefile | 19 +-
README.org | 235 +++++++-----
macrostep-test.el | 136 +++++--
macrostep.el | 1049 +++++++++++++++++++++++++++++++++--------------------
4 files changed, 931 insertions(+), 508 deletions(-)
diff --git a/Makefile b/Makefile
index 0540b44..1af7050 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,17 @@
-test:
- emacs --batch --load macrostep-test.el
+EMACS ?= emacs
-.PHONY: test
+all: macrostep.elc
+
+clean:
+ rm -f macrostep.elc macrostep-test.elc
+
+test: macrostep.elc
+ $(EMACS) --batch -L . --load macrostep-test.el
+
+sandbox: macrostep.elc
+ $(EMACS) -Q -L . --load macrostep.elc
+
+%.elc: %.el
+ $(EMACS) --batch --funcall batch-byte-compile "$<"
+
+.PHONY: test all clean
diff --git a/README.org b/README.org
index 2cfbf08..cdb7fe0 100644
--- a/README.org
+++ b/README.org
@@ -1,33 +1,37 @@
-* macrostep: interactive macro expansion for Emacs Lisp
-
-** Overview
- This is a minor mode for interactively stepping through the
- expansion of macros in Emacs Lisp source code. It lets you see
- exactly what happens at each step of the expansion process by
- pretty-printing the expanded forms inline in the source buffer,
- which is read-only while macro expansions are visible. You can
- expand and collapse macro forms one step at a time, and evaluate or
- instrument them for debugging with Edebug as normal (but see "Bugs
- and known limitations", below). Single-stepping through the
- expansion is useful for debugging macros that expand into another
- macro form, especially one like =lexical-let= that does significant
- rewriting. These can be difficult to debug with Emacs' built-in
- =macroexpand=, because =macroexpand= continues expansion until the
- top-level form is no longer a macro call.
-
- The mode also adds some simple additional fontification to
- macro-expanded code. The heads of macro sub-forms are fontified
- using =macrostep-macro-face=. Uninterned symbols (gensyms) are
+* macrostep: interactive macro-expander
+
+ =macrostep= is an Emacs minor mode for interactively stepping
+ through the expansion of macros in Emacs Lisp source code. It lets
+ you see exactly what happens at each step of the expansion process
+ by pretty-printing the expanded forms inline in the source buffer,
+ which is temporarily read-only while macro expansions are visible.
+ You can expand and collapse macro forms one step at a time, and
+ evaluate or instrument the expansions for debugging with Edebug as
+ normal (but see "Bugs and known limitations", below).
+ Single-stepping through the expansion is particularly useful for
+ debugging macros that expand into another macro form. These can be
+ difficult to debug with Emacs' built-in =macroexpand=, which
+ continues expansion until the top-level form is no longer a macro
+ call.
+
+ Both globally-visible macros as defined by =defmacro= and local
+ macros bound by =(cl-)macrolet= or another macro-defining form can
+ be expanded. Within macro expansions, calls to macros and compiler
+ macros are fontified specially: macro forms using
+ =macrostep-macro-face=, and functions with compiler macros using
+ =macrostep-compiler-macro-face=. Uninterned symbols (gensyms) are
fontified based on which step in the expansion created them, to
- distinguish them from normal symbols and from other gensyms with
- the same print name. Use =customize-group= with the =macrostep=
- group to customize these faces.
+ distinguish them both from normal symbols and from other gensyms
+ with the same print name.
- Both macros defined by =defmacro= and local macros created by
- =macrolet= and =cl-macrolet= can be expanded.
+ As of version 0.9, it is also possible to extend =macrostep= to
+ work with other languages with macro systems in addition to Emacs
+ Lisp. An extension for Common Lisp (via SLIME) is in the works;
+ contributions for other languages are welcome. See "Extending
+ macrostep" below for details.
** Key-bindings and usage
- The standard macrostep-mode keybindings are the following:
+ The standard keybindings in =macrostep-mode= are the following:
- e, =, RET :: expand the macro form following point one step
- c, u, DEL :: collapse the form following point
@@ -36,7 +40,7 @@
- p, M-TAB :: jump to the previous macro form in the expansion
It's not very useful to enable and disable macrostep-mode
- directly. Instead, bind =macrostep-expand= to a key in
+ directly. Instead, bind =macrostep-expand= to a key in
=emacs-lisp-mode-map=, for example C-c e:
#+BEGIN_SRC emacs-lisp
@@ -46,62 +50,113 @@
You can then enter macrostep-mode and expand a macro form
completely by typing =C-c e e e ...= as many times as necessary.
- Exit macrostep-mode either with =q=, =C-c C-c=, or by successively
- typing =c= to collapse all expanded forms back to their original
- text.
+ Exit macrostep-mode by typing =q= or =C-c C-c=, or by successively
+ typing =c= to collapse all surrounding expansions.
+
+** Customization options
+ Type =M-x customize-group RET macrostep RET= to customize options
+ and faces.
+
+ To display macro expansions in a separate window, instead of inline
+ in the source buffer, customize
+ =macrostep-expand-in-separate-buffer= to =t=. The default is
+ =nil=. Whichever default behavior is selected, the alternative
+ behavior can be obtained temporarily by giving a prefix argument to
+ =macrostep-expand=.
+
+ To have =macrostep= ignore compiler macros, customize
+ =macrostep-expand-compiler-macros= to =nil=. The default is =t=.
+
+ Customize the faces =macrostep-macro-face=,
+ =macrostep-compiler-macro-face=, and =macrostep-gensym-1= through
+ =macrostep-gensym-5= to alter the appearance of macro expansions.
+
+** Locally-bound macros
+ As of version 0.9, =macrostep= can expand calls to a locally-bound
+ macro, whether defined by a surrounding =(cl-)macrolet= form, or by
+ another macro-defining macro. In other words, it is possible to
+ expand the inner =local-macro= forms in both the following
+ examples, whether =local-macro= is defined by an enclosing
+ =cl-macrolet= --
+
+ #+BEGIN_SRC emacs-lisp
+ (cl-macrolet ((local-macro (&rest args)
+ `(expansion of ,args)))
+ (local-macro (do-something)))
+ #+END_SRC
+
+ -- or by a macro which expands into =cl-macrolet=, provided that
+ its definition of macro is evaluated prior to calling
+ =macrostep-expand=:
+
+ #+BEGIN_SRC emacs-lisp
+ (defmacro with-local-macro (&rest body)
+ `(cl-macrolet ((local-macro (&rest args)
+ `(expansion of ,args)))
+ ,@body))
+
+ (with-local-macro
+ (local-macro (do something (else)))
+ #+END_SRC
+
+ See the =with-js= macro in Emacs's =js.el= for a real example of
+ the latter kind of macro.
+
+ Expansion of locally-bound macros is implemented by instrumenting
+ Emacs Lisp's macro-expander to capture the environment at point. A
+ similar trick is used to detect macro- and compiler-macro calls
+ within expanded text so that they can be fontified accurately.
** Expanding sub-forms
- By moving point around in the macro expansion (perhaps using the
- =n= and =p= keys), you can macro-expand sub-forms before fully
- expanding the enclosing form. This can be useful in some cases,
- but you should keep in mind that it does not correspond to the way
- Emacs actually expands macro calls when evaluating or compiling
- your code. Macro expansion in Emacs Lisp always proceeds by fully
- expanding the outer form to a non-macro form before doing anything
- with the sub-forms.
-
- For example, with =cl= loaded, try expanding the following form:
-
-#+BEGIN_SRC emacs-lisp
- (dolist (l list-of-lists)
- (incf (car l)))
-#+END_SRC
-
- to produce the following:
-
-#+BEGIN_SRC emacs-lisp
- (block nil
- (let
- ((--cl-dolist-temp-- list-of-lists)
- l)
- (while --cl-dolist-temp--
- (setq l
- (car --cl-dolist-temp--))
- (incf
- (car l))
- (setq --cl-dolist-temp--
- (cdr --cl-dolist-temp--)))
- nil))
-#+END_SRC
-
- where the forms beginning =block= and =incf= are both macro calls.
-
- At this point, you can either continue expanding the =block= form,
- which corresponds to the real order of macro expansion in
- evaluation, or type =n= to move point to the unexpanded =incf= and
- expand it to a =callf= form and finally to a =let*= form. If you
- then move point back to the =block= and expand it, an unexpanded
- =incf= form appears again in the result. This might look visually
- confusing, but it does at least correspond to the way real macro
- expansion works.
-
- Why allow expanding sub-forms out of order like this at all? The
- main reason is for debugging macros which expand into another
- macro, like =lexical-let=, that programmatically expands its
- contents in order to rewrite them. In this case, expanding the
- sub-forms first allows you to see what =lexical-let= would compute
- via =cl-macroexpand-all=.
-
+ By moving point around in the macro expansion using
+ =macrostep-next-macro= and =macrostep-prev-macro= (bound to the =n=
+ and =p= keys), it is possible to expand other macro calls within
+ the expansion before expanding the outermost form. This can
+ sometimes be useful, although it does not correspond to the real
+ order of macro expansion in Emacs Lisp, which proceeds by fully
+ expanding the outer form to a non-macro form before expanding
+ sub-forms.
+
+ The main reason to expand sub-forms out of order is to help with
+ debugging macros which programmatically expand their arguments in
+ order to rewrite them. Expanding the arguments of such a macro
+ lets you visualise what the macro definition would compute via
+ =macroexpand-all=.
+
+** Extending macrostep for other languages
+ Since version 0.9, it is possible to extend macrostep to work with
+ other languages besides Emacs Lisp. In typical Emacs fashion, this
+ is implemented by setting buffer-local variables to different
+ function values. Six buffer-local variables define the
+ language-specific part of the implementation:
+
+ - =macrostep-sexp-bounds-function=
+ - =macrostep-sexp-at-point-function=
+ - =macrostep-environment-at-point-function=
+ - =macrostep-expand-1-function=
+ - =macrostep-print-function=
+ - =macrostep-macro-form-p-function=
+
+ Typically, an implementation for another language would set these
+ variables in a major-mode hook. See the docstrings of each
+ variable for details on how each one is called and what it should
+ return. At a minimum, another language implementation needs to
+ provide =macrostep-sexp-at-point-function=,
+ =macrostep-expand-1-function=, and =macrostep-print-function=.
+ Lisp-like languages may be able to reuse the default
+ =macrostep-sexp-bounds-function= if they provide another
+ implementation of =macrostep-macro-form-p-function=. Languages
+ which do not implement locally-defined macros can set
+ =macrostep-environment-at-point-function= to =ignore=.
+
+ Note that the core =macrostep= machinery only interprets the return
+ value of =macrostep-sexp-bounds-function=, so implementations for
+ other languages can use any internal representations of code and
+ environments which is convenient. Although the terminology is
+ Lisp-specific, there is no reason that implementations could not be
+ provided for non-Lisp languages with macro systems, provided there
+ is some way of identifying macro calls and calling the compiler /
+ preprocessor to obtain their expansions.
** Bugs and known limitations
You can evaluate and edebug macro-expanded forms and step through
@@ -113,13 +168,6 @@
possible that using =print-circle= and =print-gensym= could get
around this.
- The macro stepper doesn't bother trying to determine whether or not
- a sub-form is in an evaluated position before highlighting it as a
- macro. It does exclude =lambda= from treatment as a macro, since
- that leads to an endless series of expansions: =(function (function
- ... ))=. It would be better to recognise =function=, =quote= and
- other special forms using their =edebug-form-spec= property.
-
Please send other bug reports and feature requests to the author.
** Acknowledgements
@@ -130,12 +178,21 @@
the expanded region and properly handle backquotes.
- Nic Ferrier for suggesting support for local definitions within
macrolet forms
+ - Luís Oliveira for suggesting and implementing SLIME support
+
+ =macrostep= was originally inspired by J. V. Toups's 'Deep Emacs
+ Lisp' articles
([[http://dorophone.blogspot.co.uk/2011/04/deep-emacs-part-1.html][part 1]],
[[http://dorophone.blogspot.co.uk/2011/04/deep-emacs-lisp-part-2.html][part
2]],
[[http://dorophone.blogspot.co.uk/2011/05/monadic-parser-combinators-in-elisp.html][screencast]]).
** Changelog
+ - v0.9, 2015-10-01:
+ - separate into Elisp-specific and generic components
+ - highlight and expand compiler macros
+ - improve local macro expansion and macro form identification by
+ instrumenting =macroexpand(-all)=
- v0.8, 2014-05-29: fix a bug with printing the first element of
lists
- v0.7, 2014-05-11: expand locally-defined macros within
- (cl-)macrolet forms
+ =(cl-)macrolet= forms
- v0.6, 2013-05-04: better handling of quote and backquote
- v0.5, 2013-04-16: highlight region, maintain cleaner buffer state
- v0.4, 2013-04-07: only enter macrostep-mode on successful
diff --git a/macrostep-test.el b/macrostep-test.el
index 0d0233c..1863b61 100644
--- a/macrostep-test.el
+++ b/macrostep-test.el
@@ -17,6 +17,8 @@
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see `http://www.gnu.org/licenses/'.
+
+;;;; Conveniences for defining tests
(defmacro macrostep-with-text (object &rest forms)
(declare (indent 1)
@@ -30,6 +32,12 @@
(print ,object))
,@forms)))
+(defun macrostep-goto (text &optional from-point-min)
+ (when from-point-min (goto-char (point-min)))
+ (let ((search-whitespace-regexp "[[:space:]\n]+"))
+ (search-forward-lax-whitespace text)
+ (goto-char (match-beginning 0))))
+
(defmacro macrostep-should-expand (form expansion)
`(save-excursion
(goto-char (point-min))
@@ -45,6 +53,8 @@
,expansion)))
(macrostep-collapse-all)))))
+
+;;;; Tests
(ert-deftest macrostep-expand-defmacro ()
(defmacro macrostep-dummy-macro (&rest args)
`(expansion of ,@args))
@@ -58,6 +68,34 @@
'(macrostep-dummy-macro (first (argument)) second (third argument))
'(expansion of (first (argument)) second (third argument)))))
+(ert-deftest macrostep-expand-and-collapse ()
+ (dolist (expander
+ (list
+ (lambda (sexp _env) sexp)
+ (lambda (sexp _env) `(progn ,sexp ,sexp))
+ (lambda (sexp _env) `(long
+ (complicated
+ (expansion of ,sexp () ())
+ (with trailing forms))))))
+ (let ((macrostep-expand-1-function expander)
+ (macrostep-macro-form-p-function
+ (lambda (&rest _) t)))
+ (macrostep-with-text
+ '(progn
+ (first form)
+ (second form)
+ (third (nested form)))
+ (macrostep-goto "(first ")
+ (let ((original-text (buffer-string)))
+ (dotimes (i 10)
+ (dotimes (j i)
+ (macrostep-expand))
+ (dotimes (j i)
+ (macrostep-collapse))
+ (should (null macrostep-overlays))
+ (should (string= (buffer-string)
+ original-text))))))))
+
(ert-deftest macrostep-expand-macrolet ()
(macrostep-with-text
'(macrolet
@@ -162,6 +200,30 @@
(apply (cdr (assq 'get env)) '(:begin))
'(plist-get list :begin))))))
+(ert-deftest macrostep-environment-at-point-2 ()
+ (defmacro macrostep-with-dummy (&rest body)
+ `(cl-macrolet ((dummy (&rest forms) `(expansion of ,@forms)))
+ ,@body))
+ (macrostep-with-text
+ '(macrostep-with-dummy
+ (body)
+ (forms)
+ (cl-loop for i from 6 to 10
+ do (something)))
+ (macrostep-goto "(macrostep-with-dummy" t)
+ (should (null (macrostep-environment-at-point)))
+
+ (dolist (place (list "(body)" "dy)" "(forms)" "rms)"
+ "(something)"))
+ (macrostep-goto place)
+ (let* ((env (macrostep-environment-at-point))
+ (dummy-defn (cdr (assq 'dummy env))))
+ (should dummy-defn)
+ (should (functionp dummy-defn))
+ (should (equal
+ (funcall dummy-defn 'lorem 'ipsum)
+ `(expansion of lorem ipsum)))))))
+
(ert-deftest macrostep-print-sexp ()
(cl-macrolet ((should-print (form string)
`(should (equal
@@ -169,6 +231,7 @@
(macrostep-print-sexp ,form)
(buffer-string))
,string))))
+ (should-print nil "nil")
(should-print 'symbol "symbol")
(should-print '(single-element-list) "(single-element-list)")
(should-print '(two-element list) "(two-element list)")
@@ -183,38 +246,51 @@
(should-print '`(backquoted (form) ,with ,@splices)
"`(backquoted (form) ,with ,@splices)")))
-(ert-deftest macrostep-print-sexp-macrolet-environment ()
+(ert-deftest macrostep-pp-macrolet-environment ()
(with-temp-buffer
(emacs-lisp-mode)
- (save-excursion
- (macrostep-print-sexp
- '(macrolet ((some-macro (&rest forms) (cons 'progn forms)))
- (some-macro with (arguments))
- (intervening body forms)
- (some-macro with (more) (arguments))))
- (cl-flet ((search (text)
- (goto-char (point-min))
- (search-forward text)
- (goto-char (match-beginning 0))
- ;; Leave point on the head of the form
- (forward-char)))
- ;; The occurrence of "(some-macro" in the binding list should
- ;; not be fontified as a macro form
- (search "(some-macro (&rest")
- (should-not
- (eq (get-char-property (point) 'font-lock-face)
- 'macrostep-macro-face))
+ (macrostep-pp
+ '(macrolet ((some-macro (&rest forms) (cons 'progn forms)))
+ (some-macro with (arguments))
+ (intervening body forms)
+ (some-macro with (more) (arguments)))
+ nil)
+ (cl-flet ((search (text)
+ (macrostep-goto text t)
+ ;; Leave point on the head of the form
+ (forward-char)))
+ ;; The occurrence of "(some-macro" in the binding list should
+ ;; not be fontified as a macro form
+ (search "(some-macro (&rest")
+ (should-not
+ (eq (get-char-property (point) 'font-lock-face)
+ 'macrostep-macro-face))
+
+ ;; However, the two occurrences in the body of the macrolet should be.
+ (search "(some-macro with (arguments)")
+ (should
+ (eq (get-char-property (point) 'font-lock-face)
+ 'macrostep-macro-face))
- ;; However, the two occurrences in the body of the macrolet should be.
- (search "(some-macro with (arguments)")
- (should
- (eq (get-char-property (point) 'font-lock-face)
- 'macrostep-macro-face))
+ (search "(some-macro with (more)")
+ (should
+ (eq (get-char-property (point) 'font-lock-face)
+ 'macrostep-macro-face)))))
- (search "(some-macro with (more)")
- (should
- (eq (get-char-property (point) 'font-lock-face)
- 'macrostep-macro-face))))))
+(ert-deftest macrostep-expand-macro-defined-macros ()
+ (defmacro with-local-dummy-macro (&rest body)
+ `(cl-macrolet ((dummy (&rest args) `(expansion (of) ,@args)))
+ ,@body))
+ (macrostep-with-text
+ '(with-local-dummy-macro
+ (dummy form (one))
+ (dummy (form two)))
+ (macrostep-should-expand
+ '(dummy form (one))
+ '(expansion (of) form (one)))
+ (macrostep-should-expand
+ '(dummy (form two))
+ '(expansion (of) (form two)))))
(ert-deftest macrostep-expand-in-separate-buffer ()
(defmacro macrostep-dummy-macro (&rest args)
@@ -244,8 +320,8 @@
`(expansion of ,@args)))
(dummy-macro-1 (some (arguments))))
(let ((original-buffer (current-buffer)))
- (search-forward "(dummy-macro-1 (some")
- (goto-char (match-beginning 0))
+ (macrostep-goto "(dummy-macro-1 (some")
+
(macrostep-expand)
(should (not (equal (current-buffer) original-buffer)))
(should macrostep-expansion-buffer)
diff --git a/macrostep.el b/macrostep.el
index b4954a2..24286d0 100644
--- a/macrostep.el
+++ b/macrostep.el
@@ -1,12 +1,12 @@
-;;; macrostep.el --- interactive macro stepper for Emacs Lisp
+;;; macrostep.el --- interactive macro expander
-;; Copyright (C) 2012-2014 Jonathan Oddie <j.j.oddie@gmail.com>
+;; Copyright (C) 2012-2015 Jonathan Oddie <j.j.oddie@gmail.com>
;; Author: joddie <j.j.oddie@gmail.com>
;; Maintainer: joddie <j.j.oddie@gmail.com>
;; Created: 16 January 2012
-;; Updated: 11 May 2014
-;; Version: 0.8
+;; Updated: 01 October 2015
+;; Version: 0.9
;; Keywords: lisp, languages, macro, debugging
;; Url: https://github.com/joddie/macrostep
@@ -27,39 +27,39 @@
;;; Commentary:
-;; 1 Overview
-;; ==========
-
-;; This is a minor mode for interactively stepping through the expansion
-;; of macros in Emacs Lisp source code. It lets you see exactly what
-;; happens at each step of the expansion process by pretty-printing the
-;; expanded forms inline in the source buffer, which is read-only while
-;; macro expansions are visible. You can expand and collapse macro forms
-;; one step at a time, and evaluate or instrument them for debugging with
-;; Edebug as normal (but see "Bugs and known limitations",
-;; below). Single-stepping through the expansion is useful for debugging
-;; macros that expand into another macro form, especially one like
-;; `lexical-let' that does significant rewriting. These can be difficult
-;; to debug with Emacs' built-in `macroexpand', because `macroexpand'
-;; continues expansion until the top-level form is no longer a macro
-;; call.
-
-;; The mode also adds some simple additional fontification to
-;; macro-expanded code. The heads of macro sub-forms are fontified using
-;; `macrostep-macro-face'. Uninterned symbols (gensyms) are fontified
-;; based on which step in the expansion created them, to distinguish them
-;; from normal symbols and from other gensyms with the same print
-;; name. Use `customize-group' with the `macrostep' group to customize
-;; these faces.
-
-;; Both macros defined by `defmacro' and local macros created by
-;; `macrolet' and `cl-macrolet' can be expanded.
-
-
-;; 2 Key-bindings and usage
+;; `macrostep' is an Emacs minor mode for interactively stepping through
+;; the expansion of macros in Emacs Lisp source code. It lets you see
+;; exactly what happens at each step of the expansion process by
+;; pretty-printing the expanded forms inline in the source buffer, which is
+;; temporarily read-only while macro expansions are visible. You can
+;; expand and collapse macro forms one step at a time, and evaluate or
+;; instrument the expansions for debugging with Edebug as normal (but see
+;; "Bugs and known limitations", below). Single-stepping through the
+;; expansion is particularly useful for debugging macros that expand into
+;; another macro form. These can be difficult to debug with Emacs'
+;; built-in `macroexpand', which continues expansion until the top-level
+;; form is no longer a macro call.
+
+;; Both globally-visible macros as defined by `defmacro' and local macros
+;; bound by `(cl-)macrolet' or another macro-defining form can be expanded.
+;; Within macro expansions, calls to macros and compiler macros are
+;; fontified specially: macro forms using `macrostep-macro-face', and
+;; functions with compiler macros using `macrostep-compiler-macro-face'.
+;; Uninterned symbols (gensyms) are fontified based on which step in the
+;; expansion created them, to distinguish them both from normal symbols and
+;; from other gensyms with the same print name.
+
+;; As of version 0.9, it is also possible to extend `macrostep' to work
+;; with other languages with macro systems in addition to Emacs Lisp. An
+;; extension for Common Lisp (via SLIME) is in the works; contributions for
+;; other languages are welcome. See "Extending macrostep" below for
+;; details.
+
+
+;; 1 Key-bindings and usage
;; ========================
-;; The standard macrostep-mode keybindings are the following:
+;; The standard keybindings in `macrostep-mode' are the following:
;; e, =, RET : expand the macro form following point one step
;; c, u, DEL : collapse the form following point
@@ -67,9 +67,9 @@
;; n, TAB : jump to the next macro form in the expansion
;; p, M-TAB : jump to the previous macro form in the expansion
-;; It's not very useful to enable and disable macrostep-mode
-;; directly. Instead, bind `macrostep-expand' to a key in
-;; `emacs-lisp-mode-map', for example C-c e:
+;; It's not very useful to enable and disable macrostep-mode directly.
+;; Instead, bind `macrostep-expand' to a key in `emacs-lisp-mode-map',
+;; for example C-c e:
;; ,----
;; | (define-key emacs-lisp-mode-map (kbd "C-c e") 'macrostep-expand)
@@ -78,63 +78,123 @@
;; You can then enter macrostep-mode and expand a macro form completely
;; by typing `C-c e e e ...' as many times as necessary.
-;; Exit macrostep-mode either with `q', `C-c C-c', or by successively
-;; typing `c' to collapse all expanded forms back to their original text.
+;; Exit macrostep-mode by typing `q' or `C-c C-c', or by successively
+;; typing `c' to collapse all surrounding expansions.
-;; 3 Expanding sub-forms
-;; =====================
+;; 2 Customization options
+;; =======================
+
+;; Type `M-x customize-group RET macrostep RET' to customize options and
+;; faces.
+
+;; To display macro expansions in a separate window, instead of inline in
+;; the source buffer, customize `macrostep-expand-in-separate-buffer' to
+;; `t'. The default is `nil'. Whichever default behavior is selected,
+;; the alternative behavior can be obtained temporarily by giving a
+;; prefix argument to `macrostep-expand'.
+
+;; To have `macrostep' ignore compiler macros, customize
+;; `macrostep-expand-compiler-macros' to `nil'. The default is `t'.
-;; By moving point around in the macro expansion (perhaps using the `n'
-;; and `p' keys), you can macro-expand sub-forms before fully expanding
-;; the enclosing form. This can be useful in some cases, but you should
-;; keep in mind that it does not correspond to the way Emacs actually
-;; expands macro calls when evaluating or compiling your code. Macro
-;; expansion in Emacs Lisp always proceeds by fully expanding the outer
-;; form to a non-macro form before doing anything with the sub-forms.
+;; Customize the faces `macrostep-macro-face',
+;; `macrostep-compiler-macro-face', and `macrostep-gensym-1' through
+;; `macrostep-gensym-5' to alter the appearance of macro expansions.
-;; For example, with `cl' loaded, try expanding the following form:
+
+;; 3 Locally-bound macros
+;; ======================
+
+;; As of version 0.9, `macrostep' can expand calls to a locally-bound
+;; macro, whether defined by a surrounding `(cl-)macrolet' form, or by
+;; another macro-defining macro. In other words, it is possible to
+;; expand the inner `local-macro' forms in both the following examples,
+;; whether `local-macro' is defined by an enclosing `cl-macrolet' --
;; ,----
-;; | (dolist (l list-of-lists)
-;; | (incf (car l)))
+;; | (cl-macrolet ((local-macro (&rest args)
+;; | `(expansion of ,args)))
+;; | (local-macro (do-something)))
;; `----
-;; to produce the following:
+;; -- or by a macro which expands into `cl-macrolet', provided that its
+;; definition of macro is evaluated prior to calling `macrostep-expand':
;; ,----
-;; | (block nil
-;; | (let
-;; | ((--cl-dolist-temp-- list-of-lists)
-;; | l)
-;; | (while --cl-dolist-temp--
-;; | (setq l
-;; | (car --cl-dolist-temp--))
-;; | (incf
-;; | (car l))
-;; | (setq --cl-dolist-temp--
-;; | (cdr --cl-dolist-temp--)))
-;; | nil))
+;; | (defmacro with-local-macro (&rest body)
+;; | `(cl-macrolet ((local-macro (&rest args)
+;; | `(expansion of ,args)))
+;; | ,@body))
+;; |
+;; | (with-local-macro
+;; | (local-macro (do something (else)))
;; `----
-;; where the forms beginning `block' and `incf' are both macro calls.
+;; See the `with-js' macro in Emacs's `js.el' for a real example of the
+;; latter kind of macro.
-;; At this point, you can either continue expanding the `block' form,
-;; which corresponds to the real order of macro expansion in evaluation,
-;; or type `n' to move point to the unexpanded `incf' and expand it to a
-;; `callf' form and finally to a `let*' form. If you then move point
-;; back to the `block' and expand it, an unexpanded `incf' form appears
-;; again in the result. This might look visually confusing, but it does
-;; at least correspond to the way real macro expansion works.
+;; Expansion of locally-bound macros is implemented by instrumenting
+;; Emacs Lisp's macro-expander to capture the environment at point. A
+;; similar trick is used to detect macro- and compiler-macro calls within
+;; expanded text so that they can be fontified accurately.
-;; Why allow expanding sub-forms out of order like this at all? The main
-;; reason is for debugging macros which expand into another macro, like
-;; `lexical-let', that programmatically expands its contents in order to
-;; rewrite them. In this case, expanding the sub-forms first allows you
-;; to see what `lexical-let' would compute via `cl-macroexpand-all'.
+;; 4 Expanding sub-forms
+;; =====================
-;; 4 Bugs and known limitations
+;; By moving point around in the macro expansion using
+;; `macrostep-next-macro' and `macrostep-prev-macro' (bound to the `n'
+;; and `p' keys), it is possible to expand other macro calls within the
+;; expansion before expanding the outermost form. This can sometimes be
+;; useful, although it does not correspond to the real order of macro
+;; expansion in Emacs Lisp, which proceeds by fully expanding the outer
+;; form to a non-macro form before expanding sub-forms.
+
+;; The main reason to expand sub-forms out of order is to help with
+;; debugging macros which programmatically expand their arguments in
+;; order to rewrite them. Expanding the arguments of such a macro lets
+;; you visualise what the macro definition would compute via
+;; `macroexpand-all'.
+
+
+;; 5 Extending macrostep for other languages
+;; =========================================
+
+;; Since version 0.9, it is possible to extend macrostep to work with
+;; other languages besides Emacs Lisp. In typical Emacs fashion, this is
+;; implemented by setting buffer-local variables to different function
+;; values. Six buffer-local variables define the language-specific part
+;; of the implementation:
+
+;; - `macrostep-sexp-bounds-function'
+;; - `macrostep-sexp-at-point-function'
+;; - `macrostep-environment-at-point-function'
+;; - `macrostep-expand-1-function'
+;; - `macrostep-print-function'
+;; - `macrostep-macro-form-p-function'
+
+;; Typically, an implementation for another language would set these
+;; variables in a major-mode hook. See the docstrings of each variable
+;; for details on how each one is called and what it should return. At a
+;; minimum, another language implementation needs to provide
+;; `macrostep-sexp-at-point-function', `macrostep-expand-1-function', and
+;; `macrostep-print-function'. Lisp-like languages may be able to reuse
+;; the default `macrostep-sexp-bounds-function' if they provide another
+;; implementation of `macrostep-macro-form-p-function'. Languages which
+;; do not implement locally-defined macros can set
+;; `macrostep-environment-at-point-function' to `ignore'.
+
+;; Note that the core `macrostep' machinery only interprets the return
+;; value of `macrostep-sexp-bounds-function', so implementations for
+;; other languages can use any internal representations of code and
+;; environments which is convenient. Although the terminology is
+;; Lisp-specific, there is no reason that implementations could not be
+;; provided for non-Lisp languages with macro systems, provided there is
+;; some way of identifying macro calls and calling the compiler /
+;; preprocessor to obtain their expansions.
+
+
+;; 6 Bugs and known limitations
;; ============================
;; You can evaluate and edebug macro-expanded forms and step through the
@@ -145,17 +205,10 @@
;; same print name as another symbol in the expansion. It's possible that
;; using `print-circle' and `print-gensym' could get around this.
-;; The macro stepper doesn't bother trying to determine whether or not a
-;; sub-form is in an evaluated position before highlighting it as a
-;; macro. It does exclude `lambda' from treatment as a macro, since that
-;; leads to an endless series of expansions: `(function (function
-;; ... ))'. It would be better to recognise `function', `quote' and other
-;; special forms using their `edebug-form-spec' property.
-
;; Please send other bug reports and feature requests to the author.
-;; 5 Acknowledgements
+;; 7 Acknowledgements
;; ==================
;; Thanks to:
@@ -165,17 +218,34 @@
;; expanded region and properly handle backquotes.
;; - Nic Ferrier for suggesting support for local definitions within
;; macrolet forms
+;; - Luís Oliveira for suggesting and implementing SLIME support
+;; `macrostep' was originally inspired by J. V. Toups's 'Deep Emacs Lisp'
+;; articles ([part 1], [part 2], [screencast]).
;; [EmacsConf presentation] http://youtu.be/RvPFZL6NJNQ
+;; [part 1]
+;; http://dorophone.blogspot.co.uk/2011/04/deep-emacs-part-1.html
-;; 6 Changelog
+;; [part 2]
+;; http://dorophone.blogspot.co.uk/2011/04/deep-emacs-lisp-part-2.html
+
+;; [screencast]
+;;
http://dorophone.blogspot.co.uk/2011/05/monadic-parser-combinators-in-elisp.html
+
+
+;; 8 Changelog
;; ===========
+;; - v0.9, 2015-10-01:
+;; - separate into Elisp-specific and generic components
+;; - highlight and expand compiler macros
+;; - improve local macro expansion and macro form identification by
+;; instrumenting `macroexpand(-all)'
;; - v0.8, 2014-05-29: fix a bug with printing the first element of lists
-;; - v0.7, 2014-05-11: expand locally-defined macros within (cl-)macrolet
-;; forms
+;; - v0.7, 2014-05-11: expand locally-defined macros within
+;; `(cl-)macrolet' forms
;; - v0.6, 2013-05-04: better handling of quote and backquote
;; - v0.5, 2013-04-16: highlight region, maintain cleaner buffer state
;; - v0.4, 2013-04-07: only enter macrostep-mode on successful
@@ -216,9 +286,6 @@
"Saved value of buffer-read-only upon entering macrostep mode.")
(make-variable-buffer-local 'macrostep-saved-read-only)
-(defvar macrostep-environment nil
- "Local macro-expansion environment, including macros declared by
`cl-macrolet'.")
-
(defvar macrostep-expansion-buffer nil
"Non-nil if the current buffer is a macro-expansion buffer.")
(make-variable-buffer-local 'macrostep-expansion-buffer)
@@ -285,11 +352,21 @@ buffer.")
"Face for macros in macro-expanded code."
:group 'macrostep)
+(defface macrostep-compiler-macro-face
+ '((t :slant italic))
+ "Face for compiler macros in macro-expanded code."
+ :group 'macrostep)
+
(defcustom macrostep-expand-in-separate-buffer nil
"When non-nil, show expansions in a separate buffer instead of inline."
:group 'macrostep
:type 'boolean)
+(defcustom macrostep-expand-compiler-macros t
+ "When non-nil, expand compiler macros as well as `defmacro' and `macrolet'
macros."
+ :group 'macrostep
+ :type 'boolean)
+
;; Need the following for making the ring of faces
(defun macrostep-make-ring (&rest items)
"Make a ring containing all of ITEMS with no empty slots."
@@ -303,6 +380,87 @@ buffer.")
'macrostep-gensym-4 'macrostep-gensym-5)
"Ring of all macrostepper faces for fontifying gensyms.")
+;; Other modes can enable macrostep by redefining these functions to
+;; language-specific versions.
+(defvar-local macrostep-sexp-bounds-function
+ #'macrostep-sexp-bounds
+ "Function to return the bounds of the macro form nearest point.
+
+It will be called with no arguments and should return a cons of
+buffer positions, (START . END). It should use `save-excursion'
+to avoid changing the position of point.
+
+The default value, `macrostep-sexp-bounds', implements this for
+Emacs Lisp, and may be suitable for other Lisp-like languages.")
+
+(defvar-local macrostep-sexp-at-point-function
+ #'macrostep-sexp-at-point
+ "Function to return the macro form at point for expansion.
+
+It will be called with no arguments, with point positioned at the
+START position returned by `macrostep-sexp-bounds-function', and
+should return a value suitable for passing as the first argument
+to `macrostep-expand-1-function'.
+
+The default value, `macrostep-sexp-at-point', implements this for
+Emacs Lisp, and may be suitable for other Lisp-like languages.")
+
+(defvar-local macrostep-environment-at-point-function
+ #'macrostep-environment-at-point
+ "Function to return the local macro-expansion environment at point.
+
+It will be called with no arguments, and should return a value
+suitable for passing as the second argument to
+`macrostep-expand-1-function'.
+
+The default value, `macrostep-environment-at-point', is specific
+to Emacs Lisp. For languages which do not implement local
+macro-expansion environments, this should be set to `ignore'
+or `(lambda () nil)'.")
+
+(defvar-local macrostep-expand-1-function
+ #'macrostep-expand-1
+ "Function to perform one step of macro-expansion.
+
+It will be called with two arguments, FORM and ENVIRONMENT, the
+return values of `macrostep-sexp-at-point-function' and
+`macrostep-environment-at-point-function' respectively. It
+should return the result of expanding FORM by one step as a value
+which is suitable for passing as the argument to
+`macrostep-print-function'.
+
+The default value, `macrostep-expand-1', is specific to Emacs Lisp.")
+
+(defvar-local macrostep-print-function
+ #'macrostep-pp
+ "Function to pretty-print macro expansions.
+
+It will be called with two arguments, FORM and ENVIRONMENT, the
+return values of `macrostep-sexp-at-point-function' and
+`macrostep-environment-at-point-function' respectively. It
+should insert a pretty-printed representation at point in the
+current buffer, leaving point just after the inserted
+representation, without altering any other text in the current
+buffer.
+
+The default value, `macrostep-pp', is specific to Emacs Lisp.")
+
+(defvar-local macrostep-macro-form-p-function
+ #'macrostep-macro-form-p
+ "Function to check whether a form is a macro call.
+
+It will be called with two arguments, FORM and ENVIRONMENT -- the
+return values of `macrostep-sexp-at-point-function' and
+`macrostep-environment-at-point-function' respectively -- and
+should return non-nil if FORM would undergo macro-expansion in
+ENVIRONMENT.
+
+This is called only from `macrostep-sexp-bounds', so it need not
+be provided if a different value is used for
+`macrostep-sexp-bounds-function'.
+
+The default value, `macrostep-macro-form-p', is specific to Emacs Lisp.")
+
;;; Define keymap and minor mode
(defvar macrostep-keymap
@@ -374,7 +532,7 @@ quit and return to normal editing.
;;; Interactive functions
;;;###autoload
-(defun macrostep-expand (toggle-separate-buffer)
+(defun macrostep-expand (&optional toggle-separate-buffer)
"Expand the Elisp macro form following point by one step.
Enters `macrostep-mode' if it is not already active, making the
@@ -387,73 +545,71 @@ buffer instead of inline in the current buffer. Setting
`macrostep-expand-in-separate-buffer' to non-nil swaps these two
behaviors."
(interactive "P")
- (let ((sexp (macrostep-sexp-at-point))
- (macrostep-environment (macrostep-environment-at-point)))
- (when (not (macrostep-macro-form-p sexp))
- (condition-case nil
- (progn
- (macrostep-next-macro)
- (setq sexp (macrostep-sexp-at-point)))
- (error
- (if (consp sexp)
- (error "(%s ...) is not a macro form" (car sexp))
- (error "Text at point is not a macro form.")))))
-
- ;; Create a dedicated macro-expansion buffer and copy the text to
- ;; be expanded into it, if required
- (let ((separate-buffer-p
- (if toggle-separate-buffer
- (not macrostep-expand-in-separate-buffer)
- macrostep-expand-in-separate-buffer)))
- (when (and separate-buffer-p (not macrostep-expansion-buffer))
- (let ((buffer
- (get-buffer-create (generate-new-buffer-name "*macro
expansion*"))))
- (set-buffer buffer)
- (emacs-lisp-mode)
- (setq macrostep-expansion-buffer t)
- (setq macrostep-outer-environment macrostep-environment)
- (save-excursion
- (let ((print-level nil)
- (print-length nil))
- (print sexp (current-buffer))))
- (pop-to-buffer buffer))))
-
- (let* ((inhibit-read-only t)
- (expansion (macrostep-expand-1 sexp))
- (existing-ol (macrostep-overlay-at-point))
- (macrostep-gensym-depth macrostep-gensym-depth)
- (macrostep-gensyms-this-level nil)
- text priority)
- (unless macrostep-mode (macrostep-mode t))
- (if existing-ol ; expanding an expansion
- (setq text sexp
- priority (1+ (overlay-get existing-ol 'priority))
-
- macrostep-gensym-depth
- (overlay-get existing-ol 'macrostep-gensym-depth))
- ;; expanding buffer text
- (setq text (buffer-substring (point) (scan-sexps (point) 1))
- priority 1
- macrostep-gensym-depth -1))
+ (cl-destructuring-bind (start . end)
+ (funcall macrostep-sexp-bounds-function)
+ (goto-char start)
+ (let* ((sexp (funcall macrostep-sexp-at-point-function))
+ (end (copy-marker end))
+ (text (buffer-substring start end))
+ (env (funcall macrostep-environment-at-point-function))
+ (expansion (funcall macrostep-expand-1-function sexp env)))
+
+ ;; Create a dedicated macro-expansion buffer and copy the text to
+ ;; be expanded into it, if required
+ (let ((separate-buffer-p
+ (if toggle-separate-buffer
+ (not macrostep-expand-in-separate-buffer)
+ macrostep-expand-in-separate-buffer)))
+ (when (and separate-buffer-p (not macrostep-expansion-buffer))
+ (let ((mode major-mode)
+ (buffer
+ (get-buffer-create (generate-new-buffer-name "*macro
expansion*"))))
+ (set-buffer buffer)
+ (funcall mode)
+ (setq macrostep-expansion-buffer t)
+ (setq macrostep-outer-environment env)
+ (save-excursion
+ (setq start (point))
+ (insert text)
+ (setq end (point-marker)))
+ (pop-to-buffer buffer))))
- (with-silent-modifications
- (atomic-change-group
- (macrostep-replace-sexp-at-point expansion)
- (let ((new-ol
- (make-overlay (point)
- (scan-sexps (point) 1))))
- ;; Move overlay over newline to make it prettier
- (when (equal (char-after (overlay-end new-ol)) ?\n)
- (move-overlay new-ol
- (overlay-start new-ol) (+ (overlay-end new-ol) 1)))
- ;; Highlight the overlay in original source buffers only
- (unless macrostep-expansion-buffer
- (overlay-put new-ol 'face 'macrostep-expansion-highlight-face))
- (overlay-put new-ol 'evaporate t)
- (overlay-put new-ol 'priority priority)
- (overlay-put new-ol 'macrostep-original-text text)
- (overlay-put new-ol 'macrostep-gensym-depth macrostep-gensym-depth)
- (push new-ol macrostep-overlays)))))))
+ (unless macrostep-mode (macrostep-mode t))
+ (let ((existing-overlay (macrostep-overlay-at-point))
+ (macrostep-gensym-depth macrostep-gensym-depth)
+ (macrostep-gensyms-this-level nil)
+ priority)
+ (if existing-overlay
+ (progn ; Expanding part of a previous macro-expansion
+ (setq priority (1+ (overlay-get existing-overlay 'priority)))
+ (setq macrostep-gensym-depth
+ (overlay-get existing-overlay 'macrostep-gensym-depth)))
+ ;; Expanding source buffer text
+ (setq priority 1)
+ (setq macrostep-gensym-depth -1))
+
+ (with-silent-modifications
+ (atomic-change-group
+ (let ((inhibit-read-only t))
+ (save-excursion
+ ;; Insert expansion
+ (funcall macrostep-print-function expansion env)
+ ;; Delete the original form
+ (macrostep-collapse-overlays-in (point) end)
+ (delete-region (point) end)
+ ;; Create a new overlay
+ (let ((overlay
+ (make-overlay start
+ (if (looking-at "\n")
+ (1+ (point))
+ (point)))))
+ (unless macrostep-expansion-buffer
+ ;; Highlight the overlay in original source buffers only
+ (overlay-put overlay 'face
'macrostep-expansion-highlight-face))
+ (overlay-put overlay 'priority priority)
+ (overlay-put overlay 'macrostep-original-text text)
+ (overlay-put overlay 'macrostep-gensym-depth
macrostep-gensym-depth)
+ (push overlay macrostep-overlays))))))))))
(defun macrostep-collapse ()
"Collapse the innermost macro expansion near point to its source text.
@@ -463,7 +619,7 @@ If no more macro expansions are visible after this, exit
(interactive)
(let ((overlay (macrostep-overlay-at-point)))
(when (not overlay) (error "No macro expansion at point"))
- (let ((buffer-read-only nil))
+ (let ((inhibit-read-only t))
(with-silent-modifications
(atomic-change-group
(macrostep-collapse-overlay overlay)))))
@@ -473,7 +629,7 @@ If no more macro expansions are visible after this, exit
(defun macrostep-collapse-all ()
"Collapse all visible macro expansions and exit `macrostep-mode'."
(interactive)
- (let ((buffer-read-only nil))
+ (let ((inhibit-read-only t))
(with-silent-modifications
(dolist (overlay macrostep-overlays)
(let ((outermost (= (overlay-get overlay 'priority) 1)))
@@ -487,10 +643,10 @@ If no more macro expansions are visible after this, exit
"Move point forward to the next macro form in macro-expanded text."
(interactive)
(let* ((start
- (if (get-text-property (point) 'macrostep-expanded-text)
+ (if (get-text-property (point) 'macrostep-macro-start)
(1+ (point))
(point)))
- (next (next-single-property-change start 'macrostep-expanded-text)))
+ (next (next-single-property-change start 'macrostep-macro-start)))
(if next
(goto-char next)
(error "No more macro forms found"))))
@@ -503,104 +659,17 @@ If no more macro expansions are visible after this, exit
(while
(progn
(setq prev
- (previous-single-property-change (point)
'macrostep-expanded-text))
+ (previous-single-property-change (point)
'macrostep-macro-start))
(if (or (not prev)
- (get-text-property (1- prev) 'macrostep-expanded-text))
+ (get-text-property (1- prev) 'macrostep-macro-start))
nil
(prog1 t (goto-char prev))))))
(if prev
(goto-char (1- prev))
(error "No previous macro form found"))))
-
-;;; Utility functions
-(defun macrostep-macro-form-p (form)
- "Return t if FORM is a sexp that would be evaluated via macro expansion."
- (if (or (not (consp form))
- (eq (car form) 'lambda)) ; hack
- nil
- (condition-case err
- (or
- ;; Locally bound as a macro?
- (assq (car form) macrostep-environment)
- ;; Globally defined?
- (let ((fun (indirect-function (car form))))
- (and (consp fun)
- (or (eq (car fun) 'macro)
- (and
- (eq (car fun) 'autoload)
- (eq (nth 4 fun) 'macro))))))
- (error nil))))
-
-(defun macrostep-macro-definition (form)
- "Return, as a function, the macro definition to apply in expanding FORM."
- (or
- ;; Locally bound by `macrolet'
- (cdr (assq (car form) macrostep-environment))
- ;; Globally defined
- (let ((fun (indirect-function (car form))))
- (if (consp fun)
- (case (car fun)
- ((macro)
- (cdr fun))
-
- ((autoload)
- (load-library (nth 1 fun))
- (macrostep-macro-definition form)))
- (error "(%s ...) is not a macro form" form)))))
-
-(defun macrostep-expand-1 (form)
- "Return result of macro-expanding the top level of FORM by exactly one step.
-Unlike `macroexpand', this function does not continue macro
-expansion until a non-macro-call results."
- (if (not (macrostep-macro-form-p form)) form
- (apply (macrostep-macro-definition form) (cdr form))))
-
-(defun macrostep-environment-at-point ()
- "Return the local macro-expansion environment at point, if any.
-
-The local environment includes macros declared by any `macrolet'
-or `cl-macrolet' forms surrounding point.
-
-The return value is an alist of elements (NAME . FUNCTION), where
-NAME is the symbol locally bound to the macro and FUNCTION is the
-lambda expression that returns its expansion."
- (save-excursion
- (let
- ((enclosing-form
- (ignore-errors
- (backward-up-list)
- (read (copy-marker (point))))))
- (pcase enclosing-form
- (`(,(or `macrolet `cl-macrolet) ,bindings . ,_)
- (let ((binding-environment
- (macrostep-bindings-to-environment bindings))
- (enclosing-environment
- (macrostep-environment-at-point)))
- (append binding-environment enclosing-environment)))
- (`nil
- (if macrostep-expansion-buffer
- macrostep-outer-environment
- nil))
- (_
- (macrostep-environment-at-point))))))
-
-(defun macrostep-bindings-to-environment (bindings)
- "Return the macro-expansion environment declared by BINDINGS as an alist.
-
-BINDINGS is a list in the form expected by `macrolet' or
-`cl-macrolet'. The return value is an alist, as described in
-`macrostep-environment-at-point'."
- ;; So that the later elements of bindings properly shadow the
- ;; earlier ones in the returned environment, we must reverse the
- ;; list before mapping over it.
- (cl-loop for (name . forms) in (reverse bindings)
- collect
- ;; Adapted from the definition of `cl-macrolet':
- (let ((res (cl--transform-lambda forms name)))
- (eval (car res))
- (cons name `(lambda ,@(cdr res))))))
+;;; Utility functions (not language-specific)
(defun macrostep-overlay-at-point ()
"Return the innermost macro stepper overlay at point."
@@ -608,28 +677,6 @@ BINDINGS is a list in the form expected by `macrolet' or
(get-char-property-and-overlay (point) 'macrostep-original-text)))
(cdr result)))
-(defun macrostep-sexp-at-point ()
- "Return the sexp near point for purposes of macro-stepper expansion.
-
-If the sexp near point is part of a macro expansion, returns the
-saved text of the macro expansion, and does not read from the
-buffer. This preserves uninterned symbols in the macro expansion,
-so that they can be colored consistently. See also
-`macrostep-print-sexp'.
-
-Also moves point to the beginning of the returned s-expression."
- (if (not (looking-at "[(`]"))
- (backward-up-list 1))
- (if (equal (char-before) ?`)
- (backward-char))
- (or (get-text-property (point) 'macrostep-expanded-text)
- (progn
- ;; use scan-sexps for the side-effect of producing an error
- ;; message for unbalanced parens, etc.
- (scan-sexps (point) 1)
- (sexp-at-point))))
-
-
(defun macrostep-collapse-overlay (overlay &optional no-restore-p)
"Collapse a macro-expansion overlay and restore the unexpanded source text.
@@ -639,20 +686,25 @@ collapsing all the sub-expansions of an outer overlay,
since the
outer overlay will restore the original source itself.
Also removes the overlay from `macrostep-overlays'."
- (when (and (overlay-start overlay)
- (eq (overlay-buffer overlay) (current-buffer)))
- ;; If we're cleaning up we don't need to bother restoring text
- ;; or checking for inner overlays to delete
- (unless no-restore-p
- (macrostep-collapse-overlays-in
- (overlay-start overlay) (overlay-end overlay))
- (let ((text (overlay-get overlay 'macrostep-original-text)))
- (goto-char (overlay-start overlay))
- (macrostep-replace-sexp-at-point text (stringp text))))
- ;; Remove overlay from the list and delete it
- (setq macrostep-overlays
- (delq overlay macrostep-overlays))
- (delete-overlay overlay)))
+ (with-current-buffer (overlay-buffer overlay)
+ ;; If we're cleaning up we don't need to bother restoring text
+ ;; or checking for inner overlays to delete
+ (unless no-restore-p
+ (let* ((start (overlay-start overlay))
+ (end (overlay-end overlay))
+ (text (overlay-get overlay 'macrostep-original-text))
+ (sexp-end
+ (copy-marker
+ (if (equal (char-before end) ?\n) (1- end) end))))
+ (macrostep-collapse-overlays-in start end)
+ (goto-char (overlay-start overlay))
+ (save-excursion
+ (insert text)
+ (delete-region (point) sexp-end))))
+ ;; Remove overlay from the list and delete it
+ (setq macrostep-overlays
+ (delq overlay macrostep-overlays))
+ (delete-overlay overlay)))
(defun macrostep-collapse-overlays-in (start end)
"Collapse all macrostepper overlays that are strictly between START and END.
@@ -664,56 +716,277 @@ Will not collapse overlays that begin at START and end
at END."
(overlay-get ol 'macrostep-original-text))
(macrostep-collapse-overlay ol t))))
-(defun macrostep-replace-sexp-at-point (sexp &optional original)
- "Replace the form following point with SEXP.
+
+;;; Emacs Lisp implementation
+
+(defun macrostep-sexp-bounds ()
+ "Find the bounds of the macro form nearest point.
-If ORIGINAL is non-nil, SEXP is assumed to be a string
-representing the original source text, and inserted verbatim as a
-replacement for the form following point. Otherwise, if ORIGINAL
-is nil, SEXP is treated as the macro expansion of the source,
-inserted using `macrostep-print-sexp' and pretty-printed using
-`pp-buffer'."
- (let ((print-quoted t))
- (save-excursion
- ;; Insert new text first so that existing overlays don't
- ;; evaporate
- (if original
- (insert sexp) ; insert original source text
- (macrostep-print-sexp sexp))
- ;; Delete the old form and remove any sub-form overlays in it
- (let ((start (point)) (end (scan-sexps (point) 1)))
- (macrostep-collapse-overlays-in start end)
- (delete-region start end)))
-
- (unless original ; inserting macro expansion
+If point is not before an open-paren, moves up to the nearest
+enclosing list. If the form at point is not a macro call,
+attempts to move forward to the next macro form as determined by
+`macrostep-macro-form-p-function'.
+
+Returns a cons of buffer positions, (START . END)."
+ (save-excursion
+ (if (not (looking-at "[(`]"))
+ (backward-up-list 1))
+ (if (equal (char-before) ?`)
+ (backward-char))
+ (let ((sexp (funcall macrostep-sexp-at-point-function))
+ (env (funcall macrostep-environment-at-point-function)))
+ ;; If this isn't a macro form, try to find the next one in the buffer
+ (unless (funcall macrostep-macro-form-p-function sexp env)
+ (condition-case nil
+ (macrostep-next-macro)
+ (error
+ (if (consp sexp)
+ (error "(%s ...) is not a macro form" (car sexp))
+ (error "Text at point is not a macro form."))))))
+ (cons (point) (scan-sexps (point) 1))))
+
+(defun macrostep-sexp-at-point ()
+ "Return the sexp near point for purposes of macro-stepper expansion.
+
+If the sexp near point is part of a macro expansion, returns the
+saved text of the macro expansion, and does not read from the
+buffer. This preserves uninterned symbols in the macro
+expansion, so that they can be fontified consistently. (See
+`macrostep-print-sexp'.)"
+ (or (get-text-property (point) 'macrostep-expanded-text)
+ (sexp-at-point)))
+
+(defun macrostep-macro-form-p (form environment)
+ "Return non-nil if FORM would be evaluated via macro expansion.
+
+If FORM is an invocation of a macro defined by `defmacro' or an
+enclosing `cl-macrolet' form, return the symbol `macro'.
+
+If `macrostep-expand-compiler-macros' is non-nil and FORM is a
+call to a function with a compiler macro, return the symbol
+`compiler-macro'.
+
+Otherwise, return nil."
+ (car (macrostep--macro-form-info form environment t)))
+
+(defun macrostep--macro-form-info (form environment &optional inhibit-autoload)
+ "Return information about macro definitions that apply to FORM.
+
+If no macros are involved in the evaluation of FORM within
+ENVIRONMENT, returns nil. Otherwise, returns a cons (TYPE
+. DEFINITION).
+
+If FORM would be evaluated by a macro defined by `defmacro',
+`cl-macrolet', etc., TYPE is the symbol `macro' and DEFINITION is
+the macro definition, as a function.
+
+If `macrostep-expand-compiler-macros' is non-nil and FORM would
+be compiled using a compiler macro, TYPE is the symbol
+`compmiler-macro' and DEFINITION is the function that implements
+the compiler macro.
+
+If FORM is an invocation of an autoloaded macro, the behavior
+depends on the value of INHIBIT-AUTOLOAD. If INHIBIT-AUTOLOAD is
+nil, the file containing the macro definition will be loaded
+using `load-library' and the macro definition returned as normal.
+If INHIBIT-AUTOLOAD is non-nil, no files will be loaded, and the
+value of DEFINITION in the result will be nil."
+ (pcase form
+ (`(,(and (pred symbolp) head) . ,_)
+ (let ((macrolet-definition
+ (assoc-default head environment 'eq)))
+ (if macrolet-definition
+ `(macro . ,macrolet-definition)
+ (let ((compiler-macro-definition
+ (and macrostep-expand-compiler-macros
+ (get head 'compiler-macro))))
+ (if compiler-macro-definition
+ `(compiler-macro . ,compiler-macro-definition)
+ (condition-case _
+ (let ((fun (indirect-function head)))
+ (pcase fun
+ (`(macro . ,definition)
+ `(macro . ,definition))
+ (`(autoload ,_ ,_ ,_ macro . ,_)
+ (if inhibit-autoload
+ `(macro)
+ (autoload-do-load fun)
+ (macrostep--macro-form-info form nil)))
+ (_ nil)))
+ (void-function nil)))))))
+ (_ nil)))
+
+(defun macrostep-expand-1 (form environment)
+ "Return result of macro-expanding the top level of FORM by exactly one step.
+Unlike `macroexpand', this function does not continue macro
+expansion until a non-macro-call results."
+ (pcase (macrostep--macro-form-info form environment)
+ (`nil form)
+ (`(macro . ,definition)
+ (apply definition (cdr form)))
+ (`(compiler-macro . ,definition)
+ (let ((expansion
+ (apply definition form (cdr form))))
+ (if (equal form expansion)
+ (error "Form left unchanged by compiler macro")
+ expansion)))))
+
+(define-error 'macrostep-grab-environment-failed
+ "Extracting the macro environment at point failed.")
+
+(defun macrostep-environment-at-point ()
+ "Return the local macro-expansion environment at point, if any.
+
+The local environment includes macros declared by any `macrolet'
+or `cl-macrolet' forms surrounding point, as well as by any macro
+forms which expand into a `macrolet'.
+
+The return value is an alist of elements (NAME . FUNCTION), where
+NAME is the symbol locally bound to the macro and FUNCTION is the
+lambda expression that returns its expansion."
+ ;; If point is on a macro form within an expansion inserted by
+ ;; `macrostep-print-sexp', a local environment may have been
+ ;; previously saved as a text property.
+ (let ((saved-environment
+ (get-text-property (point) 'macrostep-environment)))
+ (if saved-environment
+ saved-environment
+ ;; Otherwise, we (ab)use the macro-expander to return the
+ ;; environment at point. If point is not at an evaluated
+ ;; position in the containing form,
+ ;; `macrostep-environment-at-point-1' will raise an error, and
+ ;; we back up progressively through the containing forms until
+ ;; it succeeds.
+ (cl-flet ((move-backward ()
+ (condition-case nil
+ (backward-sexp)
+ (scan-error (backward-up-list)))))
+ (save-excursion
+ (catch 'done
+ (while t
+ (condition-case nil
+ (throw 'done (macrostep-environment-at-point-1))
+ (macrostep-grab-environment-failed
+ (move-backward))))))))))
+
+(defun macrostep-environment-at-point-1 ()
+ "Attempt to extract the macro environment that would be active at point.
+
+If point is not at an evaluated position within the containing
+form, raise an error."
+ ;; Macro environments are extracted using Emacs Lisp's builtin
+ ;; macro-expansion machinery. The form containing point is copied
+ ;; to a temporary buffer, and a call to
+ ;; `--macrostep-grab-environment--' is inserted at point. This
+ ;; altered form is then fully macro-expanded, in an environment
+ ;; where `--macrostep-grab-environment--' is defined as a macro
+ ;; which throws the environment to a uniquely-generated tag.
+ (let* ((point-at-top-level
+ (save-excursion
+ (while (ignore-errors (backward-up-list) t))
+ (point)))
+ (enclosing-form
+ (buffer-substring point-at-top-level
+ (scan-sexps point-at-top-level 1)))
+ (position (- (point) point-at-top-level))
+ (tag (make-symbol "macrostep-grab-environment-tag"))
+ (grab-environment '--macrostep-grab-environment--))
+ (if (= position 0)
+ nil
+ (with-temp-buffer
+ (emacs-lisp-mode)
+ (insert enclosing-form)
+ (goto-char (+ (point-min) position))
+ (prin1 `(,grab-environment) (current-buffer))
+ (let ((form (read (copy-marker (point-min)))))
+ (catch tag
+ (cl-letf (((symbol-function #'message) (symbol-function #'format)))
+ (with-no-warnings
+ (ignore-errors
+ (macroexpand-all
+ `(cl-macrolet ((,grab-environment (&environment env)
+ (throw ',tag env)))
+ ,form)))))
+ (signal 'macrostep-grab-environment-failed nil)))))))
+
+(defun macrostep-collect-macro-forms (form &optional environment)
+ "Identify sub-forms of FORM which undergo macro-expansion.
+
+FORM is an Emacs Lisp form. ENVIRONMENT is a local environment of
+macro definitions.
+
+The return value is a list of two elements, (MACRO-FORM-ALIST
+COMPILER-MACRO-FORMS).
+
+MACRO-FORM-ALIST is an alist of elements of the form (SUBFORM
+. ENVIRONMENT), where SUBFORM is a form which undergoes
+macro-expansion in the course of expanding FORM, and ENVIRONMENT
+is the local macro environment in force when it is expanded.
+
+COMPILER-MACRO-FORMS is a list of subforms which would be
+compiled using a compiler macro. Since there is no standard way
+to provide a local compiler-macro definition in Emacs Lisp, no
+corresponding local environments are collected for these.
+
+Forms and environments are extracted from FORM by instrumenting
+Emacs's builtin `macroexpand' function and calling
+`macroexpand-all'."
+ (let ((real-macroexpand (indirect-function #'macroexpand))
+ (macro-form-alist '())
+ (compiler-macro-forms '()))
+ (cl-letf
+ (((symbol-function #'macroexpand)
+ (lambda (form environment &rest args)
+ (let ((expansion
+ (apply real-macroexpand form environment args)))
+ (cond ((not (eq expansion form))
+ (setq macro-form-alist
+ (cons (cons form environment)
+ macro-form-alist)))
+ ((and (consp form)
+ (symbolp (car form))
+ macrostep-expand-compiler-macros
+ (not (eq form
+ (cl-compiler-macroexpand form))))
+ (setq compiler-macro-forms
+ (cons form compiler-macro-forms))))
+ expansion))))
+ (ignore-errors
+ (macroexpand-all form environment)))
+ (list macro-form-alist compiler-macro-forms)))
+
+(defvar macrostep-collected-macro-form-alist nil
+ "An alist of macro forms and environments.
+Controls the printing of sub-forms in `macrostep-print-sexp'.")
+
+(defvar macrostep-collected-compiler-macro-forms nil
+ "A list of compiler-macro forms to be highlighted in
`macrostep-print-sexp'.")
+
+(defun macrostep-pp (sexp environment)
+ "Pretty-print SEXP, fontifying macro forms and uninterned symbols."
+ (cl-destructuring-bind
+ (macrostep-collected-macro-form-alist
+ macrostep-collected-compiler-macro-forms)
+ (macrostep-collect-macro-forms sexp environment)
+ (let ((print-quoted t))
+ (macrostep-print-sexp sexp)
+ ;; Point is now after the expanded form; pretty-print it
(save-restriction
- ;; point is now before the expanded form; pretty-print it
- (narrow-to-region (point) (scan-sexps (point) 1))
+ (narrow-to-region (scan-sexps (point) -1) (point))
(save-excursion
(pp-buffer)
- ;; remove the extra newline that pp-buffer inserts
+ ;; Remove the extra newline inserted by pp-buffer
(goto-char (point-max))
(delete-region
(point)
(save-excursion (skip-chars-backward " \t\n") (point))))
+ ;; Indent the newly-inserted form in context
(widen)
- (indent-sexp)))))
-
-(defun macrostep-get-gensym-face (symbol)
- "Return the face to use in fontifying SYMBOL in printed macro expansions.
-
-All symbols introduced in the same level of macro expansion are
-fontified using the same face (modulo the number of faces; see
-`macrostep-gensym-faces')."
- (or (get symbol 'macrostep-gensym-face)
- (progn
- (if (not macrostep-gensyms-this-level)
- (setq macrostep-gensym-depth (1+ macrostep-gensym-depth)
- macrostep-gensyms-this-level t))
- (let ((face (ring-ref macrostep-gensym-faces macrostep-gensym-depth)))
- (put symbol 'macrostep-gensym-face face)
- face))))
+ (save-excursion
+ (backward-sexp)
+ (indent-sexp))))))
+;; This must be defined before `macrostep-print-sexp':
(defmacro macrostep-propertize (form &rest plist)
"Evaluate FORM, applying syntax properties in PLIST to any inserted text."
(declare (indent 1)
@@ -726,16 +999,19 @@ fontified using the same face (modulo the number of
faces; see
collect `(put-text-property ,start (point)
,key ,value))))))
-(defun macrostep-print-sexp (sexp &optional quoted-form-p)
- "Pretty-print SEXP, a macro expansion, in the current buffer.
+(defun macrostep-print-sexp (sexp)
+ "Insert SEXP like `print', fontifying macro forms and uninterned symbols.
Fontifies uninterned symbols and macro forms using
`font-lock-face' property, and saves the actual text of SEXP's
sub-forms as the `macrostep-expanded-text' text property so that
any uninterned symbols can be reused in macro expansions of the
-sub-forms. If QUOTED-FORM-P is non-nil then any macros in the
-expansion will not be fontified. See also
-`macrostep-sexp-at-point'."
+sub-forms. See also `macrostep-sexp-at-point'.
+
+Macro and compiler-macro forms within SEXP are identified by
+comparison with the `macrostep-collected-macro-form-alist' and
+`macrostep-collected-compiler-macro-forms' variables, which
+should be dynamically let-bound around calls to this function."
(cond
((symbolp sexp)
;; Fontify gensyms
@@ -752,16 +1028,18 @@ expansion will not be fontified. See also
(cond ((and (eq head 'quote) ; quote
(= (length sexp) 2))
(insert "'")
- (macrostep-print-sexp (cadr sexp) t))
+ (macrostep-print-sexp (cadr sexp)))
((and (eq head '\`) ; backquote
(= (length sexp) 2))
- ;; Treat backquote as a macro
- (macrostep-propertize
- (insert "`")
- 'macrostep-expanded-text sexp
- 'font-lock-face 'macrostep-macro-face)
- (macrostep-print-sexp (cadr sexp) t))
+ (if (assq sexp macrostep-collected-macro-form-alist)
+ (macrostep-propertize
+ (insert "`")
+ 'macrostep-expanded-text sexp
+ 'macrostep-macro-start t
+ 'font-lock-face 'macrostep-macro-face)
+ (insert "`"))
+ (macrostep-print-sexp (cadr sexp)))
((and (memq head '(\, \,@)) ; unquote
(= (length sexp) 2))
@@ -769,64 +1047,63 @@ expansion will not be fontified. See also
(macrostep-print-sexp (cadr sexp)))
(t ; other list form
- ;; If the sexp is a (cl-)macrolet form, the
- ;; macro-expansion environment should be extended using
- ;; its bindings while printing the body forms in order to
- ;; correctly mark any uses of locally-bound macros. (See
- ;; `with-js' in `js.el.gz' for an example of a macro that
- ;; works this way).
- (let ((extended-environment
- (pcase sexp
- (`(,(or `cl-macrolet `macrolet) ,bindings . ,_)
- (append (macrostep-bindings-to-environment bindings)
- macrostep-environment))
- (_ macrostep-environment))))
-
- ;; Is it an (unquoted) macro form?
- (if (and (not quoted-form-p)
- (macrostep-macro-form-p sexp))
+ (pcase-let
+ ((`(,macro? . ,environment)
+ (assq sexp macrostep-collected-macro-form-alist))
+ (compiler-macro?
+ (memq sexp macrostep-collected-compiler-macro-forms)))
+ (if (or macro? compiler-macro?)
(progn
;; Save the real expansion as a text property on the
;; opening paren
(macrostep-propertize
(insert "(")
- 'macrostep-expanded-text sexp)
+ 'macrostep-macro-start t
+ 'macrostep-expanded-text sexp
+ 'macrostep-environment environment)
;; Fontify the head of the macro
(macrostep-propertize
(macrostep-print-sexp head)
- 'font-lock-face 'macrostep-macro-face))
+ 'font-lock-face
+ (if macro?
+ 'macrostep-macro-face
+ 'macrostep-compiler-macro-face)))
;; Not a macro form
(insert "(")
- (macrostep-print-sexp head quoted-form-p))
-
- ;; Print remaining list elements
- (setq sexp (cdr sexp))
- (when sexp (insert " "))
- ;; macrostep-environment will be setq'd after printing
- ;; the second element of the list (i.e., the binding
- ;; list in a macrolet form)
- (let ((macrostep-environment macrostep-environment))
- (while sexp
- (if (listp sexp)
- (progn
- (macrostep-print-sexp (car sexp) quoted-form-p)
- (when (cdr sexp) (insert " "))
- (setq sexp (cdr sexp))
- ;; At this point the first and second
- ;; elements of the list have been printed, so
- ;; it is time to extend the macro-expansion
- ;; environment inside a macrolet for the body
- ;; forms.
- (setq macrostep-environment extended-environment))
- ;; Print tail of dotted list
- (insert ". ")
- (macrostep-print-sexp sexp)
- (setq sexp nil))))
- (insert ")"))))))
+ (macrostep-print-sexp head)))
+
+ ;; Print remaining list elements
+ (setq sexp (cdr sexp))
+ (when sexp (insert " "))
+ (while sexp
+ (if (listp sexp)
+ (progn
+ (macrostep-print-sexp (car sexp))
+ (when (cdr sexp) (insert " "))
+ (setq sexp (cdr sexp)))
+ ;; Print tail of dotted list
+ (insert ". ")
+ (macrostep-print-sexp sexp)
+ (setq sexp nil)))
+ (insert ")")))))
;; Print everything except symbols and lists as normal
(t (prin1 sexp (current-buffer)))))
+(defun macrostep-get-gensym-face (symbol)
+ "Return the face to use in fontifying SYMBOL in printed macro expansions.
+
+All symbols introduced in the same level of macro expansion are
+fontified using the same face (modulo the number of faces; see
+`macrostep-gensym-faces')."
+ (or (get symbol 'macrostep-gensym-face)
+ (progn
+ (if (not macrostep-gensyms-this-level)
+ (setq macrostep-gensym-depth (1+ macrostep-gensym-depth)
+ macrostep-gensyms-this-level t))
+ (let ((face (ring-ref macrostep-gensym-faces macrostep-gensym-depth)))
+ (put symbol 'macrostep-gensym-face face)
+ face))))
(provide 'macrostep)
- [nongnu] elpa/macrostep 2d216ba 109/110: Merge pull request #19 from fice-t/autoload, (continued)
- [nongnu] elpa/macrostep 2d216ba 109/110: Merge pull request #19 from fice-t/autoload, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 83b2a0a 015/110: Tweak visible highlight, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 3f815fb 031/110: Test for normal defmacro expansion, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 9d2c58c 040/110: Add option to expand in a separate buffer, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 3f965b0 048/110: Use new `macrostep-macro-start` text property, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep ff3888f 065/110: Use a form-tracking stream rather than gensym substitution, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 0fd292c 006/110: Better README, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 0db08ec 007/110: add Url: header for marmalade, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep aa4736c 067/110: Fix some silly errors, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 0b1c0fe 069/110: Add swank/gray:stream-read-char-no-hang implementation, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 704f164 091/110: Merge branch 'generic',
ELPA Syncer <=
- [nongnu] elpa/macrostep bf87b7c 095/110: Whitespace cleanup, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep d1e954d 099/110: Attempt to shell out to "indent" in macrostep-c, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep b65cc96 102/110: Makefile tweaks, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 424e373 110/110: Merge pull request #16 from fice-t/master, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep c5d1c5b 009/110: Autoload macro definitions if needed., ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 8b89694 012/110: Only enter macrostep-mode after successful macro expansion, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep b060e1a 014/110: Add visible highlight of expanded section, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 4b0d96f 013/110: Remove incorrect use of :version in defgroup, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 0bcb143 019/110: Handle backquotes better, ELPA Syncer, 2021/08/07
- [nongnu] elpa/macrostep 1c01145 039/110: Merge pull request #9 from xuchunyang/master, ELPA Syncer, 2021/08/07