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.