Search code examples
clojure

Clojure macros: Passing filtered map and arity to other macro based on condition


(defmacro action1 [] `(prn "action1" ~'start ~'etype1))

(defmacro block [bindings & body] 
  `(let [~@(mapcat (fn [[k v]] [(if (symbol? k) k (symbol (name k))) `'~v]) (cond
                     (map? bindings) bindings
                     (symbol? bindings) (var-get (resolve bindings))
                     :else (throw (Exception. "bindings must be map or symbol"))))]
       ~body))

(defmacro bar [ctx arity & expr]
      `(let [~'t1 "lv" ~'np (prn "bar_1st_let" '~ctx ~ctx '~arity ~arity '~arity(resolve (first '~arity)) )
            ] 
            (block ~ctx ;;~ctx = {start "s1" top "x"}
              (fn '~arity ;; '~arity = [etype1 cid1 id1 pl1]
                (let [~'__execonceresult 1]
                  (do ~@expr)     
                )
              )
            )
        )
  )

(defmacro foo_multi [metadata ctxv aritym & expr]
  `(let [~@(mapcat (fn [[k v]] [k `~v]) metadata) ~'np (prn "foo_multi_1st_let" '~aritym)] 
  (fn ~aritym 
      (for [~'ctx (filter #(= (% (some (fn [~'m] (if (= (name ~'m) "top") ~'m)) (keys %))) ~'etype) '~ctxv)]
        (do (prn "foo_multi_b4_case" ~'ctx ~'etype ~aritym)
        (case ~'etype
        "x"
          (let [[~'etype1 ~'cid1 ~'id1 ~'pl1] ~aritym ~'np (prn "foo_multi_2nd_let" ~'ctx ~'etype1 ~'cid1 ~'id1 ~'pl1)]
            (bar ~'ctx [~'etype1 ~'cid1 ~'id1 ~'pl1] ~@expr))
        "y"
          (let [[~'etype2 ~'cid2 ~'id2 ~'pl2] ~aritym]
            (bar ~'ctx [~'etype2 ~'cid2 ~'id2 ~'pl2] ~@expr))
        ))))))

(def foo (foo_multi { meta1 "m1" meta2 "m2" } [{start "s1" top "x"} 
  {start "s3" top "x"} {start "s2" top "y"}] [etype a1 a2 a3] (block {toc "c"} 
   (block {c1 "d"} (action1)) "end"))
   )

(let [myarr ["x" 100 200 {"p" 1 "q" 2}]] (apply foo myarr))

Unable to pass arity from bar macro to block macro and getting java.lang.NullPointerException. The rest of the code executes if I comment the block call from the bar macro.

(defmacro bar [ctx arity & expr]
      `(let [~'t1 "lv" ~'np (prn "bar_1st_let" '~ctx ~ctx '~arity ~arity '~arity(resolve (first '~arity)) )
            ] 
            (comment block ~ctx ;;~ctx = {start "s1" top "x"}
              (fn '~arity ;; etype specific ~arity eg: [etype1 cid1 id1 pl1]
                (let [~'__execonceresult 1]
                  (do ~@expr) ;; uses etype1     
                )
              )
            )
        )
  )

After commenting below is the output of the debug lines :

"foo_multi_1st_let" [etype a1 a2 a3]
"foo_multi_b4_case" {start "s1", top "x"} "x" ["x" 100 200 {"p" 1, "q" 2}]
"foo_multi_2nd_let" {start "s1", top "x"} "x" 100 200 {"p" 1, "q" 2}
"bar_1st_let" ctx {start "s1", top "x"} [etype1 cid1 id1 pl1] ["x" 100 200 {"p" 1, "q" 2}] [etype1 cid1 id1 pl1] nil
"foo_multi_b4_case" {start "s3", top "x"} "x" ["x" 100 200 {"p" 1, "q" 2}]
"foo_multi_2nd_let" {start "s3", top "x"} "x" 100 200 {"p" 1, "q" 2}
"bar_1st_let" ctx {start "s3", top "x"} [etype1 cid1 id1 pl1] ["x" 100 200 {"p" 1, "q" 2}] [etype1 cid1 id1 pl1] nil

As per the debug lines printed above, In the bar macro I am unable to resolve first arity symbol and it is printed nil (Don't know the reason why). The goal is to pass arity correctly from bar macro to the block macro and be able to access and print start and etype1 value in action1 macro.


Solution

  • as @I0st3d pointed out, this could be your solution after modifing bar definition and foo_multi will become like above solution my-multi-let.

    (defmacro action1 [] `(prn "action1" ~'start ~'etype))
    
    (defn named-type? [m] (when (= (name m) "top") m))
    
    (defmacro block [ctx & expr] 
      `(let [~@(mapcat (fn [[k v]] [k `~v]) ctx)] ~@expr))
    
    (defmacro bar [bindings & body]
      `(block ~bindings (if (= ~'top ~'etype) (do 
         ~@body))))
    
    (defmacro foo_multi [metadata bindings-list arity & body]
      (let [fns (->> bindings-list
           (map (fn [b] `(bar ~b ~arity ~@body))))
      ] `(block ~metadata (fn ~arity (do ~@fns)))
    ))
    
    (def foo (foo_multi {meta1 "m1" meta2 "m2"} [{start "s1" top "x"} 
      {start "s2" top "y"}] [etype a1 a2 a3] 
    (block {toc "c"} (block {c1 "d"} (action1)) "end")
    ))
    
    (let [myarr ["x" 100 200 {"p" 1 "q" 2}]] (apply foo myarr))
    

    In bar macro you'll get all arity params accessible, so you can create a map vars varying for etype specific symbols also if required. your (let [myarr ["x" 100 200 {"p" 1 "q" 2}]] (apply foo myarr)) will also work as expected.