Clojure's analysis compilation phase holds rich information about Clojure forms, like type/reflection information.
jvm.tools.analyzer provides an interface to this phase, callable a la carte. The output is similar to ClojureScript's analyzer.
Supports Clojure 1.4.0 or later.
Latest stable release is 0.6.1.
CLI/deps.edn
dependency information:
org.clojure/jvm.tools.analyzer {:mvn/version "0.6.1"}
Leiningen dependency information:
[org.clojure/jvm.tools.analyzer "0.6.1"]
; for very recent releases
:repositories {"sonatype-oss-public" "https://oss.sonatype.org/content/groups/public/"}
Maven dependency information:
<dependency>
<groupId>org.clojure</groupId>
<artifactId>jvm.tools.analyzer</artifactId>
<version>0.6.1</version>
</dependency>
The tools.analyzer.* libraries are Clojure compilers/analyzers written in Clojure, with tools.analyzer.jvm targetting the JVM.
This library jvm.tools.analyzer is a set of tools for manipulating the output of Compiler.java, the official Clojure compiler written in Java as of Clojure 1.5.
Every AST node is eval
ed after it is processed by analysis. This is to recognise
scope introduced by require
and refer
, and other global side effects.
It follows that analysing a def
will result in it being redefined as if it were
evaluated.
This library will be deprecated if and when a sufficient Clojure-in-Clojure compiler
is implemented. For now, jvm.tools.analyzer
is probably your best bet for libraries
you want to build today.
The implementation consists of reflective calls to the Clojure JVM Compiler to extract AST data. It should work with Clojure 1.4.0 or later, but there may be subtle changes in c.l.Compiler.java which we don't account for. It is optimised to support the latest versions of Clojure (1.5.1, as of 8 April 2013).
The shape of the AST map is exactly based on the Compiler's internal representation. No effort has been made to conform to a ClojureScript-like AST. In practice, the main differences are:
- local bindings are wrapped in a
:local-binding-expr
node - there are several AST nodes for constants (eg.
:constant
,:nil
,:empty-expr
) - several interop nodes (no :dot)
- some ops/fields have different names
I highly recommend browsing the implementation of clojure.jvm.tools.analyzer
to check the current
state of the AST. It should be familiar if you have experience with the ClojureScript analyzer.
Note: Column numbers are only supported with Clojure 1.5.0 or later.
clojure.jvm.tools.analyzer=> (ast [1])
{:op :constant, :env {:locals {}, :ns {:name clojure.jvm.tools.analyzer}}, :val [1]}
clojure.jvm.tools.analyzer=> (-> (ast (if true 1 2)) clojure.pprint/pprint)
{:op :if,
:env
{:column 10,
:line 4,
:locals {},
:ns {:name clojure.jvm.tools.analyzer}},
:test
{:op :boolean,
:env {:locals {}, :ns {:name clojure.jvm.tools.analyzer}},
:val true},
:then
{:op :number,
:env {:locals {}, :ns {:name clojure.jvm.tools.analyzer}},
:val 1},
:else
{:op :number,
:env {:locals {}, :ns {:name clojure.jvm.tools.analyzer}},
:val 2}}
nil
clojure.jvm.tools.analyzer=> (-> (ast (fn [x] (+ x 1))) clojure.pprint/pprint)
{:op :fn-expr,
:env {:line 5, :locals {}, :ns {:name clojure.jvm.tools.analyzer}},
:methods
({:op :fn-method,
:env {:locals {}, :ns {:name clojure.jvm.tools.analyzer}},
:body
{:op :do,
:env
{:source "REPL",
:column 18,
:line 5,
:locals {},
:ns {:name clojure.jvm.tools.analyzer}},
:exprs
({:op :static-method,
:env
{:source "REPL",
:column 18,
:line 5,
:locals {},
:ns {:name clojure.jvm.tools.analyzer}},
:class clojure.lang.Numbers,
:method-name "add",
:method
{:name add,
:return-type java.lang.Number,
:declaring-class clojure.lang.Numbers,
:parameter-types [java.lang.Object long],
:exception-types [],
:flags #{:static :public}},
:args
({:op :local-binding-expr,
:env {:locals {}, :ns {:name clojure.jvm.tools.analyzer}},
:local-binding
{:op :local-binding,
:env {:locals {}, :ns {:name clojure.jvm.tools.analyzer}},
:sym x,
:tag nil,
:init nil},
:tag nil}
{:op :number,
:env {:locals {}, :ns {:name clojure.jvm.tools.analyzer}},
:val 1}),
:tag nil})},
:required-params
({:op :local-binding,
:env {:locals {}, :ns {:name clojure.jvm.tools.analyzer}},
:sym x,
:tag nil,
:init nil}),
:rest-param nil}),
:variadic-method nil,
:tag nil}
nil
clojure.jvm.tools.analyzer=> (require '[clojure.jvm.tools.analyzer.emit-form :as e])
nil
clojure.jvm.tools.analyzer=> (-> (ast 1) e/emit-form)
1
clojure.jvm.tools.analyzer=> (-> (ast [(+ 1 2)]) e/emit-form)
[(clojure.lang.Numbers/add 1 2)]
Use clojure.jvm.tools.analyzer/macroexpand
as a substitute
for macroexpand
for fully macroexpanding forms.
clojure.jvm.tools.analyzer.hygienic/macroexpand
returns a hygienic form.
Currently the analyzer evaluates each form after it is analyzed.
analyze
is a thin wrapper over clojure.lang.Compiler
, so to get our
hands on analysis results some compromises are made.
The following form normally evaluates to the Var clojure.set/intersection
, but
analyses to clojure.core/require
.
;normal evaluation
(eval
'(do
(require '[clojure.set])
(refer 'clojure.set
:only '[intersection]
:rename '{intersection require})
require))
;=> #'clojure.set/intersection
;analysis result
(-> (ast
(do (require '[clojure.set])
(refer 'clojure.set
:only '[intersection]
:rename '{intersection require})
require))
:exprs last :var)
;=> #'clojure.core/require
All vars are identical to the Clojure implementation, where relevant,
except the namespace prefix is cljs.jvm.tools.analyzer
instead of
clojure.jvm.tools.analyzer
.
Some examples:
Normal AST generation:
(cljs.jvm.tools.analyzer/ast 1)
;=> {:op :constant, :env {:ns {:defs {a {:column 18, :line 2, :file nil, :name cljs.user/a}}, :name cljs.user}, :context :statement, :locals {}}, :form 1}
Hygienic transformation:
(cljs.jvm.tools.analyzer.hygienic/macroexpand
'(let [a 1 a a b a a a] a))
;=> (let* [a 1 a11306 a b a11306 a11307 a11306] (do a11307))
- analyze a leiningen
project.clj
file - analyze
clojure.core
- use :locals if necessary
See clojure.jvm.tools.analyzer.examples.*
namespaces.
- Jonas Enlund (jonase)
- Nicola Mometto (Bronsa)
- Chris Gray (chrismgray)
Copyright © Ambrose Bonnaire-Sergeant, Rich Hickey & contributors.
Licensed under the EPL (see the file epl.html).