简体   繁体   中英

Clojure tools.analyzer, don't expand macros

What is the best way to avoid expanding macros when creating ast's using tools.analyzer.jvm/analyze

This is an example of the information I am currently collecting: (map of function name to dependency set)

  {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]}}}

The actual code calls log/info but as this is macroexanded, I can never capture the the name and ns in which it was declared - instead I get the macro expansion giving me:

: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]

As I am building a dependency graph, ideally I just want to find the endpoints, ie :name info:ns #namespace[clojure.tools.logging] .

Reading the documentation for analyzer.jvm/analyze it gives an example as:

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

but when I try this, ie:

(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}})

I get the error

IllegalStateException Can't dynamically bind non-dynamic var:
clojure.tools.analyzer.jvm/macroexpand-1  clojure.lang.Var.pushThreadBindings (Var.java:320)

If macros were not expanded, you couldn't get any useful information, because what a macro expands into is completely opaque to you. Perhaps someone has written this function:

(defn foo [x]
  (-> x
      inc
      println))

If you don't expand the -> macro, you will not find out that foo depends on inc and println . And finding out that it depends on -> is not very interesting.

I solved this for specific macros by providing implementations of macroexpand-1 and parse . My macroexpand-1 implementation ignores the specific macros I want to parse myself (by simply returning the form) and my parse implementation provides custom parsing. You can pass custom method bindings in the options to analyze :

For example, to provide custom parsing of the binding macro:

(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}})

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM