how to pass quoted sexp to macro

196 views Asked by At

I have a function that replaces all instances of a symbol in a list:

(defun replace-symbol-in-sexp-fn (symbol-to-replace new-symbol sexp)
  (if (eq sexp nil)
      sexp
      (cons
       (if (listp (car sexp))
           (replace-symbol-in-sexp-fn symbol-to-replace new-symbol (car sexp))
           (if (eq (car sexp) symbol-to-replace)
               (setf (car sexp) new-symbol)
               (car sexp)))
       (replace-symbol-in-sexp-fn symbol-to-replace new-symbol (cdr sexp)))))

(defmacro replace-symbol-in-sexp (symbol-to-replace new-symbol sexp)
  `(replace-symbol-in-sexp-fn ,symbol-to-replace ,new-symbol ,sexp))

(macroexpand-1 (replace-symbol-in-sexp '+ '* (+ 2 3)))
; => TYPE-ERROR "The value 5 is not of type LIST" if sexp has comma,
; => UNBOUND-VARIABLE "The variable SEXP is unbound" if sexp has no comma

I'm getting either a type error or an undefined-variable error when attempting to evaluate the final expression, depending upon whether sexp is comma'd or not in the last line. I've tested and replace-symbol-in-sexp-fn works when given, say:

(replace-symbol-in-sexp-fn '+ '* '(+ 2 3)) ; => (* 2 3)

I'm trying to now produce this with a macro so that the sexp doesn't have to be quoted like '(+ 2 3), and so I can run replace-symbol-in-sexp-fn with arbitrary lisp code. Obviously, I could eval and pass in a sexp quoted to replace-symbol-in-sexp-fn, like:

(eval (replace-symbol-in-sexp-fn '+ '* '(+ 2 3))

But that's a clunky attempt to imitate macros, so I'd prefer to actually just use a macro. Is there a clean way to do what I'm trying to do with macros? What am I missing?

2

There are 2 answers

7
Rainer Joswig On BEST ANSWER

So you reimplemented the Common Lisp function nsubst. subst is the normal version and n indicates that it is the destructive version (non-consing).

Note that in portable Common Lisp it is not a good idea to modify literal data. The effects are undefined. Ignoring that for a while:

(macroexpand-1 (replace-symbol-in-sexp '+ '* (+ 2 3)))

But probably you wanted to macroexpand the expression and not the result? Probably should be:

(macroexpand-1 '(replace-symbol-in-sexp '+ '* (+ 2 3)))

But that macro makes no sense. The generated code is false, since the last argument does not evaluate to a list. The macro has to create useful code. As you see the last expression is unquoted, which make no sense.

CL-USER 14 > (macroexpand-1 '(replace-symbol-in-sexp '+ '* (+ 2 3)))
(REPLACE-SYMBOL-IN-SEXP-FN (QUOTE +) (QUOTE *) (+ 2 3))

Let's introduce a quote:

(defmacro replace-symbol-in-sexp (symbol-to-replace new-symbol sexp)
   `(replace-symbol-in-sexp-fn ,symbol-to-replace ,new-symbol ',sexp))

CL-USER 17 > (macroexpand-1 '(replace-symbol-in-sexp '+ '* (+ 2 3)))
(REPLACE-SYMBOL-IN-SEXP-FN (QUOTE +) (QUOTE *) (QUOTE (+ 2 3)))

Is that macro useful? I have my doubts.

1
Svante On

It seems that you do not want to expand to the function invocation, but use the function to expand your code. You should not quote it then:

(defmacro replace-symbol-in-sexp (symbol-to-replace new-symbol sexp)
  (replace-symbol-in-sexp-fn symbol-to-replace new-symbol sexp))

I have the impression that you are just trying to re-implement something like symbol-macrolet.