common-lisplisp-macrosbackquote

Quoting in macro-defining macro


I'm trying to write a macro that defines some helpers for struct-of-arrays data structure (based on this snippet). Inside that macro I define another macro that helps with traversing all of the slot values in struct. The thing is I can't make double unquoting work properly. Here's the code:

(defmacro defcomponent (name-and-options &body slots)
  (setf name-and-options (ensure-list name-and-options))
  (let ((struct (first name-and-options))
        (slot-names (iter (for s in slots)
                          (collecting
                           (ematch s
                             ((or (and name (symbol)
                                       (<> _ '*)
                                       (<> _ nil))
                                  (list* name _ (plist :type _ :read-only _)))
                              name))))))
    `(progn (defstruct ,name-and-options
              ;; some task-specific stuff omitted here
              )
            (defmacro ,(symbolicate 'with- struct) (components &rest body)
              `(loop
                 ,@',(iter (for s in slot-names)
                          (appending `(for ,s across (,(symbolicate struct '- s) components))))
                 do ,@body)))))

So for instance (defcomponent buzz x y) macroexpands to

(PROGN
 (DEFSTRUCT (BUZZ)
   X Y)  ;; details omitted
 (DEFMACRO WITH-BUZZ (COMPONENTS &REST BODY)
   `(LOOP ,@'(FOR X ACROSS (BUZZ-X COMPONENTS) FOR Y ACROSS (BUZZ-Y COMPONENTS))
          DO ,@BODY))

which kinda works, but I want to access components parameter of the internal with-buzz macro, i.e. something like this

(DEFMACRO WITH-BUZZ (COMPONENTS &REST BODY)
   `(LOOP FOR X ACROSS (BUZZ-X ,COMPONENTS) FOR Y ACROSS (BUZZ-Y ,COMPONENTS)
          DO ,@BODY))

How do I possibly acheive that? I've tried a lot of the combinations of , and ,@ to no avail.


Solution

  • Sometimes it helps not to work with backquote patterns. Then scope problems can be easier understood with the help of a compiler, which would warn about the usual variable scope problems.

    As a slightly simplified exercise, we will write a function, which generates code. The generated code is a macro definition, which itself generates code.

    (defun makeit (name slots)
      (labels ((symbolicate (pattern &rest things)
                 (intern (apply #'format nil pattern things)))
               (compute-for-clauses (slots)
                 (loop for s in slots
                       append (list ''for (list 'quote s)
                                    ''across (list 'list
                                                   (list 'quote
                                                         (symbolicate "~a-~a" name s))
                                                   'components)))))
    
        (list 'progn
              (list 'defmacro
                    (symbolicate "WITH-~a" name)
                    '(components &rest body)
                    (append '(list* 'loop)
                            (compute-for-clauses slots)
                            (list ''do 'body))))))
    

    Example

    CL-USER 51 > (pprint (makeit 'buzz '(x y)))
    
    (PROGN
      (DEFMACRO WITH-BUZZ (COMPONENTS &REST BODY)
        (LIST* 'LOOP
               'FOR
               'X
               'ACROSS
               (LIST 'BUZZ-X COMPONENTS)
               'FOR
               'Y
               'ACROSS
               (LIST 'BUZZ-Y COMPONENTS)
               'DO
               BODY)))
    
    CL-USER 52 > (eval *)
    NIL
    
    CL-USER 53 > (macroexpand-1 '(with-buzz a (+ 12) (+ 30)))
    (LOOP FOR X ACROSS (BUZZ-X A) FOR Y ACROSS (BUZZ-Y A) DO (+ 12) (+ 30))
    T