在Clojure字符串中执行函数?

在Clojure字符串中执行函数?,clojure,Clojure,我正在做一个静态站点生成器项目,有一次我得到了一个可以操作的站点的字符串表示形式(hiccup格式)。我将该字符串转换为映射: (clojure.edn/read-string page) 在这一点上,我的页面将类似于: { :header { :title "Index" :meta-desc "Indx Page"} :page [:div.Page.Home !!+Do-Math+

我正在做一个静态站点生成器项目,有一次我得到了一个可以操作的站点的字符串表示形式(hiccup格式)。我将该字符串转换为映射:

(clojure.edn/read-string page)
在这一点上,我的页面将类似于:

{ :header {
           :title "Index"
           :meta-desc "Indx Page"}
  :page [:div.Page.Home
          !!+Do-Math+!! ; This is the core of the question
          [:p (do-math 2 2)] ; This is also an option to replace the above line
          [:div.Home-primary-image-wrapper]
          ;More HTML stuff here
        ]
}
然后,正如上面两行所述,目标是允许在稍后呈现html之前执行特定代码

我的第一个想法是嵌入特定字符串,然后用函数值替换这些字符串:

(defn do-math []
  (str [:p (+ 2 2)]))

(clojure.edn/read-string (clojure.string/replace page "!!+Do-Math+!!" (do-math)))
我遇到的问题是,我希望能够将值嵌入到字符串中,以做更多有用的事情

我想完成的一个非工作示例

我们有这个字符串
+做数学2+
将转到上述函数的修改版本,需要两个参数:

(defn do-math [val val2]
      (str [:p (+ val val2)]))
我遇到的问题是,我不确定如何可靠地替换那样的字符串,也无法将被替换的字符串值传递到进行替换的函数中。我不确定那条路线是否可行

另一个选项是直接执行映射中的函数,这些函数是在呈现html之前返回的。在上面的例子中,我有:

[:p (do-math 2 2)]
这会给我同样的结果。是否可以通过map/vector查找这些函数调用的实例并在当前名称空间中执行它们,如果可以,那么上面的string replace方法是否是一个更好的选项?

您可以使用 要遍历任意数据结构,请使用修改的版本替换任何所需的项

如果不想更改,请记住返回原始项目

代码通常如下所示:

(walk/postwalk
  (fn [item]
    (if <desired item>
      <modify item>
      item  ; original item 
    ))
  <some data structure> 
)
(行走/后行走)
(fn[项目]
(如果
项目;原始项目
))
)

请参阅,尤其是Clojure备忘单。

我将定义一种特殊语法,用于可以评估的内容,然后使用
postwark
遍历树。例如,您可以确定要计算的所有内容都是以命名空间关键字
::eval
开头的向量。例如,您可以拥有以下数据:

(def test-data
  [:div.Page.Home
   [:p [::eval
        :add
        [::eval :mul 3 3]
        [::eval :mul 4 4]]]
   [:div.Home-primary-image-wrapper]])
满足此谓词的树节点称为可计算的节点:

(defn evaluable? [x]
  (and (vector? x)
       (= ::eval (first x))))
(defn evaluate-tree-nodes [tree evaluator-fn]
  (clojure.walk/postwalk
   #(if (evaluable? %)
      (apply evaluator-fn (rest %))
      %)
   tree))
以下是遍历树并计算要计算的节点的函数:

(defn evaluable? [x]
  (and (vector? x)
       (= ::eval (first x))))
(defn evaluate-tree-nodes [tree evaluator-fn]
  (clojure.walk/postwalk
   #(if (evaluable? %)
      (apply evaluator-fn (rest %))
      %)
   tree))
这是一个求值函数,您可以根据自己的喜好定义它来执行求值。例如,您可以决定只支持两个操作,
:mul
:add

(defn my-evaluator [f & args]
  (case f
    :add (apply + args)
    :mul (apply * args)))
你可以这样使用它:

(evaluate-tree-nodes test-data my-evaluator)
;; => [:div.Page.Home [:p 25] [:div.Home-primary-image-wrapper]]

的确,您可以做一些更奇特的事情,尝试将表达式中的符号映射到当前名称空间中定义的函数,但如果不小心,可能会重新实现Lisp。这也可能带来其他复杂性。上述解决方案只是提示您可以做些什么,您可以扩展它。

警告:很容易在无意中为要执行的任意代码创建开口。EDN源字符串应该是可信的,或者评估器应该特别小心。有关沙箱的示例,请参见clojail。