Gustav Bertram
Gustav Bertram

Reputation: 14911

How can my macro take a variable instead of an unquoted list?

I'm building an alphametic solver, and I'd like to make a macro that substitutes numbers into a symbol template.

Here is a self-contained example:

(defparameter *symbol-positions* '(#\H #\T #\S #\R #\A #\U #\E #\O #\W #\N))

(defmacro word-to-number (symbols lst)
  `(tonumber (list ,@(loop for symbol in symbols
                           when (not (eql symbol #\ ))
                             collect `(nth ,(position symbol *symbol-positions*) ,lst )))))

(defparameter num '(0 1 2 3 4 5 6 7 8 9))
(defparameter east '(#\  #\E #\A #\S #\T))

The following call works:

(word-to-number (#\  #\E #\A #\S #\T) num)

But this one doesn't:

(word-to-number east num) ;=> The value EAST is not of type LIST

Is there a way I can modify the macro to take a variable for the SYMBOLS parameter? ,symbols doesn't work, and neither does `(,@symbols)

Upvotes: 2

Views: 54

Answers (1)

Sylwester
Sylwester

Reputation: 48745

When you do:

(word-to-number east num)

The macro expander gets called with the arguments being east and num. They won't be lists and number for you macro. Only for the resulting code will they be evaluated in a contex which yields values.

A macro is syntax transformation. eg.

(cond (p1 c1)
      (p2 c2)
      (t a))

; ==

(if p1
    c1
    (if p2 
        c2
        a))

It is regardless if p1 is (< a b) or my-boolean-value. A macro just places the expressions there without having to know what a, b or my-boolean-value is.

So tell me.. How should the expansion look with (word-to-number east num)? Perhaps it shouldn't be a macro at all? eg.

(defun word-to-number (symbols lst)
  (tonumber (loop :for symbol :in symbols
                  :when (not (eql symbol #\ ))
                  :collect (nth (position symbol *symbol-positions*) lst))))

UPDATE

(defmacro word-to-number (symbols lst)
  `(tonumber (loop :for symbol :in ,symbols
                   :with clst := ,lst
                   :when (not (eql symbol #\ ))
                   :collect (nth (position symbol *symbol-positions*) clst))))

You might notice that I'm storing lst in a variable clst and I do it after evaluating symbols. The reason is that when you expect arguments to be evaluated you expect them to be evaluated in order and only once unless repeated evaluation is a feature of the macro like loop does. Eg. this should only print "Oh, happy day" once:

(word-to-number (progn (princ "Oh") east) (progn (princ ", happy day!") num))

Upvotes: 4

Related Questions