Dan Passaro
Dan Passaro

Reputation: 4397

Common Lisp: How to use a macro within a macro?

I'm writing an application (a simple game) using cl-sdl2. cl-sdl2 includes a macro called WITH-EVENT-LOOP that can be used to start the SDL application with some event handlers.

Currently I use the macro like so:

(sdl2:with-event-loop (:method :poll)
  (:idle () (on-idle :renderer renderer
                     :tick-func tick-func
                     :render-func render-func))
  (:quit () t)

  ;; Input handlers below
  (:keydown (:keysym keysym)
            (funcall input-func :keydown :keysym keysym))
  (:keyup (:keysym keysym)
          (funcall input-func :keyup :keysym keysym)))

This code listing is small because I omitted several more input handlers. There are at least 6 more handlers defined following this same pattern, for handling mouse and game controller input events, so my actual code is longer and highly repetitive.

I can write a macro that generates these boilerplate forms:

(defmacro forward-evt-to (target evt &rest evt-args)
  `(,evt ,evt-args
         (funcall ,target ,evt ,@evt-args)))

This produces a list that can be understood by WITH-EVENT-LOOP:

> (macroexpand-1 '(forward-evt-to input-func :keydown :keysym keysym))
(:KEYDOWN (:KEYSYM KEYSYM) (FUNCALL INPUT-FUNC :KEYDOWN :KEYSYM KEYSYM))
T

But when I try to use that macro, I get a compiler error. For example this fails:

(sdl2:with-event-loop (:method :poll)
  (:idle () (on-idle :renderer renderer
                     :tick-func tick-func
                     :render-func render-func))
  (:quit () t)

  (forward-evt-to input-func :keydown :keysym keysym)
  (forward-evt-to input-func :keyup :keysym keysym))

WITH-EVENT-LOOP tries to compile the code without expanding it and gives the error

; The value
;      INPUT-FUNC
;    is not of type
;      LIST

Is there any way I can use my FORWARD-EVT-TO macro from inside the WITH-EVENT-LOOP macro? I can't/don't want to modify WITH-EVENT-LOOP directly.

Upvotes: 1

Views: 85

Answers (1)

Rainer Joswig
Rainer Joswig

Reputation: 139411

One way would be to write a new macro, which expands into your target form.

Sketch of an example:

CL-USER 8 > (defmacro with-event-loop-1 (foo &body clauses &environment env)
              `(with-event-loop ,foo
                                ,(loop for clause in clauses
                                       when (keywordp (first clause))
                                         collect clause
                                       else
                                         collect (macroexpand-1 clause env))))
WITH-EVENT-LOOP-1

CL-USER 9 > (pprint
             (macroexpand
              '(with-event-loop-1 (:method :poll)
                 (:idle () (on-idle :renderer renderer
                                    :tick-func tick-func
                                    :render-func render-func))
                 (:quit () t)
                 
                 (forward-evt-to input-func :keydown :keysym keysym)
                 (forward-evt-to input-func :keyup :keysym keysym))))

(WITH-EVENT-LOOP (:METHOD :POLL)
  ((:IDLE NIL (ON-IDLE :RENDERER RENDERER
                       :TICK-FUNC TICK-FUNC
                       :RENDER-FUNC RENDER-FUNC))
  (:QUIT NIL T)
  (:KEYDOWN (:KEYSYM KEYSYM) (FUNCALL INPUT-FUNC :KEYDOWN :KEYSYM KEYSYM))
  (:KEYUP (:KEYSYM KEYSYM) (FUNCALL INPUT-FUNC :KEYUP :KEYSYM KEYSYM))))

Upvotes: 3

Related Questions