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