【发布时间】:2011-08-17 18:08:33
【问题描述】:
我想知道是否可以在不编译的情况下将 Clojure .clj 源文件中包含的代码加载为列表。
如果我可以将 .clj 文件加载为列表,我可以修改该列表并将其漂亮地打印回同一个文件,然后可以再次加载。
(也许这是个坏主意。)有人知道这是否可能吗?
【问题讨论】:
-
这不是一个坏主意。它可用于代码分析和重构等。
我想知道是否可以在不编译的情况下将 Clojure .clj 源文件中包含的代码加载为列表。
如果我可以将 .clj 文件加载为列表,我可以修改该列表并将其漂亮地打印回同一个文件,然后可以再次加载。
(也许这是个坏主意。)有人知道这是否可能吗?
【问题讨论】:
这不是一个坏主意,它是 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)))))
这会读取自身并切换布尔值并将其写回。
【讨论】:
read 直到文件被使用,这样更干净、更健壮。
一个稍微简单的例子:
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 给你评估表格的结果。
【讨论】:
有一个名为 rewrite-clj 的库可以处理这类事情。
【讨论】:
没有任何库,你想做的是
(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。
【讨论】: