Alternative (MACROEXPAND ...)

Discussion of Common Lisp
Post Reply
pjstirling
Posts: 166
Joined: Sun Nov 28, 2010 4:21 pm

Alternative (MACROEXPAND ...)

Post by pjstirling » Thu Mar 17, 2011 1:07 am

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.

Warren Wilkinson
Posts: 117
Joined: Tue Aug 10, 2010 11:24 pm
Location: Calgary, Alberta
Contact:

Re: Alternative (MACROEXPAND ...)

Post by Warren Wilkinson » Thu Mar 17, 2011 12:49 pm

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.
Need an online wiki database? My Lisp startup http://www.formlis.com combines a wiki with forms and reports.

pjstirling
Posts: 166
Joined: Sun Nov 28, 2010 4:21 pm

Re: Alternative (MACROEXPAND ...)

Post by pjstirling » Sat Mar 19, 2011 6:44 am

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 :)

JohnGraham
Posts: 4
Joined: Thu Nov 05, 2009 2:54 pm

Re: Alternative (MACROEXPAND ...)

Post by JohnGraham » Wed Mar 23, 2011 3:41 am

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>

claytontstanley
Posts: 1
Joined: Sat Dec 31, 2011 10:17 pm

Re: Alternative (MACROEXPAND ...)

Post by claytontstanley » Sat Dec 31, 2011 10:20 pm

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))))

Post Reply