branch: elpa/macrostep
commit b04f8dbe69fff2aac57dfb29bdfeb5731ecb1439
Author: joddie <[email protected]>
Commit: joddie <[email protected]>

    Basic support for expanding macros bound by `macrolet'
---
 macrostep.el | 92 ++++++++++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 74 insertions(+), 18 deletions(-)

diff --git a/macrostep.el b/macrostep.el
index 1f2c741..ba44f3b 100644
--- a/macrostep.el
+++ b/macrostep.el
@@ -179,7 +179,9 @@
 ;; We use `pp-buffer' to pretty-print macro expansions
 (require 'pp)
 (require 'ring)
-(eval-when-compile (require 'cl))
+(eval-when-compile
+  (require 'cl)
+  (require 'pcase))
 
 
 ;;; Constants and dynamically bound variables
@@ -203,6 +205,10 @@
   "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'.")
+(make-variable-buffer-local 'macrostep-environment)
+
 ;;; Faces
 (defgroup macrostep nil
   "Interactive macro stepper for Emacs Lisp."
@@ -345,7 +351,8 @@ buffer temporarily read-only. If macrostep-mode is active 
and the
 form following point is not a macro form, search forward in the
 buffer and expand the next macro form found, if any."
   (interactive)
-  (let ((sexp (macrostep-sexp-at-point)))
+  (let ((sexp (macrostep-sexp-at-point))
+        (macrostep-environment (macrostep-environment-at-point)))
     (when (not (macrostep-macro-form-p sexp))
       (condition-case nil
          (progn
@@ -457,26 +464,34 @@ If no more macro expansions are visible after this, exit
          (eq (car form) 'lambda))      ; hack
       nil
     (condition-case err
-        (let ((fun (indirect-function (car form))))
-          (and (consp fun)
-               (or (eq (car fun) 'macro)
-                   (and
-                    (eq (car fun) 'autoload)
-                    (eq (nth 4 fun) 'macro)))))
+        (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."
-  (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))))
+  (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.
@@ -485,6 +500,47 @@ 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 enclosing-environment binding-environment)))
+        (`nil 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))))))
+
 (defun macrostep-overlay-at-point ()
   "Return the innermost macro stepper overlay at point."
   (let ((result

Reply via email to