Search code examples
macroscommon-lisplisp-macros

lisp macro to build a list of an expression and it's evaluation


I'm trying to write a macro in Common Lisp that takes any number of expressions and builds a list containing each expression followed by its evaluation in a single line. For example, if I name my macro as

(defmacro list-builder (&rest exp)
    ...)

and I run

(let ((X 1) (Y 2)) (list-builder (+ X Y) (- Y X) X))

I want it to return:

'((+ X Y) 3 (- Y X) 1 X 1)

The best I've been able to do so far is get a list of the expressions using the code

(defmacro list-builder (&rest exp)
  `',@`(',exp ,exp))

INPUT: (let ((X 1) (Y 2)) (list-builder (+ X Y) (+ Y X) X))
'((+ X Y) (+ Y X) X)

Solution

  • Strictly speaking, the macro itself cannot do that; what the macro must do is generate code in which the argument expressions are embedded in such a way that they are evaluated, and also in such a way that they are quoted.

    Given (list-builder (+ x y) (+ y x) x) we would like to generate this code: (list '(+ x y) (+ x y) '(+ y x) (+ y x) 'x x).

    We can split the macro into an top-level wrapper defined with defmacro and an expander function that does the bulk of the work of producing the list arguments; The macro's body just sticks the list symbol on it and returns it.

    Macro helper functions have to be wrapped with a little eval-when dance in Common Lisp to make sure they are available in all conceivable situations that the macro might be processed:

    (eval-when (:compile-toplevel :load-toplevel :execute)
      (defun list-builder-expander (exprs)
        (cond
          ((null exprs) nil)
          ((atom exprs) (error "list-builder: dotted syntax unsupported":))
          (t (list* `',(car exprs) (car exprs)
                    (list-builder-expander (cdr exprs)))))))
    
    (defmacro list-builder (&rest exprs)
      (cons 'list (list-builder-expander exprs)))
    

    A "slick" implementation, all in one defmacro, inside a single backquote expression, might go like this:

    (defmacro list-builder (&rest exprs)
      `(list ,@(mapcan (lambda (expr) (list `',expr expr)) exprs)))
    

    The "dotted syntax unsupported" check we implemented before now becomes an error out of mapcan.

    The lambda turns each expression E into the list ((quote E) E). mapcan catenates these lists together to form the arguments for list, which are then spliced into the (list ...) form with ,@.

    The form `',expr follows from applying the quote shorthand to `(quote ,expr).