Page 1 of 1

reader macro communicating with a compile-time macro

Posted: Wed Jul 30, 2008 10:52 am
by Harnon
So, i'm trying to make a certain macro that functions like this
Example:

Code: Select all

  (with-program-cases ((listp x) t)
               (loop for y = x then #?((cdr y) (1+ y)) until #?((null y) (> y 5))
                       do(print y)))
This should expand into something like this:

Code: Select all

    (cond ((listp x) (loop for y=x then (cdr y) until (null y) do(print y)))
                (t (loop for y=x then (1+ y) until (> y 5) do(print y))))

In other words, each 'case' in the argument to the with-program-cases macro has a slight different
body, which is given by a list of different choices which is prefixed by the #? reader macro.
Preferably, i would also want to be able to define the macro so i could have something like this
#?([for y = x] [for y = 1])
where the brackets would indicate that for y=x and for y=1 would be pasted into the appropriate place without surrounding parentheses. But that's for another time!

I figured i would implement this by making the reader macro #? append the appropriate data to two global variables
, *cases* and *case-vars*. The macro with-program-cases would then use this data to expand into an intermediate state given by the following (using the first example above):

Code: Select all

(COND ((LISTP X) 
            (SYMBOL-MACROLET ((#:G1757 (CDR X)) (#:G1761 (NULL Y))) 
                        (LOOP FOR Y = X THEN #:G1757 UNTIL #:G1761)))
           (T (SYMBOL-MACROLET ((#:G1757 (1+ Y)) (#:G1761 (> Y 5))) 
                         (LOOP FOR Y = X THEN #:G1757 UNTIL #:G1761))))
The problem occurs when there are multiple with-program-codes expanded at the same time.
First, all the reader macros are expanded. When the with-program-cases is macroexpanded,
it now sees all the data pertaining to all the cases within each with-program-cases body. In other words, it has
no clue which data belongs to itself. So, what i need, i think, is either a totally different way to do this whole thing :? ,
or some way to communicate between the reader macros and the with-program-cases macro that surrounds them.
Thix! :D

Re: reader macro communicating with a compile-time macro

Posted: Wed Jul 30, 2008 1:09 pm
by ramarren
Side effecting read macros are a bad idea. There is no guarantee that they will be executed once when expanding, and in general run into weird issues like the one you have here. What I would do if trying something like this is to make the read macro expand to some unexported symbol, and then walk the source tree in with-program-cases rebuilding the tree.

I think that in this case you can get away without a full code walker even, as this is just a source tree transformation, rather than syntax tree. This would make the #?([for y = x] [for y = 1]) mostly trivial as well, as you could just make it expand into a different marker symbol.

Re: reader macro communicating with a compile-time macro

Posted: Thu Jul 31, 2008 12:30 am
by danb
Harnon wrote:So, i'm trying to make a certain macro

each 'case' in the argument to the with-program-cases macro has a slight different
body, which is given by a list of different choices which is prefixed by the #? reader macro.

I figured i would implement this by making the reader macro #? append the appropriate data to two global variables

The problem occurs when there are multiple with-program-codes expanded at the same time.
Try to avoid using globals.
This seems to work:

Code: Select all

(set-dispatch-macro-character
   #\# #\?
   (lambda (stream char n)
     (declare (ignore char n))
     `(program-alternatives ,@(read stream t :eof t))))

(defun copy-nth-alternative-tree (tree n)
  (cond ((atom tree) tree)
        ((eq (car tree) 'program-alternatives)
         (nth n (cdr tree)))
        (t (mapcar (lambda (tree) (copy-nth-alternative-tree tree n)) tree))))

(defmacro with-program-cases (cases &body body)
  `(cond
    ,@(loop for n below (length cases)
            collect `(,(nth n cases) ,@(copy-nth-alternative-tree body n)))))

Re: reader macro communicating with a compile-time macro

Posted: Thu Jul 31, 2008 7:59 am
by Harnon
Wow! Thx, guys. I'm boggled by the simplicity (lol) compared to how hard i tried to make it.
In this case, maybe one constant variable would be good, such as

Code: Select all

(let ((program-alternatives (gensym)))
(set-dispatch-macro-character
   #\# #\?
   (lambda (stream char n)
     (declare (ignore char n))
     `(,program-alternatives ,@(read stream t :eof t))))

(defun copy-nth-alternative-tree (tree n)
  (cond ((atom tree) tree)
        ((equal (car tree) program-alternatives)
         (nth n (cdr tree)))
        (t (mapcar (lambda (tree) (copy-nth-alternative-tree tree n)) tree))))

(defmacro with-program-cases (cases &body body)
  `(cond
    ,@(loop for n below (length cases)
            collect `(,(nth n cases) ,@(copy-nth-alternative-tree body n))))))
Do you think this lexical variable would be fine?

Re: reader macro communicating with a compile-time macro

Posted: Thu Jul 31, 2008 12:23 pm
by danb
Harnon wrote:

Code: Select all

(let ((program-alternatives (gensym)))
  ...
Do you think this lexical variable would be fine?
I think so, as long as you make sure the function and the read macro are always (re)defined together.