https://github.com/clojure/jvm.tools.analyzer
https://github.com/clojure/jvm.tools.analyzer
Last synced: about 1 month ago
JSON representation
- Host: GitHub
- URL: https://github.com/clojure/jvm.tools.analyzer
- Owner: clojure
- Created: 2013-03-12T14:20:34.000Z (over 12 years ago)
- Default Branch: master
- Last Pushed: 2024-07-15T18:01:24.000Z (11 months ago)
- Last Synced: 2025-05-07T04:03:54.157Z (about 1 month ago)
- Language: Clojure
- Size: 270 KB
- Stars: 53
- Watchers: 30
- Forks: 5
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
Awesome Lists containing this project
README
# jvm.tools.analyzer: An Interface to Clojure's Analyzer
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.
# Releases and Dependency Information
Latest stable release is 0.6.1.
[CLI/`deps.edn`](https://clojure.org/reference/deps_and_cli) dependency information:
```clojure
org.clojure/jvm.tools.analyzer {:mvn/version "0.6.1"}
```Leiningen dependency information:
```clojure
[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:
```XML
org.clojure
jvm.tools.analyzer
0.6.1```
# Differences from tools.analyzer.jvm
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.# Caveats (of provided Clojure AST analysis)
## Implicit Evalutation
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.## Future deprecation
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*.## Fragile Implementation
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).## Non-standard AST
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 namesI 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.# Usage (Clojure)
## Generating AST from syntax
Note: Column numbers are only supported with Clojure 1.5.0 or later.
```clojure
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}}
nilclojure.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
```## Syntax from AST
```clojure
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)]
```# Macroexpander
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.
# Known Issues
## Evaluating forms
Currently the analyzer evaluates each form after it is analyzed.
## Incorrect handling of Var mappings within the same form
`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`.```clojure
;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
```# Usage (Clojurescript)
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:
```clojure
(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:
```clojure
(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))
```# Developer Information
- [Github Project](https://github.com/clojure/jvm.tools.analyzer)
- [Bug Tracker](http://dev.clojure.org/jira/browse/JVMTA)
- [Continuous Integration](https://github.com/clojure/jvm.tools.analyzer/actions/workflows/test.yml)# Todo
- analyze a leiningen `project.clj` file
- analyze `clojure.core`
- use :locals if necessary# Examples
See `clojure.jvm.tools.analyzer.examples.*` namespaces.
# Contributors
- Jonas Enlund (jonase)
- Nicola Mometto (Bronsa)
- Chris Gray (chrismgray)## License
Copyright © Ambrose Bonnaire-Sergeant, Rich Hickey & contributors.
Licensed under the EPL (see the file epl.html).