Clojure工具分析器tools.analyzer:不要展开宏

4

如何在使用 tools.analyzer.jvm/analyze 创建ast时避免扩展宏?

以下是我目前正在收集的信息示例:(函数名称映射到依赖项集合的映射)

  {some-function
  #{{:name load-order-ns-file-maps, :ns #namespace[clj-graph.core]}
    {:name *logger-factory*, :ns #namespace[clojure.tools.logging]}
    {:name analyze, :ns #namespace[clojure.tools.analyzer.jvm]}
    {:name make-dir-tracker, :ns #namespace[clj-graph.core]}
    {:name enabled?, :ns #namespace[clojure.tools.logging.impl]}
    {:name read-all, :ns #namespace[clj-graph.core]}
    {:name get-logger, :ns #namespace[clojure.tools.logging.impl]}
    {:name traverse-expr, :ns #namespace[clj-graph.core]}
    {:name log*, :ns #namespace[clojure.tools.logging]}
    {:name track-reload,
     :ns #namespace[clojure.tools.namespace.reload]}
    {:name examine-form, :ns #namespace[clj-graph.core]}}}

实际代码调用了log/info,但由于这是宏展开的,我永远无法捕获它被声明的名称和命名空间 - 相反,我得到的是宏展开的结果:
:name *logger-factory*, :ns #namespace[clojure.tools.logging]
:name enabled?, :ns #namespace[clojure.tools.logging.impl]
:name get-logger, :ns #namespace[clojure.tools.logging.impl]
:name log*, :ns #namespace[clojure.tools.logging]

作为一个正在构建依赖图的人,理想情况下我只想找到端点,即:name info :ns #namespace[clojure.tools.logging]

阅读analyzer.jvm/analyze的文档时,它给出了一个示例:

(analyze form env {:bindings  {#'ana/macroexpand-1 my-mexpand-1}})

但是当我尝试这样做时,即:
(defn ^:dynamic my-expand-1 [form] form)

(ana/analyze
 '(defn prnt [xs] (my-pre-defined-macro xs))
 (ana/empty-env)
 {:bindings  {#'ana/macroexpand-1 my-expand-1}})

我得到了一个错误。
IllegalStateException Can't dynamically bind non-dynamic var:
clojure.tools.analyzer.jvm/macroexpand-1  clojure.lang.Var.pushThreadBindings (Var.java:320)

你能找到解决方案吗?我有完全相同的问题。 - dAnjou
2个回答

0

我通过提供macroexpand-1parse的实现来解决了特定宏的问题。我的macroexpand-1实现忽略了我想要自己解析的特定宏(只需返回表达式),而我的parse实现提供了自定义解析。您可以在选项中传递自定义方法绑定给analyze

例如,要提供对binding宏的自定义解析:

(require '[clojure.tools.analyzer.jvm :as ja])
(require '[clojure.tools.analyzer :as ana])
(defn my-parse [[op & _ :as form] env]
  (if (= op 'binding)
    (merge (ana/parse-let* form env)
      {:op :unexpanded-dynamic-binding})
    (ja/parse form env)))

(defn binding-form? [o] (and (seq? o) (= (first o) 'binding)))

(defn my-macroexpand-1
  ([form] (my-macroexpand-1 form (ja/empty-env)))
  ([form env]
   (if (binding-form? form)
     form
     (ja/macroexpand-1 form env))))

(ja/analyze
  '(binding [*clojure-version* 1] *clojure-version*)
  (ja/empty-env)
  {:bindings {#'ana/parse my-parse 
              #'ana/macroexpand-1 my-macroexpand-1}})

0
如果宏没有被展开,你将无法获得任何有用的信息,因为宏展开后的内容对你来说是完全不透明的。也许有人已经编写了这个函数:
(defn foo [x]
  (-> x
      inc
      println))

如果您不展开->宏,您将无法发现foo依赖于incprintln。而发现它依赖于->也不是很有趣。


当然,但如果->是由用户定义的 - 目前,扩展会导致它“消失” - 至少从我所知道的情况来看。我在ast中找不到一个变量。但问题是我无法从ast中判断哪些是宏。我认为我想要做的是扩展以便我可以捕获参数并测试它们 - 但忽略所有进入'->'的垃圾。我不关心 ->是如何定义的,我只想知道它来自哪里(以及它所需的参数)。 - beoliver
你能理解我在问题中给出的例子吗?你能看到即使调用了该宏,也没有引用“log/info”吗? - beoliver
通过你的例子,(macroexpand '(-> 10 (partial + 1))) 变成了 (partial 10 + 1),但是 ->至关重要的,因为如果它被重新编写,结果将会改变。我想我只需要编写自己的解析器... - beoliver

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接