├── .github
├── PULL_REQUEST_TEMPLATE
└── workflows
│ ├── doc-build.yml
│ ├── release.yml
│ ├── snapshot.yml
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── epl.html
├── pom.xml
├── project.clj
├── spec
├── ast-ref.edn
├── buildref.sh
├── gen-ref.clj
└── quickref.html.tpl
└── src
├── main
├── clojure
│ └── clojure
│ │ └── tools
│ │ ├── analyzer.clj
│ │ └── analyzer
│ │ ├── ast.clj
│ │ ├── ast
│ │ └── query.clj
│ │ ├── env.clj
│ │ ├── passes.clj
│ │ ├── passes
│ │ ├── add_binding_atom.clj
│ │ ├── cleanup.clj
│ │ ├── collect_closed_overs.clj
│ │ ├── constant_lifter.clj
│ │ ├── elide_meta.clj
│ │ ├── emit_form.clj
│ │ ├── index_vector_nodes.clj
│ │ ├── source_info.clj
│ │ ├── trim.clj
│ │ ├── uniquify.clj
│ │ └── warn_earmuff.clj
│ │ └── utils.clj
└── dotnet
│ └── packager
│ ├── clojure.tools.analyzer.csproj
│ └── clojure.tools.analyzer.sln
└── test
└── clojure
└── clojure
└── tools
└── analyzer
├── core_test.clj
├── passes_test.clj
└── query_test.clj
/.github/PULL_REQUEST_TEMPLATE:
--------------------------------------------------------------------------------
1 | Hi! Thanks for your interest in contributing to this project.
2 |
3 | Clojure contrib projects do not use GitHub issues or pull requests, and
4 | require a signed Contributor Agreement. If you would like to contribute,
5 | please read more about the CA and sign that first (this can be done online).
6 |
7 | Then go to this project's issue tracker in JIRA to create tickets, update
8 | tickets, or submit patches. For help in creating tickets and patches,
9 | please see:
10 |
11 | - Contributing FAQ: https://clojure.org/dev
12 | - Signing the CA: https://clojure.org/dev/contributor_agreement
13 | - Creating Tickets: https://clojure.org/dev/creating_tickets
14 | - Developing Patches: https://clojure.org/dev/developing_patches
15 |
--------------------------------------------------------------------------------
/.github/workflows/doc-build.yml:
--------------------------------------------------------------------------------
1 | name: Build API Docs
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | call-doc-build-workflow:
8 | uses: clojure/build.ci/.github/workflows/doc-build.yml@master
9 | with:
10 | project: clojure/tools.analyzer
11 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release on demand
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | releaseVersion:
7 | description: "Version to release"
8 | required: true
9 | snapshotVersion:
10 | description: "Snapshot version after release"
11 | required: true
12 |
13 | jobs:
14 | call-release:
15 | uses: clojure/build.ci/.github/workflows/release.yml@master
16 | with:
17 | releaseVersion: ${{ github.event.inputs.releaseVersion }}
18 | snapshotVersion: ${{ github.event.inputs.snapshotVersion }}
19 | secrets: inherit
--------------------------------------------------------------------------------
/.github/workflows/snapshot.yml:
--------------------------------------------------------------------------------
1 | name: Snapshot on demand
2 |
3 | on: [workflow_dispatch]
4 |
5 | jobs:
6 | call-snapshot:
7 | uses: clojure/build.ci/.github/workflows/snapshot.yml@master
8 | secrets: inherit
9 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Test
2 |
3 | on: [push]
4 |
5 | jobs:
6 | call-test:
7 | uses: clojure/build.ci/.github/workflows/test.yml@master
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /lib
3 | /classes
4 | /checkouts
5 | *.jar
6 | *.class
7 | .lein-deps-sum
8 | .lein-failures
9 | .lein-plugins
10 | .lein-repl-history
11 |
12 | #Visual Studio artifacts
13 | bin/
14 | obj/
15 | .vs/
16 | *.user
17 | *.suo
18 | *.nupkg
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | ========================================
3 | * Release 1.2.0 on 19 Feb 2024
4 | * Update parent pom
5 | * Release 1.1.1 on 26 Nov 2022
6 | * Remove JVM-specific code to make this usable for ClojureCLR
7 | * Release 1.1.0 on 14 Sep 2021
8 | * TANAL-134: Remove :init from :def's :children when absent
9 | * Added :refer-clojure :exclude for `update-keys` and `update-vals` being added in Clojure 1.11
10 | * Release 1.0.0 on 18 Feb 2020
11 | * Fixed docstrings
12 | * Release 0.7.0 on 10 Dec 2018
13 | * Handle injected locals in add-binding-atom phase
14 | * Release 0.6.9 on 9 Jun 2015
15 | * Exclude `boolean?` from clojure.core
16 | * Release 0.6.7 on 3 Nov 2015
17 | * Don't elide significant 'quote
18 | * Release 0.6.6 on 23 Apr 2015
19 | * Fix emit-form for :host-field
20 | * Release 0.6.5 on 23 Feb 2015
21 | * Small performance enhancements
22 | * Fixed some metadata handling
23 | * Removed :ctx.invoke, derive :ctx/return from :ctx/expr
24 | * Renamed resolve-var to resolve-sym
25 | * Attached resolved op to :raw-forms
26 | * Added var special form
27 | * Release 0.6.4 on 20 Nov 2014
28 | * Fixed a bug in the pass scheduler regarding transitive deps
29 | * Added reduced support for update-children, walk, prewalk and postwalk
30 | * Fixed parsing of catch expressions outside a try block
31 | * Release 0.6.3 on 03 Oct 2014
32 | * Preserve correct meta on emit-form
33 | * Preserve :raw-forms in elide-meta
34 | * Better source-info propagation
35 | * Release 0.6.2 on 27 Oct 2014
36 | * Fixes and improvements for looping pass scheduling
37 | * Performance improvements on the scheduled pass function
38 | * Release 0.6.1 on 13 Oct 2014
39 | * Significant performance enhancements
40 | * Fixed scheduling of looping passes
41 | * Uniquify :env :locals only if the :uniquify/uniquify-env pass-opt is true
42 | * Release 0.6.0 on 18 Sep 2014
43 | * Added pass scheduler (clojure.tools.analyzer.passes/schedule) and configured all the passes
44 | * Changed the interface of the collect-closed-overs pass
45 | * Changed the interface of the add-binding-atom pass
46 | * Removed the (experimental) trim pass
47 | * Release 0.5.3 on 31 Aug 2014
48 | * Made the source clojure-clr compatible
49 | * Added butlast+last to utils
50 | * Release 0.5.2 on 20 Aug 2014
51 | * Compare contexts with isa? rather than =
52 | * Release 0.5.1 on 09 Aug 2014
53 | * Removed collect pass
54 | * Moved collect-closed-overs pass to its own namespace
55 | * Release 0.5.0 on 29 Jul 2014
56 | * Made :host-field and :host-interop :assignable?
57 | * Release 0.4.0 on 26 Jul 2014
58 | * BREAKING CHANGE: The :class field for :new and :catch nodes are now children nodes rather than symbols
59 | * More fine-grained elide-meta
60 | * Release 0.3.0 on 21 Jun 2014
61 | * BREAKING API CHANGE: :context is now either :ctx/statement, :ctx/return, :ctx/expr or a keyword derived from one of those
62 | * elide-meta: elides can be any IFn, not only a set
63 | * analyze :symbol will not throw when a Var is not found
64 | * Release 0.2.3 on 16 Jun 2014
65 | * Preserve :raw-forms for macroexpanded symbols
66 | * Add :end-line :end-column info to source-info (only when directly available)
67 | * Release 0.2.2 on 13 Jun 2014
68 | * :fn node can be wrapped by :with-meta
69 | * Remove :meta from :def :children when elide-meta removes it
70 | * Release 0.2.1 on 08 Jun 2014
71 | * Made constant-lift preserve the original AST fields
72 | * Made elide-meta discard all meta on form if metadata becomes nil
73 | * Release 0.2.0 on 05 Jun 2014
74 | * BREAKING API CHANGE: Add global-env interface, move :namespaces from env to the global env
75 | * Preserve original forms under :raw-forms in case of macroexpansion
76 | * Open analyze-form dispatch
77 | * Fixed collect-closed-overs for letfn*
78 | * Make cleanup work on :env :locals
79 | * Release 0.1.0-beta13 on 11 Mar 2014
80 | * Fix elide-meta pass
81 | * Release 0.1.0-beta12 on 25 Apr 2014
82 | * Annotated top-level nodes with :top-level true
83 | * Moved rseqv and into! to the c.t.a.utils namespace
84 | * Don't uniquify "constructed" locals
85 | * Preserve :locals in :env, uniquify locals :name in :env :locals
86 | * Release 0.1.0-beta11 on 18 Apr 2014
87 | * Reduced the number of calls to `symbol`, leading to some performance improvements
88 | * Performance improvements on the uniquify pass
89 | * BREAKING API CHANGE: ast/children* now returns a vector of [key node] rather than
90 | a vector of nodes
91 | * Performance improvement on ast/update-children
92 | * Added options set to emit-form
93 | * Release 0.1.0-beta10 on 1 Apr 2014
94 | * Don't discard macroexpanded form meta, merge it with &form meta
95 | * Improvements on source-info handling
96 | * Release 0.1.0-beta9 on 29 Mar 2014
97 | * Fixed a bug in constant-lift regarding array-maps
98 | * Fixed elide-meta implementation
99 | * :const nodes will have :meta only if the const object is an IObj
100 | * Release 0.1.0-beta8 on 11 Mar 2014
101 | * Removed :name in env for the :fn name, moved in a tools.analyzer.jvm pass
102 | * Added docstrings
103 | * Release 0.1.0-beta7 on 28 Feb 2014
104 | * Fix macroexpand implementation
105 | * Release 0.1.0-beta5 on 26 Feb 2014
106 | * Unwrap the try if there's no catch/finally
107 | * Fixed uniquify pass on letfn bindings
108 | * Correctly quote :arglists meta in def sym
109 | * Release 0.1.0-beta4 on 17 Feb 2014
110 | * Analyze throws on `(quote)`
111 | * General code cleanup, added docstrings
112 | * Changed :loop-locals to hold the count of locals rather than their form
113 | Holding their form was problematic since the uniquify pass would invaldiate those
114 | * Attached :once to :fn nodes when ^:once fn*
115 | * Release 0.1.0-beta3 on 15 Feb 2014
116 | * Allowed :top-level collecting for collect-closed-overs
117 | * Release 0.1.0-beta2 on 14 Feb 2014
118 | * Fixed fn name munging
119 | * Release 0.1.0-beta1 on 11 Feb 2014
120 | * First beta release
121 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | This is a [Clojure contrib] project.
2 |
3 | Under the Clojure contrib [guidelines], this project cannot accept
4 | pull requests. All patches must be submitted via [JIRA].
5 |
6 | See [Contributing] on the Clojure website for
7 | more information on how to contribute.
8 |
9 | [Clojure contrib]: https://clojure.org/community/contrib_libs
10 | [Contributing]: https://clojure.org/community/contributing
11 | [JIRA]: https://clojure.atlassian.net/browse/TANAL
12 | [guidelines]: https://clojure.org/community/contrib_howto
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Eclipse Public License - v 1.0
2 |
3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
4 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
5 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
6 |
7 | 1. DEFINITIONS
8 |
9 | "Contribution" means:
10 |
11 | a) in the case of the initial Contributor, the initial code and documentation
12 | distributed under this Agreement, and
13 | b) in the case of each subsequent Contributor:
14 | i) changes to the Program, and
15 | ii) additions to the Program;
16 |
17 | where such changes and/or additions to the Program originate from and are
18 | distributed by that particular Contributor. A Contribution 'originates'
19 | from a Contributor if it was added to the Program by such Contributor
20 | itself or anyone acting on such Contributor's behalf. Contributions do not
21 | include additions to the Program which: (i) are separate modules of
22 | software distributed in conjunction with the Program under their own
23 | license agreement, and (ii) are not derivative works of the Program.
24 |
25 | "Contributor" means any person or entity that distributes the Program.
26 |
27 | "Licensed Patents" mean patent claims licensable by a Contributor which are
28 | necessarily infringed by the use or sale of its Contribution alone or when
29 | combined with the Program.
30 |
31 | "Program" means the Contributions distributed in accordance with this
32 | Agreement.
33 |
34 | "Recipient" means anyone who receives the Program under this Agreement,
35 | including all Contributors.
36 |
37 | 2. GRANT OF RIGHTS
38 | a) Subject to the terms of this Agreement, each Contributor hereby grants
39 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
40 | reproduce, prepare derivative works of, publicly display, publicly
41 | perform, distribute and sublicense the Contribution of such Contributor,
42 | if any, and such derivative works, in source code and object code form.
43 | b) Subject to the terms of this Agreement, each Contributor hereby grants
44 | Recipient a non-exclusive, worldwide, royalty-free patent license under
45 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
46 | transfer the Contribution of such Contributor, if any, in source code and
47 | object code form. This patent license shall apply to the combination of
48 | the Contribution and the Program if, at the time the Contribution is
49 | added by the Contributor, such addition of the Contribution causes such
50 | combination to be covered by the Licensed Patents. The patent license
51 | shall not apply to any other combinations which include the Contribution.
52 | No hardware per se is licensed hereunder.
53 | c) Recipient understands that although each Contributor grants the licenses
54 | to its Contributions set forth herein, no assurances are provided by any
55 | Contributor that the Program does not infringe the patent or other
56 | intellectual property rights of any other entity. Each Contributor
57 | disclaims any liability to Recipient for claims brought by any other
58 | entity based on infringement of intellectual property rights or
59 | otherwise. As a condition to exercising the rights and licenses granted
60 | hereunder, each Recipient hereby assumes sole responsibility to secure
61 | any other intellectual property rights needed, if any. For example, if a
62 | third party patent license is required to allow Recipient to distribute
63 | the Program, it is Recipient's responsibility to acquire that license
64 | before distributing the Program.
65 | d) Each Contributor represents that to its knowledge it has sufficient
66 | copyright rights in its Contribution, if any, to grant the copyright
67 | license set forth in this Agreement.
68 |
69 | 3. REQUIREMENTS
70 |
71 | A Contributor may choose to distribute the Program in object code form under
72 | its own license agreement, provided that:
73 |
74 | a) it complies with the terms and conditions of this Agreement; and
75 | b) its license agreement:
76 | i) effectively disclaims on behalf of all Contributors all warranties
77 | and conditions, express and implied, including warranties or
78 | conditions of title and non-infringement, and implied warranties or
79 | conditions of merchantability and fitness for a particular purpose;
80 | ii) effectively excludes on behalf of all Contributors all liability for
81 | damages, including direct, indirect, special, incidental and
82 | consequential damages, such as lost profits;
83 | iii) states that any provisions which differ from this Agreement are
84 | offered by that Contributor alone and not by any other party; and
85 | iv) states that source code for the Program is available from such
86 | Contributor, and informs licensees how to obtain it in a reasonable
87 | manner on or through a medium customarily used for software exchange.
88 |
89 | When the Program is made available in source code form:
90 |
91 | a) it must be made available under this Agreement; and
92 | b) a copy of this Agreement must be included with each copy of the Program.
93 | Contributors may not remove or alter any copyright notices contained
94 | within the Program.
95 |
96 | Each Contributor must identify itself as the originator of its Contribution,
97 | if
98 | any, in a manner that reasonably allows subsequent Recipients to identify the
99 | originator of the Contribution.
100 |
101 | 4. COMMERCIAL DISTRIBUTION
102 |
103 | Commercial distributors of software may accept certain responsibilities with
104 | respect to end users, business partners and the like. While this license is
105 | intended to facilitate the commercial use of the Program, the Contributor who
106 | includes the Program in a commercial product offering should do so in a manner
107 | which does not create potential liability for other Contributors. Therefore,
108 | if a Contributor includes the Program in a commercial product offering, such
109 | Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
110 | every other Contributor ("Indemnified Contributor") against any losses,
111 | damages and costs (collectively "Losses") arising from claims, lawsuits and
112 | other legal actions brought by a third party against the Indemnified
113 | Contributor to the extent caused by the acts or omissions of such Commercial
114 | Contributor in connection with its distribution of the Program in a commercial
115 | product offering. The obligations in this section do not apply to any claims
116 | or Losses relating to any actual or alleged intellectual property
117 | infringement. In order to qualify, an Indemnified Contributor must:
118 | a) promptly notify the Commercial Contributor in writing of such claim, and
119 | b) allow the Commercial Contributor to control, and cooperate with the
120 | Commercial Contributor in, the defense and any related settlement
121 | negotiations. The Indemnified Contributor may participate in any such claim at
122 | its own expense.
123 |
124 | For example, a Contributor might include the Program in a commercial product
125 | offering, Product X. That Contributor is then a Commercial Contributor. If
126 | that Commercial Contributor then makes performance claims, or offers
127 | warranties related to Product X, those performance claims and warranties are
128 | such Commercial Contributor's responsibility alone. Under this section, the
129 | Commercial Contributor would have to defend claims against the other
130 | Contributors related to those performance claims and warranties, and if a
131 | court requires any other Contributor to pay any damages as a result, the
132 | Commercial Contributor must pay those damages.
133 |
134 | 5. NO WARRANTY
135 |
136 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
137 | "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
138 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
140 | Recipient is solely responsible for determining the appropriateness of using
141 | and distributing the Program and assumes all risks associated with its
142 | exercise of rights under this Agreement , including but not limited to the
143 | risks and costs of program errors, compliance with applicable laws, damage to
144 | or loss of data, programs or equipment, and unavailability or interruption of
145 | operations.
146 |
147 | 6. DISCLAIMER OF LIABILITY
148 |
149 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
150 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
151 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
152 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
153 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
154 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
155 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
156 | OF SUCH DAMAGES.
157 |
158 | 7. GENERAL
159 |
160 | If any provision of this Agreement is invalid or unenforceable under
161 | applicable law, it shall not affect the validity or enforceability of the
162 | remainder of the terms of this Agreement, and without further action by the
163 | parties hereto, such provision shall be reformed to the minimum extent
164 | necessary to make such provision valid and enforceable.
165 |
166 | If Recipient institutes patent litigation against any entity (including a
167 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
168 | (excluding combinations of the Program with other software or hardware)
169 | infringes such Recipient's patent(s), then such Recipient's rights granted
170 | under Section 2(b) shall terminate as of the date such litigation is filed.
171 |
172 | All Recipient's rights under this Agreement shall terminate if it fails to
173 | comply with any of the material terms or conditions of this Agreement and does
174 | not cure such failure in a reasonable period of time after becoming aware of
175 | such noncompliance. If all Recipient's rights under this Agreement terminate,
176 | Recipient agrees to cease use and distribution of the Program as soon as
177 | reasonably practicable. However, Recipient's obligations under this Agreement
178 | and any licenses granted by Recipient relating to the Program shall continue
179 | and survive.
180 |
181 | Everyone is permitted to copy and distribute copies of this Agreement, but in
182 | order to avoid inconsistency the Agreement is copyrighted and may only be
183 | modified in the following manner. The Agreement Steward reserves the right to
184 | publish new versions (including revisions) of this Agreement from time to
185 | time. No one other than the Agreement Steward has the right to modify this
186 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
187 | Eclipse Foundation may assign the responsibility to serve as the Agreement
188 | Steward to a suitable separate entity. Each new version of the Agreement will
189 | be given a distinguishing version number. The Program (including
190 | Contributions) may always be distributed subject to the version of the
191 | Agreement under which it was received. In addition, after a new version of the
192 | Agreement is published, Contributor may elect to distribute the Program
193 | (including its Contributions) under the new version. Except as expressly
194 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
195 | licenses to the intellectual property of any Contributor under this Agreement,
196 | whether expressly, by implication, estoppel or otherwise. All rights in the
197 | Program not expressly granted under this Agreement are reserved.
198 |
199 | This Agreement is governed by the laws of the State of New York and the
200 | intellectual property laws of the United States of America. No party to this
201 | Agreement will bring a legal action under this Agreement more than one year
202 | after the cause of action arose. Each party waives its rights to a jury trial in
203 | any resulting litigation.
204 |
205 |
206 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # tools.analyzer
2 |
3 | An analyzer for host agnostic Clojure code, written in Clojure and producing AST in EDN.
4 |
5 | I gave a talk on tools.analyzer[.jvm] at ClojureX in December 2015. Video [here](https://www.youtube.com/watch?v=oZyt93lmF5s)
6 |
7 | Timothy Baldridge gave a talk on tools.analyzer[.jvm] at Clojure/West in
8 | March 2014. Video
9 | [here](https://www.youtube.com/watch?v=KhRQmT22SSg&list=PLZdCLR02grLp__wRg5OTavVj4wefg69hM&index=11).
10 |
11 | Note that the analyzer in this library should not to be used directly as it lacks any knowledge about host-specific special forms and it should only be considered as a building platform for host-specific analyzers.
12 | Currently the following platform specific analyzers written on top of tools.analyzer exist: [tools.analyzer.jvm](https://github.com/clojure/tools.analyzer.jvm), [tools.analyzer.js](https://github.com/clojure/tools.analyzer.js)
13 |
14 | * [Example Usage](#example-usage)
15 | * [Quickref](#quickref)
16 | * [Releases and Dependency Information](#releases-and-dependency-information)
17 | * [Changelog](#changelog)
18 | * [API Index](#api-index)
19 | * [Developer Information](#developer-information)
20 | * [License](#license)
21 |
22 | [Quickref](https://clojure.github.io/tools.analyzer/spec/quickref.html)
23 | ========================================
24 |
25 | ## Example Usage
26 | `clojure.tools.analyzer/analyze` will not work out of the box, as it requires a number of entry-points to be set.
27 | Here's what could happen trying to use `clojure.tools.analyzer/analyze` directly:
28 | ```clojure
29 | clojure.tools.analyzer> (analyze 'a {})
30 | Attempting to call unbound fn: #'clojure.tools.analyzer/macroexpand-1
31 | [Thrown class java.lang.IllegalStateException]
32 | ```
33 |
34 | At the moment there exist two official analyzers written on top of [tools.analyzer](https://github.com/clojure/tools.analyzer): [tools.analyzer.jvm](https://github.com/clojure/tools.analyzer.jvm) for clojure on the JVM and [tools.analyzer.js](https://github.com/clojure/tools.analyzer.js) for clojurescript.
35 | We will use [tools.analyzer.jvm](https://github.com/clojure/tools.analyzer.jvm) for those examples.
36 |
37 | Here's a simplified version of how `clojure.tools.analyzer.jvm/analyze` is defined:
38 | ```clojure
39 | (require '[clojure.tools.analyzer :as ana])
40 | (require '[clojure.tools.analyzer.env :as env])
41 | (defn analyze [form env]
42 | (binding [ana/macroexpand-1 macroexpand-1
43 | ana/create-var create-var
44 | ana/parse parse
45 | ana/var? var?]
46 | (env/ensure (global-env)
47 | (run-passes (-analyze form env))))))
48 | ```
49 |
50 | Here, `-analyze` is a multimethod that defaults to `ana/analyze` and defines analysis methods for the JVM specific special forms, `global-env` is a function that returns a global environment for the JVM analyzer and `run-passes` is a function that takes an AST and applies a number of passes to it.
51 |
52 | The `tools.analyzer.jvm` [README](https://github.com/clojure/tools.analyzer.jvm#example-usage) contains more examples on how the `analyze` function works as well as a reference for all the nodes it can return.
53 |
54 | One of the most important features of `tools.analyzer` is the ability to walk generically through the AST nodes, this has been immensely useful to write most of the passes used by the various analyzers.
55 | The `tools.analyzer.ast` namespace provides a number of functions that implement various generic AST walking strategies.
56 |
57 | The `children` function returns a vector of the children nodes of the current node (the output has been elided and pretty-printed for clarity):
58 | ```clojure
59 | clojure.tools.analyzer.jvm> (require '[clojure.tools.analyzer.ast :as ast])
60 | nil
61 | clojure.tools.analyzer.jvm> (ast/children (analyze '(do 1 2 :foo)))
62 | [{:op :const,
63 | :id 0,
64 | :type :number,
65 | :val 1,
66 | :form 1,
67 | ...}
68 | {:op :const,
69 | :id 1,
70 | :type :number,
71 | :val 2,
72 | :form 2,
73 | ...}
74 | {:op :const,
75 | :id 3,
76 | :type :keyword,
77 | :val :foo,
78 | :form :foo,
79 | ...}]
80 | ```
81 |
82 | If we want to access a flattened view of all the nodes of an AST, we can use the `nodes` function:
83 | ```clojure
84 | clojure.tools.analyzer.jvm> (ast/nodes (analyze '[1 (+ 1 2)]))
85 | ({:op :vector,
86 | :top-level true,
87 | :items
88 | [{:op :const,
89 | :type :number,
90 | :val 1,
91 | ...}
92 | {:op :static-call,
93 | :class clojure.lang.Numbers,
94 | :method add,
95 | :form (. clojure.lang.Numbers (add 1 2)),
96 | :args [{:op :const,
97 | :val 1,
98 | ...}
99 | {:op :const,
100 | :val 2,
101 | ...}],
102 | ...}]
103 | :form [1 (+ 1 2)],
104 | ...}
105 | {:op :const,
106 | :type :number,
107 | :val 1,
108 | ...}
109 | {:op :static-call,
110 | :class clojure.lang.Numbers,
111 | :method add,
112 | :form (. clojure.lang.Numbers (add 1 2)),
113 | :args [{:op :const,
114 | :val 1,
115 | ...}
116 | {:op :const,
117 | :val 2,
118 | ...}],
119 | ...}
120 | ..)
121 | ```
122 |
123 | The `update-children` function takes an AST node and a function and replaces the children nodes of the given node with the result of applying the function to each children node.
124 | ```clojure
125 | clojure.tools.analyzer.jvm> (ast/update-children (analyze '(do 1 (+ 1 2) :foo))
126 | #(assoc % :visited true))
127 | {:op :do
128 | :statements
129 | [{:op :const,
130 | :val 1,
131 | :visited true,
132 | ...}
133 | {:op :static-call,
134 | :class clojure.lang.Numbers,
135 | :method add,
136 | :visited true,
137 | :args [{:op :const
138 | :val 1,
139 | ...}
140 | {:op :const,
141 | :val 2,
142 | ...}],
143 | ...}]
144 | :ret
145 | {:op :const,
146 | :val :foo,
147 | :visited true,
148 | ...},
149 | ...}
150 | ```
151 | If it's desirable to walk all the AST applying a function to all the nodes and the children nodes, one of `walk`, `prewalk` or `postwalk` should be used, read the docstrings of the three functions to understand the differences.
152 | Here's the previous example using `prewalk` instead of `update-children`:
153 | ```clojure
154 | clojure.tools.analyzer.jvm> (ast/prewalk (analyze '(do 1 (+ 1 2) :foo))
155 | #(assoc % :visited true))
156 | {:op :do
157 | :visited true,
158 | :statements
159 | [{:op :const,
160 | :val 1,
161 | :visited true,
162 | ...}
163 | {:op :static-call,
164 | :class clojure.lang.Numbers,
165 | :method add,
166 | :visited true,
167 | :args [{:op :const
168 | :val 1,
169 | :visited true,
170 | ...}
171 | {:op :const,
172 | :val 2,
173 | :visited true,
174 | ...}],
175 | ...}]
176 | :ret
177 | {:op :const,
178 | :val :foo,
179 | :visited true,
180 | ...},
181 | ...}
182 | ```
183 | As you can see, this time all the nodes have been marked `:visited`.
184 |
185 | Since version `0.6.0`, passes can be scheduled automatically using `clojure.tools.analyzer.passes/schedule` rather than having to compose them and sort out pass dependencies manually, refer to its docstrings and examples from `tools.analyzer.jvm` for more info.
186 |
187 | ## SPONSORSHIP
188 |
189 | * Cognitect (https://cognitect.com/) has sponsored tools.analyzer development (https://groups.google.com/d/msg/clojure/iaP16MHpX0E/EMtnGmOz-rgJ)
190 | * Ambrose BS (https://twitter.com/ambrosebs) has sponsored tools.analyzer development in his typed clojure campaign (https://www.indiegogo.com/projects/typed-clojure).
191 |
192 | ## YourKit
193 |
194 | YourKit has given an open source license for their profiler, greatly simplifying the profiling of tools.analyzer performance.
195 |
196 | YourKit is kindly supporting open source projects with its full-featured Java Profiler. YourKit, LLC is the creator of innovative and intelligent tools for profiling Java and .NET applications. Take a look at YourKit's leading software products:
197 |
198 | * YourKit Java Profiler and
199 | * YourKit .NET Profiler .
200 |
201 | Releases and Dependency Information
202 | ========================================
203 |
204 | Latest stable release: 1.2.0
205 |
206 | * [All Released Versions](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22tools.analyzer%22)
207 | * [Development Snapshot Versions](https://clojure.org/releases/downloads#_using_clojure_snapshot_releases)
208 |
209 | [Leiningen](https://github.com/technomancy/leiningen) dependency information:
210 |
211 | ```clojure
212 | [org.clojure/tools.analyzer "1.2.0"]
213 | ```
214 | [Maven](https://maven.apache.org/) dependency information:
215 |
216 | ```xml
217 |
218 | org.clojure
219 | tools.analyzer
220 | 1.2.0
221 |
222 | ```
223 |
224 | [Changelog](CHANGELOG.md)
225 | ========================================
226 |
227 | API Index
228 | ========================================
229 |
230 | * [API index](https://clojure.github.io/tools.analyzer)
231 |
232 | Developer Information
233 | ========================================
234 |
235 | * [GitHub project](https://github.com/clojure/tools.analyzer)
236 | * [Bug Tracker](https://clojure.atlassian.net/browse/TANAL)
237 | * [Continuous Integration](https://github.com/clojure/tools.analyzer/actions/workflows/test.yml)
238 |
239 | ## License
240 |
241 | Copyright © Nicola Mometto, Rich Hickey & contributors.
242 |
243 | Distributed under the Eclipse Public License, the same as Clojure.
244 |
--------------------------------------------------------------------------------
/epl.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Eclipse Public License - Version 1.0
8 |
25 |
26 |
27 |
28 |
29 |
30 | Eclipse Public License - v 1.0
31 |
32 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
33 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR
34 | DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
35 | AGREEMENT.
36 |
37 | 1. DEFINITIONS
38 |
39 | "Contribution" means:
40 |
41 | a) in the case of the initial Contributor, the initial
42 | code and documentation distributed under this Agreement, and
43 | b) in the case of each subsequent Contributor:
44 | i) changes to the Program, and
45 | ii) additions to the Program;
46 | where such changes and/or additions to the Program
47 | originate from and are distributed by that particular Contributor. A
48 | Contribution 'originates' from a Contributor if it was added to the
49 | Program by such Contributor itself or anyone acting on such
50 | Contributor's behalf. Contributions do not include additions to the
51 | Program which: (i) are separate modules of software distributed in
52 | conjunction with the Program under their own license agreement, and (ii)
53 | are not derivative works of the Program.
54 |
55 | "Contributor" means any person or entity that distributes
56 | the Program.
57 |
58 | "Licensed Patents" mean patent claims licensable by a
59 | Contributor which are necessarily infringed by the use or sale of its
60 | Contribution alone or when combined with the Program.
61 |
62 | "Program" means the Contributions distributed in accordance
63 | with this Agreement.
64 |
65 | "Recipient" means anyone who receives the Program under
66 | this Agreement, including all Contributors.
67 |
68 | 2. GRANT OF RIGHTS
69 |
70 | a) Subject to the terms of this Agreement, each
71 | Contributor hereby grants Recipient a non-exclusive, worldwide,
72 | royalty-free copyright license to reproduce, prepare derivative works
73 | of, publicly display, publicly perform, distribute and sublicense the
74 | Contribution of such Contributor, if any, and such derivative works, in
75 | source code and object code form.
76 |
77 | b) Subject to the terms of this Agreement, each
78 | Contributor hereby grants Recipient a non-exclusive, worldwide,
79 | royalty-free patent license under Licensed Patents to make, use, sell,
80 | offer to sell, import and otherwise transfer the Contribution of such
81 | Contributor, if any, in source code and object code form. This patent
82 | license shall apply to the combination of the Contribution and the
83 | Program if, at the time the Contribution is added by the Contributor,
84 | such addition of the Contribution causes such combination to be covered
85 | by the Licensed Patents. The patent license shall not apply to any other
86 | combinations which include the Contribution. No hardware per se is
87 | licensed hereunder.
88 |
89 | c) Recipient understands that although each Contributor
90 | grants the licenses to its Contributions set forth herein, no assurances
91 | are provided by any Contributor that the Program does not infringe the
92 | patent or other intellectual property rights of any other entity. Each
93 | Contributor disclaims any liability to Recipient for claims brought by
94 | any other entity based on infringement of intellectual property rights
95 | or otherwise. As a condition to exercising the rights and licenses
96 | granted hereunder, each Recipient hereby assumes sole responsibility to
97 | secure any other intellectual property rights needed, if any. For
98 | example, if a third party patent license is required to allow Recipient
99 | to distribute the Program, it is Recipient's responsibility to acquire
100 | that license before distributing the Program.
101 |
102 | d) Each Contributor represents that to its knowledge it
103 | has sufficient copyright rights in its Contribution, if any, to grant
104 | the copyright license set forth in this Agreement.
105 |
106 | 3. REQUIREMENTS
107 |
108 | A Contributor may choose to distribute the Program in object code
109 | form under its own license agreement, provided that:
110 |
111 | a) it complies with the terms and conditions of this
112 | Agreement; and
113 |
114 | b) its license agreement:
115 |
116 | i) effectively disclaims on behalf of all Contributors
117 | all warranties and conditions, express and implied, including warranties
118 | or conditions of title and non-infringement, and implied warranties or
119 | conditions of merchantability and fitness for a particular purpose;
120 |
121 | ii) effectively excludes on behalf of all Contributors
122 | all liability for damages, including direct, indirect, special,
123 | incidental and consequential damages, such as lost profits;
124 |
125 | iii) states that any provisions which differ from this
126 | Agreement are offered by that Contributor alone and not by any other
127 | party; and
128 |
129 | iv) states that source code for the Program is available
130 | from such Contributor, and informs licensees how to obtain it in a
131 | reasonable manner on or through a medium customarily used for software
132 | exchange.
133 |
134 | When the Program is made available in source code form:
135 |
136 | a) it must be made available under this Agreement; and
137 |
138 | b) a copy of this Agreement must be included with each
139 | copy of the Program.
140 |
141 | Contributors may not remove or alter any copyright notices contained
142 | within the Program.
143 |
144 | Each Contributor must identify itself as the originator of its
145 | Contribution, if any, in a manner that reasonably allows subsequent
146 | Recipients to identify the originator of the Contribution.
147 |
148 | 4. COMMERCIAL DISTRIBUTION
149 |
150 | Commercial distributors of software may accept certain
151 | responsibilities with respect to end users, business partners and the
152 | like. While this license is intended to facilitate the commercial use of
153 | the Program, the Contributor who includes the Program in a commercial
154 | product offering should do so in a manner which does not create
155 | potential liability for other Contributors. Therefore, if a Contributor
156 | includes the Program in a commercial product offering, such Contributor
157 | ("Commercial Contributor") hereby agrees to defend and
158 | indemnify every other Contributor ("Indemnified Contributor")
159 | against any losses, damages and costs (collectively "Losses")
160 | arising from claims, lawsuits and other legal actions brought by a third
161 | party against the Indemnified Contributor to the extent caused by the
162 | acts or omissions of such Commercial Contributor in connection with its
163 | distribution of the Program in a commercial product offering. The
164 | obligations in this section do not apply to any claims or Losses
165 | relating to any actual or alleged intellectual property infringement. In
166 | order to qualify, an Indemnified Contributor must: a) promptly notify
167 | the Commercial Contributor in writing of such claim, and b) allow the
168 | Commercial Contributor to control, and cooperate with the Commercial
169 | Contributor in, the defense and any related settlement negotiations. The
170 | Indemnified Contributor may participate in any such claim at its own
171 | expense.
172 |
173 | For example, a Contributor might include the Program in a commercial
174 | product offering, Product X. That Contributor is then a Commercial
175 | Contributor. If that Commercial Contributor then makes performance
176 | claims, or offers warranties related to Product X, those performance
177 | claims and warranties are such Commercial Contributor's responsibility
178 | alone. Under this section, the Commercial Contributor would have to
179 | defend claims against the other Contributors related to those
180 | performance claims and warranties, and if a court requires any other
181 | Contributor to pay any damages as a result, the Commercial Contributor
182 | must pay those damages.
183 |
184 | 5. NO WARRANTY
185 |
186 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
187 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
188 | OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
189 | ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
190 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
191 | responsible for determining the appropriateness of using and
192 | distributing the Program and assumes all risks associated with its
193 | exercise of rights under this Agreement , including but not limited to
194 | the risks and costs of program errors, compliance with applicable laws,
195 | damage to or loss of data, programs or equipment, and unavailability or
196 | interruption of operations.
197 |
198 | 6. DISCLAIMER OF LIABILITY
199 |
200 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
201 | NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
202 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
203 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
204 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
205 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
206 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
207 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
208 |
209 | 7. GENERAL
210 |
211 | If any provision of this Agreement is invalid or unenforceable under
212 | applicable law, it shall not affect the validity or enforceability of
213 | the remainder of the terms of this Agreement, and without further action
214 | by the parties hereto, such provision shall be reformed to the minimum
215 | extent necessary to make such provision valid and enforceable.
216 |
217 | If Recipient institutes patent litigation against any entity
218 | (including a cross-claim or counterclaim in a lawsuit) alleging that the
219 | Program itself (excluding combinations of the Program with other
220 | software or hardware) infringes such Recipient's patent(s), then such
221 | Recipient's rights granted under Section 2(b) shall terminate as of the
222 | date such litigation is filed.
223 |
224 | All Recipient's rights under this Agreement shall terminate if it
225 | fails to comply with any of the material terms or conditions of this
226 | Agreement and does not cure such failure in a reasonable period of time
227 | after becoming aware of such noncompliance. If all Recipient's rights
228 | under this Agreement terminate, Recipient agrees to cease use and
229 | distribution of the Program as soon as reasonably practicable. However,
230 | Recipient's obligations under this Agreement and any licenses granted by
231 | Recipient relating to the Program shall continue and survive.
232 |
233 | Everyone is permitted to copy and distribute copies of this
234 | Agreement, but in order to avoid inconsistency the Agreement is
235 | copyrighted and may only be modified in the following manner. The
236 | Agreement Steward reserves the right to publish new versions (including
237 | revisions) of this Agreement from time to time. No one other than the
238 | Agreement Steward has the right to modify this Agreement. The Eclipse
239 | Foundation is the initial Agreement Steward. The Eclipse Foundation may
240 | assign the responsibility to serve as the Agreement Steward to a
241 | suitable separate entity. Each new version of the Agreement will be
242 | given a distinguishing version number. The Program (including
243 | Contributions) may always be distributed subject to the version of the
244 | Agreement under which it was received. In addition, after a new version
245 | of the Agreement is published, Contributor may elect to distribute the
246 | Program (including its Contributions) under the new version. Except as
247 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
248 | rights or licenses to the intellectual property of any Contributor under
249 | this Agreement, whether expressly, by implication, estoppel or
250 | otherwise. All rights in the Program not expressly granted under this
251 | Agreement are reserved.
252 |
253 | This Agreement is governed by the laws of the State of New York and
254 | the intellectual property laws of the United States of America. No party
255 | to this Agreement will bring a legal action under this Agreement more
256 | than one year after the cause of action arose. Each party waives its
257 | rights to a jury trial in any resulting litigation.
258 |
259 |
260 |
261 |
262 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | tools.analyzer
5 | 1.2.1-SNAPSHOT
6 | tools.analyzer
7 | An analyzer for Clojure code, written in Clojure and producing AST in EDN
8 |
9 |
10 | 1.9.0
11 |
12 |
13 |
14 | org.clojure
15 | pom.contrib
16 | 1.3.0
17 |
18 |
19 |
20 |
21 | bronsa
22 | Nicola Mometto
23 |
24 |
25 |
26 |
27 | scm:git:git://github.com/clojure/tools.analyzer.git
28 | scm:git:git://github.com/clojure/tools.analyzer.git
29 | https://github.com/clojure/tools.analyzer
30 | HEAD
31 |
32 |
33 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject org.clojure/tools.analyzer "1.0.1-SNAPSHOT"
2 | :description "An analyzer for Clojure code, written in Clojure and producing AST in EDN."
3 | :url "https://github.com/clojure/tools.analyzer"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 | :source-paths ["src/main/clojure"]
7 | :test-paths ["src/test/clojure"]
8 | :dependencies [[org.clojure/clojure "1.10.3"]
9 | [com.datomic/datomic-free "0.9.5327" :scope "provided" :exclusions [joda-time]]]
10 | :repositories [["sonatype" "https://oss.sonatype.org/content/repositories/releases"]
11 | ["snapshots" "https://oss.sonatype.org/content/repositories/snapshots"]])
12 |
--------------------------------------------------------------------------------
/spec/ast-ref.edn:
--------------------------------------------------------------------------------
1 | {:all-keys
2 |
3 | [[:op "The node op"]
4 | [:form "The clojure form from which the node originated"]
5 | [:env "The environment map"]
6 | ^:optional
7 | [:children "A vector of keywords, representing the children nodes of this node, in order of evaluation"]
8 | ^:optional
9 | [:raw-forms "If this node's :form has been macroexpanded, a sequence of all the intermediate forms from the original form to the macroexpanded form"]
10 | ^:optional
11 | [:top-level "`true` if this is the root node"]]
12 |
13 | :node-keys
14 | [{:op :binding
15 | :doc "Node for a binding symbol"
16 | :keys [[:form "The binding symbol"]
17 | [:name "The binding symbol"]
18 | [:local "One of :arg, :catch, :fn, :let, :letfn or :loop"]
19 | ^:optional
20 | [:arg-id "When :local is :arg, the parameter index"]
21 | ^:optional
22 | [:variadic? "When :local is :arg, a boolean indicating whether this parameter binds to a variable number of arguments"]
23 | ^:optional ^:children
24 | [:init "When :local is :let, :letfn or :loop, an AST node representing the bound value"]]}
25 | {:op :catch
26 | :doc "Node for a catch expression"
27 | :keys [[:form "`(catch class local body*)`"]
28 | ^:children
29 | [:class "A :maybe-class AST node representing the type of exception to catch"]
30 | ^:children
31 | [:local "The :binding AST node for the caught exception"]
32 | ^:children
33 | [:body "Synthetic :do AST node (with :body? `true`) representing the body of the catch clause"]]}
34 | {:op :const
35 | :doc "Node for a constant literal or a quoted collection literal"
36 | :keys [[:form "A constant literal or a quoted collection literal"]
37 | [:literal? "`true`"]
38 | [:type "one of :nil, :bool, :keyword, :symbol, :string, :number, :type, :record, :map, :vector, :set, :seq, :char, :regex, :class, :var, or :unknown"]
39 | [:val "The value of the constant node"]
40 | ^:optional ^:children
41 | [:meta "An AST node representing the metadata of the constant value, if present. The node will be either a :map node or a :const node with :type :map"]]}
42 | {:op :def
43 | :doc "Node for a def special-form expression"
44 | :keys [[:form "`(def name docstring? init?)`"]
45 | [:name "The var symbol to define in the current namespace"]
46 | [:var "The var object created (or found, if it already existed) named by the symbol :name in the current namespace"]
47 | ^:optional ^:children
48 | [:meta "An AST node representing the metadata attached to :name, if present. The node will be either a :map node or a :const node with :type :map"]
49 | ^:optional ^:children
50 | [:init "An AST node representing the initial value of the var"]
51 | ^:optional
52 | [:doc "The docstring for this var"]]}
53 | {:op :do
54 | :doc "Node for a do special-form expression or for another special-form's body"
55 | :keys [[:form "`(do statement* ret)`"]
56 | ^:children
57 | [:statements "A vector of AST nodes representing all but the last expression in the do body"]
58 | ^:children
59 | [:ret "An AST node representing the last expression in the do body (the block's return value)"]
60 | ^:optional
61 | [:body? "`true` if this node is a synthetic body"]]}
62 | {:op :fn
63 | :doc "Node for a fn* special-form expression"
64 | :keys [[:form "`(fn* name? [arg*] body*)` or `(fn* name? method*)`"]
65 | [:variadic? "`true` if this function contains a variadic arity method"]
66 | [:max-fixed-arity "The number of arguments taken by the fixed-arity method taking the most arguments"]
67 | ^:optional ^:children
68 | [:local "A :binding AST node with :local :fn representing the function's local name, if one is supplied"]
69 | ^:children
70 | [:methods "A vector of :fn-method AST nodes representing the fn method arities"]
71 | [:once "`true` if the fn is marked as `^:once fn*`, meaning it will only be executed once and thus allowing for the clearing of closed-over locals"]]}
72 | {:op :fn-method
73 | :doc "Node for an arity method in a fn* expression"
74 | :keys [[:form "`([arg*] body*)`"]
75 | [:loop-id "Unique symbol identifying this method as a target for recursion"]
76 | [:variadic? "`true` if this fn-method takes a variable number of arguments"]
77 | ^:children
78 | [:params "A vector of :binding AST nodes with :local :arg representing this fn-method args"]
79 | [:fixed-arity "The number of non-variadic args this fn-method takes"]
80 | ^:children
81 | [:body "Synthetic :do node (with :body? `true`) representing the body of this fn-method"]]}
82 | {:op :host-call
83 | :doc "Node for a host interop call"
84 | :keys [[:form "`(.method target arg*)`"]
85 | [:method "Symbol naming the method to call"]
86 | ^:children
87 | [:target "An AST node representing the target object"]
88 | ^:children
89 | [:args "A vector of AST nodes representing the args passed to the method call"]]}
90 | {:op :host-field
91 | :doc "Node for a host interop field access"
92 | :keys [[:form "`(.-field target)`"]
93 | [:field "Symbol naming the field to access"]
94 | ^:children
95 | [:target "An AST node representing the target object"]
96 | [:assignable? "`true`"]]}
97 | {:op :host-interop
98 | :doc "Node for a no-arg host interop call or for a host interop field access"
99 | :keys [[:form "`(. target m-or-f)`"]
100 | ^:children
101 | [:target "An AST node representing the target object"]
102 | [:m-or-f "Symbol naming the no-arg method or field to access in the target"]
103 | [:assignable? "`true`"]]}
104 | {:op :if
105 | :doc "Node for an if special-form expression"
106 | :keys [[:form "`(if test then else?)`"]
107 | ^:children
108 | [:test "An AST node representing the test expression"]
109 | ^:children
110 | [:then "An AST node representing the expression's return value if :test evaluated to a truthy value"]
111 | ^:children
112 | [:else "An AST node representing the expression's return value if :test evaluated to a falsey value, if not supplied it will default to a :const node representing nil"]]}
113 | {:op :invoke
114 | :doc "Node for an invoke expression"
115 | :keys [[:form "`(f arg*)`"]
116 | ^:children
117 | [:fn "An AST node representing the function to invoke"]
118 | ^:children
119 | [:args "A vector of AST nodes representing the args to the function"]
120 | ^:optional
121 | [:meta "Map of metadata attached to the invoke :form"]]}
122 | {:op :let
123 | :doc "Node for a let* special-form expression"
124 | :keys [[:form "`(let* [binding*] body*)`"]
125 | ^:children
126 | [:bindings "A vector of :binding AST nodes with :local :let"]
127 | ^:children
128 | [:body "Synthetic :do node (with :body? `true`) representing the body of the let expression"]]}
129 | {:op :letfn
130 | :doc "Node for a letfn* special-form expression"
131 | :keys [[:form "`(letfn* [binding*] body*)`"]
132 | ^:children
133 | [:bindings "A vector of :binding AST nodes with :local :letfn"]
134 | ^:children
135 | [:body "Synthetic :do node (with :body? `true`) representing the body of the letfn expression"]]}
136 | {:op :local
137 | :doc "Node for a local symbol"
138 | :keys [[:form "The local symbol"]
139 | [:name "The local symbol"]
140 | [:local "One of :arg, :catch, :fn, :let, :letfn or :loop"]
141 | ^:optional
142 | [:arg-id "When :local is :arg, the parameter index"]
143 | [:assignable? "`true` if the local is mutable"]
144 | ^:optional
145 | [:variadic? "When :local is :arg, a boolean indicating whether this parameter binds to a variable number of arguments"]]}
146 | {:op :loop
147 | :doc "Node a loop* special-form expression"
148 | :keys [[:form "`(loop* [binding*] body*)`"]
149 | ^:children
150 | [:bindings "A vector of :binding AST nodes with :local :loop"]
151 | ^:children
152 | [:body "Synthetic :do node (with :body? `true`) representing the body of the loop expression"]
153 | [:loop-id "Unique symbol identifying this loop as a target for recursion"]]}
154 | {:op :map
155 | :doc "Node for a map literal"
156 | :keys [[:form "`{[key val]*}`"]
157 | ^:children
158 | [:keys "A vector of AST nodes representing the keys of the map"]
159 | ^:children
160 | [:vals "A vector of AST nodes representing the vals of the map"]]}
161 | {:op :maybe-class
162 | :doc "Node for a not-namespaced symbol that couldn't be resolved as a var"
163 | :keys [[:form "The not namespaced symbol"]
164 | [:class "The not namespaced symbol that might represent a class"]]}
165 | {:op :maybe-host-form
166 | :doc "Node for namespaced symbol that couldn't be resolved as a var"
167 | :keys [[:form "The namespaced symbol"]
168 | [:class "The namespace part of the symbol, as a symbol"]
169 | [:field "The name part of the symbol, as a symbol"]]}
170 | {:op :new
171 | :doc "Node for a new special-form expression"
172 | :keys [[:form "`(new Class arg*)`"]
173 | ^:children
174 | [:class "A :maybe-class AST node :class representing the Class to instantiate"]
175 | ^:children
176 | [:args "A vector of AST nodes representing the arguments passed to the Class constructor"]]}
177 | {:op :quote
178 | :doc "Node for a quote special-form expression"
179 | :keys [[:form "`(quote expr)`"]
180 | ^:children
181 | [:expr "A :const AST node representing the quoted value"]
182 | [:literal? "`true`"]]}
183 | {:op :recur
184 | :doc "Node for a recur special-form expression"
185 | :keys [[:form "`(recur expr*)`"]
186 | ^:children
187 | [:exprs "A vector of AST nodes representing the new bound values for the loop binding on the next loop iteration"]
188 | [:loop-id "Unique symbol identifying the enclosing loop target"]]}
189 | {:op :set
190 | :doc "Node for a set literal"
191 | :keys [[:form "`#{item*}`"]
192 | ^:children
193 | [:items "A vector of AST nodes representing the items of the set"]]}
194 | {:op :set!
195 | :doc "Node for a set! special-form expression"
196 | :keys [[:form "`(set! target val)`"]
197 | ^:children
198 | [:target "An AST node representing the target of the set! expression, must be :assignable?"]
199 | ^:children
200 | [:val "An AST node representing the new value for the target"]]}
201 | {:op :throw
202 | :doc "Node for a throw special-form statement"
203 | :keys [[:form "`(throw exception)`"]
204 | ^:children
205 | [:exception "An AST node representing the exception to throw"]]}
206 | {:op :try
207 | :doc "Node for a try special-form expression"
208 | :keys [[:form "`(try body* catch* finally?)`"]
209 | ^:children
210 | [:body "Synthetic :do AST node (with :body? `true`) representing the body of this try expression"]
211 | ^:children
212 | [:catches "A vector of :catch AST nodes representing the catch clauses of this try expression"]
213 | ^:optional ^:children
214 | [:finally "Synthetic :do AST node (with :body? `true`) representing the final clause of this try expression"]]}
215 | {:op :var
216 | :doc "Node for a var symbol"
217 | :keys [[:form "A symbol naming the var"]
218 | [:var "The var object this symbol refers to"]
219 | ^:optional
220 | [:assignable? "`true` if the Var is :dynamic"]]}
221 | {:op :vector
222 | :doc "Node for a vector literal with attached metadata and/or non literal elements"
223 | :keys [[:form "`[item*]`"]
224 | ^:children
225 | [:items "A vector of AST nodes representing the items of the vector"]]}
226 | {:op :with-meta
227 | :doc "Node for a non quoted collection literal or a fn expression with attached metadata"
228 | :keys [[:form "Non quoted collection literal or fn expression with attached metadata"]
229 | ^:children
230 | [:meta "An AST node representing the metadata of expression. The node will be either a :map node or a :const node with :type :map"]
231 | ^:children
232 | [:expr "The expression this metadata is attached to, :op is one of :vector, :map, :set or :fn"]]}]}
233 |
--------------------------------------------------------------------------------
/spec/buildref.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | java -cp .:`lein cp` clojure.main < (str x)
8 | (replace #"`(.*?)`" "$1
")
9 | (replace #":([a-zA-Z\?!\-]*)" ":$1
")))
10 |
11 | (defn build-children [children]
12 | (if (some #(:optional (meta %)) children)
13 | (let [[c & rest] children]
14 | (let [k (build-children rest)
15 | kc (mapv (fn [x] (cons c x)) k)]
16 | (if (:optional (meta c))
17 | (into k kc)
18 | kc)))
19 | (if (seq children)
20 | [children]
21 | [[]])))
22 |
23 | (defn children [keys]
24 | (when-let [children (seq (filter #(:children (meta %)) keys))]
25 | (mapv #(mapv first %) (build-children children))))
26 |
27 | (def nodes
28 | (apply str (for [{:keys [op doc keys]} (:node-keys tej-ref) :let [op (name op)]]
29 | (str ""
30 | "" "# " op " "
31 | "" doc " "
32 | ""
33 | " :op :" op "
"
34 | (apply str (for [[k d :as f] keys]
35 | (str "" k " "
36 | "" (if (:optional (meta f))
37 | "optional ") (fix d) " ")))
38 | (if-let [c (children keys)]
39 | (str " :children "
40 | (join ", " (mapv (fn [c] (str "" c "
")) c)) " "))
41 | " "
42 | " \n"))))
43 |
44 | (def nav
45 | (apply str (for [{op :op} (:node-keys tej-ref) :let [op (name op)]]
46 | (str "" op " \n"))))
47 |
48 | (def common
49 | (apply str (str ""
50 | ""
51 | (apply str (for [[k d :as f] (:all-keys tej-ref)]
52 | (str "" k " "
53 | "" (if (:optional (meta f))
54 | "optional ") (fix d) " ")))
55 | " "
56 | " \n")))
57 |
58 | (spit "quickref.html"
59 | (-> html
60 | (replace "{nav}" nav)
61 | (replace "{common}" common)
62 | (replace "{nodes}" nodes)))
63 |
--------------------------------------------------------------------------------
/spec/quickref.html.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | tools.analyzer AST Quickref
6 |
94 |
95 |
96 |
97 | Nodes reference
98 |
101 |
102 |
103 | tools.analyzer AST Quickref
104 | Common AST fields
105 | {common}
106 | Nodes reference
107 | {nodes}
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer
10 | "Analyzer for clojure code, host agnostic.
11 |
12 | Entry point:
13 | * analyze
14 |
15 | Platform implementers must provide dynamic bindings for:
16 | * macroexpand-1
17 | * parse
18 | * create-var
19 | * var?
20 |
21 | Setting up the global env is also required, see clojure.tools.analyzer.env
22 |
23 | See clojure.tools.analyzer.core-test for an example on how to setup the analyzer."
24 | (:refer-clojure :exclude [macroexpand-1 macroexpand var? record? boolean? update-keys update-vals])
25 | (:require [clojure.tools.analyzer.utils :refer :all]
26 | [clojure.tools.analyzer.env :as env])
27 | (:import (clojure.lang Symbol IPersistentVector IPersistentMap IPersistentSet ISeq IType IRecord)))
28 |
29 | (derive :ctx/return :ctx/expr)
30 |
31 | (defmulti -analyze-form (fn [form _] (class form)))
32 |
33 | (declare analyze-symbol
34 | analyze-vector
35 | analyze-map
36 | analyze-set
37 | analyze-seq
38 | analyze-const)
39 |
40 | (def ^:dynamic analyze-form
41 | "Like analyze, but does not mark the form with :top-level true"
42 | -analyze-form)
43 |
44 | (defmethod -analyze-form Symbol
45 | [form env]
46 | (analyze-symbol form env))
47 |
48 | (defmethod -analyze-form IPersistentVector
49 | [form env]
50 | (analyze-vector form env))
51 |
52 | (defmethod -analyze-form IPersistentMap
53 | [form env]
54 | (analyze-map form env))
55 |
56 | (defmethod -analyze-form IPersistentSet
57 | [form env]
58 | (analyze-set form env))
59 |
60 | (defmethod -analyze-form ISeq
61 | [form env]
62 | (if-let [form (seq form)]
63 | (analyze-seq form env)
64 | (analyze-const form env)))
65 |
66 | (defmethod -analyze-form IType
67 | [form env]
68 | (analyze-const form env :type))
69 |
70 | (prefer-method -analyze-form IType IPersistentMap)
71 | (prefer-method -analyze-form IType IPersistentVector)
72 | (prefer-method -analyze-form IType IPersistentSet)
73 | (prefer-method -analyze-form IType ISeq)
74 |
75 | (defmethod -analyze-form IRecord
76 | [form env]
77 | (analyze-const form env :record))
78 |
79 | (prefer-method -analyze-form IRecord IPersistentMap)
80 | (prefer-method -analyze-form IRecord IPersistentVector)
81 | (prefer-method -analyze-form IRecord IPersistentSet)
82 | (prefer-method -analyze-form IRecord ISeq)
83 |
84 | (defmethod -analyze-form :default
85 | [form env]
86 | (analyze-const form env))
87 |
88 | (defn analyze
89 | "Given a form to analyze and an environment, a map containing:
90 | * :locals a map from binding symbol to AST of the binding value
91 | * :context a keyword describing the form's context from the :ctx/* hierarchy.
92 | ** :ctx/expr the form is an expression: its value is used
93 | ** :ctx/return the form is an expression in return position, derives :ctx/expr
94 | ** :ctx/statement the value of the form is not used
95 | * :ns a symbol representing the current namespace of the form to be
96 | analyzed
97 |
98 | returns an AST for that form.
99 |
100 | Every node in the AST is a map that is *guaranteed* to have the following keys:
101 | * :op a keyword describing the AST node
102 | * :form the form represented by the AST node
103 | * :env the environment map of the AST node
104 |
105 | Additionally if the AST node contains sub-nodes, it is guaranteed to have:
106 | * :children a vector of the keys of the AST node mapping to the sub-nodes,
107 | ordered, when that makes sense
108 |
109 | It is considered a node either the top-level node (marked with :top-level true)
110 | or a node that can be reached via :children; if a node contains a node-like
111 | map that is not reachable by :children, there's no guarantee that such a map
112 | will contain the guaranteed keys."
113 |
114 | [form env]
115 | (assoc (analyze-form form env) :top-level true))
116 |
117 | (defn empty-env
118 | "Returns an empty env"
119 | []
120 | {:context :ctx/expr
121 | :locals {}
122 | :ns 'user})
123 |
124 | (defn analyze-in-env
125 | "Takes an env map and returns a function that analyzes a form in that env"
126 | [env]
127 | (fn [form] (analyze-form form env)))
128 |
129 | (def ^{:dynamic true
130 | :arglists '([form env])
131 | :doc "If form represents a macro form, returns its expansion,
132 | else returns form."}
133 | macroexpand-1)
134 |
135 | (def ^{:dynamic true
136 | :arglists '([[op & args] env])
137 | :doc "Multimethod that dispatches on op, should default to -parse"}
138 | parse)
139 |
140 | (def ^{:dynamic true
141 | :arglists '([sym env])
142 | :doc "Creates a var for sym and returns it"}
143 | create-var)
144 |
145 | (def ^{:dynamic true
146 | :arglists '([obj])
147 | :doc "Returns true if obj represent a var form as returned by create-var"}
148 | var?)
149 |
150 | ;; this node wraps non-quoted collections literals with metadata attached
151 | ;; to them, the metadata will be evaluated at run-time, not treated like a constant
152 | (defn wrapping-meta
153 | [{:keys [form env] :as expr}]
154 | (let [meta (meta form)]
155 | (if (and (obj? form)
156 | (seq meta))
157 | {:op :with-meta
158 | :env env
159 | :form form
160 | :meta (analyze-form meta (ctx env :ctx/expr))
161 | :expr (assoc-in expr [:env :context] :ctx/expr)
162 | :children [:meta :expr]}
163 | expr)))
164 |
165 | (defn analyze-const
166 | [form env & [type]]
167 | (let [type (or type (classify form))]
168 | (merge
169 | {:op :const
170 | :env env
171 | :type type
172 | :literal? true
173 | :val form
174 | :form form}
175 | (when-let [m (and (obj? form)
176 | (not-empty (meta form)))]
177 | {:meta (analyze-const m (ctx env :ctx/expr) :map) ;; metadata on a constant literal will not be evaluated at
178 | :children [:meta]})))) ;; runtime, this is also true for metadata on quoted collection literals
179 |
180 | (defn analyze-vector
181 | [form env]
182 | (let [items-env (ctx env :ctx/expr)
183 | items (mapv (analyze-in-env items-env) form)]
184 | (wrapping-meta
185 | {:op :vector
186 | :env env
187 | :items items
188 | :form form
189 | :children [:items]})))
190 |
191 | (defn analyze-map
192 | [form env]
193 | (let [kv-env (ctx env :ctx/expr)
194 | [keys vals] (reduce-kv (fn [[keys vals] k v]
195 | [(conj keys k) (conj vals v)])
196 | [[] []] form)
197 | ks (mapv (analyze-in-env kv-env) keys)
198 | vs (mapv (analyze-in-env kv-env) vals)]
199 | (wrapping-meta
200 | {:op :map
201 | :env env
202 | :keys ks
203 | :vals vs
204 | :form form
205 | :children [:keys :vals]})))
206 |
207 | (defn analyze-set
208 | [form env]
209 | (let [items-env (ctx env :ctx/expr)
210 | items (mapv (analyze-in-env items-env) form)]
211 | (wrapping-meta
212 | {:op :set
213 | :env env
214 | :items items
215 | :form form
216 | :children [:items]})))
217 |
218 | (def specials
219 | "Set of special forms common to every clojure variant"
220 | '#{do if new quote set! try var
221 | catch throw finally def .
222 | let* letfn* loop* recur fn*})
223 |
224 | (defn macroexpand
225 | "Repeatedly calls macroexpand-1 on form until it no longer
226 | represents a macro form, then returns it."
227 | [form env]
228 | (loop [form form]
229 | (let [mform (macroexpand-1 form env)]
230 | (if (= mform form)
231 | mform
232 | (recur mform)))))
233 |
234 | (defn analyze-symbol
235 | [sym env]
236 | (let [mform (macroexpand-1 sym env)] ;; t.a.j/macroexpand-1 macroexpands Class/Field into (. Class Field)
237 | (if (= mform sym)
238 | (merge (if-let [{:keys [mutable children] :as local-binding} (-> env :locals sym)] ;; locals shadow globals
239 | (merge (dissoc local-binding :init) ;; avoids useless passes later
240 | {:op :local
241 | :assignable? (boolean mutable)
242 | :children (vec (remove #{:init} children))})
243 | (if-let [var (let [v (resolve-sym sym env)]
244 | (and (var? v) v))]
245 | (let [m (meta var)]
246 | {:op :var
247 | :assignable? (dynamic? var m) ;; we cannot statically determine if a Var is in a thread-local context
248 | :var var ;; so checking whether it's dynamic or not is the most we can do
249 | :meta m})
250 | (if-let [maybe-class (namespace sym)] ;; e.g. js/foo.bar or Long/MAX_VALUE
251 | (let [maybe-class (symbol maybe-class)]
252 | {:op :maybe-host-form
253 | :class maybe-class
254 | :field (symbol (name sym))})
255 | {:op :maybe-class ;; e.g. java.lang.Integer or Long
256 | :class mform})))
257 | {:env env
258 | :form mform})
259 | (-> (analyze-form mform env)
260 | (update-in [:raw-forms] (fnil conj ()) sym)))))
261 |
262 | (defn analyze-seq
263 | [form env]
264 | (let [op (first form)]
265 | (when (nil? op)
266 | (throw (ex-info "Can't call nil"
267 | (merge {:form form}
268 | (-source-info form env)))))
269 | (let [mform (macroexpand-1 form env)]
270 | (if (= form mform) ;; function/special-form invocation
271 | (parse mform env)
272 | (-> (analyze-form mform env)
273 | (update-in [:raw-forms] (fnil conj ())
274 | (vary-meta form assoc ::resolved-op (resolve-sym op env))))))))
275 |
276 | (defn parse-do
277 | [[_ & exprs :as form] env]
278 | (let [statements-env (ctx env :ctx/statement)
279 | [statements ret] (loop [statements [] [e & exprs] exprs]
280 | (if (seq exprs)
281 | (recur (conj statements e) exprs)
282 | [statements e]))
283 | statements (mapv (analyze-in-env statements-env) statements)
284 | ret (analyze-form ret env)]
285 | {:op :do
286 | :env env
287 | :form form
288 | :statements statements
289 | :ret ret
290 | :children [:statements :ret]}))
291 |
292 | (defn parse-if
293 | [[_ test then else :as form] env]
294 | (let [formc (count form)]
295 | (when-not (or (= formc 3) (= formc 4))
296 | (throw (ex-info (str "Wrong number of args to if, had: " (dec (count form)))
297 | (merge {:form form}
298 | (-source-info form env))))))
299 | (let [test-expr (analyze-form test (ctx env :ctx/expr))
300 | then-expr (analyze-form then env)
301 | else-expr (analyze-form else env)]
302 | {:op :if
303 | :form form
304 | :env env
305 | :test test-expr
306 | :then then-expr
307 | :else else-expr
308 | :children [:test :then :else]}))
309 |
310 | (defn parse-new
311 | [[_ class & args :as form] env]
312 | (when-not (>= (count form) 2)
313 | (throw (ex-info (str "Wrong number of args to new, had: " (dec (count form)))
314 | (merge {:form form}
315 | (-source-info form env)))))
316 | (let [args-env (ctx env :ctx/expr)
317 | args (mapv (analyze-in-env args-env) args)]
318 | {:op :new
319 | :env env
320 | :form form
321 | :class (analyze-form class (assoc env :locals {})) ;; avoid shadowing
322 | :args args
323 | :children [:class :args]}))
324 |
325 | (defn parse-quote
326 | [[_ expr :as form] env]
327 | (when-not (= 2 (count form))
328 | (throw (ex-info (str "Wrong number of args to quote, had: " (dec (count form)))
329 | (merge {:form form}
330 | (-source-info form env)))))
331 | (let [const (analyze-const expr env)]
332 | {:op :quote
333 | :expr const
334 | :form form
335 | :env env
336 | :literal? true
337 | :children [:expr]}))
338 |
339 | (defn parse-set!
340 | [[_ target val :as form] env]
341 | (when-not (= 3 (count form))
342 | (throw (ex-info (str "Wrong number of args to set!, had: " (dec (count form)))
343 | (merge {:form form}
344 | (-source-info form env)))))
345 | (let [target (analyze-form target (ctx env :ctx/expr))
346 | val (analyze-form val (ctx env :ctx/expr))]
347 | {:op :set!
348 | :env env
349 | :form form
350 | :target target
351 | :val val
352 | :children [:target :val]}))
353 |
354 | (defn analyze-body [body env]
355 | ;; :body is used by emit-form to remove the artificial 'do
356 | (assoc (parse (cons 'do body) env) :body? true))
357 |
358 | (defn valid-binding-symbol? [s]
359 | (and (symbol? s)
360 | (not (namespace s))
361 | (not (re-find #"\." (name s)))))
362 |
363 | (defn ^:private split-with' [pred coll]
364 | (loop [take [] drop coll]
365 | (if (seq drop)
366 | (let [[el & r] drop]
367 | (if (pred el)
368 | (recur (conj take el) r)
369 | [(seq take) drop]))
370 | [(seq take) ()])))
371 |
372 | (declare parse-catch)
373 | (defn parse-try
374 | [[_ & body :as form] env]
375 | (let [catch? (every-pred seq? #(= (first %) 'catch))
376 | finally? (every-pred seq? #(= (first %) 'finally))
377 | [body tail'] (split-with' (complement (some-fn catch? finally?)) body)
378 | [cblocks tail] (split-with' catch? tail')
379 | [[fblock & fbs :as fblocks] tail] (split-with' finally? tail)]
380 | (when-not (empty? tail)
381 | (throw (ex-info "Only catch or finally clause can follow catch in try expression"
382 | (merge {:expr tail
383 | :form form}
384 | (-source-info form env)))))
385 | (when-not (empty? fbs)
386 | (throw (ex-info "Only one finally clause allowed in try expression"
387 | (merge {:expr fblocks
388 | :form form}
389 | (-source-info form env)))))
390 | (let [env' (assoc env :in-try true)
391 | body (analyze-body body env')
392 | cenv (ctx env' :ctx/expr)
393 | cblocks (mapv #(parse-catch % cenv) cblocks)
394 | fblock (when-not (empty? fblock)
395 | (analyze-body (rest fblock) (ctx env :ctx/statement)))]
396 | (merge {:op :try
397 | :env env
398 | :form form
399 | :body body
400 | :catches cblocks}
401 | (when fblock
402 | {:finally fblock})
403 | {:children (into [:body :catches]
404 | (when fblock [:finally]))}))))
405 |
406 | (defn parse-catch
407 | [[_ etype ename & body :as form] env]
408 | (when-not (valid-binding-symbol? ename)
409 | (throw (ex-info (str "Bad binding form: " ename)
410 | (merge {:sym ename
411 | :form form}
412 | (-source-info form env)))))
413 | (let [env (dissoc env :in-try)
414 | local {:op :binding
415 | :env env
416 | :form ename
417 | :name ename
418 | :local :catch}]
419 | {:op :catch
420 | :class (analyze-form etype (assoc env :locals {}))
421 | :local local
422 | :env env
423 | :form form
424 | :body (analyze-body body (assoc-in env [:locals ename] (dissoc-env local)))
425 | :children [:class :local :body]}))
426 |
427 | (defn parse-throw
428 | [[_ throw :as form] env]
429 | (when-not (= 2 (count form))
430 | (throw (ex-info (str "Wrong number of args to throw, had: " (dec (count form)))
431 | (merge {:form form}
432 | (-source-info form env)))))
433 | {:op :throw
434 | :env env
435 | :form form
436 | :exception (analyze-form throw (ctx env :ctx/expr))
437 | :children [:exception]})
438 |
439 | (defn validate-bindings
440 | [[op bindings & _ :as form] env]
441 | (when-let [error-msg
442 | (cond
443 | (not (vector? bindings))
444 | (str op " requires a vector for its bindings, had: "
445 | (class bindings))
446 |
447 | (not (even? (count bindings)))
448 | (str op " requires an even number of forms in binding vector, had: "
449 | (count bindings)))]
450 | (throw (ex-info error-msg
451 | (merge {:form form
452 | :bindings bindings}
453 | (-source-info form env))))))
454 |
455 | (defn parse-letfn*
456 | [[_ bindings & body :as form] env]
457 | (validate-bindings form env)
458 | (let [bindings (apply array-map bindings) ;; pick only one local with the same name, if more are present.
459 | fns (keys bindings)]
460 | (when-let [[sym] (seq (remove valid-binding-symbol? fns))]
461 | (throw (ex-info (str "Bad binding form: " sym)
462 | (merge {:form form
463 | :sym sym}
464 | (-source-info form env)))))
465 | (let [binds (reduce (fn [binds name]
466 | (assoc binds name
467 | {:op :binding
468 | :env env
469 | :name name
470 | :form name
471 | :local :letfn}))
472 | {} fns)
473 | e (update-in env [:locals] merge binds) ;; pre-seed locals
474 | binds (reduce-kv (fn [binds name bind]
475 | (assoc binds name
476 | (merge bind
477 | {:init (analyze-form (bindings name)
478 | (ctx e :ctx/expr))
479 | :children [:init]})))
480 | {} binds)
481 | e (update-in env [:locals] merge (update-vals binds dissoc-env))
482 | body (analyze-body body e)]
483 | {:op :letfn
484 | :env env
485 | :form form
486 | :bindings (vec (vals binds)) ;; order is irrelevant
487 | :body body
488 | :children [:bindings :body]})))
489 |
490 | (defn analyze-let
491 | [[op bindings & body :as form] {:keys [context loop-id] :as env}]
492 | (validate-bindings form env)
493 | (let [loop? (= 'loop* op)]
494 | (loop [bindings bindings
495 | env (ctx env :ctx/expr)
496 | binds []]
497 | (if-let [[name init & bindings] (seq bindings)]
498 | (if (not (valid-binding-symbol? name))
499 | (throw (ex-info (str "Bad binding form: " name)
500 | (merge {:form form
501 | :sym name}
502 | (-source-info form env))))
503 | (let [init-expr (analyze-form init env)
504 | bind-expr {:op :binding
505 | :env env
506 | :name name
507 | :init init-expr
508 | :form name
509 | :local (if loop? :loop :let)
510 | :children [:init]}]
511 | (recur bindings
512 | (assoc-in env [:locals name] (dissoc-env bind-expr))
513 | (conj binds bind-expr))))
514 | (let [body-env (assoc env :context (if loop? :ctx/return context))
515 | body (analyze-body body (merge body-env
516 | (when loop?
517 | {:loop-id loop-id
518 | :loop-locals (count binds)})))]
519 | {:body body
520 | :bindings binds
521 | :children [:bindings :body]})))))
522 |
523 | (defn parse-let*
524 | [form env]
525 | (into {:op :let
526 | :form form
527 | :env env}
528 | (analyze-let form env)))
529 |
530 | (defn parse-loop*
531 | [form env]
532 | (let [loop-id (gensym "loop_") ;; can be used to find matching recur
533 | env (assoc env :loop-id loop-id)]
534 | (into {:op :loop
535 | :form form
536 | :env env
537 | :loop-id loop-id}
538 | (analyze-let form env))))
539 |
540 | (defn parse-recur
541 | [[_ & exprs :as form] {:keys [context loop-locals loop-id]
542 | :as env}]
543 | (when-let [error-msg
544 | (cond
545 | (not (isa? context :ctx/return))
546 | "Can only recur from tail position"
547 |
548 | (not (= (count exprs) loop-locals))
549 | (str "Mismatched argument count to recur, expected: " loop-locals
550 | " args, had: " (count exprs)))]
551 | (throw (ex-info error-msg
552 | (merge {:exprs exprs
553 | :form form}
554 | (-source-info form env)))))
555 |
556 | (let [exprs (mapv (analyze-in-env (ctx env :ctx/expr)) exprs)]
557 | {:op :recur
558 | :env env
559 | :form form
560 | :exprs exprs
561 | :loop-id loop-id
562 | :children [:exprs]}))
563 |
564 | (defn analyze-fn-method [[params & body :as form] {:keys [locals local] :as env}]
565 | (when-not (vector? params)
566 | (throw (ex-info "Parameter declaration should be a vector"
567 | (merge {:params params
568 | :form form}
569 | (-source-info form env)
570 | (-source-info params env)))))
571 | (when (not-every? valid-binding-symbol? params)
572 | (throw (ex-info (str "Params must be valid binding symbols, had: "
573 | (mapv class params))
574 | (merge {:params params
575 | :form form}
576 | (-source-info form env)
577 | (-source-info params env))))) ;; more specific
578 | (let [variadic? (boolean (some '#{&} params))
579 | params-names (if variadic? (conj (pop (pop params)) (peek params)) params)
580 | env (dissoc env :local)
581 | arity (count params-names)
582 | params-expr (mapv (fn [name id]
583 | {:env env
584 | :form name
585 | :name name
586 | :variadic? (and variadic?
587 | (= id (dec arity)))
588 | :op :binding
589 | :arg-id id
590 | :local :arg})
591 | params-names (range))
592 | fixed-arity (if variadic?
593 | (dec arity)
594 | arity)
595 | loop-id (gensym "loop_")
596 | body-env (into (update-in env [:locals]
597 | merge (zipmap params-names (map dissoc-env params-expr)))
598 | {:context :ctx/return
599 | :loop-id loop-id
600 | :loop-locals (count params-expr)})
601 | body (analyze-body body body-env)]
602 | (when variadic?
603 | (let [x (drop-while #(not= % '&) params)]
604 | (when (contains? #{nil '&} (second x))
605 | (throw (ex-info "Invalid parameter list"
606 | (merge {:params params
607 | :form form}
608 | (-source-info form env)
609 | (-source-info params env)))))
610 | (when (not= 2 (count x))
611 | (throw (ex-info (str "Unexpected parameter: " (first (drop 2 x))
612 | " after variadic parameter: " (second x))
613 | (merge {:params params
614 | :form form}
615 | (-source-info form env)
616 | (-source-info params env)))))))
617 | (merge
618 | {:op :fn-method
619 | :form form
620 | :loop-id loop-id
621 | :env env
622 | :variadic? variadic?
623 | :params params-expr
624 | :fixed-arity fixed-arity
625 | :body body
626 | :children [:params :body]}
627 | (when local
628 | {:local (dissoc-env local)}))))
629 |
630 | (defn parse-fn*
631 | [[op & args :as form] env]
632 | (wrapping-meta
633 | (let [[n meths] (if (symbol? (first args))
634 | [(first args) (next args)]
635 | [nil (seq args)])
636 | name-expr {:op :binding
637 | :env env
638 | :form n
639 | :local :fn
640 | :name n}
641 | e (if n (assoc (assoc-in env [:locals n] (dissoc-env name-expr)) :local name-expr) env)
642 | once? (-> op meta :once boolean)
643 | menv (assoc (dissoc e :in-try) :once once?)
644 | meths (if (vector? (first meths)) (list meths) meths) ;;turn (fn [] ...) into (fn ([]...))
645 | methods-exprs (mapv #(analyze-fn-method % menv) meths)
646 | variadic (seq (filter :variadic? methods-exprs))
647 | variadic? (boolean variadic)
648 | fixed-arities (seq (map :fixed-arity (remove :variadic? methods-exprs)))
649 | max-fixed-arity (when fixed-arities (apply max fixed-arities))]
650 | (when (>= (count variadic) 2)
651 | (throw (ex-info "Can't have more than 1 variadic overload"
652 | (merge {:variadics (mapv :form variadic)
653 | :form form}
654 | (-source-info form env)))))
655 | (when (not= (seq (distinct fixed-arities)) fixed-arities)
656 | (throw (ex-info "Can't have 2 or more overloads with the same arity"
657 | (merge {:form form}
658 | (-source-info form env)))))
659 | (when (and variadic?
660 | (not-every? #(<= (:fixed-arity %)
661 | (:fixed-arity (first variadic)))
662 | (remove :variadic? methods-exprs)))
663 | (throw (ex-info "Can't have fixed arity overload with more params than variadic overload"
664 | (merge {:form form}
665 | (-source-info form env)))))
666 | (merge {:op :fn
667 | :env env
668 | :form form
669 | :variadic? variadic?
670 | :max-fixed-arity max-fixed-arity
671 | :methods methods-exprs
672 | :once once?}
673 | (when n
674 | {:local name-expr})
675 | {:children (conj (if n [:local] []) :methods)}))))
676 |
677 | (defn parse-def
678 | [[_ sym & expr :as form] {:keys [ns] :as env}]
679 | (when (not (symbol? sym))
680 | (throw (ex-info (str "First argument to def must be a symbol, had: " (class sym))
681 | (merge {:form form}
682 | (-source-info form env)))))
683 | (when (and (namespace sym)
684 | (not= *ns* (the-ns (symbol (namespace sym)))))
685 | (throw (ex-info "Cannot def namespace qualified symbol"
686 | (merge {:form form
687 | :sym sym}
688 | (-source-info form env)))))
689 | (let [pfn (fn
690 | ([])
691 | ([init]
692 | {:init init})
693 | ([doc init]
694 | {:pre [(string? doc)]}
695 | {:init init :doc doc}))
696 | args (apply pfn expr)
697 |
698 | doc (or (:doc args) (-> sym meta :doc))
699 | arglists (when-let [arglists (:arglists (meta sym))]
700 | (second arglists)) ;; drop quote
701 |
702 | sym (with-meta (symbol (name sym))
703 | (merge (meta sym)
704 | (when arglists
705 | {:arglists arglists})
706 | (when doc
707 | {:doc doc})
708 | (-source-info form env)))
709 |
710 | var (create-var sym env) ;; interned var will have quoted arglists, replaced on evaluation
711 | _ (env/deref-env) ;; make sure *env* is bound
712 | _ (swap! env/*env* assoc-in [:namespaces ns :mappings sym] var)
713 |
714 | meta (merge (meta sym)
715 | (when arglists
716 | {:arglists (list 'quote arglists)}))
717 |
718 | meta-expr (when meta (analyze-form meta (ctx env :ctx/expr))) ;; meta on def sym will be evaluated
719 |
720 | args (when-let [[_ init] (find args :init)]
721 | (assoc args :init (analyze-form init (ctx env :ctx/expr))))
722 | init? (:init args)
723 | children (into (into [] (when meta [:meta]))
724 | (when init? [:init]))]
725 |
726 | (merge {:op :def
727 | :env env
728 | :form form
729 | :name sym
730 | :var var}
731 | (when meta
732 | {:meta meta-expr})
733 | args
734 | (when-not (empty? children)
735 | {:children children}))))
736 |
737 | (defn parse-dot
738 | [[_ target & [m-or-f & args] :as form] env]
739 | (when-not (>= (count form) 3)
740 | (throw (ex-info (str "Wrong number of args to ., had: " (dec (count form)))
741 | (merge {:form form}
742 | (-source-info form env)))))
743 | (let [[m-or-f field?] (if (and (symbol? m-or-f)
744 | (= \- (first (name m-or-f))))
745 | [(-> m-or-f name (subs 1) symbol) true]
746 | [(if args (cons m-or-f args) m-or-f) false])
747 | target-expr (analyze-form target (ctx env :ctx/expr))
748 | call? (and (not field?) (seq? m-or-f))]
749 |
750 | (when (and call? (not (symbol? (first m-or-f))))
751 | (throw (ex-info (str "Method name must be a symbol, had: " (class (first m-or-f)))
752 | (merge {:form form
753 | :method m-or-f}
754 | (-source-info form env)))))
755 | (merge {:form form
756 | :env env
757 | :target target-expr}
758 | (cond
759 | call?
760 | {:op :host-call
761 | :method (symbol (name (first m-or-f)))
762 | :args (mapv (analyze-in-env (ctx env :ctx/expr)) (next m-or-f))
763 | :children [:target :args]}
764 |
765 | field?
766 | {:op :host-field
767 | :assignable? true
768 | :field (symbol (name m-or-f))
769 | :children [:target]}
770 |
771 | :else
772 | {:op :host-interop ;; either field access or no-args method call
773 | :assignable? true
774 | :m-or-f (symbol (name m-or-f))
775 | :children [:target]}))))
776 |
777 | (defn parse-invoke
778 | [[f & args :as form] env]
779 | (let [fenv (ctx env :ctx/expr)
780 | fn-expr (analyze-form f fenv)
781 | args-expr (mapv (analyze-in-env fenv) args)
782 | m (meta form)]
783 | (merge {:op :invoke
784 | :form form
785 | :env env
786 | :fn fn-expr
787 | :args args-expr}
788 | (when (seq m)
789 | {:meta m}) ;; meta on invoke form will not be evaluated
790 | {:children [:fn :args]})))
791 |
792 | (defn parse-var
793 | [[_ var :as form] env]
794 | (when-not (= 2 (count form))
795 | (throw (ex-info (str "Wrong number of args to var, had: " (dec (count form)))
796 | (merge {:form form}
797 | (-source-info form env)))))
798 | (if-let [var (resolve-sym var env)]
799 | {:op :the-var
800 | :env env
801 | :form form
802 | :var var}
803 | (throw (ex-info (str "var not found: " var) {:var var}))))
804 |
805 | (defn -parse
806 | "Takes a form and an env map and dispatches on the head of the form, that is
807 | a special form."
808 | [form env]
809 | ((case (first form)
810 | do parse-do
811 | if parse-if
812 | new parse-new
813 | quote parse-quote
814 | set! parse-set!
815 | try parse-try
816 | throw parse-throw
817 | def parse-def
818 | . parse-dot
819 | let* parse-let*
820 | letfn* parse-letfn*
821 | loop* parse-loop*
822 | recur parse-recur
823 | fn* parse-fn*
824 | var parse-var
825 | #_:else parse-invoke)
826 | form env))
827 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/ast.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.ast
10 | "Utilities for AST walking/updating"
11 | (:refer-clojure :exclude [unreduced])
12 | (:require [clojure.tools.analyzer.utils :refer [into! rseqv mapv']]))
13 |
14 | (defn cycling
15 | "Combine the given passes in a single pass that will be applied repeatedly
16 | to the AST until applying it another time will have no effect"
17 | [& fns*]
18 | (let [fns (cycle fns*)]
19 | (fn [ast]
20 | (loop [[f & fns] fns ast ast res (zipmap fns* (repeat nil))]
21 | (let [ast* (f ast)]
22 | (if (= ast* (res f))
23 | ast
24 | (recur fns ast* (assoc res f ast*))))))))
25 |
26 | (defn children*
27 | "Return a vector of vectors of the children node key and the children expression
28 | of the AST node, if it has any.
29 | The returned vector returns the children in the order as they appear in the
30 | :children field of the AST, and the children expressions may be either a node
31 | or a vector of nodes."
32 | [{:keys [children] :as ast}]
33 | (when children
34 | (mapv #(find ast %) children)))
35 |
36 | (defn children
37 | "Return a vector of the children expression of the AST node, if it has any.
38 | The children expressions are kept in order and flattened so that the returning
39 | vector contains only nodes and not vectors of nodes."
40 | [ast]
41 | (persistent!
42 | (reduce (fn [acc [_ c]] ((if (vector? c) into! conj!) acc c))
43 | (transient []) (children* ast))))
44 |
45 | ;; return transient or reduced holding transient
46 | (defn ^:private -update-children
47 | [ast f r?]
48 | (let [fix (if r? rseqv identity)]
49 | (reduce (fn [ast [k v]]
50 | (let [multi (vector? v)
51 | val (if multi (mapv' f (fix v)) (f v))]
52 | (if (reduced? val)
53 | (reduced (reduced (assoc! ast k (if multi (fix @val) @val))))
54 | (assoc! ast k (if multi (fix val) val)))))
55 | (transient ast)
56 | (fix (children* ast)))))
57 |
58 | (defn update-children-reduced
59 | "Like update-children but returns a reduced holding the AST if f short-circuited."
60 | ([ast f] (update-children-reduced ast f false))
61 | ([ast f reversed?]
62 | (if (and (not (reduced? ast))
63 | (:children ast))
64 | (let [ret (-update-children ast f reversed?)]
65 | (if (reduced? ret)
66 | (reduced (persistent! @ret))
67 | (persistent! ret)))
68 | ast)))
69 |
70 | (defn ^:private unreduced [x]
71 | (if (reduced? x)
72 | @x
73 | x))
74 |
75 | (defn update-children
76 | "Applies `f` to each AST children node, replacing it with the returned value.
77 | If reversed? is not-nil, `pre` and `post` will be applied starting from the last
78 | children of the AST node to the first one.
79 | Short-circuits on reduced."
80 | ([ast f] (update-children ast f false))
81 | ([ast f reversed?]
82 | (unreduced (update-children-reduced ast f reversed?))))
83 |
84 | (defn walk
85 | "Walk the ast applying `pre` when entering the nodes, and `post` when exiting.
86 | Both functions must return a valid node since the returned value will replace
87 | the node in the AST which was given as input to the function.
88 | If reversed? is not-nil, `pre` and `post` will be applied starting from the last
89 | children of the AST node to the first one.
90 | Short-circuits on reduced."
91 | ([ast pre post]
92 | (walk ast pre post false))
93 | ([ast pre post reversed?]
94 | (unreduced
95 | ((fn walk [ast pre post reversed?]
96 | (let [walk #(walk % pre post reversed?)]
97 | (if (reduced? ast)
98 | ast
99 | (let [ret (update-children-reduced (pre ast) walk reversed?)]
100 | (if (reduced? ret)
101 | ret
102 | (post ret))))))
103 | ast pre post reversed?))))
104 |
105 | (defn prewalk
106 | "Shorthand for (walk ast f identity)"
107 | [ast f]
108 | (walk ast f identity))
109 |
110 | (defn postwalk
111 | "Shorthand for (walk ast identity f reversed?)"
112 | ([ast f]
113 | (postwalk ast f false))
114 | ([ast f reversed?]
115 | (walk ast identity f reversed?)))
116 |
117 | (defn nodes
118 | "Returns a lazy-seq of all the nodes in the given AST, in depth-first pre-order."
119 | [ast]
120 | (lazy-seq
121 | (cons ast (mapcat nodes (children ast)))))
122 |
123 | (defn ast->eav
124 | "Returns an EAV representation of the current AST that can be used by
125 | Datomic's Datalog."
126 | [ast]
127 | (let [children (set (:children ast))]
128 | (mapcat (fn [[k v]]
129 | (if (children k)
130 | (if (map? v)
131 | (into [[ast k v]] (ast->eav v))
132 | (mapcat (fn [v] (into [[ast k v]] (ast->eav v))) v))
133 | [[ast k v]])) ast)))
134 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/ast/query.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.ast.query
10 | "Utilities for querying tools.analyzer ASTs with Datomic"
11 | (:require [clojure.tools.analyzer.ast :as ast]
12 | [clojure.tools.analyzer.utils :refer [compile-if]]))
13 |
14 | (defn query-map
15 | "Transforms a Datomic query from its vector representation to its map one.
16 | If the given query is already in its map representation, the original query
17 | is returned."
18 | [query]
19 | (if (map? query)
20 | query
21 | (loop [ret {:find [] :in [] :where []} query query op nil]
22 | (if-let [[el & query] (seq query)]
23 | (if (keyword? el)
24 | (recur ret query el)
25 | (recur (update-in ret [op] conj el) query op))
26 | (reduce-kv (fn [m k v] (if (seq v) (assoc m k v) m)) {} ret)))))
27 |
28 | (defn unfold-expression-clauses
29 | "Given a Datomic query, walk the :where clauses searching for
30 | expression clauses with nested calls, unnesting those calls.
31 |
32 | E.g {:where [[(inc (dec ?foo)) ?bar] ..] ..} will be transformed into
33 | {:where [[(dec ?foo) ?1234] [(inc ?1234) ?bar] ..] ..}"
34 | [query]
35 | (let [{:keys [where] :as query} (query-map query)]
36 | (if-not where
37 | query
38 | (assoc query :where
39 | (mapcat (fn [[op & rest :as form]]
40 | (if-let [[f & args] (and (seq? op) op)]
41 | (if (some seq? args)
42 | (loop [args args to-ssa {} cur [f] binds rest ret []]
43 | (if-let [[a & args] (seq args)]
44 | (if (and (seq? a)
45 | (not= 'quote (first a)))
46 | (let [g (gensym "?")]
47 | (recur args (assoc to-ssa g a) (conj cur g) binds ret))
48 | (recur args to-ssa (conj cur a) binds ret))
49 | (let [ret (conj ret (into [(seq cur)] binds))]
50 | (if-let [[k [f & args]] (first to-ssa)]
51 | (recur args (dissoc to-ssa k) [f] [k] ret)
52 | ret))))
53 | [form])
54 | [form])) where)))))
55 |
56 | (defn resolve-calls
57 | "Automatically replace fn name symbols in expression clauses with
58 | their namespace qualified one if the symbol can be resolved in the
59 | current namespace."
60 | [query]
61 | (let [{:keys [where] :as query} (query-map query)]
62 | (if-not where
63 | query
64 | (assoc query :where
65 | (mapv (fn [[op & rest :as form]]
66 | (if-let [[f & args] (and (seq? op) op)]
67 | (if-let [f-var (and (symbol? f) (resolve f))]
68 | (into [(seq (into [(symbol (str (ns-name (.ns f-var)))
69 | (str (.sym f-var)))] args))]
70 | rest)
71 | form)
72 | form)) where)))))
73 |
74 | (defn db
75 | "Given a list of ASTs, returns a representation of those
76 | that can be used as a database in a Datomic Datalog query."
77 | [asts]
78 | (mapcat ast/ast->eav asts))
79 |
80 | (defn q
81 | "Execute a Datomic Datalog query against the ASTs.
82 | The first input is always assumed to be an AST database, if more
83 | are required, it's required to call `db` on them.
84 | `unfold-expression-clauses` is automatically applied to the
85 | query."
86 | [query asts & inputs]
87 | (compile-if (Class/forName "datomic.Datom")
88 | (do (require '[datomic.api :as d])
89 | (apply (resolve 'datomic.api/q) (unfold-expression-clauses query) (db asts) inputs))
90 | (throw (Exception. "Datomic is required"))))
91 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/env.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.env
10 | (:refer-clojure :exclude [ensure]))
11 |
12 | (def ^:dynamic *env*
13 | "Global env atom containing a map.
14 | Required options:
15 | * :namespaces a map from namespace symbol to namespace map,
16 | the namespace map contains at least the following keys:
17 | ** :mappings a map of mappings of the namespace, symbol to var/class
18 | ** :aliases a map of the aliases of the namespace, symbol to symbol
19 | ** :ns a symbol representing the namespace"
20 | nil)
21 |
22 | (defmacro with-env
23 | "Binds the global env to env, then executes the body"
24 | [env & body]
25 | `(let [env# ~env
26 | env# (cond
27 | (map? env#) (atom env#)
28 | (and (instance? clojure.lang.Atom env#)
29 | (map? @env#)) env#
30 | :default (throw (ex-info (str "global env must be a map or atom containing a map, not "
31 | (class env#))
32 | {:env env#})))]
33 | (binding [*env* env#] ~@body)))
34 |
35 | ;; if *env* is not bound, bind it to env
36 | (defmacro ensure
37 | "If *env* is not bound it binds it to env before executing the body"
38 | [env & body]
39 | `(if *env*
40 | (do ~@body)
41 | (with-env ~env
42 | ~@body)))
43 |
44 | (defn deref-env
45 | "Returns the value of the current global env if bound, otherwise
46 | throws an exception."
47 | []
48 | (if *env*
49 | @*env*
50 | (throw (Exception. "global env not bound"))))
51 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/passes.clj:
--------------------------------------------------------------------------------
1 | (ns clojure.tools.analyzer.passes
2 | "Utilities for pass scheduling"
3 | (:refer-clojure :exclude [update-vals])
4 | (:require [clojure.tools.analyzer.ast :refer [prewalk postwalk]]
5 | [clojure.tools.analyzer.utils :refer [update-vals]]))
6 |
7 | (defn ^:private has-deps?
8 | "Returns true if the pass has any dependencies"
9 | [pass]
10 | (seq (:dependencies pass)))
11 |
12 | (defn ^:private indicize
13 | "Takes a set of pass-infos and returns a map of pass-name -> pass-info"
14 | [passes]
15 | (zipmap (map :name passes) passes))
16 |
17 | (defn ^:private remove-pass
18 | "Takes a set of pass-infos and a pass, and removes the pass from the set of
19 | pass-infos, updating :dependencies and :dependants as well."
20 | [passes pass]
21 | (indicize (reduce (fn [m p] (conj m (-> p (update-in [:dependencies] disj pass)
22 | (update-in [:dependants] disj pass))))
23 | #{} (vals (dissoc passes pass)))))
24 |
25 | (defn desugar-deps
26 | "Takes a map of pass-name -> pass deps and puts the :after :affects and :before passes
27 | in the appropriate pass :depends"
28 | [passes]
29 | (reduce-kv (fn [m name {:keys [after affects before]}]
30 | (reduce (fn [m p] (update-in m [p :depends] (fnil conj #{}) name))
31 | (update-in m [name :depends] (fnil into #{}) (into affects (filter passes after)))
32 | before)) passes passes))
33 |
34 | (defn ^:private calc-deps
35 | "Takes a map of pass-name -> pass deps, a pass name, the explicit pass dependencies
36 | and a set of available pass-infos.
37 | Resolves all the transitive deps of the pass and assocs them to the map, indexed by
38 | the pass name."
39 | [m k deps passes]
40 | (if (m k)
41 | m
42 | (reduce (fn [m dep]
43 | (let [m (calc-deps m dep (get-in passes [dep :depends]) passes)]
44 | (update-in m [k] into (conj (or (m dep) #{}) dep))))
45 | (assoc m k deps) deps)))
46 |
47 | (defn calculate-deps
48 | "Takes a map of pass-name -> pass-info and adds to each pass-info :dependencies and
49 | :dependants info, which also contains the transitive dependencies"
50 | [passes]
51 | (let [passes (desugar-deps passes)
52 | dependencies (reduce-kv (fn [deps pname {:keys [depends]}]
53 | (calc-deps deps pname depends passes))
54 | {} passes)
55 | dependants (reduce-kv (fn [m k v] (reduce (fn [m v] (update-in m [v] (fnil conj #{}) k))
56 | (update-in m [k] (fnil into #{}) nil) v))
57 | {} dependencies)]
58 | (reduce-kv (fn [m k v] (assoc m k (merge (dissoc (passes k) :depends)
59 | {:dependencies (set v) :dependants (set (dependants k))})))
60 | {} dependencies)))
61 |
62 | (defn group
63 | "Takes a scheduler state and returns a vector of three elements (or nil):
64 | * the :walk of the current group
65 | * a vector of consecutive passes that can be collapsed in a single pass (the current group)
66 | * the remaining scheduler state
67 |
68 | E.g. given:
69 | [{:walk :any ..} {:walk :pre ..} {:walk :post ..} {:walk :pre ..}]
70 | it will return:
71 | [:pre [{:walk :any ..} {:walk :pre ..}] [{:walk :post ..} {:walk :pre ..}]]"
72 | [state]
73 | (loop [w nil group [] [cur & rest :as state] state]
74 | (if (seq state)
75 | (cond
76 | (:affects (last group))
77 | [w group state]
78 |
79 | w
80 | (if (#{w :any} (:walk cur))
81 | (recur w (conj group cur) rest)
82 | [w group state])
83 |
84 | :else
85 | (case (:walk cur)
86 | :any
87 | (recur nil (conj group cur) rest)
88 | :none
89 | [w group state]
90 | (recur (:walk cur) (conj group cur) rest)))
91 | [w group state])))
92 |
93 | (defn satisfies-affected? [{:keys [affects walk]} passes]
94 | (loop [passes passes]
95 | (let [free (vals (filter (comp empty? :dependants val) passes))]
96 | (if-let [available-passes (seq (filter (comp #{walk :any} :walk) free))]
97 | (recur (reduce remove-pass passes (mapv :name available-passes)))
98 | (empty? (filter (fn [{:keys [name]}] ((set affects) name)) (vals passes)))))))
99 |
100 | (defn maybe-looping-pass [free passes]
101 | (if-let [looping (seq (filter :affects free))]
102 | (loop [[l & ls] looping]
103 | (if l
104 | (if (satisfies-affected? l (remove-pass passes (:name l)))
105 | ;; all deps satisfied
106 | l
107 | (recur ls))
108 | (if-let [p (first (remove :affects free))]
109 | ;; pick a random avaliable non-looping pass
110 | p
111 | (throw (ex-info (str "looping pass doesn't encompass affected passes: " (:name l))
112 | {:pass l})))))
113 | ;; pick a random available pass
114 | (first free)))
115 |
116 | (def ^:private ffilter (comp first filter))
117 |
118 | (defn ^:private first-walk [f c]
119 | (ffilter (comp #{f} :walk) c))
120 |
121 | (defn schedule* [state passes]
122 | (let [free (filter (comp empty? :dependants) (vals passes))
123 | w (first (group state))
124 | non-looping-free (remove :affects free)]
125 | (if (seq passes)
126 | (let [{:keys [name] :as pass} (or (ffilter :compiler free)
127 | (and w (or (first-walk w non-looping-free)
128 | (first-walk :any non-looping-free)))
129 | (first-walk :none free)
130 | (maybe-looping-pass free passes))]
131 | (recur (cons (assoc pass :passes [name]) state)
132 | (remove-pass passes name)))
133 | state)))
134 |
135 | (defn collapse [state]
136 | (loop [[cur & rest :as state] state ret []]
137 | (if (seq state)
138 | (if (= :none (:walk cur))
139 | (recur rest (conj ret cur))
140 | (let [[w g state] (group state)]
141 | (recur state (conj ret {:walk (or w :pre) :passes (mapv :name g)}))))
142 | ret)))
143 |
144 | (defn schedule-passes
145 | [passes]
146 | (let [passes (calculate-deps passes)]
147 |
148 | (when (every? has-deps? (vals passes))
149 | (throw (ex-info "Dependency cycle detected" passes)))
150 |
151 | (when (next (filter :compiler (vals passes)))
152 | (throw (ex-info "Only one compiler pass allowed" passes)))
153 |
154 | (collapse (schedule* () passes))))
155 |
156 | (defn compile-passes [passes walk info]
157 | (let [with-state (filter (comp :state info) passes)
158 | state (zipmap with-state (mapv #(:state (info %)) with-state))
159 | pfns (reduce (fn [f p]
160 | (let [i (info p)
161 | p (cond
162 | (:state i)
163 | (fn [_ s ast] (p (s p) ast))
164 | (:affects i)
165 | (fn [a _ ast] ((p a) ast))
166 | :else
167 | (fn [_ _ ast] (p ast)))]
168 | (fn [a s ast]
169 | (p a s (f a s ast))))) (fn [_ _ ast] ast) passes)]
170 | (fn analyze [ast]
171 | (walk ast (partial pfns analyze (update-vals state #(%)))))))
172 |
173 | (defn schedule
174 | "Takes a set of Vars that represent tools.analyzer passes and returns a function
175 | that takes an AST and applies all the passes and their dependencies to the AST,
176 | trying to compose together as many passes as possible to reduce the number of
177 | full tree traversals.
178 |
179 | Each pass must have a :pass-info element in its Var's metadata and it must point
180 | to a map with the following parameters (:before, :after, :affects and :state are
181 | optional):
182 | * :after a set of Vars, the passes that must be run before this pass
183 | * :before a set of Vars, the passes that must be run after this pass
184 | * :depends a set of Vars, the passes this pass depends on, implies :after
185 | * :walk a keyword, one of:
186 | - :none if the pass does its own tree walking and cannot be composed
187 | with other passes
188 | - :post if the pass requires a postwalk and can be composed with other
189 | passes
190 | - :pre if the pass requires a prewalk and can be composed with other
191 | passes
192 | - :any if the pass can be composed with other passes in both a prewalk
193 | or a postwalk
194 | * :affects a set of Vars, this pass must be the last in the same tree traversal that all
195 | the specified passes must participate in
196 | This pass must take a function as argument and return the actual pass, the
197 | argument represents the reified tree traversal which the pass can use to
198 | control a recursive traversal, implies :depends
199 | * :state a no-arg function that should return an atom holding an init value that will be
200 | passed as the first argument to the pass (the pass will thus take the ast
201 | as the second parameter), the atom will be the same for the whole tree traversal
202 | and thus can be used to preserve state across the traversal
203 | An opts map might be provided, valid parameters:
204 | * :debug? if true, returns a vector of the scheduled passes rather than the concrete
205 | function"
206 | [passes & [opts]]
207 | {:pre [(set? passes)
208 | (every? var? passes)]}
209 | (let [info (indicize (mapv (fn [p] (merge {:name p} (:pass-info (meta p)))) passes))
210 | passes+deps (into passes (mapcat :depends (vals info)))]
211 | (if (not= passes passes+deps)
212 | (recur passes+deps [opts])
213 | (if (:debug? opts)
214 | (mapv #(select-keys % [:passes :walk])
215 | (schedule-passes info))
216 | (reduce (fn [f {:keys [passes walk]}]
217 | (let [pass (if (= walk :none)
218 | (first passes)
219 | (compile-passes passes (if (= :pre walk) prewalk postwalk) info))]
220 | (comp pass f)))
221 | identity (schedule-passes info))))))
222 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/passes/add_binding_atom.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.passes.add-binding-atom
10 | (:require [clojure.tools.analyzer.ast :refer [prewalk]]
11 | [clojure.tools.analyzer.passes.uniquify :refer [uniquify-locals]]))
12 |
13 | (defn add-binding-atom
14 | "Adds an atom-backed-map to every local binding,the same
15 | atom will be shared between all occurences of that local.
16 |
17 | The atom is put in the :atom field of the node."
18 | {:pass-info {:walk :pre :depends #{#'uniquify-locals} :state (fn [] (atom {}))}}
19 | ([ast] (prewalk ast (partial add-binding-atom (atom {}))))
20 | ([state ast]
21 | (case (:op ast)
22 | :binding
23 | (let [a (atom {})]
24 | (swap! state assoc (:name ast) a)
25 | (assoc ast :atom a))
26 | :local
27 | (if-let [a (@state (:name ast))]
28 | (assoc ast :atom a)
29 | ;; handle injected locals
30 | (let [a (get-in ast [:env :locals (:name ast) :atom] (atom {}))]
31 | (swap! state assoc (:name ast) a)
32 | (assoc ast :atom a)))
33 | ast)))
34 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/passes/cleanup.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.passes.cleanup)
10 |
11 | (defn cleanup
12 | {:pass-info {:walk :any :depends #{}}}
13 | [ast]
14 | (-> ast
15 | (update-in [:env] dissoc :loop-locals-casts)
16 | (update-in [:env :locals] #(reduce-kv (fn [m k l] (assoc m k (dissoc l :env :init))) {} %))
17 | (dissoc :atom)))
18 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/passes/collect_closed_overs.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.passes.collect-closed-overs
10 | (:require [clojure.tools.analyzer.ast :refer [update-children]]
11 | [clojure.tools.analyzer.env :as env]
12 | [clojure.tools.analyzer.passes.cleanup :refer [cleanup]]
13 | [clojure.tools.analyzer.passes.uniquify :refer [uniquify-locals]]))
14 |
15 | (def ^:private ^:dynamic *collects*)
16 |
17 | (declare collect-closed-overs*)
18 | (defn -collect-closed-overs
19 | [ast]
20 | (-> (case (:op ast)
21 | :letfn ;; seed letfn bindings
22 | (let [bindings (:bindings ast)]
23 | (doseq [{:keys [name]} bindings]
24 | (swap! *collects* #(update-in % [:locals] conj name)))
25 | ast)
26 | :binding
27 | (let [name (:name ast)]
28 | (if (= :field (:local ast))
29 | (swap! *collects* #(assoc-in % [:closed-overs name] (cleanup ast))) ;; special-case: put directly as closed-overs
30 | (swap! *collects* #(update-in % [:locals] conj name))) ;; register the local as a frame-local locals
31 | ast)
32 | :local
33 | (let [name (:name ast)]
34 | (when-not ((:locals @*collects*) name) ;; if the local is not in the frame-local locals
35 | (swap! *collects* #(assoc-in % [:closed-overs name] (cleanup ast)))) ;; then it's from the outer frame locals, thus a closed-over
36 | ast)
37 | ast)
38 | (update-children collect-closed-overs*))) ;; recursively collect closed-overs in the children nodes
39 |
40 | (defn collect-closed-overs*
41 | [{:keys [op] :as ast}]
42 | (let [collects @*collects*
43 | collect? ((:where collects) op)]
44 | (if collect?
45 | (let [[ast {:keys [closed-overs locals]}]
46 | (binding [*collects* (atom (merge @*collects*
47 | {:closed-overs {} :locals #{}}))]
48 | [(update-children ast -collect-closed-overs) @*collects*])]
49 | (swap! *collects* #(update-in % [:closed-overs] merge ;; propagate closed-overs from the inner frame to the outer frame
50 | (into {}
51 | (remove (fn [[_ {:keys [local]}]] ;; remove deftype fields from the closed-over locals
52 | (and (= op :deftype)
53 | (= :field local)))
54 | (apply dissoc closed-overs ;; remove from the closed-overs locals that were
55 | (:locals @*collects*)))))) ;; local to the inner frame
56 | (assoc ast :closed-overs closed-overs))
57 | (-collect-closed-overs ast))))
58 |
59 | (defn collect-closed-overs
60 | "Attach closed-overs info to the AST as specified by the passes opts:
61 | * :where set of :op nodes where to attach the closed-overs
62 | * :top-level? if true attach closed-overs info to the top-level node
63 |
64 | The info will be attached in the :closed-overs field of the AST node
65 | and will be a map of local name -> binding AST node"
66 | {:pass-info {:walk :none :depends #{#'uniquify-locals}}}
67 | [ast]
68 | (let [passes-opts (:passes-opts (env/deref-env))
69 | {:keys [top-level?] :as opts} {:where (or (:collect-closed-overs/where passes-opts) #{})
70 | :top-level? (:collect-closed-overs/top-level? passes-opts)}]
71 | (binding [*collects* (atom (merge opts {:closed-overs {} :locals #{}}))]
72 | (let [ast (collect-closed-overs* ast)]
73 | (if top-level?
74 | (assoc ast :closed-overs (:closed-overs @*collects*))
75 | ast)))))
76 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/passes/constant_lifter.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.passes.constant-lifter
10 | (:require [clojure.tools.analyzer.utils :refer [const-val]]))
11 |
12 | (defmulti constant-lift
13 | "If the node represents a collection with no metadata, and every item of that
14 | collection is a literal, transform the node to an equivalent :const node."
15 | {:pass-info {:walk :post :depends #{}}}
16 | :op)
17 |
18 | (defmethod constant-lift :vector
19 | [{:keys [items form env] :as ast}]
20 | (if (and (every? :literal? items)
21 | (empty? (meta form)))
22 | (merge (dissoc ast :items :children)
23 | {:op :const
24 | :val (mapv const-val items)
25 | :type :vector
26 | :literal? true})
27 | ast))
28 |
29 | (defmethod constant-lift :map
30 | [{:keys [keys vals form env] :as ast}]
31 | (if (and (every? :literal? keys)
32 | (every? :literal? vals)
33 | (empty? (meta form)))
34 | (let [c (into (empty form)
35 | (zipmap (mapv const-val keys)
36 | (mapv const-val vals)))
37 | c (if (= (class c) (class form))
38 | c
39 | (apply array-map (mapcat identity c)))]
40 | (merge (dissoc ast :keys :vals :children)
41 | {:op :const
42 | :val c
43 | :type :map
44 | :literal? true}))
45 | ast))
46 |
47 | (defmethod constant-lift :set
48 | [{:keys [items form env] :as ast}]
49 | (if (and (every? :literal? items)
50 | (empty? (meta form)))
51 | (merge (dissoc ast :items :children)
52 | {:op :const
53 | :val (into (empty form) (mapv const-val items))
54 | :type :set
55 | :literal? true})
56 | ast))
57 |
58 | (defmethod constant-lift :default [ast] ast)
59 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/passes/elide_meta.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.passes.elide-meta
10 | (:require [clojure.tools.analyzer.passes.source-info :refer [source-info]]))
11 |
12 | (def ^:dynamic elides
13 | "A map of op keywords to predicate IFns.
14 | The predicate will be used to indicate what map keys should be elided on
15 | metadata of nodes for that op.
16 | :all can be used to indicate what should be elided for every node with
17 | metadata.
18 | Defaults to {:all (set (:elide-meta *compiler-options*))}"
19 | {:all (set (:elide-meta *compiler-options*))})
20 |
21 | (defn replace-meta [meta new-meta]
22 | (if (= :const (:op meta))
23 | (assoc meta :val new-meta)
24 | (let [meta-map (mapv (fn [k v]
25 | (when-not (elides (:form k))
26 | [k v]))
27 | (:keys meta) (:vals meta))]
28 | (assoc meta
29 | :keys (vec (keep first meta-map))
30 | :vals (vec (keep second meta-map))))))
31 |
32 | (defn get-elides [{:keys [op expr type]}]
33 | (let [k (case op
34 | :with-meta
35 | (:op expr)
36 |
37 | :const
38 | type
39 |
40 | nil)
41 | f (get elides k)]
42 | (if f
43 | (some-fn (:all elides) f)
44 | (:all elides))))
45 |
46 | (defn -elide-meta
47 | [{:keys [op meta expr env] :as ast}]
48 | (let [form (:form meta)
49 | new-meta (apply dissoc form (filter (get-elides ast) (keys form)))]
50 | (case op
51 | :const
52 | (if (or (not meta)
53 | (= new-meta (:form meta)))
54 | ast
55 | (if (not (empty? new-meta))
56 | (assoc-in ast [:meta :val] new-meta)
57 | (-> ast
58 | (update-in [:val] with-meta nil)
59 | (dissoc :children :meta))))
60 | :with-meta
61 | (if (not (empty? new-meta))
62 | (if (= new-meta (:form meta))
63 | ast
64 | (assoc ast :meta (replace-meta meta new-meta)))
65 | (merge (dissoc ast :meta :expr)
66 | {:op :do
67 | :body? true
68 | :ret expr
69 | :statements []
70 | :children [:statements :ret]}))
71 | :def
72 | (if (not (empty? new-meta))
73 | (if (= new-meta (:form meta))
74 | ast
75 | (assoc ast :meta (replace-meta meta new-meta)))
76 | (let [ast (dissoc ast :meta)]
77 | (if-let [new-children (not-empty (filterv (complement #{:meta}) (:children ast)))]
78 | (assoc ast :children new-children)
79 | (dissoc ast :children))))
80 | ast)))
81 |
82 | (defn elide-meta
83 | "If elides is not empty and the AST node contains metadata,
84 | dissoc all the keys in elides from the metadata."
85 | {:pass-info {:walk :any :depends #{} :after #{#'source-info}}}
86 | [ast]
87 | (if (some #(if (seq? %) (seq %) %) (vals elides))
88 | (-elide-meta ast)
89 | ast))
90 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/passes/emit_form.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.passes.emit-form
10 | (:require [clojure.tools.analyzer.passes.uniquify :refer [uniquify-locals]]))
11 |
12 | (defmulti -emit-form (fn [{:keys [op]} _] op))
13 |
14 | (defn ^:dynamic -emit-form*
15 | "Extension point for custom emit-form implementations, should be rebound
16 | to a multimethod with custom emit-form :opts."
17 | [{:keys [form] :as ast} opts]
18 | (let [expr (-emit-form ast opts)]
19 | (if-let [m (and (instance? clojure.lang.IObj expr)
20 | (meta form))]
21 | (with-meta expr (merge m (meta expr)))
22 | expr)))
23 |
24 | (defn emit-form
25 | "Return the form represented by the given AST.
26 | Opts is a set of options, valid options are:
27 | * :hygienic"
28 | {:pass-info {:walk :none :depends #{#'uniquify-locals} :compiler true}}
29 | ([ast] (emit-form ast #{}))
30 | ([ast opts] (-emit-form* ast opts)))
31 |
32 | (defn emit-hygienic-form
33 | "Return an hygienic form represented by the given AST"
34 | {:pass-info {:walk :none :depends #{#'uniquify-locals} :compiler true}}
35 | [ast]
36 | (-emit-form* ast #{:hygienic}))
37 |
38 | (defmethod -emit-form :maybe-class
39 | [{:keys [class]} opts]
40 | class)
41 |
42 | (defmethod -emit-form :maybe-host-form
43 | [{:keys [class field]} opts]
44 | (symbol (name class) (name field)))
45 |
46 | (defmethod -emit-form :host-call
47 | [{:keys [target method args]} opts]
48 | (list '. (-emit-form* target opts)
49 | (list* method (mapv #(-emit-form* % opts) args))))
50 |
51 | (defmethod -emit-form :host-field
52 | [{:keys [target field]} opts]
53 | (list '. (-emit-form* target opts)
54 | (symbol (str "-" (name field)))))
55 |
56 | (defmethod -emit-form :host-interop
57 | [{:keys [target m-or-f]} opts]
58 | (list '. (-emit-form* target opts) m-or-f))
59 |
60 | (defmethod -emit-form :local
61 | [{:keys [name form]} opts]
62 | (if (:hygienic opts) (with-meta name (meta form)) form))
63 |
64 | (defmethod -emit-form :binding
65 | [{:keys [name form]} opts]
66 | (if (:hygienic opts) (with-meta name (meta form)) form))
67 |
68 | (defmethod -emit-form :var
69 | [{:keys [form]} opts]
70 | form)
71 |
72 | (defn emit-bindings [bindings opts]
73 | (mapcat (fn [{:keys [name form init]}]
74 | [(if (:hygienic opts) name form) (-emit-form* init opts)])
75 | bindings))
76 |
77 | (defmethod -emit-form :letfn
78 | [{:keys [bindings body]} opts]
79 | `(letfn* [~@(emit-bindings bindings opts)]
80 | ~(-emit-form* body opts)))
81 |
82 | (defmethod -emit-form :let
83 | [{:keys [bindings body]} opts]
84 | `(let* [~@(emit-bindings bindings opts)]
85 | ~(-emit-form* body opts)))
86 |
87 | (defmethod -emit-form :loop
88 | [{:keys [bindings body]} opts]
89 | `(loop* [~@(emit-bindings bindings opts)]
90 | ~(-emit-form* body opts)))
91 |
92 | (defmethod -emit-form :const
93 | [{:keys [form]} _]
94 | form)
95 |
96 | (defmethod -emit-form :quote
97 | [{:keys [expr]} opts]
98 | (list 'quote (-emit-form* expr opts)))
99 |
100 | (defmethod -emit-form :vector
101 | [{:keys [items]} opts]
102 | (mapv #(-emit-form* % opts) items))
103 |
104 | (defmethod -emit-form :set
105 | [{:keys [items]} opts]
106 | (set (mapv #(-emit-form* % opts) items)))
107 |
108 | (defmethod -emit-form :map
109 | [{:keys [keys vals]} opts]
110 | (apply hash-map (interleave (mapv #(-emit-form* % opts) keys)
111 | (mapv #(-emit-form* % opts) vals))))
112 |
113 | (defmethod -emit-form :with-meta
114 | [{:keys [expr meta]} opts]
115 | (with-meta (-emit-form* expr opts)
116 | (-emit-form* meta opts)))
117 |
118 | (defmethod -emit-form :do
119 | [{:keys [ret statements body?]} opts]
120 | (if (and body? (empty? statements))
121 | (-emit-form* ret opts)
122 | `(do ~@(mapv #(-emit-form* % opts) statements)
123 | ~(-emit-form* ret opts))))
124 |
125 | (defmethod -emit-form :if
126 | [{:keys [test then else]} opts]
127 | `(if ~(-emit-form* test opts)
128 | ~(-emit-form* then opts)
129 | ~@(when-not (nil? (:form else))
130 | [(-emit-form* else opts)])))
131 |
132 | (defmethod -emit-form :new
133 | [{:keys [class args]} opts]
134 | `(new ~(-emit-form* class opts) ~@(mapv #(-emit-form* % opts) args)))
135 |
136 | (defmethod -emit-form :set!
137 | [{:keys [target val]} opts]
138 | `(set! ~(-emit-form* target opts) ~(-emit-form* val opts)))
139 |
140 | (defmethod -emit-form :recur
141 | [{:keys [exprs]} opts]
142 | `(recur ~@(mapv #(-emit-form* % opts) exprs)))
143 |
144 | (defmethod -emit-form :fn-method
145 | [{:keys [variadic? params body form]} opts]
146 | (let [params-form (mapv #(-emit-form* % opts) params)]
147 | `(~(with-meta
148 | (if variadic? (into (pop params-form)
149 | (conj '[&] (peek params-form)))
150 | params-form)
151 | (meta (first form)))
152 | ~(-emit-form* body opts))))
153 |
154 | (defmethod -emit-form :fn
155 | [{:keys [local methods]} opts]
156 | `(fn* ~@(when local [(-emit-form* local opts)])
157 | ~@(mapv #(-emit-form* % opts) methods)))
158 |
159 | (defmethod -emit-form :def
160 | [{:keys [name doc init]} opts]
161 | (let [name (if-let [arglists (:arglists (meta name))]
162 | (with-meta name (assoc (meta name) :arglists (list 'quote arglists)))
163 | name)]
164 | `(def ~name ~@(when doc [doc]) ~@(when init [(-emit-form* init opts)]))))
165 |
166 | (defmethod -emit-form :invoke
167 | [{:keys [fn args meta]} opts]
168 | (let [expr `(~(-emit-form* fn opts)
169 | ~@(mapv #(-emit-form* % opts) args))]
170 | (if meta
171 | (with-meta expr meta)
172 | expr)))
173 |
174 | (defmethod -emit-form :try
175 | [{:keys [body catches finally]} opts]
176 | `(try ~(-emit-form* body opts)
177 | ~@(mapv #(-emit-form* % opts) catches)
178 | ~@(when finally
179 | [`(finally ~(-emit-form* finally opts))])))
180 |
181 | (defmethod -emit-form :catch
182 | [{:keys [class local body]} opts]
183 | `(catch ~(-emit-form* class opts) ~(-emit-form* local opts)
184 | ~(-emit-form* body opts)))
185 |
186 | (defmethod -emit-form :throw
187 | [{:keys [exception]} opts]
188 | `(throw ~(-emit-form* exception opts)))
189 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/passes/index_vector_nodes.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.passes.index-vector-nodes)
10 |
11 | (defn index-vector-nodes
12 | "Adds an :idx attribute to nodes in a vector children, representing the position
13 | of the node vector."
14 | {:pass-info {:walk :any :depends #{}}}
15 | [ast]
16 | (merge ast
17 | (reduce (fn [m c]
18 | (let [v (c ast)
19 | v (if (vector? v)
20 | (mapv (fn [x i] (assoc x :idx i ))
21 | v (range))
22 | v)]
23 | (assoc m c v))) {} (:children ast))))
24 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/passes/source_info.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.passes.source-info
10 | (:require [clojure.tools.analyzer.utils :refer [-source-info merge']]
11 | [clojure.tools.analyzer.ast :refer [update-children]]))
12 |
13 | (defn -merge-source-info [source-info]
14 | (fn [ast]
15 | (update-in ast [:env] merge' source-info)))
16 |
17 | (defn source-info
18 | "Adds (when avaliable) :line, :column, :end-line, :end-column and :file info to the AST :env"
19 | {:pass-info {:walk :pre :depends #{}}}
20 | [ast]
21 | (let [source-info (-source-info (:form ast) (:env ast))
22 | merge-source-info (-merge-source-info source-info)]
23 | (update-children (merge-source-info ast) merge-source-info)))
24 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/passes/trim.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.passes.trim
10 | (:require [clojure.tools.analyzer.passes.elide-meta :refer [elide-meta]]
11 | [clojure.tools.analyzer.ast :refer [postwalk]]))
12 |
13 | (defmulti -trim :op)
14 |
15 | (defmethod -trim :default [ast] ast)
16 |
17 | (defn preserving-raw-forms [{:keys [form raw-forms] :as ast} body]
18 | (let [raw-forms (reverse (cons form raw-forms))]
19 | (update-in (into ast body) [:raw-forms] into raw-forms)))
20 |
21 | (defmethod -trim :do
22 | [{:keys [statements ret form] :as ast}]
23 | (if (and (every? :literal? statements)
24 | (not (:tag (meta form))))
25 | (preserving-raw-forms (dissoc ast :children :statements :ret) ret)
26 | ast))
27 |
28 | ;;TODO: letfn/loop
29 | (defmethod -trim :let
30 | [{:keys [bindings body form] :as ast}]
31 | (if (and (or (and (every? (comp :literal? :init) bindings)
32 | (:literal? body))
33 | (empty? bindings))
34 | (not (:tag (meta form))))
35 | (preserving-raw-forms (dissoc ast :children :bindings :body) body)
36 | ast))
37 |
38 | (defmethod -trim :try
39 | [{:keys [catches finally body form] :as ast}]
40 | (if (and (empty? catches)
41 | (empty? finally)
42 | (not (:tag (meta form))))
43 | (preserving-raw-forms (dissoc ast :children :body :finally :catches) body)
44 | ast))
45 |
46 | (defn trim
47 | "Trims the AST of unnecessary nodes, e.g. (do (do 1)) -> 1"
48 | {:pass-info {:walk :none :depends #{} :after #{#'elide-meta}}}
49 | [ast]
50 | (postwalk ast -trim))
51 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/passes/uniquify.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.passes.uniquify
10 | (:refer-clojure :exclude [update-vals])
11 | (:require [clojure.tools.analyzer.ast :refer [update-children children]]
12 | [clojure.tools.analyzer.utils :refer [update-vals]]
13 | [clojure.tools.analyzer.env :as env]))
14 |
15 | (def ^:dynamic *locals-counter*) ;; global counter, map sym -> count
16 | (def ^:dynamic *locals-frame*) ;; holds the id for the locals in the current frame
17 |
18 | (defn normalize [name]
19 | (or (@*locals-frame* name) name))
20 |
21 | (defn uniquify [name]
22 | (swap! *locals-counter* #(update-in % [name] (fnil inc -1)))
23 | (swap! *locals-frame* #(assoc-in % [name] (symbol (str name "__#" (@*locals-counter* name))))))
24 |
25 | (defmulti -uniquify-locals :op)
26 |
27 | (defn uniquify-locals-around
28 | [ast]
29 | (let [ast (if (-> (env/deref-env) :passes-opts :uniquify/uniquify-env)
30 | (update-in ast [:env :locals]
31 | update-vals #(update-in % [:name] normalize))
32 | ast)]
33 | (-uniquify-locals ast)))
34 |
35 | (defn uniquify-locals* [ast]
36 | (update-children ast uniquify-locals-around))
37 |
38 | (defmethod -uniquify-locals :local
39 | [ast]
40 | (if (= :field (:local ast)) ;; deftype fields cannot be uniquified
41 | ast ;; to allow field access/set! to work
42 | (let [name (normalize (:name ast))]
43 | (assoc ast :name name))))
44 |
45 | (defn uniquify-binding
46 | [b]
47 | (let [i (binding [*locals-frame* (atom @*locals-frame*)] ;; inits need to be uniquified before the local
48 | (uniquify-locals-around (:init b))) ;; to avoid potential shadowings
49 | name (:name b)]
50 | (uniquify name)
51 | (let [name (normalize name)]
52 | (assoc b
53 | :name name
54 | :init i))))
55 |
56 | (defmethod -uniquify-locals :letfn
57 | [ast]
58 | (doseq [{:keys [name]} (:bindings ast)] ;; take into account that letfn
59 | (uniquify name)) ;; accepts parallel bindings
60 | (uniquify-locals* ast))
61 |
62 | (defmethod -uniquify-locals :binding
63 | [{:keys [name local] :as ast}]
64 | (case local
65 | (:let :loop)
66 | (uniquify-binding ast)
67 |
68 | :letfn
69 | (-> ast
70 | (assoc :name (normalize name))
71 | uniquify-locals*)
72 |
73 | :field
74 | ast
75 |
76 | (do (uniquify name)
77 | (assoc ast :name (normalize name)))))
78 |
79 | (defmethod -uniquify-locals :default
80 | [ast]
81 | (if (some #(= :binding (:op %)) (children ast))
82 | (binding [*locals-frame* (atom @*locals-frame*)] ;; set up frame so locals won't leak
83 | (uniquify-locals* ast))
84 | (uniquify-locals* ast)))
85 |
86 | (defn uniquify-locals
87 | "Walks the AST performing alpha-conversion on the :name field
88 | of :local/:binding nodes, invalidates :local map in :env field
89 |
90 | Passes opts:
91 | * :uniquify/uniquify-env If true, uniquifies the :env :locals map"
92 | {:pass-info {:walk :none :depends #{}}}
93 | [ast]
94 | (binding [*locals-counter* (atom {})
95 | *locals-frame* (atom {})]
96 | (uniquify-locals-around ast)))
97 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/passes/warn_earmuff.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.passes.warn-earmuff
10 | (:require [clojure.tools.analyzer.utils :refer [dynamic?]]))
11 |
12 | (defn warn-earmuff
13 | "Prints a warning to *err* if the AST node is a :def node and the
14 | var name contains earmuffs but the var is not marked dynamic"
15 | {:pass-info {:walk :pre :depends #{}}}
16 | [ast]
17 | (let [name (str (:name ast))]
18 | (when (and (= :def (:op ast))
19 | (> (count name) 2) ;; Allow * and ** as non-dynamic names
20 | (= (nth name 0) \*)
21 | (= (nth name (dec (count name))) \*)
22 | (not (dynamic? (:var ast) (:val (:meta ast)))))
23 | (binding [*out* *err*]
24 | (println "Warning:" name "not declared dynamic and thus is not dynamically rebindable,"
25 | "but its name suggests otherwise."
26 | "Please either indicate ^:dynamic" name "or change the name"))))
27 | ast)
28 |
--------------------------------------------------------------------------------
/src/main/clojure/clojure/tools/analyzer/utils.clj:
--------------------------------------------------------------------------------
1 | ;; Copyright (c) Nicola Mometto, Rich Hickey & contributors.
2 | ;; The use and distribution terms for this software are covered by the
3 | ;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4 | ;; which can be found in the file epl-v10.html at the root of this distribution.
5 | ;; By using this software in any fashion, you are agreeing to be bound by
6 | ;; the terms of this license.
7 | ;; You must not remove this notice, or any other, from this software.
8 |
9 | (ns clojure.tools.analyzer.utils
10 | (:refer-clojure :exclude [record? boolean? update-keys update-vals])
11 | (:require [clojure.tools.analyzer.env :as env])
12 | (:import (clojure.lang IRecord IType IObj
13 | IReference Var)))
14 |
15 | (defn into!
16 | "Like into, but for transients"
17 | [to from]
18 | (reduce conj! to from))
19 |
20 | (defn rseqv
21 | "Same as (comp vec rseq)"
22 | [v]
23 | (vec (rseq v)))
24 |
25 | (defn ctx
26 | "Returns a copy of the passed environment with :context set to ctx"
27 | [env ctx]
28 | (assoc env :context ctx))
29 |
30 | (defn dissoc-env
31 | "Dissocs :env from the ast"
32 | [ast]
33 | (dissoc ast :env))
34 |
35 | (defn butlast+last
36 | "Returns same value as (juxt butlast last), but slightly more
37 | efficient since it only traverses the input sequence s once, not
38 | twice."
39 | [s]
40 | (loop [butlast (transient [])
41 | s s]
42 | (if-let [xs (next s)]
43 | (recur (conj! butlast (first s)) xs)
44 | [(seq (persistent! butlast)) (first s)])))
45 |
46 | (defn update-vals
47 | "Applies f to all the vals in the map"
48 | [m f]
49 | (reduce-kv (fn [m k v] (assoc m k (f v))) {} (or m {})))
50 |
51 | (defn update-keys
52 | "Applies f to all the keys in the map"
53 | [m f]
54 | (reduce-kv (fn [m k v] (assoc m (f k) v)) {} (or m {})))
55 |
56 | (defn update-kv
57 | "Applies f to all the keys and vals in the map"
58 | [m f]
59 | (reduce-kv (fn [m k v] (assoc m (f k) (f v))) {} (or m {})))
60 |
61 | (defn record?
62 | "Returns true if x is a record"
63 | [x]
64 | (instance? IRecord x))
65 |
66 | (defn type?
67 | "Returns true if x is a type"
68 | [x]
69 | (instance? IType x))
70 |
71 | (defn obj?
72 | "Returns true if x implements IObj"
73 | [x]
74 | (instance? IObj x))
75 |
76 | (defn reference?
77 | "Returns true if x implements IReference"
78 | [x]
79 | (instance? IReference x))
80 |
81 | (defmacro compile-if
82 | [exp then & else]
83 | (if (try (eval exp)
84 | (catch Exception _ false))
85 | `(do ~then)
86 | `(do ~@else)))
87 |
88 | (defn regex?
89 | "Returns true if x is a regex"
90 | [x]
91 | (instance? (compile-if (Class/forName "java.util.regex.Pattern")
92 | java.util.regex.Pattern
93 | System.Text.RegularExpressions.Regex)
94 | x))
95 |
96 | (defn boolean?
97 | "Returns true if x is a boolean"
98 | [x]
99 | (or (true? x) (false? x)))
100 |
101 | (defn classify
102 | "Returns a keyword describing the form type"
103 | [form]
104 | (cond
105 | (nil? form) :nil
106 | (boolean? form) :bool
107 | (keyword? form) :keyword
108 | (symbol? form) :symbol
109 | (string? form) :string
110 | (number? form) :number
111 | (type? form) :type
112 | (record? form) :record
113 | (map? form) :map
114 | (vector? form) :vector
115 | (set? form) :set
116 | (seq? form) :seq
117 | (char? form) :char
118 | (regex? form) :regex
119 | (class? form) :class
120 | (var? form) :var
121 | :else :unknown))
122 |
123 | (defn private?
124 | "Returns true if the var is private"
125 | ([var] (private? var nil))
126 | ([var m]
127 | (:private (or m (meta var)))))
128 |
129 | (defn macro?
130 | "Returns true if the var maps to a macro"
131 | ([var] (macro? var nil))
132 | ([var m]
133 | (:macro (or m (meta var)))))
134 |
135 | (defn constant?
136 | "Returns true if the var is a const"
137 | ([var] (constant? var nil))
138 | ([var m]
139 | (:const (or m (meta var)))))
140 |
141 | (defn dynamic?
142 | "Returns true if the var is dynamic"
143 | ([var] (dynamic? var nil))
144 | ([var m]
145 | (or (:dynamic (or m (meta var)))
146 | (when (var? var) ;; workaround needed since Clojure doesn't always propagate :dynamic
147 | (.isDynamic ^Var var)))))
148 |
149 | (defn protocol-node?
150 | "Returns true if the var maps to a protocol function"
151 | ([var] (protocol-node? var nil))
152 | ([var m]
153 | (boolean (:protocol (or m (meta var)))))) ;; conveniently this is true in both clojure and clojurescript
154 |
155 | (defn resolve-ns
156 | "Resolves the ns mapped by the given sym in the global env"
157 | [ns-sym {:keys [ns]}]
158 | (when ns-sym
159 | (let [namespaces (:namespaces (env/deref-env))]
160 | (or (get-in namespaces [ns :aliases ns-sym])
161 | (:ns (namespaces ns-sym))))))
162 |
163 | (defn resolve-sym
164 | "Resolves the value mapped by the given sym in the global env"
165 | [sym {:keys [ns] :as env}]
166 | (when (symbol? sym)
167 | (let [sym-ns (when-let [ns (namespace sym)]
168 | (symbol ns))
169 | full-ns (resolve-ns sym-ns env)]
170 | (when (or (not sym-ns) full-ns)
171 | (let [name (if sym-ns (-> sym name symbol) sym)]
172 | (-> (env/deref-env) :namespaces (get (or full-ns ns)) :mappings (get name)))))))
173 |
174 | (defn arglist-for-arity
175 | "Takes a fn node and an argc and returns the matching arglist"
176 | [fn argc]
177 | (let [arglists (->> fn :arglists (sort-by count))
178 | arglist (->> arglists (filter #(= argc (count %))) first)
179 | last-arglist (last arglists)]
180 | (or arglist
181 | (when (and (some '#{&} last-arglist)
182 | (>= argc (- (count last-arglist) 2)))
183 | last-arglist))))
184 |
185 | (defn select-keys'
186 | "Like clojure.core/select-keys, but uses transients and doesn't preserve meta"
187 | [map keyseq]
188 | (loop [ret (transient {}) keys (seq keyseq)]
189 | (if keys
190 | (let [entry (find map (first keys))]
191 | (recur (if entry
192 | (conj! ret entry)
193 | ret)
194 | (next keys)))
195 | (persistent! ret))))
196 |
197 | (defn merge'
198 | "Like merge, but uses transients"
199 | [m & mms]
200 | (persistent! (reduce conj! (transient (or m {})) mms)))
201 |
202 | (defn mapv'
203 | "Like mapv, but short-circuits on reduced"
204 | [f v]
205 | (let [c (count v)]
206 | (loop [ret (transient []) i 0]
207 | (if (> c i)
208 | (let [val (f (nth v i))]
209 | (if (reduced? val)
210 | (reduced (persistent! (reduce conj! (conj! ret @val) (subvec v (inc i)))))
211 | (recur (conj! ret val) (inc i))))
212 | (persistent! ret)))))
213 |
214 | (defn source-info
215 | "Returns the available source-info keys from a map"
216 | [m]
217 | (when (:line m)
218 | (select-keys' m #{:file :line :column :end-line :end-column :source-span})))
219 |
220 | (defn -source-info
221 | "Returns the source-info of x"
222 | [x env]
223 | (merge' (source-info env)
224 | (source-info (meta x))
225 | (when-let [file (and (not= *file* "NO_SOURCE_FILE")
226 | *file*)]
227 | {:file file})))
228 |
229 | (defn const-val
230 | "Returns the value of a constant node (either :quote or :const)"
231 | [{:keys [form val]}]
232 | (or val form))
233 |
234 | (def mmerge
235 | "Same as (fn [m1 m2] (merge-with merge m2 m1))"
236 | #(merge-with merge' %2 %1))
237 |
--------------------------------------------------------------------------------
/src/main/dotnet/packager/clojure.tools.analyzer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;netstandard2.1
5 |
6 |
7 |
8 | clojure.tools.analyzer
9 | clojure.tools
10 | clojure.tools.analyzer
11 | clojure.tools.analyzer
12 | clojure.tools.analyzer
13 | ClojureCLR contributors
14 | A port of the tools.analyzer library to ClojureCLR.
15 | Copyright © Rich Hickey, ClojureCLR contributors
16 | EPL-1.0
17 | https://github.com/clojure/clojure.tools.analyzer
18 | ClojureCLR contributors
19 | Clojure;ClojureCLR
20 | 1.1.1
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/main/dotnet/packager/clojure.tools.analyzer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.4.33103.184
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "clojure.tools.analyzer", "clojure.tools.analyzer.csproj", "{A2F25F49-41E8-489A-8245-1A848CAFB1D6}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {A2F25F49-41E8-489A-8245-1A848CAFB1D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {A2F25F49-41E8-489A-8245-1A848CAFB1D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {A2F25F49-41E8-489A-8245-1A848CAFB1D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {A2F25F49-41E8-489A-8245-1A848CAFB1D6}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {C8D02027-FB8B-46F2-BCC1-E9B29C8744F2}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/src/test/clojure/clojure/tools/analyzer/core_test.clj:
--------------------------------------------------------------------------------
1 | (ns clojure.tools.analyzer.core-test
2 | (:refer-clojure :exclude [macroexpand-1])
3 | (:require [clojure.tools.analyzer :as ana]
4 | [clojure.tools.analyzer.ast :refer [postwalk]]
5 | [clojure.tools.analyzer.env :refer [with-env]]
6 | [clojure.tools.analyzer.passes.elide-meta :refer [elides elide-meta]]
7 | [clojure.test :refer [deftest is]]
8 | [clojure.tools.analyzer.utils :refer [resolve-sym]]))
9 |
10 | (defn desugar-host-expr [[op & expr :as form]]
11 | (if (symbol? op)
12 | (let [opname (name op)]
13 | (cond
14 |
15 | (= (first opname) \.) ; (.foo bar ..)
16 | (let [[target & args] expr
17 | args (list* (symbol (subs opname 1)) args)]
18 | (with-meta (list '. target (if (= 1 (count args)) ;; we don't know if (.foo bar) is
19 | (first args) args)) ;; a method call or a field access
20 | (meta form)))
21 |
22 | (= (last opname) \.) ;; (class. ..)
23 | (with-meta (list* 'new (symbol (subs opname 0 (dec (count opname)))) expr)
24 | (meta form))
25 |
26 | :else form))
27 | form))
28 |
29 | (defn macroexpand-1 [form env]
30 | (if (seq? form)
31 | (let [op (first form)]
32 | (if (ana/specials op)
33 | form
34 | (let [v (resolve-sym op env)]
35 | (if (and (not (-> env :locals (get op))) ;; locals cannot be macros
36 | (:macro (meta v)))
37 | (apply v form env (rest form)) ; (m &form &env & args)
38 | (desugar-host-expr form)))))
39 | form))
40 |
41 | (defmacro foo [] 1)
42 |
43 | (def e {:context :ctx/expr
44 | :locals {}
45 | :ns 'user})
46 |
47 | (def e1 (atom {:namespaces {'user {:mappings (into (ns-map 'clojure.core)
48 | {'foo #'foo})
49 | :aliases {}
50 | :ns 'user}
51 | 'clojure.core {:mappings (ns-map 'clojure.core)
52 | :aliases {}
53 | :ns 'clojure.core}}}))
54 | (defmacro ast [form]
55 | `(binding [ana/macroexpand-1 macroexpand-1
56 | ana/create-var ~(fn [sym env]
57 | (doto (intern (:ns env) sym)
58 | (reset-meta! (meta sym))))
59 | ana/parse ana/-parse
60 | ana/var? ~var?
61 | elides {:all #{:line :column :file :source-span}}]
62 | (with-env e1
63 | (postwalk (ana/analyze '~form e) elide-meta))))
64 |
65 | (defmacro mexpand [form]
66 | `(with-env e1
67 | (macroexpand-1 '~form e)))
68 |
69 | (deftest analyzer-test
70 |
71 | (let [nil-ast (ast nil)]
72 | (is (= :const (:op nil-ast)))
73 | (is (= :nil (:type nil-ast)))
74 | (is (:literal? nil-ast)))
75 |
76 | (let [v-ast (ast ^:foo [1 2])]
77 | (is (= :with-meta (:op v-ast)))
78 | (is (= :map (-> v-ast :meta :op)))
79 | (is (= {:foo true} (-> v-ast :meta :form)))
80 | (is (= [1 2] (-> v-ast :expr :form))))
81 |
82 | (let [m-ast (ast {:a 1 :b 2})]
83 | (is (= {:a 1 :b 2} (:form m-ast)))
84 | (is (= [:a :b] (->> m-ast :keys (mapv :form))))
85 | (is (= [1 2] (->> m-ast :vals (mapv :form)))))
86 |
87 | (is (= 'a (mexpand a)))
88 | (is (= ::foo (mexpand ::foo)))
89 | (is (= '(new foo) (mexpand (foo.))))
90 | (is (= '(new foo a) (mexpand (foo. a))))
91 | (is (= 'foo/bar (mexpand foo/bar)))
92 | (is (= '(. bar (foo 1)) (mexpand (.foo bar 1))))
93 | (is (= '(. bar foo) (mexpand (.foo bar))))
94 | (is (= 1 (mexpand (user/foo))))
95 |
96 | (let [s-ast (:expr (ast '+))]
97 | (is (= :symbol (:type s-ast)))
98 | (is (= '+ (:form s-ast))))
99 |
100 | (let [v-ast (ast +)]
101 | (is (= :var (:op v-ast)))
102 | (is (= '+ (:form v-ast)))
103 | (is (= #'+ (:var v-ast)))
104 | (is (not (:assignable? v-ast))))
105 |
106 | (is (:assignable? (ast *warn-on-reflection*)))
107 |
108 | (let [mh-ast (ast foo/bar)]
109 | (is (= :maybe-host-form (:op mh-ast)))
110 | (is (= 'foo (:class mh-ast)))
111 | (is (= 'bar (:field mh-ast))))
112 |
113 | (let [mc-ast (ast bar)]
114 | (is (= :maybe-class (:op mc-ast)))
115 | (is (= 'bar (:class mc-ast))))
116 |
117 | (let [l-ast (ast (let [a 1] a))]
118 | (is (= :local (-> l-ast :body :ret :op)))
119 | (is (= :let (-> l-ast :body :ret :local))))
120 |
121 | (let [do-ast (ast (do 1 2 3))]
122 | (is (= 3 (-> do-ast :ret :form)))
123 | (is (= [1 2] (->> do-ast :statements (mapv :form))))
124 | (is (= :ctx/statement (-> do-ast :statements first :env :context))))
125 |
126 | (let [if-ast (ast (if 1 2 3))]
127 | (is (= [1 2 3] (->> if-ast ((juxt :test :then :else)) (mapv :form)))))
128 |
129 | (let [new-ast (ast (foo. 1 2))]
130 | (is (= 'foo (-> new-ast :class :form)))
131 | (is (= [1 2] (->> new-ast :args (mapv :form)))))
132 |
133 | (let [q-ast (:expr (ast '^{a b} [c d]))]
134 | (is (= :const (-> q-ast :meta :op)))
135 | (is (= :const (-> q-ast :op)))
136 | (is (= '{a b} (-> q-ast :meta :form)))
137 | (is (= '[c d] (-> q-ast :form))))
138 |
139 | (let [s-ast (ast (set! *warn-on-reflection* true))]
140 | (is (= :set! (:op s-ast)))
141 | (is (= #'*warn-on-reflection* (-> s-ast :target :var)))
142 | (is (= true (-> s-ast :val :form))))
143 |
144 | (let [t-ast (ast (try 0 (catch E1 e e) (catch E2 e 2) (finally 3)))]
145 | (is (= 0 (-> t-ast :body :ret :form)))
146 | (is (= 2 (-> t-ast :catches second :body :ret :form)))
147 | (is (= :maybe-class (-> t-ast :catches first :class :op)))
148 | (is (= 'E1 (-> t-ast :catches first :class :class)))
149 | (is (= 'e (-> t-ast :catches first :local :name)))
150 | (is (= 3 (-> t-ast :finally :ret :form))))
151 |
152 | (let [lfn-ast (ast (letfn [(a [] (b)) (b [] (a))] a))]
153 | (is (= :letfn (-> lfn-ast :body :ret :local)))
154 | (is (= '#{a b} (->> lfn-ast :bindings (mapv :name) set))))
155 |
156 | (let [l-ast (ast (loop [x 1] (recur 2)))]
157 | (is (= :loop (-> l-ast :bindings first :local)))
158 | (is (= :ctx/return (-> l-ast :body :env :context))))
159 |
160 | (let [f-ast (:ret (ast (fn a ([y & x] [x y]) ([] a) ([z] z))))]
161 | (is (= 1 (-> f-ast :max-fixed-arity)) (:meta f-ast))
162 | (is (:variadic? f-ast))
163 | (is (= true (-> f-ast :methods first :variadic?))))
164 |
165 | (let [d-ast (ast (def ^{c d} a 1))]
166 | (is (= 'a (-> d-ast :name)))
167 | (is (= '{c d} (-> d-ast :var meta (dissoc :line :column :file :source-span))))
168 | (is (= (ns-resolve 'user 'a)
169 | (-> e1 deref :namespaces (get 'user) :mappings (get 'a)))))
170 |
171 | (let [hc-ast (ast (.foo bar baz))]
172 | (is (= :host-call (-> hc-ast :op)))
173 | (is (= 'foo (-> hc-ast :method))))
174 |
175 | (let [hf-ast (ast (.-foo bar))]
176 | (is (= :host-field (-> hf-ast :op)))
177 | (is (= 'foo (-> hf-ast :field))))
178 |
179 | (let [hi-ast (ast (.foo bar))]
180 | (is (= :host-interop (-> hi-ast :op)))
181 | (is (= 'foo (-> hi-ast :m-or-f))))
182 |
183 | (let [i-ast (ast (1 2))]
184 | (is (= :invoke (-> i-ast :op)))
185 | (is (= 1 (-> i-ast :fn :form)))
186 | (is (= [2] (->> i-ast :args (mapv :form)))))
187 |
188 | (let [def-ast (ast (def a))]
189 | (is (= :def (-> def-ast :op)))
190 | (is (empty? (-> def-ast :children)))
191 | (is (nil? (:init def-ast))))
192 |
193 | (let [def-ast (ast (def a nil))]
194 | (is (= :def (-> def-ast :op)))
195 | (is (= [:init] (:children def-ast)))
196 | (is (= :const (-> def-ast :init :op))))
197 |
198 | (let [def-ast (ast (def a "doc" nil))]
199 | (is (= :def (-> def-ast :op)))
200 | (is (= [:meta :init] (:children def-ast)))
201 | (is (= "doc" (-> def-ast :doc)))
202 | (is (= :const (-> def-ast :init :op)))))
203 |
--------------------------------------------------------------------------------
/src/test/clojure/clojure/tools/analyzer/passes_test.clj:
--------------------------------------------------------------------------------
1 | (ns clojure.tools.analyzer.passes-test
2 | (:refer-clojure :exclude [macroexpand-1])
3 | (:require [clojure.tools.analyzer.ast :refer :all]
4 | [clojure.test :refer [deftest is]]
5 | [clojure.set :as set]
6 | [clojure.tools.analyzer.core-test :refer [ast e e1]]
7 | [clojure.tools.analyzer.passes.add-binding-atom :refer [add-binding-atom]]
8 | [clojure.tools.analyzer.passes.source-info :refer [source-info]]
9 | [clojure.tools.analyzer.passes.uniquify :refer [uniquify-locals]]
10 | [clojure.tools.analyzer.passes.constant-lifter :refer [constant-lift]]
11 | [clojure.tools.analyzer.passes.emit-form :refer [emit-form emit-hygienic-form]]
12 | [clojure.tools.analyzer.env :refer [with-env]]))
13 |
14 | (deftest passes-utils-test
15 | (let [ast {:foo [{:a 1} {:a 2}] :bar [{:a 3}] :children [:foo :bar]}]
16 | (is (= 2 (-> ast (prewalk (fn [ast] (if (:a ast)
17 | (update-in ast [:a] inc)
18 | ast)))
19 | :foo first :a)))
20 | (is (= 2 (-> ast (postwalk (fn [ast] (if (:a ast)
21 | (update-in ast [:a] inc)
22 | ast)))
23 | :foo first :a)))
24 | (is (= nil (-> ast (walk (fn [ast] (dissoc ast :a))
25 | (fn [ast] (if (:a ast)
26 | (update-in ast [:a] inc)
27 | ast)))
28 | :foo first :a)))
29 | (is (= [3 2 1] (let [a (atom [])]
30 | (-> ast (postwalk
31 | (fn [ast] (when-let [el (:a ast)]
32 | (swap! a conj el))
33 | ast) :reversed))
34 | @a)))
35 | (is (= [[{:a 1} {:a 2}] [{:a 3}]] (mapv second (children* ast))))
36 | (is (= [{:a 1} {:a 2} {:a 3}] (children ast)))))
37 |
38 | (deftest add-binding-atom-test
39 | (let [the-ast (prewalk (ast (let [a 1] a))
40 | (partial add-binding-atom (atom {})))]
41 | (swap! (-> the-ast :bindings first :atom) assoc :a 1)
42 | (is (= 1 (-> the-ast :body :ret :atom deref :a)))))
43 |
44 | (deftest source-info-test
45 | (is (= 1 (-> {:form ^{:line 1} [1]} source-info :env :line)))
46 | (is (= 1 (-> {:form ^{:column 1 :line 1} [1]} source-info :env :column)))
47 | (is (= 1 (-> {:form ^{:end-line 1 :line 1} [1]} source-info :env :end-line)))
48 | (is (= 1 (-> {:form ^{:end-column 1 :line 1} [1]} source-info :env :end-column))))
49 |
50 | (deftest constant-lift-test
51 | (is (= :const (-> (ast {:a {:b :c}}) (postwalk constant-lift) :op)))
52 | (is (not= :const (-> (ast {:a {:b #()}}) (postwalk constant-lift) :op)))
53 | (is (= :const (-> (ast [:foo 1 "bar" #{#"baz" {23 []}}])
54 | (postwalk constant-lift) :op))))
55 |
56 | (deftest uniquify-test
57 | (let [the-ast (with-env e1
58 | (uniquify-locals (ast (let [x 1 y x x (let [x x] x)]
59 | (fn [y] x)))))]
60 | (is (= 'x__#2 (-> the-ast :body :ret :ret :methods first :body :ret :name)))
61 | (is (= 'y__#1 (-> the-ast :body :ret :ret :methods first :params first :name)))
62 | (is (apply not= (->> the-ast :bindings (mapv :name))))))
63 |
64 | (deftest emit-form-test
65 | (is (= 1 (emit-form (ast 1))))
66 | (is (= "a" (emit-form (ast "a"))))
67 | (is (= :foo/bar (emit-form (ast :foo/bar))))
68 | (is (= 'a (emit-form (ast a))))
69 | (is (= 'a/b (emit-form (ast a/b))))
70 | (is (= 'a.b (emit-form (ast a.b))))
71 | (is (= 'a.b/c (emit-form (ast a.b/c))))
72 | (is (= '(. b (a c)) (emit-form (ast (.a b c)))))
73 | (is (= '(. b (a (c))) (emit-form (ast (.a b (c))))))
74 | (is (= '(. b -a) (emit-form (ast (.-a b)))))
75 | (is (= '(. b a) (emit-form (ast (.a b)))))
76 | (is (= '(let* [a 1] a) (emit-form (ast (let [a 1] a)))))
77 | (is (= '(fn* ([] nil)) (emit-form (ast (fn [])))))
78 | (is (= '(fn* ([] nil) ([a] nil)) (emit-form (ast (fn ([]) ([a]))))))
79 | (is (= '(loop* [a 1] (recur 2)) (emit-form (ast (loop [a 1] (recur 2))))))
80 | (is (= ''a (emit-form (ast 'a))))
81 | (is (= [1 2 3] (emit-form (ast [1 2 3]))))
82 | (is (= {:a 1 [:b] 2} (emit-form (ast {:a 1 [:b] 2}))))
83 | (is (= {:a 1} (meta (emit-form (ast ^{:a 1} [:foo])))))
84 | (is (= '(do 1) (emit-form (ast (do 1)))))
85 | (is (= '(do a b c) (emit-form (ast (do a b c)))))
86 | (is (= '(if 1 2) (emit-form (ast (if 1 2)))))
87 | (is (= '(if 1 2 3) (emit-form (ast (if 1 2 3)))))
88 | (is (= '(new a b c) (emit-form (ast (a. b c)))))
89 | (is (= '(set! a 1) (emit-form (ast (set! a 1)))))
90 | (is (= '(def a 1) (emit-form (ast (def a 1)))))
91 | (is (= '(def a "doc" 1) (emit-form (ast (def a "doc" 1)))))
92 | (is (= '(a b) (emit-form (ast (a b)))))
93 | (is (= '(try (throw 1) (catch e t b) (finally 2))
94 | (emit-form (ast (try (throw 1) (catch e t b) (finally 2)))))))
95 |
96 | (deftest emit-hygienic-form-test
97 | (with-env e1
98 | (is (= '(let* [a__#0 1 a__#1 a__#0] a__#1)
99 | (emit-hygienic-form (uniquify-locals (ast (let [a 1 a a] a))))))
100 | (is (= '(let* [x__#0 1] (fn* ([x__#1] x__#1)))
101 | (emit-hygienic-form (uniquify-locals (ast (let [x 1] (fn [x] x)))))))
102 | (is (= '(fn* x__#0 ([x__#1] x__#1))
103 | (emit-hygienic-form (uniquify-locals (ast (fn x [x] x))))))))
104 |
105 | (deftest deeply-nested-uniquify
106 | (is (= '(fn* ([x__#0 y__#0 z__#0]
107 | (let* [foo__#0 (fn* ([y__#1 z__#1] [y__#1 z__#1]))]
108 | (foo__#0 x__#0 y__#0))))
109 | (with-env e1
110 | (emit-hygienic-form (uniquify-locals (ast (fn [x y z]
111 | (let [foo (fn [y z]
112 | [y z])]
113 | (foo x y))))))))))
114 |
--------------------------------------------------------------------------------
/src/test/clojure/clojure/tools/analyzer/query_test.clj:
--------------------------------------------------------------------------------
1 | (ns clojure.tools.analyzer.query-test
2 | (:refer-clojure :exclude [macroexpand-1])
3 | (:require [clojure.tools.analyzer.ast :refer :all]
4 | [clojure.test :refer [deftest is]]
5 | [clojure.tools.analyzer.core-test :refer [ast e]]
6 | [clojure.tools.analyzer.ast.query :refer [q]]
7 | [clojure.tools.analyzer.ast :as ast]
8 | [clojure.tools.analyzer.utils :refer [compile-if]]
9 | [clojure.tools.analyzer.passes.index-vector-nodes :refer [index-vector-nodes]]))
10 |
11 | (def clojure-version-seven-query
12 | '[:find ?docstring
13 | :where
14 | [?def :op :def]
15 | [?def :init ?fn]
16 | [?fn :methods ?method]
17 | [?method :body ?body]
18 | [?body :statements ?statement]
19 | [?statement :val ?docstring]
20 | [?statement :type :string]
21 | [?statement :idx 0]])
22 |
23 | (def clojure-version-eight-and-above-query
24 | '[:find ?docstring
25 | :where
26 | [?def :op :def]
27 | [?def :init ?fn]
28 | [?fn :expr ?expr]
29 | [?expr :methods ?method]
30 | [?method :body ?body]
31 | [?body :statements ?statement]
32 | [?statement :val ?docstring]
33 | [?statement :type :string]
34 | [?statement :idx 0]])
35 |
36 | (compile-if (Class/forName "datomic.Datom")
37 | (deftest query
38 | (let [ast (ast/prewalk (ast (defn x [] "misplaced docstring" 1))
39 | index-vector-nodes)
40 | the-query (if (< (:minor *clojure-version*) 8)
41 | clojure-version-seven-query
42 | clojure-version-eight-and-above-query)]
43 | (is (= "misplaced docstring"
44 | (ffirst (q the-query [ast])))))))
45 |
--------------------------------------------------------------------------------