Search code examples
common-lispreader-macro

Using reader macro characters inside a macro definition


I wrote the following code as part of a thin wrapper for LispWorks' COM package, in order to make accessing properties in COM objects look more like s-expressions:

;;; Create dispatch function for '#<' and '#>'.
(eval-when (:load-toplevel :compile-toplevel :execute)
  (defun com-dispatch-function (invoke-function)
    (lambda (stream sub-char infix)
      (declare (ignore sub-char infix))
      (destructuring-bind (thing object &rest args)
          (read stream)
        `(,invoke-function ,object (symbol-name ',thing) ,@args)))))


;;; COM 'get property' read macro.
(set-dispatch-macro-character
 #\# #\<
 (com-dispatch-function 'com::invoke-dispatch-get-property))

So now, instead of

(invoke-dispatch-get-property object "Property" argument)

I can write

#<(property object argument)

However when I try to use this syntax in a macro definition like so:

(defmacro something ((object) &body body)
  (let ((excel (gensym)))
    `(cclet* ((,excel #<(application ,object)))
      ,@))) 

the compiler will complain for using a comma outside a backquote.

I guess the reason for the error is that the reader macro function should co-operate with the backquote syntax somehow, but I can not figure out a way to do that. Any suggestions?

Thanks!


Solution

  • Having stated in a comment that this was not possible to do portably, here is why it must in fact be possible: if it was not it would not be possible to write a readmacro for #\( without relying on special implementation-specific magic.

    However one thing you need to do when implementing readmacros like this is to tell the reader that you are reading recursively. This allows things like #n= / #n# to work but it looks like it may also let the reader know that it might expect to see a comma without a surrounding backquote for LispWorks. That's probably done as a sanity check although I can't speak for the LW implementors.

    To do this you must be sure to call read as ‘(read s t nil t)` (or anyway make sure the fourth argument is true).

    This means the guts of your function should be

    (destructuring-bind (thing object &rest args) (read stream t nil t)
      `(,invoke-function ,object (symbol-name ',thing) ,@args))