Search code examples
macroscommon-lispeval

Evaluating a macro when form is read from a string


I have a macro with-voice:

(defmacro with-voice (tag body)
  `(format nil "<span class=\"~a\">~%~a~%</span>" ',tag ,body))

which emits some text surrounded by a tag with some class. I know there are great libraries like CL-WHO - but I just need something tiny...

CL-USER> (with-voice narrator "foo")
"<span class=\"NARRATOR\">
foo
</span>"

which is is the desired outcome

I would like to be able to do this from a string

(let ((s (read-from-string "(with-voice narrator \"foo\")")))
  (print (eval s)))

This works:

CL-USER> (let ((s (read-from-string "(with-voice narrator \"foo\")")))
  (print (eval s)))

"<span class=\"NARRATOR\">
foo
</span>" 
"<span class=\"NARRATOR\">
foo
</span>

but it has the dreaded eval. I've tried to get this working using macros and lambdas, but I can't get it working.

I'd be grateful for some help

thanks!


Solution

  • You could funcall the symbol you expect in a certain position of the read form:

    (destructuring-bind (operator class text)
        (read-from-string "(with-voice narrator \"foo\")")
      (funcall operator class text))
    

    Or even just expect those forms to be funcallable:

    (apply #'funcall (read-from-string "(with-voice narrator \"foo\")"))
    

    If you have more different shapes of data, you might want to match those, e. g. using optima, or using dispatch by eql specializer. This can also help with validation if the read input might contain harmful intent.