我正在从clojure函数生成emacs elisp代码。我最初开始使用defmacro,但我意识到,因为我要跨平台并且必须手动将代码评估到elisp环境中,所以我可以轻松地使用标准的clojure函数。但基本上我正在做的事情非常宏观。
我这样做是因为我的目标是创建一个DSL,我将在其中生成elisp,clojure / java,clojurescript / javascript,甚至haskell中的代码。
我的“宏”如下所示:
(defn vt-fun-3 []
(let [hlq "vt"]
(let [
f0 'list
f1 '(quote (defun vt-inc (n) (+ n 1)))
f2 '(quote (ert-deftest vt-inc-test () (should (= (vt-inc 7) 8))))]
`(~f0 ~f1 ~f2)
)))
这将生成两个函数定义的列表 - 生成的elisp defun和单元测试:
(list (quote (defun vt-inc (n) (+ n 1))) (quote (ert-deftest vt-inc-test () (should (= (vt-inc 7) 8)))))
然后从emacs暂存缓冲区,我利用clomacs https://github.com/clojure-emacs/clomacs导入到elisp环境中:
(clomacs-defun vt-fun-3 casc-gen.core/vt-fun-3)
(progn
(eval (nth 0 (eval (read (vt-fun-3)))))
(eval (nth 1 (eval (read (vt-fun-3))))))
然后我可以运行该功能和单元测试:
(vt-inc 4)
--> 5
(ert "vt-inc-test")
--> t
注意:与所有宏一样,语法引用和转义非常脆弱。我花了一段时间才弄清楚在elisp中正确使用eval的正确方法(整个“(quote(list ..)”前缀)。
无论如何,正如第一个“let”中“hlq”(高级限定符)的存在所暗示的那样,我想用这个hlq为任何生成的符号添加前缀而不是硬编码。
不幸的是,当我在“f1”上使用标准引号和转义时,例如:
f1 '(quote (defun ~hlq -inc (n) (+ n 1)))
这会产生:
(list (quote (defun (clojure.core/unquote hlq) -inc (n) (+ n 1)))
(quote (ert-deftest vt-inc-test () (should (= (vt-inc 7) 8)))))
换句话说,它将'clojure.core / unquote'替换为“〜”,这不是我想要的。
clojure语法back-quote:
f1 `(quote (defun ~hlq -inc (n) (+ n 1)))
没有这个问题:
(list (quote (casc-gen.core/defun vt casc-gen.core/-inc (casc-gen.core/n) (clojure.core/+ casc-gen.core/n 1))) (quote (ert-deftest vt-inc-test () (should (= (vt-inc 7) 8)))))
它正确地逃脱并插入“vt”,因为我想要(我仍然需要努力连接到名称的词干,但我并不担心这一点。)
问题解决了吧?不幸的是语法引用完全符合所有符号,我不想要,因为代码将在elisp下运行。
有没有办法在使用语法引用时关闭符号限定(返回勾号)?
在我看来,语法引用比标准引用更“有能力”。这是真的?或者,你可以通过欺骗,总是使标准引用的行为与语法引用相同吗?如果你不能用语法引用来关闭资格,我怎么能用标准引用呢?我会尝试以defmacro的形式获得任何收益吗?
最糟糕的情况是我必须在生成的elisp上运行正则表达式并手动删除任何资格。
答案 0 :(得分:3)
使用语法引用时,无法“关闭”符号限定。但是你可以这样做:
(let [hlq 'vt] `(~'quote (~'defun ~hlq ~'-inc (~'n) (~'+ ~'n 1))))
这无疑是相当繁琐的。没有语法引用的等价物是:
(let [hlq 'vt] (list 'quote (list 'defun hlq '-inc '(n) '(+ n 1))))
然而,当使用标准quote
为整个表单添加前缀时,无法获得所需的输出。
至于使用defmacro
的问题,据我了解你的意图,我认为你不会通过使用宏获得任何收益。
答案 1 :(得分:1)
根据justncon的输入,这是我的最终解决方案。我必须做一些额外的格式化才能在函数名称上获得字符串concat,但一切都非常像他推荐的那样:
(defn vt-gen-4 []
(let [hlq 'vt]
(let [
f1 `(~'quote (~'defun ~(symbol (str hlq "-inc")) (~'n) (~'+ ~'n 1)))
f2 `(~'quote (~'defun ~(symbol (str hlq "-inc-test")) () (~'should (~'= (~(symbol (str hlq "-inc")) 7) 8))))
]
`(~'list ~f1 ~f2))))
我学到了什么:
语法引用是要走的路,你只需要知道如何在元素级别控制不引用。
〜'(代字号)是我的朋友。在语法引用表达式中,如果在函数或var之前指定〜',它将按指定的方式传递给调用者。
取表达式(+ 1 1)
以下是基于各种转义级别在语法引用表达式中扩展此表达式的概要:
(defn vt-foo []
(println "(+ 1 1) -> " `(+ 1 1)) --> (clojure.core/+ 1 1)
(println "~(+ 1 1) -> " `~(+ 1 1)) --> 2
(println "~'(+ 1 1) -> " `~'(+ 1 1)) --> (+ 1 1)
)
最后一行是我想要的。第一行是我得到的。
;; this works
f1 `(quote (defun ~(str hlq "-inc") ~hlq (n) (+ n 1)))
;; doesn't work if you escape the hlq:
f1 `(quote (defun ~(str ~hlq "-inc") ~hlq (n) (+ n 1)))
我想逃跑会逃离你逃跑的单位内的一切。通常你会转义原子(如字符串或符号),但如果它是一个列表,那么列表中的所有内容也会自动转义,所以不要双重转义。
4)FWIW,在我得到最终答案之前,我结束了写一个正则表达式解决方案。这绝对不是那么好:
(defn vt-gen-3 []
(let [hlq "vt"]
(let
[
f0 'list
f1 `(quote (defun ~(symbol (str hlq "-inc")) (n) (+ n 1)))
f2 '(quote (ert-deftest vt-inc-test () (should (= (vt-inc 7) 8))))
]
`(~f0 ~f1 ~f2)
))
)
;; this strips out any qualifiers like "casc-gen.core/"
(defn vt-gen-3-regex []
(clojure.string/replace (str (vt-gen-3)) #"([\( ])([a-zA-Z0-9-\.]+\/)" "$1" ))