lispcommon-lisplisp-macros

Common Lisp Execute expression as parameter in macro


So using common lisp, I want to be able to do something of the sorts of:

(defmacro foo (count &rest someExpression)
    `(do
        ((,count 0 (+ ,count 1)))
        ((= ,count 5) T)
        `(eval ,someExpression)
    )
)
(foo (print 1) temp)

With the result of it printing 1 5 times. I do not want to simply call (print 1) directly, but by passing the expression through a macro parameter and calling it via the macro. In other words, the macro foo should handle any expression(s) as input and run it. This case does not seem to work.

Edited to clarify an explicit script and intended function.


Solution

  • Starting with your recent version, which is at least a reasonable candidate for a macro unlike the older one:

    (defmacro foo (someExpression count-var)
      `(do ((,count-var 0 (+ ,count 1)))
           ((= ,count-var 5) T)
         `(eval (,someExpression))))
    

    Well what is the expansion of (foo (print 1) c)?

    (foo (print 1) x)
     -> (do ((x 0 (+ x 1))) ((= x 5) t)
          `(eval (,someexpression)))
    

    Well, that's a disaster: what is that nested backquote doing? Let's just remove it:

    (defmacro foo (someExpression count-var)
      `(do ((,count-var 0 (+ ,count 1)))
           ((= ,count-var 5) T)
         (eval (,someExpression))))
    
    (foo (print 1) x)
     -> (do ((x 0 (+ x 1))) ((= x 5) t)
          (eval ((print 1))))
    

    That's less disastrous, but the eval form is entirely bogus. We can make that 'work' by changing it to be at least syntactically legal:

    (defmacro foo (someExpression count)
      `(do ((,count 0 (+ ,count 1)))
           ((= ,count 5) T)
         (eval ,someExpression)))
    

    And now

    (foo (print 1) x)
     -> (do ((x 0 (+ x 1))) ((= x 5) t)
          (eval (print 1)))
    

    And this will 'work' but it will work purely by coincidence: because (print 1) returns 1 and the value of 1 is 1.

    (foo (print 'foo) x)
      -> (do ((x 0 (+ x 1))) ((= x 5) t)
           (eval (print 'foo)))
    

    and that's a run-time error.

    But ... why are you using eval? eval is a terrible, terrible solution to almost any problem you can think of, unless the solution to the problem is called 'code injection attack', and in this case it's not just terrible: it's wrong. So we just remove it.

    (defmacro foo (someExpression count)
      `(do ((,count 0 (+ ,count 1)))
           ((= ,count 5) T)
         ,someExpression))
    

    And now

    (foo (print 'foo) x)
     -> (do ((x 0 (+ x 1))) ((= x 5) t)
          (print 'foo))
    

    Which looks like the code transformation we want. So, finally:

    > (foo (print 'foo) x)
    
    foo 
    foo 
    foo 
    foo 
    foo 
    t
    

    Which is, finally, fine. And this works:

    > (foo (print x) x)
    
    0 
    1 
    2 
    3 
    4 
    t
    

    As with yet another edit to the question it probably is more useful to put the variable name first and allow a bunch of expressions:

    (defmacro foo (count-var &body forms)
      `(do ((,count-var 0 (+ ,count-var 1)))
           ((= ,count-var 5))
         ,@forms))
    

    This will now allow multiple expressions in the body. And we could go further: we could allow it to specify the number of iterations and the return value`:

    (defmacro foo ((count-var &optional (count 1) (value 'nil)) &body forms)
      `(do ((,count-var 0 (1+ ,count-var)))
           ((= ,count-var ,count) ,value)
         ,@forms))
    

    And now

    > (foo (x 2)
        (print x)
        (print (* x 2)))
    
    0 
    0 
    1 
    2
    nil
    

    Well, the name of this macro is dotimes of course.