Reputation: 6775
I am trying to write a macro in lisp that returns the nth expression passed into it and only evaluates that expression. For example:
(let ((n 2))
(nth-expr n (/ 1 0) (+ 1 2) (/ 1 0)))
should return 3. I am getting a divide by 0 error. My macro definition is as follows:
(defmacro nth-expr (n &rest expressions)
(let ((i (gensym))
(expr (gensym)))
`(do ((,i 1 (1+ ,i))
(,expr ,expressions (cdr ,expr)))
((= ,i ,n) (car ,expr)))))
Any idea what I'm doing wrong? Thanks.
EDIT:
Thanks to @Vsevolod Dyomkin for helping me with the above part. Now there is one more problem. When I try to do
(let ((n 3) (x "win") (y "lose"))
(nth-expr n (princ x) (princ x) (princ y) (princ x)))
I am getting the error Error: Attempt to take the value of the unbound variable 'Y'
.
My updated code looks like this:
(defmacro nth-expr (n &rest expressions)
(let ((i (gensym))
(expr (gensym))
(num (gensym)))
`(let ((,num (eval ,n)))
(do ((,i 1 (1+ ,i))
(,expr ',expressions (cdr ,expr)))
((= ,i ,num) (eval (car ,expr)))))))
Upvotes: 5
Views: 419
Reputation: 48659
The error in your second example is that EVAL
cannot see your lexically-bound N
, X
and Y
variables.
From the CLHS for EVAL
:
Evaluates form in the current dynamic environment and the null lexical environment.
It would work if you declared X
and Y
as special:
(let ((n 3) (x "win") (y "lose"))
(declare (special x y))
(nth-expr n (princ x) (princ x) (princ y) (princ x)))
but this is still not as good as the CASE
solution suggested by SK-logic.
Upvotes: 0
Reputation: 9725
You do not need eval
here, and no need in storing expressions in a list.
A proper implementation is following:
(defmacro nth-expr (n &rest expressions)
`(case ,n
,@(loop for e in expressions
for n from 1
collect
`((,n) ,e))))
Your example expands as:
(CASE N ((1) (/ 1 0)) ((2) (+ 1 2)) ((3) (/ 1 0)))
Upvotes: 4
Reputation: 139411
The main thing is to FIRST come up with the expansion.
What should the working code look like? The code that the macro usage would expand to?
Then you write the macro to create such code.
Make sure that you don't eval any of the supplied code in the macro.
A simple useful expansion target for your problem is a CASE form.
(case n
(0 (do-this))
(1 (do-that))
(2 (do-something-else)))
Now it should be easy to write a macro which expands (nth-expr n (/ 1 0) (+ 1 2) (/ 1 0))
into a CASE form...
Upvotes: 5
Reputation: 9451
You've got to qoute
,expressions
, like this:
(defmacro nth-expr (n &rest expressions)
(let ((i (gensym))
(expr (gensym)))
`(do ((,i 1 (1+ ,i))
(,expr ',expressions (cdr ,expr)))
((= ,i ,n) (car ,expr)))))
Otherwise, what you'll get is this - the expressions
list is inserted as is:
CL-USER> (let ((n 2))
(macroexpand-1 '(nth-expr n (/ 1 0) (+ 1 2) (/ 1 0))))
(DO ((#:G864 1 (1+ #:G864))
(#:G865 ((/ 1 0) (+ 1 2) (/ 1 0)) (CDR #:G865)))
((= #:G864 N) (CAR #:G865)))
Upvotes: 1