Build macro result with loops

90 Views Asked by At

I'm building a macro that should be called like this:

(myMacro MyController something otherthing
  (defn onFoo [this event] 
    (println "ok"))

  (defn onBar [this event] 
    (println "ok"))
)

After the first three parameters I want to be able to pass a few functions that should be used to build the function definitions in the (definterface and the (deftype part of the macro.

The result of the above call should be this:

(definterface IMyController
  (^void onFoo [^javafx.event.ActionEvent event])
  (^void onBar [^javafx.event.ActionEvent event])
 )

(deftype MyController []
  IHandler (^{javafx.fxml.FXML {}} onFoo [this event] (println "ok"))
  IHandler (^{javafx.fxml.FXML {}} onBar [this event] (println "ok"))
 )

Im very new to Clojure but the hand build implementation of the controller for the FXML file is already working, I just want to simplify it with a macro but I wasn't able to find any help on how do this kind of loop inside a macro definition.


UPDATE

The macro is almost done, and is already running successfully.

(defmacro viewHandler [className & fn-defs]
  (def interface (symbol (join ["I" className])))
 `(do
    (definterface ~interface
      ~@(for [curr-fn fn-defs]
          `(~(second curr-fn) [~'event])
      ))
    (deftype ~className []
      ~interface
      ~@(for [curr-fn fn-defs]
          (rest curr-fn))
    ))
 )

Called by:

(viewHandler Bar  
     (defn onFoo [this event] (println "ok-1"))
     (defn onBar [this event] (println "ok-2"))
  )

But I still can't do the type-hints for the java method annotations.

1

There are 1 best solutions below

1
On BEST ANSWER

Start off with something simple:

(defmacro looper [ifc & fn-names]
 `(do
    ~@(for [curr-fn fn-names]
       [curr-fn] )))
(println (macroexpand-1 
 '(looper IFC fun1 fun2 fun3)))

;=>  (do [fun1] [fun2] [fun3])

the backquote (backtick?) starts an inline code template. The ~@ turns off the template part and starts live code execution (try substituting ~ instead of ~@ to see the difference - an extra layer of parentheses).

But, the code we want to output is more like (fun1 [] ...), which is literal code we DON'T want to execute. So, that must be wrapped inside of another syntax-quote/template, but we need another ~ to make the "curr-fn" be "live code" again:

(defmacro looper2 [ifc & fn-defs]
 `(do
    (definterface ~ifc
      ~@(for [curr-fn fn-defs]
         `(~(second curr-fn) [~'event] )))
    (deftype ~ifc []
      ~@(for [curr-fn fn-defs]
         `(IHandler ( ~@(rest curr-fn) [] ))))
    ))
(newline)
(pprint (macroexpand-1 
 '(looper2 MyController 
     (defn fun1 [this event] (println "ok-1"))
     (defn fun2 [this event] (println "ok-2"))
  )))

Result is:

(do
  (clojure.core/definterface MyController 
    (fun1 [event]) 
    (fun2 [event]))
  (clojure.core/deftype
    MyController
    []
    (basic.t1/IHandler (fun1 [this event] (println "ok-1") []))
    (basic.t1/IHandler (fun2 [this event] (println "ok-2") []))))

We need the outer (do ...) form since we are outputting both a (definterface ...) and a (deftype ...) forms.

I'll leave it to you to figure out the type-hints, etc.