lispcommon-lispsbcl

Is there a standard/portable way to query if a symbol stands for a symbol macro?


As the title says, how do I (programmatically) check in a portable way (if possible) if a symbol stands for a symbol-macro?

CL-USER> (define-symbol-macro some-macro some)
SOME-MACRO
CL-USER> (macro-function 'some-macro)
NIL
CL-USER> (fboundp 'some-macro)
NIL

SBCL has sb-impl::info:

CL-USER> (describe 'sb-impl::info)
SB-INT:INFO
  [symbol]

INFO names a compiled function:
  Lambda-list: (CATEGORY KIND NAME)
  Declared type: (FUNCTION (T T T) (VALUES T T &OPTIONAL))
  Source file: SYS:SRC;COMPILER;GLOBALDB.LISP

INFO has a compiler-macro:
  Source file: SYS:SRC;COMPILER;EARLY-GLOBALDB.LISP

(SETF INFO) names a compiled function:
  Lambda-list: (NEW-VALUE CATEGORY KIND NAME)
  Declared type: (FUNCTION (T T T T) (VALUES T &OPTIONAL))
  Source file: SYS:SRC;COMPILER;GLOBALDB.LISP

(SETF INFO) has a compiler-macro:
  Source file: SYS:SRC;COMPILER;EARLY-GLOBALDB.LISP

Seems to work:

CL-USER> (sb-impl::info :variable :kind 'some-macro)
:MACRO
T

That is what they use in the source code for the 'describe' function (I founded by looking there).

Prior to that I was looking at clhs but didn't found anything. Have I missed it or is there no standard way? Is there some portable/"trivial" library that does it?

Edit:

After helpful answer, I have found a "trivial" wrapper for cltl2. It seems to be in quicklisp too, so we can have in addition to built-in 'special-operator-p':

(defun special-variable-p (symbol)
    (multiple-value-bind (info ignore1 ignore2)
        (variable-information symbol)
      (declare (ignore ignore1 ignore2))
      (eq :special info)))

(defun symbol-macro-p (symbol)
    (multiple-value-bind (info ignore1 ignore2)
        (variable-information symbol)
      (declare (ignore ignore1 ignore2))
      (eq :symbol-macro info)))

I actually prefer a generalized boolean, seems more useful, so I am using this:

(defun symbol-macro-p (symbol)
  "Return if SYMBOL stands for a symbol macro."
    (multiple-value-bind (info ignore1 ignore2)
        (variable-information symbol)
      (declare (ignore ignore1 ignore2))
      (when (eq :symbol-macro info)
        (multiple-value-bind (expansion ignored)
            (macroexpand symbol)
          (declare (ignore ignored))
          expansion))))

CL-USER> (symbol-macro-p 'some-macro)
SOME

Edit 2: The simplest and portable way, as shown by Gwang-Jin Kim: (macroexpand-1 'symbol)

(macroexpand-1 'some-macro) => (some T)

Solution

  • There is an easier way which works in all Common Lisp implementations, because we use macroexpand-1.

    We use the fact that macroexpand-1 returns T when expansion happened and NIL when not.

    Therefore, only if the call of the symbol itself is a symbol-macro, the expression (macroexpand-1 my-symbol-macro) would return T as the second value, since the macroexpansion would take place when calling the symbolmacro's name itself. In all other cases it would return NIL as the second value.

    So actually, macroexpand-1 when called with a symbol itself is somehow symbol-macro-p. But since macroexpand-1 does a lot more than just symbol-macro-p (it expands also composed macro calls), it might make sense to give this specific function an extra name (symbol-macro-p):

    (defun symbol-macro-p (symbol)
      "Return T as the second value, if symbol is a symbol-macro. The first value is then the expanded symbol. If NIL is the second value, the first value can be ignored."
      (macroexpand-1 symbol))
    

    Test

    (define-symbol-macro some-macro some)
    (defmacro bar (x) `,x)
    (defun foo (x) (+ x 1))
    (defparameter baz 1)
    
    (symbol-macro-p 'some-macro)  ;; => SOME   T
    (symbol-macro-p 'bar)         ;; => BAR    NIL
    (symbol-macro-p 'foo)         ;; => FOO    NIL
    (symbol-macro-p 'baz)         ;; => BAZ    NIL
    

    So, only in case of a symbol-macro, we get T as the second value. and in this case, we can take the first value as its expanded symbol. In all other cases, the answer of the second value is NIl.

    Boolean Return

    To simplify the function, we could also use:

    (defun symbol-macro-p (symbol)
      (multiple-value-bind (expansion result) (macroexpand-1 symbol)
        (declare (ignore expansion))
        result))
    

    Boolean Return and Expansion

    or we could also use:

    (defun symbol-macro-p (symbol)
      "Return T or NIL as the first value and as the second value,
       if symbol is a symbol-macro the expanded symbol, otherwise NIL."
      (multiple-value-bind (expansion result) (macroexpand-1 symbol)
        (if result
            (values result expansion)
            (values nil nil))))
    

    which returns the results:

    * (symbol-macro-p 'some-macro)
    T
    SOME
    * (symbol-macro-p 'bar)
    NIL
    NIL
    * (symbol-macro-p 'foo) 
    NIL
    NIL
    * (symbol-macro-p 'baz)
    NIL
    NIL