在宏中使用clojure语法引用时是否可以关闭符号限定?

时间:2015-03-06 01:40:34

标签: emacs clojure elisp

我正在从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上运行正则表达式并手动删除任何资格。

2 个答案:

答案 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))))

我学到了什么:

  1. 语法引用是要走的路,你只需要知道如何在元素级别控制不引用。

  2. 〜'(代字号)是我的朋友。在语法引用表达式中,如果在函数或var之前指定〜',它将按指定的方式传递给调用者。

  3. 取表达式(+ 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)
      )
    

    最后一行是我想要的。第一行是我得到的。

    1. 如果您转义某个函数,请执行 not 转义要转义的任何参数。例如,我们想要 在宏扩展时调用“str”函数并将变量“hlq”扩展为它的值'vt:
    2. 
          ;; 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" ))
      
      1. 宏观扩张非常微妙,需要大量练习。