Page 1 of 1

Alternative (MACROEXPAND ...)

Posted: Thu Mar 17, 2011 1:07 am
by pjstirling
Hi all,

I've been programming in common-lisp for a couple of years now, and in that time I've grown somewhat frustrated with (MACROEXPAND ...) for debugging macros, because quite a lot of symbols in COMMON-LISP are macros with ugly expansions. e.g. (AND ...) and (OR ...) are changed to (IF ...) chains, (DEFUN ...) is defined in terms of a number of different sbcl internals, etc. This means that any reasonably complicated macro will expand into a lot of detail that you only need to see if you suspect that there is a bug in the macro provided by your implementation. So I decided to try and write my own version (How hard could it be?) that would special-case the COMMON-LISP macros, and call (MACROEXPAND-1 ...) only for unknown macros.

After quite a bit more effort than I expected (symbol macros interact with every form that binds variables, and I still need some kind of test suite to be sure that I've got it right for all of them) I've now got it working "good enough for me", and my next step is to figure out how to get it hooked up to C-c Ret in slime (my first attempt produced only errors).

I'm wondering whether this would be useful to others?

There are some caveats: some macros I wrote expanders for purely from my (mis?)understanding of the hyperspec (most of the condition handling stuff and PROGV spring to mind here), there may easily be some bugs in the ones that I *have* used regularly, and I've not even attempted expanders for DEFCLASS, DEFINE-COMPILER-MACRO, DEFINE-METHOD-COMBINATION, DEFINE-MODIFY-MACRO, DEFINE-SETF-EXPANDER, DEFSETF, DEFSTRUCT, DEFTYPE and (probably most important for people that choose to use it) LOOP.

Re: Alternative (MACROEXPAND ...)

Posted: Thu Mar 17, 2011 12:49 pm
by Warren Wilkinson
If you are using emacs, does C-c C-m [macroexpand-1 the form] do what you need? You can run it again inside expanded code to expand nested macros.

Re: Alternative (MACROEXPAND ...)

Posted: Sat Mar 19, 2011 6:44 am
by pjstirling
When I was half-way there I did sort-of wonder that someone might point me at something that would make my work superfluous :D

I had actually tried C-c C-m but I was never able to get it to work properly, at your prompting I tried it again and discovered that you need to put the cursor at the OPENING paren, if you aren't trying to expand the top level form, which should probably be in the slime manual...

If I'd not just spent a week writing my own version I'd probably settle for that, but it seems a bit fiddly for the use case "I want to see what the compiler sees, if the compiler treats all of COMMON-LISP as a black box". I guess that the degree of fiddly-ness is dependant on how many macros you are using in the code to be expanded :)

Re: Alternative (MACROEXPAND ...)

Posted: Wed Mar 23, 2011 3:41 am
by JohnGraham
If it's any help, here's two functions I find useful for debugging macros:

Code: Select all

(defun macroexpand-n (n body)
  "Return `body' macroexpanded `n' times."
  (let ((body-exp (macroexpand-1 body)))
    (if (= 1 n)
      body-exp
      (macroexpand-n (1- n) body-exp))))

(defun macroexpand-loop (body)
  "Continuously read a number and print `body' expanded that many               
times. Exit when we see the symbol `q'."
  (do ((in nil (read)))
      ((eql in 'q))
    (if (numberp in)
        (format t "~a~%" (macroexpand-n in body))
        (format t "Please enter a number (or `q' to quit). "))))
Usage:

Code: Select all

CL-USER> (defmacro mac (&body body)  ; Infinitely recursive macro.
           `(mac (list ,@body)))
MAC                                                                             
CL-USER> (macroexpand-n 1 '(mac a b c))
(MAC (LIST A B C))                                                              
CL-USER> (macroexpand-n 2 '(mac a b c))
(MAC (LIST (LIST A B C)))                                                       
T                                                                               
CL-USER> (macroexpand-n 3 '(mac a b c))
(MAC (LIST (LIST (LIST A B C))))                                                
T                                                                               
CL-USER> (macroexpand-loop '(mac a b c))
Please enter a number (or `q' to quit). 1  ; Raw numbers are input from me.
(MAC (LIST A B C))                                                              
2
(MAC (LIST (LIST A B C)))                                                       
3
(MAC (LIST (LIST (LIST A B C))))                                                
4
(MAC (LIST (LIST (LIST (LIST A B C)))))                                         
5
(MAC (LIST (LIST (LIST (LIST (LIST A B C))))))                                  
q
NIL                                                                             
CL-USER>

Re: Alternative (MACROEXPAND ...)

Posted: Sat Dec 31, 2011 10:20 pm
by claytontstanley
Here's a simpler implementation of macroexpand-n. This one doesn't require the let binding:

Code: Select all

(defun macroexpand-n (n body)
  "Return body macroexpanded n times"
  (if (= 0 n)
    body
    (macroexpand-n (1- n) (macroexpand-1 body))))