在clojure中修改Clojure源代码文件

时间:2011-08-17 18:08:34

标签: file-io clojure

我想知道是否可以将Clojure .clj源文件中包含的代码作为列表加载,而无需编译它。

如果我可以将.clj文件作为列表加载,我可以修改该列表并将其打印回同一个文件,然后再次加载。

(也许这是一个坏主意。)有谁知道这是否可能?

4 个答案:

答案 0 :(得分:7)

这不是一个坏主意,它是lisp的主要属性之一,代码就是数据。 您可以使用读取字符串将clj文件作为列表读取并将其写回来。


(ns tmp
  (:require [clojure.zip :as zip])
  (:use clojure.contrib.pprint))

(def some-var true)

;;stolen from http://nakkaya.com/2011/06/29/ferret-an-experimental-clojure-compiler/
(defn morph-form [tree pred f]
  (loop [loc (zip/seq-zip tree)]
    (if (zip/end? loc)
      (zip/root loc)
      (recur
       (zip/next
        (if (pred (zip/node loc))
          (zip/replace loc (f (zip/node loc)))
          loc))))))

(let [morphed (morph-form (read-string (str \( (slurp "test.clj")\)))
                          #(or (= 'true %)
                               (= 'false %))
                          (fn [v] (if (= 'true v)
                                   'false
                                   'true)))]
  (spit "test.clj"
        (with-out-str
          (doseq [f morphed]
            (pprint f)))))

这会读取自身并切换布尔值并将其写回。

答案 1 :(得分:1)

一个稍微简单的例子:

user=> (def a '(println (+ 1 1))) ; "'" escapes the form to prevent immediate evaluation
#'user/a
user=> (spit "test.code" a) ; write it to a file
nil

user=> (def from-file (read-string (slurp "test.code"))) ; read it from a file
#'user/from-file
user=> (def modified (clojure.walk/postwalk-replace {1 2} from-file)) ; modify the code
#'user/modified
user=> (spit "new.code" modified) ; write it back
nil
user=> (load-string (slurp "new.code")) ; check it worked!
4
nil

slurp为您提供字符串,read-string为您提供未评估的表单,load-string为您提供评估表单的结果。

答案 2 :(得分:0)

有一个名为 rewrite-clj 的库,可以使用这种类型的东西。

https://github.com/xsc/rewrite-clj

答案 3 :(得分:0)

没有任何库,您要做的就是

(with-open [reader (java.io.PushbackReader. 
                     (clojure.java.io/reader "src/my/file/ns_path.clj"))]
  (loop [forms []]
    (try 
      (recur (conj forms (read reader)))
      (catch Exception ex
        (println (.getMessage ex))
        forms))))

但是Clojure不允许recur中使用try,所以您可以这样做

(with-open [reader (java.io.PushbackReader. 
                     (clojure.java.io/reader "src/my/file/ns_path.clj"))]
  (loop [[ forms done? ] 
         [ []    false ]]
    (if done?
      forms
      (recur (try
               [(conj forms (read reader)) false]
               (catch Exception ex
                 (println (.getMessage ex))
                 [forms true]))))))

一旦EOF(文件结束)错误读取完成,您需要自己传播done?信号。我试图对loop绑定进行布局,以使它们清楚地上下匹配。

这将返回文件中所有表单的向量。从那里,您可以在表单上执行所需的任何转换,作为数据,然后再次将它们吐出/打印/写回到文件中。


read相关的法律免责声明

不要对您没有先编写或检查过的代码/数据运行clojure.core/read。有关可能出现问题的示例,请参见this ClojureDocs comment