我对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"))))
对于非常具体和长篇文章感到抱歉,但我没有在没有描述所有内容的情况下如何解释问题。
提前谢谢你, 再见
答案 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但它按预期工作。 不过,我现在将尝试研究新的方法和策略。