I have this macro:
(defmacro if (test-form &body body)
(let ((test (gensym)))
`(let ((,test (cl:if (booleanp ,test-form)
(cl-truth ,test-form)
,test-form)))
(cl:if ,test
,@body))))
Using it results in this:
LO> (if (wordp (wordify ""))
(print 1)
(print 0))
NIL
(You can see, I work in a package lo
that makes:use
of common-lisp
but cl:if
is in the :shadow
. -- The function wordify
uses make-word
to create an instance of the coresponding defstruct
. The predicate booleanp
checks, whether a predicate's output is => TRUE, t
resp. => FALSE, nil
. cl-truth
returns the second value then.)
macroexpand-1
is not useful here. It returns => NIL, NIL
If I try to consider the possible output, I appreciate destructuring-bind
as a helpful tool:
(destructuring-bind (test-form &body body)
'((wordp (wordify "")) (print 1) (print 0))
(let ((test (gensym)))
`(let ((,test (cl:if (booleanp ,test-form)
(cl-truth ,test-form)
,test-form)))
(cl:if ,test
,@body))))
The output is:
(LET ((#:G698
(COMMON-LISP:IF (BOOLEANP (WORDP (WORDIFY "")))
(CL-TRUTH
(WORDP (WORDIFY "")))
(WORDP (WORDIFY "")))))
(COMMON-LISP:IF #:G698
(PRINT
1)
(PRINT
0)))
And the result is the desired:
1
; No value
(It's a custom print
that uses princ
with no return value.)
Can you tell me, why the macro behaves different?
Thank you so very much.
Just to add a maybe more appropriate version of the macro, but without the strange behaviour solved.
(defmacro if-c (test-form instructionlist1 &optional instructionlist2)
(let ((test (gensym)))
`(let (;; The general Logo testform will return => TRUE, T / FALSE, NIL
(,test (destructuring-bind (logo &optional cl)
(multiple-value-list ,test-form)
(cl:if (booleanp logo)
cl
(error "if doesn't like ~a as input"
logo)))))
(cl:if ,test
,instructionlist1
,instructionlist2))))
I forgot to mention: Yes, the strange behaviour is solved. Trivially, the macro above of package LC
was not expanded in package LO
but the one below instead:
(defmacro if (test-form &body body)
())
It was a left-over in LO
where I was going to define it, before I decided to move it to LC
.
MACROEXPAND-1
is actually sufficient to investigate macroexpansion in Common Lisp.
There is a possibility to use *MACROEXPAND-HOOK*
to let things happen during macroexpansion.
I was thinking of TRACE
for macros.
It is possible to print out the macroexpansions happening during a macro call. (And also count the depth of the expansions.)
(defun tracing-macroexpand-hook (expander form &optional env)
(format t "[expanding] ~S~%" form)
(let ((result (funcall expander form env)))
(format t "=> ~S~%" result)
result))
;; for convenience, let's define also a `with-macroexpansion-trace`
(defmacro with-macroexpansion-trace (&body body)
`(let ((*macroexpand-hook* #'tracing-macroexpand-hook))
,@body))
Now, you can define some macros which are called in layers:
(defmacro m1 (x)
`(m2 (+ ,x 1)))
(defmacro m2 (x)
`(m3 (* ,x 2)))
(defmacro m3 (x)
`(+ ,x 42))
And we can go:
(with-macroexpansion-trace
(macroexpand '(m1 5)))
Resulting in the output:
[expanding] (M1 5)
=> (M2 (+ 5 1))
[expanding] (M2 (+ 5 1))
=> (M3 (* (+ 5 1) 2))
[expanding] (M3 (* (+ 5 1) 2))
=> (+ (* (+ 5 1) 2) 42)
(+ (* (+ 5 1) 2) 42) ;
T
You can now also define a with-macroexpansion-result
which applies with-macroexpansion-trace
but at the end presents you also the evaluated result
after the macroexpansion - and counts the total macro calls and returns the result:
(defmacro with-macroexpansion-result (form &key (evaluate nil))
`(let ((*macroexpand-hook* #'tracing-macroexpand-hook))
(let ((expanded (macroexpand ',form)))
,(if evaluate
`(let ((result (eval expanded)))
(format t "~&[result] ~S~%" result)
result)
'expanded))))
Let's try it:
(with-macroexpansion-result (m1 5) :evaluate t)
;; printing out:
[expanding] (M1 5)
=> (M2 (+ 5 1))
[expanding] (M2 (+ 5 1))
=> (M3 (* (+ 5 1) 2))
[expanding] (M3 (* (+ 5 1) 2))
=> (+ (* (+ 5 1) 2) 42)
[result] 54
54
(defmacro with-macroexpansion-result (form &key (evaluate nil))
`(let ((macro-call-count 0))
(flet ((hook (expander f &optional env)
(incf macro-call-count)
(format t "~&[expanding] ~S~%" f)
(let ((result (funcall expander f env)))
(format t "=> ~S~%" result)
result)))
(let ((*macroexpand-hook* #'hook))
(let ((expanded (macroexpand ',form)))
(format t "~&[macro-calls-total] ~D~%" macro-call-count)
,(if evaluate
`(let ((result (eval expanded)))
(format t "~&[result] ~S~%" result)
result)
'expanded))))))
(with-macroexpansion-result (m1 5) :evaluate t)
[expanding] (M1 5)
=> (M2 (+ 5 1))
[expanding] (M2 (+ 5 1))
=> (M3 (* (+ 5 1) 2))
[expanding] (M3 (* (+ 5 1) 2))
=> (+ (* (+ 5 1) 2) 42)
[macro-calls-total] 3
[result] 54
54
I guess that is the tool you wanted to have, isn't it? Some kind of macro-trace.