Daniel
Daniel

Reputation: 6775

Lisp macro evaluating expressions when I do not want it to

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

Answers (4)

finnw
finnw

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

SK-logic
SK-logic

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

Rainer Joswig
Rainer Joswig

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

Vsevolod Dyomkin
Vsevolod Dyomkin

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

Related Questions