Clojure宏嵌套。的NullPointerException

时间:2017-03-20 16:07:59

标签: clojure macros

我对Clojure很新,我认为,编程一般,但我最终试图编写某种DSL来生成PDF文件(使用PDFBox,一个java库)。 我希望最终语法看起来像这样:

(document {:name "test" :bleed "22mm" ...} 
  (page {:height "560mm" ...) (text {:color "red"} "blabla") (line-break))

所以事实是每个表达式都是一个宏,它会在eval时间执行一些操作+在第一个参数之后递归读取每个表达式,并使用conj'ed选项作为其第一个参数进行扩展。目前,例如它看起来像这样:

   (defmacro document
      [{:keys [name] :as options}
       & body]
      `(let [~'pdoc ~'(PDDocument.)]
         (do
           ~@(unfold (conj options {:pdoc 'pdoc}) ~body)
           (doto ~'pdoc
             (...)))

并展开宏(一个扩展参数):

(defmacro unfold
  [options & body]
  (loop [
         [f & fs] body
         opts options
         output []]
    (if (empty? f)
      output
      (recur fs
             (conj opts (second f))
             (conj output `(~(first f) ~(conj opts (second f)))))
      )))

问题在于我无法让它们一起工作。经过一些调试后,我仍然得到java.lang.NullPointerException:

我感觉问题来自unfold输出到document的方式,但我完全无法了解嵌套时宏的反应。当我使用macroexpand时,嵌套宏永远不会扩展,我不知道它是否是预期的,或者我的编程错误。无论如何,何时

(macroexpand '(unfold {...} (document) (document)))

我得到了

[(document {...}) (document {...})]

,在我目前的理解中,似乎没问题,因为我之后使用它unquote-splice。但我显然遗漏了一些东西。

---------编辑-----------

对于非常无用的

(document {:name "hey"}} (document {:name "ho"}))

我想以((doto ...)为例)结束:

(let [pdoc (.PDDocument)]
  (do
    (let [pdoc (.PDDocument)]
      (do
        (doto pdoc .save (str "ho" ".pdf"))))
    (doto pdoc .save (str "hey" ".pdf"))))

对于非常具体和长篇文章感到抱歉,但我没有在没有描述所有内容的情况下如何解释问题。

提前谢谢你, 再见

2 个答案:

答案 0 :(得分:0)

我不确定这会解决问题,只是提示一些:

  • 对于let绑定,请使用foo#而不是~'foo来避免命名冲突。最后一个尖端产生一个随机的机器符号,因此你的范围没有隐藏的错误。
  • 尝试使用(macroexpand '(document {...}))扩展您的宏定义,以查看内部的内容。您可以在REPL中重新评估结果列表以检查错误。
  • 最后,尽量避免使用宏。首先尝试使用普通函数实现您的想法。可能就够了。

答案 1 :(得分:0)

感谢各位的帮助和建议。 现在,我努力做到这样:

(defn unfold
  [options body]
  (loop [
     [f & fs] body
     opts options
     output []]
    (if (empty? f)
      output
      (recur fs
          (conj opts (second f))
          (conj output (list (first f) (conj opts (second f)))))))))

(defmacro document
  [{:keys [name] :as options}
  & body]
  `(let [~'pdoc ~'(PDDocument.)]
    (do
      ~@(map macroexpand
          (unfold (conj options {:pdoc 'pdoc}) body))
      (doto ~'pdoc
            (...)))))

感觉有点hacky但它​​按预期工作。 不过,我现在将尝试研究新的方法和策略。