diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..8a2d87838c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +name: Release on demand + +on: + workflow_dispatch: + inputs: + deploy: + description: "Deploy to Maven Central" + required: true + default: false + type: boolean + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Set Github identity + run: | + git config --global user.name clojure-build + git config --global user.email "clojure-build@users.noreply.github.com" + - name: Set up Java + uses: actions/setup-java@v3 + with: + java-version: 21 + distribution: 'temurin' + cache: 'maven' + server-id: central + server-username: MAVEN_USERNAME + server-password: MAVEN_PASSWORD + gpg-private-key: ${{ secrets.CENTRAL_GPG_SECRET_KEY }} + gpg-passphrase: GPG_PASSPHRASE + - name: Release + run: script/build + env: + HUDSON: ${{ github.event.inputs.deploy }} + MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.CENTRAL_PASSWORD }} + GPG_PASSPHRASE: ${{ secrets.CENTRAL_GPG_SECRET_KEY_PASSWORD }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000000..b8c5ae94ba --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,311 @@ +name: Tests +on: [push] + +jobs: + # Runtime Tests + runtime-test: + name: Runtime Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - uses: DeLaGuardo/setup-clojure@3.1 + with: + tools-deps: '1.10.1.763' + + - name: Cache maven + uses: actions/cache@v4.2.0 + env: + cache-name: cache-maven + with: + path: ~/.m2 + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Cache gitlibs + uses: actions/cache@v4.2.0 + env: + cache-name: cache-gitlibs + with: + path: ~/.gitlibs + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Build tests + run: clojure -M:runtime.test.build + + - name: Run tests + run: | + node builds/out-adv/core-advanced-test.js | tee test-out.txt + grep -qxF '0 failures, 0 errors.' test-out.txt + + # Lite Tests + lite-test: + name: Lite Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - uses: DeLaGuardo/setup-clojure@3.1 + with: + tools-deps: '1.10.1.763' + + - name: Cache maven + uses: actions/cache@v4.2.0 + env: + cache-name: cache-maven + with: + path: ~/.m2 + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Cache gitlibs + uses: actions/cache@v4.2.0 + env: + cache-name: cache-gitlibs + with: + path: ~/.gitlibs + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Build tests + run: clojure -M:lite.test.build + + - name: Run tests + run: | + node builds/out-lite/lite-test.js | tee test-out.txt + grep -qxF '0 failures, 0 errors.' test-out.txt + + # Runtime Tests + runtime-windows-test: + name: Runtime Windows Tests + runs-on: windows-2022 + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - uses: DeLaGuardo/setup-clojure@3.5 + with: + cli: '1.10.1.763' + + - name: Build tests + run: clojure -M:runtime.test.build + shell: powershell + + - name: Run tests + run: | + node builds/out-adv/core-advanced-test.js | tee test-out.txt + findstr "0 failures, 0 errors." test-out.txt + shell: powershell + + # Self-host Tests + self-host-test: + name: Self-host Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - uses: DeLaGuardo/setup-clojure@3.1 + with: + tools-deps: '1.10.1.763' + + - name: Cache maven + uses: actions/cache@v4.2.0 + env: + cache-name: cache-maven + with: + path: ~/.m2 + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Cache gitlibs + uses: actions/cache@v4.2.0 + env: + cache-name: cache-gitlibs + with: + path: ~/.gitlibs + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Build tests + run: clojure -M:selfhost.test.build + + - name: Run tests + run: | + node builds/out-self/core-self-test.js | tee test-out.txt + grep -qxF '0 failures, 0 errors.' test-out.txt + + # Self-parity Tests + self-parity-test: + name: Self-parity Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - uses: DeLaGuardo/setup-clojure@3.1 + with: + tools-deps: '1.10.1.763' + + - name: Cache maven + uses: actions/cache@v4.2.0 + env: + cache-name: cache-maven + with: + path: ~/.m2 + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Cache gitlibs + uses: actions/cache@v4.2.0 + env: + cache-name: cache-gitlibs + with: + path: ~/.gitlibs + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Build tests + run: clojure -M:selfparity.test.build + + - name: Run tests + run: | + node builds/out-self-parity/main.js | tee test-out.txt + grep -qxF '0 failures, 0 errors.' test-out.txt + + # Compiler Tests + compiler-test: + name: Compiler Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - uses: DeLaGuardo/setup-clojure@3.1 + with: + tools-deps: '1.10.1.763' + + - name: Cache maven + uses: actions/cache@v4.2.0 + env: + cache-name: cache-maven + with: + path: ~/.m2 + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Cache gitlibs + uses: actions/cache@v4.2.0 + env: + cache-name: cache-gitlibs + with: + path: ~/.gitlibs + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Run tests + run: clojure -M:compiler.test:compiler.test.run + + # Compiler Windows Tests + compiler-windows-test: + name: Compiler Windows Tests + runs-on: windows-2022 + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - uses: DeLaGuardo/setup-clojure@3.5 + with: + cli: '1.10.1.763' + + - name: Run tests + run: clojure -M:compiler.test:compiler.test.run + shell: powershell + + # CLI Tests + cli-test: + name: CLI Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - uses: DeLaGuardo/setup-clojure@3.1 + with: + tools-deps: '1.10.1.763' + + - name: Cache maven + uses: actions/cache@v4.2.0 + env: + cache-name: cache-maven + with: + path: ~/.m2 + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Cache gitlibs + uses: actions/cache@v4.2.0 + env: + cache-name: cache-gitlibs + with: + path: ~/.gitlibs + key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('deps.edn', '*/deps.edn') }} + restore-keys: | + ${{ runner.os }}-${{ env.cache-name }}- + + - name: Build Uberjar + run: ./script/uberjar + + - name: Run tests + run: | + unset JAVA_TOOL_OPTIONS + clojure -M:cli.test.run | tee test-out.txt + grep -qxF '0 failures, 0 errors.' test-out.txt diff --git a/.gitignore b/.gitignore index 5d24a84228..ad9f11ed93 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *~ .idea /.DS_Store +.DS_Store /classes /lib /target @@ -9,8 +10,10 @@ closure /coreadvanced.js /coresimple.js /out -/pom.xml -.repl +*out +.lein* +*.iml +.repl* *.swp *.zip clojurescript_release_* @@ -18,3 +21,16 @@ closure-release-* .lein-repl-history .nrepl-port .nrepl-repl-history +builds +.cljs* +node_modules +nashorn_code_cache +src/main/cljs/cljs/core.aot.js +src/main/cljs/cljs/core.aot.js.map +src/main/cljs/cljs/core.cljs.cache.aot.edn +src/main/cljs/cljs/core.cljs.cache.aot.json +.node_repl +package.json +package-lock.json +.cpcache +resources/brepl_client.js diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..c05c84b404 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,63 @@ +dist: xenial + +git: + depth: 1000 + +language: node_js + +node_js: + - "6" + +before_install: + - wget https://ftp.mozilla.org/pub/firefox/nightly/latest-mozilla-central/jsshell-linux-x86_64.zip + - unzip jsshell-linux-x86_64.zip -d spidermoney + - sudo apt-get install -y libjavascriptcoregtk-4.0-bin + - wget https://aka.ms/chakracore/cc_linux_x64_1_8_1 -O chakra-core.tar.gz + - tar xvzf chakra-core.tar.gz + - wget https://github.com/oracle/graal/releases/download/vm-1.0.0-rc12/graalvm-ce-1.0.0-rc12-linux-amd64.tar.gz + - tar xzf graalvm-ce-1.0.0-rc12-linux-amd64.tar.gz + +before_script: + - script/bootstrap + - script/uberjar + - mkdir -p builds/out-adv + - bin/cljsc src/test/cljs "{:optimizations :advanced + :output-wrapper true + :verbose true + :compiler-stats true + :parallel-build true + :output-dir \"builds/out-adv\" + :npm-deps {:lodash \"4.17.4\"} + :closure-warnings {:non-standard-jsdoc :off :global-this :off} + :language-in :es6 + :language-out :es5 + :install-deps true + :foreign-libs [{:file \"src/test/cljs/calculator_global.js\" + :provides [\"calculator\"] + :global-exports {calculator Calculator}} + {:file \"src/test/cljs/es6_dep.js\" + :module-type :es6 + :provides [\"es6_calc\"]} + {:file \"src/test/cljs/calculator.js\" + :module-type :commonjs + :provides [\"calculator\"]} + {:file \"src/test/cljs/es6_default_hello.js\" + :provides [\"es6_default_hello\"] + :module-type :es6}]}" > builds/out-adv/core-advanced-test.js + +script: + - lein test + - jsc builds/out-adv/core-advanced-test.js | tee test-out.txt + - grep '0 failures, 0 errors.' test-out.txt + - ./spidermoney/js -f builds/out-adv/core-advanced-test.js | tee test-out.txt + - grep '0 failures, 0 errors.' test-out.txt + - ./ChakraCoreFiles/bin/ch builds/out-adv/core-advanced-test.js | tee test-out.txt + - grep '0 failures, 0 errors.' test-out.txt + - ./graalvm-ce-1.0.0-rc12/bin/js builds/out-adv/core-advanced-test.js | tee test-out.txt + - grep '0 failures, 0 errors.' test-out.txt + - script/test-self-host | tee test-out.txt + - grep '0 failures, 0 errors.' test-out.txt + - script/test-self-parity | tee test-out.txt + - grep '0 failures, 0 errors.' test-out.txt + - script/test-cli node | tee test-out.txt + - grep '0 failures, 0 errors.' test-out.txt diff --git a/Clojurescript.iml b/Clojurescript.iml deleted file mode 100644 index d5c0743275..0000000000 --- a/Clojurescript.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..e246f6a217 --- /dev/null +++ b/LICENSE @@ -0,0 +1,205 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' + from a Contributor if it was added to the Program by such Contributor + itself or anyone acting on such Contributor's behalf. Contributions do not + include additions to the Program which: (i) are separate modules of + software distributed in conjunction with the Program under their own + license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly + perform, distribute and sublicense the Contribution of such Contributor, + if any, and such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of + the Contribution and the Program if, at the time the Contribution is + added by the Contributor, such addition of the Contribution causes such + combination to be covered by the Licensed Patents. The patent license + shall not apply to any other combinations which include the Contribution. + No hardware per se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses + to its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other + entity based on infringement of intellectual property rights or + otherwise. As a condition to exercising the rights and licenses granted + hereunder, each Recipient hereby assumes sole responsibility to secure + any other intellectual property rights needed, if any. For example, if a + third party patent license is required to allow Recipient to distribute + the Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or + conditions of title and non-infringement, and implied warranties or + conditions of merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained + within the Program. + +Each Contributor must identify itself as the originator of its Contribution, +if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, +if a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits and +other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such Commercial +Contributor in connection with its distribution of the Program in a commercial +product offering. The obligations in this section do not apply to any claims +or Losses relating to any actual or alleged intellectual property +infringement. In order to qualify, an Indemnified Contributor must: +a) promptly notify the Commercial Contributor in writing of such claim, and +b) allow the Commercial Contributor to control, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such claim at +its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If +that Commercial Contributor then makes performance claims, or offers +warranties related to Product X, those performance claims and warranties are +such Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to the +risks and costs of program errors, compliance with applicable laws, damage to +or loss of data, programs or equipment, and unavailability or interruption of +operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION +LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by the +parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement will +be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version of the +Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly +stated in Sections 2(a) and 2(b) above, Recipient receives no rights or +licenses to the intellectual property of any Contributor under this Agreement, +whether expressly, by implication, estoppel or otherwise. All rights in the +Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. + + diff --git a/README.md b/README.md index 29d7e819f2..2975a3f157 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,57 @@ ## What is ClojureScript? ## -ClojureScript is a new compiler for [Clojure](http://clojure.org) that targets JavaScript. It is designed to emit JavaScript code which is compatible with the advanced compilation mode of the [Google Closure](http://code.google.com/closure/) optimizing compiler. +[ClojureScript](https://clojurescript.org) is a compiler for [Clojure](https://clojure.org) that targets JavaScript. It is designed to emit JavaScript code which is compatible with the advanced compilation mode of the [Google Closure](https://developers.google.com/closure/compiler/) optimizing compiler. + +Official web site: https://clojurescript.org ## Releases and dependency information ## -Latest stable release: 0.0-2173 +Latest stable release: 1.12.134 + +* [All released versions](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22clojurescript%22) -* [All released versions](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.clojure%22%20AND%20a%3A%22clojurescript%22) +[Clojure deps.edn](http://clojure.org/guides/deps_and_cli) dependency information: -[Leiningen](http://github.com/technomancy/leiningen/) dependency information: + ``` + org.clojure/clojurescript {:mvn/version "1.12.134"} + ``` + +[Leiningen](https://github.com/technomancy/leiningen/) dependency information: ``` -[org.clojure/clojurescript "0.0-2173"] +[org.clojure/clojurescript "1.12.134"] ``` -[Maven](http://maven.apache.org) dependency information: +[Maven](https://maven.apache.org) dependency information: ``` org.clojure clojurescript - 0.0-2173 + 1.12.134 ``` ## Getting Started ## -* [Compare with JavaScript](http://himera.herokuapp.com/synonym.html) -* [Try it online](http://himera.herokuapp.com/index.html) -* Read the [Quick Start](https://github.com/clojure/clojurescript/wiki/Quick-Start) guide. -* Read the [Documentation](https://github.com/clojure/clojurescript/wiki). -* Try a [tutorial](https://github.com/clojure/clojurescript/wiki). -* Look at the [Sample Applications](https://github.com/clojure/clojurescript/tree/master/samples). +* Read the [Quick Start](https://clojurescript.org/guides/quick-start) guide. +* Read the [Documentation](https://clojurescript.org). +* Try a [tutorial](https://clojurescript.org/guides). +* [Companies using ClojureScript](https://clojurescript.org/community/companies) ## Questions, Feedback? ## -Please point all of your questions and feedback -to the [Clojure mailing list](http://groups.google.com/group/clojure). There -is also a community run [ClojureScript user mailing list](http://groups.google.com/group/clojurescript). The Jira bug/feature tracking application is located at . +Please point all of your questions and feedback to the +[Clojure mailing list](https://groups.google.com/group/clojure). There +is a community run +[ClojureScript user mailing list](https://groups.google.com/group/clojurescript) and +the IRC channel, `#clojurescript` on [freenode.net](https://freenode.net/), is quite active. +There is also a community run [Slack channel](https://clojurians.slack.com). The +Jira bug/feature tracking application is located at +. Before submitting issues +please read the +[Reporting Issues](https://github.com/clojure/clojurescript/wiki/Reporting-Issues) +page first. ## Developers Welcome ## @@ -45,16 +59,28 @@ ClojureScript operates under the same license as Clojure. All contributors must have a signed CA (Contributor's Agreement) and submit their patch via the appropriate channels. If you're interested in contributing to the project, please see the -[contributing](http://clojure.org/contributing) page on -[clojure.org](http://clojure.org). For more information about working +[contributing](https://clojure.org/dev/contributing) page on +[clojure.org](https://clojure.org). For more information about working on the compiler and testing check the -[Developer section of the wiki](http://github.com/clojure/clojurescript/wiki/Developers). +[Developer section of the wiki](https://github.com/clojure/clojurescript/wiki/Developers). + +YourKit +---- + + + +YourKit has given an open source license for their profiler, greatly simplifying the profiling of ClojureScript performance. + +YourKit supports open source projects with its full-featured Java Profiler. +YourKit, LLC is the creator of YourKit Java Profiler +and YourKit .NET Profiler, +innovative and intelligent tools for profiling Java and .NET applications. ## License ## Copyright (c) Rich Hickey. All rights reserved. The use and distribution terms for this software are covered by the Eclipse - Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + Public License 1.0 (https://opensource.org/license/epl-1-0/) which can be found in the file epl-v10.html at the root of this distribution. By using this software in any fashion, you are agreeing to be bound by the terms of this license. You must diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000000..6ed0cdaba6 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,52 @@ +image: Visual Studio 2017 + +environment: + nodejs_version: "6" + +platform: + - x64 + +configuration: + - Release + +matrix: + allow_failures: + - platform: x64 + configuration: Release + +cache: + - '%UserProfile%\.m2' + +install: + # these need to have a line in between because of Windows line endings + - ps: >- + New-Item c:\scripts -type directory + + $env:Path += ";C:\scripts" + + Invoke-WebRequest -Uri https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/lein.bat -OutFile "C:\scripts\lein.bat" + + lein self-install + + lein version + - ps: Install-Product node $env:nodejs_version x64 + - ps: wget 'http://ftp.mozilla.org/pub/firefox/nightly/latest-mozilla-central/jsshell-win64.zip' -OutFile "$pwd\jsshell.zip" + - ps: 7z x "-o$pwd\jsshell" jsshell.zip -r + - ps: wget 'https://aka.ms/chakracore/cc_windows_all_1_8_1' -OutFile "$pwd\chakra-core.zip" + - ps: 7z x "-o$pwd\chakra-core" chakra-core.zip -r + - ps: .\script\bootstrap.ps1 + - ps: "[Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8" + - ps: $env:SPIDERMONKEY_HOME = "$pwd/jsshell" + - ps: $SPIDERMONKEY_HOME = "$pwd/jsshell" + - ps: $env:CHAKRACORE_HOME = "$pwd/chakra-core/x64_release" + - ps: $CHAKRACORE_HOME = "$pwd/chakra-core/x64_release" + +test_script: + - cmd: lein test + - cmd: powershell -noninteractive -noprofile -command .\script\test.ps1 > test-out.txt + - cmd: type test-out.txt + # Since tests are currently only run in 2 JavaScript environments, look for exactly 2 counts of "0 failures, 0 errors." + - cmd: powershell -noninteractive -noprofile -command if (-not ((sls -Pattern '0 failures, 0 errors.' -SimpleMatch test-out.txt).count -eq 2)) { exit 1 } + +# Don't actually build (MSBuild). +build: off diff --git a/ast-ref/ast-ref.edn b/ast-ref/ast-ref.edn new file mode 100644 index 0000000000..a0ca1a42bd --- /dev/null +++ b/ast-ref/ast-ref.edn @@ -0,0 +1,313 @@ +{:all-keys + + [[:op "The node op"] + [:form "The ClojureScript form from which the node originated"] + [:env "The environment map"] + [:context "Either :expr, :return or :statement."] + ^:optional + [:children "A vector of keywords, representing the children nodes of this node, in order of evaluation"] +; ^:optional +; [: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"] + ;^:optional + ;[:top-level "`true` if this is the root node"] + [:tag "The tag this expression is required to have"] +; [:o-tag "The tag of this expression, based on the node's children"] +; ^:optional +; [:ignore-tag "`true` if this node returns a statement rather than an expression"] + ; ^:optional + ; [:loops "A set of the loop-ids that might cause this node to recur"] + ] + + :node-keys + [{:op :binding + :doc "Node for a binding symbol" + :keys [[:form "The binding symbol"] + [:name "The binding symbol"] + [:local "One of :arg, :catch, :fn, :let, :letfn, :loop or :field"] + ^:optional + [:variadic? "When :local is :arg, a boolean indicating whether this parameter binds to a variable number of arguments"] + ^:optional ^:children + [:init "When :local is :let, :letfn or :loop, an AST node representing the bound value"] + ^:optional ;^:children + [:shadow "When this binding shadows another local binding, an AST node representing the shadowed local"] + ]} + {:op :case + :doc "Node for a case* special-form expression" + :keys [[:form "`(case* expr shift maks default case-map switch-type test-type skip-check?)`"] + ^:children + [:test "The AST node for the expression to test against"] + ^:children + [:nodes "A vector of :case-node AST nodes representing the test/then clauses of the case* expression"] + ^:children + [:default "An AST node representing the default value of the case expression"] + ]} + {:op :case-node + :doc "Grouping node for tests/then expressions in a case* expression" + :keys [^:children + [:tests "A vector of :case-test AST nodes representing the test values"] + ^:children + [:then "A :case-then AST node representing the value the case expression will evaluate to when one of the :tests expressions matches the :case :test value"]]} + {:op :case-test + :doc "Node for a test value in a case* expression" + :keys [^:children + [:test "A :const AST node representing the test value"] + #_[:hash]]} + {:op :case-then + :doc "Node for a then expression in a case* expression" + :keys [^:children + [:then "An AST node representing the expression the case will evaluate to when the :test expression matches this node's corresponding :case-test value"] + #_[:hash]]} + {:op :const + :doc "Node for a constant literal or a quoted collection literal" + :keys [[:form "A constant literal or a quoted collection literal"] + [:literal? "`true`"] + [:type "one of :nil, :bool, :keyword, :symbol, :string, :number, :type, :record, :map, :vector, :set, :seq, :char, :regex, :class, :var, or :unknown"] + [:val "The value of the constant node"] + ;^:optional ^:children + ;; FIXME + ;[: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"] + ; + ;^:optional + ;[:id "A numeric id for the constant value, will be the same for every instance of this constant inside the same compilation unit, not present if :type is :nil or :bool"] + ]} + {:op :def + :doc "Node for a def special-form expression" + :keys [[:form "`(def name docstring? init?)`"] + [:name "The var symbol to define in the current namespace"] + ;[:var "The Var object created (or found, if it already existed) named by the symbol :name in the current namespace"] + ;^:optional ^:children + ;[: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"] + ^:optional ^:children + [:init "An AST node representing the initial value of the var"] + ^:children + [:the-var "A :the-var AST node representing the return of this :def."] + ;^:optional + ;[:doc "The docstring for this var"] + ]} + {:op :defrecord + :doc "Node for a defrecord* special-form expression" + :keys [[:form "`(deftype* name class.name [arg*] :implements [interface*] method*)`"] + ;[:interfaces "A set of the interfaces implemented by the type"] + [:t "The symbol name of the defrecord."] + ^:children + [:body "An AST node containing method implementations for this record."] + ;^:children + ;[:fields "A vector of :binding AST nodes with :local :field representing the deftype fields"] + ]} + {:op :deftype + :doc "Node for a deftype* special-form expression" + :keys [[:form "`(deftype* name class.name [arg*] :implements [interface*] method*)`"] + ;[:interfaces "A set of the interfaces implemented by the type"] + [:t "The symbol name of the deftype"] + ;[:class-name "A class for the deftype, should *never* be instantiated or used on instance? checks as this will not be the same class the deftype will evaluate to after compilation"] + ^:children + [:body "An AST node containing method implemented for this type."] + ;^:children + ;[:fields "A vector of :binding AST nodes with :local :field representing the deftype fields"] + ]} + {:op :do + :doc "Node for a do special-form expression or for another special-form's body" + :keys [[:form "`(do statement* ret)`"] + ^:children + [:statements "A vector of AST nodes representing all but the last expression in the do body"] + ^:children + [:ret "An AST node representing the last expression in the do body (the block's return value)"] + ^:optional + [:body? "`true` if this node is a synthetic body"]]} + {:op :fn + :doc "Node for a fn* special-form expression" + :keys [[:form "`(fn* name? [arg*] body*)` or `(fn* name? method*)`"] + [:variadic? "`true` if this function contains a variadic arity method"] + [:max-fixed-arity "The number of arguments taken by the fixed-arity method taking the most arguments"] + ^:optional ^:children + [:local "A :binding AST node with :local :fn representing the function's local name, if one is supplied"] + ^:children + [:methods "A vector of :fn-method AST nodes representing the fn method arities"] + ]} + {:op :fn-method + :doc "Node for an arity method in a fn* expression" + :keys [[:form "`([arg*] body*)`"] + [:variadic? "`true` if this fn-method takes a variable number of arguments"] + ^:children + [:params "A vector of :binding AST nodes with :local :arg representing this fn-method args"] + [:fixed-arity "The number of non-variadic args this fn-method takes"] + ^:children + [:body "Synthetic :do node (with :body? `true`) representing the body of this fn-method"]]} + {:op :host-call + :doc "Node for a host interop call" + :keys [[:form "`(.method target arg*)`"] + [:method "Symbol naming the method to call"] + ^:children + [:target "An AST node representing the target object"] + ^:children + [:args "A vector of AST nodes representing the args passed to the method call"]]} + {:op :host-field + :doc "Node for a host interop field access" + :keys [[:form "`(.-field target)`"] + [:field "Symbol naming the field to access"] + ^:children + [:target "An AST node representing the target object"]]} + {:op :if + :doc "Node for an if special-form expression" + :keys [[:form "`(if test then else?)`"] + ^:children + [:test "An AST node representing the test expression"] + ^:children + [:then "An AST node representing the expression's return value if :test evaluated to a truthy value"] + ^:children + [: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"]]} + {:op :invoke + :doc "Node for an invoke expression" + :keys [[:form "`(f arg*)`"] + ^:children + [:fn "An AST node representing the function to invoke"] + ^:children + [:args "A vector of AST nodes representing the args to the function"] + ;FIXME + ;^:optional + ;[:meta "Map of metadata attached to the invoke :form"] + ]} + {:op :js + :doc "Node for a js* special-form expression" + :keys [[:form "`(js* js-string arg*)`"] + [:segs "A vector of js strings that delimit the compiled args"] + ^:children + [:args "A vector of AST nodes representing the cljs expressions that will be interposed with the strings in segs"]]} + {:op :js-array + :doc "Node for a js array literal" + :keys [[:form "`#js [item*]`"] + ^:children + [:items "A vector of AST nodes representing the items of the js array"]]} + {:op :js-object + :doc "Node for a js object literal" + :keys [[:form "`#js {[key value]*}`"] + [:keys "A vector of values representing the keys of the js object"] + ^:children + [:vals "A vector of AST nodes representing the vals of the js object"]]} + {:op :js-var + :doc "Node for a js-var symbol" + :keys [[:form "A symbol naming the js-var in the form: `js/foo`, `js-ns/foo` or `js-var`"] + [:ns "The namespace symbol for this js-var."] + [:name "The fully qualified symbol naming this js-var."] + ]} + {:op :let + :doc "Node for a let* special-form expression" + :keys [[:form "`(let* [binding*] body*)`"] + ^:children + [:bindings "A vector of :binding AST nodes with :local :let"] + ^:children + [:body "Synthetic :do node (with :body? `true`) representing the body of the let expression"]]} + {:op :letfn + :doc "Node for a letfn* special-form expression" + :keys [[:form "`(letfn* [binding*] body*)`"] + ^:children + [:bindings "A vector of :binding AST nodes with :local :letfn"] + ^:children + [:body "Synthetic :do node (with :body? `true`) representing the body of the letfn expression"]]} + {:op :local + :doc "Node for a local symbol" + :keys [[:form "The local symbol"] + [:name "The uniquified local symbol"] + [:local "One of :arg, :catch, :fn, :let, :letfn, :loop, :field or :this"] + ]} + {:op :loop + :doc "Node a loop* special-form expression" + :keys [[:form "`(loop* [binding*] body*)`"] + ^:children + [:bindings "A vector of :binding AST nodes with :local :loop"] + ^:children + [:body "Synthetic :do node (with :body? `true`) representing the body of the loop expression"]]} + {:op :map + :doc "Node for a map literal with attached metadata and/or non literal elements" + :keys [[:form "`{[key val]*}`"] + ^:children + [:keys "A vector of AST nodes representing the keys of the map"] + ^:children + [:vals "A vector of AST nodes representing the vals of the map"]]} + {:op :new + :doc "Node for a new special-form expression" + :keys [[:form "`(new Class arg*)`"] + ^:children + [:class "A :const AST node with :type :class representing the Class to instantiate"] + ^:children + [:args "A vector of AST nodes representing the arguments passed to the Class constructor"] + ]} + {:op :no-op + :doc "Node for a no-op" + :keys [ + ]} + {:op :ns + :doc "Node for a clojure.core/ns form." + :keys [ + ]} + {:op :ns* + :doc "Node for a special file-loading form." + :keys [ + ]} + {:op :quote + :doc "Node for a quote special-form expression" + :keys [[:form "`(quote expr)`"] + ^:children + [:expr "A :const AST node representing the quoted value"] + [:literal? "`true`"]]} + {:op :recur + :doc "Node for a recur special-form expression" + :keys [[:form "`(recur expr*)`"] + ^:children + [:exprs "A vector of AST nodes representing the new bound values for the loop binding on the next loop iteration"]]} + {:op :set + :doc "Node for a set literal with attached metadata and/or non literal elements" + :keys [[:form "`#{item*}`"] + ^:children + [:items "A vector of AST nodes representing the items of the set"]]} + {:op :set! + :doc "Node for a set! special-form expression" + :keys [[:form "`(set! target val)`"] + ^:children + [:target "An AST node representing the target of the set! expression, must be :assignable?"] + ^:children + [:val "An AST node representing the new value for the target"]]} + {:op :the-var + :doc "Node for a var special-form expression" + :keys [[:form "`(var var-name)`"] + ^:children + [:var "A :var AST node that this expression refers to"] + ^:children + [:sym "An AST node for the quoted fully qualified name of this var."] + ^:children + [:meta "A :map AST node of this var's metadata."] + ]} + {:op :throw + :doc "Node for a throw special-form statement" + :keys [[:form "`(throw exception)`"] + ^:children + [:exception "An AST node representing the exception to throw"]]} + {:op :try + :doc "Node for a try special-form expression" + :keys [[:form "`(try body* catch* finally?)`"] + ^:children + [:body "Synthetic :do AST node (with :body? `true`) representing the body of this try expression"] + ^:optional + [:name "A binding in scope in :catch. (symbol)"] + ^:optional ^:children + [:catch "An AST node representing an unconditional JavaScript catch."] + ^:optional ^:children + [:finally "Synthetic :do AST node (with :body? `true`) representing the final clause of this try expression"]]} + {:op :var + :doc "Node for a var symbol" + :keys [[:form "A symbol naming the var"] + [:ns "The namespace symbol this var is defined in."] + [:name "The fully qualified symbol naming this var."] + ]} + {:op :vector + :doc "Node for a vector literal with attached metadata and/or non literal elements" + :keys [[:form "`[item*]`"] + ^:children + [:items "A vector of AST nodes representing the items of the vector"]]} + {:op :with-meta + :doc "Node for a non quoted collection literal or fn/reify expression with attached metadata" + :keys [[:form "Non quoted collection literal or fn/reify expression with attached metadata"] + ^:children + [:meta "An AST node representing the metadata of expression. The node will be either a :map node or a :const node with :type :map"] + ^:children + [:expr "The expression this metadata is attached to, :op is one of :vector, :map, :set, :fn or :reify"]]}]} diff --git a/ast-ref/buildref.sh b/ast-ref/buildref.sh new file mode 100755 index 0000000000..639710eb9e --- /dev/null +++ b/ast-ref/buildref.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +java -cp .:`lein cp` clojure.main < (str x) + (replace #"`(.*?)`" "$1") + (replace #":([a-zA-Z\?!\-]*)" ":$1"))) + +(defn build-children [children] + (if (some #(:optional (meta %)) children) + (let [[c & rest] children] + (let [k (build-children rest) + kc (mapv (fn [x] (cons c x)) k)] + (if (:optional (meta c)) + (into k kc) + kc))) + (if (seq children) + [children] + [[]]))) + +(defn children [keys] + (when-let [children (seq (filter #(:children (meta %)) keys))] + (mapv #(mapv first %) (build-children children)))) + +(def nodes + (apply str (for [{:keys [op doc keys]} (:node-keys tej-ref) :let [op (name op)]] + (str "
" + "

" "#" op "

" + "

" doc "

" + "
" + "
:op
:" op "
" + (apply str (for [[k d :as f] keys] + (str "
" k "
" + "
" (if (:optional (meta f)) + "optional ") (fix d) "
"))) + (if-let [c (children keys)] + (str "
:children
" + (join ", " (mapv (fn [c] (str "" c "")) c)) "
")) + "
" + "
\n")))) + +(def nav + (apply str (for [{op :op} (:node-keys tej-ref) :let [op (name op)]] + (str "
  • " op "
  • \n")))) + +(def common + (apply str (str "
    " + "
    " + (apply str (for [[k d :as f] (:all-keys tej-ref)] + (str "
    " k "
    " + "
    " (if (:optional (meta f)) + "optional ") (fix d) "
    "))) + "
    " + "
    \n"))) + +(spit "quickref.html" + (-> html + (replace "{nav}" nav) + (replace "{common}" common) + (replace "{nodes}" nodes))) diff --git a/ast-ref/quickref.html.tpl b/ast-ref/quickref.html.tpl new file mode 100644 index 0000000000..913c210705 --- /dev/null +++ b/ast-ref/quickref.html.tpl @@ -0,0 +1,110 @@ + + + + + cljs.analyzer AST Quickref (alpha) + + + + +
    +

    cljs.analyzer AST Quickref (alpha)

    +

    Common AST fields

    + {common} +

    Nodes reference

    + {nodes} +
    + + diff --git a/benchmark/cljs/benchmark_runner.cljs b/benchmark/cljs/benchmark_runner.cljs index e77e548c27..a4ea583c9c 100644 --- a/benchmark/cljs/benchmark_runner.cljs +++ b/benchmark/cljs/benchmark_runner.cljs @@ -1,7 +1,8 @@ (ns cljs.benchmark-runner (:refer-clojure :exclude [println]) (:require [cljs.reader :as reader] - [clojure.core.reducers :as r])) + [clojure.core.reducers :as r] + [clojure.string :as string])) (def println print) @@ -24,6 +25,17 @@ (simple-benchmark [coll arr] (array-reduce coll + 0) 1) (simple-benchmark [coll arr] (array-reduce coll sum 0) 1) +(println ";; areduce") +(def x (atom 0)) +(simple-benchmark [arr (to-array (range 1000000))] (reset! x (areduce arr i ret 0 (+ ret (aget arr i)))) 1) + +(println ";; amap") +(simple-benchmark [arr (to-array (range 1000000))] (amap arr i ret (* 10 (aget arr i))) 1) + +(println ";; js-keys") +(simple-benchmark [obj (js-obj "a" 1 "b" 2) f js-keys] (f obj) 400000) +(simple-benchmark [obj (js-obj "a" 1 "b" 2 "c" 3 "d" 4 "e" 5 "f" 6) f js-keys] (f obj) 400000) + (println ";;; instance?") ;; WARNING: will get compiled away under advanced (simple-benchmark [coll []] (instance? PersistentVector coll) 1000000) @@ -60,8 +72,9 @@ (println ";;; vector ops") (simple-benchmark [] [] 1000000) -(simple-benchmark [] [1 2 3] 1000000) -(simple-benchmark [] (vector 1) 1000000) +(simple-benchmark [[a b c] (take 3 (repeatedly #(rand-int 10)))] (-count [a b c]) 1000000) +(simple-benchmark [[a b c] (take 3 (repeatedly #(rand-int 10)))] (-count (vec #js [a b c])) 1000000) +(simple-benchmark [[a b c] (take 3 (repeatedly #(rand-int 10)))] (-count (vector a b c)) 1000000) (simple-benchmark [coll [1 2 3]] (transient coll) 100000) (simple-benchmark [coll [1 2 3]] (nth coll 0) 1000000) (simple-benchmark [coll [1 2 3]] (-nth coll 0) 1000000) @@ -104,6 +117,25 @@ (persistent! v) (recur (inc i) (conj! v i)))))) +(println ";;; vector equality") +(simple-benchmark + [a (into [] (range 1000000)) + b (into [] (range 1000000))] + (= a b) 1) +(println) + +(println ";;; keyword compare") +(let [seed ["amelia" "olivia" "jessica" "emily" "lily" "ava" "isla" "sophie" "mia" "isabella" "evie" "poppy" "ruby" "grace" "sophia" "chloe" "freya" "isabelle" "ella" "charlotte" "scarlett" "daisy" "lola" "holly" "eva" "lucy" "millie" "phoebe" "layla" "maisie" "sienna" "alice" "florence" "lilly" "ellie" "erin" "elizabeth" "imogen" "summer" "molly" "hannah" "sofia" "abigail" "jasmine" "matilda" "megan" "rosie" "lexi" "lacey" "emma" "amelie" "maya" "gracie" "emilia" "georgia" "hollie" "evelyn" "eliza" "amber" "eleanor" "bella" "amy" "brooke" "leah" "esme" "harriet" "anna" "katie" "zara" "willow" "elsie" "annabelle" "bethany" "faith" "madison" "isabel" "rose" "julia" "martha" "maryam" "paige" "heidi" "maddison" "niamh" "skye" "aisha" "mollie" "ivy" "francesca" "darcey" "maria" "zoe" "keira" "sarah" "tilly" "isobel" "violet" "lydia" "sara" "caitlin"]] + (simple-benchmark + [arr (into-array (repeatedly 10000 #(keyword (rand-nth seed))))] + (.sort arr compare) + 100) + (simple-benchmark + [arr (into-array (repeatedly 10000 #(keyword (rand-nth seed) (rand-nth seed))))] + (.sort arr compare) + 100)) +(println) + (println ";;; reduce lazy-seqs, vectors, ranges") (simple-benchmark [coll (take 100000 (iterate inc 0))] (reduce + 0 coll) 1) (simple-benchmark [coll (range 1000000)] (reduce + 0 coll) 1) @@ -208,6 +240,7 @@ [:q 16] [:r 17] [:s 18] [:t 19] [:u 20] [:v 21] [:w 22] [:x 23] [:y 24] [:z 25] [:a0 26] [:b0 27] [:c0 28] [:d0 29] [:e0 30] [:f0 31]])) (simple-benchmark [key :f0] (hash key) 1000000) +(simple-benchmark [key "f0"] (m3-hash-unencoded-chars key) 1000000) (simple-benchmark [key :unsynchronized-mutable] (hash key) 1000000) (def hash-coll-test (loop [i 0 r []] @@ -215,9 +248,24 @@ (recur (inc i) (conj r (str "foo" i))) r))) (simple-benchmark [coll hash-coll-test] (hash-coll coll) 100) +(simple-benchmark [coll hash-coll-test] (hash-ordered-coll coll) 100) +(def hash-imap-test + (loop [i 0 r {}] + (if (< i 1000) + (recur (inc i) (conj r [(keyword (str "foo" i)) i])) + r))) +(def hash-imap-int-test + (loop [i 0 r {}] + (if (< i 1000) + (recur (inc i) (conj r [i i])) + r))) +(simple-benchmark [coll hash-imap-test] (hash-imap coll) 100) +(simple-benchmark [coll hash-imap-test] (hash-unordered-coll coll) 100) (simple-benchmark [coll pmap] (:f0 coll) 1000000) (simple-benchmark [coll pmap] (get coll :f0) 1000000) (simple-benchmark [coll pmap] (-lookup coll :f0 nil) 1000000) +(simple-benchmark [coll pmap] (-lookup ^not-native hash-imap-test :foo500 nil) 1000000) +(simple-benchmark [coll pmap] (-lookup ^not-native hash-imap-int-test 500 nil) 1000000) (simple-benchmark [coll pmap] (assoc coll :g0 32) 1000000) (simple-benchmark [coll pmap] (loop [i 0 m coll] @@ -255,7 +303,7 @@ (def strings (take 10 (iterate (fn [s] (str s "string")) "string"))) -(def big-str-data +(def big-str-data (pr-str {:nils (repeat 10 nil) :bools (concat (repeat 5 false) (repeat 5 true)) :ints (range 10000 10100) @@ -286,6 +334,26 @@ (simple-benchmark [r r] (last r) 1) (println) +(println ";; iterators") +(def ipmap (apply hash-map (range 2000))) + +(println ";; Sequence iterator") +(simple-benchmark [s (seq ipmap)] + (let [iter (seq-iter s)] + (loop [v nil] + (if (.hasNext iter) + (recur (.next iter)) + v))) + 1000) +(println ";; Direct iterator") +(simple-benchmark [] + (let [iter (-iterator ipmap)] + (loop [v nil] + (if (.hasNext iter) + (recur (.next iter)) + v))) + 1000) + (println ";;; comprehensions") (simple-benchmark [xs (range 512)] (last (for [x xs y xs] (+ x y))) 1) (simple-benchmark [xs (vec (range 512))] (last (for [x xs y xs] (+ x y))) 4) @@ -296,10 +364,88 @@ (println ";; reducers") (simple-benchmark [xs (into [] (range 1000000))] (r/reduce + (r/map inc (r/map inc (r/map inc xs)))) 1) +(println ";; transducers") +(simple-benchmark [xs (into [] (range 1000000))] (transduce (comp (map inc) (map inc) (map inc)) + 0 xs) 1) + +(println ";; primitive array reduce 1000000 many ops") +(simple-benchmark [xs (into-array (range 1000000))] + (-> xs (.map inc) (.map inc) (.map inc) (.reduce (fn [a b] (+ a b)) 0)) 1) + +(println ";; reduce range 1000000 many ops") +(simple-benchmark [xs (range 1000000)] (reduce + 0 (map inc (map inc (map inc xs)))) 1) + +(println ";; transduce range 1000000 many ops ") +(simple-benchmark [xs (range 1000000)] (transduce (comp (map inc) (map inc) (map inc)) + 0 xs) 1) + (println "\n") (println ";; multimethods") (defmulti simple-multi identity) (defmethod simple-multi :foo [x] x) -(simple-benchmark [] (simple-multi :foo) 100000) +(simple-benchmark [] (simple-multi :foo) 1000000) (println "\n") + + +(println ";; higher-order variadic function calls") +;; Deliberately frustrates static-fn optimization and macros +(simple-benchmark [f array] (f 1 2 3 4 5 6 7 8 9 0) 100000) +(simple-benchmark [f vector] (f 1 2 3 4 5 6 7 8 9 0) 100000) +(simple-benchmark [] (= 1 1 1 1 1 1 1 1 1 0) 100000) + +(println "\n") +(println ";; Destructuring a sequence") +(simple-benchmark [v (into [] (range 1000000))] + (loop [[x & xs] v] + (if-not (nil? xs) + (recur xs) + x)) + 10) + +(println "\n") +(println ";;; str") +(simple-benchmark [] (str 1) 1000000) +(simple-benchmark [] (str nil) 1000000) +(simple-benchmark [] (str "1") 1000000) +(simple-benchmark [] (str "1" "2") 1000000) +(simple-benchmark [] (str "1" "2" "3") 1000000) + +(println "\n") +(println ";;; clojure.string") +(simple-benchmark [s "a" f clojure.string/capitalize] (f s) 1000000) +(simple-benchmark [s "aBcDeF" f clojure.string/capitalize] (f s) 1000000) + +(println ";; printing of numbers and handling of ##Nan, ##Inf, ##-Inf") +(simple-benchmark [x true] (pr-str x) 1000000) +(simple-benchmark [x 10] (pr-str x) 1000000) +(simple-benchmark [x js/NaN] (pr-str x) 1000000) +(simple-benchmark [x js/Infinity] (pr-str x) 1000000) +(simple-benchmark [x js/-Infinity] (pr-str x) 1000000) +(simple-benchmark [x (js-obj)] (pr-str x) 1000000) + +(println "\n") +(println ";; cycle") +(simple-benchmark [] (doall (take 1000 (cycle [1 2 3]))) 1000) +(simple-benchmark [] (into [] (take 1000) (cycle [1 2 3])) 1000) +(simple-benchmark [] (reduce + (take 64 (cycle [1 2 3]))) 10000) +(simple-benchmark [] (transduce (take 64) + (cycle [1 2 3])) 10000) + +(println "\n") +(println ";; repeat") +(simple-benchmark [] (doall (take 1000 (repeat 1))) 1000) +(simple-benchmark [] (into [] (take 1000) (repeat 1)) 1000) +(simple-benchmark [] (doall (repeat 1000 1)) 1000) +(simple-benchmark [] (into [] (repeat 1000 1)) 1000) +(simple-benchmark [] (reduce + 0 (repeat 1000 1)) 1000) +(simple-benchmark [] (into [] (take 1000) (repeat 1)) 1000) +(simple-benchmark [] (reduce + (take 64 (repeat 1))) 10000) +(simple-benchmark [] (transduce (take 64) + (repeat 1)) 10000) +(simple-benchmark [] (reduce + (take 64 (repeat 48 1))) 10000) +(simple-benchmark [] (transduce (take 64) + (repeat 48 1)) 10000) + +(println "\n") +(println ";; iterate") +(simple-benchmark [] (doall (take 1000 (iterate inc 0))) 1000) +(simple-benchmark [] (into [] (take 1000) (iterate inc 0)) 1000) +(simple-benchmark [] (reduce + (take 64 (iterate inc 0))) 10000) +(simple-benchmark [] (transduce (take 64) + (iterate inc 0)) 10000) +(println) diff --git a/bin/cljsc b/bin/cljsc index 28f744f1aa..a1b46c23d0 100755 --- a/bin/cljsc +++ b/bin/cljsc @@ -7,8 +7,13 @@ if [ "$CLOJURESCRIPT_HOME" = "" ]; then CLOJURESCRIPT_HOME="`dirname $0`/.." fi +if ! test "$(ls -A "$CLOJURESCRIPT_HOME/lib" 2>/dev/null)"; then + >&2 echo lib/ folder is empty, have you run \`script/bootstrap\`? + exit 1 +fi + CLJSC_CP='' -for next in lib/*: src/clj: src/cljs: test/cljs; do +for next in lib/*: src/main/clojure: src/main/cljs: src/test/cljs: src/test/self; do CLJSC_CP="${CLJSC_CP}${CLOJURESCRIPT_HOME}/${next}" done @@ -17,5 +22,5 @@ then echo 'Usage: cljsc ' echo ' cljsc "{:optimizations :advanced}"' else - java -server -cp "$CLJSC_CP" clojure.main "$CLOJURESCRIPT_HOME/bin/cljsc.clj" "$@" + java -server -Xms2g -Xmx4g -cp "$CLJSC_CP" clojure.main "$CLOJURESCRIPT_HOME/bin/cljsc.clj" "$@" fi diff --git a/bin/cljsc.bat b/bin/cljsc.bat index 780b535b68..694f32587a 100644 --- a/bin/cljsc.bat +++ b/bin/cljsc.bat @@ -1,14 +1,13 @@ - @echo off setLocal EnableDelayedExpansion if "%CLOJURESCRIPT_HOME%" == "" set CLOJURESCRIPT_HOME=%~dp0..\ -set CLASSPATH=%CLOJURESCRIPT_HOME%src\clj;%CLOJURESCRIPT_HOME%src\cljs" -for /R "%CLOJURESCRIPT_HOME%\lib" %%a in (*.jar) do ( +set CLASSPATH=%CLOJURESCRIPT_HOME%src\main\clojure;%CLOJURESCRIPT_HOME%src\main\cljs;%CLOJURESCRIPT_HOME%src\test\cljs +for /R "%CLOJURESCRIPT_HOME%lib" %%a in (*.jar) do ( set CLASSPATH=!CLASSPATH!;%%a ) -set CLASSPATH=!CLASSPATH!" +set CLASSPATH=!CLASSPATH! if (%1) == () ( echo Usage: "cljsc > out.js" diff --git a/bin/cljsc.clj b/bin/cljsc.clj index 1e376323b7..3ae8a78b0d 100644 --- a/bin/cljsc.clj +++ b/bin/cljsc.clj @@ -14,7 +14,9 @@ opts-string (apply str (interpose " " (rest args))) options (when (> (count opts-string) 1) (try (read-string opts-string) - (catch Exception e (println e))))] + (catch Exception e + (binding [*out* *err*] + (println "Failed to parse command line args:" e)))))] {:source source :options (merge {:output-to :print} options)})) (let [args (transform-cl-args *command-line-args*)] diff --git a/build.edn b/build.edn new file mode 100644 index 0000000000..c54f2ec135 --- /dev/null +++ b/build.edn @@ -0,0 +1,25 @@ +{:optimizations :advanced + :output-to "builds/out-adv/core-advanced-test.js" + :main test-runner + :output-wrapper true + :verbose true + :compiler-stats true + :parallel-build true + :output-dir "builds/out-adv" + :npm-deps {:lodash "4.17.4"} + :closure-warnings {:non-standard-jsdoc :off :global-this :off} + :install-deps true + :language-in :es6 + :language-out :es5 + :foreign-libs [{:file "src/test/cljs/calculator_global.js" + :provides ["calculator"] + :global-exports {calculator Calculator}} + {:file "src/test/cljs/es6_dep.js" + :module-type :es6 + :provides ["es6_calc"]} + {:file "src/test/cljs/calculator.js" + :module-type :commonjs + :provides ["calculator"]} + {:file "src/test/cljs/es6_default_hello.js" + :provides ["es6_default_hello"] + :module-type :es6}]} \ No newline at end of file diff --git a/changes.md b/changes.md new file mode 100644 index 0000000000..54c63d8051 --- /dev/null +++ b/changes.md @@ -0,0 +1,2357 @@ +## 1.12.134 + +### Changes +* Be less specific about the behavior of integer coercion fns +* Provide `cljs.proxy/proxy` default +* `cljs.proxy/builder`, `cache-fn` parameterization + +### Fixes +* `cljs.proxy` doesn't handle `for .. of` correctly +* Docstrings for `:lite-mode` support fns +* CLJS-3466: support qualified method in return position +* CLJS-3464: `parents` does not walk JavaScript prototype chain +* CLJS-3456: bootstrap wasn't updated for cljs.compiler/emit-global-export change +* CLJS-3463: rename all the lite mode data structures / fns to avoid clashing + +## 1.12.116 + +### Changes +* CLJS-3233: `:refer-global` + `:only`, `:require-global` +* CLJS-3451: make munge-str public +* various small DCE enhancements +* browser REPL reuses same window + +### Enhancements +* Clojure method values syntax support +* `cljs.proxy`, experimental namespace for efficient interop +* CLJS-2471: ChunkedSeq should implemented ICounted +* CLJS-3452: optimize str by compiling to + / .toString + compile time optimizations +* `:lite-mode` and `:elide-to-string`, new experimental compiler flags for smaller artifacts +* CLJS-3439: REPL doc support for externs + +### Fixes +* Fix REPL load-file issue +* CLJS-3425: Incorrect handling of ##NaN with min/max +* CLJS-3461: don't hard-code destructuring to PAM +* CLJS-3454: New set instances are created when redundant data is added +* CLJS-3438: Inference for `goog.object/containsKey` returns any, not boolean + +## 1.12.42 + +### Changes +* Update Google Closure Compiler dependency to v20250402 +* Depend on Clojure fork of Google Closure Library, 0.0-20250515-f04e4c0e + - restores goog.dom.query + - restores goog.isString and other simple fns to goog.base that were unnecessarily removed + - restore debug loader as default + - remove unused Closure directive `unusedPrivateMembers` +* CLJS-3290: implement IHash for js Symbol (#225) +* Updated vendorized tools.reader to 1.4.2 +* CLJS-3419: JS Map & Set should return true for seqable? +* CLJS-3421: Throw when calling ana-api/ns-publics on non-existing ns + +### Fixes +* CLJS-3242: trailing keys bug +* CLJS-2292: refer-clojure rename should also exclude +* CLJS-3418: Some Closure libraries are not lowered +* CLJS-3413: Macros not loaded w/ single segment namespace loaded via `:preloads` +* CLJS-3411: cljs.core/test behavior does not match docstring (#226) +* CLJS-3320: Compiler warning on trying to use `js` as an ns +* remove unnecessary key-check for HashCollisionNode +* CLJS-3429: Handle More Complex Closure Type Annotations + +## 1.11.132 + +### Fixes +* CLJS-3410: JavaScript double values should not hash to the same result +* CLJS-3381: Invokable JS namespaces used as constructors not hinted properly +* CLJS-3395: `(set! a -x false)` doesn't work +* CLJS-3399: :as-alias does not work with symbols +* CLJS-3401: dedupe '+ and '_PLUS symbols with :optimize-constants +* CLJS-3400: macroexpand does not expand and and or without args correctly +* CLJS-3398: Docstring of with-redefs should mention usage of ^:dynamic in production +* CLJS-3386: defn varargs argument should be nil when no varargs are passed +* CLJS-3384: get-in should not unreduce values. + +### Changes +* CLJS-3378: Change default :language-in to :ecmascript-next +* CLJS-3385: Extend empty? to counted? colls that arent seqable, such as transients +* CLJS-3327 Add :node-modules-dirs configuration +* CLJS-3391: add cljs.test/run-test +* CLJS-3369: Speed up random-uuid by generating 4 digits at a time +* CLJS-3014: Promote Error->map to be a core fn +* CLJS-3394: Add ECMASCRIPT options for 2018-2021 +* CLJS-2268: Make clojure.string/escape consistent with Clojure +* Bump closure lib to 2023 release +* CLJS-3407: bump tools.reader to 1.3.7 +* remove EXPERIMENTAL from ES6 iterator support +* CLJS-3406 implement reset-vals! and swap-vals! through protocol +* CLJS-3363: reduce-kv on seq of map entries +* CLJS-3393: Efficient drop, partition for persistent/algo colls +* CLJS-3408: Handle @extends in externs +* CLJS-3392: datafy support for js/Error and ExceptionInfo +* CLJS-3379: Add support for node_modules with .cjs extension +* CLJS-3387: Browser repl unable to serve wasm files + +## 1.11.60 + +### Fixes +* broaden scope of UUID equiv to implementers of protocol rather than concrete type +* CLJS-3382: Fix regression in .apply after CLJS-3024 + +## 1.11.57 + +### Fixes +* CLJS-3377: Objects created from required JS constructor are not correctly hinted +* get-bridged-alias-map is not needed in self-hosted + +## 1.11.54 + +### Changes +* use `require` instead of `load` for `cljs.vendor.bridge`, addresses issue + reported by Bruce Hauman wrt. Figwheel + +## 1.11.51 + +### Changes +* CLJS-3372: Vendorize data.json, transit-clj, and tools.reader + data.json and transit-clj are no longer dependencies. CLJS-3375 bridges + tools.reader for backwards compatibility +* Clojure 1.10 minimum version +* Update Google Closure Compiler, transit-java, tools.reader dependencies to latest +* CLJS-2820 Compile cljs.loader regardless of whether :modules are used +* CLJS-3370: improved uuid regex to only accept hex characters +* Update / clarify docstrings, CLJS-3358, CLJS-3359, CLJS-3360, CLJS-3361, CLJS-3364 +* CLJS-3354: map-invert should use transients and reduce-kv instead of reduce +* CLJS-3350: Update test.check dependency +* CLJS-3294: data_readers.cljc doesn't provide a way to have target-specific + behaviour + +### Fixes +* CLJS-3373: Externs Inference issue with vars invoked from foreign libs +* CLJS-3368: let binding can shadow globals, leading to strange behavior +* CLJS-3367: Backward conflict test in prefer-method causes incorrect exception +* CLJS-3371: Invalid warning on record constructor +* Fix apply of IFn for more than 20 arguments +* CLJS-3288: selfhost: *eval-fn* not bound for :js sources +* CLJS-3362: some-fn has different short-circuiting when using 3 predicates +* CLJS-3356: halt-when not usable within #'c.c/into +* CLJS-3352: Self-host negative zero emitted as positive zero +* CLJS-3319: make PersistentHashMap release inodes correctly + +### Enhancements +* CLJS-3348: Implement new functions for parity with Clojure 1.11 +* CLJS-3353: Add the new iteration function introduced in Clojure 1.11 +* CLJS-3347: Create clojure.math namespace +* CLJS-3299: port CLJ-2603 +* CLJS-3346: :as-alias +* add update-vals & update-keys + +## 1.11.4 + +### Fixes +* CLJS-3345: package.json exports can be a dupe of main + +## 1.10.914 + +### Fixes +* CLJS-3339: cljs.core/hash should type hint call to js/isFinite +* CLJS-3333: defonce expansion non-hygienic with respect to core names +* CLJS-3334: exists? evaluates to true for cljs.user// +* CLJS-3341: Revert dependency issue in dep order patch + +### Changes +* CLJS-3332: Cannot require `@firebase/auth` +* CLJS-3335: CI, test-and-or-code-gen-pass fails on Windows +* CLJS-3440: CI, Compiler tests fail test-cljs-3235 regarding react-select +* CLJS-3342: Run CI compiler unit tests on Windows +* CLJS-3338: Missing CI test coverage +* CLJS-3343: Failing js module processing test on Windows + +## 1.10.896 + +### Fixes +* CLJS-3336: REGRESSION: Cannot require `goog` +* CLJS-3337: REPL, Regression for :reload +* Fix Windows path issue in cljs.externs + +## 1.10.891 + +### Changes +* Update Google Closure Compiler `v20210808` +* Update Google Closure Library `0.0-20211011-0726fdeb` +* CLJS-3330: Flag for legacy loading of goog.object & goog.array + +### Fixes +* CLJS-3324: hash-map behavior differs from Clojure +* CLJS-3056: runtime namespace load order is independent from ordering in ns macro :require form +* CLJS-3074: Resolve :warning-handlers compiler option when symbols +* CLJS-3317: PersistentVector invoke does not align with Clojure + +## 1.10.879 + +### Changes +* Revert CLJS-3276 - macros that expand to require + +### Fixes +* CLJS-3096 Add :exception true to prepl errors +* CLJS-3313: Protocol implementations via metadata: ClojureScript behaves differently from Clojure + +## 1.10.866 + +### Changes +* Google Closure v20210505 + +### Enhancements +* CLJS-3260: and/or optimization as compiler pass, fixes core.async go macro issue +* CLJS-3276: Support macros that expand to require statements + +### Fixes +* CLJS-3309: and/or opt bug - passes to remove dropped locals from anon fns, + missing :children keys on binding nodes +* CLJS-3300: cljs.loader regression +* CLJS-3293: Some npm packages fail to require +* CLJS-3306: subvecs must implement IAssociative -contains-key? +* CLJS-3307: Allow extending IAssociative -contains-key? to native +* CLJS-3305: defrecord must implement IAssociative -contains-key? +* CLJS-3303: checked arrays enabled in advanced +* CLJS-3304: Higher order checked arrays +* CLJS-3284: Use of private deftype by public function in another namespace when + inside an if causes warning +* CLJS-3282: document bundle target in CLI help +* CLJS-3283: Support -contains-key? protocol check in contains? +* CLJS-3296: Update conj docstring for missing arities +* CLJS-3298: visibility diagnostic group typo +* CLJS-3302: Fixup docstring for `default-dispatch-val` and `dispatch-fn` + +## 1.10.844 + +### Changes +* Google Closure Compiler v20210302, Google Closure Library +* Update to tools.reader 1.3.3 + +### Enhancements +* CLJS-3235: Support accessing a property of a library as a namespace itself + +### Fixes +* CLJS-3287: selfhost: eval does not catch and return errors +* CLJS-3263: Always print #inst year with 4 digits +* CLJS-3291: Incorrect #inst parsing with respect to Julian / Gregorian calendar transition +* CLJS-3281: CLI help for target missing comma +* CLJ-3279: Error when :npm-deps is boolean and :install-deps true +* CLJS-3275: compute upstream npm-deps when :npm-deps is not set +* CLJS-3273: preserve ns-name when processing an :ns* op +* CLJS-3200: reduce code generated by destructure macro for maps +* CLJS-3271: nth on range produces nonexistent values considering floating point +* CLJS-3261: Docstring for cljs.js/eval-str specifies incorrect default for :context +* CLJS-2959: sort and sort-by should retain meta +* CLJS-3255: cljs.build.api/build doesn't work with single arity / 2-arity with nil +* CLJS-3019: Error->map should produce qualified symbols for :type +* CLJS-3130: UUID compares equal to other values +* CLJS-3257: `satisfies?` produces an inference warning when given an unhinted argument +* add `:nodejs-rt` to the list of build affecting options +* CLJS-2880: cl-format octal and Unicode character directives fail + +## 1.10.764 + +### Fixes +* Export Google Closure Library config directly to window in brwoser +* CLI: If :main supplied to -c pass it along to -r +* Revert CLJS-2582 + +## 1.10.758 + +### Changes +* More useful functions added to cljs.analyzer.api + +### Fixes +* CLJS-3242: Code Splitting Breakage +* CLJS-3244: Warnings for clojure.browser.net with :static-fns true +* Fix foreign-lib loading, was checking for `:nodejs` instead of `:nodejs-rt` +* CLJS-3239: Infinite analysis with dotted symbols +* CLJS-3238: Watch incompatible with :bundle, throws :nodejs-related exception +* CLJS-3237: compiling with --target node errors at runtime with document undefined +* CLJS-3236: defprotocol externs inference warnings +* Fix (require ... :reload) REPL pattern + +## 1.10.741 + +### Changes +* Removed REPL/target support for Rhino, Nashorn, Graaljs +* Update Closure Compiler and Google Closure Compiler dependencies +* CLJS-1628: Make instances of js/Symbol printable + +### Enhancements +* Add :target :bundle for integration with JavaScript bundlers (webpack, metro, etc.) +* Add cljs.main --install-deps flag +* Add :bundle-cmd compiler option for triggering JS bundler at end of build +* Add :nodejs-rt compiler option to diable Node.js runtime support (for bundles) +* Add :target-fn compiler option to allow users to support other targets outside of ClojureScript +* Add :npm-cmd to allow users to choose `npm` or `yarn` for their dep tool +* Make fast REPL prompt availble to 3rd party REPLs +* Transpile GCL libraries that need it +* Enhance cljs.main to be properly extensible +* repl-env providing namespaces can now be arbitrary not limited to cljs.repl.* +* CLJS-3185: Facilitate protocol static dispatch inlining +* CLJS-3199: Interop with JavaScript's iterable objects via Iterator protocol + +### Fixes +* CLJS-3230: seq on empty Iterable produces an empty seq +* CLJS-2908: Don't show quick prompt if :verbose or :repl-verbose +* CLJS-2898: cljs.main: ClojureScript version should not be displayed if there are inits +* CLJS-2863: Need to reject incorrect fn with fixed arity of more params than variadic +* CLJS-3086: Switch clj-nil to not be considered a numeric type +* CLJS-3211: Widen cljs.core/defprotocol sig arguments from list to seq (to include Cons) +* CLJS-712 & CLJS-2957: resolve-var for dot still wrong, externs inference regression +* CLJS-2862: Externs inference warning when extending Object +* CLJS-3161: Include :property for :warning-type :target +* CLJS-3181: Externs inference fails, even when manual type hint is provided +* CLJS-3224: Fix cljs.loader due to usage of removed setModuleUris API +* CLJS-3223: get-js-index 0-arity should call get-js-index 1-arity +* CLJS-3220: Self-host test-import failing on asserts.assert +* CLJS-3218: NPE during Closure transpilation in self-host tests +* CLJS-3217: script/test-self-parity compilation failure stale reference to goog.text.LoremIpsum +* CLJS-3215: Travis has remnant CLI test involving GraalJS +* CLJS-3219: Problem with asserts namespace inside goog.math.Long +* CLJS-3119: get with negative ndx on string inconsistent with Clojure +* CLJS-3214: script/uberjar fails with GraalJS +* CLJS-3210: Single arity arithmetic ops don't warn on bad arguments +* CLJS-3213: Browser REPL server hang when receive unknown POST +* CLJS-3209: With clojurescript 1.10.597 HelloWorld compiled with to 94K of JS with advanced optimizations turned on +* CLJS-3191: Optimize cljs.core/re-find +* CLJS-3192: Optimize cljs.core/re-matches +* CLJS-3193: Optimize cljs.core/re-pattern +* CLJS-3202: Make :/ keyword hash consistent with = +* CLJS-3203: Overriding root path in s/cat using s/gen gives an error + +## 1.10.597 + +### Changes +* CLJS-3120: Add :sigs to protocol var for compatibility with Clojure +* CLJS-2247: Warn when overwriting protocol method +* CLJS-3085: Types are inferred for dynamic vars +* CLJS-3097: Fix compatibility with tools.reader 1.3.1 and bump it +* CLJS-2750: tag coll in ci-reduce as not-native +* CLJS-3095: `apply vector` with array acts as `vec` +* CLJS-3093: Check subvec arguments +* CLJS-2868: Add ^string hints +* CLJS-3054: Align behavior of set/union and into with Clojure + +### Enhancements +* CLJS-3077: Avoid generating unnecessary functions +* CLJS-3107: Eliminate checked ifs in TransientArrayMap +* CLJS-3164: Optimize assoc on IAssociative values +* CLJS-3147: Allow Node require from foreign lib +* CLJS-3144: NPM Modules should have all their vars marked to avoid .call invokes +* CLJS-3145: Node.js support libs cljs.nodejs and cljs.nodejscli generate random files +* CLJS-3141: Improve perf of cljs.source-map.base64/encode +* CLJS-3134: Thread predicate-induced inference through and +* CLJS-3123: analyze google closure namespaces +* CLJS-3133: simple-* / qualified-* predicate-induced inference +* CLJS-2886: count specializations for string and array +* CLJS-2950: Direct field access for keyword lookup on records + +### Fixes +* CLJS-3190: Double arity warning constructing directly-accessed record +* CLJS-3137: fspec cannot be reused in clojurescript but can be in clojure +* CLJS-3124: Non-number lookup on transient vector succeeds after persistent! +* CLJS-3149: REPL load-file doesn't resolve npm requires correctly +* CLJS-3163: Skip analyzing specials in type-check-induced-tag +* CLJS-3172: Unable to import goog.async.ConditionalDelay +* CLJS-3158: Improperly widened cross-param loop/recur inference +* CLJS-3168: Self-host: externs ns used unconditionally in analyzer +* CLJS-3140: Not inferring on implements? +* CLJS-3143: assoc docstring regarding growing vector +* CLJS-3123: 'for' loop silently ignores extra forms in body +* CLJS-3017: Error->map: Map js/InternalError and js/TypeError +* CLJS-2683: Suppress compiler options in watch log +* CLJS-2881: cl-format character directive with \space fails +* CLJS-2879: Close analysis cache file +* CLJS-3051: Update to Graal RC12 in CI +* CLJS-3088: Update CI to use JavaScriptCore 4 +* CLJS-3092: Peek on subvecs doesn't behave properly +* CLJS-3076: let defined variadic functions not destructuring as expected with :static-fns true +* CLJS-3067: Fix compiler crash when requiring cljs.loader w/o modules +* CLJS-3068: Compiler error with if and emit-var +* CLJS-2301: Avoid use of deprecated goog.string/isEmptySafe in clojure.string/blank? +* CLJS-3058: Remove dangling goog.date.relativeWithPlurals reference +* CLJS-3061 Fix docstring for chunked-seq? + +## 1.10.520 + +### Changes +* Browser REPL serves more mime types + +### Fixes +* CLJS-3048: Revert CLJS-3038 +* CLJS-3049: Undefined fdef is still present in result of (stest/checkable-syms) + +## 1.10.516 + +### Changes +* CLJS-3036: Provide a clojure.edn namespace for Clojure compatibility +* CLJS-2967: Make clojure.spec.alpha reloadable +* CLJS-2968: Support immutable GCC DependencyOptions +* CLJS-2693: Have Range implement IChunkedSeq +* CLJS-2971: Make cljs.spec.alpha/fn-sym private +* CLJS-2912: Reuse seq in some + +### Enhancements +* CLJS-2865: Optimize string expression concatenation +* CLJS-2866: Predicate-induced type inference +* CLJS-2901: Return type inference for multi-arity & variadic fns + +### Fixes +* CLJS-3043: error__GT_str not defined for cli scripts +* CLJS-3037: Self-host: Add datafy tests to self-parity tests +* CLJS-3031: loop / recur inference, warnings not suppressed on initial pass +* CLJS-3030: Regression with core.async surrounding select-keys / find on String +* CLJS-3038: Improve error message when clojure.test.check is not required +* CLJS-3034: Truthy-induced inference +* CLJS-3023: Instrumenting next gives maximum call stack size exceeded +* CLJS-3033: Maintain backward compatibility test.check keyword +* CLJS-2964: Requiring spec.test.alpha loads clojure.test.check +* CLJS-2103: clarify arg type and order constraints of keys and vals +* CLJS-3011: Port improved runtime exception printing to non-Node REPLs +* CLJS-2981: Mishandling of :npm-deps Boolean value computing upstream deps +* CLJS-3027: sorted-map can no longer be returned by a macro unless it has keyword keys +* CLJS-3028: atom docstring typo +* CLJS-2994 Ensure all prepl :vals are pr-str-ed +* CLJS-3020: cljs.main: Incorrect documentation for the --compile flag +* CLJS-2652: Line breaks in long options for compile +* CLJS-3025: Typo when updating cljs.analyzer.macros/wrapping-errors +* CLJS-2955: Self-host: spec check macro compile-time expansion +* CLJS-2999: Update datafy to use inherent support for protocols via metadata +* CLJS-2945: Print spec failure details +* CLJS-3010: Datafy does not properly check for whether the datafied value supports metadata +* CLJS-3008: Typo in error phase key placed in exception and misplaced cause +* CLJS-2956: Stack overflow when specing core = +* CLJS-2913: improvements to exception messages and printing +* CLJS-3005: empty on Cons shouldn't keep metadata +* CLJS-2958 - make symbol work on keywords and vars +* CLJS-3000: Don't pass meta to next/rest/empty of seqs +* Add support for protocols via metadata +* CLJS-3000: Don't pass meta to next/rest/empty of seqs +* CLJS-1888 - Seqs of PHMs and PAMs do not handle metadata correctly +* CLJS-2794 :Return identity when with-meta is called with identical meta +* CLJS-2980: Calling "check-fn" gives "is not public" warning +* CLJS-2977: Spec instrumentation regression with varargs / :static-fns +* CLJS-2929: Port datafy +* CLJS-2995: Instrumented self-calling multi-arity fn throws maximum call stack exceeded with optimizations advanced +* Fix source maps missing local binding names +* CLJS-2991: Need to wrap js-obj output with parens +* CLJS-2976: s/fdef docstring should refer to cljs.spec.test.alpha/check +* CLJS-2538: nth on fractional indices near array and string bounds +* CLJS-2909: clojure.walk/postwalk does not preserve MapEntry type objects +* CLJS-2537: Negative fractional index in contains? on array +* CLJS-2933: Consistent #object printing whitespace +* CLJS-2873: Improved inference for loop / recur +* CLJS-2989: Fast-path issues for predicate-induced inference based on satisfies? +* CLJS-2867: Inferred return type of namespace is string +* CLJS-2975: unstrument returns symbol of non-instrumented var +* CLJS-2974: empty for non-emptyable should return nil +* CLJS-2825: Eliminate unnecessary ^boolean annotations +* CLJS-2979: re-seq is relying on undefined behavior of subs +* remove redundant exists? check in dynaload +* fix incorrect cljs.core.MapEntry usage + +## 1.10.439 + +### Changes +* CLJS-2904: Default :npm-deps to false +* CLJS-2878: Update Closure Compiler to v20180805 +* CLJS-2827: Avoid var special in core macros for private var access +* CLJS-2819: Warn on non-dynamic earmuffed vars +* CLJS-2806: Bump test.check to 0.10.0-alpha3 +* CLJS-2815: Support string keys in :global-exports +* CLJS-2812: Support for overriding object printing +* CLJS-2805: Bump tools.reader to 1.3.0 +* CLJS-1702: Warning when using private vars +* Align ClojureScript AST to tools.analyzer + +### Enhancements +* CLJS-2903: Support fingerprinting +* CLJS-2897: cljs.main: Display initial REPL prompt sooner +* CLJS-2884: Support for GraalJS RC6 +* CLJS-2859: Graal.JS: Enable high-res timers by default, allow user-configuration +* CLJS-2831: Add a graaljs REPL environment +* CLJS-1997: Outward function type hint propagation +* CLJS-844: Optimize js->clj by switching to transients +* CLJS-2442: `set` and `vec` performance enhancements + +### Fixes +* CLJS-2953: stest/with-instrument-disabled prints warning of private use +* CLJS-2728: Ability to disable macro spec checks +* CLJS-2843: s/explain of evaluated predicate yields :s/unknown +* CLJS-2951: Add a spec generator for some? +* CLJS-2940: Can't define nilable spec on undefined pred +* CLJS-2948: Stack overflow calling instrumented variadic fn with zero args +* CLJS-2793: Instrumenting breaks function with varargs +* CLJS-2934: Enhanced delay printing +* CLJS-2864: Optimize str macro for single arity case +* CLJS-1297: defrecord does not emit IKVReduce protocol +* CLJS-2937: docstring for to-array +* CLJS-2943: Update merge-with to use key / val +* CLJS-2941: seqable? should return true for nil +* CLJS-2915: Tests fail if directory has a period (.) in the path +* CLJS-2782: lein test fails if directory has hyphens +* CLJS-2911: Avoid infinite loop on infinite partitions +* CLJS-2906: cljs.main: Crash when with default REPL +* CLJS-2883: Instrumentation fails compilation with a large number of spec'd functions +* CLJS-2896: Allow parallel analysis and compilation +* CLJS-2893: seq: use .-length instead of alength for strings +* CLJS-2890: fspec role in problem path is not useful +* CLJS-2887: Improve names in core macro specs +* CLJS-2891: stop including data in ex-info message +* CLJS-2888: Printing of spec problems buries the failing predicate which should be more prominent +* CLJS-2861: Self-host: :checked-arrays not working +* CLJS-2852: Clojure imparity: ns-publics returns different arglists for macros +* CLJS-2725: Doc on spec keywords +* CLJS-2665: Port clojure.spec.test.alpha/enumerate-namespace +* CLJS-2848: Default explain printer prints root val and spec +* CLJS-2846: [spec] s/tuple explain-data :pred problem +* CLJS-2847: s/coll-of and s/every gen is very slow if :kind specified without :into +* CLJS-2841: [spec] instrument exception doesn't contain function name in ex-data +* CLJS-2842: [spec] Clarify s/every docstring for :kind +* CLJS-2845: [spec] generate random subsets of or'd required keys in map specs +* CLJS-2844: [spec] Add support for undefining a spec +* CLJS-2840: [spec] s/keys explain-data :pred problem +* CLJS-2839: [spec] s/& explain-data :pred problem +* CLJS-2838: [spec] s/& does not check preds if regex matches empty collection +* CLJS-2837: [spec] `cat` specs should verify value is sequential +* CLJS-2541: binding not made in parallel +* CLJS-2832: Bad code gen for `((not empty?) "foo")` when compiled with no optimizations +* CLJS-2855: Browser REPL prints empty string after require +* CLJS-2821: Update doto docstring to not use Java example +* CLJS-2817: Suppress private var warnings for specs on private vars +* CLJS-2822: cljs.core.specs.alpha: Map bindings should be `:kind map?` +* CLJS-2829: Fix deep object property access for :global-exports +* CLJS-2816: Skip non-string package.json browser entry values +* CLJS-2814: Fix munge-node-lib/global-export on self-host +* CLJS-2811: cljs-1537-circular-deps fail on Windows +* CLJS-2807: Macroexpand failure with set literal +* CLJS-2799: Handle nth on seqables with negative indexes +* CLJS-2798: ChunkCons -next doesn't handle nil more +* CLJS-2589: allow / as a protocol method name in cljs + +## 1.10.339 + +### Changes +* Bump transit-clj to 0.8.309 + +## 1.10.329 + +### Changes +* add :val to :const node +* rename ast op :constant -> :const + +### Fixes +* CLJS-2787: Record comparison is broken when instance is constructed from another record instance via map factory +* CLJS-2780: Async tests prematurely terminate in Node +* CLJS-2783: with-out-str conflicts with :infer-externs +* CLJS-2730: Fix docstrings in filter, filtev, remove, and take-while +* CLJS-2703: module name substitution test fails if hypen in directory path +* CLJS-2731: Failure comparing sorted sets +* CLJS-2746: Missing provides for JS modules +* CLJS-2772: Trying to run `cljs.main` repl with `:modules` results in `brepl_deps.js` being `clojure.lang.LazySeq` +* CLJS-2736: Elements returned from sets as functions are not the actual elements in the set +* CLJS-2298: REPLs should automatically load user.(cljs|cljc) files at root of Java classpath + +## 1.10.312 + +### Enhancements +* CLJS-1871: A declare with :arglists should generate static function calls +* CLJS-2688 cljs.main: Accumulate all meaningful repeated inits modules using global-exports +* CLJS-2681: Accepting multiple paths to the --watch option for cljs.main +* CLJS-2706: Better error messages when missing namespaces contain dashes + +### Changes +* CLJS-2777: Bump Closure-compiler +* validate :main +* CLJS-2771: Elide "use strict"1 from final output + +### Fixes +* CLJS-2278 & CLJS-2279 +* goog.global lookup must be a string +* CLJS-2775: cljs.main: Node modules not installed if -re node +* CLJS-2767: Externs inference warnings for defrecord and deftype +* CLJS-2754: Broken cli tests +* CLJS-2769: Eliminate goog.structs.AvlTree.Node in self-parity test +* CLJS-2766: Revisions to exists? fails in self-host +* CLJS-2764: exists? is not nil safe +* CLJS-2760 Make browser repl web-severs mime-type case-insensitive +* CLJS-2755: Can't generate uri instances +* CLJS-1677: Requiring [goog] breaks an :advanced build, but the compiler exits successfully +* Recompile cljs.loader in REPL +* CLJS-2733: Throw error message if too few or too many args to throw +* CLJS-2751: script/bootstrap --closure-library-head misses goog/text +* CLJS-2480: Periods at end of analyzer warnings +* CLJS-2618 Fix docstring for `remove-tap` +* CLJS-2743 Fix docstring misspelling +* CLJS-2724: Native Node modules Node (like "fs") cannot be required +* CLJS-2702: Accomodate new Closure Library dependency loading strategy +* CLJS-2741: Function invoke errors report arity off by 1 +* CLJS-2745: Add GraalVM to the set of JavaScript engines we can test against +* CLJS-2739: Optimize node_modules indexing +* CLJS-2619: clojure.reflect needs exclude for macroexpand +* CLJS-2713: test-reader fails on Windows +* CLJS-2715: Have goog-define return the var at the REPL +* CLJS-2727: cljs.repl/err-out visible from cljs +* CLJS-2734: Add :arglists to defmulti +* CLJS-2721: test-cljs-2580 failing in windows CI +* CLJS-2726: test-cljs-2678-global-exports-infer failing on Windows +* CLJS-2678: Infer-externs doesn't work for JS modules using global-exports +* CLJS-2718: Setting *warn-on-infer* in REPL throws a SyntaxError +* CLJS-2385: cljs.analyzer/infer-type pass infers tag with incorrect priority +* CLJS-1918: case needs a type hint for keywords case when using *warn-on-infer* +* CLJS-1970: Cannot infer target type for list/vector expressions +* CLJS-2669: Use simple dummy package for test-cljs-2580 +* CLJS-2716: Add ChakraCore to Windows CI (AppVeyor) +* CLJS-2147: apply test suit +* CLJS-2711: System newline breaking some tests on Windows +* CLJS-2712: Make Windows CI fail if a test fails +* CLJS-2708: Windows. ClojureScript fails to compile when node.js module is `require`d + +## 1.10.238 + +### Enhancements +* cljs.main, simple command line access to Compiler & REPLs +* cljs.server.* namespaces for integration with -Dclojure.server.repl +* :aot-cache compiler to enable global AOT caching of dependencies in JARs +* :stable-names compiler flag, to support vendorization when using :modules, + defaults to true when using :modules. +* Add :webworker & :nashorn target +* pREPL implementation (usage requires Clojure 1.10.0-alpha) +* Add :package-json-resolution build option, allowing to choose which + package.json entries are being used; defaults to :webpack (if no :target + is set) or :nodejs (if the :target is :nodejs); also supports a custom + vector of entries (e.g. ["browser", "main"]). + +### Changes +* CLJS-2592: :npm-deps using ES6 modules with .mjs extensions are not detected correctly +* AOTed ClojureScript artifact is now the default, for sources only use the + "slim" Maven classifier +* Bump Closure Compiler +* REPL now show uniform prompts +* CLJS-2660: Add cljs.core/eval which, delegates to an overridable *eval* +* CLJS-2375: Remove AMD Module Support +* CLJS-2413: Port core.specs.alpha to ClojureScript +* CLJS-2423: Allow custom :output-wrapper function +* Map entries are no longer two element vectors, now MapEntry instances +* *print-fn* automatically set +* CLJS-2561: AOT compile browser REPL client js +* CLJS-2581: Create a cljs.repl/*repl-env* dynamic var and bind it around cljs repl loops + +### Fixes +* CLJS-2680: Passing :watch-fn via --compile-opts to cljs.main +* CLJS-2692: cljs.core.specs.alpha: Import list needs to require quote +* CLJS-2696: Large code size in Clojurescript 1.10.x for minimal code with optimizations advanced +* CLJS-2699: Use higher-level Closure API for module-processing +* CLJS-2691: goog.require in module-processed files shouldn't require goog.base +* CLJS-2689: Don't try to use node resolve for goog: modules +* CLJS-2676: Bad cljs.loader behavior for modules with multiple provides +* CLJS-2673: Regression: Can't require cljs.js +* CLJS-2650: Fix JAR compilation of cljs.loader +* CLJS-2671: Double analysis warning for source in JAR with AOT cache +* CLJS-2643: Socket REPL output can be directed to the wrong place +* CLJS-2670: Update cljs.compiler/warning-types +* CLJS-2491: Inference warnings are not reported +* CLJS-2653: REPL crash when mapping stacktrace in Chrome for js/blah +* CLJS-2639: Compiler crash when using aot cache with parallel compile +* CLJS-2520: Synthesize ClojureScript version if using non-built ClojureScript dep +* CLJS-2522: Handle sources that are maps in build-modules +* CLJS-2521: Only expand module graph when modules are actually used +* CLJS-2519: Module loader doesn't load :cljs-base properly +* CLJS-2493: Self host: respect :source-map-timestamp compiler option +* CLJS-2500: Call process-js-modules after compiler restart +* CLJS-2516 Build API fails targeting Node (QuickStart) +* CLJS-2462: subvec on non-integral indexes fails +* CLJS-2474: with-meta on lazy-seq causes separate realization +* CLJS-2501: Fix crash in cljs.util/compiled-by-version and build-options +* CLJS-2476: recur across try should fail compilation +* CLJS-2495: Closure compilation errors should stop Cljs compilation +* CLJS-2496 PHM seq and iter should return MapEntry on nil key case +* CLJS-2473: Infer character literals to have string type +* CLJS-2455: nth fails on eduction +* CLJS-2001: Add map-entry? predicate +* CLJS-2131: Calling empty on a ChunkedSeq should return empty list +* CLJS-1743: Transient maps should support IFn +* CLJS-2452: reverse empty vector returns nil +* CLJS-2450: Allow configuring ignored JS module extensions +* CLJS-2417: Inter-ns s/fdef expansion side effect fails when load cached source +* CLJS-2447: Ignore css JS modules +* CLJS-2397: Multi-arity function instrumentation fails with :static-fns true + CLJS-2197: Calling instrumented var fails to check conformance +* CLJS-2443: doseq should return nil with no collections +* CLJS-2430: Fix foreign-libs with Node target +* CLJS-2414: Self-host: Macro specs are instrumented +* CLJS-2387: CLJS Analyzer does not correctly detect cache hits for analyzed spec files +* CLJS-2405: Register dumped specs fails +* CLJS-2416: Self-host: defn macro Var doesn't have :macro true meta +* CLJS-2425: Remove unnecessary zero? checks from nat-int? +* CLJS-2377: The CLJS compiled uses deprecated modules on Java 9 +* Allow clj->js to preserve namespaces +* CLJS-2391: Unable to :stub a function using stest/instrument +* CLJS-2378: keep the :npm-deps-installed? to avoid to reinstall NPM deps + +## 1.9.946 + +### Changes +* CLJS-2300: Delegate clojure.string/capitalize to goog.string/capitalize +* CLJS-2374: Print js/Infinity, js/-Infinity, js/NaN using new reader literals +* bump tools.reader (1.1.0) +* CLJS-2372: update hash to use the new infinity literals +* CLJS-2364: Bump Closure Compiler to the Sep 2017 version +* CLJS-2340: Have js-keys delegate directly to good.object/getKeys +* CLJS-2338: Support renamePrefix{Namespace} closure compiler option + +### Fixes +* CLJS-1576: fix source-map string encoding by applying encodeURIComponent and fixing string/replace call +* CLJS-2294: Always use opts with implicit opts added +* CLJS-2166: Add uri? predicate +* CLJS-2368: Self-host: Never compile macro namespaces with `:optimize-constants true +* CLJS-2367: Self-host: :def-emits-var leaks into loaded namespace processing +* CLJS-2352: Emit valid JS for NaN etc. even when used w/ CLJ >= 1.9.0-alpha20 +* CLJS-2339: Significant code reload slowdown with :npm-deps +* CLJS-2361: Self-host: circular dependency detection doesn't handle REPL self-require +* CLJS-2356: Self-host: circular dependency detection is not correct +* CLJS-2354: Self-host: `compile-str` doesn't handle `clojure` -> `cljs` aliasing +* CLJS-2353: use portable `node-module-dep?` function in analyze-deps +* CLJS-2345: escape paths emitted as args to cljs.core.load_file +* CLJS-2349: Port reset-vals! and swap-vals! over from Clojure +* CLJS-2336: Call alength once in areduce and amap +* CLJS-2335: Avoid alength on strings +* CLJS-2334: Also gather dependencies from foreign-libs that are modules +* CLJS-2333: module-deps.js doesn't correctly compute `main` if aliased in browser field +* CLJS-2332: module_deps.js doesn't process `export from` correctly +* CLJS-2330: Don't set `"browser"` field for Closure if target is :nodejs +* CLJS-2326: Indexing node_modules can't find `main` when it doesn't have an extension +* CLJS-2328: Args are not provided to *main-cli-fn* with optimizations advanced +* CLJS-2327: module_deps.js doesn't know about browser field advanced usage + +## 1.9.908 + +### Enhancements +* CLJS-2323: data readers support for records + +### Changes +* CLJS-2322: Require only `@cljs-oss/module-deps` to be installed to figure out Node.js dep graph +* CLJS-2321: Do not automatically call `set-loaded!` on the user's behalf +* CLJS-2316: Upgrade Closure Compiler to August release +* CLJS-2317: Upgrade Google Closure Library +* CLJS-2234: Make build scripts optionally less verbose +* CLJS-2314: Eliminate str call on literal strings in str macro +* CLJS-2291: Set up Windows CI +* CLJS-2286: Simplify JS module processing + +### Fixes +* CLJS-2324: module-graph doesn't munge :requires when indexing inputs +* CLJS-2309: :module foreign-libs order not preserved +* CLJS-2318: module-deps.js doesn't respect the package.json `module` field +* CLJS-2312: Miss-compile: Uncaught SyntaxError: Unexpected token default +* CLJS-2315: module_deps.js can't resolve JSON modules +* CLJS-2313: :language-out is a build affecting option +* CLJS-2306: Provide better warning message when namespace can't be found +* CLJS-2303: Disable duplicate alias checking for self-host +* CLJS-2307: Closure warns on unreachable checked array code +* CLJS-2305 Tests: Unable to resolve symbol: opts in this context +* CLJS-2299: Failure with alias and bad require of clojure.spec +* CLJS-2302: Disable process-shim by default in Node.js targets +* CLJS-2266: Self-host: Cannot require clojure.x where clojure.x has no macros namespace +* CLJS-2304: Fix compiler infrastructure tests on Windows +* CLJS-2261: Issue using interop record constructors in macros namespaces +* CLJS-2296: Foreign libs that expose modules are not being processed under target nod +* CLJS-2293: Self-host: Can't load cljs.js owing to set alias +* CLJS-2295: `index-node-modules-dir` can't determine :main for package.json files that have `.` in the string +* CLJS-1620: In JavaScript ES2015 modules default export name is munged to default$ +* CLJS-2287: Self-host: `require` prints result of loading node deps / global exports +* CLJS-2290: Node packages using require('assert') fail compilation +* CLJS-2281: module_deps.js cannot compute inputs for ES6 sources +* CLJS-2284: Fix build API tests not to pollute `out` in the current directory +* CLJS-2282: Some valid keywords are strings in JS object literals +* CLJS-2283: Regression with js-obj and gobject alias + +## 1.9.854 + +### Enhancements +* CLJS-2280: Provide process.env :preload and auto-configure +* CLJS-2279: Infer `:module-type ` for provided `node_modules` +* CLJS-2250: Support :foreign-libs overrides via :provides +* CLJS-2243: Self-host: Add support for :global-exports +* CLJS-2232: Self-host: Add support for string-based requires +* add *print-fn-bodies* knob, set to false +* CLJS-2198: Safe array operations +* CLJS-2217: Support `:rename` for JS modules +* CLJS-2214: Support :global-exports for foreign libraries +* CLJS-1428: Add a cljs.core/*command-line-args* var +* CLJS-2061: Support ns :require for JS libs, allow strings along with symbol +* CLJS-2148: Add warnings for invalid use of aget and aset +* CLJS-2143: Add support for symbol preprocess values + +### Changes +* CLJS-2273: Bump tools.reader to 1.0.3 and development dependencies +* CLJS-2235: Allow passing extra maven opts to build scripts +* CLJS-2267: Allow ^:const inlined vars to affect if emission +* CLJS-2245: Add support for using a local `node_modules` installation through a new `:node-modules` compiler flag +* CLJS-2002: Don't throw when no *print-fn* is set +* support Clojure primitive array type hints, core.async no longer + gives warnings +* CLJS-2213: Node.js target should use node_modules index to emit platform specific require +* CLJS-2200: bump to tools.reader 1.0.2 +* CLJS-2135: require macro prints last result of loaded-libs +* CLJS-2192: Add ChakraCore testing facilities +* CLJS-1800: Defer to tools.reader for cljs.reader functionality +* CLJS-2163: Clean up uses of aget / aset on objects +* CLJS-2184: Add `ns-publics` and `ns-imports` +* CLJS-2183: Assert arguments are quoted symbols in some core macros +* CLJS-2182: Assert argument to resolve is a quoted symbol +* CLJS-2186: Update docstrings for aget/aset to be consistent with Clojure +* CLJS-2180: Allow compiling `:modules` with whitespace optimizations +* CLJS-1822: Use `:file-min` when processing JS modules with advanced optimizations +* CLJS-2169: Error when compiling with :source-map and advanced optimizations +* CLJS-2037: Throw if overwriting alias in current namespace +* CLJS-2160: Add loaded? and prefetch functions to cljs.loader +* CLJS-2148: Add unsafe-get and use goog.object +* CLJS-2161: Bump Closure Compiler to June 2017 release + +### Fixes +* CLJS-1854: Self-host: Reload ns with const +* CLJS-2278: JavaScript object literals are printed wth keys that cannot be read +* CLJS-2276: Self-host: Need test.check dep for CLJS-2275 +* CLJS-2275: cljs.spec.alpha/fdef resolves eagerly +* CLJS-2259: Extra .cljs_node_repl directory containing cljs.core output +* CLJS-2274: Update CI script to install deps +* CLJS-2269: Warn on top level code split loads +* CLJS-2272: Tests that depended on default install deps behavior failing +* CLJS-2255: Clean up :npm-deps +* CLJS-2263: Docstring for neg-int? backwards +* CLJS-2262: Correct comment that *warn-on-infer* is file-scope +* CLJS-2258: Stack overflow regression for sequence xform applied to eduction +* CLJS-2256: Generated code doesn't add newline after sourceMappingURL comment +* CLJS-2254: Module Indexing: Provide relative paths for a package's main module +* CLJS-2248: Build API tests rely on Yarn +* CLJS-2239: Self-host: Add `:target :nodejs` to the docstrings in cljs.js +* CLJS-2251: Follow-up fix to CLJS-2249 and related commit +* CLJS-2249: Provide a test for d4b871cce73 +* CLJS-2246: Revert CLJS-2245 and CLJS-2240 and fix `lein test` +* CLJS-2244: Orphaned processed JS modules breaks :modules +* CLJS-2242: Lots of undeclared Var warns in cljs.spec.gen.alpha +* CLJS-2241: Multiple requires of Node.js modules in non :nodejs target are not idempotent at the REPL +* CLJS-2229: Ensure that new modules work works correctly with REPLs +* CLJS-2238: Perf regression with node module indexing +* CLJS-2240: don't shell out to module_deps.js if `:npm-deps` not specified +* CLJS-2230: Double checked arrays +* CLJS-2227: Squelch some of the array access tests +* CLJS-2228: Port CLJS-2226 to module_deps.js +* CLJS-1955: data_readers.cljc can't reference handlers in user code +* CLJS-2225: Need to add :checked-arrays to known compiler opts +* CLJS-2226: :npm-deps can't index scoped packages +* CLJS-2224: Resolve-var is wrong wrt. module resolution +* CLJS-2223: Self-host: Undeclared Var deps/native-node-modules +* CLJS-2222: CI failing after CLJS-2217 +* CLJS-2219: Enable JSC under test-simple +* CLJS-2218: Make ClojureScript aware of native node modules +* CLJS-2220: Add runtime :npm-deps tests +* CLJS-2212: Replace missing-js-modules with new index-node-modules-dir +* CLJS-2211: Add function to index a top-level node_modules installation +* CLJS-2208: module_deps.js is not compatible with older JS implementations +* CLJS-2207: cljs.test/js-filename is using non-portable .endsWith +* CLJS-1764: Double warning for undeclared Var (REPL only) +* CLJS-2204: Tests failing with respect to lodash/array namespace +* CLJS-2205: NPM deps: Correctly compute `:provides` if file ends in `index.js` +* CLJS-2203: REPL is turning on all warnings by default (including :invalid-array-access) +* CLJS-2201: Self-host: test-js-filename failing +* CLJS-2202: String requires should work from Cljs files in classpath +* CLJS-2199: String requires broken after recompile +* CLJS-2172: memfn docstring refers to Java and reflection +* CLJS-1959: under :nodejs target we should provide __dirname and __filename constants +* CLJS-1966: cljs.test assumes the output directory is '/out/' when determining the filename for a failed or errored test result. +* CLJS-2191: Clean up doc references to clojure.spec.* in favor of cljs.spec.* +* CLJS-2194: cljs.util/relative-name bug +* CLJS-2195: npm-deps tests are not idempotent +* CLJS-2179: Add test for preprocess JS module as symbol +* CLJS-2152: "is not a relative path" exception thrown when `:libs` directory is provided. +* CLJS-2193: :npm-deps dependencies are implicit +* CLJS-1797: Update aot_core to support build with MINGW on Windows +* CLJS-2189: Add test for :preloads +* CLJS-2188: Use :invalid-array-access instead of :invalid-aget / :invalid-aset +* CLJS-2181: Can't compile string sources with modules +* CLJS-2185: Self-host: Docstrings for bootstrap helpers +* CLJS-2178: Add tests for `:npm-deps` +* CLJS-2177: NPM deps & JS modules fixes for Windows +* CLJS-2175: ES6 Module processing broken with Closure v20170626 +* CLJS-2175: Add test to check ES6 module processing works +* CLJS-2176: module_deps.js: fix regexes for Windows paths +* CLJS-2173: Fix `npm install` when `:npm-deps` in Windows +* CLJS-2164: Require cljs.js results in warning about new unsafe-get macro +* CLJS-1998: Printing an Object with a null prototype throws an error +* CLJS-2158: cljs_base module generates empty goog.require +* CLJS-2157: Automatically generate cljs.loader/set-loaded! call +* CLJS-2154: Provide compiler info & timing when compiling modules +* CLJS-2151: Rollback removal of dependency information for node targeted compilation +* CLJS-2141: Self-host: cljs.js is using undeclared symbol lib +* CLJS-2145: inode_find issue with hash-map +* CLJS-2142: Can't instrument a namespace containing constants + +## 1.9.671 + +### Fixes +* CLJS-2139: Undeclared var regression in fn bodies +* CLJS-2137: Missing INext on some sequences +* CLJS-2136: Clarify IFind contract to avoid double-lookups +* need to elide :c.a/analyzed in c.a/analyze-wrap-meta to avoid dumping unintended + with-meta expressions +* resolve returns improperly constructed Var +* fix :fn-invoke-direct edgecase around keywords + +## 1.9.660 + +### Changes +* CLJS-2134: Warn on variadic signatures in protocol method implementation + +### Fixes +* CLJS-2133: Invalid variadic IFn implementations now fail + +## 1.9.655 + +### Enhancements +* CLJS-2108: faster set equivalence +* CLJS-2099: Optimize apply by avoiding .apply +* CLJS-2046: Optimize expression in call position +* CLJS-1876: Faster reduce for PV, Subvec and ChunkedSeq +* CLJS-2080: Faster equiv-map +* CLJS-2066: Avoid analyzing named fn literal bodies twice +* CLJS-2065: Improve analyzer munge performance + +### Changes +* CLJS-2130: Self-host: Add `:fn-invoke-direct` to public API docstrings +* CLJS-2112: Iterator based reduce path +* CLJS-2100: to-array calls seq too often +* CLJS-2041: Compiler flag to drop Function.prototype.call invokes +* CLJS-2093: inline ^:const var values +* CLJS-2042: Variadic invoke calls array_seq inefficiently +* CLJS-2003 remove redundant calls to `str` in munge/demunge +* CLJS-1907: Improve error message from cljs.reader/read-string +* CLJS-1724: Include IIterable in fast-path-protocols +* CLJS-924: Better error message for mistaken use of 'def' +* CLJS-1599: UUIDs are not equal for upper/lower case strings +* NodeJS REPL accepts a :path opt to set NODE_PATH +* CLJS-1886: RangedIterator should only be created from cljs.core.PersistentVector instances +* CLJS-2068: MapEntry, RedNode and BlackNode are IComparable +* CLJS-2073: Don't flush for every emitted line +* CLJS-2089: Warn message wrong for recur to protocol with nil +* CLJS-2085: defrecord recur method head target object +* CLJS-1977: defrecord should use murmur hashing like Clojure +* CLJS-2076: modules should support wildcard namespaces +* CLJS-2078: add resolve macro + +### Fixes +* CLJS-2128: Fix regression in CLJS-1886 +* CLJS-2126: Add new compiler option :fn-invoke-direct to build-affecting options +* CLJS-2054: Private core names still result in "already declared" warnings +* CLJS-2125: Duplicate HOF invoke warnings if :static-fns true +* CLJS-2119: s/form for s/& is qualified with `clojure.spec.alpha` +* CLJS-2121: Self-host: Document string as valid name arg +* CLJS-2124: Self-host: Tests failing wth Could not find tag parser for :cljs.spec.alpha +* CLJS-2122: Self-host: Non-symbol ns names dumped into env +* CLJS-2117: Self-host: Port CLJS-1989 to self-hosted +* CLJS-1989: s/fdef expansion side effect fails when load cached source +* CLJS-2116: Need to handle un-namespaced symbol when evaluating `foo.core +* CLJS-2109: incorrect syntax-quote symbol resolution (resolve-symbol 'clojure.core) -> 'clojure/core +* CLJS-2113: nth function produces different results from clojure when using a negative index on a sequence +* CLJS-2115: Pass not-found in the native-satisfies? branch of nth +* CLJS-2111: Transit analysis caching broken for JSValue or regex +* CLJS-2101: Undeclared var in do chain of defs +* CLJS-2104: Const-replaced exprs do not emit js "return" +* CLJS-1992: declare after def should have no effect +* CLJS-1251: Missing semicolons when emitting deftype and defrecord mistaken use of 'def' +* CLJS-1685: Incorrectly lazy subvec when start param is nil +* CLJS-1641: Multi-arity defn copies arguments unnecessarily for all cases +* CLJS-2092: Redundant call to equiv-map in PAM.-equiv +* Check for compilation success, and lib folder +* CLJS-2030: Case with grouped keyword test emit result-expr multiple times +* CLJS-2094: Predicates unit tests constructs a uuid with nil +* CLJS-1891: UUID.toString can return non-string +* CLJS-2072: Eliminate reflection in cljs.js-deps/build-index +* CLJS-2012: Find on PHM with nil entry always returns nil entry +* CLJS-2057: fix language-in options (es6 deprecated and add missing es2016) +* CLJS-2060: Backport CLJ-2141 Return only true/false from qualified-* predicates +* CLJS-2091: reify docstring ISeqable example needs correction +* CLJS-2088: fix caching collision between macros ns and regular ns in boostrap +* CLJS-2036: Relative path exception thrown when :preloads requires a :foreign-lib +* CLJS-2083: Test equiv-map for maps which do not impl IKVReduce +* CLJS-2081: Self-host: Regression with CLJS-2079 +* CLJS-2079: Records and maps are not equal +* CLJS-2075: PersistentTreeMap.reduce-kv does not honor reduced? +* Browser REPL regression +* CLJS-2069: Self-host: automatic `clojure` -> `cljs` aliasing doesn't work when loading macro namespaces +* CLJS-2067: reduce-kv / inode-kv-reduce fails to honor reduced? +* CLJS-2056: Self-host: test-self-parity failing wrt cljs.core/fn symbol + +## 1.9.562 + +### Enhancements +* CLJS-2027: Add language-in for ECMA 2017 and ECMA Next +* CLJS-2026: Add Compiler option for rewrite polyfills + +### Changes +* CLJS-2021: subvec throws when passed non-vector +* CLJS-1884: Give a chance to MetaFn to be removed by closure under :advanced + optimization Replace with-meta calls by -with-meta calls where possible +* CLJS-2052: Port new spec.alpha enhancements +* Update Google Closure Compiler dependency +* Update Google Closure Library dependency + +### Fixes +* CLJS-2053: Regression: cljs.spec.alpha/any for fdef +* CLJS-2039: remove extraneous argument from ChunkBuffer.chunk +* Fix assumption that all closure-compliant JS is goog.* +* CLJS-2035: Self-host: Add map-entry-test to self-parity +* CLJS-2033: set-validator! should check current state +* CLJS-2008: Self-host: backport fixes to threading macros +* CLJS-2005: Bad error message with duplicate arity function definitions +* CLJS-2032: Case macro expansion evaluates expression twice when no matching clause +* CLJS-2023: User supplied type hints stopped working on js/goog.DEBUG +* CLJS-2020: defmulti "miss" performance poor +* CLJS-2034: Sequence and Eduction produce infinite loop in transducer that appends to the reduction + +## 1.9.542 + +### Enhancements +* CLJS-1572: REPL doesn't give error for expressions with too many right parentheses + +### Changes +* cljs.spec -> cljs.spec.alpha +* CLJS-2013 - Add MapEntry type +* CLJS-2015: Self-host: `defmacro` should return the Var +* CLJS-2017: Upgrade Closure Compiler to latest April 2017 release + +### Fixes +* CLJS-485: RegExp flags are being dropped by string/replace +* CLJS-1518: Case macro expansion evaluates expression twice +* CLJS-2024: Self-host: `find-ns-obj` broken for namespaces with 'a' as the first segment +* CLJS-2028: `realized?` throws on LazyTransformer +* CLJS-2010: refer-clojure :rename throws on valid invocations +* CLJS-2007: Whitespace optimizations should respect :main option. + +## 1.9.521 + +### Fixes +* correct CLJS-1923 :foreign-libs regression + +## 1.9.518 + +### Enhancements +* CLJS-1973: Add support for `:npm-deps` in upstream `deps.cljs` +* CLJS-1968: Enable calling JS modules that export a single function +* CLJS-1960: Require CommonJS modules directly from a ClojureScript namespace + +### Changes +* CLJS-2006: Upgrade Closure Compiler to April 2017 release + +### Fixes +* CLJS-1497: `find` on an associative collection does not return collection key +* CLJS-1996: Support correct checking of :preloads when :optimizations not specified +* CLJS-1994: assoc on nil returns PHM (expected PAM) +* CLJS-1988: add :npm-deps to recognized compiler options +* Fix tiny bug in index-node-modules when no second argument is given +* CLJS-1985: `index-node-modules` should pass opts to `node-inputs` +* CLJS-1987: don't index node modules blindly +* CLJS-1519 Collection invoke errors report arity off by 1 +* CLJS-1964: Validate that `:target :nodejs` and no optimizations requires a `:main` option to be present +* CLJS-1956: Add missing JS reserved keywords +* CLJS-1983: res -> mres in spec.cljs +* CLJS-1978: port CLJ-2035 +* CLJS-1979: port CLJ-2043 (fix s/form of s/conformer) +* CLJS-1980: port CLJ-2100 (s/nilable form should retain original spec form) +* CLJS-1976: hash-map assoc stackoverflow +* CLJS-1957: Process JS modules errors and warnings don't get printed +* CLJS-1868 - Output simpler dependency rel paths when compiling with Closure libs +* CLJS-1967: Missing ^boolean for removed-leaf? in THM impl + +## 1.9.494 + +### Fixes +* revert CLJS-1636: Mark some symbols in core macros ns as private + +## 1.9.493 + +### Fixes +* CLJS-1948: Possible race condition in compiler w/ parallel-build true +* CLJS-1941: `cljs.compiler/cljs-files-in` shouldn't return `.cljc` files if a `.cljs` file exists for the namespace +* CLJS-1941: `cljs.compiler/cljs-files-in` shouldn't return `.cljc` files if a `.cljs` file exists for the namespace +* CLJS-1940: Undeclared var warning when invoking a protocol method on a `js` interop form +* CLJS-1951: Missing 0 and 1 arity versions of interleave +* CLJS-1952: Bump Closure Compiler to Feb 2017 release +* CLJS-1937: Self-host: undeclared cljs.core$macros/mod when compiling cljs/core.cljs +* CLJS-1936: cljs.analyzer declares vars which are only used in Clojure +* CLJS-1949: Self-host: cljs.compiler/munge doesn't preserve JVM compiler semantics +* CLJS-1950: Eliminate instances of #^ +* CLJS-1943: Self-host: `cljs.pprint`'s macros can't be compiled +* CLJS-1945: cljs.spec/every-impl kind-fn kind-form dead code +* CLJS-1944: Can't spec generate non-vector collections +* CLJS-1946: Self-hosted: don't emit `goog.require` calls for foreign libs if optimizations different than `:none` +* CLJS-1636: Mark some symbols in core macros ns as private +* CLJS-1939: Fix Node load_file call for foreign-deps +* CLJS-1942: Self-host: `cljs.env.macros` and `cljs.analyzer.macros` can't be loaded +* CLJS-1935: When calling cljs.spec/valid?, subsequent predicates of cljs.spec/and are evaluated even when early predicate is unsatisfied + +## 1.9.473 + +### Fixes +* CLJS-1931: Closure Compiler {{--generate_exports}} flag not supported +* CLJS-1934: Self-host: require-macros :reload / :reload-all fails +* CLJS-1932: Self-host: Perf regression macroexpand-check +* CLJS-1930: Master broken wrt static field: ES5_STRICT_UNCOMMON +* CLJS-1929: When expanding libs don't include Hidden files +* CLJS-1905: Self-host: Stacktraces for script/test-self-parity +* CLJS-1795: Support more options in the `:closure-warnings` compiler option +* CLJS-1922: Use :file as relative output path for foreign-libs +* CLJS-1831: Self-host: Improperly munge ns names +* CLJS-1925: Use of undeclared Var cljs.user/RegExp when extending protocol for RegExp +* CLJS-1920: cljs.build.api/node-inputs: package.json files are only added if module entries are top-leve +* CLJS-1916: __dirname and __filename are not defined when compiling for Node.js with optimizations :none +* CLJS-1915: cljs.test: Index out of bounds for stack element w/o line/column + +## 1.9.456 + +### Enhancements +* Enhanced JavaScript module support +* Support Node resolution for CommonJS modules +* Externs inference +* Performance enhancements +* CLJS-1835: REPL load special fn +* CLJS-1194: Support for `data_readers.cljc` + +### Changes +* expose :closure-module-roots option +* bump Closure Compiler dep +* Under Node.js don't need require entries in the goog.addDependency calls in cljs_deps.js +* do not throw on circular dependencies between Google Closure JS libs +* str macro should call str/1 function directly, added str benchmark +* CLJS-1718: Foreign lib files should be placed in a relative location +* CLJS-1858: Should allow `:cache-analysis true` and `cache-analysis-format nil` +* CLJS-1616: Self-host: improve documentation for compile-str +* CLJS-1643: Emit more informative error when emitting a type which has no emit multimethod case +* CLJS-1816: Basic timing info in verbose output +* add support for emitting inferred externs file +* add cljs.analyzer/analyze-form-seq +* CLJS-1666: Flag to optionally disable transit analysis cache encoding +* Provide more descriptive error message when invalid libspec detected +* CLJS-1768: cljs.spec perf tweaks +* CLJS-1842: Remove analyzer `:merge` hack for REPLs +* CLJS-1839: Relax the constraint that `new` and dot forms must be passed a symbol +* default to :ecmascript3 if :language-out not specified for :es6 module +* respect :language-out when processing ES6 modules +* default to :ecmascript3 if :language-out not specified for :es6 module +* inline some? + +### Fixes +* CLJS-1911: Need to bind Node.js require +* CLJS-1909: Self-host: circular dependency when requiring cljs.reader +* CLJS-1906: Self-host: script/test-self-parity fails +* CLJS-1903: Remove anonymous vars from dir and apropos output +* CLJS-1897: Too many externs generated +* CLJS-1895: Externs inference needs to support user supplied externs +* CLJS-1873: Self-host: Unit tests fail owing to test.check dep +* CLJS-1874: Self-host: :fn-var true for macros +* CLJS-1877: :foreign-libs entries should be allowed to specify directories along with individual files +* CLJS-1890: s/form for s/nilable in cljs.spec does not match clojure.spec +* CLJS-1811: Can't compose cljs.spec.test.instrument (or cljs.spec.test.check) with cljs.spec.test.enumerate-namespace +* CLJS-1894: Unnecessary analysis of core.cljs on first compile +* CLJS-1893: Unnecessary analysis of core.cljs +* CLJS-1892: Dependencies in JARs are analyzed every time even if an analysis cache file exists +* CLJS-1887: add :watch-error-fn option +* CLJS-1883 Foreign libs can't be found on Node.js +* CLJS-1882 Fix constant table sort order when using :modules +* CLJS-1853: var metadata in compiled output +* CLJS-1878: prefer `some?` over `(not (nil? %))` in analyzer +* CLJS-1880: missing ^boolean on some hasNext calls +* CLJS-1875 Difference in seqable? between CLJ & CLJS +* CLJS-1829: get does not return not-found on negative indexes +* cljs.spec.test/unstrument shouldn't return the names of vars that weren't instrumented in the first place. Fixes CLJS-1812 +* CLJS-1786: Add knob for controlling printing of namespaced maps +* CLJS-1836: nth doesn't throw for IndexedSeqs +* CLJS-1870: Quoted specs check in require macro symbols +* CLJS-1869: Regression importing goog.Uri +* Fix CLJS-1653 regression +* CLJS-1860: Resolve JS modules referred by their fully-qualified namespace +* CLJS-1861: Use usr/bin/env in build scripts for portability +* CLJS-1857: Fix self-host tests +* CLJS-1855: Subvec should implement IIterable +* CLJS-1856: Self-host: load-deps doesn't delegate to itself +* CLJS-1651: Self-host: Cannot replace core macro-function +* CLJS-1848: Analyzer can't find JS modules during macro-expansion +* CLJS-1851: Only output JS module processing time when `:compiler-stats` is true +* CLJS-1850: *unchecked-if* not declared ^:dynamic warning after commit a732f0 +* CLJS-1849: Self-host: regression introduced by CLJS-1794 +* CLJS-1844: port over Maria Geller's externs file parsing code +* CLJS-1845: Assoc on subvec should throw if out of bounds +* CLJS-1847: REPL should recognize `clojure.core/load` +* CLJS-1745: refer-clojure doesn't pull in previously excluded vars +* CLJS-1794: incomplete alias created for namespace cljs.spec warning under advanced compilation +* CLJS-1834: REPL regression, require of ns from the ns itself errors out in circular reference +* fix ns aliasing regression for JS namespaces +* CLJS-1837: Port halt-when over from Clojure +* CLJS-1820: "No such namespace" warning when referring to JS module namespace without using alias +* CLJS-1828: Add `:rename` to `require`'s docstring + +## 1.9.293 + +### Enhancements +* CLJS-1346: Support require outside of ns + +### Changes +* CLJS-1762: Bump Closure Compiler, refactor module support +* CLJS-1658: testing for protocol membership may return false positives +* CLJS-1536: REPL def symbol init collision +* CLJS-1805: Source map should take false +* CLJS-1804: Self-host: process namespace side-effects for new require without NS +* CLJS-1803: Use new require capability in REPLs +* CLJS-1796: Measure Google Closure specific optimization time +* CLJS-1782: Self-host: allow namespaces to require their own macros +* CLJS-1563: :source-map option to cljs.build.api/build should take nil +* CLJS-1785: Warn on reference to js/foo shadowed by local binding + +### Fixes +* make String an implicit ns like Math. revert char? and clarify docstring. add unit tests for char? +* fix cljs.spec.test/check docstring +* CLJS-1826: Self-host: load-deps doesn't honor `:reload` and `reload-all` +* CLJS-1825: :source-map error when passing `false` under simple optimizations +* CLJS-1821: `add-preloads` should only touch sources if `:preloads` option specified +* CLJS-1814: Move docstrings for require, etc. from `cljs.repl` to their new definitions in `cljs.core` +* CLJS-1809: Add 0/1 arity to `into` +* CLJS-1824: transit cache feature leaks files +* CLJS-1294: Let macroexpand(-1) accept any quoted argument. +* CLJS-1818: (hash false) returns different value from Clojure +* CLJS-1817: Strange result when assoc'ing 0 to persistent hash map +* CLJS-1815: Fix failing analyzer tests +* follow-up on CLJS-460 defmulti ignores optional :hierarchy argument +* CLJS-1807: Better error messages for `ns*` calls +* CLJS-1802: Generated namespaces should be of the form `cljs.user.fileXXXX` +* CLJ-1935: Use multimethod dispatch value method lookup to take hierarchies into account in multi-spec +* CLJS-1682 :foreign-libs with module conversion does not works properly if it is used form deps.cljs +* CLJS-1710: spec/double-in not implemented +* CLJS-1787: Make cljs.spec explain pluggable +* CLJS-1781: Add cljs.hash-map-test to self-parity tests +* CLJS-1788: Port CLJ-2004: include retag in multi-spec form +* CLJS-1765: Empty iterator for hash maps with nil key +* CLJS-1784: nth doesn't throw on strings or arrays +* CLJS-1773: Self-host: Don't resolve unqualified symbols / keywords with $macros +* CLJS-1770: goog-defines broken for integers +* CLJS-1600: Destructuring defprotocol fn args causes defrecord impls to silently fail +* CLJS-1335: resolve-macro-var: information missing for macros +* CLJS-1633: Improve error associated with invalid foreign-libs :file path +* CLJS-1775: `get` with `nil` returns as if `get` with `0` +* CLJS-1780: Records without extmaps fail to iterate +* CLJS-1774: Self-host: Report filenames in warns in test-self-parity +* CLJS-1779: keyword 2-arity constructor accepts anything for both parameters which leads to different hashing + +## 1.9.229 + +### Fixes +* CLJS-1772: Dependency index can incorrectly overwrite `.cljs` files with `.cljc` files if both are present +* pass unconform along on conformer with-gen instead of warning + +## 1.9.227 + +### Fixes +* CLJS-1763: Defining a var that clashes with `cljs.core` throws a compiler error instead of warning + +## 1.9.225 + +### Fixes +* CLJS-1759: Errors writing transit analysis cache if parallel build +* CLJS-1760: Self-host: test-cljs-1757 failing in test-self-parity +* CLJS-1751: port fix lost type hints in map destructuring +* CLJS-1756: Add test.check JAR to the bootstrap script +* CLJS-1757: cljs.spec/exercise-fn doesn't work when passed a quoted symbol +* CLJS-1754: Add boolean? generator +* fix REPL regression which removed warnings + +## 1.9.216 + +### Fixes +* CLJS-1749: Missing `cljs.spec.impl.gen/double*` +* CLJS-1747: Port `clojure.spec/assert` over to ClojureScript +* fix CLJS-1663 multi-arity fn instrument regression + +## 1.9.211 + +### Fixes +* CLJS-1746: Log the result of loading a dependency +* CLJS-1657: Self-host: Implicit macro loading with alias +* CLJS-1742: Add docstring for new refer-clojure REPL special +* CLJS-1274: Allow assignment to namespace-qualified names in current namespace +* CLJS-1744: rest produces nil for larger maps +* CLJS-1740: Self-host: Need to port more of CLJS-1733 +* CLJS-1741: Add :rename to :refer-clojure in ns docstring +* CLJS-1737: Self-host: clojure alias implicit macro use regression +* invalid cljs.spec/merge res call +* CLJS-1739: seq on map literal with 9 elements leads to rest producing nil +* CLJS-1738: Self-host: need to update call to check-use-macros-inferring-missing + +## 1.9.198 + +### Enhancements +* CLJS-1508: Extend ns form to support :rename option +* CLJS-1507: Implicit macro loading: macro var inference in :refer +* CLJS-1692: Autoalias clojure.* to exisiting cljs.* namespaces if +possible +* CLJS-1350: Compiler support for browser REPL +* CLJS-1729: Support `use` special function in REPLs +* CLJS-1730: Support `refer-clojure` special function in REPLs + +### Changes +* CLJS-1515: Self-host: Allow :file key in cljs.js/*load-fn* +* add toString implementation to Vars +* Use a js array to create collections in cljs.reader +* CLJS-1640: Use the unshaded version of the closure compiler +* add :browser-repl to list of known opts +* add browser REPL preload +* parity with Clojure 1.9.0-alpha10 clojure.spec +* bump to tools.reader 1.0.0-beta3 + +### Fixes +* CLJS-1733: Macro inference issue for macros & runtime vars with the same name +* CLJS-1735: Self-host: cljs.spec speced-vars instance +* CLJS-1736: cljs.spec.test: checkable-syms* called with 0-arity +* CLJS-1708: Self-host: [iu]nstrument-1 needs to qualify [iu]nstrument-1* +* CLJS-1707: Self-host: with-instrument-disabled needs to qualify *instrument-enabled* +* CLJS-1732: Add docstrings for new use and use-macros REPL specials +* CLJS-1720: Qualify symbols and namespaced keywords in spec macros +* CLJS-1731: Self-host: do_template problem with script/test-self-parity +* CLJS-1556: Invalid code emit for obj literal +* CLJS-1607: bug with `specify!` in JS prototypes with `static-fns` true +* CLJS-1591 avoid analyzing invoke arguments multiple times +* CLJS-1638: :elide-asserts disables atom validators in :advanced +* CLJS-1721: 3-arity get-in fails on types which do not implement ILookup +* CLJS-1728: Update doc for ns for new :rename capability +* CLJS-1727: Regression when evaluating non-sequential forms at the REPL +* CLJS-1490: Watch macro files in cljs.build.api/watch +* CLJS-1719: Port destructuring namespaced keys and symbols +* CLJS-1653: cljs.spec: keys* causes exception +* CLJS-1700: Support clojure.* aliasing when not in vector +* CLJS-1717 remove map from equiv-map +* CLJS-1716: No longer possible to use same alias for :require-macros and :require +* Use keyword options in js->clj 1-arg impl +* Add support for regex in transit for compiler analysis cache +* Escape non-Latin1 characters before base64 encoding the source-map string +* CLJS-1698: cljs.spec: every res call needs &env +* CLJS-1695: Self-host: Port cljs / clojure namespace aliasing +* CLJS-1697: doc on inferred macros fails +* CLJS-1699: Update docstring for ns +* CLJS-1694: Self-host: Port macro var inference in :refer + + +## 1.9.89 + +### Enhancements +* CLJS-1688: :preloads compiler option for loading other entry points prior to :main +* cljs.spec - support gen overrides by name in addition to path +* cljs.spec - every and every-kv + +### Changes +* added bounded-count + +### Fixes +* missing cljs.spec/fn-specs -> cljs.spec/get-spec in cljs.spec.test ns +* CLJS-1687: Self-host: cljs.spec: inst-in-range? and int-in-range? need qualification +* CLJS-1668: cljs.spec: c alias needs expansion in int-in + +## 1.9.76 + +### Enhancements +* CLJS-1648: Getting Source Info into ex-info data for Analysis Errors +* cljs.spec updated to Clojure 1.9.0-alpha7 changes + +### Changes +* bump Google Closure Library dep +* AOT cljs.spec nses + +### Fixes +* CLJS-1679: Self-host: Incorporate spec tests +* CLJS-1680: Self-host: Don't require items no longer provided by Closure +* CLJS-1654: cljs.spec: var name in s/fdef non-conformance +* CLJS-1655: cljs.spec: conformer docstring indicates :clojure.spec/invalid +* CLJS-1656: Self-host: cljs.spec: speced-vars* fn not resolving +* CLJS-1661: cljs.spec: non-spec'ed fn var printing +* compute read/write opts for transit if possible, handle JSValue +* CLJS-1660: cljs.spec: Always return var from instrument / unstrument +* CLJS-1671: Bad cljs.spec interactive instrumentation session +* CLJS-1664: The filename aux.cljs is a problem on windows. +* CLJS-1667: bad describe* for and-spec-impl +* CLJS-1699: Self-host: s/fdef ns-qualify *ns* name field access + +## 1.9.36 + +### Enhancements +* Write analysis caches as Transit if transit-clj available + +### Changes +* Clojure 1f25347 +* Clojure 47b8d6b +* Optimize seq (&) destructuring as per commit (0aa3467) of Clojure + +### Fixes +* CLJS-1611: Function arity dispatch returns arity +* only print specs in REPL if we actually have some +* CLJS-1663: Calling instrumented multi-arity function causes exception +* CLJS-1650: `cljs.reader/read-map` now returns array-map/hash-map based on the size of the sequence. + +## 1.9.14 + +### Enhancements +* clojure.spec ported to cljs.spec + +### Fixes +* CLJS-1649: Possible issue with in cljs.reader or cljs.core/PersistentHashMap +* CLJS-1647: Rethrow exception from parallel-build +* CLJS-1642: cljs.core/reductions does not respect 'reduced' +* CLJS-1635: Var type implements IEquiv but not IHash +* CLJS-1629: Fix warning about duplicate test-pr-str +* CLJS-1637: Missing docstrings for a few vars + +## 1.8.51 + +### Changes +* bump Closure Compiler to v20160315 +* bump tools.reader to 1.0.0-beta1 +* CLJS-1624: Avoid useage of JSC_HOME in test bash scripts + +### Enhancements +* CLJS-1626: cljs.test for bootstrap + +### Fixes +* CLJS-1588: defrecord satisfies? behavior under bootstrap +* CLJS-1632: docs / arglist consistency +* CLJS-1612: Resolve ns aliases in syntax-quote +* CLJS-1621: Foreign libs modules of different types don't compile together +* CLJS-1617: inlined `list` evaluation order +* :parallel-build race condition + +## 1.8.40 + +### Fixes +* CLJS-1603: Only warn for misspelled comp/REPL opts +* :warning-handlers missing for known compiler options +* CLJS-1592: Self-host: Robustness for core tests + +## 1.8.34 + +### Changes +* CLJS-1582: Type-hint extend-type first arg for primitives +* CLJS-1590: split, split-lines differs from Clojure on empty string +* CLJS-1594: NaN and both infinities cannot be elements of a set +* CLJS-1597: Redundant IPrintWithWriter test in pr-writer-impl +* CLJS-1583: (hash (symbol "/")) does not match (hash '/) +* bump tools reader +* CLJS-1492: Warn when using :optimisations instead of :optimizations +* less cryptic error if :main doesn't correspond to any file +* CLJS-744: ISequential types should implement JS indexOf, lastIndexOf +* CLJS-1411: make-array signature differs from clojure + +### Fixes +* CLJS-1589: Self-host: case fail with nil +* CLJS-1596: Self-host: :load-macros and :analyze-deps don't work in cljs.js +* CLJS-1420 - get-in behavior differs from Clojure by always deferring to the 3 arity fn +* CLJS-1585: Self-host: Alias-scoped keywords +* CLJS-1577: Self-host: syntax-quote resolves on dot forms +* CLJS-1564: Self-host: cached macro *loaded* update +* CLJS-1584: Self-host: core/str error with condp +* CLJS-1521: Self-host: Macro namespaces cannot be aliased +* CLJS-1573: Self-host: Invalid UTF escaping in cljs-in-cljs +* CLJS-1570: :parallel-build causes invalid truth check in cljs.reader/read-number +* CLJS-1568: LazyTransformer doesn't implement IMeta +* CLJS-1578: Corrupted Analysis Files Break Compilation +* CLJS-1579: cljs.source-map/invert-reverse-map discards gcol +* CLJS-1580: Self-host: goog.provide offsets source-maps +* CLJS-1569: IndexedSeq doesn't implement IWithMeta / IMeta +* CLJS-1567: make-array macro missing > 2 arg arity +* CLJS-1571: Make special-symbol? true for 'var +* CLJS-1555: make-array macro missing 2 arg arity +* CLJS-970: generate assert message when compiling +* CLJS-1565: Self-host: whitespace optimization is broken +* CLJS-1541: Self-host: Cannot require 'cljs.js using cljs.jar +* CLJS-1550: Enhance docstring for extend-type wrt type-sym +* CLJS-1551: Self-host: assert-args dormant in macros +* CLJS-1552: doc for & should match fn +* CLJS-1488: cljs.repl/source Cannot read source of cljs functions that use #js reader +* CLJS-1557: Make special-symbol? return true for catch and finally +* CLJS-1542: Self-host: cljs/compile-str not handling errors properly +* CLJS-1318: Fix typo in documentation of `specify` +* CLJS-620: Warnings are generated when using a macro in argument position +* CLJS-1547: Wrong output encoding when compile with goog.LOCALE +* CLJS-1546: cljs.core/run! does not always return nil + +## 1.7.228 + +### Enhancements +* New experimental :parallel-build compiler option + +### Changes +* CLJS-1538: Type hint some cljs.core predicates +* Docstring typos +* CLJS-1463: (js-debugger) should generate nil-returning expression +* CLJS-1516: better error message when calling macros with arity +* CLJS-1514: Remove Alpha designators on *-watch and ex-* +* clojure.core/require is not thread safe, use locks +* CLJS-1505: Add tests to characterize `type` and `instance?` behavior +* CLJS-1491: Check :source-map is boolean when :optimizations :none +* split sm/encode into 2 functions so JSON generation is optional + +### Fixes +* CLJS-1539: Parallel compilation fails on circular dependencies +* CLJS-1425: self-host: cljs.js/eval cb argument inconsistent with docstring +* CLJS-1425: self-host: cljs.js/eval cb argument inconsistent with docstring +* CLJS-1524: Bad hashing for Cons +* CLJS-1487: Fix handling of timestamp comparison for dependencies in JARs +* CLJS-1498: Fix parallel build logging +* CLJS-1477: Do not attempt to resolve "native" type symbols +* CLJS-1236: `constructor` needs to munged if used as namespace segment +* CLJS-1330: self-host: .toString on int needs parens +* CLJS-1512: Self-host: arithmetic form meta missing :numeric +* CLJS-1506: doc for referred fn displays alias ns +* CLJS-1504: Self-host: Pseudo-namespace for macro namespace analysis +metadata +* CLJS-1483: Minor DCE regression with advanced compilation mode + +## 1.7.170 + +This is a breaking change for tooling libraries like lein-cljsbuild, +lein-figwheel, and boot-cljs. Refer to the corresponding documentation to +determine which version you should use. + +### Enhancements +* Refactor build pipeline +* CLJS-1478: Self-host: Allow static-fns opt + +### Changes +* Generate larger range of random UUIDs +* make browser REPL file reloads less chatty +* CLJS-1475: indicate that cljs.reader/read is safe +* CLJS-1470: Bump GCL Dependency +* bump Google Closure dep + +### Fixes +* in system-time check that js/process.hrtime is actually a thing +* CLJS-1228: cljs.util/topo-sort is polynomial on larger dependency graphs +* check that performance.now method actually exists +* CLJS-1476: Self-host: Protocol prefixing broken for three- (or more) segment namespaces +* CLJS-1472 Patch for CLJS-1467 causes regression for nodejscli +* CLJS-1469 :modules regression +* CLJS-1445: Syntax error for var args in protocol methods +* Warn if protocol impl methods do not match its protocol +* CLJS-1451 Protocol impl do not support qualified method names +* CLJS-1422: cljs.js/eval-str fails for ns form on node.js with simple optimizations +* CLJS-1423: self-host: Requiring analyzer/compiler breaks unchecked Boolean +* CLJS-1466: Improperly munged output path for GClosure JavaScript +* CLJS-1467: Foreign Libraries not included when using :main with :simple or :advanced + +## 1.7.145 + +### Enhancements +* CLJS-1455: high resoluting timing where available +* CLJS-1403: Add updated Windows shell scripts +* CLJS-1017: support :main for :advanced and :simple builds +* CLJS-1409: allow basic type checking of protocols +* CLJS-1404: var resolution for @param and @return +* CLJS-1395: Node.js REPL debug port support + +### Changes +* CLJS-1464: docstrings for transducer arities +* Latest Google Closure Compiler dependency +* Node.js REPL sets *target* +* add cljs.analyzer.api/get-js-index +* add goog.object to list of implicit namespaces +* CLJS-1393: turn *target* into goog-define + +### Fixes +* UUID hashing +* CLJS-1465: fix *main-cli-fn* doc +* CLJS-1456: bad require forms at REPL can corrupt REPL session +* CLJS-1449: self host :require-macros bug +* CLJS-1462: self host regression +* Add header bits for Node.js under :none +* CLJS-1457: unicode symbol munging +* CLJS-1442: self host, docstring typos +* CLJS-1441: portable clojure.string +* CLJS-1436: self-host, dep ns not loaded +* CLJS-1440: self-host, eval support in Web Workers +* CLJS-1400: self-host, doseq broken +* CLJS-1435: self-host, bad lexical scope +* CLJS-1434: clojure.walk no longer preseves meta +* CLJS-1432: '$ and '. symbol collision under advanced +* CLJS-1304: c.string/replace differs from Clojure +* CLJS-1430: bad code gen for self host .toString method calls +* CLJS-1353: range inconsistent with Clojure +* CLJS-1431: load-file doc output missing arglists +* CLJS-1433: cljs.js/*eval-fn* passed nil :cache +* CLJS-1299: add more support for literals to cljs.reader +* CLJS-1417: cljs.js require macros failures +* CLJS-1416: cljs.util/last-modified leaks files +* CLJS-1481: self host defprotocol regression +* CLJS-1414: only munge @param & @return if type checking +* CLJS-1401: unify runtime & compile UUID hashing +* CLJS-1395: no trailing semicolons after JS comment +* CLJS-1394: reify gensyms can clash + +## 1.7.48 + +### Enhancements +* provide goog-define macro to support proper use of goog.define +* CLJS-1177: A compiler support for non-Closure transforms (JSX, etc) +* CLJS-1296: browser REPL should queue prints before connection then flush after connection +* add :dump-core compiler option for cljs.js config +* CLJS-1386: Symbols should be added to the constants table + +### Changes +* Bump Closure Compiler dependency +* Bump Closure Library dependency + +### Fixes +* CLJS-1392: cljs.repl/source regression +* CLJS-1391: Error when building for target :nodejs +* CLJS-1388: Stacktrace element handling for :output-dir w/o file/line/column +* CLJS-1311: Improve error reporting when converting JavaScript modules +* CLJS-1387: support local Closure libs that conform to classpath + +## 1.7.28 + +### Enhancements +* New namespace cljs.js provides analysis, compilation, and eval +* CLJS-1360: Refactor JS module processing to work with recent Google Closure compiler changes +* CLJS-1282: Add a :pprint option to the default reporter in cljs.test +* CLJS-1308: :analyze-path should be extended to take a vector of paths +* CLJS-1230: ES 2015 Module Processing +* CLJS-1231: AMD Module Processing +* CLJS-1092: CommonJS Module processing + +### Changes +* CLJS-1376: Printing in a tagged literal data form +* CLJS-836: Replace seq-based iterators with direct iterator for all non-seq collections that use SeqIterator +* CLJS-1367: Expose default-warning-handler and warning-enabled? +* CLJS-1267: Added the :end-test-all-vars and :end-test-vars events to have end events for all cljs.test api functions +* CLJS-1337: Move parse ns side-effects into a separate compiler pass +* CLJS-1247: Split out error printing from regular printing +* CLJS-1329: Support for reading #js tagged literals in bootstrap +* CLJS-1191: rebased patch Update clojure.walk to the current version on clojure +* CLJS-1321: remove getNamespace & getName method calls from defrecord +* CLJS-1281: Preserve test order +* CLJS-934: In the REPL return vars after defs + +### Fixes +* CLJS-1316 let does not detect invalid binding vector when it contains destructuring +* CLJS-1033: take a drop accept nil as n argument +* CLJS-1324: Compiler fails to raise warning/error when invoking a keyword without arguments +* CLJS-1352: cljs.js: Allow conditional readers +* CLJS-1348: meta is printing for def at REPL +* CLJS-1342: cljs.reader/read-string should throw Error when not called with string +* CLJS-1341: Fix CommonJS conversion bug +* CLJS-1333: Analyze meta on quoted symbols +* CLJS-1210: Javascript built-in arguments replaces nil arguments locally defined by let +* CLJS-1248: alter-meta! does not work on vars +* CLJS-1276: var equality differs from Clojure +* CLJS-1310: ns libspec error message misses :import +* CLJS-428: Added step to escape docstrings with */ and associated test +* CLJS-1331: Regex literal emits invalid JS +* CLJS-1338: NPE in confirm-var-exists if suffix is ".." +* CLJS-1319: Cannot locate module namespace when filename contains dash +* CLJS-1317: Incremental compilation issues for :nodejs target +* CLJS-1227 Raise error when if form has more than 4 statements +* CLJS-1306: Browser REPL :asset-path with leading slash breaks source map support +* CLJS-1290: :refer does not work with Closure JS namespaces +* CLJS-1307: Doc for ns missing +* CLJS-1301: local :foreign-libs are not picked up the first time browser REPL is started + +## 0.0-3308 + +### Changes +* Clojure 1.7.0-RC1 dependency +* CLJS-1292: Add IPrintWithWriter implementation for TaggedLiteral +* add cljs.core/random-uuid +* flush immediately when forwarding Node process out & err +* CLJS-1256 cache UUID hash value +* CLJS-1226: Added the :end-run-tests event to cljs.test and a dummy event handler for it + +### Fixes +* CLJS-1200: compare behaves differently from Clojure +* CLJS-1293: Warning settings not conveyed via REPL +* CLJS-1291: pprint whitespace/letter checks are incomplete +* CLJS-1288: compiler doesn't emit "goog.require" for foreign library when optimization level is not set +* check that we actually read something in cjls.repl.server/read-request +* clarify cljs.test/run-tests docstring +* CLJS-1285: load-file regression +* CLJS-1284: IndexedSeq -seq implementation incorrect for i >= alength of internal array +* finish CLJS-1176, remove stray .isAlive method call +* add zero arity `newline` to match Clojure +* CLJS-1206: Images in HTML don't show up when served from localhost:9000 +* CLJS-1272: :include-macros description inaccurate in require +* CLJS-1275: Corrected :test-paths in project.clj +* CLJS-1270: Docstring for delay not printed by cljs.repl/doc +* CLJS-1268: cljc support for cljs.closure/compile-file +* CLJS-1269: realized? docstring refers to promise and future +* match Clojure behavior for get on string / array. Need to coerce key into int. +* CLJS-1263: :libs regression, can no longer specify specific files +* CLJS-1209: Reduce produces additional final nil when used w/ eduction +* CLJS-1261: source fn fails for fns with conditional code + +## 0.0-3269 + +### Fixes +* REPL support for Closure libraries that follow classpath conventions +* don't break closure libs that follow classpath conventions +* build missing .map source map & .edn caches files + +## 0.0-3264 + +### Fixes +* Add missing JS files back to the build +* CLJS-1168: REPL fails to find .js files in :libs +* CLJS-1196: Assert failed on 3190+ while :require-ing .js file in :libs directory +* CLJS-1235: non-upstream :foreign-libs not copied to :output-dir +* CLJS-1258: stack trace mapping does not appear to work with :asset-path +* CLJS-1257: find-doc regression + +## 0.0-3255 + +### Changes +* Update Closure Library dependency +* CLJS-1252: Update Closure Compiler Dependency to v20150505 +* .clj -> .cljc for important analysis / compilation bits +* add public cljs.compiler.api namespace +* CLJS-1224: cljs.repl: Memoize stack frame mapping +* depend on tools.reader 0.9.2 + +### Enhancements +* add cljs.pprint/pp macro +* CLJS-710: port clojure.pprint +* CLJS-1178: Compiler does not know Math ns is not not-native +* add getBasis methods to deftype and defrecord ctors a la Clojure JVM +* support ^long and ^double type hints + +### Fixes +* fix cljs-1198 async testing regression +* CLJS-1254: Update REPL browser agent detection CLJS-1253: Create/Use new Closure Library Release +* CLJS-1225: Variadic function with same name as parent function gives runtime error in advanced compile mode. +* CLJS-1246: Add cljs.core/record? predicate. +* CLJS-1239: Make eduction variadic. +* CLJS-1244: tagged-literal precondition check missing wrapping vector +* CLJS-1243: Add TaggedLiteral type & related fns +* CLJS-1240: Add cljs.core/var? +* CLJS-1214: :arglists meta has needless quoting CLJS-1232: bad arglists for doc, regression +* CLJS-1212: Error in set ctor for > 8-entry map literal +* CLJS-1218: Syntax quoting an alias created with :require-macros throws ClassCastException +* CLJS-1213: cljs.analyzer incorrectly marks all defs as tests when eliding test metadata +* CLJS-742: Compilation with :output-file option set fails + +## 0.0-3211 + +### Changes +* CLJS-1205: Conditional reading in REPLs +* CLJS-1204: cljs.build.api/watch can now take compilation inputs +* CLJS-1203: standard way to pass multiple directories to build + +### Fixes +* CLJS-1216: incorrect max fixed arity for fns both multi-arity and variadic +* cljs.analyzer/parse-ns did not bind *cljs-file* +* CLJS-1201: compare broken for IIndexed collections +* CLJS-1202: cljs.repl/load-file is not additive +* CLJS-1199: array-map should skip dropped elements of IndexedSeq +* CLJS-1197: load-file does not reload associated macro namespace + +## 0.0-3196 + +### Enhancements +* Conditional reading +* map clojure.core/in-ns to REPL in-ns special for existing tools +* CLJS-1171: map clojure.repl/doc, clojure.repl/source, clojure.repl/dir +* add macroexpand and macroexpand-1 macros +* CLJS-1019: REPL source map caching support +* CLJS-1154: Unmunged function names for stacktrace + +### Changes +* Clojure 1.7.0-beta1 dependency +* tools.reader 0.9.1 dependency +* CLJS-1188: multi-arity fns hinder cross-module code motion +* cljs.test needs to default to sync +* CLJS-1184: log module building activity under verbose +* CLJS-1175: CLJS defmulti doesn't exhibit same defonce behavior as Clojure's defmulti, suggesting an even better reloading behavior +* CLJS-1176: redirect node REPL output through *out* and *err*, not System/out, System/err +* CLJS-1144 - expose defaul-dispatch-val and dispatch-fn multifn accessors +* CLJ-1172: supply main entry points for all standard REPLs +* less noisy REPL prompt +* add docstrings & validation to macroexpand & macroexpand-1 + +### Fixes +* CLJS-1192: eliminate JDK8 API dependency in cljs.repl.node +* CLJS-1158: Regression: compiler fails to see symbols defined in another namespace +* CLJS-1189: array-map will return PersistentHashMap if applied to more than (.-HASHMAP-THRESHOLD PersistentArrayMap) pairs +* CLJS-1183: load-file doesn't copy source to output directory +* CLJS-1187: var ast contains internal nodes with bad analysis :context +* CLJS-1182: semantics of load-file should be require + implicit :reload +* CLJS-1179: strange load-file behavior +* CLJS-808: Warning from `find-classpath-lib` mistakenly included in generated source +* CLJS-1169: cannot use REPL load-file on files that declare single segment namespaces +* don't use print unless printing the result of eval +* CLJS-1162: Failure to printStackTrace when REPL initialized +* CLJS-1161: actually print error stack traces to *err*, allow higher-level rebindings of *cljs-ns* +* CLJS-841: cljs.closure/build file locks +* CLJS-1156: load-file fails with :make-reader issue +* CLJS-1152: (require 'some.ns :reload) causes printing to stop working in browser REPL +* CLJS-1157: Stacktrace unmunging blindly use locals +* CLJS-1155: REPL :watch support does not play nicely with :cljs/quit +* CLJS-1137: :cljs/quit fails to actually quit in browser REPL +* CLJS-1148: ClojureScript REPL must maintain eval/print pairing +* make quit-prompt configurable +* CLJS-1149: cljs.repl/repl needs to support :compiler-env option +* CLJS-1140: typo in cljs.repl/repl, `:need-prompt prompt` instead of `:need-prompt need-prompt` + +## 0.0-3126 + +### Fixes +* Need to wrap REPL -setup calls in cljs.compiler/with-core-cljs + +## 0.0-3123 + +### Fixes +* CLJS-1131: cljs.closure/add-dependencies needs to be more aggressively set oriented +* CLJS-1132: compile-file analysis pass optimization broken under Closure optimization and :cache-analysis true + +## 0.0-3119 + +### Fixes +* CLJS-1130: :foreign-libs regression under Closure optimized builds + +## 0.0-3117 + +### Fixes +* CLJS-1126: File are not recompiled when build affecting options changes + +## 0.0-3115 + +### Enhancements +* CLJS-806: support ^:const +* CLJS-1115: Reusable repl-bootstrap! fn + +### Changes +* CLJS-667: validate extend-type and extend-protocol shape +* CLJS-1112: :repl-requires option for REPL evaluation environment +* CLJS-1111: browser REPL should have no side effects until -setup + +### Fixes +* CLJS-1085: Allow to pass test environment to cljs.test/run-all-tests +* CLJS-867: extend-type with Object methods requires multi-arity style definition +* CLJS-1118: cljs.repl/doc support for protocols +* CLJS-889: re-pattern works on strings containing \u2028 or \u2029 +* CLJS-109: Compiler errors/warnings should be displayed when cljs namespace 'package' names start with an unacceptable javascript symbol +* CLJS-891: Defs in "parent" namespaces clash with "child" namespaces with the same name? +* CLJS-813: Warn about reserved JS keyword usage in namespace names +* CLJS-876: merged sourcemap doesn't account for output-wrapper +* CLJS-1062: Incorrect deftype/defrecord definition leads to complex error messages +* CLJS-1120: analyze-deps does not appear to work when analyzing analysis caches +* CLJS-1119: constant table emission logic is incorrect +* CLJS-977: implement IKVReduce in Subvec +* CLJS-1117: Dependencies in JARs don't use cached analysis +* CLJS-689: js/-Infinity munges to _Infinity +* CLJS-1114: browser REPL script loading race condition +* CLJS-1110: cljs.closure/watch needs to print errors to *err* +* CLJS-1101 cljs.test might throw when trying to detect file-and-line +* CLJS-1090: macros imported from clojure.core missing docs +* CLJS-1108: :modules :output-to needs to create directories +* CLJS-1095: UUID to implement IComparable +* CLJS-1096: Update js/Date -equiv and -compare semantics based on Date.valueOf() value +* CLJS-1102 clojure.test should print column number of exception when available + +## 0.0-3058 + +### Enhancements +* browser REPL source mapping for Firefox, Safari, Chrome +* macro support in REPL special functions +* CLJS-897: AOT core.cljs CLJS-899: AOT cache core.cljs analysis +* CLJS-1078: Nashorn REPL should use persistent code cache +* CLJS-1079: add way to execute arbitrary fn upon watch build completion +* CLJS-1034: Support REPL-defined functions in stacktrace infrastructure +* source mapping for Rhino +* CLJS-1071: support symbol keys in :closure-defines +* CLJS-1014: Support Closure Defines under :none +* CLJS-1068: node target define +* CLJS-1069: Generic :jsdoc support +* CLJS-1030: add `cljs.repl/pst` +* add `cljs.repl/apropos`, `cljs.repl/find-doc`, `cljs.repl/dir` +* fix `cljs.analyzer.api/all-ns` docstring +* add `cljs.analyzer.api/ns-publics` +* CLJS-1055: cljs.repl/doc should support namespaces and special forms +* Add ClojureScript special form doc map +* CLJS-1054: add clojure.repl/source functionality to cljs.repl +* CLJS-1053: REPLs need import special fn + +### Changes +* move :init up in cljs.repl/repl +* CLJS-1087: with-out-str unexpectedly affected by *print-newline* +* CLJS-1093: Better compiler defaults +* Bump deps latest Closure Compiler, Rhino 1.7R5, data.json 0.2.6, tool.reader 0.8.16 +* more sensible error if cljs.repl/repl arguments after the first incorrectly supplied +* default REPLs to :cache-analysis true +* default :output-dir for Nashorn and Node REPLs +* change ES6 Map `get` support to take additional `not-found` parameter +* deprecate clojure.reflect namespace now that REPLs are significantly enhanced, static vars, etc. + +### Fixes +* stop blowing away cljs.user in browser REPL so REPL fns/macros remain available +* CLJS-1098: Browser REPL needs to support :reload and :reload-all +* CLJS-1097: source map url for AOTed cljs.core is wrong +* CLJS-1094: read option not used by cljs.repl/repl* +* CLJS-1089: AOT analysis cache has bad :file paths +* CLJS-1057: Nashorn REPL should not use EDN rep for errors +* CLJS-1086: Keyword constants should have stable names +* CLJS-964: Redefining exists? does not emit a warning like redefining array? does. +* CLJS-937: local fn name should be lexically munged +* CLJS-1082: analysis memoization bug +* CLJS-978: Analysis caching doesn't account for constants table +* CLJS-865: remove `cljs.js-deps/goog-resource` hack +* CLJS-1077: analyze-deps infinite recursive loop +* manually set *e in Rhino on JS exception +* REPL options merging needs to be more disciplined +* CLJS-1072: Calling .hasOwnProperty("source") in Clojurescript's string/replace will break with ES6 +* CLJS-1064: ex-info is not printable +* Fix REPLs emitting code into .repl directory +* CLJS-1066: Rhino REPL regression +* be more disciplined about ints in murmur3 code +* Node.js REPL should work even if :output-dir not supplied +* Nashorn environment doesn't supply console, setup printing correctly + +## 0.0-2913 +* Support custom :output-to for :cljs-base module + +## 0.0-2911 + +### Enhancements +* CLJS-1042: Google Closure Modules :source-map support +* CLJS-1041: Google Closure Modules :foreign-libs support +* Google Closure Modules support via :modules +* CLJS-1040: Source-mapped script stack frames for the Nashorn repl + +### Changes +* CLJS-960: On carriage return REPLs should always show new REPL prompt +* CLJS-941: Warn when a symbol is defined multiple times in a file +* REPLs now support parameterization a la clojure.main/repl +* all REPLs analyze cljs.core before entering loop +* can emit :closure-source-map option for preserving JS->JS map +* REPLs can now merge new REPL/compiler options via -setup + +### Fixes +* CLJS-998: Nashorn REPL does not support require special fn +* CLJS-1052: Cannot require ns from within the ns at the REPL for reloading purposes +* CLJS-975: preserve :reload & :reload-all in ns macro sugar +* CLJS-1039: Under Emacs source directory watching triggers spurious recompilation +* CLJS-1046: static vars do not respect user compile time metadata +* CLJS-989: ClojureScript REPL loops on EOF signal +* fix DCE regression for trivial programs +* CLJS-1036: use getResources not findResources in get-upstream-deps* + +## 0.0-2850 + +### Enhancements +* CLJS-1035: REPLs should support watch recompilation + +### Fixes +* CLJS-1037: cls.analyzer/ns-dependents fails for common cases + +## 0.0-2843 + +### Enhancements +* CLJS-1032: Node.js target should support :main +* require cljs.test macro ns in cljs.test to get macro inference goodness +* include :url entries to original sources in mapped stacktraces if it can be determined from the classpath +* support custom mapped stacktrace printing +* provide data oriented stacktrace mapping api +* CLJS-1025: make REPL source mapping infrastructure generic +* CLJS-1010: Printing hook for cljs-devtools +* CLJS-1016: make "..." marker configurable + +### Changes +* CLJS-887: browser repl should serve CSS +* CLJS-1031: Get Closure Compiler over https in the bootstrap script + +### Fixes +* cljs.nodejscli ns needs to set `goog.global` when `COMPILED` is true, this fixes the fundamental issues for ASYNC-110 +* CLJS-967: "java.net.ConnectException: Connection refused" when running node repl +* pass relevant source map options in the incremental compile case +* add some missing source-map customization flags to optimized builds +* fix missed Rhino REPL regression, the surrounding REPL infrastructure creates cljs.user for us +* util.print has been deprecated in Node.js v0.12. Switch to console.log in Node.js REPLs. +* change `cljs.closure/watch` so it correctly watches all subdirectories do not recompile unless changed path is a file with .cljs or .js extension + +## 0.0-2816 + +### Fixes +* CLJS-1001: reify did not elide reader metadata + +## 0.0-2814 + +### Enhancements +* add simple source directory `cljs.closure/watch` watcher using java.nio +* CLJS-1022: Concatenate foreign dependencies safely +* CLJS-988: Support async testing in cljs.test +* CLJS-1018: Add support for cljs.core/*e Modify the JavaScript that is sent for evaluation to wrap in a try and then catch any exception thrown, assign it to *e, and then rethrow. +* CLJS-1012: Correct behavior when *print-length* is set to 0 +* Added new :closure-extra-annotations compiler option allowing to define extra JSDoc annotation used by closure libraries. +* Mirrored source map support APIs on server/client +* Unified source mapping support in REPLs +* Nashorn REPL (thanks Pieter van Prooijen) + +### Fixes +* CLJS-1023: regression, macro-autoload-ns? and ns-dependents need to throw on cyclic dependencies +* fix require with browser REPL, set base path to "goog/" +* CLJS-1020: off by one error in REPL source map support +* Node.js 0.12 support +* browser REPL needs to respect :output-dir +* CLJS-1006: Implicit dependency of clojure.browser.repl on cljs.repl +* CLJS-1005: Browser REPL creates 'out' directory no matter what +* CLJS-1003: fix cljs.test run-tests do-report :summary issues +* CLJS-1003: Cannot pass custom env to run-tests +* Windows Node.js REPL issues + +## 0.0-2760 + +### Fixes +* ns spec handling regression + +## 0.0-2758 + +### Fixes +* fix autoload macro enhancement + +## 0.0-2755 + +### Enhancements +* CLJS-948: simplify macro usage + +### Fixes +* CLJS-927: real incremental compilation +* Browser REPL regressions +* CLJS-991: Wrong inference - inconsistent behavior? +* CLJS-993: binding macro returns non-nil with empty body +* CLJS-972: Node.js REPL eats errors in required ns when using require +* CLJS-986: Add :target to the list of build options that should trigger recompilation +* CLJS-976: Node REPL breaks from uncaught exceptions + +## 0.0-2740 + +### Changes +* local :foreign-libs can precisely override upstream :foreign-libs +* :foreign-libs :file-min is only used under :advanced optimizations +* file generated by supplying :main now idempotent +* more informative error if :main incorrectly supplied + +### Fixes +* many fixes around file/resource handling for Windows users + +## 0.0-2727 + +### Fixes +* Allow :main script imports to be configured via :asset-path + +## 0.0-2725 + +### Fixes +* Fix Node.js support regression + +## 0.0-2723 + +### Enhancements +* CLJS-851: simplify :none script inclusion if :main supplied +* CLJS-983: make ExceptionInfo printable + +### Fixes + +## 0.0-2719 + +### Changes +* CLJS-985: make ex-info not lose stack information +* CLJS-984: Update Node.js REPL support to use public API +* CLJS-963: do not bother computing goog/dep.js under :none + +### Fixes +* CLJS-982: Var derefing should respect Clojure semantics +* CLJS-980: ClojureScript REPL stacktraces overrun prompt in many cases +* CLJS-979: ClojureScript REPL needs error handling for the special functions +* CLJS-971: :reload should work for require-macros special fn +* CLJS-936: Multi arity bitwise operators +* CLJS-962: fix inconsistent hashing of empty collections + +## 0.0-2665 + +### Changes +* REPL -setup now must take opts +* CLJS-916: Optimize use of js-arguments in array and variadic +functions +* special case `'cljs.core/unquote` +* CLJS-945: Compile core with :static-fns true by default +* CLJS-958: Node.js REPL: Upon error, last successfully item printed + +## 0.0-2657 + +### Changes +* Add require-macros REPL special fn + +## 0.0-2655 + +### Changes +* add defonced cljs.core/*loaded-libs* dynamic var +* cljs.core/*print-fn* is now defonced +* throw on (var foo) when foo is not defined +* cljs.analyzer.api/resolve matches cljs.core/resolve if + var doesn't exist return nil + +### Fixes +* require needs to respect Clojure semantics, do not + reload unless requested +* add ns/require support for :reload & :reload-all + +## 0.0-2644 + +### Fixes +* CLJS-953: require REPL special fn can only take one argument +* CLJS-952: Bad type hinting on bit-test +* CLJS-947: REPL require of goog namespaces does not work +* CLJS-951: goog.require emitted multiple times under Node.js REPL +* CLJS-946: goog.require in REPLs will not reload recompiled libs +* CLJS-950: Revert adding compiled-by string to CLJS deps file +* CLJS-929: Minor fixes to test script +* CLJS-946: goog.require in REPLs will not reload recompiled libs + +## 0.0-2629 + +### Enhancements +* Add Node.js REPL +* REPLs can now reuse build/analysis caching +* in-ns, require, doc support in REPLs + +### Changes +* add :verbose flag to compiler to output compiler activity +* add *load-macros* to cljs.analyzer to optionally disable macro loading +* errors during ns parsing always through +* `cljs.util/compiled-by-version` needs to always return String +* pin Closure Compiler in bootstrap script +* refactor cljs.build.api namespace + +### Fixes +* add cljs.test/are macro +* CLJS-931 : cljs.compiler/requires-compilation? ignores changes to build options +* CLJS-943: REPL require special fn is brittle +* CLJS-941: Warn when a symbol is defined multiple times in a file +* CLJS-942: Randomized port for Node.js REPL if port not specified +* CLJS-675: QuickStart example not working properly +* CLJS-935: script/noderepljs leaves node running after exit +* CLJS-918: preserve :arglists metadata in analysis cache +* CLJS-907: False positives from arithmetic checks +* CLJS-919 compare-and-set! relies on Atom record structure instead of protocols +* CLJS-920 add-watch/remove-watch should return reference, as in Clojure +* CLJS-921: cljs.repl/doc output includes namespace twice + +## 0.0-2511 + +### Enhancements +* analysis caching via :cache-analysis build flag + +## 0.0-2505 + +### Changes +* Stop generating random files for IJavaScript Strings +* added :source-map-timestamp build flag to get cache busting source + map urls +* Enhancements to bootstrap script +* Stop warning about deps.cljs usage + +### Fixes +* Fix Node.js source mapping regression introduced by commit 254e548 +* CLJS-914: thrown-with-msg? is unable to get message of exception +* CLJS-915: On empty call, List and PersistentQueue do not retain meta, sorted-set/sorted map do not retain comparator + +## 0.0-2498 + +### Fixes +* Support cljs.test/use-fixtures + +## 0.0-2496 + +### Enhancements +* cljs.test added, mirrors clojure.test +* New cljs.analyzer.api namespace for easier access to analysis info from macros +* New cljs.analyzer.api namespace for easier access to analysis info from macros +* Support :test metadata on vars +* Support static vars +* cljs.source-map for client side source mapping +* expose ClojureScript :warnings build option +* CLJS-909: Add stable api for consumers of compiler data. + +### Changes +* convert all ClojureScript tests to cljs.test +* add volatile! from Clojure 1.7 +* stateful transducers use volatile! +* added `js-debugger` macro, compiles to "debugger;" +* CLJS-892: Improve performance of compare-symbols/compare-keywords +* CLJS-696: remove arguments usage from defrecord constructor +* unroll `partial`, copy & pasted from Clojure core.clj +* optimize clojure.string/join + +### Fixes +* fix `cljs.nodejs/enable-util-print!`, incorrectly monkey patched `cjls.core/string-print` instead of setting `cljs.core/*print-fn*` +* cljs.reader bug, '/ incorrectly read +* avoid emitting the same goog.require + +## 0.0-2411 + +### Enhancements +* forcing source maps to load for dynamic js reloads +* All ISeqable types are now ES6 iterable +* CLJS-863: Invalid arity error when calling 0-arity multimethod +* CLJS-622: better error reporting for zero arity protocol methods +* CLJS-506: expose more Closure minification knobs + +### Changes +* CLJS-807: Emitter cannot emit BigInt or BigDecimal +* CLJS-749: Ignore .repl-* given that CLJS version is appended by default. +* CLJS-749: Append CLJS version to browser repl-env +* CLJS-749: *clojurescript-version* is unbound return empty string +* implement INamed for multi-method +* revert CLJS-801 +* CLJS-888: Omit redundant {} around emitted recur +* CLJS-888: Better placement of newlines in emitter +* Join preambles with newline line to catch cases with files without newlines. +* add js-in interop macro +* Add nthrest +* CLJS-510: Throw error when :output-wrapper and :optimizations :whitespace combined +* CLJS-875: bump tools.reader dep to 0.8.10 +* CLJS-879: add `update` from Clojure 1.7 +* CLJS-857: change deftype*/defrecord* special forms to include their inline methods decls + +### Fixes +* CLJS-885: relax type inference around numbers +* fix var resolution bug pointed out by Brandon Bloom +* CLJS-853: propagate read-time metadata on fn and reify forms at runtime +* CLJS-716: support hashing of JavaScript dates +* CLJS-814: clojure.string/reverse breaks surrogate pairs +* Recursively check IEncodeClojure in js->clj +* CLJS-873: non-higher-order calls to array-map should return PAMs +* CLJS-881: check for duplicate keys in array-map +* select-keys did not preserve metadata + +## 0.0-2371 + +### Fixes +* CLJS-862: fix inconsistent re-pattern +* CLJS-866: Faulty ns macro desugaring +* CLJS-869: When preamble is not found in source directory, compiler does not report it + +## 0.0-2356 + +### Fixes +* fix var analysis so that some.ns/foo.bar is handled correctly +* CLJS-854: cljs.reader could not read numbers under IE8 + +## 0.0-2342 + +### Changes +* depend on tools.reader 0.8.9 + +## 0.0-2341 + +### Enhancements +* transducers + +### Fixes +* CLJS-704: warn if protocol extended to type multiple times in extend-type +* CLJS-702: warn if protocol doesn't match declared +* CLJS-859: use https for the bootstrap script +* CLJS-855: combinatorial code generation under advanced +* CLJS-858: resolve-existing var does not check vars outside current ns +* CLJS-852: same group-by as Clojure +* CLJS-847: Safari toString fix +* CLJS-846: preserve namespace metadata + +## 0.0-2322 + +### Fixes +* CLJS-839: Mobile Safari Math.imul issue +* CLJS-845: incorrect behavior of `sequence` when given multiple collections +* count check in equiv-sequential if both arguments are ICounted +* only keep the param names when storing :method-params instead of the + entire param AST +* preserve var metadata for deftype* and defrecord* +* preserve var metadata when creating deftype/record factory fns +* CLJS-831: Extending EventType to js/Element breaks Nashorn + +## 0.0-2311 + +### Fixes +* fix typo which broke browser REPL +* lazier seq iterators a la CLJ-1497 + +## 0.0-2307 + +### Enhancement +* Allow multi-arity anonymous fns to optimize + +## 0.0-2301 + +### Changes +* transducers + +### Fixes +* eliminate dead branches in conditionals to prevent Closure warnings +* bad var resolution if when local contained . + +## 0.0-2280 + +### Changes +* depend on latest org.clojure/google-closure-library + +### Fixes +* fix constants table bug where keywords did not include precomputed hash-code + +## 0.0-2277 + +## Enhancements +* All IEquiv implementor now export equiv Object method + +## Fixes +* CLJS-824: Unsigned hash for keywords produced via keyword fn +* CLJS-827: CLJS-827: wrap macro expansion in try/catch +* CLJS-826: fix broken closure release script +* CLJS-825: conflict between node js support files +* typo in unchecked-subtract-int + +## 0.0-2268 + +### Changes +* Experimental support for ES6 Map/Set interface + +### Fixes +* CLJS-823: use non-native imul in Safari +* CLJS-810: re-matches returns [] if string is nil + +## 0.0-2261 + +### Changes +* Dependency on Clojure 1.6.0 + +### Enhancements +* Murmur3 hashing for collections + +### Fixes +* CLJS-817: Warning on use of undeclared var when creating recursive definition +* CLJS-819: cljs.reader cannot handle character classes beginning with slashes in regex literals +* CLJS-820: Missing invoke without arguments in MetaFn +* CLJS-816: clojure.set/rename-keys accidentally deletes keys + +## 0.0-2234 + +### Fixes +* CLJS-812: Recur from case statement generated invalid JavaScript +* CLJS-811: use the correct class loader in cljs.js-deps/goog-resource +* fix fns with metadata under advanced compilation +* CLJS-809: dissoc :file metadata introduced by tools.reader 0.8.4 +* mark cljs.reader vars as ^:dynamic to avoid compiler warnings diff --git a/ci/install_jsc.sh b/ci/install_jsc.sh new file mode 100755 index 0000000000..b5255c94c7 --- /dev/null +++ b/ci/install_jsc.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +if [ ! -d WebKit ] +then + git clone -b Safari-611.1.5.1 --depth=1 https://github.com/WebKit/WebKit.git WebKit; + cd WebKit; + Tools/Scripts/build-jsc --jsc-only; + cd .. +fi diff --git a/deps.edn b/deps.edn new file mode 100644 index 0000000000..b7057d93f0 --- /dev/null +++ b/deps.edn @@ -0,0 +1,38 @@ +{:paths ["src/main/clojure" "src/main/cljs" "resources"] + :deps + {com.google.javascript/closure-compiler {:mvn/version "v20250820"} + com.cognitect/transit-java {:mvn/version "1.0.362"} + org.clojure/clojure {:mvn/version "1.10.0"} + org.clojure/core.specs.alpha {:mvn/version "0.1.24"} + org.clojure/google-closure-library {:mvn/version "0.0-20250515-f04e4c0e"} + org.clojure/spec.alpha {:mvn/version "0.1.143"} + org.clojure/tools.reader {:mvn/version "1.3.6"} + org.clojure/test.check {:mvn/version "1.1.1"}} + :aliases + {:cljs-repl {:extra-paths ["src/test/cljs"] + :main-opts ["-m" "cljs.main" "-re" "node" "-d" ".cljs_repl" "-r"]} + :cljs-brepl {:extra-paths ["src/test/cljs"] + :main-opts ["-m" "cljs.main" "-d" ".cljs_brepl" "-r"]} + :cljs-lite-repl {:extra-paths ["src/test/cljs"] + :main-opts ["-m" "cljs.main" "-co" "{:lite-mode true}" "-re" "node" "-d" ".cljs_lite_repl" "-r"]} + :cli.test.run {:extra-paths ["src/test/cljs_cli"] + :main-opts ["-i" "src/test/cljs_cli/cljs_cli/test_runner.clj" + "-e" "(cljs-cli.test-runner/-main)"]} + :compiler.test {:extra-paths ["src/test/cljs" "src/test/cljs_build" "src/test/cljs_cp" + "src/test/clojure" "src/test/self"] + :extra-deps {org.clojure/spec.alpha {:mvn/version "0.5.238"}}} + :compiler.test.run {:main-opts ["-i" "src/test/clojure/cljs/test_runner.clj" + "-e" "(cljs.test-runner/-main)"]} + :runtime.test.build {:extra-paths ["src/test/cljs"] + :main-opts ["-m" "cljs.main" "-co" "resources/test.edn" "-c"]} + :lite.test.build {:extra-paths ["src/test/cljs"] + :main-opts ["-m" "cljs.main" "-co" "resources/lite_test.edn" "-c"]} + :selfhost.test.build {:extra-paths ["src/test/self"] + :main-opts ["-m" "cljs.main" "-co" "resources/self_host_test.edn" "-c"]} + :selfparity.test.build {:extra-paths ["src/test/self"] + :main-opts ["-i" "src/test/self/self_parity/setup.clj" + "-e" "(self-parity.setup/-main)" + "-m" "cljs.main" "-co" "resources/self_parity_test.edn" "-c"]} + :uberjar {:extra-deps {com.github.seancorfield/depstar {:mvn/version "2.0.193"}} + :exec-fn hf.depstar/uberjar + :exec-args {:aot true}}}} diff --git a/devnotes/corelib.org b/devnotes/corelib.org index 3e92978d6f..f74b369139 100644 --- a/devnotes/corelib.org +++ b/devnotes/corelib.org @@ -5,25 +5,25 @@ * DONE *3 * *agent* * *allow-unresolved-vars* -* *assert* +* DONE *assert* * *clojure-version* * *command-line-args* * *compile-files* * *compile-path* -* TODO *e +* DONE *e * *err* * *file* -* *flush-on-newline* +* DONE *flush-on-newline* * *fn-loader* * *in* * *math-context* -* *ns* -* *out* -* *print-dup* -* *print-length* -* *print-level* -* *print-meta* -* *print-readably* +* DONE *ns* +* DONE *out* +* DONE *print-dup* +* DONE *print-length* +* DONE *print-level* +* DONE *print-meta* +* DONE *print-readably* * *read-eval* * *source-path* * TODO *unchecked-math* @@ -37,7 +37,7 @@ does what? * -' * DONE -> * DONE ->> -* ->ArrayChunk +* DONE ->ArrayChunk * ->Vec * ->VecNode * ->VecSeq @@ -123,7 +123,7 @@ does what? * char-array * char-escape-string * char-name-string -* char? +* DONE char? * DONE chars * DONE chunk * DONE chunk-append @@ -171,7 +171,7 @@ does what? * DONE defmulti * DONE defn * DONE defn- -* defonce +* DONE defonce * DONE defprotocol * DONE defrecord * defstruct @@ -209,7 +209,7 @@ For macros only, uses clojure.core version * enumeration-seq * error-handler * error-mode -* eval +* DONE eval (bootstrapped) * DONE even? * DONE every-pred * DONE every? @@ -226,7 +226,7 @@ macro currently expands into extend call * DONE filter * DONE find * TODO find-keyword -* find-ns +* DONE find-ns (not advanced compliation compatible) * find-protocol-impl * find-protocol-method * find-var @@ -234,9 +234,9 @@ macro currently expands into extend call * DONE flatten * DONE float * float-array -* float? +* DONE float? * DONE floats -* flush +* DONE flush * DONE fn * DONE fn? * DONE fnext @@ -327,7 +327,7 @@ does what? * DONE mapcat * DONE max * DONE max-key -* memfn +* DONE memfn * DONE memoize * DONE merge * DONE merge-with @@ -337,12 +337,12 @@ does what? * DONE min * DONE min-key * DONE mod -* munge +* DONE munge * DONE name * DONE namespace * namespace-munge * DONE neg? -* newline +* DONE newline * DONE next * DONE nfirst * DONE nil? @@ -424,9 +424,9 @@ dunno about regex * DONE re-matches * DONE re-pattern * DONE re-seq -* read +* DONE read (via tools.reader) * read-line -* read-string +* DONE read-string (via tools.reader) * DONE realized? * DONE reduce * DONE reductions @@ -453,8 +453,8 @@ dunno about regex * TODO require ticket #8 * DONE reset! -* reset-meta! -* resolve +* DONE reset-meta! +* DONE resolve (as macro) * DONE rest * restart-agent * resultset-seq @@ -513,7 +513,7 @@ as macro * DONE take-last * DONE take-nth * DONE take-while -* test +* DONE test * the-ns * thread-bound? * DONE time @@ -555,7 +555,7 @@ as macro * DONE vals * var-get * var-set -* var? +* DONE var? * DONE vary-meta * DONE vec * DONE vector @@ -573,9 +573,9 @@ as macro * TODO with-local-vars * DONE with-meta * with-open -* with-out-str +* DONE with-out-str * with-precision -* with-redefs +* DONE with-redefs * with-redefs-fn * TODO xml-seq * DONE zero? diff --git a/devnotes/day1.org b/devnotes/day1.org index 07f57c3760..4f54424c4d 100644 --- a/devnotes/day1.org +++ b/devnotes/day1.org @@ -189,11 +189,11 @@ **** ... *** strategy for apply * What's where -** src/clj/cljs/compiler.clj +** src/main/clojure/cljs/compiler.clj *** the compiler -** src/clj/cljs/core.clj +** src/main/clojure/cljs/core.clj *** core macros -** src/cljs/core.cljs +** src/main/cljs/core.cljs *** core library * Todo ** separate org file diff --git a/pom.template.xml b/pom.template.xml index 57f3f43205..f6a75f4839 100644 --- a/pom.template.xml +++ b/pom.template.xml @@ -1,194 +1,447 @@ - 4.0.0 - org.clojure - clojurescript - - CLOJURESCRIPT_VERSION - jar - ClojureScript + 4.0.0 + org.clojure + clojurescript + + CLOJURESCRIPT_VERSION + jar + ClojureScript - https://github.com/clojure/clojurescript - - - ClojureScript compiler and core runtime library. - + https://github.com/clojure/clojurescript - - - Eclipse Public License 1.0 - http://opensource.org/licenses/eclipse-1.0.php - repo - - + + ClojureScript compiler and core runtime library. + - - - com.google.javascript - closure-compiler - v20131014 - - - org.clojure - google-closure-library - 0.0-20130212-95c19e7f0f5f - - - org.clojure - data.json - 0.2.3 - - - org.mozilla - rhino - 1.7R4 - - - org.clojure - tools.reader - 0.8.3 - - + + + Eclipse Public License 1.0 + http://opensource.org/licenses/eclipse-1.0.php + repo + + - - Aaron Bedra - Alan Dipert - Alan Malloy - Alen Ribic - Alex Redington - Bobby Calderwood - Brandon Bloom - Brenton Ashworth - Chris Houser - Christopher Redinger - Creighton Kirkendall - David Nolen - Devin Walters - Eric Thorsen - Frank Failla - Hubert Iwaniuk - Hugo Duncan - Jess Martin - John Li - Jonas Enlund - Juergen Hoetzel - Kevin J. Lynagh - Laszlo Toeroek - Luke VanderHart - Michael Fogus - Michał Marczyk - Moritz Ulrich - Nicola Mometto - Paul Michael Bauer - Rich Hickey - Roman Gonzalez - Russ Olsen - Stuart Halloway - Stuart Sierra - Takahiro Hozumi - Thomas Scheiblauer - Tom Hickey - Wilkes Joiner - + + + org.clojure + clojure + 1.10.0 + + + com.google.javascript + closure-compiler + v20250820 + + + org.clojure + google-closure-library + 0.0-20250515-f04e4c0e + + + com.cognitect + transit-java + 1.0.362 + + + org.clojure + clojure + + + + + org.clojure + tools.reader + 1.3.6 + + + org.clojure + test.check + 1.1.1 + test + + + org.clojure + clojure + + + + - - scm:git:git://github.com/clojure/clojurescript.git - scm:git:git@github.com:clojure/clojurescript.git - https://github.com/clojure/clojurescript - + + Aaron Bedra + Alan Dipert + Alex Dowad + Alan Malloy + Alen Ribic + Alex Redington + Ambrose Bonnaire-Sergeant + Andrew Rosa + Antonin Hildebrand + Ben Moss + Benjamin Meyer + Bo Jeanes + Bobby Calderwood + Brandon Bloom + Brenton Ashworth + Brian Jenkins + Brian Kim + Brian Taylor + Bruce Hauman + Chad Taylor + Chas Emerick + Charles Duffy + Chris Granger + Chris Pickard + Chris Houser + Chris Truter + Christopher Redinger + Colin Jones + Creighton Kirkendall + David Nolen + Daniel Compton + Daniel Skarda + Dave Sann + Devin Walters + Dylan Butman + Edward Tsech + Eric Normand + Eric Thorsen + Erik Ouchterlony + Evan Mezeske + Francis Avila + Frank Failla + Francoise De Serre + Gary Fredericks + Gary Trakhman + Herwig Hochleitner + Hubert Iwaniuk + Hugo Duncan + Immo Heikkinen + Ivan Willig + J. Pablo Fernandez + Jamie Brandon + Jeff Dik + Jess Martin + Joel Holdbrooks + Joel Martin + John Li + Jonas De Vuyst + Jonas Enlund + Jonathan Boston + Jozef Wagner + Juergen Hoetzel + Juho Teperi + Julian Eluard + Justin Tirrell + Kovas Boguta + Kevin J. Lynagh + Laszlo Toeroek + Leon Grapenthin + Luke VanderHart + Maria Geller + Martin Klepsch + Matjaz Gregoric + Max Gonzih + Max Penet + Max Veytsman + Michael Ballantyne + Michael Fogus + Michael Glaesemann + Michael Griffiths + Michael O. Church + Michał Marczyk + Michiel Borkent + Mike Fikes + Moritz Ulrich + Murphy McMahon + Nelson Morris + Nicola Mometto + Nikita Prokopov + Osbert Feng + Paul Michael Bauer + Paul deGrandis + Peter Schuck + Peter Stephens + Peter Taoussanis + Pieter van Prooijen + Raphaël Amiard + Raymond Huang + Rich Hickey + Roman Gonzalez + Roman Scherer + Rupa Shankar + Russ Olsen + Sam Umbach + Samuel Miller + Sean Grove + Sebastien Bensusan + Sean LeBron + Steven Kallstrom + Stuart Halloway + Stuart Mitchell + Stuart Sierra + Takahiro Hozumi + Thomas Heller + Thomas Scheiblauer + Tim Griesser + Timothy Pratley + Toby Crawley + Tom Hickey + Tom Jack + Tom Marble + Travis Thieman + Travis Vachon + Wilkes Joiner + Zachary Allaun + Zach Oakes + Zubair Quraishi + - - org.sonatype.oss - oss-parent - 7 - + + scm:git:git://github.com/clojure/clojurescript.git + scm:git:git@github.com:clojure/clojurescript.git + https://github.com/clojure/clojurescript + - - UTF-8 - src/clj - src/cljs - + + UTF-8 + src/main/clojure + src/main/cljs + src/main/cljs + resources + true + - - - - - org.codehaus.mojo - build-helper-maven-plugin - 1.5 - - - add-clojure-source-dirs - generate-sources - - add-source - add-resource - - - - ${clojure.source.dir} - ${cljs.source.dir} - - - - ${clojure.source.dir} - - - ${cljs.source.dir} - - - - - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.4 - - Clojure/core - - - - + + + central + https://central.sonatype.com + + + central-snapshot + https://central.sonatype.com/repository/maven-snapshots/ + + - - - sonatype-oss-release - - + - - org.apache.maven.plugins - maven-deploy-plugin - 2.7 - - true - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.4.4 - - - default-deploy - deploy - - - deploy - - - - - - https://oss.sonatype.org/ - - sonatype-nexus-staging - - + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + package + + jar + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + add-clojure-source-dirs + generate-sources + + add-source + add-resource + + + + ${clojure.source.dir} + ${cljs.source.dir} + + + + ${clojure.source.dir} + + + ${cljs.source.dir} + + + ${resources.dir} + + + + + + + + com.theoryinpractise + clojure-maven-plugin + 1.8.3 + + false + + + + clojure-compile + compile + + compile + + + true + + !cljs.vendor.bridge + + + + + + + maven-jar-plugin + 3.4.2 + + + + cljs.main + + + + + + default-jar + package + + jar + + + + **/*.clj + **/*.cljc + **/*.cljs + **/*.js + **/*.map + **/*.edn + **/*.svg + **/*.png + + + + + javadoc-jar + package + + jar + + + + ** + + javadoc + + + + + + maven-assembly-plugin + 3.7.1 + + + aot-jar + package + + single + + + false + + src/assembly/aot.xml + + + + + slim-jar + package + + single + + + + src/assembly/slim.xml + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.1.0 + + + --pinentry-mode + loopback + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 21 + 21 + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 + true + + central + true + + + - - - + + + + + sign + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000..b524959cc6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,447 @@ + + 4.0.0 + org.clojure + clojurescript + + 1.12.41 + jar + ClojureScript + + https://github.com/clojure/clojurescript + + + ClojureScript compiler and core runtime library. + + + + + Eclipse Public License 1.0 + http://opensource.org/licenses/eclipse-1.0.php + repo + + + + + + org.clojure + clojure + 1.10.0 + + + com.google.javascript + closure-compiler + v20250402 + + + org.clojure + google-closure-library + 0.0-20250515-f04e4c0e + + + com.cognitect + transit-java + 1.0.362 + + + org.clojure + clojure + + + + + org.clojure + tools.reader + 1.3.6 + + + org.clojure + test.check + 1.1.1 + test + + + org.clojure + clojure + + + + + + + Aaron Bedra + Alan Dipert + Alex Dowad + Alan Malloy + Alen Ribic + Alex Redington + Ambrose Bonnaire-Sergeant + Andrew Rosa + Antonin Hildebrand + Ben Moss + Benjamin Meyer + Bo Jeanes + Bobby Calderwood + Brandon Bloom + Brenton Ashworth + Brian Jenkins + Brian Kim + Brian Taylor + Bruce Hauman + Chad Taylor + Chas Emerick + Charles Duffy + Chris Granger + Chris Pickard + Chris Houser + Chris Truter + Christopher Redinger + Colin Jones + Creighton Kirkendall + David Nolen + Daniel Compton + Daniel Skarda + Dave Sann + Devin Walters + Dylan Butman + Edward Tsech + Eric Normand + Eric Thorsen + Erik Ouchterlony + Evan Mezeske + Francis Avila + Frank Failla + Francoise De Serre + Gary Fredericks + Gary Trakhman + Herwig Hochleitner + Hubert Iwaniuk + Hugo Duncan + Immo Heikkinen + Ivan Willig + J. Pablo Fernandez + Jamie Brandon + Jeff Dik + Jess Martin + Joel Holdbrooks + Joel Martin + John Li + Jonas De Vuyst + Jonas Enlund + Jonathan Boston + Jozef Wagner + Juergen Hoetzel + Juho Teperi + Julian Eluard + Justin Tirrell + Kovas Boguta + Kevin J. Lynagh + Laszlo Toeroek + Leon Grapenthin + Luke VanderHart + Maria Geller + Martin Klepsch + Matjaz Gregoric + Max Gonzih + Max Penet + Max Veytsman + Michael Ballantyne + Michael Fogus + Michael Glaesemann + Michael Griffiths + Michael O. Church + Michał Marczyk + Michiel Borkent + Mike Fikes + Moritz Ulrich + Murphy McMahon + Nelson Morris + Nicola Mometto + Nikita Prokopov + Osbert Feng + Paul Michael Bauer + Paul deGrandis + Peter Schuck + Peter Stephens + Peter Taoussanis + Pieter van Prooijen + Raphaël Amiard + Raymond Huang + Rich Hickey + Roman Gonzalez + Roman Scherer + Rupa Shankar + Russ Olsen + Sam Umbach + Samuel Miller + Sean Grove + Sebastien Bensusan + Sean LeBron + Steven Kallstrom + Stuart Halloway + Stuart Mitchell + Stuart Sierra + Takahiro Hozumi + Thomas Heller + Thomas Scheiblauer + Tim Griesser + Timothy Pratley + Toby Crawley + Tom Hickey + Tom Jack + Tom Marble + Travis Thieman + Travis Vachon + Wilkes Joiner + Zachary Allaun + Zach Oakes + Zubair Quraishi + + + + scm:git:git://github.com/clojure/clojurescript.git + scm:git:git@github.com:clojure/clojurescript.git + https://github.com/clojure/clojurescript + + + + UTF-8 + src/main/clojure + src/main/cljs + src/main/cljs + resources + true + + + + + central + https://central.sonatype.com + + + central-snapshot + https://central.sonatype.com/repository/maven-snapshots/ + + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + package + + jar + + + + + + + + org.codehaus.mojo + build-helper-maven-plugin + 3.0.0 + + + add-clojure-source-dirs + generate-sources + + add-source + add-resource + + + + ${clojure.source.dir} + ${cljs.source.dir} + + + + ${clojure.source.dir} + + + ${cljs.source.dir} + + + ${resources.dir} + + + + + + + + com.theoryinpractise + clojure-maven-plugin + 1.8.3 + + false + + + + clojure-compile + compile + + compile + + + true + + !cljs.vendor.bridge + + + + + + + maven-jar-plugin + 3.4.2 + + + + cljs.main + + + + + + default-jar + package + + jar + + + + **/*.clj + **/*.cljc + **/*.cljs + **/*.js + **/*.map + **/*.edn + **/*.svg + **/*.png + + + + + javadoc-jar + package + + jar + + + + ** + + javadoc + + + + + + maven-assembly-plugin + 3.7.1 + + + aot-jar + package + + single + + + false + + src/assembly/aot.xml + + + + + slim-jar + package + + single + + + + src/assembly/slim.xml + + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.1.0 + + + --pinentry-mode + loopback + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 21 + 21 + + + + + + org.apache.maven.plugins + maven-release-plugin + 2.5.3 + + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.7.0 + true + + central + true + + + + + + + + + sign + + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + + + + + + diff --git a/project.clj b/project.clj index d80ceeff2b..0bba1a4896 100644 --- a/project.clj +++ b/project.clj @@ -1,21 +1,25 @@ (defproject org.clojure/clojurescript "0.0-SNAPSHOT" :description "ClojureScript compiler and core runtime library" - :parent [org.clojure/pom.contrib "0.1.2"] + :parent [org.clojure/pom.contrib "1.3.0"] :url "https://github.com/clojure/clojurescript" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} - :jvm-opts ^:replace ["-Xmx512m" "-server"] - :source-paths ["src/clj"] - :resource-paths ["src/cljs"] - :test-paths ["test/clj"] - :dependencies [[org.clojure/clojure "1.5.1"] - [org.clojure/data.json "0.2.3"] - [org.clojure/tools.reader "0.8.3"] - [org.clojure/google-closure-library "0.0-20130212-95c19e7f0f5f"] - [com.google.javascript/closure-compiler "v20131014"] - [org.mozilla/rhino "1.7R4"]] - :profiles {:1.5 {:dependencies [[org.clojure/clojure "1.5.1"]]} - :1.6 {:dependencies [[org.clojure/clojure "1.6.0-master-SNAPSHOT"]]}} + :jvm-opts ^:replace ["-Dclojure.compiler.direct-linking=true" "-Xmx512m" "-server"] + :source-paths ["src/main/clojure" "src/main/cljs"] + :resource-paths ["src/main/cljs" "resources"] + :test-paths ["src/test/clojure" "src/test/cljs" "src/test/self" "src/test/cljs_build" "src/test/cljs_cp"] + :dependencies [[org.clojure/clojure "1.10.0"] + [org.clojure/spec.alpha "0.1.143"] + [org.clojure/core.specs.alpha "0.1.24"] + [org.clojure/tools.reader "1.3.6"] + [org.clojure/test.check "1.1.1" :scope "test"] + [com.cognitect/transit-java "1.0.362"] + [org.clojure/google-closure-library "0.0-20250515-f04e4c0e"] + [com.google.javascript/closure-compiler "v20250820"]] + :profiles {:1.6 {:dependencies [[org.clojure/clojure "1.6.0"]]} + :uberjar {:aot :all :main cljs.main} + :closure-snapshot {:dependencies [[com.google.javascript/closure-compiler-unshaded "1.0-SNAPSHOT"]]}} :aliases {"test-all" ["with-profile" "test,1.5:test,1.6" "test"] "check-all" ["with-profile" "1.5:1.6" "check"]} - :min-lein-version "2.0.0") + :min-lein-version "2.0.0" + :repositories {"sonatype-snapshot" {:url "https://oss.sonatype.org/content/repositories/snapshots"}}) diff --git a/resources/cljs-logo-icon-32.png b/resources/cljs-logo-icon-32.png new file mode 100644 index 0000000000..16d54958d2 Binary files /dev/null and b/resources/cljs-logo-icon-32.png differ diff --git a/resources/cljs-logo.svg b/resources/cljs-logo.svg new file mode 100644 index 0000000000..714dad59bd --- /dev/null +++ b/resources/cljs-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/resources/lite_test.edn b/resources/lite_test.edn new file mode 100644 index 0000000000..44508575d6 --- /dev/null +++ b/resources/lite_test.edn @@ -0,0 +1,28 @@ +{:optimizations :advanced + :main lite-test-runner + :output-to "builds/out-lite/lite-test.js" + :output-dir "builds/out-lite" + :output-wrapper true + :verbose true + :compiler-stats true + :parallel-build true + :npm-deps {:lodash "4.17.4"} + :closure-warnings {:non-standard-jsdoc :off :global-this :off} + :install-deps true + :language-out :es5 + :foreign-libs + [{:file "src/test/cljs/calculator_global.js" + :provides ["calculator"] + :global-exports {calculator Calculator}} + {:file "src/test/cljs/es6_dep.js" + :module-type :es6 + :provides ["es6_calc"]} + {:file "src/test/cljs/calculator.js" + :module-type :commonjs + :provides ["calculator"]} + {:file "src/test/cljs/es6_default_hello.js" + :provides ["es6_default_hello"] + :module-type :es6}] + :pseudo-names true + :pretty-print true + :lite-mode true} diff --git a/resources/self_host_test.edn b/resources/self_host_test.edn new file mode 100644 index 0000000000..abbfc16af6 --- /dev/null +++ b/resources/self_host_test.edn @@ -0,0 +1,10 @@ +{:optimizations :simple + :main self-host.test + :static-fns true + :output-to "builds/out-self/core-self-test.js" + :output-dir "builds/out-self" + :optimize-constants true + :verbose true + :compiler-stats true + :parallel-build true + :target :nodejs} \ No newline at end of file diff --git a/resources/self_parity_test.edn b/resources/self_parity_test.edn new file mode 100644 index 0000000000..2c5baffe78 --- /dev/null +++ b/resources/self_parity_test.edn @@ -0,0 +1,8 @@ +{:optimizations :none + :main self-parity.test + :language-out :es6 + :verbose true + :output-to "builds/out-self-parity/main.js" + :output-dir "builds/out-self-parity" + :cache-analysis-format :edn + :target :nodejs} diff --git a/resources/test.edn b/resources/test.edn new file mode 100644 index 0000000000..a9b40633e5 --- /dev/null +++ b/resources/test.edn @@ -0,0 +1,25 @@ +{:optimizations :advanced + :main test-runner + :output-to "builds/out-adv/core-advanced-test.js" + :output-dir "builds/out-adv" + :output-wrapper true + :verbose true + :compiler-stats true + :parallel-build true + :npm-deps {:lodash "4.17.4"} + :closure-warnings {:non-standard-jsdoc :off :global-this :off} + :install-deps true + :language-out :es6 + :foreign-libs + [{:file "src/test/cljs/calculator_global.js" + :provides ["calculator"] + :global-exports {calculator Calculator}} + {:file "src/test/cljs/es6_dep.js" + :module-type :es6 + :provides ["es6_calc"]} + {:file "src/test/cljs/calculator.js" + :module-type :commonjs + :provides ["calculator"]} + {:file "src/test/cljs/es6_default_hello.js" + :provides ["es6_default_hello"] + :module-type :es6}]} diff --git a/samples/hello-js/README.md b/samples/hello-js/README.md index 32fa6793c1..b796e53867 100644 --- a/samples/hello-js/README.md +++ b/samples/hello-js/README.md @@ -1,3 +1,5 @@ +*NOTE: this sample is now out of date. Please refer to the Quick Start* + Simple ClojureScript Project Example Using an External JavaScript Library One-time Setup diff --git a/samples/hello/README.md b/samples/hello/README.md index d54ef3467a..405ad9c82d 100644 --- a/samples/hello/README.md +++ b/samples/hello/README.md @@ -1,3 +1,5 @@ +*NOTE: this sample is now out of date. Please refer to the Quick Start* + Simple ClojureScript Project Example. One-time Setup diff --git a/samples/hello/src/hello/core.cljs b/samples/hello/src/hello/core.cljs index 96928ef3c9..67a479f3b0 100644 --- a/samples/hello/src/hello/core.cljs +++ b/samples/hello/src/hello/core.cljs @@ -1,5 +1,6 @@ (ns hello.core - (:require [hello.foo.bar :as bar])) + (:require [hello.foo.bar :as bar] + [cljs.reader :as reader])) (defn ^{:export greet} greet [n] (str "Hello " n)) diff --git a/samples/repl/README.md b/samples/repl/README.md index 808bf15561..8af28b93c8 100644 --- a/samples/repl/README.md +++ b/samples/repl/README.md @@ -1,3 +1,6 @@ +*NOTE: this sample is now out of date. Please refer to the Quick +Start* + # ClojureScript REPL Examples The ClojureScript REPL has been updated to work with multiple @@ -14,15 +17,6 @@ There are currently four steps in starting a ClojureScript REPL. 3. create a new evaluation environment 4. start the REPL with the created environment -## Evaluating with Rhino - -```clj -(require '[cljs.repl :as repl]) -(require '[cljs.repl.rhino :as rhino]) -(def env (rhino/repl-env)) -(repl/repl env) -``` - ## Evaluating in the Browser A browser-connected REPL works in much the same way as a normal REPL: @@ -57,9 +51,9 @@ cd samples/repl and evaluate the following forms: ```clj -(use 'cljs.closure) +(require '[cljs.closure :as cljsc]) (def opts {:output-to "main.js" :output-dir "out"}) -(build "src" opts) +(cljsc/build "src" opts) ``` ### Starting the REPL and connecting to the browser diff --git a/samples/repl/repl.clj b/samples/repl/repl.clj new file mode 100644 index 0000000000..d5a3d52f2b --- /dev/null +++ b/samples/repl/repl.clj @@ -0,0 +1,10 @@ +(require '[cljs.closure :as cljsc]) +(require '[cljs.repl :as repl]) +(require '[cljs.repl.browser :as brepl]) + +(cljsc/build "src" + {:output-to "main.js" + :verbose true}) + +(repl/repl (brepl/repl-env) + :repl-verbose true) \ No newline at end of file diff --git a/samples/repl/src/repl/foo.cljs b/samples/repl/src/repl/foo.cljs new file mode 100644 index 0000000000..d3de63014d --- /dev/null +++ b/samples/repl/src/repl/foo.cljs @@ -0,0 +1,4 @@ +(ns repl.foo) + +(defn bar [a b] + (+ a b)) \ No newline at end of file diff --git a/samples/repl/src/repl/test.cljs b/samples/repl/src/repl/test.cljs index 065f365849..0853715faa 100644 --- a/samples/repl/src/repl/test.cljs +++ b/samples/repl/src/repl/test.cljs @@ -8,9 +8,11 @@ (ns repl.test (:require [clojure.browser.repl :as repl] - [clojure.reflect :as reflect])) + ;[clojure.reflect :as reflect] + )) -(repl/connect "http://localhost:9000/repl") +(defonce conn + (repl/connect "http://localhost:9000/repl")) (comment diff --git a/samples/string-requires-npm-deps/.gitignore b/samples/string-requires-npm-deps/.gitignore new file mode 100644 index 0000000000..89f9ac04aa --- /dev/null +++ b/samples/string-requires-npm-deps/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/samples/string-requires-npm-deps/README.md b/samples/string-requires-npm-deps/README.md new file mode 100644 index 0000000000..010d79ecde --- /dev/null +++ b/samples/string-requires-npm-deps/README.md @@ -0,0 +1,13 @@ +# ClojureScript string-based requires demo + +Running: + +1. At the root of the ClojureScript repo, run `./script/bootstrap` +2. Switch into this directory: `cd samples/string-requires-npm-deps` +3. Build the project: + +``` shell +$ java -cp `ls ../../lib/*.jar | paste -sd ":" -`:../../src/main/cljs:../../src/main/clojure:src clojure.main build.clj +``` + +4. run the generated JavaScript with `node out/main.js` diff --git a/samples/string-requires-npm-deps/build.clj b/samples/string-requires-npm-deps/build.clj new file mode 100644 index 0000000000..4b6ddc4014 --- /dev/null +++ b/samples/string-requires-npm-deps/build.clj @@ -0,0 +1,13 @@ +(require '[cljs.build.api :as b]) + +(b/build "src" + {:output-dir "out" + :output-to "out/main.js" + :optimizations :none + :verbose true + :target :nodejs + :compiler-stats true + :main 'foo.core + :npm-deps {:react "15.6.1" + :react-dom "15.6.1"} + :closure-warnings {:non-standard-jsdoc :off}}) diff --git a/samples/string-requires-npm-deps/package.json b/samples/string-requires-npm-deps/package.json new file mode 100644 index 0000000000..97a37b05b7 --- /dev/null +++ b/samples/string-requires-npm-deps/package.json @@ -0,0 +1,3 @@ +{ + "name": "string-requires-npm-deps" +} diff --git a/samples/string-requires-npm-deps/src/foo/core.cljs b/samples/string-requires-npm-deps/src/foo/core.cljs new file mode 100644 index 0000000000..43cd8328e1 --- /dev/null +++ b/samples/string-requires-npm-deps/src/foo/core.cljs @@ -0,0 +1,10 @@ +(ns foo.core + (:require [react :refer [createElement]] + ["react-dom/server" :as rds :refer [renderToString]] + "create-react-class")) + +(enable-console-print!) + +(println "resolves single exports" create-react-class) + +(println (renderToString (createElement "div" nil "Hello World!"))) diff --git a/samples/twitterbuzz/README.md b/samples/twitterbuzz/README.md index 3b9fd2eda6..0eff7a65bf 100644 --- a/samples/twitterbuzz/README.md +++ b/samples/twitterbuzz/README.md @@ -1,3 +1,5 @@ +*NOTE: this sample is now out of date. Please refer to the Quick Start* + # ClojureScript "TwitterBuzz" Demo ## One-time Setup diff --git a/script/aot.clj b/script/aot.clj new file mode 100644 index 0000000000..1f2f5e80f9 --- /dev/null +++ b/script/aot.clj @@ -0,0 +1,2 @@ +(require '[cljs.closure :as cljsc]) +(cljsc/aot-cache-core) diff --git a/script/aot_core b/script/aot_core new file mode 100755 index 0000000000..350c5a29c9 --- /dev/null +++ b/script/aot_core @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +set -e + +if [[ -z "$CLJS_SCRIPT_QUIET" ]]; then + set -x +fi + +FILE_SEP='/' +PATH_SEP=':' +OS_ID=`uname | tr [:upper:] [:lower:]` +CLJS_SCRIPT_MVN_OPTS=${CLJS_SCRIPT_MVN_OPTS:-""} + +if [[ $OS_ID == *mingw* ]] +then + echo "MINGW detected" + # Refer to http://www.mingw.org/wiki/Posix_path_conversion + FILE_SEP='//' + PATH_SEP=';' +fi + +CP_FILE=`mktemp /tmp/cljs_cp.txt.XXXXXXXXXXX` + +mvn -B -f pom.template.xml dependency:build-classpath -Dmdep.outputFile=$CP_FILE -Dmdep.fileSeparator=$FILE_SEP -Dmdep.pathSeparator=$PATH_SEP $CLJS_SCRIPT_MVN_OPTS + +CLJS_CP=`cat $CP_FILE` + +# For Hudson server +if [ "$HUDSON" = "true" ]; then + $JAVA_HOME/bin/java -server -cp "$CLJS_CP""$PATH_SEP""src/main/clojure""$PATH_SEP""src/main/cljs" clojure.main script/aot.clj +else + java -server -cp "$CLJS_CP""$PATH_SEP""src/main/clojure""$PATH_SEP""src/main/cljs" clojure.main script/aot.clj +fi diff --git a/script/benchmark b/script/benchmark index 6ce13b3429..c1ae2d2c87 100755 --- a/script/benchmark +++ b/script/benchmark @@ -1,30 +1,48 @@ #!/bin/sh -rm -rf out -mkdir -p out +rm -rf builds/out-adv-bench +mkdir -p builds/out-adv-bench -#bin/cljsc benchmark > out/core-benchmark.js -bin/cljsc benchmark "{:optimizations :advanced :output-wrapper true}" >out/core-advanced-benchmark.js +bin/cljsc benchmark "{:optimizations :advanced :output-wrapper true :compiler-stats true :verbose true :output-dir \"builds/out-adv-bench\"}" > builds/out-adv-bench/core-advanced-benchmark.js if [ "$V8_HOME" = "" ]; then echo "V8_HOME not set, skipping V8 benchmarks" else echo "Benchmarking with V8" - "${V8_HOME}/d8" out/core-advanced-benchmark.js - # TODO: figure out path problem when not in advanced mode - # "${V8_HOME}/d8" out/core-benchmark.js + "${V8_HOME}/d8" builds/out-adv-bench/core-advanced-benchmark.js fi if [ "$SPIDERMONKEY_HOME" = "" ]; then echo "SPIDERMONKEY_HOME not set, skipping SpiderMonkey benchmarks" else echo "Benchmarking with SpiderMonkey" - "${SPIDERMONKEY_HOME}/js" -m -n -a -f out/core-advanced-benchmark.js + "${SPIDERMONKEY_HOME}/js" -f builds/out-adv-bench/core-advanced-benchmark.js fi -if [ "$JSC_HOME" = "" ]; then - echo "JSC_HOME not set, skipping JavaScriptCore benchmarks" +if ! hash jsc 2>/dev/null; then + echo "jsc not on path, skipping JavaScriptCore benchmarks" else echo "Benchmarking with JavaScriptCore" - "${JSC_HOME}/jsc" -f out/core-advanced-benchmark.js + jsc -f builds/out-adv-bench/core-advanced-benchmark.js +fi + +if [ "$NASHORN_HOME" = "" ]; then + echo "NASHORN_HOME not set, skipping Nashorn benchmarks" +else + echo "Benchmarking with Nashorn" + "${NASHORN_HOME}/jjs" builds/out-adv-bench/core-advanced-benchmark.js +fi + +if [ "$CHAKRACORE_HOME" = "" ]; then + echo "CHAKRACORE_HOME not set, skipping ChakraCore benchmarks" +else + echo "Benchmarking with ChakraCore" + "${CHAKRACORE_HOME}/ch" builds/out-adv-bench/core-advanced-benchmark.js +fi + +if [ "$GRAALVM_HOME" = "" ]; then + echo "GRAALVM_HOME not set, skipping GraalVM benchmarks" +else + echo "Benchmarking with GraalVM" + "${GRAALVM_HOME}/js" builds/out-adv-bench/core-advanced-benchmark.js fi diff --git a/script/bootstrap b/script/bootstrap index d88c66bea3..464cc08da6 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -2,31 +2,42 @@ set -e -CLOJURE_RELEASE="1.5.1" -DJSON_RELEASE="0.2.3" -GCLOSURE_LIB_RELEASE="0.0-20130212-95c19e7f0f5f" -RHINO_RELEASE="1_7R3" -TREADER_RELEASE="0.8.3" +CLOJURE_RELEASE="1.9.0" +SPEC_ALPHA_RELEASE="0.1.143" +CORE_SPECS_ALPHA_RELEASE="0.1.24" +CLOSURE_RELEASE="20250820" +GCLOSURE_LIB_RELEASE="0.0-20250515-f04e4c0e" +TREADER_RELEASE="1.3.6" +TEST_CHECK_RELEASE="1.1.1" + +# check dependencies +curl -V >/dev/null || { echo "cURL is missing, or not on your system path."; exit 1; } +unzip -v >/dev/null || { echo "The 'unzip' utility is missing, or not on your system path."; exit 1; } + +rm -rf lib mkdir -p lib echo "Fetching Clojure..." -curl -O -s http://repo1.maven.org/maven2/org/clojure/clojure/$CLOJURE_RELEASE/clojure-$CLOJURE_RELEASE.zip -unzip -qu clojure-$CLOJURE_RELEASE.zip -echo "Copying clojure-$CLOJURE_RELEASE/clojure-$CLOJURE_RELEASE.jar to lib/clojure.jar..." -cp clojure-$CLOJURE_RELEASE/clojure-$CLOJURE_RELEASE.jar lib/clojure.jar - -echo "Cleaning up Clojure directory..." -rm -rf clojure-$CLOJURE_RELEASE/ -echo "Cleaning up Clojure archive..." -rm clojure-$CLOJURE_RELEASE.zip - -echo "Fetching data.json..." -curl -O -s http://repo1.maven.org/maven2/org/clojure/data.json/$DJSON_RELEASE/data.json-$DJSON_RELEASE.jar -echo "Copying data.json-$DJSON_RELEASE.jar to lib/data.json-$DJSON_RELEASE.jar..." -cp data.json-$DJSON_RELEASE.jar lib/data.json-$DJSON_RELEASE.jar -echo "Cleaning up data.json..." -rm data.json-$DJSON_RELEASE.jar +curl -O -s https://repo1.maven.org/maven2/org/clojure/clojure/$CLOJURE_RELEASE/clojure-$CLOJURE_RELEASE.jar || { echo "Download failed."; exit 1; } +echo "Copying clojure-$CLOJURE_RELEASE.jar to lib/clojure.jar..." +cp clojure-$CLOJURE_RELEASE.jar lib/clojure.jar +echo "Cleaning up Clojure jar..." +rm clojure-$CLOJURE_RELEASE.jar + +echo "Fetching specs.alpha...." +curl --retry 3 -O -s https://repo1.maven.org/maven2/org/clojure/spec.alpha/$SPEC_ALPHA_RELEASE/spec.alpha-$SPEC_ALPHA_RELEASE.jar || { echo "Download failed."; exit 1; } +echo "Copying spec.alpha-$SPEC_ALPHA_RELEASE/spec.alpha-$SPEC_ALPHA_RELEASE.jar to lib/spec.alpha.jar..." +cp spec.alpha-$SPEC_ALPHA_RELEASE.jar lib/spec.alpha-$SPEC_ALPHA_RELEASE.jar +echo "Cleaning up spec.alpha..." +rm spec.alpha-$SPEC_ALPHA_RELEASE.jar + +echo "Fetching core.specs.alpha...." +curl --retry 3 -O -s https://repo1.maven.org/maven2/org/clojure/core.specs.alpha/$CORE_SPECS_ALPHA_RELEASE/core.specs.alpha-$CORE_SPECS_ALPHA_RELEASE.jar || { echo "Download failed."; exit 1; } +echo "Copying core.specs.alpha-$CORE_SPECS_ALPHA_RELEASE/core.specs.alpha-$CORE_SPECS_ALPHA_RELEASE.jar to lib/core.specs.alpha.jar..." +cp core.specs.alpha-$CORE_SPECS_ALPHA_RELEASE.jar lib/core.specs.alpha-$CORE_SPECS_ALPHA_RELEASE.jar +echo "Cleaning up core.specs.alpha..." +rm core.specs.alpha-$CORE_SPECS_ALPHA_RELEASE.jar echo "Fetching Google Closure library..." mkdir -p closure/library @@ -34,63 +45,72 @@ cd closure/library if [ "$1" = "--closure-library-head" ] ; then echo "Building against HEAD of Google Closure library..." - # Check if svn present - type svn >/dev/null 2>&1 || { echo >&2 "Need svn command to checkout HEAD of Google Closure library. Aborting."; exit 1; } - # Existing checkout? - if svn info --non-interactive >/dev/null 2>&1; then + if [ -d closure-library ] ; then echo "Updating Google Closure library from HEAD..." - svn update -q --non-interactive + cd closure-library + git pull + cd .. else echo "Checking out HEAD of Google Closure library..." rm -rf * - svn checkout -q --non-interactive http://closure-library.googlecode.com/svn/trunk/ ./ + git clone https://github.com/google/closure-library fi else - curl -O -s http://repo1.maven.org/maven2/org/clojure/google-closure-library/$GCLOSURE_LIB_RELEASE/google-closure-library-$GCLOSURE_LIB_RELEASE.jar + curl --retry 3 -O -s https://repo1.maven.org/maven2/org/clojure/google-closure-library/$GCLOSURE_LIB_RELEASE/google-closure-library-$GCLOSURE_LIB_RELEASE.jar || { echo "Download failed."; exit 1; } cp google-closure-library-$GCLOSURE_LIB_RELEASE.jar ../../lib/google-closure-library-$GCLOSURE_LIB_RELEASE.jar rm google-closure-library-$GCLOSURE_LIB_RELEASE.jar echo "Fetching Google Closure third party library..." - curl -O -s http://repo1.maven.org/maven2/org/clojure/google-closure-library-third-party/$GCLOSURE_LIB_RELEASE/google-closure-library-third-party-$GCLOSURE_LIB_RELEASE.jar + curl --retry 3 -O -s https://repo1.maven.org/maven2/org/clojure/google-closure-library-third-party/$GCLOSURE_LIB_RELEASE/google-closure-library-third-party-$GCLOSURE_LIB_RELEASE.jar || { echo "Download failed."; exit 1; } cp google-closure-library-third-party-$GCLOSURE_LIB_RELEASE.jar ../../lib/google-closure-library-third-party-$GCLOSURE_LIB_RELEASE.jar rm google-closure-library-third-party-$GCLOSURE_LIB_RELEASE.jar fi -cd .. +cd ../.. echo "Fetching Google Closure compiler..." -mkdir -p compiler -cd compiler -curl -O -s http://dl.google.com/closure-compiler/compiler-latest.zip -unzip -qu compiler-latest.zip -echo "Cleaning up Google Closure compiler archive..." -rm compiler-latest.zip -cd ../.. +if [ "$1" = "--closure-compiler-snapshot" ] ; then + curl --retry 3 -O -s https://oss.sonatype.org/content/repositories/snapshots/com/google/javascript/closure-compiler/1.0-SNAPSHOT/maven-metadata.xml || { echo "Download failed."; exit 1; } + CC_JAR_VERSION=`grep value maven-metadata.xml | head -1 | awk -F '[<>]' '/value/{print $3}'` + CC_JAR_NAME=closure-compiler-$CC_JAR_VERSION.jar + curl --retry 3 -O -s https://oss.sonatype.org/content/repositories/snapshots/com/google/javascript/closure-compiler/1.0-SNAPSHOT/$CC_JAR_NAME || { echo "Download failed."; exit 1; } + cp $CC_JAR_NAME lib/$CC_JAR_NAME + echo "Cleaning up closure-compiler.jar..." + rm $CC_JAR_NAME + rm maven-metadata.xml +else + curl --retry 3 -O -s https://repo1.maven.org/maven2/com/google/javascript/closure-compiler/v$CLOSURE_RELEASE/closure-compiler-v$CLOSURE_RELEASE.jar || { echo "Download failed."; exit 1; } + cp closure-compiler-v$CLOSURE_RELEASE.jar lib/closure-compiler-v$CLOSURE_RELEASE.jar + echo "Cleaning up closure-compiler.jar..." + rm closure-compiler-v$CLOSURE_RELEASE.jar +fi if [ "$1" = "--closure-library-head" ] ; then - echo "Building lib/goog.jar..." - echo "jar cf ./lib/goog.jar -C closure/library/closure/ goog" - jar cf ./lib/goog.jar -C closure/library/closure/ goog + echo "Building lib/google-closure-library-HEAD.jar..." + # Set up the third-party deps paths to match what we would get when depending on a release + sed -e 's/..\/..\/third_party\/closure\/goog\///' closure/library/closure-library/closure/goog/deps.js > revised-deps.js + cp closure/library/closure-library/closure/goog/deps.js orig-deps.js + mv revised-deps.js closure/library/closure-library/closure/goog/deps.js + jar cf ./lib/google-closure-library-HEAD.jar -C closure/library/closure-library/closure/ goog + mv orig-deps.js closure/library/closure-library/closure/goog/deps.js + echo "Building lib/google-closure-library-third-party-HEAD.jar..." + mv closure/library/closure-library/third_party/closure/goog/deps.js orig-deps.js + mv closure/library/closure-library/third_party/closure/goog/base.js orig-base.js + jar cf ./lib/google-closure-library-third-party-HEAD.jar -C closure/library/closure-library/third_party/closure/ goog + mv orig-base.js closure/library/closure-library/third_party/closure/goog/base.js + mv orig-deps.js closure/library/closure-library/third_party/closure/goog/deps.js fi -echo "Fetching Rhino..." -curl -O -s http://ftp.mozilla.org/pub/mozilla.org/js/rhino$RHINO_RELEASE.zip -unzip -qu rhino$RHINO_RELEASE.zip -echo "Copying rhino$RHINO_RELEASE/js.jar to lib/js.jar..." -cp rhino$RHINO_RELEASE/js.jar lib/js.jar -echo "Cleaning up Rhino directory..." -rm -rf rhino$RHINO_RELEASE/ -echo "Cleaning up Rhino archive..." -rm rhino$RHINO_RELEASE.zip - -echo "Copying closure/compiler/compiler.jar to lib/compiler.jar" -cp closure/compiler/compiler.jar lib - echo "Fetching tools.reader $TREADER_RELEASE ..." -curl -O -s http://repo1.maven.org/maven2/org/clojure/tools.reader/$TREADER_RELEASE/tools.reader-$TREADER_RELEASE.jar +curl --retry 3 -O -s https://repo1.maven.org/maven2/org/clojure/tools.reader/$TREADER_RELEASE/tools.reader-$TREADER_RELEASE.jar || { echo "Download failed."; exit 1; } echo "Moving tools.reader.jar to lib/tools.reader.jar" - mv tools.reader-$TREADER_RELEASE.jar lib/tools.reader-$TREADER_RELEASE.jar +echo "Fetching test.check $TEST_CHECK_RELEASE ..." +curl --retry 3 -O -s https://repo1.maven.org/maven2/org/clojure/test.check/$TEST_CHECK_RELEASE/test.check-$TEST_CHECK_RELEASE.jar || { echo "Download failed."; exit 1; } + +echo "Moving test.check.jar to lib/test.check.jar" +mv test.check-$TEST_CHECK_RELEASE.jar lib/test.check.jar + echo "[Bootstrap Completed]" diff --git a/script/bootstrap.ps1 b/script/bootstrap.ps1 new file mode 100644 index 0000000000..d0091dc20e --- /dev/null +++ b/script/bootstrap.ps1 @@ -0,0 +1,142 @@ +$ErrorActionPreference = "Stop" +$root = Resolve-Path "$PSScriptRoot\.." +$shell = New-Object -com shell.application + +# Read white listed dependency version info from the /bin/sh script and store in variables +Get-Content $root\script\bootstrap | + Where-Object { $_ -match '^\s*(\w+)\s*=\s*\"([^\"]*)\"\s*$' } | + Where-Object { $matches[1] -in "CLOJURE_RELEASE", "SPEC_ALPHA_RELEASE", + "CORE_SPECS_ALPHA_RELEASE", "CLOSURE_RELEASE", "DJSON_RELEASE", + "TRANSIT_RELEASE", "GCLOSURE_LIB_RELEASE", "RHINO_RELEASE", + "TREADER_RELEASE", "TEST_CHECK_RELEASE" } | + Foreach-Object { New-Variable $matches[1] $matches[2] -Scope private } + +function Get-WebResource($url, $dstPath) { + Write-Verbose "Downloading '$url' -> '$dstPath'" + Invoke-RestMethod $url -OutFile $dstPath +} + +function Expand-ZipFile($srcPath, $dstDir, $items) { + Write-Verbose "Unzipping '$srcPath'" + + function Get-ShellFolder($dir) { + $folder = $shell.NameSpace($dir) + if($folder -eq $null) { + throw "Failed to bind to folder '$dir'" + } + $folder + } + + function Copy-ShellItem([Parameter(ValueFromPipeline=$true)] $src) { + process { + Write-Verbose "Expanding '$($src.Path)' -> '$dstDir'" + $dstFolder.CopyHere($src, 4 + 16 + 1024) + } + } + + function Parse-ShellItem([Parameter(ValueFromPipeline=$true)] $name) { + process { + $x = $srcFolder.ParseName($name) + if($x -eq $null) { + throw "Failed fo find item '$name' in zip file '$srcPath'" + } + $x + } + } + + $srcFolder = Get-ShellFolder($srcPath) + $dstFolder = Get-ShellFolder($dstDir) + + if($items -ne $null) { + $items | Parse-ShellItem | Copy-ShellItem + } + else { + $srcFolder.Items() | Copy-ShellItem + } +} + +function Move-File($srcPath, $dstPath) { + Delete-File $dstPath + Write-Verbose "Moving '$srcPath' -> '$dstPath'" + Move-Item $srcPath $dstPath +} + +function Copy-File($srcPath, $dstPath) { + Delete-File $dstPath + Write-Verbose "Copying '$srcPath' -> '$dstPath'" + Copy-Item $srcPath $dstPath +} + +function Delete-File([Parameter(ValueFromPipeline=$true)] $path) +{ + process { + if(Test-Path $path) { + Write-Verbose "Deleting '$path'" + Remove-Item $path -Recurse + } + } +} + +function Make-Dir($dir) { + if(!(Test-Path $dir -Type Container)) { + Write-Verbose "Making directory '$dir'" + New-Item $dir -ItemType Directory | Out-Null + } +} + +Make-Dir $root\lib +Make-Dir $root\closure\library +Make-Dir $root\closure\compiler + +Write-Host "Fetching Clojure..." +Get-WebResource ` + https://repo1.maven.org/maven2/org/clojure/clojure/$CLOJURE_RELEASE/clojure-$CLOJURE_RELEASE.jar ` + $root\lib\clojure-$CLOJURE_RELEASE.jar + +Write-Host "Fetching specs.alpha...." +Get-WebResource ` + https://repo1.maven.org/maven2/org/clojure/spec.alpha/$SPEC_ALPHA_RELEASE/spec.alpha-$SPEC_ALPHA_RELEASE.jar ` + $root\lib\spec.alpha-$SPEC_ALPHA_RELEASE.jar + +Write-Host "Fetching core.specs.alpha...." +Get-WebResource ` + https://repo1.maven.org/maven2/org/clojure/core.specs.alpha/$CORE_SPECS_ALPHA_RELEASE/core.specs.alpha-$CORE_SPECS_ALPHA_RELEASE.jar ` + $root\lib\core.specs.alpha-$CORE_SPECS_ALPHA_RELEASE.jar + +Write-Host "Fetching data.json..." +Get-WebResource ` + https://repo1.maven.org/maven2/org/clojure/data.json/$DJSON_RELEASE/data.json-$DJSON_RELEASE.jar ` + $root\lib\data.json-$DJSON_RELEASE.jar + +Write-Host "Fetching transit-clj..." +Get-WebResource ` + https://repo1.maven.org/maven2/com/cognitect/transit-clj/$TRANSIT_RELEASE/transit-clj-$TRANSIT_RELEASE.jar ` + $root\lib\transit-clj-$TRANSIT_RELEASE.jar + +# TODO: Implement Closure SVN support +Write-Host "Fetching Google Closure library..." +Get-WebResource ` + https://repo1.maven.org/maven2/org/clojure/google-closure-library/$GCLOSURE_LIB_RELEASE/google-closure-library-$GCLOSURE_LIB_RELEASE.jar ` + $root\lib\google-closure-library-$GCLOSURE_LIB_RELEASE.jar +Get-WebResource ` + https://repo1.maven.org/maven2/org/clojure/google-closure-library-third-party/$GCLOSURE_LIB_RELEASE/google-closure-library-third-party-$GCLOSURE_LIB_RELEASE.jar ` + $root\lib\google-closure-library-third-party-$GCLOSURE_LIB_RELEASE.jar + +Write-Host "Fetching Google Closure compiler..." +Get-WebResource ` + https://repo1.maven.org/maven2/com/google/javascript/closure-compiler/v$CLOSURE_RELEASE/closure-compiler-v$CLOSURE_RELEASE.jar ` + $root\closure-compiler-v$CLOSURE_RELEASE.jar +Copy-File $root\closure-compiler-v$CLOSURE_RELEASE.jar $root\lib\compiler.jar +Delete-File $root\closure-compiler-v$CLOSURE_RELEASE.jar + +Write-Host "Fetching tools.reader $TREADER_RELEASE ..." +Get-WebResource ` + https://repo1.maven.org/maven2/org/clojure/tools.reader/$TREADER_RELEASE/tools.reader-$TREADER_RELEASE.jar ` + $root\lib\tools.reader-$TREADER_RELEASE.jar + +Write-Host "Fetching test.check $TEST_CHECK_RELEASE ..." +Get-WebResource ` + https://repo1.maven.org/maven2/org/clojure/test.check/$TEST_CHECK_RELEASE/test.check-$TEST_CHECK_RELEASE.jar ` + $root\lib\test.check-$TEST_CHECK_RELEASE.jar + +Write-Host "[Bootstrap Completed]" diff --git a/script/browser-repl b/script/browser-repl index 3736b9b701..34b0de55ab 100755 --- a/script/browser-repl +++ b/script/browser-repl @@ -5,7 +5,7 @@ if [ "$CLOJURESCRIPT_HOME" = "" ]; then fi CLJSC_CP='' -for next in lib/*: src/clj: src/cljs: test/cljs; do +for next in lib/*: src/main/clojure: src/main/cljs: test/cljs; do CLJSC_CP=$CLJSC_CP$CLOJURESCRIPT_HOME'/'$next done diff --git a/script/browser_repl.clj b/script/browser_repl.clj new file mode 100644 index 0000000000..a67e4312cc --- /dev/null +++ b/script/browser_repl.clj @@ -0,0 +1,3 @@ +(require '[cljs.repl :as repl]) +(require '[cljs.repl.browser :as browser]) +(repl/repl (browser/repl-env)) \ No newline at end of file diff --git a/script/build b/script/build index cdaf00e72e..9ecd7f03e4 100755 --- a/script/build +++ b/script/build @@ -3,12 +3,21 @@ # This script must be run within the ClojureScript top-level project # directory. -set -ex +set -e + +if [[ -z "$CLJS_SCRIPT_QUIET" ]]; then + set -x +fi cd `dirname $0`/.. +rm -rf target +rm -f resources/brepl_client.js POM_TEMPLATE="pom.template.xml" POM_FILE="pom.xml" +CLJS_SCRIPT_MVN_OPTS=${CLJS_SCRIPT_MVN_OPTS:-""} + +export MAVEN_OPTS="${MAVEN_OPTS} --add-opens java.base/java.util=ALL-UNNAMED" # The command `git describe --match v0.0` will return a string like # @@ -17,34 +26,57 @@ POM_FILE="pom.xml" # where 856 is the number of commits since the v0.0 tag. It will always # find the v0.0 tag and will always return the total number of commits (even # if the tag is v0.0.1). -REVISION=`git --no-replace-objects describe --match v0.0` +MAJOR="1" +MINOR="12" +REVISION=`git --no-replace-objects describe --match v$MAJOR.$MINOR` -# Extract the version number from the string. Do this in two steps so -# it is a little easier to understand. -REVISION=${REVISION:5} # drop the first 5 characters -REVISION=${REVISION:0:${#REVISION}-9} # drop the last 9 characters +# Extract the version number from the string. +REVISION_REGEX="v[0-9]*\.[0-9]*-([0-9]*)-.*" +if [[ $REVISION =~ $REVISION_REGEX ]] +then + REVISION="${BASH_REMATCH[1]}" +fi -TAG=r$REVISION +TAG=r$MAJOR.$MINOR.$REVISION -sed -e s/CLOJURESCRIPT_VERSION/0.0-$REVISION/ < "$POM_TEMPLATE" > "$POM_FILE" +sed -e s/CLOJURESCRIPT_VERSION/$MAJOR.$MINOR.$REVISION/ < "$POM_TEMPLATE" > "$POM_FILE" COMP_FILE=`mktemp /tmp/compiler.clj.XXXXXXXXXXX` -sed -e 's/^.def ^:dynamic \*clojurescript-version\*.*$/(def ^:dynamic *clojurescript-version* {:major 0, :minor 0, :qualifier '"$REVISION"'})/' src/clj/cljs/compiler.clj > $COMP_FILE -mv $COMP_FILE src/clj/cljs/compiler.clj +sed -e 's/^.def ^:dynamic \*clojurescript-version\*.*$/(def ^:dynamic *clojurescript-version* {:major '"$MAJOR"', :minor '"$MINOR"', :qualifier '"$REVISION"'})/' src/main/clojure/cljs/util.cljc > $COMP_FILE +mv $COMP_FILE src/main/clojure/cljs/util.cljc CLJS_FILE=`mktemp /tmp/core.cljs.XXXXXXXXXXX` -sed -e 's/^.def \*clojurescript-version\*.*$/(def *clojurescript-version* '\""0.0-$REVISION"\"')/' src/cljs/cljs/core.cljs > $CLJS_FILE -mv $CLJS_FILE src/cljs/cljs/core.cljs +sed -e 's/^.def \*clojurescript-version\*.*$/(def *clojurescript-version* '\""$MAJOR.$MINOR.$REVISION"\"')/' src/main/cljs/cljs/core.cljs > $CLJS_FILE +mv $CLJS_FILE src/main/cljs/cljs/core.cljs + +rm -f src/main/cljs/cljs/core.aot.js +rm -f src/main/cljs/cljs/core.aot.js.map +rm -f src/main/cljs/cljs/core.cljs.cache.aot.edn +rm -f src/main/cljs/cljs/core.cljs.cache.aot.json + +./script/aot_core + +AOT_FILE=`mktemp /tmp/core.aot.js.XXXXXXXXXXX` +sed -e 's/0.0.0000/$MAJOR.$MINOR.$REVISION/' src/main/cljs/cljs/core.aot.js > $AOT_FILE +mv $AOT_FILE src/main/cljs/cljs/core.aot.js + +AOT_CACHE_FILE=`mktemp /tmp/core.cljs.cache.aot.edn.XXXXXXXXXXX` +sed -e 's/0.0.0000/$MAJOR.$MINOR.$REVISION/' src/main/cljs/cljs/core.cljs.cache.aot.edn > $AOT_CACHE_FILE +mv $AOT_CACHE_FILE src/main/cljs/cljs/core.cljs.cache.aot.edn # For Hudson server if [ "$HUDSON" = "true" ]; then - mvn --fail-at-end -Psonatype-oss-release \ - clean deploy nexus-staging:release + mvn -B -ntp --fail-at-end -DskipStaging=true -Psign $CLJS_SCRIPT_MVN_OPTS clean deploy echo "Creating tag $TAG" git tag -f "$TAG" git push origin "$TAG" else echo "Skipping remote deployment and Git tag because we are not on Hudson." - mvn clean install + mvn -B -ntp $CLJS_SCRIPT_MVN_OPTS clean install fi + +rm -f src/main/cljs/cljs/core.aot.js +rm -f src/main/cljs/cljs/core.aot.js.map +rm -f src/main/cljs/cljs/core.cljs.cache.aot.edn +rm -f src/main/cljs/cljs/core.cljs.cache.aot.json diff --git a/script/clean b/script/clean index c9e906188a..cd990434a6 100755 --- a/script/clean +++ b/script/clean @@ -3,3 +3,10 @@ rm -rf closure rm -rf compilation rm -rf lib +rm -rf target +rm -rf builds +rm -rf clojure +rm -rf out +rm -rf node_modules +rm -rf package.json +rm -rf package-lock.json diff --git a/script/closure-library-release/google-closure-library-third-party.pom.template b/script/closure-library-release/google-closure-library-third-party.pom.template deleted file mode 100644 index 746114c58a..0000000000 --- a/script/closure-library-release/google-closure-library-third-party.pom.template +++ /dev/null @@ -1,64 +0,0 @@ - - 4.0.0 - org.clojure - google-closure-library-third-party - RELEASE_VERSION - jar - Google Closure Library Third-Party Extensions - - http://code.google.com/p/closure-library/ - - - The Google Closure Library is a collection of JavaScript code - designed for use with the Google Closure JavaScript Compiler. - - This non-official distribution was prepared by the ClojureScript - team at http://clojure.org/ - - This package contains extensions to the Google Closure Library - using third-party components, which may be distributed under - licenses other than the Apache license. Licenses for individual - library components may be found in source-code comments. - - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html - repo - - Note: the Google Closure library third-party extensions - contain code under a variety of licenses, which may be found - in source-code comments in each file. - - - - - - Google - http://www.google.com - - - - Google, Inc. - Mohamed Mansourhello@mohamedmansour.com - Bjorn Tiplingbjorn.tipling@gmail.com - SameGoal LLChelp@samegoal.com - Guido Tapiaguido.tapia@gmail.com - Andrew Mattieamattie@gmail.com - Ilia Mirkinibmirkin@gmail.com - Ivan Kozikivan.kozik@gmail.com - Rich Doughertyrich@rd.gen.nz - - - - scm:svn:http://closure-library.googlecode.com/svn/trunk - scm:svn:http://closure-library.googlecode.com/svn/trunk - http://code.google.com/p/closure-library/source/browse/#svn/trunk - - - - code.google.com - http://code.google.com/p/closure-library/issues - - \ No newline at end of file diff --git a/script/closure-library-release/google-closure-library.pom.template b/script/closure-library-release/google-closure-library.pom.template deleted file mode 100644 index c5b2a3495c..0000000000 --- a/script/closure-library-release/google-closure-library.pom.template +++ /dev/null @@ -1,62 +0,0 @@ - - 4.0.0 - org.clojure - google-closure-library - RELEASE_VERSION - jar - Google Closure Library - - http://code.google.com/p/closure-library/ - - - The Google Closure Library is a collection of JavaScript code - designed for use with the Google Closure JavaScript Compiler. - - This non-official distribution was prepared by the ClojureScript - team at http://clojure.org/ - - - - - org.clojure - google-closure-library-third-party - RELEASE_VERSION - - - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html - repo - - - - - Google - http://www.google.com - - - - Google, Inc. - Mohamed Mansourhello@mohamedmansour.com - Bjorn Tiplingbjorn.tipling@gmail.com - SameGoal LLChelp@samegoal.com - Guido Tapiaguido.tapia@gmail.com - Andrew Mattieamattie@gmail.com - Ilia Mirkinibmirkin@gmail.com - Ivan Kozikivan.kozik@gmail.com - Rich Doughertyrich@rd.gen.nz - - - - scm:svn:http://closure-library.googlecode.com/svn/trunk - scm:svn:http://closure-library.googlecode.com/svn/trunk - http://code.google.com/p/closure-library/source/browse/#svn/trunk - - - - code.google.com - http://code.google.com/p/closure-library/issues - - \ No newline at end of file diff --git a/script/closure-library-release/make-closure-library-jars.sh b/script/closure-library-release/make-closure-library-jars.sh deleted file mode 100755 index a69eb48e6c..0000000000 --- a/script/closure-library-release/make-closure-library-jars.sh +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env bash - -set -e - -## Set the version numbers to download and release: - -ZIP_VERSION="20130212-95c19e7f0f5f" -RELEASE_VERSION="0.0-20130212-95c19e7f0f5f" - -## These only need to change if the URL or file names change: - -ZIP_BASE="closure-library-${ZIP_VERSION}" -ZIP_FILE="${ZIP_BASE}.zip" -ZIP_URL="http://closure-library.googlecode.com/files/${ZIP_FILE}" - -RELEASE_BASE="google-closure-library-${RELEASE_VERSION}" -JAR_FILE="$RELEASE_BASE.jar" -POM_FILE="$RELEASE_BASE.pom" - -THIRD_PARTY_RELEASE_BASE="google-closure-library-third-party-${RELEASE_VERSION}" -THIRD_PARTY_JAR_FILE="$THIRD_PARTY_RELEASE_BASE.jar" -THIRD_PARTY_POM_FILE="$THIRD_PARTY_RELEASE_BASE.pom" - -POM_TEMPLATE_FILE="google-closure-library.pom.template" -THIRD_PARTY_POM_TEMPLATE_FILE="google-closure-library-third-party.pom.template" - -## Main script begins: - -cd `dirname $0` - -DATE=`date "+%Y%m%d%H%M%S"` -WORKING="closure-release-${DATE}" - -rm -rf "$WORKING" -mkdir "$WORKING" - -if [ ! -e "$ZIP_FILE" ]; then - curl "$ZIP_URL" -o "$ZIP_FILE" -fi - -if [ ! -d "$WORKING/$ZIP_BASE" ]; then - ( cd "$WORKING" && unzip "../$ZIP_FILE" ) -fi - -cd "$WORKING" - -## Modify deps.js for third-party JAR; see CLJS-276: - -perl -p -i -e 's/..\/..\/third_party\/closure\/goog\///go' \ - closure/goog/deps.js - -rm -f ./third_party/closure/goog/base.js \ - ./third_party/closure/goog/deps.js - -## Build the JARs: - -jar cf "$JAR_FILE" \ - AUTHORS \ - LICENSE \ - README \ - -C closure goog \ - -C closure css - -jar cf "$THIRD_PARTY_JAR_FILE" \ - AUTHORS \ - LICENSE \ - README \ - -C third_party/closure goog - -## Generate the POM files: - -perl -p -e "s/RELEASE_VERSION/$RELEASE_VERSION/go" \ - "../$POM_TEMPLATE_FILE" \ - > "$POM_FILE" - -perl -p -e "s/RELEASE_VERSION/$RELEASE_VERSION/go" \ - "../$THIRD_PARTY_POM_TEMPLATE_FILE" \ - > "$THIRD_PARTY_POM_FILE" - -## Uncomment these lines for an official release: - -# for FILE in "$JAR_FILE" "$THIRD_PARTY_JAR_FILE" "$POM_FILE" "$THIRD_PARTY_POM_FILE" -# do -# gpg --verbose --armor --detach-sign \ -# --default-key "Clojure/core (build.clojure.org Release Key version 2) " \ -# "$FILE" -# done diff --git a/script/compile b/script/compile index d9bf5d524a..be4beb7f66 100755 --- a/script/compile +++ b/script/compile @@ -2,7 +2,7 @@ rm -f core.js -java -server -Xmx2G -Xms2G -Xmn256m -cp 'lib/*:src/clj:src/cljs' clojure.main - < package.json +mkdir -p builds/out-adv + +possible=6 ran=0 -#bin/cljsc test >out/core-test.js -bin/cljsc test "{:optimizations :advanced :output-wrapper true}" >out/core-advanced-test.js +if ! bin/cljsc src/test/cljs "{:optimizations :advanced + :main test-runner + :output-wrapper true + :verbose true + :compiler-stats true + :parallel-build true + :output-dir \"builds/out-adv\" + :npm-deps {:lodash \"4.17.4\"} + :closure-warnings {:non-standard-jsdoc :off :global-this :off} + :install-deps true + :language-out :es5 + :foreign-libs [{:file \"src/test/cljs/calculator_global.js\" + :provides [\"calculator\"] + :global-exports {calculator Calculator}} + {:file \"src/test/cljs/es6_dep.js\" + :module-type :es6 + :provides [\"es6_calc\"]} + {:file \"src/test/cljs/calculator.js\" + :module-type :commonjs + :provides [\"calculator\"]} + {:file \"src/test/cljs/es6_default_hello.js\" + :provides [\"es6_default_hello\"] + :module-type :es6}]}" > builds/out-adv/core-advanced-test.js; then + >&2 echo ClojureScript compilation failed + exit 1 +fi; if [ "$V8_HOME" = "" ]; then echo "V8_HOME not set, skipping V8 tests" else echo "Testing with V8" - "${V8_HOME}/d8" out/core-advanced-test.js - # TODO: figure out path problem when not in advanced mode - # "${V8_HOME}/d8" out/core-test.js - ran=$[ran+1] + "${V8_HOME}/d8" builds/out-adv/core-advanced-test.js + ran=$((ran+1)) fi if [ "$SPIDERMONKEY_HOME" = "" ]; then echo "SPIDERMONKEY_HOME not set, skipping SpiderMonkey tests" else echo "Testing with SpiderMonkey" - ${SPIDERMONKEY_HOME}/js -m -a -f out/core-advanced-test.js - ran=$[ran+1] + ${SPIDERMONKEY_HOME}/js -f builds/out-adv/core-advanced-test.js + ran=$((ran+1)) fi -if [ "$JSC_HOME" = "" ]; then - echo "JSC_HOME not set, skipping JavaScriptCore tests" +if ! hash jsc 2>/dev/null; then + echo "jsc not on path, skipping JavaScriptCore tests" else echo "Testing with JavaScriptCore" - "${JSC_HOME}/jsc" -f out/core-advanced-test.js - ran=$[ran+1] + jsc -f builds/out-adv/core-advanced-test.js + ran=$((ran+1)) +fi + +if [ "$NASHORN_HOME" = "" ]; then + echo "NASHORN_HOME not set, skipping Nashorn tests" +else + echo "Testing with Nashorn" + "${NASHORN_HOME}/jjs" builds/out-adv/core-advanced-test.js + ran=$((ran+1)) +fi + +if [ "$CHAKRACORE_HOME" = "" ]; then + echo "CHAKRACORE_HOME not set, skipping ChakraCore tests" +else + echo "Testing with ChakraCore" + "${CHAKRACORE_HOME}/ch" builds/out-adv/core-advanced-test.js + ran=$((ran+1)) +fi + +if [ "$GRAALVM_HOME" = "" ]; then + echo "GRAALVM_HOME not set, skipping GraalVM tests" +else + echo "Testing with GraalVM" + "${GRAALVM_HOME}/js" builds/out-adv/core-advanced-test.js + ran=$((ran+1)) fi echo "Tested with $ran out of $possible possible js targets" diff --git a/script/test-cli b/script/test-cli new file mode 100755 index 0000000000..fecf455d8f --- /dev/null +++ b/script/test-cli @@ -0,0 +1,8 @@ +#!/bin/sh + +if [ ! -f target/cljs.jar ]; then + echo "Run script/uberjar first" + exit 1 +fi + +java -cp target/cljs.jar:src/test/cljs_cli clojure.main -m cljs-cli.test-runner "$@" diff --git a/script/test-self-host b/script/test-self-host new file mode 100755 index 0000000000..c3df253a8c --- /dev/null +++ b/script/test-self-host @@ -0,0 +1,13 @@ +#!/bin/sh + +# stop blowing compiled stuff +rm -rf builds/out-self +mkdir -p builds/out-self + +if ! bin/cljsc src/test/self/self_host "{:optimizations :simple :static-fns true :output-dir \"builds/out-self\" :optimize-constants true :verbose true :compiler-stats true :parallel-build true :target :nodejs}" > builds/out-self/core-self-test.js; then + >&2 echo ClojureScript compilation failed + exit 1 +fi; + +echo "Testing with Node" +node builds/out-self/core-self-test.js diff --git a/script/test-self-parity b/script/test-self-parity new file mode 100755 index 0000000000..2af3069a95 --- /dev/null +++ b/script/test-self-parity @@ -0,0 +1,24 @@ +#!/bin/sh + +# stop blowing compiled stuff +rm -rf builds/out-self-parity +mkdir -p builds/out-self-parity + +# extract needed files from clojure.jar +if [ ! -f lib/clojure.jar ]; then + echo "Run script/bootstrap first" + exit 1 +fi +jar xvf lib/clojure.jar clojure/template.clj > /dev/null +unzip lib/test.check.jar 'clojure/*' > /dev/null +mkdir -p builds/out-self-parity/clojure/test +mv clojure/template.clj builds/out-self-parity/clojure +mv clojure/test builds/out-self-parity/clojure + +if ! bin/cljsc src/test/self/self_parity "{:optimizations :simple :language-out :es5 :output-to \"builds/out-self-parity/main.js\" :output-dir \"builds/out-self-parity\" :main self-parity.test :target :nodejs}"; then + >&2 echo ClojureScript compilation failed + exit 1 +fi; + +echo "Testing with Node" +node builds/out-self-parity/main.js diff --git a/script/test-simple b/script/test-simple new file mode 100755 index 0000000000..90495bea11 --- /dev/null +++ b/script/test-simple @@ -0,0 +1,90 @@ +#!/bin/sh + +# stop blowing compiled stuff +rm -rf builds/out-simp +rm -rf package.json +rm -rf package-lock.json + +echo {} > package.json +mkdir -p builds/out-simp + +possible=6 +ran=0 + +#bin/cljsc test >out/core-test.js +if ! bin/cljsc src/test/cljs "{:optimizations :simple + :static-fns true + :output-dir \"builds/out-simp\" + :cache-analysis true + :output-wrapper true + :verbose true + :compiler-stats true + :npm-deps {:lodash \"4.17.4\"} + :closure-warnings {:non-standard-jsdoc :off :global-this :off} + :install-deps true + :language-out :es5 + :foreign-libs [{:file \"src/test/cljs/calculator_global.js\" + :provides [\"calculator\"] + :global-exports {calculator Calculator}} + {:file \"src/test/cljs/es6_dep.js\" + :module-type :es6 + :provides [\"es6_calc\"]} + {:file \"src/test/cljs/calculator.js\" + :module-type :commonjs + :provides [\"calculator\"]} + {:file \"src/test/cljs/es6_default_hello.js\" + :provides [\"es6_default_hello\"] + :module-type :es6}]}" > builds/out-simp/core-simple-test.js; then + >&2 echo ClojureScript compilation failed + exit 1 +fi; + +if [ "$V8_HOME" = "" ]; then + echo "V8_HOME not set, skipping V8 tests" +else + echo "Testing with V8" + "${V8_HOME}/d8" builds/out-simp/core-simple-test.js + ran=$[ran+1] +fi + +if [ "$SPIDERMONKEY_HOME" = "" ]; then + echo "SPIDERMONKEY_HOME not set, skipping SpiderMonkey tests" +else + echo "Testing with SpiderMonkey" + ${SPIDERMONKEY_HOME}/js -f builds/out-simp/core-simple-test.js + ran=$[ran+1] +fi + +if ! hash jsc 2>/dev/null; then + echo "jsc not on path, skipping JavaScriptCore tests" +else + echo "Testing with JavaScriptCore" + jsc -f builds/out-simp/core-simple-test.js + ran=$[ran+1] +fi + +if [ "$NASHORN_HOME" = "" ]; then + echo "NASHORN_HOME not set, skipping Nashorn tests" +else + echo "Testing with Nashorn" + "${NASHORN_HOME}/jjs" builds/out-simp/core-simple-test.js + ran=$[ran+1] +fi + +if [ "$CHAKRACORE_HOME" = "" ]; then + echo "CHAKRACORE_HOME not set, skipping ChakraCore tests" +else + echo "Testing with ChakraCore" + "${CHAKRACORE_HOME}/ch" builds/out-simp/core-simple-test.js + ran=$[ran+1] +fi + +if [ "$GRAALVM_HOME" = "" ]; then + echo "GRAALVM_HOME not set, skipping GraalVM tests" +else + echo "Testing with GraalVM" + "${GRAALVM_HOME}/js" builds/out-simp/core-simple-test.js + ran=$[ran+1] +fi + +echo "Tested with $ran out of $possible possible js targets" diff --git a/script/test.ps1 b/script/test.ps1 new file mode 100644 index 0000000000..ed383eac57 --- /dev/null +++ b/script/test.ps1 @@ -0,0 +1,43 @@ +$ErrorActionPreference = "Stop" +$root = Resolve-Path $PSScriptRoot\.. + +$testjs = "builds/out-adv/core-advanced-test.js" + +$targets = + @{ env="V8_HOME"; name="V8"; cmd={ & "$env:V8_HOME\d8" $testjs } }, + @{ env="SPIDERMONKEY_HOME"; name="SpiderMonkey"; cmd={ & "$env:SPIDERMONKEY_HOME\js" -f $testjs } }, + @{ env="JSC_HOME"; name="JavaScriptCore"; cmd={ & "$env:JSC_HOME\jsc" -f $testjs } }, + @{ env="NASHORN_HOME"; name="Nashorn"; cmd={ & "$env:NASHORN_HOME\jjs" $testjs } }, + @{ env="CHAKRACORE_HOME"; name="ChakraCore"; cmd={ & "$env:CHAKRACORE_HOME\ch" $testjs } } +$ran = 0 + +$opts = $('{:optimizations :advanced :output-wrapper true :verbose true :compiler-stats true :parallel-build true :output-dir \"builds/out-adv\" :output-to \"' + $testjs + '\" :npm-deps {:lodash \"4.17.4\"} :closure-warnings {:non-standard-jsdoc :off :global-this :off} :install-deps true :language-in :es6 :language-out :es5 :foreign-libs [{:file \"src/test/cljs/calculator_global.js\" :provides [\"calculator\"] :global-exports {calculator Calculator}} {:file \"src/test/cljs/es6_dep.js\" :module-type :es6 :provides [\"es6_calc\"]} {:file \"src/test/cljs/calculator.js\" :module-type :commonjs :provides [\"calculator\"]} {:file \"src/test/cljs/es6_default_hello.js\" :provides [\"es6_default_hello\"] :module-type :es6}]}"') + +function Test-It($env, $name, [scriptblock] $cmd) { + $env_val = if(Test-Path env:$env) { (Get-Item env:$env).Value } else { "" } + if("$env_val" -eq "") { + Write-Host "$env not set, skipping $name tests" + } else { + Write-Host "Testing with $name" + & $cmd + $ran++ + } +} + +Push-Location $root +try { + "builds\out-adv", "out", "target" | + Where-Object { Test-Path $_ -Type leaf } | + Foreach-Object { Remove-Item $_ -recurse -force } + + New-Item builds\out-adv -ItemType Directory -Force | Out-Null + + bin\cljsc src\test\cljs $opts + + $targets | Foreach-Object { Test-It @_ } +} +finally { + Pop-Location + + Write-Host "Tested with $ran out of $($targets.Length) possible js targets" +} \ No newline at end of file diff --git a/script/uberjar b/script/uberjar new file mode 100755 index 0000000000..8b88196f01 --- /dev/null +++ b/script/uberjar @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +# This script must be run within the ClojureScript top-level project +# directory. + +set -e + +if [[ -z "$CLJS_SCRIPT_QUIET" ]]; then + set -x +fi + +rm -f resources/brepl_client.js + +# The command `git describe --match v0.0` will return a string like +# +# v0.0-856-g329708b +# +# where 856 is the number of commits since the v0.0 tag. It will always +# find the v0.0 tag and will always return the total number of commits (even +# if the tag is v0.0.1). +MAJOR="1" +MINOR="11" +REVISION=`git --no-replace-objects describe --match v$MAJOR.$MINOR` + +# Extract the version number from the string. +REVISION_REGEX="v[0-9]*\.[0-9]*-([0-9]*)-.*" +if [[ $REVISION =~ $REVISION_REGEX ]] +then + REVISION="${BASH_REMATCH[1]}" +fi + +COMP_FILE=`mktemp /tmp/compiler.clj.XXXXXXXXXXX` +sed -e 's/^.def ^:dynamic \*clojurescript-version\*.*$/(def ^:dynamic *clojurescript-version* {:major '"$MAJOR"', :minor '"$MINOR"', :qualifier '"$REVISION"'})/' src/main/clojure/cljs/util.cljc > $COMP_FILE +mv $COMP_FILE src/main/clojure/cljs/util.cljc + +CLJS_FILE=`mktemp /tmp/core.cljs.XXXXXXXXXXX` +sed -e 's/^.def \*clojurescript-version\*.*$/(def *clojurescript-version* '\""$MAJOR.$MINOR.$REVISION"\"')/' src/main/cljs/cljs/core.cljs > $CLJS_FILE +mv $CLJS_FILE src/main/cljs/cljs/core.cljs + +rm -f src/main/cljs/cljs/core.aot.js +rm -f src/main/cljs/cljs/core.aot.js.map +rm -f src/main/cljs/cljs/core.cljs.cache.aot.edn +rm -f src/main/cljs/cljs/core.cljs.cache.aot.json + +./script/aot_core + +AOT_FILE=`mktemp /tmp/core.aot.js.XXXXXXXXXXX` +sed -e "s/0.0.0000/$MAJOR.$MINOR.$REVISION/" src/main/cljs/cljs/core.aot.js > $AOT_FILE +mv $AOT_FILE src/main/cljs/cljs/core.aot.js + +AOT_CACHE_FILE=`mktemp /tmp/core.cljs.cache.aot.edn.XXXXXXXXXXX` +sed -e "s/0.0.0000/$MAJOR.$MINOR-$REVISION/" src/main/cljs/cljs/core.cljs.cache.aot.edn > $AOT_CACHE_FILE +mv $AOT_CACHE_FILE src/main/cljs/cljs/core.cljs.cache.aot.edn + +clojure -X:uberjar :jar target/cljs.jar :compile-ns :all + +rm -f src/main/cljs/cljs/core.aot.js +rm -f src/main/cljs/cljs/core.aot.js.map +rm -f src/main/cljs/cljs/core.cljs.cache.aot.edn +rm -f src/main/cljs/cljs/core.cljs.cache.aot.json diff --git a/script/vendorize_deps b/script/vendorize_deps new file mode 100755 index 0000000000..1353079cd8 --- /dev/null +++ b/script/vendorize_deps @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -e + +mkdir -p src/main/clojure/cljs/vendor +cd src/main/clojure/cljs + +DJSON_RELEASE="2.4.0" +TRANSIT_RELEASE="1.0.329" +TREADER_RELEASE="1.4.2" + +rm -rf data.json +git clone -b "v$DJSON_RELEASE" --depth 1 git@github.com:clojure/data.json.git +mkdir -p vendor/clojure/data +mv data.json/src/main/clojure/clojure/data/json.clj vendor/clojure/data/ +rm -rf data.json +DATA_JSON_FILE=`mktemp /tmp/json.clj.XXXXXXXXXXX` +sed -e 's/clojure.data.json/cljs.vendor.clojure.data.json/' vendor/clojure/data/json.clj > $DATA_JSON_FILE +mv $DATA_JSON_FILE vendor/clojure/data/json.clj + +rm -rf transit-clj +git clone -b "v$TRANSIT_RELEASE" --depth 1 git@github.com:cognitect/transit-clj.git +mkdir -p vendor/cognitect +mv transit-clj/src/cognitect/transit.clj vendor/cognitect/ +rm -rf transit-clj +TRANSIT_FILE=`mktemp /tmp/transit.clj.XXXXXXXXXXX` +sed -e 's/ns cognitect.transit/ns cljs.vendor.cognitect.transit/' vendor/cognitect/transit.clj > $TRANSIT_FILE +mv $TRANSIT_FILE vendor/cognitect/transit.clj +TRANSIT_FILE=`mktemp /tmp/transit.clj.XXXXXXXXXXX` +sed -e 's/cognitect.transit.WithMeta/cljs.vendor.cognitect.transit.WithMeta/' vendor/cognitect/transit.clj > $TRANSIT_FILE +mv $TRANSIT_FILE vendor/cognitect/transit.clj + +rm -rf tools.reader +rm -rf vendor/clojure/tools +git clone -b "v$TREADER_RELEASE" --depth 1 git@github.com:clojure/tools.reader.git +mkdir -p vendor/clojure/tools +mv tools.reader/src/main/clojure/clojure/tools/* vendor/clojure/tools/ +rm -rf tools.reader + +echo "rewriting tool.reader namespaces" +find vendor/clojure/tools -name '*.clj' -print0 | xargs -0 sed -iBAK 's/clojure.tools/cljs.vendor.clojure.tools/g' +find vendor/clojure/tools -name '*BAK' -delete diff --git a/src/assembly/aot.xml b/src/assembly/aot.xml new file mode 100644 index 0000000000..63b796e4d0 --- /dev/null +++ b/src/assembly/aot.xml @@ -0,0 +1,31 @@ + + aot + + jar + + false + + + target/classes + / + + + + + + + pom.xml + META-INF/maven/org.clojure/clojurescript + + + target/maven-archiver/pom.properties + META-INF/maven/org.clojure/clojurescript + + + diff --git a/src/assembly/slim.xml b/src/assembly/slim.xml new file mode 100644 index 0000000000..3e22d5e4fb --- /dev/null +++ b/src/assembly/slim.xml @@ -0,0 +1,31 @@ + + slim + + jar + + false + + + src/main/cljs + / + + + src/main/clojure + / + + + resources + / + + + + + pom.xml + META-INF/maven/org.clojure/clojurescript + + + target/maven-archiver/pom.properties + META-INF/maven/org.clojure/clojurescript + + + diff --git a/src/clj/cljs/analyzer.clj b/src/clj/cljs/analyzer.clj deleted file mode 100644 index d4cc55739f..0000000000 --- a/src/clj/cljs/analyzer.clj +++ /dev/null @@ -1,1577 +0,0 @@ -; Copyright (c) Rich Hickey. All rights reserved. -; The use and distribution terms for this software are covered by the -; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -; which can be found in the file epl-v10.html at the root of this distribution. -; By using this software in any fashion, you are agreeing to be bound by -; the terms of this license. -; You must not remove this notice, or any other, from this software. - -(ns cljs.analyzer - (:refer-clojure :exclude [macroexpand-1]) - (:require [clojure.java.io :as io] - [clojure.string :as string] - [clojure.set :as set] - [cljs.env :as env] - [cljs.tagged-literals :as tags] - [clojure.tools.reader :as reader] - [clojure.tools.reader.reader-types :as readers]) - (:import java.lang.StringBuilder - java.io.File - [cljs.tagged_literals JSValue])) - -(set! *warn-on-reflection* true) - -(def ^:dynamic *cljs-ns* 'cljs.user) -(def ^:dynamic *cljs-file* nil) -(def ^:dynamic *unchecked-if* (atom false)) -(def ^:dynamic *cljs-static-fns* false) -(def ^:dynamic *cljs-macros-path* "/cljs/core") -(def ^:dynamic *cljs-macros-is-classpath* true) -(def ^:dynamic *cljs-dep-set* (with-meta #{} {:dep-path []})) -(def ^:dynamic *analyze-deps* true) -(def -cljs-macros-loaded (atom false)) - -(def ^:dynamic *cljs-warnings* - {:unprovided true - :undeclared-var false - :undeclared-ns false - :undeclared-ns-form true - :redef true - :dynamic true - :fn-var true - :fn-arity true - :fn-deprecated true - :protocol-deprecated true - :undeclared-protocol-symbol true - :invalid-protocol-symbol true - :multiple-variadic-overloads true - :variadic-max-arity true - :overload-arity true - :extending-base-js-type true - :invoke-ctor true - :invalid-arithmetic true}) - -(declare message namespaces) - -(defmulti error-message (fn [warning-type & _] warning-type)) - -(defmethod error-message :unprovided - [warning-type info] - (str "Required namespace not provided for " (clojure.string/join " " (:unprovided info)))) - -(defmethod error-message :undeclared-var - [warning-type info] - (str "Use of undeclared Var " (:prefix info) "/" (:suffix info))) - -(defmethod error-message :undeclared-ns - [warning-type info] - (str "No such namespace: " (:ns-sym info))) - -(defmethod error-message :dynamic - [warning-type info] - (str (:name info) " not declared ^:dynamic")) - -(defmethod error-message :redef - [warning-type info] - (str (:sym info) " already refers to: " (symbol (str (:ns info)) (str (:sym info))) - " being replaced by: " (symbol (str (:ns-name info)) (str (:sym info))))) - -(defmethod error-message :fn-var - [warning-type info] - (str (symbol (str (:ns-name info)) (str (:sym info))) - " no longer fn, references are stale")) - -(defmethod error-message :fn-arity - [warning-type info] - (str "Wrong number of args (" (:argc info) ") passed to " - (or (:ctor info) - (:name info)))) - -(defmethod error-message :fn-deprecated - [warning-type info] - (str (-> info :fexpr :info :name) " is deprecated.")) - -(defmethod error-message :undeclared-ns-form - [warning-type info] - (str "Referred " (:type info) " " (:lib info) "/" (:sym info) " does not exist")) - -(defmethod error-message :protocol-deprecated - [warning-type info] - (str "Protocol " (:protocol info) " is deprecated")) - -(defmethod error-message :undeclared-protocol-symbol - [warning-type info] - (str "Can't resolve protocol symbol " (:protocol info))) - -(defmethod error-message :invalid-protocol-symbol - [warning-type info] - (str "Symbol " (:protocol info) " is not a protocol")) - -(defmethod error-message :multiple-variadic-overloads - [warning-type info] - (str (:name info) ": Can't have more than 1 variadic overload")) - -(defmethod error-message :variadic-max-arity - [warning-type info] - (str (:name info) ": Can't have fixed arity function with more params than variadic function")) - -(defmethod error-message :overload-arity - [warning-type info] - (str (:name info) ": Can't have 2 overloads with same arity")) - -(defmethod error-message :extending-base-js-type - [warning-type info] - (str "Extending an existing JavaScript type - use a different symbol name " - "instead of " (:current-symbol info) " e.g " (:suggested-symbol info))) - -(defmethod error-message :invalid-arithmetic - [warning-type info] - (str (:js-op info) ", all arguments must be numbers, got " (:types info) " instead.")) - -(defmethod error-message :invoke-ctor - [warning-type info] - (str "Cannot invoke type constructor " (-> info :fexpr :info :name) " as function ")) - -(defn ^:private default-warning-handler [warning-type env extra] - (when (warning-type *cljs-warnings*) - (when-let [s (error-message warning-type extra)] - (binding [*out* *err*] - (println (message env (str "WARNING: " s))))))) - -(def ^:dynamic *cljs-warning-handlers* - [default-warning-handler]) - -(defmacro with-warning-handlers [handlers & body] - `(binding [*cljs-warning-handlers* ~handlers] - ~@body)) - -(defn munge-path [ss] - (clojure.lang.Compiler/munge (str ss))) - -(defn ns->relpath [s] - (str (string/replace (munge-path s) \. \/) ".cljs")) - -(def ^:private constant-counter (atom 0)) - -(defn gen-constant-id [value] - (let [prefix (cond - (keyword? value) "constant$keyword$" - :else - (throw - (Exception. (str "constant type " (type value) " not supported"))))] - (symbol (str prefix (swap! constant-counter inc))))) - -(defn- register-constant! [val] - (swap! env/*compiler* update-in [::constant-table] - (fn [table] - (if (get table val) - table - (assoc table val (gen-constant-id val)))))) - -(def default-namespaces '{cljs.core {:name cljs.core} - cljs.user {:name cljs.user}}) - -;; this exists solely to support read-only namespace access from macros. -;; External tools should look at the authoritative ::namespaces slot in the -;; compiler-env atoms/maps they're using already; this value will yield only -;; `default-namespaces` when accessed outside the scope of a -;; compilation/analysis call -(def namespaces - (reify clojure.lang.IDeref - (deref [_] - (if-not (nil? env/*compiler*) - (::namespaces @env/*compiler*) - default-namespaces)))) - -(defn get-namespace [key] - (get-in @env/*compiler* [::namespaces key])) - -(defmacro no-warn [& body] - (let [no-warnings (zipmap (keys *cljs-warnings*) (repeat false))] - `(binding [*cljs-warnings* ~no-warnings] - ~@body))) - -(defmacro all-warn [& body] - (let [all-warnings (zipmap (keys *cljs-warnings*) (repeat true))] - `(binding [*cljs-warnings* ~all-warnings] - ~@body))) - -(defn get-line [x env] - (or (-> x meta :line) (:line env))) - -(defn get-col [x env] - (or (-> x meta :column) (:column env))) - -(defn load-core [] - (when (not @-cljs-macros-loaded) - (reset! -cljs-macros-loaded true) - (if *cljs-macros-is-classpath* - (load *cljs-macros-path*) - (load-file *cljs-macros-path*)))) - -(defmacro with-core-macros - [path & body] - `(do - (when (not= *cljs-macros-path* ~path) - (reset! -cljs-macros-loaded false)) - (binding [*cljs-macros-path* ~path] - ~@body))) - -(defmacro with-core-macros-file - [path & body] - `(do - (when (not= *cljs-macros-path* ~path) - (reset! -cljs-macros-loaded false)) - (binding [*cljs-macros-path* ~path - *cljs-macros-is-classpath* false] - ~@body))) - -(defn empty-env [] - (env/ensure - {:ns (get-namespace *cljs-ns*) - :context :statement - :locals {} - :js-globals (into {} - (map #(vector % {:name %}) - '(alert window document console escape unescape - screen location navigator history location - global process require module exports)))})) - -(defmacro ^:private debug-prn - [& args] - `(.println System/err (str ~@args))) - -(defn source-info - ([env] - (when-let [line (:line env)] - {:file *cljs-file* - :line (get-line name env) - :column (get-col name env)})) - ([name env] - {:file *cljs-file* - :line (get-line name env) - :column (get-col name env)})) - -(defn message [env s] - (str s (when (:line env) - (str " at line " (:line env) " " *cljs-file*)))) - -(defn warning [warning-type env extra] - (doseq [handler *cljs-warning-handlers*] - (handler warning-type env extra))) - -(defn error - ([env s] (error env s nil)) - ([env s cause] - (ex-info (message env s) - (assoc (source-info env) :tag :cljs/analysis-error) - cause))) - -(defn analysis-error? [ex] - (= :cljs/analysis-error (:tag (ex-data ex)))) - -(defmacro wrapping-errors [env & body] - `(try - ~@body - (catch Throwable err# - (if (analysis-error? err#) - (throw err#) - (throw (error ~env (.getMessage err#) err#)))))) - -(defn confirm-var-exists [env prefix suffix] - (let [crnt-ns (-> env :ns :name)] - (when (and (= prefix crnt-ns) - (not (get-in @env/*compiler* [::namespaces crnt-ns :defs suffix]))) - (warning :undeclared-var env {:prefix prefix :suffix suffix})))) - -(defn resolve-ns-alias [env name] - (let [sym (symbol name)] - (get (:requires (:ns env)) sym sym))) - -(defn confirm-ns [env ns-sym] - (when (and (nil? (get '#{cljs.core goog Math goog.string} ns-sym)) - (nil? (get (-> env :ns :requires) ns-sym)) - ;; macros may refer to namespaces never explicitly required - ;; confirm that the library at least exists - (nil? (io/resource (ns->relpath ns-sym)))) - (warning :undeclared-ns env {:ns-sym ns-sym}))) - -(defn core-name? - "Is sym visible from core in the current compilation namespace?" - [env sym] - (and (get-in @env/*compiler* [::namespaces 'cljs.core :defs sym]) - (not (contains? (-> env :ns :excludes) sym)))) - -(defn resolve-var - "Resolve a var. Accepts a side-effecting confirm fn for producing - warnings about unresolved vars." - ([env sym] (resolve-var env sym nil)) - ([env sym confirm] - (if (= (namespace sym) "js") - {:name sym :ns 'js} - (let [s (str sym) - lb (-> env :locals sym)] - (cond - lb lb - - (namespace sym) - (let [ns (namespace sym) - ns (if (= "clojure.core" ns) "cljs.core" ns) - full-ns (resolve-ns-alias env ns)] - (when confirm - (when (not= (-> env :ns :name) full-ns) - (confirm-ns env full-ns)) - (confirm env full-ns (symbol (name sym)))) - (merge (get-in @env/*compiler* [::namespaces full-ns :defs (symbol (name sym))]) - {:name (symbol (str full-ns) (str (name sym))) - :ns full-ns})) - - (.contains s ".") - (let [idx (.indexOf s ".") - prefix (symbol (subs s 0 idx)) - suffix (subs s (inc idx)) - lb (-> env :locals prefix)] - (if lb - {:name (symbol (str (:name lb) suffix))} - (let [cur-ns (-> env :ns :name)] - (if-let [full-ns (get-in @env/*compiler* [::namespaces cur-ns :imports prefix])] - {:name (symbol (str full-ns) suffix)} - (if-let [info (get-in @env/*compiler* [::namespaces cur-ns :defs prefix])] - (merge info - {:name (symbol (str cur-ns) (str sym)) - :ns cur-ns}) - (merge (get-in @env/*compiler* [::namespaces prefix :defs (symbol suffix)]) - {:name (if (= "" prefix) (symbol suffix) (symbol (str prefix) suffix)) - :ns prefix})))))) - - (get-in @env/*compiler* [::namespaces (-> env :ns :name) :uses sym]) - (let [full-ns (get-in @env/*compiler* [::namespaces (-> env :ns :name) :uses sym])] - (merge - (get-in @env/*compiler* [::namespaces full-ns :defs sym]) - {:name (symbol (str full-ns) (str sym)) - :ns (-> env :ns :name)})) - - (get-in @env/*compiler* [::namespaces (-> env :ns :name) :imports sym]) - (recur env (get-in @env/*compiler* [::namespaces (-> env :ns :name) :imports sym]) confirm) - - :else - (let [full-ns (if (core-name? env sym) - 'cljs.core - (-> env :ns :name))] - (when confirm - (confirm env full-ns sym)) - (merge (get-in @env/*compiler* [::namespaces full-ns :defs sym]) - {:name (symbol (str full-ns) (str sym)) - :ns full-ns}))))))) - -(defn resolve-existing-var [env sym] - (if-not (-> sym meta ::no-resolve) - (resolve-var env sym confirm-var-exists) - (resolve-var env sym))) - -(defn confirm-bindings [env names] - (doseq [name names] - (let [env (assoc env :ns (get-namespace *cljs-ns*)) - ev (resolve-existing-var env name)] - (when (and ev (not (-> ev :dynamic))) - (warning :dynamic env {:ev ev}))))) - -(declare analyze analyze-symbol analyze-seq) - -(def specials '#{if def fn* do let* loop* letfn* throw try recur new set! ns deftype* defrecord* . js* & quote}) - -(def ^:dynamic *recur-frames* nil) -(def ^:dynamic *loop-lets* ()) - -(defmacro disallowing-recur [& body] - `(binding [*recur-frames* (cons nil *recur-frames*)] ~@body)) - -;; TODO: move this logic out - David -(defn analyze-keyword - [env sym] - (register-constant! sym) - {:op :constant :env env :form sym :tag 'cljs.core/Keyword}) - -(defn get-tag [e] - (or (-> e :tag) - (-> e :info :tag) - (-> e :form meta :tag))) - -(defn find-matching-method [f params] - ;; if local fn, need to look in :info - (let [methods (or (:methods f) (-> f :info :methods)) - c (count params)] - (some - (fn [m] - (and (or (== (:max-fixed-arity m) c) - (:variadic m)) - m)) - methods))) - -(defn type? [env t] - ;; don't use resolve-existing-var to avoid warnings - (when (and t (symbol? t)) - (let [var (resolve-var env t)] - (or (:type var) - (-> var :info :type) - (:protocol-symbol var) - ;; need to hard code some cases because of - ;; forward declaration - David - ('#{cljs.core/PersistentHashMap - cljs.core/List} t))))) - -(defn infer-tag [env e] - (if-let [tag (get-tag e)] - tag - (case (:op e) - :recur 'ignore - :throw 'ignore - :let (infer-tag env (:expr e)) - :loop (infer-tag env (:expr e)) - :do (infer-tag env (:ret e)) - :method (infer-tag env (:expr e)) - :def (infer-tag env (:init e)) - :invoke (let [{info :info :as f} (:f e)] - (or (and (:fn-var info) (:ret-tag info)) - (infer-tag env - (assoc (find-matching-method f (:args e)) :op :method)) - 'any)) - :if (let [{{:keys [op form]} :test} e - then-tag (infer-tag env (:then e))] - (if (and (= op :constant) - (not (#{nil false} form))) - then-tag - (let [else-tag (infer-tag env (:else e))] - (cond - (or (= then-tag else-tag) - (= else-tag 'ignore)) then-tag - (= then-tag 'ignore) else-tag - ;; TODO: temporary until we move not-native -> clj - David - (and (or ('#{clj not-native} then-tag) (type? env then-tag)) - (or ('#{clj not-native} else-tag) (type? env else-tag))) - 'clj - :else - (if (every? '#{boolean seq} [then-tag else-tag]) - 'seq - (let [then-tag (if (set? then-tag) then-tag #{then-tag}) - else-tag (if (set? else-tag) else-tag #{else-tag})] - (into then-tag else-tag))))))) - :constant (case (:form e) - true 'boolean - false 'boolean - 'any) - :var (if (:init e) - (infer-tag env (:init e)) - (infer-tag env (:info e))) - nil))) - -(defmulti parse (fn [op & rest] op)) - -(defmethod parse 'if - [op env [_ test then else :as form] name] - (when (< (count form) 3) - (throw (error env "Too few arguments to if"))) - (let [test-expr (disallowing-recur (analyze (assoc env :context :expr) test)) - then-expr (analyze env then) - else-expr (analyze env else)] - {:env env :op :if :form form - :test test-expr :then then-expr :else else-expr - :unchecked @*unchecked-if* - :children [test-expr then-expr else-expr]})) - -(defmethod parse 'throw - [op env [_ throw :as form] name] - (let [throw-expr (disallowing-recur (analyze (assoc env :context :expr) throw))] - {:env env :op :throw :form form - :throw throw-expr - :children [throw-expr]})) - -(defmethod parse 'try - [op env [_ & body :as form] name] - (let [catchenv (update-in env [:context] #(if (= :expr %) :return %)) - catch? (every-pred seq? #(= (first %) 'catch)) - default? (every-pred catch? #(= (second %) :default)) - finally? (every-pred seq? #(= (first %) 'finally)) - - {:keys [body cblocks dblock fblock]} - (loop [parser {:state :start :forms body - :body [] :cblocks [] :dblock nil :fblock nil}] - (if (seq? (:forms parser)) - (let [[form & forms*] (:forms parser) - parser* (assoc parser :forms forms*)] - (case (:state parser) - :start (cond - (catch? form) (recur (assoc parser :state :catches)) - (finally? form) (recur (assoc parser :state :finally)) - :else (recur (update-in parser* [:body] conj form))) - :catches (cond - (default? form) (recur (assoc parser* :dblock form :state :finally)) - (catch? form) (recur (update-in parser* [:cblocks] conj form)) - (finally? form) (recur (assoc parser :state :finally)) - :else (throw (error env "Invalid try form"))) - :finally (recur (assoc parser* :fblock form :state :done)) - :done (throw (error env "Unexpected form after finally")))) - parser)) - - finally (when (seq fblock) - (analyze (assoc env :context :statement) `(do ~@(rest fblock)))) - e (when (or (seq cblocks) dblock) (gensym "e")) - default (if-let [[_ _ name & cb] dblock] - `(cljs.core/let [~name ~e] ~@cb) - `(throw ~e)) - cblock (if (seq cblocks) - `(cljs.core/cond - ~@(mapcat - (fn [[_ type name & cb]] - (when name (assert (not (namespace name)) "Can't qualify symbol in catch")) - `[(cljs.core/instance? ~type ~e) - (cljs.core/let [~name ~e] ~@cb)]) - cblocks) - :else ~default) - default) - locals (:locals catchenv) - locals (if e - (assoc locals e - {:name e - :line (get-line e env) - :column (get-col e env)}) - locals) - catch (when cblock - (analyze (assoc catchenv :locals locals) cblock)) - try (analyze (if (or e finally) catchenv env) `(do ~@body))] - - {:env env :op :try :form form - :try try - :finally finally - :name e - :catch catch - :children [try catch finally]})) - -(defmethod parse 'def - [op env form name] - (let [pfn (fn - ([_ sym] {:sym sym}) - ([_ sym init] {:sym sym :init init}) - ([_ sym doc init] {:sym sym :doc doc :init init})) - args (apply pfn form) - sym (:sym args) - sym-meta (meta sym) - tag (-> sym meta :tag) - protocol (-> sym meta :protocol) - dynamic (-> sym meta :dynamic) - ns-name (-> env :ns :name)] - (when (namespace sym) - (throw (error env "Can't def ns-qualified name"))) - (when-let [doc (:doc args)] - (when-not (string? doc) - (throw (error env "Too many arguments to def")))) - (let [env (if (or (and (not= ns-name 'cljs.core) - (core-name? env sym)) - (get-in @env/*compiler* [::namespaces ns-name :uses sym])) - (let [ev (resolve-existing-var (dissoc env :locals) sym)] - (warning :redef env {:ev ev :sym sym :ns-name ns-name}) - (swap! env/*compiler* update-in [::namespaces ns-name :excludes] conj sym) - (update-in env [:ns :excludes] conj sym)) - env) - name (:name (resolve-var (dissoc env :locals) sym)) - var-expr (assoc (analyze (-> env (dissoc :locals) - (assoc :context :expr) - (assoc :def-var true)) - sym) - :op :var) - init-expr (when (contains? args :init) - (disallowing-recur - (analyze (assoc env :context :expr) (:init args) sym))) - fn-var? (and init-expr (= (:op init-expr) :fn)) - tag (if fn-var? - (or (:ret-tag init-expr) tag) - tag) - export-as (when-let [export-val (-> sym meta :export)] - (if (= true export-val) name export-val)) - doc (or (:doc args) (-> sym meta :doc))] - (when-let [v (get-in @env/*compiler* [::namespaces ns-name :defs sym])] - (when (and (not (-> sym meta :declared)) - (and (:fn-var v) (not fn-var?))) - (warning :fn-var env {:ns-name ns-name :sym sym}))) - (swap! env/*compiler* assoc-in [::namespaces ns-name :defs sym] - (merge - {:name name} - sym-meta - (when doc {:doc doc}) - (when dynamic {:dynamic true}) - (source-info name env) - ;; the protocol a protocol fn belongs to - (when protocol - {:protocol protocol}) - ;; symbol for reified protocol - (when-let [protocol-symbol (-> sym meta :protocol-symbol)] - {:protocol-symbol protocol-symbol - :impls #{}}) - (when fn-var? - {:fn-var true - ;; protocol implementation context - :protocol-impl (:protocol-impl init-expr) - ;; inline protocol implementation context - :protocol-inline (:protocol-inline init-expr) - :variadic (:variadic init-expr) - :max-fixed-arity (:max-fixed-arity init-expr) - :method-params (map :params (:methods init-expr)) - :methods (map (fn [method] - (let [tag (infer-tag env (assoc method :op :method))] - (cond-> (select-keys method - [:max-fixed-arity :variadic]) - tag (assoc :tag tag)))) - (:methods init-expr))}) - (when (and fn-var? tag) - {:ret-tag tag}))) - (merge {:env env :op :def :form form - :name name :var var-expr :doc doc :init init-expr} - (when tag - (if fn-var? - {:ret-tag tag} - {:tag tag})) - (when dynamic {:dynamic true}) - (when export-as {:export export-as}) - (when init-expr {:children [init-expr]}))))) - -(defn- analyze-fn-method [env locals form type] - (let [param-names (first form) - variadic (boolean (some '#{&} param-names)) - param-names (vec (remove '#{&} param-names)) - body (next form) - [locals params] (reduce (fn [[locals params] name] - (let [param {:name name - :line (get-line name env) - :column (get-col name env) - :tag (-> name meta :tag) - :shadow (when locals (locals name)) - ;; Give the fn params the same shape - ;; as a :var, so it gets routed - ;; correctly in the compiler - :op :var - :env (merge (select-keys env [:context]) - {:line (get-line name env) - :column (get-col name env)}) - :info {:name name - :shadow (when locals (locals name))} - :binding-form? true}] - [(assoc locals name param) (conj params param)])) - [locals []] param-names) - fixed-arity (count (if variadic (butlast params) params)) - recur-frame {:params params :flag (atom nil)} - expr (binding [*recur-frames* (cons recur-frame *recur-frames*)] - (analyze (assoc env :context :return :locals locals) `(do ~@body)))] - {:env env :variadic variadic :params params :max-fixed-arity fixed-arity - :type type :form form :recurs @(:flag recur-frame) :expr expr})) - -(defmethod parse 'fn* - [op env [_ & args :as form] name] - (let [[name meths] (if (symbol? (first args)) - [(first args) (next args)] - [name (seq args)]) - ;;turn (fn [] ...) into (fn ([]...)) - meths (if (vector? (first meths)) (list meths) meths) - locals (:locals env) - name-var (if name - (merge - {:name name - :info {:shadow (or (locals name) - (get-in env [:js-globals name]))}} - (when-let [tag (-> name meta :tag)] - {:ret-tag tag}))) - locals (if (and locals name) (assoc locals name name-var) locals) - type (-> form meta ::type) - fields (-> form meta ::fields) - protocol-impl (-> form meta :protocol-impl) - protocol-inline (-> form meta :protocol-inline) - locals (reduce (fn [m fld] - (assoc m fld - {:name fld - :line (get-line fld env) - :column (get-col fld env) - :field true - :mutable (-> fld meta :mutable) - :unsynchronized-mutable (-> fld meta :unsynchronized-mutable) - :volatile-mutable (-> fld meta :volatile-mutable) - :tag (-> fld meta :tag) - :shadow (m fld)})) - locals fields) - - menv (if (> (count meths) 1) (assoc env :context :expr) env) - menv (merge menv - {:protocol-impl protocol-impl - :protocol-inline protocol-inline}) - methods (map #(analyze-fn-method menv locals % type) meths) - max-fixed-arity (apply max (map :max-fixed-arity methods)) - variadic (boolean (some :variadic methods)) - locals (if name - (update-in locals [name] assoc - ;; TODO: can we simplify? - David - :fn-var true - :variadic variadic - :max-fixed-arity max-fixed-arity - :method-params (map :params methods) - :methods methods) - locals) - methods (if name - ;; a second pass with knowledge of our function-ness/arity - ;; lets us optimize self calls - (no-warn (doall (map #(analyze-fn-method menv locals % type) meths))) - methods)] - (let [variadic-methods (filter :variadic methods) - variadic-params (count (:params (first variadic-methods))) - param-counts (map (comp count :params) methods)] - (when (< 1 (count variadic-methods)) - (warning :multiple-variadic-overloads env {:name name-var})) - (when (not (or (zero? variadic-params) (= variadic-params (+ 1 max-fixed-arity)))) - (warning :variadic-max-arity env {:name name-var})) - (when (not= (distinct param-counts) param-counts) - (warning :overload-arity env {:name name-var}))) - {:env env :op :fn :form form :name name-var :methods methods :variadic variadic - :tag 'function - :recur-frames *recur-frames* :loop-lets *loop-lets* - :jsdoc [(when variadic "@param {...*} var_args")] - :max-fixed-arity max-fixed-arity - :protocol-impl protocol-impl - :protocol-inline protocol-inline - :children (mapv :expr methods)})) - -(defmethod parse 'letfn* - [op env [_ bindings & exprs :as form] name] - (when-not (and (vector? bindings) (even? (count bindings))) - (throw (error env "bindings must be vector of even number of elements"))) - (let [n->fexpr (into {} (map (juxt first second) (partition 2 bindings))) - names (keys n->fexpr) - context (:context env) - ;; first pass to collect information for recursive references - [meth-env bes] - (reduce (fn [[{:keys [locals] :as env} bes] n] - (let [ret-tag (-> n meta :tag) - fexpr (no-warn (analyze env (n->fexpr n))) - be (cond-> - {:name n - :fn-var true - :line (get-line n env) - :column (get-col n env) - :local true - :shadow (locals n) - :variadic (:variadic fexpr) - :max-fixed-arity (:max-fixed-arity fexpr) - :method-params (map :params (:methods fexpr)) - :methods (:methods fexpr)} - ret-tag (assoc :ret-tag ret-tag))] - [(assoc-in env [:locals n] be) - (conj bes be)])) - [env []] names) - meth-env (assoc meth-env :context :expr) - ;; the real pass - [meth-env bes] - (reduce (fn [[meth-env bes] {:keys [name shadow] :as be}] - (let [env (assoc-in meth-env [:locals name] shadow) - fexpr (analyze env (n->fexpr name)) - be' (assoc be - :init fexpr - :variadic (:variadic fexpr) - :max-fixed-arity (:max-fixed-arity fexpr) - :method-params (map :params (:methods fexpr)) - :methods (:methods fexpr))] - [(assoc-in env [:locals name] be') - (conj bes be')])) - [meth-env []] bes) - expr (analyze (assoc meth-env :context (if (= :expr context) :return context)) `(do ~@exprs))] - {:env env :op :letfn :bindings bes :expr expr :form form - :children (conj (vec (map :init bes)) expr)})) - -(defmethod parse 'do - [op env [_ & exprs :as form] _] - (let [statements (disallowing-recur - (seq (map #(analyze (assoc env :context :statement) %) (butlast exprs)))) - ret (if (<= (count exprs) 1) - (analyze env (first exprs)) - (analyze (assoc env :context (if (= :statement (:context env)) :statement :return)) (last exprs)))] - {:env env :op :do :form form - :statements statements :ret ret - :children (conj (vec statements) ret)})) - -(defn analyze-let - [encl-env [_ bindings & exprs :as form] is-loop] - (when-not (and (vector? bindings) (even? (count bindings))) - (throw (error encl-env "bindings must be vector of even number of elements"))) - (let [context (:context encl-env) - [bes env] - (disallowing-recur - (loop [bes [] - env (assoc encl-env :context :expr) - bindings (seq (partition 2 bindings))] - (if-let [[name init] (first bindings)] - (do - (when (or (namespace name) (.contains (str name) ".")) - (throw (error encl-env (str "Invalid local name: " name)))) - (let [init-expr (binding [*loop-lets* (cons {:params bes} *loop-lets*)] - (analyze env init)) - be {:name name - :line (get-line name env) - :column (get-col name env) - :init init-expr - :tag (or (-> name meta :tag) - (-> init-expr :tag) - (-> init-expr :info :tag)) - :local true - :shadow (-> env :locals name) - ;; Give let* bindings same shape as var so - ;; they get routed correctly in the compiler - :op :var - :env {:line (get-line name env) - :column (get-col name env)} - :info {:name name - :shadow (-> env :locals name)} - :binding-form? true} - be (if (= (:op init-expr) :fn) - ;; TODO: can we simplify - David - (merge be - {:fn-var true - :variadic (:variadic init-expr) - :max-fixed-arity (:max-fixed-arity init-expr) - :method-params (map :params (:methods init-expr)) - :methods (:methods init-expr)}) - be)] - (recur (conj bes be) - (assoc-in env [:locals name] be) - (next bindings)))) - [bes env]))) - recur-frame (when is-loop {:params bes :flag (atom nil)}) - expr - (binding [*recur-frames* (if recur-frame (cons recur-frame *recur-frames*) *recur-frames*) - *loop-lets* (cond - is-loop *loop-lets* - *loop-lets* (cons {:params bes} *loop-lets*))] - (analyze (assoc env :context (if (= :expr context) :return context)) `(do ~@exprs)))] - {:env encl-env :op (if is-loop :loop :let) - :bindings bes :expr expr :form form - :children (conj (vec (map :init bes)) expr)})) - -(defmethod parse 'let* - [op encl-env form _] - (analyze-let encl-env form false)) - -(defmethod parse 'loop* - [op encl-env form _] - (analyze-let encl-env form true)) - -(defmethod parse 'recur - [op env [_ & exprs :as form] _] - (let [context (:context env) - frame (first *recur-frames*) - exprs (disallowing-recur (vec (map #(analyze (assoc env :context :expr) %) exprs)))] - (when-not frame - (throw (error env "Can't recur here"))) - (when-not (= (count exprs) (count (:params frame))) - (throw (error env "recur argument count mismatch"))) - (reset! (:flag frame) true) - (assoc {:env env :op :recur :form form} - :frame frame - :exprs exprs - :children exprs))) - -(defmethod parse 'quote - [_ env [_ x] _] - (analyze (assoc env :quoted? true) x)) - -(defmethod parse 'new - [_ env [_ ctor & args :as form] _] - (when-not (symbol? ctor) - (throw (error env "First arg to new must be a symbol"))) - (disallowing-recur - (let [enve (assoc env :context :expr) - ctorexpr (analyze enve ctor) - argexprs (vec (map #(analyze enve %) args)) - known-num-fields (:num-fields (resolve-existing-var env ctor)) - argc (count args)] - (when (and (not (-> ctor meta :internal-ctor)) - known-num-fields (not= known-num-fields argc)) - (warning :fn-arity env {:argc argc :ctor ctor})) - {:env env :op :new :form form :ctor ctorexpr :args argexprs - :children (into [ctorexpr] argexprs) - :tag (let [name (-> ctorexpr :info :name)] - (or ('{js/Object object - js/String string - js/Array array - js/Number number - js/Function function - js/Boolean boolean} name) - name))}))) - -(defmethod parse 'set! - [_ env [_ target val alt :as form] _] - (let [[target val] (if alt - ;; (set! o -prop val) - [`(. ~target ~val) alt] - [target val])] - (disallowing-recur - (let [enve (assoc env :context :expr) - targetexpr (cond - ;; TODO: proper resolve - (= target '*unchecked-if*) - (do - (reset! *unchecked-if* val) - ::set-unchecked-if) - - (symbol? target) - (do - (let [local (-> env :locals target)] - (when-not (or (nil? local) - (and (:field local) - (or (:mutable local) - (:unsynchronized-mutable local) - (:volatile-mutable local)))) - (throw (error env "Can't set! local var or non-mutable field")))) - (analyze-symbol enve target)) - - :else - (when (seq? target) - (let [targetexpr (analyze-seq enve target nil)] - (when (:field targetexpr) - targetexpr)))) - valexpr (analyze enve val)] - (when-not targetexpr - (throw (error env "set! target must be a field or a symbol naming a var"))) - (cond - (= targetexpr ::set-unchecked-if) {:env env :op :no-op} - :else {:env env :op :set! :form form :target targetexpr :val valexpr - :children [targetexpr valexpr]}))))) - -(declare analyze-file) - -(defn locate-src [relpath] - (or (io/resource relpath) - (let [root (:root @env/*compiler*) - root-path (when root (.getPath ^File root)) - f (io/file (str root-path \/ relpath))] - (when (and (.exists f) (.isFile f)) - f)))) - -(defn analyze-deps [lib deps env] - (binding [*cljs-dep-set* (vary-meta (conj *cljs-dep-set* lib) update-in [:dep-path] conj lib)] - (assert (every? #(not (contains? *cljs-dep-set* %)) deps) - (str "Circular dependency detected " (-> *cljs-dep-set* meta :dep-path))) - (doseq [dep deps] - (when-not (or (contains? (::namespaces @env/*compiler*) dep) - (contains? (:js-dependency-index @env/*compiler*) (name dep))) - (let [relpath (ns->relpath dep) - src (locate-src relpath)] - (if src - (analyze-file src) - (warning :undeclared-ns env {:ns-sym dep}))))))) - -(defn check-uses [uses env] - (doseq [[sym lib] uses] - (when (= (get-in @env/*compiler* [::namespaces lib :defs sym] ::not-found) ::not-found) - (warning :undeclared-ns-form env {:type "var" :lib lib :sym sym})))) - -(defn check-use-macros [use-macros env] - (doseq [[sym lib] use-macros] - (when (nil? (.findInternedVar ^clojure.lang.Namespace (find-ns lib) sym)) - (warning :undeclared-ns-form env {:type "macro" :lib lib :sym sym})))) - -(defn parse-ns-error-msg [spec msg] - (str msg "; offending spec: " (pr-str spec))) - -(defn basic-validate-ns-spec [env macros? spec] - (when-not (or (symbol? spec) (sequential? spec)) - (throw - (error env - (parse-ns-error-msg spec - "Only [lib.ns & options] and lib.ns specs supported in :require / :require-macros")))) - (when (sequential? spec) - (when-not (symbol? (first spec)) - (throw - (error env - (parse-ns-error-msg spec - "Library name must be specified as a symbol in :require / :require-macros")))) - (when-not (odd? (count spec)) - (throw - (error env - (parse-ns-error-msg spec - "Only :as alias and :refer (names) options supported in :require")))) - (when-not (every? #{:as :refer} (map first (partition 2 (next spec)))) - (throw - (error env - (parse-ns-error-msg spec - "Only :as and :refer options supported in :require / :require-macros")))) - (when-not (let [fs (frequencies (next spec))] - (and (<= (fs :as 0) 1) - (<= (fs :refer 0) 1))) - (throw - (error env - (parse-ns-error-msg spec - "Each of :as and :refer options may only be specified once in :require / :require-macros")))))) - -(defn parse-ns-excludes [env args] - (reduce - (fn [s [k exclude xs]] - (if (= k :refer-clojure) - (do - (when-not (= exclude :exclude) - (throw (error env "Only [:refer-clojure :exclude (names)] form supported"))) - (when (seq s) - (throw (error env "Only one :refer-clojure form is allowed per namespace definition"))) - (into s xs)) - s)) - #{} args)) - -(defn use->require [env [lib kw referred :as spec]] - (when-not (and (symbol? lib) (= :only kw) (sequential? referred) (every? symbol? referred)) - (throw - (error env - (parse-ns-error-msg spec - "Only [lib.ns :only (names)] specs supported in :use / :use-macros")))) - [lib :refer referred]) - -(defn parse-require-spec [env macros? deps aliases spec] - (if (symbol? spec) - (recur env macros? deps aliases [spec]) - (do - (basic-validate-ns-spec env macros? spec) - (let [[lib & opts] spec - {alias :as referred :refer :or {alias lib}} (apply hash-map opts) - [rk uk] (if macros? [:require-macros :use-macros] [:require :use])] - (when-not (or (symbol? alias) (nil? alias)) - (throw - (error env - (parse-ns-error-msg spec - ":as must be followed by a symbol in :require / :require-macros")))) - (when alias - (let [alias-type (if macros? :macros :fns)] - (when (contains? (alias-type @aliases) alias) - (throw (error env (parse-ns-error-msg spec ":as alias must be unique")))) - (swap! aliases - update-in [alias-type] - conj alias))) - (when-not (or (and (sequential? referred) - (every? symbol? referred)) - (nil? referred)) - (throw - (error env - (parse-ns-error-msg spec - ":refer must be followed by a sequence of symbols in :require / :require-macros")))) - (when-not macros? - (swap! deps conj lib)) - (merge - (when alias - {rk (merge {alias lib} {lib lib})}) - (when referred {uk (apply hash-map (interleave referred (repeat lib)))})))))) - -(defn parse-import-spec [env deps spec] - (when-not (or (and (sequential? spec) - (every? symbol? spec)) - (and (symbol? spec) (nil? (namespace spec)))) - (throw (error env (parse-ns-error-msg spec "Only lib.ns.Ctor or [lib.ns Ctor*] spec supported in :import")))) - (let [import-map (if (sequential? spec) - (->> (rest spec) - (map #(vector % (symbol (str (first spec) "." %)))) - (into {})) - {(symbol (last (string/split (str spec) #"\."))) spec})] - (doseq [[_ spec] import-map] - (swap! deps conj spec)) - {:import import-map - :require import-map})) - -(defn desugar-ns-specs [args] - (let [{:keys [require] :as indexed} - (->> args - (map (fn [[k & specs]] [k (into [] specs)])) - (into {})) - sugar-keys #{:include-macros :refer-macros} - to-macro-specs - (fn [specs] - (->> specs - (filter #(and (sequential? %) (some sugar-keys %))) - (map #(->> % (remove #{:include-macros true}) - (map (fn [x] (if (= x :refer-macros) :refer x))))))) - remove-sugar - (fn [spec] - (if (and (sequential? spec) (some sugar-keys spec)) - (let [[l & r] (split-with #(not (contains? sugar-keys %)) spec)] - (concat l (drop 2 r))) - spec))] - (if-let [require-specs (seq (to-macro-specs require))] - (map (fn [[k v]] (cons k (map remove-sugar v))) - (update-in indexed [:require-macros] (fnil into []) require-specs)) - args))) - -(defmethod parse 'ns - [_ env [_ name & args :as form] _] - (when-not (symbol? name) - (throw (error env "Namespaces must be named by a symbol."))) - (let [docstring (if (string? (first args)) (first args)) - args (if docstring (next args) args) - metadata (if (map? (first args)) (first args)) - args (desugar-ns-specs (if metadata (next args) args)) - excludes (parse-ns-excludes env args) - deps (atom #{}) - aliases (atom {:fns #{} :macros #{}}) - spec-parsers {:require (partial parse-require-spec env false deps aliases) - :require-macros (partial parse-require-spec env true deps aliases) - :use (comp (partial parse-require-spec env false deps aliases) - (partial use->require env)) - :use-macros (comp (partial parse-require-spec env true deps aliases) - (partial use->require env)) - :import (partial parse-import-spec env deps)} - valid-forms (atom #{:use :use-macros :require :require-macros :import}) - {uses :use requires :require use-macros :use-macros require-macros :require-macros imports :import :as params} - (reduce (fn [m [k & libs]] - (when-not (#{:use :use-macros :require :require-macros :import} k) - (throw (error env "Only :refer-clojure, :require, :require-macros, :use and :use-macros libspecs supported"))) - (when-not (@valid-forms k) - (throw (error env (str "Only one " k " form is allowed per namespace definition")))) - (swap! valid-forms disj k) - (apply merge-with merge m (map (spec-parsers k) libs))) - {} (remove (fn [[r]] (= r :refer-clojure)) args))] - (when (and *analyze-deps* (seq @deps)) - (analyze-deps name @deps env)) - (when (seq uses) - (check-uses uses env)) - (set! *cljs-ns* name) - (load-core) - (doseq [nsym (concat (vals require-macros) (vals use-macros))] - (clojure.core/require nsym)) - (when (seq use-macros) - (check-use-macros use-macros env)) - (swap! env/*compiler* update-in [::namespaces name] assoc - :name name - :doc docstring - :excludes excludes - :uses uses - :requires requires - :use-macros use-macros - :require-macros require-macros - :imports imports) - {:env env :op :ns :form form :name name :doc docstring :uses uses :requires requires :imports imports - :use-macros use-macros :require-macros require-macros :excludes excludes})) - -(defmethod parse 'deftype* - [_ env [_ tsym fields pmasks :as form] _] - (let [t (:name (resolve-var (dissoc env :locals) tsym))] - (swap! env/*compiler* update-in [::namespaces (-> env :ns :name) :defs tsym] - (fn [m] - (let [m (assoc (or m {}) - :name t - :type true - :num-fields (count fields))] - (merge m - {:protocols (-> tsym meta :protocols)} - (source-info tsym env))))) - {:env env :op :deftype* :form form :t t :fields fields :pmasks pmasks})) - -(defmethod parse 'defrecord* - [_ env [_ tsym fields pmasks :as form] _] - (let [t (:name (resolve-var (dissoc env :locals) tsym))] - (swap! env/*compiler* update-in [::namespaces (-> env :ns :name) :defs tsym] - (fn [m] - (let [m (assoc (or m {}) - :name t - :type true - :num-fields (count fields))] - (merge m - {:protocols (-> tsym meta :protocols)} - (source-info tsym env))))) - {:env env :op :defrecord* :form form :t t :fields fields :pmasks pmasks})) - -;; dot accessor code - -(def ^:private property-symbol? #(boolean (and (symbol? %) (re-matches #"^-.*" (name %))))) - -(defn- classify-dot-form - [[target member args]] - [(cond (nil? target) ::error - :default ::expr) - (cond (property-symbol? member) ::property - (symbol? member) ::symbol - (seq? member) ::list - :default ::error) - (cond (nil? args) () - :default ::expr)]) - -(defmulti build-dot-form #(classify-dot-form %)) - -;; (. o -p) -;; (. (...) -p) -(defmethod build-dot-form [::expr ::property ()] - [[target prop _]] - {:dot-action ::access :target target :field (-> prop name (.substring 1) symbol)}) - -;; (. o -p ) -(defmethod build-dot-form [::expr ::property ::list] - [[target prop args]] - (throw (Error. (str "Cannot provide arguments " args " on property access " prop)))) - -(defn- build-method-call - "Builds the intermediate method call map used to reason about the parsed form during - compilation." - [target meth args] - (if (symbol? meth) - {:dot-action ::call :target target :method meth :args args} - {:dot-action ::call :target target :method (first meth) :args args})) - -;; (. o m 1 2) -(defmethod build-dot-form [::expr ::symbol ::expr] - [[target meth args]] - (build-method-call target meth args)) - -;; (. o m) -(defmethod build-dot-form [::expr ::symbol ()] - [[target meth args]] - (build-method-call target meth args)) - -;; (. o (m)) -;; (. o (m 1 2)) -(defmethod build-dot-form [::expr ::list ()] - [[target meth-expr _]] - (build-method-call target (first meth-expr) (rest meth-expr))) - -(defmethod build-dot-form :default - [dot-form] - (throw (Error. (str "Unknown dot form of " (list* '. dot-form) " with classification " (classify-dot-form dot-form))))) - -(defmethod parse '. - [_ env [_ target & [field & member+] :as form] _] - (disallowing-recur - (let [{:keys [dot-action target method field args]} (build-dot-form [target field member+]) - enve (assoc env :context :expr) - targetexpr (analyze enve target)] - (case dot-action - ::access {:env env :op :dot :form form - :target targetexpr - :field field - :children [targetexpr] - :tag (-> form meta :tag)} - ::call (let [argexprs (map #(analyze enve %) args)] - {:env env :op :dot :form form - :target targetexpr - :method method - :args argexprs - :children (into [targetexpr] argexprs) - :tag (-> form meta :tag)}))))) - -(defmethod parse 'js* - [op env [_ jsform & args :as form] _] - (when-not (string? jsform) - (throw (error env "Invalid js* form"))) - (if args - (disallowing-recur - (let [seg (fn seg [^String s] - (let [idx (.indexOf s "~{")] - (if (= -1 idx) - (list s) - (let [end (.indexOf s "}" idx)] - (lazy-seq - (cons (subs s 0 idx) - (seg (subs s (inc end))))))))) - enve (assoc env :context :expr) - argexprs (vec (map #(analyze enve %) args))] - (when (-> form meta :numeric) - (let [types (map #(infer-tag env %) argexprs)] - (when-not (every? - (fn [t] - (or (nil? t) - (and (symbol? t) ('#{any number} t)) - ;; TODO: type inference is not strong enough to detect that - ;; when functions like first won't return nil, so variadic - ;; numeric functions like cljs.core/< would produce a spurious - ;; warning without this - David - (and (set? t) (set/subset? t '#{any number nil clj-nil})))) - types) - (warning :invalid-arithmetic env - {:js-op (-> form meta :js-op) - :types (into [] types)})))) - {:env env :op :js :segs (seg jsform) :args argexprs - :tag (or (-> form meta :tag) - (and (-> form meta :numeric) 'number) - nil) - :form form :children argexprs - :js-op (-> form meta :js-op) - :numeric (-> form meta :numeric)})) - (let [interp (fn interp [^String s] - (let [idx (.indexOf s "~{")] - (if (= -1 idx) - (list s) - (let [end (.indexOf s "}" idx) - inner (:name (resolve-existing-var env (symbol (subs s (+ 2 idx) end))))] - (lazy-seq - (cons (subs s 0 idx) - (cons inner - (interp (subs s (inc end))))))))))] - {:env env :op :js :form form :code (apply str (interp jsform)) - :tag (or (-> form meta :tag) - (and (-> form meta :numeric) 'number) - nil) - :js-op (-> form meta :js-op) - :numeric (-> form meta :numeric)}))) - -(defn parse-invoke - [env [f & args :as form]] - (disallowing-recur - (let [enve (assoc env :context :expr) - fexpr (analyze enve f) - argexprs (vec (map #(analyze enve %) args)) - argc (count args)] - (when (-> fexpr :info :fn-var) - (let [{:keys [variadic max-fixed-arity method-params name]} (:info fexpr)] - (when (and (not (some #{argc} (map count method-params))) - (or (not variadic) - (and variadic (< argc max-fixed-arity)))) - (warning :fn-arity env {:name name - :argc argc})))) - (when (and (-> fexpr :info :deprecated) - (not (-> form meta :deprecation-nowarn))) - (warning :fn-deprecated env {:fexpr fexpr})) - (when (-> fexpr :info :type) - (warning :invoke-ctor env {:fexpr fexpr})) - {:env env :op :invoke :form form :f fexpr :args argexprs - :children (into [fexpr] argexprs)}))) - -(defn analyze-symbol - "Finds the var associated with sym" - [env sym] - (if (:quoted? env) - {:op :constant :env env :form sym :tag 'cljs.core/Symbol} - (let [{:keys [line column]} (meta sym) - env (cond-> env - line (assoc :line line) - column (assoc :column column)) - ret {:env env :form sym} - lb (-> env :locals sym)] - (if lb - (assoc ret :op :var :info lb) - (if-not (:def-var env) - (assoc ret :op :var :info (resolve-existing-var env sym)) - (assoc ret :op :var :info (resolve-var env sym))))))) - -(defn get-expander [sym env] - (let [mvar - (when-not (or (-> env :locals sym) ;locals hide macros - (and (or (-> env :ns :excludes sym) - (get-in @env/*compiler* [::namespaces (-> env :ns :name) :excludes sym])) - (not (or (-> env :ns :use-macros sym) - (get-in @env/*compiler* [::namespaces (-> env :ns :name) :use-macros sym]))))) - (if-let [nstr (namespace sym)] - (when-let [ns (cond - (= "clojure.core" nstr) (find-ns 'cljs.core) - (.contains nstr ".") (find-ns (symbol nstr)) - :else - (some-> env :ns :require-macros (get (symbol nstr)) find-ns))] - (.findInternedVar ^clojure.lang.Namespace ns (symbol (name sym)))) - (if-let [nsym (-> env :ns :use-macros sym)] - (.findInternedVar ^clojure.lang.Namespace (find-ns nsym) sym) - (.findInternedVar ^clojure.lang.Namespace (find-ns 'cljs.core) sym))))] - (when (and mvar (.isMacro ^clojure.lang.Var mvar)) - (with-meta @mvar (meta mvar))))) - -(defn macroexpand-1 [env form] - (env/ensure - (let [op (first form)] - (if (specials op) - form - (if-let [mac (and (symbol? op) (get-expander op env))] - (binding [*ns* (create-ns *cljs-ns*)] - (let [form' (apply mac form env (rest form))] - (if (seq? form') - (let [sym' (first form') - sym (first form)] - (if (= sym' 'js*) - (vary-meta form' merge - (cond-> {:js-op (if (namespace sym) sym (symbol "cljs.core" (str sym)))} - (-> mac meta ::numeric) (assoc :numeric true))) - form')) - form'))) - (if (symbol? op) - (let [opname (str op)] - (cond - (= (first opname) \.) (let [[target & args] (next form)] - (with-meta (list* '. target (symbol (subs opname 1)) args) - (meta form))) - (= (last opname) \.) (with-meta - (list* 'new (symbol (subs opname 0 (dec (count opname)))) (next form)) - (meta form)) - :else form)) - form)))))) - -(declare analyze-list) - -(defn analyze-seq - [env form name] - (if (:quoted? env) - (analyze-list env form) - (let [env (assoc env - :line (or (-> form meta :line) - (:line env)) - :column (or (-> form meta :column) - (:column env)))] - (let [op (first form)] - (when (nil? op) - (throw (error env "Can't call nil"))) - (let [mform (macroexpand-1 env form)] - (if (identical? form mform) - (wrapping-errors env - (if (specials op) - (parse op env form name) - (parse-invoke env form))) - (analyze env mform name))))))) - -(declare analyze-wrap-meta) - -(defn analyze-map - [env form] - (let [expr-env (assoc env :context :expr) - ks (disallowing-recur (vec (map #(analyze expr-env %) (keys form)))) - vs (disallowing-recur (vec (map #(analyze expr-env %) (vals form))))] - (analyze-wrap-meta {:op :map :env env :form form - :keys ks :vals vs - :children (vec (interleave ks vs)) - :tag 'cljs.core/IMap}))) - -(defn analyze-list - [env form] - (let [expr-env (assoc env :context :expr) - items (disallowing-recur (doall (map #(analyze expr-env %) form)))] - (analyze-wrap-meta {:op :list :env env :form form :items items :children items :tag 'cljs.core/IList}))) - -(defn analyze-vector - [env form] - (let [expr-env (assoc env :context :expr) - items (disallowing-recur (vec (map #(analyze expr-env %) form)))] - (analyze-wrap-meta {:op :vector :env env :form form :items items :children items :tag 'cljs.core/IVector}))) - -(defn analyze-set - [env form ] - (let [expr-env (assoc env :context :expr) - items (disallowing-recur (vec (map #(analyze expr-env %) form)))] - (analyze-wrap-meta {:op :set :env env :form form :items items :children items :tag 'cljs.core/ISet}))) - -(defn analyze-js-value - [env ^JSValue form] - (let [val (.val form) - expr-env (assoc env :context :expr) - items (if (map? val) - (zipmap (keys val) - (disallowing-recur (doall (map #(analyze expr-env %) (vals val))))) - (disallowing-recur (doall (map #(analyze expr-env %) val))))] - {:op :js-value - :js-type (if (map? val) :object :array) - :env env - :form form - :items items - :children items - :tag (if (map? val) 'object 'array)})) - -(defn analyze-wrap-meta [expr] - - (let [form (:form expr) - m (dissoc (meta form) :line :column :end-column :end-line :source)] - (if (seq m) - (let [env (:env expr) ; take on expr's context ourselves - expr (assoc-in expr [:env :context] :expr) ; change expr to :expr - meta-expr (analyze-map (:env expr) m)] - {:op :meta :env env :form form - :meta meta-expr :expr expr :children [meta-expr expr]}) - expr))) - -(defn infer-type [env ast] - (if-let [tag (and (not (:tag ast)) - (infer-tag env ast))] - (assoc ast :tag tag) - ast)) - -(def ^:dynamic *passes* nil) - -(defn analyze - "Given an environment, a map containing {:locals (mapping of names to bindings), :context - (one of :statement, :expr, :return), :ns (a symbol naming the - compilation ns)}, and form, returns an expression object (a map - containing at least :form, :op and :env keys). If expr has any (immediately) - nested exprs, must have :children [exprs...] entry. This will - facilitate code walking without knowing the details of the op set." - ([env form] (analyze env form nil)) - ([env form name] - (env/ensure - (wrapping-errors env - (reduce (fn [ast pass] (pass env ast)) - (binding [reader/*alias-map* (or reader/*alias-map* {})] - (let [form (if (instance? clojure.lang.LazySeq form) - (or (seq form) ()) - form)] - (load-core) - (cond - (symbol? form) (analyze-symbol env form) - (and (seq? form) (seq form)) (analyze-seq env form name) - (map? form) (analyze-map env form) - (vector? form) (analyze-vector env form) - (set? form) (analyze-set env form) - (keyword? form) (analyze-keyword env form) - (instance? JSValue form) (analyze-js-value env form) - (= form ()) (analyze-list env form) - :else - (let [tag (cond - (nil? form) 'clj-nil - (number? form) 'number - (string? form) 'string - (true? form) 'boolean - (false? form) 'boolean)] - (cond-> {:op :constant :env env :form form} - tag (assoc :tag tag)))))) - (or *passes* [infer-type])))))) - -(defn- source-path - "Returns a path suitable for providing to tools.reader as a 'filename'." - [x] - (cond - (instance? File x) (.getAbsolutePath ^File x) - :default (str x))) - -(defn forms-seq - "Seq of Clojure/ClojureScript forms from [f], which can be anything for which -`clojure.java.io/reader` can produce a `java.io.Reader`. Optionally accepts a [filename] -argument, which the reader will use in any emitted errors." - ([f] (forms-seq f (source-path f))) - ([f filename] - (let [rdr (io/reader f) - pbr (readers/indexing-push-back-reader - (java.io.PushbackReader. rdr) 1 filename) - data-readers tags/*cljs-data-readers* - forms-seq* - (fn forms-seq* [] - (lazy-seq - (let [eof-sentinel (Object.) - form (binding [*ns* (create-ns *cljs-ns*) - reader/*data-readers* data-readers - reader/*alias-map* - (apply merge - ((juxt :requires :require-macros) - (get-namespace *cljs-ns*)))] - (reader/read pbr nil eof-sentinel))] - (if (identical? form eof-sentinel) - (.close rdr) - (cons form (forms-seq*))))))] - (forms-seq*)))) - -(defn analyze-file [f] - (let [res (cond - (instance? File f) f - (instance? java.net.URL f) f - (re-find #"^file://" f) (java.net.URL. f) - :else (io/resource f))] - (assert res (str "Can't find " f " in classpath")) - (env/ensure - (let [path (if (instance? File res) - (.getPath ^File res) - (.getPath ^java.net.URL res))] - (when-not (get-in @env/*compiler* [::analyzed-cljs path]) - (binding [*cljs-ns* 'cljs.user - *cljs-file* path - reader/*alias-map* (or reader/*alias-map* {})] - (let [env (empty-env)] - (doseq [form (seq (forms-seq res))] - (let [env (assoc env :ns (get-namespace *cljs-ns*))] - (analyze env form))))) - (swap! env/*compiler* assoc-in [::analyzed-cljs path] true)))))) - diff --git a/src/clj/cljs/closure.clj b/src/clj/cljs/closure.clj deleted file mode 100644 index 04729663c8..0000000000 --- a/src/clj/cljs/closure.clj +++ /dev/null @@ -1,999 +0,0 @@ -; Copyright (c) Rich Hickey. All rights reserved. -; The use and distribution terms for this software are covered by the -; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -; which can be found in the file epl-v10.html at the root of this distribution. -; By using this software in any fashion, you are agreeing to be bound by -; the terms of this license. -; You must not remove this notice, or any other, from this software. - -(ns cljs.closure - "Compile ClojureScript to JavaScript with optimizations from Google - Closure Compiler producing runnable JavaScript. - - The Closure Compiler (compiler.jar) must be on the classpath. - - Use the 'build' function for end-to-end compilation. - - build = compile -> add-dependencies -> optimize -> output - - Two protocols are defined: IJavaScript and Compilable. The - Compilable protocol is satisfied by something which can return one - or more IJavaScripts. - - With IJavaScript objects in hand, calling add-dependencies will - produce a sequence of IJavaScript objects which includes all - required dependencies from the Closure library and ClojureScript, - in dependency order. This function replaces the closurebuilder - tool. - - The optimize function converts one or more IJavaScripts into a - single string of JavaScript source code using the Closure Compiler - API. - - The produced output is either a single string of optimized - JavaScript or a deps file for use during development. - " - (:require [cljs.compiler :as comp] - [cljs.analyzer :as ana] - [cljs.source-map :as sm] - [cljs.env :as env] - [cljs.js-deps :as deps] - [clojure.java.io :as io] - [clojure.string :as string] - [clojure.data.json :as json]) - (:import java.io.File - java.io.BufferedInputStream - java.net.URL - java.util.logging.Level - java.util.jar.JarFile - java.util.List - com.google.common.collect.ImmutableList - com.google.javascript.jscomp.CompilerOptions - com.google.javascript.jscomp.CompilerOptions$LanguageMode - com.google.javascript.jscomp.CompilationLevel - com.google.javascript.jscomp.SourceMap$Format - com.google.javascript.jscomp.SourceMap$DetailLevel - com.google.javascript.jscomp.ClosureCodingConvention - com.google.javascript.jscomp.JSSourceFile - com.google.javascript.jscomp.Result - com.google.javascript.jscomp.JSError - com.google.javascript.jscomp.CheckLevel - com.google.javascript.jscomp.DiagnosticGroups - com.google.javascript.jscomp.CommandLineRunner)) - -(defmacro ^:private debug-prn - [& args] - `(.println System/err (str ~@args))) - -(def name-chars (map char (concat (range 48 57) (range 65 90) (range 97 122)))) - -(defn random-char [] - (nth name-chars (.nextInt (java.util.Random.) (count name-chars)))) - -(defn random-string [length] - (apply str (take length (repeatedly random-char)))) - -;; Closure API -;; =========== - -(defmulti js-source-file (fn [_ source] (class source))) - -(defmethod js-source-file String [^String name ^String source] - (JSSourceFile/fromCode name source)) - -(defmethod js-source-file File [_ ^File source] - (JSSourceFile/fromFile source)) - -(defmethod js-source-file BufferedInputStream [^String name ^BufferedInputStream source] - (JSSourceFile/fromInputStream name source)) - -(defn set-options - "TODO: Add any other options that we would like to support." - [opts ^CompilerOptions compiler-options] - (when (contains? opts :pretty-print) - (set! (.prettyPrint compiler-options) (:pretty-print opts))) - - (when (contains? opts :language-in) - (case (:language-in opts) - :ecmascript5 (.setLanguageIn compiler-options CompilerOptions$LanguageMode/ECMASCRIPT5) - :ecmascript5-strict (.setLanguageIn compiler-options CompilerOptions$LanguageMode/ECMASCRIPT5_STRICT) - :ecmascript3 (.setLanguageIn compiler-options CompilerOptions$LanguageMode/ECMASCRIPT3))) - - (when (contains? opts :language-out) - (case (:language-out opts) - :ecmascript5 (.setLanguageOut compiler-options CompilerOptions$LanguageMode/ECMASCRIPT5) - :ecmascript5-strict (.setLanguageOut compiler-options CompilerOptions$LanguageMode/ECMASCRIPT5_STRICT) - :ecmascript3 (.setLanguageOut compiler-options CompilerOptions$LanguageMode/ECMASCRIPT3))) - - (when (contains? opts :print-input-delimiter) - (set! (.printInputDelimiter compiler-options) - (:print-input-delimiter opts)))) - -(def check-level - {:error CheckLevel/ERROR - :warning CheckLevel/WARNING - :off CheckLevel/OFF}) - -(def warning-types - {:access-controls DiagnosticGroups/ACCESS_CONTROLS - :ambiguous-function-decl DiagnosticGroups/AMBIGUOUS_FUNCTION_DECL - :debugger-statement-present DiagnosticGroups/DEBUGGER_STATEMENT_PRESENT - :check-regexp DiagnosticGroups/CHECK_REGEXP - :check-types DiagnosticGroups/CHECK_TYPES - :check-useless-code DiagnosticGroups/CHECK_USELESS_CODE - :check-variables DiagnosticGroups/CHECK_VARIABLES - :const DiagnosticGroups/CONST - :constant-property DiagnosticGroups/CONSTANT_PROPERTY - :deprecated DiagnosticGroups/DEPRECATED - :duplicate-message DiagnosticGroups/DUPLICATE_MESSAGE - :es5-strict DiagnosticGroups/ES5_STRICT - :externs-validation DiagnosticGroups/EXTERNS_VALIDATION - :fileoverview-jsdoc DiagnosticGroups/FILEOVERVIEW_JSDOC - :global-this DiagnosticGroups/GLOBAL_THIS - :internet-explorer-checks DiagnosticGroups/INTERNET_EXPLORER_CHECKS - :invalid-casts DiagnosticGroups/INVALID_CASTS - :missing-properties DiagnosticGroups/MISSING_PROPERTIES - :non-standard-jsdoc DiagnosticGroups/NON_STANDARD_JSDOC - :strict-module-dep-check DiagnosticGroups/STRICT_MODULE_DEP_CHECK - :tweaks DiagnosticGroups/TWEAKS - :undefined-names DiagnosticGroups/UNDEFINED_NAMES - :undefined-variables DiagnosticGroups/UNDEFINED_VARIABLES - :unknown-defines DiagnosticGroups/UNKNOWN_DEFINES - :visiblity DiagnosticGroups/VISIBILITY}) - -(defn ^CompilerOptions make-options - "Create a CompilerOptions object and set options from opts map." - [opts] - (let [level (case (:optimizations opts) - :advanced CompilationLevel/ADVANCED_OPTIMIZATIONS - :whitespace CompilationLevel/WHITESPACE_ONLY - :simple CompilationLevel/SIMPLE_OPTIMIZATIONS) - compiler-options (doto (CompilerOptions.) - (.setCodingConvention (ClosureCodingConvention.)))] - (doseq [[key val] (:closure-defines opts)] - (let [key (name key)] - (cond - (string? val) (.setDefineToStringLiteral compiler-options key val) - (integer? val) (.setDefineToIntegerLiteral compiler-options key val) - (float? val) (.setDefineToDoubleLiteral compiler-options key val) - (or (true? val) - (false? val)) (.setDefineToBooleanLiteral compiler-options key val) - :else (println "value for" key "must be string, int, float, or bool")))) - (doseq [[type level] (:closure-warnings opts)] - (. compiler-options - (setWarningLevel (type warning-types) (level check-level)))) - (when (contains? opts :source-map) - (set! (.sourceMapOutputPath compiler-options) - (:source-map opts)) - (set! (.sourceMapDetailLevel compiler-options) - SourceMap$DetailLevel/ALL) - (set! (.sourceMapFormat compiler-options) - SourceMap$Format/V3)) - (do (.setOptionsForCompilationLevel level compiler-options) - (set-options opts compiler-options) - compiler-options))) - -(defn load-externs - "Externs are JavaScript files which contain empty definitions of - functions which will be provided by the envorinment. Any function in - an extern file will not be renamed during optimization. - - Options may contain an :externs key with a list of file paths to - load. The :use-only-custom-externs flag may be used to indicate that - the default externs should be excluded." - [{:keys [externs use-only-custom-externs target ups-externs]}] - (let [filter-cp-js (fn [paths] - (for [p paths u (deps/find-js-classpath p)] u)) - filter-js (fn [paths] - (for [p paths u (deps/find-js-resources p)] u)) - add-target (fn [ext] - (if (= :nodejs target) - (cons (io/resource "cljs/nodejs_externs.js") - (or ext [])) - ext)) - load-js (fn [ext] - (map #(js-source-file (.getFile %) (slurp %)) ext))] - (let [js-sources (-> externs filter-js add-target load-js) - ups-sources (-> ups-externs filter-cp-js load-js) - all-sources (concat js-sources ups-sources)] - (if use-only-custom-externs - all-sources - (into all-sources (CommandLineRunner/getDefaultExterns)))))) - -(defn ^com.google.javascript.jscomp.Compiler make-closure-compiler [] - (let [compiler (com.google.javascript.jscomp.Compiler.)] - (do (com.google.javascript.jscomp.Compiler/setLoggingLevel Level/WARNING) - compiler))) - -(defn report-failure [^Result result] - (let [errors (.errors result) - warnings (.warnings result)] - (doseq [next (seq errors)] - (println "ERROR:" (.toString ^JSError next))) - (doseq [next (seq warnings)] - (println "WARNING:" (.toString ^JSError next))))) - - -;; Protocols for IJavaScript and Compilable -;; ======================================== - - - -(defprotocol ISourceMap - (-source-url [this] "Return the CLJS source url") - (-source-map [this] "Return the CLJS compiler generated JS source mapping")) - -(extend-protocol deps/IJavaScript - - String - (-foreign? [this] false) - (-url [this] nil) - (-provides [this] (:provides (deps/parse-js-ns (string/split-lines this)))) - (-requires [this] (:requires (deps/parse-js-ns (string/split-lines this)))) - (-source [this] this) - - clojure.lang.IPersistentMap - (-foreign? [this] (:foreign this)) - (-url [this] (or (:url this) - (deps/to-url (:file this)))) - (-provides [this] (map name (:provides this))) - (-requires [this] (map name (:requires this))) - (-source [this] (if-let [s (:source this)] - s (with-open [reader (io/reader (deps/-url this))] - (slurp reader))))) - -(defrecord JavaScriptFile [foreign ^URL url ^URL source-url provides requires lines source-map] - deps/IJavaScript - (-foreign? [this] foreign) - (-url [this] url) - (-provides [this] provides) - (-requires [this] requires) - (-source [this] - (with-open [reader (io/reader url)] - (slurp reader))) - ISourceMap - (-source-url [this] source-url) - (-source-map [this] source-map)) - -(defn javascript-file - ([foreign ^URL url provides requires] - (javascript-file foreign url nil provides requires nil nil)) - ([foreign ^URL url source-url provides requires lines source-map] - (assert (first provides) (str source-url " does not provide a namespace")) - (JavaScriptFile. foreign url source-url (map name provides) (map name requires) lines source-map))) - -(defn map->javascript-file [m] - (javascript-file - (:foreign m) - (when-let [f (:file m)] - (deps/to-url f)) - (when-let [sf (:source-file m)] - (deps/to-url sf)) - (:provides m) - (:requires m) - (:lines m) - (:source-map m))) - -(defn read-js - "Read a JavaScript file returning a map of file information." - [f] - (let [source (slurp f) - m (deps/parse-js-ns (string/split-lines source))] - (map->javascript-file (assoc m :file f)))) - - -;; Compile -;; ======= - -(defprotocol Compilable - (-compile [this opts] "Returns one or more IJavaScripts.")) - -(defn compile-form-seq - "Compile a sequence of forms to a JavaScript source string." - [forms] - (comp/with-core-cljs - (with-out-str - (binding [ana/*cljs-ns* 'cljs.user] - (doseq [form forms] - (comp/emit (ana/analyze (ana/empty-env) form))))))) - -(defn output-directory [opts] - (or (:output-dir opts) "out")) - -(defn compiled-file - "Given a map with at least a :file key, return a map with - {:file .. :provides .. :requires ..}. - - Compiled files are cached so they will only be read once." - [m] - (let [path (.getPath (.toURL ^File (:file m))) - js (if (:provides m) - (map->javascript-file m) - (if-let [js (get-in @env/*compiler* [::compiled-cljs path])] - js - (read-js (:file m))))] - (do (swap! env/*compiler* update-in [::compiled-cljs] assoc path js) - js))) - -(defn compile-file - "Compile a single cljs file. If no output-file is specified, returns - a string of compiled JavaScript. With an output-file option, the - compiled JavaScript will written to this location and the function - returns a JavaScriptFile. In either case the return value satisfies - IJavaScript." - [^File file {:keys [output-file] :as opts}] - (if output-file - (let [out-file (io/file (output-directory opts) output-file)] - (compiled-file (comp/compile-file file out-file opts))) - (binding [ana/*cljs-file* (.getPath ^java.io.File file)] - (compile-form-seq (ana/forms-seq file))))) - -(defn compile-dir - "Recursively compile all cljs files under the given source - directory. Return a list of JavaScriptFiles." - [^File src-dir opts] - (let [out-dir (output-directory opts)] - (map compiled-file - (comp/compile-root src-dir out-dir opts)))) - -(defn path-from-jarfile - "Given the URL of a file within a jar, return the path of the file - from the root of the jar." - [^URL url] - (last (string/split (.getFile url) #"\.jar!/"))) - -(defn jar-file-to-disk - "Copy a file contained within a jar to disk. Return the created file." - [url out-dir] - (let [out-file (io/file out-dir (path-from-jarfile url)) - content (with-open [reader (io/reader url)] - (slurp reader))] - (do (comp/mkdirs out-file) - (spit out-file content) - out-file))) - -;; TODO: it would be nice if we could consolidate requires-compilation? -;; logic - David -(defn compile-from-jar - "Compile a file from a jar." - [this {:keys [output-file] :as opts}] - (or (when output-file - (let [out-file (io/file (output-directory opts) output-file)] - (when (and (.exists out-file) - (= (comp/compiled-by-version out-file) - (comp/clojurescript-version))) - (compile-file - (io/file (output-directory opts) - (last (string/split (.getPath ^URL this) #"\.jar!/"))) - opts)))) - (let [file-on-disk (jar-file-to-disk this (output-directory opts))] - (-compile file-on-disk opts)))) - -(extend-protocol Compilable - - File - (-compile [this opts] - (if (.isDirectory this) - (compile-dir this opts) - (compile-file this opts))) - - URL - (-compile [this opts] - (case (.getProtocol this) - "file" (-compile (io/file this) opts) - "jar" (compile-from-jar this opts))) - - clojure.lang.PersistentList - (-compile [this opts] - (compile-form-seq [this])) - - String - (-compile [this opts] (-compile (io/file this) opts)) - - clojure.lang.PersistentVector - (-compile [this opts] (compile-form-seq this)) - ) - -(comment - ;; compile a file in memory - (-compile "samples/hello/src/hello/core.cljs" {}) - ;; compile a file to disk - see file @ 'out/clojure/set.js' - (-compile (io/resource "clojure/set.cljs") {:output-file "clojure/set.js"}) - ;; compile a project - (-compile (io/file "samples/hello/src") {}) - ;; compile a project with a custom output directory - (-compile (io/file "samples/hello/src") {:output-dir "my-output"}) - ;; compile a form - (-compile '(defn plus-one [x] (inc x)) {}) - ;; compile a vector of forms - (-compile '[(ns test.app (:require [goog.array :as array])) - (defn plus-one [x] (inc x))] - {}) - ) - -(defn js-dependencies - "Given a sequence of Closure namespace strings, return the list of - all dependencies. The returned list includes all Google and - third-party library dependencies. - - Third-party libraries are configured using the :libs option where - the value is a list of directories containing third-party - libraries." - [opts requires] - (loop [requires requires - visited (set requires) - deps #{}] - (if (seq requires) - (let [node (get (@env/*compiler* :js-dependency-index) (first requires)) - new-req (remove #(contains? visited %) (:requires node))] - (recur (into (rest requires) new-req) - (into visited new-req) - (conj deps node))) - (remove nil? deps)))) - -(comment - ;; find dependencies - (js-dependencies {} ["goog.array"]) - ;; find dependencies in an external library - (js-dependencies {:libs ["closure/library/third_party/closure"]} ["goog.dom.query"]) - ) - -(defn get-compiled-cljs - "Return an IJavaScript for this file. Compiled output will be - written to the working directory." - [opts {:keys [relative-path uri]}] - (let [js-file (comp/rename-to-js relative-path)] - (-compile uri (merge opts {:output-file js-file})))) - -(defn cljs-dependencies - "Given a list of all required namespaces, return a list of - IJavaScripts which are the cljs dependencies. The returned list will - not only include the explicitly required files but any transitive - depedencies as well. JavaScript files will be compiled to the - working directory if they do not already exist. - - Only load dependencies from the classpath." - [opts requires] - (letfn [(ns->cp [s] (str (string/replace (munge s) \. \/) ".cljs")) - (cljs-deps [coll] - (->> coll - (remove (@env/*compiler* :js-dependency-index)) - (map #(let [f (ns->cp %)] (hash-map :relative-path f :uri (io/resource f)))) - (remove #(nil? (:uri %)))))] - (loop [required-files (cljs-deps requires) - visited (set required-files) - js-deps #{}] - (if (seq required-files) - (let [next-file (first required-files) - js (get-compiled-cljs opts next-file) - new-req (remove #(contains? visited %) (cljs-deps (deps/-requires js)))] - (recur (into (rest required-files) new-req) - (into visited new-req) - (conj js-deps js))) - (remove nil? js-deps))))) - -(comment - ;; only get cljs deps - (cljs-dependencies {} ["goog.string" "cljs.core"]) - ;; get transitive deps - (cljs-dependencies {} ["clojure.string"]) - ;; don't get cljs.core twice - (cljs-dependencies {} ["cljs.core" "clojure.string"]) - ) - -(defn add-dependencies - "Given one or more IJavaScript objects in dependency order, produce - a new sequence of IJavaScript objects which includes the input list - plus all dependencies in dependency order." - [opts & inputs] - (let [requires (mapcat deps/-requires inputs) - required-cljs (remove (set inputs) (cljs-dependencies opts requires)) - required-js (js-dependencies opts (set (concat (mapcat deps/-requires required-cljs) requires))) - provided (mapcat deps/-provides (concat inputs required-cljs required-js)) - unprovided (clojure.set/difference (set requires) (set provided) #{"constants-table"})] - (when (seq unprovided) - (ana/warning :unprovided @env/*compiler* {:unprovided (sort unprovided)})) - (cons (javascript-file nil (io/resource "goog/base.js") ["goog"] nil) - (deps/dependency-order - (concat (map #(-> (javascript-file (:foreign %) - (or (:url %) (io/resource (:file %))) - (:provides %) - (:requires %)) - (assoc :group (:group %))) required-js) - [(when (-> @env/*compiler* :opts :emit-constants) - (let [url (deps/to-url (str (output-directory opts) "/constants_table.js"))] - (javascript-file nil url url ["constants-table"] ["cljs.core"] nil nil)))] - required-cljs - inputs))))) - -(defn preamble-from-paths [paths] - (str (apply str (map #(slurp (io/resource %)) paths)) "\n")) - -(defn make-preamble [{:keys [target preamble hashbang]}] - (str (when (= :nodejs target) - (str "#!" (or hashbang "/usr/bin/env node") "\n")) - (when preamble (preamble-from-paths preamble)))) - -(comment - ;; add dependencies to literal js - (add-dependencies {} "goog.provide('test.app');\ngoog.require('cljs.core');") - (add-dependencies {} "goog.provide('test.app');\ngoog.require('goog.array');") - (add-dependencies {} (str "goog.provide('test.app');\n" - "goog.require('goog.array');\n" - "goog.require('clojure.set');")) - ;; add dependencies with external lib - (add-dependencies {:libs ["closure/library/third_party/closure"]} - (str "goog.provide('test.app');\n" - "goog.require('goog.array');\n" - "goog.require('goog.dom.query');")) - ;; add dependencies with foreign lib - (add-dependencies {:foreign-libs [{:file "samples/hello/src/hello/core.cljs" - :provides ["example.lib"]}]} - (str "goog.provide('test.app');\n" - "goog.require('example.lib');\n")) - ;; add dependencies to a JavaScriptFile record - (add-dependencies {} (javascript-file false - (deps/to-url "samples/hello/src/hello/core.cljs") - ["hello.core"] - ["goog.array"])) - ) - -;; Optimize -;; ======== - -(defmulti javascript-name class) - -(defmethod javascript-name URL [^URL url] - (if url (.getPath url) "cljs/user.js")) - -(defmethod javascript-name String [s] - (if-let [name (first (deps/-provides s))] name "cljs/user.js")) - -(defmethod javascript-name JavaScriptFile [js] (javascript-name (deps/-url js))) - -(defn build-provides - "Given a vector of provides, builds required goog.provide statements" - [provides] - (apply str (map #(str "goog.provide('" % "');\n") provides))) - - -(defmethod js-source-file JavaScriptFile [_ js] - (when-let [url (deps/-url js)] - (js-source-file (javascript-name url) - (if (deps/-foreign? js) - (str (build-provides (deps/-provides js)) (slurp url)) - (io/input-stream url))))) - -(defn optimize - "Use the Closure Compiler to optimize one or more JavaScript files." - [opts & sources] - (let [closure-compiler (make-closure-compiler) - ^List externs (load-externs opts) - compiler-options (make-options opts) - sources (if (= :whitespace (:optimizations opts)) - (cons "var CLOSURE_NO_DEPS = true;" sources) - sources) - ^List inputs (map #(js-source-file (javascript-name %) %) sources) - result ^Result (.compile closure-compiler externs inputs compiler-options) - preamble (make-preamble opts) - preamble-line-count (- (count (.split #"\r?\n" preamble -1)) 1)] - (if (.success result) - ;; compiler.getSourceMap().reset() - (let [source (.toSource closure-compiler)] - (when-let [name (:source-map opts)] - (with-open [out (io/writer name)] - (.appendTo (.getSourceMap closure-compiler) out name)) - (let [sm-json (-> (io/file name) slurp - (json/read-str :key-fn keyword)) - closure-source-map (sm/decode sm-json)] - (loop [sources (seq sources) - relpaths {} - merged (sorted-map-by - (sm/source-compare - (remove nil? - (map (fn [source] - (if-let [^URL source-url (:source-url source)] - (.getPath source-url) - (if-let [^URL url (:url source)] - (.getPath url)))) - sources))))] - (if sources - (let [source (first sources)] - (recur - (next sources) - (let [{:keys [provides source-url]} source] - (if (and provides source-url) - (assoc relpaths (.getPath ^URL source-url) - (ana/ns->relpath (first provides))) - relpaths)) - (if-let [url (:url source)] - (let [path (.getPath ^URL url)] - (if-let [compiled (get-in @env/*compiler* [::comp/compiled-cljs path])] - (if-let [source-url (:source-url source)] - (assoc merged (.getPath ^URL source-url) - (sm/merge-source-maps - (:source-map compiled) - (get closure-source-map path))) - merged) - (assoc merged path (get closure-source-map path)))) - merged))) - (spit (io/file name) - (sm/encode merged - {:preamble-line-count preamble-line-count - :lines (+ (:lineCount sm-json) preamble-line-count 2) - :file (:file sm-json) - :output-dir (output-directory opts) - :source-map-path (:source-map-path opts) - :source-map (:source-map opts) - :relpaths relpaths})))))) - source) - (report-failure result)))) - -(comment - ;; optimize JavaScript strings - (optimize {:optimizations :whitespace} "var x = 3 + 2; alert(x);") - ;; => "var x=3+2;alert(x);" - (optimize {:optimizations :simple} "var x = 3 + 2; alert(x);") - ;; => "var x=5;alert(x);" - (optimize {:optimizations :advanced} "var x = 3 + 2; alert(x);") - ;; => "alert(5);" - - ;; optimize a ClojureScript form - (optimize {:optimizations :simple} (-compile '(def x 3) {})) - - ;; optimize a project - (println (->> (-compile "samples/hello/src" {}) - (apply add-dependencies {}) - (apply optimize {:optimizations :simple :pretty-print true}))) - ) - -;; Output -;; ====== -;; -;; The result of a build is always a single string of JavaScript. The -;; build process may produce files on disk but a single string is -;; always output. What this string contains depends on whether the -;; input has been optimized or not. If the :output-to option is set -;; then this string will be written to the specified file. If not, it -;; will be returned. -;; -;; The :output-dir option can be used to set the working directory -;; where any files will be written to disk. By default this directory -;; is 'out'. -;; -;; If inputs are optimized then the output string will be the complete -;; application with all dependencies included. -;; -;; For unoptimized output, the string will be a Closure deps file -;; describing where the JavaScript files are on disk and their -;; dependencies. All JavaScript files will be located in the working -;; directory, including any dependencies from the Closure library. -;; -;; Unoptimized mode is faster because the Closure Compiler is not -;; run. It also makes debugging much simpler because each file is -;; loaded in its own script tag. -;; -;; When working with uncompiled files, you will need to add additional -;; script tags to the hosting HTML file: one which pulls in Closure -;; library's base.js and one which calls goog.require to load your -;; code. See samples/hello/hello-dev.html for an example. - -(defn path-relative-to - "Generate a string which is the path to input relative to base." - [^File base input] - (let [base-path (comp/path-seq (.getCanonicalPath base)) - input-path (comp/path-seq (.getCanonicalPath (io/file ^URL (deps/-url input)))) - count-base (count base-path) - common (count (take-while true? (map #(= %1 %2) base-path input-path))) - prefix (repeat (- count-base common 1) "..")] - (if (= count-base common) - (last input-path) ;; same file - (comp/to-path (concat prefix (drop common input-path)) "/")))) - -(defn add-dep-string - "Return a goog.addDependency string for an input." - [opts input] - (letfn [(ns-list [coll] (when (seq coll) (apply str (interpose ", " (map #(str "'" (comp/munge %) "'") coll)))))] - (str "goog.addDependency(\"" - (path-relative-to (io/file (output-directory opts) "goog/base.js") input) - "\", [" - (ns-list (deps/-provides input)) - "], [" - (ns-list (deps/-requires input)) - "]);"))) - -(defn deps-file - "Return a deps file string for a sequence of inputs." - [opts sources] - (apply str (interpose "\n" (map #(add-dep-string opts %) sources)))) - -(comment - (path-relative-to (io/file "out/goog/base.js") {:url (deps/to-url "out/cljs/core.js")}) - (add-dep-string {} {:url (deps/to-url "out/cljs/core.js") :requires ["goog.string"] :provides ["cljs.core"]}) - (deps-file {} [{:url (deps/to-url "out/cljs/core.js") :requires ["goog.string"] :provides ["cljs.core"]}]) - ) - -(defn output-one-file [{:keys [output-to]} js] - (cond (nil? output-to) js - (string? output-to) (spit output-to js) - :else (println js))) - -(defn output-deps-file [opts sources] - (output-one-file opts (deps-file opts sources))) - -(defn ^String output-path - "Given an IJavaScript which is either in memory or in a jar file, - return the output path for this file relative to the working - directory." - [js] - (if-let [url ^URL (deps/-url js)] - (path-from-jarfile url) - (str (random-string 5) ".js"))) - - -(defn write-javascript - "Write a JavaScript file to disk. Only write if the file does not - already exist. Return IJavaScript for the file on disk." - [opts js] - (let [out-dir (io/file (output-directory opts)) - out-name (output-path js) - out-file (io/file out-dir out-name)] - (do (when-not (.exists out-file) - (do (comp/mkdirs out-file) - (spit out-file (deps/-source js)))) - {:url (deps/to-url out-file) :requires (deps/-requires js) - :provides (deps/-provides js) :group (:group js)}))) - -(defn source-on-disk - "Ensure that the given JavaScript exists on disk. Write in memory - sources and files contained in jars to the working directory. Return - updated IJavaScript with the new location." - [opts js] - (let [url ^URL (deps/-url js)] - (if (or (not url) - (= (.getProtocol url) "jar")) - (write-javascript opts js) - ;; always copy original sources to the output directory - ;; when source maps enabled - (let [out-file (if-let [ns (and (:source-map opts) - (first (:provides js)))] - (io/file (io/file (output-directory opts)) - (ana/ns->relpath ns))) - source-url (:source-url js)] - (when (and out-file source-url - (or (not (.exists ^File out-file)) - (> (.lastModified (io/file source-url)) - (.lastModified out-file)))) - (spit out-file (slurp source-url))) - js)))) - -(comment - (write-javascript {} "goog.provide('demo');\nalert('hello');\n") - ;; write something from a jar file to disk - (source-on-disk {} - {:url (io/resource "goog/base.js") - :source (with-open [reader (io/reader (io/resource "goog/base.js"))] - (slurp reader))}) - ;; doesn't write a file that is already on disk - (source-on-disk {} {:url (io/resource "cljs/core.cljs")}) - ) - -(defn output-unoptimized - "Ensure that all JavaScript source files are on disk (not in jars), - write the goog deps file including only the libraries that are being - used and write the deps file for the current project. - - The deps file for the current project will include third-party - libraries." - [opts & sources] - (let [disk-sources (map #(source-on-disk opts %) sources)] - (let [goog-deps (io/file (output-directory opts) "goog/deps.js")] - (do (comp/mkdirs goog-deps) - (spit goog-deps (deps-file opts (filter #(= (:group %) :goog) disk-sources))) - (output-deps-file opts (remove #(= (:group %) :goog) disk-sources)))))) - -(comment - - ;; output unoptimized alone - (output-unoptimized {} "goog.provide('test');\ngoog.require('cljs.core');\nalert('hello');\n") - ;; output unoptimized with all dependencies - (apply output-unoptimized {} - (add-dependencies {} - "goog.provide('test');\ngoog.require('cljs.core');\nalert('hello');\n")) - ;; output unoptimized with external library - (apply output-unoptimized {} - (add-dependencies {:libs ["closure/library/third_party/closure"]} - "goog.provide('test');\ngoog.require('cljs.core');\ngoog.require('goog.dom.query');\n")) - ;; output unoptimized and write deps file to 'out/test.js' - (output-unoptimized {:output-to "out/test.js"} - "goog.provide('test');\ngoog.require('cljs.core');\nalert('hello');\n") - ) - - -(defn get-upstream-deps* - "returns a merged map containing all upstream dependencies defined by libraries on the classpath" - [] - (let [classloader (. (Thread/currentThread) (getContextClassLoader)) - upstream-deps (map #(read-string (slurp %)) (enumeration-seq (. classloader (findResources "deps.cljs"))))] - (doseq [dep upstream-deps] - (println (str "Upstream deps.cljs found on classpath. " dep " This is an EXPERIMENTAL FEATURE and is not guarenteed to remain stable in future versions."))) - (apply merge-with concat upstream-deps))) - -(def get-upstream-deps (memoize get-upstream-deps*)) - -(defn add-header [opts js] - (str (make-preamble opts) js)) - -(defn add-wrapper [{:keys [output-wrapper] :as opts} js] - (if output-wrapper - (str ";(function(){\n" js "\n})();\n") - js)) - -(defn add-source-map-link [{:keys [source-map output-to] :as opts} js] - (if source-map - (if (= output-to :print) - (str js "\n//# sourceMappingURL=" source-map) - (str js "\n//# sourceMappingURL=" (path-relative-to (io/file output-to) {:url source-map}))) - js)) - -(defn absolute-path? [path] - (.isAbsolute (io/file path))) - -(defn absolute-parent [path] - (.getParent (.getAbsoluteFile (io/file path)))) - -(defn in-same-dir? [path-1 path-2] - "Checks that path-1 and path-2 are siblings in the same logical directory." - (= (absolute-parent path-1) - (absolute-parent path-2))) - -(defn same-or-subdirectory-of? [dir path] - "Checks that path names a file or directory that is the dir or a subdirectory there of." - (let [dir-path (.getAbsolutePath (io/file dir)) - path-path (.getAbsolutePath (io/file path))] - (.startsWith path-path dir-path))) - -(defn check-output-to [{:keys [output-to] :as opts}] - (when (contains? opts :output-to) - (assert (or (string? output-to) - (= :print output-to)) - (format ":output-to %s must specify a file or be :print" - (pr-str output-to)))) - true) - -(defn check-output-dir [{:keys [output-dir] :as opts}] - (when (contains? opts :output-dir) - (assert (string? output-dir) - (format ":output-dir %s must specify a directory" - (pr-str output-dir)))) - true) - -(defn check-source-map [{:keys [output-to source-map output-dir] :as opts}] - "When :source-map is specified in opts, " - (when (and (contains? opts :source-map) - (not (= (:optimizations opts) :none))) - (assert (and (contains? opts :output-to) - (contains? opts :output-dir)) - ":source-map cannot be specied without also specifying :output-to and :output-dir if optimization setting applied") - (assert (string? source-map) - (format ":source-map %s must specify a file in the same directory as :output-to %s if optimization setting applied" - (pr-str source-map) - (pr-str output-to))) - (assert (in-same-dir? source-map output-to) - (format ":source-map %s must specify a file in the same directory as :output-to %s if optimization setting applied" - (pr-str source-map) - (pr-str output-to))) - (assert (same-or-subdirectory-of? (absolute-parent output-to) output-dir) - (format ":output-dir %s must specify a directory in :output-to's parent %s if optimization setting applied" - (pr-str output-dir) - (pr-str (absolute-parent output-to))))) - true) - -(defn check-source-map-path [{:keys [source-map-path] :as opts}] - (when (contains? opts :source-map-path) - (assert (string? source-map-path) - (format ":source-map-path %s must be a directory" - source-map-path)) - (when-not (= (:optimizations opts) :none) - (assert (and (contains? opts :output-to) - (contains? opts :source-map)) - ":source-map-path cannot be specified without also specifying :output-to and :source-map if optimization setting applied"))) - true) - -(defn build - "Given a source which can be compiled, produce runnable JavaScript." - ([source opts] - (build source opts - (if-not (nil? env/*compiler*) - env/*compiler* - (env/default-compiler-env opts)))) - ([source opts compiler-env] - (env/with-compiler-env compiler-env - (let [ups-deps (get-upstream-deps) - all-opts (assoc opts - :ups-libs (:libs ups-deps) - :ups-foreign-libs (:foreign-libs ups-deps) - :ups-externs (:externs ups-deps)) - emit-constants (or (and (= (:optimizations opts) :advanced) - (not (false? (:optimize-constants opts)))) - (:optimize-constants opts))] - (check-output-to opts) - (check-output-dir opts) - (check-source-map opts) - (check-source-map-path opts) - (swap! compiler-env assoc-in [:opts :emit-constants] emit-constants) - (binding [ana/*cljs-static-fns* - (or (and (= (:optimizations opts) :advanced) - (not (false? (:static-fns opts)))) - (:static-fns opts) - ana/*cljs-static-fns*) - *assert* (not= (:elide-asserts opts) true) - ana/*cljs-warnings* - (let [enabled? (true? (opts :warnings true))] - (merge ana/*cljs-warnings* - {:unprovided enabled? - :undeclared-var enabled? - :undeclared-ns enabled? - :undeclared-ns-form enabled?}))] - (let [compiled (-compile source all-opts) - - ; the constants_table.js file is not used directly here, is picked up by - ; add-dependencies below - _ (when emit-constants - (comp/emit-constants-table-to-file (::ana/constant-table @env/*compiler*) - (str (output-directory all-opts) "/constants_table.js"))) - js-sources (concat - (apply add-dependencies all-opts - (concat (if (coll? compiled) compiled [compiled]) - (when (= :nodejs (:target all-opts)) - [(-compile (io/resource "cljs/nodejs.cljs") all-opts)]))) - (when (= :nodejs (:target all-opts)) - [(-compile (io/resource "cljs/nodejscli.cljs") all-opts)])) - optim (:optimizations all-opts)] - (if (and optim (not= optim :none)) - (do - (when-let [fname (:source-map all-opts)] - (assert (string? fname) - (str ":source-map must name a file when using :whitespace, " - ":simple, or :advanced optimizations")) - (doall (map #(source-on-disk all-opts %) js-sources))) - (->> js-sources - (apply optimize all-opts) - (add-wrapper all-opts) - (add-source-map-link all-opts) - (add-header all-opts) - (output-one-file all-opts))) - (apply output-unoptimized all-opts js-sources)) - ;; emit Node.js bootstrap script for :none & :whitespace optimizations - (when (and (= (:target opts) :nodejs) - (#{:none :whitespace} (:optimizations opts))) - (let [outfile (io/file (io/file (output-directory opts)) - "goog/bootstrap/nodejs.js")] - (comp/mkdirs outfile) - (spit outfile (slurp (io/resource "cljs/nodejs.js"))))))))))) - - -(comment - - (println (build '[(ns hello.core) - (defn ^{:export greet} greet [n] (str "Hola " n)) - (defn ^:export sum [xs] 42)] - {:optimizations :simple :pretty-print true})) - - ;; build a project with optimizations - (build "samples/hello/src" {:optimizations :advanced}) - (build "samples/hello/src" {:optimizations :advanced :output-to "samples/hello/hello.js"}) - ;; open 'samples/hello/hello.html' to see the result in action - - ;; build a project without optimizations - (build "samples/hello/src" {:output-dir "samples/hello/out" :output-to "samples/hello/hello.js"}) - ;; open 'samples/hello/hello-dev.html' to see the result in action - ;; notice how each script was loaded individually - - ;; build unoptimized from raw ClojureScript - (build '[(ns hello.core) - (defn ^{:export greet} greet [n] (str "Hola " n)) - (defn ^:export sum [xs] 42)] - {:output-dir "samples/hello/out" :output-to "samples/hello/hello.js"}) - ;; open 'samples/hello/hello-dev.html' to see the result in action - ) diff --git a/src/clj/cljs/compiler.clj b/src/clj/cljs/compiler.clj deleted file mode 100644 index 1ba737ccf8..0000000000 --- a/src/clj/cljs/compiler.clj +++ /dev/null @@ -1,1082 +0,0 @@ -; Copyright (c) Rich Hickey. All rights reserved. -; The use and distribution terms for this software are covered by the -; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -; which can be found in the file epl-v10.html at the root of this distribution. -; By using this software in any fashion, you are agreeing to be bound by -; the terms of this license. -; You must not remove this notice, or any other, from this software. - -(ns cljs.compiler - (:refer-clojure :exclude [munge macroexpand-1]) - (:require [clojure.java.io :as io] - [clojure.string :as string] - [clojure.tools.reader :as reader] - [cljs.env :as env] - [cljs.tagged-literals :as tags] - [cljs.analyzer :as ana] - [cljs.source-map :as sm]) - (:import java.lang.StringBuilder - java.io.File)) - -(set! *warn-on-reflection* true) - -;; next line is auto-generated by the build-script - Do not edit! -(def ^:dynamic *clojurescript-version*) - -(defn clojurescript-version - "Returns clojurescript version as a printable string." - [] - (str - (:major *clojurescript-version*) - "." - (:minor *clojurescript-version*) - (when-let [i (:incremental *clojurescript-version*)] - (str "." i)) - (when-let [q (:qualifier *clojurescript-version*)] - (str "-" q)) - (when (:interim *clojurescript-version*) - "-SNAPSHOT"))) - -(def js-reserved - #{"abstract" "boolean" "break" "byte" "case" - "catch" "char" "class" "const" "continue" - "debugger" "default" "delete" "do" "double" - "else" "enum" "export" "extends" "final" - "finally" "float" "for" "function" "goto" "if" - "implements" "import" "in" "instanceof" "int" - "interface" "let" "long" "native" "new" - "package" "private" "protected" "public" - "return" "short" "static" "super" "switch" - "synchronized" "this" "throw" "throws" - "transient" "try" "typeof" "var" "void" - "volatile" "while" "with" "yield" "methods" - "null"}) - -(def ^:dynamic *source-map-data* nil) -(def ^:dynamic *lexical-renames* {}) - -(def cljs-reserved-file-names #{"deps.cljs"}) - -(defmacro ^:private debug-prn - [& args] - `(.println System/err (str ~@args))) - -(defn ns-first-segments [] - (letfn [(get-first-ns-segment [ns] (first (string/split (str ns) #"\.")))] - (map get-first-ns-segment (keys (::ana/namespaces @env/*compiler*))))) - -; Helper fn -(defn shadow-depth [s] - (let [{:keys [name info]} s] - (loop [d 0, {:keys [shadow]} info] - (cond - shadow (recur (inc d) shadow) - (some #{(str name)} (ns-first-segments)) (inc d) - :else d)))) - -(defn munge - ([s] (munge s js-reserved)) - ([s reserved] - (if (map? s) - ; Unshadowing - (let [{:keys [name field] :as info} s - depth (shadow-depth s) - renamed (*lexical-renames* (System/identityHashCode s)) - munged-name (munge (cond field (str "self__." name) - renamed renamed - :else name) - reserved)] - (if (or field (zero? depth)) - munged-name - (symbol (str munged-name "__$" depth)))) - ; String munging - (let [ss (string/replace (str s) #"\/(.)" ".$1") ; Division is special - ss (apply str (map #(if (reserved %) (str % "$") %) - (string/split ss #"(?<=\.)|(?=\.)"))) - ms (clojure.lang.Compiler/munge ss)] - (if (symbol? s) - (symbol ms) - ms))))) - -(defn- comma-sep [xs] - (interpose "," xs)) - -(defn- escape-char [^Character c] - (let [cp (.hashCode c)] - (case cp - ; Handle printable escapes before ASCII - 34 "\\\"" - 92 "\\\\" - ; Handle non-printable escapes - 8 "\\b" - 12 "\\f" - 10 "\\n" - 13 "\\r" - 9 "\\t" - (if (< 31 cp 127) - c ; Print simple ASCII characters - (format "\\u%04X" cp))))) ; Any other character is Unicode - -(defn- escape-string [^CharSequence s] - (let [sb (StringBuilder. (count s))] - (doseq [c s] - (.append sb (escape-char c))) - (.toString sb))) - -(defn- wrap-in-double-quotes [x] - (str \" x \")) - -(defmulti emit* :op) - -(defn emit [ast] - (env/ensure - (when *source-map-data* - (let [{:keys [env]} ast] - (when (:line env) - (let [{:keys [line column]} env] - (swap! *source-map-data* - (fn [m] - (let [minfo (cond-> {:gcol (:gen-col m) - :gline (:gen-line m)} - (= (:op ast) :var) - (assoc :name (str (-> ast :info :name))))] - ; Dec the line/column numbers for 0-indexing. - ; tools.reader uses 1-indexed sources, chrome - ; expects 0-indexed source maps. - (update-in m [:source-map (dec line)] - (fnil (fn [line] - (update-in line [(if column (dec column) 0)] - (fnil (fn [column] (conj column minfo)) []))) - (sorted-map)))))))))) - (emit* ast))) - -(defn emits [& xs] - (doseq [x xs] - (cond - (nil? x) nil - (map? x) (emit x) - (seq? x) (apply emits x) - (fn? x) (x) - :else (let [s (print-str x)] - (when *source-map-data* - (swap! *source-map-data* - update-in [:gen-col] #(+ % (count s)))) - (print s)))) - nil) - -(defn emitln [& xs] - (apply emits xs) - (println) - (when *source-map-data* - (swap! *source-map-data* - (fn [{:keys [gen-line] :as m}] - (assoc m - :gen-line (inc gen-line) - :gen-col 0)))) - nil) - -(defn ^String emit-str [expr] - (with-out-str (emit expr))) - -(defmulti emit-constant class) -(defmethod emit-constant nil [x] (emits "null")) -(defmethod emit-constant Long [x] (emits x)) -(defmethod emit-constant Integer [x] (emits x)) ; reader puts Integers in metadata -(defmethod emit-constant Double [x] (emits x)) -(defmethod emit-constant String [x] - (emits (wrap-in-double-quotes (escape-string x)))) -(defmethod emit-constant Boolean [x] (emits (if x "true" "false"))) -(defmethod emit-constant Character [x] - (emits (wrap-in-double-quotes (escape-char x)))) - -(defmethod emit-constant java.util.regex.Pattern [x] - (if (= "" (str x)) - (emits "(new RegExp(\"\"))") - (let [[_ flags pattern] (re-find #"^(?:\(\?([idmsux]*)\))?(.*)" (str x))] - (emits \/ (.replaceAll (re-matcher #"/" pattern) "\\\\/") \/ flags)))) - -(def ^:const goog-hash-max 0x100000000) - -(defn goog-string-hash [s] - (reduce - (fn [r c] - (mod (+ (* 31 r) (int c)) goog-hash-max)) - 0 s)) - -(defmethod emit-constant clojure.lang.Keyword [x] - (if (-> @env/*compiler* :opts :emit-constants) - (let [value (-> @env/*compiler* ::ana/constant-table x)] - (emits "cljs.core." value)) - (let [ns (namespace x) - name (name x)] - (emits "new cljs.core.Keyword(") - (emit-constant ns) - (emits ",") - (emit-constant name) - (emits ",") - (emit-constant (if ns - (str ns "/" name) - name)) - (emits ",") - (emit-constant (+ (clojure.lang.Util/hashCombine - (unchecked-int (goog-string-hash ns)) - (unchecked-int (goog-string-hash name))) - 0x9e3779b9)) - (emits ")")))) - -(defmethod emit-constant clojure.lang.Symbol [x] - (let [ns (namespace x) - name (name x) - symstr (if-not (nil? ns) - (str ns "/" name) - name)] - (emits "new cljs.core.Symbol(") - (emit-constant ns) - (emits ",") - (emit-constant name) - (emits ",") - (emit-constant symstr) - (emits ",") - (emit-constant (clojure.lang.Util/hashCombine - (unchecked-int (goog-string-hash ns)) - (unchecked-int (goog-string-hash name)))) - (emits ",") - (emit-constant nil) - (emits ")"))) - -;; tagged literal support - -(defmethod emit-constant java.util.Date [^java.util.Date date] - (emits "new Date(" (.getTime date) ")")) - -(defmethod emit-constant java.util.UUID [^java.util.UUID uuid] - (emits "new cljs.core.UUID(\"" (.toString uuid) "\")")) - -(defmacro emit-wrap [env & body] - `(let [env# ~env] - (when (= :return (:context env#)) (emits "return ")) - ~@body - (when-not (= :expr (:context env#)) (emitln ";")))) - -(defmethod emit* :no-op [m]) - -(defmethod emit* :var - [{:keys [info env] :as arg}] - (let [var-name (:name info) - info (if (= (namespace var-name) "js") - (name var-name) - info)] - ; We need a way to write bindings out to source maps and javascript - ; without getting wrapped in an emit-wrap calls, otherwise we get - ; e.g. (function greet(return x, return y) {}). - (if (:binding-form? arg) - ; Emit the arg map so shadowing is properly handled when munging - ; (prevents duplicate fn-param-names) - (emits (munge arg)) - (when-not (= :statement (:context env)) - (emit-wrap env (emits (munge info))))))) - -(defmethod emit* :meta - [{:keys [expr meta env]}] - (emit-wrap env - (emits "cljs.core.with_meta(" expr "," meta ")"))) - -(def ^:private array-map-threshold 8) -(def ^:private obj-map-threshold 8) - -(defn distinct-keys? [keys] - (and (every? #(= (:op %) :constant) keys) - (= (count (into #{} keys)) (count keys)))) - -(defmethod emit* :map - [{:keys [env keys vals]}] - (let [simple-keys? (every? #(or (string? %) (keyword? %)) keys)] - (emit-wrap env - (cond - (zero? (count keys)) - (emits "cljs.core.PersistentArrayMap.EMPTY") - - (<= (count keys) array-map-threshold) - (if (distinct-keys? keys) - (emits "new cljs.core.PersistentArrayMap(null, " (count keys) ", [" - (comma-sep (interleave keys vals)) - "], null)") - (emits "new cljs.core.PersistentArrayMap.fromArray([" - (comma-sep (interleave keys vals)) - "], true, false)")) - - :else - (emits "cljs.core.PersistentHashMap.fromArrays([" - (comma-sep keys) - "],[" - (comma-sep vals) - "])"))))) - -(defmethod emit* :list - [{:keys [items env]}] - (emit-wrap env - (if (empty? items) - (emits "cljs.core.List.EMPTY") - (emits "cljs.core.list(" (comma-sep items) ")")))) - -(defmethod emit* :vector - [{:keys [items env]}] - (emit-wrap env - (if (empty? items) - (emits "cljs.core.PersistentVector.EMPTY") - (let [cnt (count items)] - (if (< cnt 32) - (emits "new cljs.core.PersistentVector(null, " cnt - ", 5, cljs.core.PersistentVector.EMPTY_NODE, [" (comma-sep items) "], null)") - (emits "cljs.core.PersistentVector.fromArray([" (comma-sep items) "], true)")))))) - -(defn distinct-constants? [items] - (and (every? #(= (:op %) :constant) items) - (= (count (into #{} items)) (count items)))) - -(defmethod emit* :set - [{:keys [items env]}] - (emit-wrap env - (cond - (empty? items) - (emits "cljs.core.PersistentHashSet.EMPTY") - - (distinct-constants? items) - (emits "new cljs.core.PersistentHashSet(null, new cljs.core.PersistentArrayMap(null, " (count items) ", [" - (comma-sep (interleave items (repeat "null"))) "], null), null)") - - :else (emits "cljs.core.PersistentHashSet.fromArray([" (comma-sep items) "], true)")))) - -(defmethod emit* :js-value - [{:keys [items js-type env]}] - (emit-wrap env - (if (= js-type :object) - (do - (emits "{") - (when-let [items (seq items)] - (let [[[k v] & r] items] - (emits "\"" (name k) "\": " v) - (doseq [[k v] r] - (emits ", \"" (name k) "\": " v)))) - (emits "}")) - (emits "[" (comma-sep items) "]")))) - -(defmethod emit* :constant - [{:keys [form env]}] - (when-not (= :statement (:context env)) - (emit-wrap env (emit-constant form)))) - -(defn safe-test? [env e] - (let [tag (ana/infer-tag env e)] - (or (#{'boolean 'seq} tag) - (when (= (:op e) :constant) - (let [form (:form e)] - (not (or (and (string? form) (= form "")) - (and (number? form) (zero? form))))))))) - -(defmethod emit* :if - [{:keys [test then else env unchecked]}] - (let [context (:context env) - checked (not (or unchecked (safe-test? env test)))] - (if (= :expr context) - (emits "(" (when checked "cljs.core.truth_") "(" test ")?" then ":" else ")") - (do - (if checked - (emitln "if(cljs.core.truth_(" test "))") - (emitln "if(" test ")")) - (emitln "{" then "} else") - (emitln "{" else "}"))))) - -(defmethod emit* :throw - [{:keys [throw env]}] - (if (= :expr (:context env)) - (emits "(function(){throw " throw "})()") - (emitln "throw " throw ";"))) - -(defn emit-comment - "Emit a nicely formatted comment string." - [doc jsdoc] - (let [docs (when doc [doc]) - docs (if jsdoc (concat docs jsdoc) docs) - docs (remove nil? docs)] - (letfn [(print-comment-lines [e] (doseq [next-line (string/split-lines e)] - (emitln "* " (string/trim next-line))))] - (when (seq docs) - (emitln "/**") - (doseq [e docs] - (when e - (print-comment-lines e))) - (emitln "*/"))))) - -(defmethod emit* :def - [{:keys [name var init env doc export]}] - (let [mname (munge name)] - (when init - (emit-comment doc (:jsdoc init)) - (emits var) - (emits " = " init) - ;; NOTE: JavaScriptCore does not like this under advanced compilation - ;; this change was primarily for REPL interactions - David - ;(emits " = (typeof " mname " != 'undefined') ? " mname " : undefined") - (when-not (= :expr (:context env)) (emitln ";")) - (when export - (emitln "goog.exportSymbol('" (munge export) "', " mname ");"))))) - -(defn emit-apply-to - [{:keys [name params env]}] - (let [arglist (gensym "arglist__") - delegate-name (str (munge name) "__delegate")] - (emitln "(function (" arglist "){") - (doseq [[i param] (map-indexed vector (drop-last 2 params))] - (emits "var ") - (emit param) - (emits " = cljs.core.first(") - (emitln arglist ");") - (emitln arglist " = cljs.core.next(" arglist ");")) - (if (< 1 (count params)) - (do - (emits "var ") - (emit (last (butlast params))) - (emitln " = cljs.core.first(" arglist ");") - (emits "var ") - (emit (last params)) - (emitln " = cljs.core.rest(" arglist ");") - (emits "return " delegate-name "(") - (doseq [param params] - (emit param) - (when-not (= param (last params)) (emits ","))) - (emitln ");")) - (do - (emits "var ") - (emit (last params)) - (emitln " = cljs.core.seq(" arglist ");") - (emits "return " delegate-name "(") - (doseq [param params] - (emit param) - (when-not (= param (last params)) (emits ","))) - (emitln ");"))) - (emits "})"))) - -(defn emit-fn-params [params] - (doseq [param params] - (emit param) - ; Avoid extraneous comma (function greet(x, y, z,) - (when-not (= param (last params)) - (emits ",")))) - -(defn emit-fn-method - [{:keys [type name variadic params expr env recurs max-fixed-arity]}] - (emit-wrap env - (emits "(function " (munge name) "(") - (emit-fn-params params) - (emits "){") - (when type - (emitln "var self__ = this;")) - (when recurs (emitln "while(true){")) - (emits expr) - (when recurs - (emitln "break;") - (emitln "}")) - (emits "})"))) - -(defn emit-variadic-fn-method - [{:keys [type name variadic params expr env recurs max-fixed-arity] :as f}] - (emit-wrap env - (let [name (or name (gensym)) - mname (munge name) - delegate-name (str mname "__delegate")] - (emitln "(function() { ") - (emits "var " delegate-name " = function (") - (doseq [param params] - (emit param) - (when-not (= param (last params)) (emits ","))) - (emits "){") - (when recurs (emitln "while(true){")) - (emits expr) - (when recurs - (emitln "break;") - (emitln "}")) - (emitln "};") - - (emitln "var " mname " = function (" (comma-sep - (if variadic - (concat (butlast params) ['var_args]) - params)) "){") - (when type - (emitln "var self__ = this;")) - (when variadic - (emits "var ") - (emit (last params)) - (emits " = null;") - (emitln "if (arguments.length > " (dec (count params)) ") {") - (emits " ") - (emit (last params)) - (emits " = cljs.core.array_seq(Array.prototype.slice.call(arguments, " (dec (count params)) "),0);") - (emitln "} ")) - (emits "return " delegate-name ".call(this,") - (doseq [param params] - (emit param) - (when-not (= param (last params)) (emits ","))) - (emits ");") - (emitln "};") - - (emitln mname ".cljs$lang$maxFixedArity = " max-fixed-arity ";") - (emits mname ".cljs$lang$applyTo = ") - (emit-apply-to (assoc f :name name)) - (emitln ";") - (emitln mname ".cljs$core$IFn$_invoke$arity$variadic = " delegate-name ";") - (emitln "return " mname ";") - (emitln "})()")))) - -(defmethod emit* :fn - [{:keys [name env methods max-fixed-arity variadic recur-frames loop-lets]}] - ;;fn statements get erased, serve no purpose and can pollute scope if named - (when-not (= :statement (:context env)) - (let [loop-locals (->> (concat (mapcat :params (filter #(and % @(:flag %)) recur-frames)) - (mapcat :params loop-lets)) - (map munge) - seq)] - (when loop-locals - (when (= :return (:context env)) - (emits "return ")) - (emitln "((function (" (comma-sep (map munge loop-locals)) "){") - (when-not (= :return (:context env)) - (emits "return "))) - (if (= 1 (count methods)) - (if variadic - (emit-variadic-fn-method (assoc (first methods) :name name)) - (emit-fn-method (assoc (first methods) :name name))) - (let [has-name? (and name true) - name (or name (gensym)) - mname (munge name) - maxparams (apply max-key count (map :params methods)) - mmap (into {} - (map (fn [method] - [(munge (symbol (str mname "__" (count (:params method))))) - method]) - methods)) - ms (sort-by #(-> % second :params count) (seq mmap))] - (when (= :return (:context env)) - (emits "return ")) - (emitln "(function() {") - (emitln "var " mname " = null;") - (doseq [[n meth] ms] - (emits "var " n " = ") - (if (:variadic meth) - (emit-variadic-fn-method meth) - (emit-fn-method meth)) - (emitln ";")) - (emitln mname " = function(" (comma-sep (if variadic - (concat (butlast maxparams) ['var_args]) - maxparams)) "){") - (when variadic - (emits "var ") - (emit (last maxparams)) - (emitln " = var_args;")) - (emitln "switch(arguments.length){") - (doseq [[n meth] ms] - (if (:variadic meth) - (do (emitln "default:") - (emitln "return " n ".cljs$core$IFn$_invoke$arity$variadic(" - (comma-sep (butlast maxparams)) - (when (> (count maxparams) 1) ", ") - "cljs.core.array_seq(arguments, " max-fixed-arity "));")) - (let [pcnt (count (:params meth))] - (emitln "case " pcnt ":") - (emitln "return " n ".call(this" (if (zero? pcnt) nil - (list "," (comma-sep (take pcnt maxparams)))) ");")))) - (emitln "}") - (emitln "throw(new Error('Invalid arity: ' + arguments.length));") - (emitln "};") - (when variadic - (emitln mname ".cljs$lang$maxFixedArity = " max-fixed-arity ";") - (emitln mname ".cljs$lang$applyTo = " (some #(let [[n m] %] (when (:variadic m) n)) ms) ".cljs$lang$applyTo;")) - (when has-name? - (doseq [[n meth] ms] - (let [c (count (:params meth))] - (if (:variadic meth) - (emitln mname ".cljs$core$IFn$_invoke$arity$variadic = " n ".cljs$core$IFn$_invoke$arity$variadic;") - (emitln mname ".cljs$core$IFn$_invoke$arity$" c " = " n ";"))))) - (emitln "return " mname ";") - (emitln "})()"))) - (when loop-locals - (emitln ";})(" (comma-sep loop-locals) "))"))))) - -(defmethod emit* :do - [{:keys [statements ret env]}] - (let [context (:context env)] - (when (and statements (= :expr context)) (emits "(function (){")) - (when statements - (emits statements)) - (emit ret) - (when (and statements (= :expr context)) (emits "})()")))) - -(defmethod emit* :try - [{:keys [env try catch name finally]}] - (let [context (:context env)] - (if (or name finally) - (do - (when (= :expr context) - (emits "(function (){")) - (emits "try{" try "}") - (when name - (emits "catch (" (munge name) "){" catch "}")) - (when finally - (assert (not= :constant (:op finally)) "finally block cannot contain constant") - (emits "finally {" finally "}")) - (when (= :expr context) - (emits "})()"))) - (emits try)))) - -(defn emit-let - [{:keys [bindings expr env]} is-loop] - (let [context (:context env)] - (when (= :expr context) (emits "(function (){")) - (binding [*lexical-renames* (into *lexical-renames* - (when (= :statement context) - (map #(vector (System/identityHashCode %) - (gensym (str (:name %) "-"))) - bindings)))] - (doseq [{:keys [init] :as binding} bindings] - (emits "var ") - (emit binding) ; Binding will be treated as a var - (emits " = " init ";")) - (when is-loop (emitln "while(true){")) - (emits expr) - (when is-loop - (emitln "break;") - (emitln "}"))) - (when (= :expr context) (emits "})()")))) - -(defmethod emit* :let [ast] - (emit-let ast false)) - -(defmethod emit* :loop [ast] - (emit-let ast true)) - -(defmethod emit* :recur - [{:keys [frame exprs env]}] - (let [temps (vec (take (count exprs) (repeatedly gensym))) - params (:params frame)] - (emitln "{") - (dotimes [i (count exprs)] - (emitln "var " (temps i) " = " (exprs i) ";")) - (dotimes [i (count exprs)] - (emitln (munge (params i)) " = " (temps i) ";")) - (emitln "continue;") - (emitln "}"))) - -(defmethod emit* :letfn - [{:keys [bindings expr env]}] - (let [context (:context env)] - (when (= :expr context) (emits "(function (){")) - (doseq [{:keys [init] :as binding} bindings] - (emitln "var " (munge binding) " = " init ";")) - (emits expr) - (when (= :expr context) (emits "})()")))) - -(defn protocol-prefix [psym] - (symbol (str (-> (str psym) (.replace \. \$) (.replace \/ \$)) "$"))) - -(defmethod emit* :invoke - [{:keys [f args env] :as expr}] - (let [info (:info f) - fn? (and ana/*cljs-static-fns* - (not (:dynamic info)) - (:fn-var info)) - protocol (:protocol info) - tag (ana/infer-tag env (first (:args expr))) - proto? (and protocol tag - (or (and ana/*cljs-static-fns* protocol (= tag 'not-native)) - (and - (or ana/*cljs-static-fns* - (:protocol-inline env)) - (or (= protocol tag) - ;; ignore new type hints for now - David - (and (not (set? tag)) - (not ('#{any clj clj-or-nil} tag)) - (when-let [ps (:protocols (ana/resolve-existing-var (dissoc env :locals) tag))] - (ps protocol))))))) - opt-not? (and (= (:name info) 'cljs.core/not) - (= (ana/infer-tag env (first (:args expr))) 'boolean)) - ns (:ns info) - js? (= ns 'js) - goog? (when ns - (or (= ns 'goog) - (when-let [ns-str (str ns)] - (= (get (string/split ns-str #"\.") 0 nil) "goog")))) - keyword? (and (= (-> f :op) :constant) - (keyword? (-> f :form))) - [f variadic-invoke] - (if fn? - (let [arity (count args) - variadic? (:variadic info) - mps (:method-params info) - mfa (:max-fixed-arity info)] - (cond - ;; if only one method, no renaming needed - (and (not variadic?) - (= (count mps) 1)) - [f nil] - - ;; direct dispatch to variadic case - (and variadic? (> arity mfa)) - [(update-in f [:info :name] - (fn [name] (symbol (str (munge info) ".cljs$core$IFn$_invoke$arity$variadic")))) - {:max-fixed-arity mfa}] - - ;; direct dispatch to specific arity case - :else - (let [arities (map count mps)] - (if (some #{arity} arities) - [(update-in f [:info :name] - (fn [name] (symbol (str (munge info) ".cljs$core$IFn$_invoke$arity$" arity)))) nil] - [f nil])))) - [f nil])] - (emit-wrap env - (cond - opt-not? - (emits "!(" (first args) ")") - - proto? - (let [pimpl (str (munge (protocol-prefix protocol)) - (munge (name (:name info))) "$arity$" (count args))] - (emits (first args) "." pimpl "(" (comma-sep (cons "null" (rest args))) ")")) - - keyword? - (emits f ".cljs$core$IFn$_invoke$arity$" (count args) "(" (comma-sep args) ")") - - variadic-invoke - (let [mfa (:max-fixed-arity variadic-invoke)] - (emits f "(" (comma-sep (take mfa args)) - (when-not (zero? mfa) ",") - "cljs.core.array_seq([" (comma-sep (drop mfa args)) "], 0))")) - - (or fn? js? goog?) - (emits f "(" (comma-sep args) ")") - - :else - (if (and ana/*cljs-static-fns* (= (:op f) :var)) - (let [fprop (str ".cljs$core$IFn$_invoke$arity$" (count args))] - (emits "(" f fprop " ? " f fprop "(" (comma-sep args) ") : " f ".call(" (comma-sep (cons "null" args)) "))")) - (emits f ".call(" (comma-sep (cons "null" args)) ")")))))) - -(defmethod emit* :new - [{:keys [ctor args env]}] - (emit-wrap env - (emits "(new " ctor "(" - (comma-sep args) - "))"))) - -(defmethod emit* :set! - [{:keys [target val env]}] - (emit-wrap env (emits target " = " val))) - -(defmethod emit* :ns - [{:keys [name requires uses require-macros env]}] - (emitln "goog.provide('" (munge name) "');") - (when-not (= name 'cljs.core) - (emitln "goog.require('cljs.core');")) - (doseq [lib (into (vals requires) (distinct (vals uses)))] - (emitln "goog.require('" (munge lib) "');"))) - -(defmethod emit* :deftype* - [{:keys [t fields pmasks]}] - (let [fields (map munge fields)] - (emitln "") - (emitln "/**") - (emitln "* @constructor") - (emitln "*/") - (emitln (munge t) " = (function (" (comma-sep fields) "){") - (doseq [fld fields] - (emitln "this." fld " = " fld ";")) - (doseq [[pno pmask] pmasks] - (emitln "this.cljs$lang$protocol_mask$partition" pno "$ = " pmask ";")) - (emitln "})"))) - -(defmethod emit* :defrecord* - [{:keys [t fields pmasks]}] - (let [fields (concat (map munge fields) '[__meta __extmap])] - (emitln "") - (emitln "/**") - (emitln "* @constructor") - (doseq [fld fields] - (emitln "* @param {*} " fld)) - (emitln "* @param {*=} __meta ") - (emitln "* @param {*=} __extmap") - (emitln "*/") - (emitln (munge t) " = (function (" (comma-sep fields) "){") - (doseq [fld fields] - (emitln "this." fld " = " fld ";")) - (doseq [[pno pmask] pmasks] - (emitln "this.cljs$lang$protocol_mask$partition" pno "$ = " pmask ";")) - (emitln "if(arguments.length>" (- (count fields) 2) "){") - (emitln "this.__meta = __meta;") - (emitln "this.__extmap = __extmap;") - (emitln "} else {") - (emits "this.__meta=") - (emit-constant nil) - (emitln ";") - (emits "this.__extmap=") - (emit-constant nil) - (emitln ";") - (emitln "}") - (emitln "})"))) - -(defmethod emit* :dot - [{:keys [target field method args env]}] - (emit-wrap env - (if field - (emits target "." (munge field #{})) - (emits target "." (munge method #{}) "(" - (comma-sep args) - ")")))) - -(defmethod emit* :js - [{:keys [env code segs args]}] - (emit-wrap env - (if code - (emits code) - (emits (interleave (concat segs (repeat nil)) - (concat args [nil])))))) - -(defn rename-to-js - "Change the file extension from .cljs to .js. Takes a File or a - String. Always returns a String." - [file-str] - (clojure.string/replace file-str #"\.cljs$" ".js")) - -(defn mkdirs - "Create all parent directories for the passed file." - [^File f] - (.mkdirs (.getParentFile (.getCanonicalFile f)))) - -(defmacro with-core-cljs - "Ensure that core.cljs has been loaded." - [& body] - `(do (when-not (get-in @env/*compiler* [::ana/namespaces 'cljs.core :defs]) - (ana/analyze-file "cljs/core.cljs")) - ~@body)) - -(defn url-path [^File f] - (.getPath (.toURL (.toURI f)))) - -(defn compile-file* - ([src dest] (compile-file* src dest nil)) - ([src dest opts] - (env/ensure - (with-core-cljs - (with-open [out ^java.io.Writer (io/make-writer dest {})] - (binding [*out* out - ana/*cljs-ns* 'cljs.user - ana/*cljs-file* (.getPath ^File src) - reader/*alias-map* (or reader/*alias-map* {}) - *source-map-data* (when (:source-map opts) - (atom - {:source-map (sorted-map) - :gen-col 0 - :gen-line 0}))] - (emitln "// Compiled by ClojureScript " (clojurescript-version)) - (loop [forms (ana/forms-seq src) - ns-name nil - deps nil] - (if (seq forms) - (let [env (ana/empty-env) - ast (ana/analyze env (first forms))] - (do (emit ast) - (if (= (:op ast) :ns) - (recur (rest forms) (:name ast) (merge (:uses ast) (:requires ast))) - (recur (rest forms) ns-name deps)))) - (let [sm-data (when *source-map-data* @*source-map-data*) - ret (merge - {:ns (or ns-name 'cljs.user) - :provides [ns-name] - :requires (if (= ns-name 'cljs.core) - (set (vals deps)) - (cond-> (conj (set (vals deps)) 'cljs.core) - (get-in @env/*compiler* [:opts :emit-constants]) - (conj 'constants-table))) - :file dest - :source-file src} - (when sm-data - {:source-map (:source-map sm-data)}))] - (when (and sm-data (= (:optimizations opts) :none)) - (let [sm-file (io/file (str (.getPath ^File dest) ".map"))] - (emits "\n//# sourceMappingURL=" (.getName sm-file)) - (spit sm-file - (sm/encode {(url-path src) (:source-map sm-data)} - {:lines (+ (:gen-line sm-data) 2) - :file (url-path dest)})))) - (let [path (.getPath (.toURL ^File dest))] - (swap! env/*compiler* assoc-in [::compiled-cljs path] ret) - (swap! env/*compiler* assoc-in [::ana/analyzed-cljs path] true)) - ret))))))))) - -(defn compiled-by-version [^File f] - (with-open [reader (io/reader f)] - (let [match (->> reader line-seq first - (re-matches #".*ClojureScript (.*)$"))] - (and match (second match))))) - -(defn requires-compilation? - "Return true if the src file requires compilation." - ([src dest] (requires-compilation? src dest nil)) - ([^File src ^File dest opts] - (env/ensure - (or (not (.exists dest)) - (> (.lastModified src) (.lastModified dest)) - (let [version' (compiled-by-version dest) - version (clojurescript-version)] - (and version (not= version version'))) - (and opts - (:source-map opts) - (if (= (:optimizations opts) :none) - (not (.exists (io/file (str (.getPath dest) ".map")))) - (not (get-in @env/*compiler* [::compiled-cljs (.getAbsolutePath dest)])))))))) - -(defn parse-ns - ([src] (parse-ns src nil nil)) - ([src dest opts] - (env/ensure - (let [namespaces' (::ana/namespaces @env/*compiler*) - ret - (binding [ana/*cljs-ns* 'cljs.user - ana/*analyze-deps* false] - (loop [forms (ana/forms-seq src)] - (if (seq forms) - (let [env (ana/empty-env) - ast (ana/no-warn (ana/analyze env (first forms)))] - (if (= (:op ast) :ns) - (let [ns-name (:name ast) - deps (merge (:uses ast) (:requires ast))] - (merge - {:ns (or ns-name 'cljs.user) - :provides [ns-name] - :requires (if (= ns-name 'cljs.core) - (set (vals deps)) - (cond-> (conj (set (vals deps)) 'cljs.core) - (get-in @env/*compiler* [:opts :emit-constants]) - (conj 'constants-table))) - :file dest - :source-file src} - (when (and dest (.exists ^File dest)) - {:lines (with-open [reader (io/reader dest)] - (-> reader line-seq count))}))) - (recur (rest forms)))))))] - ;; TODO this _was_ a reset! of the old ana/namespaces atom; should we capture and - ;; then restore the entirety of env/*compiler* here instead? - (swap! env/*compiler* assoc ::ana/namespaces namespaces') - ret)))) - -(defn compile-file - "Compiles src to a file of the same name, but with a .js extension, - in the src file's directory. - - With dest argument, write file to provided location. If the dest - argument is a file outside the source tree, missing parent - directories will be created. The src file will only be compiled if - the dest file has an older modification time. - - Both src and dest may be either a String or a File. - - Returns a map containing {:ns .. :provides .. :requires .. :file ..}. - If the file was not compiled returns only {:file ...}" - ([src] - (let [dest (rename-to-js src)] - (compile-file src dest nil))) - ([src dest] - (compile-file src dest nil)) - ([src dest opts] - (let [src-file (io/file src) - dest-file (io/file dest)] - (if (.exists src-file) - (try - (let [{ns :ns :as ns-info} (parse-ns src-file dest-file opts)] - (if (requires-compilation? src-file dest-file opts) - (do (mkdirs dest-file) - (when (contains? (::ana/namespaces @env/*compiler*) ns) - (swap! env/*compiler* update-in [::ana/namespaces] dissoc ns)) - (compile-file* src-file dest-file opts)) - (do - (when-not (contains? (::ana/namespaces @env/*compiler*) ns) - (with-core-cljs - (ana/analyze-file src-file))) - ns-info))) - (catch Exception e - (throw (ex-info (str "failed compiling file:" src) {:file src} e)))) - (throw (java.io.FileNotFoundException. (str "The file " src " does not exist."))))))) - -(defn path-seq - [file-str] - (->> File/separator - java.util.regex.Pattern/quote - re-pattern - (string/split file-str))) - -(defn to-path - ([parts] - (to-path parts File/separator)) - ([parts sep] - (apply str (interpose sep parts)))) - -(defn ^File to-target-file - [target cljs-file] - (let [relative-path (string/split - (ana/munge-path - (str (:ns (parse-ns cljs-file)))) #"\.") - parents (butlast relative-path)] - (io/file - (io/file (to-path (cons target parents))) - (str (last relative-path) ".js")))) - -(defn cljs-files-in - "Return a sequence of all .cljs files in the given directory." - [dir] - (filter #(let [name (.getName ^File %)] - (and (.endsWith name ".cljs") - (not= \. (first name)) - (not (contains? cljs-reserved-file-names name)))) - (file-seq dir))) - -(defn compile-root - "Looks recursively in src-dir for .cljs files and compiles them to - .js files. If target-dir is provided, output will go into this - directory mirroring the source directory structure. Returns a list - of maps containing information about each file which was compiled - in dependency order." - ([src-dir] - (compile-root src-dir "out")) - ([src-dir target-dir] - (compile-root src-dir target-dir nil)) - ([src-dir target-dir opts] - (swap! env/*compiler* assoc :root src-dir) - (let [src-dir-file (io/file src-dir)] - (loop [cljs-files (cljs-files-in src-dir-file) - output-files []] - (if (seq cljs-files) - (let [cljs-file (first cljs-files) - output-file (to-target-file target-dir cljs-file) - ns-info (compile-file cljs-file output-file opts)] - (recur (rest cljs-files) (conj output-files (assoc ns-info :file-name (.getPath output-file))))) - output-files))))) - -;; TODO: needs fixing, table will include other things than keywords - David - -(defn emit-constants-table [table] - (doseq [[keyword value] table] - (let [ns (namespace keyword) - name (name keyword)] - (emits "cljs.core." value " = new cljs.core.Keyword(") - (emit-constant ns) - (emits ",") - (emit-constant name) - (emits ",") - (emit-constant (if ns - (str ns "/" name) - name)) - (emits ");\n")))) - -(defn emit-constants-table-to-file [table dest] - (with-open [out ^java.io.Writer (io/make-writer dest {})] - (binding [*out* out] - (emit-constants-table table)))) diff --git a/src/clj/cljs/core.clj b/src/clj/cljs/core.clj deleted file mode 100644 index 9dfc4550db..0000000000 --- a/src/clj/cljs/core.clj +++ /dev/null @@ -1,1587 +0,0 @@ -; Copyright (c) Rich Hickey. All rights reserved. -; The use and distribution terms for this software are covered by the -; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -; which can be found in the file epl-v10.html at the root of this distribution. -; By using this software in any fashion, you are agreeing to be bound by -; the terms of this license. -; You must not remove this notice, or any other, from this software. - -(ns cljs.core - (:refer-clojure :exclude [-> ->> .. amap and areduce alength aclone assert binding bound-fn case comment cond condp - declare definline definterface defmethod defmulti defn defn- defonce - defprotocol defrecord defstruct deftype delay destructure doseq dosync dotimes doto - extend-protocol extend-type fn for future gen-class gen-interface - if-let if-not import io! lazy-cat lazy-seq let letfn locking loop - memfn ns or proxy proxy-super pvalues refer-clojure reify sync time - when when-first when-let when-not while with-bindings with-in-str - with-loading-context with-local-vars with-open with-out-str with-precision with-redefs - satisfies? identical? true? false? number? nil? instance? symbol? keyword? string? str get - make-array vector list hash-map array-map hash-set - - aget aset - + - * / < <= > >= == zero? pos? neg? inc dec max min mod - byte char short int long float double - unchecked-byte unchecked-char unchecked-short unchecked-int - unchecked-long unchecked-float unchecked-double - unchecked-add unchecked-add-int unchecked-dec unchecked-dec-int - unchecked-divide unchecked-divide-int unchecked-inc unchecked-inc-int - unchecked-multiply unchecked-multiply-int unchecked-negate unchecked-negate-int - unchecked-subtract unchecked-subtract-int unchecked-remainder-int - - bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set - bit-test bit-shift-left bit-shift-right bit-xor - - cond-> cond->> as-> some-> some->>]) - (:require clojure.walk - clojure.set - cljs.compiler - [cljs.env :as env])) - -(alias 'core 'clojure.core) -(alias 'ana 'cljs.analyzer) - -(defmacro import-macros [ns [& vars]] - (core/let [ns (find-ns ns) - vars (map #(ns-resolve ns %) vars) - syms (map (core/fn [^clojure.lang.Var v] (core/-> v .sym (with-meta {:macro true}))) vars) - defs (map (core/fn [sym var] - `(do (def ~sym (deref ~var)) - ;for AOT compilation - (alter-meta! (var ~sym) assoc :macro true))) - syms vars)] - `(do ~@defs - :imported))) - -(import-macros clojure.core - [-> ->> .. assert comment cond - declare defn defn- - doto - extend-protocol fn for - if-let if-not letfn - memfn - when when-first when-let when-not while - cond-> cond->> as-> some-> some->>]) - -(defmacro defonce [x init] - `(when-not (exists? ~x) - (def ~x ~init))) - -(defmacro ^{:private true} assert-args [fnname & pairs] - `(do (when-not ~(first pairs) - (throw (IllegalArgumentException. - ~(core/str fnname " requires " (second pairs))))) - ~(core/let [more (nnext pairs)] - (when more - (list* `assert-args fnname more))))) - -(defn destructure [bindings] - (core/let [bents (partition 2 bindings) - pb (fn pb [bvec b v] - (core/let [pvec - (fn [bvec b val] - (core/let [gvec (gensym "vec__")] - (core/loop [ret (-> bvec (conj gvec) (conj val)) - n 0 - bs b - seen-rest? false] - (if (seq bs) - (core/let [firstb (first bs)] - (core/cond - (= firstb '&) (recur (pb ret (second bs) (core/list `nthnext gvec n)) - n - (nnext bs) - true) - (= firstb :as) (pb ret (second bs) gvec) - :else (if seen-rest? - (throw (new Exception "Unsupported binding form, only :as can follow & parameter")) - (recur (pb ret firstb (core/list `nth gvec n nil)) - (core/inc n) - (next bs) - seen-rest?)))) - ret)))) - pmap - (fn [bvec b v] - (core/let [gmap (gensym "map__") - defaults (:or b)] - (core/loop [ret (-> bvec (conj gmap) (conj v) - (conj gmap) (conj `(if (seq? ~gmap) (apply core/hash-map ~gmap) ~gmap)) - ((fn [ret] - (if (:as b) - (conj ret (:as b) gmap) - ret)))) - bes (reduce - (fn [bes entry] - (reduce #(assoc %1 %2 ((val entry) %2)) - (dissoc bes (key entry)) - ((key entry) bes))) - (dissoc b :as :or) - {:keys #(if (core/keyword? %) % (keyword (core/str %))), - :strs core/str, :syms #(core/list `quote %)})] - (if (seq bes) - (core/let [bb (key (first bes)) - bk (val (first bes)) - has-default (contains? defaults bb)] - (recur (pb ret bb (if has-default - (core/list `get gmap bk (defaults bb)) - (core/list `get gmap bk))) - (next bes))) - ret))))] - (core/cond - (core/symbol? b) (-> bvec (conj (if (namespace b) (symbol (name b)) b)) (conj v)) - (core/keyword? b) (-> bvec (conj (symbol (name b))) (conj v)) - (vector? b) (pvec bvec b v) - (map? b) (pmap bvec b v) - :else (throw (new Exception (core/str "Unsupported binding form: " b)))))) - process-entry (fn [bvec b] (pb bvec (first b) (second b)))] - (if (every? core/symbol? (map first bents)) - bindings - (if-let [kwbs (seq (filter #(core/keyword? (first %)) bents))] - (throw (new Exception (core/str "Unsupported binding key: " (ffirst kwbs)))) - (reduce process-entry [] bents))))) - -(defmacro let - "binding => binding-form init-expr - - Evaluates the exprs in a lexical context in which the symbols in - the binding-forms are bound to their respective init-exprs or parts - therein." - [bindings & body] - (assert-args - (vector? bindings) "a vector for its binding" - (even? (count bindings)) "an even number of forms in binding vector") - `(let* ~(destructure bindings) ~@body)) - -(defmacro loop - "Evaluates the exprs in a lexical context in which the symbols in - the binding-forms are bound to their respective init-exprs or parts - therein. Acts as a recur target." - [bindings & body] - (assert-args - (vector? bindings) "a vector for its binding" - (even? (count bindings)) "an even number of forms in binding vector") - (let [db (destructure bindings)] - (if (= db bindings) - `(loop* ~bindings ~@body) - (let [vs (take-nth 2 (drop 1 bindings)) - bs (take-nth 2 bindings) - gs (map (fn [b] (if (core/symbol? b) b (gensym))) bs) - bfs (reduce (fn [ret [b v g]] - (if (core/symbol? b) - (conj ret g v) - (conj ret g v b g))) - [] (map core/vector bs vs gs))] - `(let ~bfs - (loop* ~(vec (interleave gs gs)) - (let ~(vec (interleave bs gs)) - ~@body))))))) - -(def fast-path-protocols - "protocol fqn -> [partition number, bit]" - (zipmap (map #(symbol "cljs.core" (core/str %)) - '[IFn ICounted IEmptyableCollection ICollection IIndexed ASeq ISeq INext - ILookup IAssociative IMap IMapEntry ISet IStack IVector IDeref - IDerefWithTimeout IMeta IWithMeta IReduce IKVReduce IEquiv IHash - ISeqable ISequential IList IRecord IReversible ISorted IPrintWithWriter IWriter - IPrintWithWriter IPending IWatchable IEditableCollection ITransientCollection - ITransientAssociative ITransientMap ITransientVector ITransientSet - IMultiFn IChunkedSeq IChunkedNext IComparable INamed ICloneable IAtom - IReset ISwap]) - (iterate (fn [[p b]] - (if (core/== 2147483648 b) - [(core/inc p) 1] - [p (core/bit-shift-left b 1)])) - [0 1]))) - -(def fast-path-protocol-partitions-count - "total number of partitions" - (let [c (count fast-path-protocols) - m (core/mod c 32)] - (if (core/zero? m) - (core/quot c 32) - (core/inc (core/quot c 32))))) - -(defmacro str [& xs] - (let [strs (->> (repeat (count xs) "cljs.core.str(~{})") - (interpose ",") - (apply core/str))] - (list* 'js* (core/str "[" strs "].join('')") xs))) - -(defn bool-expr [e] - (vary-meta e assoc :tag 'boolean)) - -(defn simple-test-expr? [env ast] - (core/and - (#{:var :invoke :constant :dot :js} (:op ast)) - ('#{boolean seq} (cljs.analyzer/infer-tag env ast)))) - -(defmacro and - "Evaluates exprs one at a time, from left to right. If a form - returns logical false (nil or false), and returns that value and - doesn't evaluate any of the other expressions, otherwise it returns - the value of the last expr. (and) returns true." - ([] true) - ([x] x) - ([x & next] - (let [forms (concat [x] next)] - (if (every? #(simple-test-expr? &env %) - (map #(cljs.analyzer/analyze &env %) forms)) - (let [and-str (->> (repeat (count forms) "(~{})") - (interpose " && ") - (apply core/str))] - (bool-expr `(~'js* ~and-str ~@forms))) - `(let [and# ~x] - (if and# (and ~@next) and#)))))) - -(defmacro or - "Evaluates exprs one at a time, from left to right. If a form - returns a logical true value, or returns that value and doesn't - evaluate any of the other expressions, otherwise it returns the - value of the last expression. (or) returns nil." - ([] nil) - ([x] x) - ([x & next] - (let [forms (concat [x] next)] - (if (every? #(simple-test-expr? &env %) - (map #(cljs.analyzer/analyze &env %) forms)) - (let [or-str (->> (repeat (count forms) "(~{})") - (interpose " || ") - (apply core/str))] - (bool-expr `(~'js* ~or-str ~@forms))) - `(let [or# ~x] - (if or# or# (or ~@next))))))) - -(defmacro nil? [x] - `(coercive-= ~x nil)) - -;; internal - do not use. -(defmacro coercive-not [x] - (bool-expr (core/list 'js* "(!~{})" x))) - -;; internal - do not use. -(defmacro coercive-not= [x y] - (bool-expr (core/list 'js* "(~{} != ~{})" x y))) - -;; internal - do not use. -(defmacro coercive-= [x y] - (bool-expr (core/list 'js* "(~{} == ~{})" x y))) - -;; internal - do not use. -(defmacro coercive-boolean [x] - (with-meta (core/list 'js* "~{}" x) - {:tag 'boolean})) - -;; internal - do not use. -(defmacro truth_ [x] - (assert (clojure.core/symbol? x) "x is substituted twice") - (core/list 'js* "(~{} != null && ~{} !== false)" x x)) - -;; internal - do not use -(defmacro js-arguments [] - (core/list 'js* "arguments")) - -(defmacro js-delete [obj key] - (core/list 'js* "delete ~{}[~{}]" obj key)) - -(defmacro true? [x] - (bool-expr (core/list 'js* "~{} === true" x))) - -(defmacro false? [x] - (bool-expr (core/list 'js* "~{} === false" x))) - -(defmacro array? [x] - (bool-expr (core/list 'js* "~{} instanceof Array" x))) - -(defmacro string? [x] - (bool-expr (core/list 'js* "typeof ~{} === 'string'" x))) - -;; TODO: x must be a symbol, not an arbitrary expression -(defmacro exists? [x] - (bool-expr - (core/list 'js* "typeof ~{} !== 'undefined'" - (vary-meta x assoc :cljs.analyzer/no-resolve true)))) - -(defmacro undefined? [x] - (bool-expr (core/list 'js* "(void 0 === ~{})" x))) - -(defmacro identical? [a b] - (bool-expr (core/list 'js* "(~{} === ~{})" a b))) - -(defmacro instance? [t o] - ;; Google Closure warns about some references to RegExp, so - ;; (instance? RegExp ...) needs to be inlined, but the expansion - ;; should preserve the order of argument evaluation. - (bool-expr (if (clojure.core/symbol? t) - (core/list 'js* "(~{} instanceof ~{})" o t) - `(let [t# ~t o# ~o] - (~'js* "(~{} instanceof ~{})" o# t#))))) - -(defmacro number? [x] - (bool-expr (core/list 'js* "typeof ~{} === 'number'" x))) - -(defmacro symbol? [x] - (bool-expr `(instance? Symbol ~x))) - -(defmacro keyword? [x] - (bool-expr `(instance? Keyword ~x))) - -(defmacro aget - ([a i] - (core/list 'js* "(~{}[~{}])" a i)) - ([a i & idxs] - (let [astr (apply core/str (repeat (count idxs) "[~{}]"))] - `(~'js* ~(core/str "(~{}[~{}]" astr ")") ~a ~i ~@idxs)))) - -(defmacro aset - ([a i v] - (core/list 'js* "(~{}[~{}] = ~{})" a i v)) - ([a idx idx2 & idxv] - (let [n (core/dec (count idxv)) - astr (apply core/str (repeat n "[~{}]"))] - `(~'js* ~(core/str "(~{}[~{}][~{}]" astr " = ~{})") ~a ~idx ~idx2 ~@idxv)))) - -(defmacro ^::ana/numeric + - ([] 0) - ([x] x) - ([x y] (core/list 'js* "(~{} + ~{})" x y)) - ([x y & more] `(+ (+ ~x ~y) ~@more))) - -(defmacro byte [x] x) -(defmacro short [x] x) -(defmacro float [x] x) -(defmacro double [x] x) - -(defmacro unchecked-byte [x] x) -(defmacro unchecked-char [x] x) -(defmacro unchecked-short [x] x) -(defmacro unchecked-float [x] x) -(defmacro unchecked-double [x] x) - -(defmacro ^::ana/numeric unchecked-add - ([& xs] `(+ ~@xs))) - -(defmacro ^::ana/numeric unchecked-add-int - ([& xs] `(+ ~@xs))) - -(defmacro ^::ana/numeric unchecked-dec - ([x] `(dec ~x))) - -(defmacro ^::ana/numeric unchecked-dec-int - ([x] `(dec ~x))) - -(defmacro ^::ana/numeric unchecked-divide-int - ([& xs] `(/ ~@xs))) - -(defmacro ^::ana/numeric unchecked-inc - ([x] `(inc ~x))) - -(defmacro ^::ana/numeric unchecked-inc-int - ([x] `(inc ~x))) - -(defmacro ^::ana/numeric unchecked-multiply - ([& xs] `(* ~@xs))) - -(defmacro ^::ana/numeric unchecked-multiply-int - ([& xs] `(* ~@xs))) - -(defmacro ^::ana/numeric unchecked-negate - ([x] `(- ~x))) - -(defmacro ^::ana/numeric unchecked-negate-int - ([x] `(- ~x))) - -(defmacro ^::ana/numeric unchecked-remainder-int - ([x n] `(mod ~x ~n))) - -(defmacro ^::ana/numeric unchecked-subtract - ([& xs] `(- ~@xs))) - -(defmacro ^::ana/numeric unchecked-subtract-int - ([& xs] `(- ~@xs))) - -(defmacro ^::ana/numeric - - ([x] (core/list 'js* "(- ~{})" x)) - ([x y] (core/list 'js* "(~{} - ~{})" x y)) - ([x y & more] `(- (- ~x ~y) ~@more))) - -(defmacro ^::ana/numeric * - ([] 1) - ([x] x) - ([x y] (core/list 'js* "(~{} * ~{})" x y)) - ([x y & more] `(* (* ~x ~y) ~@more))) - -(defmacro ^::ana/numeric / - ([x] `(/ 1 ~x)) - ([x y] (core/list 'js* "(~{} / ~{})" x y)) - ([x y & more] `(/ (/ ~x ~y) ~@more))) - -(defmacro ^::ana/numeric divide - ([x] `(/ 1 ~x)) - ([x y] (core/list 'js* "(~{} / ~{})" x y)) - ([x y & more] `(/ (/ ~x ~y) ~@more))) - -(defmacro ^::ana/numeric < - ([x] true) - ([x y] (bool-expr (core/list 'js* "(~{} < ~{})" x y))) - ([x y & more] `(and (< ~x ~y) (< ~y ~@more)))) - -(defmacro ^::ana/numeric <= - ([x] true) - ([x y] (bool-expr (core/list 'js* "(~{} <= ~{})" x y))) - ([x y & more] `(and (<= ~x ~y) (<= ~y ~@more)))) - -(defmacro ^::ana/numeric > - ([x] true) - ([x y] (bool-expr (core/list 'js* "(~{} > ~{})" x y))) - ([x y & more] `(and (> ~x ~y) (> ~y ~@more)))) - -(defmacro ^::ana/numeric >= - ([x] true) - ([x y] (bool-expr (core/list 'js* "(~{} >= ~{})" x y))) - ([x y & more] `(and (>= ~x ~y) (>= ~y ~@more)))) - -(defmacro ^::ana/numeric == - ([x] true) - ([x y] (bool-expr (core/list 'js* "(~{} === ~{})" x y))) - ([x y & more] `(and (== ~x ~y) (== ~y ~@more)))) - -(defmacro ^::ana/numeric dec [x] - `(- ~x 1)) - -(defmacro ^::ana/numeric inc [x] - `(+ ~x 1)) - -(defmacro ^::ana/numeric zero? [x] - `(== ~x 0)) - -(defmacro ^::ana/numeric pos? [x] - `(> ~x 0)) - -(defmacro ^::ana/numeric neg? [x] - `(< ~x 0)) - -(defmacro ^::ana/numeric max - ([x] x) - ([x y] `(let [x# ~x, y# ~y] - (~'js* "((~{} > ~{}) ? ~{} : ~{})" x# y# x# y#))) - ([x y & more] `(max (max ~x ~y) ~@more))) - -(defmacro ^::ana/numeric min - ([x] x) - ([x y] `(let [x# ~x, y# ~y] - (~'js* "((~{} < ~{}) ? ~{} : ~{})" x# y# x# y#))) - ([x y & more] `(min (min ~x ~y) ~@more))) - -(defmacro ^::ana/numeric js-mod [num div] - (core/list 'js* "(~{} % ~{})" num div)) - -(defmacro ^::ana/numeric bit-not [x] - (core/list 'js* "(~ ~{})" x)) - -(defmacro ^::ana/numeric bit-and - ([x y] (core/list 'js* "(~{} & ~{})" x y)) - ([x y & more] `(bit-and (bit-and ~x ~y) ~@more))) - -;; internal do not use -(defmacro ^::ana/numeric unsafe-bit-and - ([x y] (bool-expr (core/list 'js* "(~{} & ~{})" x y))) - ([x y & more] `(unsafe-bit-and (unsafe-bit-and ~x ~y) ~@more))) - -(defmacro ^::ana/numeric bit-or - ([x y] (core/list 'js* "(~{} | ~{})" x y)) - ([x y & more] `(bit-or (bit-or ~x ~y) ~@more))) - -(defmacro ^::ana/numeric int [x] - `(bit-or ~x 0)) - -(defmacro ^::ana/numeric bit-xor - ([x y] (core/list 'js* "(~{} ^ ~{})" x y)) - ([x y & more] `(bit-xor (bit-xor ~x ~y) ~@more))) - -(defmacro ^::ana/numeric bit-and-not - ([x y] (core/list 'js* "(~{} & ~~{})" x y)) - ([x y & more] `(bit-and-not (bit-and-not ~x ~y) ~@more))) - -(defmacro ^::ana/numeric bit-clear [x n] - (core/list 'js* "(~{} & ~(1 << ~{}))" x n)) - -(defmacro ^::ana/numeric bit-flip [x n] - (core/list 'js* "(~{} ^ (1 << ~{}))" x n)) - -(defmacro ^::ana/numeric bit-test [x n] - (core/list 'js* "((~{} & (1 << ~{})) != 0)" x n)) - -(defmacro ^::ana/numeric bit-shift-left [x n] - (core/list 'js* "(~{} << ~{})" x n)) - -(defmacro ^::ana/numeric bit-shift-right [x n] - (core/list 'js* "(~{} >> ~{})" x n)) - -(defmacro ^::ana/numeric bit-shift-right-zero-fill [x n] - (core/list 'js* "(~{} >>> ~{})" x n)) - -(defmacro ^::ana/numeric unsigned-bit-shift-right [x n] - (core/list 'js* "(~{} >>> ~{})" x n)) - -(defmacro ^::ana/numeric bit-set [x n] - (core/list 'js* "(~{} | (1 << ~{}))" x n)) - -;; internal -(defmacro mask [hash shift] - (core/list 'js* "((~{} >>> ~{}) & 0x01f)" hash shift)) - -;; internal -(defmacro bitpos [hash shift] - (core/list 'js* "(1 << ~{})" `(mask ~hash ~shift))) - -;; internal -(defmacro caching-hash [coll hash-fn hash-key] - (assert (clojure.core/symbol? hash-key) "hash-key is substituted twice") - `(let [h# ~hash-key] - (if-not (nil? h#) - h# - (let [h# (~hash-fn ~coll)] - (set! ~hash-key h#) - h#)))) - -;;; internal -- reducers-related macros - -(defn- do-curried - [name doc meta args body] - (let [cargs (vec (butlast args))] - `(defn ~name ~doc ~meta - (~cargs (fn [x#] (~name ~@cargs x#))) - (~args ~@body)))) - -(defmacro ^:private defcurried - "Builds another arity of the fn that returns a fn awaiting the last - param" - [name doc meta args & body] - (do-curried name doc meta args body)) - -(defn- do-rfn [f1 k fkv] - `(fn - ([] (~f1)) - ~(clojure.walk/postwalk - #(if (sequential? %) - ((if (vector? %) vec identity) - (core/remove #{k} %)) - %) - fkv) - ~fkv)) - -(defmacro ^:private rfn - "Builds 3-arity reducing fn given names of wrapped fn and key, and k/v impl." - [[f1 k] fkv] - (do-rfn f1 k fkv)) - -;;; end of reducers macros - -(defn protocol-prefix [psym] - (core/str (-> (core/str psym) (.replace \. \$) (.replace \/ \$)) "$")) - -(def #^:private base-type - {nil "null" - 'object "object" - 'string "string" - 'number "number" - 'array "array" - 'function "function" - 'boolean "boolean" - 'default "_"}) - -(def #^:private js-base-type - {'js/Boolean "boolean" - 'js/String "string" - 'js/Array "array" - 'js/Object "object" - 'js/Number "number" - 'js/Function "function"}) - -(defmacro reify [& impls] - (let [t (gensym "t") - meta-sym (gensym "meta") - this-sym (gensym "_") - locals (keys (:locals &env)) - ns (-> &env :ns :name) - munge cljs.compiler/munge] - `(do - (when-not (exists? ~(symbol (core/str ns) (core/str t))) - (deftype ~t [~@locals ~meta-sym] - IWithMeta - (~'-with-meta [~this-sym ~meta-sym] - (new ~t ~@locals ~meta-sym)) - IMeta - (~'-meta [~this-sym] ~meta-sym) - ~@impls)) - (new ~t ~@locals nil)))) - -(defmacro specify! [expr & impls] - (let [x (with-meta (gensym "x") {:extend :instance})] - `(let [~x ~expr] - (extend-type ~x ~@impls) - ~x))) - -(defmacro specify [expr & impls] - `(cljs.core/specify! (cljs.core/clone ~expr) - ~@impls)) - -(defmacro ^:private js-this [] - (core/list 'js* "this")) - -(defmacro this-as - "Defines a scope where JavaScript's implicit \"this\" is bound to the name provided." - [name & body] - `(let [~name (js-this)] - ~@body)) - -(defn to-property [sym] - (symbol (core/str "-" sym))) - -(defn warn-and-update-protocol [p type env] - (when-not (= 'Object p) - (if-let [var (cljs.analyzer/resolve-existing-var (dissoc env :locals) p)] - (do - (when-not (:protocol-symbol var) - (cljs.analyzer/warning :invalid-protocol-symbol env {:protocol p})) - (when (core/and (:protocol-deprecated cljs.analyzer/*cljs-warnings*) - (-> var :deprecated) - (not (-> p meta :deprecation-nowarn))) - (cljs.analyzer/warning :protocol-deprecated env {:protocol p})) - (when (:protocol-symbol var) - (swap! env/*compiler* update-in [:cljs.analyzer/namespaces] - (fn [ns] - (update-in ns [(:ns var) :defs (symbol (name p)) :impls] - conj type))))) - (when (:undeclared cljs.analyzer/*cljs-warnings*) - (cljs.analyzer/warning :undeclared-protocol-symbol env {:protocol p}))))) - -(defn resolve-var [env sym] - (let [ret (-> (dissoc env :locals) - (cljs.analyzer/resolve-var sym) - :name)] - (assert ret (core/str "Can't resolve: " sym)) - ret)) - -(defn ->impl-map [impls] - (loop [ret {} s impls] - (if (seq s) - (recur (assoc ret (first s) (take-while seq? (next s))) - (drop-while seq? (next s))) - ret))) - -(defn base-assign-impls [env resolve tsym type [p sigs]] - (warn-and-update-protocol p tsym env) - (let [psym (resolve p) - pfn-prefix (subs (core/str psym) 0 - (clojure.core/inc (.indexOf (core/str psym) "/")))] - (cons `(aset ~psym ~type true) - (map (fn [[f & meths :as form]] - `(aset ~(symbol (core/str pfn-prefix f)) - ~type ~(with-meta `(fn ~@meths) (meta form)))) - sigs)))) - -(core/defmulti extend-prefix (fn [tsym sym] (-> tsym meta :extend))) - -(core/defmethod extend-prefix :instance - [tsym sym] `(.. ~tsym ~(to-property sym))) - -(core/defmethod extend-prefix :default - [tsym sym] `(.. ~tsym -prototype ~(to-property sym))) - -(defn adapt-obj-params [type [[this & args :as sig] & body]] - (core/list (vec args) - (list* 'this-as (vary-meta this assoc :tag type) body))) - -(defn adapt-ifn-params [type [[this & args :as sig] & body]] - (let [self-sym (with-meta 'self__ {:tag type})] - `(~(vec (cons self-sym args)) - (this-as ~self-sym - (let [~this ~self-sym] - ~@body))))) - -;; for IFn invoke implementations, we need to drop first arg -(defn adapt-ifn-invoke-params [type [[this & args :as sig] & body]] - `(~(vec args) - (this-as ~(vary-meta this assoc :tag type) - ~@body))) - -(defn adapt-proto-params [type [[this & args :as sig] & body]] - `(~(vec (cons (vary-meta this assoc :tag type) args)) - (this-as ~this - ~@body))) - -(defn add-obj-methods [type type-sym sigs] - (map (fn [[f & meths :as form]] - `(set! ~(extend-prefix type-sym f) - ~(with-meta `(fn ~@(map #(adapt-obj-params type %) meths)) (meta form)))) - sigs)) - -(defn ifn-invoke-methods [type type-sym [f & meths :as form]] - (map - (fn [meth] - (let [arity (count (first meth))] - `(set! ~(extend-prefix type-sym (symbol (core/str "cljs$core$IFn$_invoke$arity$" arity))) - ~(with-meta `(fn ~meth) (meta form))))) - (map #(adapt-ifn-invoke-params type %) meths))) - -(defn add-ifn-methods [type type-sym [f & meths :as form]] - (let [meths (map #(adapt-ifn-params type %) meths) - this-sym (with-meta 'self__ {:tag type}) - argsym (gensym "args")] - (concat - [`(set! ~(extend-prefix type-sym 'call) ~(with-meta `(fn ~@meths) (meta form))) - `(set! ~(extend-prefix type-sym 'apply) - ~(with-meta - `(fn ~[this-sym argsym] - (this-as ~this-sym - (.apply (.-call ~this-sym) ~this-sym - (.concat (array ~this-sym) (aclone ~argsym))))) - (meta form)))] - (ifn-invoke-methods type type-sym form)))) - -(defn add-proto-methods* [pprefix type type-sym [f & meths :as form]] - (let [pf (core/str pprefix f)] - (if (vector? (first meths)) - ;; single method case - (let [meth meths] - [`(set! ~(extend-prefix type-sym (core/str pf "$arity$" (count (first meth)))) - ~(with-meta `(fn ~@(adapt-proto-params type meth)) (meta form)))]) - (map (fn [[sig & body :as meth]] - `(set! ~(extend-prefix type-sym (core/str pf "$arity$" (count sig))) - ~(with-meta `(fn ~(adapt-proto-params type meth)) (meta form)))) - meths)))) - -(defn proto-assign-impls [env resolve type-sym type [p sigs]] - (warn-and-update-protocol p type env) - (let [psym (resolve p) - pprefix (protocol-prefix psym) - skip-flag (set (-> type-sym meta :skip-protocol-flag))] - (if (= p 'Object) - (add-obj-methods type type-sym sigs) - (concat - (when-not (skip-flag psym) - [`(set! ~(extend-prefix type-sym pprefix) true)]) - (mapcat - (fn [sig] - (if (= psym 'cljs.core/IFn) - (add-ifn-methods type type-sym sig) - (add-proto-methods* pprefix type type-sym sig))) - sigs))))) - -(defmacro extend-type [type-sym & impls] - (let [env &env - resolve (partial resolve-var env) - impl-map (->impl-map impls) - [type assign-impls] (if-let [type (base-type type-sym)] - [type base-assign-impls] - [(resolve type-sym) proto-assign-impls])] - (when (core/and (:extending-base-js-type cljs.analyzer/*cljs-warnings*) - (js-base-type type-sym)) - (cljs.analyzer/warning :extending-base-js-type env - {:current-symbol type-sym :suggested-symbol (js-base-type type-sym)})) - `(do ~@(mapcat #(assign-impls env resolve type-sym type %) impl-map)))) - -(defn- prepare-protocol-masks [env impls] - (let [resolve (partial resolve-var env) - impl-map (->impl-map impls) - fpp-pbs (seq - (keep fast-path-protocols - (map resolve - (keys impl-map))))] - (if fpp-pbs - (let [fpps (into #{} - (filter (partial contains? fast-path-protocols) - (map resolve (keys impl-map)))) - parts (as-> (group-by first fpp-pbs) parts - (into {} - (map (juxt key (comp (partial map peek) val)) - parts)) - (into {} - (map (juxt key (comp (partial reduce core/bit-or) val)) - parts)))] - [fpps (reduce (fn [ps p] (update-in ps [p] (fnil identity 0))) - parts - (range fast-path-protocol-partitions-count))])))) - -(defn annotate-specs [annots v [f sigs]] - (conj v - (vary-meta (cons f (map #(cons (second %) (nnext %)) sigs)) - merge annots))) - -(defn dt->et - ([type specs fields] - (dt->et type specs fields false)) - ([type specs fields inline] - (let [annots {:cljs.analyzer/type type - :cljs.analyzer/fields fields - :protocol-impl true - :protocol-inline inline}] - (loop [ret [] specs specs] - (if (seq specs) - (let [ret (-> (conj ret (first specs)) - (into (reduce (partial annotate-specs annots) [] - (group-by first (take-while seq? (next specs)))))) - specs (drop-while seq? (next specs))] - (recur ret specs)) - ret))))) - -(defn collect-protocols [impls env] - (->> impls - (filter core/symbol?) - (map #(:name (cljs.analyzer/resolve-var (dissoc env :locals) %))) - (into #{}))) - -(defn- build-positional-factory - [rsym rname fields] - (let [fn-name (symbol (core/str '-> rsym))] - `(defn ~fn-name - [~@fields] - (new ~rname ~@fields)))) - -(defmacro deftype [t fields & impls] - (let [r (:name (cljs.analyzer/resolve-var (dissoc &env :locals) t)) - [fpps pmasks] (prepare-protocol-masks &env impls) - protocols (collect-protocols impls &env) - t (vary-meta t assoc - :protocols protocols - :skip-protocol-flag fpps) ] - (if (seq impls) - `(do - (deftype* ~t ~fields ~pmasks) - (set! (.-cljs$lang$type ~t) true) - (set! (.-cljs$lang$ctorStr ~t) ~(core/str r)) - (set! (.-cljs$lang$ctorPrWriter ~t) (fn [this# writer# opt#] (-write writer# ~(core/str r)))) - (extend-type ~t ~@(dt->et t impls fields true)) - ~(build-positional-factory t r fields) - ~t) - `(do - (deftype* ~t ~fields ~pmasks) - (set! (.-cljs$lang$type ~t) true) - (set! (.-cljs$lang$ctorStr ~t) ~(core/str r)) - (set! (.-cljs$lang$ctorPrWriter ~t) (fn [this# writer# opts#] (-write writer# ~(core/str r)))) - ~(build-positional-factory t r fields) - ~t)))) - -(defn- emit-defrecord - "Do not use this directly - use defrecord" - [env tagname rname fields impls] - (let [hinted-fields fields - fields (vec (map #(with-meta % nil) fields)) - base-fields fields - pr-open (core/str "#" (.getNamespace rname) "." (.getName rname) "{") - fields (conj fields '__meta '__extmap (with-meta '__hash {:mutable true}))] - (let [gs (gensym) - ksym (gensym "k") - impls (concat - impls - ['IRecord - 'ICloneable - `(~'-clone [this#] (new ~tagname ~@fields)) - 'IHash - `(~'-hash [this#] (caching-hash this# ~'hash-imap ~'__hash)) - 'IEquiv - `(~'-equiv [this# other#] - (if (and other# - (identical? (.-constructor this#) - (.-constructor other#)) - (equiv-map this# other#)) - true - false)) - 'IMeta - `(~'-meta [this#] ~'__meta) - 'IWithMeta - `(~'-with-meta [this# ~gs] (new ~tagname ~@(replace {'__meta gs} fields))) - 'ILookup - `(~'-lookup [this# k#] (-lookup this# k# nil)) - `(~'-lookup [this# ~ksym else#] - (cond - ~@(mapcat (fn [f] [`(keyword-identical? ~ksym ~(keyword f)) f]) base-fields) - :else (get ~'__extmap ~ksym else#))) - 'ICounted - `(~'-count [this#] (+ ~(count base-fields) (count ~'__extmap))) - 'ICollection - `(~'-conj [this# entry#] - (if (vector? entry#) - (-assoc this# (-nth entry# 0) (-nth entry# 1)) - (reduce -conj - this# - entry#))) - 'IAssociative - `(~'-assoc [this# k# ~gs] - (condp keyword-identical? k# - ~@(mapcat (fn [fld] - [(keyword fld) (list* `new tagname (replace {fld gs '__hash nil} fields))]) - base-fields) - (new ~tagname ~@(remove #{'__extmap '__hash} fields) (assoc ~'__extmap k# ~gs) nil))) - 'IMap - `(~'-dissoc [this# k#] (if (contains? #{~@(map keyword base-fields)} k#) - (dissoc (with-meta (into {} this#) ~'__meta) k#) - (new ~tagname ~@(remove #{'__extmap '__hash} fields) - (not-empty (dissoc ~'__extmap k#)) - nil))) - 'ISeqable - `(~'-seq [this#] (seq (concat [~@(map #(core/list `vector (keyword %) %) base-fields)] - ~'__extmap))) - - 'IPrintWithWriter - `(~'-pr-writer [this# writer# opts#] - (let [pr-pair# (fn [keyval#] (pr-sequential-writer writer# pr-writer "" " " "" opts# keyval#))] - (pr-sequential-writer - writer# pr-pair# ~pr-open ", " "}" opts# - (concat [~@(map #(core/list `vector (keyword %) %) base-fields)] - ~'__extmap)))) - ]) - [fpps pmasks] (prepare-protocol-masks env impls) - protocols (collect-protocols impls env) - tagname (vary-meta tagname assoc - :protocols protocols - :skip-protocol-flag fpps)] - `(do - (~'defrecord* ~tagname ~hinted-fields ~pmasks) - (extend-type ~tagname ~@(dt->et tagname impls fields true)))))) - -(defn- build-map-factory [rsym rname fields] - (let [fn-name (symbol (core/str 'map-> rsym)) - ms (gensym) - ks (map keyword fields) - getters (map (fn [k] `(~k ~ms)) ks)] - `(defn ~fn-name [~ms] - (new ~rname ~@getters nil (dissoc ~ms ~@ks))))) - -(defmacro defrecord [rsym fields & impls] - (let [rsym (vary-meta rsym assoc :internal-ctor true) - r (vary-meta - (:name (cljs.analyzer/resolve-var (dissoc &env :locals) rsym)) - assoc :internal-ctor true)] - `(let [] - ~(emit-defrecord &env rsym r fields impls) - (set! (.-cljs$lang$type ~r) true) - (set! (.-cljs$lang$ctorPrSeq ~r) (fn [this#] (core/list ~(core/str r)))) - (set! (.-cljs$lang$ctorPrWriter ~r) (fn [this# writer#] (-write writer# ~(core/str r)))) - ~(build-positional-factory rsym r fields) - ~(build-map-factory rsym r fields) - ~r))) - -(defmacro defprotocol [psym & doc+methods] - (let [p (:name (cljs.analyzer/resolve-var (dissoc &env :locals) psym)) - psym (vary-meta psym assoc :protocol-symbol true) - ns-name (-> &env :ns :name) - fqn (fn [n] (symbol (core/str ns-name "." n))) - prefix (protocol-prefix p) - methods (if (core/string? (first doc+methods)) (next doc+methods) doc+methods) - expand-sig (fn [fname slot sig] - `(~sig - (if (and ~(first sig) (. ~(first sig) ~(symbol (core/str "-" slot)))) ;; Property access needed here. - (. ~(first sig) ~slot ~@sig) - (let [x# (if (nil? ~(first sig)) nil ~(first sig))] - ((or - (aget ~(fqn fname) (goog.typeOf x#)) - (aget ~(fqn fname) "_") - (throw (missing-protocol - ~(core/str psym "." fname) ~(first sig)))) - ~@sig))))) - method (fn [[fname & sigs]] - (let [sigs (take-while vector? sigs) - slot (symbol (core/str prefix (name fname))) - fname (vary-meta fname assoc :protocol p)] - `(defn ~fname ~@(map (fn [sig] - (expand-sig fname - (symbol (core/str slot "$arity$" (count sig))) - sig)) - sigs))))] - `(do - (set! ~'*unchecked-if* true) - (def ~psym (js-obj)) - ~@(map method methods) - (set! ~'*unchecked-if* false)))) - -(defmacro implements? - "EXPERIMENTAL" - [psym x] - (let [p (:name - (cljs.analyzer/resolve-var - (dissoc &env :locals) psym)) - prefix (protocol-prefix p) - xsym (bool-expr (gensym)) - [part bit] (fast-path-protocols p) - msym (symbol - (core/str "-cljs$lang$protocol_mask$partition" part "$"))] - `(let [~xsym ~x] - (if ~xsym - (let [bit# ~(if bit `(unsafe-bit-and (. ~xsym ~msym) ~bit))] - (if (or bit# - ~(bool-expr `(. ~xsym ~(symbol (core/str "-" prefix))))) - true - false)) - false)))) - -(defmacro satisfies? - "Returns true if x satisfies the protocol" - [psym x] - (let [p (:name - (cljs.analyzer/resolve-var - (dissoc &env :locals) psym)) - prefix (protocol-prefix p) - xsym (bool-expr (gensym)) - [part bit] (fast-path-protocols p) - msym (symbol - (core/str "-cljs$lang$protocol_mask$partition" part "$"))] - `(let [~xsym ~x] - (if ~xsym - (let [bit# ~(if bit `(unsafe-bit-and (. ~xsym ~msym) ~bit))] - (if (or bit# - ~(bool-expr `(. ~xsym ~(symbol (core/str "-" prefix))))) - true - (if (coercive-not (. ~xsym ~msym)) - (cljs.core/native-satisfies? ~psym ~xsym) - false))) - (cljs.core/native-satisfies? ~psym ~xsym))))) - -(defmacro lazy-seq [& body] - `(new cljs.core/LazySeq nil (fn [] ~@body) nil nil)) - -(defmacro delay [& body] - "Takes a body of expressions and yields a Delay object that will - invoke the body only the first time it is forced (with force or deref/@), and - will cache the result and return it on all subsequent force - calls." - `(new cljs.core/Delay (atom {:done false, :value nil}) (fn [] ~@body))) - -(defmacro with-redefs - "binding => var-symbol temp-value-expr - - Temporarily redefines vars while executing the body. The - temp-value-exprs will be evaluated and each resulting value will - replace in parallel the root value of its var. After the body is - executed, the root values of all the vars will be set back to their - old values. Useful for mocking out functions during testing." - [bindings & body] - (let [names (take-nth 2 bindings) - vals (take-nth 2 (drop 1 bindings)) - tempnames (map (comp gensym name) names) - binds (map core/vector names vals) - resets (reverse (map core/vector names tempnames)) - bind-value (fn [[k v]] (core/list 'set! k v))] - `(let [~@(interleave tempnames names)] - (try - ~@(map bind-value binds) - ~@body - (finally - ~@(map bind-value resets)))))) - -(defmacro binding - "binding => var-symbol init-expr - - Creates new bindings for the (already-existing) vars, with the - supplied initial values, executes the exprs in an implicit do, then - re-establishes the bindings that existed before. The new bindings - are made in parallel (unlike let); all init-exprs are evaluated - before the vars are bound to their new values." - [bindings & body] - (let [names (take-nth 2 bindings)] - (cljs.analyzer/confirm-bindings &env names) - `(with-redefs ~bindings ~@body))) - -(defmacro condp - "Takes a binary predicate, an expression, and a set of clauses. - Each clause can take the form of either: - - test-expr result-expr - - test-expr :>> result-fn - - Note :>> is an ordinary keyword. - - For each clause, (pred test-expr expr) is evaluated. If it returns - logical true, the clause is a match. If a binary clause matches, the - result-expr is returned, if a ternary clause matches, its result-fn, - which must be a unary function, is called with the result of the - predicate as its argument, the result of that call being the return - value of condp. A single default expression can follow the clauses, - and its value will be returned if no clause matches. If no default - expression is provided and no clause matches, an - IllegalArgumentException is thrown." - {:added "1.0"} - - [pred expr & clauses] - (let [gpred (gensym "pred__") - gexpr (gensym "expr__") - emit (fn emit [pred expr args] - (let [[[a b c :as clause] more] - (split-at (if (= :>> (second args)) 3 2) args) - n (count clause)] - (core/cond - (= 0 n) `(throw (js/Error. (core/str "No matching clause: " ~expr))) - (= 1 n) a - (= 2 n) `(if (~pred ~a ~expr) - ~b - ~(emit pred expr more)) - :else `(if-let [p# (~pred ~a ~expr)] - (~c p#) - ~(emit pred expr more))))) - gres (gensym "res__")] - `(let [~gpred ~pred - ~gexpr ~expr] - ~(emit gpred gexpr clauses)))) - -(defmacro case [e & clauses] - (let [default (if (odd? (count clauses)) - (last clauses) - `(throw (js/Error. (core/str "No matching clause: " ~e)))) - assoc-test (fn assoc-test [m test expr] - (if (contains? m test) - (throw (clojure.core/IllegalArgumentException. - (core/str "Duplicate case test constant '" - test "'" - (when (:line &env) - (core/str " on line " (:line &env) " " - cljs.analyzer/*cljs-file*))))) - (assoc m test expr))) - pairs (reduce (fn [m [test expr]] - (core/cond - (seq? test) (reduce (fn [m test] - (let [test (if (core/symbol? test) - (core/list 'quote test) - test)] - (assoc-test m test expr))) - m test) - (core/symbol? test) (assoc-test m (core/list 'quote test) expr) - :else (assoc-test m test expr))) - {} (partition 2 clauses)) - esym (gensym)] - `(let [~esym ~e] - (cond - ~@(mapcat (fn [[m c]] `((cljs.core/= ~m ~esym) ~c)) pairs) - :else ~default)))) - -(defmacro assert - "Evaluates expr and throws an exception if it does not evaluate to - logical true." - ([x] - (when *assert* - `(when-not ~x - (throw (js/Error. - (cljs.core/str "Assert failed: " (cljs.core/pr-str '~x))))))) - ([x message] - (when *assert* - `(when-not ~x - (throw (js/Error. - (cljs.core/str "Assert failed: " ~message "\n" (cljs.core/pr-str '~x)))))))) - -(defmacro for - "List comprehension. Takes a vector of one or more - binding-form/collection-expr pairs, each followed by zero or more - modifiers, and yields a lazy sequence of evaluations of expr. - Collections are iterated in a nested fashion, rightmost fastest, - and nested coll-exprs can refer to bindings created in prior - binding-forms. Supported modifiers are: :let [binding-form expr ...], - :while test, :when test. - - (take 100 (for [x (range 100000000) y (range 1000000) :while (< y x)] [x y]))" - [seq-exprs body-expr] - (assert-args for - (vector? seq-exprs) "a vector for its binding" - (even? (count seq-exprs)) "an even number of forms in binding vector") - (let [to-groups (fn [seq-exprs] - (reduce (fn [groups [k v]] - (if (core/keyword? k) - (conj (pop groups) (conj (peek groups) [k v])) - (conj groups [k v]))) - [] (partition 2 seq-exprs))) - err (fn [& msg] (throw (ex-info (apply core/str msg) {}))) - emit-bind (fn emit-bind [[[bind expr & mod-pairs] - & [[_ next-expr] :as next-groups]]] - (let [giter (gensym "iter__") - gxs (gensym "s__") - do-mod (fn do-mod [[[k v :as pair] & etc]] - (core/cond - (= k :let) `(let ~v ~(do-mod etc)) - (= k :while) `(when ~v ~(do-mod etc)) - (= k :when) `(if ~v - ~(do-mod etc) - (recur (rest ~gxs))) - (core/keyword? k) (err "Invalid 'for' keyword " k) - next-groups - `(let [iterys# ~(emit-bind next-groups) - fs# (seq (iterys# ~next-expr))] - (if fs# - (concat fs# (~giter (rest ~gxs))) - (recur (rest ~gxs)))) - :else `(cons ~body-expr - (~giter (rest ~gxs)))))] - (if next-groups - #_ "not the inner-most loop" - `(fn ~giter [~gxs] - (lazy-seq - (loop [~gxs ~gxs] - (when-first [~bind ~gxs] - ~(do-mod mod-pairs))))) - #_"inner-most loop" - (let [gi (gensym "i__") - gb (gensym "b__") - do-cmod (fn do-cmod [[[k v :as pair] & etc]] - (core/cond - (= k :let) `(let ~v ~(do-cmod etc)) - (= k :while) `(when ~v ~(do-cmod etc)) - (= k :when) `(if ~v - ~(do-cmod etc) - (recur - (unchecked-inc ~gi))) - (core/keyword? k) - (err "Invalid 'for' keyword " k) - :else - `(do (chunk-append ~gb ~body-expr) - (recur (unchecked-inc ~gi)))))] - `(fn ~giter [~gxs] - (lazy-seq - (loop [~gxs ~gxs] - (when-let [~gxs (seq ~gxs)] - (if (chunked-seq? ~gxs) - (let [c# ^not-native (chunk-first ~gxs) - size# (count c#) - ~gb (chunk-buffer size#)] - (if (coercive-boolean - (loop [~gi 0] - (if (< ~gi size#) - (let [~bind (-nth c# ~gi)] - ~(do-cmod mod-pairs)) - true))) - (chunk-cons - (chunk ~gb) - (~giter (chunk-rest ~gxs))) - (chunk-cons (chunk ~gb) nil))) - (let [~bind (first ~gxs)] - ~(do-mod mod-pairs)))))))))))] - `(let [iter# ~(emit-bind (to-groups seq-exprs))] - (iter# ~(second seq-exprs))))) - -(defmacro doseq - "Repeatedly executes body (presumably for side-effects) with - bindings and filtering as provided by \"for\". Does not retain - the head of the sequence. Returns nil." - [seq-exprs & body] - (assert-args doseq - (vector? seq-exprs) "a vector for its binding" - (even? (count seq-exprs)) "an even number of forms in binding vector") - (let [err (fn [& msg] (throw (ex-info (apply core/str msg) {}))) - step (fn step [recform exprs] - (if-not exprs - [true `(do ~@body)] - (let [k (first exprs) - v (second exprs) - - seqsym (gensym "seq__") - recform (if (core/keyword? k) recform `(recur (next ~seqsym) nil 0 0)) - steppair (step recform (nnext exprs)) - needrec (steppair 0) - subform (steppair 1)] - (core/cond - (= k :let) [needrec `(let ~v ~subform)] - (= k :while) [false `(when ~v - ~subform - ~@(when needrec [recform]))] - (= k :when) [false `(if ~v - (do - ~subform - ~@(when needrec [recform])) - ~recform)] - (core/keyword? k) (err "Invalid 'doseq' keyword" k) - :else (let [chunksym (with-meta (gensym "chunk__") - {:tag 'not-native}) - countsym (gensym "count__") - isym (gensym "i__") - recform-chunk `(recur ~seqsym ~chunksym ~countsym (unchecked-inc ~isym)) - steppair-chunk (step recform-chunk (nnext exprs)) - subform-chunk (steppair-chunk 1)] - [true `(loop [~seqsym (seq ~v) - ~chunksym nil - ~countsym 0 - ~isym 0] - (if (coercive-boolean (< ~isym ~countsym)) - (let [~k (-nth ~chunksym ~isym)] - ~subform-chunk - ~@(when needrec [recform-chunk])) - (when-let [~seqsym (seq ~seqsym)] - (if (chunked-seq? ~seqsym) - (let [c# (chunk-first ~seqsym)] - (recur (chunk-rest ~seqsym) c# - (count c#) 0)) - (let [~k (first ~seqsym)] - ~subform - ~@(when needrec [recform]))))))])))))] - (nth (step nil (seq seq-exprs)) 1))) - -(defmacro array [& rest] - (let [xs-str (->> (repeat "~{}") - (take (count rest)) - (interpose ",") - (apply core/str))] - (vary-meta - (list* 'js* (core/str "[" xs-str "]") rest) - assoc :tag 'array))) - -(defmacro make-array - [size] - (vary-meta - (if (core/number? size) - `(array ~@(take size (repeat nil))) - `(js/Array. ~size)) - assoc :tag 'array)) - -(defmacro list - ([] `cljs.core.List.EMPTY) - ([x & xs] - `(-conj (list ~@xs) ~x))) - -(defmacro vector - ([] `cljs.core.PersistentVector.EMPTY) - ([& xs] - (let [cnt (count xs)] - (if (core/< cnt 32) - `(cljs.core.PersistentVector. nil ~cnt 5 - cljs.core.PersistentVector.EMPTY_NODE (array ~@xs) nil) - (vary-meta - `(cljs.core.PersistentVector.fromArray (array ~@xs) true) - assoc :tag 'cljs.core/PersistentVector))))) - -(defmacro array-map - ([] `cljs.core.PersistentArrayMap.EMPTY) - ([& kvs] - (core/cond - (core/> (count kvs) 16) - `(hash-map ~@kvs) - - (let [keys (map first (partition 2 kvs))] - (core/and (every? #(= (:op %) :constant) - (map #(cljs.analyzer/analyze &env %) keys)) - (= (count (into #{} keys)) (count keys)))) - `(cljs.core.PersistentArrayMap. nil ~(clojure.core// (count kvs) 2) (array ~@kvs) nil) - - :else - `(cljs.core.PersistentArrayMap.fromArray (array ~@kvs) true false)))) - -(defmacro hash-map - ([] `cljs.core.PersistentHashMap.EMPTY) - ([& kvs] - (let [pairs (partition 2 kvs) - ks (map first pairs) - vs (map second pairs)] - (vary-meta - `(cljs.core.PersistentHashMap.fromArrays (array ~@ks) (array ~@vs)) - assoc :tag 'cljs.core/PersistentHashMap)))) - -(defmacro hash-set - ([] `cljs.core.PersistentHashSet.EMPTY) - ([& xs] - (if (core/and (core/<= (count xs) 8) - (every? #(= (:op %) :constant) - (map #(cljs.analyzer/analyze &env %) xs)) - (= (count (into #{} xs)) (count xs))) - `(cljs.core.PersistentHashSet. nil - (cljs.core.PersistentArrayMap. nil ~(count xs) (array ~@(interleave xs (repeat nil))) nil) - nil) - (vary-meta - `(cljs.core.PersistentHashSet.fromArray (array ~@xs) true) - assoc :tag 'cljs.core/PersistentHashSet)))) - -(defn js-obj* [kvs] - (let [kvs-str (->> (repeat "~{}:~{}") - (take (count kvs)) - (interpose ",") - (apply core/str))] - (vary-meta - (list* 'js* (core/str "{" kvs-str "}") (apply concat kvs)) - assoc :tag 'object))) - -(defmacro js-obj [& rest] - (let [sym-or-str? (fn [x] (core/or (core/symbol? x) (core/string? x))) - filter-on-keys (fn [f coll] - (->> coll - (filter (fn [[k _]] (f k))) - (into {}))) - kvs (into {} (map vec (partition 2 rest))) - sym-pairs (filter-on-keys core/symbol? kvs) - expr->local (zipmap - (filter (complement sym-or-str?) (keys kvs)) - (repeatedly gensym)) - obj (gensym "obj")] - `(let [~@(apply concat (clojure.set/map-invert expr->local)) - ~obj ~(js-obj* (filter-on-keys core/string? kvs))] - ~@(map (fn [[k v]] `(aset ~obj ~k ~v)) sym-pairs) - ~@(map (fn [[k v]] `(aset ~obj ~v ~(core/get kvs k))) expr->local) - ~obj))) - -(defmacro alength [a] - (vary-meta - (core/list 'js* "~{}.length" a) - assoc :tag 'number)) - -(defmacro amap - "Maps an expression across an array a, using an index named idx, and - return value named ret, initialized to a clone of a, then setting - each element of ret to the evaluation of expr, returning the new - array ret." - [a idx ret expr] - `(let [a# ~a - ~ret (aclone a#)] - (loop [~idx 0] - (if (< ~idx (alength a#)) - (do - (aset ~ret ~idx ~expr) - (recur (inc ~idx))) - ~ret)))) - -(defmacro areduce - "Reduces an expression across an array a, using an index named idx, - and return value named ret, initialized to init, setting ret to the - evaluation of expr at each step, returning ret." - [a idx ret init expr] - `(let [a# ~a] - (loop [~idx 0 ~ret ~init] - (if (< ~idx (alength a#)) - (recur (inc ~idx) ~expr) - ~ret)))) - -(defmacro dotimes - "bindings => name n - - Repeatedly executes body (presumably for side-effects) with name - bound to integers from 0 through n-1." - [bindings & body] - (let [i (first bindings) - n (second bindings)] - `(let [n# ~n] - (loop [~i 0] - (when (< ~i n#) - ~@body - (recur (inc ~i))))))) - -(defn ^:private check-valid-options - "Throws an exception if the given option map contains keys not listed - as valid, else returns nil." - [options & valid-keys] - (when (seq (apply disj (apply core/hash-set (keys options)) valid-keys)) - (throw - (apply core/str "Only these options are valid: " - (first valid-keys) - (map #(core/str ", " %) (rest valid-keys)))))) - -(defmacro defmulti - "Creates a new multimethod with the associated dispatch function. - The docstring and attribute-map are optional. - - Options are key-value pairs and may be one of: - :default the default dispatch value, defaults to :default - :hierarchy the isa? hierarchy to use for dispatching - defaults to the global hierarchy" - [mm-name & options] - (let [docstring (if (core/string? (first options)) - (first options) - nil) - options (if (core/string? (first options)) - (next options) - options) - m (if (map? (first options)) - (first options) - {}) - options (if (map? (first options)) - (next options) - options) - dispatch-fn (first options) - options (next options) - m (if docstring - (assoc m :doc docstring) - m) - m (if (meta mm-name) - (conj (meta mm-name) m) - m)] - (when (= (count options) 1) - (throw (Exception. "The syntax for defmulti has changed. Example: (defmulti name dispatch-fn :default dispatch-value)"))) - (let [options (apply core/hash-map options) - default (core/get options :default :default)] - (check-valid-options options :default :hierarchy) - `(def ~(with-meta mm-name m) - (let [method-table# (atom {}) - prefer-table# (atom {}) - method-cache# (atom {}) - cached-hierarchy# (atom {}) - hierarchy# (get ~options :hierarchy (cljs.core/get-global-hierarchy))] - (cljs.core/MultiFn. ~(name mm-name) ~dispatch-fn ~default hierarchy# - method-table# prefer-table# method-cache# cached-hierarchy#)))))) - -(defmacro defmethod - "Creates and installs a new method of multimethod associated with dispatch-value. " - [multifn dispatch-val & fn-tail] - `(-add-method ~(with-meta multifn {:tag 'cljs.core/MultiFn}) ~dispatch-val (fn ~@fn-tail))) - -(defmacro time - "Evaluates expr and prints the time it took. Returns the value of expr." - [expr] - `(let [start# (.getTime (js/Date.)) - ret# ~expr] - (prn (core/str "Elapsed time: " (- (.getTime (js/Date.)) start#) " msecs")) - ret#)) - -(defmacro simple-benchmark - "Runs expr iterations times in the context of a let expression with - the given bindings, then prints out the bindings and the expr - followed by number of iterations and total time. The optional - argument print-fn, defaulting to println, sets function used to - print the result. expr's string representation will be produced - using pr-str in any case." - [bindings expr iterations & {:keys [print-fn] :or {print-fn 'println}}] - (let [bs-str (pr-str bindings) - expr-str (pr-str expr)] - `(let ~bindings - (let [start# (.getTime (js/Date.)) - ret# (dotimes [_# ~iterations] ~expr) - end# (.getTime (js/Date.)) - elapsed# (- end# start#)] - (~print-fn (str ~bs-str ", " ~expr-str ", " - ~iterations " runs, " elapsed# " msecs")))))) - -(def cs (into [] (map (comp symbol core/str core/char) (range 97 118)))) - -(defn gen-apply-to-helper - ([] (gen-apply-to-helper 1)) - ([n] - (let [prop (symbol (core/str "-cljs$core$IFn$_invoke$arity$" n)) - f (symbol (core/str "cljs$core$IFn$_invoke$arity$" n))] - (if (core/<= n 20) - `(let [~(cs (core/dec n)) (-first ~'args) - ~'args (-rest ~'args)] - (if (core/== ~'argc ~n) - (if (. ~'f ~prop) - (. ~'f (~f ~@(take n cs))) - (~'f ~@(take n cs))) - ~(gen-apply-to-helper (core/inc n)))) - `(throw (js/Error. "Only up to 20 arguments supported on functions")))))) - -(defmacro gen-apply-to [] - `(do - (set! ~'*unchecked-if* true) - (defn ~'apply-to [~'f ~'argc ~'args] - (let [~'args (seq ~'args)] - (if (zero? ~'argc) - (~'f) - ~(gen-apply-to-helper)))) - (set! ~'*unchecked-if* false))) - -(defmacro with-out-str - "Evaluates exprs in a context in which *print-fn* is bound to .append - on a fresh StringBuffer. Returns the string created by any nested - printing calls." - [& body] - `(let [sb# (goog.string/StringBuffer.)] - (binding [cljs.core/*print-fn* (fn [x#] (.append sb# x#))] - ~@body) - (cljs.core/str sb#))) - -(defmacro lazy-cat - "Expands to code which yields a lazy sequence of the concatenation - of the supplied colls. Each coll expr is not evaluated until it is - needed. - - (lazy-cat xs ys zs) === (concat (lazy-seq xs) (lazy-seq ys) (lazy-seq zs))" - [& colls] - `(concat ~@(map #(core/list `lazy-seq %) colls))) diff --git a/src/clj/cljs/js_deps.clj b/src/clj/cljs/js_deps.clj deleted file mode 100644 index 4838e41b03..0000000000 --- a/src/clj/cljs/js_deps.clj +++ /dev/null @@ -1,223 +0,0 @@ -(ns cljs.js-deps - (:require [clojure.java.io :as io] - [clojure.string :as string]) - (:import java.io.File)) - -(defn jar-entry-names* [jar-path] - (with-open [z (java.util.zip.ZipFile. jar-path)] - (doall (map #(.getName %) (enumeration-seq (.entries z)))))) - -(def jar-entry-names (memoize jar-entry-names*)) - -(defn find-js-jar - "finds js resources from a given path in a jar file" - [jar-path lib-path] - (doall - (map #(io/resource %) - (filter #(do - (and - (.startsWith ^String % lib-path) - (.endsWith ^String % ".js"))) - (jar-entry-names jar-path))))) - -(defmulti to-url class) - -(defmethod to-url File [^File f] (.toURL (.toURI f))) - -(defmethod to-url String [s] (to-url (io/file s))) - -(defn find-js-fs - "finds js resources from a path on the files system" - [path] - (let [file (io/file path)] - (when (.exists file) - (map to-url (filter #(.endsWith ^String (.getName ^File %) ".js") (file-seq (io/file path))))))) - - -(defn find-js-classpath - "finds all js files on the classpath matching the path provided" - [path] - (let [process-entry #(if (.endsWith ^String % ".jar") - (find-js-jar % path) - (find-js-fs (str % "/" path))) - cpath-list (let [sysp (System/getProperty "java.class.path" )] - (if (.contains sysp ";") - (string/split sysp #";") - (string/split sysp #":")))] - (doall (reduce #(let [p (process-entry %2)] - (if p (concat %1 p) %1)) [] cpath-list)))) - -(defn find-js-resources [path] - "finds js resources in a given path on either the file system or - the classpath" - (let [file (io/file path)] - (if (.exists file) - (find-js-fs path) - (find-js-classpath path)))) - -(defn parse-js-ns - "Given the lines from a JavaScript source file, parse the provide - and require statements and return them in a map. Assumes that all - provide and require statements appear before the first function - definition." - [lines] - (letfn [(conj-in [m k v] (update-in m [k] (fn [old] (conj old v))))] - (->> (for [line lines x (string/split line #";")] x) - (map string/trim) - (take-while #(not (re-matches #".*=[\s]*function\(.*\)[\s]*[{].*" %))) - (map #(re-matches #".*goog\.(provide|require)\(['\"](.*)['\"]\)" %)) - (remove nil?) - (map #(drop 1 %)) - (reduce (fn [m ns] - (let [munged-ns (string/replace (last ns) "_" "-")] - (if (= (first ns) "require") - (conj-in m :requires munged-ns) - (conj-in m :provides munged-ns)))) - {:requires [] :provides []})))) - -(defprotocol IJavaScript - (-foreign? [this] "Whether the Javascript represents a foreign - library (a js file that not have any goog.provide statement") - (-url [this] "The URL where this JavaScript is located. Returns nil - when JavaScript exists in memory only.") - (-provides [this] "A list of namespaces that this JavaScript provides.") - (-requires [this] "A list of namespaces that this JavaScript requires.") - (-source [this] "The JavaScript source string.")) - -(defn build-index - "Index a list of dependencies by namespace and file name. There can - be zero or more namespaces provided per file." - [deps] - (reduce (fn [m next] - (let [provides (:provides next)] - (-> (if (seq provides) - (reduce (fn [m* provide] - (assoc m* provide next)) - m - provides) - m) - (assoc (:file next) next)))) - {} - deps)) - -(defn dependency-order-visit - [state ns-name] - (let [file (get state ns-name)] - (if (or (:visited file) (nil? file)) - state - (let [state (assoc-in state [ns-name :visited] true) - deps (:requires file) - state (reduce dependency-order-visit state deps)] - (assoc state :order (conj (:order state) file)))))) - -(defn- pack-string [s] - (if (string? s) - {:provides (-provides s) - :requires (-requires s) - :file (str "from_source_" (gensym) ".clj") - ::original s} - s)) - -(defn- unpack-string [m] - (or (::original m) m)) - -(defn dependency-order - "Topologically sort a collection of dependencies." - [coll] - (let [state (build-index (map pack-string coll))] - (map unpack-string - (distinct - (:order (reduce dependency-order-visit (assoc state :order []) (keys state))))))) - - -;; Dependencies -;; ============ -;; -;; Find all dependencies from files on the classpath. Eliminates the -;; need for closurebuilder. cljs dependencies will be compiled as -;; needed. - -(defn find-url - "Given a string, returns a URL. Attempts to resolve as a classpath-relative - path, then as a path relative to the working directory or a URL string" - [path-or-url] - (or (io/resource path-or-url) - (try (io/as-url path-or-url) - (catch java.net.MalformedURLException e - false)) - (io/as-url (io/as-file path-or-url)))) - -(defn load-foreign-library* - "Given a library spec (a map containing the keys :file - and :provides), returns a map containing :provides, :requires, :file - and :url" - ([lib-spec] (load-foreign-library* lib-spec false)) - ([lib-spec cp-only?] - (let [find-func (if cp-only? io/resource find-url)] - (merge lib-spec {:foreign true - :url (find-func (:file lib-spec))})))) - -(def load-foreign-library (memoize load-foreign-library*)) - -(defn load-library* - "Given a path to a JavaScript library, which is a directory - containing Javascript files, return a list of maps - containing :provides, :requires, :file and :url." - ([path] (load-library* path false)) - ([path cp-only?] - (let [find-func (if cp-only? find-js-classpath find-js-resources) - graph-node (fn [u] - (with-open [reader (io/reader u)] - (-> reader line-seq parse-js-ns - (assoc :url u))))] - (let [js-sources (find-js-resources path)] - (filter #(seq (:provides %)) (map graph-node js-sources)))))) - -(def load-library (memoize load-library*)) - -(defn library-dependencies [{libs :libs foreign-libs :foreign-libs - ups-libs :ups-libs ups-flibs :ups-foreign-libs}] - (concat - (mapcat #(load-library % true) ups-libs) ;upstream deps - (mapcat load-library libs) - (map #(load-foreign-library % true) ups-flibs) ;upstream deps - (map load-foreign-library foreign-libs))) - -(comment - ;; load one library - (load-library* "closure/library/third_party/closure") - ;; load all library dependencies - (library-dependencies {:libs ["closure/library/third_party/closure"]}) - (library-dependencies {:foreign-libs [{:file "http://example.com/remote.js" - :provides ["my.example"]}]}) - (library-dependencies {:foreign-libs [{:file "local/file.js" - :provides ["my.example"]}]}) - (library-dependencies {:foreign-libs [{:file "cljs/nodejs_externs.js" - :provides ["my.example"]}]})) - -(defn goog-dependencies* - "Create an index of Google dependencies by namespace and file name." - [] - (letfn [(parse-list [s] (when (> (count s) 0) - (-> (.substring ^String s 1 (dec (count s))) - (string/split #"'\s*,\s*'"))))] - (with-open [reader (io/reader (io/resource "goog/deps.js"))] - (->> (line-seq reader) - (map #(re-matches #"^goog\.addDependency\(['\"](.*)['\"],\s*\[(.*)\],\s*\[(.*)\]\);.*" %)) - (remove nil?) - (map #(drop 1 %)) - (remove #(.startsWith ^String (first %) "../../third_party")) - (map #(hash-map :file (str "goog/"(first %)) - :provides (parse-list (second %)) - :requires (parse-list (last %)) - :group :goog)) - (doall))))) - -(def goog-dependencies (memoize goog-dependencies*)) - - -(defn js-dependency-index - "Returns the index for all JavaScript dependencies. Lookup by - namespace or file name." - [opts] - (build-index (concat (goog-dependencies) (library-dependencies opts)))) diff --git a/src/clj/cljs/repl.clj b/src/clj/cljs/repl.clj deleted file mode 100644 index 5829060a02..0000000000 --- a/src/clj/cljs/repl.clj +++ /dev/null @@ -1,235 +0,0 @@ -;; Copyright (c) Rich Hickey. All rights reserved. -;; The use and distribution terms for this software are covered by the -;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -;; which can be found in the file epl-v10.html at the root of this distribution. -;; By using this software in any fashion, you are agreeing to be bound by -;; the terms of this license. -;; You must not remove this notice, or any other, from this software. - -(ns cljs.repl - (:refer-clojure :exclude [load-file]) - (:import java.io.File - javax.xml.bind.DatatypeConverter) - (:require [clojure.string :as string] - [clojure.java.io :as io] - [cljs.compiler :as comp] - [cljs.analyzer :as ana] - [cljs.env :as env] - [cljs.tagged-literals :as tags] - [cljs.closure :as cljsc] - [cljs.source-map :as sm] - [clojure.tools.reader :as reader] - [clojure.tools.reader.reader-types :as readers])) - -(def ^:dynamic *cljs-verbose* false) - -(defprotocol IJavaScriptEnv - (-setup [this] "initialize the environment") - (-evaluate [this filename line js] "evaluate a javascript string") - (-load [this ns url] "load code at url into the environment") - (-tear-down [this] "dispose of the environment")) - -(defn- env->opts - "Returns a hash-map containing all of the entries in [repl-env], translating -:working-dir to :output-dir." - [repl-env] - ; some bits in cljs.closure use the options value as an ifn :-/ - (-> (into {} repl-env) - (assoc :optimizations (get repl-env :optimizations :none)) - (assoc :output-dir (get repl-env :working-dir ".repl")))) - -(defn load-namespace - "Load a namespace and all of its dependencies into the evaluation environment. - The environment is responsible for ensuring that each namespace is loaded once and - only once." - [repl-env sym] - (let [sym (if (and (seq? sym) - (= (first sym) 'quote)) - (second sym) - sym) - deps (->> (cljsc/add-dependencies (env->opts repl-env) - {:requires [(name sym)] :type :seed}) - (remove (comp #{["goog"]} :provides)) - (remove (comp #{:seed} :type)) - (map #(select-keys % [:provides :url])))] - (doseq [{:keys [url provides]} deps] - (-load repl-env provides url)))) - -(defn- load-dependencies - [repl-env requires] - (doseq [ns requires] - (load-namespace repl-env ns))) - -(defn- display-error - ([ret form] - (display-error ret form (constantly nil))) - ([ret form f] - (when-not (and (seq? form) (= 'ns (first form))) - (f) - (println (:value ret)) - (when-let [st (:stacktrace ret)] - (println st))))) - -(defn evaluate-form - "Evaluate a ClojureScript form in the JavaScript environment. Returns a - string which is the ClojureScript return value. This string may or may - not be readable by the Clojure reader." - ([repl-env env filename form] - (evaluate-form repl-env env filename form identity)) - ([repl-env env filename form wrap] - (try - (binding [ana/*cljs-file* filename] - (let [ast (ana/analyze env form) - js (comp/emit-str ast) - wrap-js - (if (:source-map repl-env) - (binding [comp/*source-map-data* - (atom {:source-map (sorted-map) - :gen-col 0 - :gen-line 0})] - (let [js (comp/emit-str (ana/no-warn (ana/analyze env (wrap form)))) - t (System/currentTimeMillis)] - (str js - "\n//# sourceURL=repl-" t ".js" - "\n//# sourceMappingURL=data:application/json;base64," - (DatatypeConverter/printBase64Binary - (.getBytes - (sm/encode - {(str "repl-" t ".cljs") - (:source-map @comp/*source-map-data*)} - {:lines (+ (:gen-line @comp/*source-map-data*) 3) - :file (str "repl-" t ".js") - :sources-content - [(or (:source (meta form)) - ;; handle strings / primitives without metadata - (with-out-str (pr form)))]}) - "UTF-8"))))) - (comp/emit-str (ana/no-warn (ana/analyze env (wrap form)))))] - (when (= (:op ast) :ns) - (load-dependencies repl-env (into (vals (:requires ast)) - (distinct (vals (:uses ast)))))) - (when *cljs-verbose* - (print js)) - (let [ret (-evaluate repl-env filename (:line (meta form)) wrap-js)] - (case (:status ret) - ;;we eat ns errors because we know goog.provide() will throw when reloaded - ;;TODO - file bug with google, this is bs error - ;;this is what you get when you try to 'teach new developers' - ;;via errors (goog/base.js 104) - :error (display-error ret form) - :exception (display-error ret form - #(prn "Error evaluating:" form :as js)) - :success (:value ret))))) - (catch Throwable ex - (.printStackTrace ex) - (println (str ex)))))) - -(defn load-stream [repl-env filename res] - (let [env (ana/empty-env)] - (doseq [form (ana/forms-seq res filename)] - (let [env (assoc env :ns (ana/get-namespace ana/*cljs-ns*))] - (evaluate-form repl-env env filename form))))) - -(defn load-file - [repl-env f] - (binding [ana/*cljs-ns* 'cljs.user] - (let [res (if (= \/ (first f)) f (io/resource f))] - (assert res (str "Can't find " f " in classpath")) - (load-stream repl-env f res)))) - -(defn- wrap-fn [form] - (cond (and (seq? form) (= 'ns (first form))) identity - ('#{*1 *2 *3} form) (fn [x] `(cljs.core.pr-str ~x)) - :else (fn [x] `(cljs.core.pr-str - (let [ret# ~x] - (do (set! *3 *2) - (set! *2 *1) - (set! *1 ret#) - ret#)))))) - -(defn- eval-and-print [repl-env env form] - (let [ret (evaluate-form repl-env - (assoc env :ns (ana/get-namespace ana/*cljs-ns*)) - "" - form - (wrap-fn form))] - (try (prn (read-string ret)) - (catch Exception e - (if (string? ret) - (println ret) - (prn nil)))))) - -(def default-special-fns - (let [load-file-fn (fn [repl-env file] (load-file repl-env file))] - {'in-ns (fn [_ quoted-ns] - (let [ns-name (second quoted-ns)] - (when-not (ana/get-namespace ns-name) - (swap! env/*compiler* update-in [::ana/namespaces ns-name] {:name ns-name})) - (set! ana/*cljs-ns* ns-name))) - 'load-file load-file-fn - 'clojure.core/load-file load-file-fn - 'load-namespace (fn [repl-env ns] (load-namespace repl-env ns))})) - -(defn analyze-source - "Given a source directory, analyzes all .cljs files. Used to populate - (:cljs.analyzer/namespaces compiler-env) so as to support code reflection." - [src-dir] - (if-let [src-dir (and (not (empty? src-dir)) - (File. src-dir))] - (doseq [file (comp/cljs-files-in src-dir)] - (ana/analyze-file (str "file://" (.getAbsolutePath file)))))) - -(defn repl - "Note - repl will reload core.cljs every time, even if supplied old repl-env" - [repl-env & {:keys [analyze-path verbose warn-on-undeclared special-fns static-fns] :as opts - :or {warn-on-undeclared true}}] - (print "To quit, type: ") - (prn :cljs/quit) - (env/with-compiler-env - (or (::env/compiler repl-env) (env/default-compiler-env opts)) - (binding [ana/*cljs-ns* 'cljs.user - *cljs-verbose* verbose - ana/*cljs-warnings* (assoc ana/*cljs-warnings* - :unprovided warn-on-undeclared - :undeclared-var warn-on-undeclared - :undeclared-ns warn-on-undeclared - :undeclared-ns-form warn-on-undeclared) - ana/*cljs-static-fns* static-fns] - (when analyze-path - (analyze-source analyze-path)) - (let [env {:context :expr :locals {}} - special-fns (merge default-special-fns special-fns) - is-special-fn? (set (keys special-fns)) - read-error (Object.)] - (-setup repl-env) - (loop [] - (print (str "ClojureScript:" ana/*cljs-ns* "> ")) - (flush) - (let [rdr (readers/source-logging-push-back-reader - (java.io.PushbackReader. (io/reader *in*)) - 1 - "NO_SOURCE_FILE") - form (try - (binding [*ns* (create-ns ana/*cljs-ns*) - reader/*data-readers* tags/*cljs-data-readers* - reader/*alias-map* - (apply merge - ((juxt :requires :require-macros) - (ana/get-namespace ana/*cljs-ns*)))] - (reader/read rdr nil read-error)) - (catch Exception e - (println (.getMessage e)) - read-error))] - (cond - (identical? form read-error) (recur) - (= form :cljs/quit) :quit - - (and (seq? form) (is-special-fn? (first form))) - (do (apply (get special-fns (first form)) repl-env (rest form)) - (newline) - (recur)) - - :else - (do (eval-and-print repl-env env form) - (recur))))) - (-tear-down repl-env))))) diff --git a/src/clj/cljs/repl/browser.clj b/src/clj/cljs/repl/browser.clj deleted file mode 100644 index a1a1790df8..0000000000 --- a/src/clj/cljs/repl/browser.clj +++ /dev/null @@ -1,279 +0,0 @@ -;; Copyright (c) Rich Hickey. All rights reserved. -;; The use and distribution terms for this software are covered by the -;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -;; which can be found in the file epl-v10.html at the root of this distribution. -;; By using this software in any fashion, you are agreeing to be bound by -;; the terms of this license. -;; You must not remove this notice, or any other, from this software. - -(ns cljs.repl.browser - (:refer-clojure :exclude [loaded-libs]) - (:require [clojure.java.io :as io] - [clojure.string :as string] - [cljs.compiler :as comp] - [cljs.closure :as cljsc] - [cljs.repl :as repl] - [cljs.repl.server :as server]) - (:import cljs.repl.IJavaScriptEnv - [java.util.regex Pattern])) - -(defonce browser-state (atom {:return-value-fn nil - :client-js nil})) - -(def loaded-libs (atom #{})) -(def preloaded-libs (atom #{})) - -(defn- set-return-value-fn - "Save the return value function which will be called when the next - return value is received." - [f] - (swap! browser-state (fn [old] (assoc old :return-value-fn f)))) - -(defn send-for-eval - "Given a form and a return value function, send the form to the - browser for evaluation. The return value function will be called - when the return value is received." - ([form return-value-fn] - (send-for-eval @(server/connection) form return-value-fn)) - ([conn form return-value-fn] - (do (set-return-value-fn return-value-fn) - (server/send-and-close conn 200 form "text/javascript")))) - -(defn- return-value - "Called by the server when a return value is received." - [val] - (when-let [f (:return-value-fn @browser-state)] - (f val))) - -(defn repl-client-js [] - (slurp @(:client-js @browser-state))) - -(defn send-repl-client-page - [request conn opts] - (server/send-and-close conn 200 - (str " - " - "" - "") - "text/html")) - -(defn send-static [{path :path :as request} conn opts] - (if (and (:static-dir opts) - (not= "/favicon.ico" path)) - (let [path (if (= "/" path) "/index.html" path) - st-dir (:static-dir opts) - local-path (cond-> - (seq (for [x (if (string? st-dir) [st-dir] st-dir) - :when (.exists (io/file (str x path)))] - (str x path))) - (complement nil?) first) - local-path (if (nil? local-path) - (cond - (re-find #".jar" path) - (io/resource (second (string/split path #".jar!/"))) - (re-find (Pattern/compile (System/getProperty "user.dir")) path) - (io/file (string/replace path (str (System/getProperty "user.dir") "/") "")) - :else nil) - local-path)] - (if local-path - (server/send-and-close conn 200 (slurp local-path) - (condp #(.endsWith %2 %1) path - ".html" "text/html" - ".css" "text/css" - ".html" "text/html" - ".jpg" "image/jpeg" - ".js" "text/javascript" - ".cljs" "text/x-clojure" - ".map" "application/json" - ".png" "image/png" - "text/plain")) - (server/send-404 conn path))) - (server/send-404 conn path))) - -(server/dispatch-on :get - (fn [{:keys [path]} _ _] (.startsWith path "/repl")) - send-repl-client-page) - -(server/dispatch-on :get - (fn [{:keys [path]} _ _] (or (= path "/") - (.endsWith path ".js") - (.endsWith path ".cljs") - (.endsWith path ".map") - (.endsWith path ".html"))) - send-static) - -(defmulti handle-post (fn [m _ _ ] (:type m))) - -(server/dispatch-on :post (constantly true) handle-post) - -(def ordering (agent {:expecting nil :fns {}})) - -(defmethod handle-post :ready [_ conn _] - (do (reset! loaded-libs @preloaded-libs) - (send ordering (fn [_] {:expecting nil :fns {}})) - (send-for-eval conn - (cljsc/-compile - '[(ns cljs.user) - (set! *print-fn* clojure.browser.repl/repl-print)] {}) - identity))) - -(defn add-in-order [{:keys [expecting fns]} order f] - {:expecting (or expecting order) :fns (assoc fns order f)}) - -(defn run-in-order [{:keys [expecting fns]}] - (loop [order expecting - fns fns] - (if-let [f (get fns order)] - (do (f) - (recur (inc order) (dissoc fns order))) - {:expecting order :fns fns}))) - -(defn constrain-order - "Elements to be printed in the REPL will arrive out of order. Ensure - that they are printed in the correct order." - [order f] - (send-off ordering add-in-order order f) - (send-off ordering run-in-order)) - -(defmethod handle-post :print [{:keys [content order]} conn _ ] - (do (constrain-order order (fn [] (do (print (read-string content)) - (.flush *out*)))) - (server/send-and-close conn 200 "ignore__"))) - -(defmethod handle-post :result [{:keys [content order]} conn _ ] - (constrain-order order (fn [] (do (return-value content) - (server/set-connection conn))))) - -(defn browser-eval - "Given a string of JavaScript, evaluate it in the browser and return a map representing the - result of the evaluation. The map will contain the keys :type and :value. :type can be - :success, :exception, or :error. :success means that the JavaScript was evaluated without - exception and :value will contain the return value of the evaluation. :exception means that - there was an exception in the browser while evaluating the JavaScript and :value will - contain the error message. :error means that some other error has occured." - [form] - (let [return-value (promise)] - (send-for-eval form - (fn [val] (deliver return-value val))) - (let [ret @return-value] - (try (read-string ret) - (catch Exception e - {:status :error - :value (str "Could not read return value: " ret)}))))) - -(defn load-javascript - "Accepts a REPL environment, a list of namespaces, and a URL for a - JavaScript file which contains the implementation for the list of - namespaces. Will load the JavaScript file into the REPL environment - if any of the namespaces have not already been loaded from the - ClojureScript REPL." - [repl-env ns-list url] - (let [missing (remove #(contains? @loaded-libs %) ns-list)] - (when (seq missing) - (browser-eval (slurp url)) - (swap! loaded-libs (partial apply conj) missing)))) - -(defrecord BrowserEnv [] - repl/IJavaScriptEnv - (-setup [this] - (do (require 'cljs.repl.reflect) - (repl/analyze-source (:src this)) - (comp/with-core-cljs (server/start this)))) - (-evaluate [_ _ _ js] (browser-eval js)) - (-load [this ns url] (load-javascript this ns url)) - (-tear-down [_] - (do (server/stop) - (reset! server/state {}) - (reset! browser-state {})))) - -(defn compile-client-js [opts] - (cljsc/build '[(ns clojure.browser.repl.client - (:require [goog.events :as event] - [clojure.browser.repl :as repl])) - (defn start [url] - (event/listen js/window - "load" - (fn [] - (repl/start-evaluator url))))] - {:optimizations (:optimizations opts) - :output-dir (:working-dir opts)})) - -(defn create-client-js-file [opts file-path] - (let [file (io/file file-path)] - (when (not (.exists file)) - (spit file (compile-client-js opts))) - file)) - -(defn- provides-and-requires - "Return a flat list of all provided and required namespaces from a - sequence of IJavaScripts." - [deps] - (flatten (mapcat (juxt :provides :requires) deps))) - -(defn- always-preload - "Return a list of all namespaces which are always loaded into the browser - when using a browser-connected REPL." - [] - (let [cljs (provides-and-requires (cljsc/cljs-dependencies {} ["clojure.browser.repl"])) - goog (provides-and-requires (cljsc/js-dependencies {} cljs))] - (disj (set (concat cljs goog)) nil))) - -(defn repl-env - "Create a browser-connected REPL environment. - - Options: - - port: The port on which the REPL server will run. Defaults to 9000. - working-dir: The directory where the compiled REPL client JavaScript will - be stored. Defaults to \".repl\". - serve-static: Should the REPL server attempt to serve static content? - Defaults to true. - static-dir: List of directories to search for static content. Defaults to - [\".\" \"out/\"]. - preloaded-libs: List of namespaces that should not be sent from the REPL server - to the browser. This may be required if the browser is already - loading code and reloading it would cause a problem. - optimizations: The level of optimization to use when compiling the client - end of the REPL. Defaults to :simple. - src: The source directory containing user-defined cljs files. Used to - support reflection. Defaults to \"src/\". - " - [& {:as opts}] - (let [compiler-env (cljs.env/default-compiler-env opts) - opts (merge (BrowserEnv.) - {:port 9000 - :optimizations :simple - :working-dir ".repl" - :serve-static true - :static-dir ["." "out/"] - :preloaded-libs [] - :src "src/" - :cljs.env/compiler compiler-env - :source-map true} - opts)] - (cljs.env/with-compiler-env compiler-env - (reset! preloaded-libs (set (concat (always-preload) (map str (:preloaded-libs opts))))) - (reset! loaded-libs @preloaded-libs) - (swap! browser-state - (fn [old] (assoc old :client-js - (future (create-client-js-file - opts - (io/file (:working-dir opts) "client.js")))))) - opts))) - -(comment - - (require '[cljs.repl :as repl]) - (require '[cljs.repl.browser :as browser]) - (def env (browser/repl-env)) - (repl/repl env) - ;; simulate the browser with curl - ;; curl -v -d "ready" http://127.0.0.1:9000 - ClojureScript:> (+ 1 1) - ;; curl -v -d "2" http://127.0.0.1:9000 - - ) diff --git a/src/clj/cljs/repl/rhino.clj b/src/clj/cljs/repl/rhino.clj deleted file mode 100644 index 082b1fc870..0000000000 --- a/src/clj/cljs/repl/rhino.clj +++ /dev/null @@ -1,175 +0,0 @@ -;; Copyright (c) Rich Hickey. All rights reserved. -;; The use and distribution terms for this software are covered by the -;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -;; which can be found in the file epl-v10.html at the root of this distribution. -;; By using this software in any fashion, you are agreeing to be bound by -;; the terms of this license. -;; You must not remove this notice, or any other, from this software. - -(ns cljs.repl.rhino - (:require [clojure.string :as string] - [clojure.java.io :as io] - [cljs.compiler :as comp] - [cljs.analyzer :as ana] - [cljs.repl :as repl]) - (:import cljs.repl.IJavaScriptEnv - [org.mozilla.javascript Context ScriptableObject])) - -(def current-repl-env (atom nil)) - -;;todo - move to core.cljs, using js -(def ^String bootjs (str "goog.require = function(rule){" - "Packages.clojure.lang.RT[\"var\"](\"cljs.repl.rhino\",\"goog-require\")" - ".invoke(___repl_env, rule);}")) - -(defprotocol IEval - (-eval [this env filename line])) - -(extend-protocol IEval - - java.lang.String - (-eval [this {:keys [cx scope]} filename line] - (.evaluateString cx scope this filename line nil)) - - java.io.Reader - (-eval [this {:keys [cx scope]} filename line] - (.evaluateReader cx scope this filename line nil)) - ) - -(defmulti stacktrace class) - -(defmethod stacktrace :default [e] - (apply str (interpose "\n" (map #(str " " (.toString %)) (.getStackTrace e))))) - -(defmethod stacktrace org.mozilla.javascript.RhinoException [e] - (.getScriptStackTrace e)) - -(defmulti eval-result class) - -(defmethod eval-result :default [r] - (.toString r)) - -(defmethod eval-result nil [_] "") - -(defmethod eval-result org.mozilla.javascript.Undefined [_] "") - -(defn rhino-eval - [repl-env filename line js] - (try - (let [linenum (or line Integer/MIN_VALUE)] - {:status :success - :value (eval-result (-eval js repl-env filename linenum))}) - (catch Throwable ex - {:status :exception - :value (.toString ex) - :stacktrace (stacktrace ex)}))) - -(defn goog-require [repl-env rule] - (when-not (contains? @(:loaded-libs repl-env) rule) - (let [repl-env @current-repl-env - path (string/replace (comp/munge rule) \. java.io.File/separatorChar) - cljs-path (str path ".cljs") - js-path (str "goog/" - (-eval (str "goog.dependencies_.nameToPath['" rule "']") - repl-env - "" - 1))] - (if-let [res (io/resource cljs-path)] - (binding [ana/*cljs-ns* 'cljs.user] - (repl/load-stream repl-env cljs-path res)) - (if-let [res (io/resource js-path)] - (with-open [reader (io/reader res)] - (-eval reader repl-env js-path 1)) - (throw (Exception. (str "Cannot find " cljs-path " or " js-path " in classpath"))))) - (swap! (:loaded-libs repl-env) conj rule)))) - -(defn load-javascript [repl-env ns url] - (let [missing (remove #(contains? @(:loaded-libs repl-env) %) ns)] - (when (seq missing) - (do (try - (with-open [reader (io/reader url)] - (-eval reader repl-env (.toString url) 1)) - ;; TODO: don't show errors for goog/base.js line number 105 - (catch Throwable ex (println (.getMessage ex)))) - (swap! (:loaded-libs repl-env) (partial apply conj) missing))))) - -(defn rhino-setup [repl-env] - (let [env (ana/empty-env) - scope (:scope repl-env)] - (repl/load-file repl-env "cljs/core.cljs") - (swap! (:loaded-libs repl-env) conj "cljs.core") - (repl/evaluate-form repl-env - env - "" - '(ns cljs.user)) - (ScriptableObject/putProperty scope - "out" - (Context/javaToJS *out* scope)) - (repl/evaluate-form repl-env - env - "" - '(set! *print-fn* (fn [x] (.write js/out x)))))) - -(defrecord RhinoEnv [loaded-libs] - repl/IJavaScriptEnv - (-setup [this] - (rhino-setup this)) - (-evaluate [this filename line js] - (rhino-eval this filename line js)) - (-load [this ns url] - (load-javascript this ns url)) - (-tear-down [_] (Context/exit))) - -(defn repl-env - "Returns a fresh JS environment, suitable for passing to repl. - Hang on to return for use across repl calls." - [] - (let [cx (Context/enter) - scope (.initStandardObjects cx) - base (io/resource "goog/base.js") - deps (io/resource "goog/deps.js") - new-repl-env (merge (RhinoEnv. (atom #{})) {:cx cx :scope scope})] - (assert base "Can't find goog/base.js in classpath") - (assert deps "Can't find goog/deps.js in classpath") - (swap! current-repl-env (fn [old] new-repl-env)) - (ScriptableObject/putProperty scope - "___repl_env" - (Context/javaToJS new-repl-env scope)) - (with-open [r (io/reader base)] - (-eval r new-repl-env "goog/base.js" 1)) - (-eval bootjs new-repl-env "bootjs" 1) - ;; Load deps.js line-by-line to avoid 64K method limit - (with-open [reader (io/reader deps)] - (doseq [^String line (line-seq reader)] - (-eval line new-repl-env "goog/deps.js" 1))) - new-repl-env)) - -(comment - - (require '[cljs.repl :as repl]) - (require '[cljs.repl.rhino :as rhino]) - (def env (rhino/repl-env)) - (repl/repl env) - (+ 1 1) - "hello" - {:a "hello"} - (:a {:a "hello"}) - (:a {:a :b}) - (reduce + [1 2 3 4 5]) - (time (reduce + [1 2 3 4 5])) - (even? :a) - (throw (js/Error. "There was an error")) - (load-file "clojure/string.cljs") - (clojure.string/triml " hello") - (clojure.string/reverse " hello") - - (load-namespace 'clojure.set) - - (ns test.crypt - (:require [goog.crypt :as c])) - (c/stringToByteArray "Hello") - - (load-namespace 'goog.date.Date) - (goog.date.Date.) - - ) diff --git a/src/clj/cljs/repl/server.clj b/src/clj/cljs/repl/server.clj deleted file mode 100644 index 3e40f70eee..0000000000 --- a/src/clj/cljs/repl/server.clj +++ /dev/null @@ -1,173 +0,0 @@ -(ns cljs.repl.server - (:refer-clojure :exclude [loaded-libs]) - (:require [clojure.string :as str] - [clojure.java.io :as io] - [cljs.compiler :as comp] - [cljs.closure :as cljsc] - [cljs.repl :as repl]) - (:import java.io.BufferedReader - java.io.BufferedWriter - java.io.InputStreamReader - java.io.OutputStreamWriter - java.net.Socket - java.net.ServerSocket - cljs.repl.IJavaScriptEnv)) - -(defonce state (atom {:socket nil - :connection nil - :promised-conn nil})) - -(defn connection - "Promise to return a connection when one is available. If a - connection is not available, store the promise in server/state." - [] - (let [p (promise) - conn (:connection @state)] - (if (and conn (not (.isClosed conn))) - (do (deliver p conn) - p) - (do (swap! state (fn [old] (assoc old :promised-conn p))) - p)))) - -(defn set-connection - "Given a new available connection, either use it to deliver the - connection which was promised or store the connection for later - use." - [conn] - (if-let [promised-conn (:promised-conn @state)] - (do (swap! state (fn [old] (-> old - (assoc :connection nil) - (assoc :promised-conn nil)))) - (deliver promised-conn conn)) - (swap! state (fn [old] (assoc old :connection conn))))) - -(defonce handlers (atom {})) - -(defn dispatch-on - "Registers a handler to be dispatched based on a request method and a - predicate. - - pred should be a function that accepts an options map, a connection, - and a request map and returns a boolean value based on whether or not - that request should be dispatched to the related handler." - ([method pred handler] - (dispatch-on method {:pred pred :handler handler})) - ([method {:as m}] - (swap! handlers (fn [old] - (update-in old [method] #(conj (vec %) m)))))) - -;;; assumes first line already consumed -(defn parse-headers - "Parse the headers of an HTTP POST request." - [header-lines] - (apply hash-map - (mapcat - (fn [line] - (let [[k v] (str/split line #":" 2)] - [(keyword (str/lower-case k)) (str/triml v)])) - header-lines))) - -(defn read-headers [rdr] - (loop [next-line (.readLine rdr) - header-lines []] - (if (= "" next-line) - header-lines ;we're done reading headers - (recur (.readLine rdr) (conj header-lines next-line))))) - -(defn read-post [line rdr] - (let [[_ path _] (str/split line #" ") - headers (parse-headers (read-headers rdr)) - content-length (Integer/parseInt (:content-length headers)) - content (char-array content-length)] - (io! (.read rdr content 0 content-length) - {:method :post - :path path - :headers headers - :content (String. content)}))) - -(defn read-get [line rdr] - (let [[_ path _] (str/split line #" ") - headers (parse-headers (read-headers rdr))] - {:method :get - :path path - :headers headers})) - -(defn read-request [rdr] - (let [line (.readLine rdr)] - (cond (.startsWith line "POST") (read-post line rdr) - (.startsWith line "GET") (read-get line rdr) - :else {:method :unknown :content line}))) - -(defn- status-line [status] - (case status - 200 "HTTP/1.1 200 OK" - 404 "HTTP/1.1 404 Not Found" - "HTTP/1.1 500 Error")) - -(defn send-and-close - "Use the passed connection to send a form to the browser. Send a - proper HTTP response." - ([conn status form] - (send-and-close conn status form "text/html")) - ([conn status form content-type] - (let [utf-8-form (.getBytes form "UTF-8") - content-length (count utf-8-form) - headers (map #(.getBytes (str % "\r\n")) - [(status-line status) - "Server: ClojureScript REPL" - (str "Content-Type: " - content-type - "; charset=utf-8") - (str "Content-Length: " content-length) - ""])] - (with-open [os (.getOutputStream conn)] - (do (doseq [header headers] - (.write os header 0 (count header))) - (.write os utf-8-form 0 content-length) - (.flush os) - (.close conn)))))) - -(defn send-404 [conn path] - (send-and-close conn 404 - (str "" - "

    Page not found

    " - "No page " path " found on this server." - "") - "text/html")) - -(defn- dispatch-request [request conn opts] - (if-let [handlers ((:method request) @handlers)] - (if-let [handler (some (fn [{:keys [pred handler]}] - (when (pred request conn opts) - handler)) - handlers)] - (if (= :post (:method request)) - (handler (read-string (:content request)) conn opts ) - (handler request conn opts)) - (send-404 conn (:path request))) - (.close conn))) - -(defn- handle-connection - [opts conn] - (let [rdr (BufferedReader. (InputStreamReader. (.getInputStream conn)))] - (if-let [request (read-request rdr)] - (dispatch-request request conn opts) - (.close conn)))) - -(defn- server-loop - [opts server-socket] - (let [conn (.accept server-socket)] - (do (.setKeepAlive conn true) - (future (handle-connection opts conn)) - (recur opts server-socket)))) - -(defn start - "Start the server on the specified port." - [opts] - (let [ss (ServerSocket. (:port opts))] - (future (server-loop opts ss)) - (swap! state (fn [old] (assoc old :socket ss :port (:port opts)))))) - -(defn stop - [] - (.close (:socket @state))) diff --git a/src/clj/cljs/source_map/base64.clj b/src/clj/cljs/source_map/base64.clj deleted file mode 100644 index 58505748d0..0000000000 --- a/src/clj/cljs/source_map/base64.clj +++ /dev/null @@ -1,17 +0,0 @@ -(ns cljs.source-map.base64) - -(def chars64 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") -(def char->int (zipmap chars64 (range 0 64))) -(def int->char (zipmap (range 0 64) chars64)) - -(defn encode [n] - (let [e (find int->char n)] - (if e - (second e) - (throw (Error. (str "Must be between 0 and 63: " n)))))) - -(defn ^Character decode [c] - (let [e (find char->int c)] - (if e - (second e) - (throw (Error. (str "Not a valid base 64 digit: " c)))))) \ No newline at end of file diff --git a/src/clj/cljs/tagged_literals.clj b/src/clj/cljs/tagged_literals.clj deleted file mode 100644 index 52a6e09457..0000000000 --- a/src/clj/cljs/tagged_literals.clj +++ /dev/null @@ -1,48 +0,0 @@ -(ns cljs.tagged-literals - (:require [clojure.instant :as inst])) - -(defn read-queue - [form] - (when-not (vector? form) - (throw (RuntimeException. "Queue literal expects a vector for its elements."))) - (list 'cljs.core/into 'cljs.core.PersistentQueue.EMPTY form)) - -(defn read-uuid - [form] - (when-not (string? form) - (throw (RuntimeException. "UUID literal expects a string as its representation."))) - (try - (java.util.UUID/fromString form) - (catch Throwable e - (throw (RuntimeException. (.getMessage e)))))) - -(defn read-inst - [form] - (when-not (string? form) - (throw (RuntimeException. "Instance literal expects a string for its timestamp."))) - (try - (inst/read-instant-date form) - (catch Throwable e - (throw (RuntimeException. (.getMessage e)))))) - -(defn valid-js-literal-key? [k] - (or (string? k) - (and (keyword? k) - (nil? (namespace k))))) - -(deftype JSValue [val]) - -(defn read-js - [form] - (when-not (or (vector? form) (map? form)) - (throw (RuntimeException. "JavaScript literal must use map or vector notation"))) - (when-not (or (not (map? form)) - (every? valid-js-literal-key? (keys form))) - (throw (RuntimeException. "JavaScript literal keys must be strings or unqualified keywords"))) - (JSValue. form)) - -(def ^:dynamic *cljs-data-readers* - {'queue read-queue - 'uuid read-uuid - 'inst read-inst - 'js read-js}) diff --git a/src/cljs/cljs/core.cljs b/src/cljs/cljs/core.cljs deleted file mode 100644 index 6b559e11fc..0000000000 --- a/src/cljs/cljs/core.cljs +++ /dev/null @@ -1,7778 +0,0 @@ -; Copyright (c) Rich Hickey. All rights reserved. -; The use and distribution terms for this software are covered by the -; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -; which can be found in the file epl-v10.html at the root of this distribution. -; By using this software in any fashion, you are agreeing to be bound by -; the terms of this license. -; You must not remove this notice, or any other, from this software. - -(ns cljs.core - (:require [goog.string :as gstring] - [goog.string.StringBuffer :as gstringbuf] - [goog.object :as gobject] - [goog.array :as garray])) - -;; next line is auto-generated by the build-script - Do not edit! -(def *clojurescript-version*) - -(def *unchecked-if* false) - -(def - ^{:doc "Each runtime environment provides a different way to print output. - Whatever function *print-fn* is bound to will be passed any - Strings which should be printed." :dynamic true} - *print-fn* - (fn [_] - (throw (js/Error. "No *print-fn* fn set for evaluation environment")))) - -(defn set-print-fn! - "Set *print-fn* to f." - [f] (set! *print-fn* f)) - -(def ^:dynamic *flush-on-newline* true) -(def ^:dynamic *print-newline* true) -(def ^:dynamic *print-readably* true) -(def ^:dynamic *print-meta* false) -(def ^:dynamic *print-dup* false) -(def ^:dynamic *print-length* nil) -(def ^:dynamic *print-level* nil) - -(defn- pr-opts [] - {:flush-on-newline *flush-on-newline* - :readably *print-readably* - :meta *print-meta* - :dup *print-dup* - :print-length *print-length*}) - -(declare into-array) - -(defn enable-console-print! - "Set *print-fn* to console.log" - [] - (set! *print-newline* false) - (set! *print-fn* - (fn [& args] - (.apply js/console.log js/console (into-array args))))) - -(def - ^{:doc "bound in a repl thread to the most recent value printed"} - *1) - -(def - ^{:doc "bound in a repl thread to the second most recent value printed"} - *2) - -(def - ^{:doc "bound in a repl thread to the third most recent value printed"} - *3) - -(defn truth_ - "Internal - do not use!" - [x] - (cljs.core/truth_ x)) - -(def not-native nil) - -(declare instance? Keyword) - -(defn ^boolean identical? - "Tests if 2 arguments are the same object" - [x y] - (cljs.core/identical? x y)) - -(defn ^boolean nil? - "Returns true if x is nil, false otherwise." - [x] - (coercive-= x nil)) - -(defn ^boolean array? [x] - (cljs.core/array? x)) - -(defn ^boolean number? [n] - (cljs.core/number? n)) - -(defn ^boolean not - "Returns true if x is logical false, false otherwise." - [x] (if x false true)) - -(defn ^boolean object? [x] - (if-not (nil? x) - (identical? (.-constructor x) js/Object) - false)) - -(defn ^boolean string? [x] - (goog/isString x)) - -(set! *unchecked-if* true) -(defn ^boolean native-satisfies? - "Internal - do not use!" - [p x] - (let [x (if (nil? x) nil x)] - (cond - (aget p (goog.typeOf x)) true - (aget p "_") true - :else false))) -(set! *unchecked-if* false) - -(defn is_proto_ - [x] - (identical? (.-prototype (.-constructor x)) x)) - -(def - ^{:doc "When compiled for a command-line target, whatever - function *main-fn* is set to will be called with the command-line - argv as arguments"} - *main-cli-fn* nil) - -(defn type [x] - (when-not (nil? x) - (.-constructor x))) - -(defn missing-protocol [proto obj] - (let [ty (type obj) - ty (if (and ty (.-cljs$lang$type ty)) - (.-cljs$lang$ctorStr ty) - (goog/typeOf obj))] - (js/Error. - (.join (array "No protocol method " proto - " defined for type " ty ": " obj) "")))) - -(defn type->str [ty] - (if-let [s (.-cljs$lang$ctorStr ty)] - s - (str ty))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; arrays ;;;;;;;;;;;;;;;; - -(defn ^array make-array - ([size] - (js/Array. size)) - ([type size] - (make-array size))) - -(defn aclone - "Returns a javascript array, cloned from the passed in array" - [arr] - (let [len (alength arr) - new-arr (make-array len)] - (dotimes [i len] - (aset new-arr i (aget arr i))) - new-arr)) - -(defn ^array array - "Creates a new javascript array. -@param {...*} var_args" ;;array is a special case, don't emulate this doc string - [var-args] ;; [& items] - (.call (.-slice (.-prototype js/Array)) (cljs.core/js-arguments))) - -(declare apply) - -(defn aget - "Returns the value at the index." - ([array i] - (cljs.core/aget array i)) - ([array i & idxs] - (apply aget (aget array i) idxs))) - -(defn aset - "Sets the value at the index." - ([array i val] - (cljs.core/aset array i val)) - ([array idx idx2 & idxv] - (apply aset (aget array idx) idx2 idxv))) - -(defn ^number alength - "Returns the length of the array. Works on arrays of all types." - [array] - (cljs.core/alength array)) - -(declare reduce) - -(defn ^array into-array - ([aseq] - (into-array nil aseq)) - ([type aseq] - (reduce (fn [a x] (.push a x) a) (array) aseq))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;; core protocols ;;;;;;;;;;;;; - -(defprotocol Fn - "Marker protocol") - -(defprotocol IFn - (-invoke - [this] - [this a] - [this a b] - [this a b c] - [this a b c d] - [this a b c d e] - [this a b c d e f] - [this a b c d e f g] - [this a b c d e f g h] - [this a b c d e f g h i] - [this a b c d e f g h i j] - [this a b c d e f g h i j k] - [this a b c d e f g h i j k l] - [this a b c d e f g h i j k l m] - [this a b c d e f g h i j k l m n] - [this a b c d e f g h i j k l m n o] - [this a b c d e f g h i j k l m n o p] - [this a b c d e f g h i j k l m n o p q] - [this a b c d e f g h i j k l m n o p q s] - [this a b c d e f g h i j k l m n o p q s t] - [this a b c d e f g h i j k l m n o p q s t rest])) - -(defprotocol ICloneable - (^clj -clone [value])) - -(defprotocol ICounted - (^number -count [coll] "constant time count")) - -(defprotocol IEmptyableCollection - (-empty [coll])) - -(defprotocol ICollection - (^clj -conj [coll o])) - -#_(defprotocol IOrdinal - (-index [coll])) - -(defprotocol IIndexed - (-nth [coll n] [coll n not-found])) - -(defprotocol ASeq) - -(defprotocol ISeq - (-first [coll]) - (^clj -rest [coll])) - -(defprotocol INext - (^clj-or-nil -next [coll])) - -(defprotocol ILookup - (-lookup [o k] [o k not-found])) - -(defprotocol IAssociative - (^boolean -contains-key? [coll k]) - #_(-entry-at [coll k]) - (^clj -assoc [coll k v])) - -(defprotocol IMap - #_(-assoc-ex [coll k v]) - (^clj -dissoc [coll k])) - -(defprotocol IMapEntry - (-key [coll]) - (-val [coll])) - -(defprotocol ISet - (^clj -disjoin [coll v])) - -(defprotocol IStack - (-peek [coll]) - (^clj -pop [coll])) - -(defprotocol IVector - (^clj -assoc-n [coll n val])) - -(defprotocol IDeref - (-deref [o])) - -(defprotocol IDerefWithTimeout - (-deref-with-timeout [o msec timeout-val])) - -(defprotocol IMeta - (^clj-or-nil -meta [o])) - -(defprotocol IWithMeta - (^clj -with-meta [o meta])) - -(defprotocol IReduce - (-reduce [coll f] [coll f start])) - -(defprotocol IKVReduce - (-kv-reduce [coll f init])) - -(defprotocol IEquiv - (^boolean -equiv [o other])) - -(defprotocol IHash - (-hash [o])) - -(defprotocol ISeqable - (^clj-or-nil -seq [o])) - -(defprotocol ISequential - "Marker interface indicating a persistent collection of sequential items") - -(defprotocol IList - "Marker interface indicating a persistent list") - -(defprotocol IRecord - "Marker interface indicating a record object") - -(defprotocol IReversible - (^clj -rseq [coll])) - -(defprotocol ISorted - (^clj -sorted-seq [coll ascending?]) - (^clj -sorted-seq-from [coll k ascending?]) - (-entry-key [coll entry]) - (-comparator [coll])) - -(defprotocol IWriter - (-write [writer s]) - (-flush [writer])) - -(defprotocol IPrintWithWriter - "The old IPrintable protocol's implementation consisted of building a giant - list of strings to concatenate. This involved lots of concat calls, - intermediate vectors, and lazy-seqs, and was very slow in some older JS - engines. IPrintWithWriter implements printing via the IWriter protocol, so it - be implemented efficiently in terms of e.g. a StringBuffer append." - (-pr-writer [o writer opts])) - -(defprotocol IPending - (^boolean -realized? [d])) - -(defprotocol IWatchable - (-notify-watches [this oldval newval]) - (-add-watch [this key f]) - (-remove-watch [this key])) - -(defprotocol IEditableCollection - (^clj -as-transient [coll])) - -(defprotocol ITransientCollection - (^clj -conj! [tcoll val]) - (^clj -persistent! [tcoll])) - -(defprotocol ITransientAssociative - (^clj -assoc! [tcoll key val])) - -(defprotocol ITransientMap - (^clj -dissoc! [tcoll key])) - -(defprotocol ITransientVector - (^clj -assoc-n! [tcoll n val]) - (^clj -pop! [tcoll])) - -(defprotocol ITransientSet - (^clj -disjoin! [tcoll v])) - -(defprotocol IComparable - (^number -compare [x y])) - -(defprotocol IChunk - (-drop-first [coll])) - -(defprotocol IChunkedSeq - (-chunked-first [coll]) - (-chunked-rest [coll])) - -(defprotocol IChunkedNext - (-chunked-next [coll])) - -(defprotocol INamed - (^string -name [x]) - (^string -namespace [x])) - -;; Printing support - -(deftype StringBufferWriter [sb] - IWriter - (-write [_ s] (.append sb s)) - (-flush [_] nil)) - -(defn pr-str* - "Support so that collections can implement toString without - loading all the printing machinery." - [^not-native obj] - (let [sb (gstring/StringBuffer.) - writer (StringBufferWriter. sb)] - (-pr-writer obj writer (pr-opts)) - (-flush writer) - (str sb))) - -;;;;;;;;;;;;;;;;;;; symbols ;;;;;;;;;;;;;;; - -(declare list hash-combine hash Symbol = compare) - -(defn ^boolean instance? [t o] - (cljs.core/instance? t o)) - -(defn ^boolean symbol? [x] - (instance? Symbol x)) - -(defn- hash-symbol [sym] - (hash-combine (hash (.-ns sym)) (hash (.-name sym)))) - -(defn- compare-symbols [a b] - (cond - (= a b) 0 - (and (not (.-ns a)) (.-ns b)) -1 - (.-ns a) (if-not (.-ns b) - 1 - (let [nsc (compare (.-ns a) (.-ns b))] - (if (zero? nsc) - (compare (.-name a) (.-name b)) - nsc))) - :default (compare (.-name a) (.-name b)))) - -(deftype Symbol [ns name str ^:mutable _hash _meta] - Object - (toString [_] str) - IEquiv - (-equiv [_ other] - (if (instance? Symbol other) - (identical? str (.-str other)) - false)) - IFn - (-invoke [sym coll] - (-lookup coll sym nil)) - (-invoke [sym coll not-found] - (-lookup coll sym not-found)) - IMeta - (-meta [_] _meta) - IWithMeta - (-with-meta [_ new-meta] (Symbol. ns name str _hash new-meta)) - IHash - (-hash [sym] - (caching-hash sym hash-symbol _hash)) - INamed - (-name [_] name) - (-namespace [_] ns) - IPrintWithWriter - (-pr-writer [o writer _] (-write writer str))) - -(defn symbol - ([name] - (if (symbol? name) - name - (symbol nil name))) - ([ns name] - (let [sym-str (if-not (nil? ns) - (str ns "/" name) - name)] - (Symbol. ns name sym-str nil nil)))) - -;;;;;;;;;;;;;;;;;;; fundamentals ;;;;;;;;;;;;;;; - -(declare array-seq prim-seq IndexedSeq) - -(defn clone [value] - (-clone value)) - -(defn cloneable? [value] - (satisfies? ICloneable value)) - -(defn ^seq seq - "Returns a seq on the collection. If the collection is - empty, returns nil. (seq nil) returns nil. seq also works on - Strings." - [coll] - (when-not (nil? coll) - (cond - (implements? ISeqable coll) - (-seq ^not-native coll) - - (array? coll) - (when-not (zero? (alength coll)) - (IndexedSeq. coll 0)) - - (string? coll) - (when-not (zero? (alength coll)) - (IndexedSeq. coll 0)) - - (native-satisfies? ISeqable coll) - (-seq coll) - - :else (throw (js/Error. (str coll "is not ISeqable")))))) - -(defn first - "Returns the first item in the collection. Calls seq on its - argument. If coll is nil, returns nil." - [coll] - (when-not (nil? coll) - (if (implements? ISeq coll) - (-first ^not-native coll) - (let [s (seq coll)] - (when-not (nil? s) - (-first s)))))) - -(defn ^seq rest - "Returns a possibly empty seq of the items after the first. Calls seq on its - argument." - [coll] - (if-not (nil? coll) - (if (implements? ISeq coll) - (-rest ^not-native coll) - (let [s (seq coll)] - (if s - (-rest ^not-native s) - ()))) - ())) - -(defn ^seq next - "Returns a seq of the items after the first. Calls seq on its - argument. If there are no more items, returns nil" - [coll] - (when-not (nil? coll) - (if (implements? INext coll) - (-next ^not-native coll) - (seq (rest coll))))) - -(defn ^boolean = - "Equality. Returns true if x equals y, false if not. Compares - numbers and collections in a type-independent manner. Clojure's immutable data - structures define -equiv (and thus =) as a value, not an identity, - comparison." - ([x] true) - ([x y] - (if (nil? x) - (nil? y) - (or (identical? x y) - ^boolean (-equiv x y)))) - ([x y & more] - (if (= x y) - (if (next more) - (recur y (first more) (next more)) - (= y (first more))) - false))) - -;;;;;;;;;;;;;;;;;;; protocols on primitives ;;;;;;;; -(declare hash-map list equiv-sequential) - -(extend-type nil - ICounted - (-count [_] 0)) - -;; TODO: we should remove this and handle date equality checking -;; by some other means, probably by adding a new primitive type -;; case to the hash table lookup - David - -(extend-type js/Date - IEquiv - (-equiv [o other] - (and (instance? js/Date other) - (identical? (.toString o) (.toString other))))) - -(extend-type number - IEquiv - (-equiv [x o] (identical? x o))) - -(declare with-meta) - -(extend-type function - Fn - IMeta - (-meta [_] nil)) - -(extend-type default - IHash - (-hash [o] - (goog/getUid o))) - -;;this is primitive because & emits call to array-seq -(defn inc - "Returns a number one greater than num." - [x] (cljs.core/+ x 1)) - -(declare deref) - -(deftype Reduced [val] - IDeref - (-deref [o] val)) - -(defn reduced - "Wraps x in a way such that a reduce will terminate with the value x" - [x] - (Reduced. x)) - -(defn ^boolean reduced? - "Returns true if x is the result of a call to reduced" - [r] - (instance? Reduced r)) - -(defn- ci-reduce - "Accepts any collection which satisfies the ICount and IIndexed protocols and -reduces them without incurring seq initialization" - ([cicoll f] - (let [cnt (-count cicoll)] - (if (zero? cnt) - (f) - (loop [val (-nth cicoll 0), n 1] - (if (< n cnt) - (let [nval (f val (-nth cicoll n))] - (if (reduced? nval) - @nval - (recur nval (inc n)))) - val))))) - ([cicoll f val] - (let [cnt (-count cicoll)] - (loop [val val, n 0] - (if (< n cnt) - (let [nval (f val (-nth cicoll n))] - (if (reduced? nval) - @nval - (recur nval (inc n)))) - val)))) - ([cicoll f val idx] - (let [cnt (-count cicoll)] - (loop [val val, n idx] - (if (< n cnt) - (let [nval (f val (-nth cicoll n))] - (if (reduced? nval) - @nval - (recur nval (inc n)))) - val))))) - -(defn- array-reduce - ([arr f] - (let [cnt (alength arr)] - (if (zero? (alength arr)) - (f) - (loop [val (aget arr 0), n 1] - (if (< n cnt) - (let [nval (f val (aget arr n))] - (if (reduced? nval) - @nval - (recur nval (inc n)))) - val))))) - ([arr f val] - (let [cnt (alength arr)] - (loop [val val, n 0] - (if (< n cnt) - (let [nval (f val (aget arr n))] - (if (reduced? nval) - @nval - (recur nval (inc n)))) - val)))) - ([arr f val idx] - (let [cnt (alength arr)] - (loop [val val, n idx] - (if (< n cnt) - (let [nval (f val (aget arr n))] - (if (reduced? nval) - @nval - (recur nval (inc n)))) - val))))) - -(declare hash-coll cons RSeq) - -(defn ^boolean counted? - "Returns true if coll implements count in constant time" - [x] (satisfies? ICounted x)) - -(defn ^boolean indexed? - "Returns true if coll implements nth in constant time" - [x] (satisfies? IIndexed x)) - -(deftype IndexedSeq [arr i] - Object - (toString [coll] - (pr-str* coll)) - - ICloneable - (-clone [_] (IndexedSeq. arr i)) - - ISeqable - (-seq [this] this) - - ASeq - ISeq - (-first [_] (aget arr i)) - (-rest [_] (if (< (inc i) (alength arr)) - (IndexedSeq. arr (inc i)) - (list))) - - INext - (-next [_] (if (< (inc i) (alength arr)) - (IndexedSeq. arr (inc i)) - nil)) - - ICounted - (-count [_] (- (alength arr) i)) - - IIndexed - (-nth [coll n] - (let [i (+ n i)] - (when (< i (alength arr)) - (aget arr i)))) - (-nth [coll n not-found] - (let [i (+ n i)] - (if (< i (alength arr)) - (aget arr i) - not-found))) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - ICollection - (-conj [coll o] (cons o coll)) - - IEmptyableCollection - (-empty [coll] cljs.core.List.EMPTY) - - IReduce - (-reduce [coll f] - (array-reduce arr f (aget arr i) (inc i))) - (-reduce [coll f start] - (array-reduce arr f start i)) - - IHash - (-hash [coll] (hash-coll coll)) - - IReversible - (-rseq [coll] - (let [c (-count coll)] - (if (pos? c) - (RSeq. coll (dec c) nil))))) - -(defn prim-seq - ([prim] - (prim-seq prim 0)) - ([prim i] - (when (< i (alength prim)) - (IndexedSeq. prim i)))) - -(defn array-seq - ([array] - (prim-seq array 0)) - ([array i] - (prim-seq array i))) - -(declare with-meta seq-reduce) - -(deftype RSeq [ci i meta] - Object - (toString [coll] - (pr-str* coll)) - - ICloneable - (-clone [_] (RSeq. ci i meta)) - - IMeta - (-meta [coll] meta) - IWithMeta - (-with-meta [coll new-meta] - (RSeq. ci i new-meta)) - - ISeqable - (-seq [coll] coll) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - ISeq - (-first [coll] - (-nth ci i)) - (-rest [coll] - (if (pos? i) - (RSeq. ci (dec i) nil))) - - ICounted - (-count [coll] (inc i)) - - ICollection - (-conj [coll o] - (cons o coll)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY meta)) - - IHash - (-hash [coll] (hash-coll coll)) - - IReduce - (-reduce [col f] (seq-reduce f col)) - (-reduce [col f start] (seq-reduce f start col))) - -(defn second - "Same as (first (next x))" - [coll] - (first (next coll))) - -(defn ffirst - "Same as (first (first x))" - [coll] - (first (first coll))) - -(defn nfirst - "Same as (next (first x))" - [coll] - (next (first coll))) - -(defn fnext - "Same as (first (next x))" - [coll] - (first (next coll))) - -(defn nnext - "Same as (next (next x))" - [coll] - (next (next coll))) - -(defn last - "Return the last item in coll, in linear time" - [s] - (let [sn (next s)] - (if-not (nil? sn) - (recur sn) - (first s)))) - -(extend-type default - IEquiv - (-equiv [x o] (identical? x o))) - -(defn conj - "conj[oin]. Returns a new collection with the xs - 'added'. (conj nil item) returns (item). The 'addition' may - happen at different 'places' depending on the concrete type." - ([coll x] - (if-not (nil? coll) - (-conj coll x) - (list x))) - ([coll x & xs] - (if xs - (recur (conj coll x) (first xs) (next xs)) - (conj coll x)))) - -(defn empty - "Returns an empty collection of the same category as coll, or nil" - [coll] - (when-not (nil? coll) - (-empty coll))) - -(defn- accumulating-seq-count [coll] - (loop [s (seq coll) acc 0] - (if (counted? s) ; assumes nil is counted, which it currently is - (+ acc (-count s)) - (recur (next s) (inc acc))))) - -(defn count - "Returns the number of items in the collection. (count nil) returns - 0. Also works on strings, arrays, and Maps" - [coll] - (if-not (nil? coll) - (cond - (implements? ICounted coll) - (-count ^not-native coll) - - (array? coll) - (alength coll) - - (string? coll) - (alength coll) - - (native-satisfies? ICounted coll) - (-count coll) - - :else (accumulating-seq-count coll)) - 0)) - -(defn- linear-traversal-nth - ([coll n] - (cond - (nil? coll) (throw (js/Error. "Index out of bounds")) - (zero? n) (if (seq coll) - (first coll) - (throw (js/Error. "Index out of bounds"))) - (indexed? coll) (-nth coll n) - (seq coll) (recur (next coll) (dec n)) - :else (throw (js/Error. "Index out of bounds")))) - ([coll n not-found] - (cond - (nil? coll) not-found - (zero? n) (if (seq coll) - (first coll) - not-found) - (indexed? coll) (-nth coll n not-found) - (seq coll) (recur (next coll) (dec n) not-found) - :else not-found))) - -(defn nth - "Returns the value at the index. get returns nil if index out of - bounds, nth throws an exception unless not-found is supplied. nth - also works for strings, arrays, regex Matchers and Lists, and, - in O(n) time, for sequences." - ([coll n] - (cond - (not (number? n)) - (throw (js/Error. "index argument to nth must be a number")) - - (nil? coll) - coll - - (implements? IIndexed coll) - (-nth ^not-native coll n) - - (array? coll) - (when (< n (.-length coll)) - (aget coll n)) - - (string? coll) - (when (< n (.-length coll)) - (aget coll n)) - - (native-satisfies? IIndexed coll) - (-nth coll n) - - (satisfies? ISeq coll) - (linear-traversal-nth coll n) - - :else - (throw (js/Error. (str "nth not supported on this type " - (type->str (type coll))))))) - ([coll n not-found] - (cond - (not (number? n)) - (throw (js/Error. "index argument to nth must be a number.")) - - (nil? coll) - not-found - - (implements? IIndexed coll) - (-nth ^not-native coll n not-found) - - (array? coll) - (if (< n (.-length coll)) - (aget coll n) - not-found) - - (string? coll) - (if (< n (.-length coll)) - (aget coll n) - not-found) - - (native-satisfies? IIndexed coll) - (-nth coll n) - - (satisfies? ISeq coll) - (linear-traversal-nth coll n not-found) - - :else - (throw (js/Error. (str "nth not supported on this type " - (type->str (type coll)))))))) - -(defn get - "Returns the value mapped to key, not-found or nil if key not present." - ([o k] - (when-not (nil? o) - (cond - (implements? ILookup o) - (-lookup ^not-native o k) - - (array? o) - (when (< k (.-length o)) - (aget o k)) - - (string? o) - (when (< k (.-length o)) - (aget o k)) - - (native-satisfies? ILookup o) - (-lookup o k) - - :else nil))) - ([o k not-found] - (if-not (nil? o) - (cond - (implements? ILookup o) - (-lookup ^not-native o k not-found) - - (array? o) - (if (< k (.-length o)) - (aget o k) - not-found) - - (string? o) - (if (< k (.-length o)) - (aget o k) - not-found) - - (native-satisfies? ILookup o) - (-lookup o k not-found) - - :else not-found) - not-found))) - -(defn assoc - "assoc[iate]. When applied to a map, returns a new map of the - same (hashed/sorted) type, that contains the mapping of key(s) to - val(s). When applied to a vector, returns a new vector that - contains val at index." - ([coll k v] - (if-not (nil? coll) - (-assoc coll k v) - (hash-map k v))) - ([coll k v & kvs] - (let [ret (assoc coll k v)] - (if kvs - (recur ret (first kvs) (second kvs) (nnext kvs)) - ret)))) - -(defn dissoc - "dissoc[iate]. Returns a new map of the same (hashed/sorted) type, - that does not contain a mapping for key(s)." - ([coll] coll) - ([coll k] - (when-not (nil? coll) - (-dissoc coll k))) - ([coll k & ks] - (when-not (nil? coll) - (let [ret (dissoc coll k)] - (if ks - (recur ret (first ks) (next ks)) - ret))))) - -(defn ^boolean fn? [f] - (or ^boolean (goog/isFunction f) (satisfies? Fn f))) - -(defn with-meta - "Returns an object of the same type and value as obj, with - map m as its metadata." - [o meta] - (if (and (fn? o) (not (satisfies? IWithMeta o))) - (with-meta - (reify - Fn - IFn - (-invoke [_ & args] - (apply o args))) - meta) - (when-not (nil? o) - (-with-meta o meta)))) - -(defn meta - "Returns the metadata of obj, returns nil if there is no metadata." - [o] - (when (and (not (nil? o)) - (satisfies? IMeta o)) - (-meta o))) - -(defn peek - "For a list or queue, same as first, for a vector, same as, but much - more efficient than, last. If the collection is empty, returns nil." - [coll] - (when-not (nil? coll) - (-peek coll))) - -(defn pop - "For a list or queue, returns a new list/queue without the first - item, for a vector, returns a new vector without the last item. - Note - not the same as next/butlast." - [coll] - (when-not (nil? coll) - (-pop coll))) - -(defn disj - "disj[oin]. Returns a new set of the same (hashed/sorted) type, that - does not contain key(s)." - ([coll] coll) - ([coll k] - (when-not (nil? coll) - (-disjoin coll k))) - ([coll k & ks] - (when-not (nil? coll) - (let [ret (disj coll k)] - (if ks - (recur ret (first ks) (next ks)) - ret))))) - -;; Simple caching of string hashcode -(def string-hash-cache (js-obj)) -(def string-hash-cache-count 0) - -(defn add-to-string-hash-cache [k] - (let [h (goog.string/hashCode k)] - (aset string-hash-cache k h) - (set! string-hash-cache-count (inc string-hash-cache-count)) - h)) - -(defn check-string-hash-cache [k] - (when (> string-hash-cache-count 255) - (set! string-hash-cache (js-obj)) - (set! string-hash-cache-count 0)) - (let [h (aget string-hash-cache k)] - (if (number? h) - h - (add-to-string-hash-cache k)))) - -(defn hash [o] - (cond - (implements? IHash o) - (-hash ^not-native o) - - (number? o) - (js-mod (.floor js/Math o) 2147483647) - - (true? o) 1 - - (false? o) 0 - - (string? o) - (check-string-hash-cache o) - - (nil? o) 0 - - :else - (-hash o))) - -(defn ^boolean empty? - "Returns true if coll has no items - same as (not (seq coll)). - Please use the idiom (seq x) rather than (not (empty? x))" - [coll] (or (nil? coll) - (not (seq coll)))) - -(defn ^boolean coll? - "Returns true if x satisfies ICollection" - [x] - (if (nil? x) - false - (satisfies? ICollection x))) - -(defn ^boolean set? - "Returns true if x satisfies ISet" - [x] - (if (nil? x) - false - (satisfies? ISet x))) - -(defn ^boolean associative? - "Returns true if coll implements Associative" - [x] (satisfies? IAssociative x)) - -(defn ^boolean sequential? - "Returns true if coll satisfies ISequential" - [x] (satisfies? ISequential x)) - -(defn ^boolean sorted? - "Returns true if coll satisfies ISorted" - [x] (satisfies? ISorted x)) - -(defn ^boolean reduceable? - "Returns true if coll satisfies IReduce" - [x] (satisfies? IReduce x)) - -(defn ^boolean map? - "Return true if x satisfies IMap" - [x] - (if (nil? x) - false - (satisfies? IMap x))) - -(defn ^boolean vector? - "Return true if x satisfies IVector" - [x] (satisfies? IVector x)) - -(declare ChunkedCons ChunkedSeq) - -(defn ^boolean chunked-seq? - [x] (implements? IChunkedSeq x)) - -;;;;;;;;;;;;;;;;;;;; js primitives ;;;;;;;;;;;; -(defn js-obj - ([] - (cljs.core/js-obj)) - ([& keyvals] - (apply gobject/create keyvals))) - -(defn js-keys [obj] - (let [keys (array)] - (goog.object/forEach obj (fn [val key obj] (.push keys key))) - keys)) - -(defn js-delete [obj key] - (cljs.core/js-delete obj key)) - -(defn- array-copy - ([from i to j len] - (loop [i i j j len len] - (if (zero? len) - to - (do (aset to j (aget from i)) - (recur (inc i) (inc j) (dec len))))))) - -(defn- array-copy-downward - ([from i to j len] - (loop [i (+ i (dec len)) j (+ j (dec len)) len len] - (if (zero? len) - to - (do (aset to j (aget from i)) - (recur (dec i) (dec j) (dec len))))))) - -;;;;;;;;;;;;;;;; preds ;;;;;;;;;;;;;;;;;; - -(def ^:private lookup-sentinel (js-obj)) - -(defn ^boolean false? - "Returns true if x is the value false, false otherwise." - [x] (cljs.core/false? x)) - -(defn ^boolean true? - "Returns true if x is the value true, false otherwise." - [x] (cljs.core/true? x)) - -(defn ^boolean undefined? [x] - (cljs.core/undefined? x)) - -(defn ^boolean seq? - "Return true if s satisfies ISeq" - [s] - (if (nil? s) - false - (satisfies? ISeq s))) - -(defn ^boolean seqable? - "Return true if s satisfies ISeqable" - [s] - (satisfies? ISeqable s)) - -(defn ^boolean boolean [x] - (if x true false)) - -(defn ^boolean ifn? [f] - (or (fn? f) (satisfies? IFn f))) - -(defn ^boolean integer? - "Returns true if n is an integer." - [n] - (and (number? n) - (not ^boolean (js/isNaN n)) - (not (identical? n js/Infinity)) - (== (js/parseFloat n) (js/parseInt n 10)))) - -(defn ^boolean contains? - "Returns true if key is present in the given collection, otherwise - returns false. Note that for numerically indexed collections like - vectors and arrays, this tests if the numeric key is within the - range of indexes. 'contains?' operates constant or logarithmic time; - it will not perform a linear search for a value. See also 'some'." - [coll v] - (if (identical? (get coll v lookup-sentinel) lookup-sentinel) - false - true)) - -(defn find - "Returns the map entry for key, or nil if key not present." - [coll k] - (when (and (not (nil? coll)) - (associative? coll) - (contains? coll k)) - [k (get coll k)])) - -(defn ^boolean distinct? - "Returns true if no two of the arguments are =" - ([x] true) - ([x y] (not (= x y))) - ([x y & more] - (if (not (= x y)) - (loop [s #{x y} xs more] - (let [x (first xs) - etc (next xs)] - (if xs - (if (contains? s x) - false - (recur (conj s x) etc)) - true))) - false))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Seq fns ;;;;;;;;;;;;;;;; - -(defn ^seq sequence - "Coerces coll to a (possibly empty) sequence, if it is not already - one. Will not force a lazy seq. (sequence nil) yields ()" - [coll] - (if (seq? coll) - coll - (or (seq coll) ()))) - -(defn ^number compare - "Comparator. Returns a negative number, zero, or a positive number - when x is logically 'less than', 'equal to', or 'greater than' - y. Uses IComparable if available and google.array.defaultCompare for objects - of the same type and special-cases nil to be less than any other object." - [x y] - (cond - (identical? x y) 0 - - (nil? x) -1 - - (nil? y) 1 - - (identical? (type x) (type y)) - (if (implements? IComparable x) - (-compare ^not-native x y) - (garray/defaultCompare x y)) - - :else - (throw (js/Error. "compare on non-nil objects of different types")))) - -(defn ^:private compare-indexed - "Compare indexed collection." - ([xs ys] - (let [xl (count xs) - yl (count ys)] - (cond - (< xl yl) -1 - (> xl yl) 1 - :else (compare-indexed xs ys xl 0)))) - ([xs ys len n] - (let [d (compare (nth xs n) (nth ys n))] - (if (and (zero? d) (< (+ n 1) len)) - (recur xs ys len (inc n)) - d)))) - -(defn ^:private fn->comparator - "Given a fn that might be boolean valued or a comparator, - return a fn that is a comparator." - [f] - (if (= f compare) - compare - (fn [x y] - (let [r (f x y)] - (if (number? r) - r - (if r - -1 - (if (f y x) 1 0))))))) - -(declare to-array) - -(defn sort - "Returns a sorted sequence of the items in coll. Comp can be - boolean-valued comparison funcion, or a -/0/+ valued comparator. - Comp defaults to compare." - ([coll] - (sort compare coll)) - ([comp coll] - (if (seq coll) - (let [a (to-array coll)] - ;; matching Clojure's stable sort, though docs don't promise it - (garray/stableSort a (fn->comparator comp)) - (seq a)) - ()))) - -(defn sort-by - "Returns a sorted sequence of the items in coll, where the sort - order is determined by comparing (keyfn item). Comp can be - boolean-valued comparison funcion, or a -/0/+ valued comparator. - Comp defaults to compare." - ([keyfn coll] - (sort-by keyfn compare coll)) - ([keyfn comp coll] - (sort (fn [x y] ((fn->comparator comp) (keyfn x) (keyfn y))) coll))) - -; simple reduce based on seqs, used as default -(defn- seq-reduce - ([f coll] - (if-let [s (seq coll)] - (reduce f (first s) (next s)) - (f))) - ([f val coll] - (loop [val val, coll (seq coll)] - (if coll - (let [nval (f val (first coll))] - (if (reduced? nval) - @nval - (recur nval (next coll)))) - val)))) - -(declare vec) - -(defn shuffle - "Return a random permutation of coll" - [coll] - (let [a (to-array coll)] - (garray/shuffle a) - (vec a))) - -(defn reduce - "f should be a function of 2 arguments. If val is not supplied, - returns the result of applying f to the first 2 items in coll, then - applying f to that result and the 3rd item, etc. If coll contains no - items, f must accept no arguments as well, and reduce returns the - result of calling f with no arguments. If coll has only 1 item, it - is returned and f is not called. If val is supplied, returns the - result of applying f to val and the first item in coll, then - applying f to that result and the 2nd item, etc. If coll contains no - items, returns val and f is not called." - ([f coll] - (cond - (implements? IReduce coll) - (-reduce ^not-native coll f) - - (array? coll) - (array-reduce coll f) - - (string? coll) - (array-reduce coll f) - - (native-satisfies? IReduce coll) - (-reduce coll f) - - :else - (seq-reduce f coll))) - ([f val coll] - (cond - (implements? IReduce coll) - (-reduce ^not-native coll f val) - - (array? coll) - (array-reduce coll f val) - - (string? coll) - (array-reduce coll f val) - - (native-satisfies? IReduce coll) - (-reduce coll f val) - - :else - (seq-reduce f val coll)))) - -(defn reduce-kv - "Reduces an associative collection. f should be a function of 3 - arguments. Returns the result of applying f to init, the first key - and the first value in coll, then applying f to that result and the - 2nd key and value, etc. If coll contains no entries, returns init - and f is not called. Note that reduce-kv is supported on vectors, - where the keys will be the ordinals." - ([f init coll] - (if-not (nil? coll) - (-kv-reduce coll f init) - init))) - -;;; Math - variadic forms will not work until the following implemented: -;;; first, next, reduce - -(defn ^number + - "Returns the sum of nums. (+) returns 0." - ([] 0) - ([x] x) - ([x y] (cljs.core/+ x y)) - ([x y & more] - (reduce + (cljs.core/+ x y) more))) - -(defn ^number - - "If no ys are supplied, returns the negation of x, else subtracts - the ys from x and returns the result." - ([x] (cljs.core/- x)) - ([x y] (cljs.core/- x y)) - ([x y & more] (reduce - (cljs.core/- x y) more))) - -(defn ^number * - "Returns the product of nums. (*) returns 1." - ([] 1) - ([x] x) - ([x y] (cljs.core/* x y)) - ([x y & more] (reduce * (cljs.core/* x y) more))) - -(declare divide) - -(defn ^number / - "If no denominators are supplied, returns 1/numerator, - else returns numerator divided by all of the denominators." - ([x] (/ 1 x)) - ([x y] (cljs.core/divide x y)) ;; FIXME: waiting on cljs.core// - ([x y & more] (reduce / (/ x y) more))) - -(defn ^boolean < - "Returns non-nil if nums are in monotonically increasing order, - otherwise false." - ([x] true) - ([x y] (cljs.core/< x y)) - ([x y & more] - (if (cljs.core/< x y) - (if (next more) - (recur y (first more) (next more)) - (cljs.core/< y (first more))) - false))) - -(defn ^boolean <= - "Returns non-nil if nums are in monotonically non-decreasing order, - otherwise false." - ([x] true) - ([x y] (cljs.core/<= x y)) - ([x y & more] - (if (cljs.core/<= x y) - (if (next more) - (recur y (first more) (next more)) - (cljs.core/<= y (first more))) - false))) - -(defn ^boolean > - "Returns non-nil if nums are in monotonically decreasing order, - otherwise false." - ([x] true) - ([x y] (cljs.core/> x y)) - ([x y & more] - (if (cljs.core/> x y) - (if (next more) - (recur y (first more) (next more)) - (cljs.core/> y (first more))) - false))) - -(defn ^boolean >= - "Returns non-nil if nums are in monotonically non-increasing order, - otherwise false." - ([x] true) - ([x y] (cljs.core/>= x y)) - ([x y & more] - (if (cljs.core/>= x y) - (if (next more) - (recur y (first more) (next more)) - (cljs.core/>= y (first more))) - false))) - -(defn dec - "Returns a number one less than num." - [x] (- x 1)) - -(defn ^number max - "Returns the greatest of the nums." - ([x] x) - ([x y] (cljs.core/max x y)) - ([x y & more] - (reduce max (cljs.core/max x y) more))) - -(defn ^number min - "Returns the least of the nums." - ([x] x) - ([x y] (cljs.core/min x y)) - ([x y & more] - (reduce min (cljs.core/min x y) more))) - -(defn ^number byte [x] x) - -(defn char - "Coerce to char" - [x] - (cond - (number? x) (.fromCharCode js/String x) - (and (string? x) (== (.-length x) 1)) x - :else (throw (js/Error. "Argument to char must be a character or number")))) - -(defn ^number short [x] x) -(defn ^number float [x] x) -(defn ^number double [x] x) - -(defn ^number unchecked-byte [x] x) -(defn ^number unchecked-char [x] x) -(defn ^number unchecked-short [x] x) -(defn ^number unchecked-float [x] x) -(defn ^number unchecked-double [x] x) - -(defn ^number unchecked-add - "Returns the sum of nums. (+) returns 0." - ([] 0) - ([x] x) - ([x y] (cljs.core/unchecked-add x y)) - ([x y & more] (reduce unchecked-add (cljs.core/unchecked-add x y) more))) - -(defn ^number unchecked-add-int - "Returns the sum of nums. (+) returns 0." - ([] 0) - ([x] x) - ([x y] (cljs.core/unchecked-add-int x y)) - ([x y & more] (reduce unchecked-add-int (cljs.core/unchecked-add-int x y) more))) - -(defn unchecked-dec [x] - (cljs.core/unchecked-dec x)) - -(defn unchecked-dec-int [x] - (cljs.core/unchecked-dec-int x)) - -(defn ^number unchecked-divide-int - "If no denominators are supplied, returns 1/numerator, - else returns numerator divided by all of the denominators." - ([x] (unchecked-divide-int 1 x)) - ([x y] (cljs.core/divide x y)) ;; FIXME: waiting on cljs.core// - ([x y & more] (reduce unchecked-divide-int (unchecked-divide-int x y) more))) - -(defn unchecked-inc [x] - (cljs.core/unchecked-inc x)) - -(defn unchecked-inc-int [x] - (cljs.core/unchecked-inc-int x)) - -(defn ^number unchecked-multiply - "Returns the product of nums. (*) returns 1." - ([] 1) - ([x] x) - ([x y] (cljs.core/unchecked-multiply x y)) - ([x y & more] (reduce unchecked-multiply (cljs.core/unchecked-multiply x y) more))) - -(defn ^number unchecked-multiply-int - "Returns the product of nums. (*) returns 1." - ([] 1) - ([x] x) - ([x y] (cljs.core/unchecked-multiply-int x y)) - ([x y & more] (reduce unchecked-multiply-int (cljs.core/unchecked-multiply-int x y) more))) - -(defn unchecked-negate [x] - (cljs.core/unchecked-negate x)) - -(defn unchecked-negate-int [x] - (cljs.core/unchecked-negate-int x)) - -(declare mod) - -(defn unchecked-remainder-int [x n] - (cljs.core/unchecked-remainder-int x n)) - -(defn ^number unchecked-substract - "If no ys are supplied, returns the negation of x, else subtracts - the ys from x and returns the result." - ([x] (cljs.core/unchecked-subtract x)) - ([x y] (cljs.core/unchecked-subtract x y)) - ([x y & more] (reduce unchecked-substract (cljs.core/unchecked-subtract x y) more))) - -(defn ^number unchecked-substract-int - "If no ys are supplied, returns the negation of x, else subtracts - the ys from x and returns the result." - ([x] (cljs.core/unchecked-subtract-int x)) - ([x y] (cljs.core/unchecked-subtract-int x y)) - ([x y & more] (reduce unchecked-substract-int (cljs.core/unchecked-subtract-int x y) more))) - -(defn- ^number fix [q] - (if (>= q 0) - (Math/floor q) - (Math/ceil q))) - -(defn int - "Coerce to int by stripping decimal places." - [x] - (bit-or x 0)) - -(defn unchecked-int - "Coerce to int by stripping decimal places." - [x] - (fix x)) - -(defn long - "Coerce to long by stripping decimal places. Identical to `int'." - [x] - (fix x)) - -(defn unchecked-long - "Coerce to long by stripping decimal places. Identical to `int'." - [x] - (fix x)) - -(defn booleans [x] x) -(defn bytes [x] x) -(defn chars [x] x) -(defn shorts [x] x) -(defn ints [x] x) -(defn floats [x] x) -(defn doubles [x] x) -(defn longs [x] x) - -(defn js-mod - "Modulus of num and div with original javascript behavior. i.e. bug for negative numbers" - [n d] - (cljs.core/js-mod n d)) - -(defn mod - "Modulus of num and div. Truncates toward negative infinity." - [n d] - (js-mod (+ (js-mod n d) d) d)) - -(defn quot - "quot[ient] of dividing numerator by denominator." - [n d] - (let [rem (js-mod n d)] - (fix (/ (- n rem) d)))) - -(defn rem - "remainder of dividing numerator by denominator." - [n d] - (let [q (quot n d)] - (- n (* d q)))) - -(defn ^number rand - "Returns a random floating point number between 0 (inclusive) and n (default 1) (exclusive)." - ([] (Math/random)) - ([n] (* n (rand)))) - -(defn rand-int - "Returns a random integer between 0 (inclusive) and n (exclusive)." - [n] (fix (rand n))) - -(defn bit-xor - "Bitwise exclusive or" - [x y] (cljs.core/bit-xor x y)) - -(defn bit-and - "Bitwise and" - [x y] (cljs.core/bit-and x y)) - -(defn bit-or - "Bitwise or" - [x y] (cljs.core/bit-or x y)) - -(defn bit-and-not - "Bitwise and" - [x y] (cljs.core/bit-and-not x y)) - -(defn bit-clear - "Clear bit at index n" - [x n] - (cljs.core/bit-clear x n)) - -(defn bit-flip - "Flip bit at index n" - [x n] - (cljs.core/bit-flip x n)) - -(defn bit-not - "Bitwise complement" - [x] (cljs.core/bit-not x)) - -(defn bit-set - "Set bit at index n" - [x n] - (cljs.core/bit-set x n)) - -(defn bit-test - "Test bit at index n" - [x n] - (cljs.core/bit-test x n)) - -(defn bit-shift-left - "Bitwise shift left" - [x n] (cljs.core/bit-shift-left x n)) - -(defn bit-shift-right - "Bitwise shift right" - [x n] (cljs.core/bit-shift-right x n)) - -(defn bit-shift-right-zero-fill - "DEPRECATED: Bitwise shift right with zero fill" - [x n] (cljs.core/bit-shift-right-zero-fill x n)) - -(defn unsigned-bit-shift-right - "Bitwise shift right with zero fill" - [x n] (cljs.core/unsigned-bit-shift-right x n)) - -(defn bit-count - "Counts the number of bits set in n" - [v] - (let [v (- v (bit-and (bit-shift-right v 1) 0x55555555)) - v (+ (bit-and v 0x33333333) (bit-and (bit-shift-right v 2) 0x33333333))] - (bit-shift-right (* (bit-and (+ v (bit-shift-right v 4)) 0xF0F0F0F) 0x1010101) 24))) - -(defn ^boolean == - "Returns non-nil if nums all have the equivalent - value, otherwise false. Behavior on non nums is - undefined." - ([x] true) - ([x y] (-equiv x y)) - ([x y & more] - (if (== x y) - (if (next more) - (recur y (first more) (next more)) - (== y (first more))) - false))) - -(defn ^boolean pos? - "Returns true if num is greater than zero, else false" - [n] (cljs.core/pos? n)) - -(defn ^boolean zero? [n] - (cljs.core/zero? n)) - -(defn ^boolean neg? - "Returns true if num is less than zero, else false" - [x] (cljs.core/neg? x)) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; protocols for host types ;;;;;; - -(defn nthnext - "Returns the nth next of coll, (seq coll) when n is 0." - [coll n] - (loop [n n xs (seq coll)] - (if (and xs (pos? n)) - (recur (dec n) (next xs)) - xs))) - -;;;;;;;;;;;;;;;;;;;;;;;;;; basics ;;;;;;;;;;;;;;;;;; - -(defn str - "With no args, returns the empty string. With one arg x, returns - x.toString(). (str nil) returns the empty string. With more than - one arg, returns the concatenation of the str values of the args." - ([] "") - ([x] (if (nil? x) - "" - (.toString x))) - ([x & ys] - (loop [sb (gstring/StringBuffer. (str x)) more ys] - (if more - (recur (. sb (append (str (first more)))) (next more)) - (.toString sb))))) - -(defn subs - "Returns the substring of s beginning at start inclusive, and ending - at end (defaults to length of string), exclusive." - ([s start] (.substring s start)) - ([s start end] (.substring s start end))) - -(declare map name) - -(defn- equiv-sequential - "Assumes x is sequential. Returns true if x equals y, otherwise - returns false." - [x y] - (boolean - (when (sequential? y) - (loop [xs (seq x) ys (seq y)] - (cond (nil? xs) (nil? ys) - (nil? ys) false - (= (first xs) (first ys)) (recur (next xs) (next ys)) - :else false))))) - -(defn hash-combine [seed hash] - ; a la boost - (bit-xor seed (+ hash 0x9e3779b9 - (bit-shift-left seed 6) - (bit-shift-right seed 2)))) - -(defn- hash-coll [coll] - (if (seq coll) - (loop [res (hash (first coll)) s (next coll)] - (if (nil? s) - res - (recur (hash-combine res (hash (first s))) (next s)))) - 0)) - -(declare key val) - -(defn- hash-imap [m] - ;; a la clojure.lang.APersistentMap - (loop [h 0 s (seq m)] - (if s - (let [e (first s)] - (recur (js-mod (+ h (bit-xor (hash (key e)) (hash (val e)))) - 4503599627370496) - (next s))) - h))) - -(defn- hash-iset [s] - ;; a la clojure.lang.APersistentSet - (loop [h 0 s (seq s)] - (if s - (let [e (first s)] - (recur (js-mod (+ h (hash e)) 4503599627370496) - (next s))) - h))) - -(declare name chunk-first chunk-rest) - -(defn- extend-object! - "Takes a JavaScript object and a map of names to functions and - attaches said functions as methods on the object. Any references to - JavaScript's implict this (via the this-as macro) will resolve to the - object that the function is attached." - [obj fn-map] - (doseq [[key-name f] fn-map] - (let [str-name (name key-name)] - (aset obj str-name f))) - obj) - -;;;;;;;;;;;;;;;; cons ;;;;;;;;;;;;;;;; -(deftype List [meta first rest count ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - IList - - ICloneable - (-clone [_] (List. meta first rest count __hash)) - - IWithMeta - (-with-meta [coll meta] (List. meta first rest count __hash)) - - IMeta - (-meta [coll] meta) - - ASeq - ISeq - (-first [coll] first) - (-rest [coll] - (if (== count 1) - () - rest)) - - INext - (-next [coll] - (if (== count 1) - nil - rest)) - - IStack - (-peek [coll] first) - (-pop [coll] (-rest coll)) - - ICollection - (-conj [coll o] (List. meta o coll (inc count) nil)) - - IEmptyableCollection - (-empty [coll] cljs.core.List.EMPTY) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - ISeqable - (-seq [coll] coll) - - ICounted - (-count [coll] count) - - IReduce - (-reduce [coll f] (seq-reduce f coll)) - (-reduce [coll f start] (seq-reduce f start coll))) - -(deftype EmptyList [meta] - Object - (toString [coll] - (pr-str* coll)) - - IList - - ICloneable - (-clone [_] (EmptyList. meta)) - - IWithMeta - (-with-meta [coll meta] (EmptyList. meta)) - - IMeta - (-meta [coll] meta) - - ISeq - (-first [coll] nil) - (-rest [coll] ()) - - INext - (-next [coll] nil) - - IStack - (-peek [coll] nil) - (-pop [coll] (throw (js/Error. "Can't pop empty list"))) - - ICollection - (-conj [coll o] (List. meta o nil 1 nil)) - - IEmptyableCollection - (-empty [coll] coll) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IHash - (-hash [coll] 0) - - ISeqable - (-seq [coll] nil) - - ICounted - (-count [coll] 0) - - IReduce - (-reduce [coll f] (seq-reduce f coll)) - (-reduce [coll f start] (seq-reduce f start coll))) - -(set! cljs.core.List.EMPTY (EmptyList. nil)) - -(defn ^boolean reversible? [coll] - (satisfies? IReversible coll)) - -(defn ^seq rseq [coll] - (-rseq coll)) - -(defn reverse - "Returns a seq of the items in coll in reverse order. Not lazy." - [coll] - (if (reversible? coll) - (rseq coll) - (reduce conj () coll))) - -(defn list [& xs] - (let [arr (if (and (instance? IndexedSeq xs) (zero? (.-i xs))) - (.-arr xs) - (let [arr (array)] - (loop [^not-native xs xs] - (if-not (nil? xs) - (do - (.push arr (-first xs)) - (recur (-next xs))) - arr))))] - (loop [i (alength arr) ^not-native r ()] - (if (> i 0) - (recur (dec i) (-conj r (aget arr (dec i)))) - r)))) - -(deftype Cons [meta first rest ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - IList - - ICloneable - (-clone [_] (Cons. meta first rest __hash)) - - IWithMeta - (-with-meta [coll meta] (Cons. meta first rest __hash)) - - IMeta - (-meta [coll] meta) - - ASeq - ISeq - (-first [coll] first) - (-rest [coll] (if (nil? rest) () rest)) - - INext - (-next [coll] - (if (nil? rest) nil (seq rest))) - - ICollection - (-conj [coll o] (Cons. nil o coll __hash)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY meta)) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - ISeqable - (-seq [coll] coll) - - IReduce - (-reduce [coll f] (seq-reduce f coll)) - (-reduce [coll f start] (seq-reduce f start coll))) - -(defn cons - "Returns a new seq where x is the first element and seq is the rest." - [x coll] - (if (or (nil? coll) - (implements? ISeq coll)) - (Cons. nil x coll nil) - (Cons. nil x (seq coll) nil))) - -(defn ^boolean list? [x] - (satisfies? IList x)) - -(deftype Keyword [ns name fqn ^:mutable _hash] - Object - (toString [_] (str ":" fqn)) - - IEquiv - (-equiv [_ other] - (if (instance? Keyword other) - (identical? fqn (.-fqn other)) - false)) - IFn - (-invoke [kw coll] - (get coll kw)) - (-invoke [kw coll not-found] - (get coll kw not-found)) - - IHash - (-hash [_] - ; This was checking if _hash == -1, should it stay that way? - (if (nil? _hash) - (do - (set! _hash (+ (hash-combine (hash ns) (hash name)) - 0x9e3779b9)) - _hash) - _hash)) - - INamed - (-name [_] name) - (-namespace [_] ns) - - IPrintWithWriter - (-pr-writer [o writer _] (-write writer (str ":" fqn)))) - -(defn ^boolean keyword? [x] - (instance? Keyword x)) - -(defn ^boolean keyword-identical? [x y] - (if (identical? x y) - true - (if (and (keyword? x) - (keyword? y)) - (identical? (.-fqn x) (.-fqn y)) - false))) - -(defn namespace - "Returns the namespace String of a symbol or keyword, or nil if not present." - [x] - (if (implements? INamed x) - (-namespace ^not-native x) - (throw (js/Error. (str "Doesn't support namespace: " x))))) - -(defn keyword - "Returns a Keyword with the given namespace and name. Do not use : - in the keyword strings, it will be added automatically." - ([name] (cond - (keyword? name) name - (symbol? name) (Keyword. - (cljs.core/namespace name) - (cljs.core/name name) (.-str name) nil) - (string? name) (let [parts (.split name "/")] - (if (== (alength parts) 2) - (Keyword. (aget parts 0) (aget parts 1) name nil) - (Keyword. nil (aget parts 0) name nil))))) - ([ns name] (Keyword. ns name (str (when ns (str ns "/")) name) nil))) - - -(deftype LazySeq [meta ^:mutable fn ^:mutable s ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - (sval [coll] - (if (nil? fn) - s - (do - (set! s (fn)) - (set! fn nil) - s))) - - IWithMeta - (-with-meta [coll meta] (LazySeq. meta fn s __hash)) - - IMeta - (-meta [coll] meta) - - ISeq - (-first [coll] - (-seq coll) - (when-not (nil? s) - (first s))) - (-rest [coll] - (-seq coll) - (if-not (nil? s) - (rest s) - ())) - - INext - (-next [coll] - (-seq coll) - (when-not (nil? s) - (next s))) - - ICollection - (-conj [coll o] (cons o coll)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY meta)) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - ISeqable - (-seq [coll] - (.sval coll) - (when-not (nil? s) - (loop [ls s] - (if (instance? LazySeq ls) - (recur (.sval ls)) - (do (set! s ls) - (seq s)))))) - - IReduce - (-reduce [coll f] (seq-reduce f coll)) - (-reduce [coll f start] (seq-reduce f start coll))) - -(declare ArrayChunk) - -(deftype ChunkBuffer [^:mutable buf ^:mutable end] - Object - (add [_ o] - (aset buf end o) - (set! end (inc end))) - - (chunk [_ o] - (let [ret (ArrayChunk. buf 0 end)] - (set! buf nil) - ret)) - - ICounted - (-count [_] end)) - -(defn chunk-buffer [capacity] - (ChunkBuffer. (make-array capacity) 0)) - -(deftype ArrayChunk [arr off end] - ICounted - (-count [_] (- end off)) - - IIndexed - (-nth [coll i] - (aget arr (+ off i))) - (-nth [coll i not-found] - (if (and (>= i 0) (< i (- end off))) - (aget arr (+ off i)) - not-found)) - - IChunk - (-drop-first [coll] - (if (== off end) - (throw (js/Error. "-drop-first of empty chunk")) - (ArrayChunk. arr (inc off) end))) - - IReduce - (-reduce [coll f] - (array-reduce arr f (aget arr off) (inc off))) - (-reduce [coll f start] - (array-reduce arr f start off))) - -(defn array-chunk - ([arr] - (ArrayChunk. arr 0 (alength arr))) - ([arr off] - (ArrayChunk. arr off (alength arr))) - ([arr off end] - (ArrayChunk. arr off end))) - -(deftype ChunkedCons [chunk more meta ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - IWithMeta - (-with-meta [coll m] - (ChunkedCons. chunk more m __hash)) - - IMeta - (-meta [coll] meta) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - ISeqable - (-seq [coll] coll) - - ASeq - ISeq - (-first [coll] (-nth chunk 0)) - (-rest [coll] - (if (> (-count chunk) 1) - (ChunkedCons. (-drop-first chunk) more meta nil) - (if (nil? more) - () - more))) - - INext - (-next [coll] - (if (> (-count chunk) 1) - (ChunkedCons. (-drop-first chunk) more meta nil) - (let [more (-seq more)] - (when-not (nil? more) - more)))) - - IChunkedSeq - (-chunked-first [coll] chunk) - (-chunked-rest [coll] - (if (nil? more) - () - more)) - - IChunkedNext - (-chunked-next [coll] - (if (nil? more) - nil - more)) - - ICollection - (-conj [this o] - (cons o this)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY meta)) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash))) - -(defn chunk-cons [chunk rest] - (if (zero? (-count chunk)) - rest - (ChunkedCons. chunk rest nil nil))) - -(defn chunk-append [b x] - (.add b x)) - -(defn chunk [b] - (.chunk b)) - -(defn chunk-first [s] - (-chunked-first s)) - -(defn chunk-rest [s] - (-chunked-rest s)) - -(defn chunk-next [s] - (if (implements? IChunkedNext s) - (-chunked-next s) - (seq (-chunked-rest s)))) - -;;;;;;;;;;;;;;;; - -(defn to-array - "Naive impl of to-array as a start." - [s] - (let [ary (array)] - (loop [s s] - (if (seq s) - (do (. ary push (first s)) - (recur (next s))) - ary)))) - -(defn to-array-2d - "Returns a (potentially-ragged) 2-dimensional array - containing the contents of coll." - [coll] - (let [ret (make-array (count coll))] - (loop [i 0 xs (seq coll)] - (when xs - (aset ret i (to-array (first xs))) - (recur (inc i) (next xs)))) - ret)) - -(defn int-array - ([size-or-seq] - (if (number? size-or-seq) - (int-array size-or-seq nil) - (into-array size-or-seq))) - ([size init-val-or-seq] - (let [a (make-array size)] - (if (seq? init-val-or-seq) - (let [s (seq init-val-or-seq)] - (loop [i 0 s s] - (if (and s (< i size)) - (do - (aset a i (first s)) - (recur (inc i) (next s))) - a))) - (do - (dotimes [i size] - (aset a i init-val-or-seq)) - a))))) - -(defn long-array - ([size-or-seq] - (if (number? size-or-seq) - (long-array size-or-seq nil) - (into-array size-or-seq))) - ([size init-val-or-seq] - (let [a (make-array size)] - (if (seq? init-val-or-seq) - (let [s (seq init-val-or-seq)] - (loop [i 0 s s] - (if (and s (< i size)) - (do - (aset a i (first s)) - (recur (inc i) (next s))) - a))) - (do - (dotimes [i size] - (aset a i init-val-or-seq)) - a))))) - -(defn double-array - ([size-or-seq] - (if (number? size-or-seq) - (double-array size-or-seq nil) - (into-array size-or-seq))) - ([size init-val-or-seq] - (let [a (make-array size)] - (if (seq? init-val-or-seq) - (let [s (seq init-val-or-seq)] - (loop [i 0 s s] - (if (and s (< i size)) - (do - (aset a i (first s)) - (recur (inc i) (next s))) - a))) - (do - (dotimes [i size] - (aset a i init-val-or-seq)) - a))))) - -(defn object-array - ([size-or-seq] - (if (number? size-or-seq) - (object-array size-or-seq nil) - (into-array size-or-seq))) - ([size init-val-or-seq] - (let [a (make-array size)] - (if (seq? init-val-or-seq) - (let [s (seq init-val-or-seq)] - (loop [i 0 s s] - (if (and s (< i size)) - (do - (aset a i (first s)) - (recur (inc i) (next s))) - a))) - (do - (dotimes [i size] - (aset a i init-val-or-seq)) - a))))) - -(defn- bounded-count [s n] - (if (counted? s) - (count s) - (loop [s s i n sum 0] - (if (and (pos? i) (seq s)) - (recur (next s) (dec i) (inc sum)) - sum)))) - -(defn spread - [arglist] - (cond - (nil? arglist) nil - (nil? (next arglist)) (seq (first arglist)) - :else (cons (first arglist) - (spread (next arglist))))) - -(defn concat - "Returns a lazy seq representing the concatenation of the elements in the supplied colls." - ([] (lazy-seq nil)) - ([x] (lazy-seq x)) - ([x y] - (lazy-seq - (let [s (seq x)] - (if s - (if (chunked-seq? s) - (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) - (cons (first s) (concat (rest s) y))) - y)))) - ([x y & zs] - (let [cat (fn cat [xys zs] - (lazy-seq - (let [xys (seq xys)] - (if xys - (if (chunked-seq? xys) - (chunk-cons (chunk-first xys) - (cat (chunk-rest xys) zs)) - (cons (first xys) (cat (rest xys) zs))) - (when zs - (cat (first zs) (next zs)))))))] - (cat (concat x y) zs)))) - -(defn list* - "Creates a new list containing the items prepended to the rest, the - last of which will be treated as a sequence." - ([args] (seq args)) - ([a args] (cons a args)) - ([a b args] (cons a (cons b args))) - ([a b c args] (cons a (cons b (cons c args)))) - ([a b c d & more] - (cons a (cons b (cons c (cons d (spread more))))))) - - -;;; Transients - -(defn transient - "Returns a new, transient version of the collection, in constant time." - [coll] - (-as-transient coll)) - -(defn persistent! - "Returns a new, persistent version of the transient collection, in - constant time. The transient collection cannot be used after this - call, any such use will throw an exception." - [tcoll] - (-persistent! tcoll)) - -(defn conj! - "Adds x to the transient collection, and return coll. The 'addition' - may happen at different 'places' depending on the concrete type." - ([tcoll val] - (-conj! tcoll val)) - ([tcoll val & vals] - (let [ntcoll (-conj! tcoll val)] - (if vals - (recur ntcoll (first vals) (next vals)) - ntcoll)))) - -(defn assoc! - "When applied to a transient map, adds mapping of key(s) to - val(s). When applied to a transient vector, sets the val at index. - Note - index must be <= (count vector). Returns coll." - ([tcoll key val] - (-assoc! tcoll key val)) - ([tcoll key val & kvs] - (let [ntcoll (-assoc! tcoll key val)] - (if kvs - (recur ntcoll (first kvs) (second kvs) (nnext kvs)) - ntcoll)))) - -(defn dissoc! - "Returns a transient map that doesn't contain a mapping for key(s)." - ([tcoll key] - (-dissoc! tcoll key)) - ([tcoll key & ks] - (let [ntcoll (-dissoc! tcoll key)] - (if ks - (recur ntcoll (first ks) (next ks)) - ntcoll)))) - -(defn pop! - "Removes the last item from a transient vector. If - the collection is empty, throws an exception. Returns coll" - [tcoll] - (-pop! tcoll)) - -(defn disj! - "disj[oin]. Returns a transient set of the same (hashed/sorted) type, that - does not contain key(s)." - ([tcoll val] - (-disjoin! tcoll val)) - ([tcoll val & vals] - (let [ntcoll (-disjoin! tcoll val)] - (if vals - (recur ntcoll (first vals) (next vals)) - ntcoll)))) - - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; apply ;;;;;;;;;;;;;;;; - -;; see core.clj -(gen-apply-to) - -(set! *unchecked-if* true) -(defn apply - "Applies fn f to the argument list formed by prepending intervening arguments to args. - First cut. Not lazy. Needs to use emitted toApply." - ([f args] - (let [fixed-arity (.-cljs$lang$maxFixedArity f)] - (if (.-cljs$lang$applyTo f) - (let [bc (bounded-count args (inc fixed-arity))] - (if (<= bc fixed-arity) - (apply-to f bc args) - (.cljs$lang$applyTo f args))) - (.apply f f (to-array args))))) - ([f x args] - (let [arglist (list* x args) - fixed-arity (.-cljs$lang$maxFixedArity f)] - (if (.-cljs$lang$applyTo f) - (let [bc (bounded-count arglist (inc fixed-arity))] - (if (<= bc fixed-arity) - (apply-to f bc arglist) - (.cljs$lang$applyTo f arglist))) - (.apply f f (to-array arglist))))) - ([f x y args] - (let [arglist (list* x y args) - fixed-arity (.-cljs$lang$maxFixedArity f)] - (if (.-cljs$lang$applyTo f) - (let [bc (bounded-count arglist (inc fixed-arity))] - (if (<= bc fixed-arity) - (apply-to f bc arglist) - (.cljs$lang$applyTo f arglist))) - (.apply f f (to-array arglist))))) - ([f x y z args] - (let [arglist (list* x y z args) - fixed-arity (.-cljs$lang$maxFixedArity f)] - (if (.-cljs$lang$applyTo f) - (let [bc (bounded-count arglist (inc fixed-arity))] - (if (<= bc fixed-arity) - (apply-to f bc arglist) - (.cljs$lang$applyTo f arglist))) - (.apply f f (to-array arglist))))) - ([f a b c d & args] - (let [arglist (cons a (cons b (cons c (cons d (spread args))))) - fixed-arity (.-cljs$lang$maxFixedArity f)] - (if (.-cljs$lang$applyTo f) - (let [bc (bounded-count arglist (inc fixed-arity))] - (if (<= bc fixed-arity) - (apply-to f bc arglist) - (.cljs$lang$applyTo f arglist))) - (.apply f f (to-array arglist)))))) -(set! *unchecked-if* false) - -(defn vary-meta - "Returns an object of the same type and value as obj, with - (apply f (meta obj) args) as its metadata." - ([obj f] - (with-meta obj (f (meta obj)))) - ([obj f a] - (with-meta obj (f (meta obj) a))) - ([obj f a b] - (with-meta obj (f (meta obj) a b))) - ([obj f a b c] - (with-meta obj (f (meta obj) a b c))) - ([obj f a b c d] - (with-meta obj (f (meta obj) a b c d))) - ([obj f a b c d & args] - (with-meta obj (apply f (meta obj) a b c d args)))) - -(defn ^boolean not= - "Same as (not (= obj1 obj2))" - ([x] false) - ([x y] (not (= x y))) - ([x y & more] - (not (apply = x y more)))) - -(defn not-empty - "If coll is empty, returns nil, else coll" - [coll] (when (seq coll) coll)) - -(defn ^boolean every? - "Returns true if (pred x) is logical true for every x in coll, else - false." - [pred coll] - (cond - (nil? (seq coll)) true - (pred (first coll)) (recur pred (next coll)) - :else false)) - -(defn ^boolean not-every? - "Returns false if (pred x) is logical true for every x in - coll, else true." - [pred coll] (not (every? pred coll))) - -(defn some - "Returns the first logical true value of (pred x) for any x in coll, - else nil. One common idiom is to use a set as pred, for example - this will return :fred if :fred is in the sequence, otherwise nil: - (some #{:fred} coll)" - [pred coll] - (when (seq coll) - (or (pred (first coll)) (recur pred (next coll))))) - -(defn ^boolean not-any? - "Returns false if (pred x) is logical true for any x in coll, - else true." - [pred coll] (not (some pred coll))) - -(defn ^boolean even? - "Returns true if n is even, throws an exception if n is not an integer" - [n] (if (integer? n) - (zero? (bit-and n 1)) - (throw (js/Error. (str "Argument must be an integer: " n))))) - -(defn ^boolean odd? - "Returns true if n is odd, throws an exception if n is not an integer" - [n] (not (even? n))) - -(defn identity [x] x) - -(defn ^boolean complement - "Takes a fn f and returns a fn that takes the same arguments as f, - has the same effects, if any, and returns the opposite truth value." - [f] - (fn - ([] (not (f))) - ([x] (not (f x))) - ([x y] (not (f x y))) - ([x y & zs] (not (apply f x y zs))))) - -(defn constantly - "Returns a function that takes any number of arguments and returns x." - [x] (fn [& args] x)) - -(defn comp - "Takes a set of functions and returns a fn that is the composition - of those fns. The returned fn takes a variable number of args, - applies the rightmost of fns to the args, the next - fn (right-to-left) to the result, etc." - ([] identity) - ([f] f) - ([f g] - (fn - ([] (f (g))) - ([x] (f (g x))) - ([x y] (f (g x y))) - ([x y z] (f (g x y z))) - ([x y z & args] (f (apply g x y z args))))) - ([f g h] - (fn - ([] (f (g (h)))) - ([x] (f (g (h x)))) - ([x y] (f (g (h x y)))) - ([x y z] (f (g (h x y z)))) - ([x y z & args] (f (g (apply h x y z args)))))) - ([f1 f2 f3 & fs] - (let [fs (reverse (list* f1 f2 f3 fs))] - (fn [& args] - (loop [ret (apply (first fs) args) fs (next fs)] - (if fs - (recur ((first fs) ret) (next fs)) - ret)))))) - -(defn partial - "Takes a function f and fewer than the normal arguments to f, and - returns a fn that takes a variable number of additional args. When - called, the returned function calls f with args + additional args." - ([f] f) - ([f arg1] - (fn [& args] (apply f arg1 args))) - ([f arg1 arg2] - (fn [& args] (apply f arg1 arg2 args))) - ([f arg1 arg2 arg3] - (fn [& args] (apply f arg1 arg2 arg3 args))) - ([f arg1 arg2 arg3 & more] - (fn [& args] (apply f arg1 arg2 arg3 (concat more args))))) - -(defn fnil - "Takes a function f, and returns a function that calls f, replacing - a nil first argument to f with the supplied value x. Higher arity - versions can replace arguments in the second and third - positions (y, z). Note that the function f can take any number of - arguments, not just the one(s) being nil-patched." - ([f x] - (fn - ([a] (f (if (nil? a) x a))) - ([a b] (f (if (nil? a) x a) b)) - ([a b c] (f (if (nil? a) x a) b c)) - ([a b c & ds] (apply f (if (nil? a) x a) b c ds)))) - ([f x y] - (fn - ([a b] (f (if (nil? a) x a) (if (nil? b) y b))) - ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) c)) - ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) c ds)))) - ([f x y z] - (fn - ([a b] (f (if (nil? a) x a) (if (nil? b) y b))) - ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c))) - ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c) ds))))) - -(defn map-indexed - "Returns a lazy sequence consisting of the result of applying f to 0 - and the first item of coll, followed by applying f to 1 and the second - item in coll, etc, until coll is exhausted. Thus function f should - accept 2 arguments, index and item." - [f coll] - (letfn [(mapi [idx coll] - (lazy-seq - (when-let [s (seq coll)] - (if (chunked-seq? s) - (let [c (chunk-first s) - size (count c) - b (chunk-buffer size)] - (dotimes [i size] - (chunk-append b (f (+ idx i) (-nth c i)))) - (chunk-cons (chunk b) (mapi (+ idx size) (chunk-rest s)))) - (cons (f idx (first s)) (mapi (inc idx) (rest s)))))))] - (mapi 0 coll))) - -(defn keep - "Returns a lazy sequence of the non-nil results of (f item). Note, - this means false return values will be included. f must be free of - side-effects." - ([f coll] - (lazy-seq - (when-let [s (seq coll)] - (if (chunked-seq? s) - (let [c (chunk-first s) - size (count c) - b (chunk-buffer size)] - (dotimes [i size] - (let [x (f (-nth c i))] - (when-not (nil? x) - (chunk-append b x)))) - (chunk-cons (chunk b) (keep f (chunk-rest s)))) - (let [x (f (first s))] - (if (nil? x) - (keep f (rest s)) - (cons x (keep f (rest s)))))))))) - -(defn keep-indexed - "Returns a lazy sequence of the non-nil results of (f index item). Note, - this means false return values will be included. f must be free of - side-effects." - ([f coll] - (letfn [(keepi [idx coll] - (lazy-seq - (when-let [s (seq coll)] - (if (chunked-seq? s) - (let [c (chunk-first s) - size (count c) - b (chunk-buffer size)] - (dotimes [i size] - (let [x (f (+ idx i) (-nth c i))] - (when-not (nil? x) - (chunk-append b x)))) - (chunk-cons (chunk b) (keepi (+ idx size) (chunk-rest s)))) - (let [x (f idx (first s))] - (if (nil? x) - (keepi (inc idx) (rest s)) - (cons x (keepi (inc idx) (rest s)))))))))] - (keepi 0 coll)))) - -(defn every-pred - "Takes a set of predicates and returns a function f that returns true if all of its - composing predicates return a logical true value against all of its arguments, else it returns - false. Note that f is short-circuiting in that it will stop execution on the first - argument that triggers a logical false result against the original predicates." - ([p] - (fn ep1 - ([] true) - ([x] (boolean (p x))) - ([x y] (boolean (and (p x) (p y)))) - ([x y z] (boolean (and (p x) (p y) (p z)))) - ([x y z & args] (boolean (and (ep1 x y z) - (every? p args)))))) - ([p1 p2] - (fn ep2 - ([] true) - ([x] (boolean (and (p1 x) (p2 x)))) - ([x y] (boolean (and (p1 x) (p1 y) (p2 x) (p2 y)))) - ([x y z] (boolean (and (p1 x) (p1 y) (p1 z) (p2 x) (p2 y) (p2 z)))) - ([x y z & args] (boolean (and (ep2 x y z) - (every? #(and (p1 %) (p2 %)) args)))))) - ([p1 p2 p3] - (fn ep3 - ([] true) - ([x] (boolean (and (p1 x) (p2 x) (p3 x)))) - ([x y] (boolean (and (p1 x) (p2 x) (p3 x) (p1 y) (p2 y) (p3 y)))) - ([x y z] (boolean (and (p1 x) (p2 x) (p3 x) (p1 y) (p2 y) (p3 y) (p1 z) (p2 z) (p3 z)))) - ([x y z & args] (boolean (and (ep3 x y z) - (every? #(and (p1 %) (p2 %) (p3 %)) args)))))) - ([p1 p2 p3 & ps] - (let [ps (list* p1 p2 p3 ps)] - (fn epn - ([] true) - ([x] (every? #(% x) ps)) - ([x y] (every? #(and (% x) (% y)) ps)) - ([x y z] (every? #(and (% x) (% y) (% z)) ps)) - ([x y z & args] (boolean (and (epn x y z) - (every? #(every? % args) ps)))))))) - -(defn some-fn - "Takes a set of predicates and returns a function f that returns the first logical true value - returned by one of its composing predicates against any of its arguments, else it returns - logical false. Note that f is short-circuiting in that it will stop execution on the first - argument that triggers a logical true result against the original predicates." - ([p] - (fn sp1 - ([] nil) - ([x] (p x)) - ([x y] (or (p x) (p y))) - ([x y z] (or (p x) (p y) (p z))) - ([x y z & args] (or (sp1 x y z) - (some p args))))) - ([p1 p2] - (fn sp2 - ([] nil) - ([x] (or (p1 x) (p2 x))) - ([x y] (or (p1 x) (p1 y) (p2 x) (p2 y))) - ([x y z] (or (p1 x) (p1 y) (p1 z) (p2 x) (p2 y) (p2 z))) - ([x y z & args] (or (sp2 x y z) - (some #(or (p1 %) (p2 %)) args))))) - ([p1 p2 p3] - (fn sp3 - ([] nil) - ([x] (or (p1 x) (p2 x) (p3 x))) - ([x y] (or (p1 x) (p2 x) (p3 x) (p1 y) (p2 y) (p3 y))) - ([x y z] (or (p1 x) (p2 x) (p3 x) (p1 y) (p2 y) (p3 y) (p1 z) (p2 z) (p3 z))) - ([x y z & args] (or (sp3 x y z) - (some #(or (p1 %) (p2 %) (p3 %)) args))))) - ([p1 p2 p3 & ps] - (let [ps (list* p1 p2 p3 ps)] - (fn spn - ([] nil) - ([x] (some #(% x) ps)) - ([x y] (some #(or (% x) (% y)) ps)) - ([x y z] (some #(or (% x) (% y) (% z)) ps)) - ([x y z & args] (or (spn x y z) - (some #(some % args) ps))))))) - -(defn map - "Returns a lazy sequence consisting of the result of applying f to the - set of first items of each coll, followed by applying f to the set - of second items in each coll, until any one of the colls is - exhausted. Any remaining items in other colls are ignored. Function - f should accept number-of-colls arguments." - ([f coll] - (lazy-seq - (when-let [s (seq coll)] - (if (chunked-seq? s) - (let [c (chunk-first s) - size (count c) - b (chunk-buffer size)] - (dotimes [i size] - (chunk-append b (f (-nth c i)))) - (chunk-cons (chunk b) (map f (chunk-rest s)))) - (cons (f (first s)) (map f (rest s))))))) - ([f c1 c2] - (lazy-seq - (let [s1 (seq c1) s2 (seq c2)] - (when (and s1 s2) - (cons (f (first s1) (first s2)) - (map f (rest s1) (rest s2))))))) - ([f c1 c2 c3] - (lazy-seq - (let [s1 (seq c1) s2 (seq c2) s3 (seq c3)] - (when (and s1 s2 s3) - (cons (f (first s1) (first s2) (first s3)) - (map f (rest s1) (rest s2) (rest s3))))))) - ([f c1 c2 c3 & colls] - (let [step (fn step [cs] - (lazy-seq - (let [ss (map seq cs)] - (when (every? identity ss) - (cons (map first ss) (step (map rest ss)))))))] - (map #(apply f %) (step (conj colls c3 c2 c1)))))) - -(defn take - "Returns a lazy sequence of the first n items in coll, or all items if - there are fewer than n." - [n coll] - (lazy-seq - (when (pos? n) - (when-let [s (seq coll)] - (cons (first s) (take (dec n) (rest s))))))) - -(defn drop - "Returns a lazy sequence of all but the first n items in coll." - [n coll] - (let [step (fn [n coll] - (let [s (seq coll)] - (if (and (pos? n) s) - (recur (dec n) (rest s)) - s)))] - (lazy-seq (step n coll)))) - -(defn drop-last - "Return a lazy sequence of all but the last n (default 1) items in coll" - ([s] (drop-last 1 s)) - ([n s] (map (fn [x _] x) s (drop n s)))) - -(defn take-last - "Returns a seq of the last n items in coll. Depending on the type - of coll may be no better than linear time. For vectors, see also subvec." - [n coll] - (loop [s (seq coll), lead (seq (drop n coll))] - (if lead - (recur (next s) (next lead)) - s))) - -(defn drop-while - "Returns a lazy sequence of the items in coll starting from the first - item for which (pred item) returns nil." - [pred coll] - (let [step (fn [pred coll] - (let [s (seq coll)] - (if (and s (pred (first s))) - (recur pred (rest s)) - s)))] - (lazy-seq (step pred coll)))) - -(defn cycle - "Returns a lazy (infinite!) sequence of repetitions of the items in coll." - [coll] (lazy-seq - (when-let [s (seq coll)] - (concat s (cycle s))))) - -(defn split-at - "Returns a vector of [(take n coll) (drop n coll)]" - [n coll] - [(take n coll) (drop n coll)]) - -(defn repeat - "Returns a lazy (infinite!, or length n if supplied) sequence of xs." - ([x] (lazy-seq (cons x (repeat x)))) - ([n x] (take n (repeat x)))) - -(defn replicate - "Returns a lazy seq of n xs." - [n x] (take n (repeat x))) - -(defn repeatedly - "Takes a function of no args, presumably with side effects, and - returns an infinite (or length n if supplied) lazy sequence of calls - to it" - ([f] (lazy-seq (cons (f) (repeatedly f)))) - ([n f] (take n (repeatedly f)))) - -(defn iterate - "Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects" - {:added "1.0"} - [f x] (cons x (lazy-seq (iterate f (f x))))) - -(defn interleave - "Returns a lazy seq of the first item in each coll, then the second etc." - ([c1 c2] - (lazy-seq - (let [s1 (seq c1) s2 (seq c2)] - (when (and s1 s2) - (cons (first s1) (cons (first s2) - (interleave (rest s1) (rest s2)))))))) - ([c1 c2 & colls] - (lazy-seq - (let [ss (map seq (conj colls c2 c1))] - (when (every? identity ss) - (concat (map first ss) (apply interleave (map rest ss)))))))) - -(defn interpose - "Returns a lazy seq of the elements of coll separated by sep" - [sep coll] (drop 1 (interleave (repeat sep) coll))) - - - -(defn- flatten1 - "Take a collection of collections, and return a lazy seq - of items from the inner collection" - [colls] - (let [cat (fn cat [coll colls] - (lazy-seq - (if-let [coll (seq coll)] - (cons (first coll) (cat (rest coll) colls)) - (when (seq colls) - (cat (first colls) (rest colls))))))] - (cat nil colls))) - -(defn mapcat - "Returns the result of applying concat to the result of applying map - to f and colls. Thus function f should return a collection." - ([f coll] - (flatten1 (map f coll))) - ([f coll & colls] - (flatten1 (apply map f coll colls)))) - -(defn filter - "Returns a lazy sequence of the items in coll for which - (pred item) returns true. pred must be free of side-effects." - ([pred coll] - (lazy-seq - (when-let [s (seq coll)] - (if (chunked-seq? s) - (let [c (chunk-first s) - size (count c) - b (chunk-buffer size)] - (dotimes [i size] - (when (pred (-nth c i)) - (chunk-append b (-nth c i)))) - (chunk-cons (chunk b) (filter pred (chunk-rest s)))) - (let [f (first s) r (rest s)] - (if (pred f) - (cons f (filter pred r)) - (filter pred r)))))))) - -(defn remove - "Returns a lazy sequence of the items in coll for which - (pred item) returns false. pred must be free of side-effects." - [pred coll] - (filter (complement pred) coll)) - -(defn tree-seq - "Returns a lazy sequence of the nodes in a tree, via a depth-first walk. - branch? must be a fn of one arg that returns true if passed a node - that can have children (but may not). children must be a fn of one - arg that returns a sequence of the children. Will only be called on - nodes for which branch? returns true. Root is the root node of the - tree." - [branch? children root] - (let [walk (fn walk [node] - (lazy-seq - (cons node - (when (branch? node) - (mapcat walk (children node))))))] - (walk root))) - -(defn flatten - "Takes any nested combination of sequential things (lists, vectors, - etc.) and returns their contents as a single, flat sequence. - (flatten nil) returns nil." - [x] - (filter #(not (sequential? %)) - (rest (tree-seq sequential? seq x)))) - -(defn into - "Returns a new coll consisting of to-coll with all of the items of - from-coll conjoined." - [to from] - (if-not (nil? to) - (if (implements? IEditableCollection to) - (persistent! (reduce -conj! (transient to) from)) - (reduce -conj to from)) - (reduce conj () from))) - -(defn mapv - "Returns a vector consisting of the result of applying f to the - set of first items of each coll, followed by applying f to the set - of second items in each coll, until any one of the colls is - exhausted. Any remaining items in other colls are ignored. Function - f should accept number-of-colls arguments." - ([f coll] - (-> (reduce (fn [v o] (conj! v (f o))) (transient []) coll) - persistent!)) - ([f c1 c2] - (into [] (map f c1 c2))) - ([f c1 c2 c3] - (into [] (map f c1 c2 c3))) - ([f c1 c2 c3 & colls] - (into [] (apply map f c1 c2 c3 colls)))) - -(defn filterv - "Returns a vector of the items in coll for which - (pred item) returns true. pred must be free of side-effects." - [pred coll] - (-> (reduce (fn [v o] (if (pred o) (conj! v o) v)) - (transient []) - coll) - persistent!)) - -(defn partition - "Returns a lazy sequence of lists of n items each, at offsets step - apart. If step is not supplied, defaults to n, i.e. the partitions - do not overlap. If a pad collection is supplied, use its elements as - necessary to complete last partition upto n items. In case there are - not enough padding elements, return a partition with less than n items." - ([n coll] - (partition n n coll)) - ([n step coll] - (lazy-seq - (when-let [s (seq coll)] - (let [p (take n s)] - (when (== n (count p)) - (cons p (partition n step (drop step s)))))))) - ([n step pad coll] - (lazy-seq - (when-let [s (seq coll)] - (let [p (take n s)] - (if (== n (count p)) - (cons p (partition n step pad (drop step s))) - (list (take n (concat p pad))))))))) - -(defn get-in - "Returns the value in a nested associative structure, - where ks is a sequence of keys. Returns nil if the key is not present, - or the not-found value if supplied." - {:added "1.2" - :static true} - ([m ks] - (get-in m ks nil)) - ([m ks not-found] - (loop [sentinel lookup-sentinel - m m - ks (seq ks)] - (if ks - (if (not (satisfies? ILookup m)) - not-found - (let [m (get m (first ks) sentinel)] - (if (identical? sentinel m) - not-found - (recur sentinel m (next ks))))) - m)))) - -(defn assoc-in - "Associates a value in a nested associative structure, where ks is a - sequence of keys and v is the new value and returns a new nested structure. - If any levels do not exist, hash-maps will be created." - [m [k & ks] v] - (if ks - (assoc m k (assoc-in (get m k) ks v)) - (assoc m k v))) - -(defn update-in - "'Updates' a value in a nested associative structure, where ks is a - sequence of keys and f is a function that will take the old value - and any supplied args and return the new value, and returns a new - nested structure. If any levels do not exist, hash-maps will be - created." - ([m [k & ks] f] - (if ks - (assoc m k (update-in (get m k) ks f)) - (assoc m k (f (get m k))))) - ([m [k & ks] f a] - (if ks - (assoc m k (update-in (get m k) ks f a)) - (assoc m k (f (get m k) a)))) - ([m [k & ks] f a b] - (if ks - (assoc m k (update-in (get m k) ks f a b)) - (assoc m k (f (get m k) a b)))) - ([m [k & ks] f a b c] - (if ks - (assoc m k (update-in (get m k) ks f a b c)) - (assoc m k (f (get m k) a b c)))) - ([m [k & ks] f a b c & args] - (if ks - (assoc m k (apply update-in (get m k) ks f a b c args)) - (assoc m k (apply f (get m k) a b c args))))) - -;;; PersistentVector - -(deftype VectorNode [edit arr]) - -(defn- pv-fresh-node [edit] - (VectorNode. edit (make-array 32))) - -(defn- pv-aget [node idx] - (aget (.-arr node) idx)) - -(defn- pv-aset [node idx val] - (aset (.-arr node) idx val)) - -(defn- pv-clone-node [node] - (VectorNode. (.-edit node) (aclone (.-arr node)))) - -(defn- tail-off [pv] - (let [cnt (.-cnt pv)] - (if (< cnt 32) - 0 - (bit-shift-left (bit-shift-right-zero-fill (dec cnt) 5) 5)))) - -(defn- new-path [edit level node] - (loop [ll level - ret node] - (if (zero? ll) - ret - (let [embed ret - r (pv-fresh-node edit) - _ (pv-aset r 0 embed)] - (recur (- ll 5) r))))) - -(defn- push-tail [pv level parent tailnode] - (let [ret (pv-clone-node parent) - subidx (bit-and (bit-shift-right-zero-fill (dec (.-cnt pv)) level) 0x01f)] - (if (== 5 level) - (do - (pv-aset ret subidx tailnode) - ret) - (let [child (pv-aget parent subidx)] - (if-not (nil? child) - (let [node-to-insert (push-tail pv (- level 5) child tailnode)] - (pv-aset ret subidx node-to-insert) - ret) - (let [node-to-insert (new-path nil (- level 5) tailnode)] - (pv-aset ret subidx node-to-insert) - ret)))))) - -(defn- vector-index-out-of-bounds [i cnt] - (throw (js/Error. (str "No item " i " in vector of length " cnt)))) - -(defn- first-array-for-longvec [pv] - ;; invariants: (count pv) > 32. - (loop [node (.-root pv) - level (.-shift pv)] - (if (pos? level) - (recur (pv-aget node 0) (- level 5)) - (.-arr node)))) - -(defn- unchecked-array-for [pv i] - ;; invariant: i is a valid index of pv (use array-for if unknown). - (if (>= i (tail-off pv)) - (.-tail pv) - (loop [node (.-root pv) - level (.-shift pv)] - (if (pos? level) - (recur (pv-aget node (bit-and (bit-shift-right-zero-fill i level) 0x01f)) - (- level 5)) - (.-arr node))))) - -(defn- array-for [pv i] - (if (and (<= 0 i) (< i (.-cnt pv))) - (unchecked-array-for pv i) - (vector-index-out-of-bounds i (.-cnt pv)))) - -(defn- do-assoc [pv level node i val] - (let [ret (pv-clone-node node)] - (if (zero? level) - (do - (pv-aset ret (bit-and i 0x01f) val) - ret) - (let [subidx (bit-and (bit-shift-right-zero-fill i level) 0x01f)] - (pv-aset ret subidx (do-assoc pv (- level 5) (pv-aget node subidx) i val)) - ret)))) - -(defn- pop-tail [pv level node] - (let [subidx (bit-and (bit-shift-right-zero-fill (- (.-cnt pv) 2) level) 0x01f)] - (cond - (> level 5) (let [new-child (pop-tail pv (- level 5) (pv-aget node subidx))] - (if (and (nil? new-child) (zero? subidx)) - nil - (let [ret (pv-clone-node node)] - (pv-aset ret subidx new-child) - ret))) - (zero? subidx) nil - :else (let [ret (pv-clone-node node)] - (pv-aset ret subidx nil) - ret)))) - -(declare tv-editable-root tv-editable-tail TransientVector deref - pr-sequential-writer pr-writer chunked-seq) - -(deftype PersistentVector [meta cnt shift root tail ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - ICloneable - (-clone [_] (PersistentVector. meta cnt shift root tail __hash)) - - IWithMeta - (-with-meta [coll meta] (PersistentVector. meta cnt shift root tail __hash)) - - IMeta - (-meta [coll] meta) - - IStack - (-peek [coll] - (when (> cnt 0) - (-nth coll (dec cnt)))) - (-pop [coll] - (cond - (zero? cnt) (throw (js/Error. "Can't pop empty vector")) - (== 1 cnt) (-with-meta cljs.core.PersistentVector.EMPTY meta) - (< 1 (- cnt (tail-off coll))) - (PersistentVector. meta (dec cnt) shift root (.slice tail 0 -1) nil) - :else (let [new-tail (unchecked-array-for coll (- cnt 2)) - nr (pop-tail coll shift root) - new-root (if (nil? nr) cljs.core.PersistentVector.EMPTY_NODE nr) - cnt-1 (dec cnt)] - (if (and (< 5 shift) (nil? (pv-aget new-root 1))) - (PersistentVector. meta cnt-1 (- shift 5) (pv-aget new-root 0) new-tail nil) - (PersistentVector. meta cnt-1 shift new-root new-tail nil))))) - - ICollection - (-conj [coll o] - (if (< (- cnt (tail-off coll)) 32) - (let [len (alength tail) - new-tail (make-array (inc len))] - (dotimes [i len] - (aset new-tail i (aget tail i))) - (aset new-tail len o) - (PersistentVector. meta (inc cnt) shift root new-tail nil)) - (let [root-overflow? (> (bit-shift-right-zero-fill cnt 5) (bit-shift-left 1 shift)) - new-shift (if root-overflow? (+ shift 5) shift) - new-root (if root-overflow? - (let [n-r (pv-fresh-node nil)] - (pv-aset n-r 0 root) - (pv-aset n-r 1 (new-path nil shift (VectorNode. nil tail))) - n-r) - (push-tail coll shift root (VectorNode. nil tail)))] - (PersistentVector. meta (inc cnt) new-shift new-root (array o) nil)))) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.PersistentVector.EMPTY meta)) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - ISeqable - (-seq [coll] - (cond - (zero? cnt) nil - (<= cnt 32) (IndexedSeq. tail 0) - :else (chunked-seq coll (first-array-for-longvec coll) 0 0))) - - ICounted - (-count [coll] cnt) - - IIndexed - (-nth [coll n] - (aget (array-for coll n) (bit-and n 0x01f))) - (-nth [coll n not-found] - (if (and (<= 0 n) (< n cnt)) - (aget (unchecked-array-for coll n) (bit-and n 0x01f)) - not-found)) - - ILookup - (-lookup [coll k] (-lookup coll k nil)) - (-lookup [coll k not-found] (if (number? k) - (-nth coll k not-found) - not-found)) - - IMapEntry - (-key [coll] - (-nth coll 0)) - (-val [coll] - (-nth coll 1)) - - IAssociative - (-assoc [coll k v] - (if (number? k) - (-assoc-n coll k v) - (throw (js/Error. "Vector's key for assoc must be a number.")))) - - IVector - (-assoc-n [coll n val] - (cond - (and (<= 0 n) (< n cnt)) - (if (<= (tail-off coll) n) - (let [new-tail (aclone tail)] - (aset new-tail (bit-and n 0x01f) val) - (PersistentVector. meta cnt shift root new-tail nil)) - (PersistentVector. meta cnt shift (do-assoc coll shift root n val) tail nil)) - (== n cnt) (-conj coll val) - :else (throw (js/Error. (str "Index " n " out of bounds [0," cnt "]"))))) - - IReduce - (-reduce [v f] - (ci-reduce v f)) - (-reduce [v f start] - (ci-reduce v f start)) - - IKVReduce - (-kv-reduce [v f init] - (let [step-init (array 0 init)] ; [step 0 init init] - (loop [i 0] - (if (< i cnt) - (let [arr (unchecked-array-for v i) - len (alength arr)] - (let [init (loop [j 0 init (aget step-init 1)] - (if (< j len) - (let [init (f init (+ j i) (aget arr j))] - (if (reduced? init) - init - (recur (inc j) init))) - (do (aset step-init 0 len) - (aset step-init 1 init) - init)))] - (if (reduced? init) - @init - (recur (+ i (aget step-init 0)))))) - (aget step-init 1))))) - - IFn - (-invoke [coll k] - (-nth coll k)) - (-invoke [coll k not-found] - (-nth coll k not-found)) - - IEditableCollection - (-as-transient [coll] - (TransientVector. cnt shift (tv-editable-root root) (tv-editable-tail tail))) - - IReversible - (-rseq [coll] - (if (pos? cnt) - (RSeq. coll (dec cnt) nil)))) - -(set! cljs.core.PersistentVector.EMPTY_NODE (VectorNode. nil (make-array 32))) - -(set! cljs.core.PersistentVector.EMPTY - (PersistentVector. nil 0 5 cljs.core.PersistentVector.EMPTY_NODE (array) 0)) - -(set! cljs.core.PersistentVector.fromArray - (fn [xs ^boolean no-clone] - (let [l (alength xs) - xs (if no-clone xs (aclone xs))] - (if (< l 32) - (PersistentVector. nil l 5 cljs.core.PersistentVector.EMPTY_NODE xs nil) - (let [node (.slice xs 0 32) - v (PersistentVector. nil 32 5 cljs.core.PersistentVector.EMPTY_NODE node nil)] - (loop [i 32 out (-as-transient v)] - (if (< i l) - (recur (inc i) (conj! out (aget xs i))) - (persistent! out)))))))) - -(defn vec [coll] - (-persistent! - (reduce -conj! - (-as-transient cljs.core.PersistentVector.EMPTY) - coll))) - -(defn vector [& args] - (if (and (instance? IndexedSeq args) (zero? (.-i args))) - (cljs.core.PersistentVector.fromArray (.-arr args) true) - (vec args))) - -(declare subvec) - -(deftype ChunkedSeq [vec node i off meta ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - IWithMeta - (-with-meta [coll m] - (chunked-seq vec node i off m)) - (-meta [coll] meta) - - ISeqable - (-seq [coll] coll) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - ASeq - ISeq - (-first [coll] - (aget node off)) - (-rest [coll] - (if (< (inc off) (alength node)) - (let [s (chunked-seq vec node i (inc off))] - (if (nil? s) - () - s)) - (-chunked-rest coll))) - - INext - (-next [coll] - (if (< (inc off) (alength node)) - (let [s (chunked-seq vec node i (inc off))] - (if (nil? s) - nil - s)) - (-chunked-next coll))) - - ICollection - (-conj [coll o] - (cons o coll)) - - IEmptyableCollection - (-empty [coll] - (with-meta cljs.core.PersistentVector.EMPTY meta)) - - IChunkedSeq - (-chunked-first [coll] - (array-chunk node off)) - (-chunked-rest [coll] - (let [end (+ i (alength node))] - (if (< end (-count vec)) - (chunked-seq vec (unchecked-array-for vec end) end 0) - ()))) - - IChunkedNext - (-chunked-next [coll] - (let [end (+ i (alength node))] - (when (< end (-count vec)) - (chunked-seq vec (unchecked-array-for vec end) end 0)))) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - IReduce - (-reduce [coll f] - (ci-reduce (subvec vec (+ i off) (count vec)) f)) - - (-reduce [coll f start] - (ci-reduce (subvec vec (+ i off) (count vec)) f start))) - -(defn chunked-seq - ([vec i off] (ChunkedSeq. vec (array-for vec i) i off nil nil)) - ([vec node i off] (ChunkedSeq. vec node i off nil nil)) - ([vec node i off meta] - (ChunkedSeq. vec node i off meta nil))) - -(declare build-subvec) - -(deftype Subvec [meta v start end ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - ICloneable - (-clone [_] (Subvec. meta v start end __hash)) - - IWithMeta - (-with-meta [coll meta] (build-subvec meta v start end __hash)) - - IMeta - (-meta [coll] meta) - - IStack - (-peek [coll] - (-nth v (dec end))) - (-pop [coll] - (if (== start end) - (throw (js/Error. "Can't pop empty vector")) - (build-subvec meta v start (dec end) nil))) - - ICollection - (-conj [coll o] - (build-subvec meta (-assoc-n v end o) start (inc end) nil)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.PersistentVector.EMPTY meta)) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - ISeqable - (-seq [coll] - (let [subvec-seq (fn subvec-seq [i] - (when-not (== i end) - (cons (-nth v i) - (lazy-seq - (subvec-seq (inc i))))))] - (subvec-seq start))) - - IReversible - (-rseq [coll] - (if-not (== start end) - (RSeq. coll (dec (- end start)) nil))) - - ICounted - (-count [coll] (- end start)) - - IIndexed - (-nth [coll n] - (if (or (neg? n) (<= end (+ start n))) - (vector-index-out-of-bounds n (- end start)) - (-nth v (+ start n)))) - (-nth [coll n not-found] - (if (or (neg? n) (<= end (+ start n))) - not-found - (-nth v (+ start n) not-found))) - - ILookup - (-lookup [coll k] (-lookup coll k nil)) - (-lookup [coll k not-found] (if (number? k) - (-nth coll k not-found) - not-found)) - - IAssociative - (-assoc [coll key val] - (if (number? key) - (-assoc-n coll key val) - (throw (js/Error. "Subvec's key for assoc must be a number.")))) - - IVector - (-assoc-n [coll n val] - (let [v-pos (+ start n)] - (build-subvec meta (assoc v v-pos val) start (max end (inc v-pos)) nil))) - - IReduce - (-reduce [coll f] - (ci-reduce coll f)) - (-reduce [coll f start] - (ci-reduce coll f start)) - - IFn - (-invoke [coll k] - (-nth coll k)) - (-invoke [coll k not-found] - (-nth coll k not-found))) - -(defn- build-subvec [meta v start end __hash] - (if (instance? Subvec v) - (recur meta (.-v v) (+ (.-start v) start) (+ (.-start v) end) __hash) - (let [c (count v)] - (when (or (neg? start) - (neg? end) - (> start c) - (> end c)) - (throw (js/Error. "Index out of bounds"))) - (Subvec. meta v start end __hash)))) - -(defn subvec - "Returns a persistent vector of the items in vector from - start (inclusive) to end (exclusive). If end is not supplied, - defaults to (count vector). This operation is O(1) and very fast, as - the resulting vector shares structure with the original and no - trimming is done." - ([v start] - (subvec v start (count v))) - ([v start end] - (build-subvec nil v start end nil))) - -(defn- tv-ensure-editable [edit node] - (if (identical? edit (.-edit node)) - node - (VectorNode. edit (aclone (.-arr node))))) - -(defn- tv-editable-root [node] - (VectorNode. (js-obj) (aclone (.-arr node)))) - -(defn- tv-editable-tail [tl] - (let [ret (make-array 32)] - (array-copy tl 0 ret 0 (alength tl)) - ret)) - -(defn- tv-push-tail [tv level parent tail-node] - (let [ret (tv-ensure-editable (.. tv -root -edit) parent) - subidx (bit-and (bit-shift-right-zero-fill (dec (.-cnt tv)) level) 0x01f)] - (pv-aset ret subidx - (if (== level 5) - tail-node - (let [child (pv-aget ret subidx)] - (if-not (nil? child) - (tv-push-tail tv (- level 5) child tail-node) - (new-path (.. tv -root -edit) (- level 5) tail-node))))) - ret)) - -(defn- tv-pop-tail [tv level node] - (let [node (tv-ensure-editable (.. tv -root -edit) node) - subidx (bit-and (bit-shift-right-zero-fill (- (.-cnt tv) 2) level) 0x01f)] - (cond - (> level 5) (let [new-child (tv-pop-tail - tv (- level 5) (pv-aget node subidx))] - (if (and (nil? new-child) (zero? subidx)) - nil - (do (pv-aset node subidx new-child) - node))) - (zero? subidx) nil - :else (do (pv-aset node subidx nil) - node)))) - -(defn- unchecked-editable-array-for [tv i] - ;; invariant: i is a valid index of tv. - (if (>= i (tail-off tv)) - (.-tail tv) - (let [root (.-root tv)] - (loop [node root - level (.-shift tv)] - (if (pos? level) - (recur (tv-ensure-editable - (.-edit root) - (pv-aget node - (bit-and (bit-shift-right-zero-fill i level) - 0x01f))) - (- level 5)) - (.-arr node)))))) - -(deftype TransientVector [^:mutable cnt - ^:mutable shift - ^:mutable root - ^:mutable tail] - ITransientCollection - (-conj! [tcoll o] - (if ^boolean (.-edit root) - (if (< (- cnt (tail-off tcoll)) 32) - (do (aset tail (bit-and cnt 0x01f) o) - (set! cnt (inc cnt)) - tcoll) - (let [tail-node (VectorNode. (.-edit root) tail) - new-tail (make-array 32)] - (aset new-tail 0 o) - (set! tail new-tail) - (if (> (bit-shift-right-zero-fill cnt 5) - (bit-shift-left 1 shift)) - (let [new-root-array (make-array 32) - new-shift (+ shift 5)] - (aset new-root-array 0 root) - (aset new-root-array 1 (new-path (.-edit root) shift tail-node)) - (set! root (VectorNode. (.-edit root) new-root-array)) - (set! shift new-shift) - (set! cnt (inc cnt)) - tcoll) - (let [new-root (tv-push-tail tcoll shift root tail-node)] - (set! root new-root) - (set! cnt (inc cnt)) - tcoll)))) - (throw (js/Error. "conj! after persistent!")))) - - (-persistent! [tcoll] - (if ^boolean (.-edit root) - (do (set! (.-edit root) nil) - (let [len (- cnt (tail-off tcoll)) - trimmed-tail (make-array len)] - (array-copy tail 0 trimmed-tail 0 len) - (PersistentVector. nil cnt shift root trimmed-tail nil))) - (throw (js/Error. "persistent! called twice")))) - - ITransientAssociative - (-assoc! [tcoll key val] - (if (number? key) - (-assoc-n! tcoll key val) - (throw (js/Error. "TransientVector's key for assoc! must be a number.")))) - - ITransientVector - (-assoc-n! [tcoll n val] - (if ^boolean (.-edit root) - (cond - (and (<= 0 n) (< n cnt)) - (if (<= (tail-off tcoll) n) - (do (aset tail (bit-and n 0x01f) val) - tcoll) - (let [new-root - ((fn go [level node] - (let [node (tv-ensure-editable (.-edit root) node)] - (if (zero? level) - (do (pv-aset node (bit-and n 0x01f) val) - node) - (let [subidx (bit-and (bit-shift-right-zero-fill n level) - 0x01f)] - (pv-aset node subidx - (go (- level 5) (pv-aget node subidx))) - node)))) - shift root)] - (set! root new-root) - tcoll)) - (== n cnt) (-conj! tcoll val) - :else - (throw - (js/Error. - (str "Index " n " out of bounds for TransientVector of length" cnt)))) - (throw (js/Error. "assoc! after persistent!")))) - - (-pop! [tcoll] - (if ^boolean (.-edit root) - (cond - (zero? cnt) (throw (js/Error. "Can't pop empty vector")) - (== 1 cnt) (do (set! cnt 0) tcoll) - (pos? (bit-and (dec cnt) 0x01f)) (do (set! cnt (dec cnt)) tcoll) - :else - (let [new-tail (unchecked-editable-array-for tcoll (- cnt 2)) - new-root (let [nr (tv-pop-tail tcoll shift root)] - (if-not (nil? nr) - nr - (VectorNode. (.-edit root) (make-array 32))))] - (if (and (< 5 shift) (nil? (pv-aget new-root 1))) - (let [new-root (tv-ensure-editable (.-edit root) (pv-aget new-root 0))] - (set! root new-root) - (set! shift (- shift 5)) - (set! cnt (dec cnt)) - (set! tail new-tail) - tcoll) - (do (set! root new-root) - (set! cnt (dec cnt)) - (set! tail new-tail) - tcoll)))) - (throw (js/Error. "pop! after persistent!")))) - - ICounted - (-count [coll] - (if ^boolean (.-edit root) - cnt - (throw (js/Error. "count after persistent!")))) - - IIndexed - (-nth [coll n] - (if ^boolean (.-edit root) - (aget (array-for coll n) (bit-and n 0x01f)) - (throw (js/Error. "nth after persistent!")))) - - (-nth [coll n not-found] - (if (and (<= 0 n) (< n cnt)) - (-nth coll n) - not-found)) - - ILookup - (-lookup [coll k] (-lookup coll k nil)) - - (-lookup [coll k not-found] (if (number? k) - (-nth coll k not-found) - not-found)) - - IFn - (-invoke [coll k] - (-lookup coll k)) - - (-invoke [coll k not-found] - (-lookup coll k not-found))) - -;;; PersistentQueue ;;; - -(deftype PersistentQueueSeq [meta front rear ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - IWithMeta - (-with-meta [coll meta] (PersistentQueueSeq. meta front rear __hash)) - - IMeta - (-meta [coll] meta) - - ISeq - (-first [coll] (first front)) - (-rest [coll] - (if-let [f1 (next front)] - (PersistentQueueSeq. meta f1 rear nil) - (if (nil? rear) - (-empty coll) - (PersistentQueueSeq. meta rear nil nil)))) - - ICollection - (-conj [coll o] (cons o coll)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY meta)) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - ISeqable - (-seq [coll] coll)) - -(deftype PersistentQueue [meta count front rear ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - ICloneable - (-clone [coll] (PersistentQueue. meta count front rear __hash)) - - IWithMeta - (-with-meta [coll meta] (PersistentQueue. meta count front rear __hash)) - - IMeta - (-meta [coll] meta) - - ISeq - (-first [coll] (first front)) - (-rest [coll] (rest (seq coll))) - - IStack - (-peek [coll] (first front)) - (-pop [coll] - (if front - (if-let [f1 (next front)] - (PersistentQueue. meta (dec count) f1 rear nil) - (PersistentQueue. meta (dec count) (seq rear) [] nil)) - coll)) - - ICollection - (-conj [coll o] - (if front - (PersistentQueue. meta (inc count) front (conj (or rear []) o) nil) - (PersistentQueue. meta (inc count) (conj front o) [] nil))) - - IEmptyableCollection - (-empty [coll] cljs.core.PersistentQueue.EMPTY) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - ISeqable - (-seq [coll] - (let [rear (seq rear)] - (if (or front rear) - (PersistentQueueSeq. nil front (seq rear) nil)))) - - ICounted - (-count [coll] count)) - -(set! cljs.core.PersistentQueue.EMPTY (PersistentQueue. nil 0 nil [] 0)) - -(deftype NeverEquiv [] - IEquiv - (-equiv [o other] false)) - -(def ^:private never-equiv (NeverEquiv.)) - -(defn- equiv-map - "Assumes y is a map. Returns true if x equals y, otherwise returns - false." - [x y] - (boolean - (when (map? y) - ; assume all maps are counted - (when (== (count x) (count y)) - (every? identity - (map (fn [xkv] (= (get y (first xkv) never-equiv) - (second xkv))) - x)))))) - - -(defn- scan-array [incr k array] - (let [len (alength array)] - (loop [i 0] - (when (< i len) - (if (identical? k (aget array i)) - i - (recur (+ i incr))))))) - -; The keys field is an array of all keys of this map, in no particular -; order. Any string, keyword, or symbol key is used as a property name -; to store the value in strobj. If a key is assoc'ed when that same -; key already exists in strobj, the old value is overwritten. If a -; non-string key is assoc'ed, return a HashMap object instead. - -(defn- obj-map-compare-keys [a b] - (let [a (hash a) - b (hash b)] - (cond - (< a b) -1 - (> a b) 1 - :else 0))) - -(defn- obj-map->hash-map [m k v] - (let [ks (.-keys m) - len (alength ks) - so (.-strobj m) - mm (meta m)] - (loop [i 0 - out (transient cljs.core.PersistentHashMap.EMPTY)] - (if (< i len) - (let [k (aget ks i)] - (recur (inc i) (assoc! out k (aget so k)))) - (with-meta (persistent! (assoc! out k v)) mm))))) - -;;; ObjMap - DEPRECATED - -(defn- obj-clone [obj ks] - (let [new-obj (js-obj) - l (alength ks)] - (loop [i 0] - (when (< i l) - (let [k (aget ks i)] - (aset new-obj k (aget obj k)) - (recur (inc i))))) - new-obj)) - -(deftype ObjMap [meta keys strobj update-count ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - IWithMeta - (-with-meta [coll meta] (ObjMap. meta keys strobj update-count __hash)) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll entry] - (if (vector? entry) - (-assoc coll (-nth entry 0) (-nth entry 1)) - (reduce -conj - coll - entry))) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.ObjMap.EMPTY meta)) - - IEquiv - (-equiv [coll other] (equiv-map coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-imap __hash)) - - ISeqable - (-seq [coll] - (when (pos? (alength keys)) - (map #(vector % (aget strobj %)) - (.sort keys obj-map-compare-keys)))) - - ICounted - (-count [coll] (alength keys)) - - ILookup - (-lookup [coll k] (-lookup coll k nil)) - (-lookup [coll k not-found] - (if (and ^boolean (goog/isString k) - (not (nil? (scan-array 1 k keys)))) - (aget strobj k) - not-found)) - - IAssociative - (-assoc [coll k v] - (if ^boolean (goog/isString k) - (if (or (> update-count cljs.core.ObjMap.HASHMAP_THRESHOLD) - (>= (alength keys) cljs.core.ObjMap.HASHMAP_THRESHOLD)) - (obj-map->hash-map coll k v) - (if-not (nil? (scan-array 1 k keys)) - (let [new-strobj (obj-clone strobj keys)] - (aset new-strobj k v) - (ObjMap. meta keys new-strobj (inc update-count) nil)) ; overwrite - (let [new-strobj (obj-clone strobj keys) ; append - new-keys (aclone keys)] - (aset new-strobj k v) - (.push new-keys k) - (ObjMap. meta new-keys new-strobj (inc update-count) nil)))) - ;; non-string key. game over. - (obj-map->hash-map coll k v))) - (-contains-key? [coll k] - (if (and ^boolean (goog/isString k) - (not (nil? (scan-array 1 k keys)))) - true - false)) - - IKVReduce - (-kv-reduce [coll f init] - (let [len (alength keys)] - (loop [keys (.sort keys obj-map-compare-keys) - init init] - (if (seq keys) - (let [k (first keys) - init (f init k (aget strobj k))] - (if (reduced? init) - @init - (recur (rest keys) init))) - init)))) - - IMap - (-dissoc [coll k] - (if (and ^boolean (goog/isString k) - (not (nil? (scan-array 1 k keys)))) - (let [new-keys (aclone keys) - new-strobj (obj-clone strobj keys)] - (.splice new-keys (scan-array 1 k new-keys) 1) - (js-delete new-strobj k) - (ObjMap. meta new-keys new-strobj (inc update-count) nil)) - coll)) ; key not found, return coll unchanged - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found)) - - IEditableCollection - (-as-transient [coll] - (transient (into (hash-map) coll)))) - -(set! cljs.core.ObjMap.EMPTY (ObjMap. nil (array) (js-obj) 0 0)) - -(set! cljs.core.ObjMap.HASHMAP_THRESHOLD 8) - -(set! cljs.core.ObjMap.fromObject (fn [ks obj] (ObjMap. nil ks obj 0 nil))) - -;;; PersistentArrayMap - -(defn- array-map-index-of-nil? [arr m k] - (let [len (alength arr)] - (loop [i 0] - (cond - (<= len i) -1 - (nil? (aget arr i)) i - :else (recur (+ i 2)))))) - -(defn- array-map-index-of-keyword? [arr m k] - (let [len (alength arr) - kstr (.-fqn k)] - (loop [i 0] - (cond - (<= len i) -1 - (let [k' (aget arr i)] - (and (keyword? k') - (identical? kstr (.-fqn k')))) i - :else (recur (+ i 2)))))) - -(defn- array-map-index-of-symbol? [arr m k] - (let [len (alength arr) - kstr (.-str k)] - (loop [i 0] - (cond - (<= len i) -1 - (let [k' (aget arr i)] - (and (symbol? k') - (identical? kstr (.-str k')))) i - :else (recur (+ i 2)))))) - -(defn- array-map-index-of-identical? [arr m k] - (let [len (alength arr)] - (loop [i 0] - (cond - (<= len i) -1 - (identical? k (aget arr i)) i - :else (recur (+ i 2)))))) - -(defn- array-map-index-of-equiv? [arr m k] - (let [len (alength arr)] - (loop [i 0] - (cond - (<= len i) -1 - (= k (aget arr i)) i - :else (recur (+ i 2)))))) - -(defn- array-map-index-of [m k] - (let [arr (.-arr m)] - (cond - (keyword? k) (array-map-index-of-keyword? arr m k) - - (or ^boolean (goog/isString k) (number? k)) - (array-map-index-of-identical? arr m k) - - (symbol? k) (array-map-index-of-symbol? arr m k) - - (nil? k) - (array-map-index-of-nil? arr m k) - - :else (array-map-index-of-equiv? arr m k)))) - -(defn- array-map-extend-kv [m k v] - (let [arr (.-arr m) - l (alength arr) - narr (make-array (+ l 2))] - (loop [i 0] - (when (< i l) - (aset narr i (aget arr i)) - (recur (inc i)))) - (aset narr l k) - (aset narr (inc l) v) - narr)) - -(declare TransientArrayMap) - -(deftype PersistentArrayMapSeq [arr i _meta] - Object - (toString [coll] - (pr-str* coll)) - - IMeta - (-meta [coll] _meta) - - IWithMeta - (-with-meta [coll new-meta] - (PersistentArrayMapSeq. arr i new-meta)) - - ICounted - (-count [coll] - (/ (- (alength arr) i) 2)) - - ISeqable - (-seq [coll] coll) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - ICollection - (-conj [coll o] - (cons o coll)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY _meta)) - - IHash - (-hash [coll] (hash-coll coll)) - - ISeq - (-first [coll] - [(aget arr i) (aget arr (inc i))]) - - (-rest [coll] - (if (< i (- (alength arr) 2)) - (PersistentArrayMapSeq. arr (+ i 2) _meta) - ())) - - INext - (-next [coll] - (when (< i (- (alength arr) 2)) - (PersistentArrayMapSeq. arr (+ i 2) _meta))) - - IReduce - (-reduce [coll f] (seq-reduce f coll)) - (-reduce [coll f start] (seq-reduce f start coll))) - -(defn persistent-array-map-seq [arr i _meta] - (when (<= i (- (alength arr) 2)) - (PersistentArrayMapSeq. arr i _meta))) - -(deftype PersistentArrayMap [meta cnt arr ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - ICloneable - (-clone [_] (PersistentArrayMap. meta cnt arr __hash)) - - IWithMeta - (-with-meta [coll meta] (PersistentArrayMap. meta cnt arr __hash)) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll entry] - (if (vector? entry) - (-assoc coll (-nth entry 0) (-nth entry 1)) - (reduce -conj coll entry))) - - IEmptyableCollection - (-empty [coll] (-with-meta cljs.core.PersistentArrayMap.EMPTY meta)) - - IEquiv - (-equiv [coll other] (equiv-map coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-imap __hash)) - - ISeqable - (-seq [coll] - (persistent-array-map-seq arr 0 nil)) - - ICounted - (-count [coll] cnt) - - ILookup - (-lookup [coll k] - (-lookup coll k nil)) - - (-lookup [coll k not-found] - (let [idx (array-map-index-of coll k)] - (if (== idx -1) - not-found - (aget arr (inc idx))))) - - IAssociative - (-assoc [coll k v] - (let [idx (array-map-index-of coll k)] - (cond - (== idx -1) - (if (< cnt cljs.core.PersistentArrayMap.HASHMAP_THRESHOLD) - (let [arr (array-map-extend-kv coll k v)] - (PersistentArrayMap. meta (inc cnt) arr nil)) - (-> (into cljs.core.PersistentHashMap.EMPTY coll) - (-assoc k v) - (-with-meta meta))) - - (identical? v (aget arr (inc idx))) - coll - - :else - (let [arr (doto (aclone arr) - (aset (inc idx) v))] - (PersistentArrayMap. meta cnt arr nil))))) - - (-contains-key? [coll k] - (not (== (array-map-index-of coll k) -1))) - - IMap - (-dissoc [coll k] - (let [idx (array-map-index-of coll k)] - (if (>= idx 0) - (let [len (alength arr) - new-len (- len 2)] - (if (zero? new-len) - (-empty coll) - (let [new-arr (make-array new-len)] - (loop [s 0 d 0] - (cond - (>= s len) (PersistentArrayMap. meta (dec cnt) new-arr nil) - (= k (aget arr s)) (recur (+ s 2) d) - :else (do (aset new-arr d (aget arr s)) - (aset new-arr (inc d) (aget arr (inc s))) - (recur (+ s 2) (+ d 2)))))))) - coll))) - - IKVReduce - (-kv-reduce [coll f init] - (let [len (alength arr)] - (loop [i 0 init init] - (if (< i len) - (let [init (f init (aget arr i) (aget arr (inc i)))] - (if (reduced? init) - @init - (recur (+ i 2) init))) - init)))) - - IFn - (-invoke [coll k] - (-lookup coll k)) - - (-invoke [coll k not-found] - (-lookup coll k not-found)) - - IEditableCollection - (-as-transient [coll] - (TransientArrayMap. (js-obj) (alength arr) (aclone arr)))) - -(set! cljs.core.PersistentArrayMap.EMPTY (PersistentArrayMap. nil 0 (array) nil)) - -(set! cljs.core.PersistentArrayMap.HASHMAP_THRESHOLD 8) - -(set! cljs.core.PersistentArrayMap.fromArray - (fn [arr ^boolean no-clone ^boolean no-check] - (let [arr (if no-clone arr (aclone arr))] - (if no-check - (let [cnt (/ (alength arr) 2)] - (PersistentArrayMap. nil cnt arr nil)) - (let [len (alength arr)] - (loop [i 0 - ret (transient cljs.core.PersistentArrayMap.EMPTY)] - (if (< i len) - (recur (+ i 2) - (-assoc! ret (aget arr i) (aget arr (inc i)))) - (-persistent! ret)))))))) - -(declare array->transient-hash-map) - -(deftype TransientArrayMap [^:mutable editable? - ^:mutable len - arr] - ICounted - (-count [tcoll] - (if editable? - (quot len 2) - (throw (js/Error. "count after persistent!")))) - - ILookup - (-lookup [tcoll k] - (-lookup tcoll k nil)) - - (-lookup [tcoll k not-found] - (if editable? - (let [idx (array-map-index-of tcoll k)] - (if (== idx -1) - not-found - (aget arr (inc idx)))) - (throw (js/Error. "lookup after persistent!")))) - - ITransientCollection - (-conj! [tcoll o] - (if editable? - (if (satisfies? IMapEntry o) - (-assoc! tcoll (key o) (val o)) - (loop [es (seq o) tcoll tcoll] - (if-let [e (first es)] - (recur (next es) - (-assoc! tcoll (key e) (val e))) - tcoll))) - (throw (js/Error. "conj! after persistent!")))) - - (-persistent! [tcoll] - (if editable? - (do (set! editable? false) - (PersistentArrayMap. nil (quot len 2) arr nil)) - (throw (js/Error. "persistent! called twice")))) - - ITransientAssociative - (-assoc! [tcoll key val] - (if editable? - (let [idx (array-map-index-of tcoll key)] - (if (== idx -1) - (if (<= (+ len 2) (* 2 cljs.core.PersistentArrayMap.HASHMAP_THRESHOLD)) - (do (set! len (+ len 2)) - (.push arr key) - (.push arr val) - tcoll) - (assoc! (array->transient-hash-map len arr) key val)) - (if (identical? val (aget arr (inc idx))) - tcoll - (do (aset arr (inc idx) val) - tcoll)))) - (throw (js/Error. "assoc! after persistent!")))) - - ITransientMap - (-dissoc! [tcoll key] - (if editable? - (let [idx (array-map-index-of tcoll key)] - (when (>= idx 0) - (aset arr idx (aget arr (- len 2))) - (aset arr (inc idx) (aget arr (dec len))) - (doto arr .pop .pop) - (set! len (- len 2))) - tcoll) - (throw (js/Error. "dissoc! after persistent!"))))) - -(declare TransientHashMap PersistentHashMap) - -(defn- array->transient-hash-map [len arr] - (loop [out (transient cljs.core.PersistentHashMap.EMPTY) - i 0] - (if (< i len) - (recur (assoc! out (aget arr i) (aget arr (inc i))) (+ i 2)) - out))) - -;;; PersistentHashMap - -(deftype Box [^:mutable val]) - -(declare create-inode-seq create-array-node-seq reset! create-node atom deref) - -(defn ^boolean key-test [key other] - (cond - (identical? key other) true - (keyword-identical? key other) true - :else (= key other))) - -(defn- mask [hash shift] - (bit-and (bit-shift-right-zero-fill hash shift) 0x01f)) - -(defn- clone-and-set - ([arr i a] - (doto (aclone arr) - (aset i a))) - ([arr i a j b] - (doto (aclone arr) - (aset i a) - (aset j b)))) - -(defn- remove-pair [arr i] - (let [new-arr (make-array (- (alength arr) 2))] - (array-copy arr 0 new-arr 0 (* 2 i)) - (array-copy arr (* 2 (inc i)) new-arr (* 2 i) (- (alength new-arr) (* 2 i))) - new-arr)) - -(defn- bitmap-indexed-node-index [bitmap bit] - (bit-count (bit-and bitmap (dec bit)))) - -(defn- bitpos [hash shift] - (bit-shift-left 1 (mask hash shift))) - -(defn- edit-and-set - ([inode edit i a] - (let [editable (.ensure-editable inode edit)] - (aset (.-arr editable) i a) - editable)) - ([inode edit i a j b] - (let [editable (.ensure-editable inode edit)] - (aset (.-arr editable) i a) - (aset (.-arr editable) j b) - editable))) - -(defn- inode-kv-reduce [arr f init] - (let [len (alength arr)] - (loop [i 0 init init] - (if (< i len) - (let [init (let [k (aget arr i)] - (if-not (nil? k) - (f init k (aget arr (inc i))) - (let [node (aget arr (inc i))] - (if-not (nil? node) - (.kv-reduce node f init) - init))))] - (if (reduced? init) - @init - (recur (+ i 2) init))) - init)))) - -(declare ArrayNode) - -(deftype BitmapIndexedNode [edit ^:mutable bitmap ^:mutable arr] - Object - (inode-assoc [inode shift hash key val added-leaf?] - (let [bit (bitpos hash shift) - idx (bitmap-indexed-node-index bitmap bit)] - (if (zero? (bit-and bitmap bit)) - (let [n (bit-count bitmap)] - (if (>= n 16) - (let [nodes (make-array 32) - jdx (mask hash shift)] - (aset nodes jdx (.inode-assoc cljs.core.BitmapIndexedNode.EMPTY (+ shift 5) hash key val added-leaf?)) - (loop [i 0 j 0] - (if (< i 32) - (if (zero? (bit-and (bit-shift-right-zero-fill bitmap i) 1)) - (recur (inc i) j) - (do (aset nodes i - (if-not (nil? (aget arr j)) - (.inode-assoc cljs.core.BitmapIndexedNode.EMPTY - (+ shift 5) (cljs.core/hash (aget arr j)) (aget arr j) (aget arr (inc j)) added-leaf?) - (aget arr (inc j)))) - (recur (inc i) (+ j 2)))))) - (ArrayNode. nil (inc n) nodes)) - (let [new-arr (make-array (* 2 (inc n)))] - (array-copy arr 0 new-arr 0 (* 2 idx)) - (aset new-arr (* 2 idx) key) - (aset new-arr (inc (* 2 idx)) val) - (array-copy arr (* 2 idx) new-arr (* 2 (inc idx)) (* 2 (- n idx))) - (set! (.-val added-leaf?) true) - (BitmapIndexedNode. nil (bit-or bitmap bit) new-arr)))) - (let [key-or-nil (aget arr (* 2 idx)) - val-or-node (aget arr (inc (* 2 idx)))] - (cond (nil? key-or-nil) - (let [n (.inode-assoc val-or-node (+ shift 5) hash key val added-leaf?)] - (if (identical? n val-or-node) - inode - (BitmapIndexedNode. nil bitmap (clone-and-set arr (inc (* 2 idx)) n)))) - - (key-test key key-or-nil) - (if (identical? val val-or-node) - inode - (BitmapIndexedNode. nil bitmap (clone-and-set arr (inc (* 2 idx)) val))) - - :else - (do (set! (.-val added-leaf?) true) - (BitmapIndexedNode. nil bitmap - (clone-and-set arr (* 2 idx) nil (inc (* 2 idx)) - (create-node (+ shift 5) key-or-nil val-or-node hash key val))))))))) - - (inode-without [inode shift hash key] - (let [bit (bitpos hash shift)] - (if (zero? (bit-and bitmap bit)) - inode - (let [idx (bitmap-indexed-node-index bitmap bit) - key-or-nil (aget arr (* 2 idx)) - val-or-node (aget arr (inc (* 2 idx)))] - (cond (nil? key-or-nil) - (let [n (.inode-without val-or-node (+ shift 5) hash key)] - (cond (identical? n val-or-node) inode - (not (nil? n)) (BitmapIndexedNode. nil bitmap (clone-and-set arr (inc (* 2 idx)) n)) - (== bitmap bit) nil - :else (BitmapIndexedNode. nil (bit-xor bitmap bit) (remove-pair arr idx)))) - (key-test key key-or-nil) - (BitmapIndexedNode. nil (bit-xor bitmap bit) (remove-pair arr idx)) - :else inode))))) - - (inode-lookup [inode shift hash key not-found] - (let [bit (bitpos hash shift)] - (if (zero? (bit-and bitmap bit)) - not-found - (let [idx (bitmap-indexed-node-index bitmap bit) - key-or-nil (aget arr (* 2 idx)) - val-or-node (aget arr (inc (* 2 idx)))] - (cond (nil? key-or-nil) (.inode-lookup val-or-node (+ shift 5) hash key not-found) - (key-test key key-or-nil) val-or-node - :else not-found))))) - - (inode-find [inode shift hash key not-found] - (let [bit (bitpos hash shift)] - (if (zero? (bit-and bitmap bit)) - not-found - (let [idx (bitmap-indexed-node-index bitmap bit) - key-or-nil (aget arr (* 2 idx)) - val-or-node (aget arr (inc (* 2 idx)))] - (cond (nil? key-or-nil) (.inode-find val-or-node (+ shift 5) hash key not-found) - (key-test key key-or-nil) [key-or-nil val-or-node] - :else not-found))))) - - (inode-seq [inode] - (create-inode-seq arr)) - - (ensure-editable [inode e] - (if (identical? e edit) - inode - (let [n (bit-count bitmap) - new-arr (make-array (if (neg? n) 4 (* 2 (inc n))))] - (array-copy arr 0 new-arr 0 (* 2 n)) - (BitmapIndexedNode. e bitmap new-arr)))) - - (edit-and-remove-pair [inode e bit i] - (if (== bitmap bit) - nil - (let [editable (.ensure-editable inode e) - earr (.-arr editable) - len (alength earr)] - (set! (.-bitmap editable) (bit-xor bit (.-bitmap editable))) - (array-copy earr (* 2 (inc i)) - earr (* 2 i) - (- len (* 2 (inc i)))) - (aset earr (- len 2) nil) - (aset earr (dec len) nil) - editable))) - - (inode-assoc! [inode edit shift hash key val added-leaf?] - (let [bit (bitpos hash shift) - idx (bitmap-indexed-node-index bitmap bit)] - (if (zero? (bit-and bitmap bit)) - (let [n (bit-count bitmap)] - (cond - (< (* 2 n) (alength arr)) - (let [editable (.ensure-editable inode edit) - earr (.-arr editable)] - (set! (.-val added-leaf?) true) - (array-copy-downward earr (* 2 idx) - earr (* 2 (inc idx)) - (* 2 (- n idx))) - (aset earr (* 2 idx) key) - (aset earr (inc (* 2 idx)) val) - (set! (.-bitmap editable) (bit-or (.-bitmap editable) bit)) - editable) - - (>= n 16) - (let [nodes (make-array 32) - jdx (mask hash shift)] - (aset nodes jdx (.inode-assoc! cljs.core.BitmapIndexedNode.EMPTY edit (+ shift 5) hash key val added-leaf?)) - (loop [i 0 j 0] - (if (< i 32) - (if (zero? (bit-and (bit-shift-right-zero-fill bitmap i) 1)) - (recur (inc i) j) - (do (aset nodes i - (if-not (nil? (aget arr j)) - (.inode-assoc! cljs.core.BitmapIndexedNode.EMPTY - edit (+ shift 5) (cljs.core/hash (aget arr j)) (aget arr j) (aget arr (inc j)) added-leaf?) - (aget arr (inc j)))) - (recur (inc i) (+ j 2)))))) - (ArrayNode. edit (inc n) nodes)) - - :else - (let [new-arr (make-array (* 2 (+ n 4)))] - (array-copy arr 0 new-arr 0 (* 2 idx)) - (aset new-arr (* 2 idx) key) - (aset new-arr (inc (* 2 idx)) val) - (array-copy arr (* 2 idx) new-arr (* 2 (inc idx)) (* 2 (- n idx))) - (set! (.-val added-leaf?) true) - (let [editable (.ensure-editable inode edit)] - (set! (.-arr editable) new-arr) - (set! (.-bitmap editable) (bit-or (.-bitmap editable) bit)) - editable)))) - (let [key-or-nil (aget arr (* 2 idx)) - val-or-node (aget arr (inc (* 2 idx)))] - (cond (nil? key-or-nil) - (let [n (.inode-assoc! val-or-node edit (+ shift 5) hash key val added-leaf?)] - (if (identical? n val-or-node) - inode - (edit-and-set inode edit (inc (* 2 idx)) n))) - - (key-test key key-or-nil) - (if (identical? val val-or-node) - inode - (edit-and-set inode edit (inc (* 2 idx)) val)) - - :else - (do (set! (.-val added-leaf?) true) - (edit-and-set inode edit (* 2 idx) nil (inc (* 2 idx)) - (create-node edit (+ shift 5) key-or-nil val-or-node hash key val)))))))) - - (inode-without! [inode edit shift hash key removed-leaf?] - (let [bit (bitpos hash shift)] - (if (zero? (bit-and bitmap bit)) - inode - (let [idx (bitmap-indexed-node-index bitmap bit) - key-or-nil (aget arr (* 2 idx)) - val-or-node (aget arr (inc (* 2 idx)))] - (cond (nil? key-or-nil) - (let [n (.inode-without! val-or-node edit (+ shift 5) hash key removed-leaf?)] - (cond (identical? n val-or-node) inode - (not (nil? n)) (edit-and-set inode edit (inc (* 2 idx)) n) - (== bitmap bit) nil - :else (.edit-and-remove-pair inode edit bit idx))) - (key-test key key-or-nil) - (do (aset removed-leaf? 0 true) - (.edit-and-remove-pair inode edit bit idx)) - :else inode))))) - - (kv-reduce [inode f init] - (inode-kv-reduce arr f init))) - -(set! cljs.core.BitmapIndexedNode.EMPTY (BitmapIndexedNode. nil 0 (make-array 0))) - -(defn- pack-array-node [array-node edit idx] - (let [arr (.-arr array-node) - len (* 2 (dec (.-cnt array-node))) - new-arr (make-array len)] - (loop [i 0 j 1 bitmap 0] - (if (< i len) - (if (and (not (== i idx)) - (not (nil? (aget arr i)))) - (do (aset new-arr j (aget arr i)) - (recur (inc i) (+ j 2) (bit-or bitmap (bit-shift-left 1 i)))) - (recur (inc i) j bitmap)) - (BitmapIndexedNode. edit bitmap new-arr))))) - -(deftype ArrayNode [edit ^:mutable cnt ^:mutable arr] - Object - (inode-assoc [inode shift hash key val added-leaf?] - (let [idx (mask hash shift) - node (aget arr idx)] - (if (nil? node) - (ArrayNode. nil (inc cnt) (clone-and-set arr idx (.inode-assoc cljs.core.BitmapIndexedNode.EMPTY (+ shift 5) hash key val added-leaf?))) - (let [n (.inode-assoc node (+ shift 5) hash key val added-leaf?)] - (if (identical? n node) - inode - (ArrayNode. nil cnt (clone-and-set arr idx n))))))) - - (inode-without [inode shift hash key] - (let [idx (mask hash shift) - node (aget arr idx)] - (if-not (nil? node) - (let [n (.inode-without node (+ shift 5) hash key)] - (cond - (identical? n node) - inode - - (nil? n) - (if (<= cnt 8) - (pack-array-node inode nil idx) - (ArrayNode. nil (dec cnt) (clone-and-set arr idx n))) - - :else - (ArrayNode. nil cnt (clone-and-set arr idx n)))) - inode))) - - (inode-lookup [inode shift hash key not-found] - (let [idx (mask hash shift) - node (aget arr idx)] - (if-not (nil? node) - (.inode-lookup node (+ shift 5) hash key not-found) - not-found))) - - (inode-find [inode shift hash key not-found] - (let [idx (mask hash shift) - node (aget arr idx)] - (if-not (nil? node) - (.inode-find node (+ shift 5) hash key not-found) - not-found))) - - (inode-seq [inode] - (create-array-node-seq arr)) - - (ensure-editable [inode e] - (if (identical? e edit) - inode - (ArrayNode. e cnt (aclone arr)))) - - (inode-assoc! [inode edit shift hash key val added-leaf?] - (let [idx (mask hash shift) - node (aget arr idx)] - (if (nil? node) - (let [editable (edit-and-set inode edit idx (.inode-assoc! cljs.core.BitmapIndexedNode.EMPTY edit (+ shift 5) hash key val added-leaf?))] - (set! (.-cnt editable) (inc (.-cnt editable))) - editable) - (let [n (.inode-assoc! node edit (+ shift 5) hash key val added-leaf?)] - (if (identical? n node) - inode - (edit-and-set inode edit idx n)))))) - - (inode-without! [inode edit shift hash key removed-leaf?] - (let [idx (mask hash shift) - node (aget arr idx)] - (if (nil? node) - inode - (let [n (.inode-without! node edit (+ shift 5) hash key removed-leaf?)] - (cond - (identical? n node) - inode - - (nil? n) - (if (<= cnt 8) - (pack-array-node inode edit idx) - (let [editable (edit-and-set inode edit idx n)] - (set! (.-cnt editable) (dec (.-cnt editable))) - editable)) - - :else - (edit-and-set inode edit idx n)))))) - - (kv-reduce [inode f init] - (let [len (alength arr)] ; actually 32 - (loop [i 0 init init] - (if (< i len) - (let [node (aget arr i)] - (if-not (nil? node) - (let [init (.kv-reduce node f init)] - (if (reduced? init) - @init - (recur (inc i) init))) - (recur (inc i) init))) - init))))) - -(defn- hash-collision-node-find-index [arr cnt key] - (let [lim (* 2 cnt)] - (loop [i 0] - (if (< i lim) - (if (key-test key (aget arr i)) - i - (recur (+ i 2))) - -1)))) - -(deftype HashCollisionNode [edit - ^:mutable collision-hash - ^:mutable cnt - ^:mutable arr] - Object - (inode-assoc [inode shift hash key val added-leaf?] - (if (== hash collision-hash) - (let [idx (hash-collision-node-find-index arr cnt key)] - (if (== idx -1) - (let [len (* 2 cnt) - new-arr (make-array (+ len 2))] - (array-copy arr 0 new-arr 0 len) - (aset new-arr len key) - (aset new-arr (inc len) val) - (set! (.-val added-leaf?) true) - (HashCollisionNode. nil collision-hash (inc cnt) new-arr)) - (if (= (aget arr idx) val) - inode - (HashCollisionNode. nil collision-hash cnt (clone-and-set arr (inc idx) val))))) - (.inode-assoc (BitmapIndexedNode. nil (bitpos collision-hash shift) (array nil inode)) - shift hash key val added-leaf?))) - - (inode-without [inode shift hash key] - (let [idx (hash-collision-node-find-index arr cnt key)] - (cond (== idx -1) inode - (== cnt 1) nil - :else (HashCollisionNode. nil collision-hash (dec cnt) (remove-pair arr (quot idx 2)))))) - - (inode-lookup [inode shift hash key not-found] - (let [idx (hash-collision-node-find-index arr cnt key)] - (cond (< idx 0) not-found - (key-test key (aget arr idx)) (aget arr (inc idx)) - :else not-found))) - - (inode-find [inode shift hash key not-found] - (let [idx (hash-collision-node-find-index arr cnt key)] - (cond (< idx 0) not-found - (key-test key (aget arr idx)) [(aget arr idx) (aget arr (inc idx))] - :else not-found))) - - (inode-seq [inode] - (create-inode-seq arr)) - - (ensure-editable [inode e] - (if (identical? e edit) - inode - (let [new-arr (make-array (* 2 (inc cnt)))] - (array-copy arr 0 new-arr 0 (* 2 cnt)) - (HashCollisionNode. e collision-hash cnt new-arr)))) - - (ensure-editable-array [inode e count array] - (if (identical? e edit) - (do (set! arr array) - (set! cnt count) - inode) - (HashCollisionNode. edit collision-hash count array))) - - (inode-assoc! [inode edit shift hash key val added-leaf?] - (if (== hash collision-hash) - (let [idx (hash-collision-node-find-index arr cnt key)] - (if (== idx -1) - (if (> (alength arr) (* 2 cnt)) - (let [editable (edit-and-set inode edit (* 2 cnt) key (inc (* 2 cnt)) val)] - (set! (.-val added-leaf?) true) - (set! (.-cnt editable) (inc (.-cnt editable))) - editable) - (let [len (alength arr) - new-arr (make-array (+ len 2))] - (array-copy arr 0 new-arr 0 len) - (aset new-arr len key) - (aset new-arr (inc len) val) - (set! (.-val added-leaf?) true) - (.ensure-editable-array inode edit (inc cnt) new-arr))) - (if (identical? (aget arr (inc idx)) val) - inode - (edit-and-set inode edit (inc idx) val)))) - (.inode-assoc! (BitmapIndexedNode. edit (bitpos collision-hash shift) (array nil inode nil nil)) - edit shift hash key val added-leaf?))) - - (inode-without! [inode edit shift hash key removed-leaf?] - (let [idx (hash-collision-node-find-index arr cnt key)] - (if (== idx -1) - inode - (do (aset removed-leaf? 0 true) - (if (== cnt 1) - nil - (let [editable (.ensure-editable inode edit) - earr (.-arr editable)] - (aset earr idx (aget earr (- (* 2 cnt) 2))) - (aset earr (inc idx) (aget earr (dec (* 2 cnt)))) - (aset earr (dec (* 2 cnt)) nil) - (aset earr (- (* 2 cnt) 2) nil) - (set! (.-cnt editable) (dec (.-cnt editable))) - editable)))))) - - (kv-reduce [inode f init] - (inode-kv-reduce arr f init))) - -(defn- create-node - ([shift key1 val1 key2hash key2 val2] - (let [key1hash (hash key1)] - (if (== key1hash key2hash) - (HashCollisionNode. nil key1hash 2 (array key1 val1 key2 val2)) - (let [added-leaf? (Box. false)] - (-> cljs.core.BitmapIndexedNode.EMPTY - (.inode-assoc shift key1hash key1 val1 added-leaf?) - (.inode-assoc shift key2hash key2 val2 added-leaf?)))))) - ([edit shift key1 val1 key2hash key2 val2] - (let [key1hash (hash key1)] - (if (== key1hash key2hash) - (HashCollisionNode. nil key1hash 2 (array key1 val1 key2 val2)) - (let [added-leaf? (Box. false)] - (-> cljs.core.BitmapIndexedNode.EMPTY - (.inode-assoc! edit shift key1hash key1 val1 added-leaf?) - (.inode-assoc! edit shift key2hash key2 val2 added-leaf?))))))) - -(deftype NodeSeq [meta nodes i s ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - IMeta - (-meta [coll] meta) - - IWithMeta - (-with-meta [coll meta] (NodeSeq. meta nodes i s __hash)) - - ICollection - (-conj [coll o] (cons o coll)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY meta)) - - ICollection - (-conj [coll o] (cons o coll)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY meta)) - - ISequential - ISeq - (-first [coll] - (if (nil? s) - [(aget nodes i) (aget nodes (inc i))] - (first s))) - - (-rest [coll] - (if (nil? s) - (create-inode-seq nodes (+ i 2) nil) - (create-inode-seq nodes i (next s)))) - - ISeqable - (-seq [this] this) - - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - IReduce - (-reduce [coll f] (seq-reduce f coll)) - (-reduce [coll f start] (seq-reduce f start coll))) - -(defn- create-inode-seq - ([nodes] - (create-inode-seq nodes 0 nil)) - ([nodes i s] - (if (nil? s) - (let [len (alength nodes)] - (loop [j i] - (if (< j len) - (if-not (nil? (aget nodes j)) - (NodeSeq. nil nodes j nil nil) - (if-let [node (aget nodes (inc j))] - (if-let [node-seq (.inode-seq node)] - (NodeSeq. nil nodes (+ j 2) node-seq nil) - (recur (+ j 2))) - (recur (+ j 2))))))) - (NodeSeq. nil nodes i s nil)))) - -(deftype ArrayNodeSeq [meta nodes i s ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - IMeta - (-meta [coll] meta) - - IWithMeta - (-with-meta [coll meta] (ArrayNodeSeq. meta nodes i s __hash)) - - ICollection - (-conj [coll o] (cons o coll)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY meta)) - - ICollection - (-conj [coll o] (cons o coll)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY meta)) - - ISequential - ISeq - (-first [coll] (first s)) - (-rest [coll] (create-array-node-seq nil nodes i (next s))) - - ISeqable - (-seq [this] this) - - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - IReduce - (-reduce [coll f] (seq-reduce f coll)) - (-reduce [coll f start] (seq-reduce f start coll))) - -(defn- create-array-node-seq - ([nodes] (create-array-node-seq nil nodes 0 nil)) - ([meta nodes i s] - (if (nil? s) - (let [len (alength nodes)] - (loop [j i] - (if (< j len) - (if-let [nj (aget nodes j)] - (if-let [ns (.inode-seq nj)] - (ArrayNodeSeq. meta nodes (inc j) ns nil) - (recur (inc j))) - (recur (inc j)))))) - (ArrayNodeSeq. meta nodes i s nil)))) - -(declare TransientHashMap) - -(deftype PersistentHashMap [meta cnt root ^boolean has-nil? nil-val ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - ICloneable - (-clone [_] (PersistentHashMap. meta cnt root has-nil? nil-val __hash)) - - IWithMeta - (-with-meta [coll meta] (PersistentHashMap. meta cnt root has-nil? nil-val __hash)) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll entry] - (if (vector? entry) - (-assoc coll (-nth entry 0) (-nth entry 1)) - (reduce -conj coll entry))) - - IEmptyableCollection - (-empty [coll] (-with-meta cljs.core.PersistentHashMap.EMPTY meta)) - - IEquiv - (-equiv [coll other] (equiv-map coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-imap __hash)) - - ISeqable - (-seq [coll] - (when (pos? cnt) - (let [s (if-not (nil? root) (.inode-seq root))] - (if has-nil? - (cons [nil nil-val] s) - s)))) - - ICounted - (-count [coll] cnt) - - ILookup - (-lookup [coll k] - (-lookup coll k nil)) - - (-lookup [coll k not-found] - (cond (nil? k) (if has-nil? - nil-val - not-found) - (nil? root) not-found - :else (.inode-lookup root 0 (hash k) k not-found))) - - IAssociative - (-assoc [coll k v] - (if (nil? k) - (if (and has-nil? (identical? v nil-val)) - coll - (PersistentHashMap. meta (if has-nil? cnt (inc cnt)) root true v nil)) - (let [added-leaf? (Box. false) - new-root (-> (if (nil? root) - cljs.core.BitmapIndexedNode.EMPTY - root) - (.inode-assoc 0 (hash k) k v added-leaf?))] - (if (identical? new-root root) - coll - (PersistentHashMap. meta (if ^boolean (.-val added-leaf?) (inc cnt) cnt) new-root has-nil? nil-val nil))))) - - (-contains-key? [coll k] - (cond (nil? k) has-nil? - (nil? root) false - :else (not (identical? (.inode-lookup root 0 (hash k) k lookup-sentinel) - lookup-sentinel)))) - - IMap - (-dissoc [coll k] - (cond (nil? k) (if has-nil? - (PersistentHashMap. meta (dec cnt) root false nil nil) - coll) - (nil? root) coll - :else - (let [new-root (.inode-without root 0 (hash k) k)] - (if (identical? new-root root) - coll - (PersistentHashMap. meta (dec cnt) new-root has-nil? nil-val nil))))) - - IKVReduce - (-kv-reduce [coll f init] - (let [init (if has-nil? (f init nil nil-val) init)] - (cond - (reduced? init) @init - (not (nil? root)) (.kv-reduce root f init) - :else init))) - - IFn - (-invoke [coll k] - (-lookup coll k)) - - (-invoke [coll k not-found] - (-lookup coll k not-found)) - - IEditableCollection - (-as-transient [coll] - (TransientHashMap. (js-obj) root cnt has-nil? nil-val))) - -(set! cljs.core.PersistentHashMap.EMPTY (PersistentHashMap. nil 0 nil false nil 0)) - -(set! cljs.core.PersistentHashMap.fromArrays - (fn [ks vs] - (let [len (alength ks)] - (loop [i 0 ^not-native out (transient cljs.core.PersistentHashMap.EMPTY)] - (if (< i len) - (recur (inc i) (-assoc! out (aget ks i) (aget vs i))) - (persistent! out)))))) - -(deftype TransientHashMap [^:mutable ^boolean edit - ^:mutable root - ^:mutable count - ^:mutable ^boolean has-nil? - ^:mutable nil-val] - Object - (conj! [tcoll o] - (if edit - (if (satisfies? IMapEntry o) - (.assoc! tcoll (key o) (val o)) - (loop [es (seq o) tcoll tcoll] - (if-let [e (first es)] - (recur (next es) - (.assoc! tcoll (key e) (val e))) - tcoll))) - (throw (js/Error. "conj! after persistent")))) - - (assoc! [tcoll k v] - (if edit - (if (nil? k) - (do (if (identical? nil-val v) - nil - (set! nil-val v)) - (if has-nil? - nil - (do (set! count (inc count)) - (set! has-nil? true))) - tcoll) - (let [added-leaf? (Box. false) - node (-> (if (nil? root) - cljs.core.BitmapIndexedNode.EMPTY - root) - (.inode-assoc! edit 0 (hash k) k v added-leaf?))] - (if (identical? node root) - nil - (set! root node)) - (if ^boolean (.-val added-leaf?) - (set! count (inc count))) - tcoll)) - (throw (js/Error. "assoc! after persistent!")))) - - (without! [tcoll k] - (if edit - (if (nil? k) - (if has-nil? - (do (set! has-nil? false) - (set! nil-val nil) - (set! count (dec count)) - tcoll) - tcoll) - (if (nil? root) - tcoll - (let [removed-leaf? (Box. false) - node (.inode-without! root edit 0 (hash k) k removed-leaf?)] - (if (identical? node root) - nil - (set! root node)) - (if (aget removed-leaf? 0) - (set! count (dec count))) - tcoll))) - (throw (js/Error. "dissoc! after persistent!")))) - - (persistent! [tcoll] - (if edit - (do (set! edit nil) - (PersistentHashMap. nil count root has-nil? nil-val nil)) - (throw (js/Error. "persistent! called twice")))) - - ICounted - (-count [coll] - (if edit - count - (throw (js/Error. "count after persistent!")))) - - ILookup - (-lookup [tcoll k] - (if (nil? k) - (if has-nil? - nil-val) - (if (nil? root) - nil - (.inode-lookup root 0 (hash k) k)))) - - (-lookup [tcoll k not-found] - (if (nil? k) - (if has-nil? - nil-val - not-found) - (if (nil? root) - not-found - (.inode-lookup root 0 (hash k) k not-found)))) - - ITransientCollection - (-conj! [tcoll val] (.conj! tcoll val)) - - (-persistent! [tcoll] (.persistent! tcoll)) - - ITransientAssociative - (-assoc! [tcoll key val] (.assoc! tcoll key val)) - - ITransientMap - (-dissoc! [tcoll key] (.without! tcoll key))) - -;;; PersistentTreeMap - -(defn- tree-map-seq-push [node stack ^boolean ascending?] - (loop [t node stack stack] - (if-not (nil? t) - (recur (if ascending? (.-left t) (.-right t)) - (conj stack t)) - stack))) - -(deftype PersistentTreeMapSeq [meta stack ^boolean ascending? cnt ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - ISeqable - (-seq [this] this) - - ISequential - ISeq - (-first [this] (peek stack)) - (-rest [this] - (let [t (first stack) - next-stack (tree-map-seq-push (if ascending? (.-right t) (.-left t)) - (next stack) - ascending?)] - (if-not (nil? next-stack) - (PersistentTreeMapSeq. nil next-stack ascending? (dec cnt) nil) - ()))) - - ICounted - (-count [coll] - (if (neg? cnt) - (inc (count (next coll))) - cnt)) - - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - ICollection - (-conj [coll o] (cons o coll)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY meta)) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - IMeta - (-meta [coll] meta) - - IWithMeta - (-with-meta [coll meta] - (PersistentTreeMapSeq. meta stack ascending? cnt __hash)) - - IReduce - (-reduce [coll f] (seq-reduce f coll)) - (-reduce [coll f start] (seq-reduce f start coll))) - -(defn- create-tree-map-seq [tree ascending? cnt] - (PersistentTreeMapSeq. nil (tree-map-seq-push tree nil ascending?) ascending? cnt nil)) - -(declare RedNode BlackNode) - -(defn- balance-left [key val ins right] - (if (instance? RedNode ins) - (cond - (instance? RedNode (.-left ins)) - (RedNode. (.-key ins) (.-val ins) - (.blacken (.-left ins)) - (BlackNode. key val (.-right ins) right nil) - nil) - - (instance? RedNode (.-right ins)) - (RedNode. (.. ins -right -key) (.. ins -right -val) - (BlackNode. (.-key ins) (.-val ins) - (.-left ins) - (.. ins -right -left) - nil) - (BlackNode. key val - (.. ins -right -right) - right - nil) - nil) - - :else - (BlackNode. key val ins right nil)) - (BlackNode. key val ins right nil))) - -(defn- balance-right [key val left ins] - (if (instance? RedNode ins) - (cond - (instance? RedNode (.-right ins)) - (RedNode. (.-key ins) (.-val ins) - (BlackNode. key val left (.-left ins) nil) - (.blacken (.-right ins)) - nil) - - (instance? RedNode (.-left ins)) - (RedNode. (.. ins -left -key) (.. ins -left -val) - (BlackNode. key val left (.. ins -left -left) nil) - (BlackNode. (.-key ins) (.-val ins) - (.. ins -left -right) - (.-right ins) - nil) - nil) - - :else - (BlackNode. key val left ins nil)) - (BlackNode. key val left ins nil))) - -(defn- balance-left-del [key val del right] - (cond - (instance? RedNode del) - (RedNode. key val (.blacken del) right nil) - - (instance? BlackNode right) - (balance-right key val del (.redden right)) - - (and (instance? RedNode right) (instance? BlackNode (.-left right))) - (RedNode. (.. right -left -key) (.. right -left -val) - (BlackNode. key val del (.. right -left -left) nil) - (balance-right (.-key right) (.-val right) - (.. right -left -right) - (.redden (.-right right))) - nil) - - :else - (throw (js/Error. "red-black tree invariant violation")))) - -(defn- balance-right-del [key val left del] - (cond - (instance? RedNode del) - (RedNode. key val left (.blacken del) nil) - - (instance? BlackNode left) - (balance-left key val (.redden left) del) - - (and (instance? RedNode left) (instance? BlackNode (.-right left))) - (RedNode. (.. left -right -key) (.. left -right -val) - (balance-left (.-key left) (.-val left) - (.redden (.-left left)) - (.. left -right -left)) - (BlackNode. key val (.. left -right -right) del nil) - nil) - - :else - (throw (js/Error. "red-black tree invariant violation")))) - -(defn- tree-map-kv-reduce [node f init] - (let [init (if-not (nil? (.-left node)) - (tree-map-kv-reduce (.-left node) f init) - init)] - (if (reduced? init) - @init - (let [init (f init (.-key node) (.-val node))] - (if (reduced? init) - @init - (let [init (if-not (nil? (.-right node)) - (tree-map-kv-reduce (.-right node) f init) - init)] - (if (reduced? init) - @init - init))))))) - -(deftype BlackNode [key val left right ^:mutable __hash] - Object - (add-left [node ins] - (.balance-left ins node)) - - (add-right [node ins] - (.balance-right ins node)) - - (remove-left [node del] - (balance-left-del key val del right)) - - (remove-right [node del] - (balance-right-del key val left del)) - - (blacken [node] node) - - (redden [node] (RedNode. key val left right nil)) - - (balance-left [node parent] - (BlackNode. (.-key parent) (.-val parent) node (.-right parent) nil)) - - (balance-right [node parent] - (BlackNode. (.-key parent) (.-val parent) (.-left parent) node nil)) - - (replace [node key val left right] - (BlackNode. key val left right nil)) - - (kv-reduce [node f init] - (tree-map-kv-reduce node f init)) - - IMapEntry - (-key [node] key) - (-val [node] val) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IMeta - (-meta [node] nil) - - IWithMeta - (-with-meta [node meta] - (with-meta [key val] meta)) - - IStack - (-peek [node] val) - - (-pop [node] [key]) - - ICollection - (-conj [node o] [key val o]) - - IEmptyableCollection - (-empty [node] []) - - ISequential - ISeqable - (-seq [node] (list key val)) - - ICounted - (-count [node] 2) - - IIndexed - (-nth [node n] - (cond (== n 0) key - (== n 1) val - :else nil)) - - (-nth [node n not-found] - (cond (== n 0) key - (== n 1) val - :else not-found)) - - ILookup - (-lookup [node k] (-nth node k nil)) - (-lookup [node k not-found] (-nth node k not-found)) - - IAssociative - (-assoc [node k v] - (assoc [key val] k v)) - - IVector - (-assoc-n [node n v] - (-assoc-n [key val] n v)) - - IReduce - (-reduce [node f] - (ci-reduce node f)) - - (-reduce [node f start] - (ci-reduce node f start)) - - IFn - (-invoke [node k] - (-lookup node k)) - - (-invoke [node k not-found] - (-lookup node k not-found))) - -(deftype RedNode [key val left right ^:mutable __hash] - Object - (add-left [node ins] - (RedNode. key val ins right nil)) - - (add-right [node ins] - (RedNode. key val left ins nil)) - - (remove-left [node del] - (RedNode. key val del right nil)) - - (remove-right [node del] - (RedNode. key val left del nil)) - - (blacken [node] - (BlackNode. key val left right nil)) - - (redden [node] - (throw (js/Error. "red-black tree invariant violation"))) - - (balance-left [node parent] - (cond - (instance? RedNode left) - (RedNode. key val - (.blacken left) - (BlackNode. (.-key parent) (.-val parent) right (.-right parent) nil) - nil) - - (instance? RedNode right) - (RedNode. (.-key right) (.-val right) - (BlackNode. key val left (.-left right) nil) - (BlackNode. (.-key parent) (.-val parent) - (.-right right) - (.-right parent) - nil) - nil) - - :else - (BlackNode. (.-key parent) (.-val parent) node (.-right parent) nil))) - - (balance-right [node parent] - (cond - (instance? RedNode right) - (RedNode. key val - (BlackNode. (.-key parent) (.-val parent) - (.-left parent) - left - nil) - (.blacken right) - nil) - - (instance? RedNode left) - (RedNode. (.-key left) (.-val left) - (BlackNode. (.-key parent) (.-val parent) - (.-left parent) - (.-left left) - nil) - (BlackNode. key val (.-right left) right nil) - nil) - - :else - (BlackNode. (.-key parent) (.-val parent) (.-left parent) node nil))) - - (replace [node key val left right] - (RedNode. key val left right nil)) - - (kv-reduce [node f init] - (tree-map-kv-reduce node f init)) - - IMapEntry - (-key [node] key) - (-val [node] val) - - IHash - (-hash [coll] (caching-hash coll hash-coll __hash)) - - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - IMeta - (-meta [node] nil) - - IWithMeta - (-with-meta [node meta] - (with-meta [key val] meta)) - - IStack - (-peek [node] val) - - (-pop [node] [key]) - - ICollection - (-conj [node o] [key val o]) - - IEmptyableCollection - (-empty [node] []) - - ISequential - ISeqable - (-seq [node] (list key val)) - - ICounted - (-count [node] 2) - - IIndexed - (-nth [node n] - (cond (== n 0) key - (== n 1) val - :else nil)) - - (-nth [node n not-found] - (cond (== n 0) key - (== n 1) val - :else not-found)) - - ILookup - (-lookup [node k] (-nth node k nil)) - (-lookup [node k not-found] (-nth node k not-found)) - - IAssociative - (-assoc [node k v] - (assoc [key val] k v)) - - IVector - (-assoc-n [node n v] - (-assoc-n [key val] n v)) - - IReduce - (-reduce [node f] - (ci-reduce node f)) - - (-reduce [node f start] - (ci-reduce node f start)) - - IFn - (-invoke [node k] - (-lookup node k)) - - (-invoke [node k not-found] - (-lookup node k not-found))) - -(defn- tree-map-add [comp tree k v found] - (if (nil? tree) - (RedNode. k v nil nil nil) - (let [c (comp k (.-key tree))] - (cond - (zero? c) - (do (aset found 0 tree) - nil) - - (neg? c) - (let [ins (tree-map-add comp (.-left tree) k v found)] - (if-not (nil? ins) - (.add-left tree ins))) - - :else - (let [ins (tree-map-add comp (.-right tree) k v found)] - (if-not (nil? ins) - (.add-right tree ins))))))) - -(defn- tree-map-append [left right] - (cond - (nil? left) - right - - (nil? right) - left - - (instance? RedNode left) - (if (instance? RedNode right) - (let [app (tree-map-append (.-right left) (.-left right))] - (if (instance? RedNode app) - (RedNode. (.-key app) (.-val app) - (RedNode. (.-key left) (.-val left) - (.-left left) - (.-left app) - nil) - (RedNode. (.-key right) (.-val right) - (.-right app) - (.-right right) - nil) - nil) - (RedNode. (.-key left) (.-val left) - (.-left left) - (RedNode. (.-key right) (.-val right) app (.-right right) nil) - nil))) - (RedNode. (.-key left) (.-val left) - (.-left left) - (tree-map-append (.-right left) right) - nil)) - - (instance? RedNode right) - (RedNode. (.-key right) (.-val right) - (tree-map-append left (.-left right)) - (.-right right) - nil) - - :else - (let [app (tree-map-append (.-right left) (.-left right))] - (if (instance? RedNode app) - (RedNode. (.-key app) (.-val app) - (BlackNode. (.-key left) (.-val left) - (.-left left) - (.-left app) - nil) - (BlackNode. (.-key right) (.-val right) - (.-right app) - (.-right right) - nil) - nil) - (balance-left-del (.-key left) (.-val left) - (.-left left) - (BlackNode. (.-key right) (.-val right) - app - (.-right right) - nil)))))) - -(defn- tree-map-remove [comp tree k found] - (if-not (nil? tree) - (let [c (comp k (.-key tree))] - (cond - (zero? c) - (do (aset found 0 tree) - (tree-map-append (.-left tree) (.-right tree))) - - (neg? c) - (let [del (tree-map-remove comp (.-left tree) k found)] - (if (or (not (nil? del)) (not (nil? (aget found 0)))) - (if (instance? BlackNode (.-left tree)) - (balance-left-del (.-key tree) (.-val tree) del (.-right tree)) - (RedNode. (.-key tree) (.-val tree) del (.-right tree) nil)))) - - :else - (let [del (tree-map-remove comp (.-right tree) k found)] - (if (or (not (nil? del)) (not (nil? (aget found 0)))) - (if (instance? BlackNode (.-right tree)) - (balance-right-del (.-key tree) (.-val tree) (.-left tree) del) - (RedNode. (.-key tree) (.-val tree) (.-left tree) del nil)))))))) - -(defn- tree-map-replace [comp tree k v] - (let [tk (.-key tree) - c (comp k tk)] - (cond (zero? c) (.replace tree tk v (.-left tree) (.-right tree)) - (neg? c) (.replace tree tk (.-val tree) (tree-map-replace comp (.-left tree) k v) (.-right tree)) - :else (.replace tree tk (.-val tree) (.-left tree) (tree-map-replace comp (.-right tree) k v))))) - -(declare key) - -(deftype PersistentTreeMap [comp tree cnt meta ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - (entry-at [coll k] - (loop [t tree] - (if-not (nil? t) - (let [c (comp k (.-key t))] - (cond (zero? c) t - (neg? c) (recur (.-left t)) - :else (recur (.-right t))))))) - - ICloneable - (-clone [_] (PersistentTreeMap. comp tree cnt meta __hash)) - - IWithMeta - (-with-meta [coll meta] (PersistentTreeMap. comp tree cnt meta __hash)) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll entry] - (if (vector? entry) - (-assoc coll (-nth entry 0) (-nth entry 1)) - (reduce -conj - coll - entry))) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.PersistentTreeMap.EMPTY meta)) - - IEquiv - (-equiv [coll other] (equiv-map coll other)) - - IHash - (-hash [coll] (caching-hash coll hash-imap __hash)) - - ICounted - (-count [coll] cnt) - - IKVReduce - (-kv-reduce [coll f init] - (if-not (nil? tree) - (tree-map-kv-reduce tree f init) - init)) - - IFn - (-invoke [coll k] - (-lookup coll k)) - - (-invoke [coll k not-found] - (-lookup coll k not-found)) - - ISeqable - (-seq [coll] - (if (pos? cnt) - (create-tree-map-seq tree true cnt))) - - IReversible - (-rseq [coll] - (if (pos? cnt) - (create-tree-map-seq tree false cnt))) - - ILookup - (-lookup [coll k] - (-lookup coll k nil)) - - (-lookup [coll k not-found] - (let [n (.entry-at coll k)] - (if-not (nil? n) - (.-val n) - not-found))) - - IAssociative - (-assoc [coll k v] - (let [found (array nil) - t (tree-map-add comp tree k v found)] - (if (nil? t) - (let [found-node (nth found 0)] - (if (= v (.-val found-node)) - coll - (PersistentTreeMap. comp (tree-map-replace comp tree k v) cnt meta nil))) - (PersistentTreeMap. comp (.blacken t) (inc cnt) meta nil)))) - - (-contains-key? [coll k] - (not (nil? (.entry-at coll k)))) - - IMap - (-dissoc [coll k] - (let [found (array nil) - t (tree-map-remove comp tree k found)] - (if (nil? t) - (if (nil? (nth found 0)) - coll - (PersistentTreeMap. comp nil 0 meta nil)) - (PersistentTreeMap. comp (.blacken t) (dec cnt) meta nil)))) - - ISorted - (-sorted-seq [coll ascending?] - (if (pos? cnt) - (create-tree-map-seq tree ascending? cnt))) - - (-sorted-seq-from [coll k ascending?] - (if (pos? cnt) - (loop [stack nil t tree] - (if-not (nil? t) - (let [c (comp k (.-key t))] - (cond - (zero? c) (PersistentTreeMapSeq. nil (conj stack t) ascending? -1 nil) - ascending? (if (neg? c) - (recur (conj stack t) (.-left t)) - (recur stack (.-right t))) - :else (if (pos? c) - (recur (conj stack t) (.-right t)) - (recur stack (.-left t))))) - (when-not (nil? stack) - (PersistentTreeMapSeq. nil stack ascending? -1 nil)))))) - - (-entry-key [coll entry] (key entry)) - - (-comparator [coll] comp)) - -(set! cljs.core.PersistentTreeMap.EMPTY (PersistentTreeMap. compare nil 0 nil 0)) - -(defn hash-map - "keyval => key val - Returns a new hash map with supplied mappings." - [& keyvals] - (loop [in (seq keyvals), out (transient cljs.core.PersistentHashMap.EMPTY)] - (if in - (recur (nnext in) (assoc! out (first in) (second in))) - (persistent! out)))) - -(defn array-map - "keyval => key val - Returns a new array map with supplied mappings." - [& keyvals] - (PersistentArrayMap. nil (quot (count keyvals) 2) (apply array keyvals) nil)) - -(defn obj-map - "keyval => key val - Returns a new object map with supplied mappings." - [& keyvals] - (let [ks (array) - obj (js-obj)] - (loop [kvs (seq keyvals)] - (if kvs - (do (.push ks (first kvs)) - (aset obj (first kvs) (second kvs)) - (recur (nnext kvs))) - (cljs.core.ObjMap.fromObject ks obj))))) - -(defn sorted-map - "keyval => key val - Returns a new sorted map with supplied mappings." - ([& keyvals] - (loop [in (seq keyvals) out cljs.core.PersistentTreeMap.EMPTY] - (if in - (recur (nnext in) (assoc out (first in) (second in))) - out)))) - -(defn sorted-map-by - "keyval => key val - Returns a new sorted map with supplied mappings, using the supplied comparator." - ([comparator & keyvals] - (loop [in (seq keyvals) - out (cljs.core.PersistentTreeMap. (fn->comparator comparator) nil 0 nil 0)] - (if in - (recur (nnext in) (assoc out (first in) (second in))) - out)))) - -(deftype KeySeq [^not-native mseq _meta] - Object - (toString [coll] - (pr-str* coll)) - - IMeta - (-meta [coll] _meta) - - IWithMeta - (-with-meta [coll new-meta] (KeySeq. mseq new-meta)) - - ISeqable - (-seq [coll] coll) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - ICollection - (-conj [coll o] - (cons o coll)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY _meta)) - - IHash - (-hash [coll] (hash-coll coll)) - - ISeq - (-first [coll] - (let [^not-native me (-first mseq)] - (-key me))) - - (-rest [coll] - (let [nseq (if (satisfies? INext mseq) - (-next mseq) - (next mseq))] - (if-not (nil? nseq) - (KeySeq. nseq _meta) - ()))) - - INext - (-next [coll] - (let [nseq (if (satisfies? INext mseq) - (-next mseq) - (next mseq))] - (when-not (nil? nseq) - (KeySeq. nseq _meta)))) - - IReduce - (-reduce [coll f] (seq-reduce f coll)) - (-reduce [coll f start] (seq-reduce f start coll))) - -(defn keys - "Returns a sequence of the map's keys." - [hash-map] - (when-let [mseq (seq hash-map)] - (KeySeq. mseq nil))) - -(defn key - "Returns the key of the map entry." - [map-entry] - (-key map-entry)) - -(deftype ValSeq [^not-native mseq _meta] - Object - (toString [coll] - (pr-str* coll)) - - IMeta - (-meta [coll] _meta) - - IWithMeta - (-with-meta [coll new-meta] (ValSeq. mseq new-meta)) - - ISeqable - (-seq [coll] coll) - - ISequential - IEquiv - (-equiv [coll other] (equiv-sequential coll other)) - - ICollection - (-conj [coll o] - (cons o coll)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.List.EMPTY _meta)) - - IHash - (-hash [coll] (hash-coll coll)) - - ISeq - (-first [coll] - (let [^not-native me (-first mseq)] - (-val me))) - - (-rest [coll] - (let [nseq (if (satisfies? INext mseq) - (-next mseq) - (next mseq))] - (if-not (nil? nseq) - (ValSeq. nseq _meta) - ()))) - - INext - (-next [coll] - (let [nseq (if (satisfies? INext mseq) - (-next mseq) - (next mseq))] - (when-not (nil? nseq) - (ValSeq. nseq _meta)))) - - IReduce - (-reduce [coll f] (seq-reduce f coll)) - (-reduce [coll f start] (seq-reduce f start coll))) - -(defn vals - "Returns a sequence of the map's values." - [hash-map] - (when-let [mseq (seq hash-map)] - (ValSeq. mseq nil))) - -(defn val - "Returns the value in the map entry." - [map-entry] - (-val map-entry)) - -(defn merge - "Returns a map that consists of the rest of the maps conj-ed onto - the first. If a key occurs in more than one map, the mapping from - the latter (left-to-right) will be the mapping in the result." - [& maps] - (when (some identity maps) - (reduce #(conj (or %1 {}) %2) maps))) - -(defn merge-with - "Returns a map that consists of the rest of the maps conj-ed onto - the first. If a key occurs in more than one map, the mapping(s) - from the latter (left-to-right) will be combined with the mapping in - the result by calling (f val-in-result val-in-latter)." - [f & maps] - (when (some identity maps) - (let [merge-entry (fn [m e] - (let [k (first e) v (second e)] - (if (contains? m k) - (assoc m k (f (get m k) v)) - (assoc m k v)))) - merge2 (fn [m1 m2] - (reduce merge-entry (or m1 {}) (seq m2)))] - (reduce merge2 maps)))) - -(defn select-keys - "Returns a map containing only those entries in map whose key is in keys" - [map keyseq] - (loop [ret {} keys (seq keyseq)] - (if keys - (let [key (first keys) - entry (get map key ::not-found)] - (recur - (if (not= entry ::not-found) - (assoc ret key entry) - ret) - (next keys))) - ret))) - -;;; PersistentHashSet - -(declare TransientHashSet) - -(deftype PersistentHashSet [meta hash-map ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - ICloneable - (-clone [_] (PersistentHashSet. meta hash-map __hash)) - - IWithMeta - (-with-meta [coll meta] (PersistentHashSet. meta hash-map __hash)) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll o] - (PersistentHashSet. meta (assoc hash-map o nil) nil)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.PersistentHashSet.EMPTY meta)) - - IEquiv - (-equiv [coll other] - (and - (set? other) - (== (count coll) (count other)) - (every? #(contains? coll %) - other))) - - IHash - (-hash [coll] (caching-hash coll hash-iset __hash)) - - ISeqable - (-seq [coll] (keys hash-map)) - - ICounted - (-count [coll] (-count hash-map)) - - ILookup - (-lookup [coll v] - (-lookup coll v nil)) - (-lookup [coll v not-found] - (if (-contains-key? hash-map v) - v - not-found)) - - ISet - (-disjoin [coll v] - (PersistentHashSet. meta (-dissoc hash-map v) nil)) - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found)) - - IEditableCollection - (-as-transient [coll] (TransientHashSet. (-as-transient hash-map)))) - -(set! cljs.core.PersistentHashSet.EMPTY - (PersistentHashSet. nil cljs.core.PersistentArrayMap.EMPTY 0)) - -(set! cljs.core.PersistentHashSet.fromArray - (fn [items ^boolean no-clone] - (let [len (alength items)] - (if (<= len cljs.core.PersistentArrayMap.HASHMAP_THRESHOLD) - (let [arr (if no-clone items (aclone items))] - (loop [i 0 - out (transient cljs.core.PersistentArrayMap.EMPTY)] - (if (< i len) - (recur (inc i) (-assoc! out (aget items i) nil)) - (cljs.core.PersistentHashSet. nil (-persistent! out) nil)))) - (loop [i 0 - out (transient cljs.core.PersistentHashSet.EMPTY)] - (if (< i len) - (recur (inc i) (-conj! out (aget items i))) - (-persistent! out))))))) - -(deftype TransientHashSet [^:mutable transient-map] - ITransientCollection - (-conj! [tcoll o] - (set! transient-map (assoc! transient-map o nil)) - tcoll) - - (-persistent! [tcoll] - (PersistentHashSet. nil (persistent! transient-map) nil)) - - ITransientSet - (-disjoin! [tcoll v] - (set! transient-map (dissoc! transient-map v)) - tcoll) - - ICounted - (-count [tcoll] (count transient-map)) - - ILookup - (-lookup [tcoll v] - (-lookup tcoll v nil)) - - (-lookup [tcoll v not-found] - (if (identical? (-lookup transient-map v lookup-sentinel) lookup-sentinel) - not-found - v)) - - IFn - (-invoke [tcoll k] - (if (identical? (-lookup transient-map k lookup-sentinel) lookup-sentinel) - nil - k)) - - (-invoke [tcoll k not-found] - (if (identical? (-lookup transient-map k lookup-sentinel) lookup-sentinel) - not-found - k))) - -(deftype PersistentTreeSet [meta tree-map ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - ICloneable - (-clone [_] (PersistentTreeSet. meta tree-map __hash)) - - IWithMeta - (-with-meta [coll meta] (PersistentTreeSet. meta tree-map __hash)) - - IMeta - (-meta [coll] meta) - - ICollection - (-conj [coll o] - (PersistentTreeSet. meta (assoc tree-map o nil) nil)) - - IEmptyableCollection - (-empty [coll] (with-meta cljs.core.PersistentTreeSet.EMPTY meta)) - - IEquiv - (-equiv [coll other] - (and - (set? other) - (== (count coll) (count other)) - (every? #(contains? coll %) - other))) - - IHash - (-hash [coll] (caching-hash coll hash-iset __hash)) - - ISeqable - (-seq [coll] (keys tree-map)) - - ISorted - (-sorted-seq [coll ascending?] - (map key (-sorted-seq tree-map ascending?))) - - (-sorted-seq-from [coll k ascending?] - (map key (-sorted-seq-from tree-map k ascending?))) - - (-entry-key [coll entry] entry) - - (-comparator [coll] (-comparator tree-map)) - - IReversible - (-rseq [coll] - (if (pos? (count tree-map)) - (map key (rseq tree-map)))) - - ICounted - (-count [coll] (count tree-map)) - - ILookup - (-lookup [coll v] - (-lookup coll v nil)) - (-lookup [coll v not-found] - (let [n (.entry-at tree-map v)] - (if-not (nil? n) - (.-key n) - not-found))) - - ISet - (-disjoin [coll v] - (PersistentTreeSet. meta (dissoc tree-map v) nil)) - - IFn - (-invoke [coll k] - (-lookup coll k)) - (-invoke [coll k not-found] - (-lookup coll k not-found))) - -(set! cljs.core.PersistentTreeSet.EMPTY - (PersistentTreeSet. nil cljs.core.PersistentTreeMap.EMPTY 0)) - -(defn set-from-indexed-seq [iseq] - (let [arr (.-arr iseq) - ret (areduce arr i ^not-native res (-as-transient #{}) - (-conj! res (aget arr i)))] - (-persistent! ^not-native ret))) - -(defn set - "Returns a set of the distinct elements of coll." - [coll] - (let [^not-native in (seq coll)] - (cond - (nil? in) #{} - - (and (instance? IndexedSeq in) (zero? (.-i in))) - (set-from-indexed-seq in) - - :else - (loop [in in - ^not-native out (-as-transient #{})] - (if-not (nil? in) - (recur (-next in) (-conj! out (-first in))) - (-persistent! out)))))) - -(defn hash-set - ([] #{}) - ([& keys] (set keys))) - -(defn sorted-set - "Returns a new sorted set with supplied keys." - ([& keys] - (reduce -conj cljs.core.PersistentTreeSet.EMPTY keys))) - -(defn sorted-set-by - "Returns a new sorted set with supplied keys, using the supplied comparator." - ([comparator & keys] - (reduce -conj - (cljs.core.PersistentTreeSet. nil (sorted-map-by comparator) 0) - keys))) - -(defn replace - "Given a map of replacement pairs and a vector/collection, returns a - vector/seq with any elements = a key in smap replaced with the - corresponding val in smap" - [smap coll] - (if (vector? coll) - (let [n (count coll)] - (reduce (fn [v i] - (if-let [e (find smap (nth v i))] - (assoc v i (second e)) - v)) - coll (take n (iterate inc 0)))) - (map #(if-let [e (find smap %)] (second e) %) coll))) - -(defn distinct - "Returns a lazy sequence of the elements of coll with duplicates removed" - [coll] - (let [step (fn step [xs seen] - (lazy-seq - ((fn [[f :as xs] seen] - (when-let [s (seq xs)] - (if (contains? seen f) - (recur (rest s) seen) - (cons f (step (rest s) (conj seen f)))))) - xs seen)))] - (step coll #{}))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn butlast [s] - (loop [ret [] s s] - (if (next s) - (recur (conj ret (first s)) (next s)) - (seq ret)))) - -(defn name - "Returns the name String of a string, symbol or keyword." - [x] - (if (implements? INamed x) - (-name ^not-native x) - (if (string? x) - x - (throw (js/Error. (str "Doesn't support name: " x)))))) - -(defn zipmap - "Returns a map with the keys mapped to the corresponding vals." - [keys vals] - (loop [map (transient {}) - ks (seq keys) - vs (seq vals)] - (if (and ks vs) - (recur (assoc! map (first ks) (first vs)) - (next ks) - (next vs)) - (persistent! map)))) - -(defn max-key - "Returns the x for which (k x), a number, is greatest." - ([k x] x) - ([k x y] (if (> (k x) (k y)) x y)) - ([k x y & more] - (reduce #(max-key k %1 %2) (max-key k x y) more))) - -(defn min-key - "Returns the x for which (k x), a number, is least." - ([k x] x) - ([k x y] (if (< (k x) (k y)) x y)) - ([k x y & more] - (reduce #(min-key k %1 %2) (min-key k x y) more))) - -(defn partition-all - "Returns a lazy sequence of lists like partition, but may include - partitions with fewer than n items at the end." - ([n coll] - (partition-all n n coll)) - ([n step coll] - (lazy-seq - (when-let [s (seq coll)] - (cons (take n s) (partition-all n step (drop step s))))))) - -(defn take-while - "Returns a lazy sequence of successive items from coll while - (pred item) returns true. pred must be free of side-effects." - [pred coll] - (lazy-seq - (when-let [s (seq coll)] - (when (pred (first s)) - (cons (first s) (take-while pred (rest s))))))) - -(defn mk-bound-fn - [sc test key] - (fn [e] - (let [comp (-comparator sc)] - (test (comp (-entry-key sc e) key) 0)))) - -(defn subseq - "sc must be a sorted collection, test(s) one of <, <=, > or - >=. Returns a seq of those entries with keys ek for - which (test (.. sc comparator (compare ek key)) 0) is true" - ([sc test key] - (let [include (mk-bound-fn sc test key)] - (if (#{> >=} test) - (when-let [[e :as s] (-sorted-seq-from sc key true)] - (if (include e) s (next s))) - (take-while include (-sorted-seq sc true))))) - ([sc start-test start-key end-test end-key] - (when-let [[e :as s] (-sorted-seq-from sc start-key true)] - (take-while (mk-bound-fn sc end-test end-key) - (if ((mk-bound-fn sc start-test start-key) e) s (next s)))))) - -(defn rsubseq - "sc must be a sorted collection, test(s) one of <, <=, > or - >=. Returns a reverse seq of those entries with keys ek for - which (test (.. sc comparator (compare ek key)) 0) is true" - ([sc test key] - (let [include (mk-bound-fn sc test key)] - (if (#{< <=} test) - (when-let [[e :as s] (-sorted-seq-from sc key false)] - (if (include e) s (next s))) - (take-while include (-sorted-seq sc false))))) - ([sc start-test start-key end-test end-key] - (when-let [[e :as s] (-sorted-seq-from sc end-key false)] - (take-while (mk-bound-fn sc start-test start-key) - (if ((mk-bound-fn sc end-test end-key) e) s (next s)))))) - -(deftype Range [meta start end step ^:mutable __hash] - Object - (toString [coll] - (pr-str* coll)) - - ICloneable - (-clone [_] (Range. meta start end step __hash)) - - IWithMeta - (-with-meta [rng meta] (Range. meta start end step __hash)) - - IMeta - (-meta [rng] meta) - - ISeqable - (-seq [rng] - (if (pos? step) - (when (< start end) - rng) - (when (> start end) - rng))) - - ISeq - (-first [rng] - (when-not (nil? (-seq rng)) start)) - (-rest [rng] - (if-not (nil? (-seq rng)) - (Range. meta (+ start step) end step nil) - ())) - - INext - (-next [rng] - (if (pos? step) - (when (< (+ start step) end) - (Range. meta (+ start step) end step nil)) - (when (> (+ start step) end) - (Range. meta (+ start step) end step nil)))) - - ICollection - (-conj [rng o] (cons o rng)) - - IEmptyableCollection - (-empty [rng] (with-meta cljs.core.List.EMPTY meta)) - - ISequential - IEquiv - (-equiv [rng other] (equiv-sequential rng other)) - - IHash - (-hash [rng] (caching-hash rng hash-coll __hash)) - - ICounted - (-count [rng] - (if-not (-seq rng) - 0 - (js/Math.ceil (/ (- end start) step)))) - - IIndexed - (-nth [rng n] - (if (< n (-count rng)) - (+ start (* n step)) - (if (and (> start end) (zero? step)) - start - (throw (js/Error. "Index out of bounds"))))) - (-nth [rng n not-found] - (if (< n (-count rng)) - (+ start (* n step)) - (if (and (> start end) (zero? step)) - start - not-found))) - - IReduce - (-reduce [rng f] (ci-reduce rng f)) - (-reduce [rng f s] (ci-reduce rng f s))) - -(defn range - "Returns a lazy seq of nums from start (inclusive) to end - (exclusive), by step, where start defaults to 0, step to 1, - and end to infinity." - ([] (range 0 js/Number.MAX_VALUE 1)) - ([end] (range 0 end 1)) - ([start end] (range start end 1)) - ([start end step] (Range. nil start end step nil))) - -(defn take-nth - "Returns a lazy seq of every nth item in coll." - [n coll] - (lazy-seq - (when-let [s (seq coll)] - (cons (first s) (take-nth n (drop n s)))))) - -(defn split-with - "Returns a vector of [(take-while pred coll) (drop-while pred coll)]" - [pred coll] - [(take-while pred coll) (drop-while pred coll)]) - -(defn partition-by - "Applies f to each value in coll, splitting it each time f returns - a new value. Returns a lazy seq of partitions." - [f coll] - (lazy-seq - (when-let [s (seq coll)] - (let [fst (first s) - fv (f fst) - run (cons fst (take-while #(= fv (f %)) (next s)))] - (cons run (partition-by f (seq (drop (count run) s)))))))) - -(defn frequencies - "Returns a map from distinct items in coll to the number of times - they appear." - [coll] - (persistent! - (reduce (fn [counts x] - (assoc! counts x (inc (get counts x 0)))) - (transient {}) coll))) - -(defn reductions - "Returns a lazy seq of the intermediate values of the reduction (as - per reduce) of coll by f, starting with init." - ([f coll] - (lazy-seq - (if-let [s (seq coll)] - (reductions f (first s) (rest s)) - (list (f))))) - ([f init coll] - (cons init - (lazy-seq - (when-let [s (seq coll)] - (reductions f (f init (first s)) (rest s))))))) - -(defn juxt - "Takes a set of functions and returns a fn that is the juxtaposition - of those fns. The returned fn takes a variable number of args, and - returns a vector containing the result of applying each fn to the - args (left-to-right). - ((juxt a b c) x) => [(a x) (b x) (c x)]" - ([f] - (fn - ([] (vector (f))) - ([x] (vector (f x))) - ([x y] (vector (f x y))) - ([x y z] (vector (f x y z))) - ([x y z & args] (vector (apply f x y z args))))) - ([f g] - (fn - ([] (vector (f) (g))) - ([x] (vector (f x) (g x))) - ([x y] (vector (f x y) (g x y))) - ([x y z] (vector (f x y z) (g x y z))) - ([x y z & args] (vector (apply f x y z args) (apply g x y z args))))) - ([f g h] - (fn - ([] (vector (f) (g) (h))) - ([x] (vector (f x) (g x) (h x))) - ([x y] (vector (f x y) (g x y) (h x y))) - ([x y z] (vector (f x y z) (g x y z) (h x y z))) - ([x y z & args] (vector (apply f x y z args) (apply g x y z args) (apply h x y z args))))) - ([f g h & fs] - (let [fs (list* f g h fs)] - (fn - ([] (reduce #(conj %1 (%2)) [] fs)) - ([x] (reduce #(conj %1 (%2 x)) [] fs)) - ([x y] (reduce #(conj %1 (%2 x y)) [] fs)) - ([x y z] (reduce #(conj %1 (%2 x y z)) [] fs)) - ([x y z & args] (reduce #(conj %1 (apply %2 x y z args)) [] fs)))))) - -(defn dorun - "When lazy sequences are produced via functions that have side - effects, any effects other than those needed to produce the first - element in the seq do not occur until the seq is consumed. dorun can - be used to force any effects. Walks through the successive nexts of - the seq, does not retain the head and returns nil." - ([coll] - (when (seq coll) - (recur (next coll)))) - ([n coll] - (when (and (seq coll) (pos? n)) - (recur (dec n) (next coll))))) - -(defn doall - "When lazy sequences are produced via functions that have side - effects, any effects other than those needed to produce the first - element in the seq do not occur until the seq is consumed. doall can - be used to force any effects. Walks through the successive nexts of - the seq, retains the head and returns it, thus causing the entire - seq to reside in memory at one time." - ([coll] - (dorun coll) - coll) - ([n coll] - (dorun n coll) - coll)) - -;;;;;;;;;;;;;;;;;;;;;;;;; Regular Expressions ;;;;;;;;;; - -(defn regexp? [o] - (instance? js/RegExp o)) - -(defn re-matches - "Returns the result of (re-find re s) if re fully matches s." - [re s] - (let [matches (.exec re s)] - (when (= (first matches) s) - (if (== (count matches) 1) - (first matches) - (vec matches))))) - -(defn re-find - "Returns the first regex match, if any, of s to re, using - re.exec(s). Returns a vector, containing first the matching - substring, then any capturing groups if the regular expression contains - capturing groups." - [re s] - (let [matches (.exec re s)] - (when-not (nil? matches) - (if (== (count matches) 1) - (first matches) - (vec matches))))) - -(defn re-seq - "Returns a lazy sequence of successive matches of re in s." - [re s] - (let [match-data (re-find re s) - match-idx (.search s re) - match-str (if (coll? match-data) (first match-data) match-data) - post-match (subs s (+ match-idx (count match-str)))] - (when match-data (lazy-seq (cons match-data (when (seq post-match) (re-seq re post-match))))))) - -(defn re-pattern - "Returns an instance of RegExp which has compiled the provided string." - [s] - (let [[_ flags pattern] (re-find #"^(?:\(\?([idmsux]*)\))?(.*)" s)] - (js/RegExp. pattern flags))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Printing ;;;;;;;;;;;;;;;; - -(defn pr-sequential-writer [writer print-one begin sep end opts coll] - (binding [*print-level* (when-not (nil? *print-level*) (dec *print-level*))] - (if (and (not (nil? *print-level*)) (neg? *print-level*)) - (-write writer "#") - (do - (-write writer begin) - (when (seq coll) - (print-one (first coll) writer opts)) - (loop [coll (next coll) n (:print-length opts)] - (when (and coll (or (nil? n) (not (zero? n)))) - (-write writer sep) - (print-one (first coll) writer opts) - (recur (next coll) (dec n)))) - (when (:print-length opts) - (-write writer sep) - (print-one "..." writer opts)) - (-write writer end))))) - -(defn write-all [writer & ss] - (doseq [s ss] - (-write writer s))) - -(defn string-print [x] - (*print-fn* x) - nil) - -(defn flush [] ;stub - nil) - -(def ^:private char-escapes - (js-obj - "\"" "\\\"" - "\\" "\\\\" - "\b" "\\b" - "\f" "\\f" - "\n" "\\n" - "\r" "\\r" - "\t" "\\t")) - -(defn ^:private quote-string - [s] - (str \" - (.replace s (js/RegExp "[\\\\\"\b\f\n\r\t]" "g") - (fn [match] (aget char-escapes match))) - \")) - -(declare print-map) - -(defn- pr-writer - "Prefer this to pr-seq, because it makes the printing function - configurable, allowing efficient implementations such as appending - to a StringBuffer." - [obj writer opts] - (cond - (nil? obj) (-write writer "nil") - (undefined? obj) (-write writer "#") - :else (do - (when (and (get opts :meta) - (satisfies? IMeta obj) - (meta obj)) - (-write writer "^") - (pr-writer (meta obj) writer opts) - (-write writer " ")) - (cond - (nil? obj) (-write writer "nil") - - ;; handle CLJS ctors - ^boolean (.-cljs$lang$type obj) - (.cljs$lang$ctorPrWriter obj obj writer opts) - - ; Use the new, more efficient, IPrintWithWriter interface when possible. - (implements? IPrintWithWriter obj) - (-pr-writer ^not-native obj writer opts) - - (or (identical? (type obj) js/Boolean) (number? obj)) - (-write writer (str obj)) - - (object? obj) - (do - (-write writer "#js ") - (print-map - (map (fn [k] [(keyword k) (aget obj k)]) (js-keys obj)) - pr-writer writer opts)) - - (array? obj) - (pr-sequential-writer writer pr-writer "#js [" " " "]" opts obj) - - ^boolean (goog/isString obj) - (if (:readably opts) - (-write writer (quote-string obj)) - (-write writer obj)) - - (fn? obj) - (write-all writer "#<" (str obj) ">") - - (instance? js/Date obj) - (let [normalize (fn [n len] - (loop [ns (str n)] - (if (< (count ns) len) - (recur (str "0" ns)) - ns)))] - (write-all writer - "#inst \"" - (str (.getUTCFullYear obj)) "-" - (normalize (inc (.getUTCMonth obj)) 2) "-" - (normalize (.getUTCDate obj) 2) "T" - (normalize (.getUTCHours obj) 2) ":" - (normalize (.getUTCMinutes obj) 2) ":" - (normalize (.getUTCSeconds obj) 2) "." - (normalize (.getUTCMilliseconds obj) 3) "-" - "00:00\"")) - - (regexp? obj) (write-all writer "#\"" (.-source obj) "\"") - - (satisfies? IPrintWithWriter obj) - (-pr-writer obj writer opts) - - :else (write-all writer "#<" (str obj) ">"))))) - -(defn pr-seq-writer [objs writer opts] - (pr-writer (first objs) writer opts) - (doseq [obj (next objs)] - (-write writer " ") - (pr-writer obj writer opts))) - -(defn- pr-sb-with-opts [objs opts] - (let [sb (gstring/StringBuffer.) - writer (StringBufferWriter. sb)] - (pr-seq-writer objs writer opts) - (-flush writer) - sb)) - -(defn pr-str-with-opts - "Prints a sequence of objects to a string, observing all the - options given in opts" - [objs opts] - (if (empty? objs) - "" - (str (pr-sb-with-opts objs opts)))) - -(defn prn-str-with-opts - "Same as pr-str-with-opts followed by (newline)" - [objs opts] - (if (empty? objs) - "\n" - (let [sb (pr-sb-with-opts objs opts)] - (.append sb \newline) - (str sb)))) - -(defn- pr-with-opts - "Prints a sequence of objects using string-print, observing all - the options given in opts" - [objs opts] - (string-print (pr-str-with-opts objs opts))) - -(defn newline [opts] - (string-print "\n") - (when (get opts :flush-on-newline) - (flush))) - -(defn pr-str - "pr to a string, returning it. Fundamental entrypoint to IPrintWithWriter." - [& objs] - (pr-str-with-opts objs (pr-opts))) - -(defn prn-str - "Same as pr-str followed by (newline)" - [& objs] - (prn-str-with-opts objs (pr-opts))) - -(defn pr - "Prints the object(s) using string-print. Prints the - object(s), separated by spaces if there is more than one. - By default, pr and prn print in a way that objects can be - read by the reader" - [& objs] - (pr-with-opts objs (pr-opts))) - -(def ^{:doc - "Prints the object(s) using string-print. - print and println produce output for human consumption."} - print - (fn cljs-core-print [& objs] - (pr-with-opts objs (assoc (pr-opts) :readably false)))) - -(defn print-str - "print to a string, returning it" - [& objs] - (pr-str-with-opts objs (assoc (pr-opts) :readably false))) - -(defn println - "Same as print followed by (newline)" - [& objs] - (pr-with-opts objs (assoc (pr-opts) :readably false)) - (when *print-newline* - (newline (pr-opts)))) - -(defn println-str - "println to a string, returning it" - [& objs] - (prn-str-with-opts objs (assoc (pr-opts) :readably false))) - -(defn prn - "Same as pr followed by (newline)." - [& objs] - (pr-with-opts objs (pr-opts)) - (when *print-newline* - (newline (pr-opts)))) - -(defn print-map [m print-one writer opts] - (pr-sequential-writer - writer - (fn [e w opts] - (do (print-one (key e) w opts) - (-write w \space) - (print-one (val e) w opts))) - "{" ", " "}" - opts (seq m))) - -(extend-protocol IPrintWithWriter - LazySeq - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - IndexedSeq - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - RSeq - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - PersistentQueue - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "#queue [" " " "]" opts (seq coll))) - - PersistentTreeMapSeq - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - NodeSeq - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - ArrayNodeSeq - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - List - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - Cons - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - EmptyList - (-pr-writer [coll writer opts] (-write writer "()")) - - PersistentVector - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) - - ChunkedCons - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - ChunkedSeq - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - Subvec - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) - - BlackNode - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) - - RedNode - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) - - ObjMap - (-pr-writer [coll writer opts] - (print-map coll pr-writer writer opts)) - - KeySeq - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - ValSeq - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - PersistentArrayMapSeq - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) - - PersistentArrayMap - (-pr-writer [coll writer opts] - (print-map coll pr-writer writer opts)) - - PersistentHashMap - (-pr-writer [coll writer opts] - (print-map coll pr-writer writer opts)) - - PersistentTreeMap - (-pr-writer [coll writer opts] - (print-map coll pr-writer writer opts)) - - PersistentHashSet - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "#{" " " "}" opts coll)) - - PersistentTreeSet - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "#{" " " "}" opts coll)) - - Range - (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll))) - - -;; IComparable -(extend-protocol IComparable - Symbol - (-compare [x y] (compare-symbols x y)) - - Keyword - ; keyword happens to have the same fields as Symbol, so this just works - (-compare [x y] (compare-symbols x y)) - - Subvec - (-compare [x y] (compare-indexed x y)) - - PersistentVector - (-compare [x y] (compare-indexed x y))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Reference Types ;;;;;;;;;;;;;;;; - -(defprotocol IAtom) - -(defprotocol IReset - (-reset! [o new-value])) - -(defprotocol ISwap - (-swap! [o f] [o f a] [o f a b] [o f a b xs])) - -(deftype Atom [state meta validator watches] - IAtom - - IEquiv - (-equiv [o other] (identical? o other)) - - IDeref - (-deref [_] state) - - IMeta - (-meta [_] meta) - - IPrintWithWriter - (-pr-writer [a writer opts] - (-write writer "#")) - - IWatchable - (-notify-watches [this oldval newval] - (doseq [[key f] watches] - (f key this oldval newval))) - (-add-watch [this key f] - (set! (.-watches this) (assoc watches key f))) - (-remove-watch [this key] - (set! (.-watches this) (dissoc watches key))) - - IHash - (-hash [this] (goog/getUid this))) - -(defn atom - "Creates and returns an Atom with an initial value of x and zero or - more options (in any order): - - :meta metadata-map - - :validator validate-fn - - If metadata-map is supplied, it will be come the metadata on the - atom. validate-fn must be nil or a side-effect-free fn of one - argument, which will be passed the intended new state on any state - change. If the new state is unacceptable, the validate-fn should - return false or throw an Error. If either of these error conditions - occur, then the value of the atom will not change." - ([x] (Atom. x nil nil nil)) - ([x & {:keys [meta validator]}] (Atom. x meta validator nil))) - -(defn reset! - "Sets the value of atom to newval without regard for the - current value. Returns newval." - [a new-value] - (if (instance? Atom a) - (let [validate (.-validator a)] - (when-not (nil? validate) - (assert (validate new-value) "Validator rejected reference state")) - (let [old-value (.-state a)] - (set! (.-state a) new-value) - (when-not (nil? (.-watches a)) - (-notify-watches a old-value new-value)) - new-value)) - (-reset! a new-value))) - -;; generic to all refs -;; (but currently hard-coded to atom!) -(defn deref - [o] - (-deref o)) - -(defn swap! - "Atomically swaps the value of atom to be: - (apply f current-value-of-atom args). Note that f may be called - multiple times, and thus should be free of side effects. Returns - the value that was swapped in." - ([a f] - (if (instance? Atom a) - (reset! a (f (.-state a))) - (-swap! a f))) - ([a f x] - (if (instance? Atom a) - (reset! a (f (.-state a) x)) - (-swap! a f x))) - ([a f x y] - (if (instance? Atom a) - (reset! a (f (.-state a) x y)) - (-swap! a f x y))) - ([a f x y & more] - (if (instance? Atom a) - (reset! a (apply f (.-state a) x y more)) - (-swap! a f x y more)))) - -(defn compare-and-set! - "Atomically sets the value of atom to newval if and only if the - current value of the atom is identical to oldval. Returns true if - set happened, else false." - [a oldval newval] - (if (= (.-state a) oldval) - (do (reset! a newval) true) - false)) - -(defn set-validator! - "Sets the validator-fn for an atom. validator-fn must be nil or a - side-effect-free fn of one argument, which will be passed the intended - new state on any state change. If the new state is unacceptable, the - validator-fn should return false or throw an Error. If the current state - is not acceptable to the new validator, an Error will be thrown and the - validator will not be changed." - [iref val] - (set! (.-validator iref) val)) - -(defn get-validator - "Gets the validator-fn for a var/ref/agent/atom." - [iref] - (.-validator iref)) - -(defn alter-meta! - "Atomically sets the metadata for a namespace/var/ref/agent/atom to be: - - (apply f its-current-meta args) - - f must be free of side-effects" - [iref f & args] - (set! (.-meta iref) (apply f (.-meta iref) args))) - -(defn reset-meta! - "Atomically resets the metadata for an atom" - [iref m] - (set! (.-meta iref) m)) - -(defn add-watch - "Alpha - subject to change. - - Adds a watch function to an atom reference. The watch fn must be a - fn of 4 args: a key, the reference, its old-state, its - new-state. Whenever the reference's state might have been changed, - any registered watches will have their functions called. The watch - fn will be called synchronously. Note that an atom's state - may have changed again prior to the fn call, so use old/new-state - rather than derefing the reference. Keys must be unique per - reference, and can be used to remove the watch with remove-watch, - but are otherwise considered opaque by the watch mechanism. Bear in - mind that regardless of the result or action of the watch fns the - atom's value will change. Example: - - (def a (atom 0)) - (add-watch a :inc (fn [k r o n] (assert (== 0 n)))) - (swap! a inc) - ;; Assertion Error - (deref a) - ;=> 1" - [iref key f] - (-add-watch iref key f)) - -(defn remove-watch - "Alpha - subject to change. - - Removes a watch (set by add-watch) from a reference" - [iref key] - (-remove-watch iref key)) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; gensym ;;;;;;;;;;;;;;;; -;; Internal - do not use! -(def gensym_counter nil) - -(defn gensym - "Returns a new symbol with a unique name. If a prefix string is - supplied, the name is prefix# where # is some unique number. If - prefix is not supplied, the prefix is 'G__'." - ([] (gensym "G__")) - ([prefix-string] - (when (nil? gensym_counter) - (set! gensym_counter (atom 0))) - (symbol (str prefix-string (swap! gensym_counter inc))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Fixtures ;;;;;;;;;;;;;;;; - -(def fixture1 1) -(def fixture2 2) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Delay ;;;;;;;;;;;;;;;;;;;; - -(deftype Delay [state f] - IDeref - (-deref [_] - (:value (swap! state (fn [{:keys [done] :as curr-state}] - (if done - curr-state, - {:done true :value (f)}))))) - - IPending - (-realized? [d] - (:done @state))) - -(defn ^boolean delay? - "returns true if x is a Delay created with delay" - [x] (instance? cljs.core.Delay x)) - -(defn force - "If x is a Delay, returns the (possibly cached) value of its expression, else returns x" - [x] - (if (delay? x) - (deref x) - x)) - -(defn ^boolean realized? - "Returns true if a value has been produced for a promise, delay, future or lazy sequence." - [d] - (-realized? d)) - -(defprotocol IEncodeJS - (-clj->js [x] "Recursively transforms clj values to JavaScript") - (-key->js [x] "Transforms map keys to valid JavaScript keys. Arbitrary keys are - encoded to their string representation via (pr-str x)")) - -(declare clj->js) - -(defn key->js [k] - (if (satisfies? IEncodeJS k) - (-clj->js k) - (if (or (string? k) - (number? k) - (keyword? k) - (symbol? k)) - (clj->js k) - (pr-str k)))) - -(defn clj->js - "Recursively transforms ClojureScript values to JavaScript. -sets/vectors/lists become Arrays, Keywords and Symbol become Strings, -Maps become Objects. Arbitrary keys are encoded to by key->js." - [x] - (when-not (nil? x) - (if (satisfies? IEncodeJS x) - (-clj->js x) - (cond - (keyword? x) (name x) - (symbol? x) (str x) - (map? x) (let [m (js-obj)] - (doseq [[k v] x] - (aset m (key->js k) (clj->js v))) - m) - (coll? x) (let [arr (array)] - (doseq [x (map clj->js x)] - (.push arr x)) - arr) - :else x)))) - -(defprotocol IEncodeClojure - (-js->clj [x options] "Transforms JavaScript values to Clojure")) - -(defn js->clj - "Recursively transforms JavaScript arrays into ClojureScript - vectors, and JavaScript objects into ClojureScript maps. With - option ':keywordize-keys true' will convert object fields from - strings to keywords." - ([x] (js->clj x {:keywordize-keys false})) - ([x & opts] - (cond - (satisfies? IEncodeClojure x) - (-js->clj x (apply array-map opts)) - - (seq opts) - (let [{:keys [keywordize-keys]} opts - keyfn (if keywordize-keys keyword str) - f (fn thisfn [x] - (cond - (seq? x) - (doall (map thisfn x)) - - (coll? x) - (into (empty x) (map thisfn x)) - - (array? x) - (vec (map thisfn x)) - - (identical? (type x) js/Object) - (into {} (for [k (js-keys x)] - [(keyfn k) (thisfn (aget x k))])) - - :else x))] - (f x))))) - -(defn memoize - "Returns a memoized version of a referentially transparent function. The - memoized version of the function keeps a cache of the mapping from arguments - to results and, when calls with the same arguments are repeated often, has - higher performance at the expense of higher memory use." - [f] - (let [mem (atom {})] - (fn [& args] - (if-let [v (get @mem args)] - v - (let [ret (apply f args)] - (swap! mem assoc args ret) - ret))))) - -(defn trampoline - "trampoline can be used to convert algorithms requiring mutual - recursion without stack consumption. Calls f with supplied args, if - any. If f returns a fn, calls that fn with no arguments, and - continues to repeat, until the return value is not a fn, then - returns that non-fn value. Note that if you want to return a fn as a - final value, you must wrap it in some data structure and unpack it - after trampoline returns." - ([f] - (let [ret (f)] - (if (fn? ret) - (recur ret) - ret))) - ([f & args] - (trampoline #(apply f args)))) - -(defn rand - "Returns a random floating point number between 0 (inclusive) and - n (default 1) (exclusive)." - ([] (rand 1)) - ([n] (* (Math/random) n))) - -(defn rand-int - "Returns a random integer between 0 (inclusive) and n (exclusive)." - [n] (Math/floor (* (Math/random) n))) - -(defn rand-nth - "Return a random element of the (sequential) collection. Will have - the same performance characteristics as nth for the given - collection." - [coll] - (nth coll (rand-int (count coll)))) - -(defn group-by - "Returns a map of the elements of coll keyed by the result of - f on each element. The value at each key will be a vector of the - corresponding elements, in the order they appeared in coll." - [f coll] - (reduce - (fn [ret x] - (let [k (f x)] - (assoc ret k (conj (get ret k []) x)))) - {} coll)) - -(defn make-hierarchy - "Creates a hierarchy object for use with derive, isa? etc." - [] {:parents {} :descendants {} :ancestors {}}) - -(def ^:private -global-hierarchy nil) - -(defn- get-global-hierarchy [] - (when (nil? -global-hierarchy) - (set! -global-hierarchy (atom (make-hierarchy)))) - -global-hierarchy) - -(defn- swap-global-hierarchy! [f & args] - (apply swap! (get-global-hierarchy) f args)) - -(defn ^boolean isa? - "Returns true if (= child parent), or child is directly or indirectly derived from - parent, either via a JavaScript type inheritance relationship or a - relationship established via derive. h must be a hierarchy obtained - from make-hierarchy, if not supplied defaults to the global - hierarchy" - ([child parent] (isa? @(get-global-hierarchy) child parent)) - ([h child parent] - (or (= child parent) - ;; (and (class? parent) (class? child) - ;; (. ^Class parent isAssignableFrom child)) - (contains? ((:ancestors h) child) parent) - ;;(and (class? child) (some #(contains? ((:ancestors h) %) parent) (supers child))) - (and (vector? parent) (vector? child) - (== (count parent) (count child)) - (loop [ret true i 0] - (if (or (not ret) (== i (count parent))) - ret - (recur (isa? h (child i) (parent i)) (inc i)))))))) - -(defn parents - "Returns the immediate parents of tag, either via a JavaScript type - inheritance relationship or a relationship established via derive. h - must be a hierarchy obtained from make-hierarchy, if not supplied - defaults to the global hierarchy" - ([tag] (parents @(get-global-hierarchy) tag)) - ([h tag] (not-empty (get (:parents h) tag)))) - -(defn ancestors - "Returns the immediate and indirect parents of tag, either via a JavaScript type - inheritance relationship or a relationship established via derive. h - must be a hierarchy obtained from make-hierarchy, if not supplied - defaults to the global hierarchy" - ([tag] (ancestors @(get-global-hierarchy) tag)) - ([h tag] (not-empty (get (:ancestors h) tag)))) - -(defn descendants - "Returns the immediate and indirect children of tag, through a - relationship established via derive. h must be a hierarchy obtained - from make-hierarchy, if not supplied defaults to the global - hierarchy. Note: does not work on JavaScript type inheritance - relationships." - ([tag] (descendants @(get-global-hierarchy) tag)) - ([h tag] (not-empty (get (:descendants h) tag)))) - -(defn derive - "Establishes a parent/child relationship between parent and - tag. Parent must be a namespace-qualified symbol or keyword and - child can be either a namespace-qualified symbol or keyword or a - class. h must be a hierarchy obtained from make-hierarchy, if not - supplied defaults to, and modifies, the global hierarchy." - ([tag parent] - (assert (namespace parent)) - ;; (assert (or (class? tag) (and (instance? cljs.core.Named tag) (namespace tag)))) - (swap-global-hierarchy! derive tag parent) nil) - ([h tag parent] - (assert (not= tag parent)) - ;; (assert (or (class? tag) (instance? clojure.lang.Named tag))) - ;; (assert (instance? clojure.lang.INamed tag)) - ;; (assert (instance? clojure.lang.INamed parent)) - (let [tp (:parents h) - td (:descendants h) - ta (:ancestors h) - tf (fn [m source sources target targets] - (reduce (fn [ret k] - (assoc ret k - (reduce conj (get targets k #{}) (cons target (targets target))))) - m (cons source (sources source))))] - (or - (when-not (contains? (tp tag) parent) - (when (contains? (ta tag) parent) - (throw (js/Error. (str tag "already has" parent "as ancestor")))) - (when (contains? (ta parent) tag) - (throw (js/Error. (str "Cyclic derivation:" parent "has" tag "as ancestor")))) - {:parents (assoc (:parents h) tag (conj (get tp tag #{}) parent)) - :ancestors (tf (:ancestors h) tag td parent ta) - :descendants (tf (:descendants h) parent ta tag td)}) - h)))) - -(defn underive - "Removes a parent/child relationship between parent and - tag. h must be a hierarchy obtained from make-hierarchy, if not - supplied defaults to, and modifies, the global hierarchy." - ([tag parent] - (swap-global-hierarchy! underive tag parent) - nil) - ([h tag parent] - (let [parentMap (:parents h) - childsParents (if (parentMap tag) - (disj (parentMap tag) parent) #{}) - newParents (if (not-empty childsParents) - (assoc parentMap tag childsParents) - (dissoc parentMap tag)) - deriv-seq (flatten (map #(cons (first %) (interpose (first %) (second %))) - (seq newParents)))] - (if (contains? (parentMap tag) parent) - (reduce #(apply derive %1 %2) (make-hierarchy) - (partition 2 deriv-seq)) - h)))) - -(defn- reset-cache - [method-cache method-table cached-hierarchy hierarchy] - (swap! method-cache (fn [_] (deref method-table))) - (swap! cached-hierarchy (fn [_] (deref hierarchy)))) - -(defn- prefers* - [x y prefer-table] - (let [xprefs (@prefer-table x)] - (or - (when (and xprefs (xprefs y)) - true) - (loop [ps (parents y)] - (when (pos? (count ps)) - (when (prefers* x (first ps) prefer-table) - true) - (recur (rest ps)))) - (loop [ps (parents x)] - (when (pos? (count ps)) - (when (prefers* (first ps) y prefer-table) - true) - (recur (rest ps)))) - false))) - -(defn- dominates - [x y prefer-table] - (or (prefers* x y prefer-table) (isa? x y))) - -(defn- find-and-cache-best-method - [name dispatch-val hierarchy method-table prefer-table method-cache cached-hierarchy] - (let [best-entry (reduce (fn [be [k _ :as e]] - (if (isa? @hierarchy dispatch-val k) - (let [be2 (if (or (nil? be) (dominates k (first be) prefer-table)) - e - be)] - (when-not (dominates (first be2) k prefer-table) - (throw (js/Error. - (str "Multiple methods in multimethod '" name - "' match dispatch value: " dispatch-val " -> " k - " and " (first be2) ", and neither is preferred")))) - be2) - be)) - nil @method-table)] - (when best-entry - (if (= @cached-hierarchy @hierarchy) - (do - (swap! method-cache assoc dispatch-val (second best-entry)) - (second best-entry)) - (do - (reset-cache method-cache method-table cached-hierarchy hierarchy) - (find-and-cache-best-method name dispatch-val hierarchy method-table prefer-table - method-cache cached-hierarchy)))))) - -(defprotocol IMultiFn - (-reset [mf]) - (-add-method [mf dispatch-val method]) - (-remove-method [mf dispatch-val]) - (-prefer-method [mf dispatch-val dispatch-val-y]) - (-get-method [mf dispatch-val]) - (-methods [mf]) - (-prefers [mf]) - (-dispatch [mf args])) - -(defn- do-dispatch - [mf name dispatch-fn args] - (let [dispatch-val (apply dispatch-fn args) - target-fn (-get-method mf dispatch-val)] - (when-not target-fn - (throw (js/Error. (str "No method in multimethod '" name "' for dispatch value: " dispatch-val)))) - (apply target-fn args))) - -(deftype MultiFn [name dispatch-fn default-dispatch-val hierarchy - method-table prefer-table method-cache cached-hierarchy] - IFn - - IMultiFn - (-reset [mf] - (swap! method-table (fn [mf] {})) - (swap! method-cache (fn [mf] {})) - (swap! prefer-table (fn [mf] {})) - (swap! cached-hierarchy (fn [mf] nil)) - mf) - - (-add-method [mf dispatch-val method] - (swap! method-table assoc dispatch-val method) - (reset-cache method-cache method-table cached-hierarchy hierarchy) - mf) - - (-remove-method [mf dispatch-val] - (swap! method-table dissoc dispatch-val) - (reset-cache method-cache method-table cached-hierarchy hierarchy) - mf) - - (-get-method [mf dispatch-val] - (when-not (= @cached-hierarchy @hierarchy) - (reset-cache method-cache method-table cached-hierarchy hierarchy)) - (if-let [target-fn (@method-cache dispatch-val)] - target-fn - (if-let [target-fn (find-and-cache-best-method name dispatch-val hierarchy method-table - prefer-table method-cache cached-hierarchy)] - target-fn - (@method-table default-dispatch-val)))) - - (-prefer-method [mf dispatch-val-x dispatch-val-y] - (when (prefers* dispatch-val-x dispatch-val-y prefer-table) - (throw (js/Error. (str "Preference conflict in multimethod '" name "': " dispatch-val-y - " is already preferred to " dispatch-val-x)))) - (swap! prefer-table - (fn [old] - (assoc old dispatch-val-x - (conj (get old dispatch-val-x #{}) - dispatch-val-y)))) - (reset-cache method-cache method-table cached-hierarchy hierarchy)) - - (-methods [mf] @method-table) - (-prefers [mf] @prefer-table) - - (-dispatch [mf args] (do-dispatch mf name dispatch-fn args)) - - IHash - (-hash [this] (goog/getUid this))) - -(set! cljs.core.MultiFn.prototype.call - (fn [_ & args] - (this-as self - (-dispatch self args)))) - -(set! cljs.core.MultiFn.prototype.apply - (fn [_ args] - (this-as self - (-dispatch self args)))) - -(defn remove-all-methods - "Removes all of the methods of multimethod." - [multifn] - (-reset multifn)) - -(defn remove-method - "Removes the method of multimethod associated with dispatch-value." - [multifn dispatch-val] - (-remove-method multifn dispatch-val)) - -(defn prefer-method - "Causes the multimethod to prefer matches of dispatch-val-x over dispatch-val-y - when there is a conflict" - [multifn dispatch-val-x dispatch-val-y] - (-prefer-method multifn dispatch-val-x dispatch-val-y)) - -(defn methods - "Given a multimethod, returns a map of dispatch values -> dispatch fns" - [multifn] (-methods multifn)) - -(defn get-method - "Given a multimethod and a dispatch value, returns the dispatch fn - that would apply to that value, or nil if none apply and no default" - [multifn dispatch-val] (-get-method multifn dispatch-val)) - -(defn prefers - "Given a multimethod, returns a map of preferred value -> set of other values" - [multifn] (-prefers multifn)) - -;; UUID - -(deftype UUID [uuid] - IEquiv - (-equiv [_ other] - (and (instance? UUID other) (identical? uuid (.-uuid other)))) - - IPrintWithWriter - (-pr-writer [_ writer _] - (-write writer (str "#uuid \"" uuid "\""))) - - IHash - (-hash [this] - (goog.string/hashCode (pr-str this)))) - -;;; ExceptionInfo - -(deftype ExceptionInfo [message data cause]) - -;;; ExceptionInfo is a special case, do not emulate this -(set! cljs.core.ExceptionInfo.prototype (js/Error.)) -(set! (.-constructor cljs.core.ExceptionInfo.prototype) ExceptionInfo) - -(defn ex-info - "Alpha - subject to change. - Create an instance of ExceptionInfo, an Error type that carries a - map of additional data." - ([msg map] - (ExceptionInfo. msg map nil)) - ([msg map cause] - (ExceptionInfo. msg map cause))) - -(defn ex-data - "Alpha - subject to change. - Returns exception data (a map) if ex is an ExceptionInfo. - Otherwise returns nil." - [ex] - (when (instance? ExceptionInfo ex) - (.-data ex))) - -(defn ex-message - "Alpha - subject to change. - Returns the message attached to the given Error / ExceptionInfo object. - For non-Errors returns nil." - [ex] - (when (instance? js/Error ex) - (.-message ex))) - -(defn ex-cause - "Alpha - subject to change. - Returns exception cause (an Error / ExceptionInfo) if ex is an - ExceptionInfo. - Otherwise returns nil." - [ex] - (when (instance? ExceptionInfo ex) - (.-cause ex))) - -(defn comparator - "Returns an JavaScript compatible comparator based upon pred." - [pred] - (fn [x y] - (cond (pred x y) -1 (pred y x) 1 :else 0))) - -(defn ^boolean special-symbol? [x] - (contains? - '#{if def fn* do let* loop* letfn* throw try - recur new set! ns deftype* defrecord* . js* & quote} - x)) diff --git a/src/cljs/cljs/nodejs.cljs b/src/cljs/cljs/nodejs.cljs deleted file mode 100644 index 01f131882e..0000000000 --- a/src/cljs/cljs/nodejs.cljs +++ /dev/null @@ -1,12 +0,0 @@ -; Projects compiled with :target :nodejs can 'require' this namespace -; to get the nodejs globals loaded into cljs.nodejs and get -; ClojureScript's 'print' set up correctly. -(ns cljs.nodejs) - -; Define namespaced references to Node's externed globals: -(def require (js* "require")) -(def process (js* "process")) - -; Have ClojureScript print using Node's sys.print function -(defn enable-util-print! [] - (set! cljs.core/string-print (.-print (require "util")))) diff --git a/src/cljs/cljs/nodejs.js b/src/cljs/cljs/nodejs.js deleted file mode 100644 index 076203791d..0000000000 --- a/src/cljs/cljs/nodejs.js +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2013 The Closure Library Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS-IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/** - * @fileoverview A nodejs script for dynamically requiring Closure within - * nodejs. - * - * Example of usage: - * - * require('./bootstrap/nodejs') - * goog.require('goog.ui.Component') - * - * - * This loads goog.ui.Component in the global scope. - * - * If you want to load custom libraries, you can require the custom deps file - * directly. If your custom libraries introduce new globals, you may - * need to run goog.nodeGlobalRequire to get them to load correctly. - * - * - * require('./path/to/my/deps.js') - * goog.bootstrap.nodeJs.nodeGlobalRequire('./path/to/my/base.js') - * goog.require('my.Class') - * - * - * @author nick@medium.com (Nick Santos) - * - * @nocompile - */ - - -var fs = require('fs'); -var path = require('path'); - - -/** - * The goog namespace in the global scope. - */ -global.goog = {}; - - -/** - * Imports a script using Node's require() API. - * - * @param {string} src The script source. - * @return {boolean} True if the script was imported, false otherwise. - */ -global.CLOSURE_IMPORT_SCRIPT = function(src) { - // Sources are always expressed relative to closure's base.js, but - // require() is always relative to the current source. - require('./../' + src); - return true; -}; - - -// Declared here so it can be used to require base.js -function nodeGlobalRequire(file) { - process.binding('evals').NodeScript.runInThisContext.call( - global, fs.readFileSync(file), file); -} - - -// Load Closure's base.js into memory. It is assumed base.js is in the -// directory above this directory given this script's location in -// bootstrap/nodejs.js. -nodeGlobalRequire(path.resolve(__dirname, '..', 'base.js')); - - -/** - * Bootstraps a file into the global scope. - * - * This is strictly for cases where normal require() won't work, - * because the file declares global symbols with 'var' that need to - * be added to the global scope. - * @suppress {missingProvide} - * - * @param {string} file The path to the file. - */ -goog.nodeGlobalRequire = nodeGlobalRequire; - diff --git a/src/cljs/cljs/nodejs_externs.js b/src/cljs/cljs/nodejs_externs.js deleted file mode 100644 index 708db372ba..0000000000 --- a/src/cljs/cljs/nodejs_externs.js +++ /dev/null @@ -1,2 +0,0 @@ -function require(){} -function process(){} diff --git a/src/cljs/cljs/nodejscli.cljs b/src/cljs/cljs/nodejscli.cljs deleted file mode 100644 index 20acc8d89b..0000000000 --- a/src/cljs/cljs/nodejscli.cljs +++ /dev/null @@ -1,9 +0,0 @@ -; Projects compiled with :target :nodejs have this file appended. Its -; job is to make sure cljs.nodejs is loaded and that the *main-cli-fn* -; is called with the script's command-line arguments. -(ns cljs.nodejscli - (:require [cljs.nodejs :as nodejs])) - -; Call the user's main function -(apply cljs.core/*main-cli-fn* (drop 2 (.-argv nodejs/process))) - diff --git a/src/cljs/cljs/reader.cljs b/src/cljs/cljs/reader.cljs deleted file mode 100644 index 782d07a09d..0000000000 --- a/src/cljs/cljs/reader.cljs +++ /dev/null @@ -1,599 +0,0 @@ -; Copyright (c) Rich Hickey. All rights reserved. -; The use and distribution terms for this software are covered by the -; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -; which can be found in the file epl-v10.html at the root of this distribution. -; By using this software in any fashion, you are agreeing to be bound by -; the terms of this license. -; You must not remove this notice, or any other, from this software. - -(ns cljs.reader - (:require [goog.string :as gstring])) - -(defprotocol PushbackReader - (read-char [reader] "Returns the next char from the Reader, -nil if the end of stream has been reached") - (unread [reader ch] "Push back a single character on to the stream")) - -(deftype StringPushbackReader [s buffer ^:mutable idx] - PushbackReader - (read-char [reader] - (if (zero? (alength buffer)) - (do - (set! idx (inc idx)) - (aget s idx)) - (.pop buffer))) - (unread [reader ch] - (.push buffer ch))) - -(defn push-back-reader [s] - "Creates a StringPushbackReader from a given string" - (StringPushbackReader. s (array) -1)) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; predicates -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn- ^boolean whitespace? - "Checks whether a given character is whitespace" - [ch] - (or (gstring/isBreakingWhitespace ch) (identical? \, ch))) - -(defn- ^boolean numeric? - "Checks whether a given character is numeric" - [ch] - (gstring/isNumeric ch)) - -(defn- ^boolean comment-prefix? - "Checks whether the character begins a comment." - [ch] - (identical? \; ch)) - -(defn- ^boolean number-literal? - "Checks whether the reader is at the start of a number literal" - [reader initch] - (or (numeric? initch) - (and (or (identical? \+ initch) (identical? \- initch)) - (numeric? (let [next-ch (read-char reader)] - (unread reader next-ch) - next-ch))))) - -(declare read macros dispatch-macros) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; read helpers -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - - -; later will do e.g. line numbers... -(defn reader-error - [rdr & msg] - (throw (js/Error. (apply str msg)))) - -(defn ^boolean macro-terminating? [ch] - (and (not (identical? ch "#")) - (not (identical? ch \')) - (not (identical? ch ":")) - (macros ch))) - -(defn read-token - [rdr initch] - (loop [sb (gstring/StringBuffer. initch) - ch (read-char rdr)] - (if (or (nil? ch) - (whitespace? ch) - (macro-terminating? ch)) - (do (unread rdr ch) (. sb (toString))) - (recur (do (.append sb ch) sb) (read-char rdr))))) - -(defn skip-line - "Advances the reader to the end of a line. Returns the reader" - [reader _] - (loop [] - (let [ch (read-char reader)] - (if (or (identical? ch \newline) (identical? ch \return) (nil? ch)) - reader - (recur))))) - -(def int-pattern (re-pattern "([-+]?)(?:(0)|([1-9][0-9]*)|0[xX]([0-9A-Fa-f]+)|0([0-7]+)|([1-9][0-9]?)[rR]([0-9A-Za-z]+)|0[0-9]+)(N)?")) -(def ratio-pattern (re-pattern "([-+]?[0-9]+)/([0-9]+)")) -(def float-pattern (re-pattern "([-+]?[0-9]+(\\.[0-9]*)?([eE][-+]?[0-9]+)?)(M)?")) -(def symbol-pattern (re-pattern "[:]?([^0-9/].*/)?([^0-9/][^/]*)")) - -(defn- re-find* - [re s] - (let [matches (.exec re s)] - (when-not (nil? matches) - (if (== (alength matches) 1) - (aget matches 0) - matches)))) - -(defn- match-int - [s] - (let [groups (re-find* int-pattern s) - group3 (aget groups 2)] - (if-not (or (nil? group3) - (< (alength group3) 1)) - 0 - (let [negate (if (identical? "-" (aget groups 1)) -1 1) - a (cond - (aget groups 3) (array (aget groups 3) 10) - (aget groups 4) (array (aget groups 4) 16) - (aget groups 5) (array (aget groups 5) 8) - (aget groups 7) (array (aget groups 7) (js/parseInt (aget groups 7))) - :default (array nil nil)) - n (aget a 0) - radix (aget a 1)] - (if (nil? n) - nil - (* negate (js/parseInt n radix))))))) - - -(defn- match-ratio - [s] - (let [groups (re-find* ratio-pattern s) - numinator (aget groups 1) - denominator (aget groups 2)] - (/ (js/parseInt numinator) (js/parseInt denominator)))) - -(defn- match-float - [s] - (js/parseFloat s)) - -(defn- re-matches* - [re s] - (let [matches (.exec re s)] - (when (and (not (nil? matches)) - (identical? (aget matches 0) s)) - (if (== (alength matches) 1) - (aget matches 0) - matches)))) - -(defn- match-number - [s] - (cond - (re-matches* int-pattern s) (match-int s) - (re-matches* ratio-pattern s) (match-ratio s) - (re-matches* float-pattern s) (match-float s))) - -(defn escape-char-map [c] - (cond - (identical? c \t) "\t" - (identical? c \r) "\r" - (identical? c \n) "\n" - (identical? c \\) \\ - (identical? c \") \" - (identical? c \b) "\b" - (identical? c \f) "\f" - :else nil)) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; unicode -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn read-2-chars [reader] - (.toString - (gstring/StringBuffer. - (read-char reader) - (read-char reader)))) - -(defn read-4-chars [reader] - (.toString - (gstring/StringBuffer. - (read-char reader) - (read-char reader) - (read-char reader) - (read-char reader)))) - -(def unicode-2-pattern (re-pattern "[0-9A-Fa-f]{2}")) -(def unicode-4-pattern (re-pattern "[0-9A-Fa-f]{4}")) - -(defn validate-unicode-escape [unicode-pattern reader escape-char unicode-str] - (if (re-matches unicode-pattern unicode-str) - unicode-str - (reader-error reader "Unexpected unicode escape \\" escape-char unicode-str))) - -(defn make-unicode-char [code-str] - (let [code (js/parseInt code-str 16)] - (.fromCharCode js/String code))) - -(defn escape-char - [buffer reader] - (let [ch (read-char reader) - mapresult (escape-char-map ch)] - (if mapresult - mapresult - (cond - (identical? ch \x) - (->> (read-2-chars reader) - (validate-unicode-escape unicode-2-pattern reader ch) - (make-unicode-char)) - - (identical? ch \u) - (->> (read-4-chars reader) - (validate-unicode-escape unicode-4-pattern reader ch) - (make-unicode-char)) - - (numeric? ch) - (.fromCharCode js/String ch) - - :else - (reader-error reader "Unexpected unicode escape \\" ch ))))) - -(defn read-past - "Read until first character that doesn't match pred, returning - char." - [pred rdr] - (loop [ch (read-char rdr)] - (if (pred ch) - (recur (read-char rdr)) - ch))) - -(defn read-delimited-list - [delim rdr recursive?] - (loop [a (transient [])] - (let [ch (read-past whitespace? rdr)] - (when-not ch (reader-error rdr "EOF while reading")) - (if (identical? delim ch) - (persistent! a) - (if-let [macrofn (macros ch)] - (let [mret (macrofn rdr ch)] - (recur (if (identical? mret rdr) a (conj! a mret)))) - (do - (unread rdr ch) - (let [o (read rdr true nil recursive?)] - (recur (if (identical? o rdr) a (conj! a o)))))))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; data structure readers -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn not-implemented - [rdr ch] - (reader-error rdr "Reader for " ch " not implemented yet")) - -(declare maybe-read-tagged-type) - -(defn read-dispatch - [rdr _] - (let [ch (read-char rdr) - dm (dispatch-macros ch)] - (if dm - (dm rdr _) - (if-let [obj (maybe-read-tagged-type rdr ch)] - obj - (reader-error rdr "No dispatch macro for " ch))))) - -(defn read-unmatched-delimiter - [rdr ch] - (reader-error rdr "Unmached delimiter " ch)) - -(defn read-list - [rdr _] - (apply list (read-delimited-list ")" rdr true))) - -(def read-comment skip-line) - -(defn read-vector - [rdr _] - (read-delimited-list "]" rdr true)) - -(defn read-map - [rdr _] - (let [l (read-delimited-list "}" rdr true)] - (when (odd? (count l)) - (reader-error rdr "Map literal must contain an even number of forms")) - (apply hash-map l))) - -(defn read-number - [reader initch] - (loop [buffer (gstring/StringBuffer. initch) - ch (read-char reader)] - (if (or (nil? ch) (whitespace? ch) (macros ch)) - (do - (unread reader ch) - (let [s (. buffer (toString))] - (or (match-number s) - (reader-error reader "Invalid number format [" s "]")))) - (recur (do (.append buffer ch) buffer) (read-char reader))))) - -(defn read-string* - [reader _] - (loop [buffer (gstring/StringBuffer.) - ch (read-char reader)] - (cond - (nil? ch) (reader-error reader "EOF while reading") - (identical? "\\" ch) (recur (do (.append buffer (escape-char buffer reader)) buffer) - (read-char reader)) - (identical? \" ch) (. buffer (toString)) - :default (recur (do (.append buffer ch) buffer) (read-char reader))))) - -(defn special-symbols [t not-found] - (cond - (identical? t "nil") nil - (identical? t "true") true - (identical? t "false") false - :else not-found)) - -(defn read-symbol - [reader initch] - (let [token (read-token reader initch)] - (if (gstring/contains token "/") - (symbol (subs token 0 (.indexOf token "/")) - (subs token (inc (.indexOf token "/")) (.-length token))) - (special-symbols token (symbol token))))) - -(defn read-keyword - [reader initch] - (let [token (read-token reader (read-char reader)) - a (re-matches* symbol-pattern token) - token (aget a 0) - ns (aget a 1) - name (aget a 2)] - (if (or (and (not (undefined? ns)) - (identical? (. ns (substring (- (.-length ns) 2) (.-length ns))) ":/")) - (identical? (aget name (dec (.-length name))) ":") - (not (== (.indexOf token "::" 1) -1))) - (reader-error reader "Invalid token: " token) - (if (and (not (nil? ns)) (> (.-length ns) 0)) - (keyword (.substring ns 0 (.indexOf ns "/")) name) - (keyword token))))) - -(defn desugar-meta - [f] - (cond - (symbol? f) {:tag f} - (string? f) {:tag f} - (keyword? f) {f true} - :else f)) - -(defn wrapping-reader - [sym] - (fn [rdr _] - (list sym (read rdr true nil true)))) - -(defn throwing-reader - [msg] - (fn [rdr _] - (reader-error rdr msg))) - -(defn read-meta - [rdr _] - (let [m (desugar-meta (read rdr true nil true))] - (when-not (map? m) - (reader-error rdr "Metadata must be Symbol,Keyword,String or Map")) - (let [o (read rdr true nil true)] - (if (satisfies? IWithMeta o) - (with-meta o (merge (meta o) m)) - (reader-error rdr "Metadata can only be applied to IWithMetas"))))) - -(defn read-set - [rdr _] - (set (read-delimited-list "}" rdr true))) - -(defn read-regex - [rdr ch] - (-> (read-string* rdr ch) re-pattern)) - -(defn read-discard - [rdr _] - (read rdr true nil true) - rdr) - -(defn macros [c] - (cond - (identical? c \") read-string* - (identical? c \:) read-keyword - (identical? c \;) read-comment - (identical? c \') (wrapping-reader 'quote) - (identical? c \@) (wrapping-reader 'deref) - (identical? c \^) read-meta - (identical? c \`) not-implemented - (identical? c \~) not-implemented - (identical? c \() read-list - (identical? c \)) read-unmatched-delimiter - (identical? c \[) read-vector - (identical? c \]) read-unmatched-delimiter - (identical? c \{) read-map - (identical? c \}) read-unmatched-delimiter - (identical? c \\) read-char - (identical? c \#) read-dispatch - :else nil)) - -;; omitted by design: var reader, eval reader -(defn dispatch-macros [s] - (cond - (identical? s "{") read-set - (identical? s "<") (throwing-reader "Unreadable form") - (identical? s "\"") read-regex - (identical? s"!") read-comment - (identical? s "_") read-discard - :else nil)) - -(defn read - "Reads the first object from a PushbackReader. Returns the object read. - If EOF, throws if eof-is-error is true. Otherwise returns sentinel." - [reader eof-is-error sentinel is-recursive] - (let [ch (read-char reader)] - (cond - (nil? ch) (if eof-is-error (reader-error reader "EOF while reading") sentinel) - (whitespace? ch) (recur reader eof-is-error sentinel is-recursive) - (comment-prefix? ch) (recur (read-comment reader ch) eof-is-error sentinel is-recursive) - :else (let [f (macros ch) - res - (cond - f (f reader ch) - (number-literal? reader ch) (read-number reader ch) - :else (read-symbol reader ch))] - (if (identical? res reader) - (recur reader eof-is-error sentinel is-recursive) - res))))) - -(defn read-string - "Reads one object from the string s" - [s] - (let [r (push-back-reader s)] - (read r true nil false))) - - -;; read instances - -(defn ^:private zero-fill-right-and-truncate [s width] - (cond (= width (count s)) s - (< width (count s)) (subs s 0 width) - :else (loop [b (gstring/StringBuffer. s)] - (if (< (.getLength b) width) - (recur (.append b "0")) - (.toString b))))) - -(defn ^:private divisible? - [num div] - (zero? (mod num div))) - -(defn ^:private indivisible? - [num div] - (not (divisible? num div))) - -(defn ^:private leap-year? - [year] - (and (divisible? year 4) - (or (indivisible? year 100) - (divisible? year 400)))) - -(def ^:private days-in-month - (let [dim-norm [nil 31 28 31 30 31 30 31 31 30 31 30 31] - dim-leap [nil 31 29 31 30 31 30 31 31 30 31 30 31]] - (fn [month leap-year?] - (get (if leap-year? dim-leap dim-norm) month)))) - -(def ^:private timestamp-regex #"(\d\d\d\d)(?:-(\d\d)(?:-(\d\d)(?:[T](\d\d)(?::(\d\d)(?::(\d\d)(?:[.](\d+))?)?)?)?)?)?(?:[Z]|([-+])(\d\d):(\d\d))?") - -(defn ^:private parse-int [s] - (let [n (js/parseInt s)] - (if-not (js/isNaN n) - n))) - -(defn ^:private check [low n high msg] - (when-not (<= low n high) - (reader-error nil (str msg " Failed: " low "<=" n "<=" high))) - n) - -(defn parse-and-validate-timestamp [s] - (let [[_ years months days hours minutes seconds fraction offset-sign offset-hours offset-minutes :as v] - (re-matches timestamp-regex s)] - (if-not v - (reader-error nil (str "Unrecognized date/time syntax: " s)) - (let [years (parse-int years) - months (or (parse-int months) 1) - days (or (parse-int days) 1) - hours (or (parse-int hours) 0) - minutes (or (parse-int minutes) 0) - seconds (or (parse-int seconds) 0) - fraction (or (parse-int (zero-fill-right-and-truncate fraction 3)) 0) - offset-sign (if (= offset-sign "-") -1 1) - offset-hours (or (parse-int offset-hours) 0) - offset-minutes (or (parse-int offset-minutes) 0) - offset (* offset-sign (+ (* offset-hours 60) offset-minutes))] - [years - (check 1 months 12 "timestamp month field must be in range 1..12") - (check 1 days (days-in-month months (leap-year? years)) "timestamp day field must be in range 1..last day in month") - (check 0 hours 23 "timestamp hour field must be in range 0..23") - (check 0 minutes 59 "timestamp minute field must be in range 0..59") - (check 0 seconds (if (= minutes 59) 60 59) "timestamp second field must be in range 0..60") - (check 0 fraction 999 "timestamp millisecond field must be in range 0..999") - offset])))) - -(defn parse-timestamp - [ts] - (if-let [[years months days hours minutes seconds ms offset] - (parse-and-validate-timestamp ts)] - (js/Date. - (- (.UTC js/Date years (dec months) days hours minutes seconds ms) - (* offset 60 1000))) - (reader-error nil (str "Unrecognized date/time syntax: " ts)))) - -(defn ^:private read-date - [s] - (if (string? s) - (parse-timestamp s) - (reader-error nil "Instance literal expects a string for its timestamp."))) - - -(defn ^:private read-queue - [elems] - (if (vector? elems) - (into cljs.core.PersistentQueue.EMPTY elems) - (reader-error nil "Queue literal expects a vector for its elements."))) - - -(defn ^:private read-js - [form] - (cond - (vector? form) - (let [arr (array)] - (doseq [x form] - (.push arr x)) - arr) - - (map? form) - (let [obj (js-obj)] - (doseq [[k v] form] - (aset obj (name k) v)) - obj) - - :else - (reader-error nil - (str "JS literal expects a vector or map containing " - "only string or unqualified keyword keys")))) - - -(defn ^:private read-uuid - [uuid] - (if (string? uuid) - (UUID. uuid) - (reader-error nil "UUID literal expects a string as its representation."))) - -(def *tag-table* (atom {"inst" read-date - "uuid" read-uuid - "queue" read-queue - "js" read-js})) - -(def *default-data-reader-fn* - (atom nil)) - -(defn maybe-read-tagged-type - [rdr initch] - (let [tag (read-symbol rdr initch) - pfn (get @*tag-table* (str tag)) - dfn @*default-data-reader-fn*] - (cond - pfn (pfn (read rdr true nil false)) - dfn (dfn tag (read rdr true nil false)) - :else (reader-error rdr - "Could not find tag parser for " (str tag) - " in " (pr-str (keys @*tag-table*)))))) - -(defn register-tag-parser! - [tag f] - (let [tag (str tag) - old-parser (get @*tag-table* tag)] - (swap! *tag-table* assoc tag f) - old-parser)) - -(defn deregister-tag-parser! - [tag] - (let [tag (str tag) - old-parser (get @*tag-table* tag)] - (swap! *tag-table* dissoc tag) - old-parser)) - -(defn register-default-tag-parser! - [f] - (let [old-parser @*default-data-reader-fn*] - (swap! *default-data-reader-fn* (fn [_] f)) - old-parser)) - -(defn deregister-default-tag-parser! - [] - (let [old-parser @*default-data-reader-fn*] - (swap! *default-data-reader-fn* (fn [_] nil)) - old-parser)) diff --git a/src/cljs/clojure/browser/repl.cljs b/src/cljs/clojure/browser/repl.cljs deleted file mode 100644 index 5038e4addb..0000000000 --- a/src/cljs/clojure/browser/repl.cljs +++ /dev/null @@ -1,109 +0,0 @@ -;; Copyright (c) Rich Hickey. All rights reserved. -;; The use and distribution terms for this software are covered by the -;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -;; which can be found in the file epl-v10.html at the root of this distribution. -;; By using this software in any fashion, you are agreeing to be bound by -;; the terms of this license. -;; You must not remove this notice, or any other, from this software. - -(ns ^{:doc "Receive - Eval - Print - Loop - - Receive a block of JS (presumably generated by a ClojureScript compiler) - Evaluate it naively - Print the result of evaluation to a string - Send the resulting string back to the server Loop!" - - :author "Bobby Calderwood and Alex Redington"} - clojure.browser.repl - (:require [clojure.browser.net :as net] - [clojure.browser.event :as event])) - -(def xpc-connection (atom nil)) - -(defn repl-print [data] - (if-let [conn @xpc-connection] - (net/transmit conn :print (pr-str data)))) - -(defn evaluate-javascript - "Process a single block of JavaScript received from the server" - [conn block] - (let [result (try {:status :success :value (str (js* "eval(~{block})"))} - (catch :default e - {:status :exception :value (pr-str e) - :stacktrace (if (.hasOwnProperty e "stack") - (.-stack e) - "No stacktrace available.")}))] - (pr-str result))) - -(defn send-result [connection url data] - (net/transmit connection url "POST" data nil 0)) - -(defn send-print - "Send data to be printed in the REPL. If there is an error, try again - up to 10 times." - ([url data] - (send-print url data 0)) - ([url data n] - (let [conn (net/xhr-connection)] - (event/listen conn :error - (fn [_] - (if (< n 10) - (send-print url data (inc n)) - (.log js/console (str "Could not send " data " after " n " attempts."))))) - (net/transmit conn url "POST" data nil 0)))) - -(def order (atom 0)) - -(defn wrap-message [t data] - (pr-str {:type t :content data :order (swap! order inc)})) - -(defn start-evaluator - "Start the REPL server connection." - [url] - (if-let [repl-connection (net/xpc-connection)] - (let [connection (net/xhr-connection)] - (event/listen connection - :success - (fn [e] - (net/transmit - repl-connection - :evaluate-javascript - (.getResponseText (.-currentTarget e) - ())))) - - (net/register-service repl-connection - :send-result - (fn [data] - (send-result connection url (wrap-message :result data)))) - - (net/register-service repl-connection - :print - (fn [data] - (send-print url (wrap-message :print data)))) - - (net/connect repl-connection - (constantly nil)) - - (js/setTimeout #(send-result connection url (wrap-message :ready "ready")) 50)) - (js/alert "No 'xpc' param provided to child iframe."))) - -(defn connect - "Connects to a REPL server from an HTML document. After the - connection is made, the REPL will evaluate forms in the context of - the document that called this function." - [repl-server-url] - (let [repl-connection (net/xpc-connection - {:peer_uri repl-server-url})] - (swap! xpc-connection (constantly repl-connection)) - (net/register-service repl-connection - :evaluate-javascript - (fn [js] - (net/transmit - repl-connection - :send-result - (evaluate-javascript repl-connection js)))) - (net/connect repl-connection - (constantly nil) - (fn [iframe] - (set! (.-display (.-style iframe)) - "none"))))) diff --git a/src/cljs/clojure/string.cljs b/src/cljs/clojure/string.cljs deleted file mode 100644 index 0ced536247..0000000000 --- a/src/cljs/clojure/string.cljs +++ /dev/null @@ -1,180 +0,0 @@ -; Copyright (c) Rich Hickey. All rights reserved. -; The use and distribution terms for this software are covered by the -; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) -; which can be found in the file epl-v10.html at the root of this distribution. -; By using this software in any fashion, you are agreeing to be bound by -; the terms of this license. -; You must not remove this notice, or any other, from this software. - -(ns clojure.string - (:refer-clojure :exclude [replace reverse]) - (:require [goog.string :as gstring] - [goog.string.StringBuffer :as gstringbuf])) - -(defn- seq-reverse - [coll] - (reduce conj () coll)) - -(defn reverse - "Returns s with its characters reversed." - [s] - (.. s (split "") (reverse) (join ""))) - -(defn replace - "Replaces all instance of match with replacement in s. - match/replacement can be: - - string / string - pattern / (string or function of match)." - [s match replacement] - (cond (string? match) - (.replace s (js/RegExp. (gstring/regExpEscape match) "g") replacement) - (.hasOwnProperty match "source") - (.replace s (js/RegExp. (.-source match) "g") replacement) - :else (throw (str "Invalid match arg: " match)))) - -(defn replace-first - "Replaces the first instance of match with replacement in s. - match/replacement can be: - - string / string - pattern / (string or function of match)." - [s match replacement] - (.replace s match replacement)) - -(defn join - "Returns a string of all elements in coll, as returned by (seq coll), - separated by an optional separator." - ([coll] - (apply str coll)) - ([separator coll] - (apply str (interpose separator coll)))) - -(defn upper-case - "Converts string to all upper-case." - [s] - (.toUpperCase s)) - -(defn lower-case - "Converts string to all lower-case." - [s] - (.toLowerCase s)) - -(defn capitalize - "Converts first character of the string to upper-case, all other - characters to lower-case." - [s] - (if (< (count s) 2) - (upper-case s) - (str (upper-case (subs s 0 1)) - (lower-case (subs s 1))))) - -;; The JavaScript split function takes a limit argument but the return -;; value is not the same as the Java split function. -;; -;; Java: (.split "a-b-c" #"-" 2) => ["a" "b-c"] -;; JavaScript: (.split "a-b-c" #"-" 2) => ["a" "b"] -;; -;; For consistency, the three arg version has been implemented to -;; mimic Java's behavior. - -(defn- pop-last-while-empty - [v] - (loop [v v] - (if (= "" (peek v)) - (recur (pop v)) - v))) - -(defn- discard-trailing-if-needed - [limit v] - (if (= 0 limit) - (pop-last-while-empty v) - v)) - -(defn- split-with-empty-regex - [s limit] - (if (or (<= limit 0) (>= limit (+ 2 (count s)))) - (conj (vec (cons "" (map str (seq s)))) "") - (condp = limit - 1 (vector s) - 2 (vector "" s) - (let [c (- limit 2)] - (conj (vec (cons "" (subvec (vec (map str (seq s))) 0 c))) (subs s c)))))) - -(defn split - "Splits string on a regular expression. Optional argument limit is - the maximum number of splits. Not lazy. Returns vector of the splits." - ([s re] - (split s re 0)) - ([s re limit] - (discard-trailing-if-needed limit - (if (= (str re) "/(?:)/") - (split-with-empty-regex s limit) - (if (< limit 1) - (vec (.split (str s) re)) - (loop [s s - limit limit - parts []] - (if (= limit 1) - (conj parts s) - (if-let [m (re-find re s)] - (let [index (.indexOf s m)] - (recur (.substring s (+ index (count m))) - (dec limit) - (conj parts (.substring s 0 index)))) - (conj parts s))))))))) - -(defn split-lines - "Splits s on \n or \r\n." - [s] - (split s #"\n|\r\n")) - -(defn trim - "Removes whitespace from both ends of string." - [s] - (gstring/trim s)) - -(defn triml - "Removes whitespace from the left side of string." - [s] - (gstring/trimLeft s)) - -(defn trimr - "Removes whitespace from the right side of string." - [s] - (gstring/trimRight s)) - -(defn trim-newline - "Removes all trailing newline \\n or return \\r characters from - string. Similar to Perl's chomp." - [s] - (loop [index (.-length s)] - (if (zero? index) - "" - (let [ch (get s (dec index))] - (if (or (= ch \newline) (= ch \return)) - (recur (dec index)) - (.substring s 0 index)))))) - -(defn blank? - "True is s is nil, empty, or contains only whitespace." - [s] - (gstring/isEmptySafe s)) - -(defn escape - "Return a new string, using cmap to escape each character ch - from s as follows: - - If (cmap ch) is nil, append ch to the new string. - If (cmap ch) is non-nil, append (str (cmap ch)) instead." - [s cmap] - (let [buffer (gstring/StringBuffer.) - length (.-length s)] - (loop [index 0] - (if (= length index) - (. buffer (toString)) - (let [ch (.charAt s index)] - (if-let [replacement (get cmap ch)] - (.append buffer (str replacement)) - (.append buffer ch)) - (recur (inc index))))))) diff --git a/src/main/cljs/cljs/analyzer/passes.cljc b/src/main/cljs/cljs/analyzer/passes.cljc new file mode 100644 index 0000000000..422504493c --- /dev/null +++ b/src/main/cljs/cljs/analyzer/passes.cljc @@ -0,0 +1,32 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer.passes) + +(defn apply-passes + ([ast passes] + (apply-passes ast passes nil)) + ([ast passes opts] + (reduce + (fn [ast pass] + (pass (:env ast) ast opts)) + ast passes))) + +(defn walk + ([ast passes] + (walk ast passes nil)) + ([ast passes opts] + (reduce + (fn [ast child-k] + (assoc ast + child-k + (let [child (get ast child-k)] + (if (vector? child) + (into [] (map #(walk % passes opts)) child) + (walk child passes opts))))) + (some-> ast (apply-passes passes opts)) (:children ast)))) diff --git a/src/main/cljs/cljs/analyzer/passes/and_or.cljc b/src/main/cljs/cljs/analyzer/passes/and_or.cljc new file mode 100644 index 0000000000..52bc76c8a6 --- /dev/null +++ b/src/main/cljs/cljs/analyzer/passes/and_or.cljc @@ -0,0 +1,118 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer.passes.and-or + (:require [cljs.analyzer.passes :as passes])) + +(def simple-ops + #{:var :js-var :local :invoke :const :host-field :host-call :js :quote}) + +(defn ->expr-env [ast] + (assoc-in ast [:env :context] :expr)) + +(defn simple-op? [ast] + (contains? simple-ops (:op ast))) + +(defn simple-test-expr? + [{:keys [op] :as ast}] + (boolean + (and (simple-op? ast) + ('#{boolean seq} + (or (:tag ast) + (when (#{:local :var} op) + (-> ast :info :tag))))))) + +(defn single-binding-let? [ast] + (and (= :let (:op ast)) + (= 1 (count (-> ast :bindings))))) + +(defn no-statements? [let-ast] + (= [] (-> let-ast :body :statements))) + +(defn returns-if? [let-ast] + (= :if (-> let-ast :body :ret :op))) + +(defn simple-test-binding-let? [ast] + (and (single-binding-let? ast) + (no-statements? ast) + (simple-test-expr? (-> ast :bindings first :init)) + (returns-if? ast))) + +(defn test=then? [if-ast] + ;; remove :env, if same, local will differ only by + ;; :context (:expr | :statement) + (= (dissoc (:test if-ast) :env) + (dissoc (:then if-ast) :env))) + +(defn test=else? [if-ast] + ;; remove :env, if same, local will differ only by + ;; :context (:expr | :statement) + (= (dissoc (:test if-ast) :env) + (dissoc (:else if-ast) :env))) + +(defn simple-and? [ast] + (and (simple-test-binding-let? ast) + (test=else? (-> ast :body :ret)))) + +(defn simple-or? [ast] + (and (simple-test-binding-let? ast) + (test=then? (-> ast :body :ret)))) + +(defn optimizable-and? [ast] + (and (simple-and? ast) + (simple-test-expr? (-> ast :body :ret :then)))) + +(defn optimizable-or? [ast] + (and (simple-or? ast) + (simple-test-expr? (-> ast :body :ret :else)))) + +(defn remove-loop-let [fn-ast local] + (update fn-ast :loop-lets + (fn [loop-lets] + (map + (fn [m] + (update m :params + (fn [xs] (remove #(= local (:name %)) xs)))) + loop-lets)))) + +(defn remove-local-pass [local] + (fn [env ast opts] + (cond-> (update-in ast [:env :locals] dissoc local) + (= :fn (:op ast)) (remove-loop-let local)))) + +(defn optimize-and [ast] + (let [{:keys [init name]} (-> ast :bindings first)] + {:op :js + :env (:env ast) + :segs ["((" ") && (" "))"] + :args [init + (passes/walk + (->expr-env (-> ast :body :ret :then)) + [(remove-local-pass name)])] + :form (:form ast) + :children [:args] + :tag 'boolean})) + +(defn optimize-or [ast] + (let [{:keys [init name]} (-> ast :bindings first)] + {:op :js + :env (:env ast) + :segs ["((" ") || (" "))"] + :args [init + (passes/walk + (->expr-env (-> ast :body :ret :else)) + [(remove-local-pass name)])] + :form (:form ast) + :children [:args] + :tag 'boolean})) + +(defn optimize [env ast _] + (cond + (optimizable-and? ast) (optimize-and ast) + (optimizable-or? ast) (optimize-or ast) + :else ast)) diff --git a/src/main/cljs/cljs/analyzer/passes/lite.cljc b/src/main/cljs/cljs/analyzer/passes/lite.cljc new file mode 100644 index 0000000000..d0ea8c659b --- /dev/null +++ b/src/main/cljs/cljs/analyzer/passes/lite.cljc @@ -0,0 +1,32 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer.passes.lite + (:refer-clojure :exclude [var?])) + +(defn var? [ast] + (= :var (:op ast))) + +(def ctor->ctor-lite + '{cljs.core/vector cljs.core/vector-lite + cljs.core/vec cljs.core/vec-lite}) + +(defn update-var [{:keys [name] :as ast}] + (let [replacement (get ctor->ctor-lite name)] + (-> ast + (assoc :name replacement) + (assoc-in [:info :name] replacement)))) + +(defn replace-var? [ast] + (and (var? ast) + (contains? ctor->ctor-lite (:name ast)))) + +(defn use-lite-types + [env ast _] + (cond-> ast + (replace-var? ast) update-var)) diff --git a/src/main/cljs/cljs/bootstrap_nodejs.js b/src/main/cljs/cljs/bootstrap_nodejs.js new file mode 100644 index 0000000000..a1cbd771c1 --- /dev/null +++ b/src/main/cljs/cljs/bootstrap_nodejs.js @@ -0,0 +1,154 @@ +// Copyright 2013 The Closure Library Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview A nodejs script for dynamically requiring Closure within + * nodejs. + * + * Example of usage: + * + * require('./bootstrap/nodejs') + * goog.require('goog.ui.Component') + * + * + * This loads goog.ui.Component in the global scope. + * + * If you want to load custom libraries, you can require the custom deps file + * directly. If your custom libraries introduce new globals, you may + * need to run goog.nodeGlobalRequire to get them to load correctly. + * + * + * require('./path/to/my/deps.js') + * goog.bootstrap.nodeJs.nodeGlobalRequire('./path/to/my/base.js') + * goog.require('my.Class') + * + * + * @author nick@medium.com (Nick Santos) + * + * @nocompile + */ + +var fs = require("fs"); +var vm = require("vm"); +var path = require("path"); +var CLJS_ROOT = "."; + + +/** + * The goog namespace in the global scope. + */ +global.goog = {}; + + +/** + * Imports a script using Node's require() API. + * + * @param {string} src The script source. + * @return {boolean} True if the script was imported, false otherwise. + */ +global.CLOSURE_IMPORT_SCRIPT = function(src, opt_sourceText) { + // if CLJS_ROOT has been rewritten (by REPLs) need to compute require path + // so we can delete the old entry from the Node.js require cache + if(CLJS_ROOT !== ".") { + var cached = null; + if(src.substring(0, 2) == "..") { + cached = path.join(CLJS_ROOT, src.substring(3)); + } else { + cached = path.join(CLJS_ROOT, "goog", src); + } + if(require.cache[cached]) delete require.cache[cached]; + } + + // Sources are always expressed relative to closure's base.js, but + // require() is always relative to the current source. + if (opt_sourceText === undefined) { + var flags = null; + if (goog.debugLoader_) { + var dep = goog.debugLoader_.dependencies_[src]; + if (dep) { + flags = dep.loadFlags; + } + } else { + flags = goog.dependencies_.loadFlags[src]; + } + if (flags && flags["foreign-lib"]) { + nodeGlobalRequire(path.resolve(__dirname, "..", src)); + } else { + require(path.join(".", "..", src)); + } + } else { + eval(opt_sourceText); + } + return true; +}; + + +/** + * Loads a file when using Closure's goog.require() API with goog.modules. + * + * @param {string} src The file source. + * @return {string} The file contents. + */ +global.CLOSURE_LOAD_FILE_SYNC = function(src) { + return fs.readFileSync( + path.resolve(__dirname, "..", src), {encoding: "utf-8"}); +}; + + +// Declared here so it can be used to require base.js +function nodeGlobalRequire(file) { + var _module = global.module, + _exports = global.exports, + exportedRequire = false; + + // to circumvent Node.js environment detection in bundled libraries + global.module = undefined; + global.exports = undefined; + + // to allow requires of Node.js libraries (i.e. platform libs) that + // couldn't be bundled for some reason + if(global.require == undefined) { + exportedRequire = true; + global.require = require; + } + + vm.runInThisContext.call(global, fs.readFileSync(file), file); + + global.exports = _exports; + global.module = _module; + + if(exportedRequire) { + global.require = undefined; + } +} + + +// Load Closure's base.js into memory. It is assumed base.js is in the +// directory above this directory given this script's location in +// bootstrap/nodejs.js. +nodeGlobalRequire(path.resolve(__dirname, "..", "base.js")); + + +/** + * Bootstraps a file into the global scope. + * + * This is strictly for cases where normal require() won't work, + * because the file declares global symbols with 'var' that need to + * be added to the global scope. + * @suppress {missingProvide} + * + * @param {string} file The path to the file. + */ +goog.nodeGlobalRequire = nodeGlobalRequire; + diff --git a/src/main/cljs/cljs/core.cljs b/src/main/cljs/cljs/core.cljs new file mode 100644 index 0000000000..c5d1866893 --- /dev/null +++ b/src/main/cljs/cljs/core.cljs @@ -0,0 +1,13148 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.core + (:require goog.math.Long + goog.math.Integer + [goog.string :as gstring] + [goog.object :as gobject] + [goog.array :as garray] + [goog.Uri]) + (:import [goog.string StringBuffer])) + +;; next line is auto-generated by the build-script - Do not edit! +(def *clojurescript-version*) + +;; Setting of these Vars is in ClojureScript code is associated with intrinsics +;; that affect compilation state, but otherwise turn into no-ops in the emitted +;; JavaScript. + +;; The scope of *unchecked-if* is controlled by balanced pairs of set! calls. +(def *unchecked-if* false) +;; The scope of *unchecked-arrays* is file-scope: In JVM ClojureScript its side- +;; effect is to set same-named analyzer dynamic Var, which is unset via binding +;; scopes. In self-hosted it is cleared via cljs.js/post-file-side-effects. +(def *unchecked-arrays* false) +;; The scope of *warn-on-infer* is file-scope: Its side effect is to set the +;; cljs.analyzer/*cljs-warnings* dynamic Var, which is unset via binding scopes. +(def *warn-on-infer* false) + +(set! *unchecked-arrays* true) + +(defonce PROTOCOL_SENTINEL #js {}) + +(def MODULE_URIS nil) ;; set by compiler +(def MODULE_INFOS nil) ;; set by compiler + +(goog-define + ^{:dynamic true + :doc "Var bound to the name value of the compiler build :target option. + For example, if the compiler build :target is :nodejs, *target* will be bound + to \"nodejs\". *target* is a Google Closure define and can be set by compiler + :closure-defines option."} + *target* "default") + +(goog-define + ^{:dynamic true + :doc "Manually set the JavaScript global context. Only \"window\", \"self\" + , and \"global\" supported. "} + *global* "default") + +(goog-define + ^{:doc "Boolean flag for LITE_MODE"} + LITE_MODE false) + +(def + ^{:dynamic true + :doc "Var bound to the current namespace. Only used for bootstrapping." + :jsdoc ["@type {*}"]} + *ns* nil) + +(def + ^{:dynamic true + :jsdoc ["@type {*}"]} + *out* nil) + +(def + ^{:dynamic true} + *assert* true) + +(defonce + ^{:doc "Each runtime environment provides a different way to print output. + Whatever function *print-fn* is bound to will be passed any + Strings which should be printed." :dynamic true} + *print-fn* nil) + +(declare boolean) + +(defn ^{:doc "Arranges to have tap functions executed via the supplied f, a + function of no arguments. Returns true if successful, false otherwise." :dynamic true} + *exec-tap-fn* + [f] + (and + (exists? js/setTimeout) + ;; See CLJS-3274 - workaround for recent WebKit releases + (boolean (js/setTimeout f 0)))) + +(defonce + ^{:doc "Each runtime environment provides a different way to print error output. + Whatever function *print-err-fn* is bound to will be passed any + Strings which should be printed." :dynamic true} + *print-err-fn* nil) + +(defn set-print-fn! + "Set *print-fn* to f." + [f] (set! *print-fn* f)) + +(defn set-print-err-fn! + "Set *print-err-fn* to f." + [f] (set! *print-err-fn* f)) + +(def + ^{:dynamic true + :doc "When set to true, output will be flushed whenever a newline is printed. + + Defaults to true."} + *flush-on-newline* true) + +(def + ^{:dynamic true + :doc "When set to logical false will drop newlines from printing calls. + This is to work around the implicit newlines emitted by standard JavaScript + console objects."} + *print-newline* true) + +(def + ^{:dynamic true + :doc "When set to logical false, strings and characters will be printed with + non-alphanumeric characters converted to the appropriate escape sequences. + + Defaults to true"} + *print-readably* true) + +(def + ^{:dynamic true + :doc "If set to logical true, when printing an object, its metadata will also + be printed in a form that can be read back by the reader. + + Defaults to false."} + *print-meta* false) + +(def + ^{:dynamic true + :doc "When set to logical true, objects will be printed in a way that preserves + their type when read in later. + + Defaults to false."} + *print-dup* false) + +(def + ^{:dynamic true + :doc "*print-namespace-maps* controls whether the printer will print + namespace map literal syntax. + + Defaults to false, but the REPL binds it to true."} + *print-namespace-maps* false) + +(def + ^{:dynamic true + :doc "*print-length* controls how many items of each collection the + printer will print. If it is bound to logical false, there is no + limit. Otherwise, it must be bound to an integer indicating the maximum + number of items of each collection to print. If a collection contains + more items, the printer will print items up to the limit followed by + '...' to represent the remaining items. The root binding is nil + indicating no limit." + :jsdoc ["@type {null|number}"]} + *print-length* nil) + +(def + ^{:dynamic true + :doc "*print-level* controls how many levels deep the printer will + print nested objects. If it is bound to logical false, there is no + limit. Otherwise, it must be bound to an integer indicating the maximum + level to print. Each argument to print is at level 0; if an argument is a + collection, its items are at level 1; and so on. If an object is a + collection and is at a level greater than or equal to the value bound to + *print-level*, the printer prints '#' to represent it. The root binding + is nil indicating no limit." + :jsdoc ["@type {null|number}"]} + *print-level* nil) + +(def + ^{:dynamic true + :doc "*print-fns-bodies* controls whether functions print their source or + only their names."} + *print-fn-bodies* false) + +(defonce + ^{:dynamic true + :jsdoc ["@type {*}"]} + *loaded-libs* nil) + +(defn- pr-opts [] + {:flush-on-newline *flush-on-newline* + :readably *print-readably* + :meta *print-meta* + :dup *print-dup* + :print-length *print-length*}) + +(declare into-array) + +(defn enable-console-print! + "Set *print-fn* to console.log" + [] + (set! *print-newline* false) + (set-print-fn! + (fn [] + (let [xs (js-arguments)] + (.apply (.-log js/console) js/console (garray/clone xs))))) + (set-print-err-fn! + (fn [] + (let [xs (js-arguments)] + (.apply (.-error js/console) js/console (garray/clone xs))))) + nil) + +(def + ^{:dynamic true + :doc "bound in a repl thread to the most recent value printed"} + *1) + +(def + ^{:dynamic true + :doc "bound in a repl thread to the second most recent value printed"} + *2) + +(def + ^{:dynamic true + :doc "bound in a repl thread to the third most recent value printed"} + *3) + +(def + ^{:dynamic true + :doc "bound in a repl thread to the most recent exception caught by the repl"} + *e) + +(defn truth_ + "Internal - do not use!" + [x] + (cljs.core/truth_ x)) + +(def not-native nil) + +(declare instance? Keyword) + +(defn ^boolean identical? + "Tests if 2 arguments are the same object" + [x y] + (cljs.core/identical? x y)) + +(defn ^boolean nil? + "Returns true if x is nil, false otherwise." + [x] + (coercive-= x nil)) + +(defn array? + "Returns true if x is a JavaScript array." + [x] + (if (identical? *target* "nodejs") + (.isArray js/Array x) + (instance? js/Array x))) + +(defn ^boolean number? + "Returns true if x is a JavaScript number." + [x] + (cljs.core/number? x)) + +(defn not + "Returns true if x is logical false, false otherwise." + [x] + (cond + (nil? x) true + (false? x) true + :else false)) + +(defn ^boolean some? + "Returns true if x is not nil, false otherwise." + [x] (not (nil? x))) + +(defn- pr-opts-fnl [opts] + (if-not (nil? opts) + (:flush-on-newline opts) + *flush-on-newline*)) + +(defn- pr-opts-readably [opts] + (if-not (nil? opts) + (:readably opts) + *print-readably*)) + +(defn- pr-opts-meta [opts] + (if-not (nil? opts) + (:meta opts) + *print-meta*)) + +(defn- pr-opts-dup [opts] + (if-not (nil? opts) + (:dup opts) + *print-dup*)) + +(defn- pr-opts-len [opts] + (if-not (nil? opts) + (:print-length opts) + *print-length*)) + +(defn object? + "Returns true if x's constructor is Object" + [x] + (if-not (nil? x) + (identical? (.-constructor x) js/Object) + false)) + +(defn ^boolean string? + "Returns true if x is a JavaScript string." + [x] + (identical? "string" (goog/typeOf x))) + +(defn char? + "Returns true if x is a JavaScript string of length one." + [x] + (and (string? x) (== 1 (.-length x)))) + +(defn any? + "Returns true if given any argument." + [x] true) + +(set! *unchecked-if* true) +(defn native-satisfies? + "Internal - do not use!" + [p x] + (let [x (if (nil? x) nil x)] + (cond + (unchecked-get p (goog/typeOf x)) true + (unchecked-get p "_") true + :else false))) +(set! *unchecked-if* false) + +(defn is_proto_ + [x] + (identical? (.-prototype (.-constructor x)) x)) + +(def + ^{:doc "When compiled for a command-line target, whatever function + *main-cli-fn* is set to will be called with the command-line + argv as arguments"} + *main-cli-fn* nil) + +(def + ^{:doc "A sequence of the supplied command line arguments, or nil if + none were supplied"} + *command-line-args* nil) + +(defn type + "Return x's constructor." + [x] + (when-not (nil? x) + (.-constructor x))) + +(defn missing-protocol [proto obj] + (let [ty (type obj) + ty (if (and ty (.-cljs$lang$type ty)) + (.-cljs$lang$ctorStr ty) + (goog/typeOf obj))] + (js/Error. + (.join (array "No protocol method " proto + " defined for type " ty ": " obj) "")))) + +(defn type->str [ty] + (if-let [s (.-cljs$lang$ctorStr ty)] + s + (str_ ty))) + +;; INTERNAL - do not use, only for Node.js +(defn load-file [file] + (when-not js/COMPILED + (cljs.core/load-file* file))) + +(if (and (exists? js/Symbol) + (identical? (goog/typeOf js/Symbol) "function")) + (def ITER_SYMBOL (.-iterator js/Symbol)) + (def ITER_SYMBOL "@@iterator")) + +(def ^{:jsdoc ["@enum {string}"]} + CHAR_MAP + #js {"-" "_" + ":" "_COLON_" + "+" "_PLUS_" + ">" "_GT_" + "<" "_LT_" + "=" "_EQ_" + "~" "_TILDE_" + "!" "_BANG_" + "@" "_CIRCA_" + "#" "_SHARP_" + "'" "_SINGLEQUOTE_" + "\\\"" "_DOUBLEQUOTE_" + "%" "_PERCENT_" + "^" "_CARET_" + "&" "_AMPERSAND_" + "*" "_STAR_" + "|" "_BAR_" + "{" "_LBRACE_" + "}" "_RBRACE_" + "[" "_LBRACK_" + "]" "_RBRACK_" + "/" "_SLASH_" + "\\\\" "_BSLASH_" + "?" "_QMARK_"}) + +(def ^{:jsdoc ["@enum {string}"]} + DEMUNGE_MAP + #js {"_" "-" + "_COLON_" ":" + "_PLUS_" "+" + "_GT_" ">" + "_LT_" "<" + "_EQ_" "=" + "_TILDE_" "~" + "_BANG_" "!" + "_CIRCA_" "@" + "_SHARP_" "#" + "_SINGLEQUOTE_" "'" + "_DOUBLEQUOTE_" "\\\"" + "_PERCENT_" "%" + "_CARET_" "^" + "_AMPERSAND_" "&" + "_STAR_" "*" + "_BAR_" "|" + "_LBRACE_" "{" + "_RBRACE_" "}" + "_LBRACK_" "[" + "_RBRACK_" "]" + "_SLASH_" "/" + "_BSLASH_" "\\\\" + "_QMARK_" "?"}) + +(def DEMUNGE_PATTERN nil) + +(defn system-time + "Returns highest resolution time offered by host in milliseconds." + [] + (cond + (and (exists? js/performance) + (not (nil? (. js/performance -now)))) + (.now js/performance) + + (and (exists? js/process) + (not (nil? (. js/process -hrtime)))) + (let [t (.hrtime js/process)] + (/ (+ (* (aget t 0) 1e9) (aget t 1)) 1e6)) + + :else (.getTime (js/Date.)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; arrays ;;;;;;;;;;;;;;;; + +(declare apply) + +(defn make-array + "Construct a JavaScript array of the specified dimensions. Accepts ignored + type argument for compatibility with Clojure. Note that there is no efficient + way to allocate multi-dimensional arrays in JavaScript; as such, this function + will run in polynomial time when called with 3 or more arguments." + ([size] + (js/Array. size)) + ([type size] + (make-array size)) + ([type size & more-sizes] + (let [dims more-sizes + dimarray (make-array size)] + (dotimes [i (alength dimarray)] + (aset dimarray i (apply make-array nil dims))) + dimarray))) + +(defn aclone + "Returns a javascript array, cloned from the passed in array" + [arr] + (let [len (alength arr) + new-arr (make-array len)] + (dotimes [i len] + (aset new-arr i (aget arr i))) + new-arr)) + +(defn ^array array + "Creates a new javascript array. +@param {...*} var_args" ;;array is a special case, don't emulate this doc string + [var-args] ;; [& items] + (let [a (js/Array. (alength (cljs.core/js-arguments)))] + (loop [i 0] + (if (< i (alength a)) + (do + (aset a i (aget (cljs.core/js-arguments) i)) + (recur (inc i))) + a)))) + +(defn- maybe-warn + [e] + (when *print-err-fn* + (*print-err-fn* e))) + +(defn- checked-aget + ([array idx] + (when-assert + (try + (assert (or (array? array) (goog/isArrayLike array))) + (assert (number? idx)) + (assert (not (neg? idx))) + (assert (< idx (alength array))) + (catch :default e + (maybe-warn e)))) + (unchecked-get array idx)) + ([array idx & idxs] + (apply checked-aget (checked-aget array idx) idxs))) + +(defn- checked-aset + ([array idx val] + (when-assert + (try + (assert (or (array? array) (goog/isArrayLike array))) + (assert (number? idx)) + (assert (not (neg? idx))) + (assert (< idx (alength array))) + (catch :default e + (maybe-warn e)))) + (unchecked-set array idx val)) + ([array idx idx2 & idxv] + (apply checked-aset (checked-aget array idx) idx2 idxv))) + +(defn- checked-aget' + ([array idx] + {:pre [(or (array? array) (goog/isArrayLike array)) + (number? idx) (not (neg? idx)) (< idx (alength array))]} + (unchecked-get array idx)) + ([array idx & idxs] + (apply checked-aget' (checked-aget' array idx) idxs))) + +(defn- checked-aset' + ([array idx val] + {:pre [(or (array? array) (goog/isArrayLike array)) + (number? idx) (not (neg? idx)) (< idx (alength array))]} + (unchecked-set array idx val)) + ([array idx idx2 & idxv] + (apply checked-aset' (checked-aget' array idx) idx2 idxv))) + +(defn aget + "Returns the value at the index/indices. Works on JavaScript arrays." + ([array idx] + (cljs.core/aget array idx)) + ([array idx & idxs] + (apply aget (aget array idx) idxs))) + +(defn aset + "Sets the value at the index/indices. Works on JavaScript arrays. + Returns val." + ([array idx val] + (cljs.core/aset array idx val)) + ([array idx idx2 & idxv] + (apply aset (aget array idx) idx2 idxv))) + +(defn ^number alength + "Returns the length of the array. Works on arrays of all types." + [array] + (cljs.core/alength array)) + +(declare reduce) + +(defn ^array into-array + "Returns an array with components set to the values in aseq. Optional type + argument accepted for compatibility with Clojure." + ([aseq] + (into-array nil aseq)) + ([type aseq] + (reduce (fn [a x] (.push a x) a) (array) aseq))) + +(defn js-invoke + "Invoke JavaScript object method via string. Needed when the + string is not a valid unquoted property name." + [obj s & args] + (.apply (unchecked-get obj s) obj (into-array args))) + +(defn js-symbol? + "Returns true if x is an instance of Symbol" + [x] + (or (identical? (goog/typeOf x) "symbol") + (and (exists? js/Symbol) + (instance? js/Symbol x)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;; core protocols ;;;;;;;;;;;;; + +(defprotocol Fn + "Marker protocol") + +(defprotocol IFn + "Protocol for adding the ability to invoke an object as a function. + For example, a vector can also be used to look up a value: + ([1 2 3 4] 1) => 2" + (-invoke + [this] + [this a] + [this a b] + [this a b c] + [this a b c d] + [this a b c d e] + [this a b c d e f] + [this a b c d e f g] + [this a b c d e f g h] + [this a b c d e f g h i] + [this a b c d e f g h i j] + [this a b c d e f g h i j k] + [this a b c d e f g h i j k l] + [this a b c d e f g h i j k l m] + [this a b c d e f g h i j k l m n] + [this a b c d e f g h i j k l m n o] + [this a b c d e f g h i j k l m n o p] + [this a b c d e f g h i j k l m n o p q] + [this a b c d e f g h i j k l m n o p q r] + [this a b c d e f g h i j k l m n o p q r s] + [this a b c d e f g h i j k l m n o p q r s t] + [this a b c d e f g h i j k l m n o p q r s t rest])) + +(defprotocol ICloneable + "Protocol for cloning a value." + (^clj -clone [value] + "Creates a clone of value.")) + +(defprotocol ICounted + "Protocol for adding the ability to count a collection in constant time." + (^number -count [coll] + "Calculates the count of coll in constant time. Used by cljs.core/count.")) + +(defprotocol IEmptyableCollection + "Protocol for creating an empty collection." + (-empty [coll] + "Returns an empty collection of the same category as coll. Used + by cljs.core/empty.")) + +(defprotocol ICollection + "Protocol for adding to a collection." + (^clj -conj [coll o] + "Returns a new collection of coll with o added to it. The new item + should be added to the most efficient place, e.g. + (conj [1 2 3 4] 5) => [1 2 3 4 5] + (conj '(2 3 4 5) 1) => '(1 2 3 4 5)")) + +#_(defprotocol IOrdinal + (-index [coll])) + +(defprotocol IIndexed + "Protocol for collections to provide indexed-based access to their items." + (-nth [coll n] [coll n not-found] + "Returns the value at the index n in the collection coll. + Returns not-found if index n is out of bounds and not-found is supplied.")) + +(defprotocol ASeq + "Marker protocol indicating an array sequence.") + +(defprotocol ISeq + "Protocol for collections to provide access to their items as sequences." + (-first [coll] + "Returns the first item in the collection coll. Used by cljs.core/first.") + (^clj -rest [coll] + "Returns a new collection of coll without the first item. It should + always return a seq, e.g. + (rest []) => () + (rest nil) => ()")) + +(defprotocol INext + "Protocol for accessing the next items of a collection." + (^clj-or-nil -next [coll] + "Returns a new collection of coll without the first item. In contrast to + rest, it should return nil if there are no more items, e.g. + (next []) => nil + (next nil) => nil")) + +(defprotocol ILookup + "Protocol for looking up a value in a data structure." + (-lookup [o k] [o k not-found] + "Use k to look up a value in o. If not-found is supplied and k is not + a valid value that can be used for look up, not-found is returned.")) + +(defprotocol IAssociative + "Protocol for adding associativity to collections." + (^boolean -contains-key? [coll k] + "Returns true if k is a key in coll.") + #_(-entry-at [coll k]) + (^clj -assoc [coll k v] + "Returns a new collection of coll with a mapping from key k to + value v added to it.")) + +(defprotocol IFind + "Protocol for implementing entry finding in collections." + (-find [coll k] "Returns the map entry for key, or nil if key not present.")) + +(defprotocol IMap + "Protocol for adding mapping functionality to collections." + #_(-assoc-ex [coll k v]) + (^clj -dissoc [coll k] + "Returns a new collection of coll without the mapping for key k.")) + +(defprotocol IMapEntry + "Protocol for examining a map entry." + (-key [coll] + "Returns the key of the map entry.") + (-val [coll] + "Returns the value of the map entry.")) + +(defprotocol ISet + "Protocol for adding set functionality to a collection." + (^clj -disjoin [coll v] + "Returns a new collection of coll that does not contain v.")) + +(defprotocol IStack + "Protocol for collections to provide access to their items as stacks. The top + of the stack should be accessed in the most efficient way for the different + data structures." + (-peek [coll] + "Returns the item from the top of the stack. Is used by cljs.core/peek.") + (^clj -pop [coll] + "Returns a new stack without the item on top of the stack. Is used + by cljs.core/pop.")) + +(defprotocol IVector + "Protocol for adding vector functionality to collections." + (^clj -assoc-n [coll n val] + "Returns a new vector with value val added at position n.")) + +(defprotocol IDeref + "Protocol for adding dereference functionality to a reference." + (-deref [o] + "Returns the value of the reference o.")) + +(defprotocol IDerefWithTimeout + (-deref-with-timeout [o msec timeout-val])) + +(defprotocol IMeta + "Protocol for accessing the metadata of an object." + (^clj-or-nil -meta [o] + "Returns the metadata of object o.")) + +(defprotocol IWithMeta + "Protocol for adding metadata to an object." + (^clj -with-meta [o meta] + "Returns a new object with value of o and metadata meta added to it.")) + +(defprotocol IReduce + "Protocol for seq types that can reduce themselves. + Called by cljs.core/reduce." + (-reduce [coll f] [coll f start] + "f should be a function of 2 arguments. If start is not supplied, + returns the result of applying f to the first 2 items in coll, then + applying f to that result and the 3rd item, etc.")) + +(defprotocol IKVReduce + "Protocol for associative types that can reduce themselves + via a function of key and val. Called by cljs.core/reduce-kv." + (-kv-reduce [coll f init] + "Reduces an associative collection and returns the result. f should be + a function that takes three arguments.")) + +(defprotocol IEquiv + "Protocol for adding value comparison functionality to a type." + (^boolean -equiv [o other] + "Returns true if o and other are equal, false otherwise.")) + +(defprotocol IHash + "Protocol for adding hashing functionality to a type." + (-hash [o] + "Returns the hash code of o.")) + +(defprotocol ISeqable + "Protocol for adding the ability to a type to be transformed into a sequence." + (^clj-or-nil -seq [o] + "Returns a seq of o, or nil if o is empty.")) + +(defprotocol ISequential + "Marker interface indicating a persistent collection of sequential items") + +(defprotocol IList + "Marker interface indicating a persistent list") + +(defprotocol IRecord + "Marker interface indicating a record object") + +(defprotocol IReversible + "Protocol for reversing a seq." + (^clj -rseq [coll] + "Returns a seq of the items in coll in reversed order.")) + +(defprotocol ISorted + "Protocol for a collection which can represent their items + in a sorted manner. " + (^clj -sorted-seq [coll ascending?] + "Returns a sorted seq from coll in either ascending or descending order.") + (^clj -sorted-seq-from [coll k ascending?] + "Returns a sorted seq from coll in either ascending or descending order. + If ascending is true, the result should contain all items which are > or >= + than k. If ascending is false, the result should contain all items which + are < or <= than k, e.g. + (-sorted-seq-from (sorted-set 1 2 3 4 5) 3 true) => (3 4 5) + (-sorted-seq-from (sorted-set 1 2 3 4 5) 3 false) => (3 2 1)") + (-entry-key [coll entry] + "Returns the key for entry.") + (-comparator [coll] + "Returns the comparator for coll.")) + +(defprotocol IWriter + "Protocol for writing. Currently only implemented by StringBufferWriter." + (-write [writer s] + "Writes s with writer and returns the result.") + (-flush [writer] + "Flush writer.")) + +(defprotocol IPrintWithWriter + "The old IPrintable protocol's implementation consisted of building a giant + list of strings to concatenate. This involved lots of concat calls, + intermediate vectors, and lazy-seqs, and was very slow in some older JS + engines. IPrintWithWriter implements printing via the IWriter protocol, so it + be implemented efficiently in terms of e.g. a StringBuffer append." + (-pr-writer [o writer opts])) + +(defprotocol IPending + "Protocol for types which can have a deferred realization. Currently only + implemented by Delay and LazySeq." + (^boolean -realized? [x] + "Returns true if a value for x has been produced, false otherwise.")) + +(defprotocol IWatchable + "Protocol for types that can be watched. Currently only implemented by Atom." + (-notify-watches [this oldval newval] + "Calls all watchers with this, oldval and newval.") + (-add-watch [this key f] + "Adds a watcher function f to this. Keys must be unique per reference, + and can be used to remove the watch with -remove-watch.") + (-remove-watch [this key] + "Removes watcher that corresponds to key from this.")) + +(defprotocol IEditableCollection + "Protocol for collections which can transformed to transients." + (^clj -as-transient [coll] + "Returns a new, transient version of the collection, in constant time.")) + +(defprotocol ITransientCollection + "Protocol for adding basic functionality to transient collections." + (^clj -conj! [tcoll val] + "Adds value val to tcoll and returns tcoll.") + (^clj -persistent! [tcoll] + "Creates a persistent data structure from tcoll and returns it.")) + +(defprotocol ITransientAssociative + "Protocol for adding associativity to transient collections." + (^clj -assoc! [tcoll key val] + "Returns a new transient collection of tcoll with a mapping from key to + val added to it.")) + +(defprotocol ITransientMap + "Protocol for adding mapping functionality to transient collections." + (^clj -dissoc! [tcoll key] + "Returns a new transient collection of tcoll without the mapping for key.")) + +(defprotocol ITransientVector + "Protocol for adding vector functionality to transient collections." + (^clj -assoc-n! [tcoll n val] + "Returns tcoll with value val added at position n.") + (^clj -pop! [tcoll] + "Returns tcoll with the last item removed from it.")) + +(defprotocol ITransientSet + "Protocol for adding set functionality to a transient collection." + (^clj -disjoin! [tcoll v] + "Returns tcoll without v.")) + +(defprotocol IComparable + "Protocol for values that can be compared." + (^number -compare [x y] + "Returns a negative number, zero, or a positive number when x is logically + 'less than', 'equal to', or 'greater than' y.")) + +(defprotocol IChunk + "Protocol for accessing the items of a chunk." + (-drop-first [coll] + "Return a new chunk of coll with the first item removed.")) + +(defprotocol IChunkedSeq + "Protocol for accessing a collection as sequential chunks." + (-chunked-first [coll] + "Returns the first chunk in coll.") + (-chunked-rest [coll] + "Return a new collection of coll with the first chunk removed.")) + +(defprotocol IChunkedNext + "Protocol for accessing the chunks of a collection." + (-chunked-next [coll] + "Returns a new collection of coll without the first chunk.")) + +(defprotocol INamed + "Protocol for adding a name." + (^string -name [x] + "Returns the name String of x.") + ( ^{:tag #{string clj-nil}}-namespace [x] + "Returns the namespace String of x.")) + +(defprotocol IAtom + "Marker protocol indicating an atom.") + +(defprotocol IReset + "Protocol for adding resetting functionality." + (-reset! [o new-value] + "Sets the value of o to new-value.")) + +(defprotocol ISwap + "Protocol for adding swapping functionality." + (-swap! [o f] [o f a] [o f a b] [o f a b xs] + "Swaps the value of o to be (apply f current-value-of-atom args).")) + +(defprotocol IVolatile + "Protocol for adding volatile functionality." + (-vreset! [o new-value] + "Sets the value of volatile o to new-value without regard for the + current value. Returns new-value.")) + +(defprotocol IIterable + "Protocol for iterating over a collection." + (-iterator [coll] + "Returns an iterator for coll.")) + +(defprotocol IDrop + "Protocol for persistent or algorithmically defined collections to provide a + means of dropping N items that is more efficient than sequential walking." + (^clj-or-nil -drop [coll n] + "Returns a collection that is ISequential, ISeq, and IReduce, or nil if past + the end. The number of items to drop n must be > 0. It is also useful if the + returned coll implements IDrop for subsequent use in a partition-like scenario.")) + +;; Printing support + +(deftype StringBufferWriter [sb] + IWriter + (-write [_ s] (.append sb s)) + (-flush [_] nil)) + +(defn pr-str* + "Support so that collections can implement toString without + loading all the printing machinery." + [^not-native obj] + (let [sb (StringBuffer.) + writer (StringBufferWriter. sb)] + (-pr-writer obj writer nil) + (-flush writer) + (.toString sb))) + +;;;;;;;;;;;;;;;;;;; Murmur3 ;;;;;;;;;;;;;;; + +;;http://hg.openjdk.java.net/jdk7u/jdk7u6/jdk/file/8c2c5d63a17e/src/share/classes/java/lang/Integer.java +(defn ^number int-rotate-left [x n] + (bit-or + (bit-shift-left x n) + (unsigned-bit-shift-right x (- n)))) + +;; http://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul +(if (and (exists? Math/imul) + (not (zero? (Math/imul 0xffffffff 5)))) + (defn ^number imul [a b] (Math/imul a b)) + (defn ^number imul [a b] + (let [ah (bit-and (unsigned-bit-shift-right a 16) 0xffff) + al (bit-and a 0xffff) + bh (bit-and (unsigned-bit-shift-right b 16) 0xffff) + bl (bit-and b 0xffff)] + (bit-or + (+ (* al bl) + (unsigned-bit-shift-right + (bit-shift-left (+ (* ah bl) (* al bh)) 16) 0)) 0)))) + +;; http://smhasher.googlecode.com/svn/trunk/MurmurHash3.cpp +(def m3-seed 0) +(def m3-C1 (int 0xcc9e2d51)) +(def m3-C2 (int 0x1b873593)) + +(defn ^number m3-mix-K1 [k1] + (-> (int k1) (imul m3-C1) (int-rotate-left 15) (imul m3-C2))) + +(defn ^number m3-mix-H1 [h1 k1] + (int (-> (int h1) (bit-xor (int k1)) (int-rotate-left 13) (imul 5) (+ (int 0xe6546b64))))) + +(defn ^number m3-fmix [h1 len] + (as-> (int h1) h1 + (bit-xor h1 len) + (bit-xor h1 (unsigned-bit-shift-right h1 16)) + (imul h1 (int 0x85ebca6b)) + (bit-xor h1 (unsigned-bit-shift-right h1 13)) + (imul h1 (int 0xc2b2ae35)) + (bit-xor h1 (unsigned-bit-shift-right h1 16)))) + +(defn ^number m3-hash-int [in] + (if (zero? in) + in + (let [k1 (m3-mix-K1 in) + h1 (m3-mix-H1 m3-seed k1)] + (m3-fmix h1 4)))) + +(defn hash-long [high low] + (bit-xor high low)) + +(defn hash-double [f] + (let [arr (doto (js/Float64Array. 1) (aset 0 f)) + buf (.-buffer arr) + high (.getInt32 (js/DataView. buf 0 4)) + low (.getInt32 (js/DataView. buf 4 4))] + (hash-long high low))) + +(defn ^number m3-hash-unencoded-chars [in] + (let [h1 (loop [i 1 h1 m3-seed] + (if (< i (.-length in)) + (recur (+ i 2) + (m3-mix-H1 h1 + (m3-mix-K1 + (bit-or (.charCodeAt in (dec i)) + (bit-shift-left (.charCodeAt in i) 16))))) + h1)) + h1 (if (== (bit-and (.-length in) 1) 1) + (bit-xor h1 (m3-mix-K1 (.charCodeAt in (dec (.-length in))))) + h1)] + (m3-fmix h1 (imul 2 (.-length in))))) + +;;;;;;;;;;;;;;;;;;; symbols ;;;;;;;;;;;;;;; + +(declare list Symbol = compare) + +;; Simple caching of string hashcode +(def string-hash-cache (js-obj)) +(def string-hash-cache-count 0) + +;;http://hg.openjdk.java.net/jdk7u/jdk7u6/jdk/file/8c2c5d63a17e/src/share/classes/java/lang/String.java +(defn hash-string* [s] + (if-not (nil? s) + (let [len (.-length s)] + (if (pos? len) + (loop [i 0 hash 0] + (if (< i len) + (recur (inc i) (+ (imul 31 hash) (.charCodeAt s i))) + hash)) + 0)) + 0)) + +(defn add-to-string-hash-cache [k] + (let [h (hash-string* k)] + (gobject/set string-hash-cache k h) + (set! string-hash-cache-count (inc string-hash-cache-count)) + h)) + +(defn hash-string [k] + (when (> string-hash-cache-count 1024) + (set! string-hash-cache (js-obj)) + (set! string-hash-cache-count 0)) + (if (nil? k) + 0 + (let [h (unchecked-get string-hash-cache k)] + (if (number? h) + h + (add-to-string-hash-cache k))))) + +(defn hash + "Returns the hash code of its argument. Note this is the hash code + consistent with =." + [o] + (cond + (implements? IHash o) + (bit-xor (-hash o) 0) + + (number? o) + (if (js/isFinite o) + (if-not (.isSafeInteger js/Number o) + (hash-double o) + (js-mod (Math/floor o) 2147483647)) + (case o + ##Inf + 2146435072 + ##-Inf + -1048576 + 2146959360)) + + ;; note: mirrors Clojure's behavior on the JVM, where the hashCode is + ;; 1231 for true and 1237 for false + ;; http://docs.oracle.com/javase/7/docs/api/java/lang/Boolean.html#hashCode%28%29 + (true? o) 1231 + + (false? o) 1237 + + (string? o) + (m3-hash-int (hash-string o)) + + (instance? js/Date o) + (bit-xor (.valueOf o) 0) + + (nil? o) 0 + + :else + (bit-xor (-hash o) 0))) + +(defn hash-combine [seed hash] + ; a la boost + (bit-xor seed + (+ hash 0x9e3779b9 + (bit-shift-left seed 6) + (bit-shift-right seed 2)))) + +(defn ^boolean instance? + "Evaluates x and tests if it is an instance of the type + c. Returns true or false" + [c x] + (cljs.core/instance? c x)) + +(defn ^boolean symbol? + "Return true if x is a Symbol" + [x] + (instance? Symbol x)) + +(defn- hash-symbol [sym] + (hash-combine + (m3-hash-unencoded-chars (.-name sym)) + (hash-string (.-ns sym)))) + +(defn- compare-symbols [a b] + (cond + (identical? (.-str a) (.-str b)) 0 + (and (not (.-ns a)) (.-ns b)) -1 + (.-ns a) (if-not (.-ns b) + 1 + (let [nsc (garray/defaultCompare (.-ns a) (.-ns b))] + (if (== 0 nsc) + (garray/defaultCompare (.-name a) (.-name b)) + nsc))) + :default (garray/defaultCompare (.-name a) (.-name b)))) + +(declare get) + +(deftype Symbol [ns name str ^:mutable _hash _meta] + Object + (toString [_] str) + (equiv [this other] (-equiv this other)) + + IEquiv + (-equiv [_ other] + (if (instance? Symbol other) + (identical? str (.-str other)) + false)) + + IFn + (-invoke [sym coll] + (get coll sym)) + (-invoke [sym coll not-found] + (get coll sym not-found)) + + IMeta + (-meta [_] _meta) + + IWithMeta + (-with-meta [_ new-meta] (Symbol. ns name str _hash new-meta)) + + IHash + (-hash [sym] + (caching-hash sym hash-symbol _hash)) + + INamed + (-name [_] name) + (-namespace [_] ns) + + IPrintWithWriter + (-pr-writer [o writer _] (-write writer str))) + +(defn var? + "Returns true if v is of type cljs.core.Var" + [v] + (instance? cljs.core.Var v)) + +(defn symbol + "Returns a Symbol with the given namespace and name. Arity-1 works + on strings, keywords, and vars." + ([name] + (cond (symbol? name) name + (string? name) (let [idx (.indexOf name "/")] + (if (< idx 1) + (symbol nil name) + (symbol (.substring name 0 idx) + (.substring name (inc idx) (. name -length))))) + (var? name) (.-sym name) + (keyword? name) (recur (.-fqn name)) + :else (throw (new js/Error "no conversion to symbol")))) + ([ns name] + (let [sym-str (if-not (nil? ns) + (str_ ns "/" name) + name)] + (Symbol. ns name sym-str nil nil)))) + +(deftype Var [val sym _meta] + Object + (isMacro [_] + (. (val) -cljs$lang$macro)) + (toString [_] + (str_ "#'" sym)) + IDeref + (-deref [_] (val)) + IMeta + (-meta [_] _meta) + IWithMeta + (-with-meta [_ new-meta] + (Var. val sym new-meta)) + IEquiv + (-equiv [this other] + (if (instance? Var other) + (= (.-sym this) (.-sym other)) + false)) + IHash + (-hash [_] + (hash-symbol sym)) + Fn + IFn + (-invoke [_] + ((val))) + (-invoke [_ a] + ((val) a)) + (-invoke [_ a b] + ((val) a b)) + (-invoke [_ a b c] + ((val) a b c)) + (-invoke [_ a b c d] + ((val) a b c d)) + (-invoke [_ a b c d e] + ((val) a b c d e)) + (-invoke [_ a b c d e f] + ((val) a b c d e f)) + (-invoke [_ a b c d e f g] + ((val) a b c d e f g)) + (-invoke [_ a b c d e f g h] + ((val) a b c d e f g h)) + (-invoke [_ a b c d e f g h i] + ((val) a b c d e f g h i)) + (-invoke [_ a b c d e f g h i j] + ((val) a b c d e f g h i j)) + (-invoke [_ a b c d e f g h i j k] + ((val) a b c d e f g h i j k)) + (-invoke [_ a b c d e f g h i j k l] + ((val) a b c d e f g h i j k l)) + (-invoke [_ a b c d e f g h i j k l m] + ((val) a b c d e f g h i j k l m)) + (-invoke [_ a b c d e f g h i j k l m n] + ((val) a b c d e f g h i j k l m n)) + (-invoke [_ a b c d e f g h i j k l m n o] + ((val) a b c d e f g h i j k l m n o)) + (-invoke [_ a b c d e f g h i j k l m n o p] + ((val) a b c d e f g h i j k l m n o p)) + (-invoke [_ a b c d e f g h i j k l m n o p q] + ((val) a b c d e f g h i j k l m n o p q)) + (-invoke [_ a b c d e f g h i j k l m n o p q r] + ((val) a b c d e f g h i j k l m n o p q r)) + (-invoke [_ a b c d e f g h i j k l m n o p q r s] + ((val) a b c d e f g h i j k l m n o p q r s)) + (-invoke [_ a b c d e f g h i j k l m n o p q r s t] + ((val) a b c d e f g h i j k l m n o p q r s t)) + (-invoke [_ a b c d e f g h i j k l m n o p q r s t rest] + (apply (val) a b c d e f g h i j k l m n o p q r s t rest))) + +;;;;;;;;;;;;;;;;;;; fundamentals ;;;;;;;;;;;;;;; + +(declare array-seq prim-seq IndexedSeq) + +(defn iterable? + "Return true if x implements IIterable protocol." + [x] + (satisfies? IIterable x)) + +(defn js-iterable? + "Return true if x has a JavaScript iterator property" + [x] + (and (not (nil? x)) + (not (nil? (js* "~{}[~{}]" x ITER_SYMBOL))))) + +(defn clone + "Clone the supplied value which must implement ICloneable." + [value] + (-clone value)) + +(defn cloneable? + "Return true if x implements ICloneable protocol." + [value] + (satisfies? ICloneable value)) + +(declare es6-iterator-seq) + +(defn ^seq seq + "Returns a seq on the collection. If the collection is + empty, returns nil. (seq nil) returns nil. seq also works on + Strings." + [coll] + (when-not (nil? coll) + (cond + (implements? ISeqable coll) + (-seq coll) + + (array? coll) + (when-not (zero? (alength coll)) + (IndexedSeq. coll 0 nil)) + + (string? coll) + (when-not (zero? (.-length coll)) + (IndexedSeq. coll 0 nil)) + + (js-iterable? coll) + (es6-iterator-seq + (.call (gobject/get coll ITER_SYMBOL) coll)) + + (native-satisfies? ISeqable coll) + (-seq coll) + + :else (throw (js/Error. (str_ coll " is not ISeqable")))))) + +(defn first + "Returns the first item in the collection. Calls seq on its + argument. If coll is nil, returns nil." + [coll] + (when-not (nil? coll) + (if (implements? ISeq coll) + (-first coll) + (let [s (seq coll)] + (when-not (nil? s) + (-first s)))))) + +(defn ^seq rest + "Returns a possibly empty seq of the items after the first. Calls seq on its + argument." + [coll] + (if-not (nil? coll) + (if (implements? ISeq coll) + (-rest coll) + (let [s (seq coll)] + (if s + (-rest ^not-native s) + ()))) + ())) + +(defn ^seq next + "Returns a seq of the items after the first. Calls seq on its + argument. If there are no more items, returns nil" + [coll] + (when-not (nil? coll) + (if (implements? INext coll) + (-next coll) + (seq (rest coll))))) + +(defn ^boolean = + "Equality. Returns true if x equals y, false if not. Compares + numbers and collections in a type-independent manner. Clojure's immutable data + structures define -equiv (and thus =) as a value, not an identity, + comparison." + ([x] true) + ([x y] + (if (nil? x) + (nil? y) + (or (identical? x y) + ^boolean (-equiv x y)))) + ([x y & more] + (if (= x y) + (if (next more) + (recur y (first more) (next more)) + (= y (first more))) + false))) + +(deftype ES6Iterator [^:mutable s] + Object + (next [_] + (if-not (nil? s) + (let [x (first s)] + (set! s (next s)) + #js {:value x :done false}) + #js {:value nil :done true}))) + +(defn es6-iterator + "Return a ES2015+ compatible iterator for coll." + [coll] + (ES6Iterator. (seq coll))) + +(deftype ES6IteratorSeq [value iter ^:mutable _rest] + ISeqable + (-seq [this] this) + ISeq + (-first [_] value) + (-rest [_] + (when (nil? _rest) + (set! _rest (es6-iterator-seq iter))) + _rest)) + +(defn es6-iterator-seq + "Given an ES2015+ compatible iterator return a seq." + [iter] + (let [v (.next iter)] + (if (.-done v) + nil + (ES6IteratorSeq. (.-value v) iter nil)))) + +;;;;;;;;;;;;;;;;;;; Murmur3 Helpers ;;;;;;;;;;;;;;;; + +(defn ^number mix-collection-hash + "Mix final collection hash for ordered or unordered collections. + hash-basis is the combined collection hash, count is the number + of elements included in the basis. Note this is the hash code + consistent with =, different from .hashCode. + See http://clojure.org/data_structures#hash for full algorithms." + [hash-basis count] + (let [h1 m3-seed + k1 (m3-mix-K1 hash-basis) + h1 (m3-mix-H1 h1 k1)] + (m3-fmix h1 count))) + +(defn ^number hash-ordered-coll + "Returns the hash code, consistent with =, for an external ordered + collection implementing Iterable. + See http://clojure.org/data_structures#hash for full algorithms." + [coll] + (loop [n 0 hash-code 1 coll (seq coll)] + (if-not (nil? coll) + (recur (inc n) (bit-or (+ (imul 31 hash-code) (hash (first coll))) 0) + (next coll)) + (mix-collection-hash hash-code n)))) + +(def ^:private empty-ordered-hash + (mix-collection-hash 1 0)) + +(defn ^number hash-unordered-coll + "Returns the hash code, consistent with =, for an external unordered + collection implementing Iterable. For maps, the iterator should + return map entries whose hash is computed as + (hash-ordered-coll [k v]). + See http://clojure.org/data_structures#hash for full algorithms." + [coll] + (loop [n 0 hash-code 0 coll (seq coll)] + (if-not (nil? coll) + (recur (inc n) (bit-or (+ hash-code (hash (first coll))) 0) (next coll)) + (mix-collection-hash hash-code n)))) + +(def ^:private empty-unordered-hash + (mix-collection-hash 0 0)) + +;;;;;;;;;;;;;;;;;;; protocols on primitives ;;;;;;;; +(declare hash-map list equiv-sequential) + +(extend-type nil + ICounted + (-count [_] 0)) + +;; TODO: we should remove this and handle date equality checking +;; by some other means, probably by adding a new primitive type +;; case to the hash table lookup - David + +(extend-type js/Date + IEquiv + (-equiv [o other] + (and (instance? js/Date other) + (== (.valueOf o) (.valueOf other)))) + + IComparable + (-compare [this other] + (if (instance? js/Date other) + (garray/defaultCompare (.valueOf this) (.valueOf other)) + (throw (js/Error. (str_ "Cannot compare " this " to " other)))))) + +(defprotocol Inst + (inst-ms* [inst])) + +(extend-protocol Inst + js/Date + (inst-ms* [inst] (.getTime inst))) + +(defn inst-ms + "Return the number of milliseconds since January 1, 1970, 00:00:00 GMT" + [inst] + (inst-ms* inst)) + +(defn inst? + "Return true if x satisfies Inst" + [x] + (satisfies? Inst x)) + +(extend-type number + IEquiv + (-equiv [x o] (identical? x o))) + +(declare with-meta) + +(extend-type function + Fn + IMeta + (-meta [_] nil)) + +(defn- root-obj + [] + (->> js/Function + (.getPrototypeOf js/Object) + (.getPrototypeOf js/Object))) + +(extend-type default + IHash + (-hash [o] + (if (identical? o (root-obj)) + 0 + (goog/getUid o)))) + +(extend-type symbol + IHash + (-hash [o] + (hash (.toString o)))) + +;;this is primitive because & emits call to array-seq +(defn inc + "Returns a number one greater than num." + [x] (cljs.core/+ x 1)) + +(declare deref) + +(deftype Reduced [val] + IDeref + (-deref [o] val)) + +(defn reduced + "Wraps x in a way such that a reduce will terminate with the value x" + [x] + (Reduced. x)) + +(defn reduced? + "Returns true if x is the result of a call to reduced" + [r] + (instance? Reduced r)) + +(defn ensure-reduced + "If x is already reduced?, returns it, else returns (reduced x)" + [x] + (if (reduced? x) x (reduced x))) + +(defn unreduced + "If x is reduced?, returns (deref x), else returns x" + [x] + (if (reduced? x) (deref x) x)) + +;; generic to all refs +;; (but currently hard-coded to atom!) +(defn deref + "Also reader macro: @var/@atom/@delay. Returns the + most-recently-committed value of ref. When applied to a var + or atom, returns its current state. When applied to a delay, forces + it if not already forced. See also - realized?." + [o] + (-deref o)) + +(defn- ci-reduce + "Accepts any collection which satisfies the ICount and IIndexed protocols and +reduces them without incurring seq initialization" + ([^not-native cicoll f] + (let [cnt (-count cicoll)] + (if (zero? cnt) + (f) + (loop [val (-nth cicoll 0), n 1] + (if (< n cnt) + (let [nval (f val (-nth cicoll n))] + (if (reduced? nval) + @nval + (recur nval (inc n)))) + val))))) + ([^not-native cicoll f val] + (let [cnt (-count cicoll)] + (loop [val val, n 0] + (if (< n cnt) + (let [nval (f val (-nth cicoll n))] + (if (reduced? nval) + @nval + (recur nval (inc n)))) + val))))) + +(defn- array-reduce + ([arr f] + (let [cnt (alength arr)] + (if (zero? (alength arr)) + (f) + (loop [val (aget arr 0), n 1] + (if (< n cnt) + (let [nval (f val (aget arr n))] + (if (reduced? nval) + @nval + (recur nval (inc n)))) + val))))) + ([arr f val] + (let [cnt (alength arr)] + (loop [val val, n 0] + (if (< n cnt) + (let [nval (f val (aget arr n))] + (if (reduced? nval) + @nval + (recur nval (inc n)))) + val)))) + ([arr f val idx] + (let [cnt (alength arr)] + (loop [val val, n idx] + (if (< n cnt) + (let [nval (f val (aget arr n))] + (if (reduced? nval) + @nval + (recur nval (inc n)))) + val))))) + +(declare hash-coll cons drop count nth RSeq List) + +(defn counted? + "Returns true if coll implements count in constant time" + [x] (satisfies? ICounted x)) + +(defn indexed? + "Returns true if coll implements nth in constant time" + [x] (satisfies? IIndexed x)) + +(defn- -indexOf + ([coll x] + (-indexOf coll x 0)) + ([coll x start] + (let [len (count coll)] + (if (>= start len) + -1 + (loop [idx (cond + (pos? start) start + (neg? start) (unchecked-max 0 (+ start len)) + :else start)] + (if (< idx len) + (if (= (nth coll idx) x) + idx + (recur (inc idx))) + -1)))))) + +(defn- -lastIndexOf + ([coll x] + (-lastIndexOf coll x (count coll))) + ([coll x start] + (let [len (count coll)] + (if (zero? len) + -1 + (loop [idx (cond + (pos? start) (unchecked-min (dec len) start) + (neg? start) (+ len start) + :else start)] + (if (>= idx 0) + (if (= (nth coll idx) x) + idx + (recur (dec idx))) + -1)))))) + +(deftype IndexedSeqIterator [arr ^:mutable i] + Object + (hasNext [_] + (< i (alength arr))) + (next [_] + (let [ret (aget arr i)] + (set! i (inc i)) + ret))) + +(deftype IndexedSeq [arr i meta] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + ICloneable + (-clone [_] (IndexedSeq. arr i meta)) + + ISeqable + (-seq [this] + (when (< i (alength arr)) + this)) + + IMeta + (-meta [coll] meta) + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (IndexedSeq. arr i new-meta))) + + ASeq + ISeq + (-first [_] (aget arr i)) + (-rest [_] (if (< (inc i) (alength arr)) + (IndexedSeq. arr (inc i) nil) + ())) + + INext + (-next [_] (if (< (inc i) (alength arr)) + (IndexedSeq. arr (inc i) nil) + nil)) + + IDrop + (-drop [coll n] + (if (pos? n) + (if (< (+ i n) (alength arr)) + (IndexedSeq. arr (+ i n) nil) + nil) + coll)) + + ICounted + (-count [_] + (unchecked-max 0 (- (alength arr) i))) + + IIndexed + (-nth [coll n] + (let [i (+ n i)] + (if (and (<= 0 i) (< i (alength arr))) + (aget arr i) + (throw (js/Error. "Index out of bounds"))))) + (-nth [coll n not-found] + (let [i (+ n i)] + (if (and (<= 0 i) (< i (alength arr))) + (aget arr i) + not-found))) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IIterable + (-iterator [coll] + (IndexedSeqIterator. arr i)) + + ICollection + (-conj [coll o] (cons o coll)) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + IReduce + (-reduce [coll f] + (array-reduce arr f (aget arr i) (inc i))) + (-reduce [coll f start] + (array-reduce arr f start i)) + + IHash + (-hash [coll] (hash-ordered-coll coll)) + + IReversible + (-rseq [coll] + (let [c (-count coll)] + (if (pos? c) + (RSeq. coll (dec c) nil))))) + +(es6-iterable IndexedSeq) + +(defn prim-seq + "Create seq from a primitive JavaScript Array-like." + ([prim] + (prim-seq prim 0)) + ([prim i] + (when (< i (alength prim)) + (IndexedSeq. prim i nil)))) + +(defn array-seq + "Create a seq from a JavaScript array." + ([array] + (prim-seq array 0)) + ([array i] + (prim-seq array i))) + +(declare with-meta seq-reduce) + +(deftype RSeq [ci i meta] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (-count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + ICloneable + (-clone [_] (RSeq. ci i meta)) + + IMeta + (-meta [coll] meta) + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (RSeq. ci i new-meta))) + + ISeqable + (-seq [coll] coll) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + ISeq + (-first [coll] + (-nth ci i)) + (-rest [coll] + (if (pos? i) + (RSeq. ci (dec i) nil) + ())) + + INext + (-next [coll] + (when (pos? i) + (RSeq. ci (dec i) nil))) + + ICounted + (-count [coll] (inc i)) + + ICollection + (-conj [coll o] + (cons o coll)) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + IHash + (-hash [coll] (hash-ordered-coll coll)) + + IReduce + (-reduce [col f] (seq-reduce f col)) + (-reduce [col f start] (seq-reduce f start col))) + +(es6-iterable RSeq) + +(defn second + "Same as (first (next x))" + [coll] + (first (next coll))) + +(defn ffirst + "Same as (first (first x))" + [coll] + (first (first coll))) + +(defn nfirst + "Same as (next (first x))" + [coll] + (next (first coll))) + +(defn fnext + "Same as (first (next x))" + [coll] + (first (next coll))) + +(defn nnext + "Same as (next (next x))" + [coll] + (next (next coll))) + +(defn last + "Return the last item in coll, in linear time" + [s] + (let [sn (next s)] + (if-not (nil? sn) + (recur sn) + (first s)))) + +(extend-type default + IEquiv + (-equiv [x o] (identical? x o))) + +(defn conj + "conj[oin]. Returns a new collection with the xs + 'added'. (conj nil item) returns (item). + (conj coll) returns coll. (conj) returns []. + The 'addition' may happen at different 'places' depending + on the concrete type." + ([] []) + ([coll] coll) + ([coll x] + (if-not (nil? coll) + (-conj coll x) + (list x))) + ([coll x & xs] + (if xs + (recur (conj coll x) (first xs) (next xs)) + (conj coll x)))) + +(defn empty + "Returns an empty collection of the same category as coll, or nil" + [coll] + (when-not (nil? coll) + (cond + (implements? IEmptyableCollection coll) + (-empty coll) + + (satisfies? IEmptyableCollection coll) + (-empty coll) + + :else nil))) + +(defn- accumulating-seq-count [coll] + (loop [s (seq coll) acc 0] + (if (counted? s) ; assumes nil is counted, which it currently is + (+ acc (-count s)) + (recur (next s) (inc acc))))) + +(defn count + "Returns the number of items in the collection. (count nil) returns + 0. Also works on strings, arrays, and Maps" + [coll] + (if-not (nil? coll) + (cond + (implements? ICounted coll) + (-count coll) + + (array? coll) + (alength coll) + + (string? coll) + ^number (.-length coll) + + (implements? ISeqable coll) + (accumulating-seq-count coll) + + :else (-count coll)) + 0)) + +(defn- linear-traversal-nth + ([coll n] + (cond + (nil? coll) (throw (js/Error. "Index out of bounds")) + (zero? n) (if (seq coll) + (first coll) + (throw (js/Error. "Index out of bounds"))) + (indexed? coll) (-nth coll n) + (seq coll) (recur (next coll) (dec n)) + :else (throw (js/Error. "Index out of bounds")))) + ([coll n not-found] + (cond + (nil? coll) not-found + (zero? n) (if (seq coll) + (first coll) + not-found) + (indexed? coll) (-nth coll n not-found) + (seq coll) (recur (next coll) (dec n) not-found) + :else not-found))) + +(defn nth + "Returns the value at the index. get returns nil if index out of + bounds, nth throws an exception unless not-found is supplied. nth + also works for strings, arrays, regex Matchers and Lists, and, + in O(n) time, for sequences." + ([coll n] + (cond + (not (number? n)) + (throw (js/Error. "Index argument to nth must be a number")) + + (nil? coll) + coll + + (implements? IIndexed coll) + (-nth coll n) + + (array? coll) + (if (and (< -1 n (.-length coll))) + (aget coll (int n)) + (throw (js/Error. "Index out of bounds"))) + + (string? coll) + (if (and (< -1 n (.-length coll))) + (.charAt coll (int n)) + (throw (js/Error. "Index out of bounds"))) + + (or (implements? ISeq coll) + (implements? ISequential coll)) + (if (neg? n) + (throw (js/Error. "Index out of bounds")) + (linear-traversal-nth coll n)) + + (native-satisfies? IIndexed coll) + (-nth coll n) + + :else + (throw (js/Error. (str_ "nth not supported on this type " + (type->str (type coll))))))) + ([coll n not-found] + (cond + (not (number? n)) + (throw (js/Error. "Index argument to nth must be a number.")) + + (nil? coll) + not-found + + (implements? IIndexed coll) + (-nth coll n not-found) + + (array? coll) + (if (and (< -1 n (.-length coll))) + (aget coll (int n)) + not-found) + + (string? coll) + (if (and (< -1 n (.-length coll))) + (.charAt coll (int n)) + not-found) + + (or (implements? ISeq coll) + (implements? ISequential coll)) + (if (neg? n) + not-found + (linear-traversal-nth coll n not-found)) + + (native-satisfies? IIndexed coll) + (-nth coll n not-found) + + :else + (throw (js/Error. (str_ "nth not supported on this type " + (type->str (type coll)))))))) + +(defn nthrest + "Returns the nth rest of coll, coll when n is 0." + [coll n] + (if (implements? IDrop coll) + (if (pos? n) + (or (-drop coll (Math/ceil n)) ()) + coll) + (loop [n n xs coll] + (if-let [xs (and (pos? n) (seq xs))] + (recur (dec n) (rest xs)) + xs)))) + +(defn get + "Returns the value mapped to key, not-found or nil if key not present + in associative collection, set, string, array, or ILookup instance." + ([o k] + (when-not (nil? o) + (cond + (implements? ILookup o) + (-lookup o k) + + (array? o) + (when (and (some? k) (< k (.-length o))) + (aget o (int k))) + + (string? o) + (when (and (some? k) (< -1 k (.-length o))) + (.charAt o (int k))) + + (native-satisfies? ILookup o) + (-lookup o k) + + :else nil))) + ([o k not-found] + (if-not (nil? o) + (cond + (implements? ILookup o) + (-lookup o k not-found) + + (array? o) + (if (and (some? k) (< -1 k (.-length o))) + (aget o (int k)) + not-found) + + (string? o) + (if (and (some? k) (< -1 k (.-length o))) + (.charAt o (int k)) + not-found) + + (native-satisfies? ILookup o) + (-lookup o k not-found) + + :else not-found) + not-found))) + +(declare PersistentHashMap PersistentArrayMap MapEntry) + +(defn assoc + "assoc[iate]. When applied to a map, returns a new map of the + same (hashed/sorted) type, that contains the mapping of key(s) to + val(s). When applied to a vector, returns a new vector that + contains val at index. Note - index must be <= (count vector)." + ([coll k v] + (if (implements? IAssociative coll) + (-assoc coll k v) + (if-not (nil? coll) + (-assoc coll k v) + {k v}))) + ([coll k v & kvs] + (let [ret (assoc coll k v)] + (if kvs + (recur ret (first kvs) (second kvs) (nnext kvs)) + ret)))) + +(defn dissoc + "dissoc[iate]. Returns a new map of the same (hashed/sorted) type, + that does not contain a mapping for key(s)." + ([coll] coll) + ([coll k] + (when-not (nil? coll) + (-dissoc coll k))) + ([coll k & ks] + (when-not (nil? coll) + (let [ret (dissoc coll k)] + (if ks + (recur ret (first ks) (next ks)) + ret))))) + +(defn fn? + "Return true if f is a JavaScript function or satisfies the Fn protocol." + [f] + (or (js-fn? f) (satisfies? Fn f))) + +(deftype MetaFn [afn meta] + IMeta + (-meta [_] meta) + IWithMeta + (-with-meta [_ new-meta] + (MetaFn. afn new-meta)) + Fn + IFn + (-invoke [_] + (afn)) + (-invoke [_ a] + (afn a)) + (-invoke [_ a b] + (afn a b)) + (-invoke [_ a b c] + (afn a b c)) + (-invoke [_ a b c d] + (afn a b c d)) + (-invoke [_ a b c d e] + (afn a b c d e)) + (-invoke [_ a b c d e f] + (afn a b c d e f)) + (-invoke [_ a b c d e f g] + (afn a b c d e f g)) + (-invoke [_ a b c d e f g h] + (afn a b c d e f g h)) + (-invoke [_ a b c d e f g h i] + (afn a b c d e f g h i)) + (-invoke [_ a b c d e f g h i j] + (afn a b c d e f g h i j)) + (-invoke [_ a b c d e f g h i j k] + (afn a b c d e f g h i j k)) + (-invoke [_ a b c d e f g h i j k l] + (afn a b c d e f g h i j k l)) + (-invoke [_ a b c d e f g h i j k l m] + (afn a b c d e f g h i j k l m)) + (-invoke [_ a b c d e f g h i j k l m n] + (afn a b c d e f g h i j k l m n)) + (-invoke [_ a b c d e f g h i j k l m n o] + (afn a b c d e f g h i j k l m n o)) + (-invoke [_ a b c d e f g h i j k l m n o p] + (afn a b c d e f g h i j k l m n o p)) + (-invoke [_ a b c d e f g h i j k l m n o p q] + (afn a b c d e f g h i j k l m n o p q)) + (-invoke [_ a b c d e f g h i j k l m n o p q r] + (afn a b c d e f g h i j k l m n o p q r)) + (-invoke [_ a b c d e f g h i j k l m n o p q r s] + (afn a b c d e f g h i j k l m n o p q r s)) + (-invoke [_ a b c d e f g h i j k l m n o p q r s t] + (afn a b c d e f g h i j k l m n o p q r s t)) + (-invoke [_ a b c d e f g h i j k l m n o p q r s t rest] + (apply afn a b c d e f g h i j k l m n o p q r s t rest))) + +(defn with-meta + "Returns an object of the same type and value as obj, with + map m as its metadata." + [o meta] + (if (js-fn? o) + (MetaFn. o meta) + (when-not (nil? o) + (-with-meta o meta)))) + +(defn meta + "Returns the metadata of obj, returns nil if there is no metadata." + [o] + (when (and (not (nil? o)) + (satisfies? IMeta o)) + (-meta o))) + +(defn peek + "For a list or queue, same as first, for a vector, same as, but much + more efficient than, last. If the collection is empty, returns nil." + [coll] + (when-not (nil? coll) + (-peek coll))) + +(defn pop + "For a list or queue, returns a new list/queue without the first + item, for a vector, returns a new vector without the last item. + Note - not the same as next/butlast." + [coll] + (when-not (nil? coll) + (-pop coll))) + +(defn disj + "disj[oin]. Returns a new set of the same (hashed/sorted) type, that + does not contain key(s)." + ([coll] coll) + ([coll k] + (when-not (nil? coll) + (-disjoin coll k))) + ([coll k & ks] + (when-not (nil? coll) + (let [ret (disj coll k)] + (if ks + (recur ret (first ks) (next ks)) + ret))))) + +(defn empty? + "Returns true if coll has no items. To check the emptiness of a seq, + please use the idiom (seq x) rather than (not (empty? x))" + [coll] + (cond + (nil? coll) + true + + (satisfies? ICounted coll) + (zero? (-count coll)) + + :else + (not (seq coll)))) + +(defn coll? + "Returns true if x satisfies ICollection" + [x] + (if (nil? x) + false + (satisfies? ICollection x))) + +(defn set? + "Returns true if x satisfies ISet" + [x] + (if (nil? x) + false + (satisfies? ISet x))) + +(defn associative? + "Returns true if coll implements IAssociative" + [x] (satisfies? IAssociative x)) + +(defn ifind? + "Returns true if coll implements IFind" + [x] (satisfies? IFind x)) + +(defn sequential? + "Returns true if coll satisfies ISequential" + [x] (satisfies? ISequential x)) + +(defn sorted? + "Returns true if coll satisfies ISorted" + [x] (satisfies? ISorted x)) + +(defn reduceable? + "Returns true if coll satisfies IReduce" + [x] (satisfies? IReduce x)) + +(defn map? + "Return true if x satisfies IMap" + [x] + (if (nil? x) + false + (satisfies? IMap x))) + +(defn record? + "Return true if x satisfies IRecord" + [x] + (satisfies? IRecord x)) + +(defn vector? + "Return true if x satisfies IVector" + [x] (satisfies? IVector x)) + +(declare ChunkedCons ChunkedSeq) + +(defn chunked-seq? + "Return true if x satisfies IChunkedSeq." + [x] + (if-not ^boolean LITE_MODE + (implements? IChunkedSeq x) + false)) + +;;;;;;;;;;;;;;;;;;;; js primitives ;;;;;;;;;;;; +(defn js-obj + "Create JavaSript object from an even number arguments representing + interleaved keys and values." + ([] + (cljs.core/js-obj)) + ([& keyvals] + (apply gobject/create keyvals))) + +(defn js-keys + "Return the JavaScript keys for an object." + [obj] + (gobject/getKeys obj)) + +(defn js-delete + "Delete a property from a JavaScript object. + Returns true upon success, false otherwise." + [obj key] + (cljs.core/js-delete obj key)) + +(defn- array-copy + ([from i to j len] + (loop [i i j j len len] + (if (zero? len) + to + (do (aset to j (aget from i)) + (recur (inc i) (inc j) (dec len))))))) + +(defn- array-copy-downward + ([from i to j len] + (loop [i (+ i (dec len)) j (+ j (dec len)) len len] + (if (zero? len) + to + (do (aset to j (aget from i)) + (recur (dec i) (dec j) (dec len))))))) + +;;;;;;;;;;;;;;;; preds ;;;;;;;;;;;;;;;;;; + +(def ^:private lookup-sentinel (js-obj)) + +(defn ^boolean false? + "Returns true if x is the value false, false otherwise." + [x] (cljs.core/false? x)) + +(defn ^boolean true? + "Returns true if x is the value true, false otherwise." + [x] (cljs.core/true? x)) + +(defn boolean? + "Return true if x is a Boolean" + [x] (or (cljs.core/true? x) (cljs.core/false? x))) + +(defn ^boolean undefined? + "Returns true if x identical to the JavaScript undefined value." + [x] + (cljs.core/undefined? x)) + +(defn seq? + "Return true if s satisfies ISeq" + [s] + (if (nil? s) + false + (satisfies? ISeq s))) + +(defn seqable? + "Return true if the seq function is supported for s" + [s] + (or + (nil? s) + (satisfies? ISeqable s) + (js-iterable? s) + (array? s) + (string? s))) + +(defn boolean + "Coerce to boolean" + [x] + (cond + (nil? x) false + (false? x) false + :else true)) + +(defn ifn? + "Returns true if f returns true for fn? or satisfies IFn." + [f] + (or (fn? f) (satisfies? IFn f))) + +(defn integer? + "Returns true if n is a JavaScript number with no decimal part." + [n] + (and (number? n) + (not (js/isNaN n)) + (not (identical? n js/Infinity)) + (== (js/parseFloat n) (js/parseInt n 10)))) + +(def + ^{:doc "INTERNAL: do not use"} + LongImpl goog.math.Long) + +(defn int? + "Return true if x satisfies integer? or is an instance of goog.math.Integer + or goog.math.Long." + [x] + (or (integer? x) + (instance? goog.math.Integer x) + (instance? goog.math.Long x))) + +(defn pos-int? + "Return true if x satisfies int? and is positive." + [x] + (cond + (integer? x) (pos? x) + + (instance? goog.math.Integer x) + (and (not (.isNegative x)) + (not (.isZero x))) + + (instance? goog.math.Long x) + (and (not (.isNegative x)) + (not (.isZero x))) + + :else false)) + +(defn ^boolean neg-int? + "Return true if x satisfies int? and is negative." + [x] + (cond + (integer? x) (neg? x) + + (instance? goog.math.Integer x) + (.isNegative x) + + (instance? goog.math.Long x) + (.isNegative x) + + :else false)) + +(defn nat-int? + "Return true if x satisfies int? and is a natural integer value." + [x] + (cond + (integer? x) + (not (neg? x)) + + (instance? goog.math.Integer x) + (not (.isNegative x)) + + (instance? goog.math.Long x) + (not (.isNegative x)) + + :else false)) + +(defn float? + "Returns true for JavaScript numbers, false otherwise." + [x] + (number? x)) + +(defn double? + "Returns true for JavaScript numbers, false otherwise." + [x] + (number? x)) + +(defn infinite? + "Returns true for Infinity and -Infinity values." + [x] + (or (identical? x js/Number.POSITIVE_INFINITY) + (identical? x js/Number.NEGATIVE_INFINITY))) + +(defn ^boolean contains? + "Returns true if key is present in the given collection, otherwise + returns false. Note that for numerically indexed collections like + vectors and arrays, this tests if the numeric key is within the + range of indexes. 'contains?' operates constant or logarithmic time; + it will not perform a linear search for a value. See also 'some'." + [coll v] + (cond + (implements? IAssociative coll) + (-contains-key? coll v) + + (native-satisfies? IAssociative coll) + (-contains-key? coll v) + + (identical? (get coll v lookup-sentinel) lookup-sentinel) + false + + :else + true)) + +(defn find + "Returns the map entry for key, or nil if key not present." + [coll k] + (if (ifind? coll) + (-find coll k) + (when (and (not (nil? coll)) + (associative? coll) + (contains? coll k)) + (MapEntry. k (get coll k) nil)))) + +(defn distinct? + "Returns true if no two of the arguments are =" + ([x] true) + ([x y] (not (= x y))) + ([x y & more] + (if (not (= x y)) + (loop [s #{x y} xs more] + (let [x (first xs) + etc (next xs)] + (if xs + (if (contains? s x) + false + (recur (conj s x) etc)) + true))) + false))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Seq fns ;;;;;;;;;;;;;;;; + +(defn ^number compare + "Comparator. Returns a negative number, zero, or a positive number + when x is logically 'less than', 'equal to', or 'greater than' + y. Uses IComparable if available and google.array.defaultCompare for objects + of the same type and special-cases nil to be less than any other object." + [x y] + (cond + (identical? x y) 0 + + (nil? x) -1 + + (nil? y) 1 + + (number? x) (if (number? y) + (garray/defaultCompare x y) + (throw (js/Error. (str_ "Cannot compare " x " to " y)))) + + (satisfies? IComparable x) + (-compare x y) + + :else + (if (and (or (string? x) (array? x) (true? x) (false? x)) + (identical? (type x) (type y))) + (garray/defaultCompare x y) + (throw (js/Error. (str_ "Cannot compare " x " to " y)))))) + +(defn ^:private compare-indexed + "Compare indexed collection." + ([xs ys] + (let [xl (count xs) + yl (count ys)] + (cond + (< xl yl) -1 + (> xl yl) 1 + (== xl 0) 0 + :else (compare-indexed xs ys xl 0)))) + ([xs ys len n] + (let [d (compare (nth xs n) (nth ys n))] + (if (and (zero? d) (< (+ n 1) len)) + (recur xs ys len (inc n)) + d)))) + +(defn ^:private fn->comparator + "Given a fn that might be boolean valued or a comparator, + return a fn that is a comparator." + [f] + (if (= f compare) + compare + (fn [x y] + (let [r (f x y)] + (if (number? r) + r + (if r + -1 + (if (f y x) 1 0))))))) + +(declare to-array) + +(defn sort + "Returns a sorted sequence of the items in coll. Comp can be + boolean-valued comparison function, or a -/0/+ valued comparator. + Comp defaults to compare." + ([coll] + (sort compare coll)) + ([comp coll] + (if (seq coll) + (let [a (to-array coll)] + ;; matching Clojure's stable sort, though docs don't promise it + (garray/stableSort a (fn->comparator comp)) + (with-meta (seq a) (meta coll))) + ()))) + +(defn sort-by + "Returns a sorted sequence of the items in coll, where the sort + order is determined by comparing (keyfn item). Comp can be + boolean-valued comparison function, or a -/0/+ valued comparator. + Comp defaults to compare." + ([keyfn coll] + (sort-by keyfn compare coll)) + ([keyfn comp coll] + (sort (fn [x y] ((fn->comparator comp) (keyfn x) (keyfn y))) coll))) + +; simple reduce based on seqs, used as default +(defn- seq-reduce + ([f coll] + (if-let [s (seq coll)] + (reduce f (first s) (next s)) + (f))) + ([f val coll] + (loop [val val, coll (seq coll)] + (if coll + (let [nval (f val (first coll))] + (if (reduced? nval) + @nval + (recur nval (next coll)))) + val)))) + +(declare vec) + +(defn shuffle + "Return a random permutation of coll" + [coll] + (let [a (to-array coll)] + (garray/shuffle a) + (vec a))) + +(defn- iter-reduce + ([coll f] + (let [iter (-iterator coll)] + (if (.hasNext iter) + (let [init (.next iter)] + (loop [acc init] + (if ^boolean (.hasNext iter) + (let [nacc (f acc (.next iter))] + (if (reduced? nacc) + @nacc + (recur nacc))) + acc))) + (f)))) + ([coll f init] + (let [iter (-iterator coll)] + (loop [acc init] + (if ^boolean (.hasNext iter) + (let [nacc (f acc (.next iter))] + (if (reduced? nacc) + @nacc + (recur nacc))) + acc))))) + +(defn reduce + "f should be a function of 2 arguments. If val is not supplied, + returns the result of applying f to the first 2 items in coll, then + applying f to that result and the 3rd item, etc. If coll contains no + items, f must accept no arguments as well, and reduce returns the + result of calling f with no arguments. If coll has only 1 item, it + is returned and f is not called. If val is supplied, returns the + result of applying f to val and the first item in coll, then + applying f to that result and the 2nd item, etc. If coll contains no + items, returns val and f is not called." + ([f coll] + (cond + (implements? IReduce coll) + (-reduce coll f) + + (array? coll) + (array-reduce coll f) + + (string? coll) + (array-reduce coll f) + + (native-satisfies? IReduce coll) + (-reduce coll f) + + (iterable? coll) + (iter-reduce coll f) + + :else + (seq-reduce f coll))) + ([f val coll] + (cond + (implements? IReduce coll) + (-reduce coll f val) + + (array? coll) + (array-reduce coll f val) + + (string? coll) + (array-reduce coll f val) + + (native-satisfies? IReduce coll) + (-reduce coll f val) + + (iterable? coll) + (iter-reduce coll f val) + + :else + (seq-reduce f val coll)))) + +(defn reduce-kv + "Reduces an associative collection. f should be a function of 3 + arguments. Returns the result of applying f to init, the first key + and the first value in coll, then applying f to that result and the + 2nd key and value, etc. If coll contains no entries, returns init + and f is not called. Note that reduce-kv is supported on vectors, + where the keys will be the ordinals." + ([f init coll] + (if (satisfies? IKVReduce coll) + (-kv-reduce coll f init) + (reduce (fn [ret me] + (f ret (-key me) (-val me))) + init coll)))) + +(defn identity + "Returns its argument." + [x] x) + +(defn completing + "Takes a reducing function f of 2 args and returns a fn suitable for + transduce by adding an arity-1 signature that calls cf (default - + identity) on the result argument." + ([f] (completing f identity)) + ([f cf] + (fn + ([] (f)) + ([x] (cf x)) + ([x y] (f x y))))) + +(defn transduce + "reduce with a transformation of f (xf). If init is not + supplied, (f) will be called to produce it. f should be a reducing + step function that accepts both 1 and 2 arguments, if it accepts + only 2 you can add the arity-1 with 'completing'. Returns the result + of applying (the transformed) xf to init and the first item in coll, + then applying xf to that result and the 2nd item, etc. If coll + contains no items, returns init and f is not called. Note that + certain transforms may inject or skip items." + ([xform f coll] (transduce xform f (f) coll)) + ([xform f init coll] + (let [f (xform f) + ret (reduce f init coll)] + (f ret)))) + +;;; Math - variadic forms will not work until the following implemented: +;;; first, next, reduce + +(defn ^number + + "Returns the sum of nums. (+) returns 0." + ([] 0) + ([x] x) + ([x y] (cljs.core/+ x y)) + ([x y & more] + (reduce + (cljs.core/+ x y) more))) + +(defn ^number - + "If no ys are supplied, returns the negation of x, else subtracts + the ys from x and returns the result." + ([x] (cljs.core/- x)) + ([x y] (cljs.core/- x y)) + ([x y & more] (reduce - (cljs.core/- x y) more))) + +(defn ^number * + "Returns the product of nums. (*) returns 1." + ([] 1) + ([x] x) + ([x y] (cljs.core/* x y)) + ([x y & more] (reduce * (cljs.core/* x y) more))) + +(declare divide) + +(defn ^number / + "If no denominators are supplied, returns 1/numerator, + else returns numerator divided by all of the denominators." + ([x] (/ 1 x)) + ([x y] (cljs.core/divide x y)) ;; FIXME: waiting on cljs.core// + ([x y & more] (reduce / (/ x y) more))) + +(defn ^boolean < + "Returns non-nil if nums are in monotonically increasing order, + otherwise false." + ([x] true) + ([x y] (cljs.core/< x y)) + ([x y & more] + (if (cljs.core/< x y) + (if (next more) + (recur y (first more) (next more)) + (cljs.core/< y (first more))) + false))) + +(defn ^boolean <= + "Returns non-nil if nums are in monotonically non-decreasing order, + otherwise false." + ([x] true) + ([x y] (cljs.core/<= x y)) + ([x y & more] + (if (cljs.core/<= x y) + (if (next more) + (recur y (first more) (next more)) + (cljs.core/<= y (first more))) + false))) + +(defn ^boolean > + "Returns non-nil if nums are in monotonically decreasing order, + otherwise false." + ([x] true) + ([x y] (cljs.core/> x y)) + ([x y & more] + (if (cljs.core/> x y) + (if (next more) + (recur y (first more) (next more)) + (cljs.core/> y (first more))) + false))) + +(defn ^boolean >= + "Returns non-nil if nums are in monotonically non-increasing order, + otherwise false." + ([x] true) + ([x y] (cljs.core/>= x y)) + ([x y & more] + (if (cljs.core/>= x y) + (if (next more) + (recur y (first more) (next more)) + (cljs.core/>= y (first more))) + false))) + +(defn dec + "Returns a number one less than num." + [x] (- x 1)) + +(defn ^number abs + {:doc "Returns the absolute value of a." + :added "1.11.10"} + [a] (Math/abs a)) + +(defn NaN? + "Returns true if num is NaN, else false" + [val] + (js/isNaN val)) + +(defn ^number max + "Returns the greatest of the nums." + ([x] x) + ([x y] + (cond + (NaN? x) x + (NaN? y) y + (> x y) x + :else y)) + ([x y & more] + (reduce max (cljs.core/max x y) more))) + +(defn ^number min + "Returns the least of the nums." + ([x] x) + ([x y] + (cond + (NaN? x) x + (NaN? y) y + (< x y) x + :else y)) + ([x y & more] + (reduce min (cljs.core/min x y) more))) + +(defn ^number byte [x] x) + +(defn char + "Coerce to char" + [x] + (cond + (number? x) (.fromCharCode js/String x) + (and (string? x) (== (.-length x) 1)) x + :else (throw (js/Error. "Argument to char must be a character or number")))) + +(defn ^number short [x] x) +(defn ^number float [x] x) +(defn ^number double [x] x) + +(defn ^number unchecked-byte [x] x) +(defn ^number unchecked-char [x] x) +(defn ^number unchecked-short [x] x) +(defn ^number unchecked-float [x] x) +(defn ^number unchecked-double [x] x) + +(defn ^number unchecked-add + "Returns the sum of nums. (+) returns 0." + ([] 0) + ([x] x) + ([x y] (cljs.core/unchecked-add x y)) + ([x y & more] (reduce unchecked-add (cljs.core/unchecked-add x y) more))) + +(defn ^number unchecked-add-int + "Returns the sum of nums. (+) returns 0." + ([] 0) + ([x] x) + ([x y] (cljs.core/unchecked-add-int x y)) + ([x y & more] (reduce unchecked-add-int (cljs.core/unchecked-add-int x y) more))) + +(defn unchecked-dec + "Returns a number one less than x, an int." + [x] + (cljs.core/unchecked-dec x)) + +(defn unchecked-dec-int + "Returns a number one less than x, an int." + [x] + (cljs.core/unchecked-dec-int x)) + +(defn ^number unchecked-divide-int + "If no denominators are supplied, returns 1/numerator, + else returns numerator divided by all of the denominators." + ([x] (unchecked-divide-int 1 x)) + ([x y] (cljs.core/divide x y)) ;; FIXME: waiting on cljs.core// + ([x y & more] (reduce unchecked-divide-int (unchecked-divide-int x y) more))) + +(defn unchecked-inc [x] + (cljs.core/unchecked-inc x)) + +(defn unchecked-inc-int [x] + (cljs.core/unchecked-inc-int x)) + +(defn ^number unchecked-multiply + "Returns the product of nums. (*) returns 1." + ([] 1) + ([x] x) + ([x y] (cljs.core/unchecked-multiply x y)) + ([x y & more] (reduce unchecked-multiply (cljs.core/unchecked-multiply x y) more))) + +(defn ^number unchecked-multiply-int + "Returns the product of nums. (*) returns 1." + ([] 1) + ([x] x) + ([x y] (cljs.core/unchecked-multiply-int x y)) + ([x y & more] (reduce unchecked-multiply-int (cljs.core/unchecked-multiply-int x y) more))) + +(defn unchecked-negate [x] + (cljs.core/unchecked-negate x)) + +(defn unchecked-negate-int [x] + (cljs.core/unchecked-negate-int x)) + +(declare mod) + +(defn unchecked-remainder-int [x n] + (cljs.core/unchecked-remainder-int x n)) + +(defn ^number unchecked-subtract + "If no ys are supplied, returns the negation of x, else subtracts + the ys from x and returns the result." + ([x] (cljs.core/unchecked-subtract x)) + ([x y] (cljs.core/unchecked-subtract x y)) + ([x y & more] (reduce unchecked-subtract (cljs.core/unchecked-subtract x y) more))) + +(defn ^number unchecked-subtract-int + "If no ys are supplied, returns the negation of x, else subtracts + the ys from x and returns the result." + ([x] (cljs.core/unchecked-subtract-int x)) + ([x y] (cljs.core/unchecked-subtract-int x y)) + ([x y & more] (reduce unchecked-subtract-int (cljs.core/unchecked-subtract-int x y) more))) + +(defn- ^number fix [q] + (if (>= q 0) + (Math/floor q) + (Math/ceil q))) + +(defn int + "Coerce to int." + [x] + (bit-or x 0)) + +(defn unchecked-int + "Coerce to int." + [x] + (fix x)) + +(defn long + "Coerce to long. Identical to `int'." + [x] + (fix x)) + +(defn unchecked-long + "Coerce to long. Identical to `int'." + [x] + (fix x)) + +(defn booleans [x] x) +(defn bytes [x] x) +(defn chars [x] x) +(defn shorts [x] x) +(defn ints [x] x) +(defn floats [x] x) +(defn doubles [x] x) +(defn longs [x] x) + +(defn js-mod + "Modulus of num and div with original javascript behavior. i.e. bug for negative numbers" + [n d] + (cljs.core/js-mod n d)) + +(defn mod + "Modulus of num and div. Truncates toward negative infinity." + [n d] + (js-mod (+ (js-mod n d) d) d)) + +(defn quot + "quot[ient] of dividing numerator by denominator." + [n d] + (let [rem (js-mod n d)] + (fix (/ (- n rem) d)))) + +(defn rem + "remainder of dividing numerator by denominator." + [n d] + (let [q (quot n d)] + (- n (* d q)))) + +(defn bit-xor + "Bitwise exclusive or" + ([x y] (cljs.core/bit-xor x y)) + ([x y & more] + (reduce bit-xor (cljs.core/bit-xor x y) more))) + +(defn bit-and + "Bitwise and" + ([x y] (cljs.core/bit-and x y)) + ([x y & more] + (reduce bit-and (cljs.core/bit-and x y) more))) + +(defn bit-or + "Bitwise or" + ([x y] (cljs.core/bit-or x y)) + ([x y & more] + (reduce bit-or (cljs.core/bit-or x y) more))) + +(defn bit-and-not + "Bitwise and with complement" + ([x y] (cljs.core/bit-and-not x y)) + ([x y & more] + (reduce bit-and-not (cljs.core/bit-and-not x y) more))) + +(defn bit-clear + "Clear bit at index n" + [x n] + (cljs.core/bit-clear x n)) + +(defn bit-flip + "Flip bit at index n" + [x n] + (cljs.core/bit-flip x n)) + +(defn bit-not + "Bitwise complement" + [x] (cljs.core/bit-not x)) + +(defn bit-set + "Set bit at index n" + [x n] + (cljs.core/bit-set x n)) + +(defn ^boolean bit-test + "Test bit at index n" + [x n] + (cljs.core/bit-test x n)) + +(defn bit-shift-left + "Bitwise shift left" + [x n] (cljs.core/bit-shift-left x n)) + +(defn bit-shift-right + "Bitwise shift right" + [x n] (cljs.core/bit-shift-right x n)) + +(defn bit-shift-right-zero-fill + "DEPRECATED: Bitwise shift right with zero fill" + [x n] (cljs.core/bit-shift-right-zero-fill x n)) + +(defn unsigned-bit-shift-right + "Bitwise shift right with zero fill" + [x n] (cljs.core/unsigned-bit-shift-right x n)) + +(defn bit-count + "Counts the number of bits set in n" + [v] + (let [v (- v (bit-and (bit-shift-right v 1) 0x55555555)) + v (+ (bit-and v 0x33333333) (bit-and (bit-shift-right v 2) 0x33333333))] + (bit-shift-right (* (bit-and (+ v (bit-shift-right v 4)) 0xF0F0F0F) 0x1010101) 24))) + +(defn ^boolean == + "Returns non-nil if nums all have the equivalent + value, otherwise false. Behavior on non nums is + undefined." + ([x] true) + ([x y] (-equiv x y)) + ([x y & more] + (if (== x y) + (if (next more) + (recur y (first more) (next more)) + (== y (first more))) + false))) + +(defn ^boolean pos? + "Returns true if num is greater than zero, else false" + [x] (cljs.core/pos? x)) + +(defn ^boolean zero? + "Returns true if num is zero, else false" + [x] + (cljs.core/zero? x)) + +(defn ^boolean neg? + "Returns true if num is less than zero, else false" + [x] (cljs.core/neg? x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; protocols for host types ;;;;;; + +(defn nthnext + "Returns the nth next of coll, (seq coll) when n is 0." + [coll n] + (if (implements? IDrop coll) + (if (pos? n) + (-drop coll (Math/ceil n)) + (seq coll)) + (loop [n n xs (seq coll)] + (if (and xs (pos? n)) + (recur (dec n) (next xs)) + xs)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;; basics ;;;;;;;;;;;;;;;;;; + +(defn- str_ + "Implementation detail. Internal str without circularity on IndexedSeq. + @param x + @param {...*} var_args" + [x var-args] + (cond + ;; works whether x is undefined or null (cljs nil) + (nil? x) "" + ;; if we have no more parameters, return + (undefined? var-args) (.join #js [x] "") + ;; var arg case without relying on CLJS fn machinery which creates + ;; a circularity via IndexedSeq + :else + (let [sb (StringBuffer.) + args (js-arguments) + len (alength args)] + (loop [i 0] + (if (< i len) + (do + (.append sb (cljs.core/str_ (aget args i))) + (recur (inc i))) + (.toString sb)))))) + +(defn str + "With no args, returns the empty string. With one arg x, returns + x.toString(). (str nil) returns the empty string. With more than + one arg, returns the concatenation of the str values of the args." + ([] "") + ([x] (if (nil? x) + "" + (.toString x))) + ([x & ys] + (loop [sb (StringBuffer. (str x)) more ys] + (if more + (recur (. sb (append (str (first more)))) (next more)) + (.toString sb))))) + +(defn subs + "Returns the substring of s beginning at start inclusive, and ending + at end (defaults to length of string), exclusive." + ([s start] ^string (.substring s start)) + ([s start end] ^string (.substring s start end))) + +(declare map name) + +(defn- equiv-sequential + "Assumes x is sequential. Returns true if x equals y, otherwise + returns false." + [x y] + (boolean + (when (sequential? y) + (if (and (counted? x) (counted? y) + (not (== (count x) (count y)))) + false + (loop [xs (seq x) ys (seq y)] + (cond (nil? xs) (nil? ys) + (nil? ys) false + (= (first xs) (first ys)) (recur (next xs) (next ys)) + :else false)))))) + +(defn- hash-coll [coll] + (if (seq coll) + (loop [res (hash (first coll)) s (next coll)] + (if (nil? s) + res + (recur (hash-combine res (hash (first s))) (next s)))) + 0)) + +(declare key val) + +(defn- hash-imap [m] + ;; a la clojure.lang.APersistentMap + (loop [h 0 s (seq m)] + (if s + (let [e (first s)] + (recur (js-mod (+ h (bit-xor (hash (key e)) (hash (val e)))) + 4503599627370496) + (next s))) + h))) + +(defn- hash-iset [s] + ;; a la clojure.lang.APersistentSet + (loop [h 0 s (seq s)] + (if s + (let [e (first s)] + (recur (js-mod (+ h (hash e)) 4503599627370496) + (next s))) + h))) + +(declare name chunk-first chunk-rest) + +(defn- extend-object! + "Takes a JavaScript object and a map of names to functions and + attaches said functions as methods on the object. Any references to + JavaScript's implicit this (via the this-as macro) will resolve to the + object that the function is attached." + [obj fn-map] + (doseq [[key-name f] fn-map] + (let [str-name (name key-name)] + (gobject/set obj str-name f))) + obj) + +;;;;;;;;;;;;;;;; cons ;;;;;;;;;;;;;;;; +(deftype List [meta first rest count ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x count)) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IList + + ICloneable + (-clone [_] (List. meta first rest count __hash)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (List. new-meta first rest count __hash))) + + IMeta + (-meta [coll] meta) + + ASeq + ISeq + (-first [coll] first) + (-rest [coll] + (if (== count 1) + () + rest)) + + INext + (-next [coll] + (if (== count 1) + nil + rest)) + + IStack + (-peek [coll] first) + (-pop [coll] (-rest coll)) + + ICollection + (-conj [coll o] (List. meta o coll (inc count) nil)) + + IEmptyableCollection + (-empty [coll] (-with-meta (.-EMPTY List) meta)) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + ISeqable + (-seq [coll] coll) + + ICounted + (-count [coll] count) + + IReduce + (-reduce [coll f] (seq-reduce f coll)) + (-reduce [coll f start] (seq-reduce f start coll))) + +(defn list? + "Returns true if x implements IList" + [x] + (satisfies? IList x)) + +(es6-iterable List) + +(deftype EmptyList [meta] + Object + (toString [coll] "()") + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IList + + ICloneable + (-clone [_] (EmptyList. meta)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (EmptyList. new-meta))) + + IMeta + (-meta [coll] meta) + + ISeq + (-first [coll] nil) + (-rest [coll] ()) + + INext + (-next [coll] nil) + + IStack + (-peek [coll] nil) + (-pop [coll] (throw (js/Error. "Can't pop empty list"))) + + ICollection + (-conj [coll o] (List. meta o nil 1 nil)) + + IEmptyableCollection + (-empty [coll] coll) + + ISequential + IEquiv + (-equiv [coll other] + (if (or (list? other) + (sequential? other)) + (nil? (seq other)) + false)) + + IHash + (-hash [coll] empty-ordered-hash) + + ISeqable + (-seq [coll] nil) + + ICounted + (-count [coll] 0) + + IReduce + (-reduce [coll f] (seq-reduce f coll)) + (-reduce [coll f start] (seq-reduce f start coll))) + +(set! (.-EMPTY List) (EmptyList. nil)) + +(es6-iterable EmptyList) + +(defn reversible? + "Returns true if coll satisfies? IReversible." + [coll] + (satisfies? IReversible coll)) + +(defn ^seq rseq + "Returns, in constant time, a seq of the items in rev (which + can be a vector or sorted-map), in reverse order. If rev is empty returns nil" + [rev] + (-rseq rev)) + +(defn reverse + "Returns a seq of the items in coll in reverse order. Not lazy." + [coll] + (if (reversible? coll) + (or (rseq coll) ()) + (reduce conj () coll))) + +(defn list + "Creates a new list containing the items." + [& xs] + (let [arr (if (and (instance? IndexedSeq xs) (zero? (.-i xs))) + (.-arr xs) + (let [arr (array)] + (loop [^not-native xs xs] + (if-not (nil? xs) + (do + (.push arr (-first xs)) + (recur (-next xs))) + arr))))] + (loop [i (alength arr) r ()] + (if (> i 0) + (recur (dec i) (-conj r (aget arr (dec i)))) + r)))) + +(deftype Cons [meta first rest ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IList + + ICloneable + (-clone [_] (Cons. meta first rest __hash)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (Cons. new-meta first rest __hash))) + + IMeta + (-meta [coll] meta) + + ASeq + ISeq + (-first [coll] first) + (-rest [coll] (if (nil? rest) () rest)) + + INext + (-next [coll] + (if (nil? rest) nil (seq rest))) + + ICollection + (-conj [coll o] (Cons. nil o coll nil)) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + ISeqable + (-seq [coll] coll) + + IReduce + (-reduce [coll f] (seq-reduce f coll)) + (-reduce [coll f start] (seq-reduce f start coll))) + +(es6-iterable Cons) + +(defn cons + "Returns a new seq where x is the first element and coll is the rest." + [x coll] + (cond + (nil? coll) (List. nil x nil 1 nil) + (implements? ISeq coll) (Cons. nil x coll nil) + :default (Cons. nil x (seq coll) nil))) + +(defn hash-keyword [k] + (int (+ (hash-symbol k) 0x9e3779b9))) + +(defn- compare-keywords [a b] + (cond + (identical? (.-fqn a) (.-fqn b)) 0 + (and (not (.-ns a)) (.-ns b)) -1 + (.-ns a) (if-not (.-ns b) + 1 + (let [nsc (garray/defaultCompare (.-ns a) (.-ns b))] + (if (== 0 nsc) + (garray/defaultCompare (.-name a) (.-name b)) + nsc))) + :default (garray/defaultCompare (.-name a) (.-name b)))) + +(deftype Keyword [ns name fqn ^:mutable _hash] + Object + (toString [_] (str_ ":" fqn)) + (equiv [this other] + (-equiv this other)) + + IEquiv + (-equiv [_ other] + (if (instance? Keyword other) + (identical? fqn (.-fqn other)) + false)) + IFn + (-invoke [kw coll] + (get coll kw)) + (-invoke [kw coll not-found] + (get coll kw not-found)) + + IHash + (-hash [this] + (caching-hash this hash-keyword _hash)) + + INamed + (-name [_] name) + (-namespace [_] ns) + + IPrintWithWriter + (-pr-writer [o writer _] (-write writer (str_ ":" fqn)))) + +(defn keyword? + "Return true if x is a Keyword" + [x] + (instance? Keyword x)) + +(defn keyword-identical? + "Efficient test to determine that two keywords are identical." + [x y] + (if (identical? x y) + true + (if (and (keyword? x) (keyword? y)) + (identical? (.-fqn x) (.-fqn y)) + false))) + +(defn symbol-identical? + "Efficient test to determine that two symbols are identical." + [x y] + (if (identical? x y) + true + (if (and (symbol? x) (symbol? y)) + (identical? (.-str x) (.-str y)) + false))) + +(defn namespace + "Returns the namespace String of a symbol or keyword, or nil if not present." + [x] + (if (implements? INamed x) + (-namespace x) + (throw (js/Error. (str_ "Doesn't support namespace: " x))))) + +(defn ident? + "Return true if x is a symbol or keyword" + [x] (or (keyword? x) (symbol? x))) + +(defn simple-ident? + "Return true if x is a symbol or keyword without a namespace" + [x] (and (ident? x) (nil? (namespace x)))) + +(defn qualified-ident? + "Return true if x is a symbol or keyword with a namespace" + [x] (boolean (and (ident? x) (namespace x) true))) + +(defn simple-symbol? + "Return true if x is a symbol without a namespace" + [x] (and (symbol? x) (nil? (namespace x)))) + +(defn qualified-symbol? + "Return true if x is a symbol with a namespace" + [x] (boolean (and (symbol? x) (namespace x) true))) + +(defn simple-keyword? + "Return true if x is a keyword without a namespace" + [x] (and (keyword? x) (nil? (namespace x)))) + +(defn qualified-keyword? + "Return true if x is a keyword with a namespace" + [x] (boolean (and (keyword? x) (namespace x) true))) + +(defn keyword + "Returns a Keyword with the given namespace and name. Do not use : + in the keyword strings, it will be added automatically." + ([name] (cond + (keyword? name) name + (symbol? name) (Keyword. + (cljs.core/namespace name) + (cljs.core/name name) (.-str name) nil) + (= "/" name) (Keyword. nil name name nil) + (string? name) (let [parts (.split name "/")] + (if (== (alength parts) 2) + (Keyword. (aget parts 0) (aget parts 1) name nil) + (Keyword. nil (aget parts 0) name nil))))) + ([ns name] + (let [ns (cond + (keyword? ns) (cljs.core/name ns) + (symbol? ns) (cljs.core/name ns) + :else ns) + name (cond + (keyword? name) (cljs.core/name name) + (symbol? name) (cljs.core/name name) + :else name)] + (Keyword. ns name (str_ (when ns (str_ ns "/")) name) nil)))) + +(deftype LazySeq [meta ^:mutable fn ^:mutable s ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (sval [coll] + (if (nil? fn) + s + (do + (set! s (fn)) + (set! fn nil) + s))) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IPending + (-realized? [coll] + (not fn)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (LazySeq. new-meta #(-seq coll) nil __hash))) + + IMeta + (-meta [coll] meta) + + ISeq + (-first [coll] + (-seq coll) + (when-not (nil? s) + (first s))) + (-rest [coll] + (-seq coll) + (if-not (nil? s) + (rest s) + ())) + + INext + (-next [coll] + (-seq coll) + (when-not (nil? s) + (next s))) + + ICollection + (-conj [coll o] (cons o coll)) + + IEmptyableCollection + (-empty [coll] + ;; MAYBE FIXME: :lite-mode testing uncovered a very old bug, empty on seq + ;; should discard the metadata, we changed the behavior in LITE_MODE for now + ;; to avoid a breaking change + (if-not ^boolean LITE_MODE + (-with-meta (.-EMPTY List) meta) + (.-EMPTY List))) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + ISeqable + (-seq [coll] + (.sval coll) + (when-not (nil? s) + (loop [ls s] + (if (instance? LazySeq ls) + (recur (.sval ls)) + (do (set! s ls) + (seq s)))))) + + IReduce + (-reduce [coll f] (seq-reduce f coll)) + (-reduce [coll f start] (seq-reduce f start coll))) + +(es6-iterable LazySeq) + +(declare ArrayChunk) + +(deftype ChunkBuffer [^:mutable buf ^:mutable end] + Object + (add [_ o] + (aset buf end o) + (set! end (inc end))) + + (chunk [_] + (let [ret (ArrayChunk. buf 0 end)] + (set! buf nil) + ret)) + + ICounted + (-count [_] end)) + +(defn chunk-buffer [capacity] + (ChunkBuffer. (make-array capacity) 0)) + +(deftype ArrayChunk [arr off end] + ICounted + (-count [_] (- end off)) + + IIndexed + (-nth [coll i] + (aget arr (+ off i))) + (-nth [coll i not-found] + (if (and (>= i 0) (< i (- end off))) + (aget arr (+ off i)) + not-found)) + + IChunk + (-drop-first [coll] + (if (== off end) + (throw (js/Error. "-drop-first of empty chunk")) + (ArrayChunk. arr (inc off) end))) + + IReduce + (-reduce [coll f] + (array-reduce arr f (aget arr off) (inc off))) + (-reduce [coll f start] + (array-reduce arr f start off))) + +(defn array-chunk + ([arr] + (ArrayChunk. arr 0 (alength arr))) + ([arr off] + (ArrayChunk. arr off (alength arr))) + ([arr off end] + (ArrayChunk. arr off end))) + +(deftype ChunkedCons [chunk more meta ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (ChunkedCons. chunk more new-meta __hash))) + + IMeta + (-meta [coll] meta) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + ISeqable + (-seq [coll] coll) + + ASeq + ISeq + (-first [coll] (-nth chunk 0)) + (-rest [coll] + (if (> (-count chunk) 1) + (ChunkedCons. (-drop-first chunk) more nil nil) + (if (nil? more) + () + more))) + + INext + (-next [coll] + (if (> (-count chunk) 1) + (ChunkedCons. (-drop-first chunk) more nil nil) + (when-not (nil? more) + (-seq more)))) + + IChunkedSeq + (-chunked-first [coll] chunk) + (-chunked-rest [coll] + (if (nil? more) + () + more)) + + IChunkedNext + (-chunked-next [coll] + (if (nil? more) + nil + more)) + + ICollection + (-conj [this o] + (cons o this)) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash))) + +(es6-iterable ChunkedCons) + +(defn chunk-cons [chunk rest] + (if (zero? (-count chunk)) + rest + (ChunkedCons. chunk rest nil nil))) + +(defn chunk-append [b x] + (.add b x)) + +(defn chunk [b] + (.chunk b)) + +(defn chunk-first [s] + (-chunked-first s)) + +(defn chunk-rest [s] + (-chunked-rest s)) + +(defn chunk-next [s] + (if (implements? IChunkedNext s) + (-chunked-next s) + (seq (-chunked-rest s)))) + +;;;;;;;;;;;;;;;; + +(defn to-array + "Returns an array containing the contents of coll." + [coll] + (let [ary (array)] + (loop [s (seq coll)] + (if-not (nil? s) + (do (. ary push (first s)) + (recur (next s))) + ary)))) + +(defn to-array-2d + "Returns a (potentially-ragged) 2-dimensional array + containing the contents of coll." + [coll] + (let [ret (make-array (count coll))] + (loop [i 0 xs (seq coll)] + (when-not (nil? xs) + (aset ret i (to-array (first xs))) + (recur (inc i) (next xs)))) + ret)) + +(defn int-array + "Creates an array of ints. Does not coerce array, provided for compatibility + with Clojure." + ([size-or-seq] + (if (number? size-or-seq) + (int-array size-or-seq nil) + (into-array size-or-seq))) + ([size init-val-or-seq] + (let [a (make-array size)] + (if (seq? init-val-or-seq) + (let [s (seq init-val-or-seq)] + (loop [i 0 s s] + (if (and s (< i size)) + (do + (aset a i (first s)) + (recur (inc i) (next s))) + a))) + (do + (dotimes [i size] + (aset a i init-val-or-seq)) + a))))) + +(defn long-array + "Creates an array of longs. Does not coerce array, provided for compatibility + with Clojure." + ([size-or-seq] + (if (number? size-or-seq) + (long-array size-or-seq nil) + (into-array size-or-seq))) + ([size init-val-or-seq] + (let [a (make-array size)] + (if (seq? init-val-or-seq) + (let [s (seq init-val-or-seq)] + (loop [i 0 s s] + (if (and s (< i size)) + (do + (aset a i (first s)) + (recur (inc i) (next s))) + a))) + (do + (dotimes [i size] + (aset a i init-val-or-seq)) + a))))) + +(defn double-array + "Creates an array of doubles. Does not coerce array, provided for compatibility + with Clojure." + ([size-or-seq] + (if (number? size-or-seq) + (double-array size-or-seq nil) + (into-array size-or-seq))) + ([size init-val-or-seq] + (let [a (make-array size)] + (if (seq? init-val-or-seq) + (let [s (seq init-val-or-seq)] + (loop [i 0 s s] + (if (and s (< i size)) + (do + (aset a i (first s)) + (recur (inc i) (next s))) + a))) + (do + (dotimes [i size] + (aset a i init-val-or-seq)) + a))))) + +(defn object-array + "Creates an array of objects. Does not coerce array, provided for compatibility + with Clojure." + ([size-or-seq] + (if (number? size-or-seq) + (object-array size-or-seq nil) + (into-array size-or-seq))) + ([size init-val-or-seq] + (let [a (make-array size)] + (if (seq? init-val-or-seq) + (let [s (seq init-val-or-seq)] + (loop [i 0 s s] + (if (and s (< i size)) + (do + (aset a i (first s)) + (recur (inc i) (next s))) + a))) + (do + (dotimes [i size] + (aset a i init-val-or-seq)) + a))))) + +(defn bounded-count + "If coll is counted? returns its count, else will count at most the first n + elements of coll using its seq" + {:added "1.9"} + [n coll] + (if (counted? coll) + (count coll) + (loop [i 0 s (seq coll)] + (if (and (not (nil? s)) (< i n)) + (recur (inc i) (next s)) + i)))) + +(defn spread + [arglist] + (when-not (nil? arglist) + (let [n (next arglist)] + (if (nil? n) + (seq (first arglist)) + (cons (first arglist) + (spread n)))))) + +(defn concat + "Returns a lazy seq representing the concatenation of the elements in the supplied colls." + ([] (lazy-seq nil)) + ([x] (lazy-seq x)) + ([x y] + (lazy-seq + (let [s (seq x)] + (if s + (if (chunked-seq? s) + (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) + (cons (first s) (concat (rest s) y))) + y)))) + ([x y & zs] + (let [cat (fn cat [xys zs] + (lazy-seq + (let [xys (seq xys)] + (if xys + (if (chunked-seq? xys) + (chunk-cons (chunk-first xys) + (cat (chunk-rest xys) zs)) + (cons (first xys) (cat (rest xys) zs))) + (when zs + (cat (first zs) (next zs)))))))] + (cat (concat x y) zs)))) + +(defn list* + "Creates a new list containing the items prepended to the rest, the + last of which will be treated as a sequence." + ([args] (seq args)) + ([a args] (cons a args)) + ([a b args] (cons a (cons b args))) + ([a b c args] (cons a (cons b (cons c args)))) + ([a b c d & more] + (cons a (cons b (cons c (cons d (spread more))))))) + + +;;; Transients + +(defn transient + "Returns a new, transient version of the collection, in constant time." + [coll] + (-as-transient coll)) + +(defn persistent! + "Returns a new, persistent version of the transient collection, in + constant time. The transient collection cannot be used after this + call, any such use will throw an exception." + [tcoll] + (-persistent! tcoll)) + +(defn conj! + "Adds val to the transient collection, and return tcoll. The 'addition' + may happen at different 'places' depending on the concrete type." + ([] (transient [])) + ([tcoll] tcoll) + ([tcoll val] + (-conj! tcoll val)) + ([tcoll val & vals] + (let [ntcoll (-conj! tcoll val)] + (if vals + (recur ntcoll (first vals) (next vals)) + ntcoll)))) + +(defn assoc! + "When applied to a transient map, adds mapping of key(s) to + val(s). When applied to a transient vector, sets the val at index. + Note - index must be <= (count vector). Returns coll." + ([tcoll key val] + (-assoc! tcoll key val)) + ([tcoll key val & kvs] + (let [ntcoll (-assoc! tcoll key val)] + (if kvs + (recur ntcoll (first kvs) (second kvs) (nnext kvs)) + ntcoll)))) + +(defn dissoc! + "Returns a transient map that doesn't contain a mapping for key(s)." + ([tcoll key] + (-dissoc! tcoll key)) + ([tcoll key & ks] + (let [ntcoll (-dissoc! tcoll key)] + (if ks + (recur ntcoll (first ks) (next ks)) + ntcoll)))) + +(defn pop! + "Removes the last item from a transient vector. If + the collection is empty, throws an exception. Returns tcoll" + [tcoll] + (-pop! tcoll)) + +(defn disj! + "disj[oin]. Returns a transient set of the same (hashed/sorted) type, that + does not contain key(s)." + ([tcoll val] + (-disjoin! tcoll val)) + ([tcoll val & vals] + (let [ntcoll (-disjoin! tcoll val)] + (if vals + (recur ntcoll (first vals) (next vals)) + ntcoll)))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; apply ;;;;;;;;;;;;;;;; + +;; see core.clj +(gen-apply-to) + +(set! *unchecked-if* true) + +(defn- ^seq next* + "Internal. DO NOT USE! Next without the nil? check." + [coll] + (if (implements? INext coll) + (-next ^not-native coll) + (seq (rest coll)))) + +(defn- apply-to-simple + "Internal. DO NOT USE! + Assumes args was already called with seq beforehand!" + ([f ^seq args] + (if (nil? args) + (if (.-cljs$core$IFn$_invoke$arity$0 f) + (.cljs$core$IFn$_invoke$arity$0 f) + (.call f f)) + (apply-to-simple f (-first args) (next* args)))) + ([f a0 ^seq args] + (if (nil? args) + (if (.-cljs$core$IFn$_invoke$arity$1 f) + (.cljs$core$IFn$_invoke$arity$1 f a0) + (.call f f a0)) + (apply-to-simple f a0 (-first args) (next* args)))) + ([f a0 a1 ^seq args] + (if (nil? args) + (if (.-cljs$core$IFn$_invoke$arity$2 f) + (.cljs$core$IFn$_invoke$arity$2 f a0 a1) + (.call f f a0 a1)) + (apply-to-simple f a0 a1 (-first args) (next* args)))) + ([f a0 a1 a2 ^seq args] + (if (nil? args) + (if (.-cljs$core$IFn$_invoke$arity$3 f) + (.cljs$core$IFn$_invoke$arity$3 f a0 a1 a2) + (.call f f a0 a1 a2)) + (apply-to-simple f a0 a1 a2 (-first args) (next* args)))) + ([f a0 a1 a2 a3 ^seq args] + (if (nil? args) + (if (.-cljs$core$IFn$_invoke$arity$4 f) + (.cljs$core$IFn$_invoke$arity$4 f a0 a1 a2 a3) + (.call f f a0 a1 a2 a3)) + (gen-apply-to-simple f 4 args)))) + +(defn apply + "Applies fn f to the argument list formed by prepending intervening arguments to args." + ([f args] + (if (.-cljs$lang$applyTo f) + (let [fixed-arity (.-cljs$lang$maxFixedArity f) + bc (bounded-count (inc fixed-arity) args)] + (if (<= bc fixed-arity) + (apply-to f bc args) + (.cljs$lang$applyTo f args))) + (apply-to-simple f (seq args)))) + ([f x args] + (if (.-cljs$lang$applyTo f) + (let [arglist (list* x args) + fixed-arity (.-cljs$lang$maxFixedArity f) + bc (inc (bounded-count fixed-arity args))] + (if (<= bc fixed-arity) + (apply-to f bc arglist) + (.cljs$lang$applyTo f arglist))) + (apply-to-simple f x (seq args)))) + ([f x y args] + (if (.-cljs$lang$applyTo f) + (let [arglist (list* x y args) + fixed-arity (.-cljs$lang$maxFixedArity f) + bc (+ 2 (bounded-count (dec fixed-arity) args))] + (if (<= bc fixed-arity) + (apply-to f bc arglist) + (.cljs$lang$applyTo f arglist))) + (apply-to-simple f x y (seq args)))) + ([f x y z args] + (if (.-cljs$lang$applyTo f) + (let [arglist (list* x y z args) + fixed-arity (.-cljs$lang$maxFixedArity f) + bc (+ 3 (bounded-count (- fixed-arity 2) args))] + (if (<= bc fixed-arity) + (apply-to f bc arglist) + (.cljs$lang$applyTo f arglist))) + (apply-to-simple f x y z (seq args)))) + ([f a b c d & args] + (if (.-cljs$lang$applyTo f) + (let [spread-args (spread args) + arglist (cons a (cons b (cons c (cons d spread-args)))) + fixed-arity (.-cljs$lang$maxFixedArity f) + bc (+ 4 (bounded-count (- fixed-arity 3) spread-args))] + (if (<= bc fixed-arity) + (apply-to f bc arglist) + (.cljs$lang$applyTo f arglist))) + (apply-to-simple f a b c d (spread args))))) + +(set! *unchecked-if* false) + +(declare ObjMap) + +;; CLJS-3200: used by destructure macro for maps to reduce amount of repeated code +;; placed here because it needs apply and hash-map (only declared at this point) +(defn --destructure-map [gmap] + (if ^boolean LITE_MODE + (if (implements? ISeq gmap) + (if (next gmap) + (.createAsIfByAssoc ObjMap (to-array gmap)) + (if (seq gmap) + (first gmap) + (.-EMPTY ObjMap))) + gmap) + (if (implements? ISeq gmap) + (if (next gmap) + (.createAsIfByAssoc PersistentArrayMap (to-array gmap)) + (if (seq gmap) + (first gmap) + (.-EMPTY PersistentArrayMap))) + gmap))) + +(defn vary-meta + "Returns an object of the same type and value as obj, with + (apply f (meta obj) args) as its metadata." + ([obj f] + (with-meta obj (f (meta obj)))) + ([obj f a] + (with-meta obj (f (meta obj) a))) + ([obj f a b] + (with-meta obj (f (meta obj) a b))) + ([obj f a b c] + (with-meta obj (f (meta obj) a b c))) + ([obj f a b c d] + (with-meta obj (f (meta obj) a b c d))) + ([obj f a b c d & args] + (with-meta obj (apply f (meta obj) a b c d args)))) + +(defn ^boolean not= + "Same as (not (= obj1 obj2))" + ([x] false) + ([x y] (not (= x y))) + ([x y & more] + (not (apply = x y more)))) + +(defn not-empty + "If coll is empty, returns nil, else coll" + [coll] (when (seq coll) coll)) + +(defn nil-iter [] + (reify + Object + (hasNext [_] false) + (next [_] (js/Error. "No such element")) + (remove [_] (js/Error. "Unsupported operation")))) + +(deftype StringIter [s ^:mutable i] + Object + (hasNext [_] (< i (.-length s))) + (next [_] + (let [ret (.charAt s i)] + (set! i (inc i)) + ret)) + (remove [_] (js/Error. "Unsupported operation"))) + +(defn string-iter [x] + (StringIter. x 0)) + +(deftype ArrayIter [arr ^:mutable i] + Object + (hasNext [_] (< i (alength arr))) + (next [_] + (let [ret (aget arr i)] + (set! i (inc i)) + ret)) + (remove [_] (js/Error. "Unsupported operation"))) + +(defn array-iter [x] + (ArrayIter. x 0)) + +(def INIT #js {}) +(def START #js {}) + +(deftype SeqIter [^:mutable _seq ^:mutable _next] + Object + (hasNext [_] + (if (identical? _seq INIT) + (do + (set! _seq START) + (set! _next (seq _next))) + (if (identical? _seq _next) + (set! _next (next _seq)))) + (not (nil? _next))) + (next [this] + (if-not ^boolean (.hasNext this) + (throw (js/Error. "No such element")) + (do + (set! _seq _next) + (first _next)))) + (remove [_] (js/Error. "Unsupported operation"))) + +(defn seq-iter [coll] + (SeqIter. INIT coll)) + +(defn iter [coll] + (cond + (iterable? coll) (-iterator coll) + (nil? coll) (nil-iter) + (string? coll) (string-iter coll) + (array? coll) (array-iter coll) + (seqable? coll) (seq-iter coll) + :else (throw (js/Error. (str_ "Cannot create iterator from " coll))))) + +(deftype Many [vals] + Object + (add [this o] + (.push vals o) + this) + (remove [this] + (.shift vals)) + (isEmpty [this] + (zero? (.-length vals))) + (toString [this] + (str_ "Many: " vals))) + +(def ^:private NONE #js {}) + +(deftype Single [^:mutable val] + Object + (add [this o] + (if (identical? val NONE) + (do + (set! val o) + this) + (Many. #js [val o]))) + (remove [this] + (if (identical? val NONE) + (throw (js/Error. (str_ "Removing object from empty buffer"))) + (let [ret val] + (set! val NONE) + ret))) + (isEmpty [this] + (identical? val NONE)) + (toString [this] + (str_ "Single: " val))) + +(deftype Empty [] + Object + (add [this o] + (Single. o)) + (remove [this] + (throw (js/Error. (str_ "Removing object from empty buffer")))) + (isEmpty [this] + true) + (toString [this] + "Empty")) + +(def ^:private EMPTY (Empty.)) + +(deftype MultiIterator [iters] + Object + (hasNext [_] + (loop [iters (seq iters)] + (if-not (nil? iters) + (let [iter (first iters)] + (if-not ^boolean (.hasNext iter) + false + (recur (next iters)))) + true))) + (next [_] + (let [nexts (array)] + (dotimes [i (alength iters)] + (aset nexts i (.next (aget iters i)))) + (prim-seq nexts 0)))) + +(defn- chunkIteratorSeq [iter] + (lazy-seq + (when ^boolean (.hasNext iter) + (let [arr (array)] + (loop [n 0] + (if (and (.hasNext iter) (< n 32)) + (do + (aset arr n (.next iter)) + (recur (inc n))) + (chunk-cons (array-chunk arr 0 n) (chunkIteratorSeq iter)))))))) + +(deftype TransformerIterator [^:mutable buffer ^:mutable _next ^:mutable completed ^:mutable xf sourceIter multi] + Object + (step [this] + (if-not (identical? _next NONE) + true + (loop [] + (if (identical? _next NONE) + (if ^boolean (.isEmpty buffer) + (if ^boolean completed + false + (if ^boolean (.hasNext sourceIter) + (let [iter (if ^boolean multi + (apply xf (cons nil (.next sourceIter))) + (xf nil (.next sourceIter)))] + (when (reduced? iter) + (xf nil) + (set! completed true)) + (recur)) + (do + (xf nil) + (set! completed true) + (recur)))) + (do + (set! _next (.remove buffer)) + (recur))) + true)))) + (hasNext [this] + (.step this)) + (next [this] + (if ^boolean (.hasNext this) + (let [ret _next] + (set! _next NONE) + ret) + (throw (js/Error. "No such element")))) + (remove [_] + (js/Error. "Unsupported operation"))) + +(es6-iterable TransformerIterator) + +(defn transformer-iterator + [xform sourceIter multi] + (let [iterator (TransformerIterator. EMPTY NONE false nil sourceIter multi)] + (set! (.-xf iterator) + (xform (fn + ([] nil) + ([acc] acc) + ([acc o] + (set! (.-buffer iterator) (.add (.-buffer iterator) o)) + acc)))) + iterator)) + +(set! (.-create TransformerIterator) + (fn [xform source] + (transformer-iterator xform source false))) + +(set! (.-createMulti TransformerIterator) + (fn [xform sources] + (transformer-iterator xform (MultiIterator. (to-array sources)) true))) + +(defn sequence + "Coerces coll to a (possibly empty) sequence, if it is not already + one. Will not force a lazy seq. (sequence nil) yields (), When a + transducer is supplied, returns a lazy sequence of applications of + the transform to the items in coll(s), i.e. to the set of first + items of each coll, followed by the set of second + items in each coll, until any one of the colls is exhausted. Any + remaining items in other colls are ignored. The transform should accept + number-of-colls arguments" + ([coll] + (if (seq? coll) + coll + (or (seq coll) ()))) + ([xform coll] + (or (chunkIteratorSeq + (.create TransformerIterator xform (iter coll))) + ())) + ([xform coll & colls] + (or (chunkIteratorSeq + (.createMulti TransformerIterator xform (map iter (cons coll colls)))) + ()))) + +(defn every? + "Returns true if (pred x) is logical true for every x in coll, else + false." + [pred coll] + (cond + (nil? (seq coll)) true + (pred (first coll)) (recur pred (next coll)) + :else false)) + +(defn not-every? + "Returns false if (pred x) is logical true for every x in + coll, else true." + [pred coll] (not (every? pred coll))) + +(defn some + "Returns the first logical true value of (pred x) for any x in coll, + else nil. One common idiom is to use a set as pred, for example + this will return :fred if :fred is in the sequence, otherwise nil: + (some #{:fred} coll)" + [pred coll] + (when-let [s (seq coll)] + (or (pred (first s)) (recur pred (next s))))) + +(defn not-any? + "Returns false if (pred x) is logical true for any x in coll, + else true." + [pred coll] (not (some pred coll))) + +(defn even? + "Returns true if n is even, throws an exception if n is not an integer" + [n] (if (integer? n) + (zero? (bit-and n 1)) + (throw (js/Error. (str_ "Argument must be an integer: " n))))) + +(defn odd? + "Returns true if n is odd, throws an exception if n is not an integer" + [n] (not (even? n))) + +(defn complement + "Takes a fn f and returns a fn that takes the same arguments as f, + has the same effects, if any, and returns the opposite truth value." + [f] + (fn + ([] (not (f))) + ([x] (not (f x))) + ([x y] (not (f x y))) + ([x y & zs] (not (apply f x y zs))))) + +(defn constantly + "Returns a function that takes any number of arguments and returns x." + [x] (fn [& args] x)) + +(defn comp + "Takes a set of functions and returns a fn that is the composition + of those fns. The returned fn takes a variable number of args, + applies the rightmost of fns to the args, the next + fn (right-to-left) to the result, etc." + ([] identity) + ([f] f) + ([f g] + (fn + ([] (f (g))) + ([x] (f (g x))) + ([x y] (f (g x y))) + ([x y z] (f (g x y z))) + ([x y z & args] (f (apply g x y z args))))) + ([f g h] + (fn + ([] (f (g (h)))) + ([x] (f (g (h x)))) + ([x y] (f (g (h x y)))) + ([x y z] (f (g (h x y z)))) + ([x y z & args] (f (g (apply h x y z args)))))) + ([f1 f2 f3 & fs] + (let [fs (reverse (list* f1 f2 f3 fs))] + (fn [& args] + (loop [ret (apply (first fs) args) fs (next fs)] + (if fs + (recur ((first fs) ret) (next fs)) + ret)))))) + +(defn partial + "Takes a function f and fewer than the normal arguments to f, and + returns a fn that takes a variable number of additional args. When + called, the returned function calls f with args + additional args." + ([f] f) + ([f arg1] + (fn + ([] (f arg1)) + ([x] (f arg1 x)) + ([x y] (f arg1 x y)) + ([x y z] (f arg1 x y z)) + ([x y z & args] (apply f arg1 x y z args)))) + ([f arg1 arg2] + (fn + ([] (f arg1 arg2)) + ([x] (f arg1 arg2 x)) + ([x y] (f arg1 arg2 x y)) + ([x y z] (f arg1 arg2 x y z)) + ([x y z & args] (apply f arg1 arg2 x y z args)))) + ([f arg1 arg2 arg3] + (fn + ([] (f arg1 arg2 arg3)) + ([x] (f arg1 arg2 arg3 x)) + ([x y] (f arg1 arg2 arg3 x y)) + ([x y z] (f arg1 arg2 arg3 x y z)) + ([x y z & args] (apply f arg1 arg2 arg3 x y z args)))) + ([f arg1 arg2 arg3 & more] + (fn [& args] (apply f arg1 arg2 arg3 (concat more args))))) + +(defn fnil + "Takes a function f, and returns a function that calls f, replacing + a nil first argument to f with the supplied value x. Higher arity + versions can replace arguments in the second and third + positions (y, z). Note that the function f can take any number of + arguments, not just the one(s) being nil-patched." + ([f x] + (fn + ([a] (f (if (nil? a) x a))) + ([a b] (f (if (nil? a) x a) b)) + ([a b c] (f (if (nil? a) x a) b c)) + ([a b c & ds] (apply f (if (nil? a) x a) b c ds)))) + ([f x y] + (fn + ([a b] (f (if (nil? a) x a) (if (nil? b) y b))) + ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) c)) + ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) c ds)))) + ([f x y z] + (fn + ([a b] (f (if (nil? a) x a) (if (nil? b) y b))) + ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c))) + ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c) ds))))) + +(declare volatile!) + +(defn map-indexed + "Returns a lazy sequence consisting of the result of applying f to 0 + and the first item of coll, followed by applying f to 1 and the second + item in coll, etc, until coll is exhausted. Thus function f should + accept 2 arguments, index and item. Returns a stateful transducer when + no collection is provided." + ([f] + (fn [rf] + (let [i (volatile! -1)] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (rf result (f (vswap! i inc) input))))))) + ([f coll] + (letfn [(mapi [idx coll] + (lazy-seq + (when-let [s (seq coll)] + (if (chunked-seq? s) + (let [c (chunk-first s) + size (count c) + b (chunk-buffer size)] + (dotimes [i size] + (chunk-append b (f (+ idx i) (-nth c i)))) + (chunk-cons (chunk b) (mapi (+ idx size) (chunk-rest s)))) + (cons (f idx (first s)) (mapi (inc idx) (rest s)))))))] + (mapi 0 coll)))) + +(defn keep + "Returns a lazy sequence of the non-nil results of (f item). Note, + this means false return values will be included. f must be free of + side-effects. Returns a transducer when no collection is provided." + ([f] + (fn [rf] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (let [v (f input)] + (if (nil? v) + result + (rf result v))))))) + ([f coll] + (lazy-seq + (when-let [s (seq coll)] + (if (chunked-seq? s) + (let [c (chunk-first s) + size (count c) + b (chunk-buffer size)] + (dotimes [i size] + (let [x (f (-nth c i))] + (when-not (nil? x) + (chunk-append b x)))) + (chunk-cons (chunk b) (keep f (chunk-rest s)))) + (let [x (f (first s))] + (if (nil? x) + (keep f (rest s)) + (cons x (keep f (rest s)))))))))) + +;; ============================================================================= +;; Atom + +(deftype Atom [state meta validator watches] + Object + (equiv [this other] + (-equiv this other)) + + IAtom + + IEquiv + (-equiv [o other] (identical? o other)) + + IDeref + (-deref [_] state) + + IMeta + (-meta [_] meta) + + IWatchable + (-notify-watches [this oldval newval] + (doseq [[key f] watches] + (f key this oldval newval))) + (-add-watch [this key f] + (set! (.-watches this) (assoc watches key f)) + this) + (-remove-watch [this key] + (set! (.-watches this) (dissoc watches key))) + + IHash + (-hash [this] (goog/getUid this))) + +(defn atom + "Creates and returns an Atom with an initial value of x and zero or + more options (in any order): + + :meta metadata-map + + :validator validate-fn + + If metadata-map is supplied, it will become the metadata on the + atom. validate-fn must be nil or a side-effect-free fn of one + argument, which will be passed the intended new state on any state + change. If the new state is unacceptable, the validate-fn should + return false or throw an Error. If either of these error conditions + occur, then the value of the atom will not change." + ([x] (Atom. x nil nil nil)) + ([x & {:keys [meta validator]}] (Atom. x meta validator nil))) + +(declare pr-str) + +(defn reset! + "Sets the value of atom to newval without regard for the + current value. Returns new-value." + [a new-value] + (if (instance? Atom a) + (let [validate (.-validator a)] + (when-not (nil? validate) + (when-not (validate new-value) + (throw (js/Error. "Validator rejected reference state")))) + (let [old-value (.-state a)] + (set! (.-state a) new-value) + (when-not (nil? (.-watches a)) + (-notify-watches a old-value new-value)) + new-value)) + (-reset! a new-value))) + +(defn reset-vals! + "Sets the value of atom to newval. Returns [old new], the value of the + atom before and after the reset." + {:added "1.9"} + [a new-value] + (if (instance? Atom a) + (let [validate (.-validator a)] + (when-not (nil? validate) + (when-not (validate new-value) + (throw (js/Error. "Validator rejected reference state")))) + (let [old-value (.-state a)] + (set! (.-state a) new-value) + (when-not (nil? (.-watches a)) + (-notify-watches a old-value new-value)) + [old-value new-value])) + [(-deref a) (-reset! a new-value)])) + +(defn swap! + "Atomically swaps the value of atom to be: + (apply f current-value-of-atom args). Note that f may be called + multiple times, and thus should be free of side effects. Returns + the value that was swapped in." + ([a f] + (if (instance? Atom a) + (reset! a (f (.-state a))) + (-swap! a f))) + ([a f x] + (if (instance? Atom a) + (reset! a (f (.-state a) x)) + (-swap! a f x))) + ([a f x y] + (if (instance? Atom a) + (reset! a (f (.-state a) x y)) + (-swap! a f x y))) + ([a f x y & more] + (if (instance? Atom a) + (reset! a (apply f (.-state a) x y more)) + (-swap! a f x y more)))) + +(defn swap-vals! + "Atomically swaps the value of atom to be: + (apply f current-value-of-atom args). Note that f may be called + multiple times, and thus should be free of side effects. + Returns [old new], the value of the atom before and after the swap." + {:added "1.9"} + ([a f] + (if (instance? Atom a) + (reset-vals! a (f (.-state a))) + [(-deref a) (-swap! a f)])) + ([a f x] + (if (instance? Atom a) + (reset-vals! a (f (.-state a) x)) + [(-deref a) (-swap! a f x)])) + ([a f x y] + (if (instance? Atom a) + (reset-vals! a (f (.-state a) x y)) + [(-deref a) (-swap! a f x y)])) + ([a f x y & more] + (if (instance? Atom a) + (reset-vals! a (apply f (.-state a) x y more)) + [(-deref a) (-swap! a f x y more)]))) + +(defn compare-and-set! + "Atomically sets the value of atom to newval if and only if the + current value of the atom is equal to oldval. Returns true if + set happened, else false." + [^not-native a oldval newval] + (if (= (-deref a) oldval) + (do (reset! a newval) true) + false)) + +(defn set-validator! + "Sets the validator-fn for an atom. validator-fn must be nil or a + side-effect-free fn of one argument, which will be passed the intended + new state on any state change. If the new state is unacceptable, the + validator-fn should return false or throw an Error. If the current state + is not acceptable to the new validator, an Error will be thrown and the + validator will not be changed." + [iref val] + (when (and (some? val) + (not (val (-deref iref)))) + (throw (js/Error. "Validator rejected reference state"))) + (set! (.-validator iref) val)) + +(defn get-validator + "Gets the validator-fn for a var/ref/agent/atom." + [iref] + (.-validator iref)) + +(deftype Volatile [^:mutable state] + IVolatile + (-vreset! [_ new-state] + (set! state new-state)) + + IDeref + (-deref [_] state)) + +(defn volatile! + "Creates and returns a Volatile with an initial value of val." + [val] + (Volatile. val)) + +(defn volatile? + "Returns true if x is a volatile." + [x] (instance? Volatile x)) + +(defn vreset! + "Sets the value of volatile to newval without regard for the + current value. Returns newval." + [vol newval] (-vreset! vol newval)) + +(defn keep-indexed + "Returns a lazy sequence of the non-nil results of (f index item). Note, + this means false return values will be included. f must be free of + side-effects. Returns a stateful transducer when no collection is + provided." + ([f] + (fn [rf] + (let [ia (volatile! -1)] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (let [i (vswap! ia inc) + v (f i input)] + (if (nil? v) + result + (rf result v)))))))) + ([f coll] + (letfn [(keepi [idx coll] + (lazy-seq + (when-let [s (seq coll)] + (if (chunked-seq? s) + (let [c (chunk-first s) + size (count c) + b (chunk-buffer size)] + (dotimes [i size] + (let [x (f (+ idx i) (-nth c i))] + (when-not (nil? x) + (chunk-append b x)))) + (chunk-cons (chunk b) (keepi (+ idx size) (chunk-rest s)))) + (let [x (f idx (first s))] + (if (nil? x) + (keepi (inc idx) (rest s)) + (cons x (keepi (inc idx) (rest s)))))))))] + (keepi 0 coll)))) + +(defn every-pred + "Takes a set of predicates and returns a function f that returns true if all of its + composing predicates return a logical true value against all of its arguments, else it returns + false. Note that f is short-circuiting in that it will stop execution on the first + argument that triggers a logical false result against the original predicates." + ([p] + (fn ep1 + ([] true) + ([x] (boolean (p x))) + ([x y] (boolean (and (p x) (p y)))) + ([x y z] (boolean (and (p x) (p y) (p z)))) + ([x y z & args] (boolean (and (ep1 x y z) + (every? p args)))))) + ([p1 p2] + (fn ep2 + ([] true) + ([x] (boolean (and (p1 x) (p2 x)))) + ([x y] (boolean (and (p1 x) (p1 y) (p2 x) (p2 y)))) + ([x y z] (boolean (and (p1 x) (p1 y) (p1 z) (p2 x) (p2 y) (p2 z)))) + ([x y z & args] (boolean (and (ep2 x y z) + (every? #(and (p1 %) (p2 %)) args)))))) + ([p1 p2 p3] + (fn ep3 + ([] true) + ([x] (boolean (and (p1 x) (p2 x) (p3 x)))) + ([x y] (boolean (and (p1 x) (p1 y) (p2 x) (p2 y) (p3 x) (p3 y)))) + ([x y z] (boolean (and (p1 x) (p1 y) (p1 z) (p2 x) (p2 y) (p2 z) (p3 x) (p3 y) (p3 z)))) + ([x y z & args] (boolean (and (ep3 x y z) + (every? #(and (p1 %) (p2 %) (p3 %)) args)))))) + ([p1 p2 p3 & ps] + (let [ps (list* p1 p2 p3 ps)] + (fn epn + ([] true) + ([x] (every? #(% x) ps)) + ([x y] (every? #(and (% x) (% y)) ps)) + ([x y z] (every? #(and (% x) (% y) (% z)) ps)) + ([x y z & args] (boolean (and (epn x y z) + (every? #(every? % args) ps)))))))) + +(defn some-fn + "Takes a set of predicates and returns a function f that returns the first logical true value + returned by one of its composing predicates against any of its arguments, else it returns + logical false. Note that f is short-circuiting in that it will stop execution on the first + argument that triggers a logical true result against the original predicates." + ([p] + (fn sp1 + ([] nil) + ([x] (p x)) + ([x y] (or (p x) (p y))) + ([x y z] (or (p x) (p y) (p z))) + ([x y z & args] (or (sp1 x y z) + (some p args))))) + ([p1 p2] + (fn sp2 + ([] nil) + ([x] (or (p1 x) (p2 x))) + ([x y] (or (p1 x) (p1 y) (p2 x) (p2 y))) + ([x y z] (or (p1 x) (p1 y) (p1 z) (p2 x) (p2 y) (p2 z))) + ([x y z & args] (or (sp2 x y z) + (some #(or (p1 %) (p2 %)) args))))) + ([p1 p2 p3] + (fn sp3 + ([] nil) + ([x] (or (p1 x) (p2 x) (p3 x))) + ([x y] (or (p1 x) (p1 y) (p2 x) (p2 y) (p3 x) (p3 y))) + ([x y z] (or (p1 x) (p1 y) (p1 z) (p2 x) (p2 y) (p2 z) (p3 x) (p3 y) (p3 z))) + ([x y z & args] (or (sp3 x y z) + (some #(or (p1 %) (p2 %) (p3 %)) args))))) + ([p1 p2 p3 & ps] + (let [ps (list* p1 p2 p3 ps)] + (fn spn + ([] nil) + ([x] (some #(% x) ps)) + ([x y] (some #(or (% x) (% y)) ps)) + ([x y z] (some #(or (% x) (% y) (% z)) ps)) + ([x y z & args] (or (spn x y z) + (some #(some % args) ps))))))) + +(defn map + "Returns a lazy sequence consisting of the result of applying f to + the set of first items of each coll, followed by applying f to the + set of second items in each coll, until any one of the colls is + exhausted. Any remaining items in other colls are ignored. Function + f should accept number-of-colls arguments. Returns a transducer when + no collection is provided." + ([f] + (fn [rf] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (rf result (f input))) + ([result input & inputs] + (rf result (apply f input inputs)))))) + ([f coll] + (lazy-seq + (when-let [s (seq coll)] + (if (chunked-seq? s) + (let [c (chunk-first s) + size (count c) + b (chunk-buffer size)] + (dotimes [i size] + (chunk-append b (f (-nth c i)))) + (chunk-cons (chunk b) (map f (chunk-rest s)))) + (cons (f (first s)) (map f (rest s))))))) + ([f c1 c2] + (lazy-seq + (let [s1 (seq c1) s2 (seq c2)] + (when (and s1 s2) + (cons (f (first s1) (first s2)) + (map f (rest s1) (rest s2))))))) + ([f c1 c2 c3] + (lazy-seq + (let [s1 (seq c1) s2 (seq c2) s3 (seq c3)] + (when (and s1 s2 s3) + (cons (f (first s1) (first s2) (first s3)) + (map f (rest s1) (rest s2) (rest s3))))))) + ([f c1 c2 c3 & colls] + (let [step (fn step [cs] + (lazy-seq + (let [ss (map seq cs)] + (when (every? identity ss) + (cons (map first ss) (step (map rest ss)))))))] + (map #(apply f %) (step (conj colls c3 c2 c1)))))) + +(defn take + "Returns a lazy sequence of the first n items in coll, or all items if + there are fewer than n. Returns a stateful transducer when + no collection is provided." + ([n] + {:pre [(number? n)]} + (fn [rf] + (let [na (volatile! n)] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (let [n @na + nn (vswap! na dec) + result (if (pos? n) + (rf result input) + result)] + (if (not (pos? nn)) + (ensure-reduced result) + result))))))) + ([n coll] + {:pre [(number? n)]} + (lazy-seq + (when (pos? n) + (when-let [s (seq coll)] + (cons (first s) (take (dec n) (rest s)))))))) + +(defn drop + "Returns a laziness-preserving sequence of all but the first n items in coll. + Returns a stateful transducer when no collection is provided." + ([n] + {:pre [(number? n)]} + (fn [rf] + (let [na (volatile! n)] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (let [n @na] + (vswap! na dec) + (if (pos? n) + result + (rf result input)))))))) + ([n coll] + {:pre [(number? n)]} + (if (implements? IDrop coll) + (or + (if (pos? n) + (-drop coll (Math/ceil n)) + (seq coll)) + ()) + (let [step (fn [n coll] + (let [s (seq coll)] + (if (and (pos? n) s) + (recur (dec n) (rest s)) + s)))] + (lazy-seq (step n coll)))))) + +(defn drop-last + "Return a lazy sequence of all but the last n (default 1) items in coll" + ([s] (drop-last 1 s)) + ([n s] (map (fn [x _] x) s (drop n s)))) + +(defn take-last + "Returns a seq of the last n items in coll. Depending on the type + of coll may be no better than linear time. For vectors, see also subvec." + [n coll] + (loop [s (seq coll), lead (seq (drop n coll))] + (if lead + (recur (next s) (next lead)) + s))) + +(defn drop-while + "Returns a lazy sequence of the items in coll starting from the + first item for which (pred item) returns logical false. Returns a + stateful transducer when no collection is provided." + ([pred] + (fn [rf] + (let [da (volatile! true)] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (let [drop? @da] + (if (and drop? (pred input)) + result + (do + (vreset! da nil) + (rf result input))))))))) + ([pred coll] + (let [step (fn [pred coll] + (let [s (seq coll)] + (if (and s (pred (first s))) + (recur pred (rest s)) + s)))] + (lazy-seq (step pred coll))))) + +(deftype Cycle [meta all prev ^:mutable current ^:mutable _next] + Object + (toString [coll] + (pr-str* coll)) + (currentval [coll] + (when-not ^seq current + (if-let [c (next prev)] + (set! current c) + (set! current all))) + current) + + IPending + (-realized? [coll] + (some? current)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (Cycle. new-meta all prev current _next))) + + IMeta + (-meta [coll] meta) + + ISeq + (-first [coll] + (first (.currentval coll))) + (-rest [coll] + (when (nil? _next) + (set! _next (Cycle. nil all (.currentval coll) nil nil))) + _next) + + INext + (-next [coll] + (-rest coll)) + + ICollection + (-conj [coll o] (cons o coll)) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + ISequential + ISeqable + (-seq [coll] coll) + + IReduce + (-reduce [coll f] + (loop [s (.currentval coll) ret (first s)] + (let [s (or (next s) all) + ret (f ret (first s))] + (if (reduced? ret) + @ret + (recur s ret))))) + (-reduce [coll f start] + (loop [s (.currentval coll) ret start] + (let [ret (f ret (first s))] + (if (reduced? ret) + @ret + (recur (or (next s) all) ret)))))) + +(defn cycle + "Returns a lazy (infinite!) sequence of repetitions of the items in coll." + [coll] (if-let [vals (seq coll)] + (Cycle. nil vals nil vals nil) + (.-EMPTY List))) + +(defn split-at + "Returns a vector of [(take n coll) (drop n coll)]" + [n coll] + [(take n coll) (drop n coll)]) + +(deftype Repeat [meta count val ^:mutable next ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x count)) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IPending + (-realized? [coll] false) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (Repeat. new-meta count val next nil))) + + IMeta + (-meta [coll] meta) + + ISeq + (-first [coll] + val) + (-rest [coll] + (if (nil? next) + (if (> count 1) + (do + (set! next (Repeat. nil (dec count) val nil nil)) + next) + (if (== -1 count) + coll + ())) + next)) + + INext + (-next [coll] + (if (nil? next) + (if (> count 1) + (do + (set! next (Repeat. nil (dec count) val nil nil)) + next) + (if (== -1 count) + coll + nil)) + next)) + + ICollection + (-conj [coll o] (cons o coll)) + + IDrop + (-drop [coll n] + (if (== count -1) + coll + (let [dropped-count (- count n)] + (when (pos? dropped-count) + (Repeat. nil dropped-count val nil nil))))) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + ISequential + ISeqable + (-seq [coll] coll) + + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IReduce + (-reduce [coll f] + (if (== count -1) + (loop [ret (f val val)] + (if (reduced? ret) + @ret + (recur (f ret val)))) + (loop [i 1 ret val] + (if (< i count) + (let [ret (f ret val)] + (if (reduced? ret) + @ret + (recur (inc i) ret))) + ret)))) + (-reduce [coll f start] + (if (== count -1) + (loop [ret (f start val)] + (if (reduced? ret) + @ret + (recur (f ret val)))) + (loop [i 0 ret start] + (if (< i count) + (let [ret (f ret val)] + (if (reduced? ret) + @ret + (recur (inc i) ret))) + ret))))) + +(defn repeat + "Returns a lazy (infinite!, or length n if supplied) sequence of xs." + ([x] (Repeat. nil -1 x nil nil)) + ([n x] (if (pos? n) + (Repeat. nil n x nil nil) + (.-EMPTY List)))) + +(defn replicate + "DEPRECATED: Use 'repeat' instead. + Returns a lazy seq of n xs." + [n x] (take n (repeat x))) + +(defn repeatedly + "Takes a function of no args, presumably with side effects, and + returns an infinite (or length n if supplied) lazy sequence of calls + to it" + ([f] (lazy-seq (cons (f) (repeatedly f)))) + ([n f] (take n (repeatedly f)))) + +(def ^:private UNREALIZED-SEED #js {}) + +(deftype Iterate [meta f prev-seed ^:mutable seed ^:mutable next] + Object + (toString [coll] + (pr-str* coll)) + + IPending + (-realized? [coll] + (not (identical? seed UNREALIZED-SEED))) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (Iterate. new-meta f prev-seed seed next))) + + IMeta + (-meta [coll] meta) + + ISeq + (-first [coll] + (when (identical? UNREALIZED-SEED seed) + (set! seed (f prev-seed))) + seed) + (-rest [coll] + (when (nil? next) + (set! next (Iterate. nil f (-first coll) UNREALIZED-SEED nil))) + next) + + INext + (-next [coll] + (-rest coll)) + + ICollection + (-conj [coll o] (cons o coll)) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + ISequential + ISeqable + (-seq [coll] coll) + + IReduce + (-reduce [coll rf] + (let [first (-first coll) + v (f first)] + (loop [ret (rf first v) v v] + (if (reduced? ret) + @ret + (let [v (f v)] + (recur (rf ret v) v)))))) + (-reduce [coll rf start] + (let [v (-first coll)] + (loop [ret (rf start v) v v] + (if (reduced? ret) + @ret + (let [v (f v)] + (recur (rf ret v) v))))))) + +(defn iterate + "Returns a lazy sequence of x, (f x), (f (f x)) etc. f must be free of side-effects" + {:added "1.0"} + [f x] (Iterate. nil f nil x nil)) + +(defn interleave + "Returns a lazy seq of the first item in each coll, then the second etc." + ([] ()) + ([c1] (lazy-seq c1)) + ([c1 c2] + (lazy-seq + (let [s1 (seq c1) s2 (seq c2)] + (when (and s1 s2) + (cons (first s1) (cons (first s2) + (interleave (rest s1) (rest s2)))))))) + ([c1 c2 & colls] + (lazy-seq + (let [ss (map seq (conj colls c2 c1))] + (when (every? identity ss) + (concat (map first ss) (apply interleave (map rest ss)))))))) + +(defn interpose + "Returns a lazy seq of the elements of coll separated by sep. + Returns a stateful transducer when no collection is provided." + ([sep] + (fn [rf] + (let [started (volatile! false)] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (if @started + (let [sepr (rf result sep)] + (if (reduced? sepr) + sepr + (rf sepr input))) + (do + (vreset! started true) + (rf result input)))))))) + ([sep coll] (drop 1 (interleave (repeat sep) coll)))) + + + +(defn- flatten1 + "Take a collection of collections, and return a lazy seq + of items from the inner collection" + [colls] + (let [cat (fn cat [coll colls] + (lazy-seq + (if-let [coll (seq coll)] + (cons (first coll) (cat (rest coll) colls)) + (when (seq colls) + (cat (first colls) (rest colls))))))] + (cat nil colls))) + +(declare cat) + +(defn mapcat + "Returns the result of applying concat to the result of applying map + to f and colls. Thus function f should return a collection. Returns + a transducer when no collections are provided" + {:added "1.0" + :static true} + ([f] (comp (map f) cat)) + ([f & colls] + (apply concat (apply map f colls)))) + +(defn filter + "Returns a lazy sequence of the items in coll for which + (pred item) returns logical true. pred must be free of side-effects. + Returns a transducer when no collection is provided." + ([pred] + (fn [rf] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (if (pred input) + (rf result input) + result))))) + ([pred coll] + (lazy-seq + (when-let [s (seq coll)] + (if (chunked-seq? s) + (let [c (chunk-first s) + size (count c) + b (chunk-buffer size)] + (dotimes [i size] + (when (pred (-nth c i)) + (chunk-append b (-nth c i)))) + (chunk-cons (chunk b) (filter pred (chunk-rest s)))) + (let [f (first s) r (rest s)] + (if (pred f) + (cons f (filter pred r)) + (filter pred r)))))))) + +(defn remove + "Returns a lazy sequence of the items in coll for which + (pred item) returns logical false. pred must be free of side-effects. + Returns a transducer when no collection is provided." + ([pred] (filter (complement pred))) + ([pred coll] + (filter (complement pred) coll))) + +(defn tree-seq + "Returns a lazy sequence of the nodes in a tree, via a depth-first walk. + branch? must be a fn of one arg that returns true if passed a node + that can have children (but may not). children must be a fn of one + arg that returns a sequence of the children. Will only be called on + nodes for which branch? returns true. Root is the root node of the + tree." + [branch? children root] + (let [walk (fn walk [node] + (lazy-seq + (cons node + (when (branch? node) + (mapcat walk (children node))))))] + (walk root))) + +(defn flatten + "Takes any nested combination of sequential things (lists, vectors, + etc.) and returns their contents as a single, flat sequence. + (flatten nil) returns nil." + [x] + (filter #(not (sequential? %)) + (rest (tree-seq sequential? seq x)))) + +(defn into + "Returns a new coll consisting of to-coll with all of the items of + from-coll conjoined. A transducer may be supplied." + ([] []) + ([to] to) + ([to from] + (if-not (nil? to) + (if (implements? IEditableCollection to) + (-with-meta (persistent! (reduce -conj! (transient to) from)) (meta to)) + (reduce -conj to from)) + (reduce conj to from))) + ([to xform from] + (if (implements? IEditableCollection to) + (let [tm (meta to) + rf (fn + ([coll] (-> (persistent! coll) (-with-meta tm))) + ([coll v] (conj! coll v)))] + (transduce xform rf (transient to) from)) + (transduce xform conj to from)))) + +(defn mapv + "Returns a vector consisting of the result of applying f to the + set of first items of each coll, followed by applying f to the set + of second items in each coll, until any one of the colls is + exhausted. Any remaining items in other colls are ignored. Function + f should accept number-of-colls arguments." + ([f coll] + (-> (reduce (fn [v o] (conj! v (f o))) (transient []) coll) + persistent!)) + ([f c1 c2] + (into [] (map f c1 c2))) + ([f c1 c2 c3] + (into [] (map f c1 c2 c3))) + ([f c1 c2 c3 & colls] + (into [] (apply map f c1 c2 c3 colls)))) + +(defn filterv + "Returns a vector of the items in coll for which + (pred item) returns logical true. pred must be free of side-effects." + [pred coll] + (-> (reduce (fn [v o] (if (pred o) (conj! v o) v)) + (transient []) + coll) + persistent!)) + +(defn partition + "Returns a lazy sequence of lists of n items each, at offsets step + apart. If step is not supplied, defaults to n, i.e. the partitions + do not overlap. If a pad collection is supplied, use its elements as + necessary to complete last partition up to n items. In case there are + not enough padding elements, return a partition with less than n items." + ([n coll] + (partition n n coll)) + ([n step coll] + (lazy-seq + (when-let [s (seq coll)] + (let [p (take n s)] + (when (== n (count p)) + (cons p (partition n step (drop step s)))))))) + ([n step pad coll] + (lazy-seq + (when-let [s (seq coll)] + (let [p (take n s)] + (if (== n (count p)) + (cons p (partition n step pad (drop step s))) + (list (take n (concat p pad))))))))) + +(defn get-in + "Returns the value in a nested associative structure, + where ks is a sequence of keys. Returns nil if the key is not present, + or the not-found value if supplied." + {:added "1.2" + :static true} + ([m ks] + (loop [m m + ks (seq ks)] + (if (nil? ks) + m + (recur (get m (first ks)) + (next ks))))) + ([m ks not-found] + (loop [sentinel lookup-sentinel + m m + ks (seq ks)] + (if-not (nil? ks) + (let [m (get m (first ks) sentinel)] + (if (identical? sentinel m) + not-found + (recur sentinel m (next ks)))) + m)))) + +(defn assoc-in + "Associates a value in a nested associative structure, where ks is a + sequence of keys and v is the new value and returns a new nested structure. + If any levels do not exist, hash-maps will be created." + [m [k & ks] v] + (if ks + (assoc m k (assoc-in (get m k) ks v)) + (assoc m k v))) + +(defn update-in + "'Updates' a value in a nested associative structure, where ks is a + sequence of keys and f is a function that will take the old value + and any supplied args and return the new value, and returns a new + nested structure. If any levels do not exist, hash-maps will be + created." + ([m [k & ks] f] + (if ks + (assoc m k (update-in (get m k) ks f)) + (assoc m k (f (get m k))))) + ([m [k & ks] f a] + (if ks + (assoc m k (update-in (get m k) ks f a)) + (assoc m k (f (get m k) a)))) + ([m [k & ks] f a b] + (if ks + (assoc m k (update-in (get m k) ks f a b)) + (assoc m k (f (get m k) a b)))) + ([m [k & ks] f a b c] + (if ks + (assoc m k (update-in (get m k) ks f a b c)) + (assoc m k (f (get m k) a b c)))) + ([m [k & ks] f a b c & args] + (if ks + (assoc m k (apply update-in (get m k) ks f a b c args)) + (assoc m k (apply f (get m k) a b c args))))) + +(defn update + "'Updates' a value in an associative structure, where k is a + key and f is a function that will take the old value + and any supplied args and return the new value, and returns a new + structure. If the key does not exist, nil is passed as the old value." + ([m k f] + (assoc m k (f (get m k)))) + ([m k f x] + (assoc m k (f (get m k) x))) + ([m k f x y] + (assoc m k (f (get m k) x y))) + ([m k f x y z] + (assoc m k (f (get m k) x y z))) + ([m k f x y z & more] + (assoc m k (apply f (get m k) x y z more)))) + +;;; PersistentVector + +(deftype VectorNode [edit arr]) + +(defn- pv-fresh-node [edit] + (VectorNode. edit (make-array 32))) + +(defn- pv-aget [node idx] + (aget (.-arr node) idx)) + +(defn- pv-aset [node idx val] + (aset (.-arr node) idx val)) + +(defn- pv-clone-node [node] + (VectorNode. (.-edit node) (aclone (.-arr node)))) + +(defn- tail-off [pv] + (let [cnt (.-cnt pv)] + (if (< cnt 32) + 0 + (bit-shift-left (bit-shift-right-zero-fill (dec cnt) 5) 5)))) + +(defn- new-path [edit level node] + (loop [ll level + ret node] + (if (zero? ll) + ret + (let [embed ret + r (pv-fresh-node edit) + _ (pv-aset r 0 embed)] + (recur (- ll 5) r))))) + +(defn- push-tail [pv level parent tailnode] + (let [ret (pv-clone-node parent) + subidx (bit-and (bit-shift-right-zero-fill (dec (.-cnt pv)) level) 0x01f)] + (if (== 5 level) + (do + (pv-aset ret subidx tailnode) + ret) + (let [child (pv-aget parent subidx)] + (if-not (nil? child) + (let [node-to-insert (push-tail pv (- level 5) child tailnode)] + (pv-aset ret subidx node-to-insert) + ret) + (let [node-to-insert (new-path nil (- level 5) tailnode)] + (pv-aset ret subidx node-to-insert) + ret)))))) + +(defn- vector-index-out-of-bounds [i cnt] + (throw (js/Error. (str_ "No item " i " in vector of length " cnt)))) + +(defn- first-array-for-longvec [pv] + ;; invariants: (count pv) > 32. + (loop [node (.-root pv) + level (.-shift pv)] + (if (pos? level) + (recur (pv-aget node 0) (- level 5)) + (.-arr node)))) + +(defn- unchecked-array-for [pv i] + ;; invariant: i is a valid index of pv (use array-for if unknown). + (if (>= i (tail-off pv)) + (.-tail pv) + (loop [node (.-root pv) + level (.-shift pv)] + (if (pos? level) + (recur (pv-aget node (bit-and (bit-shift-right-zero-fill i level) 0x01f)) + (- level 5)) + (.-arr node))))) + +(defn- array-for [pv i] + (if (and (<= 0 i) (< i (.-cnt pv))) + (unchecked-array-for pv i) + (vector-index-out-of-bounds i (.-cnt pv)))) + +(defn- do-assoc [pv level node i val] + (let [ret (pv-clone-node node)] + (if (zero? level) + (do + (pv-aset ret (bit-and i 0x01f) val) + ret) + (let [subidx (bit-and (bit-shift-right-zero-fill i level) 0x01f)] + (pv-aset ret subidx (do-assoc pv (- level 5) (pv-aget node subidx) i val)) + ret)))) + +(defn- pop-tail [pv level node] + (let [subidx (bit-and (bit-shift-right-zero-fill (- (.-cnt pv) 2) level) 0x01f)] + (cond + (> level 5) (let [new-child (pop-tail pv (- level 5) (pv-aget node subidx))] + (if (and (nil? new-child) (zero? subidx)) + nil + (let [ret (pv-clone-node node)] + (pv-aset ret subidx new-child) + ret))) + (zero? subidx) nil + :else (let [ret (pv-clone-node node)] + (pv-aset ret subidx nil) + ret)))) + +(deftype RangedIterator [^:mutable i ^:mutable base ^:mutable arr v start end] + Object + (hasNext [this] + (< i end)) + (next [this] + (when (== (- i base) 32) + (set! arr (unchecked-array-for v i)) + (set! base (+ base 32))) + (let [ret (aget arr (bit-and i 0x01f))] + (set! i (inc i)) + ret))) + +(defn ranged-iterator [v start end] + (let [i start] + (RangedIterator. i (- i (js-mod i 32)) + (when (< start (count v)) + (unchecked-array-for v i)) + v start end))) + +(defn- pv-reduce + ([pv f start end] + (if (< start end) + (pv-reduce pv f (nth pv start) (inc start) end) + (f))) + ([pv f init start end] + (loop [acc init i start arr (unchecked-array-for pv start)] + (if (< i end) + (let [j (bit-and i 0x01f) + arr (if (zero? j) (unchecked-array-for pv i) arr) + nacc (f acc (aget arr j))] + (if (reduced? nacc) + @nacc + (recur nacc (inc i) arr))) + acc)))) + +(declare tv-editable-root tv-editable-tail TransientVector + pr-sequential-writer pr-writer chunked-seq) + +(defprotocol APersistentVector + "Marker protocol") + +(deftype PersistentVector [meta cnt shift root tail ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + ICloneable + (-clone [_] (PersistentVector. meta cnt shift root tail __hash)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (PersistentVector. new-meta cnt shift root tail __hash))) + + IMeta + (-meta [coll] meta) + + IStack + (-peek [coll] + (when (> cnt 0) + (-nth coll (dec cnt)))) + (-pop [coll] + (cond + (zero? cnt) (throw (js/Error. "Can't pop empty vector")) + (== 1 cnt) (-with-meta (.-EMPTY PersistentVector) meta) + (< 1 (- cnt (tail-off coll))) + (PersistentVector. meta (dec cnt) shift root (.slice tail 0 -1) nil) + :else (let [new-tail (unchecked-array-for coll (- cnt 2)) + nr (pop-tail coll shift root) + new-root (if (nil? nr) (.-EMPTY-NODE PersistentVector) nr) + cnt-1 (dec cnt)] + (if (and (< 5 shift) (nil? (pv-aget new-root 1))) + (PersistentVector. meta cnt-1 (- shift 5) (pv-aget new-root 0) new-tail nil) + (PersistentVector. meta cnt-1 shift new-root new-tail nil))))) + + ICollection + (-conj [coll o] + (if (< (- cnt (tail-off coll)) 32) + (let [len (alength tail) + new-tail (make-array (inc len))] + (dotimes [i len] + (aset new-tail i (aget tail i))) + (aset new-tail len o) + (PersistentVector. meta (inc cnt) shift root new-tail nil)) + (let [root-overflow? (> (bit-shift-right-zero-fill cnt 5) (bit-shift-left 1 shift)) + new-shift (if root-overflow? (+ shift 5) shift) + new-root (if root-overflow? + (let [n-r (pv-fresh-node nil)] + (pv-aset n-r 0 root) + (pv-aset n-r 1 (new-path nil shift (VectorNode. nil tail))) + n-r) + (push-tail coll shift root (VectorNode. nil tail)))] + (PersistentVector. meta (inc cnt) new-shift new-root (array o) nil)))) + + IEmptyableCollection + (-empty [coll] (-with-meta (.-EMPTY PersistentVector) meta)) + + ISequential + IEquiv + (-equiv [coll other] + (if (instance? PersistentVector other) + (if (== cnt (count other)) + (let [me-iter (-iterator coll) + you-iter (-iterator other)] + (loop [] + (if ^boolean (.hasNext me-iter) + (let [x (.next me-iter) + y (.next you-iter)] + (if (= x y) + (recur) + false)) + true))) + false) + (equiv-sequential coll other))) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + ISeqable + (-seq [coll] + (cond + (zero? cnt) nil + (<= cnt 32) (IndexedSeq. tail 0 nil) + :else (chunked-seq coll (first-array-for-longvec coll) 0 0))) + + IDrop + (-drop [coll n] + (if (< n cnt) + (let [offset (js-mod n 32)] + (chunked-seq coll (unchecked-array-for coll n) (- n offset) offset)) + nil)) + + ICounted + (-count [coll] cnt) + + IIndexed + (-nth [coll n] + (aget (array-for coll n) (bit-and n 0x01f))) + (-nth [coll n not-found] + (if (and (<= 0 n) (< n cnt)) + (aget (unchecked-array-for coll n) (bit-and n 0x01f)) + not-found)) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] (if (number? k) + (-nth coll k not-found) + not-found)) + + IAssociative + (-assoc [coll k v] + (if (number? k) + (-assoc-n coll k v) + (throw (js/Error. "Vector's key for assoc must be a number.")))) + (-contains-key? [coll k] + (if (integer? k) + (and (<= 0 k) (< k cnt)) + false)) + + IFind + (-find [coll n] + (when (and (<= 0 n) (< n cnt)) + (MapEntry. n (aget (unchecked-array-for coll n) (bit-and n 0x01f)) nil))) + + APersistentVector + IVector + (-assoc-n [coll n val] + (cond + (and (<= 0 n) (< n cnt)) + (if (<= (tail-off coll) n) + (let [new-tail (aclone tail)] + (aset new-tail (bit-and n 0x01f) val) + (PersistentVector. meta cnt shift root new-tail nil)) + (PersistentVector. meta cnt shift (do-assoc coll shift root n val) tail nil)) + (== n cnt) (-conj coll val) + :else (throw (js/Error. (str_ "Index " n " out of bounds [0," cnt "]"))))) + + IReduce + (-reduce [v f] + (pv-reduce v f 0 cnt)) + (-reduce [v f init] + (loop [i 0 init init] + (if (< i cnt) + (let [arr (unchecked-array-for v i) + len (alength arr) + init (loop [j 0 init init] + (if (< j len) + (let [init (f init (aget arr j))] + (if (reduced? init) + init + (recur (inc j) init))) + init))] + (if (reduced? init) + @init + (recur (+ i len) init))) + init))) + + IKVReduce + (-kv-reduce [v f init] + (loop [i 0 init init] + (if (< i cnt) + (let [arr (unchecked-array-for v i) + len (alength arr) + init (loop [j 0 init init] + (if (< j len) + (let [init (f init (+ j i) (aget arr j))] + (if (reduced? init) + init + (recur (inc j) init))) + init))] + (if (reduced? init) + @init + (recur (+ i len) init))) + init))) + + IFn + (-invoke [coll k] + (if (number? k) + (-nth coll k) + (throw (js/Error. "Key must be integer")))) + + IEditableCollection + (-as-transient [coll] + (TransientVector. cnt shift (tv-editable-root root) (tv-editable-tail tail))) + + IReversible + (-rseq [coll] + (when (pos? cnt) + (RSeq. coll (dec cnt) nil))) + + IIterable + (-iterator [this] + (ranged-iterator this 0 cnt))) + +(set! (.-EMPTY-NODE PersistentVector) (VectorNode. nil (make-array 32))) + +(set! (.-EMPTY PersistentVector) + (PersistentVector. nil 0 5 (.-EMPTY-NODE PersistentVector) (array) empty-ordered-hash)) + +(set! (.-fromArray PersistentVector) + (fn [xs ^boolean no-clone] + (let [l (alength xs) + xs (if no-clone xs (aclone xs))] + (if (< l 32) + (PersistentVector. nil l 5 (.-EMPTY-NODE PersistentVector) xs nil) + (let [node (.slice xs 0 32) + v (PersistentVector. nil 32 5 (.-EMPTY-NODE PersistentVector) node nil)] + (loop [i 32 out (-as-transient v)] + (if (< i l) + (recur (inc i) (conj! out (aget xs i))) + (persistent! out)))))))) + +(es6-iterable PersistentVector) + +(declare map-entry?) + +(defn vec + "Creates a new vector containing the contents of coll. JavaScript arrays + will be aliased and should not be modified." + [coll] + (cond + (map-entry? coll) + [(key coll) (val coll)] + + (vector? coll) + (with-meta coll nil) + + (array? coll) + (.fromArray PersistentVector coll true) + + :else + (-persistent! + (reduce -conj! + (-as-transient (.-EMPTY PersistentVector)) + coll)))) + +(defn vector + "Creates a new vector containing the args." + [& args] + (if (and (instance? IndexedSeq args) (zero? (.-i args))) + (.fromArray PersistentVector (.-arr args) (not (array? (.-arr args)))) + (vec args))) + +(declare subvec) + +(deftype ChunkedSeq [vec node i off meta ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (chunked-seq vec node i off new-meta))) + IMeta + (-meta [coll] meta) + + ISeqable + (-seq [coll] coll) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + ASeq + ISeq + (-first [coll] + (aget node off)) + (-rest [coll] + (if (< (inc off) (alength node)) + (let [s (chunked-seq vec node i (inc off))] + (if (nil? s) + () + s)) + (-chunked-rest coll))) + + INext + (-next [coll] + (if (< (inc off) (alength node)) + (let [s (chunked-seq vec node i (inc off))] + (if (nil? s) + nil + s)) + (-chunked-next coll))) + + IDrop + (-drop [coll n] + (let [o (+ off n)] + (if (< o (alength node)) + (chunked-seq vec node i o) + (let [i (+ i o)] + (if (< i (-count vec)) + (let [new-offset (js-mod i 32)] + (chunked-seq vec (unchecked-array-for vec i) (- i new-offset) new-offset)) + nil))))) + + ICollection + (-conj [coll o] + (cons o coll)) + + IEmptyableCollection + (-empty [coll] + ()) + + ICounted + (-count [coll] + (- (-count vec) (+ i off))) + + IChunkedSeq + (-chunked-first [coll] + (array-chunk node off)) + (-chunked-rest [coll] + (let [end (+ i (alength node))] + (if (< end (-count vec)) + (chunked-seq vec (unchecked-array-for vec end) end 0) + ()))) + + IChunkedNext + (-chunked-next [coll] + (let [end (+ i (alength node))] + (when (< end (-count vec)) + (chunked-seq vec (unchecked-array-for vec end) end 0)))) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + IReduce + (-reduce [coll f] + (pv-reduce vec f (+ i off) (count vec))) + + (-reduce [coll f start] + (pv-reduce vec f start (+ i off) (count vec)))) + +(es6-iterable ChunkedSeq) + +(defn chunked-seq + ([vec i off] (ChunkedSeq. vec (array-for vec i) i off nil nil)) + ([vec node i off] (ChunkedSeq. vec node i off nil nil)) + ([vec node i off meta] + (ChunkedSeq. vec node i off meta nil))) + +(declare build-subvec) + +(deftype Subvec [meta v start end ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + ICloneable + (-clone [_] (Subvec. meta v start end __hash)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (build-subvec new-meta v start end __hash))) + + IMeta + (-meta [coll] meta) + + IStack + (-peek [coll] + (when-not (== start end) + (-nth v (dec end)))) + (-pop [coll] + (if (== start end) + (throw (js/Error. "Can't pop empty vector")) + (build-subvec meta v start (dec end) nil))) + + ICollection + (-conj [coll o] + (build-subvec meta (-assoc-n v end o) start (inc end) nil)) + + IEmptyableCollection + (-empty [coll] (-with-meta (.-EMPTY PersistentVector) meta)) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + ISeqable + (-seq [coll] + (let [subvec-seq (fn subvec-seq [i] + (when-not (== i end) + (cons (-nth v i) + (lazy-seq + (subvec-seq (inc i))))))] + (subvec-seq start))) + + IReversible + (-rseq [coll] + (if-not (== start end) + (RSeq. coll (dec (- end start)) nil))) + + ICounted + (-count [coll] (- end start)) + + IIndexed + (-nth [coll n] + (if (or (neg? n) (<= end (+ start n))) + (vector-index-out-of-bounds n (- end start)) + (-nth v (+ start n)))) + (-nth [coll n not-found] + (if (or (neg? n) (<= end (+ start n))) + not-found + (-nth v (+ start n) not-found))) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] (if (number? k) + (-nth coll k not-found) + not-found)) + + IAssociative + (-assoc [coll key val] + (if (number? key) + (-assoc-n coll key val) + (throw (js/Error. "Subvec's key for assoc must be a number.")))) + (-contains-key? [coll key] + (if (integer? key) + (and (<= 0 key) (< key (- end start))) + false)) + + IFind + (-find [coll n] + (when-not (neg? n) + (let [idx (+ start n)] + (when (< idx end) + (MapEntry. n (-lookup v idx) nil))))) + + IVector + (-assoc-n [coll n val] + (let [v-pos (+ start n)] + (if (or (neg? n) (<= (inc end) v-pos)) + (throw (js/Error. (str_ "Index " n " out of bounds [0," (-count coll) "]"))) + (build-subvec meta (assoc v v-pos val) start (unchecked-max end (inc v-pos)) nil)))) + + IReduce + (-reduce [coll f] + (if (implements? APersistentVector v) + (pv-reduce v f start end) + (ci-reduce coll f))) + (-reduce [coll f init] + (if (implements? APersistentVector v) + (pv-reduce v f init start end) + (ci-reduce coll f init))) + + IKVReduce + (-kv-reduce [coll f init] + (loop [i start j 0 init init] + (if (< i end) + (let [init (f init j (-nth v i))] + (if (reduced? init) + @init + (recur (inc i) (inc j) init))) + init))) + + IFn + (-invoke [coll k] + (-nth coll k)) + (-invoke [coll k not-found] + (-nth coll k not-found)) + + IIterable + (-iterator [coll] + (if (implements? APersistentVector v) + (ranged-iterator v start end) + (seq-iter coll)))) + +(es6-iterable Subvec) + +(defn- build-subvec [meta v start end __hash] + (if (instance? Subvec v) + (recur meta (.-v v) (+ (.-start v) start) (+ (.-start v) end) __hash) + (do + (when-not (vector? v) + (throw (js/Error. "v must satisfy IVector"))) + (when (or (neg? start) + (< end start) + (> end (count v))) + (throw (js/Error. "Index out of bounds"))) + (Subvec. meta v start end __hash)))) + +(defn subvec + "Returns a persistent vector of the items in vector from + start (inclusive) to end (exclusive). If end is not supplied, + defaults to (count vector). This operation is O(1) and very fast, as + the resulting vector shares structure with the original and no + trimming is done." + ([v start] + (subvec v start (count v))) + ([v start end] + (assert (and (not (nil? start)) (not (nil? end)))) + (build-subvec nil v (int start) (int end) nil))) + +(defn- tv-ensure-editable [edit node] + (if (identical? edit (.-edit node)) + node + (VectorNode. edit (aclone (.-arr node))))) + +(defn- tv-editable-root [node] + (VectorNode. (js-obj) (aclone (.-arr node)))) + +(defn- tv-editable-tail [tl] + (let [ret (make-array 32)] + (array-copy tl 0 ret 0 (alength tl)) + ret)) + +(defn- tv-push-tail [tv level parent tail-node] + (let [ret (tv-ensure-editable (.. tv -root -edit) parent) + subidx (bit-and (bit-shift-right-zero-fill (dec (.-cnt tv)) level) 0x01f)] + (pv-aset ret subidx + (if (== level 5) + tail-node + (let [child (pv-aget ret subidx)] + (if-not (nil? child) + (tv-push-tail tv (- level 5) child tail-node) + (new-path (.. tv -root -edit) (- level 5) tail-node))))) + ret)) + +(defn- tv-pop-tail [tv level node] + (let [node (tv-ensure-editable (.. tv -root -edit) node) + subidx (bit-and (bit-shift-right-zero-fill (- (.-cnt tv) 2) level) 0x01f)] + (cond + (> level 5) (let [new-child (tv-pop-tail + tv (- level 5) (pv-aget node subidx))] + (if (and (nil? new-child) (zero? subidx)) + nil + (do (pv-aset node subidx new-child) + node))) + (zero? subidx) nil + :else (do (pv-aset node subidx nil) + node)))) + +(defn- unchecked-editable-array-for [tv i] + ;; invariant: i is a valid index of tv. + (if (>= i (tail-off tv)) + (.-tail tv) + (let [root (.-root tv)] + (loop [node root + level (.-shift tv)] + (if (pos? level) + (recur (tv-ensure-editable + (.-edit root) + (pv-aget node + (bit-and (bit-shift-right-zero-fill i level) + 0x01f))) + (- level 5)) + (.-arr node)))))) + +(deftype TransientVector [^:mutable cnt + ^:mutable shift + ^:mutable root + ^:mutable tail] + ITransientCollection + (-conj! [tcoll o] + (if ^boolean (.-edit root) + (if (< (- cnt (tail-off tcoll)) 32) + (do (aset tail (bit-and cnt 0x01f) o) + (set! cnt (inc cnt)) + tcoll) + (let [tail-node (VectorNode. (.-edit root) tail) + new-tail (make-array 32)] + (aset new-tail 0 o) + (set! tail new-tail) + (if (> (bit-shift-right-zero-fill cnt 5) + (bit-shift-left 1 shift)) + (let [new-root-array (make-array 32) + new-shift (+ shift 5)] + (aset new-root-array 0 root) + (aset new-root-array 1 (new-path (.-edit root) shift tail-node)) + (set! root (VectorNode. (.-edit root) new-root-array)) + (set! shift new-shift) + (set! cnt (inc cnt)) + tcoll) + (let [new-root (tv-push-tail tcoll shift root tail-node)] + (set! root new-root) + (set! cnt (inc cnt)) + tcoll)))) + (throw (js/Error. "conj! after persistent!")))) + + (-persistent! [tcoll] + (if ^boolean (.-edit root) + (do (set! (.-edit root) nil) + (let [len (- cnt (tail-off tcoll)) + trimmed-tail (make-array len)] + (array-copy tail 0 trimmed-tail 0 len) + (PersistentVector. nil cnt shift root trimmed-tail nil))) + (throw (js/Error. "persistent! called twice")))) + + ITransientAssociative + (-assoc! [tcoll key val] + (if (number? key) + (-assoc-n! tcoll key val) + (throw (js/Error. "TransientVector's key for assoc! must be a number.")))) + + ITransientVector + (-assoc-n! [tcoll n val] + (if ^boolean (.-edit root) + (cond + (and (<= 0 n) (< n cnt)) + (if (<= (tail-off tcoll) n) + (do (aset tail (bit-and n 0x01f) val) + tcoll) + (let [new-root + ((fn go [level node] + (let [node (tv-ensure-editable (.-edit root) node)] + (if (zero? level) + (do (pv-aset node (bit-and n 0x01f) val) + node) + (let [subidx (bit-and (bit-shift-right-zero-fill n level) + 0x01f)] + (pv-aset node subidx + (go (- level 5) (pv-aget node subidx))) + node)))) + shift root)] + (set! root new-root) + tcoll)) + (== n cnt) (-conj! tcoll val) + :else + (throw + (js/Error. + (str_ "Index " n " out of bounds for TransientVector of length" cnt)))) + (throw (js/Error. "assoc! after persistent!")))) + + (-pop! [tcoll] + (if ^boolean (.-edit root) + (cond + (zero? cnt) (throw (js/Error. "Can't pop empty vector")) + (== 1 cnt) (do (set! cnt 0) tcoll) + (pos? (bit-and (dec cnt) 0x01f)) (do (set! cnt (dec cnt)) tcoll) + :else + (let [new-tail (unchecked-editable-array-for tcoll (- cnt 2)) + new-root (let [nr (tv-pop-tail tcoll shift root)] + (if-not (nil? nr) + nr + (VectorNode. (.-edit root) (make-array 32))))] + (if (and (< 5 shift) (nil? (pv-aget new-root 1))) + (let [new-root (tv-ensure-editable (.-edit root) (pv-aget new-root 0))] + (set! root new-root) + (set! shift (- shift 5)) + (set! cnt (dec cnt)) + (set! tail new-tail) + tcoll) + (do (set! root new-root) + (set! cnt (dec cnt)) + (set! tail new-tail) + tcoll)))) + (throw (js/Error. "pop! after persistent!")))) + + ICounted + (-count [coll] + (if ^boolean (.-edit root) + cnt + (throw (js/Error. "count after persistent!")))) + + IIndexed + (-nth [coll n] + (if ^boolean (.-edit root) + (aget (array-for coll n) (bit-and n 0x01f)) + (throw (js/Error. "nth after persistent!")))) + + (-nth [coll n not-found] + (if (and (<= 0 n) (< n cnt)) + (-nth coll n) + not-found)) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + + (-lookup [coll k not-found] + (cond + (not ^boolean (.-edit root)) (throw (js/Error. "lookup after persistent!")) + (number? k) (-nth coll k not-found) + :else not-found)) + + IFn + (-invoke [coll k] + (-lookup coll k)) + + (-invoke [coll k not-found] + (-lookup coll k not-found))) + +;;; PersistentQueue ;;; + +(deftype PersistentQueueIter [^:mutable fseq riter] + Object + (hasNext [_] + (or (and (some? fseq) (seq fseq)) (and (some? riter) (.hasNext riter)))) + (next [_] + (cond + (some? fseq) + (let [ret (first fseq)] + (set! fseq (next fseq)) + ret) + (and (some? riter) ^boolean (.hasNext riter)) + (.next riter) + :else (throw (js/Error. "No such element")))) + (remove [_] (js/Error. "Unsupported operation"))) + +(deftype PersistentQueueSeq [meta front rear ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (PersistentQueueSeq. new-meta front rear __hash))) + + IMeta + (-meta [coll] meta) + + ISeq + (-first [coll] (first front)) + (-rest [coll] + (if-let [f1 (next front)] + (PersistentQueueSeq. meta f1 rear nil) + (if (nil? rear) + (-empty coll) + (PersistentQueueSeq. meta rear nil nil)))) + + INext + (-next [coll] + (if-let [f1 (next front)] + (PersistentQueueSeq. meta f1 rear nil) + (when (some? rear) + (PersistentQueueSeq. meta rear nil nil)))) + + ICollection + (-conj [coll o] (cons o coll)) + + IEmptyableCollection + (-empty [coll] (-with-meta (.-EMPTY List) meta)) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + ISeqable + (-seq [coll] coll)) + +(es6-iterable PersistentQueueSeq) + +(deftype PersistentQueue [meta count front rear ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + ICloneable + (-clone [coll] (PersistentQueue. meta count front rear __hash)) + + IIterable + (-iterator [coll] + (PersistentQueueIter. front (-iterator rear))) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (PersistentQueue. new-meta count front rear __hash))) + + IMeta + (-meta [coll] meta) + + ISeq + (-first [coll] (first front)) + (-rest [coll] (rest (seq coll))) + + IStack + (-peek [coll] (first front)) + (-pop [coll] + (if front + (if-let [f1 (next front)] + (PersistentQueue. meta (dec count) f1 rear nil) + (PersistentQueue. meta (dec count) (seq rear) [] nil)) + coll)) + + ICollection + (-conj [coll o] + (if front + (PersistentQueue. meta (inc count) front (conj (or rear []) o) nil) + (PersistentQueue. meta (inc count) (conj front o) [] nil))) + + IEmptyableCollection + (-empty [coll] (-with-meta (.-EMPTY PersistentQueue) meta)) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + ISeqable + (-seq [coll] + (let [rear (seq rear)] + (if (or front rear) + (PersistentQueueSeq. nil front (seq rear) nil)))) + + ICounted + (-count [coll] count)) + +(set! (.-EMPTY PersistentQueue) (PersistentQueue. nil 0 nil (.-EMPTY PersistentVector) empty-ordered-hash)) + +(es6-iterable PersistentQueue) + +(deftype NeverEquiv [] + Object + (equiv [this other] + (-equiv this other)) + IEquiv + (-equiv [o other] false)) + +(def ^:private never-equiv (NeverEquiv.)) + +(defn equiv-map + "Test map equivalence. Returns true if x equals y, otherwise returns false." + [x y] + (boolean + (when (and (map? y) (not (record? y))) + ; assume all maps are counted + (when (== (count x) (count y)) + (if (satisfies? IKVReduce x) + (reduce-kv + (fn [_ k v] + (if (= (get y k never-equiv) v) + true + (reduced false))) + true x) + (every? + (fn [xkv] + (= (get y (first xkv) never-equiv) (second xkv))) + x)))))) + +;; Record Iterator +(deftype RecordIter [^:mutable i record base-count fields ext-map-iter] + Object + (hasNext [_] + (or (< i base-count) (.hasNext ext-map-iter))) + (next [_] + (if (< i base-count) + (let [k (nth fields i)] + (set! i (inc i)) + (MapEntry. k (-lookup record k) nil)) + (.next ext-map-iter))) + (remove [_] (js/Error. "Unsupported operation"))) + +(deftype ES6EntriesIterator [^:mutable s] + Object + (next [_] + (if-not (nil? s) + (let [[k v] (first s)] + (set! s (next s)) + #js {:value #js [k v] :done false}) + #js {:value nil :done true}))) + +(defn es6-entries-iterator [coll] + (ES6EntriesIterator. (seq coll))) + +(deftype ES6SetEntriesIterator [^:mutable s] + Object + (next [_] + (if-not (nil? s) + (let [x (first s)] + (set! s (next s)) + #js {:value #js [x x] :done false}) + #js {:value nil :done true}))) + +(defn es6-set-entries-iterator [coll] + (ES6SetEntriesIterator. (seq coll))) + +;;; PersistentArrayMap + +(defn- array-index-of-nil? [arr] + (let [len (alength arr)] + (loop [i 0] + (cond + (<= len i) -1 + (nil? (aget arr i)) i + :else (recur (+ i 2)))))) + +(defn- array-index-of-keyword? [arr k] + (let [len (alength arr) + kstr (.-fqn k)] + (loop [i 0] + (cond + (<= len i) -1 + (and (keyword? (aget arr i)) + (identical? kstr (.-fqn (aget arr i)))) i + :else (recur (+ i 2)))))) + +(defn- array-index-of-symbol? [arr k] + (let [len (alength arr) + kstr (.-str k)] + (loop [i 0] + (cond + (<= len i) -1 + (and (symbol? (aget arr i)) + (identical? kstr (.-str (aget arr i)))) i + :else (recur (+ i 2)))))) + +(defn- array-index-of-identical? [arr k] + (let [len (alength arr)] + (loop [i 0] + (cond + (<= len i) -1 + (identical? k (aget arr i)) i + :else (recur (+ i 2)))))) + +(defn- array-index-of-equiv? [arr k] + (let [len (alength arr)] + (loop [i 0] + (cond + (<= len i) -1 + (= k (aget arr i)) i + :else (recur (+ i 2)))))) + +(defn array-index-of [arr k] + (cond + (keyword? k) (array-index-of-keyword? arr k) + + (or (string? k) (number? k)) + (array-index-of-identical? arr k) + + (symbol? k) (array-index-of-symbol? arr k) + + (nil? k) + (array-index-of-nil? arr) + + :else (array-index-of-equiv? arr k))) + +(defn- array-map-index-of [m k] + (array-index-of (.-arr m) k)) + +(defn- array-extend-kv [arr k v] + (let [l (alength arr) + narr (make-array (+ l 2))] + (loop [i 0] + (when (< i l) + (aset narr i (aget arr i)) + (recur (inc i)))) + (aset narr l k) + (aset narr (inc l) v) + narr)) + +(defn- array-map-extend-kv [m k v] + (array-extend-kv (.-arr m) k v)) + +(declare TransientArrayMap) + +(deftype MapEntry [key val ^:mutable __hash] + Object + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IMapEntry + (-key [node] key) + (-val [node] val) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IMeta + (-meta [node] nil) + + IWithMeta + (-with-meta [node meta] + (with-meta [key val] meta)) + + IStack + (-peek [node] val) + + (-pop [node] [key]) + + ICollection + (-conj [node o] [key val o]) + + IEmptyableCollection + (-empty [node] nil) + + ISequential + ISeqable + (-seq [node] (IndexedSeq. #js [key val] 0 nil)) + + IReversible + (-rseq [node] (IndexedSeq. #js [val key] 0 nil)) + + ICounted + (-count [node] 2) + + IIndexed + (-nth [node n] + (case n + 0 key + 1 val + (throw (js/Error. "Index out of bounds")))) + + (-nth [node n not-found] + (case n + 0 key + 1 val + not-found)) + + ILookup + (-lookup [node k] (-nth node k nil)) + (-lookup [node k not-found] (-nth node k not-found)) + + IAssociative + (-assoc [node k v] + (assoc [key val] k v)) + (-contains-key? [node k] + (case k + 0 true + 1 true + false)) + + IFind + (-find [node k] + (case k + 0 (MapEntry. 0 key nil) + 1 (MapEntry. 1 val nil) + nil)) + + IVector + (-assoc-n [node n v] + (-assoc-n [key val] n v)) + + IReduce + (-reduce [node f] + (ci-reduce node f)) + + (-reduce [node f start] + (ci-reduce node f start)) + + IFn + (-invoke [node k] + (-nth node k)) + + (-invoke [node k not-found] + (-nth node k not-found))) + +(defn map-entry? + "Returns true if x satisfies IMapEntry" + [x] + (implements? IMapEntry x)) + +(deftype PersistentArrayMapSeq [arr i _meta] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IMeta + (-meta [coll] _meta) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta _meta) + coll + (PersistentArrayMapSeq. arr i new-meta))) + + ICounted + (-count [coll] + (/ (- (alength arr) i) 2)) + + ISeqable + (-seq [coll] coll) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + ICollection + (-conj [coll o] + (cons o coll)) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + IHash + (-hash [coll] (hash-ordered-coll coll)) + + ISeq + (-first [coll] + (MapEntry. (aget arr i) (aget arr (inc i)) nil)) + + (-rest [coll] + (if (< i (- (alength arr) 2)) + (PersistentArrayMapSeq. arr (+ i 2) nil) + ())) + + INext + (-next [coll] + (when (< i (- (alength arr) 2)) + (PersistentArrayMapSeq. arr (+ i 2) nil))) + + IDrop + (-drop [coll n] + (when (< n (-count coll)) + (PersistentArrayMapSeq. arr (+ i (* 2 n)) nil))) + + IReduce + (-reduce [coll f] (seq-reduce f coll)) + (-reduce [coll f start] (seq-reduce f start coll))) + +(es6-iterable PersistentArrayMapSeq) + +(defn persistent-array-map-seq [arr i _meta] + (when (<= i (- (alength arr) 2)) + (PersistentArrayMapSeq. arr i _meta))) + +(declare keys vals) + +(deftype PersistentArrayMapIterator [arr ^:mutable i cnt] + Object + (hasNext [_] + (< i cnt)) + (next [_] + (let [ret (MapEntry. (aget arr i) (aget arr (inc i)) nil)] + (set! i (+ i 2)) + ret))) + +(deftype PersistentArrayMap [meta cnt arr ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (keys [coll] + (es6-iterator (keys coll))) + (entries [coll] + (es6-entries-iterator (seq coll))) + (values [coll] + (es6-iterator (vals coll))) + (has [coll k] + (contains? coll k)) + (get [coll k not-found] + (-lookup coll k not-found)) + (forEach [coll f] + (doseq [[k v] coll] + (f v k))) + + ICloneable + (-clone [_] (PersistentArrayMap. meta cnt arr __hash)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (PersistentArrayMap. new-meta cnt arr __hash))) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (loop [ret coll es (seq entry)] + (if (nil? es) + ret + (let [e (first es)] + (if (vector? e) + (recur (-assoc ret (-nth e 0) (-nth e 1)) + (next es)) + (throw (js/Error. "conj on a map takes map entries or seqables of map entries")))))))) + + IEmptyableCollection + (-empty [coll] (-with-meta (.-EMPTY PersistentArrayMap) meta)) + + IEquiv + (-equiv [coll other] + (if (and (map? other) (not (record? other))) + (let [alen (alength arr) + ^not-native other other] + (if (== cnt (-count other)) + (loop [i 0] + (if (< i alen) + (let [v (-lookup other (aget arr i) lookup-sentinel)] + (if-not (identical? v lookup-sentinel) + (if (= (aget arr (inc i)) v) + (recur (+ i 2)) + false) + false)) + true)) + false)) + false)) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + IIterable + (-iterator [this] + (PersistentArrayMapIterator. arr 0 (* cnt 2))) + + ISeqable + (-seq [coll] + (persistent-array-map-seq arr 0 nil)) + + IDrop + (-drop [coll n] + (when-some [s (-seq coll)] + (-drop s n))) + + ICounted + (-count [coll] cnt) + + ILookup + (-lookup [coll k] + (-lookup coll k nil)) + + (-lookup [coll k not-found] + (let [idx (array-map-index-of coll k)] + (if (== idx -1) + not-found + (aget arr (inc idx))))) + + IAssociative + (-assoc [coll k v] + (let [idx (array-map-index-of coll k)] + (cond + (== idx -1) + (if (< cnt (.-HASHMAP-THRESHOLD PersistentArrayMap)) + (let [arr (array-map-extend-kv coll k v)] + (PersistentArrayMap. meta (inc cnt) arr nil)) + (-> (into (.-EMPTY PersistentHashMap) coll) + (-assoc k v) + (-with-meta meta))) + + (identical? v (aget arr (inc idx))) + coll + + :else + (let [arr (doto (aclone arr) + (aset (inc idx) v))] + (PersistentArrayMap. meta cnt arr nil))))) + + (-contains-key? [coll k] + (not (== (array-map-index-of coll k) -1))) + + IFind + (-find [coll k] + (let [idx (array-map-index-of coll k)] + (when-not (== idx -1) + (MapEntry. (aget arr idx) (aget arr (inc idx)) nil)))) + + IMap + (-dissoc [coll k] + (let [idx (array-map-index-of coll k)] + (if (>= idx 0) + (let [len (alength arr) + new-len (- len 2)] + (if (zero? new-len) + (-empty coll) + (let [new-arr (make-array new-len)] + (loop [s 0 d 0] + (cond + (>= s len) (PersistentArrayMap. meta (dec cnt) new-arr nil) + (= k (aget arr s)) (recur (+ s 2) d) + :else (do (aset new-arr d (aget arr s)) + (aset new-arr (inc d) (aget arr (inc s))) + (recur (+ s 2) (+ d 2)))))))) + coll))) + + IKVReduce + (-kv-reduce [coll f init] + (let [len (alength arr)] + (loop [i 0 init init] + (if (< i len) + (let [init (f init (aget arr i) (aget arr (inc i)))] + (if (reduced? init) + @init + (recur (+ i 2) init))) + init)))) + + IReduce + (-reduce [coll f] + (iter-reduce coll f)) + (-reduce [coll f start] + (iter-reduce coll f start)) + + IFn + (-invoke [coll k] + (-lookup coll k)) + + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IEditableCollection + (-as-transient [coll] + (TransientArrayMap. (js-obj) (alength arr) (aclone arr)))) + +(set! (.-EMPTY PersistentArrayMap) (PersistentArrayMap. nil 0 (array) empty-unordered-hash)) + +(set! (.-HASHMAP-THRESHOLD PersistentArrayMap) 8) + +(set! (.-fromArray PersistentArrayMap) + (fn [arr ^boolean no-clone ^boolean no-check] + (as-> (if no-clone arr (aclone arr)) arr + (if no-check + arr + (let [ret (array)] + (loop [i 0] + (when (< i (alength arr)) + (let [k (aget arr i) + v (aget arr (inc i)) + idx (array-index-of ret k)] + (when (== idx -1) + (.push ret k) + (.push ret v))) + (recur (+ i 2)))) + ret)) + (let [cnt (/ (alength arr) 2)] + (PersistentArrayMap. nil cnt arr nil))))) + +(set! (.-createWithCheck PersistentArrayMap) + (fn [arr] + (let [ret (array)] + (loop [i 0] + (when (< i (alength arr)) + (let [k (aget arr i) + v (aget arr (inc i)) + idx (array-index-of ret k)] + (if (== idx -1) + (doto ret (.push k) (.push v)) + (throw (js/Error. (str_ "Duplicate key: " k))))) + (recur (+ i 2)))) + (let [cnt (/ (alength arr) 2)] + (PersistentArrayMap. nil cnt arr nil))))) + +(defn key-test [key other] + (cond + (identical? key other) true + (keyword-identical? key other) true + :else (= key other))) + +(defn- ^boolean pam-dupes? [arr] + (loop [i 0] + (if (< i (alength arr)) + (let [dupe? (loop [j 0] + (if (< j i) + (or + (key-test (aget arr i) (aget arr j)) + (recur (+ 2 j))) + false))] + (or dupe? (recur (+ 2 i)))) + false))) + +(defn- pam-new-size [arr] + (loop [i 0 n 0] + (if (< i (alength arr)) + (let [dupe? (loop [j 0] + (if (< j i) + (or + (key-test (aget arr i) (aget arr j)) + (recur (+ 2 j))) + false))] + (recur (+ 2 i) (if dupe? n (+ n 2)))) + n))) + +(defn- pam-grow-seed-array [seed trailing] + (let [seed-cnt (dec (alength seed)) + extra-kvs (seq trailing) + ret (make-array (+ seed-cnt (* 2 (count extra-kvs)))) + ret (array-copy seed 0 ret 0 seed-cnt)] + (loop [i seed-cnt extra-kvs extra-kvs] + (if extra-kvs + (let [kv (first extra-kvs)] + (aset ret i (-key kv)) + (aset ret (inc i) (-val kv)) + (recur (+ 2 i) (next extra-kvs))) + ret)))) + +(set! (.-createAsIfByAssoc PersistentArrayMap) + (fn [init] + ;; check trailing element + (let [len (alength init) + has-trailing? (== 1 (bit-and len 1))] + (if-not (or has-trailing? (pam-dupes? init)) + (PersistentArrayMap. nil (/ len 2) init nil) + (.createAsIfByAssocComplexPath PersistentArrayMap init has-trailing?))))) + +(set! (.-createAsIfByAssocComplexPath PersistentArrayMap) + (fn [init ^boolean has-trailing?] + (let [init (if has-trailing? + (pam-grow-seed-array init + ;; into {} in case the final element is not a map but something conj-able + ;; for parity with Clojure implementation of CLJ-2603 + (into {} (aget init (dec (alength init))))) + init) + n (pam-new-size init) + len (alength init)] + (if (< n len) + (let [nodups (make-array n)] + (loop [i 0 m 0] + (if (< i len) + (let [dupe? (loop [j 0] + (if (< j m) + (or + (key-test (aget init i) (aget init j)) + (recur (+ 2 j))) + false))] + (if-not dupe? + (let [j (loop [j (- len 2)] + (if (>= j i) + (if (key-test (aget init i) (aget init j)) + j + (recur (- j 2))) + j))] + (aset nodups m (aget init i)) + (aset nodups (inc m) (aget init (inc j))) + (recur (+ 2 i) (+ 2 m))) + (recur (+ 2 i) m))))) + (PersistentArrayMap. nil (/ (alength nodups) 2) nodups nil)) + (PersistentArrayMap. nil (/ (alength init) 2) init nil))))) + +(es6-iterable PersistentArrayMap) + +(declare array->transient-hash-map) + +(deftype TransientArrayMap [^:mutable ^boolean editable? + ^:mutable len + arr] + ICounted + (-count [tcoll] + (if editable? + (quot len 2) + (throw (js/Error. "count after persistent!")))) + + ILookup + (-lookup [tcoll k] + (-lookup tcoll k nil)) + + (-lookup [tcoll k not-found] + (if editable? + (let [idx (array-map-index-of tcoll k)] + (if (== idx -1) + not-found + (aget arr (inc idx)))) + (throw (js/Error. "lookup after persistent!")))) + + ITransientCollection + (-conj! [tcoll o] + (if editable? + (cond + (map-entry? o) + (-assoc! tcoll (key o) (val o)) + + (vector? o) + (-assoc! tcoll (o 0) (o 1)) + + :else + (loop [es (seq o) tcoll tcoll] + (if-let [e (first es)] + (recur (next es) + (-assoc! tcoll (key e) (val e))) + tcoll))) + (throw (js/Error. "conj! after persistent!")))) + + (-persistent! [tcoll] + (if editable? + (do (set! editable? false) + (PersistentArrayMap. nil (quot len 2) arr nil)) + (throw (js/Error. "persistent! called twice")))) + + ITransientAssociative + (-assoc! [tcoll key val] + (if editable? + (let [idx (array-map-index-of tcoll key)] + (if (== idx -1) + (if (<= (+ len 2) (* 2 (.-HASHMAP-THRESHOLD PersistentArrayMap))) + (do (set! len (+ len 2)) + (.push arr key) + (.push arr val) + tcoll) + (assoc! (array->transient-hash-map len arr) key val)) + (if (identical? val (aget arr (inc idx))) + tcoll + (do (aset arr (inc idx) val) + tcoll)))) + (throw (js/Error. "assoc! after persistent!")))) + + ITransientMap + (-dissoc! [tcoll key] + (if editable? + (let [idx (array-map-index-of tcoll key)] + (when (>= idx 0) + (aset arr idx (aget arr (- len 2))) + (aset arr (inc idx) (aget arr (dec len))) + (doto arr .pop .pop) + (set! len (- len 2))) + tcoll) + (throw (js/Error. "dissoc! after persistent!")))) + + IFn + (-invoke [tcoll key] + (-lookup tcoll key nil)) + (-invoke [tcoll key not-found] + (-lookup tcoll key not-found))) + +(declare TransientHashMap) + +(defn- array->transient-hash-map [len arr] + (loop [out (transient (.-EMPTY PersistentHashMap)) + i 0] + (if (< i len) + (recur (assoc! out (aget arr i) (aget arr (inc i))) (+ i 2)) + out))) + +;;; PersistentHashMap + +(deftype Box [^:mutable val]) + +(declare create-inode-seq create-array-node-seq create-node) + +(defn- mask [hash shift] + (bit-and (bit-shift-right-zero-fill hash shift) 0x01f)) + +(defn- clone-and-set + ([arr i a] + (doto (aclone arr) + (aset i a))) + ([arr i a j b] + (doto (aclone arr) + (aset i a) + (aset j b)))) + +(defn- remove-pair [arr i] + (let [new-arr (make-array (- (alength arr) 2))] + (array-copy arr 0 new-arr 0 (* 2 i)) + (array-copy arr (* 2 (inc i)) new-arr (* 2 i) (- (alength new-arr) (* 2 i))) + new-arr)) + +(defn- bitmap-indexed-node-index [bitmap bit] + (bit-count (bit-and bitmap (dec bit)))) + +(defn- bitpos [hash shift] + (bit-shift-left 1 (mask hash shift))) + +(defn- edit-and-set + ([inode edit i a] + (let [editable (.ensure-editable inode edit)] + (aset (.-arr editable) i a) + editable)) + ([inode edit i a j b] + (let [editable (.ensure-editable inode edit)] + (aset (.-arr editable) i a) + (aset (.-arr editable) j b) + editable))) + +(defn- inode-kv-reduce [arr f init] + (let [len (alength arr)] + (loop [i 0 init init] + (if (< i len) + (let [init (let [k (aget arr i)] + (if-not (nil? k) + (f init k (aget arr (inc i))) + (let [node (aget arr (inc i))] + (if-not (nil? node) + (.kv-reduce node f init) + init))))] + (if (reduced? init) + init + (recur (+ i 2) init))) + init)))) + +(declare ArrayNode) + + (deftype NodeIterator [arr ^:mutable i ^:mutable next-entry ^:mutable next-iter] + Object + (advance [this] + (let [len (alength arr)] + (loop [] + (if (< i len) + (let [key (aget arr i) + node-or-val (aget arr (inc i)) + ^boolean found + (cond (some? key) + (set! next-entry (MapEntry. key node-or-val nil)) + (some? node-or-val) + (let [new-iter (-iterator node-or-val)] + (if ^boolean (.hasNext new-iter) + (set! next-iter new-iter) + false)) + :else false)] + (set! i (+ i 2)) + (if found true (recur))) + false)))) + (hasNext [this] + (or (some? next-entry) (some? next-iter) (.advance this))) + (next [this] + (cond + (some? next-entry) + (let [ret next-entry] + (set! next-entry nil) + ret) + (some? next-iter) + (let [ret (.next next-iter)] + (when-not ^boolean (.hasNext next-iter) + (set! next-iter nil)) + ret) + ^boolean (.advance this) + (.next this) + :else (throw (js/Error. "No such element")))) + (remove [_] (js/Error. "Unsupported operation"))) + +(deftype BitmapIndexedNode [edit ^:mutable bitmap ^:mutable arr] + Object + (inode-assoc [inode shift hash key val added-leaf?] + (let [bit (bitpos hash shift) + idx (bitmap-indexed-node-index bitmap bit)] + (if (zero? (bit-and bitmap bit)) + (let [n (bit-count bitmap)] + (if (>= n 16) + (let [nodes (make-array 32) + jdx (mask hash shift)] + (aset nodes jdx (.inode-assoc (.-EMPTY BitmapIndexedNode) (+ shift 5) hash key val added-leaf?)) + (loop [i 0 j 0] + (if (< i 32) + (if (zero? (bit-and (bit-shift-right-zero-fill bitmap i) 1)) + (recur (inc i) j) + (do (aset nodes i + (if-not (nil? (aget arr j)) + (.inode-assoc (.-EMPTY BitmapIndexedNode) + (+ shift 5) (cljs.core/hash (aget arr j)) (aget arr j) (aget arr (inc j)) added-leaf?) + (aget arr (inc j)))) + (recur (inc i) (+ j 2)))))) + (ArrayNode. nil (inc n) nodes)) + (let [new-arr (make-array (* 2 (inc n)))] + (array-copy arr 0 new-arr 0 (* 2 idx)) + (aset new-arr (* 2 idx) key) + (aset new-arr (inc (* 2 idx)) val) + (array-copy arr (* 2 idx) new-arr (* 2 (inc idx)) (* 2 (- n idx))) + (set! (.-val added-leaf?) true) + (BitmapIndexedNode. nil (bit-or bitmap bit) new-arr)))) + (let [key-or-nil (aget arr (* 2 idx)) + val-or-node (aget arr (inc (* 2 idx)))] + (cond (nil? key-or-nil) + (let [n (.inode-assoc val-or-node (+ shift 5) hash key val added-leaf?)] + (if (identical? n val-or-node) + inode + (BitmapIndexedNode. nil bitmap (clone-and-set arr (inc (* 2 idx)) n)))) + + (key-test key key-or-nil) + (if (identical? val val-or-node) + inode + (BitmapIndexedNode. nil bitmap (clone-and-set arr (inc (* 2 idx)) val))) + + :else + (do (set! (.-val added-leaf?) true) + (BitmapIndexedNode. nil bitmap + (clone-and-set arr (* 2 idx) nil (inc (* 2 idx)) + (create-node (+ shift 5) key-or-nil val-or-node hash key val))))))))) + + (inode-without [inode shift hash key] + (let [bit (bitpos hash shift)] + (if (zero? (bit-and bitmap bit)) + inode + (let [idx (bitmap-indexed-node-index bitmap bit) + key-or-nil (aget arr (* 2 idx)) + val-or-node (aget arr (inc (* 2 idx)))] + (cond (nil? key-or-nil) + (let [n (.inode-without val-or-node (+ shift 5) hash key)] + (cond (identical? n val-or-node) inode + (not (nil? n)) (BitmapIndexedNode. nil bitmap (clone-and-set arr (inc (* 2 idx)) n)) + (== bitmap bit) nil + :else (BitmapIndexedNode. nil (bit-xor bitmap bit) (remove-pair arr idx)))) + (key-test key key-or-nil) + (if (== bitmap bit) + nil + (BitmapIndexedNode. nil (bit-xor bitmap bit) (remove-pair arr idx))) + :else inode))))) + + (inode-lookup [inode shift hash key not-found] + (let [bit (bitpos hash shift)] + (if (zero? (bit-and bitmap bit)) + not-found + (let [idx (bitmap-indexed-node-index bitmap bit) + key-or-nil (aget arr (* 2 idx)) + val-or-node (aget arr (inc (* 2 idx)))] + (cond (nil? key-or-nil) (.inode-lookup val-or-node (+ shift 5) hash key not-found) + (key-test key key-or-nil) val-or-node + :else not-found))))) + + (inode-find [inode shift hash key not-found] + (let [bit (bitpos hash shift)] + (if (zero? (bit-and bitmap bit)) + not-found + (let [idx (bitmap-indexed-node-index bitmap bit) + key-or-nil (aget arr (* 2 idx)) + val-or-node (aget arr (inc (* 2 idx)))] + (cond (nil? key-or-nil) (.inode-find val-or-node (+ shift 5) hash key not-found) + (key-test key key-or-nil) (MapEntry. key-or-nil val-or-node nil) + :else not-found))))) + + (inode-seq [inode] + (create-inode-seq arr)) + + (ensure-editable [inode e] + (if (identical? e edit) + inode + (let [n (bit-count bitmap) + new-arr (make-array (if (neg? n) 4 (* 2 (inc n))))] + (array-copy arr 0 new-arr 0 (* 2 n)) + (BitmapIndexedNode. e bitmap new-arr)))) + + (edit-and-remove-pair [inode e bit i] + (if (== bitmap bit) + nil + (let [editable (.ensure-editable inode e) + earr (.-arr editable) + len (alength earr)] + (set! (.-bitmap editable) (bit-xor bit (.-bitmap editable))) + (array-copy earr (* 2 (inc i)) + earr (* 2 i) + (- len (* 2 (inc i)))) + (aset earr (- len 2) nil) + (aset earr (dec len) nil) + editable))) + + (inode-assoc! [inode edit shift hash key val added-leaf?] + (let [bit (bitpos hash shift) + idx (bitmap-indexed-node-index bitmap bit)] + (if (zero? (bit-and bitmap bit)) + (let [n (bit-count bitmap)] + (cond + (< (* 2 n) (alength arr)) + (let [editable (.ensure-editable inode edit) + earr (.-arr editable)] + (set! (.-val added-leaf?) true) + (array-copy-downward earr (* 2 idx) + earr (* 2 (inc idx)) + (* 2 (- n idx))) + (aset earr (* 2 idx) key) + (aset earr (inc (* 2 idx)) val) + (set! (.-bitmap editable) (bit-or (.-bitmap editable) bit)) + editable) + + (>= n 16) + (let [nodes (make-array 32) + jdx (mask hash shift)] + (aset nodes jdx (.inode-assoc! (.-EMPTY BitmapIndexedNode) edit (+ shift 5) hash key val added-leaf?)) + (loop [i 0 j 0] + (if (< i 32) + (if (zero? (bit-and (bit-shift-right-zero-fill bitmap i) 1)) + (recur (inc i) j) + (do (aset nodes i + (if-not (nil? (aget arr j)) + (.inode-assoc! (.-EMPTY BitmapIndexedNode) + edit (+ shift 5) (cljs.core/hash (aget arr j)) (aget arr j) (aget arr (inc j)) added-leaf?) + (aget arr (inc j)))) + (recur (inc i) (+ j 2)))))) + (ArrayNode. edit (inc n) nodes)) + + :else + (let [new-arr (make-array (* 2 (+ n 4)))] + (array-copy arr 0 new-arr 0 (* 2 idx)) + (aset new-arr (* 2 idx) key) + (aset new-arr (inc (* 2 idx)) val) + (array-copy arr (* 2 idx) new-arr (* 2 (inc idx)) (* 2 (- n idx))) + (set! (.-val added-leaf?) true) + (let [editable (.ensure-editable inode edit)] + (set! (.-arr editable) new-arr) + (set! (.-bitmap editable) (bit-or (.-bitmap editable) bit)) + editable)))) + (let [key-or-nil (aget arr (* 2 idx)) + val-or-node (aget arr (inc (* 2 idx)))] + (cond (nil? key-or-nil) + (let [n (.inode-assoc! val-or-node edit (+ shift 5) hash key val added-leaf?)] + (if (identical? n val-or-node) + inode + (edit-and-set inode edit (inc (* 2 idx)) n))) + + (key-test key key-or-nil) + (if (identical? val val-or-node) + inode + (edit-and-set inode edit (inc (* 2 idx)) val)) + + :else + (do (set! (.-val added-leaf?) true) + (edit-and-set inode edit (* 2 idx) nil (inc (* 2 idx)) + (create-node edit (+ shift 5) key-or-nil val-or-node hash key val)))))))) + + (inode-without! [inode edit shift hash key removed-leaf?] + (let [bit (bitpos hash shift)] + (if (zero? (bit-and bitmap bit)) + inode + (let [idx (bitmap-indexed-node-index bitmap bit) + key-or-nil (aget arr (* 2 idx)) + val-or-node (aget arr (inc (* 2 idx)))] + (cond (nil? key-or-nil) + (let [n (.inode-without! val-or-node edit (+ shift 5) hash key removed-leaf?)] + (cond (identical? n val-or-node) inode + (not (nil? n)) (edit-and-set inode edit (inc (* 2 idx)) n) + (== bitmap bit) nil + :else (.edit-and-remove-pair inode edit bit idx))) + (key-test key key-or-nil) + (do (set! (.-val removed-leaf?) true) + (.edit-and-remove-pair inode edit bit idx)) + :else inode))))) + + (kv-reduce [inode f init] + (inode-kv-reduce arr f init)) + + IIterable + (-iterator [coll] + (NodeIterator. arr 0 nil nil))) + +(set! (.-EMPTY BitmapIndexedNode) (BitmapIndexedNode. nil 0 (make-array 0))) + +(defn- pack-array-node [array-node edit idx] + (let [arr (.-arr array-node) + len (alength arr) + new-arr (make-array (* 2 (dec (.-cnt array-node))))] + (loop [i 0 j 1 bitmap 0] + (if (< i len) + (if (and (not (== i idx)) + (not (nil? (aget arr i)))) + (do (aset new-arr j (aget arr i)) + (recur (inc i) (+ j 2) (bit-or bitmap (bit-shift-left 1 i)))) + (recur (inc i) j bitmap)) + (BitmapIndexedNode. edit bitmap new-arr))))) + +(deftype ArrayNodeIterator [arr ^:mutable i ^:mutable next-iter] + Object + (hasNext [this] + (let [len (alength arr)] + (loop [] + (if-not (and (some? next-iter) ^boolean (.hasNext next-iter)) + (if (< i len) + (let [node (aget arr i)] + (set! i (inc i)) + (when (some? node) + (set! next-iter (-iterator node))) + (recur)) + false) + true)))) + (next [this] + (if ^boolean (.hasNext this) + (.next next-iter) + (throw (js/Error. "No such element")))) + (remove [_] (js/Error. "Unsupported operation"))) + +(deftype ArrayNode [edit ^:mutable cnt ^:mutable arr] + Object + (inode-assoc [inode shift hash key val added-leaf?] + (let [idx (mask hash shift) + node (aget arr idx)] + (if (nil? node) + (ArrayNode. nil (inc cnt) (clone-and-set arr idx (.inode-assoc (.-EMPTY BitmapIndexedNode) (+ shift 5) hash key val added-leaf?))) + (let [n (.inode-assoc node (+ shift 5) hash key val added-leaf?)] + (if (identical? n node) + inode + (ArrayNode. nil cnt (clone-and-set arr idx n))))))) + + (inode-without [inode shift hash key] + (let [idx (mask hash shift) + node (aget arr idx)] + (if-not (nil? node) + (let [n (.inode-without node (+ shift 5) hash key)] + (cond + (identical? n node) + inode + + (nil? n) + (if (<= cnt 8) + (pack-array-node inode nil idx) + (ArrayNode. nil (dec cnt) (clone-and-set arr idx n))) + + :else + (ArrayNode. nil cnt (clone-and-set arr idx n)))) + inode))) + + (inode-lookup [inode shift hash key not-found] + (let [idx (mask hash shift) + node (aget arr idx)] + (if-not (nil? node) + (.inode-lookup node (+ shift 5) hash key not-found) + not-found))) + + (inode-find [inode shift hash key not-found] + (let [idx (mask hash shift) + node (aget arr idx)] + (if-not (nil? node) + (.inode-find node (+ shift 5) hash key not-found) + not-found))) + + (inode-seq [inode] + (create-array-node-seq arr)) + + (ensure-editable [inode e] + (if (identical? e edit) + inode + (ArrayNode. e cnt (aclone arr)))) + + (inode-assoc! [inode edit shift hash key val added-leaf?] + (let [idx (mask hash shift) + node (aget arr idx)] + (if (nil? node) + (let [editable (edit-and-set inode edit idx (.inode-assoc! (.-EMPTY BitmapIndexedNode) edit (+ shift 5) hash key val added-leaf?))] + (set! (.-cnt editable) (inc (.-cnt editable))) + editable) + (let [n (.inode-assoc! node edit (+ shift 5) hash key val added-leaf?)] + (if (identical? n node) + inode + (edit-and-set inode edit idx n)))))) + + (inode-without! [inode edit shift hash key removed-leaf?] + (let [idx (mask hash shift) + node (aget arr idx)] + (if (nil? node) + inode + (let [n (.inode-without! node edit (+ shift 5) hash key removed-leaf?)] + (cond + (identical? n node) + inode + + (nil? n) + (if (<= cnt 8) + (pack-array-node inode edit idx) + (let [editable (edit-and-set inode edit idx n)] + (set! (.-cnt editable) (dec (.-cnt editable))) + editable)) + + :else + (edit-and-set inode edit idx n)))))) + + (kv-reduce [inode f init] + (let [len (alength arr)] ; actually 32 + (loop [i 0 init init] + (if (< i len) + (let [node (aget arr i)] + (if-not (nil? node) + (let [init (.kv-reduce node f init)] + (if (reduced? init) + init + (recur (inc i) init))) + (recur (inc i) init))) + init)))) + + IIterable + (-iterator [coll] + (ArrayNodeIterator. arr 0 nil))) + +(defn- hash-collision-node-find-index [arr cnt key] + (let [lim (* 2 cnt)] + (loop [i 0] + (if (< i lim) + (if (key-test key (aget arr i)) + i + (recur (+ i 2))) + -1)))) + +(deftype HashCollisionNode [edit + ^:mutable collision-hash + ^:mutable cnt + ^:mutable arr] + Object + (inode-assoc [inode shift hash key val added-leaf?] + (if (== hash collision-hash) + (let [idx (hash-collision-node-find-index arr cnt key)] + (if (== idx -1) + (let [len (* 2 cnt) + new-arr (make-array (+ len 2))] + (array-copy arr 0 new-arr 0 len) + (aset new-arr len key) + (aset new-arr (inc len) val) + (set! (.-val added-leaf?) true) + (HashCollisionNode. nil collision-hash (inc cnt) new-arr)) + (if (= (aget arr (inc idx)) val) + inode + (HashCollisionNode. nil collision-hash cnt (clone-and-set arr (inc idx) val))))) + (.inode-assoc (BitmapIndexedNode. nil (bitpos collision-hash shift) (array nil inode)) + shift hash key val added-leaf?))) + + (inode-without [inode shift hash key] + (let [idx (hash-collision-node-find-index arr cnt key)] + (cond (== idx -1) inode + (== cnt 1) nil + :else (HashCollisionNode. nil collision-hash (dec cnt) (remove-pair arr (quot idx 2)))))) + + (inode-lookup [inode shift hash key not-found] + (let [idx (hash-collision-node-find-index arr cnt key)] + (cond (< idx 0) not-found + :else (aget arr (inc idx))))) + + (inode-find [inode shift hash key not-found] + (let [idx (hash-collision-node-find-index arr cnt key)] + (cond (< idx 0) not-found + :else (MapEntry. (aget arr idx) (aget arr (inc idx)) nil)))) + + (inode-seq [inode] + (create-inode-seq arr)) + + (ensure-editable [inode e] + (if (identical? e edit) + inode + (let [new-arr (make-array (* 2 (inc cnt)))] + (array-copy arr 0 new-arr 0 (* 2 cnt)) + (HashCollisionNode. e collision-hash cnt new-arr)))) + + (ensure-editable-array [inode e count array] + (if (identical? e edit) + (do (set! arr array) + (set! cnt count) + inode) + (HashCollisionNode. edit collision-hash count array))) + + (inode-assoc! [inode edit shift hash key val added-leaf?] + (if (== hash collision-hash) + (let [idx (hash-collision-node-find-index arr cnt key)] + (if (== idx -1) + (if (> (alength arr) (* 2 cnt)) + (let [editable (edit-and-set inode edit (* 2 cnt) key (inc (* 2 cnt)) val)] + (set! (.-val added-leaf?) true) + (set! (.-cnt editable) (inc (.-cnt editable))) + editable) + (let [len (alength arr) + new-arr (make-array (+ len 2))] + (array-copy arr 0 new-arr 0 len) + (aset new-arr len key) + (aset new-arr (inc len) val) + (set! (.-val added-leaf?) true) + (.ensure-editable-array inode edit (inc cnt) new-arr))) + (if (identical? (aget arr (inc idx)) val) + inode + (edit-and-set inode edit (inc idx) val)))) + (.inode-assoc! (BitmapIndexedNode. edit (bitpos collision-hash shift) (array nil inode nil nil)) + edit shift hash key val added-leaf?))) + + (inode-without! [inode edit shift hash key removed-leaf?] + (let [idx (hash-collision-node-find-index arr cnt key)] + (if (== idx -1) + inode + (do (set! (.-val removed-leaf?) true) + (if (== cnt 1) + nil + (let [editable (.ensure-editable inode edit) + earr (.-arr editable)] + (aset earr idx (aget earr (- (* 2 cnt) 2))) + (aset earr (inc idx) (aget earr (dec (* 2 cnt)))) + (aset earr (dec (* 2 cnt)) nil) + (aset earr (- (* 2 cnt) 2) nil) + (set! (.-cnt editable) (dec (.-cnt editable))) + editable)))))) + + (kv-reduce [inode f init] + (inode-kv-reduce arr f init)) + + IIterable + (-iterator [coll] + (NodeIterator. arr 0 nil nil))) + +(defn- create-node + ([shift key1 val1 key2hash key2 val2] + (let [key1hash (hash key1)] + (if (== key1hash key2hash) + (HashCollisionNode. nil key1hash 2 (array key1 val1 key2 val2)) + (let [added-leaf? (Box. false)] + (-> (.-EMPTY BitmapIndexedNode) + (.inode-assoc shift key1hash key1 val1 added-leaf?) + (.inode-assoc shift key2hash key2 val2 added-leaf?)))))) + ([edit shift key1 val1 key2hash key2 val2] + (let [key1hash (hash key1)] + (if (== key1hash key2hash) + (HashCollisionNode. nil key1hash 2 (array key1 val1 key2 val2)) + (let [added-leaf? (Box. false)] + (-> (.-EMPTY BitmapIndexedNode) + (.inode-assoc! edit shift key1hash key1 val1 added-leaf?) + (.inode-assoc! edit shift key2hash key2 val2 added-leaf?))))))) + +(deftype NodeSeq [meta nodes i s ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IMeta + (-meta [coll] meta) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (NodeSeq. new-meta nodes i s __hash))) + + ICollection + (-conj [coll o] (cons o coll)) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + ISequential + ISeq + (-first [coll] + (if (nil? s) + (MapEntry. (aget nodes i) (aget nodes (inc i)) nil) + (first s))) + + (-rest [coll] + (let [ret (if (nil? s) + (create-inode-seq nodes (+ i 2) nil) + (create-inode-seq nodes i (next s)))] + (if-not (nil? ret) ret ()))) + + INext + (-next [coll] + (if (nil? s) + (create-inode-seq nodes (+ i 2) nil) + (create-inode-seq nodes i (next s)))) + + ISeqable + (-seq [this] this) + + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + IReduce + (-reduce [coll f] (seq-reduce f coll)) + (-reduce [coll f start] (seq-reduce f start coll))) + +(es6-iterable NodeSeq) + +(defn- create-inode-seq + ([nodes] + (create-inode-seq nodes 0 nil)) + ([nodes i s] + (if (nil? s) + (let [len (alength nodes)] + (loop [j i] + (if (< j len) + (if-not (nil? (aget nodes j)) + (NodeSeq. nil nodes j nil nil) + (if-let [node (aget nodes (inc j))] + (if-let [node-seq (.inode-seq node)] + (NodeSeq. nil nodes (+ j 2) node-seq nil) + (recur (+ j 2))) + (recur (+ j 2))))))) + (NodeSeq. nil nodes i s nil)))) + +(deftype ArrayNodeSeq [meta nodes i s ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IMeta + (-meta [coll] meta) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (ArrayNodeSeq. new-meta nodes i s __hash))) + + ICollection + (-conj [coll o] (cons o coll)) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + ISequential + ISeq + (-first [coll] (first s)) + (-rest [coll] + (let [ret (create-array-node-seq nodes i (next s))] + (if-not (nil? ret) ret ()))) + + INext + (-next [coll] + (create-array-node-seq nodes i (next s))) + + ISeqable + (-seq [this] this) + + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + IReduce + (-reduce [coll f] (seq-reduce f coll)) + (-reduce [coll f start] (seq-reduce f start coll))) + +(es6-iterable ArrayNodeSeq) + +(defn- create-array-node-seq + ([nodes] (create-array-node-seq nodes 0 nil)) + ([nodes i s] + (if (nil? s) + (let [len (alength nodes)] + (loop [j i] + (if (< j len) + (if-let [nj (aget nodes j)] + (if-let [ns (.inode-seq nj)] + (ArrayNodeSeq. nil nodes (inc j) ns nil) + (recur (inc j))) + (recur (inc j)))))) + (ArrayNodeSeq. nil nodes i s nil)))) + +(deftype HashMapIter [nil-val root-iter ^:mutable seen] + Object + (hasNext [_] + (or (not ^boolean seen) ^boolean (.hasNext root-iter))) + (next [_] + (if-not ^boolean seen + (do + (set! seen true) + (MapEntry. nil nil-val nil)) + (.next root-iter))) + (remove [_] (js/Error. "Unsupported operation"))) + +(deftype PersistentHashMap [meta cnt root ^boolean has-nil? nil-val ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (keys [coll] + (es6-iterator (keys coll))) + (entries [coll] + (es6-entries-iterator (seq coll))) + (values [coll] + (es6-iterator (vals coll))) + (has [coll k] + (contains? coll k)) + (get [coll k not-found] + (-lookup coll k not-found)) + (forEach [coll f] + (doseq [[k v] coll] + (f v k))) + + ICloneable + (-clone [_] (PersistentHashMap. meta cnt root has-nil? nil-val __hash)) + + IIterable + (-iterator [coll] + (let [root-iter (if ^boolean root (-iterator root) (nil-iter))] + (if has-nil? + (HashMapIter. nil-val root-iter false) + root-iter))) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (PersistentHashMap. new-meta cnt root has-nil? nil-val __hash))) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (loop [ret coll es (seq entry)] + (if (nil? es) + ret + (let [e (first es)] + (if (vector? e) + (recur (-assoc ret (-nth e 0) (-nth e 1)) + (next es)) + (throw (js/Error. "conj on a map takes map entries or seqables of map entries")))))))) + + IEmptyableCollection + (-empty [coll] (-with-meta (.-EMPTY PersistentHashMap) meta)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] + (when (pos? cnt) + (let [s (if-not (nil? root) (.inode-seq root))] + (if has-nil? + (cons (MapEntry. nil nil-val nil) s) + s)))) + + ICounted + (-count [coll] cnt) + + ILookup + (-lookup [coll k] + (-lookup coll k nil)) + + (-lookup [coll k not-found] + (cond (nil? k) (if has-nil? + nil-val + not-found) + (nil? root) not-found + :else (.inode-lookup root 0 (hash k) k not-found))) + + IAssociative + (-assoc [coll k v] + (if (nil? k) + (if (and has-nil? (identical? v nil-val)) + coll + (PersistentHashMap. meta (if has-nil? cnt (inc cnt)) root true v nil)) + (let [added-leaf? (Box. false) + new-root (-> (if (nil? root) + (.-EMPTY BitmapIndexedNode) + root) + (.inode-assoc 0 (hash k) k v added-leaf?))] + (if (identical? new-root root) + coll + (PersistentHashMap. meta (if ^boolean (.-val added-leaf?) (inc cnt) cnt) new-root has-nil? nil-val nil))))) + + (-contains-key? [coll k] + (cond (nil? k) has-nil? + (nil? root) false + :else (not (identical? (.inode-lookup root 0 (hash k) k lookup-sentinel) + lookup-sentinel)))) + + IFind + (-find [coll k] + (cond + (nil? k) (when has-nil? (MapEntry. nil nil-val nil)) + (nil? root) nil + :else (.inode-find root 0 (hash k) k nil))) + + IMap + (-dissoc [coll k] + (cond (nil? k) (if has-nil? + (PersistentHashMap. meta (dec cnt) root false nil nil) + coll) + (nil? root) coll + :else + (let [new-root (.inode-without root 0 (hash k) k)] + (if (identical? new-root root) + coll + (PersistentHashMap. meta (dec cnt) new-root has-nil? nil-val nil))))) + + IKVReduce + (-kv-reduce [coll f init] + (let [init (if has-nil? (f init nil nil-val) init)] + (cond + (reduced? init) @init + (not (nil? root)) (unreduced (.kv-reduce root f init)) + :else init))) + + IFn + (-invoke [coll k] + (-lookup coll k)) + + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IEditableCollection + (-as-transient [coll] + (TransientHashMap. (js-obj) root cnt has-nil? nil-val))) + +(set! (.-EMPTY PersistentHashMap) (PersistentHashMap. nil 0 nil false nil empty-unordered-hash)) + +(set! (.-fromArray PersistentHashMap) + (fn [arr ^boolean no-clone] + (let [arr (if no-clone arr (aclone arr)) + len (alength arr)] + (loop [i 0 ret (transient (.-EMPTY PersistentHashMap))] + (if (< i len) + (recur (+ i 2) + (-assoc! ret (aget arr i) (aget arr (inc i)))) + (-persistent! ret)))))) + +(set! (.-fromArrays PersistentHashMap) + (fn [ks vs] + (let [len (alength ks)] + (loop [i 0 ^not-native out (transient (.-EMPTY PersistentHashMap))] + (if (< i len) + (if (<= (alength vs) i) + (throw (js/Error. (str_ "No value supplied for key: " (aget ks i)))) + (recur (inc i) (-assoc! out (aget ks i) (aget vs i)))) + (persistent! out)))))) + +(set! (.-createWithCheck PersistentHashMap) + (fn [arr] + (let [len (alength arr) + ret (transient (.-EMPTY PersistentHashMap))] + (loop [i 0] + (when (< i len) + (-assoc! ret (aget arr i) (aget arr (inc i))) + (if (not= (-count ret) (inc (/ i 2))) + (throw (js/Error. (str_ "Duplicate key: " (aget arr i)))) + (recur (+ i 2))))) + (-persistent! ret)))) + +(es6-iterable PersistentHashMap) + +(deftype TransientHashMap [^:mutable ^boolean edit + ^:mutable root + ^:mutable count + ^:mutable ^boolean has-nil? + ^:mutable nil-val] + Object + (conj! [tcoll o] + (if edit + (cond + (map-entry? o) + (.assoc! tcoll (key o) (val o)) + + (vector? o) + (.assoc! tcoll (o 0) (o 1)) + + :else + (loop [es (seq o) tcoll tcoll] + (if-let [e (first es)] + (recur (next es) + (.assoc! tcoll (key e) (val e))) + tcoll))) + (throw (js/Error. "conj! after persistent")))) + + (assoc! [tcoll k v] + (if edit + (if (nil? k) + (do (if (identical? nil-val v) + nil + (set! nil-val v)) + (if has-nil? + nil + (do (set! count (inc count)) + (set! has-nil? true))) + tcoll) + (let [added-leaf? (Box. false) + node (-> (if (nil? root) + (.-EMPTY BitmapIndexedNode) + root) + (.inode-assoc! edit 0 (hash k) k v added-leaf?))] + (if (identical? node root) + nil + (set! root node)) + ;; FIXME: can we figure out something better here? + (if ^boolean (.-val added-leaf?) + (set! count (inc count))) + tcoll)) + (throw (js/Error. "assoc! after persistent!")))) + + (without! [tcoll k] + (if edit + (if (nil? k) + (if has-nil? + (do (set! has-nil? false) + (set! nil-val nil) + (set! count (dec count)) + tcoll) + tcoll) + (if (nil? root) + tcoll + (let [removed-leaf? (Box. false) + node (.inode-without! root edit 0 (hash k) k removed-leaf?)] + (if (identical? node root) + nil + (set! root node)) + ;; FIXME: can we figure out something better here? + (if ^boolean (.-val removed-leaf?) + (set! count (dec count))) + tcoll))) + (throw (js/Error. "dissoc! after persistent!")))) + + (persistent! [tcoll] + (if edit + (do (set! edit nil) + (PersistentHashMap. nil count root has-nil? nil-val nil)) + (throw (js/Error. "persistent! called twice")))) + + ICounted + (-count [coll] + (if edit + count + (throw (js/Error. "count after persistent!")))) + + ILookup + (-lookup [tcoll k] + (if (nil? k) + (if has-nil? + nil-val) + (if (nil? root) + nil + (.inode-lookup root 0 (hash k) k)))) + + (-lookup [tcoll k not-found] + (if (nil? k) + (if has-nil? + nil-val + not-found) + (if (nil? root) + not-found + (.inode-lookup root 0 (hash k) k not-found)))) + + ITransientCollection + (-conj! [tcoll val] (.conj! tcoll val)) + + (-persistent! [tcoll] (.persistent! tcoll)) + + ITransientAssociative + (-assoc! [tcoll key val] (.assoc! tcoll key val)) + + ITransientMap + (-dissoc! [tcoll key] (.without! tcoll key)) + + IFn + (-invoke [tcoll key] + (-lookup tcoll key)) + (-invoke [tcoll key not-found] + (-lookup tcoll key not-found))) + +;;; PersistentTreeMap + +(defn- tree-map-seq-push [node stack ^boolean ascending?] + (loop [t node stack stack] + (if-not (nil? t) + (recur (if ascending? (.-left t) (.-right t)) + (conj stack t)) + stack))) + +(deftype PersistentTreeMapSeq [meta stack ^boolean ascending? cnt ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + ISeqable + (-seq [this] this) + + ISequential + ISeq + (-first [this] (peek stack)) + (-rest [this] + (let [t (first stack) + next-stack (tree-map-seq-push (if ascending? (.-right t) (.-left t)) + (next stack) + ascending?)] + (if-not (nil? next-stack) + (PersistentTreeMapSeq. nil next-stack ascending? (dec cnt) nil) + ()))) + INext + (-next [this] + (let [t (first stack) + next-stack (tree-map-seq-push (if ascending? (.-right t) (.-left t)) + (next stack) + ascending?)] + (when-not (nil? next-stack) + (PersistentTreeMapSeq. nil next-stack ascending? (dec cnt) nil)))) + + ICounted + (-count [coll] + (if (neg? cnt) + (inc (count (next coll))) + cnt)) + + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + ICollection + (-conj [coll o] (cons o coll)) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + IMeta + (-meta [coll] meta) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (PersistentTreeMapSeq. new-meta stack ascending? cnt __hash))) + + IReduce + (-reduce [coll f] (seq-reduce f coll)) + (-reduce [coll f start] (seq-reduce f start coll))) + +(es6-iterable PersistentTreeMapSeq) + +(defn- create-tree-map-seq [tree ascending? cnt] + (PersistentTreeMapSeq. nil (tree-map-seq-push tree nil ascending?) ascending? cnt nil)) + +(declare RedNode BlackNode) + +(defn- balance-left [key val ins right] + (if (instance? RedNode ins) + (cond + (instance? RedNode (.-left ins)) + (RedNode. (.-key ins) (.-val ins) + (.blacken (.-left ins)) + (BlackNode. key val (.-right ins) right nil) + nil) + + (instance? RedNode (.-right ins)) + (RedNode. (.. ins -right -key) (.. ins -right -val) + (BlackNode. (.-key ins) (.-val ins) + (.-left ins) + (.. ins -right -left) + nil) + (BlackNode. key val + (.. ins -right -right) + right + nil) + nil) + + :else + (BlackNode. key val ins right nil)) + (BlackNode. key val ins right nil))) + +(defn- balance-right [key val left ins] + (if (instance? RedNode ins) + (cond + (instance? RedNode (.-right ins)) + (RedNode. (.-key ins) (.-val ins) + (BlackNode. key val left (.-left ins) nil) + (.blacken (.-right ins)) + nil) + + (instance? RedNode (.-left ins)) + (RedNode. (.. ins -left -key) (.. ins -left -val) + (BlackNode. key val left (.. ins -left -left) nil) + (BlackNode. (.-key ins) (.-val ins) + (.. ins -left -right) + (.-right ins) + nil) + nil) + + :else + (BlackNode. key val left ins nil)) + (BlackNode. key val left ins nil))) + +(defn- balance-left-del [key val del right] + (cond + (instance? RedNode del) + (RedNode. key val (.blacken del) right nil) + + (instance? BlackNode right) + (balance-right key val del (.redden right)) + + (and (instance? RedNode right) (instance? BlackNode (.-left right))) + (RedNode. (.. right -left -key) (.. right -left -val) + (BlackNode. key val del (.. right -left -left) nil) + (balance-right (.-key right) (.-val right) + (.. right -left -right) + (.redden (.-right right))) + nil) + + :else + (throw (js/Error. "red-black tree invariant violation")))) + +(defn- balance-right-del [key val left del] + (cond + (instance? RedNode del) + (RedNode. key val left (.blacken del) nil) + + (instance? BlackNode left) + (balance-left key val (.redden left) del) + + (and (instance? RedNode left) (instance? BlackNode (.-right left))) + (RedNode. (.. left -right -key) (.. left -right -val) + (balance-left (.-key left) (.-val left) + (.redden (.-left left)) + (.. left -right -left)) + (BlackNode. key val (.. left -right -right) del nil) + nil) + + :else + (throw (js/Error. "red-black tree invariant violation")))) + +(defn- tree-map-kv-reduce [node f init] + (let [init (if-not (nil? (.-left node)) + (tree-map-kv-reduce (.-left node) f init) + init)] + (if (reduced? init) + init + (let [init (f init (.-key node) (.-val node))] + (if (reduced? init) + init + (if-not (nil? (.-right node)) + (tree-map-kv-reduce (.-right node) f init) + init)))))) + +(deftype BlackNode [key val left right ^:mutable __hash] + Object + (add-left [node ins] + (.balance-left ins node)) + + (add-right [node ins] + (.balance-right ins node)) + + (remove-left [node del] + (balance-left-del key val del right)) + + (remove-right [node del] + (balance-right-del key val left del)) + + (blacken [node] node) + + (redden [node] (RedNode. key val left right nil)) + + (balance-left [node parent] + (BlackNode. (.-key parent) (.-val parent) node (.-right parent) nil)) + + (balance-right [node parent] + (BlackNode. (.-key parent) (.-val parent) (.-left parent) node nil)) + + (replace [node key val left right] + (BlackNode. key val left right nil)) + + (kv-reduce [node f init] + (tree-map-kv-reduce node f init)) + + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IMapEntry + (-key [node] key) + (-val [node] val) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IMeta + (-meta [node] nil) + + IWithMeta + (-with-meta [node meta] + (-with-meta [key val] meta)) + + IStack + (-peek [node] val) + + (-pop [node] [key]) + + ICollection + (-conj [node o] [key val o]) + + IEmptyableCollection + (-empty [node] nil) + + ISequential + ISeqable + (-seq [node] (IndexedSeq. #js [key val] 0 nil)) + + IReversible + (-rseq [node] (IndexedSeq. #js [val key] 0 nil)) + + ICounted + (-count [node] 2) + + IIndexed + (-nth [node n] + (cond (== n 0) key + (== n 1) val + :else (throw (js/Error. "Index out of bounds")))) + + (-nth [node n not-found] + (cond (== n 0) key + (== n 1) val + :else not-found)) + + ILookup + (-lookup [node k] (-nth node k nil)) + (-lookup [node k not-found] (-nth node k not-found)) + + IAssociative + (-assoc [node k v] + (assoc [key val] k v)) + (-contains-key? [node k] + (or (== k 0) (== k 1))) + + IFind + (-find [node k] + (case k + 0 (MapEntry. 0 key nil) + 1 (MapEntry. 1 val nil) + nil)) + + IVector + (-assoc-n [node n v] + (-assoc-n [key val] n v)) + + IReduce + (-reduce [node f] + (ci-reduce node f)) + + (-reduce [node f start] + (ci-reduce node f start)) + + IFn + (-invoke [node k] + (-nth node k)) + + (-invoke [node k not-found] + (-nth node k not-found))) + +(es6-iterable BlackNode) + +(deftype RedNode [key val left right ^:mutable __hash] + Object + (add-left [node ins] + (RedNode. key val ins right nil)) + + (add-right [node ins] + (RedNode. key val left ins nil)) + + (remove-left [node del] + (RedNode. key val del right nil)) + + (remove-right [node del] + (RedNode. key val left del nil)) + + (blacken [node] + (BlackNode. key val left right nil)) + + (redden [node] + (throw (js/Error. "red-black tree invariant violation"))) + + (balance-left [node parent] + (cond + (instance? RedNode left) + (RedNode. key val + (.blacken left) + (BlackNode. (.-key parent) (.-val parent) right (.-right parent) nil) + nil) + + (instance? RedNode right) + (RedNode. (.-key right) (.-val right) + (BlackNode. key val left (.-left right) nil) + (BlackNode. (.-key parent) (.-val parent) + (.-right right) + (.-right parent) + nil) + nil) + + :else + (BlackNode. (.-key parent) (.-val parent) node (.-right parent) nil))) + + (balance-right [node parent] + (cond + (instance? RedNode right) + (RedNode. key val + (BlackNode. (.-key parent) (.-val parent) + (.-left parent) + left + nil) + (.blacken right) + nil) + + (instance? RedNode left) + (RedNode. (.-key left) (.-val left) + (BlackNode. (.-key parent) (.-val parent) + (.-left parent) + (.-left left) + nil) + (BlackNode. key val (.-right left) right nil) + nil) + + :else + (BlackNode. (.-key parent) (.-val parent) (.-left parent) node nil))) + + (replace [node key val left right] + (RedNode. key val left right nil)) + + (kv-reduce [node f init] + (tree-map-kv-reduce node f init)) + + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IMapEntry + (-key [node] key) + (-val [node] val) + + IHash + (-hash [coll] (caching-hash coll hash-ordered-coll __hash)) + + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IMeta + (-meta [node] nil) + + IWithMeta + (-with-meta [node meta] + (-with-meta [key val] meta)) + + IStack + (-peek [node] val) + + (-pop [node] [key]) + + ICollection + (-conj [node o] [key val o]) + + IEmptyableCollection + (-empty [node] nil) + + ISequential + ISeqable + (-seq [node] (IndexedSeq. #js [key val] 0 nil)) + + IReversible + (-rseq [node] (IndexedSeq. #js [val key] 0 nil)) + + ICounted + (-count [node] 2) + + IIndexed + (-nth [node n] + (cond (== n 0) key + (== n 1) val + :else (throw (js/Error. "Index out of bounds")))) + + (-nth [node n not-found] + (cond (== n 0) key + (== n 1) val + :else not-found)) + + ILookup + (-lookup [node k] (-nth node k nil)) + (-lookup [node k not-found] (-nth node k not-found)) + + IAssociative + (-assoc [node k v] + (assoc [key val] k v)) + (-contains-key? [node k] + (or (== k 0) (== k 1))) + + IFind + (-find [node k] + (case k + 0 (MapEntry. 0 key nil) + 1 (MapEntry. 1 val nil) + nil)) + + IVector + (-assoc-n [node n v] + (-assoc-n [key val] n v)) + + IReduce + (-reduce [node f] + (ci-reduce node f)) + + (-reduce [node f start] + (ci-reduce node f start)) + + IFn + (-invoke [node k] + (-nth node k)) + + (-invoke [node k not-found] + (-nth node k not-found))) + +(es6-iterable RedNode) + +(defn- tree-map-add [comp tree k v found] + (if (nil? tree) + (RedNode. k v nil nil nil) + (let [c (comp k (.-key tree))] + (cond + (zero? c) + (do (aset found 0 tree) + nil) + + (neg? c) + (let [ins (tree-map-add comp (.-left tree) k v found)] + (if-not (nil? ins) + (.add-left tree ins))) + + :else + (let [ins (tree-map-add comp (.-right tree) k v found)] + (if-not (nil? ins) + (.add-right tree ins))))))) + +(defn- tree-map-append [left right] + (cond + (nil? left) + right + + (nil? right) + left + + (instance? RedNode left) + (if (instance? RedNode right) + (let [app (tree-map-append (.-right left) (.-left right))] + (if (instance? RedNode app) + (RedNode. (.-key app) (.-val app) + (RedNode. (.-key left) (.-val left) + (.-left left) + (.-left app) + nil) + (RedNode. (.-key right) (.-val right) + (.-right app) + (.-right right) + nil) + nil) + (RedNode. (.-key left) (.-val left) + (.-left left) + (RedNode. (.-key right) (.-val right) app (.-right right) nil) + nil))) + (RedNode. (.-key left) (.-val left) + (.-left left) + (tree-map-append (.-right left) right) + nil)) + + (instance? RedNode right) + (RedNode. (.-key right) (.-val right) + (tree-map-append left (.-left right)) + (.-right right) + nil) + + :else + (let [app (tree-map-append (.-right left) (.-left right))] + (if (instance? RedNode app) + (RedNode. (.-key app) (.-val app) + (BlackNode. (.-key left) (.-val left) + (.-left left) + (.-left app) + nil) + (BlackNode. (.-key right) (.-val right) + (.-right app) + (.-right right) + nil) + nil) + (balance-left-del (.-key left) (.-val left) + (.-left left) + (BlackNode. (.-key right) (.-val right) + app + (.-right right) + nil)))))) + +(defn- tree-map-remove [comp tree k found] + (if-not (nil? tree) + (let [c (comp k (.-key tree))] + (cond + (zero? c) + (do (aset found 0 tree) + (tree-map-append (.-left tree) (.-right tree))) + + (neg? c) + (let [del (tree-map-remove comp (.-left tree) k found)] + (if (or (not (nil? del)) (not (nil? (aget found 0)))) + (if (instance? BlackNode (.-left tree)) + (balance-left-del (.-key tree) (.-val tree) del (.-right tree)) + (RedNode. (.-key tree) (.-val tree) del (.-right tree) nil)))) + + :else + (let [del (tree-map-remove comp (.-right tree) k found)] + (if (or (not (nil? del)) (not (nil? (aget found 0)))) + (if (instance? BlackNode (.-right tree)) + (balance-right-del (.-key tree) (.-val tree) (.-left tree) del) + (RedNode. (.-key tree) (.-val tree) (.-left tree) del nil)))))))) + +(defn- tree-map-replace [comp tree k v] + (let [tk (.-key tree) + c (comp k tk)] + (cond (zero? c) (.replace tree tk v (.-left tree) (.-right tree)) + (neg? c) (.replace tree tk (.-val tree) (tree-map-replace comp (.-left tree) k v) (.-right tree)) + :else (.replace tree tk (.-val tree) (.-left tree) (tree-map-replace comp (.-right tree) k v))))) + +(declare key) + +(deftype PersistentTreeMap [comp tree cnt meta ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (keys [coll] + (es6-iterator (keys coll))) + (entries [coll] + (es6-entries-iterator (seq coll))) + (values [coll] + (es6-iterator (vals coll))) + (has [coll k] + (contains? coll k)) + (get [coll k not-found] + (-lookup coll k not-found)) + (forEach [coll f] + (doseq [[k v] coll] + (f v k))) + + (entry-at [coll k] + (loop [t tree] + (if-not (nil? t) + (let [c (comp k (.-key t))] + (cond (zero? c) t + (neg? c) (recur (.-left t)) + :else (recur (.-right t))))))) + + ICloneable + (-clone [_] (PersistentTreeMap. comp tree cnt meta __hash)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (PersistentTreeMap. comp tree cnt new-meta __hash))) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (loop [ret coll es (seq entry)] + (if (nil? es) + ret + (let [e (first es)] + (if (vector? e) + (recur (-assoc ret (-nth e 0) (-nth e 1)) + (next es)) + (throw (js/Error. "conj on a map takes map entries or seqables of map entries")))))))) + + IEmptyableCollection + (-empty [coll] (PersistentTreeMap. comp nil 0 meta 0)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ICounted + (-count [coll] cnt) + + IKVReduce + (-kv-reduce [coll f init] + (if-not (nil? tree) + (unreduced (tree-map-kv-reduce tree f init)) + init)) + + IFn + (-invoke [coll k] + (-lookup coll k)) + + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + ISeqable + (-seq [coll] + (if (pos? cnt) + (create-tree-map-seq tree true cnt))) + + IReversible + (-rseq [coll] + (if (pos? cnt) + (create-tree-map-seq tree false cnt))) + + ILookup + (-lookup [coll k] + (-lookup coll k nil)) + + (-lookup [coll k not-found] + (let [n (.entry-at coll k)] + (if-not (nil? n) + (.-val n) + not-found))) + + IAssociative + (-assoc [coll k v] + (let [found (array nil) + t (tree-map-add comp tree k v found)] + (if (nil? t) + (let [found-node (nth found 0)] + (if (= v (.-val found-node)) + coll + (PersistentTreeMap. comp (tree-map-replace comp tree k v) cnt meta nil))) + (PersistentTreeMap. comp (.blacken t) (inc cnt) meta nil)))) + + (-contains-key? [coll k] + (not (nil? (.entry-at coll k)))) + + IFind + (-find [coll k] + (.entry-at coll k)) + + IMap + (-dissoc [coll k] + (let [found (array nil) + t (tree-map-remove comp tree k found)] + (if (nil? t) + (if (nil? (nth found 0)) + coll + (PersistentTreeMap. comp nil 0 meta nil)) + (PersistentTreeMap. comp (.blacken t) (dec cnt) meta nil)))) + + ISorted + (-sorted-seq [coll ascending?] + (if (pos? cnt) + (create-tree-map-seq tree ascending? cnt))) + + (-sorted-seq-from [coll k ascending?] + (if (pos? cnt) + (loop [stack nil t tree] + (if-not (nil? t) + (let [c (comp k (.-key t))] + (cond + (zero? c) (PersistentTreeMapSeq. nil (conj stack t) ascending? -1 nil) + ascending? (if (neg? c) + (recur (conj stack t) (.-left t)) + (recur stack (.-right t))) + :else (if (pos? c) + (recur (conj stack t) (.-right t)) + (recur stack (.-left t))))) + (when-not (nil? stack) + (PersistentTreeMapSeq. nil stack ascending? -1 nil)))))) + + (-entry-key [coll entry] (key entry)) + + (-comparator [coll] comp)) + +(set! (.-EMPTY PersistentTreeMap) (PersistentTreeMap. compare nil 0 nil empty-unordered-hash)) + +(es6-iterable PersistentTreeMap) + +(defn hash-map + "keyval => key val + Returns a new hash map with supplied mappings." + [& keyvals] + (loop [in (seq keyvals), out (transient (.-EMPTY PersistentHashMap))] + (if in + (let [in' (next in)] + (if (nil? in') + (throw (js/Error. (str_ "No value supplied for key: " (first in)))) + (recur (next in') (assoc! out (first in) (first in')) ))) + (persistent! out)))) + +(defn array-map + "keyval => key val + Returns a new array map with supplied mappings." + [& keyvals] + (let [arr (if (and (instance? IndexedSeq keyvals) (zero? (.-i keyvals))) + (.-arr keyvals) + (into-array keyvals))] + (if (odd? (alength arr)) + (throw (js/Error. (str_ "No value supplied for key: " (last arr)))) + (.createAsIfByAssoc PersistentArrayMap arr)))) + +(defn seq-to-map-for-destructuring + "Builds a map from a seq as described in + https://clojure.org/reference/special_forms#keyword-arguments" + [s] + (if ^boolean LITE_MODE + (if (next s) + (.createAsIfByAssoc ObjMap (to-array s)) + (if (seq s) (first s) (.-EMPTY ObjMap))) + (if (next s) + (.createAsIfByAssoc PersistentArrayMap (to-array s)) + (if (seq s) (first s) (.-EMPTY PersistentArrayMap))))) + +(defn sorted-map + "keyval => key val + Returns a new sorted map with supplied mappings." + ([& keyvals] + (loop [in (seq keyvals) out (.-EMPTY PersistentTreeMap)] + (if in + (recur (nnext in) (assoc out (first in) (second in))) + out)))) + +(defn sorted-map-by + "keyval => key val + Returns a new sorted map with supplied mappings, using the supplied comparator." + ([comparator & keyvals] + (loop [in (seq keyvals) + out (PersistentTreeMap. (fn->comparator comparator) nil 0 nil 0)] + (if in + (recur (nnext in) (assoc out (first in) (second in))) + out)))) + +(deftype KeySeq [^not-native mseq _meta] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IMeta + (-meta [coll] _meta) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta _meta) + coll + (KeySeq. mseq new-meta))) + + ISeqable + (-seq [coll] coll) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + ICollection + (-conj [coll o] + (cons o coll)) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + IHash + (-hash [coll] (hash-ordered-coll coll)) + + ISeq + (-first [coll] + (let [^not-native me (-first mseq)] + (-key me))) + + (-rest [coll] + (let [nseq (if (satisfies? INext mseq) + (-next mseq) + (next mseq))] + (if-not (nil? nseq) + (KeySeq. nseq nil) + ()))) + + INext + (-next [coll] + (let [nseq (if (satisfies? INext mseq) + (-next mseq) + (next mseq))] + (when-not (nil? nseq) + (KeySeq. nseq nil)))) + + IReduce + (-reduce [coll f] (seq-reduce f coll)) + (-reduce [coll f start] (seq-reduce f start coll))) + +(es6-iterable KeySeq) + +(defn keys + "Returns a sequence of the map's keys, in the same order as (seq map)." + [map] + (when-let [mseq (seq map)] + (KeySeq. mseq nil))) + +(defn key + "Returns the key of the map entry." + [map-entry] + (-key map-entry)) + +(deftype ValSeq [^not-native mseq _meta] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + IMeta + (-meta [coll] _meta) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta _meta) + coll + (ValSeq. mseq new-meta))) + + ISeqable + (-seq [coll] coll) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + ICollection + (-conj [coll o] + (cons o coll)) + + IEmptyableCollection + (-empty [coll] (.-EMPTY List)) + + IHash + (-hash [coll] (hash-ordered-coll coll)) + + ISeq + (-first [coll] + (let [^not-native me (-first mseq)] + (-val me))) + + (-rest [coll] + (let [nseq (if (satisfies? INext mseq) + (-next mseq) + (next mseq))] + (if-not (nil? nseq) + (ValSeq. nseq nil) + ()))) + + INext + (-next [coll] + (let [nseq (if (satisfies? INext mseq) + (-next mseq) + (next mseq))] + (when-not (nil? nseq) + (ValSeq. nseq nil)))) + + IReduce + (-reduce [coll f] (seq-reduce f coll)) + (-reduce [coll f start] (seq-reduce f start coll))) + +(es6-iterable ValSeq) + +(defn vals + "Returns a sequence of the map's values, in the same order as (seq map)." + [map] + (when-let [mseq (seq map)] + (ValSeq. mseq nil))) + +(defn val + "Returns the value in the map entry." + [map-entry] + (-val map-entry)) + +(defn merge + "Returns a map that consists of the rest of the maps conj-ed onto + the first. If a key occurs in more than one map, the mapping from + the latter (left-to-right) will be the mapping in the result." + [& maps] + (when (some identity maps) + (reduce #(conj (or %1 {}) %2) maps))) + +(defn merge-with + "Returns a map that consists of the rest of the maps conj-ed onto + the first. If a key occurs in more than one map, the mapping(s) + from the latter (left-to-right) will be combined with the mapping in + the result by calling (f val-in-result val-in-latter)." + [f & maps] + (when (some identity maps) + (let [merge-entry (fn [m e] + (let [k (key e) v (val e)] + (if (contains? m k) + (assoc m k (f (get m k) v)) + (assoc m k v)))) + merge2 (fn [m1 m2] + (reduce merge-entry (or m1 {}) (seq m2)))] + (reduce merge2 maps)))) + +(defn select-keys + "Returns a map containing only those entries in map whose key is in keys" + [map keyseq] + (loop [ret {} keys (seq keyseq)] + (if keys + (let [key (first keys) + entry (get map key ::not-found)] + (recur + (if (not= entry ::not-found) + (assoc ret key entry) + ret) + (next keys))) + (-with-meta ret (meta map))))) + +;;; PersistentHashSet + +(declare TransientHashSet) + +(deftype HashSetIter [iter] + Object + (hasNext [_] + (.hasNext iter)) + (next [_] + (if ^boolean (.hasNext iter) + (.-key (.next iter)) + (throw (js/Error. "No such element")))) + (remove [_] (js/Error. "Unsupported operation"))) + +(deftype PersistentHashSet [meta hash-map ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (keys [coll] + (es6-iterator (seq coll))) + (entries [coll] + (es6-set-entries-iterator (seq coll))) + (values [coll] + (es6-iterator (seq coll))) + (has [coll k] + (contains? coll k)) + (forEach [coll f] + (doseq [[k v] coll] + (f v k))) + + ICloneable + (-clone [_] (PersistentHashSet. meta hash-map __hash)) + + IIterable + (-iterator [coll] + (HashSetIter. (-iterator hash-map))) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (PersistentHashSet. new-meta hash-map __hash))) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll o] + (let [m (-assoc hash-map o nil)] + (if (identical? m hash-map) + coll + (PersistentHashSet. meta m nil)))) + + IEmptyableCollection + (-empty [coll] (-with-meta (.-EMPTY PersistentHashSet) meta)) + + IEquiv + (-equiv [coll other] + (and + (set? other) + (== (count coll) (count other)) + ^boolean + (try + (reduce-kv + #(or (contains? other %2) (reduced false)) + true hash-map) + (catch js/Error ex + false)))) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] (keys hash-map)) + + ICounted + (-count [coll] (-count hash-map)) + + ILookup + (-lookup [coll v] + (-lookup coll v nil)) + (-lookup [coll v not-found] + (if-let [entry (-find hash-map v)] + (key entry) + not-found)) + + ISet + (-disjoin [coll v] + (let [m (-dissoc hash-map v)] + (if (identical? m hash-map) + coll + (PersistentHashSet. meta m nil)))) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IEditableCollection + (-as-transient [coll] (TransientHashSet. (-as-transient hash-map)))) + +(set! (.-EMPTY PersistentHashSet) + (PersistentHashSet. nil (.-EMPTY PersistentArrayMap) empty-unordered-hash)) + +(set! (.-fromArray PersistentHashSet) + (fn [items ^boolean no-clone] + (let [len (alength items)] + (if (<= len (.-HASHMAP-THRESHOLD PersistentArrayMap)) + (let [arr (if no-clone items (aclone items))] + (loop [i 0 + out (transient (.-EMPTY PersistentArrayMap))] + (if (< i len) + (recur (inc i) (-assoc! out (aget items i) nil)) + (PersistentHashSet. nil (-persistent! out) nil)))) + (loop [i 0 + out (transient (.-EMPTY PersistentHashSet))] + (if (< i len) + (recur (inc i) (-conj! out (aget items i))) + (-persistent! out))))))) + +(set! (.-createWithCheck PersistentHashSet) + (fn [items] + (let [len (alength items) + t (-as-transient (.-EMPTY PersistentHashSet))] + (dotimes [i len] + (-conj! t (aget items i)) + (when-not (= (count t) (inc i)) + (throw (js/Error. (str_ "Duplicate key: " (aget items i)))))) + (-persistent! t)))) + +(set! (.-createAsIfByAssoc PersistentHashSet) + (fn [items] + (let [len (alength items) + t (-as-transient (.-EMPTY PersistentHashSet))] + (dotimes [i len] (-conj! t (aget items i))) + (-persistent! t)))) + +(es6-iterable PersistentHashSet) + +(deftype TransientHashSet [^:mutable transient-map] + ITransientCollection + (-conj! [tcoll o] + (set! transient-map (assoc! transient-map o nil)) + tcoll) + + (-persistent! [tcoll] + (PersistentHashSet. nil (persistent! transient-map) nil)) + + ITransientSet + (-disjoin! [tcoll v] + (set! transient-map (dissoc! transient-map v)) + tcoll) + + ICounted + (-count [tcoll] (count transient-map)) + + ILookup + (-lookup [tcoll v] + (-lookup tcoll v nil)) + + (-lookup [tcoll v not-found] + (if (identical? (-lookup transient-map v lookup-sentinel) lookup-sentinel) + not-found + v)) + + IFn + (-invoke [tcoll k] + (if (identical? (-lookup transient-map k lookup-sentinel) lookup-sentinel) + nil + k)) + + (-invoke [tcoll k not-found] + (if (identical? (-lookup transient-map k lookup-sentinel) lookup-sentinel) + not-found + k))) + +(deftype PersistentTreeSet [meta tree-map ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (keys [coll] + (es6-iterator (seq coll))) + (entries [coll] + (es6-set-entries-iterator (seq coll))) + (values [coll] + (es6-iterator (seq coll))) + (has [coll k] + (contains? coll k)) + (forEach [coll f] + (doseq [[k v] coll] + (f v k))) + + ICloneable + (-clone [_] (PersistentTreeSet. meta tree-map __hash)) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (PersistentTreeSet. new-meta tree-map __hash))) + + IMeta + (-meta [coll] meta) + + ICollection + (-conj [coll o] + (let [m (-assoc tree-map o nil)] + (if (identical? m tree-map) + coll + (PersistentTreeSet. meta m nil)))) + + IEmptyableCollection + (-empty [coll] (PersistentTreeSet. meta (-empty tree-map) 0)) + + IEquiv + (-equiv [coll other] + (and + (set? other) + (== (count coll) (count other)) + ^boolean + (try + (reduce-kv + #(or (contains? other %2) (reduced false)) + true tree-map) + (catch js/Error ex + false)))) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] (keys tree-map)) + + ISorted + (-sorted-seq [coll ascending?] + (map key (-sorted-seq tree-map ascending?))) + + (-sorted-seq-from [coll k ascending?] + (map key (-sorted-seq-from tree-map k ascending?))) + + (-entry-key [coll entry] entry) + + (-comparator [coll] (-comparator tree-map)) + + IReversible + (-rseq [coll] + (if (pos? (count tree-map)) + (map key (rseq tree-map)))) + + ICounted + (-count [coll] (count tree-map)) + + ILookup + (-lookup [coll v] + (-lookup coll v nil)) + (-lookup [coll v not-found] + (let [n (.entry-at tree-map v)] + (if-not (nil? n) + (.-key n) + not-found))) + + ISet + (-disjoin [coll v] + (let [m (-dissoc tree-map v)] + (if (identical? m tree-map) + coll + (PersistentTreeSet. meta m nil)))) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found))) + +(set! (.-EMPTY PersistentTreeSet) + (PersistentTreeSet. nil (.-EMPTY PersistentTreeMap) empty-unordered-hash)) + +(es6-iterable PersistentTreeSet) + +(defn set-from-indexed-seq [iseq] + (let [arr (.-arr iseq) + ret (areduce arr i ^not-native res (-as-transient #{}) + (-conj! res (aget arr i)))] + (-persistent! ^not-native ret))) + +(defn set + "Returns a set of the distinct elements of coll." + [coll] + (if (set? coll) + (with-meta coll nil) + (let [in (seq coll)] + (cond + (nil? in) #{} + + (and (instance? IndexedSeq in) (zero? (.-i in))) + (.createAsIfByAssoc PersistentHashSet (.-arr in)) + + :else + (loop [^not-native in in + ^not-native out (-as-transient #{})] + (if-not (nil? in) + (recur (next in) (-conj! out (-first in))) + (persistent! out))))))) + +(defn hash-set + "Returns a new hash set with supplied keys. Any equal keys are + handled as if by repeated uses of conj." + ([] #{}) + ([& keys] (set keys))) + +(defn sorted-set + "Returns a new sorted set with supplied keys." + ([& keys] + (reduce -conj (.-EMPTY PersistentTreeSet) keys))) + +(defn sorted-set-by + "Returns a new sorted set with supplied keys, using the supplied comparator." + ([comparator & keys] + (reduce -conj + (PersistentTreeSet. nil (sorted-map-by comparator) 0) + keys))) + +(defn replace + "Given a map of replacement pairs and a vector/collection, returns a + vector/seq with any elements = a key in smap replaced with the + corresponding val in smap. Returns a transducer when no collection + is provided." + ([smap] + (map #(if-let [e (find smap %)] (val e) %))) + ([smap coll] + (if (vector? coll) + (let [n (count coll)] + (reduce (fn [v i] + (if-let [e (find smap (nth v i))] + (assoc v i (second e)) + v)) + coll (take n (iterate inc 0)))) + (map #(if-let [e (find smap %)] (second e) %) coll)))) + +(defn distinct + "Returns a lazy sequence of the elements of coll with duplicates removed. + Returns a stateful transducer when no collection is provided." + ([] + (fn [rf] + (let [seen (volatile! #{})] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (if (contains? @seen input) + result + (do (vswap! seen conj input) + (rf result input)))))))) + ([coll] + (let [step (fn step [xs seen] + (lazy-seq + ((fn [[f :as xs] seen] + (when-let [s (seq xs)] + (if (contains? seen f) + (recur (rest s) seen) + (cons f (step (rest s) (conj seen f)))))) + xs seen)))] + (step coll #{})))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn butlast + "Return a seq of all but the last item in coll, in linear time" + [s] + (loop [ret [] s s] + (if (next s) + (recur (conj ret (first s)) (next s)) + (seq ret)))) + +(defn name + "Returns the name String of a string, symbol or keyword." + [x] + (if (implements? INamed x) + (-name x) + (if (string? x) + x + (throw (js/Error. (str_ "Doesn't support name: " x)))))) + +(defn zipmap + "Returns a map with the keys mapped to the corresponding vals." + [keys vals] + (loop [map (transient {}) + ks (seq keys) + vs (seq vals)] + (if (and ks vs) + (recur (assoc! map (first ks) (first vs)) + (next ks) + (next vs)) + (persistent! map)))) + +(defn max-key + "Returns the x for which (k x), a number, is greatest. + + If there are multiple such xs, the last one is returned." + ([k x] x) + ([k x y] (if (> (k x) (k y)) x y)) + ([k x y & more] + (reduce #(max-key k %1 %2) (max-key k x y) more))) + +(defn min-key + "Returns the x for which (k x), a number, is least. + + If there are multiple such xs, the last one is returned." + ([k x] x) + ([k x y] (if (< (k x) (k y)) x y)) + ([k x y & more] + (reduce #(min-key k %1 %2) (min-key k x y) more))) + +(deftype ArrayList [^:mutable arr] + Object + (add [_ x] (.push arr x)) + (size [_] (alength arr)) + (clear [_] (set! arr (array))) + (isEmpty [_] (zero? (alength arr))) + (toArray [_] arr)) + +(defn array-list [] + (ArrayList. (array))) + +(defn partition-all + "Returns a lazy sequence of lists like partition, but may include + partitions with fewer than n items at the end. Returns a stateful + transducer when no collection is provided." + ([n] + (fn [rf] + (let [a (array-list)] + (fn + ([] (rf)) + ([result] + (let [result (if (.isEmpty a) + result + (let [v (vec (.toArray a))] + ;;clear first! + (.clear a) + (unreduced (rf result v))))] + (rf result))) + ([result input] + (.add a input) + (if (== n (.size a)) + (let [v (vec (.toArray a))] + (.clear a) + (rf result v)) + result)))))) + ([n coll] + (partition-all n n coll)) + ([n step coll] + (lazy-seq + (when-let [s (seq coll)] + (cons (take n s) (partition-all n step (drop step s))))))) + +(defn splitv-at + "Returns a vector of [(into [] (take n) coll) (drop n coll)]" + [n coll] + [(into [] (take n) coll) (drop n coll)]) + +(defn partitionv + "Returns a lazy sequence of vectors of n items each, at offsets step + apart. If step is not supplied, defaults to n, i.e. the partitions + do not overlap. If a pad collection is supplied, use its elements as + necessary to complete last partition upto n items. In case there are + not enough padding elements, return a partition with less than n items." + ([n coll] + (partitionv n n coll)) + ([n step coll] + (lazy-seq + (when-let [s (seq coll)] + (let [p (into [] (take n) s)] + (when (= n (count p)) + (cons p (partitionv n step (nthrest s step)))))))) + ([n step pad coll] + (lazy-seq + (when-let [s (seq coll)] + (let [p (into [] (take n) s)] + (if (= n (count p)) + (cons p (partitionv n step pad (nthrest s step))) + (list (into [] (take n) (concat p pad))))))))) + +(defn partitionv-all + "Returns a lazy sequence of vector partitions, but may include + partitions with fewer than n items at the end. + Returns a stateful transducer when no collection is provided." + ([n] + (partition-all n)) + ([n coll] + (partitionv-all n n coll)) + ([n step coll] + (lazy-seq + (when-let [s (seq coll)] + (let [seg (into [] (take n) coll)] + (cons seg (partitionv-all n step (drop step s)))))))) + +(defn take-while + "Returns a lazy sequence of successive items from coll while + (pred item) returns logical true. pred must be free of side-effects. + Returns a transducer when no collection is provided." + ([pred] + (fn [rf] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (if (pred input) + (rf result input) + (reduced result)))))) + ([pred coll] + (lazy-seq + (when-let [s (seq coll)] + (when (pred (first s)) + (cons (first s) (take-while pred (rest s)))))))) + +(defn mk-bound-fn + [sc test key] + (fn [e] + (let [comp (-comparator sc)] + (test (comp (-entry-key sc e) key) 0)))) + +(defn subseq + "sc must be a sorted collection, test(s) one of <, <=, > or + >=. Returns a seq of those entries with keys ek for + which (test (.. sc comparator (compare ek key)) 0) is true" + ([sc test key] + (let [include (mk-bound-fn sc test key)] + (if (#{> >=} test) + (when-let [[e :as s] (-sorted-seq-from sc key true)] + (if (include e) s (next s))) + (take-while include (-sorted-seq sc true))))) + ([sc start-test start-key end-test end-key] + (when-let [[e :as s] (-sorted-seq-from sc start-key true)] + (take-while (mk-bound-fn sc end-test end-key) + (if ((mk-bound-fn sc start-test start-key) e) s (next s)))))) + +(defn rsubseq + "sc must be a sorted collection, test(s) one of <, <=, > or + >=. Returns a reverse seq of those entries with keys ek for + which (test (.. sc comparator (compare ek key)) 0) is true" + ([sc test key] + (let [include (mk-bound-fn sc test key)] + (if (#{< <=} test) + (when-let [[e :as s] (-sorted-seq-from sc key false)] + (if (include e) s (next s))) + (take-while include (-sorted-seq sc false))))) + ([sc start-test start-key end-test end-key] + (when-let [[e :as s] (-sorted-seq-from sc end-key false)] + (take-while (mk-bound-fn sc start-test start-key) + (if ((mk-bound-fn sc end-test end-key) e) s (next s)))))) + +(deftype IntegerRangeChunk [start step count] + ICounted + (-count [coll] count) + + ISeq + (-first [coll] start) + + IIndexed + (-nth [coll i] + (+ start (* i step))) + (-nth [coll i not-found] + (if (and (>= i 0) (< i count)) + (+ start (* i step)) + not-found)) + + IChunk + (-drop-first [coll] + (if (<= count 1) + (throw (js/Error. "-drop-first of empty chunk")) + (IntegerRangeChunk. (+ start step) step (dec count))))) + +(deftype RangeIterator [^:mutable i end step] + Object + (hasNext [_] + (if (pos? step) + (< i end) + (> i end))) + (next [_] + (let [ret i] + (set! i (+ i step)) + ret))) + +(defn- range-count + "Returns exact size of remaining items in an IntegerRange." + [start end step] + (Math/ceil (/ (- end start) step))) + +(deftype IntegerRange [meta start end step cnt ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + ICloneable + (-clone [_] (IntegerRange. meta start end step cnt __hash)) + + IWithMeta + (-with-meta [rng new-meta] + (if (identical? new-meta meta) + rng + (IntegerRange. new-meta start end step cnt __hash))) + + IMeta + (-meta [rng] meta) + + ISeqable + (-seq [rng] rng) + + ISeq + (-first [rng] start) + (-rest [rng] + (let [s (-next rng)] + (if (nil? s) + () + s))) + + IIterable + (-iterator [_] + (RangeIterator. start end step)) + + INext + (-next [rng] + (if (pos? step) + (when (< (+ start step) end) + (IntegerRange. nil (+ start step) end step (range-count (+ start step) end step) nil)) + (when (> (+ start step) end) + (IntegerRange. nil (+ start step) end step (range-count (+ start step) end step) nil)))) + + IDrop + (-drop [rng n] + (if (pos? n) + (if (< n cnt) + (IntegerRange. nil (+ start (* step n)) end step (- cnt n) nil) + nil) + rng)) + + IChunkedSeq + (-chunked-first [rng] + (IntegerRangeChunk. start step (unchecked-min cnt 32))) + (-chunked-rest [rng] + (if (<= cnt 32) + () + (let [start (+ start (* step 32))] + (cond + (pos? step) + (if (<= end start) + () + (IntegerRange. nil start end step (range-count start end step) nil)) + + (neg? step) + (if (>= end start) + () + (IntegerRange. nil start end step (range-count start end step) nil)) + + :else + (if (== end start) + () + (repeat start)))))) + + IChunkedNext + (-chunked-next [rng] + (seq (-chunked-rest rng))) + + ICollection + (-conj [rng o] (cons o rng)) + + IEmptyableCollection + (-empty [rng] (.-EMPTY List)) + + ISequential + IEquiv + (-equiv [rng other] (equiv-sequential rng other)) + + IHash + (-hash [rng] (caching-hash rng hash-ordered-coll __hash)) + + ICounted + (-count [rng] + cnt) + + IIndexed + (-nth [rng n] + (if (and (<= 0 n) (< n (-count rng))) + (+ start (* n step)) + (if (and (<= 0 n) (> start end) (zero? step)) + start + (throw (js/Error. "Index out of bounds"))))) + (-nth [rng n not-found] + (if (and (<= 0 n) (< n (-count rng))) + (+ start (* n step)) + (if (and (<= 0 n) (> start end) (zero? step)) + start + not-found))) + + IReduce + (-reduce [rng f] (ci-reduce rng f)) + (-reduce [rng f init] + (loop [i start ret init] + (if (if (pos? step) (< i end) (> i end)) + (let [ret (f ret i)] + (if (reduced? ret) + @ret + (recur (+ i step) ret))) + ret)))) + +(es6-iterable IntegerRange) + +(deftype Range [meta start end step ^:mutable chunk ^:mutable chunk-next ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [this other] + (-equiv this other)) + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + (forceChunk [coll] + (when (nil? chunk) + (let [arr (make-array 32) + val (loop [n 0 val start] + (if (< n 32) + (do + (aset arr n val) + (let [n (inc n) + val (+ val step)] + (if (if (pos? step) (< val end) (> val end)) + (recur n val) + (set! chunk (array-chunk arr 0 n))))) + val))] + (when (nil? chunk) + (set! chunk (array-chunk arr 0 32)) + (when (if (pos? step) (< val end) (> val end)) + (set! chunk-next (Range. nil val end step nil nil nil))))))) + + ICloneable + (-clone [_] (Range. meta start end step chunk chunk-next __hash)) + + IWithMeta + (-with-meta [rng new-meta] + (if (identical? new-meta meta) + rng + (Range. new-meta start end step chunk chunk-next __hash))) + + IMeta + (-meta [rng] meta) + + ISeqable + (-seq [rng] rng) + + ISeq + (-first [rng] start) + (-rest [rng] + (let [s (-next rng)] + (if (nil? s) + () + s))) + + IIterable + (-iterator [_] + (RangeIterator. start end step)) + + INext + (-next [rng] + (if (pos? step) + (when (< (+ start step) end) + (Range. nil (+ start step) end step nil nil nil)) + (when (> (+ start step) end) + (Range. nil (+ start step) end step nil nil nil)))) + + IChunkedSeq + (-chunked-first [rng] + (.forceChunk rng) + chunk) + (-chunked-rest [rng] + (.forceChunk rng) + (if (nil? chunk-next) + () + chunk-next)) + + IChunkedNext + (-chunked-next [rng] + (seq (-chunked-rest rng))) + + ICollection + (-conj [rng o] (cons o rng)) + + IEmptyableCollection + (-empty [rng] (.-EMPTY List)) + + ISequential + IEquiv + (-equiv [rng other] (equiv-sequential rng other)) + + IHash + (-hash [rng] (caching-hash rng hash-ordered-coll __hash)) + + IReduce + (-reduce [rng f] (seq-reduce f rng)) + (-reduce [rng f init] + (loop [i start ret init] + (if (if (pos? step) (< i end) (> i end)) + (let [ret (f ret i)] + (if (reduced? ret) + @ret + (recur (+ i step) ret))) + ret)))) + +(es6-iterable Range) + +(defn range + "Returns a lazy seq of nums from start (inclusive) to end + (exclusive), by step, where start defaults to 0, step to 1, + and end to infinity." + ([] (range 0 (.-MAX_VALUE js/Number) 1)) + ([end] (range 0 end 1)) + ([start end] (range start end 1)) + ([start end step] + (cond + (pos? step) + (if (<= end start) + () + (if (and (integer? start) (integer? end) (integer? step)) + (IntegerRange. nil start end step (range-count start end step) nil) + (Range. nil start end step nil nil nil))) + + (neg? step) + (if (>= end start) + () + (if (and (integer? start) (integer? end) (integer? step)) + (IntegerRange. nil start end step (range-count start end step) nil) + (Range. nil start end step nil nil nil))) + + :else + (if (== end start) + () + (repeat start))))) + +(defn take-nth + "Returns a lazy seq of every nth item in coll. Returns a stateful + transducer when no collection is provided." + ([n] + {:pre [(number? n)]} + (fn [rf] + (let [ia (volatile! -1)] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (let [i (vswap! ia inc)] + (if (zero? (rem i n)) + (rf result input) + result))))))) + ([n coll] + {:pre [(number? n)]} + (lazy-seq + (when-let [s (seq coll)] + (cons (first s) (take-nth n (drop n s))))))) + +(defn split-with + "Returns a vector of [(take-while pred coll) (drop-while pred coll)]" + [pred coll] + [(take-while pred coll) (drop-while pred coll)]) + +(defn partition-by + "Applies f to each value in coll, splitting it each time f returns a + new value. Returns a lazy seq of partitions. Returns a stateful + transducer when no collection is provided." + ([f] + (fn [rf] + (let [a (array-list) + pa (volatile! ::none)] + (fn + ([] (rf)) + ([result] + (let [result (if (.isEmpty a) + result + (let [v (vec (.toArray a))] + ;;clear first! + (.clear a) + (unreduced (rf result v))))] + (rf result))) + ([result input] + (let [pval @pa + val (f input)] + (vreset! pa val) + (if (or (keyword-identical? pval ::none) + (= val pval)) + (do + (.add a input) + result) + (let [v (vec (.toArray a))] + (.clear a) + (let [ret (rf result v)] + (when-not (reduced? ret) + (.add a input)) + ret))))))))) + ([f coll] + (lazy-seq + (when-let [s (seq coll)] + (let [fst (first s) + fv (f fst) + run (cons fst (take-while #(= fv (f %)) (next s)))] + (cons run (partition-by f (lazy-seq (drop (count run) s))))))))) + +(defn frequencies + "Returns a map from distinct items in coll to the number of times + they appear." + [coll] + (persistent! + (reduce (fn [counts x] + (assoc! counts x (inc (get counts x 0)))) + (transient {}) coll))) + +(defn reductions + "Returns a lazy seq of the intermediate values of the reduction (as + per reduce) of coll by f, starting with init." + ([f coll] + (lazy-seq + (if-let [s (seq coll)] + (reductions f (first s) (rest s)) + (list (f))))) + ([f init coll] + (if (reduced? init) + (list @init) + (cons init + (lazy-seq + (when-let [s (seq coll)] + (reductions f (f init (first s)) (rest s)))))))) + +(defn juxt + "Takes a set of functions and returns a fn that is the juxtaposition + of those fns. The returned fn takes a variable number of args, and + returns a vector containing the result of applying each fn to the + args (left-to-right). + ((juxt a b c) x) => [(a x) (b x) (c x)]" + ([f] + (fn + ([] (vector (f))) + ([x] (vector (f x))) + ([x y] (vector (f x y))) + ([x y z] (vector (f x y z))) + ([x y z & args] (vector (apply f x y z args))))) + ([f g] + (fn + ([] (vector (f) (g))) + ([x] (vector (f x) (g x))) + ([x y] (vector (f x y) (g x y))) + ([x y z] (vector (f x y z) (g x y z))) + ([x y z & args] (vector (apply f x y z args) (apply g x y z args))))) + ([f g h] + (fn + ([] (vector (f) (g) (h))) + ([x] (vector (f x) (g x) (h x))) + ([x y] (vector (f x y) (g x y) (h x y))) + ([x y z] (vector (f x y z) (g x y z) (h x y z))) + ([x y z & args] (vector (apply f x y z args) (apply g x y z args) (apply h x y z args))))) + ([f g h & fs] + (let [fs (list* f g h fs)] + (fn + ([] (reduce #(conj %1 (%2)) [] fs)) + ([x] (reduce #(conj %1 (%2 x)) [] fs)) + ([x y] (reduce #(conj %1 (%2 x y)) [] fs)) + ([x y z] (reduce #(conj %1 (%2 x y z)) [] fs)) + ([x y z & args] (reduce #(conj %1 (apply %2 x y z args)) [] fs)))))) + +(defn dorun + "When lazy sequences are produced via functions that have side + effects, any effects other than those needed to produce the first + element in the seq do not occur until the seq is consumed. dorun can + be used to force any effects. Walks through the successive nexts of + the seq, does not retain the head and returns nil." + ([coll] + (when-let [s (seq coll)] + (recur (next s)))) + ([n coll] + (when (and (seq coll) (pos? n)) + (recur (dec n) (next coll))))) + +(defn doall + "When lazy sequences are produced via functions that have side + effects, any effects other than those needed to produce the first + element in the seq do not occur until the seq is consumed. doall can + be used to force any effects. Walks through the successive nexts of + the seq, retains the head and returns it, thus causing the entire + seq to reside in memory at one time." + ([coll] + (dorun coll) + coll) + ([n coll] + (dorun n coll) + coll)) + +;;;;;;;;;;;;;;;;;;;;;;;;; Regular Expressions ;;;;;;;;;; + +(defn regexp? + "Returns true if x is a JavaScript RegExp instance." + [x] + (instance? js/RegExp x)) + +(defn re-matches + "Returns the result of (re-find re s) if re fully matches s." + [re s] + (if (string? s) + (let [matches (.exec re s)] + (when (and (not (nil? matches)) + (= (aget matches 0) s)) + (if (== (count ^array matches) 1) + (aget matches 0) + (vec matches)))) + (throw (js/TypeError. "re-matches must match against a string.")))) + + +(defn re-find + "Returns the first regex match, if any, of s to re, using + re.exec(s). Returns a vector, containing first the matching + substring, then any capturing groups if the regular expression contains + capturing groups." + [re s] + (if (string? s) + (let [matches (.exec re s)] + (when-not (nil? matches) + (if (== (count ^array matches) 1) + (aget matches 0) + (vec matches)))) + (throw (js/TypeError. "re-find must match against a string.")))) + +(defn- re-seq* [re s] + (when-some [matches (.exec re s)] + (let [match-str (aget matches 0) + match-vals (if (== (.-length matches) 1) + match-str + (vec matches))] + (cons match-vals + (lazy-seq + (let [post-idx (+ (.-index matches) + (unchecked-max 1 (.-length match-str)))] + (when (<= post-idx (.-length s)) + (re-seq* re (subs s post-idx))))))))) + +(defn re-seq + "Returns a lazy sequence of successive matches of re in s." + [re s] + (if (string? s) + (re-seq* re s) + (throw (js/TypeError. "re-seq must match against a string.")))) + +(defn re-pattern + "Returns an instance of RegExp which has compiled the provided string." + [s] + (if (instance? js/RegExp s) + s + (let [[prefix flags] (re-find #"^\(\?([idmsux]*)\)" s) + pattern (subs s (if (nil? prefix) + 0 + (count ^string prefix)))] + (js/RegExp. pattern (or flags ""))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Printing ;;;;;;;;;;;;;;;; + +(defn pr-sequential-writer [writer print-one begin sep end opts coll] + (binding [*print-level* (when-not (nil? *print-level*) (dec *print-level*))] + (if (and (not (nil? *print-level*)) (neg? *print-level*)) + (-write writer "#") + (do + (-write writer begin) + (if (zero? (pr-opts-len opts)) + (when (seq coll) + (-write writer (or (:more-marker opts) "..."))) + (do + (when (seq coll) + (print-one (first coll) writer opts)) + (loop [coll (next coll) n (dec (pr-opts-len opts))] + (if (and coll (or (nil? n) (not (zero? n)))) + (do + (-write writer sep) + (print-one (first coll) writer opts) + (recur (next coll) (dec n))) + (when (and (seq coll) (zero? n)) + (-write writer sep) + (-write writer (or (:more-marker opts) "..."))))))) + (-write writer end))))) + +(defn write-all [writer & ss] + (loop [ss (seq ss)] + (when-not (nil? ss) + (-write writer (first ss)) + (recur (next ss))))) + +(defn string-print [x] + (when (nil? *print-fn*) + (throw (js/Error. "No *print-fn* fn set for evaluation environment"))) + (*print-fn* x) + nil) + +(defn flush [] ;stub + nil) + +(def ^:private char-escapes + (js-obj + "\"" "\\\"" + "\\" "\\\\" + "\b" "\\b" + "\f" "\\f" + "\n" "\\n" + "\r" "\\r" + "\t" "\\t")) + +(defn ^:private quote-string + [s] + (str_ \" + (.replace s (js/RegExp "[\\\\\"\b\f\n\r\t]" "g") + (fn [match] (unchecked-get char-escapes match))) + \")) + +(declare print-map) + +(defn print-meta? [opts obj] + (and (boolean (pr-opts-meta opts)) + (implements? IMeta obj) + (not (nil? (meta obj))))) + +(declare VectorLite) + +(defn- pr-writer-impl + [obj writer opts] + (cond + (nil? obj) (-write writer "nil") + :else + (do + (when (print-meta? opts obj) + (-write writer "^") + (pr-writer (meta obj) writer opts) + (-write writer " ")) + (cond + ;; FIXME: can we figure out something better here? + ;; handle CLJS ctors + ^boolean (.-cljs$lang$type obj) + (.cljs$lang$ctorPrWriter obj obj writer opts) + + ; Use the new, more efficient, IPrintWithWriter interface when possible. + (satisfies? IPrintWithWriter obj) + (-pr-writer obj writer opts) + + (or (true? obj) (false? obj)) + (-write writer (str_ obj)) + + (number? obj) + (-write writer + (cond + (js/isNaN obj) "##NaN" + (identical? obj js/Number.POSITIVE_INFINITY) "##Inf" + (identical? obj js/Number.NEGATIVE_INFINITY) "##-Inf" + (js/Object.is obj -0.0) "-0.0" + :else (str_ obj))) + + (object? obj) + (do + (-write writer "#js ") + (print-map + (.map + (js-keys obj) + (fn [k] + (MapEntry. + (cond-> k (some? (.match k #"^[A-Za-z_\*\+\?!\-'][\w\*\+\?!\-']*$")) keyword) + (unchecked-get obj k) + nil))) + pr-writer writer opts)) + + (array? obj) + (pr-sequential-writer writer pr-writer "#js [" " " "]" opts obj) + + (string? obj) + (if (pr-opts-readably opts) + (-write writer (quote-string obj)) + (-write writer obj)) + + (js-fn? obj) + (let [name (.-name obj) + name (if (or (nil? name) (gstring/isEmpty name)) + "Function" + name)] + (write-all writer "#object[" name + (if *print-fn-bodies* + (str_ " \"" (str_ obj) "\"") + "") + "]")) + + (instance? js/Date obj) + (let [normalize (fn [n len] + (loop [ns (str_ n)] + (if (< (count ns) len) + (recur (str_ "0" ns)) + ns)))] + (write-all writer + "#inst \"" + (normalize (.getUTCFullYear obj) 4) "-" + (normalize (inc (.getUTCMonth obj)) 2) "-" + (normalize (.getUTCDate obj) 2) "T" + (normalize (.getUTCHours obj) 2) ":" + (normalize (.getUTCMinutes obj) 2) ":" + (normalize (.getUTCSeconds obj) 2) "." + (normalize (.getUTCMilliseconds obj) 3) "-" + "00:00\"")) + + (regexp? obj) (write-all writer "#\"" (.-source obj) "\"") + + (js-symbol? obj) (write-all writer "#object[" (.toString obj) "]" ) + + :else + (if (some-> obj .-constructor .-cljs$lang$ctorStr) + (write-all writer + "#object[" (.replace (.. obj -constructor -cljs$lang$ctorStr) + (js/RegExp. "/" "g") ".") "]") + (let [name (some-> obj .-constructor .-name) + name (if (or (nil? name) (gstring/isEmpty name)) + "Object" + name)] + (if (nil? (. obj -constructor)) + (write-all writer "#object[" name "]") + (write-all writer "#object[" name " " (str_ obj) "]")))))))) + +(defn- pr-writer + "Prefer this to pr-seq, because it makes the printing function + configurable, allowing efficient implementations such as appending + to a StringBuffer." + [obj writer opts] + (if-let [alt-impl (:alt-impl opts)] + (alt-impl obj writer (-assoc opts :fallback-impl pr-writer-impl)) + (pr-writer-impl obj writer opts))) + +(defn pr-seq-writer [objs writer opts] + (pr-writer (first objs) writer opts) + (loop [objs (next objs)] + (when-not (nil? objs) + (-write writer " ") + (pr-writer (first objs) writer opts) + (recur (next objs))))) + +(defn- pr-sb-with-opts [objs opts] + (let [sb (StringBuffer.) + writer (StringBufferWriter. sb)] + (pr-seq-writer objs writer opts) + (-flush writer) + sb)) + +(defn pr-str-with-opts + "Prints a sequence of objects to a string, observing all the + options given in opts" + [objs opts] + (if (empty? objs) + "" + (str_ (pr-sb-with-opts objs opts)))) + +(defn prn-str-with-opts + "Same as pr-str-with-opts followed by (newline)" + [objs opts] + (if (empty? objs) + "\n" + (let [sb (pr-sb-with-opts objs opts)] + (.append sb \newline) + (str_ sb)))) + +(defn- pr-with-opts + "Prints a sequence of objects using string-print, observing all + the options given in opts" + [objs opts] + (string-print (pr-str-with-opts objs opts))) + +(defn newline + "Prints a newline using *print-fn*" + ([] (newline nil)) + ([opts] + (string-print "\n") + (when (pr-opts-fnl opts) + (flush)))) + +(defn pr-str + "pr to a string, returning it. Fundamental entrypoint to IPrintWithWriter." + [& objs] + (pr-str-with-opts objs nil)) + +(defn prn-str + "Same as pr-str followed by (newline)" + [& objs] + (prn-str-with-opts objs nil)) + +(defn pr + "Prints the object(s) using string-print. Prints the + object(s), separated by spaces if there is more than one. + By default, pr and prn print in a way that objects can be + read by the reader" + [& objs] + (pr-with-opts objs nil)) + +(def ^{:doc + "Prints the object(s) using string-print. + print and println produce output for human consumption."} + print + (fn cljs-core-print [& objs] + (binding [*print-readably* false] + (pr-with-opts objs nil)))) + +(defn print-str + "print to a string, returning it" + [& objs] + (binding [*print-readably* false] + (pr-str-with-opts objs nil))) + +(defn println + "Same as print followed by (newline)" + [& objs] + (binding [*print-readably* false] + (pr-with-opts objs nil)) + (when *print-newline* + (newline nil))) + +(defn println-str + "println to a string, returning it" + [& objs] + (binding [*print-readably* false] + (prn-str-with-opts objs nil))) + +(defn prn + "Same as pr followed by (newline)." + [& objs] + (pr-with-opts objs nil) + (when *print-newline* + (newline nil))) + +(defn- strip-ns + [named] + (if (symbol? named) + (symbol nil (name named)) + (keyword nil (name named)))) + +(defn- lift-ns + "Returns #js [lifted-ns lifted-map] or nil if m can't be lifted." + [m] + (when *print-namespace-maps* + (let [lm #js []] + (loop [ns nil + [[k v :as entry] & entries] (seq m)] + (if entry + (when (or (keyword? k) (symbol? k)) + (if ns + (when (= ns (namespace k)) + (.push lm (MapEntry. (strip-ns k) v nil)) + (recur ns entries)) + (when-let [new-ns (namespace k)] + (.push lm (MapEntry. (strip-ns k) v nil)) + (recur new-ns entries)))) + #js [ns lm]))))) + +(defn print-prefix-map [prefix m print-one writer opts] + (pr-sequential-writer + writer + (fn [e w opts] + (do (print-one (key e) w opts) + (-write w \space) + (print-one (val e) w opts))) + (str_ prefix "{") ", " "}" + opts (seq m))) + +(defn print-map [m print-one writer opts] + (let [ns&lift-map (when (map? m) + (lift-ns m)) + ns (some-> ns&lift-map (aget 0))] + (if ns + (print-prefix-map (str_ "#:" ns) (aget ns&lift-map 1) print-one writer opts) + (print-prefix-map nil m print-one writer opts)))) + +(extend-protocol IPrintWithWriter + LazySeq + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + TransformerIterator + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + IndexedSeq + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + RSeq + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + PersistentQueue + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "#queue [" " " "]" opts (seq coll))) + + PersistentQueueSeq + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + PersistentTreeMapSeq + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + NodeSeq + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + ArrayNodeSeq + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + List + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + Cons + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + EmptyList + (-pr-writer [coll writer opts] (-write writer "()")) + + PersistentVector + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) + + ChunkedCons + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + ChunkedSeq + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + Subvec + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) + + BlackNode + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) + + RedNode + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) + + MapEntry + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll)) + + KeySeq + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + ValSeq + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + PersistentArrayMapSeq + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + PersistentArrayMap + (-pr-writer [coll writer opts] + (print-map coll pr-writer writer opts)) + + PersistentHashMap + (-pr-writer [coll writer opts] + (print-map coll pr-writer writer opts)) + + PersistentTreeMap + (-pr-writer [coll writer opts] + (print-map coll pr-writer writer opts)) + + PersistentHashSet + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "#{" " " "}" opts coll)) + + PersistentTreeSet + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "#{" " " "}" opts coll)) + + Range + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + IntegerRange + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + Cycle + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + Repeat + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + Iterate + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + ES6IteratorSeq + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "(" " " ")" opts coll)) + + Atom + (-pr-writer [a writer opts] + (-write writer "#object[cljs.core.Atom ") + (pr-writer {:val (.-state a)} writer opts) + (-write writer "]")) + + Volatile + (-pr-writer [a writer opts] + (-write writer "#object[cljs.core.Volatile ") + (pr-writer {:val (.-state a)} writer opts) + (-write writer "]")) + + Var + (-pr-writer [a writer opts] + (-write writer "#'") + (pr-writer (.-sym a) writer opts))) + +;; IComparable +(extend-protocol IComparable + Symbol + (-compare [x y] + (if (symbol? y) + (compare-symbols x y) + (throw (js/Error. (str_ "Cannot compare " x " to " y))))) + + Keyword + (-compare [x y] + (if (keyword? y) + (compare-keywords x y) + (throw (js/Error. (str_ "Cannot compare " x " to " y))))) + + Subvec + (-compare [x y] + (if (vector? y) + (compare-indexed x y) + (throw (js/Error. (str_ "Cannot compare " x " to " y))))) + + PersistentVector + (-compare [x y] + (if (vector? y) + (compare-indexed x y) + (throw (js/Error. (str_ "Cannot compare " x " to " y))))) + + MapEntry + (-compare [x y] + (if (vector? y) + (compare-indexed x y) + (throw (js/Error. (str_ "Cannot compare " x " to " y))))) + + BlackNode + (-compare [x y] + (if (vector? y) + (compare-indexed x y) + (throw (js/Error. (str_ "Cannot compare " x " to " y))))) + + RedNode + (-compare [x y] + (if (vector? y) + (compare-indexed x y) + (throw (js/Error. (str_ "Cannot compare " x " to " y)))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Reference Types ;;;;;;;;;;;;;;;; + +(defn alter-meta! + "Atomically sets the metadata for a namespace/var/ref/agent/atom to be: + + (apply f its-current-meta args) + + f must be free of side-effects" + [iref f & args] + (set! (.-meta iref) (apply f (.-meta iref) args))) + +(defn reset-meta! + "Atomically resets the metadata for an atom" + [iref m] + (set! (.-meta iref) m)) + +(defn add-watch + "Adds a watch function to an atom reference. The watch fn must be a + fn of 4 args: a key, the reference, its old-state, its + new-state. Whenever the reference's state might have been changed, + any registered watches will have their functions called. The watch + fn will be called synchronously. Note that an atom's state + may have changed again prior to the fn call, so use old/new-state + rather than derefing the reference. Keys must be unique per + reference, and can be used to remove the watch with remove-watch, + but are otherwise considered opaque by the watch mechanism. Bear in + mind that regardless of the result or action of the watch fns the + atom's value will change. Example: + + (def a (atom 0)) + (add-watch a :inc (fn [k r o n] (assert (== 0 n)))) + (swap! a inc) + ;; Assertion Error + (deref a) + ;=> 1" + [iref key f] + (-add-watch iref key f) + iref) + +(defn remove-watch + "Removes a watch (set by add-watch) from a reference" + [iref key] + (-remove-watch iref key) + iref) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; gensym ;;;;;;;;;;;;;;;; +;; Internal - do not use! +(def + ^{:jsdoc ["@type {*}"]} + gensym_counter nil) + +(defn gensym + "Returns a new symbol with a unique name. If a prefix string is + supplied, the name is prefix# where # is some unique number. If + prefix is not supplied, the prefix is 'G__'." + ([] (gensym "G__")) + ([prefix-string] + (when (nil? gensym_counter) + (set! gensym_counter (atom 0))) + (symbol (str_ prefix-string (swap! gensym_counter inc))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Delay ;;;;;;;;;;;;;;;;;;;; + +(deftype Delay [^:mutable f ^:mutable value] + IDeref + (-deref [_] + (when f + (set! value (f)) + (set! f nil)) + value) + + IPending + (-realized? [x] + (not f)) + + IPrintWithWriter + (-pr-writer [x writer opts] + (-write writer "#object[cljs.core.Delay ") + (pr-writer {:status (if (nil? f) :ready :pending), :val value} writer opts) + (-write writer "]"))) + +(defn delay? + "returns true if x is a Delay created with delay" + [x] (instance? Delay x)) + +(defn force + "If x is a Delay, returns the (possibly cached) value of its expression, else returns x" + [x] + (if (delay? x) + (deref x) + x)) + +(defn ^boolean realized? + "Returns true if a value has been produced for a delay or lazy sequence." + [x] + (-realized? x)) + +(defn- preserving-reduced + [rf] + #(let [ret (rf %1 %2)] + (if (reduced? ret) + (reduced ret) + ret))) + +(defn cat + "A transducer which concatenates the contents of each input, which must be a + collection, into the reduction." + {:added "1.7"} + [rf] + (let [rf1 (preserving-reduced rf)] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (reduce rf1 result input))))) + +(defn halt-when + "Returns a transducer that ends transduction when pred returns true + for an input. When retf is supplied it must be a fn of 2 arguments - + it will be passed the (completed) result so far and the input that + triggered the predicate, and its return value (if it does not throw + an exception) will be the return value of the transducer. If retf + is not supplied, the input that triggered the predicate will be + returned. If the predicate never returns true the transduction is + unaffected." + {:added "1.9"} + ([pred] (halt-when pred nil)) + ([pred retf] + (fn [rf] + (fn + ([] (rf)) + ([result] + (if (and (map? result) (contains? result ::halt)) + (::halt result) + (rf result))) + ([result input] + (if (pred input) + (reduced {::halt (if retf (retf (rf result) input) input)}) + (rf result input))))))) + +(defn dedupe + "Returns a lazy sequence removing consecutive duplicates in coll. + Returns a transducer when no collection is provided." + ([] + (fn [rf] + (let [pa (volatile! ::none)] + (fn + ([] (rf)) + ([result] (rf result)) + ([result input] + (let [prior @pa] + (vreset! pa input) + (if (= prior input) + result + (rf result input)))))))) + ([coll] (sequence (dedupe) coll))) + +(declare rand) + +(defn random-sample + "Returns items from coll with random probability of prob (0.0 - + 1.0). Returns a transducer when no collection is provided." + ([prob] + (filter (fn [_] (< (rand) prob)))) + ([prob coll] + (filter (fn [_] (< (rand) prob)) coll))) + +(deftype Eduction [xform coll] + Object + (indexOf [coll x] + (-indexOf coll x 0)) + (indexOf [coll x start] + (-indexOf coll x start)) + (lastIndexOf [coll x] + (-lastIndexOf coll x (count coll))) + (lastIndexOf [coll x start] + (-lastIndexOf coll x start)) + + ISequential + + IIterable + (-iterator [_] + (.create TransformerIterator xform (iter coll))) + + ISeqable + (-seq [_] (seq (sequence xform coll))) + + IReduce + (-reduce [_ f] (transduce xform (completing f) coll)) + (-reduce [_ f init] (transduce xform (completing f) init coll)) + + IPrintWithWriter + (-pr-writer [coll writer opts] + (pr-sequential-writer writer pr-writer "(" " " ")" opts coll))) + +(es6-iterable Eduction) + +(defn eduction + "Returns a reducible/iterable application of the transducers + to the items in coll. Transducers are applied in order as if + combined with comp. Note that these applications will be + performed every time reduce/iterator is called." + {:arglists '([xform* coll])} + [& xforms] + (Eduction. (apply comp (butlast xforms)) (last xforms))) + +(defn run! + "Runs the supplied procedure (via reduce), for purposes of side + effects, on successive items in the collection. Returns nil" + [proc coll] + (reduce #(proc %2) nil coll) + nil) + +(defn iteration + "Creates a seqable/reducible via repeated calls to step, + a function of some (continuation token) 'k'. The first call to step + will be passed initk, returning 'ret'. Iff (somef ret) is true, + (vf ret) will be included in the iteration, else iteration will + terminate and vf/kf will not be called. If (kf ret) is non-nil it + will be passed to the next step call, else iteration will terminate. + This can be used e.g. to consume APIs that return paginated or batched data. + step - (possibly impure) fn of 'k' -> 'ret' + :somef - fn of 'ret' -> logical true/false, default 'some?' + :vf - fn of 'ret' -> 'v', a value produced by the iteration, default 'identity' + :kf - fn of 'ret' -> 'next-k' or nil (signaling 'do not continue'), default 'identity' + :initk - the first value passed to step, default 'nil' + It is presumed that step with non-initk is unreproducible/non-idempotent. + If step with initk is unreproducible it is on the consumer to not consume twice." + {:added "1.11"} + [step & {:keys [somef vf kf initk] + :or {vf identity + kf identity + somef some? + initk nil}}] + (reify + ISeqable + (-seq [_] + ((fn next [ret] + (when (somef ret) + (cons (vf ret) + (when-some [k (kf ret)] + (lazy-seq (next (step k))))))) + (step initk))) + IReduce + (-reduce [_ rf init] + (loop [acc init + ret (step initk)] + (if (somef ret) + (let [acc (rf acc (vf ret))] + (if (reduced? acc) + @acc + (if-some [k (kf ret)] + (recur acc (step k)) + acc))) + acc))))) + +(defprotocol IEncodeJS + (-clj->js [x] "Recursively transforms clj values to JavaScript") + (-key->js [x] "Transforms map keys to valid JavaScript keys. Arbitrary keys are + encoded to their string representation via (pr-str x)")) + +(declare clj->js) + +(defn key->js + ([k] (key->js k clj->js)) + ([k primitive-fn] + (cond + (satisfies? IEncodeJS k) (-clj->js k) + (or (string? k) + (number? k) + (keyword? k) + (symbol? k)) (primitive-fn k) + :default (pr-str k)))) + +(defn clj->js + "Recursively transforms ClojureScript values to JavaScript. + sets/vectors/lists become Arrays, Keywords and Symbol become Strings, + Maps become Objects. Arbitrary keys are encoded to by `key->js`. + Options is a key-value pair, where the only valid key is + :keyword-fn, which should point to a single-argument function to be + called on keyword keys. Default to `name`." + [x & {:keys [keyword-fn] + :or {keyword-fn name} + :as options}] + (letfn [(keyfn [k] (key->js k thisfn)) + (thisfn [x] (cond + (nil? x) nil + (satisfies? IEncodeJS x) (-clj->js x) + (keyword? x) (keyword-fn x) + (symbol? x) (str_ x) + (map? x) (let [m (js-obj)] + (doseq [[k v] x] + (gobject/set m (keyfn k) (thisfn v))) + m) + (coll? x) (let [arr (array)] + (doseq [x (map thisfn x)] + (.push arr x)) + arr) + :else x))] + (thisfn x))) + + +(defprotocol IEncodeClojure + (-js->clj [x options] "Transforms JavaScript values to Clojure")) + +(defn js->clj + "Recursively transforms JavaScript arrays into ClojureScript + vectors, and JavaScript objects into ClojureScript maps. With + option ':keywordize-keys true' will convert object fields from + strings to keywords." + ([x] (js->clj x :keywordize-keys false)) + ([x & opts] + (let [{:keys [keywordize-keys]} opts + keyfn (if keywordize-keys keyword str_) + f (fn thisfn [x] + (cond + (satisfies? IEncodeClojure x) + (-js->clj x (apply array-map opts)) + + (seq? x) + (doall (map thisfn x)) + + (map-entry? x) + (MapEntry. (thisfn (key x)) (thisfn (val x)) nil) + + (coll? x) + (into (empty x) (map thisfn) x) + + (array? x) + (persistent! + (reduce #(conj! %1 (thisfn %2)) + (transient []) x)) + + (identical? (type x) js/Object) + (persistent! + (reduce (fn [r k] (assoc! r (keyfn k) (thisfn (gobject/get x k)))) + (transient {}) (js-keys x))) + :else x))] + (f x)))) + +(defn memoize + "Returns a memoized version of a referentially transparent function. The + memoized version of the function keeps a cache of the mapping from arguments + to results and, when calls with the same arguments are repeated often, has + higher performance at the expense of higher memory use." + [f] + (let [mem (atom {})] + (fn [& args] + (let [v (get @mem args lookup-sentinel)] + (if (identical? v lookup-sentinel) + (let [ret (apply f args)] + (swap! mem assoc args ret) + ret) + v))))) + +(defn trampoline + "trampoline can be used to convert algorithms requiring mutual + recursion without stack consumption. Calls f with supplied args, if + any. If f returns a fn, calls that fn with no arguments, and + continues to repeat, until the return value is not a fn, then + returns that non-fn value. Note that if you want to return a fn as a + final value, you must wrap it in some data structure and unpack it + after trampoline returns." + ([f] + (let [ret (f)] + (if (fn? ret) + (recur ret) + ret))) + ([f & args] + (trampoline #(apply f args)))) + +(defn rand + "Returns a random floating point number between 0 (inclusive) and + n (default 1) (exclusive)." + ([] (rand 1)) + ([n] (* (Math/random) n))) + +(defn rand-int + "Returns a random integer between 0 (inclusive) and n (exclusive)." + [n] (Math/floor (* (Math/random) n))) + +(defn rand-nth + "Return a random element of the (sequential) collection. Will have + the same performance characteristics as nth for the given + collection." + [coll] + (nth coll (rand-int (count coll)))) + +(defn group-by + "Returns a map of the elements of coll keyed by the result of + f on each element. The value at each key will be a vector of the + corresponding elements, in the order they appeared in coll." + [f coll] + (persistent! + (reduce + (fn [ret x] + (let [k (f x)] + (assoc! ret k (conj (get ret k []) x)))) + (transient {}) coll))) + +(defn make-hierarchy + "Creates a hierarchy object for use with derive, isa? etc." + [] {:parents {} :descendants {} :ancestors {}}) + +(def + ^{:private true + :jsdoc ["@type {*}"]} + -global-hierarchy nil) + +(defn- get-global-hierarchy [] + (when (nil? -global-hierarchy) + (set! -global-hierarchy (atom (make-hierarchy)))) + -global-hierarchy) + +(defn- swap-global-hierarchy! [f & args] + (apply swap! (get-global-hierarchy) f args)) + +(defn bases + "Returns the immediate prototype of c" + [c] + (when c + (let [s (.getPrototypeOf js/Object c)] + (when s + (list s))))) + +(defn supers + "Returns the immediate and indirect prototypes of c, if any" + [c] + (loop [ret (set (bases c)) cs ret] + (if (seq cs) + (let [c (first cs) bs (bases c)] + (recur (into ret bs) (into (disj cs c) bs))) + (not-empty ret)))) + +(defn ^boolean isa? + "Returns true if (= child parent), or child is directly or indirectly derived from + parent, either via a JavaScript type inheritance relationship or a + relationship established via derive. h must be a hierarchy obtained + from make-hierarchy, if not supplied defaults to the global + hierarchy" + ([child parent] (isa? @(get-global-hierarchy) child parent)) + ([h child parent] + (or (= child parent) + (and (js-fn? parent) (js-fn? child) + (instance? parent child)) + (contains? ((:ancestors h) child) parent) + (and (js-fn? child) (some #(contains? ((:ancestors h) %) parent) (supers child))) + (and (vector? parent) (vector? child) + (== (count parent) (count child)) + (loop [ret true i 0] + (if (or (not ret) (== i (count parent))) + ret + (recur (isa? h (child i) (parent i)) (inc i)))))))) + +(defn parents + "Returns the immediate parents of tag, either via a JavaScript type + inheritance relationship or a relationship established via derive. h + must be a hierarchy obtained from make-hierarchy, if not supplied + defaults to the global hierarchy" + ([tag] (parents @(get-global-hierarchy) tag)) + ([h tag] + (not-empty + (let [tp (get (:parents h) tag)] + (if (js-fn? tag) + (into (set (bases tag)) tp) + tp))))) + +(defn ancestors + "Returns the immediate and indirect parents of tag, either via a JavaScript type + inheritance relationship or a relationship established via derive. h + must be a hierarchy obtained from make-hierarchy, if not supplied + defaults to the global hierarchy" + ([tag] (ancestors @(get-global-hierarchy) tag)) + ([h tag] + (not-empty + (let [ta (get (:ancestors h) tag)] + (if (js-fn? tag) + (let [superclasses (set (supers tag))] + (reduce into superclasses + (cons ta + (map #(get (:ancestors h) %) superclasses)))) + ta))))) + +(defn descendants + "Returns the immediate and indirect children of tag, through a + relationship established via derive. h must be a hierarchy obtained + from make-hierarchy, if not supplied defaults to the global + hierarchy. Note: does not work on JavaScript type inheritance + relationships." + ([tag] (descendants @(get-global-hierarchy) tag)) + ([h tag] + (if (js-fn? tag) + (throw (js/Error. "Can't get descendants of constructors")) + (not-empty (get (:descendants h) tag))))) + +(defn derive + "Establishes a parent/child relationship between parent and + tag. Parent must be a namespace-qualified symbol or keyword and + child can be either a namespace-qualified symbol or keyword or a + class. h must be a hierarchy obtained from make-hierarchy, if not + supplied defaults to, and modifies, the global hierarchy." + ([tag parent] + (assert (namespace parent)) + (assert (or (js-fn? tag) (and (implements? INamed tag) (namespace tag)))) + (swap-global-hierarchy! derive tag parent) nil) + ([h tag parent] + (assert (not= tag parent)) + (assert (or (js-fn? tag) (implements? INamed tag))) + (assert (implements? INamed parent)) + (let [tp (:parents h) + td (:descendants h) + ta (:ancestors h) + tf (fn [m source sources target targets] + (reduce (fn [ret k] + (assoc ret k + (reduce conj (get targets k #{}) (cons target (targets target))))) + m (cons source (sources source))))] + (or + (when-not (contains? (tp tag) parent) + (when (contains? (ta tag) parent) + (throw (js/Error. (str_ tag "already has" parent "as ancestor")))) + (when (contains? (ta parent) tag) + (throw (js/Error. (str_ "Cyclic derivation:" parent "has" tag "as ancestor")))) + {:parents (assoc (:parents h) tag (conj (get tp tag #{}) parent)) + :ancestors (tf (:ancestors h) tag td parent ta) + :descendants (tf (:descendants h) parent ta tag td)}) + h)))) + +(defn underive + "Removes a parent/child relationship between parent and + tag. h must be a hierarchy obtained from make-hierarchy, if not + supplied defaults to, and modifies, the global hierarchy." + ([tag parent] + (swap-global-hierarchy! underive tag parent) + nil) + ([h tag parent] + (let [parentMap (:parents h) + childsParents (if (parentMap tag) + (disj (parentMap tag) parent) #{}) + newParents (if (not-empty childsParents) + (assoc parentMap tag childsParents) + (dissoc parentMap tag)) + deriv-seq (flatten (map #(cons (first %) (interpose (first %) (second %))) + (seq newParents)))] + (if (contains? (parentMap tag) parent) + (reduce #(apply derive %1 %2) (make-hierarchy) + (partition 2 deriv-seq)) + h)))) + +(defn- reset-cache + [method-cache method-table cached-hierarchy hierarchy] + (swap! method-cache (fn [_] (deref method-table))) + (swap! cached-hierarchy (fn [_] (deref hierarchy)))) + +(defn- prefers* + [x y prefer-table] + (let [xprefs (@prefer-table x)] + (or + (when (and xprefs (xprefs y)) + true) + (loop [ps (parents y)] + (when (pos? (count ps)) + (when (prefers* x (first ps) prefer-table) + true) + (recur (rest ps)))) + (loop [ps (parents x)] + (when (pos? (count ps)) + (when (prefers* (first ps) y prefer-table) + true) + (recur (rest ps)))) + false))) + +(defn- dominates + [x y prefer-table hierarchy] + (or (prefers* x y prefer-table) (isa? hierarchy x y))) + +(defn- find-and-cache-best-method + [name dispatch-val hierarchy method-table prefer-table method-cache cached-hierarchy default-dispatch-val] + (let [best-entry (reduce (fn [be [k _ :as e]] + (if (isa? @hierarchy dispatch-val k) + (let [be2 (if (or (nil? be) (dominates k (first be) prefer-table @hierarchy)) + e + be)] + (when-not (dominates (first be2) k prefer-table @hierarchy) + (throw (js/Error. + (str_ "Multiple methods in multimethod '" name + "' match dispatch value: " dispatch-val " -> " k + " and " (first be2) ", and neither is preferred")))) + be2) + be)) + nil @method-table) + best-entry (if-let [entry (and (nil? best-entry) (@method-table default-dispatch-val))] + [default-dispatch-val entry] + best-entry)] + (when best-entry + (if (= @cached-hierarchy @hierarchy) + (do + (swap! method-cache assoc dispatch-val (second best-entry)) + (second best-entry)) + (do + (reset-cache method-cache method-table cached-hierarchy hierarchy) + (find-and-cache-best-method name dispatch-val hierarchy method-table prefer-table + method-cache cached-hierarchy default-dispatch-val)))))) + +(defprotocol IMultiFn + (-reset [mf]) + (-add-method [mf dispatch-val method]) + (-remove-method [mf dispatch-val]) + (-prefer-method [mf dispatch-val dispatch-val-y]) + (-get-method [mf dispatch-val]) + (-methods [mf]) + (-prefers [mf]) + (-default-dispatch-val [mf]) + (-dispatch-fn [mf])) + +(defn- throw-no-method-error [name dispatch-val] + (throw (js/Error. (str_ "No method in multimethod '" name "' for dispatch value: " dispatch-val)))) + +(deftype MultiFn [name dispatch-fn default-dispatch-val hierarchy + method-table prefer-table method-cache cached-hierarchy] + IFn + (-invoke [mf] + (let [dispatch-val (dispatch-fn) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn))) + (-invoke [mf a] + (let [dispatch-val (dispatch-fn a) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a))) + (-invoke [mf a b] + (let [dispatch-val (dispatch-fn a b) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b))) + (-invoke [mf a b c] + (let [dispatch-val (dispatch-fn a b c) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c))) + (-invoke [mf a b c d] + (let [dispatch-val (dispatch-fn a b c d) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d))) + (-invoke [mf a b c d e] + (let [dispatch-val (dispatch-fn a b c d e) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e))) + (-invoke [mf a b c d e f] + (let [dispatch-val (dispatch-fn a b c d e f) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f))) + (-invoke [mf a b c d e f g] + (let [dispatch-val (dispatch-fn a b c d e f g) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g))) + (-invoke [mf a b c d e f g h] + (let [dispatch-val (dispatch-fn a b c d e f g h) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h))) + (-invoke [mf a b c d e f g h i] + (let [dispatch-val (dispatch-fn a b c d e f g h i) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h i))) + (-invoke [mf a b c d e f g h i j] + (let [dispatch-val (dispatch-fn a b c d e f g h i j) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h i j))) + (-invoke [mf a b c d e f g h i j k] + (let [dispatch-val (dispatch-fn a b c d e f g h i j k) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h i j k))) + (-invoke [mf a b c d e f g h i j k l] + (let [dispatch-val (dispatch-fn a b c d e f g h i j k l) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h i j k l))) + (-invoke [mf a b c d e f g h i j k l m] + (let [dispatch-val (dispatch-fn a b c d e f g h i j k l m) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h i j k l m))) + (-invoke [mf a b c d e f g h i j k l m n] + (let [dispatch-val (dispatch-fn a b c d e f g h i j k l m n) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h i j k l m n))) + (-invoke [mf a b c d e f g h i j k l m n o] + (let [dispatch-val (dispatch-fn a b c d e f g h i j k l m n o) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h i j k l m n o))) + (-invoke [mf a b c d e f g h i j k l m n o p] + (let [dispatch-val (dispatch-fn a b c d e f g h i j k l m n o p) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h i j k l m n o p))) + (-invoke [mf a b c d e f g h i j k l m n o p q] + (let [dispatch-val (dispatch-fn a b c d e f g h i j k l m n o p q) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h i j k l m n o p q))) + (-invoke [mf a b c d e f g h i j k l m n o p q r] + (let [dispatch-val (dispatch-fn a b c d e f g h i j k l m n o p q r) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h i j k l m n o p q r))) + (-invoke [mf a b c d e f g h i j k l m n o p q r s] + (let [dispatch-val (dispatch-fn a b c d e f g h i j k l m n o p q r s) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h i j k l m n o p q r s))) + (-invoke [mf a b c d e f g h i j k l m n o p q r s t] + (let [dispatch-val (dispatch-fn a b c d e f g h i j k l m n o p q r s t) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (target-fn a b c d e f g h i j k l m n o p q r s t))) + (-invoke [mf a b c d e f g h i j k l m n o p q r s t rest] + (let [dispatch-val (apply dispatch-fn a b c d e f g h i j k l m n o p q r s t rest) + target-fn (-get-method mf dispatch-val)] + (when-not target-fn + (throw-no-method-error name dispatch-val)) + (apply target-fn a b c d e f g h i j k l m n o p q r s t rest))) + + IMultiFn + (-reset [mf] + (swap! method-table (fn [mf] {})) + (swap! method-cache (fn [mf] {})) + (swap! prefer-table (fn [mf] {})) + (swap! cached-hierarchy (fn [mf] nil)) + mf) + + (-add-method [mf dispatch-val method] + (swap! method-table assoc dispatch-val method) + (reset-cache method-cache method-table cached-hierarchy hierarchy) + mf) + + (-remove-method [mf dispatch-val] + (swap! method-table dissoc dispatch-val) + (reset-cache method-cache method-table cached-hierarchy hierarchy) + mf) + + (-get-method [mf dispatch-val] + (when-not (= @cached-hierarchy @hierarchy) + (reset-cache method-cache method-table cached-hierarchy hierarchy)) + (if-let [target-fn (@method-cache dispatch-val)] + target-fn + (find-and-cache-best-method name dispatch-val hierarchy method-table + prefer-table method-cache cached-hierarchy default-dispatch-val))) + + (-prefer-method [mf dispatch-val-x dispatch-val-y] + (when (prefers* dispatch-val-y dispatch-val-x prefer-table) + (throw (js/Error. (str_ "Preference conflict in multimethod '" name "': " dispatch-val-y + " is already preferred to " dispatch-val-x)))) + (swap! prefer-table + (fn [old] + (assoc old dispatch-val-x + (conj (get old dispatch-val-x #{}) + dispatch-val-y)))) + (reset-cache method-cache method-table cached-hierarchy hierarchy)) + + (-methods [mf] @method-table) + (-prefers [mf] @prefer-table) + (-default-dispatch-val [mf] default-dispatch-val) + (-dispatch-fn [mf] dispatch-fn) + + INamed + (-name [this] (-name name)) + (-namespace [this] (-namespace name)) + + IHash + (-hash [this] (goog/getUid this))) + +(defn remove-all-methods + "Removes all of the methods of multimethod." + [multifn] + (-reset multifn)) + +(defn remove-method + "Removes the method of multimethod associated with dispatch-value." + [multifn dispatch-val] + (-remove-method multifn dispatch-val)) + +(defn prefer-method + "Causes the multimethod to prefer matches of dispatch-val-x over dispatch-val-y + when there is a conflict" + [multifn dispatch-val-x dispatch-val-y] + (-prefer-method multifn dispatch-val-x dispatch-val-y)) + +(defn methods + "Given a multimethod, returns a map of dispatch values -> dispatch fns" + [multifn] (-methods multifn)) + +(defn get-method + "Given a multimethod and a dispatch value, returns the dispatch fn + that would apply to that value, or nil if none apply and no default" + [multifn dispatch-val] (-get-method multifn dispatch-val)) + +(defn prefers + "Given a multimethod, returns a map of preferred value -> set of other values" + [multifn] (-prefers multifn)) + +(defn default-dispatch-val + "Given a multimethod, return its default-dispatch-val." + [multifn] (-default-dispatch-val multifn)) + +(defn dispatch-fn + "Given a multimethod, return its dispatch-fn." + [multifn] (-dispatch-fn multifn)) + +;; UUID +(defprotocol IUUID "A marker protocol for UUIDs") + +(deftype UUID [uuid ^:mutable __hash] + IUUID + + Object + (toString [_] uuid) + (equiv [this other] + (-equiv this other)) + + IEquiv + (-equiv [_ other] + (and (implements? IUUID other) (identical? uuid (.-uuid other)))) + + IPrintWithWriter + (-pr-writer [_ writer _] + (-write writer (str_ "#uuid \"" uuid "\""))) + + IHash + (-hash [this] + (when (nil? __hash) + (set! __hash (hash uuid))) + __hash) + + IComparable + (-compare [this other] + (if (instance? UUID other) + (garray/defaultCompare uuid (.-uuid other)) + (throw (js/Error. (str_ "Cannot compare " this " to " other)))))) + +(defn uuid + "Returns a UUID consistent with the string s." + [s] + (assert (string? s)) + (UUID. (.toLowerCase s) nil)) + +(defn random-uuid + "Returns a pseudo-randomly generated UUID instance (i.e. type 4)." + [] + (letfn [(^string quad-hex [] + (let [unpadded-hex ^string (.toString (rand-int 65536) 16)] + (case (count unpadded-hex) + 1 (str_ "000" unpadded-hex) + 2 (str_ "00" unpadded-hex) + 3 (str_ "0" unpadded-hex) + unpadded-hex)))] + (let [ver-tripple-hex ^string (.toString (bit-or 0x4000 (bit-and 0x0fff (rand-int 65536))) 16) + res-tripple-hex ^string (.toString (bit-or 0x8000 (bit-and 0x3fff (rand-int 65536))) 16)] + (uuid + (str_ (quad-hex) (quad-hex) "-" (quad-hex) "-" + ver-tripple-hex "-" res-tripple-hex "-" + (quad-hex) (quad-hex) (quad-hex)))))) + +(defn uuid? + "Return true if x is a UUID." + [x] (implements? IUUID x)) + +;;; ExceptionInfo + +(defn- pr-writer-ex-info [obj writer opts] + (-write writer "#error {:message ") + (pr-writer (.-message obj) writer opts) + (when (.-data obj) + (-write writer ", :data ") + (pr-writer (.-data obj) writer opts)) + (when (.-cause obj) + (-write writer ", :cause ") + (pr-writer (.-cause obj) writer opts)) + (-write writer "}")) + +(defn ^{:jsdoc ["@constructor"]} + ExceptionInfo [message data cause] + (let [e (js/Error. message)] + (this-as this + (set! (.-message this) message) + (set! (.-data this) data) + (set! (.-cause this) cause) + (do + (set! (.-name this) (.-name e)) + ;; non-standard + (set! (.-description this) (.-description e)) + (set! (.-number this) (.-number e)) + (set! (.-fileName this) (.-fileName e)) + (set! (.-lineNumber this) (.-lineNumber e)) + (set! (.-columnNumber this) (.-columnNumber e)) + (set! (.-stack this) (.-stack e))) + this))) + +(set! (.. ExceptionInfo -prototype -__proto__) js/Error.prototype) + +(extend-type ExceptionInfo + IPrintWithWriter + (-pr-writer [obj writer opts] + (pr-writer-ex-info obj writer opts))) + +(set! (.. ExceptionInfo -prototype -toString) + (fn [] + (this-as this (pr-str* this)))) + +(defn ex-info + "Create an instance of ExceptionInfo, an Error type that carries a + map of additional data." + ([msg data] (ex-info msg data nil)) + ([msg data cause] + (ExceptionInfo. msg data cause))) + +(defn ex-data + "Returns exception data (a map) if ex is an ExceptionInfo. + Otherwise returns nil." + [ex] + (when (instance? ExceptionInfo ex) + (.-data ex))) + +(defn ex-message + "Returns the message attached to the given Error / ExceptionInfo object. + For non-Errors returns nil." + [ex] + (when (instance? js/Error ex) + (.-message ex))) + +(defn ex-cause + "Returns exception cause (an Error / ExceptionInfo) if ex is an + ExceptionInfo. + Otherwise returns nil." + [ex] + (when (instance? ExceptionInfo ex) + (.-cause ex))) + +(defn Throwable->map + "Constructs a data representation for an Error with keys: + :cause - root cause message + :phase - error phase + :via - cause chain, with cause keys: + :type - exception class symbol + :message - exception message + :data - ex-data + :at - top stack element + :trace - root cause stack elements" + [o] + (let [base (fn [t] + (merge {:type (cond + (instance? ExceptionInfo t) `ExceptionInfo + (instance? js/Error t) (symbol "js" (.-name t)) + :else nil)} + (when-let [msg (ex-message t)] + {:message msg}) + (when-let [ed (ex-data t)] + {:data ed}) + #_(let [st (extract-canonical-stacktrace t)] + (when (pos? (count st)) + {:at st})))) + via (loop [via [], t o] + (if t + (recur (conj via t) (ex-cause t)) + via)) + root (peek via)] + (merge {:via (vec (map base via)) + :trace nil #_(extract-canonical-stacktrace (or root o))} + (when-let [root-msg (ex-message root)] + {:cause root-msg}) + (when-let [data (ex-data root)] + {:data data}) + (when-let [phase (-> o ex-data :clojure.error/phase)] + {:phase phase})))) + +(defn comparator + "Returns an JavaScript compatible comparator based upon pred." + [pred] + (fn [x y] + (cond (pred x y) -1 (pred y x) 1 :else 0))) + +(defn special-symbol? + "Returns true if x names a special form" + [x] + (contains? + '#{if def fn* do let* loop* letfn* throw try catch finally + recur new set! ns deftype* defrecord* . js* & quote case* var ns*} + x)) + +(defn test + "test [v] - if var, finds fn at key :test in var metadata, if function, finds + special test property. Calls it, presuming failure will throw exception. + + Examples: + + (test my-fn) ;; :ok + (test #'my-fn) ;; :ok" + [v] + (let [f (if (instance? Var v) + (-> v meta :test) + (some-> v .-cljs$lang$test))] + (if f + (do (f) :ok) + :no-test))) + + +(deftype TaggedLiteral [tag form] + Object + (toString [coll] + (pr-str* coll)) + + IEquiv + (-equiv [this other] + (and (instance? TaggedLiteral other) + (= tag (.-tag other)) + (= form (.-form other)))) + + IHash + (-hash [this] + (+ (* 31 (hash tag)) + (hash form))) + + ILookup + (-lookup [this v] + (-lookup this v nil)) + (-lookup [this v not-found] + (case v + :tag tag + :form form + not-found)) + + IPrintWithWriter + (-pr-writer [o writer opts] + (-write writer (str_ "#" tag " ")) + (pr-writer form writer opts))) + +(defn tagged-literal? + "Return true if the value is the data representation of a tagged literal" + [value] + (instance? TaggedLiteral value)) + +(defn tagged-literal + "Construct a data representation of a tagged literal from a + tag symbol and a form." + [tag form] + {:pre [(symbol? tag)]} + (TaggedLiteral. tag form)) + +(def + ^{:private true + :jsdoc ["@type {*}"]} + js-reserved-arr + #js ["arguments" "abstract" "await" "boolean" "break" "byte" "case" + "catch" "char" "class" "const" "continue" + "debugger" "default" "delete" "do" "double" + "else" "enum" "export" "extends" "final" + "finally" "float" "for" "function" "goto" "if" + "implements" "import" "in" "instanceof" "int" + "interface" "let" "long" "native" "new" + "package" "private" "protected" "public" + "return" "short" "static" "super" "switch" + "synchronized" "this" "throw" "throws" + "transient" "try" "typeof" "var" "void" + "volatile" "while" "with" "yield" "methods" + "null" "constructor"]) + +(def + ^{:jsdoc ["@type {null|Object}"]} + js-reserved nil) + +(defn- js-reserved? [x] + (when (nil? js-reserved) + (set! js-reserved + (reduce #(do (gobject/set %1 %2 true) %1) + #js {} js-reserved-arr))) + (.hasOwnProperty js-reserved x)) + +(defn- demunge-pattern [] + (when-not DEMUNGE_PATTERN + (set! DEMUNGE_PATTERN + (let [ks (sort (fn [a b] (- (. b -length) (. a -length))) + (js-keys DEMUNGE_MAP))] + (loop [ks ks ret ""] + (if (seq ks) + (recur + (next ks) + (str_ + (cond-> ret + (not (identical? ret "")) (str_ "|")) + (first ks))) + (str_ ret "|\\$")))))) + DEMUNGE_PATTERN) + +(defn ^string munge-str + "Munge string `name` without considering `..` or JavaScript reserved keywords." + [name] + (let [sb (StringBuffer.)] + (loop [i 0] + (if (< i (. name -length)) + (let [c (.charAt name i) + sub (gobject/get CHAR_MAP c)] + (if-not (nil? sub) + (.append sb sub) + (.append sb c)) + (recur (inc i))))) + (.toString sb))) + +(defn munge + "Munge symbol or string `name` for safe use in JavaScript. + + - Replaces '..' with '_DOT__DOT_'. + - Appends '$' to JavaScript reserved keywords. + - Returns a symbol if `name` was a symbol, otherwise a string." + [name] + (let [name' (munge-str (str_ name)) + name' (cond + (identical? name' "..") "_DOT__DOT_" + (js-reserved? name') (str_ name' "$") + :else name')] + (if (symbol? name) + (symbol name') + name'))) + +(defn- demunge-str [munged-name] + (let [r (js/RegExp. (demunge-pattern) "g") + munged-name (if (gstring/endsWith munged-name "$") + (.substring munged-name 0 (dec (. munged-name -length))) + munged-name)] + (loop [ret "" last-match-end 0] + (if-let [match (.exec r munged-name)] + (let [[x] match] + (recur + (str_ ret + (.substring munged-name last-match-end + (- (. r -lastIndex) (. x -length))) + (if (identical? x "$") "/" (gobject/get DEMUNGE_MAP x))) + (. r -lastIndex))) + (str_ ret + (.substring munged-name last-match-end (.-length munged-name))))))) + +(defn demunge [name] + ((if (symbol? name) symbol str_) + (let [name' (str_ name)] + (if (identical? name' "_DOT__DOT_") + ".." + (demunge-str name'))))) + +(defonce ^{:jsdoc ["@type {*}"] :private true} + tapset nil) + +(defn- maybe-init-tapset [] + (when (nil? tapset) + (set! tapset (atom #{})))) + +(defn add-tap + "Adds f, a fn of one argument, to the tap set. This function will be called with + anything sent via tap>. Remember f in order to remove-tap" + [f] + (maybe-init-tapset) + (swap! tapset conj f) + nil) + +(defn remove-tap + "Remove f from the tap set." + [f] + (maybe-init-tapset) + (swap! tapset disj f) + nil) + +(defn ^boolean tap> + "Sends x to any taps. Returns the result of *exec-tap-fn*, a Boolean value." + [x] + (maybe-init-tapset) + (*exec-tap-fn* + (fn [] + (doseq [tap @tapset] + (try + (tap x) + (catch js/Error ex)))))) + +(defn update-vals + "m f => {k (f v) ...} + Given a map m and a function f of 1-argument, returns a new map where the keys of m + are mapped to result of applying f to the corresponding values of m." + {:added "1.11"} + [m f] + (with-meta + (persistent! + (reduce-kv (fn [acc k v] (assoc! acc k (f v))) + (if (implements? IEditableCollection m) + (transient m) + (transient {})) + m)) + (meta m))) + +(defn update-keys + "m f => {(f k) v ...} + Given a map m and a function f of 1-argument, returns a new map whose + keys are the result of applying f to the keys of m, mapped to the + corresponding values of m. + f must return a unique key for each key of m, else the behavior is undefined." + {:added "1.11"} + [m f] + (let [ret (persistent! + (reduce-kv (fn [acc k v] (assoc! acc (f k) v)) + (transient {}) + m))] + (with-meta ret (meta m)))) + +;; ----------------------------------------------------------------------------- +;; Bootstrap helpers - incompatible with advanced compilation + +(defn- ns-lookup + "Bootstrap only." + [ns-obj k] + (fn [] (gobject/get ns-obj k))) + +;; Bootstrap only +(deftype Namespace [obj name] + Object + (findInternedVar [this sym] + (let [k (munge (str_ sym))] + (when (gobject/containsKey obj k) + (let [var-sym (symbol (str_ name) (str_ sym)) + var-meta {:ns this}] + (Var. (ns-lookup obj k) var-sym var-meta))))) + (getName [_] name) + (toString [_] + (str_ name)) + IEquiv + (-equiv [_ other] + (if (instance? Namespace other) + (= name (.-name other)) + false)) + IHash + (-hash [_] + (hash name))) + +(def + ^{:doc "Bootstrap only." :jsdoc ["@type {*}"]} + NS_CACHE nil) + +(defn- find-ns-obj* + "Bootstrap only." + [ctxt xs] + (cond + (nil? ctxt) nil + (nil? xs) ctxt + :else (recur (gobject/get ctxt (first xs)) (next xs)))) + +(defn find-ns-obj + "Bootstrap only." + [ns] + (let [munged-ns (munge (str_ ns)) + segs (.split munged-ns ".")] + (case *target* + "nodejs" (if ^boolean js/COMPILED + ; Under simple optimizations on nodejs, namespaces will be in module + ; rather than global scope and must be accessed by a direct call to eval. + ; The first segment may refer to an undefined variable, so its evaluation + ; may throw ReferenceError. + (find-ns-obj* + (try + (let [ctxt (js/eval (first segs))] + (when (and ctxt (object? ctxt)) + ctxt)) + (catch js/ReferenceError e + nil)) + (next segs)) + (find-ns-obj* goog/global segs)) + ("default" "webworker") (find-ns-obj* goog/global segs) + (throw (js/Error. (str_ "find-ns-obj not supported for target " *target*)))))) + +(defn ns-interns* + "Returns a map of the intern mappings for the namespace. + Bootstrap only." + [sym] + (let [ns-obj (find-ns-obj sym) + ns (Namespace. ns-obj sym)] + (letfn [(step [ret k] + (let [var-sym (symbol (demunge k))] + (assoc ret + var-sym (Var. #(gobject/get ns-obj k) + (symbol (str_ sym) (str_ var-sym)) {:ns ns}))))] + (reduce step {} (js-keys ns-obj))))) + +(defn create-ns + "Create a new namespace named by the symbol. Bootstrap only." + ([sym] + (create-ns sym (find-ns-obj sym))) + ([sym ns-obj] + (Namespace. ns-obj sym))) + +(defn find-ns + "Returns the namespace named by the symbol or nil if it doesn't exist. + Bootstrap only." + [ns] + (when (nil? NS_CACHE) + (set! NS_CACHE (atom {}))) + (let [the-ns (get @NS_CACHE ns)] + (if-not (nil? the-ns) + the-ns + (let [ns-obj (find-ns-obj ns)] + (when-not (nil? ns-obj) + (let [new-ns (create-ns ns ns-obj)] + (swap! NS_CACHE assoc ns new-ns) + new-ns)))))) + +(defn find-macros-ns + "Returns the macros namespace named by the symbol or nil if it doesn't exist. + Bootstrap only." + [ns] + (when (nil? NS_CACHE) + (set! NS_CACHE (atom {}))) + (let [ns-str (str_ ns) + ns (if (not (gstring/contains ns-str "$macros")) + (symbol (str_ ns-str "$macros")) + ns) + the-ns (get @NS_CACHE ns)] + (if-not (nil? the-ns) + the-ns + (let [ns-obj (find-ns-obj ns)] + (when-not (nil? ns-obj) + (let [new-ns (create-ns ns ns-obj)] + (swap! NS_CACHE assoc ns new-ns) + new-ns)))))) + +(defn ns-name + "Returns the name of the namespace, a Namespace object. + Bootstrap only." + [ns-obj] + (.-name ns-obj)) + +(defn uri? + "Returns true x is a goog.Uri instance." + {:added "1.9"} + [x] + (instance? goog.Uri x)) + +(defn ^:private parsing-err + "Construct message for parsing for non-string parsing error" + [val] + (str_ "Expected string, got: " (if (nil? val) "nil" (goog/typeOf val)))) + +(defn ^number parse-long + "Parse string of decimal digits with optional leading -/+ and return an + integer value, or nil if parse fails" + [s] + (if (string? s) + (and (re-matches #"[+-]?\d+" s) + (let [i (js/parseInt s)] + (when (and (<= i js/Number.MAX_SAFE_INTEGER) + (>= i js/Number.MIN_SAFE_INTEGER)) + i))) + (throw (js/Error. (parsing-err s))))) + +(defn ^number parse-double + "Parse string with floating point components and return a floating point value, + or nil if parse fails. + Grammar: https://docs.oracle.com/javase/8/docs/api/java/lang/Double.html#valueOf-java.lang.String-" + [s] + (if (string? s) + (cond + ;; FIXME: another cases worth thinking about + ^boolean (re-matches #"[\x00-\x20]*[+-]?NaN[\x00-\x20]*" s) ##NaN + ^boolean (re-matches + #"[\x00-\x20]*[+-]?(Infinity|((\d+\.?\d*|\.\d+)([eE][+-]?\d+)?)[dDfF]?)[\x00-\x20]*" + s) (js/parseFloat s) + :default nil) + (throw (js/Error. (parsing-err s))))) + +(def ^:private uuid-regex + #"^[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]$") + +(defn parse-uuid + "Parse a string representing a UUID and return a UUID instance, + or nil if parse fails. + Grammar: https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html#toString--" + [s] + (if (string? s) + (when ^boolean (re-matches uuid-regex s) + (uuid s)) + (throw (js/Error. (parsing-err s))))) + +(defn parse-boolean + "Parse strings \"true\" or \"false\" and return a boolean, or nil if invalid. Note that this explicitly + excludes strings with different cases, or space characters." + [s] + (if (string? s) + (case s + "true" true + "false" false + nil) + (throw (js/Error. (parsing-err s))))) + +(defn- maybe-enable-print! [] + (cond + (exists? js/console) + (enable-console-print!) + + (or (identical? *target* "nashorn") + (identical? *target* "graaljs")) + (let [system (.type js/Java "java.lang.System")] + (set! *print-newline* false) + (set-print-fn! + (fn [] + (let [xs (js-arguments) + s (.join (garray/clone xs) "")] + (.println (.-out system) s)))) + (set-print-err-fn! + (fn [] + (let [xs (js-arguments) + s (.join (garray/clone xs) "")] + (.println (.-error system) s))))))) + +(maybe-enable-print!) + +(defonce + ^{:doc "Runtime environments may provide a way to evaluate ClojureScript + forms. Whatever function *eval* is bound to will be passed any forms which + should be evaluated." :dynamic true} + *eval* + (fn [_] + (throw (js/Error. "cljs.core/*eval* not bound")))) + +(defn eval + "Evaluates the form data structure (not text!) and returns the result. + Delegates to cljs.core/*eval*. Intended for use in self-hosted ClojureScript, + which sets up an implementation of cljs.core/*eval* for that environment." + [form] + (*eval* form)) + +(when ^boolean js/COMPILED + (when (identical? "nodejs" *target*) + (set! goog/global js/global)) + (cond + (identical? "window" *global*) (set! goog/global js/window) + (identical? "self" *global*) (set! goog/global js/self) + (identical? "global" *global*) (set! goog/global js/global))) + +;; ----------------------------------------------------------------------------- +;; Original 2011 Copy-on-Write Types + +;;; VectorLite + +(deftype VectorLiteIterator [arr ^:mutable i] + Object + (hasNext [_] + (< i (alength arr))) + (next [_] + (let [x (aget arr i)] + (set! i (inc i)) + x))) + +(deftype VectorLite [meta array ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (equiv [coll other] + (-equiv coll other)) + (indexOf [coll x start] + (let [start (if (nil? start) 0 start) + len (-count coll)] + (if (>= start len) + -1 + (loop [idx (cond + (pos? start) start + (neg? start) (unchecked-max 0 (+ start len)) + :else start)] + (if (< idx len) + (if (= (-nth coll idx) x) + idx + (recur (inc idx))) + -1))))) + (lastIndexOf [coll x start] + (let [start (if (nil? start) (alength array) start) + len (-count coll)] + (if (zero? len) + -1 + (loop [idx (cond + (pos? start) (unchecked-min (dec len) start) + (neg? start) (+ len start) + :else start)] + (if (>= idx 0) + (if (= (-nth coll idx) x) + idx + (recur (dec idx))) + -1))))) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (VectorLite. new-meta array __hash))) + + ICloneable + (-clone [coll] (VectorLite. meta array __hash)) + + IMeta + (-meta [coll] meta) + + IStack + (-peek [coll] + (let [count (alength array)] + (when (> count 0) + (aget array (dec count))))) + (-pop [coll] + (if (> (alength array) 0) + (let [new-array (aclone array)] + (. new-array (pop)) + (VectorLite. meta new-array nil)) + (throw (js/Error. "Can't pop empty vector")))) + + ICollection + (-conj [coll o] + (let [new-array (aclone array)] + (.push new-array o) + (VectorLite. meta new-array nil))) + + IEmptyableCollection + (-empty [coll] (with-meta (. VectorLite -EMPTY) meta)) + + ISequential + IEquiv + (-equiv [coll other] (equiv-sequential coll other)) + + IHash + (-hash [coll] (hash-ordered-coll coll)) + + ISeqable + (-seq [coll] + (when (> (alength array) 0) + (prim-seq array))) + + ICounted + (-count [coll] (alength array)) + + IIndexed + (-nth [coll n] + (if (and (<= 0 n) (< n (alength array))) + (aget array (int n)) + (throw (js/Error. (str "No item " n " in vector of length " (alength array)))))) + (-nth [coll n not-found] + (if (and (<= 0 n) (< n (alength array))) + (aget array (int n)) + not-found)) + + ILookup + (-lookup [coll k] + (when (number? k) + (-nth coll k nil))) + (-lookup [coll k not-found] + (if (number? k) + (-nth coll k not-found) + not-found)) + + IAssociative + (-assoc [coll k v] + (if (number? k) + (let [new-array (aclone array)] + (aset new-array k v) + (VectorLite. meta new-array nil)) + (throw (js/Error. "Vector's key for assoc must be a number.")))) + (-contains-key? [coll k] + (if (integer? k) + (and (<= 0 k) (< k (alength array))) + false)) + + IVector + (-assoc-n [coll n val] (-assoc coll n val)) + + IReversible + (-rseq [coll] + (let [cnt (alength array)] + (when (pos? cnt) + (RSeq. coll (dec cnt) nil)))) + + IReduce + (-reduce [v f] + (array-reduce array f)) + (-reduce [v f start] + (array-reduce array f start)) + + IKVReduce + (-kv-reduce [v f init] + (let [len (alength array)] + (loop [i 0 init init] + (if (< i len) + (let [init (f init i (aget array i))] + (if (reduced? init) + @init + (recur (inc i) init))) + init)))) + + IDrop + (-drop [v n] + (let [cnt (alength array)] + (if (< n cnt) + (prim-seq array n) + nil))) + + IComparable + (-compare [x y] + (if (vector? y) + (compare-indexed x y) + (throw (js/Error. "Cannot compare with Vector")))) + + IFn + (-invoke [coll k] + (if (number? k) + (-nth coll k) + (throw (js/Error. "Key must be integer")))) + + IEditableCollection + (-as-transient [coll] + coll) + + ITransientCollection + (-conj! [coll val] + (-conj coll val)) + (-persistent! [coll] + coll) + + ITransientAssociative + (-assoc! [tcoll key val] + (-assoc-n! tcoll key val)) + + ITransientVector + (-assoc-n! [tcoll key val] + (if (number? key) + (-assoc-n tcoll key val) + (throw (js/Error. "Vector's key for assoc! must be a number.")))) + + (-pop! [tcoll] + (-pop tcoll)) + + IIterable + (-iterator [coll] + (VectorLiteIterator. array 0)) + + IPrintWithWriter + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "[" " " "]" opts coll))) + +(es6-iterable VectorLite) + +(set! (. VectorLite -EMPTY) (VectorLite. nil (array) nil)) + +(set! (. VectorLite -fromArray) (fn [xs] (VectorLite. nil xs nil))) + +(defn vector-lite + ":lite-mode version of vector, not intended to be used directly." + [& args] + (if (and (instance? IndexedSeq args) (zero? (.-i args))) + (.fromArray VectorLite (aclone (.-arr args))) + (VectorLite. nil (into-array args) nil))) + +(defn vec-lite + ":lite-mode version of vec, not intended to be used directly." + [coll] + (cond + (map-entry? coll) + [(key coll) (val coll)] + + (vector? coll) + (with-meta coll nil) + + (array? coll) + (.fromArray VectorLite coll) + + :else + (into [] coll))) + +; The keys field is an array of all keys of this map, in no particular +; order. Any string, keyword, or symbol key is used as a property name +; to store the value in strobj. If a key is assoc'ed when that same +; key already exists in strobj, the old value is overwritten. If a +; non-string key is assoc'ed, return a HashMap object instead. + +(defn- obj-map-compare-keys [a b] + (let [a (hash a) + b (hash b)] + (cond + (< a b) -1 + (> a b) 1 + :else 0))) + +(defn- obj-clone [obj ks] + (let [new-obj (js-obj) + l (alength ks)] + (loop [i 0] + (when (< i l) + (let [k (aget ks i)] + (gobject/set new-obj k (gobject/get obj k)) + (recur (inc i))))) + new-obj)) + +(declare hash-map-lite HashMapLite) + +(defn- keyword->obj-map-key + [k] + (str "\uFDD0" "'" (. k -fqn))) + +(defn- obj-map-key->keyword + [k] + (if (.startsWith k "\uFDD0") + (keyword (.substring k 2 (. k -length))) + k)) + +(defn- scan-array [incr k array] + (let [len (alength array)] + (loop [i 0] + (when (< i len) + (if (identical? k (aget array i)) + i + (recur (+ i incr))))))) + +(deftype ObjMapIterator [strkeys strobj ^:mutable i] + Object + (hasNext [_] + (< i (alength strkeys))) + (next [_] + (let [k (aget strkeys i)] + (set! i (inc i)) + (MapEntry. (obj-map-key->keyword k) (unchecked-get strobj k) nil)))) + +(deftype ObjMap [meta strkeys strobj ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (keys [coll] + (es6-iterator + (prim-seq + (.map (.sort strkeys obj-map-compare-keys) + obj-map-key->keyword)))) + (entries [coll] + (es6-entries-iterator (-seq coll))) + (values [coll] + (es6-iterator + (prim-seq + (.map (.sort strkeys obj-map-compare-keys) + #(unchecked-get strobj %))))) + (has [coll k] + (contains? coll k)) + (get [coll k not-found] + (-lookup coll k not-found)) + (forEach [coll f] + (.forEach (.sort strkeys obj-map-compare-keys) + #(f (unchecked-get strobj %) (obj-map-key->keyword %)))) + + IWithMeta + (-with-meta [coll meta] (ObjMap. meta strkeys strobj __hash)) + + IMeta + (-meta [coll] meta) + + ICloneable + (-clone [coll] (ObjMap. meta strkeys strobj __hash)) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (reduce -conj coll entry))) + + IEmptyableCollection + (-empty [coll] (-with-meta (. ObjMap -EMPTY) meta)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] + (when (pos? (alength strkeys)) + (prim-seq + (.map (.sort strkeys obj-map-compare-keys) + #(MapEntry. (obj-map-key->keyword %) (unchecked-get strobj %) nil))))) + + ICounted + (-count [coll] (alength strkeys)) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (if (and (string? k) + (not (nil? (scan-array 1 k strkeys)))) + (unchecked-get strobj k) + not-found))) + + IAssociative + (-assoc [coll k v] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (if (string? k) + (if-not (nil? (scan-array 1 k strkeys)) + (if (identical? v (gobject/get strobj k)) + coll + ; overwrite + (let [new-strobj (obj-clone strobj strkeys)] + (gobject/set new-strobj k v) + (ObjMap. meta strkeys new-strobj nil))) + ; append + (let [new-strobj (obj-clone strobj strkeys) + new-keys (aclone strkeys)] + (gobject/set new-strobj k v) + (.push new-keys k) + (ObjMap. meta new-keys new-strobj nil))) + ; non-string key. game over. + (-with-meta + (-kv-reduce coll + (fn [ret k v] + (-assoc ret k v)) + (hash-map-lite k v)) + meta)))) + (-contains-key? [coll k] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (if (and (string? k) + (not (nil? (scan-array 1 k strkeys)))) + true + false))) + + IFind + (-find [coll k] + (let [k' (if-not (keyword? k) k (keyword->obj-map-key k))] + (when (and (string? k') + (not (nil? (scan-array 1 k' strkeys)))) + (MapEntry. k (unchecked-get strobj k') nil)))) + + IKVReduce + (-kv-reduce [coll f init] + (let [len (alength strkeys)] + (loop [keys (.sort strkeys obj-map-compare-keys) + init init] + (if (seq keys) + (let [k (first keys) + init (f init (obj-map-key->keyword k) (unchecked-get strobj k))] + (if (reduced? init) + @init + (recur (rest keys) init))) + init)))) + + IIterable + (-iterator [coll] + (ObjMapIterator. strkeys strobj 0)) + + IReduce + (-reduce [coll f] + (iter-reduce coll f)) + (-reduce [coll f start] + (iter-reduce coll f start)) + + IMap + (-dissoc [coll k] + (let [k (if-not (keyword? k) k (keyword->obj-map-key k))] + (if (and (string? k) + (not (nil? (scan-array 1 k strkeys)))) + (let [new-keys (aclone strkeys) + new-strobj (obj-clone strobj strkeys)] + (.splice new-keys (scan-array 1 k new-keys) 1) + (js-delete new-strobj k) + (ObjMap. meta new-keys new-strobj nil)) + coll))) ; key not found, return coll unchanged + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IEditableCollection + (-as-transient [coll] + coll) + + ITransientCollection + (-conj! [coll val] + (-conj coll val)) + (-persistent! [coll] + coll) + + ITransientAssociative + (-assoc! [coll key val] + (-assoc coll key val)) + + ITransientMap + (-dissoc! [coll key] + (-dissoc coll key)) + + IPrintWithWriter + (-pr-writer [coll writer opts] + (print-map coll pr-writer writer opts))) + +(es6-iterable ObjMap) + +(set! (. ObjMap -EMPTY) (ObjMap. nil (array) (js-obj) empty-unordered-hash)) + +(set! (. ObjMap -fromObject) (fn [ks obj] (ObjMap. nil ks obj nil))) + +(defn obj-map + ":lite-mode simple key hash-map, not intended to be used directly." + [& keyvals] + (let [ks (array) + obj (js-obj)] + (loop [kvs (seq keyvals)] + (if kvs + (let [k (-> kvs first keyword->obj-map-key)] + (.push ks k) + (gobject/set obj k (second kvs)) + (recur (nnext kvs))) + (.fromObject ObjMap ks obj))))) + +(set! (. ObjMap -createAsIfByAssoc) + (fn [init] + ;; check trailing element + (let [len (alength init) + has-trailing? (== 1 (bit-and len 1)) + init (if has-trailing? + (pam-grow-seed-array init + (into {} (aget init (dec len)))) + init) + len (alength init)] + (loop [i 0 ret {}] + (if (< i len) + (recur (+ i 2) (assoc ret (aget init i) (aget init (inc i)))) + ret))))) + +(defn- scan-array-equiv [incr k array] + (let [len (alength array)] + (loop [i 0] + (when (< i len) + (if (= k (aget array i)) + i + (recur (+ i incr))))))) + +; The keys field is an array of all keys of this map, in no particular +; order. Each key is hashed and the result used as a property name of +; hashobj. Each values in hashobj is actually a bucket in order to handle hash +; collisions. A bucket is an array of alternating keys (not their hashes) and +; vals. +(deftype HashMapLite [meta count hashobj ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (keys [coll] + (let [arr (. (-seq coll) -arr)] + (es6-iterator (prim-seq (.map arr -key (-seq coll)))))) + (entries [coll] + (es6-entries-iterator (-seq coll))) + (values [coll] + (let [arr (. (-seq coll) -arr)] + (es6-iterator (prim-seq (.map arr -val (-seq coll)))))) + (has [coll k] + (contains? coll k)) + (get [coll k not-found] + (-lookup coll k not-found)) + (forEach [coll f] + (let [xs (-seq coll)] + (when-not (nil? xs) + (.forEach (.-arr xs) + #(f (-val %) (-key %)))))) + + IWithMeta + (-with-meta [coll meta] (HashMapLite. meta count hashobj __hash)) + + IMeta + (-meta [coll] meta) + + ICloneable + (-clone [coll] (HashMapLite. meta count hashobj __hash)) + + ICollection + (-conj [coll entry] + (if (vector? entry) + (-assoc coll (-nth entry 0) (-nth entry 1)) + (reduce -conj coll entry))) + + IEmptyableCollection + (-empty [coll] (with-meta (. HashMapLite -EMPTY) meta)) + + IEquiv + (-equiv [coll other] (equiv-map coll other)) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] + (when (pos? count) + (let [hashes (.sort (js-keys hashobj)) + cnt (alength hashes) + arr (array)] + (loop [i 0] + (if (< i cnt) + (let [bckt (unchecked-get hashobj (aget hashes i)) + len (alength bckt)] + (loop [j 0] + (when (< j len) + (do + (.push arr (MapEntry. (aget bckt j) (aget bckt (inc j)) nil)) + (recur (+ j 2))))) + (recur (inc i))) + (prim-seq arr)))))) + + ICounted + (-count [coll] count) + + ILookup + (-lookup [coll k] (-lookup coll k nil)) + (-lookup [coll k not-found] + (let [bucket (unchecked-get hashobj (hash k)) + i (when bucket (scan-array-equiv 2 k bucket))] + (if (some? i) + (aget bucket (inc i)) + not-found))) + + IAssociative + (-assoc [coll k v] + (let [h (hash k) + bucket (unchecked-get hashobj h)] + (if (some? bucket) + (let [new-bucket (aclone bucket) + new-hashobj (gobject/clone hashobj) + i (scan-array-equiv 2 k new-bucket)] + (aset new-hashobj h new-bucket) + (if (some? i) + (if (identical? v (aget new-bucket (inc i))) + coll + (do + ; found key, replace + (aset new-bucket (inc i) v) + (HashMapLite. meta count new-hashobj nil))) + (do + ; did not find key, append + (.push new-bucket k v) + (HashMapLite. meta (inc count) new-hashobj nil)))) + (let [new-hashobj (gobject/clone hashobj)] + ; did not find bucket + (unchecked-set new-hashobj h (array k v)) + (HashMapLite. meta (inc count) new-hashobj nil))))) + (-contains-key? [coll k] + (let [bucket (unchecked-get hashobj (hash k)) + i (when bucket (scan-array-equiv 2 k bucket))] + (if (some? i) + true + false))) + + IMap + (-dissoc [coll k] + (let [h (hash k) + bucket (unchecked-get hashobj h) + i (when bucket (scan-array-equiv 2 k bucket))] + (if (some? i) + (let [new-hashobj (gobject/clone hashobj)] + (if (> 3 (alength bucket)) + (js-delete new-hashobj h) + (let [new-bucket (aclone bucket)] + (.splice new-bucket i 2) + (unchecked-set new-hashobj h new-bucket))) + (HashMapLite. meta (dec count) new-hashobj nil)) + ; key not found, return coll unchanged + coll))) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IEditableCollection + (-as-transient [coll] + coll) + + ITransientCollection + (-conj! [coll val] + (-conj coll val)) + (-persistent! [coll] + coll) + + ITransientAssociative + (-assoc! [coll key val] + (-assoc coll key val)) + + ITransientMap + (-dissoc! [coll key] + (-dissoc coll key)) + + IIterable + (-iterator [coll] + (let [xs (-seq coll)] + (if (some? xs) + (-iterator xs) + (nil-iter)))) + + IKVReduce + (-kv-reduce [coll f init] + (let [hashes (.sort (js-keys hashobj)) + ilen (alength hashes)] + (loop [i 0 init init] + (if (< i ilen) + (let [bckt (unchecked-get hashobj (aget hashes i)) + jlen (alength bckt) + init (loop [j 0 init init] + (if (< j jlen) + (let [init (f init (aget bckt j) (aget bckt (inc j)))] + (if (reduced? init) + init + (recur (+ j 2) init))) + init))] + (if (reduced? init) + @init + (recur (inc i) init))) + init)))) + + IPrintWithWriter + (-pr-writer [coll writer opts] + (print-map coll pr-writer writer opts))) + +(es6-iterable HashMapLite) + +(set! (. HashMapLite -EMPTY) (HashMapLite. nil 0 (js-obj) empty-unordered-hash)) + +(set! (. HashMapLite -fromArrays) (fn [ks vs] + (let [len (.-length ks)] + (loop [i 0, out (. HashMapLite -EMPTY)] + (if (< i len) + (recur (inc i) (assoc out (aget ks i) (aget vs i))) + out))))) + +(defn hash-map-lite + ":lite-mode version of hash-map, not intended to be used directly." + [& keyvals] + (loop [in (seq keyvals), out (. HashMapLite -EMPTY)] + (if in + (recur (nnext in) (-assoc out (first in) (second in))) + out))) + +(deftype SetLite [meta hash-map ^:mutable __hash] + Object + (toString [coll] + (pr-str* coll)) + (keys [coll] + (es6-iterator (-seq coll))) + (entries [coll] + (es6-set-entries-iterator (-seq coll))) + (values [coll] + (es6-iterator (-seq coll))) + (has [coll k] + (contains? coll k)) + (forEach [coll f] + (let [xs (-seq hash-map)] + (when (some? xs) + (.forEach (.-arr xs) + #(f (-val %) (-key %)))))) + + IWithMeta + (-with-meta [coll new-meta] + (if (identical? new-meta meta) + coll + (SetLite. new-meta hash-map __hash))) + + IMeta + (-meta [coll] meta) + + ICloneable + (-clone [coll] (SetLite. meta hash-map __hash)) + + ICollection + (-conj [coll o] + (let [new-hash-map (assoc hash-map o o)] + (if (identical? new-hash-map hash-map) + coll + (SetLite. meta new-hash-map nil)))) + + IEmptyableCollection + (-empty [coll] (with-meta (. SetLite -EMPTY) meta)) + + IEquiv + (-equiv [coll other] + (and + (set? other) + (= (-count coll) (count other)) + (every? #(contains? coll %) + other))) + + IHash + (-hash [coll] (caching-hash coll hash-unordered-coll __hash)) + + ISeqable + (-seq [coll] + (let [xs (-seq hash-map)] + (when (some? xs) + (prim-seq (.map (.-arr xs) (fn [kv] (-key kv))))))) + + ICounted + (-count [coll] + (let [xs (-seq coll)] + (if (some? xs) + (-count xs) + 0))) + + ILookup + (-lookup [coll v] + (-lookup coll v nil)) + (-lookup [coll v not-found] + (if (-contains-key? hash-map v) + (-lookup hash-map v) + not-found)) + + ISet + (-disjoin [coll v] + (let [new-hash-map (-dissoc hash-map v)] + (if (identical? new-hash-map hash-map) + coll + (SetLite. meta new-hash-map nil)))) + + IEditableCollection + (-as-transient [coll] + coll) + + ITransientCollection + (-conj! [coll val] + (-conj coll val)) + (-persistent! [coll] + coll) + + ITransientSet + (-disjoin! [coll key] + (-disjoin coll key)) + + IFn + (-invoke [coll k] + (-lookup coll k)) + (-invoke [coll k not-found] + (-lookup coll k not-found)) + + IIterable + (-iterator [coll] + (let [xs (-seq coll)] + (if (some? xs) + (-iterator xs) + (nil-iter)))) + + IPrintWithWriter + (-pr-writer [coll writer opts] (pr-sequential-writer writer pr-writer "#{" " " "}" opts coll))) + +(es6-iterable SetLite) + +(set! (. SetLite -EMPTY) (SetLite. nil (. HashMapLite -EMPTY) empty-unordered-hash)) + +(defn set-lite + ":lite-mode version of set, not intended ot be used directly." + [coll] + (if (set? coll) + (-with-meta coll nil) + (let [in (seq coll)] + (if (nil? in) + #{} + (loop [in in out (. SetLite -EMPTY)] + (if-not (nil? in) + (recur (next in) (-conj out (first in))) + out)))))) diff --git a/src/main/cljs/cljs/core/specs/alpha.cljc b/src/main/cljs/cljs/core/specs/alpha.cljc new file mode 100644 index 0000000000..f2475aeae5 --- /dev/null +++ b/src/main/cljs/cljs/core/specs/alpha.cljc @@ -0,0 +1,235 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.core.specs.alpha + (:require [clojure.spec.alpha :as s] + #?(:clj [cljs.core :as core] + :cljs [cljs.core$macros :as core]))) + +;;;; destructure + +(s/def ::local-name (s/and simple-symbol? #(not= '& %))) + +(s/def ::binding-form + (s/or :local-symbol ::local-name + :seq-destructure ::seq-binding-form + :map-destructure ::map-binding-form)) + +;; sequential destructuring + +(s/def ::seq-binding-form + (s/and vector? + (s/cat :forms (s/* ::binding-form) + :rest-forms (s/? (s/cat :ampersand #{'&} :form ::binding-form)) + :as-form (s/? (s/cat :as #{:as} :as-sym ::local-name))))) + +;; map destructuring + +(s/def ::keys (s/coll-of ident? :kind vector?)) +(s/def ::syms (s/coll-of symbol? :kind vector?)) +(s/def ::strs (s/coll-of simple-symbol? :kind vector?)) +(s/def ::or (s/map-of simple-symbol? any?)) +(s/def ::as ::local-name) + +(s/def ::map-special-binding + (s/keys :opt-un [::as ::or ::keys ::syms ::strs])) + +(s/def ::map-binding (s/tuple ::binding-form any?)) + +(s/def ::ns-keys + (s/tuple + (s/and qualified-keyword? #(-> % name #{"keys" "syms"})) + (s/coll-of simple-symbol? :kind vector?))) + +(s/def ::map-bindings + (s/every (s/or :map-binding ::map-binding + :qualified-keys-or-syms ::ns-keys + :special-binding (s/tuple #{:as :or :keys :syms :strs} any?)) :kind map?)) + +(s/def ::map-binding-form (s/merge ::map-bindings ::map-special-binding)) + +;; bindings + +(defn even-number-of-forms? + "Returns true if there are an even number of forms in a binding vector" + [forms] + (even? (count forms))) + +(s/def ::binding (s/cat :form ::binding-form :init-expr any?)) +(s/def ::bindings (s/and vector? even-number-of-forms? (s/* ::binding))) + +;; let, if-let, when-let + +(s/fdef core/let + :args (s/cat :bindings ::bindings + :body (s/* any?))) + +(s/fdef core/if-let + :args (s/cat :bindings (s/and vector? ::binding) + :then any? + :else (s/? any?))) + +(s/fdef core/when-let + :args (s/cat :bindings (s/and vector? ::binding) + :body (s/* any?))) + +;; defn, defn-, fn + +(s/def ::param-list + (s/and + vector? + (s/cat :params (s/* ::binding-form) + :var-params (s/? (s/cat :ampersand #{'&} :var-form ::binding-form))))) + +(s/def ::params+body + (s/cat :params ::param-list + :body (s/alt :prepost+body (s/cat :prepost map? + :body (s/+ any?)) + :body (s/* any?)))) + +(s/def ::defn-args + (s/cat :fn-name simple-symbol? + :docstring (s/? string?) + :meta (s/? map?) + :fn-tail (s/alt :arity-1 ::params+body + :arity-n (s/cat :bodies (s/+ (s/spec ::params+body)) + :attr-map (s/? map?))))) + +(s/fdef core/defn + :args ::defn-args + :ret any?) + +(s/fdef core/defn- + :args ::defn-args + :ret any?) + +(s/fdef core/fn + :args (s/cat :fn-name (s/? simple-symbol?) + :fn-tail (s/alt :arity-1 ::params+body + :arity-n (s/+ (s/spec ::params+body)))) + :ret any?) + +;;;; ns + +(s/def ::exclude (s/coll-of simple-symbol?)) +(s/def ::only (s/coll-of simple-symbol?)) +(s/def ::rename (s/map-of simple-symbol? simple-symbol?)) +(s/def ::filters (s/keys* :opt-un [::exclude ::only ::rename])) + +(s/def ::ns-refer-clojure + (s/spec (s/cat :clause #{:refer-clojure} + :refer-filters ::filters))) + +(s/def ::refer (s/coll-of simple-symbol?)) +(s/def ::refer-macros (s/coll-of simple-symbol?)) +(s/def ::include-macros #{true}) + +(s/def ::lib (s/or :sym simple-symbol? + :str string?)) + +(s/def ::libspec + (s/alt :lib ::lib + :lib+opts (s/spec (s/cat :lib ::lib + :options (s/keys* :opt-un [::as ::refer ::refer-macros ::include-macros]))))) + +(s/def ::macros-libspec + (s/alt :lib simple-symbol? + :lib+opts (s/spec (s/cat :lib simple-symbol? + :options (s/keys* :opt-un [::as ::refer]))))) + +(s/def ::ns-require + (s/spec (s/cat :clause #{:require} + :body (s/+ (s/alt :libspec ::libspec + :flag #{:reload :reload-all :verbose}))))) + +(s/def ::ns-require-macros + (s/spec (s/cat :clause #{:require-macros} + :body (s/+ (s/alt :libspec ::macros-libspec + :flag #{:reload :reload-all :verbose}))))) + +(s/def ::package-list + (s/spec + (s/cat :package simple-symbol? + :classes (s/+ simple-symbol?)))) + +(s/def ::import-list + (s/* (s/alt :class simple-symbol? + :package-list ::package-list))) + +(s/def ::ns-import + (s/spec + (s/cat :clause #{:import} + :classes ::import-list))) + +;; same as ::libspec, but also supports the ::filters options in the libspec +(s/def ::use-libspec + (s/alt :lib ::lib + :lib+opts (s/spec (s/cat :lib ::lib + :options (s/keys* :req-un [::only] :opt-un [::rename]))))) + +(s/def ::ns-use + (s/spec (s/cat :clause #{:use} + :libs (s/+ (s/alt :libspec ::use-libspec + :flag #{:reload :reload-all :verbose}))))) + +;; same as ::libspec-macros, but also supports the ::filters options in the libspec +(s/def ::use-macros-libspec + (s/alt :lib simple-symbol? + :lib+opts (s/spec (s/cat :lib simple-symbol? + :options (s/keys* :req-un [::only] :opt-un [::rename]))))) + +(s/def ::ns-use-macros + (s/spec (s/cat :clause #{:use-macros} + :libs (s/+ (s/alt :libspec ::use-macros-libspec + :flag #{:reload :reload-all :verbose}))))) + + +(s/def ::ns-clauses + (s/* (s/alt :refer-clojure ::ns-refer-clojure + :require ::ns-require + :require-macros ::ns-require-macros + :import ::ns-import + :use ::ns-use + :use-macros ::ns-use-macros))) + +(s/def ::ns-form + (s/cat :ns-name simple-symbol? + :docstring (s/? string?) + :attr-map (s/? map?) + :ns-clauses ::ns-clauses)) + +(s/fdef core/ns-special-form + :args ::ns-form) + +(defn- quoted + "Returns a spec that accepts a (quote ...) form of the spec" + [spec] + (s/spec (s/cat :quote #{'quote} :spec spec))) + +(s/def ::quoted-import-list + (s/* (s/alt :class (quoted simple-symbol?) + :package-list (quoted ::package-list)))) + +(s/fdef core/import + :args ::quoted-import-list) + +(s/fdef core/require + :args (s/+ (s/alt :libspec (quoted ::libspec) + :flag #{:reload :reload-all :verbose}))) + +(s/fdef core/require-macros + :args (s/+ (s/alt :libspec (quoted ::macros-libspec) + :flag #{:reload :reload-all :verbose}))) + +(s/fdef core/use + :args (s/+ (s/alt :libspec (quoted ::use-libspec) + :flag #{:reload :reload-all :verbose}))) + +(s/fdef core/use-macros + :args (s/+ (s/alt :libspec (quoted ::use-macros-libspec) + :flag #{:reload :reload-all :verbose}))) diff --git a/src/main/cljs/cljs/core/specs/alpha.cljs b/src/main/cljs/cljs/core/specs/alpha.cljs new file mode 100644 index 0000000000..eba64b09df --- /dev/null +++ b/src/main/cljs/cljs/core/specs/alpha.cljs @@ -0,0 +1,10 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.core.specs.alpha + (:require-macros [cljs.core.specs.alpha])) diff --git a/src/main/cljs/cljs/externs.js b/src/main/cljs/cljs/externs.js new file mode 100644 index 0000000000..4d0e0f200b --- /dev/null +++ b/src/main/cljs/cljs/externs.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + */ + +Math.imul = function(a, b) {}; + +Object.prototype.done; +Object.prototype.value; +Object.prototype.next = function() {}; + +/** + * @constructor; + */ +function IEquiv() {}; +IEquiv.prototype.equiv = function() {}; + +/** + * @constructor + */ +function Java() {}; +Java.prototype.type = function() {}; \ No newline at end of file diff --git a/src/main/cljs/cljs/imul.js b/src/main/cljs/cljs/imul.js new file mode 100644 index 0000000000..d28a33c35e --- /dev/null +++ b/src/main/cljs/cljs/imul.js @@ -0,0 +1,11 @@ +if(typeof Math.imul == "undefined" || (Math.imul(0xffffffff,5) == 0)) { + Math.imul = function (a, b) { + var ah = (a >>> 16) & 0xffff; + var al = a & 0xffff; + var bh = (b >>> 16) & 0xffff; + var bl = b & 0xffff; + // the shift by 0 fixes the sign on the high part + // the final |0 converts the unsigned value into a signed value + return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0)|0); + } +} diff --git a/src/main/cljs/cljs/js.clj b/src/main/cljs/cljs/js.clj new file mode 100644 index 0000000000..1c4a51a6bd --- /dev/null +++ b/src/main/cljs/cljs/js.clj @@ -0,0 +1,24 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.js + (:require [cljs.env :as env] + [cljs.env.macros :as menv] + [cljs.analyzer :as ana] + [clojure.java.io :as io])) + +(defmacro with-state + [state & body] + `(menv/with-compiler-env ~state + ~@body)) + +(defmacro dump-core [] + (let [state @env/*compiler*] + (if-not (false? (get-in state [:options :dump-core])) + `(quote ~(get-in state [::ana/namespaces 'cljs.core])) + `(hash-map)))) diff --git a/src/main/cljs/cljs/js.cljs b/src/main/cljs/cljs/js.cljs new file mode 100644 index 0000000000..bb8e7fbc42 --- /dev/null +++ b/src/main/cljs/cljs/js.cljs @@ -0,0 +1,1432 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.js + (:refer-clojure :exclude [require eval]) + (:require-macros [cljs.js :refer [dump-core]] + [cljs.env.macros :as env]) + (:require [clojure.string :as string] + [clojure.walk :as walk] + [cljs.env :as env] + [cljs.spec.alpha] + [cljs.analyzer :as ana] + [cljs.compiler :as comp] + [cljs.tools.reader :as r] + [cljs.tools.reader.reader-types :as rt] + [cljs.tagged-literals :as tags] + [goog.crypt.base64 :as base64] + [cljs.source-map :as sm]) + (:import [goog.string StringBuffer])) + +(js/goog.require "cljs.core$macros") + +(defn- debug-prn + [& args] + (binding [*print-fn* *print-err-fn*] + (apply println args))) + +(defn ns->relpath + "Given a namespace as a symbol return the relative path sans extension" + [ns-sym] + (string/replace (ana/munge-path ns-sym) \. \/)) + +(defn file->ns + [file] + (let [lib-name (subs (string/replace file "/" ".") + 0 (- (count file) 5))] + (symbol (demunge lib-name)))) + +(defn- drop-macros-suffix + [ns-name] + (when ns-name + (if (string/ends-with? ns-name "$macros") + (subs ns-name 0 (- (count ns-name) 7)) + ns-name))) + +(defn- elide-macros-suffix + [sym] + (symbol (drop-macros-suffix (namespace sym)) (name sym))) + +(defn- resolve-symbol + [sym] + (if (string/starts-with? (str sym) ".") + sym + (elide-macros-suffix (ana/resolve-symbol sym)))) + +(defn- read [eof rdr] + (binding [*ns* (symbol (drop-macros-suffix (str *ns*)))] + (r/read {:eof eof :read-cond :allow :features #{:cljs}} rdr))) + +(defn- atom? [x] + (instance? Atom x)) + +(defn- valid-name? [x] + (or (nil? x) (symbol? x) (string? x))) + +(defn- valid-opts? [x] + (or (nil? x) (map? x))) + +(defonce + ^{:doc "Each runtime environment provides a different way to load a library. + Whatever function *load-fn* is bound to will be passed two arguments - a + map and a callback function: The map will have the following keys: + + :name - the name of the library (a symbol) + :macros - modifier signaling a macros namespace load + :path - munged relative library path (a string) + + It is up to the implementor to correctly resolve the corresponding .cljs, + .cljc, or .js resource (the order must be respected). If :macros is true + resolution should only consider .clj or .cljc resources (the order must be + respected). Upon resolution the callback should be invoked with a map + containing the following keys: + + :lang - the language, :clj or :js + :source - the source of the library (a string) + :file - optional, the file path, it will be added to AST's :file keyword + (but not in :meta) + :cache - optional, if a :clj namespace has been precompiled to :js, can + give an analysis cache for faster loads. + :source-map - optional, if a :clj namespace has been precompiled to :js, can + give a V3 source map JSON + + If the resource could not be resolved, the callback should be invoked with + nil." + :dynamic true} + *load-fn* + (fn [m cb] + (throw (js/Error. "No *load-fn* set")))) + +(defonce + ^{:doc "Each runtime environment provides various ways to eval JavaScript + source. Whatever function *eval-fn* is bound to will be passed a map + containing the following keys: + + :source - the source of the library (string) + :name - used to unique identify the script (symbol) + :cache - if the source was originally ClojureScript, will be given the + analysis cache. + + The result of evaluation should be the return value." + :dynamic true} + *eval-fn* + (fn [m] + (throw (js/Error. "No *eval-fn* set")))) + +(defn js-eval + "A default JavaScript evaluation function." + [{:keys [source] :as resource}] + (js/eval source)) + +(defn- wrap-error [ex] + {:error ex}) + +(defn empty-state + "Construct an empty compiler state. Required to invoke analyze, compile, + eval and eval-str." + ([] + (doto (env/default-compiler-env) + (swap! + (fn [state] + (-> state + (assoc-in [::ana/namespaces 'cljs.core] (dump-core))))))) + ([init] + (doto (empty-state) (swap! init)))) + +(defn load-analysis-cache! [state ns cache] + (swap! state assoc-in [::ana/namespaces ns] cache)) + +(defn load-source-map! [state ns sm-json] + (let [sm (sm/decode (.parse js/JSON sm-json))] + (swap! state assoc-in [:source-maps ns] sm))) + +(defn- sm-data [] + (atom + {:source-map (sorted-map) + :gen-col 0 + :gen-line 0})) + +(defn- prefix [s pre] + (str pre s)) + +(defn- append-source-map + [state name source sb sm-data {:keys [output-dir asset-path source-map-timestamp] :as opts}] + (let [t (.valueOf (js/Date.)) + mn (if name + (munge (str name)) + (str "cljs-" t)) + smn (cond-> mn + name (string/replace "." "/")) + ts (.valueOf (js/Date.)) + out (or output-dir asset-path) + src (cond-> (str smn ".cljs") + (true? source-map-timestamp) (str "?rel=" ts) + out (prefix (str out "/"))) + file (cond-> (str smn ".js") + (true? source-map-timestamp) (str "?rel=" ts) + out (prefix (str out "/"))) + json (sm/encode {src (:source-map sm-data)} + {:lines (+ (:gen-line sm-data) 3) + :file file :sources-content [source]})] + (when (:verbose opts) (debug-prn json)) + (swap! state assoc-in + [:source-maps (symbol mn)] (sm/invert-reverse-map (:source-map sm-data))) + (.append sb + (str "\n//# sourceURL=" file + "\n//# sourceMappingURL=data:application/json;base64," + (-> (js/encodeURIComponent json) + (string/replace #"%([0-9A-F]{2})" (fn [[_ match]] + (.fromCharCode js/String (str "0x" match)))) + (base64/encodeString)))))) + +(defn- alias-map + [compiler cljs-ns] + (->> (env/with-compiler-env compiler + (ana/get-aliases cljs-ns)) + (remove (fn [[k v]] (symbol-identical? k v))) + (into {}))) + +;; ----------------------------------------------------------------------------- +;; Analyze + +(declare ^{:arglists '([bound-vars source name opts cb])} eval-str*) + +(def *loaded* (atom #{})) + +(defn- run-async! + "Like cljs.core/run!, but for an async procedure, and with the + ability to break prior to processing the entire collection. + + Chains successive calls to the supplied procedure for items in + the collection. The procedure should accept an item from the + collection and a callback of one argument. If the break? predicate, + when applied to the procedure callback value, yields a truthy + result, terminates early calling the supplied cb with the callback + value. Otherwise, when complete, calls cb with nil." + [proc coll break? cb] + (if (seq coll) + (proc (first coll) + (fn [res] + (if (break? res) + (cb res) + (run-async! proc (rest coll) break? cb)))) + (cb nil))) + +(declare ^{:arglists '([name cb] + [name opts cb] + [bound-vars name opts cb] + [bound-vars name reload opts cb])} require) + +(defn- process-deps + [bound-vars names opts cb] + (run-async! (fn [name cb] + (require bound-vars name nil opts cb)) + names + :error + cb)) + +(defn- process-macros-deps + [bound-vars cache opts cb] + (process-deps bound-vars + (distinct (vals (:require-macros cache))) + (-> opts + (assoc :macros-ns true) + (dissoc :emit-constants :optimize-constants)) + cb)) + +(defn- process-libs-deps + [bound-vars cache opts cb] + (process-deps bound-vars + (distinct (concat (vals (:requires cache)) (vals (:imports cache)))) + (dissoc opts :macros-ns) + cb)) + +(defn- pre-file-side-effects + [st name file opts] + (when (:verbose opts) + (debug-prn "Pre-file side-effects" file)) + ;; In case any constants are defined in the namespace, flush any analysis metadata + ;; so that the constants can be defined wihtout triggering re-defined errors. + (when (and (get-in @st [::ana/namespaces name :defs]) + (not ('#{cljs.core cljs.core$macros} name))) + (swap! st update ::ana/namespaces dissoc name))) + +(defn- post-file-side-effects + [file opts] + (when (:verbose opts) + (debug-prn "Post-file side-effects" file)) + ;; Note, we don't (set! *unchecked-arrays* false) here, as that would interpreted + ;; an intrinsic affecting the compilation of this file, emitting a no-op. We bypass this + ;; and emit our own runtime assignment code. + (js* "cljs.core._STAR_unchecked_arrays_STAR_ = false;")) + +(defn require + ([name cb] + (require name nil cb)) + ([name opts cb] + (require nil name opts cb)) + ([bound-vars name opts cb] + (require bound-vars name nil opts cb)) + ([bound-vars name reload opts cb] + (let [bound-vars (merge + {:*compiler* (env/default-compiler-env) + :*data-readers* tags/*cljs-data-readers* + :*load-macros* (:load-macros opts true) + :*analyze-deps* (:analyze-deps opts true) + :*load-fn* (or (:load opts) *load-fn*) + :*eval-fn* (or (:eval opts) *eval-fn*)} + bound-vars) + aname (cond-> name (:macros-ns opts) ana/macro-ns-name)] + (when (= :reload reload) + (swap! *loaded* disj aname)) + (when (= :reload-all reload) + (reset! *loaded* #{})) + (when (:verbose opts) + (debug-prn (str "Loading " name (when (:macros-ns opts) " macros") " namespace"))) + (if-not (contains? @*loaded* aname) + (let [env (:*env* bound-vars)] + (try + ((:*load-fn* bound-vars) + {:name name + :macros (:macros-ns opts) + :path (ns->relpath name)} + (fn [resource] + (assert (or (map? resource) (nil? resource)) + "*load-fn* may only return a map or nil") + (if resource + (let [{:keys [lang source cache source-map file]} resource] + (condp keyword-identical? lang + :clj (do + (pre-file-side-effects (:*compiler* bound-vars) aname file opts) + (eval-str* bound-vars source name (assoc opts :cljs-file file) + (fn [res] + (post-file-side-effects file opts) + (if (:error res) + (cb res) + (do + (swap! *loaded* conj aname) + (cb {:value true})))))) + :js (process-macros-deps bound-vars cache opts + (fn [res] + (if (:error res) + (cb res) + (process-libs-deps bound-vars cache opts + (fn [res] + (if (:error res) + (cb res) + (let [res (try + ((:*eval-fn* bound-vars) resource) + (when cache + (load-analysis-cache! + (:*compiler* bound-vars) aname cache) + (ana/register-specs cache)) + (when source-map + (load-source-map! + (:*compiler* bound-vars) aname source-map)) + (catch :default cause + (wrap-error + (ana/error env + (str "Could not require " name) cause))))] + (if (:error res) + (cb res) + (do + (swap! *loaded* conj aname) + (cb {:value true})))))))))) + (cb (wrap-error + (ana/error env + (str "Invalid :lang specified " lang ", only :clj or :js allowed")))))) + (cb (wrap-error + (ana/error env + (ana/error-message (if (:macros-ns opts) + :undeclared-macros-ns + :undeclared-ns) + {:ns-sym name :js-provide (cljs.core/name name)}))))))) + (catch :default cause + (cb (wrap-error + (ana/error env + (str "Could not require " name) cause)))))) + (cb {:value true}))))) + +(defn- patch-alias-map + [compiler in from to] + (let [patch (fn [k add-if-present?] + (swap! compiler update-in [::ana/namespaces in k] + (fn [m] + (let [replaced (walk/postwalk-replace {from to} m)] + (if (and add-if-present? + (some #{to} (vals replaced))) + (assoc replaced from to) + replaced))))) + patch-renames (fn [k] + (swap! compiler update-in [::ana/namespaces in k] + (fn [m] + (when m + (reduce (fn [acc [renamed qualified-sym :as entry]] + (if (= (str from) (namespace qualified-sym)) + (assoc acc renamed (symbol (str to) (name qualified-sym))) + (merge acc entry))) + {} m)))))] + (patch :requires true) + (patch :require-macros true) + (patch :uses false) + (patch :use-macros false) + (patch-renames :renames) + (patch-renames :rename-macros))) + +(defn- self-require? [deps opts] + (and (true? (:def-emits-var opts)) (some #{ana/*cljs-ns*} deps))) + +(defn- load-deps + ([bound-vars ana-env lib deps cb] + (load-deps bound-vars ana-env lib deps nil nil cb)) + ([bound-vars ana-env lib deps reload opts cb] + (when (:verbose opts) + (debug-prn "Loading dependencies for" lib)) + (binding [ana/*cljs-dep-set* (let [lib (if (self-require? deps opts) + 'cljs.user + lib)] + (vary-meta (conj (:*cljs-dep-set* bound-vars) lib) + update-in [:dep-path] conj lib))] + (let [bound-vars (assoc bound-vars :*cljs-dep-set* ana/*cljs-dep-set*)] + (if-not (every? #(not (contains? ana/*cljs-dep-set* %)) deps) + (cb (wrap-error + (ana/error ana-env + (str "Circular dependency detected " + (apply str + (interpose " -> " + (conj (-> ana/*cljs-dep-set* meta :dep-path) + (some ana/*cljs-dep-set* deps)))))))) + (if (seq deps) + (let [dep (first deps) + opts' (-> opts + (dissoc :context) + (dissoc :def-emits-var) + (dissoc :ns))] + (require bound-vars dep reload opts' + (fn [res] + (when (:verbose opts) + (debug-prn "Loading result:" res)) + (if-not (:error res) + (load-deps bound-vars ana-env lib (next deps) nil opts cb) + (if-let [cljs-dep (let [cljs-ns (ana/clj-ns->cljs-ns dep)] + (get {dep nil} cljs-ns cljs-ns))] + (require bound-vars cljs-dep opts' + (fn [res] + (if (:error res) + (cb res) + (do + (patch-alias-map (:*compiler* bound-vars) lib dep cljs-dep) + (load-deps bound-vars ana-env lib (next deps) nil opts + (fn [res] + (if (:error res) + (cb res) + (cb (update res :aliased-loads assoc dep cljs-dep))))))))) + (cb res)))))) + (cb {:value nil}))))))) + +(declare ^{:arglists '([bound-vars source name opts cb])} analyze-str*) + +(defn- analyze-deps + ([bound-vars ana-env lib deps cb] + (analyze-deps bound-vars ana-env lib deps nil cb)) + ([bound-vars ana-env lib deps opts cb] + (binding [ana/*cljs-dep-set* (vary-meta (conj (:*cljs-dep-set* bound-vars) lib) + update-in [:dep-path] conj lib)] + (let [compiler @(:*compiler* bound-vars) + bound-vars (assoc bound-vars :*cljs-dep-set* ana/*cljs-dep-set*)] + (if-not (every? #(not (contains? ana/*cljs-dep-set* %)) deps) + (cb (wrap-error + (ana/error ana-env + (str "Circular dependency detected " + (apply str + (interpose " -> " + (conj (-> ana/*cljs-dep-set* meta :dep-path) + (some ana/*cljs-dep-set* deps)))))))) + (if (seq deps) + (let [dep (first deps)] + (try + ((:*load-fn* bound-vars) {:name dep :path (ns->relpath dep)} + (fn [resource] + (assert (or (map? resource) (nil? resource)) + "*load-fn* may only return a map or nil") + (if-not resource + (if-let [cljs-dep (let [cljs-ns (ana/clj-ns->cljs-ns dep)] + (get {dep nil} cljs-ns cljs-ns))] + (do + (patch-alias-map (:*compiler* bound-vars) lib dep cljs-dep) + (analyze-deps bound-vars ana-env lib (cons cljs-dep (next deps)) opts + (fn [res] + (if (:error res) + (cb res) + (cb (update res :aliased-loads assoc dep cljs-dep)))))) + (cb (wrap-error + (ana/error ana-env + (ana/error-message :undeclared-ns + {:ns-sym dep :js-provide (name dep)}))))) + (let [{:keys [name lang source file]} resource] + (condp keyword-identical? lang + :clj (do + (pre-file-side-effects (:*compiler* bound-vars) name file opts) + (analyze-str* bound-vars source name (assoc opts :cljs-file file) + (fn [res] + (post-file-side-effects file opts) + (if-not (:error res) + (analyze-deps bound-vars ana-env lib (next deps) opts cb) + (cb res))))) + :js (analyze-deps bound-vars ana-env lib (next deps) opts cb) + (wrap-error + (ana/error ana-env + (str "Invalid :lang specified " lang ", only :clj or :js allowed")))))))) + (catch :default cause + (cb (wrap-error + (ana/error ana-env + (str "Could not analyze dep " dep) cause)))))) + (cb {:value nil}))))))) + +(defn- load-macros [bound-vars k macros lib reload reloads opts cb] + (if (seq macros) + (let [nsym (first (vals macros)) + k (or (reload k) + (get-in reloads [k nsym]) + (and (= nsym name) (:*reload-macros* bound-vars) :reload) + nil) + opts' (-> opts + (assoc :macros-ns true) + (dissoc :context) + (dissoc :def-emits-var) + (dissoc :ns) + (dissoc :emit-constants :optimize-constants))] + (require bound-vars nsym k opts' + (fn [res] + (if-not (:error res) + (load-macros bound-vars k (next macros) lib reload reloads opts cb) + (if-let [cljs-dep (let [cljs-ns (ana/clj-ns->cljs-ns nsym)] + (get {nsym nil} cljs-ns cljs-ns))] + (require bound-vars cljs-dep k opts' + (fn [res] + (if (:error res) + (cb res) + (do + (patch-alias-map (:*compiler* bound-vars) lib nsym cljs-dep) + (load-macros bound-vars k (next macros) lib reload reloads opts + (fn [res] + (if (:error res) + (cb res) + (cb (update res :aliased-loads assoc nsym cljs-dep))))))))) + (cb res)))))) + (cb {:value nil}))) + +(defn- rewrite-ns-ast + ([ast smap] + (rewrite-ns-ast ast smap false)) + ([ast smap macros?] + (let [[uk rk renk] (if macros? + [:use-macros :require-macros :rename-macros] + [:uses :requires :renames]) + rewrite-renames (fn [m] + (when m + (reduce (fn [acc [renamed qualified-sym :as entry]] + (let [from (symbol (namespace qualified-sym)) + to (get smap from)] + (if (some? to) + (assoc acc renamed (symbol (str to) (name qualified-sym))) + (merge acc entry)))) + {} m))) + rewrite-deps (fn [deps] + (into [] + (map (fn [dep] + (if-let [new-dep (get smap dep)] + new-dep + dep))) + deps))] + (-> ast + (update uk #(walk/postwalk-replace smap %)) + (update rk #(merge smap (walk/postwalk-replace smap %))) + (update renk rewrite-renames) + (update :deps rewrite-deps))))) + +(defn- check-macro-autoload-inferring-missing + [{:keys [requires name] :as ast} cenv] + (let [namespaces (-> @cenv ::ana/namespaces) + missing-require-macros (into {} + (filter (fn [[_ full-ns]] + (let [{:keys [use-macros require-macros]} (get namespaces full-ns)] + (or (some #{full-ns} (vals use-macros)) + (some #{full-ns} (vals require-macros)))))) + requires) + ast' (update-in ast [:require-macros] merge missing-require-macros)] + (swap! cenv update-in [::ana/namespaces name :require-macros] merge missing-require-macros) + ast')) + +(defn- ns-side-effects + ([bound-vars ana-env ast opts cb] + (ns-side-effects false bound-vars ana-env ast opts cb)) + ([load bound-vars ana-env {:keys [op] :as ast} opts cb] + (when (:verbose opts) + (debug-prn "Namespace side effects for" (:name ast))) + (if (#{:ns :ns*} op) + (letfn [(check-uses-and-load-macros [res rewritten-ast] + (let [env (:*compiler* bound-vars) + {:keys [uses use-macros reload reloads name]} rewritten-ast] + (if (:error res) + (cb res) + (if (:*load-macros* bound-vars) + (do + (when (:verbose opts) (debug-prn "Processing :use-macros for" name)) + (load-macros bound-vars :use-macros use-macros name reload reloads opts + (fn [res] + (if (:error res) + (cb res) + (let [{:keys [require-macros] :as rewritten-ast} (rewrite-ns-ast rewritten-ast (:aliased-loads res) true)] + (when (:verbose opts) (debug-prn "Processing :require-macros for" (:name ast))) + (load-macros bound-vars :require-macros require-macros name reload reloads opts + (fn [res'] + (if (:error res') + (cb res') + (let [{:keys [use-macros] :as rewritten-ast} (rewrite-ns-ast rewritten-ast (:aliased-loads res) true) + res' (try + (when (seq use-macros) + (when (:verbose opts) (debug-prn "Checking :use-macros for" (:name ast))) + (binding [ana/*analyze-deps* (:*analyze-deps* bound-vars) + env/*compiler* (:*compiler* bound-vars)] + (ana/check-use-macros use-macros env))) + {:value nil} + (catch :default cause + (wrap-error + (ana/error ana-env + (str "Could not parse ns form " (:name ast)) cause))))] + (if (:error res') + (cb res') + (try + (binding [ana/*analyze-deps* (:*analyze-deps* bound-vars) + env/*compiler* (:*compiler* bound-vars)] + (let [ast' (-> rewritten-ast + (ana/check-use-macros-inferring-missing env) + (ana/check-rename-macros-inferring-missing env) + (check-macro-autoload-inferring-missing env))] + (cb {:value ast'}))) + (catch :default cause + (cb (wrap-error + (ana/error ana-env + (str "Could not parse ns form " (:name ast)) cause))))))))))))))) + (try + (when (:verbose opts) (debug-prn "Checking uses")) + (ana/check-uses + (when (and (:*analyze-deps* bound-vars) (seq uses)) + (ana/missing-uses uses env)) + env) + (cb {:value ast}) + (catch :default cause + (cb (wrap-error + (ana/error ana-env + (str "Could not parse ns form " (:name ast)) cause)))))))))] + (cond + (and load (seq (:deps ast))) + (let [{:keys [reload name deps]} ast] + (load-deps bound-vars ana-env name deps (or (:require reload) (:use reload)) (dissoc opts :macros-ns) + #(check-uses-and-load-macros % (rewrite-ns-ast ast (:aliased-loads %))))) + + (and (not load) (:*analyze-deps* bound-vars) (seq (:deps ast))) + (analyze-deps bound-vars ana-env (:name ast) (:deps ast) (dissoc opts :macros-ns) + #(check-uses-and-load-macros % (rewrite-ns-ast ast (:aliased-loads %)))) + + :else + (check-uses-and-load-macros {:value nil} ast))) + (cb {:value ast})))) + +(defn- node-side-effects + [bound-vars sb deps ns-name emit-nil-result?] + (doseq [dep deps] + (.append sb + (with-out-str + (comp/emitln (munge ns-name) "." + (ana/munge-node-lib dep) + " = require('" dep "');")))) + (when (and (seq deps) emit-nil-result?) + (.append sb "null;"))) + +(defn- global-exports-side-effects + [bound-vars sb deps ns-name opts] + (let [{:keys [js-dependency-index]} @(:*compiler* bound-vars)] + (doseq [dep deps] + (let [{:keys [global-exports]} (get js-dependency-index (name dep))] + (.append sb + (with-out-str + (comp/emit-global-export ns-name global-exports dep opts))))) + (when (and (seq deps) (:def-emits-var opts)) + (.append sb "null;")))) + +(defn- trampoline-safe + "Returns a new function that calls f but discards any return value, + returning nil instead, thus avoiding any inadvertent trampoline continuation + if a function happens to be returned." + [f] + (comp (constantly nil) f)) + +(defn- analyze-str* [bound-vars source name opts cb] + (let [rdr (rt/indexing-push-back-reader source 1 name) + cb (trampoline-safe cb) + eof (js-obj) + aenv (ana/empty-env) + the-ns (or (:ns opts) 'cljs.user) + bound-vars (cond-> (merge bound-vars {:*cljs-ns* the-ns}) + (:source-map opts) (assoc :*sm-data* (sm-data)))] + (trampoline + (fn analyze-loop [last-ast ns] + (binding [env/*compiler* (:*compiler* bound-vars) + ana/*cljs-ns* ns + ana/*checked-arrays* (:checked-arrays opts) + ana/*cljs-static-fns* (:static-fns opts) + ana/*fn-invoke-direct* (and (:static-fns opts) (:fn-invoke-direct opts)) + *ns* (create-ns ns) + ana/*passes* (:*passes* bound-vars) + r/*alias-map* (alias-map @(:*compiler* bound-vars) ns) + r/*data-readers* (:*data-readers* bound-vars) + r/resolve-symbol resolve-symbol + comp/*source-map-data* (:*sm-data* bound-vars) + ana/*cljs-file* (:cljs-file opts)] + (let [res (try + {:value (read eof rdr)} + (catch :default cause + (wrap-error + (ana/error aenv + (str "Could not analyze " name) cause))))] + (if (:error res) + (cb res) + (let [form (:value res)] + (if-not (identical? eof form) + (let [aenv (cond-> (assoc aenv :ns (ana/get-namespace ana/*cljs-ns*)) + (:context opts) (assoc :context (:context opts)) + (:def-emits-var opts) (assoc :def-emits-var true)) + res (try + {:value (ana/analyze aenv form nil opts)} + (catch :default cause + (wrap-error + (ana/error aenv + (str "Could not analyze " name) cause))))] + (if (:error res) + (cb res) + (let [ast (:value res)] + (if (#{:ns :ns*} (:op ast)) + ((trampoline-safe ns-side-effects) bound-vars aenv ast opts + (fn [res] + (if (:error res) + (cb res) + (trampoline analyze-loop ast (:name ast))))) + #(analyze-loop ast ns))))) + (cb {:value last-ast}))))))) nil the-ns))) + +(defn analyze-str + "Analyze ClojureScript source. The compiler state will be populated with + the results of analyzes. The parameters: + + state (atom) + the compiler state + + source (string) + the ClojureScript source + + name (symbol or string) + optional, the name of the source + + opts (map) + compilation options. + + :eval - eval function to invoke, see *eval-fn* + :load - library resolution function, see *load-fn* + :source-map - set to true to generate inline source map information + :def-emits-var - sets whether def (and derived) forms return either a Var + (if set to true) or the def init value (if false). + Defaults to false. + :checked-arrays - if :warn or :error, checks inferred types and values passed + to aget/aset. Logs for incorrect values if :warn, throws if + :error. Defaults to false. + :static-fns - employ static dispatch to specific function arities in + emitted JavaScript, as opposed to making use of the + `call` construct. Defaults to false. + :fn-invoke-direct - if `true`, does not generate `.call(null...)` calls for + unknown functions, but instead direct invokes via + `f(a0,a1...)`. Defaults to `false`. + :target - use `:nodejs` if targeting Node.js. Takes no other options + at the moment. + :ns - optional, the namespace in which to evaluate the source. + :verbose - optional, emit details from compiler activity. Defaults to + false. + :context - optional, sets the context for the source. Possible values + are `:expr`, `:statement` and `:return`. Defaults to + `:statement`. + + cb (function) + callback, will be invoked with a map. If successful the map will contain + a key :value, the actual value is not meaningful. If unsuccessful the + map will contain a key :error with an ex-info instance describing the cause + of failure." + ([state source cb] + (analyze-str state source nil cb)) + ([state source name cb] + (analyze-str state source name nil cb)) + ([state source name opts cb] + {:pre [(atom? state) (string? source) + (valid-name? name) (valid-opts? opts) (fn? cb)]} + (analyze-str* + {:*compiler* state + :*data-readers* tags/*cljs-data-readers* + :*passes* (or (:passes opts) ana/*passes*) + :*analyze-deps* (:analyze-deps opts true) + :*cljs-dep-set* ana/*cljs-dep-set* + :*load-macros* (:load-macros opts true) + :*load-fn* (or (:load opts) *load-fn*) + :*eval-fn* (or (:eval opts) *eval-fn*)} + source name opts cb))) + +;; ----------------------------------------------------------------------------- +;; Eval + +(declare ^{:arglists '([])} clear-fns!) + +(defn- eval* [bound-vars form opts cb] + (let [the-ns (or (:ns opts) 'cljs.user) + bound-vars (cond-> (merge bound-vars {:*cljs-ns* the-ns}) + (:source-map opts) (assoc :*sm-data* (sm-data)))] + (clear-fns!) + (binding [env/*compiler* (:*compiler* bound-vars) + *eval-fn* (:*eval-fn* bound-vars) + ana/*cljs-ns* (:*cljs-ns* bound-vars) + ana/*checked-arrays* (:checked-arrays opts) + ana/*cljs-static-fns* (:static-fns opts) + ana/*fn-invoke-direct* (and (:static-fns opts) (:fn-invoke-direct opts)) + *ns* (create-ns (:*cljs-ns* bound-vars)) + r/*alias-map* (alias-map @(:*compiler* bound-vars) (:*cljs-ns* bound-vars)) + r/*data-readers* (:*data-readers* bound-vars) + r/resolve-symbol resolve-symbol + comp/*source-map-data* (:*sm-data* bound-vars)] + (let [aenv (ana/empty-env) + aenv (cond-> (assoc aenv :ns (ana/get-namespace ana/*cljs-ns*)) + (:context opts) (assoc :context (:context opts)) + (:def-emits-var opts) (assoc :def-emits-var true)) + res (try + {:value (ana/analyze aenv form nil opts)} + (catch :default cause + (wrap-error + (ana/error aenv + (str "Could not eval " form) cause))))] + (if (:error res) + (cb res) + (let [ast (:value res) + [node-deps ast] (if (keyword-identical? (:target opts) :nodejs) + (let [{node-libs true libs-to-load false} (group-by ana/node-module-dep? (:deps ast))] + [node-libs (assoc ast :deps libs-to-load)]) + [nil ast])] + (if (#{:ns :ns*} (:op ast)) + (ns-side-effects true bound-vars aenv ast opts + (fn [res] + (if (:error res) + (cb res) + (let [ns-name (:name ast) + sb (StringBuffer.)] + (.append sb + (with-out-str (comp/emitln (str "goog.provide(\"" (comp/munge ns-name) "\");")))) + (when-not (nil? node-deps) + (node-side-effects bound-vars sb node-deps ns-name (:def-emits-var opts))) + (global-exports-side-effects bound-vars sb + (filter ana/dep-has-global-exports? (:deps ast)) + ns-name opts) + (cb (try + {:ns ns-name :value ((:*eval-fn* bound-vars) {:source (.toString sb)})} + (catch :default cause + (wrap-error (ana/error aenv "ERROR" cause))))))))) + (let [src (with-out-str (comp/emit ast))] + (cb (try + {:value ((:*eval-fn* bound-vars) {:source src})} + (catch :default cause + (wrap-error (ana/error aenv "ERROR" cause))))))))))))) + +(defn eval + "Evaluate a single ClojureScript form. The parameters: + + state (atom) + the compiler state + + form (s-expr) + the ClojureScript source + + opts (map) + compilation options. + + :eval - eval function to invoke, see *eval-fn* + :load - library resolution function, see *load-fn* + :source-map - set to true to generate inline source map information + :def-emits-var - sets whether def (and derived) forms return either a Var + (if set to true) or the def init value (if false). Default + is false. + :checked-arrays - if :warn or :error, checks inferred types and values passed + to aget/aset. Logs for incorrect values if :warn, throws if + :error. Defaults to false. + :static-fns - employ static dispatch to specific function arities in + emitted JavaScript, as opposed to making use of the + `call` construct. Defaults to false. + :fn-invoke-direct - if `true`, does not generate `.call(null...)` calls for + unknown functions, but instead direct invokes via + `f(a0,a1...)`. Defaults to `false`. + :target - use `:nodejs` if targeting Node.js. Takes no other options + at the moment. + :ns - optional, the namespace in which to evaluate the source. + :verbose - optional, emit details from compiler activity. Defaults to + false. + :context - optional, sets the context for the source. Possible values + are `:expr`, `:statement` and `:return`. Defaults to + `:statement`. + + cb (function) + callback, will be invoked with a map. If successful the map will contain + a key :value with the result of evalution. If unsuccessful the map will + contain a key :error with an ex-info instance describing the cause of + failure." + ([state form cb] + (eval state form nil cb)) + ([state form opts cb] + (eval* + {:*compiler* state + :*data-readers* tags/*cljs-data-readers* + :*analyze-deps* (:analyze-deps opts true) + :*cljs-dep-set* ana/*cljs-dep-set* + :*load-macros* (:load-macros opts true) + :*load-fn* (or (:load opts) *load-fn*) + :*eval-fn* (or (:eval opts) *eval-fn*)} + form opts cb))) + +;; ----------------------------------------------------------------------------- +;; Compile + +(defn- compile-str* [bound-vars source name opts cb] + (let [rdr (rt/indexing-push-back-reader source 1 name) + cb (trampoline-safe cb) + eof (js-obj) + aenv (ana/empty-env) + sb (StringBuffer.) + the-ns (or (:ns opts) 'cljs.user) + bound-vars (cond-> (merge bound-vars {:*cljs-ns* the-ns}) + (:source-map opts) (assoc :*sm-data* (sm-data)))] + (trampoline + (fn compile-loop [ns] + (binding [env/*compiler* (:*compiler* bound-vars) + *eval-fn* (:*eval-fn* bound-vars) + ana/*cljs-ns* ns + ana/*checked-arrays* (:checked-arrays opts) + ana/*cljs-static-fns* (:static-fns opts) + ana/*fn-invoke-direct* (and (:static-fns opts) (:fn-invoke-direct opts)) + *ns* (create-ns ns) + r/*alias-map* (alias-map @(:*compiler* bound-vars) ns) + r/*data-readers* (:*data-readers* bound-vars) + r/resolve-symbol resolve-symbol + comp/*source-map-data* (:*sm-data* bound-vars)] + (let [res (try + {:value (read eof rdr)} + (catch :default cause + (wrap-error + (ana/error aenv + (str "Could not compile " name) cause))))] + (if (:error res) + (cb res) + (let [form (:value res)] + (if-not (identical? eof form) + (let [aenv (cond-> (assoc aenv :ns (ana/get-namespace ana/*cljs-ns*)) + (:context opts) (assoc :context (:context opts)) + (:def-emits-var opts) (assoc :def-emits-var true)) + res (try + {:value (ana/analyze aenv form nil opts)} + (catch :default cause + (wrap-error + (ana/error aenv + (str "Could not compile " name) cause))))] + (if (:error res) + (cb res) + (let [ast (:value res) + [node-deps ast] (if (keyword-identical? (:target opts) :nodejs) + (let [{node-libs true libs-to-load false} (group-by ana/node-module-dep? (:deps ast))] + [node-libs (assoc ast :deps libs-to-load)]) + [nil ast])] + (if (#{:ns :ns*} (:op ast)) + ((trampoline-safe ns-side-effects) bound-vars aenv ast opts + (fn [res] + (if (:error res) + (cb res) + (let [ns-name (:name ast)] + (.append sb (with-out-str (comp/emit (:value res)))) + (when-not (nil? node-deps) + (node-side-effects bound-vars sb node-deps ns-name (:def-emits-var opts))) + (trampoline compile-loop (:name ast)))))) + (do + (.append sb (with-out-str (comp/emit ast))) + #(compile-loop ns)))))) + (do + (when (:source-map opts) + (append-source-map env/*compiler* + name source sb @comp/*source-map-data* opts)) + (cb {:value (.toString sb)})))))))) the-ns))) + +(defn compile-str + "Compile ClojureScript source into JavaScript. The parameters: + + state (atom) + the compiler state + + source (string) + the ClojureScript source + + name (symbol or string) + optional, the name of the source - used as key in :source-maps + + opts (map) + compilation options. + + :eval - eval function to invoke, see *eval-fn* + :load - library resolution function, see *load-fn* + :source-map - set to true to generate inline source map information + :def-emits-var - sets whether def (and derived) forms return either a Var + (if set to true) or the def init value (if false). Default + is false. + :checked-arrays - if :warn or :error, checks inferred types and values passed + to aget/aset. Logs for incorrect values if :warn, throws if + :error. Defaults to false. + :static-fns - employ static dispatch to specific function arities in + emitted JavaScript, as opposed to making use of the + `call` construct. Defaults to false. + :fn-invoke-direct - if `true`, does not generate `.call(null...)` calls for + unknown functions, but instead direct invokes via + `f(a0,a1...)`. Defaults to `false`. + :target - use `:nodejs` if targeting Node.js. Takes no other options + at the moment. + :ns - optional, the namespace in which to evaluate the source. + :verbose - optional, emit details from compiler activity. Defaults to + false. + :context - optional, sets the context for the source. Possible values + are `:expr`, `:statement` and `:return`. Defaults to + `:statement`. + + cb (function) + callback, will be invoked with a map. If successful the map will contain + a key :value with the compilation result (string). If unsuccessful the map + will contain a key :error with an ex-info instance describing the cause + of failure." + ([state source cb] + (compile-str state source nil cb)) + ([state source name cb] + (compile-str state source name nil cb)) + ([state source name opts cb] + {:pre [(atom? state) (string? source) + (valid-name? name) (valid-opts? opts) (fn? cb)]} + (compile-str* + {:*compiler* state + :*data-readers* tags/*cljs-data-readers* + :*cljs-dep-set* ana/*cljs-dep-set* + :*analyze-deps* (:analyze-deps opts true) + :*load-macros* (:load-macros opts true) + :*load-fn* (or (:load opts) *load-fn*) + :*eval-fn* (or (:eval opts) *eval-fn*) + :*sm-data* (when (:source-map opts) (sm-data))} + source name opts cb))) + +;; ----------------------------------------------------------------------------- +;; Evaluate String + +(defn- eval-str* [bound-vars source name opts cb] + (let [rdr (rt/indexing-push-back-reader source 1 name) + cb (trampoline-safe cb) + eof (js-obj) + aenv (ana/empty-env) + sb (StringBuffer.) + the-ns (or (:ns opts) 'cljs.user) + bound-vars (cond-> (merge bound-vars {:*cljs-ns* the-ns}) + (:source-map opts) (assoc :*sm-data* (sm-data))) + aname (cond-> name (:macros-ns opts) ana/macro-ns-name)] + (when (:verbose opts) (debug-prn "Evaluating" name)) + (clear-fns!) + (trampoline + (fn compile-loop [ns] + (binding [env/*compiler* (:*compiler* bound-vars) + *eval-fn* (:*eval-fn* bound-vars) + ana/*cljs-ns* ns + ana/*checked-arrays* (:checked-arrays opts) + ana/*cljs-static-fns* (:static-fns opts) + ana/*fn-invoke-direct* (and (:static-fns opts) (:fn-invoke-direct opts)) + *ns* (create-ns ns) + r/*alias-map* (alias-map @(:*compiler* bound-vars) ns) + r/*data-readers* (:*data-readers* bound-vars) + r/resolve-symbol resolve-symbol + comp/*source-map-data* (:*sm-data* bound-vars) + ana/*cljs-file* (:cljs-file opts)] + (let [res (try + {:value (read eof rdr)} + (catch :default cause + (wrap-error + (ana/error aenv + (str "Could not eval " name) cause))))] + (if (:error res) + (cb res) + (let [form (:value res)] + (if-not (identical? eof form) + (let [aenv (cond-> (assoc aenv :ns (ana/get-namespace ns)) + (:context opts) (assoc :context (:context opts)) + (:def-emits-var opts) (assoc :def-emits-var true)) + res (try + {:value (ana/analyze aenv form nil opts)} + (catch :default cause + (wrap-error + (ana/error aenv + (str "Could not eval " name) cause))))] + (if (:error res) + (cb res) + (let [ast (:value res) + ns' ana/*cljs-ns* + [node-deps ast] (if (keyword-identical? (:target opts) :nodejs) + (let [{node-libs true libs-to-load false} (group-by ana/node-module-dep? (:deps ast))] + [node-libs (assoc ast :deps libs-to-load)]) + [nil ast])] + (if (#{:ns :ns*} (:op ast)) + (do + (.append sb + (with-out-str (comp/emitln (str "goog.provide(\"" (comp/munge (:name ast)) "\");")))) + ((trampoline-safe ns-side-effects) true bound-vars aenv ast opts + (fn [res] + (if (:error res) + (cb res) + (let [ns-name (:name ast)] + (when-not (nil? node-deps) + (node-side-effects bound-vars sb node-deps ns-name (:def-emits-var opts))) + (global-exports-side-effects bound-vars sb + (filter ana/dep-has-global-exports? (:deps ast)) + ns-name opts) + (trampoline compile-loop ns')))))) + (do + (env/with-compiler-env (assoc @(:*compiler* bound-vars) :options opts) + (.append sb (with-out-str (comp/emit ast)))) + #(compile-loop ns')))))) + (do + (when (:source-map opts) + (append-source-map env/*compiler* + aname source sb @comp/*source-map-data* opts)) + (when (symbol? aname) + (ana/dump-specs aname)) + (let [js-source (.toString sb) + evalm {:lang :clj + :name name + :path (ns->relpath name) + :source js-source + :cache (get-in @env/*compiler* [::ana/namespaces aname])} + complete (fn [res] + (if (:error res) + (cb res) + (do + (when (:verbose opts) + (debug-prn js-source)) + (let [res (try + {:ns ns :value (*eval-fn* evalm)} + (catch :default cause + (wrap-error (ana/error aenv "ERROR" cause))))] + (cb res)))))] + (if-let [f (:cache-source opts)] + ((trampoline-safe f) evalm complete) + (complete {:value nil})))))))))) + (:*cljs-ns* bound-vars)))) + +(defn eval-str + "Evalute ClojureScript source given as a string. The parameters: + + state (atom) + the compiler state + + source (string) + the ClojureScript source + + name (symbol or string) + optional, the name of the source - used as key in :source-maps + + opts (map) + compilation options. + + :eval - eval function to invoke, see *eval-fn* + :load - library resolution function, see *load-fn* + :source-map - set to true to generate inline source map information + :cache-source - optional, a function to run side-effects with the + compilation result prior to actual evalution. This function + takes two arguments, the first is the eval map, the source + will be under :source. The second argument is a callback of + one argument. If an error occurs an :error key should be + supplied. + :def-emits-var - sets whether def (and derived) forms return either a Var + (if set to true) or the def init value (if false). Default + is false. + :checked-arrays - if :warn or :error, checks inferred types and values passed + to aget/aset. Logs for incorrect values if :warn, throws if + :error. Defaults to false. + :static-fns - employ static dispatch to specific function arities in + emitted JavaScript, as opposed to making use of the + `call` construct. Defaults to false. + :fn-invoke-direct - if `true`, does not generate `.call(null...)` calls for + unknown functions, but instead direct invokes via + `f(a0,a1...)`. Defaults to `false`. + :target - use `:nodejs` if targeting Node.js. Takes no other options + at the moment. + :ns - optional, the namespace in which to evaluate the source. + :verbose - optional, emit details from compiler activity. Defaults to + false. + :context - optional, sets the context for the source. Possible values + are `:expr`, `:statement` and `:return`. Defaults to + `:statement`. + + cb (function) + callback, will be invoked with a map. If succesful the map will contain + a :value key with the result of evaluation and :ns the current namespace. + If unsuccessful will contain a :error key with an ex-info instance describing + the cause of failure." + ([state source cb] + (eval-str state source nil cb)) + ([state source name cb] + (eval-str state source name nil cb)) + ([state source name opts cb] + {:pre [(atom? state) (string? source) + (valid-name? name) (valid-opts? opts) (fn? cb)]} + (eval-str* + {:*compiler* state + :*data-readers* tags/*cljs-data-readers* + :*analyze-deps* (:analyze-deps opts true) + :*cljs-dep-set* ana/*cljs-dep-set* + :*load-macros* (:load-macros opts true) + :*load-fn* (or (:load opts) *load-fn*) + :*eval-fn* (or (:eval opts) *eval-fn*)} + source name opts cb))) + +;;; Support for cljs.core/eval + +;; The following volatiles and fns set up a scheme to +;; emit function values into JavaScript as numeric +;; references that are looked up. Needed to implement eval. + +(defonce ^:private fn-index (volatile! 0)) +(defonce ^:private fn-refs (volatile! {})) + +(defn- clear-fns! + "Clears saved functions." + [] + (vreset! fn-refs {})) + +(defn- put-fn + "Saves a function, returning a numeric representation." + [f] + (let [n (vswap! fn-index inc)] + (vswap! fn-refs assoc n f) + n)) + +(defn- get-fn + "Gets a function, given its numeric representation." + [n] + (get @fn-refs n)) + +(defn- emit-fn [f] + (print "cljs.js.get_fn(" (put-fn f) ")")) + +(defmethod comp/emit-constant* js/Function + [f] + (emit-fn f)) + +(defmethod comp/emit-constant* cljs.core/Var + [f] + (emit-fn f)) + +(defn- eval-impl + ([form] + (eval-impl form (.-name *ns*))) + ([form ns] + (let [result (atom nil)] + (let [st env/*compiler*] + (eval st form + {:ns ns + :context :expr + :def-emits-var true} + (fn [{:keys [value error]}] + (if error + (throw error) + (reset! result value))))) + @result))) + +(set! *eval* eval-impl) + +(comment + (require '[cljs.js :as cljs] + '[cljs.analyzer :as ana]) + + (def vm (js/require "vm")) + (def fs (js/require "fs")) + (def st (cljs/empty-state)) + + (set! *target* "nodejs") + + (defn node-eval [{:keys [name source]}] + (.runInThisContext vm source (str (munge name) ".js"))) + + (def libs + {'bootstrap-test.core :cljs + 'bootstrap-test.macros :clj + 'bootstrap-test.helper :clj}) + + (defn node-load [{:keys [name macros]} cb] + (if (contains? libs name) + (let [path (str "src/test/cljs/" (cljs/ns->relpath name) + "." (cljs.core/name (get libs name)))] + (.readFile fs path "utf-8" + (fn [err src] + (cb (if-not err + {:lang :clj :source src} + (.error js/console err)))))) + (cb nil))) + + (defn elide-env [env ast opts] + (dissoc ast :env)) + + (cljs/analyze-str st "(+ 1 1)" nil + {:passes [ana/infer-type elide-env] + :eval node-eval} + (fn [{:keys [value]}] + (println value))) + + (cljs/eval st '(defn foo [a b] (+ a b)) + {:eval node-eval} + (fn [res] + (println res))) + + (cljs/compile-str st "(defprotocol IFoo (foo [this]))" + (fn [{:keys [value]}] + (println "Source:") + (println value))) + + (cljs/eval-str st + "(defn foo [a b] (+ a b)) + (defn bar [c d] (+ c d))" + nil + {:eval node-eval} + (fn [res] + (println res))) + + (cljs/eval-str st "1" + nil + {:eval node-eval + :context :expr} + (fn [res] + (println res))) + + (cljs/eval-str st "(def x 1)" + nil + {:eval node-eval + :context :expr + :def-emits-var true} + (fn [res] + (println res))) + + (cljs/eval st '(ns foo.bar) + {:eval node-eval} + (fn [res] + (println res))) + + (cljs/eval st '(def x 1) + {:eval node-eval + :context :expr + :def-emits-var true + :ns 'foo.bar} + (fn [res] + (println res))) + + (cljs/compile-str st "(defn foo\n[a b]\n(+ a b))" 'cljs.foo + {:verbose true :source-map true} + (fn [js-source] + (println "Source:") + (println js-source))) + + (cljs/eval-str st + "(ns foo.bar (:require [bootstrap-test.core]))\n(bootstrap-test.core/foo 3 4)" + 'foo.bar + {:verbose true + :source-map true + :eval node-eval + :load node-load} + (fn [ret] + (println ret))) + + (cljs/eval-str st + "(ns foo.bar (:require-macros [bootstrap-test.macros :refer [foo]]))\n(foo 4 4)" + 'foo.bar + {:verbose true + :source-map true + :eval node-eval + :load node-load} + (fn [{:keys [error] :as res}] + (if error + (do + (println "Error:" error) + (println (.. error -cause -stack))) + (println "Result:" res)))) + + (cljs/compile-str st + "(ns foo.bar (:require-macros [bootstrap-test.macros :refer [foo]]))\n(foo 4 4)" + 'foo.bar + {:verbose true + :source-map true + :eval node-eval + :load node-load} + (fn [{:keys [error] :as res}] + (if error + (do + (println "Error:" error) + (println (.. error -cause -stack))) + (println "Result:" res)))) + + (cljs/eval-str st + "(ns foo.bar)\n(first [1 2 3])" + 'foo.bar + {:verbose true + :source-map true + :eval node-eval + :load node-load} + (fn [{:keys [error] :as res}] + (if error + (do + (println error) + (println (.. error -cause -stack))) + (println res)))) + + (cljs/eval-str st + "(ns foo.bar)\n(map inc [1 2 3])" + 'foo.bar + {:verbose true + :source-map true + :eval node-eval + :load node-load} + (fn [{:keys [error] :as res}] + (if error + (do + (println error) + (println (.. error -cause -stack))) + (println res)))) + + ;; *NOT* source mapped under Node.js + ;; source-map-support does not yet work, users will need to map + ;; themselves + (cljs/eval-str st + "(ns foo.bar)\n(ffirst [1 2 3])" + 'foo.bar + {:verbose true + :source-map true + :eval node-eval + :load node-load} + (fn [{:keys [error] :as res}] + (if error + (do + (println error) + (println (.. error -cause -stack))) + (println res)))) + ) diff --git a/src/main/cljs/cljs/loader.cljs b/src/main/cljs/cljs/loader.cljs new file mode 100644 index 0000000000..473d0e5b86 --- /dev/null +++ b/src/main/cljs/cljs/loader.cljs @@ -0,0 +1,107 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software + +(ns cljs.loader + (:require [goog.object :as gobj] + [goog.html.legacyconversions :as legacy]) + (:import [goog.module ModuleManager] + [goog.module ModuleLoader])) + +(def module-infos MODULE_INFOS) ;; set by compiler +(def module-uris + (if (exists? js/COMPILED_MODULE_URIS) + js/COMPILED_MODULE_URIS + MODULE_URIS)) ;; set by compiler + +(defn deps-for [x graph] + (let [depends-on (get graph x)] + (-> (mapcat #(deps-for % graph) depends-on) + (concat depends-on) distinct vec))) + +(defn munge-kw [x] + (cond-> x + (keyword? x) (-> name munge))) + +(defn to-tr-url [x] + (cond-> x + (not (keyword? x)) legacy/trustedResourceUrlFromString)) + +(defn to-js [m] + (reduce-kv + (fn [ret k xs] + (let [arr (into-array (map (comp munge-kw to-tr-url) xs))] + (doto ret (gobj/set (-> k name munge) arr)))) + #js {} m)) + +(defn create-module-manager [] + (let [mm (ModuleManager.) + ml (ModuleLoader.)] + (.setLoader mm ml) + mm)) + +(defonce ^:dynamic *module-manager* (create-module-manager)) + +(.setAllModuleInfo *module-manager* (to-js module-infos)) +(.setModuleTrustedUris *module-manager* + (cond-> module-uris (map? module-uris) to-js)) + +(defn loaded? + "Return true if modules is loaded. module-name should be a keyword matching + a :modules module definition." + [module-name] + (assert (contains? module-infos module-name) + (str "Module " module-name " does not exist")) + (let [mname (-> module-name name munge) + module (.getModuleInfo *module-manager* mname)] + (when (some? module) + (.isLoaded module)))) + +(defn load + "Load a module. module-name should be a keyword matching a :modules module + definition." + ([module-name] + (load module-name nil)) + ([module-name cb] + (assert (contains? module-infos module-name) + (str "Module " module-name " does not exist")) + (let [mname (-> module-name name munge)] + (.beforeLoadModuleCode *module-manager* mname) + (if-not (nil? cb) + (.execOnLoad *module-manager* mname cb) + (.load *module-manager* mname))))) + +(defn set-loaded! + "Set a module as being loaded. module-name should be a keyword matching a + :modules module definition. Will mark all parent modules as also being + loaded." + [module-name] + (assert (contains? module-infos module-name) + (str "Module " module-name " does not exist")) + (let [deps (deps-for module-name module-infos)] + (doseq [dep deps] + (let [dep' (munge-kw dep)] + (when (.isModuleLoading *module-manager* dep') + (.setLoaded *module-manager* dep')) + (.setLoaded (.getModuleInfo *module-manager* dep')))) + (let [module-name' (munge-kw module-name)] + (when (.isModuleLoading *module-manager* module-name') + (.setLoaded *module-manager* module-name')) + (.setLoaded (.getModuleInfo *module-manager* module-name'))))) + +(defn prefetch + "Prefetch a module. module-name should be a keyword matching a :modules + module definition. Will download the module but not evaluate it. To + complete module load, one must also call cljs.loader/load after prefetching + the module. Does nothing if the module is loading or has been loaded." + [module-name] + (assert (contains? module-infos module-name) + (str "Module " module-name " does not exist")) + (when-not (loaded? module-name) + (let [mname (-> module-name name munge)] + (when-not (.isModuleLoading *module-manager* mname) + (.prefetchModule *module-manager* mname))))) diff --git a/src/main/cljs/cljs/main.clj b/src/main/cljs/cljs/main.clj new file mode 100644 index 0000000000..eec3fb5ac7 --- /dev/null +++ b/src/main/cljs/cljs/main.clj @@ -0,0 +1,67 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.main + (:require [cljs.repl.browser :as browser] + [cljs.cli :as cli] + [clojure.string :as string]) + (:gen-class)) + +(defn single-segment? [x] + (== 1 (count (string/split x #"\.")))) + +(defn- get-js-opt [args] + (if (= 2 (count args)) + (let [ns-frag (nth args 1) + repl-ns (symbol + (cond->> ns-frag + (single-segment? ns-frag) + (str "cljs.repl.")))] + (try + (require repl-ns) + (if-let [repl-env (ns-resolve repl-ns 'repl-env)] + repl-env + (throw + (ex-info (str "REPL namespace " repl-ns " does not define repl-env var") + {:repl-ns repl-ns}))) + (catch Throwable t + (throw + (ex-info (str "Failed to load REPL namespace " repl-ns) + {:repl-ns repl-ns} t))))) + browser/repl-env)) + +(defn- normalize* [args] + (if (not (cli/dispatch? cli/default-commands :main (first args))) + (let [pred (complement #{"-re" "--repl-env"}) + [pre post] ((juxt #(take-while pred %) + #(drop-while pred %)) + args)] + (if (= pre args) + [nil pre] + (let [[js-opt post'] (normalize* (nnext post))] + (if js-opt + [js-opt (concat pre post')] + [[(first post) (fnext post)] (concat pre post')])))) + [nil args])) + +(defn normalize [args] + (let [[js-opt args] (normalize* args)] + (concat js-opt args))) + +(defn -main [& args] + (let [args (normalize (cli/normalize cli/default-commands args)) + pred (complement #{"-re" "--repl-env"}) + [pre post] ((juxt #(take-while pred %) + #(drop-while pred %)) + args) + [js-args args] ((juxt #(take 2 %) #(drop 2 %)) post) + repl-opt (get-js-opt js-args)] + (try + (apply cli/main repl-opt (concat pre args)) + (finally + (shutdown-agents))))) diff --git a/src/main/cljs/cljs/math.cljs b/src/main/cljs/cljs/math.cljs new file mode 100644 index 0000000000..1d6c4ff204 --- /dev/null +++ b/src/main/cljs/cljs/math.cljs @@ -0,0 +1,869 @@ +(ns ^{:doc "ClojureScript wrapper functions for math operations" + :author "Paula Gearon" } + cljs.math) + +(def + ^{:doc "Constant for Euler's number e, the base for natural logarithms. + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/E" + :added "1.11.10" + :tag number + :const true} E Math/E) + +(def + ^{:doc "Constant for pi, the ratio of the circumference of a circle to its diameter. + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/PI" + :added "1.11.10" + :tag number + :const true} PI Math/PI) + +(def + ^{:doc "Constant used to convert an angular value in degrees to the equivalent in radians" + :private true + :added "1.11.10" + :const true} DEGREES-TO-RADIANS 0.017453292519943295) + +(def + ^{:doc "Constant used to convert an angular value in radians to the equivalent in degrees" + :private true + :added "1.11.10" + :const true} RADIANS-TO-DEGREES 57.29577951308232) + +(def ^{:private true :const true} TWO-TO-THE-52 0x10000000000000) + +(def ^{:private true :const true} SIGNIFICAND-WIDTH32 21) + +(def ^{:private true :const true} EXP-BIAS 1023) + +(def ^{:private true :const true} EXP-BITMASK32 0x7FF00000) + +(def ^{:private true :const true} EXP-MAX EXP-BIAS) + +(def ^{:private true :const true} EXP-MIN -1022) + +;; js/Number.MIN_VALUE has a bit representation of 0x0000000000000001 + +;; js/Number.MAX_VALUE has a bit representation of 0x7FEFFFFFFFFFFFFF + +(defn- get-little-endian + "Tests the platform for endianness. Returns true when little-endian, false otherwise." + [] + (let [a (js/ArrayBuffer. 4) + i (js/Uint32Array. a) + b (js/Uint8Array. a)] + (aset i 0 0x33221100) + (zero? (aget b 0)))) + +(defonce ^:private little-endian? (get-little-endian)) + +;; the HI and LO labels are terse to reflect the C macros they represent +(def ^{:private true :doc "offset of hi integers in 64-bit values"} HI (if little-endian? 1 0)) + +(def ^{:private true :doc "offset of hi integers in 64-bit values"} LO (- 1 HI)) + +(def ^{:private true :const true} INT32-MASK 0xFFFFFFFF) + +(def ^{:private true :const true} INT32-NON-SIGN-BIT 0x80000000) + +(def ^{:private true :const true} INT32-NON-SIGN-BITS 0x7FFFFFFF) + +(defn u< + {:doc "unsigned less-than comparator for 32-bit values" + :private true} + [a b] + ;; compare the top nybble + (let [ab (unsigned-bit-shift-right a 28) + bb (unsigned-bit-shift-right b 28)] + (or (< ab bb) ;; if the top nybble of a is less then the whole value is less + (and (== ab bb) ;; if the top nybble is equal then compare the remaining bits of both + (< (bit-and a 0x0fffffff) (bit-and b 0x0fffffff)))))) + +(defn ^number sin + {:doc "Returns the sine of an angle. + If a is ##NaN, ##-Inf, ##Inf => ##NaN + If a is zero => zero with the same sign as a + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sin" + :added "1.11.10"} + [a] (Math/sin a)) + +(defn ^number cos + {:doc "Returns the cosine of an angle. + If a is ##NaN, ##-Inf, ##Inf => ##NaN + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cos" + :added "1.11.10"} + [a] (Math/cos a)) + +(defn ^number tan + {:doc "Returns the tangent of an angle. + If a is ##NaN, ##-Inf, ##Inf => ##NaN + If a is zero => zero with the same sign as a + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tan" + :added "1.11.10"} + [a] (Math/tan a)) + +(defn ^number asin + {:doc "Returns the arc sine of an angle, in the range -pi/2 to pi/2. + If a is ##NaN or |a|>1 => ##NaN + If a is zero => zero with the same sign as a + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/asin" + :added "1.11.10"} + [a] (Math/asin a)) + +(defn ^number acos + {:doc "Returns the arc cosine of a, in the range 0.0 to pi. + If a is ##NaN or |a|>1 => ##NaN + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/acos" + :added "1.11.10"} + [a] (Math/acos a)) + +(defn ^number atan + {:doc "Returns the arc tangent of a, in the range of -pi/2 to pi/2. + If a is ##NaN => ##NaN + If a is zero => zero with the same sign as a + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan" + :added "1.11.10"} + [a] (Math/atan a)) + +(defn ^number to-radians + {:doc "Converts an angle in degrees to an approximate equivalent angle in radians. + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#toRadians-double-" + :added "1.11.10"} + [deg] + (* deg DEGREES-TO-RADIANS)) + +(defn ^number to-degrees + {:doc "Converts an angle in radians to an approximate equivalent angle in degrees. + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#toDegrees-double-" + :added "1.11.10"} + [r] + (* r RADIANS-TO-DEGREES)) + +(defn ^number exp + {:doc "Returns Euler's number e raised to the power of a. + If a is ##NaN => ##NaN + If a is ##Inf => ##Inf + If a is ##-Inf => +0.0 + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/exp" + :added "1.11.10"} + [a] (Math/exp a)) + +(defn ^number log + {:doc "Returns the natural logarithm (base e) of a. + If a is ##NaN or negative => ##NaN + If a is ##Inf => ##Inf + If a is zero => ##-Inf + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log" + :added "1.11.10"} + [a] (Math/log a)) + +(defn ^number log10 + {:doc "Returns the logarithm (base 10) of a. + If a is ##NaN or negative => ##NaN + If a is ##Inf => ##Inf + If a is zero => ##-Inf + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log10" + :added "1.11.10"} + [a] (Math/log10 a)) + +(defn ^number sqrt + {:doc "Returns the positive square root of a. + If a is ##NaN or negative => ##NaN + If a is ##Inf => ##Inf + If a is zero => a + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sqrt" + :added "1.11.10"} + [a] (Math/sqrt a)) + +(defn ^number cbrt + {:doc "Returns the cube root of a. + If a is ##NaN => ##NaN + If a is ##Inf or ##-Inf => a + If a is zero => zero with sign matching a + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cbrt" + :added "1.11.10"} + [a] (Math/cbrt a)) + +(defn ^number fabs + {:doc "Internal function to convert doubles to absolute values. + This duplicates the C implementations in Java, in case there is are corner-case differences." + :private true + :added "1.11.10"} + [x] + ;; create a buffer large enough for a double + (let [a (js/ArrayBuffer. 8) + ;; represent the buffer as a double array + d (js/Float64Array. a) + ;; represent the buffer as 32 bit ints + i (js/Uint32Array. a) + hi (if little-endian? 1 0)] + ;; insert the double value into the buffer + (aset d 0 x) + ;; update the sign bit + (aset i hi (bit-and (aget i hi) INT32-NON-SIGN-BITS)) + ;; return the new double + (aget d 0))) + +(def ^{:private true} Zero + ;; a buffer that can hold a pair of 64 bit doubles + (let [a (js/ArrayBuffer. 16) + ;; represent the buffer as a 2 double array + d (js/Float64Array. a) + ;; represent the buffer as an array of bytes + b (js/Uint8Array. a)] + ;; initialize both doubles to 0.0 + (aset d 0 0.0) + (aset d 1 0.0) + ;; update the sign bit on the second double + (aset b (if little-endian? 15 8) -0x80) + ;; save the array of 2 doubles [0.0, -0.0] + d)) + +(def ^{:private true :const true} xpos 0) +(def ^{:private true :const true} ypos 1) +(def ^{:private true} HI-x (+ (* 2 xpos) HI)) +(def ^{:private true} LO-x (+ (* 2 xpos) LO)) +(def ^{:private true} HI-y (+ (* 2 ypos) HI)) +(def ^{:private true} LO-y (+ (* 2 ypos) LO)) + +(defn ^number ilogb + {:doc "internal function for ilogb(x)" + :private true} + [hx lx] + (if (< hx 0x00100000) ;; subnormal + (let [hx-zero? (zero? hx) + start-ix (if hx-zero? -1043 -1022) + start-i (if hx-zero? lx (bit-shift-left hx 11))] + (loop [ix start-ix i start-i] + (if-not (> i 0) + ix + (recur (dec ix) (bit-shift-left i 1))))) + (- (bit-shift-right hx 20) 1023))) + +(defn ^number setup-hl + {:doc "internal function to setup and align integer words" + :private true} + [i h l] + (if (>= i -1022) + [(bit-or 0x00100000 (bit-and 0x000fffff h)) l] + (let [n (- -1022 i)] + (if (<= n 31) + [(bit-or (bit-shift-left h n) (unsigned-bit-shift-right l (- 32 n))) (bit-shift-left l n)] + [(bit-shift-left l (- n 32)) 0])))) + +(defn ^number IEEE-fmod + {:doc "Return x mod y in exact arithmetic. Method: shift and subtract. + Reimplements __ieee754_fmod from the JDK. + Ported from: https://github.com/openjdk/jdk/blob/master/src/java.base/share/native/libfdlibm/e_fmod.c + bit-shift-left and bit-shift-right convert numbers to signed 32-bit + Fortunately the values that are shifted are expected to be 32 bit signed." + :private true} + [x y] + ;; return exception values + (if (or (zero? y) ^boolean (js/isNaN y) (not ^boolean (js/isFinite x))) + ##NaN + + ;; create a buffer large enough for 2 doubles + (let [a (js/ArrayBuffer. 16) + ;; represent the buffer as a double array + d (js/Float64Array. a) + ;; represent the buffer as 32 bit ints + i (js/Uint32Array. a) + ;; set the doubles to x and y + _ (aset d xpos x) + _ (aset d ypos y) + hx (aget i HI-x) + lx (aget i LO-x) + hy (aget i HI-y) + ly (aget i LO-y) + sx (bit-and hx INT32-NON-SIGN-BIT) ;; capture the sign of x + hx (bit-and hx INT32-NON-SIGN-BITS) ;; set x to |x| + hy (bit-and hy INT32-NON-SIGN-BITS) ;; set y to |y| + hx<=hy (<= hx hy)] + (cond + ;; additional exception values + (and hx<=hy (or (< hx hy) (< lx ly))) x ;; |x|<|y| return x + (and hx<=hy (== lx ly)) (aget Zero (unsigned-bit-shift-right sx 31)) ;; |x|=|y| return x*0 + + :default + ;; determine ix = ilogb(x), iy = ilogb(y) + (try + (let [ix (ilogb hx lx) + iy (ilogb hy ly) + ;; set up {hx,lx}, {hy,ly} and align y to x + [hx lx] (setup-hl ix hx lx) + [hy ly] (setup-hl iy hy ly) + ;; fix point fmod + [hx lx] (loop [n (- ix iy) hx hx lx lx] + (if (zero? n) + [hx lx] + (let [hz (if (u< lx ly) (- hx hy 1) (- hx hy)) + lz (- lx ly) + [hx lx] (if (< hz 0) + [(+ hx hx (unsigned-bit-shift-right lx 31)) (+ lx lx)] + (if (zero? (bit-or hz lz)) + (throw (ex-info "Signed zero" {:zero true})) + [(+ hz hz (unsigned-bit-shift-right lz 31)) (+ lz lz)]))] + (recur (dec n) (bit-and INT32-MASK hx) (bit-and INT32-MASK lx))))) + hz (if (u< lx ly) (- hx hy 1) (- hx hy)) + lz (- lx ly) + [hx lx] (if (>= hz 0) [hz lz] [hx lx]) + + _ (when (zero? (bit-or hx lx)) + (throw (ex-info "Signed zero" {:zero true}))) + ;; convert back to floating value and restore the sign + [hx lx iy] (loop [hx hx lx lx iy iy] + (if-not (< hx 0x00100000) + [hx lx iy] + (recur (+ hx hx (unsigned-bit-shift-right lx 31)) (+ lx lx) (dec iy))))] + ;; use these high and low ints to update the double and return it + (if (>= iy -1022) + (let [hx (bit-or (- hx 0x00100000) (bit-shift-left (+ iy 1023) 20))] + (aset i HI-x (bit-or hx sx)) + (aset i LO-x lx) + (aget d xpos)) + (let [n (- -1022 iy) + [hx lx] (cond + (<= n 20) [(bit-shift-right hx n) + (bit-or (unsigned-bit-shift-right lx n) (bit-shift-left hx (- 32 n)))] + (<= n 31) [sx + (bit-or (bit-shift-left hx (- 32 n)) (unsigned-bit-shift-right lx n))] + :default [sx (bit-shift-right hx (- n 32))])] + (aset i HI-x (bit-or hx sx)) + (aset i LO-x lx) + (* (aget d xpos) 1.0)))) + (catch :default _ (aget Zero (unsigned-bit-shift-right sx 31)))))))) + +(defn ^number IEEE-remainder + {:doc "Returns the remainder per IEEE 754 such that + remainder = dividend - divisor * n + where n is the integer closest to the exact value of dividend / divisor. + If two integers are equally close, then n is the even one. + If the remainder is zero, sign will match dividend. + If dividend or divisor is ##NaN, or dividend is ##Inf or ##-Inf, or divisor is zero => ##NaN + If dividend is finite and divisor is infinite => dividend + + Method: based on fmod return x-[x/p]chopped*p exactlp. + Ported from: https://github.com/openjdk/jdk/blob/master/src/java.base/share/native/libfdlibm/e_remainder.c + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#IEEEremainder-double-double-" + :added "1.11.10"} + [dividend divisor] + ;; check for exception values + (cond + (zero? divisor) ##NaN + ^boolean (js/isNaN divisor) ##NaN + ;; check if dividend is ##Inf ##-Inf or ##NaN + ^boolean (js/isNaN dividend) ##NaN + (not ^boolean (js/isFinite dividend)) ##NaN + ;; dividend is finish, check if divisor is infinite + (not ^boolean (js/isFinite divisor)) dividend + + :default + ;; create a buffer large enough for 2 doubles + (let [a (js/ArrayBuffer. 16) + ;; represent the buffer as a double array + d (js/Float64Array. a) + ;; represent the buffer as 32 bit ints + i (js/Uint32Array. a)] + (aset d 0 dividend) + (aset d 1 divisor) + ;; x gets the dividend high and low ints + (let [hx (aget i HI) + lx (aget i LO) + ;; p gets the divisor high and low ints + hp (aget i (+ HI 2)) + lp (aget i (+ LO 2)) + ;; sx is the sign bit + sx (bit-and hx INT32-NON-SIGN-BIT) + ;; strip the sign bit from hp and hx + hp (bit-and hp INT32-NON-SIGN-BITS) + hx (bit-and hx INT32-NON-SIGN-BITS) + + ;;make x < 2p + dividend (if (<= hp 0x7FDFFFFF) (IEEE-fmod dividend (+ divisor divisor)) dividend)] + (if (zero? (bit-or (- hx hp) (- lx lp))) + (* 0.0 dividend) + ;; convert dividend and divisor to absolute values. + (let [dividend (Math/abs dividend) + divisor (Math/abs divisor) + ;; reduce dividend within range of the divisor + dividend (if (< hp 0x00200000) + ;; smaller divisor compare 2*dividend to the divisor + (if (> (+ dividend dividend) divisor) + (let [dividend (- dividend divisor)] ;; reduce the dividend + (if (>= (+ dividend dividend) divisor) ;; 2*dividend still larger + (- dividend divisor) ;; reduce again + dividend)) + dividend) + ;; compare dividend to half the divisor + (let [divisor-half (* 0.5 divisor)] + (if (> dividend divisor-half) + (let [dividend (- dividend divisor)] ;; reduce the dividend + (if (>= dividend divisor-half) ;; still larger than half divisor + (- dividend divisor) ;; reduce again + dividend)) + dividend)))] + ;; update the buffer with the new dividend value + (aset d 0 dividend) + ;; calculate a new hi int for the dividend using the saved sign bit + (let [hx (bit-xor (aget i HI) sx)] + ;; set the dividend with this new sign bit + (aset i HI hx) + ;; retrieve the updated dividend + (aget d 0)))))))) + +(defn ^number ceil + {:doc "Returns the smallest double greater than or equal to a, and equal to a + mathematical integer. + If a is ##NaN or ##Inf or ##-Inf or already equal to an integer => a + Note that if a is `nil` then an exception will be thrown. This matches Clojure, rather than js/Math.ceil + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/ceil" + :added "1.11.10"} + [a] + (if (some? a) + (Math/ceil a) + (throw (ex-info "Unexpected Null passed to ceil" {:fn "ceil"})))) + +(defn ^number floor + {:doc "Returns the largest double less than or equal to a, and equal to a + mathematical integer. + If a is ##NaN or ##Inf or ##-Inf or already equal to an integer => a + If a is less than zero but greater than -1.0 => -0.0 + Note that if a is `nil` then an exception will be thrown. This matches Clojure, rather than js/Math.floor + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor" + :added "1.11.10"} + [a] + (if (some? a) + (Math/floor a) + (throw (ex-info "Unexpected Null passed to floor" {:fn "floor"})))) + +(defn ^number copy-sign + {:doc "Returns a double with the magnitude of the first argument and the sign of + the second. + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#copySign-double-double-" + :added "1.11.10"} + [magnitude sign] + ;; create a buffer large enough for 2 doubles + (let [a (js/ArrayBuffer. 16) + ;; represent the buffer as a double array + d (js/Float64Array. a) + ;; represent the buffer as bytes + b (js/Uint8Array. a) + ;; find the offset of the byte that holds the sign bit + sbyte (if little-endian? 7 0)] + ;; the first double holds the magnitude, the second holds the sign value + (aset d 0 magnitude) + (aset d 1 sign) + ;; read the sign bit from the sign value + (let [sign-sbyte (bit-and 0x80 (aget b (+ 8 sbyte))) + ;; read all the bits that aren't the sign bit in the same byte of the magnitude + mag-sbyte (bit-and 0x7F (aget b sbyte))] + ;; combine the sign bit from the sign value and the non-sign-bits from the magnitude value + ;; write it back into the byte in the magnitude + (aset b sbyte (bit-or sign-sbyte mag-sbyte)) + ;; retrieve the full magnitude value with the updated byte + (aget d 0)))) + +(defn ^number rint + {:doc "Returns the double closest to a and equal to a mathematical integer. + If two values are equally close, return the even one. + If a is ##NaN or ##Inf or ##-Inf or zero => a + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#rint-double-" + :added "1.11.10"} + [a] + (let [sign (copy-sign 1.0, a) + a (Math/abs a) + a (if (< a TWO-TO-THE-52) + (- (+ TWO-TO-THE-52 a) TWO-TO-THE-52) a)] + (* sign a))) + +(defn ^number atan2 + {:doc "Returns the angle theta from the conversion of rectangular coordinates (x, y) to polar coordinates (r, theta). + Computes the phase theta by computing an arc tangent of y/x in the range of -pi to pi. + For more details on special cases, see: + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan" + :added "1.11.10"} + [y x] (Math/atan2 y x)) + +(defn ^number pow + {:doc "Returns the value of a raised to the power of b. + For more details on special cases, see: + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow" + :added "1.11.10"} + [a b] (Math/pow a b)) + +(defn ^number round + {:doc "Returns the closest long to a. If equally close to two values, return the one + closer to ##Inf. + If a is ##NaN => 0 + If a is ##-Inf => js/Number.MIN_SAFE_INTEGER + If a is ##Inf => js/Number.MAX_SAFE_INTEGER + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round" + :added "1.11.10"} + [a] + (cond + ^boolean (js/isNaN a) 0 + ^boolean (js/isFinite a) (Math/round a) + (== ##Inf a) js/Number.MAX_SAFE_INTEGER + :default js/Number.MIN_SAFE_INTEGER)) + +(defn ^number random + {:doc "Returns a positive double between 0.0 and 1.0, chosen pseudorandomly with + approximately random distribution. Not cryptographically secure. The seed is chosen internally + and cannot be selected. + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random" + :added "1.11.10"} + [] (Math/random)) + +(defn ^number add-exact + {:doc "Returns the sum of x and y, throws an exception on overflow. " + :added "1.11.10"} + [x y] + (let [r (clojure.core/+ x y)] + (if (or (> r js/Number.MAX_SAFE_INTEGER) (< r js/Number.MIN_SAFE_INTEGER)) + (throw (ex-info "Integer overflow" {:fn "add-exact"})) + r))) + +(defn ^number subtract-exact + {:doc "Returns the difference of x and y, throws ArithmeticException on overflow. " + :added "1.11.10"} + [x y] + (let [r (- x y)] + (if (or (> r js/Number.MAX_SAFE_INTEGER) (< r js/Number.MIN_SAFE_INTEGER)) + (throw (ex-info "Integer overflow" {:fn "subtract-exact"})) + r))) + +(defn ^number multiply-exact + {:doc "Returns the product of x and y, throws ArithmeticException on overflow. " + :added "1.11.10"} + [x y] + (let [r (* x y)] + (if (or (> r js/Number.MAX_SAFE_INTEGER) (< r js/Number.MIN_SAFE_INTEGER)) + (throw (ex-info "Integer overflow" {:fn "multiply-exact"})) + r))) + +(defn ^number increment-exact + {:doc "Returns a incremented by 1, throws ArithmeticException on overflow." + :added "1.11.10"} + [a] + (if (or (>= a js/Number.MAX_SAFE_INTEGER) (< a js/Number.MIN_SAFE_INTEGER)) + (throw (ex-info "Integer overflow" {:fn "increment-exact"})) + (inc a))) + +(defn ^number decrement-exact + {:doc "Returns a decremented by 1, throws ArithmeticException on overflow. " + :added "1.11.10"} + [a] + (if (or (<= a js/Number.MIN_SAFE_INTEGER) (> a js/Number.MAX_SAFE_INTEGER)) + (throw (ex-info "Integer overflow" {:fn "decrement-exact"})) + (dec a))) + +(defn ^number negate-exact + {:doc "Returns the negation of a, throws ArithmeticException on overflow. " + :added "1.11.10"} + [a] + (if (or (> a js/Number.MAX_SAFE_INTEGER) (< a js/Number.MIN_SAFE_INTEGER)) + (throw (ex-info "Integer overflow" {:fn "negate-exact"})) + (- a))) + +(defn- xor + [^boolean a ^boolean b] + (or (and a (not b)) (and (not a) b))) + +(defn ^number floor-div + {:doc "Integer division that rounds to negative infinity (as opposed to zero). + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#floorDiv-long-long-" + :added "1.11.10"} + [x y] + (if-not (and ^boolean (js/Number.isSafeInteger x) ^boolean (js/Number.isSafeInteger y)) + (throw (ex-info "floor-div called with non-safe-integer arguments" + {:x-int? (js/Number.isSafeInteger x) :y-int? (js/Number.isSafeInteger y)})) + (let [r (long (/ x y))] + (if (and (xor (< x 0) (< y 0)) (not (== (* r y) x))) + (dec r) + r)))) + +(defn ^number floor-mod + {:doc "Integer modulus x - (floorDiv(x, y) * y). Sign matches y and is in the + range -|y| < r < |y|. + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#floorMod-long-long-" + :added "1.11.10"} + [x y] + (if-not (and ^boolean (js/Number.isSafeInteger x) ^boolean (js/Number.isSafeInteger y)) + (throw (ex-info "floor-mod called with non-safe-integer arguments" + {:x-int? (js/Number.isSafeInteger x) :y-int? (js/Number.isSafeInteger y)})) + ;; this avoids using floor-div to keep within the safe integer range + (let [r (long (/ x y))] + (if (and (xor (< x 0) (< y 0)) (not (== (* r y) x))) + (- x (* y r) (- y)) + (- x (* y r)))))) + +(defn ^number get-exponent + {:doc "Returns the exponent of d. + If d is ##NaN, ##Inf, ##-Inf => max_Float64_exponent + 1 + If d is zero or subnormal => min_Float64_exponent - 1 + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#getExponent-double-" + :added "1.11.10"} + [d] + (cond + (or ^boolean (js/isNaN d) (not ^boolean (js/isFinite d))) (inc EXP-MAX) + (zero? d) (dec EXP-MIN) + :default (let [a (js/ArrayBuffer. 8) + f (js/Float64Array. a) + i (js/Uint32Array. a) + hi (if little-endian? 1 0)] + (aset f 0 d) + (- (bit-shift-right (bit-and (aget i hi) EXP-BITMASK32) (dec SIGNIFICAND-WIDTH32)) EXP-BIAS)))) + +(defn ^number hi-lo->double + {:doc "Converts a pair of 32 bit integers into an IEEE-754 64 bit floating point number. + h is the high 32 bits, l is the low 32 bits." + :private true} + [h l] + (let [a (js/ArrayBuffer. 8) + f (js/Float64Array. a) + i (js/Uint32Array. a)] + (aset i LO l) + (aset i HI h) + (aget f 0))) + +(defn ^number power-of-two + {:doc "returns a floating point power of two in the normal range" + :private true} + [n] + (assert (and (>= n EXP-MIN) (<= n EXP-MAX))) + (hi-lo->double + (bit-and (bit-shift-left (+ n EXP-BIAS) (dec SIGNIFICAND-WIDTH32)) EXP-BITMASK32) 0)) + +(defn ^number ulp + {:doc "Returns the size of an ulp (unit in last place) for d. + If d is ##NaN => ##NaN + If d is ##Inf or ##-Inf => ##Inf + If d is zero => Number/MIN_VALUE + If d is +/- Number/MAX_VALUE => 2^971 + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#ulp-double-" + :added "1.11.10"} + [d] + (cond + ^boolean (js/isNaN d) d + ^boolean (js/isFinite d) + (let [e (get-exponent d)] + (case e + 1024 (Math/abs d) ;; EXP-MAX + 1 + -1023 js/Number.MIN_VALUE ;; EXP-MIN - 1 + (let [e (- e (+ 31 SIGNIFICAND-WIDTH32))] ;; SIGNIFICAND_WIDTH64 -1 + (if (>= e EXP-MIN) + (power-of-two e) + (let [shift (- e (- EXP-MIN 31 SIGNIFICAND-WIDTH32))] + (if (< shift 32) + (hi-lo->double 0 (bit-shift-left 1 shift)) + (hi-lo->double (bit-shift-left 1 (- shift 32)) 0))))))) + :default ##Inf)) + +(defn ^number signum + {:doc "Returns the signum function of d - zero for zero, 1.0 if >0, -1.0 if <0. + If d is ##NaN => ##NaN + If d is ##Inf or ##-Inf => sign of d + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#signum-double-" + :added "1.11.10"} + [d] + (if (or (zero? d) ^boolean (js/isNaN d)) + d + (copy-sign 1.0 d))) + +(defn ^number sinh + {:doc "Returns the hyperbolic sine of x, (e^x - e^-x)/2. + If x is ##NaN => ##NaN + If x is ##Inf or ##-Inf or zero => x + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sinh" + :added "1.11.10"} + [x] (Math/sinh x)) + +(defn ^number cosh + {:doc "Returns the hyperbolic cosine of x, (e^x + e^-x)/2. + If x is ##NaN => ##NaN + If x is ##Inf or ##-Inf => ##Inf + If x is zero => 1.0 + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/cosh" + :added "1.11.10"} + [x] (Math/cosh x)) + +(defn ^number tanh + {:doc "Returns the hyperbolic tangent of x, sinh(x)/cosh(x). + If x is ##NaN => ##NaN + If x is zero => zero, with same sign + If x is ##Inf => +1.0 + If x is ##-Inf => -1.0 + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/tanh" + :added "1.11.10"} + [x] (Math/tanh x)) + +(defn ^number hypot + {:doc "Returns sqrt(x^2 + y^2) without intermediate underflow or overflow. + If x or y is ##Inf or ##-Inf => ##Inf + If x or y is ##NaN and neither is ##Inf or ##-Inf => ##NaN + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot" + :added "1.11.10"} + [x y] (Math/hypot x y)) + +(defn ^number expm1 + {:doc "Returns e^x - 1. Near 0, expm1(x)+1 is more accurate to e^x than exp(x). + If x is ##NaN => ##NaN + If x is ##Inf => #Inf + If x is ##-Inf => -1.0 + If x is zero => x + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/expm1" + :added "1.11.10"} + [x] (Math/expm1 x)) + +(defn ^number log1p + {:doc "Returns ln(1+x). For small values of x, log1p(x) is more accurate than + log(1.0+x). + If x is ##NaN or ##-Inf or < -1 => ##NaN + If x is -1 => ##-Inf + If x is ##Inf => ##Inf + See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/log1p" + :added "1.11.10"} + [x] (Math/log1p x)) + +(defn ^number add64 + {:doc "Takes the high and low words for 2 different 64 bit integers, and adds them. + This handles overflow from the low-order words into the high order words." + :private true} + [hx lx hy ly] + (let [sx (unsigned-bit-shift-right (bit-and lx INT32-NON-SIGN-BIT) 31) + sy (unsigned-bit-shift-right (bit-and ly INT32-NON-SIGN-BIT) 31) + lr (+ (bit-and INT32-NON-SIGN-BITS lx) (bit-and INT32-NON-SIGN-BITS ly)) + c31 (unsigned-bit-shift-right (bit-and lr INT32-NON-SIGN-BIT) 31) + b31 (+ sx sy c31) + lr (bit-or (bit-and lr INT32-NON-SIGN-BITS) (bit-shift-left b31 31)) + c32 (bit-shift-right b31 1) + hr (bit-and INT32-MASK (+ hx hy c32))] + [hr lr])) + +(defn ^number next-after + {:doc "Returns the adjacent floating point number to start in the direction of + the second argument. If the arguments are equal, the second is returned. + If either arg is #NaN => #NaN + If both arguments are signed zeros => direction + If start is +-Number/MIN_VALUE and direction would cause a smaller magnitude + => zero with sign matching start + If start is ##Inf or ##-Inf and direction would cause a smaller magnitude + => Number/MAX_VALUE with same sign as start + If start is equal to +=Number/MAX_VALUE and direction would cause a larger magnitude + => ##Inf or ##-Inf with sign matching start + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#nextAfter-double-double-" + :added "1.11.10"} + [start direction] + ; Branch to descending case first as it is more costly than ascending + ; case due to start != 0.0f conditional. + (let [a (js/ArrayBuffer. 8) + f (js/Float64Array. a) + i (js/Uint32Array. a)] + (cond + (> start direction) (if-not (zero? start) + (let [_ (aset f 0 start) + ht (aget i HI) + lt (aget i LO) + ;; ht< != 0 since start != 0.0 + ;; So long as the top bit is not set, then whole number is > 0 + [hr lr] (if (zero? (bit-and ht INT32-NON-SIGN-BIT)) + (add64 ht lt 0xFFFFFFFF 0xFFFFFFFF) + (add64 ht lt 0 1))] + (aset i HI hr) + (aset i LO lr) + (aget f 0)) + ;; start == 0.0 && direction < 0.0 + (- js/Number.MIN_VALUE)) + ;; Add +0.0 to get rid of a -0.0 (+0.0 + -0.0 => +0.0) + ;; then bitwise convert start to integer + (< start direction) (let [_ (aset f 0 (+ start 0.0)) + ht (aget i HI) + lt (aget i LO) + [hr lr] (if (zero? (bit-and ht INT32-NON-SIGN-BIT)) + (add64 ht lt 0 1) + (add64 ht lt 0xFFFFFFFF 0xFFFFFFFF))] + (aset i HI hr) + (aset i LO lr) + (aget f 0)) + (== start direction) direction + :default (+ start direction)))) ;; isNaN(start) || isNaN(direction) + +(defn ^number next-up + {:doc "Returns the adjacent double of d in the direction of ##Inf. + If d is ##NaN => ##NaN + If d is ##Inf => ##Inf + If d is zero => Number/MIN_VALUE + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#nextUp-double-" + :added "1.11.10"} + [d] + ;; Use a single conditional and handle the likely cases first + (if (< d js/Number.POSITIVE_INFINITY) + (let [a (js/ArrayBuffer. 8) + f (js/Float64Array. a) + i (js/Uint32Array. a) + ;; Add +0.0 to get rid of a -0.0 (+0.0 + -0.0 => +0.0) + _ (aset f 0 (+ d 0.0)) + ht (aget i HI) + lt (aget i LO) + [hr lr] (if (zero? (bit-and ht INT32-NON-SIGN-BIT)) + (add64 ht lt 0 1) + (add64 ht lt 0xFFFFFFFF 0xFFFFFFFF))] + (aset i HI hr) + (aset i LO lr) + (aget f 0)) + ;; d is NaN or +Infinity + d)) + +(defn ^number next-down + {:doc "Returns the adjacent double of d in the direction of ##-Inf. + If d is ##NaN => ##NaN + If d is ##Inf => Number/MAX_VALUE + If d is zero => -Number/MIN_VALUE + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#nextDown-double-" + :added "1.11.10"} + [d] + (cond + (or ^boolean (js/isNaN d) (== ##-Inf d)) d + (zero? d) (- js/Number.MIN_VALUE) + :default + (let [a (js/ArrayBuffer. 8) + f (js/Float64Array. a) + i (js/Uint32Array. a) + _ (aset f 0 d) + ht (aget i HI) + lt (aget i LO) + [hr lr] (if (> d 0) + (add64 ht lt 0xFFFFFFFF 0xFFFFFFFF) + (add64 ht lt 0 1))] + (aset i HI hr) + (aset i LO lr) + (aget f 0)))) + +(def ^:private MAX_SCALE (+ EXP-MAX (- EXP-MIN) SIGNIFICAND-WIDTH32 32 1)) + +(def ^:private two-to-the-double-scale-up (power-of-two 512)) + +(def ^:private two-to-the-double-scale-down (power-of-two -512)) + +(defn ^number scalb + {:doc "Returns d * 2^scaleFactor, scaling by a factor of 2. If the exponent + is between min_Float64_exponent and max_Float64_exponent. + scaleFactor is an integer + If d is ##NaN => ##NaN + If d is ##Inf or ##-Inf => ##Inf or ##-Inf respectively + If d is zero => zero of same sign as d + See: https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#nextDown-double-" + :added "1.11.10"} + [d scaleFactor] + (let [[scale-factor + scale-increment + exp-delta] (if (< scaleFactor 0) + [(Math/max scaleFactor (- MAX_SCALE)) -512 two-to-the-double-scale-down] + [(Math/min scaleFactor MAX_SCALE) 512 two-to-the-double-scale-up]) + ;; Calculate (scaleFactor % +/-512), 512 = 2^9 + ;; technique from "Hacker's Delight" section 10-2 + t (unsigned-bit-shift-right (bit-shift-right scale-factor 8) 23) + exp-adjust (- (bit-and (+ scale-factor t) 511) t)] + (loop [d (* d (power-of-two exp-adjust)) scale-factor (- scale-factor exp-adjust)] + (if (zero? scale-factor) + d + (recur (* d exp-delta) (- scale-factor scale-increment)))))) diff --git a/src/main/cljs/cljs/module_deps.js b/src/main/cljs/cljs/module_deps.js new file mode 100644 index 0000000000..6b38cd083d --- /dev/null +++ b/src/main/cljs/cljs/module_deps.js @@ -0,0 +1,250 @@ +// NOTE: This code should only employ single quotes for strings. +// If double quotes are used, then when the contents of this file +// are passed to node via --eval on Windows, the double quotes +// will be elided, leading to syntactically incorrect JavaScript. + +let fs = require('fs'); +let path = require('path'); +let mdeps = require('@cljs-oss/module-deps'); +let nodeResolve = require('resolve'); +let babylon = require('babylon'); +let traverse = require('babel-traverse').default; +let enhancedResolve = require('enhanced-resolve'); + +let target = 'CLJS_TARGET'; +let filename = fs.realpathSync(path.resolve(__dirname, 'JS_FILE')); +let mainFields = MAIN_ENTRIES; +let aliasFields = target === 'nodejs' ? [] : ['browser']; + +// https://github.com/egoist/konan +let getDeps = function (src, {dynamicImport = true, parse = {sourceType: 'module', plugins: '*'}} = {}) { + const modules = {strings: [], expressions: []}; + + let ast; + + if (typeof src === 'string') { + const moduleRe = /\b(require|import|export)\b/; + + if (!moduleRe.test(src)) { + return modules; + } + + ast = babylon.parse(src, parse); + } else { + ast = src; + } + + traverse(ast, { + enter(path) { + if (path.node.type === 'CallExpression') { + const callee = path.get('callee'); + const isDynamicImport = dynamicImport && callee.isImport(); + if (callee.isIdentifier({name: 'require'}) || isDynamicImport) { + const arg = path.node.arguments[0]; + if (arg.type === 'StringLiteral') { + modules.strings.push(arg.value); + } else { + modules.expressions.push(src.slice(arg.start, arg.end)); + } + } + } else if (path.node.type === 'ImportDeclaration' || + path.node.type === 'ExportNamedDeclaration' || + path.node.type === 'ExportAllDeclaration') { + const source = path.node.source; + + if (source != null) { + modules.strings.push(path.node.source.value); + } + } + } + }); + + return modules; +}; + +let resolver = enhancedResolve.create({ + fileSystem: new enhancedResolve.CachedInputFileSystem( + new enhancedResolve.NodeJsInputFileSystem(), + 4000 + ), + extensions: ['.js', '.json'], + mainFields: mainFields, + aliasFields: target === 'nodejs' ? [] : ['browser'], + moduleExtensions: ['.js', '.json'], + symlinks: false +}); + +let md = mdeps({ + resolve: function (id, parentOpts, cb) { + // set the basedir properly so we don't try to resolve requires in the Closure + // Compiler processed `node_modules` folder. + parentOpts.basedir = + parentOpts.filename === filename + ? path.resolve(__dirname) + : path.dirname(parentOpts.filename); + + resolver(parentOpts.basedir, id, cb); + }, + filter: function (id) { + return !(target === 'nodejs' && nodeResolve.isCore(id)) && + !id.startsWith('goog:'); + }, + detect: function (src) { + let deps = getDeps(src); + + return deps.strings; + } +}); + +function getPackageJsonMainEntry(pkgJson) { + for (let i = 0; i < mainFields.length; i++) { + let entry = mainFields[i]; + const entryVal = pkgJson[entry]; + + if (entryVal != null) { + if (typeof entryVal === 'string') { + return entryVal; + } else if (typeof entryVal === 'object') { + for (let j = i; j < mainFields.length; j++) { + let otherEntry = mainFields[j]; + const otherEntryVal = pkgJson[entry]; + + if (entryVal[otherEntryVal] != null) { + return entryVal[otherEntryVal] + } + } + } + } + } + return null; +} + +function depProvides(provides, file) { + const result = provides != null ? provides.slice(0) : []; + + let providedModule = file + .substring(file.lastIndexOf('node_modules')) + .replace(/\\/g, '/') + .replace('node_modules/', ''); + + result.push( + providedModule, + providedModule.replace(/\.js(on)?$/, '') + ); + + let indexReplaced = providedModule.replace(/\/index\.js(on)?$/, ''); + + if ( + /\/index\.js(on)?$/.test(providedModule) && + result.indexOf(indexReplaced) === -1 + ) { + result.push(indexReplaced); + } + + return result; +} + +let pkgJsons = []; +let deps_files = {}; + +md.on('package', function (pkg) { + // we don't want to include the package.json for users' projects + if (/node_modules/.test(pkg.__dirname)) { + let pkgJson = { + basedir: pkg.__dirname, + file: path.join(pkg.__dirname, 'package.json'), + }; + + if (pkg.name != null) { + pkgJson.provides = [pkg.name]; + } + + let pkgJsonMainEntry = getPackageJsonMainEntry(pkg); + if (pkgJsonMainEntry != null) { + pkgJson.mainEntry = path.join(pkg.__dirname, pkgJsonMainEntry); + } + + // we'll need these later + for (let i = 0; i < aliasFields.length; i++) { + const field = aliasFields[i]; + if (pkg[field] != null) { + pkgJson[field] = pkg[field]; + } + } + + pkgJsons.push(pkgJson); + } +}); + +md.on('file', function (file) { + deps_files[file] = {file: file}; +}); + +md.on('end', function () { + for (let i = 0; i < pkgJsons.length; i++) { + let pkgJson = pkgJsons[i]; + const candidates = /\.js(on)?$/.test(pkgJson.mainEntry) + ? [pkgJson.mainEntry] + : [pkgJson.mainEntry, pkgJson.mainEntry + '.js', pkgJson.mainEntry + 'FILE_SEPARATOR' + 'index.js', pkgJson.mainEntry + '.json']; + + for (let j = 0; j < candidates.length; j++) { + const candidate = candidates[j]; + + if (deps_files[candidate] != null && pkgJson.provides != null) { + deps_files[candidate].provides = pkgJson.provides; + } + } + + for (let j = 0; j < aliasFields.length; j++) { + const field = aliasFields[j]; + const fieldValue = pkgJson[field]; + + if (fieldValue != null && typeof fieldValue === 'object') { + for (let key in fieldValue) { + // TODO: False value means that the module should be ignored + if (typeof fieldValue[key] === 'string') { + const replacement = path.resolve(pkgJson.basedir, fieldValue[key]); + + if (deps_files[replacement] != null) { + const file = path.resolve(pkgJson.basedir, key); + deps_files[replacement].provides = depProvides(deps_files[replacement].provides, file); + + if (file === pkgJson.mainEntry) { + Array.prototype.push.apply(deps_files[replacement].provides, pkgJson.provides); + } + } + } + } + } + } + + + deps_files[pkgJson.file] = {file: pkgJson.file}; + } + + let values = []; + for (let key in deps_files) { + let dep = deps_files[key]; + + // add provides to files that are not `package.json`s + if ( + !/node_modules[/\\](@[^/\\]+?[/\\])?[^/\\]+?[/\\]package\.json$/.test( + dep.file + ) + ) { + if (dep.file.indexOf('node_modules') !== -1) { + dep.provides = depProvides(dep.provides, dep.file); + } + } + + values.push(dep); + } + + process.stdout.write(JSON.stringify(values)); +}); + +md.end({ + file: filename +}); + +md.resume(); diff --git a/src/main/cljs/cljs/nodejs.cljs b/src/main/cljs/cljs/nodejs.cljs new file mode 100644 index 0000000000..ca587888d6 --- /dev/null +++ b/src/main/cljs/cljs/nodejs.cljs @@ -0,0 +1,27 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +; Projects compiled with :target :nodejs can 'require' this namespace +; to get the nodejs globals loaded into cljs.nodejs and get +; ClojureScript's 'print' set up correctly. +(ns cljs.nodejs + (:refer-clojure :exclude [require])) + +; Define namespaced references to Node's externed globals: +(def require (js* "require")) +(def process (js* "process")) + +(defn enable-util-print! [] + (set! *print-newline* false) + (set-print-fn! + (fn [& args] + (.apply (.-log js/console) js/console (into-array args)))) + (set-print-err-fn! + (fn [& args] + (.apply (.-error js/console) js/console (into-array args)))) + nil) diff --git a/src/main/cljs/cljs/nodejs_externs.js b/src/main/cljs/cljs/nodejs_externs.js new file mode 100644 index 0000000000..2dda100551 --- /dev/null +++ b/src/main/cljs/cljs/nodejs_externs.js @@ -0,0 +1,3 @@ +var global = {}; +function require(){}; +function process(){}; diff --git a/src/main/cljs/cljs/nodejscli.cljs b/src/main/cljs/cljs/nodejscli.cljs new file mode 100644 index 0000000000..256a4fa97f --- /dev/null +++ b/src/main/cljs/cljs/nodejscli.cljs @@ -0,0 +1,22 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +; Projects compiled with :target :nodejs have this file appended. Its +; job is to make sure cljs.nodejs is loaded and that the *main-cli-fn* +; is called with the script's command-line arguments. +(ns cljs.nodejscli + (:require [cljs.nodejs :as nodejs] + [goog.object :as gobj])) + +;; need to set goog.global if COMPILED +(when ^boolean js/COMPILED + (set! goog/global js/global)) + +;; Call the user's main function +(when (fn? cljs.core/*main-cli-fn*) + (apply cljs.core/*main-cli-fn* (drop 2 (gobj/get js/process "argv")))) diff --git a/src/main/cljs/cljs/pprint.cljc b/src/main/cljs/cljs/pprint.cljc new file mode 100644 index 0000000000..6793c38c27 --- /dev/null +++ b/src/main/cljs/cljs/pprint.cljc @@ -0,0 +1,167 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.pprint + (:refer-clojure :exclude [deftype #?(:cljs macroexpand)]) + (:require [clojure.walk :as walk] + #?(:cljs [cljs.analyzer :as ana]))) + + +;; required the following changes: +;; replace .ppflush with -ppflush to switch from Interface to Protocol + +(defmacro with-pretty-writer [base-writer & body] + `(let [base-writer# ~base-writer + new-writer# (not (pretty-writer? base-writer#))] + (cljs.core/binding [cljs.core/*out* (if new-writer# + (make-pretty-writer base-writer# *print-right-margin* *print-miser-width*) + base-writer#)] + ~@body + (-ppflush cljs.core/*out*)))) + + +(defmacro getf + "Get the value of the field a named by the argument (which should be a keyword)." + [sym] + `(~sym @@~'this)) + +;; change alter to swap! + +(defmacro setf + "Set the value of the field SYM to NEW-VAL" + [sym new-val] + `(swap! @~'this assoc ~sym ~new-val)) + +(defmacro deftype + [type-name & fields] + (let [name-str (name type-name) + fields (map (comp symbol name) fields)] + `(do + (defrecord ~type-name [~'type-tag ~@fields]) + (defn- ~(symbol (str "make-" name-str)) + ~(vec fields) + (~(symbol (str type-name ".")) ~(keyword name-str) ~@fields)) + (defn- ~(symbol (str name-str "?")) [x#] (= (:type-tag x#) ~(keyword name-str)))))) + +(defn- parse-lb-options [opts body] + (loop [body body + acc []] + (if (opts (first body)) + (recur (drop 2 body) (concat acc (take 2 body))) + [(apply hash-map acc) body]))) + +(defmacro pprint-logical-block + "Execute the body as a pretty printing logical block with output to *out* which + must be a pretty printing writer. When used from pprint or cl-format, this can be + assumed. + + This function is intended for use when writing custom dispatch functions. + + Before the body, the caller can optionally specify options: :prefix, :per-line-prefix + and :suffix." + [& args] + (let [[options body] (parse-lb-options #{:prefix :per-line-prefix :suffix} args)] + `(do (if (cljs.pprint/level-exceeded) + (~'-write cljs.core/*out* "#") + (do + (cljs.core/binding [cljs.pprint/*current-level* (inc cljs.pprint/*current-level*) + cljs.pprint/*current-length* 0] + (cljs.pprint/start-block cljs.core/*out* + ~(:prefix options) + ~(:per-line-prefix options) + ~(:suffix options)) + ~@body + (cljs.pprint/end-block cljs.core/*out*)))) + nil))) + +#?(:cljs + (defn macroexpand [env form] + (loop [form form + form' (ana/macroexpand-1 env form)] + (if-not (identical? form form') + (recur form' (ana/macroexpand-1 env form')) + form')))) + +(defn- pll-mod-body [env var-sym body] + (letfn [(inner [form] + (if (seq? form) + (let [form #?(:clj (macroexpand form) + :cljs (macroexpand env form))] + (condp = (first form) + 'loop* form + 'recur (concat `(recur (inc ~var-sym)) (rest form)) + (walk/walk inner identity form))) + form))] + (walk/walk inner identity body))) + +(defmacro print-length-loop + "A version of loop that iterates at most *print-length* times. This is designed + for use in pretty-printer dispatch functions." + [bindings & body] + (let [count-var (gensym "length-count") + mod-body (pll-mod-body &env count-var body)] + `(loop ~(apply vector count-var 0 bindings) + (if (or (not cljs.core/*print-length*) (< ~count-var cljs.core/*print-length*)) + (do ~@mod-body) + (~'-write cljs.core/*out* "..."))))) + +(defn- process-directive-table-element [[char params flags bracket-info & generator-fn]] + [char, + {:directive char, + :params `(array-map ~@params), + :flags flags, + :bracket-info bracket-info, + :generator-fn (concat '(fn [params offset]) generator-fn)}]) + +(defmacro ^{:private true} + defdirectives + [& directives] + `(def ^{:private true} + ~'directive-table (hash-map ~@(mapcat process-directive-table-element directives)))) + +(defmacro formatter + "Makes a function which can directly run format-in. The function is +fn [stream & args] ... and returns nil unless the stream is nil (meaning +output to a string) in which case it returns the resulting string. + +format-in can be either a control string or a previously compiled format." + [format-in] + `(let [format-in# ~format-in + my-c-c# cljs.pprint/cached-compile + my-e-f# cljs.pprint/execute-format + my-i-n# cljs.pprint/init-navigator + cf# (if (string? format-in#) (my-c-c# format-in#) format-in#)] + (fn [stream# & args#] + (let [navigator# (my-i-n# args#)] + (my-e-f# stream# cf# navigator#))))) + +(defmacro formatter-out + "Makes a function which can directly run format-in. The function is +fn [& args] ... and returns nil. This version of the formatter macro is +designed to be used with *out* set to an appropriate Writer. In particular, +this is meant to be used as part of a pretty printer dispatch method. + +format-in can be either a control string or a previously compiled format." + [format-in] + `(let [format-in# ~format-in + cf# (if (string? format-in#) (cljs.pprint/cached-compile format-in#) format-in#)] + (fn [& args#] + (let [navigator# (cljs.pprint/init-navigator args#)] + (cljs.pprint/execute-format cf# navigator#))))) + +(defmacro with-pprint-dispatch + "Execute body with the pretty print dispatch function bound to function." + [function & body] + `(cljs.core/binding [cljs.pprint/*print-pprint-dispatch* ~function] + ~@body)) + +(defmacro pp + "A convenience macro that pretty prints the last thing output. This is +exactly equivalent to (pprint *1)." + {:added "1.2"} + [] `(cljs.pprint/pprint *1)) diff --git a/src/main/cljs/cljs/pprint.cljs b/src/main/cljs/cljs/pprint.cljs new file mode 100644 index 0000000000..718ba6ec11 --- /dev/null +++ b/src/main/cljs/cljs/pprint.cljs @@ -0,0 +1,3325 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.pprint + (:refer-clojure :exclude [deftype print println pr prn float?]) + (:require-macros + [cljs.pprint :as m :refer [with-pretty-writer getf setf deftype + pprint-logical-block print-length-loop + defdirectives formatter-out]]) + (:require + [cljs.core :refer [IWriter IDeref]] + [clojure.string :as string] + [goog.string :as gstring]) + (:import [goog.string StringBuffer])) + +;;====================================================================== +;; override print fns to use *out* +;;====================================================================== + +(defn- print [& more] + (-write *out* (apply print-str more))) + +(defn- println [& more] + (apply print more) + (-write *out* \newline)) + +(defn- print-char [c] + (-write *out* (condp = c + \backspace "\\backspace" + \space "\\space" + \tab "\\tab" + \newline "\\newline" + \formfeed "\\formfeed" + \return "\\return" + \" "\\\"" + \\ "\\\\" + (str "\\" c)))) + +(defn- ^:dynamic pr [& more] + (-write *out* (apply pr-str more))) + +(defn- prn [& more] + (apply pr more) + (-write *out* \newline)) + +;;====================================================================== +;; cljs specific utils +;;====================================================================== + +(defn float? + "Returns true if n is an float." + [n] + (and (number? n) + (not ^boolean (js/isNaN n)) + (not (identical? n js/Infinity)) + (not (== (js/parseFloat n) (js/parseInt n 10))))) + +(defn char-code + "Convert char to int" + [c] + (cond + (number? c) c + (and (string? c) (== (.-length c) 1)) (.charCodeAt c 0) + :else (throw (js/Error. "Argument to char must be a character or number")))) + +;;====================================================================== +;; Utilities +;;====================================================================== + +(defn- map-passing-context [func initial-context lis] + (loop [context initial-context + lis lis + acc []] + (if (empty? lis) + [acc context] + (let [this (first lis) + remainder (next lis) + [result new-context] (apply func [this context])] + (recur new-context remainder (conj acc result)))))) + +(defn- consume [func initial-context] + (loop [context initial-context + acc []] + (let [[result new-context] (apply func [context])] + (if (not result) + [acc new-context] + (recur new-context (conj acc result)))))) + +(defn- consume-while [func initial-context] + (loop [context initial-context + acc []] + (let [[result continue new-context] (apply func [context])] + (if (not continue) + [acc context] + (recur new-context (conj acc result)))))) + +(defn- unzip-map [m] + "Take a map that has pairs in the value slots and produce a pair of maps, + the first having all the first elements of the pairs and the second all + the second elements of the pairs" + [(into {} (for [[k [v1 v2]] m] [k v1])) + (into {} (for [[k [v1 v2]] m] [k v2]))]) + +(defn- tuple-map [m v1] + "For all the values, v, in the map, replace them with [v v1]" + (into {} (for [[k v] m] [k [v v1]]))) + +(defn- rtrim [s c] + "Trim all instances of c from the end of sequence s" + (let [len (count s)] + (if (and (pos? len) (= (nth s (dec (count s))) c)) + (loop [n (dec len)] + (cond + (neg? n) "" + (not (= (nth s n) c)) (subs s 0 (inc n)) + true (recur (dec n)))) + s))) + +(defn- ltrim [s c] + "Trim all instances of c from the beginning of sequence s" + (let [len (count s)] + (if (and (pos? len) (= (nth s 0) c)) + (loop [n 0] + (if (or (= n len) (not (= (nth s n) c))) + (subs s n) + (recur (inc n)))) + s))) + +(defn- prefix-count [aseq val] + "Return the number of times that val occurs at the start of sequence aseq, +if val is a seq itself, count the number of times any element of val occurs at the +beginning of aseq" + (let [test (if (coll? val) (set val) #{val})] + (loop [pos 0] + (if (or (= pos (count aseq)) (not (test (nth aseq pos)))) + pos + (recur (inc pos)))))) + +;; Flush the pretty-print buffer without flushing the underlying stream +(defprotocol IPrettyFlush + (-ppflush [pp])) + +;;====================================================================== +;; column_writer.clj +;;====================================================================== + +(def ^:dynamic ^{:private true} *default-page-width* 72) + +(defn- get-field [this sym] + (sym @@this)) + +(defn- set-field [this sym new-val] + (swap! @this assoc sym new-val)) + +(defn- get-column [this] + (get-field this :cur)) + +(defn- get-line [this] + (get-field this :line)) + +(defn- get-max-column [this] + (get-field this :max)) + +(defn- set-max-column [this new-max] + (set-field this :max new-max) + nil) + +(defn- get-writer [this] + (get-field this :base)) + +;; Why is the c argument an integer? +(defn- c-write-char [this c] + (if (= c \newline) + (do + (set-field this :cur 0) + (set-field this :line (inc (get-field this :line)))) + (set-field this :cur (inc (get-field this :cur)))) + (-write (get-field this :base) c)) + +(defn- column-writer + ([writer] (column-writer writer *default-page-width*)) + ([writer max-columns] + (let [fields (atom {:max max-columns, :cur 0, :line 0 :base writer})] + (reify + + IDeref + (-deref [_] fields) + + IWriter + (-flush [_] + (-flush writer)) + (-write + ;;-write isn't multi-arity, so need different way to do this + #_([this ^chars cbuf ^Number off ^Number len] + (let [writer (get-field this :base)] + (-write writer cbuf off len))) + [this x] + (condp = (type x) + js/String + (let [s x + nl (.lastIndexOf s \newline)] + (if (neg? nl) + (set-field this :cur (+ (get-field this :cur) (count s))) + (do + (set-field this :cur (- (count s) nl 1)) + (set-field this :line (+ (get-field this :line) + (count (filter #(= % \newline) s)))))) + (-write (get-field this :base) s)) + js/Number + (c-write-char this x))))))) + +;;====================================================================== +;; pretty_writer.clj +;;====================================================================== + +;;====================================================================== +;; Forward declarations +;;====================================================================== + +(declare ^{:arglists '([this])} get-miser-width) + +;;====================================================================== +;; The data structures used by pretty-writer +;;====================================================================== + +(defrecord ^{:private true} logical-block + [parent section start-col indent + done-nl intra-block-nl + prefix per-line-prefix suffix + logical-block-callback]) + +(defn- ancestor? [parent child] + (loop [child (:parent child)] + (cond + (nil? child) false + (identical? parent child) true + :else (recur (:parent child))))) + +(defn- buffer-length [l] + (let [l (seq l)] + (if l + (- (:end-pos (last l)) (:start-pos (first l))) + 0))) + +;; A blob of characters (aka a string) +(deftype buffer-blob :data :trailing-white-space :start-pos :end-pos) + +;; A newline +(deftype nl-t :type :logical-block :start-pos :end-pos) + +(deftype start-block-t :logical-block :start-pos :end-pos) + +(deftype end-block-t :logical-block :start-pos :end-pos) + +(deftype indent-t :logical-block :relative-to :offset :start-pos :end-pos) + +(def ^:private pp-newline (fn [] "\n")) + +(declare emit-nl) + +(defmulti ^{:private true} write-token #(:type-tag %2)) + +(defmethod write-token :start-block-t [this token] + (when-let [cb (getf :logical-block-callback)] (cb :start)) + (let [lb (:logical-block token)] + (when-let [prefix (:prefix lb)] + (-write (getf :base) prefix)) + (let [col (get-column (getf :base))] + (reset! (:start-col lb) col) + (reset! (:indent lb) col)))) + +(defmethod write-token :end-block-t [this token] + (when-let [cb (getf :logical-block-callback)] (cb :end)) + (when-let [suffix (:suffix (:logical-block token))] + (-write (getf :base) suffix))) + +(defmethod write-token :indent-t [this token] + (let [lb (:logical-block token)] + (reset! (:indent lb) + (+ (:offset token) + (condp = (:relative-to token) + :block @(:start-col lb) + :current (get-column (getf :base))))))) + +(defmethod write-token :buffer-blob [this token] + (-write (getf :base) (:data token))) + +(defmethod write-token :nl-t [this token] + (if (or (= (:type token) :mandatory) + (and (not (= (:type token) :fill)) + @(:done-nl (:logical-block token)))) + (emit-nl this token) + (if-let [tws (getf :trailing-white-space)] + (-write (getf :base) tws))) + (setf :trailing-white-space nil)) + +(defn- write-tokens [this tokens force-trailing-whitespace] + (doseq [token tokens] + (if-not (= (:type-tag token) :nl-t) + (if-let [tws (getf :trailing-white-space)] + (-write (getf :base) tws))) + (write-token this token) + (setf :trailing-white-space (:trailing-white-space token)) + (let [tws (getf :trailing-white-space)] + (when (and force-trailing-whitespace tws) + (-write (getf :base) tws) + (setf :trailing-white-space nil))))) + +;;====================================================================== +;; emit-nl? method defs for each type of new line. This makes +;; the decision about whether to print this type of new line. +;;====================================================================== + +(defn- tokens-fit? [this tokens] + (let [maxcol (get-max-column (getf :base))] + (or + (nil? maxcol) + (< (+ (get-column (getf :base)) (buffer-length tokens)) maxcol)))) + +(defn- linear-nl? [this lb section] + (or @(:done-nl lb) + (not (tokens-fit? this section)))) + +(defn- miser-nl? [this lb section] + (let [miser-width (get-miser-width this) + maxcol (get-max-column (getf :base))] + (and miser-width maxcol + (>= @(:start-col lb) (- maxcol miser-width)) + (linear-nl? this lb section)))) + +(defmulti ^{:private true} emit-nl? (fn [t _ _ _] (:type t))) + +(defmethod emit-nl? :linear [newl this section _] + (let [lb (:logical-block newl)] + (linear-nl? this lb section))) + +(defmethod emit-nl? :miser [newl this section _] + (let [lb (:logical-block newl)] + (miser-nl? this lb section))) + +(defmethod emit-nl? :fill [newl this section subsection] + (let [lb (:logical-block newl)] + (or @(:intra-block-nl lb) + (not (tokens-fit? this subsection)) + (miser-nl? this lb section)))) + +(defmethod emit-nl? :mandatory [_ _ _ _] + true) + +;;====================================================================== +;; Various support functions +;;====================================================================== + +(defn- get-section [buffer] + (let [nl (first buffer) + lb (:logical-block nl) + section (seq (take-while #(not (and (nl-t? %) (ancestor? (:logical-block %) lb))) + (next buffer)))] + [section (seq (drop (inc (count section)) buffer))])) + +(defn- get-sub-section [buffer] + (let [nl (first buffer) + lb (:logical-block nl) + section (seq (take-while #(let [nl-lb (:logical-block %)] + (not (and (nl-t? %) (or (= nl-lb lb) (ancestor? nl-lb lb))))) + (next buffer)))] + section)) + +(defn- update-nl-state [lb] + (reset! (:intra-block-nl lb) true) + (reset! (:done-nl lb) true) + (loop [lb (:parent lb)] + (if lb + (do (reset! (:done-nl lb) true) + (reset! (:intra-block-nl lb) true) + (recur (:parent lb)))))) + +(defn- emit-nl [this nl] + (-write (getf :base) (pp-newline)) + (setf :trailing-white-space nil) + (let [lb (:logical-block nl) + prefix (:per-line-prefix lb)] + (if prefix + (-write (getf :base) prefix)) + (let [istr (apply str (repeat (- @(:indent lb) (count prefix)) \space))] + (-write (getf :base) istr)) + (update-nl-state lb))) + +(defn- split-at-newline [tokens] + (let [pre (seq (take-while #(not (nl-t? %)) tokens))] + [pre (seq (drop (count pre) tokens))])) + +;; write-token-string is called when the set of tokens in the buffer +;; is long than the available space on the line +(defn- write-token-string [this tokens] + (let [[a b] (split-at-newline tokens)] + (if a (write-tokens this a false)) + (if b + (let [[section remainder] (get-section b) + newl (first b)] + (let [do-nl (emit-nl? newl this section (get-sub-section b)) + result (if do-nl + (do + (emit-nl this newl) + (next b)) + b) + long-section (not (tokens-fit? this result)) + result (if long-section + (let [rem2 (write-token-string this section)] + (if (= rem2 section) + (do ; If that didn't produce any output, it has no nls + ; so we'll force it + (write-tokens this section false) + remainder) + (into [] (concat rem2 remainder)))) + result)] + result))))) + +(defn- write-line [this] + (loop [buffer (getf :buffer)] + (setf :buffer (into [] buffer)) + (if (not (tokens-fit? this buffer)) + (let [new-buffer (write-token-string this buffer)] + (if-not (identical? buffer new-buffer) + (recur new-buffer)))))) + +;; Add a buffer token to the buffer and see if it's time to start +;; writing +(defn- add-to-buffer [this token] + (setf :buffer (conj (getf :buffer) token)) + (if (not (tokens-fit? this (getf :buffer))) + (write-line this))) + +;; Write all the tokens that have been buffered +(defn- write-buffered-output [this] + (write-line this) + (if-let [buf (getf :buffer)] + (do + (write-tokens this buf true) + (setf :buffer [])))) + +(defn- write-white-space [this] + (when-let [tws (getf :trailing-white-space)] + (-write (getf :base) tws) + (setf :trailing-white-space nil))) + +;;; If there are newlines in the string, print the lines up until the last newline, +;;; making the appropriate adjustments. Return the remainder of the string +(defn- write-initial-lines + [^Writer this ^String s] + (let [lines (string/split s "\n" -1)] + (if (= (count lines) 1) + s + (let [^String prefix (:per-line-prefix (first (getf :logical-blocks))) + ^String l (first lines)] + (if (= :buffering (getf :mode)) + (let [oldpos (getf :pos) + newpos (+ oldpos (count l))] + (setf :pos newpos) + (add-to-buffer this (make-buffer-blob l nil oldpos newpos)) + (write-buffered-output this)) + (do + (write-white-space this) + (-write (getf :base) l))) + (-write (getf :base) \newline) + (doseq [^String l (next (butlast lines))] + (-write (getf :base) l) + (-write (getf :base) (pp-newline)) + (if prefix + (-write (getf :base) prefix))) + (setf :buffering :writing) + (last lines))))) + +(defn- p-write-char [this c] + (if (= (getf :mode) :writing) + (do + (write-white-space this) + (-write (getf :base) c)) + (if (= c \newline) + (write-initial-lines this \newline) + (let [oldpos (getf :pos) + newpos (inc oldpos)] + (setf :pos newpos) + (add-to-buffer this (make-buffer-blob (char c) nil oldpos newpos)))))) + +;;====================================================================== +;; Initialize the pretty-writer instance +;;====================================================================== + +(defn- pretty-writer [writer max-columns miser-width] + (let [lb (logical-block. nil nil (atom 0) (atom 0) (atom false) (atom false) + nil nil nil nil) + ; NOTE: may want to just `specify!` #js { ... fields ... } with the protocols + fields (atom {:pretty-writer true + :base (column-writer writer max-columns) + :logical-blocks lb + :sections nil + :mode :writing + :buffer [] + :buffer-block lb + :buffer-level 1 + :miser-width miser-width + :trailing-white-space nil + :pos 0})] + (reify + + IDeref + (-deref [_] fields) + + IWriter + (-write [this x] + (condp = (type x) + js/String + (let [s0 (write-initial-lines this x) + s (string/replace-first s0 #"\s+$" "") + white-space (subs s0 (count s)) + mode (getf :mode)] + (if (= mode :writing) + (do + (write-white-space this) + (-write (getf :base) s) + (setf :trailing-white-space white-space)) + (let [oldpos (getf :pos) + newpos (+ oldpos (count s0))] + (setf :pos newpos) + (add-to-buffer this (make-buffer-blob s white-space oldpos newpos))))) + js/Number + (p-write-char this x))) + (-flush [this] + (-ppflush this) + (-flush (getf :base))) + + IPrettyFlush + (-ppflush [this] + (if (= (getf :mode) :buffering) + (do + (write-tokens this (getf :buffer) true) + (setf :buffer [])) + (write-white-space this))) + + ))) + +;;====================================================================== +;; Methods for pretty-writer +;;====================================================================== + +(defn- start-block + [this prefix per-line-prefix suffix] + (let [lb (logical-block. (getf :logical-blocks) nil (atom 0) (atom 0) + (atom false) (atom false) + prefix per-line-prefix suffix nil)] + (setf :logical-blocks lb) + (if (= (getf :mode) :writing) + (do + (write-white-space this) + (when-let [cb (getf :logical-block-callback)] (cb :start)) + (if prefix + (-write (getf :base) prefix)) + (let [col (get-column (getf :base))] + (reset! (:start-col lb) col) + (reset! (:indent lb) col))) + (let [oldpos (getf :pos) + newpos (+ oldpos (if prefix (count prefix) 0))] + (setf :pos newpos) + (add-to-buffer this (make-start-block-t lb oldpos newpos)))))) + +(defn- end-block [this] + (let [lb (getf :logical-blocks) + suffix (:suffix lb)] + (if (= (getf :mode) :writing) + (do + (write-white-space this) + (if suffix + (-write (getf :base) suffix)) + (when-let [cb (getf :logical-block-callback)] (cb :end))) + (let [oldpos (getf :pos) + newpos (+ oldpos (if suffix (count suffix) 0))] + (setf :pos newpos) + (add-to-buffer this (make-end-block-t lb oldpos newpos)))) + (setf :logical-blocks (:parent lb)))) + +(defn- nl [this type] + (setf :mode :buffering) + (let [pos (getf :pos)] + (add-to-buffer this (make-nl-t type (getf :logical-blocks) pos pos)))) + +(defn- indent [this relative-to offset] + (let [lb (getf :logical-blocks)] + (if (= (getf :mode) :writing) + (do + (write-white-space this) + (reset! (:indent lb) + (+ offset (condp = relative-to + :block @(:start-col lb) + :current (get-column (getf :base)))))) + (let [pos (getf :pos)] + (add-to-buffer this (make-indent-t lb relative-to offset pos pos)))))) + +(defn- get-miser-width [this] + (getf :miser-width)) + +;;====================================================================== +;; pprint_base.clj +;;====================================================================== + +;;====================================================================== +;; Variables that control the pretty printer +;;====================================================================== + +;; *print-length*, *print-level*, *print-namespace-maps* and *print-dup* are defined in cljs.core +(def ^:dynamic + ^{:doc "Bind to true if you want write to use pretty printing"} + *print-pretty* true) + +(defonce ^:dynamic + ^{:doc "The pretty print dispatch function. Use with-pprint-dispatch or +set-pprint-dispatch to modify." + :added "1.2"} + *print-pprint-dispatch* nil) + +(def ^:dynamic + ^{:doc "Pretty printing will try to avoid anything going beyond this column. +Set it to nil to have pprint let the line be arbitrarily long. This will ignore all +non-mandatory newlines.", + :added "1.2"} + *print-right-margin* 72) + +(def ^:dynamic + ^{:doc "The column at which to enter miser style. Depending on the dispatch table, +miser style add newlines in more places to try to keep lines short allowing for further +levels of nesting.", + :added "1.2"} + *print-miser-width* 40) + +;;; TODO implement output limiting +(def ^:dynamic +^{:private true, + :doc "Maximum number of lines to print in a pretty print instance (N.B. This is not yet used)"} +*print-lines* nil) + +;;; TODO: implement circle and shared +(def ^:dynamic +^{:private true, + :doc "Mark circular structures (N.B. This is not yet used)"} +*print-circle* nil) + +;;; TODO: should we just use *print-dup* here? +(def ^:dynamic +^{:private true, + :doc "Mark repeated structures rather than repeat them (N.B. This is not yet used)"} +*print-shared* nil) + +(def ^:dynamic +^{:doc "Don't print namespaces with symbols. This is particularly useful when +pretty printing the results of macro expansions" + :added "1.2"} +*print-suppress-namespaces* nil) + +;;; TODO: support print-base and print-radix in cl-format +;;; TODO: support print-base and print-radix in rationals +(def ^:dynamic +^{:doc "Print a radix specifier in front of integers and rationals. If *print-base* is 2, 8, +or 16, then the radix specifier used is #b, #o, or #x, respectively. Otherwise the +radix specifier is in the form #XXr where XX is the decimal value of *print-base* " + :added "1.2"} +*print-radix* nil) + +(def ^:dynamic +^{:doc "The base to use for printing integers and rationals." + :added "1.2"} +*print-base* 10) + +;;====================================================================== +;; Internal variables that keep track of where we are in the +;; structure +;;====================================================================== + +(def ^:dynamic ^{:private true} *current-level* 0) + +(def ^:dynamic ^{:private true} *current-length* nil) + +;;====================================================================== +;; Support for the write function +;;====================================================================== + +(declare ^{:arglists '([n])} format-simple-number) + +;; This map causes var metadata to be included in the compiled output, even +;; in advanced compilation. See CLJS-1853 - António Monteiro +;; (def ^{:private true} write-option-table +;; {;:array *print-array* +;; :base #'cljs.pprint/*print-base*, +;; ;;:case *print-case*, +;; :circle #'cljs.pprint/*print-circle*, +;; ;;:escape *print-escape*, +;; ;;:gensym *print-gensym*, +;; :length #'cljs.core/*print-length*, +;; :level #'cljs.core/*print-level*, +;; :lines #'cljs.pprint/*print-lines*, +;; :miser-width #'cljs.pprint/*print-miser-width*, +;; :dispatch #'cljs.pprint/*print-pprint-dispatch*, +;; :pretty #'cljs.pprint/*print-pretty*, +;; :radix #'cljs.pprint/*print-radix*, +;; :readably #'cljs.core/*print-readably*, +;; :right-margin #'cljs.pprint/*print-right-margin*, +;; :suppress-namespaces #'cljs.pprint/*print-suppress-namespaces*}) + +(defn- table-ize [t m] + (apply hash-map (mapcat + #(when-let [v (get t (key %))] [v (val %)]) + m))) + +(defn- pretty-writer? + "Return true iff x is a PrettyWriter" + [x] (and (satisfies? IDeref x) (:pretty-writer @@x))) + +(defn- make-pretty-writer + "Wrap base-writer in a PrettyWriter with the specified right-margin and miser-width" + [base-writer right-margin miser-width] + (pretty-writer base-writer right-margin miser-width)) + +(defn write-out + "Write an object to *out* subject to the current bindings of the printer control +variables. Use the kw-args argument to override individual variables for this call (and +any recursive calls). + +*out* must be a PrettyWriter if pretty printing is enabled. This is the responsibility +of the caller. + +This method is primarily intended for use by pretty print dispatch functions that +already know that the pretty printer will have set up their environment appropriately. +Normal library clients should use the standard \"write\" interface. " + [object] + (let [length-reached (and *current-length* + *print-length* + (>= *current-length* *print-length*))] + (if-not *print-pretty* + (pr object) + (if length-reached + (-write *out* "...") ;;TODO could this (incorrectly) print ... on the next line? + (do + (if *current-length* (set! *current-length* (inc *current-length*))) + (*print-pprint-dispatch* object)))) + length-reached)) + +(defn write + "Write an object subject to the current bindings of the printer control variables. +Use the kw-args argument to override individual variables for this call (and any +recursive calls). Returns the string result if :stream is nil or nil otherwise. + +The following keyword arguments can be passed with values: + Keyword Meaning Default value + :stream Writer for output or nil true (indicates *out*) + :base Base to use for writing rationals Current value of *print-base* + :circle* If true, mark circular structures Current value of *print-circle* + :length Maximum elements to show in sublists Current value of *print-length* + :level Maximum depth Current value of *print-level* + :lines* Maximum lines of output Current value of *print-lines* + :miser-width Width to enter miser mode Current value of *print-miser-width* + :dispatch The pretty print dispatch function Current value of *print-pprint-dispatch* + :pretty If true, do pretty printing Current value of *print-pretty* + :radix If true, prepend a radix specifier Current value of *print-radix* + :readably* If true, print readably Current value of *print-readably* + :right-margin The column for the right margin Current value of *print-right-margin* + :suppress-namespaces If true, no namespaces in symbols Current value of *print-suppress-namespaces* + + * = not yet supported +" + [object & kw-args] + (let [options (merge {:stream true} (apply hash-map kw-args))] + ;;TODO rewrite this as a macro + (binding [cljs.pprint/*print-base* (:base options cljs.pprint/*print-base*) + ;;:case *print-case*, + cljs.pprint/*print-circle* (:circle options cljs.pprint/*print-circle*) + ;;:escape *print-escape* + ;;:gensym *print-gensym* + cljs.core/*print-length* (:length options cljs.core/*print-length*) + cljs.core/*print-level* (:level options cljs.core/*print-level*) + cljs.pprint/*print-lines* (:lines options cljs.pprint/*print-lines*) + cljs.pprint/*print-miser-width* (:miser-width options cljs.pprint/*print-miser-width*) + cljs.pprint/*print-pprint-dispatch* (:dispatch options cljs.pprint/*print-pprint-dispatch*) + cljs.pprint/*print-pretty* (:pretty options cljs.pprint/*print-pretty*) + cljs.pprint/*print-radix* (:radix options cljs.pprint/*print-radix*) + cljs.core/*print-readably* (:readably options cljs.core/*print-readably*) + cljs.pprint/*print-right-margin* (:right-margin options cljs.pprint/*print-right-margin*) + cljs.pprint/*print-suppress-namespaces* (:suppress-namespaces options cljs.pprint/*print-suppress-namespaces*)] + ;;TODO enable printing base + #_[bindings (if (or (not (= *print-base* 10)) *print-radix*) + {#'pr pr-with-base} + {})] + (binding [] + (let [sb (StringBuffer.) + optval (if (contains? options :stream) + (:stream options) + true) + base-writer (if (or (true? optval) (nil? optval)) + (StringBufferWriter. sb) + optval)] + (if *print-pretty* + (with-pretty-writer base-writer + (write-out object)) + (binding [*out* base-writer] + (pr object))) + (if (true? optval) + (string-print (str sb))) + (if (nil? optval) + (str sb))))))) + +(defn pprint + ([object] + (let [sb (StringBuffer.)] + (binding [*out* (StringBufferWriter. sb)] + (pprint object *out*) + (string-print (str sb))))) + ([object writer] + (with-pretty-writer writer + (binding [*print-pretty* true] + (write-out object)) + (if (not (= 0 (get-column *out*))) + (-write *out* \newline))))) + +(defn set-pprint-dispatch + [function] + (set! *print-pprint-dispatch* function) + nil) + +;;====================================================================== +;; Support for the functional interface to the pretty printer +;;====================================================================== + +(defn- check-enumerated-arg [arg choices] + (if-not (choices arg) + ;; TODO clean up choices string + (throw (js/Error. (str "Bad argument: " arg ". It must be one of " choices))))) + +(defn- level-exceeded [] + (and *print-level* (>= *current-level* *print-level*))) + +(defn pprint-newline + "Print a conditional newline to a pretty printing stream. kind specifies if the + newline is :linear, :miser, :fill, or :mandatory. + + This function is intended for use when writing custom dispatch functions. + + Output is sent to *out* which must be a pretty printing writer." + [kind] + (check-enumerated-arg kind #{:linear :miser :fill :mandatory}) + (nl *out* kind)) + +(defn pprint-indent + "Create an indent at this point in the pretty printing stream. This defines how +following lines are indented. relative-to can be either :block or :current depending +whether the indent should be computed relative to the start of the logical block or +the current column position. n is an offset. + +This function is intended for use when writing custom dispatch functions. + +Output is sent to *out* which must be a pretty printing writer." + [relative-to n] + (check-enumerated-arg relative-to #{:block :current}) + (indent *out* relative-to n)) + +;; TODO a real implementation for pprint-tab +(defn pprint-tab + "Tab at this point in the pretty printing stream. kind specifies whether the tab +is :line, :section, :line-relative, or :section-relative. + +Colnum and colinc specify the target column and the increment to move the target +forward if the output is already past the original target. + +This function is intended for use when writing custom dispatch functions. + +Output is sent to *out* which must be a pretty printing writer. + +THIS FUNCTION IS NOT YET IMPLEMENTED." + {:added "1.2"} + [kind colnum colinc] + (check-enumerated-arg kind #{:line :section :line-relative :section-relative}) + (throw (js/Error. "pprint-tab is not yet implemented"))) + +;;====================================================================== +;; cl_format.clj +;;====================================================================== + +;; Forward references +(declare ^{:arglists '([format-str])} compile-format) +(declare ^{:arglists '([stream format args] [format args])} execute-format) +(declare ^{:arglists '([s])} init-navigator) +;; End forward references + +(defn cl-format + "An implementation of a Common Lisp compatible format function. cl-format formats its +arguments to an output stream or string based on the format control string given. It +supports sophisticated formatting of structured data. + +Writer satisfies IWriter, true to output via *print-fn* or nil to output +to a string, format-in is the format control string and the remaining arguments +are the data to be formatted. + +The format control string is a string to be output with embedded 'format directives' +describing how to format the various arguments passed in. + +If writer is nil, cl-format returns the formatted result string. Otherwise, cl-format +returns nil. + +For example: + (let [results [46 38 22]] + (cl-format true \"There ~[are~;is~:;are~]~:* ~d result~:p: ~{~d~^, ~}~%\" + (count results) results)) + +Prints via *print-fn*: + There are 3 results: 46, 38, 22 + +Detailed documentation on format control strings is available in the \"Common Lisp the +Language, 2nd edition\", Chapter 22 (available online at: +http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000) +and in the Common Lisp HyperSpec at +http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm" + {:see-also [["http://www.cs.cmu.edu/afs/cs.cmu.edu/project/ai-repository/ai/html/cltl/clm/node200.html#SECTION002633000000000000000" + "Common Lisp the Language"] + ["http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm" + "Common Lisp HyperSpec"]]} + [writer format-in & args] + (let [compiled-format (if (string? format-in) (compile-format format-in) format-in) + navigator (init-navigator args)] + (execute-format writer compiled-format navigator))) + +(def ^:dynamic ^{:private true} *format-str* nil) + +(defn- format-error [message offset] + (let [full-message (str message \newline *format-str* \newline + (apply str (repeat offset \space)) "^" \newline)] + (throw (js/Error full-message)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Argument navigators manage the argument list +;; as the format statement moves through the list +;; (possibly going forwards and backwards as it does so) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defrecord ^{:private true} + arg-navigator [seq rest pos]) + +(defn- init-navigator + "Create a new arg-navigator from the sequence with the position set to 0" + {:skip-wiki true} + [s] + (let [s (seq s)] + (arg-navigator. s s 0))) + +;; TODO call format-error with offset +(defn- next-arg [navigator] + (let [rst (:rest navigator)] + (if rst + [(first rst) (arg-navigator. (:seq navigator) (next rst) (inc (:pos navigator)))] + (throw (js/Error "Not enough arguments for format definition"))))) + +(defn- next-arg-or-nil [navigator] + (let [rst (:rest navigator)] + (if rst + [(first rst) (arg-navigator. (:seq navigator) (next rst) (inc (:pos navigator)))] + [nil navigator]))) + +;; Get an argument off the arg list and compile it if it's not already compiled +(defn- get-format-arg [navigator] + (let [[raw-format navigator] (next-arg navigator) + compiled-format (if (string? raw-format) + (compile-format raw-format) + raw-format)] + [compiled-format navigator])) + +(declare relative-reposition) + +(defn- absolute-reposition [navigator position] + (if (>= position (:pos navigator)) + (relative-reposition navigator (- (:pos navigator) position)) + (arg-navigator. (:seq navigator) (drop position (:seq navigator)) position))) + +(defn- relative-reposition [navigator position] + (let [newpos (+ (:pos navigator) position)] + (if (neg? position) + (absolute-reposition navigator newpos) + (arg-navigator. (:seq navigator) (drop position (:rest navigator)) newpos)))) + +(defrecord ^{:private true} + compiled-directive [func def params offset]) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; When looking at the parameter list, we may need to manipulate +;; the argument list as well (for 'V' and '#' parameter types). +;; We hide all of this behind a function, but clients need to +;; manage changing arg navigator +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: validate parameters when they come from arg list +(defn- realize-parameter [[param [raw-val offset]] navigator] + (let [[real-param new-navigator] + (cond + (contains? #{:at :colon} param) ;pass flags through unchanged - this really isn't necessary + [raw-val navigator] + + (= raw-val :parameter-from-args) + (next-arg navigator) + + (= raw-val :remaining-arg-count) + [(count (:rest navigator)) navigator] + + true + [raw-val navigator])] + [[param [real-param offset]] new-navigator])) + +(defn- realize-parameter-list [parameter-map navigator] + (let [[pairs new-navigator] + (map-passing-context realize-parameter navigator parameter-map)] + [(into {} pairs) new-navigator])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Functions that support individual directives +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Common handling code for ~A and ~S +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(declare ^{:arglists '([base val])} opt-base-str) + +(def ^{:private true} + special-radix-markers {2 "#b" 8 "#o" 16 "#x"}) + +(defn- format-simple-number [n] + (cond + (integer? n) (if (= *print-base* 10) + (str n (if *print-radix* ".")) + (str + (if *print-radix* (or (get special-radix-markers *print-base*) (str "#" *print-base* "r"))) + (opt-base-str *print-base* n))) + ;;(ratio? n) ;;no ratio support + :else nil)) + +(defn- format-ascii [print-func params arg-navigator offsets] + (let [[arg arg-navigator] (next-arg arg-navigator) + base-output (or (format-simple-number arg) (print-func arg)) + base-width (.-length base-output) + min-width (+ base-width (:minpad params)) + width (if (>= min-width (:mincol params)) + min-width + (+ min-width + (* (+ (quot (- (:mincol params) min-width 1) + (:colinc params)) + 1) + (:colinc params)))) + chars (apply str (repeat (- width base-width) (:padchar params)))] + (if (:at params) + (print (str chars base-output)) + (print (str base-output chars))) + arg-navigator)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Support for the integer directives ~D, ~X, ~O, ~B and some +;; of ~R +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- integral? + "returns true if a number is actually an integer (that is, has no fractional part)" + [x] + (cond + (integer? x) true + ;;(decimal? x) ;;no decimal support + (float? x) (= x (Math/floor x)) + ;;(ratio? x) ;;no ratio support + :else false)) + +(defn- remainders + "Return the list of remainders (essentially the 'digits') of val in the given base" + [base val] + (reverse + (first + (consume #(if (pos? %) + [(rem % base) (quot % base)] + [nil nil]) + val)))) + +;; TODO: xlated-val does not seem to be used here. +;; NB +(defn- base-str + "Return val as a string in the given base" + [base val] + (if (zero? val) + "0" + (let [xlated-val (cond + ;(float? val) (bigdec val) ;;No bigdec + ;(ratio? val) nil ;;No ratio + :else val)] + (apply str + (map + #(if (< % 10) (char (+ (char-code \0) %)) (char (+ (char-code \a) (- % 10)))) + (remainders base val)))))) + +;;Not sure if this is accurate or necessary +(def ^{:private true} + javascript-base-formats {8 "%o", 10 "%d", 16 "%x"}) + +(defn- opt-base-str + "Return val as a string in the given base. No cljs format, so no improved performance." + [base val] + (base-str base val)) + +(defn- group-by* [unit lis] + (reverse + (first + (consume (fn [x] [(seq (reverse (take unit x))) (seq (drop unit x))]) (reverse lis))))) + +(defn- format-integer [base params arg-navigator offsets] + (let [[arg arg-navigator] (next-arg arg-navigator)] + (if (integral? arg) + (let [neg (neg? arg) + pos-arg (if neg (- arg) arg) + raw-str (opt-base-str base pos-arg) + group-str (if (:colon params) + (let [groups (map #(apply str %) (group-by* (:commainterval params) raw-str)) + commas (repeat (count groups) (:commachar params))] + (apply str (next (interleave commas groups)))) + raw-str) + signed-str (cond + neg (str "-" group-str) + (:at params) (str "+" group-str) + true group-str) + padded-str (if (< (.-length signed-str) (:mincol params)) + (str (apply str (repeat (- (:mincol params) (.-length signed-str)) + (:padchar params))) + signed-str) + signed-str)] + (print padded-str)) + (format-ascii print-str {:mincol (:mincol params) :colinc 1 :minpad 0 + :padchar (:padchar params) :at true} + (init-navigator [arg]) nil)) + arg-navigator)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Support for english formats (~R and ~:R) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^{:private true} + english-cardinal-units + ["zero" "one" "two" "three" "four" "five" "six" "seven" "eight" "nine" + "ten" "eleven" "twelve" "thirteen" "fourteen" + "fifteen" "sixteen" "seventeen" "eighteen" "nineteen"]) + +(def ^{:private true} + english-ordinal-units + ["zeroth" "first" "second" "third" "fourth" "fifth" "sixth" "seventh" "eighth" "ninth" + "tenth" "eleventh" "twelfth" "thirteenth" "fourteenth" + "fifteenth" "sixteenth" "seventeenth" "eighteenth" "nineteenth"]) + +(def ^{:private true} + english-cardinal-tens + ["" "" "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" "ninety"]) + +(def ^{:private true} + english-ordinal-tens + ["" "" "twentieth" "thirtieth" "fortieth" "fiftieth" + "sixtieth" "seventieth" "eightieth" "ninetieth"]) + +;; We use "short scale" for our units (see http://en.wikipedia.org/wiki/Long_and_short_scales) +;; Number names from http://www.jimloy.com/math/billion.htm +;; We follow the rules for writing numbers from the Blue Book +;; (http://www.grammarbook.com/numbers/numbers.asp) +(def ^{:private true} + english-scale-numbers + ["" "thousand" "million" "billion" "trillion" "quadrillion" "quintillion" + "sextillion" "septillion" "octillion" "nonillion" "decillion" + "undecillion" "duodecillion" "tredecillion" "quattuordecillion" + "quindecillion" "sexdecillion" "septendecillion" + "octodecillion" "novemdecillion" "vigintillion"]) + +(defn- format-simple-cardinal + "Convert a number less than 1000 to a cardinal english string" + [num] + (let [hundreds (quot num 100) + tens (rem num 100)] + (str + (if (pos? hundreds) (str (nth english-cardinal-units hundreds) " hundred")) + (if (and (pos? hundreds) (pos? tens)) " ") + (if (pos? tens) + (if (< tens 20) + (nth english-cardinal-units tens) + (let [ten-digit (quot tens 10) + unit-digit (rem tens 10)] + (str + (if (pos? ten-digit) (nth english-cardinal-tens ten-digit)) + (if (and (pos? ten-digit) (pos? unit-digit)) "-") + (if (pos? unit-digit) (nth english-cardinal-units unit-digit))))))))) + +(defn- add-english-scales + "Take a sequence of parts, add scale numbers (e.g., million) and combine into a string + offset is a factor of 10^3 to multiply by" + [parts offset] + (let [cnt (count parts)] + (loop [acc [] + pos (dec cnt) + this (first parts) + remainder (next parts)] + (if (nil? remainder) + (str (apply str (interpose ", " acc)) + (if (and (not (empty? this)) (not (empty? acc))) ", ") + this + (if (and (not (empty? this)) (pos? (+ pos offset))) + (str " " (nth english-scale-numbers (+ pos offset))))) + (recur + (if (empty? this) + acc + (conj acc (str this " " (nth english-scale-numbers (+ pos offset))))) + (dec pos) + (first remainder) + (next remainder)))))) + +(defn- format-cardinal-english [params navigator offsets] + (let [[arg navigator] (next-arg navigator)] + (if (= 0 arg) + (print "zero") + (let [abs-arg (if (neg? arg) (- arg) arg) ; some numbers are too big for Math/abs (is this true?) + parts (remainders 1000 abs-arg)] + (if (<= (count parts) (count english-scale-numbers)) + (let [parts-strs (map format-simple-cardinal parts) + full-str (add-english-scales parts-strs 0)] + (print (str (if (neg? arg) "minus ") full-str))) + (format-integer ;; for numbers > 10^63, we fall back on ~D + 10 + {:mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true} + (init-navigator [arg]) + {:mincol 0, :padchar 0, :commachar 0 :commainterval 0})))) + navigator)) + +(defn- format-simple-ordinal + "Convert a number less than 1000 to a ordinal english string + Note this should only be used for the last one in the sequence" + [num] + (let [hundreds (quot num 100) + tens (rem num 100)] + (str + (if (pos? hundreds) (str (nth english-cardinal-units hundreds) " hundred")) + (if (and (pos? hundreds) (pos? tens)) " ") + (if (pos? tens) + (if (< tens 20) + (nth english-ordinal-units tens) + (let [ten-digit (quot tens 10) + unit-digit (rem tens 10)] + (if (and (pos? ten-digit) (not (pos? unit-digit))) + (nth english-ordinal-tens ten-digit) + (str + (if (pos? ten-digit) (nth english-cardinal-tens ten-digit)) + (if (and (pos? ten-digit) (pos? unit-digit)) "-") + (if (pos? unit-digit) (nth english-ordinal-units unit-digit)))))) + (if (pos? hundreds) "th"))))) + +(defn- format-ordinal-english [params navigator offsets] + (let [[arg navigator] (next-arg navigator)] + (if (= 0 arg) + (print "zeroth") + (let [abs-arg (if (neg? arg) (- arg) arg) ; some numbers are too big for Math/abs (is this true?) + parts (remainders 1000 abs-arg)] + (if (<= (count parts) (count english-scale-numbers)) + (let [parts-strs (map format-simple-cardinal (drop-last parts)) + head-str (add-english-scales parts-strs 1) + tail-str (format-simple-ordinal (last parts))] + (print (str (if (neg? arg) "minus ") + (cond + (and (not (empty? head-str)) (not (empty? tail-str))) + (str head-str ", " tail-str) + + (not (empty? head-str)) (str head-str "th") + :else tail-str)))) + (do (format-integer ;for numbers > 10^63, we fall back on ~D + 10 + {:mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true} + (init-navigator [arg]) + {:mincol 0, :padchar 0, :commachar 0 :commainterval 0}) + (let [low-two-digits (rem arg 100) + not-teens (or (< 11 low-two-digits) (> 19 low-two-digits)) + low-digit (rem low-two-digits 10)] + (print (cond + (and (== low-digit 1) not-teens) "st" + (and (== low-digit 2) not-teens) "nd" + (and (== low-digit 3) not-teens) "rd" + :else "th"))))))) + navigator)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Support for roman numeral formats (~@R and ~@:R) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^{:private true} + old-roman-table + [[ "I" "II" "III" "IIII" "V" "VI" "VII" "VIII" "VIIII"] + [ "X" "XX" "XXX" "XXXX" "L" "LX" "LXX" "LXXX" "LXXXX"] + [ "C" "CC" "CCC" "CCCC" "D" "DC" "DCC" "DCCC" "DCCCC"] + [ "M" "MM" "MMM"]]) + +(def ^{:private true} + new-roman-table + [[ "I" "II" "III" "IV" "V" "VI" "VII" "VIII" "IX"] + [ "X" "XX" "XXX" "XL" "L" "LX" "LXX" "LXXX" "XC"] + [ "C" "CC" "CCC" "CD" "D" "DC" "DCC" "DCCC" "CM"] + [ "M" "MM" "MMM"]]) + +(defn- format-roman + "Format a roman numeral using the specified look-up table" + [table params navigator offsets] + (let [[arg navigator] (next-arg navigator)] + (if (and (number? arg) (> arg 0) (< arg 4000)) + (let [digits (remainders 10 arg)] + (loop [acc [] + pos (dec (count digits)) + digits digits] + (if (empty? digits) + (print (apply str acc)) + (let [digit (first digits)] + (recur (if (= 0 digit) + acc + (conj acc (nth (nth table pos) (dec digit)))) + (dec pos) + (next digits)))))) + (format-integer ; for anything <= 0 or > 3999, we fall back on ~D + 10 + {:mincol 0, :padchar \space, :commachar \, :commainterval 3, :colon true} + (init-navigator [arg]) + {:mincol 0, :padchar 0, :commachar 0 :commainterval 0})) + navigator)) + +(defn- format-old-roman [params navigator offsets] + (format-roman old-roman-table params navigator offsets)) + +(defn- format-new-roman [params navigator offsets] + (format-roman new-roman-table params navigator offsets)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Support for character formats (~C) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^{:private true} + special-chars {8 "Backspace", 9 "Tab", 10 "Newline", 13 "Return", 32 "Space"}) + +(defn- pretty-character [params navigator offsets] + (let [[c navigator] (next-arg navigator) + as-int (char-code c) + base-char (bit-and as-int 127) + meta (bit-and as-int 128) + special (get special-chars base-char)] + (if (> meta 0) (print "Meta-")) + (print (cond + special special + (< base-char 32) (str "Control-" (char (+ base-char 64))) + (= base-char 127) "Control-?" + :else (char base-char))) + navigator)) + +(defn- readable-character [params navigator offsets] + (let [[c navigator] (next-arg navigator)] + (condp = (:char-format params) + \o (cl-format true "\\o~3,'0o" (char-code c)) + \u (cl-format true "\\u~4,'0x" (char-code c)) + nil (print-char c)) + navigator)) + +(defn- plain-character [params navigator offsets] + (let [[char navigator] (next-arg navigator)] + (print char) + navigator)) + +;; Check to see if a result is an abort (~^) construct +;; TODO: move these funcs somewhere more appropriate +(defn- abort? [context] + (let [token (first context)] + (or (= :up-arrow token) (= :colon-up-arrow token)))) + +;; Handle the execution of "sub-clauses" in bracket constructions +(defn- execute-sub-format [format args base-args] + (second + (map-passing-context + (fn [element context] + (if (abort? context) + [nil context] ; just keep passing it along + (let [[params args] (realize-parameter-list (:params element) context) + [params offsets] (unzip-map params) + params (assoc params :base-args base-args)] + [nil (apply (:func element) [params args offsets])]))) + args + format))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Support for real number formats +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO - return exponent as int to eliminate double conversion +(defn- float-parts-base + "Produce string parts for the mantissa (normalize 1-9) and exponent" + [f] + (let [s (string/lower-case (str f)) + exploc (.indexOf s \e) + dotloc (.indexOf s \.)] + (if (neg? exploc) + (if (neg? dotloc) + [s (str (dec (count s)))] + [(str (subs s 0 dotloc) (subs s (inc dotloc))) (str (dec dotloc))]) + (if (neg? dotloc) + [(subs s 0 exploc) (subs s (inc exploc))] + [(str (subs s 0 1) (subs s 2 exploc)) (subs s (inc exploc))])))) + +(defn- float-parts + "Take care of leading and trailing zeros in decomposed floats" + [f] + (let [[m e] (float-parts-base f) + m1 (rtrim m \0) + m2 (ltrim m1 \0) + delta (- (count m1) (count m2)) + e (if (and (pos? (count e)) (= (nth e 0) \+)) (subs e 1) e)] + (if (empty? m2) + ["0" 0] + [m2 (- (js/parseInt e 10) delta)]))) + +(defn- inc-s + "Assumption: The input string consists of one or more decimal digits, + and no other characters. Return a string containing one or more + decimal digits containing a decimal number one larger than the input + string. The output string will always be the same length as the input + string, or one character longer." + [s] + (let [len-1 (dec (count s))] + (loop [i (int len-1)] + (cond + (neg? i) (apply str "1" (repeat (inc len-1) "0")) + (= \9 (.charAt s i)) (recur (dec i)) + :else (apply str (subs s 0 i) + (char (inc (char-code (.charAt s i)))) + (repeat (- len-1 i) "0")))))) + +(defn- round-str [m e d w] + (if (or d w) + (let [len (count m) + ;; Every formatted floating point number should include at + ;; least one decimal digit and a decimal point. + w (if w (max 2 w) + ;;NB: if w doesn't exist, it won't ever be used because d will + ;; satisfy the cond below. cljs gives a compilation warning if + ;; we don't provide a value here. + 0) + round-pos (cond + ;; If d was given, that forces the rounding + ;; position, regardless of any width that may + ;; have been specified. + d (+ e d 1) + ;; Otherwise w was specified, so pick round-pos + ;; based upon that. + ;; If e>=0, then abs value of number is >= 1.0, + ;; and e+1 is number of decimal digits before the + ;; decimal point when the number is written + ;; without scientific notation. Never round the + ;; number before the decimal point. + (>= e 0) (max (inc e) (dec w)) + ;; e < 0, so number abs value < 1.0 + :else (+ w e)) + [m1 e1 round-pos len] (if (= round-pos 0) + [(str "0" m) (inc e) 1 (inc len)] + [m e round-pos len])] + (if round-pos + (if (neg? round-pos) + ["0" 0 false] + (if (> len round-pos) + (let [round-char (nth m1 round-pos) + result (subs m1 0 round-pos)] + (if (>= (char-code round-char) (char-code \5)) + (let [round-up-result (inc-s result) + expanded (> (count round-up-result) (count result))] + [(if expanded + (subs round-up-result 0 (dec (count round-up-result))) + round-up-result) + e1 expanded]) + [result e1 false])) + [m e false])) + [m e false])) + [m e false])) + +(defn- expand-fixed [m e d] + (let [[m1 e1] (if (neg? e) + [(str (apply str (repeat (dec (- e)) \0)) m) -1] + [m e]) + len (count m1) + target-len (if d (+ e1 d 1) (inc e1))] + (if (< len target-len) + (str m1 (apply str (repeat (- target-len len) \0))) + m1))) + +(defn- insert-decimal + "Insert the decimal point at the right spot in the number to match an exponent" + [m e] + (if (neg? e) + (str "." m) + (let [loc (inc e)] + (str (subs m 0 loc) "." (subs m loc))))) + +(defn- get-fixed [m e d] + (insert-decimal (expand-fixed m e d) e)) + +(defn- insert-scaled-decimal + "Insert the decimal point at the right spot in the number to match an exponent" + [m k] + (if (neg? k) + (str "." m) + (str (subs m 0 k) "." (subs m k)))) + +;;TODO: No ratio, so not sure what to do here +(defn- convert-ratio [x] + x) + +;; the function to render ~F directives +;; TODO: support rationals. Back off to ~D/~A in the appropriate cases +(defn- fixed-float [params navigator offsets] + (let [w (:w params) + d (:d params) + [arg navigator] (next-arg navigator) + [sign abs] (if (neg? arg) ["-" (- arg)] ["+" arg]) + abs (convert-ratio abs) + [mantissa exp] (float-parts abs) + scaled-exp (+ exp (:k params)) + add-sign (or (:at params) (neg? arg)) + append-zero (and (not d) (<= (dec (count mantissa)) scaled-exp)) + [rounded-mantissa scaled-exp expanded] (round-str mantissa scaled-exp + d (if w (- w (if add-sign 1 0)))) + fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d) + fixed-repr (if (and w d + (>= d 1) + (= (.charAt fixed-repr 0) \0) + (= (.charAt fixed-repr 1) \.) + (> (count fixed-repr) (- w (if add-sign 1 0)))) + (subs fixed-repr 1) ;chop off leading 0 + fixed-repr) + prepend-zero (= (first fixed-repr) \.)] + (if w + (let [len (count fixed-repr) + signed-len (if add-sign (inc len) len) + prepend-zero (and prepend-zero (not (>= signed-len w))) + append-zero (and append-zero (not (>= signed-len w))) + full-len (if (or prepend-zero append-zero) + (inc signed-len) + signed-len)] + (if (and (> full-len w) (:overflowchar params)) + (print (apply str (repeat w (:overflowchar params)))) + (print (str + (apply str (repeat (- w full-len) (:padchar params))) + (if add-sign sign) + (if prepend-zero "0") + fixed-repr + (if append-zero "0"))))) + (print (str + (if add-sign sign) + (if prepend-zero "0") + fixed-repr + (if append-zero "0")))) + navigator)) + +;; the function to render ~E directives +;; TODO: support rationals. Back off to ~D/~A in the appropriate cases +;; TODO: define ~E representation for Infinity +(defn- exponential-float [params navigator offset] + (let [[arg navigator] (next-arg navigator) + arg (convert-ratio arg)] + (loop [[mantissa exp] (float-parts (if (neg? arg) (- arg) arg))] + (let [w (:w params) + d (:d params) + e (:e params) + k (:k params) + expchar (or (:exponentchar params) \E) + add-sign (or (:at params) (neg? arg)) + prepend-zero (<= k 0) + scaled-exp (- exp (dec k)) + scaled-exp-str (str (Math/abs scaled-exp)) + scaled-exp-str (str expchar (if (neg? scaled-exp) \- \+) + (if e (apply str + (repeat + (- e + (count scaled-exp-str)) + \0))) + scaled-exp-str) + exp-width (count scaled-exp-str) + base-mantissa-width (count mantissa) + scaled-mantissa (str (apply str (repeat (- k) \0)) + mantissa + (if d + (apply str + (repeat + (- d (dec base-mantissa-width) + (if (neg? k) (- k) 0)) \0)))) + w-mantissa (if w (- w exp-width)) + [rounded-mantissa _ incr-exp] (round-str + scaled-mantissa 0 + (cond + (= k 0) (dec d) + (pos? k) d + (neg? k) (dec d)) + (if w-mantissa + (- w-mantissa (if add-sign 1 0)))) + full-mantissa (insert-scaled-decimal rounded-mantissa k) + append-zero (and (= k (count rounded-mantissa)) (nil? d))] + (if (not incr-exp) + (if w + (let [len (+ (count full-mantissa) exp-width) + signed-len (if add-sign (inc len) len) + prepend-zero (and prepend-zero (not (= signed-len w))) + full-len (if prepend-zero (inc signed-len) signed-len) + append-zero (and append-zero (< full-len w))] + (if (and (or (> full-len w) (and e (> (- exp-width 2) e))) + (:overflowchar params)) + (print (apply str (repeat w (:overflowchar params)))) + (print (str + (apply str + (repeat + (- w full-len (if append-zero 1 0)) + (:padchar params))) + (if add-sign (if (neg? arg) \- \+)) + (if prepend-zero "0") + full-mantissa + (if append-zero "0") + scaled-exp-str)))) + (print (str + (if add-sign (if (neg? arg) \- \+)) + (if prepend-zero "0") + full-mantissa + (if append-zero "0") + scaled-exp-str))) + (recur [rounded-mantissa (inc exp)])))) + navigator)) + +;; the function to render ~G directives +;; This just figures out whether to pass the request off to ~F or ~E based +;; on the algorithm in CLtL. +;; TODO: support rationals. Back off to ~D/~A in the appropriate cases +;; TODO: refactor so that float-parts isn't called twice +(defn- general-float [params navigator offsets] + (let [[arg _] (next-arg navigator) + arg (convert-ratio arg) + [mantissa exp] (float-parts (if (neg? arg) (- arg) arg)) + w (:w params) + d (:d params) + e (:e params) + n (if (= arg 0.0) 0 (inc exp)) + ee (if e (+ e 2) 4) + ww (if w (- w ee)) + d (if d d (max (count mantissa) (min n 7))) + dd (- d n)] + (if (<= 0 dd d) + (let [navigator (fixed-float {:w ww, :d dd, :k 0, + :overflowchar (:overflowchar params), + :padchar (:padchar params), :at (:at params)} + navigator offsets)] + (print (apply str (repeat ee \space))) + navigator) + (exponential-float params navigator offsets)))) + +;; the function to render ~$ directives +;; TODO: support rationals. Back off to ~D/~A in the appropriate cases +(defn- dollar-float [params navigator offsets] + (let [[arg navigator] (next-arg navigator) + [mantissa exp] (float-parts (Math/abs arg)) + d (:d params) ; digits after the decimal + n (:n params) ; minimum digits before the decimal + w (:w params) ; minimum field width + add-sign (or (:at params) (neg? arg)) + [rounded-mantissa scaled-exp expanded] (round-str mantissa exp d nil) + fixed-repr (get-fixed rounded-mantissa (if expanded (inc scaled-exp) scaled-exp) d) + full-repr (str (apply str (repeat (- n (.indexOf fixed-repr \.)) \0)) fixed-repr) + full-len (+ (count full-repr) (if add-sign 1 0))] + (print (str + (if (and (:colon params) add-sign) (if (neg? arg) \- \+)) + (apply str (repeat (- w full-len) (:padchar params))) + (if (and (not (:colon params)) add-sign) (if (neg? arg) \- \+)) + full-repr)) + navigator)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Support for the '~[...~]' conditional construct in its +;; different flavors +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; ~[...~] without any modifiers chooses one of the clauses based on the param or +;; next argument +;; TODO check arg is positive int +(defn- choice-conditional [params arg-navigator offsets] + (let [arg (:selector params) + [arg navigator] (if arg [arg arg-navigator] (next-arg arg-navigator)) + clauses (:clauses params) + clause (if (or (neg? arg) (>= arg (count clauses))) + (first (:else params)) + (nth clauses arg))] + (if clause + (execute-sub-format clause navigator (:base-args params)) + navigator))) + +;; ~:[...~] with the colon reads the next argument treating it as a truth value +(defn- boolean-conditional [params arg-navigator offsets] + (let [[arg navigator] (next-arg arg-navigator) + clauses (:clauses params) + clause (if arg + (second clauses) + (first clauses))] + (if clause + (execute-sub-format clause navigator (:base-args params)) + navigator))) + +;; ~@[...~] with the at sign executes the conditional if the next arg is not +;; nil/false without consuming the arg +(defn- check-arg-conditional [params arg-navigator offsets] + (let [[arg navigator] (next-arg arg-navigator) + clauses (:clauses params) + clause (if arg (first clauses))] + (if arg + (if clause + (execute-sub-format clause arg-navigator (:base-args params)) + arg-navigator) + navigator))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Support for the '~{...~}' iteration construct in its +;; different flavors +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; ~{...~} without any modifiers uses the next argument as an argument list that +;; is consumed by all the iterations +(defn- iterate-sublist [params navigator offsets] + (let [max-count (:max-iterations params) + param-clause (first (:clauses params)) + [clause navigator] (if (empty? param-clause) + (get-format-arg navigator) + [param-clause navigator]) + [arg-list navigator] (next-arg navigator) + args (init-navigator arg-list)] + (loop [count 0 + args args + last-pos (int -1)] + (if (and (not max-count) (= (:pos args) last-pos) (> count 1)) + ;; TODO get the offset in here and call format exception + (throw (js/Error "%{ construct not consuming any arguments: Infinite loop!"))) + (if (or (and (empty? (:rest args)) + (or (not (:colon (:right-params params))) (> count 0))) + (and max-count (>= count max-count))) + navigator + (let [iter-result (execute-sub-format clause args (:base-args params))] + (if (= :up-arrow (first iter-result)) + navigator + (recur (inc count) iter-result (:pos args)))))))) + +;; ~:{...~} with the colon treats the next argument as a list of sublists. Each of the +;; sublists is used as the arglist for a single iteration. +(defn- iterate-list-of-sublists [params navigator offsets] + (let [max-count (:max-iterations params) + param-clause (first (:clauses params)) + [clause navigator] (if (empty? param-clause) + (get-format-arg navigator) + [param-clause navigator]) + [arg-list navigator] (next-arg navigator)] + (loop [count 0 + arg-list arg-list] + (if (or (and (empty? arg-list) + (or (not (:colon (:right-params params))) (> count 0))) + (and max-count (>= count max-count))) + navigator + (let [iter-result (execute-sub-format + clause + (init-navigator (first arg-list)) + (init-navigator (next arg-list)))] + (if (= :colon-up-arrow (first iter-result)) + navigator + (recur (inc count) (next arg-list)))))))) + +;; ~@{...~} with the at sign uses the main argument list as the arguments to the iterations +;; is consumed by all the iterations +(defn- iterate-main-list [params navigator offsets] + (let [max-count (:max-iterations params) + param-clause (first (:clauses params)) + [clause navigator] (if (empty? param-clause) + (get-format-arg navigator) + [param-clause navigator])] + (loop [count 0 + navigator navigator + last-pos (int -1)] + (if (and (not max-count) (= (:pos navigator) last-pos) (> count 1)) + ;; TODO get the offset in here and call format exception + (throw (js/Error "%@{ construct not consuming any arguments: Infinite loop!"))) + (if (or (and (empty? (:rest navigator)) + (or (not (:colon (:right-params params))) (> count 0))) + (and max-count (>= count max-count))) + navigator + (let [iter-result (execute-sub-format clause navigator (:base-args params))] + (if (= :up-arrow (first iter-result)) + (second iter-result) + (recur + (inc count) iter-result (:pos navigator)))))))) + +;; ~@:{...~} with both colon and at sign uses the main argument list as a set of sublists, one +;; of which is consumed with each iteration +(defn- iterate-main-sublists [params navigator offsets] + (let [max-count (:max-iterations params) + param-clause (first (:clauses params)) + [clause navigator] (if (empty? param-clause) + (get-format-arg navigator) + [param-clause navigator])] + (loop [count 0 + navigator navigator] + (if (or (and (empty? (:rest navigator)) + (or (not (:colon (:right-params params))) (> count 0))) + (and max-count (>= count max-count))) + navigator + (let [[sublist navigator] (next-arg-or-nil navigator) + iter-result (execute-sub-format clause (init-navigator sublist) navigator)] + (if (= :colon-up-arrow (first iter-result)) + navigator + (recur (inc count) navigator))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; The '~< directive has two completely different meanings +;; in the '~<...~>' form it does justification, but with +;; ~<...~:>' it represents the logical block operation of the +;; pretty printer. +;; +;; Unfortunately, the current architecture decides what function +;; to call at form parsing time before the sub-clauses have been +;; folded, so it is left to run-time to make the decision. +;; +;; TODO: make it possible to make these decisions at compile-time. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(declare ^{:arglists '([params navigator offsets])} format-logical-block) +(declare ^{:arglists '([params navigator offsets])} justify-clauses) + +(defn- logical-block-or-justify [params navigator offsets] + (if (:colon (:right-params params)) + (format-logical-block params navigator offsets) + (justify-clauses params navigator offsets))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Support for the '~<...~>' justification directive +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- render-clauses [clauses navigator base-navigator] + (loop [clauses clauses + acc [] + navigator navigator] + (if (empty? clauses) + [acc navigator] + (let [clause (first clauses) + [iter-result result-str] (let [sb (StringBuffer.)] + (binding [*out* (StringBufferWriter. sb)] + [(execute-sub-format clause navigator base-navigator) + (str sb)]))] + (if (= :up-arrow (first iter-result)) + [acc (second iter-result)] + (recur (next clauses) (conj acc result-str) iter-result)))))) + +;; TODO support for ~:; constructions +(defn- justify-clauses [params navigator offsets] + (let [[[eol-str] new-navigator] (when-let [else (:else params)] + (render-clauses else navigator (:base-args params))) + navigator (or new-navigator navigator) + [else-params new-navigator] (when-let [p (:else-params params)] + (realize-parameter-list p navigator)) + navigator (or new-navigator navigator) + min-remaining (or (first (:min-remaining else-params)) 0) + max-columns (or (first (:max-columns else-params)) + (get-max-column *out*)) + clauses (:clauses params) + [strs navigator] (render-clauses clauses navigator (:base-args params)) + slots (max 1 + (+ (dec (count strs)) (if (:colon params) 1 0) (if (:at params) 1 0))) + chars (reduce + (map count strs)) + mincol (:mincol params) + minpad (:minpad params) + colinc (:colinc params) + minout (+ chars (* slots minpad)) + result-columns (if (<= minout mincol) + mincol + (+ mincol (* colinc + (+ 1 (quot (- minout mincol 1) colinc))))) + total-pad (- result-columns chars) + pad (max minpad (quot total-pad slots)) + extra-pad (- total-pad (* pad slots)) + pad-str (apply str (repeat pad (:padchar params)))] + (if (and eol-str (> (+ (get-column (:base @@*out*)) min-remaining result-columns) + max-columns)) + (print eol-str)) + (loop [slots slots + extra-pad extra-pad + strs strs + pad-only (or (:colon params) + (and (= (count strs) 1) (not (:at params))))] + (if (seq strs) + (do + (print (str (if (not pad-only) (first strs)) + (if (or pad-only (next strs) (:at params)) pad-str) + (if (pos? extra-pad) (:padchar params)))) + (recur + (dec slots) + (dec extra-pad) + (if pad-only strs (next strs)) + false)))) + navigator)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Support for case modification with ~(...~). +;;; We do this by wrapping the underlying writer with +;;; a special writer to do the appropriate modification. This +;;; allows us to support arbitrary-sized output and sources +;;; that may block. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- downcase-writer + "Returns a proxy that wraps writer, converting all characters to lower case" + [writer] + (reify + IWriter + (-flush [_] (-flush writer)) + (-write + ;;no multi-arity, not sure of importance + #_([^chars cbuf ^Integer off ^Integer len] + (.write writer cbuf off len)) + [this x] + (condp = (type x) + js/String + (let [s x] + (-write writer (string/lower-case s))) + + js/Number + (let [c x] + ;;TODO need to enforce integers only? + (-write writer (string/lower-case (char c)))))))) + +(defn- upcase-writer + "Returns a proxy that wraps writer, converting all characters to upper case" + [writer] + (reify + IWriter + (-flush [_] (-flush writer)) + (-write + ;;no multi-arity, not sure of importance + #_([^chars cbuf ^Integer off ^Integer len] + (.write writer cbuf off len)) + [this x] + (condp = (type x) + js/String + (let [s x] + (-write writer (string/upper-case s))) + + js/Number + (let [c x] + ;;TODO need to enforce integers only? + (-write writer (string/upper-case (char c)))))))) + +(defn- capitalize-string + "Capitalizes the words in a string. If first? is false, don't capitalize the + first character of the string even if it's a letter." + [s first?] + (let [f (first s) + s (if (and first? f (gstring/isUnicodeChar f)) + (str (string/upper-case f) (subs s 1)) + s)] + (apply str + (first + (consume + (fn [s] + (if (empty? s) + [nil nil] + (let [m (.exec (js/RegExp "\\W\\w" "g") s) + offset (and m (inc (.-index m)))] + (if offset + [(str (subs s 0 offset) + (string/upper-case (nth s offset))) + (subs s (inc offset))] + [s nil])))) + s))))) + +(defn- capitalize-word-writer + "Returns a proxy that wraps writer, capitalizing all words" + [writer] + (let [last-was-whitespace? (atom true)] + (reify + IWriter + (-flush [_] (-flush writer)) + (-write + ;;no multi-arity + #_([^chars cbuf ^Integer off ^Integer len] + (.write writer cbuf off len)) + [this x] + (condp = (type x) + js/String + (let [s x] + (-write writer + (capitalize-string (.toLowerCase s) @last-was-whitespace?)) + (when (pos? (.-length s)) + (reset! last-was-whitespace? (gstring/isEmptyOrWhitespace (nth s (dec (count s))))))) + + js/Number + (let [c (char x)] + (let [mod-c (if @last-was-whitespace? (string/upper-case c) c)] + (-write writer mod-c) + (reset! last-was-whitespace? (gstring/isEmptyOrWhitespace c))))))))) + +(defn- init-cap-writer + "Returns a proxy that wraps writer, capitalizing the first word" + [writer] + (let [capped (atom false)] + (reify + IWriter + (-flush [_] (-flush writer)) + (-write + ;;no multi-arity + #_([^chars cbuf ^Integer off ^Integer len] + (.write writer cbuf off len)) + [this x] + (condp = (type x) + js/String + (let [s (string/lower-case x)] + (if (not @capped) + (let [m (.exec (js/RegExp "\\S" "g") s) + offset (and m (.-index m))] + (if offset + (do (-write writer + (str (subs s 0 offset) + (string/upper-case (nth s offset)) + (string/lower-case (subs s (inc offset))))) + (reset! capped true)) + (-write writer s))) + (-write writer (string/lower-case s)))) + + js/Number + (let [c (char x)] + (if (and (not @capped) (gstring/isUnicodeChar c)) + (do + (reset! capped true) + (-write writer (string/upper-case c))) + (-write writer (string/lower-case c))))))))) + +(defn- modify-case [make-writer params navigator offsets] + (let [clause (first (:clauses params))] + (binding [*out* (make-writer *out*)] + (execute-sub-format clause navigator (:base-args params))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; If necessary, wrap the writer in a PrettyWriter object +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO update this doc string to show correct way to print +(defn get-pretty-writer + "Returns the IWriter passed in wrapped in a pretty writer proxy, unless it's +already a pretty writer. Generally, it is unnecessary to call this function, since pprint, +write, and cl-format all call it if they need to. However if you want the state to be +preserved across calls, you will want to wrap them with this. + +For example, when you want to generate column-aware output with multiple calls to cl-format, +do it like in this example: + + (defn print-table [aseq column-width] + (binding [*out* (get-pretty-writer *out*)] + (doseq [row aseq] + (doseq [col row] + (cl-format true \"~4D~7,vT\" col column-width)) + (prn)))) + +Now when you run: + + user> (print-table (map #(vector % (* % %) (* % % %)) (range 1 11)) 8) + +It prints a table of squares and cubes for the numbers from 1 to 10: + + 1 1 1 + 2 4 8 + 3 9 27 + 4 16 64 + 5 25 125 + 6 36 216 + 7 49 343 + 8 64 512 + 9 81 729 + 10 100 1000" + [writer] + (if (pretty-writer? writer) + writer + (pretty-writer writer *print-right-margin* *print-miser-width*))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Support for column-aware operations ~&, ~T +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn fresh-line + "Make a newline if *out* is not already at the beginning of the line. If *out* is +not a pretty writer (which keeps track of columns), this function always outputs a newline." + [] + (if (satisfies? IDeref *out*) + (if (not (= 0 (get-column (:base @@*out*)))) + (prn)) + (prn))) + +(defn- absolute-tabulation [params navigator offsets] + (let [colnum (:colnum params) + colinc (:colinc params) + current (get-column (:base @@*out*)) + space-count (cond + (< current colnum) (- colnum current) + (= colinc 0) 0 + :else (- colinc (rem (- current colnum) colinc)))] + (print (apply str (repeat space-count \space)))) + navigator) + +(defn- relative-tabulation [params navigator offsets] + (let [colrel (:colnum params) + colinc (:colinc params) + start-col (+ colrel (get-column (:base @@*out*))) + offset (if (pos? colinc) (rem start-col colinc) 0) + space-count (+ colrel (if (= 0 offset) 0 (- colinc offset)))] + (print (apply str (repeat space-count \space)))) + navigator) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Support for accessing the pretty printer from a format +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: support ~@; per-line-prefix separator +;; TODO: get the whole format wrapped so we can start the lb at any column +(defn- format-logical-block [params navigator offsets] + (let [clauses (:clauses params) + clause-count (count clauses) + prefix (cond + (> clause-count 1) (:string (:params (first (first clauses)))) + (:colon params) "(") + body (nth clauses (if (> clause-count 1) 1 0)) + suffix (cond + (> clause-count 2) (:string (:params (first (nth clauses 2)))) + (:colon params) ")") + [arg navigator] (next-arg navigator)] + (pprint-logical-block :prefix prefix :suffix suffix + (execute-sub-format + body + (init-navigator arg) + (:base-args params))) + navigator)) + +(defn- set-indent [params navigator offsets] + (let [relative-to (if (:colon params) :current :block)] + (pprint-indent relative-to (:n params)) + navigator)) + +;;; TODO: support ~:T section options for ~T +(defn- conditional-newline [params navigator offsets] + (let [kind (if (:colon params) + (if (:at params) :mandatory :fill) + (if (:at params) :miser :linear))] + (pprint-newline kind) + navigator)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; The table of directives we support, each with its params, +;;; properties, and the compilation function +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defdirectives + (\A + [:mincol [0 js/Number] :colinc [1 js/Number] :minpad [0 js/Number] :padchar [\space js/String]] + #{:at :colon :both} {} + #(format-ascii print-str %1 %2 %3)) + + (\S + [:mincol [0 js/Number] :colinc [1 js/Number] :minpad [0 js/Number] :padchar [\space js/String]] + #{:at :colon :both} {} + #(format-ascii pr-str %1 %2 %3)) + + (\D + [:mincol [0 js/Number] :padchar [\space js/String] :commachar [\, js/String] + :commainterval [3 js/Number]] + #{:at :colon :both} {} + #(format-integer 10 %1 %2 %3)) + + (\B + [:mincol [0 js/Number] :padchar [\space js/String] :commachar [\, js/String] + :commainterval [3 js/Number]] + #{:at :colon :both} {} + #(format-integer 2 %1 %2 %3)) + + (\O + [:mincol [0 js/Number] :padchar [\space js/String] :commachar [\, js/String] + :commainterval [3 js/Number]] + #{:at :colon :both} {} + #(format-integer 8 %1 %2 %3)) + + (\X + [:mincol [0 js/Number] :padchar [\space js/String] :commachar [\, js/String] + :commainterval [3 js/Number]] + #{:at :colon :both} {} + #(format-integer 16 %1 %2 %3)) + + (\R + [:base [nil js/Number] :mincol [0 js/Number] :padchar [\space js/String] :commachar [\, js/String] + :commainterval [3 js/Number]] + #{:at :colon :both} {} + (do + (cond ; ~R is overloaded with bizareness + (first (:base params)) #(format-integer (:base %1) %1 %2 %3) + (and (:at params) (:colon params)) #(format-old-roman %1 %2 %3) + (:at params) #(format-new-roman %1 %2 %3) + (:colon params) #(format-ordinal-english %1 %2 %3) + true #(format-cardinal-english %1 %2 %3)))) + + (\P + [] + #{:at :colon :both} {} + (fn [params navigator offsets] + (let [navigator (if (:colon params) (relative-reposition navigator -1) navigator) + strs (if (:at params) ["y" "ies"] ["" "s"]) + [arg navigator] (next-arg navigator)] + (print (if (= arg 1) (first strs) (second strs))) + navigator))) + + (\C + [:char-format [nil js/String]] + #{:at :colon :both} {} + (cond + (:colon params) pretty-character + (:at params) readable-character + :else plain-character)) + + (\F + [:w [nil js/Number] :d [nil js/Number] :k [0 js/Number] :overflowchar [nil js/String] + :padchar [\space js/String]] + #{:at} {} + fixed-float) + + (\E + [:w [nil js/Number] :d [nil js/Number] :e [nil js/Number] :k [1 js/Number] + :overflowchar [nil js/String] :padchar [\space js/String] + :exponentchar [nil js/String]] + #{:at} {} + exponential-float) + + (\G + [:w [nil js/Number] :d [nil js/Number] :e [nil js/Number] :k [1 js/Number] + :overflowchar [nil js/String] :padchar [\space js/String] + :exponentchar [nil js/String]] + #{:at} {} + general-float) + + (\$ + [:d [2 js/Number] :n [1 js/Number] :w [0 js/Number] :padchar [\space js/String]] + #{:at :colon :both} {} + dollar-float) + + (\% + [:count [1 js/Number]] + #{} {} + (fn [params arg-navigator offsets] + (dotimes [i (:count params)] + (prn)) + arg-navigator)) + + (\& + [:count [1 js/Number]] + #{:pretty} {} + (fn [params arg-navigator offsets] + (let [cnt (:count params)] + (if (pos? cnt) (fresh-line)) + (dotimes [i (dec cnt)] + (prn))) + arg-navigator)) + + (\| + [:count [1 js/Number]] + #{} {} + (fn [params arg-navigator offsets] + (dotimes [i (:count params)] + (print \formfeed)) + arg-navigator)) + + (\~ + [:n [1 js/Number]] + #{} {} + (fn [params arg-navigator offsets] + (let [n (:n params)] + (print (apply str (repeat n \~))) + arg-navigator))) + + (\newline ;; Whitespace supression is handled in the compilation loop + [] + #{:colon :at} {} + (fn [params arg-navigator offsets] + (if (:at params) + (prn)) + arg-navigator)) + + (\T + [:colnum [1 js/Number] :colinc [1 js/Number]] + #{:at :pretty} {} + (if (:at params) + #(relative-tabulation %1 %2 %3) + #(absolute-tabulation %1 %2 %3))) + + (\* + [:n [1 js/Number]] + #{:colon :at} {} + (fn [params navigator offsets] + (let [n (:n params)] + (if (:at params) + (absolute-reposition navigator n) + (relative-reposition navigator (if (:colon params) (- n) n)))))) + + (\? + [] + #{:at} {} + (if (:at params) + (fn [params navigator offsets] ; args from main arg list + (let [[subformat navigator] (get-format-arg navigator)] + (execute-sub-format subformat navigator (:base-args params)))) + (fn [params navigator offsets] ; args from sub-list + (let [[subformat navigator] (get-format-arg navigator) + [subargs navigator] (next-arg navigator) + sub-navigator (init-navigator subargs)] + (execute-sub-format subformat sub-navigator (:base-args params)) + navigator)))) + + (\( + [] + #{:colon :at :both} {:right \), :allows-separator nil, :else nil} + (let [mod-case-writer (cond + (and (:at params) (:colon params)) + upcase-writer + + (:colon params) + capitalize-word-writer + + (:at params) + init-cap-writer + + :else + downcase-writer)] + #(modify-case mod-case-writer %1 %2 %3))) + + (\) [] #{} {} nil) + + (\[ + [:selector [nil js/Number]] + #{:colon :at} {:right \], :allows-separator true, :else :last} + (cond + (:colon params) + boolean-conditional + + (:at params) + check-arg-conditional + + true + choice-conditional)) + + (\; [:min-remaining [nil js/Number] :max-columns [nil js/Number]] + #{:colon} {:separator true} nil) + + (\] [] #{} {} nil) + + (\{ + [:max-iterations [nil js/Number]] + #{:colon :at :both} {:right \}, :allows-separator false} + (cond + (and (:at params) (:colon params)) + iterate-main-sublists + + (:colon params) + iterate-list-of-sublists + + (:at params) + iterate-main-list + + true + iterate-sublist)) + + (\} [] #{:colon} {} nil) + + (\< + [:mincol [0 js/Number] :colinc [1 js/Number] :minpad [0 js/Number] :padchar [\space js/String]] + #{:colon :at :both :pretty} {:right \>, :allows-separator true, :else :first} + logical-block-or-justify) + + (\> [] #{:colon} {} nil) + + ;; TODO: detect errors in cases where colon not allowed + (\^ [:arg1 [nil js/Number] :arg2 [nil js/Number] :arg3 [nil js/Number]] + #{:colon} {} + (fn [params navigator offsets] + (let [arg1 (:arg1 params) + arg2 (:arg2 params) + arg3 (:arg3 params) + exit (if (:colon params) :colon-up-arrow :up-arrow)] + (cond + (and arg1 arg2 arg3) + (if (<= arg1 arg2 arg3) [exit navigator] navigator) + + (and arg1 arg2) + (if (= arg1 arg2) [exit navigator] navigator) + + arg1 + (if (= arg1 0) [exit navigator] navigator) + + true ; TODO: handle looking up the arglist stack for info + (if (if (:colon params) + (empty? (:rest (:base-args params))) + (empty? (:rest navigator))) + [exit navigator] navigator))))) + + (\W + [] + #{:at :colon :both :pretty} {} + (if (or (:at params) (:colon params)) + (let [bindings (concat + (if (:at params) [:level nil :length nil] []) + (if (:colon params) [:pretty true] []))] + (fn [params navigator offsets] + (let [[arg navigator] (next-arg navigator)] + (if (apply write arg bindings) + [:up-arrow navigator] + navigator)))) + (fn [params navigator offsets] + (let [[arg navigator] (next-arg navigator)] + (if (write-out arg) + [:up-arrow navigator] + navigator))))) + + (\_ + [] + #{:at :colon :both} {} + conditional-newline) + + (\I + [:n [0 js/Number]] + #{:colon} {} + set-indent) + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Code to manage the parameters and flags associated with each +;; directive in the format string. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^{:private true} + param-pattern #"^([vV]|#|('.)|([+-]?\d+)|(?=,))") + +(def ^{:private true} + special-params #{:parameter-from-args :remaining-arg-count}) + +(defn- extract-param [[s offset saw-comma]] + (let [m (js/RegExp. (.-source param-pattern) "g") + param (.exec m s)] + (if param + (let [token-str (first param) + remainder (subs s (.-lastIndex m)) + new-offset (+ offset (.-lastIndex m))] + (if (not (= \, (nth remainder 0))) + [[token-str offset] [remainder new-offset false]] + [[token-str offset] [(subs remainder 1) (inc new-offset) true]])) + (if saw-comma + (format-error "Badly formed parameters in format directive" offset) + [nil [s offset]])))) + +(defn- extract-params [s offset] + (consume extract-param [s offset false])) + +(defn- translate-param + "Translate the string representation of a param to the internalized + representation" + [[p offset]] + [(cond + (= (.-length p) 0) nil + (and (= (.-length p) 1) (contains? #{\v \V} (nth p 0))) :parameter-from-args + (and (= (.-length p) 1) (= \# (nth p 0))) :remaining-arg-count + (and (= (.-length p) 2) (= \' (nth p 0))) (nth p 1) + true (js/parseInt p 10)) + offset]) + +(def ^{:private true} + flag-defs {\: :colon, \@ :at}) + +(defn- extract-flags [s offset] + (consume + (fn [[s offset flags]] + (if (empty? s) + [nil [s offset flags]] + (let [flag (get flag-defs (first s))] + (if flag + (if (contains? flags flag) + (format-error + (str "Flag \"" (first s) "\" appears more than once in a directive") + offset) + [true [(subs s 1) (inc offset) (assoc flags flag [true offset])]]) + [nil [s offset flags]])))) + [s offset {}])) + +(defn- check-flags [def flags] + (let [allowed (:flags def)] + (if (and (not (:at allowed)) (:at flags)) + (format-error (str "\"@\" is an illegal flag for format directive \"" (:directive def) "\"") + (nth (:at flags) 1))) + (if (and (not (:colon allowed)) (:colon flags)) + (format-error (str "\":\" is an illegal flag for format directive \"" (:directive def) "\"") + (nth (:colon flags) 1))) + (if (and (not (:both allowed)) (:at flags) (:colon flags)) + (format-error (str "Cannot combine \"@\" and \":\" flags for format directive \"" + (:directive def) "\"") + (min (nth (:colon flags) 1) (nth (:at flags) 1)))))) + +(defn- map-params + "Takes a directive definition and the list of actual parameters and +a map of flags and returns a map of the parameters and flags with defaults +filled in. We check to make sure that there are the right types and number +of parameters as well." + [def params flags offset] + (check-flags def flags) + (if (> (count params) (count (:params def))) + (format-error + (cl-format + nil + "Too many parameters for directive \"~C\": ~D~:* ~[were~;was~:;were~] specified but only ~D~:* ~[are~;is~:;are~] allowed" + (:directive def) (count params) (count (:params def))) + (second (first params)))) + (doall + (map #(let [val (first %1)] + (if (not (or (nil? val) (contains? special-params val) + (= (second (second %2)) (type val)))) + (format-error (str "Parameter " (name (first %2)) + " has bad type in directive \"" (:directive def) "\": " + (type val)) + (second %1))) ) + params (:params def))) + + (merge ; create the result map + (into (array-map) ; start with the default values, make sure the order is right + (reverse (for [[name [default]] (:params def)] [name [default offset]]))) + (reduce #(apply assoc %1 %2) {} (filter #(first (nth % 1)) (zipmap (keys (:params def)) params))) ; add the specified parameters, filtering out nils + flags)); and finally add the flags + +(defn- compile-directive [s offset] + (let [[raw-params [rest offset]] (extract-params s offset) + [_ [rest offset flags]] (extract-flags rest offset) + directive (first rest) + def (get directive-table (string/upper-case directive)) + params (if def (map-params def (map translate-param raw-params) flags offset))] + (if (not directive) + (format-error "Format string ended in the middle of a directive" offset)) + (if (not def) + (format-error (str "Directive \"" directive "\" is undefined") offset)) + [(compiled-directive. ((:generator-fn def) params offset) def params offset) + (let [remainder (subs rest 1) + offset (inc offset) + trim? (and (= \newline (:directive def)) + (not (:colon params))) + trim-count (if trim? (prefix-count remainder [\space \tab]) 0) + remainder (subs remainder trim-count) + offset (+ offset trim-count)] + [remainder offset])])) + +(defn- compile-raw-string [s offset] + (compiled-directive. (fn [_ a _] (print s) a) nil {:string s} offset)) + +(defn- right-bracket [this] (:right (:bracket-info (:def this)))) + +(defn- separator? [this] (:separator (:bracket-info (:def this)))) + +(defn- else-separator? [this] + (and (:separator (:bracket-info (:def this))) + (:colon (:params this)))) + +(declare ^{:arglists '([bracket-info offset remainder])} collect-clauses) + +(defn- process-bracket [this remainder] + (let [[subex remainder] (collect-clauses (:bracket-info (:def this)) + (:offset this) remainder)] + [(compiled-directive. + (:func this) (:def this) + (merge (:params this) (tuple-map subex (:offset this))) + (:offset this)) + remainder])) + +(defn- process-clause [bracket-info offset remainder] + (consume + (fn [remainder] + (if (empty? remainder) + (format-error "No closing bracket found." offset) + (let [this (first remainder) + remainder (next remainder)] + (cond + (right-bracket this) + (process-bracket this remainder) + + (= (:right bracket-info) (:directive (:def this))) + [ nil [:right-bracket (:params this) nil remainder]] + + (else-separator? this) + [nil [:else nil (:params this) remainder]] + + (separator? this) + [nil [:separator nil nil remainder]] ;; TODO: check to make sure that there are no params on ~; + + true + [this remainder])))) + remainder)) + +(defn- collect-clauses [bracket-info offset remainder] + (second + (consume + (fn [[clause-map saw-else remainder]] + (let [[clause [type right-params else-params remainder]] + (process-clause bracket-info offset remainder)] + (cond + (= type :right-bracket) + [nil [(merge-with concat clause-map + {(if saw-else :else :clauses) [clause] + :right-params right-params}) + remainder]] + + (= type :else) + (cond + (:else clause-map) + (format-error "Two else clauses (\"~:;\") inside bracket construction." offset) + + (not (:else bracket-info)) + (format-error "An else clause (\"~:;\") is in a bracket type that doesn't support it." + offset) + + (and (= :first (:else bracket-info)) (seq (:clauses clause-map))) + (format-error + "The else clause (\"~:;\") is only allowed in the first position for this directive." + offset) + + true ; if the ~:; is in the last position, the else clause + ; is next, this was a regular clause + (if (= :first (:else bracket-info)) + [true [(merge-with concat clause-map {:else [clause] :else-params else-params}) + false remainder]] + [true [(merge-with concat clause-map {:clauses [clause]}) + true remainder]])) + + (= type :separator) + (cond + saw-else + (format-error "A plain clause (with \"~;\") follows an else clause (\"~:;\") inside bracket construction." offset) + + (not (:allows-separator bracket-info)) + (format-error "A separator (\"~;\") is in a bracket type that doesn't support it." + offset) + + true + [true [(merge-with concat clause-map {:clauses [clause]}) + false remainder]])))) + [{:clauses []} false remainder]))) + +(defn- process-nesting + "Take a linearly compiled format and process the bracket directives to give it + the appropriate tree structure" + [format] + (first + (consume + (fn [remainder] + (let [this (first remainder) + remainder (next remainder) + bracket (:bracket-info (:def this))] + (if (:right bracket) + (process-bracket this remainder) + [this remainder]))) + format))) + +(defn- compile-format + "Compiles format-str into a compiled format which can be used as an argument +to cl-format just like a plain format string. Use this function for improved +performance when you're using the same format string repeatedly" + [format-str] + (binding [*format-str* format-str] + (process-nesting + (first + (consume + (fn [[s offset]] + (if (empty? s) + [nil s] + (let [tilde (.indexOf s \~)] + (cond + (neg? tilde) [(compile-raw-string s offset) ["" (+ offset (.-length s))]] + (zero? tilde) (compile-directive (subs s 1) (inc offset)) + true + [(compile-raw-string (subs s 0 tilde) offset) [(subs s tilde) (+ tilde offset)]])))) + [format-str 0]))))) + +(defn- needs-pretty + "determine whether a given compiled format has any directives that depend on the +column number or pretty printing" + [format] + (loop [format format] + (if (empty? format) + false + (if (or (:pretty (:flags (:def (first format)))) + (some needs-pretty (first (:clauses (:params (first format))))) + (some needs-pretty (first (:else (:params (first format)))))) + true + (recur (next format)))))) + +;;NB We depart from the original api. In clj, if execute-format is called multiple times with the same stream or +;; called on *out*, the results are different than if the same calls are made with different streams or printing +;; to a string. The reason is that mutating the underlying stream changes the result by changing spacing. +;; +;; clj: +;; * stream => "1 2 3" +;; * true (prints to *out*) => "1 2 3" +;; * nil (prints to string) => "1 2 3" +;; cljs: +;; * stream => "1 2 3" +;; * true (prints via *print-fn*) => "1 2 3" +;; * nil (prints to string) => "1 2 3" +(defn- execute-format + "Executes the format with the arguments." + {:skip-wiki true} + ([stream format args] + (let [sb (StringBuffer.) + real-stream (if (or (not stream) (true? stream)) + (StringBufferWriter. sb) + stream) + wrapped-stream (if (and (needs-pretty format) + (not (pretty-writer? real-stream))) + (get-pretty-writer real-stream) + real-stream)] + (binding [*out* wrapped-stream] + (try + (execute-format format args) + (finally + (if-not (identical? real-stream wrapped-stream) + (-flush wrapped-stream)))) + (cond + (not stream) (str sb) + (true? stream) (string-print (str sb)) + :else nil)))) + ([format args] + (map-passing-context + (fn [element context] + (if (abort? context) + [nil context] + (let [[params args] (realize-parameter-list + (:params element) context) + [params offsets] (unzip-map params) + params (assoc params :base-args args)] + [nil (apply (:func element) [params args offsets])]))) + args + format) + nil)) + +;;; This is a bad idea, but it prevents us from leaking private symbols +;;; This should all be replaced by really compiled formats anyway. +(def ^{:private true} cached-compile (memoize compile-format)) + +;;====================================================================== +;; dispatch.clj +;;====================================================================== + +(defn- use-method + "Installs a function as a new method of multimethod associated with dispatch-value. " + [multifn dispatch-val func] + (-add-method multifn dispatch-val func)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Implementations of specific dispatch table entries +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; Handle forms that can be "back-translated" to reader macros +;;; Not all reader macros can be dealt with this way or at all. +;;; Macros that we can't deal with at all are: +;;; ; - The comment character is absorbed by the reader and never is part of the form +;;; ` - Is fully processed at read time into a lisp expression (which will contain concats +;;; and regular quotes). +;;; ~@ - Also fully eaten by the processing of ` and can't be used outside. +;;; , - is whitespace and is lost (like all other whitespace). Formats can generate commas +;;; where they deem them useful to help readability. +;;; ^ - Adding metadata completely disappears at read time and the data appears to be +;;; completely lost. +;;; +;;; Most other syntax stuff is dealt with directly by the formats (like (), [], {}, and #{}) +;;; or directly by printing the objects using Clojure's built-in print functions (like +;;; :keyword, \char, or ""). The notable exception is #() which is special-cased. + +(def ^{:private true} reader-macros + {'quote "'" + 'var "#'" + 'clojure.core/deref "@", + 'clojure.core/unquote "~" + 'cljs.core/deref "@", + 'cljs.core/unquote "~"}) + +(defn- pprint-reader-macro [alis] + (let [macro-char (reader-macros (first alis))] + (when (and macro-char (= 2 (count alis))) + (-write *out* macro-char) + (write-out (second alis)) + true))) + +;;====================================================================== +;; Dispatch for the basic data types when interpreted +;; as data (as opposed to code). +;;====================================================================== + +;;; TODO: inline these formatter statements into funcs so that we +;;; are a little easier on the stack. (Or, do "real" compilation, a +;;; la Common Lisp) + +;;; (def pprint-simple-list (formatter-out "~:<~@{~w~^ ~_~}~:>")) +(defn- pprint-simple-list [alis] + (pprint-logical-block :prefix "(" :suffix ")" + (print-length-loop [alis (seq alis)] + (when alis + (write-out (first alis)) + (when (next alis) + (-write *out* " ") + (pprint-newline :linear) + (recur (next alis))))))) + +(defn- pprint-list [alis] + (if-not (pprint-reader-macro alis) + (pprint-simple-list alis))) + +;;; (def pprint-vector (formatter-out "~<[~;~@{~w~^ ~_~}~;]~:>")) +(defn- pprint-vector [avec] + (pprint-logical-block :prefix "[" :suffix "]" + (print-length-loop [aseq (seq avec)] + (when aseq + (write-out (first aseq)) + (when (next aseq) + (-write *out* " ") + (pprint-newline :linear) + (recur (next aseq))))))) + +(def ^{:private true} pprint-array (formatter-out "~<[~;~@{~w~^, ~:_~}~;]~:>")) + +;;; (def pprint-map (formatter-out "~<{~;~@{~<~w~^ ~_~w~:>~^, ~_~}~;}~:>")) +(defn- pprint-map [amap] + (let [[ns lift-map] (when (not (record? amap)) + (#'cljs.core/lift-ns amap)) + amap (or lift-map amap) + prefix (if ns (str "#:" ns "{") "{")] + (pprint-logical-block :prefix prefix :suffix "}" + (print-length-loop [aseq (seq amap)] + (when aseq + ;;compiler gets confused with nested macro if it isn't namespaced + ;;it tries to use clojure.pprint/pprint-logical-block for some reason + (m/pprint-logical-block + (write-out (ffirst aseq)) + (-write *out* " ") + (pprint-newline :linear) + (set! *current-length* 0) ;always print both parts of the [k v] pair + (write-out (fnext (first aseq)))) + (when (next aseq) + (-write *out* ", ") + (pprint-newline :linear) + (recur (next aseq)))))))) + +(defn- pprint-simple-default [obj] + ;;TODO: Update to handle arrays (?) and suppressing namespaces + (-write *out* (pr-str obj))) + +(def pprint-set (formatter-out "~<#{~;~@{~w~^ ~:_~}~;}~:>")) + +(def ^{:private true} +type-map {"core$future_call" "Future", + "core$promise" "Promise"}) + +(defn- map-ref-type + "Map ugly type names to something simpler" + [name] + (or (when-let [match (re-find #"^[^$]+\$[^$]+" name)] + (type-map match)) + name)) + +(defn- pprint-ideref [o] + (let [prefix (str "#<" (map-ref-type (.-name (type o))) + "@" (goog/getUid o) ": ")] + (pprint-logical-block :prefix prefix :suffix ">" + (pprint-indent :block (-> (count prefix) (- 2) -)) + (pprint-newline :linear) + (write-out + (if (and (satisfies? IPending o) (not (-realized? o))) + :not-delivered + @o))))) + +(def ^{:private true} pprint-pqueue (formatter-out "~<<-(~;~@{~w~^ ~_~}~;)-<~:>")) + +(defn- type-dispatcher [obj] + (cond + (instance? PersistentQueue obj) :queue + (satisfies? IDeref obj) :deref + (symbol? obj) :symbol + (seq? obj) :list + (map? obj) :map + (vector? obj) :vector + (set? obj) :set + (nil? obj) nil + :default :default)) + +(defmulti simple-dispatch + "The pretty print dispatch function for simple data structure format." + type-dispatcher) + +(use-method simple-dispatch :list pprint-list) +(use-method simple-dispatch :vector pprint-vector) +(use-method simple-dispatch :map pprint-map) +(use-method simple-dispatch :set pprint-set) +(use-method simple-dispatch nil #(-write *out* (pr-str nil))) +(use-method simple-dispatch :default pprint-simple-default) + +(set-pprint-dispatch simple-dispatch) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Dispatch for the code table +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(declare ^{:arglists '([alis])} pprint-simple-code-list) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Format the namespace ("ns") macro. This is quite complicated because of all the +;;; different forms supported and because programmers can choose lists or vectors +;;; in various places. +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- brackets + "Figure out which kind of brackets to use" + [form] + (if (vector? form) + ["[" "]"] + ["(" ")"])) + +(defn- pprint-ns-reference + "Pretty print a single reference (import, use, etc.) from a namespace decl" + [reference] + (if (sequential? reference) + (let [[start end] (brackets reference) + [keyw & args] reference] + (pprint-logical-block :prefix start :suffix end + ((formatter-out "~w~:i") keyw) + (loop [args args] + (when (seq args) + ((formatter-out " ")) + (let [arg (first args)] + (if (sequential? arg) + (let [[start end] (brackets arg)] + (pprint-logical-block :prefix start :suffix end + (if (and (= (count arg) 3) (keyword? (second arg))) + (let [[ns kw lis] arg] + ((formatter-out "~w ~w ") ns kw) + (if (sequential? lis) + ((formatter-out (if (vector? lis) + "~<[~;~@{~w~^ ~:_~}~;]~:>" + "~<(~;~@{~w~^ ~:_~}~;)~:>")) + lis) + (write-out lis))) + (apply (formatter-out "~w ~:i~@{~w~^ ~:_~}") arg))) + (when (next args) + ((formatter-out "~_")))) + (do + (write-out arg) + (when (next args) + ((formatter-out "~:_")))))) + (recur (next args)))))) + (write-out reference))) + +(defn- pprint-ns + "The pretty print dispatch chunk for the ns macro" + [alis] + (if (next alis) + (let [[ns-sym ns-name & stuff] alis + [doc-str stuff] (if (string? (first stuff)) + [(first stuff) (next stuff)] + [nil stuff]) + [attr-map references] (if (map? (first stuff)) + [(first stuff) (next stuff)] + [nil stuff])] + (pprint-logical-block :prefix "(" :suffix ")" + ((formatter-out "~w ~1I~@_~w") ns-sym ns-name) + (when (or doc-str attr-map (seq references)) + ((formatter-out "~@:_"))) + (when doc-str + (cl-format true "\"~a\"~:[~;~:@_~]" doc-str (or attr-map (seq references)))) + (when attr-map + ((formatter-out "~w~:[~;~:@_~]") attr-map (seq references))) + (loop [references references] + (pprint-ns-reference (first references)) + (when-let [references (next references)] + (pprint-newline :linear) + (recur references))))) + (write-out alis))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Format something that looks like a simple def (sans metadata, since the reader +;;; won't give it to us now). +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^{:private true} pprint-hold-first (formatter-out "~:<~w~^ ~@_~w~^ ~_~@{~w~^ ~_~}~:>")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Format something that looks like a defn or defmacro +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; Format the params and body of a defn with a single arity +(defn- single-defn [alis has-doc-str?] + (if (seq alis) + (do + (if has-doc-str? + ((formatter-out " ~_")) + ((formatter-out " ~@_"))) + ((formatter-out "~{~w~^ ~_~}") alis)))) + +;;; Format the param and body sublists of a defn with multiple arities +(defn- multi-defn [alis has-doc-str?] + (if (seq alis) + ((formatter-out " ~_~{~w~^ ~_~}") alis))) + +;;; TODO: figure out how to support capturing metadata in defns (we might need a +;;; special reader) +(defn- pprint-defn [alis] + (if (next alis) + (let [[defn-sym defn-name & stuff] alis + [doc-str stuff] (if (string? (first stuff)) + [(first stuff) (next stuff)] + [nil stuff]) + [attr-map stuff] (if (map? (first stuff)) + [(first stuff) (next stuff)] + [nil stuff])] + (pprint-logical-block :prefix "(" :suffix ")" + ((formatter-out "~w ~1I~@_~w") defn-sym defn-name) + (if doc-str + ((formatter-out " ~_~w") doc-str)) + (if attr-map + ((formatter-out " ~_~w") attr-map)) + ;; Note: the multi-defn case will work OK for malformed defns too + (cond + (vector? (first stuff)) (single-defn stuff (or doc-str attr-map)) + :else (multi-defn stuff (or doc-str attr-map))))) + (pprint-simple-code-list alis))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Format something with a binding form +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- pprint-binding-form [binding-vec] + (pprint-logical-block :prefix "[" :suffix "]" + (print-length-loop [binding binding-vec] + (when (seq binding) + (pprint-logical-block binding + (write-out (first binding)) + (when (next binding) + (-write *out* " ") + (pprint-newline :miser) + (write-out (second binding)))) + (when (next (rest binding)) + (-write *out* " ") + (pprint-newline :linear) + (recur (next (rest binding)))))))) + +(defn- pprint-let [alis] + (let [base-sym (first alis)] + (pprint-logical-block :prefix "(" :suffix ")" + (if (and (next alis) (vector? (second alis))) + (do + ((formatter-out "~w ~1I~@_") base-sym) + (pprint-binding-form (second alis)) + ((formatter-out " ~_~{~w~^ ~_~}") (next (rest alis)))) + (pprint-simple-code-list alis))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Format something that looks like "if" +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^{:private true} pprint-if (formatter-out "~:<~1I~w~^ ~@_~w~@{ ~_~w~}~:>")) + +(defn- pprint-cond [alis] + (pprint-logical-block :prefix "(" :suffix ")" + (pprint-indent :block 1) + (write-out (first alis)) + (when (next alis) + (-write *out* " ") + (pprint-newline :linear) + (print-length-loop [alis (next alis)] + (when alis + (pprint-logical-block alis + (write-out (first alis)) + (when (next alis) + (-write *out* " ") + (pprint-newline :miser) + (write-out (second alis)))) + (when (next (rest alis)) + (-write *out* " ") + (pprint-newline :linear) + (recur (next (rest alis))))))))) + +(defn- pprint-condp [alis] + (if (> (count alis) 3) + (pprint-logical-block :prefix "(" :suffix ")" + (pprint-indent :block 1) + (apply (formatter-out "~w ~@_~w ~@_~w ~_") alis) + (print-length-loop [alis (seq (drop 3 alis))] + (when alis + (pprint-logical-block alis + (write-out (first alis)) + (when (next alis) + (-write *out* " ") + (pprint-newline :miser) + (write-out (second alis)))) + (when (next (rest alis)) + (-write *out* " ") + (pprint-newline :linear) + (recur (next (rest alis))))))) + (pprint-simple-code-list alis))) + +;;; The map of symbols that are defined in an enclosing #() anonymous function +(def ^:dynamic ^{:private true} *symbol-map* {}) + +(defn- pprint-anon-func [alis] + (let [args (second alis) + nlis (first (rest (rest alis)))] + (if (vector? args) + (binding [*symbol-map* (if (= 1 (count args)) + {(first args) "%"} + (into {} + (map + #(vector %1 (str \% %2)) + args + (range 1 (inc (count args))))))] + ((formatter-out "~<#(~;~@{~w~^ ~_~}~;)~:>") nlis)) + (pprint-simple-code-list alis)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; The master definitions for formatting lists in code (that is, (fn args...) or +;;; special forms). +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;; This is the equivalent of (formatter-out "~:<~1I~@{~w~^ ~_~}~:>"), but is +;;; easier on the stack. + +(defn- pprint-simple-code-list [alis] + (pprint-logical-block :prefix "(" :suffix ")" + (pprint-indent :block 1) + (print-length-loop [alis (seq alis)] + (when alis + (write-out (first alis)) + (when (next alis) + (-write *out* " ") + (pprint-newline :linear) + (recur (next alis))))))) + +;;; Take a map with symbols as keys and add versions with no namespace. +;;; That is, if ns/sym->val is in the map, add sym->val to the result. +(defn- two-forms [amap] + (into {} + (mapcat + identity + (for [x amap] + [x [(symbol (name (first x))) (second x)]])))) + +(defn- add-core-ns [amap] + (let [core "clojure.core"] + (into {} + (map #(let [[s f] %] + (if (not (or (namespace s) (special-symbol? s))) + [(symbol core (name s)) f] + %)) + amap)))) + +(def ^:dynamic ^{:private true} *code-table* + (two-forms + (add-core-ns + {'def pprint-hold-first, 'defonce pprint-hold-first, + 'defn pprint-defn, 'defn- pprint-defn, 'defmacro pprint-defn, 'fn pprint-defn, + 'let pprint-let, 'loop pprint-let, 'binding pprint-let, + 'with-local-vars pprint-let, 'with-open pprint-let, 'when-let pprint-let, + 'if-let pprint-let, 'doseq pprint-let, 'dotimes pprint-let, + 'when-first pprint-let, + 'if pprint-if, 'if-not pprint-if, 'when pprint-if, 'when-not pprint-if, + 'cond pprint-cond, 'condp pprint-condp, + 'fn* pprint-anon-func, + '. pprint-hold-first, '.. pprint-hold-first, '-> pprint-hold-first, + 'locking pprint-hold-first, 'struct pprint-hold-first, + 'struct-map pprint-hold-first, 'ns pprint-ns + }))) + +(defn- pprint-code-list [alis] + (if-not (pprint-reader-macro alis) + (if-let [special-form (*code-table* (first alis))] + (special-form alis) + (pprint-simple-code-list alis)))) + +(defn- pprint-code-symbol [sym] + (if-let [arg-num (sym *symbol-map*)] + (print arg-num) + (if *print-suppress-namespaces* + (print (name sym)) + (pr sym)))) + +(defmulti + code-dispatch + "The pretty print dispatch function for pretty printing Clojure code." + {:added "1.2" :arglists '[[object]]} + type-dispatcher) + +(use-method code-dispatch :list pprint-code-list) +(use-method code-dispatch :symbol pprint-code-symbol) + +;; The following are all exact copies of simple-dispatch +(use-method code-dispatch :vector pprint-vector) +(use-method code-dispatch :map pprint-map) +(use-method code-dispatch :set pprint-set) +(use-method code-dispatch :queue pprint-pqueue) +(use-method code-dispatch :deref pprint-ideref) +(use-method code-dispatch nil pr) +(use-method code-dispatch :default pprint-simple-default) + +(set-pprint-dispatch simple-dispatch) + +;;; For testing +(comment + + (with-pprint-dispatch code-dispatch + (pprint + '(defn cl-format + "An implementation of a Common Lisp compatible format function" + [stream format-in & args] + (let [compiled-format (if (string? format-in) (compile-format format-in) format-in) + navigator (init-navigator args)] + (execute-format stream compiled-format navigator))))) + + (with-pprint-dispatch code-dispatch + (pprint + '(defn cl-format + [stream format-in & args] + (let [compiled-format (if (string? format-in) (compile-format format-in) format-in) + navigator (init-navigator args)] + (execute-format stream compiled-format navigator))))) + + (with-pprint-dispatch code-dispatch + (pprint + '(defn- -write + ([this x] + (condp = (class x) + String + (let [s0 (write-initial-lines this x) + s (.replaceFirst s0 "\\s+$" "") + white-space (.substring s0 (count s)) + mode (getf :mode)] + (if (= mode :writing) + (dosync + (write-white-space this) + (.col_write this s) + (setf :trailing-white-space white-space)) + (add-to-buffer this (make-buffer-blob s white-space)))) + + Integer + (let [c ^Character x] + (if (= (getf :mode) :writing) + (do + (write-white-space this) + (.col_write this x)) + (if (= c (int \newline)) + (write-initial-lines this "\n") + (add-to-buffer this (make-buffer-blob (str (char c)) nil)))))))))) + + (with-pprint-dispatch code-dispatch + (pprint + '(defn pprint-defn [writer alis] + (if (next alis) + (let [[defn-sym defn-name & stuff] alis + [doc-str stuff] (if (string? (first stuff)) + [(first stuff) (next stuff)] + [nil stuff]) + [attr-map stuff] (if (map? (first stuff)) + [(first stuff) (next stuff)] + [nil stuff])] + (pprint-logical-block writer :prefix "(" :suffix ")" + (cl-format true "~w ~1I~@_~w" defn-sym defn-name) + (if doc-str + (cl-format true " ~_~w" doc-str)) + (if attr-map + (cl-format true " ~_~w" attr-map)) + ;; Note: the multi-defn case will work OK for malformed defns too + (cond + (vector? (first stuff)) (single-defn stuff (or doc-str attr-map)) + :else (multi-defn stuff (or doc-str attr-map))))) + (pprint-simple-code-list writer alis))))) + ) + +;;====================================================================== +;; print_table.clj +;;====================================================================== + +(defn- add-padding [width s] + (let [padding (max 0 (- width (count s)))] + (apply str (clojure.string/join (repeat padding \space)) s))) + +(defn print-table + "Prints a collection of maps in a textual table. Prints table headings + ks, and then a line of output for each row, corresponding to the keys + in ks. If ks are not specified, use the keys of the first item in rows." + {:added "1.3"} + ([ks rows] + (when (seq rows) + (let [widths (map + (fn [k] + (apply max (count (str k)) (map #(count (str (get % k))) rows))) + ks) + spacers (map #(apply str (repeat % "-")) widths) + fmt-row (fn [leader divider trailer row] + (str leader + (apply str (interpose divider + (for [[col width] (map vector (map #(get row %) ks) widths)] + (add-padding width (str col))))) + trailer))] + (cljs.core/println) + (cljs.core/println (fmt-row "| " " | " " |" (zipmap ks ks))) + (cljs.core/println (fmt-row "|-" "-+-" "-|" (zipmap ks spacers))) + (doseq [row rows] + (cljs.core/println (fmt-row "| " " | " " |" row)))))) + ([rows] (print-table (keys (first rows)) rows))) diff --git a/src/main/cljs/cljs/proxy.cljs b/src/main/cljs/cljs/proxy.cljs new file mode 100644 index 0000000000..23a85a9f77 --- /dev/null +++ b/src/main/cljs/cljs/proxy.cljs @@ -0,0 +1,184 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.proxy + (:refer-global :only [isNaN Proxy Symbol]) + (:require [cljs.proxy.impl :refer [SimpleCache MapIterator]])) + +(defn- write-through [f] + (let [cache (SimpleCache. #js {} 0)] + (fn [x] + (let [v (.get cache x)] + (if (some? v) + v + (.set cache x (f x))))))) + +(def ^{:private true} + desc + #js {:configurable true + :enumerable true}) + +(defn builder + "EXPERIMENTAL: Returns a JavaScript Proxy ctor fn with the provided + key-fn. Invoking the returned fn on ClojureScript maps and vectors + will returned proxied values that can be used transparently as + JavaScript objects and arrays: + + (def proxy (builder)) + + (def proxied-map (proxy {:foo 1 :bar 2})) + (goog.object/get proxied-map \"foo\") ;; => 1 + + (def proxied-vec (proxy [1 2 3 4])) + (aget proxied-vec 1) ;; => 2 + + Access patterns from JavaScript on these proxied values will lazily, + recursively return further proxied values: + + (def nested-proxies (proxy [{:foo 1 :bar 2}])) + (-> nested-proxies (aget 0) (goog.object/get \"foo\")) ;; => 1 + + Note key-fn is only used for proxied ClojureScript maps. This + function should map strings to the appropriate key + representation. If unspecified, key-fn defaults to keyword. All maps + proxied from the same ctor fn will share the same key-fn cache. + + A cache-fn may be suppled to override the default cache. This fn + should take key-fn and return a memoized version." + ([] + (builder keyword)) + ([key-fn] + (builder keyword write-through)) + ([key-fn cache-fn] + (js* "var __ctor") + (let [cache-key-fn (cache-fn key-fn) + vec-handler #js {:get (fn [^cljs.core/IIndexed target prop receiver] + (cond + (identical? "length" prop) + (-count ^cljs.core/ICounted target) + + (identical? (. Symbol -iterator) prop) + (fn [] + (MapIterator. + ((.bind (unchecked-get target prop) target)) js/__ctor)) + + :else + (let [n (js* "+~{}" prop)] + (when (and (number? n) + (not (isNaN n))) + (js/__ctor (-nth target n nil)))))) + + :has (fn [^cljs.core/IAssociative target prop] + (cond + (identical? prop "length") true + + (identical? (. Symbol -iterator) prop) true + + :else + (let [n (js* "+~{}" prop)] + (and (number? n) + (not (isNaN n)) + (<= 0 n) + (< n (-count ^cljs.core/ICounted target)))))) + + :getPrototypeOf + (fn [target] nil) + + :ownKeys + (fn [target] #js ["length"]) + + :getOwnPropertyDescriptor + (fn [target prop] desc)} + map-handler #js {:get (fn [^cljs.core/ILookup target prop receiver] + (js/__ctor (-lookup target (cache-key-fn prop)))) + + :has (fn [^cljs.core/IAssociative target prop] + (-contains-key? target (cache-key-fn prop))) + + :getPrototypeOf + (fn [target] nil) + + :ownKeys + (fn [target] + (when (nil? (.-cljs$cachedOwnKeys target)) + (set! (. target -cljs$cachedOwnKeys) + (into-array (map -name (keys target))))) + (.-cljs$cachedOwnKeys target)) + + :getOwnPropertyDescriptor + (fn [target prop] desc)} + __ctor (fn [target] + (cond + (implements? IMap target) (Proxy. target map-handler) + (implements? IVector target) (Proxy. target vec-handler) + :else target))] + __ctor))) + +(def ^{:doc "Default proxy for maps and vectors."} + proxy (builder)) + +(comment + + (def c (SimpleCache. #js {} 0)) + (.set c "foo" :foo) + (.get c "foo") + (.-cnt c) + (.clear c) + (.get c "foo") + + (def kw (write-through keyword)) + (kw "foo") + + (time + (dotimes [i 1e6] + (kw "foo"))) + + (time + (dotimes [i 1e6] + (keyword "foo"))) + + (def proxy (builder)) + + (def raw-map {:foo 1 :bar 2}) + (def proxied-map (proxy {:foo 1 :bar 2})) + + (require '[goog.object :as gobj]) + (gobj/get proxied-map "foo") + (gobj/get proxied-map "bar") + (gobj/getKeys proxied-map) + (.keys js/Object proxied-map) + + (time + (dotimes [i 1e7] + (unchecked-get proxied-map "foo"))) + + (def k :foo) + (time + (dotimes [i 1e7] + (get raw-map k))) + + (def proxied-vec (proxy [1 2 3 4])) + (alength proxied-vec) + (time + (dotimes [i 1e6] + (alength proxied-vec))) + + (nth [1 2 3 4] 1) + + (aget proxied-vec 1) + + (time + (dotimes [i 1e7] + (aget proxied-vec 1))) + + (def proxied-deep (proxy [{:foo "Hello"}])) + (-> proxied-deep (aget 0) (unchecked-get "foo")) + + (aget ((cljs.proxy/builder) [{}]) 0) + +) diff --git a/src/main/cljs/cljs/proxy/impl.cljs b/src/main/cljs/cljs/proxy/impl.cljs new file mode 100644 index 0000000000..c476529314 --- /dev/null +++ b/src/main/cljs/cljs/proxy/impl.cljs @@ -0,0 +1,32 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.proxy.impl) + +(deftype SimpleCache [^:mutable obj ^:mutable cnt] + Object + (set [this k v] + (when (== cnt 1024) + (.clear this)) + (unchecked-set obj k v) + (set! cnt (inc cnt)) + v) + (get [this k] + (unchecked-get obj k)) + (clear [this] + (set! obj #js {}) + (set! cnt 0))) + +(deftype MapIterator [^:mutable iter f] + Object + (next [_] + (let [x (.next iter)] + (if-not ^boolean (. x -done) + #js {:value (f (. x -value)) + :done false} + x)))) diff --git a/src/main/cljs/cljs/reader.clj b/src/main/cljs/cljs/reader.clj new file mode 100644 index 0000000000..09afd3a533 --- /dev/null +++ b/src/main/cljs/cljs/reader.clj @@ -0,0 +1,18 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.reader + (:require [cljs.env :as env])) + +(defmacro add-data-readers [default-readers] + (let [data-readers + (->> (get @env/*compiler* :cljs.analyzer/data-readers) + (map (fn [[k v]] + `['~k (fn [x#] (~(vary-meta (-> v meta :sym) assoc :cljs.analyzer/no-resolve true) x#))])) + (into {}))] + `(do (merge ~default-readers ~data-readers)))) diff --git a/src/main/cljs/cljs/reader.cljs b/src/main/cljs/cljs/reader.cljs new file mode 100644 index 0000000000..964f6be313 --- /dev/null +++ b/src/main/cljs/cljs/reader.cljs @@ -0,0 +1,214 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.reader + (:require-macros [cljs.reader :refer [add-data-readers]]) + (:require [goog.object :as gobject] + [cljs.tools.reader :as treader] + [cljs.tools.reader.edn :as edn]) + (:import [goog.string StringBuffer])) + +(defn ^:private zero-fill-right-and-truncate [s width] + (cond + (= width (count s)) s + (< width (count s)) (subs s 0 width) + :else + (loop [b (StringBuffer. s)] + (if (< (.getLength b) width) + (recur (.append b "0")) + (.toString b))))) + +(defn ^:private divisible? + [num div] + (zero? (mod num div))) + +(defn ^:private indivisible? + [num div] + (not (divisible? num div))) + +(defn ^:private leap-year? + [year] + (and (divisible? year 4) + (or (indivisible? year 100) + (divisible? year 400)))) + +(def ^:private days-in-month + (let [dim-norm [nil 31 28 31 30 31 30 31 31 30 31 30 31] + dim-leap [nil 31 29 31 30 31 30 31 31 30 31 30 31]] + (fn [month leap-year?] + (get (if leap-year? dim-leap dim-norm) month)))) + +(def ^:private timestamp-regex #"(\d\d\d\d)(?:-(\d\d)(?:-(\d\d)(?:[T](\d\d)(?::(\d\d)(?::(\d\d)(?:[.](\d+))?)?)?)?)?)?(?:[Z]|([-+])(\d\d):(\d\d))?") + +(defn ^:private parse-int [s] + (let [n (js/parseInt s 10)] + (if-not (js/isNaN n) + n))) + +(defn ^:private check [low n high msg] + (when-not (<= low n high) + (throw (js/Error. (str msg " Failed: " low "<=" n "<=" high)))) + n) + +(defn parse-and-validate-timestamp [s] + (let [[_ years months days hours minutes seconds fraction offset-sign offset-hours offset-minutes :as v] + (re-matches timestamp-regex s)] + (if-not v + (throw (js/Error. (str "Unrecognized date/time syntax: " s))) + (let [years (parse-int years) + months (or (parse-int months) 1) + days (or (parse-int days) 1) + hours (or (parse-int hours) 0) + minutes (or (parse-int minutes) 0) + seconds (or (parse-int seconds) 0) + fraction (or (parse-int (zero-fill-right-and-truncate fraction 3)) 0) + offset-sign (if (= offset-sign "-") -1 1) + offset-hours (or (parse-int offset-hours) 0) + offset-minutes (or (parse-int offset-minutes) 0) + offset (* offset-sign (+ (* offset-hours 60) offset-minutes))] + [years + (check 1 months 12 "timestamp month field must be in range 1..12") + (check 1 days (days-in-month months (leap-year? years)) "timestamp day field must be in range 1..last day in month") + (check 0 hours 23 "timestamp hour field must be in range 0..23") + (check 0 minutes 59 "timestamp minute field must be in range 0..59") + (check 0 seconds (if (= minutes 59) 60 59) "timestamp second field must be in range 0..60") + (check 0 fraction 999 "timestamp millisecond field must be in range 0..999") + offset])))) + +(defn parse-timestamp + [ts] + (if-let [[years months days hours minutes seconds ms offset] + (parse-and-validate-timestamp ts)] + (js/Date. + (- (.UTC js/Date years (dec months) days hours minutes seconds ms) + (* offset 60 1000))) + (throw (js/Error. (str "Unrecognized date/time syntax: " ts))))) + +(defn ^:private read-date + [s] + (if (string? s) + (parse-timestamp s) + (throw (js/Error. "Instance literal expects a string for its timestamp.")))) + +(defn ^:private read-queue + [elems] + (if (vector? elems) + (into cljs.core/PersistentQueue.EMPTY elems) + (throw (js/Error. "Queue literal expects a vector for its elements.")))) + +(defn ^:private read-js + [form] + (cond + (vector? form) + (let [arr (array)] + (doseq [x form] + (.push arr x)) + arr) + + (map? form) + (let [obj (js-obj)] + (doseq [[k v] form] + (gobject/set obj (name k) v)) + obj) + + :else + (throw + (js/Error. + (str "JS literal expects a vector or map containing " + "only string or unqualified keyword keys"))))) + +(defn ^:private read-uuid + [uuid] + (if (string? uuid) + (cljs.core/uuid uuid) + (throw (js/Error. "UUID literal expects a string as its representation.")))) + +(def ^:dynamic *default-data-reader-fn* + (atom nil)) + +(def ^:dynamic *tag-table* + (atom + (add-data-readers + {'inst read-date + 'uuid read-uuid + 'queue read-queue + 'js read-js}))) + +(defn read + "Reads the first object from an cljs.tools.reader.reader-types/IPushbackReader. + Returns the object read. If EOF, throws if eof-error? is true otherwise returns eof. + If no reader is provided, *in* will be used. + + Reads data in the edn format (subset of Clojure data): + http://edn-format.org + + cljs.tools.reader.edn/read doesn't depend on dynamic Vars, all configuration + is done by passing an opt map. + + opts is a map that can include the following keys: + :eof - value to return on end-of-file. When not supplied, eof throws an exception. + :readers - a map of tag symbols to data-reader functions to be considered before default-data-readers. + When not supplied, only the default-data-readers will be used. + :default - A function of two args, that will, if present and no reader is found for a tag, + be called with the tag and the value." + ([reader] + (edn/read + {:readers @*tag-table* + :default @*default-data-reader-fn* + :eof nil} + reader)) + ([{:keys [eof] :as opts} reader] + (edn/read + (update (merge opts {:default @*default-data-reader-fn*}) + :readers (fn [m] (merge @*tag-table* m))) reader)) + ([reader eof-error? eof opts] + (edn/read reader eof-error? eof + (update (merge opts {:default @*default-data-reader-fn*}) + :readers (fn [m] (merge @*tag-table* m)))))) + +(defn read-string + "Reads one object from the string s. + Returns nil when s is nil or empty. + + Reads data in the edn format (subset of Clojure data): + http://edn-format.org + + opts is a map as per cljs.tools.reader.edn/read" + ([s] + (edn/read-string + {:readers @*tag-table* + :default @*default-data-reader-fn* + :eof nil} s)) + ([opts s] + (edn/read-string + (update (merge {:default @*default-data-reader-fn*} opts) + :readers (fn [m] (merge @*tag-table* m))) s))) + +(defn register-tag-parser! + [tag f] + (let [old-parser (get @*tag-table* tag)] + (swap! *tag-table* assoc tag f) + old-parser)) + +(defn deregister-tag-parser! + [tag] + (let [old-parser (get @*tag-table* tag)] + (swap! *tag-table* dissoc tag) + old-parser)) + +(defn register-default-tag-parser! + [f] + (let [old-parser @*default-data-reader-fn*] + (swap! *default-data-reader-fn* (fn [_] f)) + old-parser)) + +(defn deregister-default-tag-parser! + [] + (let [old-parser @*default-data-reader-fn*] + (swap! *default-data-reader-fn* (fn [_] nil)) + old-parser)) diff --git a/src/main/cljs/cljs/repl.cljs b/src/main/cljs/cljs/repl.cljs new file mode 100644 index 0000000000..16116ea468 --- /dev/null +++ b/src/main/cljs/cljs/repl.cljs @@ -0,0 +1,206 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.repl + (:require-macros cljs.repl) + (:require [cljs.spec.alpha :as spec] + [goog.string :as gstring] + [goog.string.format])) + +(defn print-doc [{n :ns nm :name :as m}] + (println "-------------------------") + (println (or (:spec m) (str (when-let [ns (:ns m)] (str ns "/")) (:name m)))) + (when (:protocol m) + (println "Protocol")) + (cond + (:forms m) (doseq [f (:forms m)] + (println " " f)) + (:arglists m) (let [arglists (:arglists m)] + (if (or (:macro m) + (:repl-special-function m)) + (prn arglists) + (prn + (if (= 'quote (first arglists)) + (second arglists) + arglists))))) + (if (:special-form m) + (do + (println "Special Form") + (println " " (:doc m)) + (if (contains? m :url) + (when (:url m) + (println (str "\n Please see http://clojure.org/" (:url m)))) + (println (str "\n Please see http://clojure.org/special_forms#" + (:name m))))) + (do + (when (:macro m) + (println "Macro")) + (when (:spec m) + (println "Spec")) + (when (:repl-special-function m) + (println "REPL Special Function")) + (println " " (:doc m)) + (when (:protocol m) + (doseq [[name {:keys [doc arglists]}] (:methods m)] + (println) + (println " " name) + (println " " arglists) + (when doc + (println " " doc)))) + (when n + (when-let [fnspec (spec/get-spec (symbol (str (ns-name n)) (name nm)))] + (print "Spec") + (doseq [role [:args :ret :fn]] + (when-let [spec (get fnspec role)] + (print (str "\n " (name role) ":") (spec/describe spec))))))))) + +(defn Error->map + "Constructs a data representation for a Error with keys: + :cause - root cause message + :phase - error phase + :via - cause chain, with cause keys: + :type - exception class symbol + :message - exception message + :data - ex-data + :at - top stack element + :trace - root cause stack elements" + [o] + (Throwable->map o)) + +(defn ex-triage + "Returns an analysis of the phase, error, cause, and location of an error that occurred + based on Throwable data, as returned by Throwable->map. All attributes other than phase + are optional: + :clojure.error/phase - keyword phase indicator, one of: + :read-source :compile-syntax-check :compilation :macro-syntax-check :macroexpansion + :execution :read-eval-result :print-eval-result + :clojure.error/source - file name (no path) + :clojure.error/line - integer line number + :clojure.error/column - integer column number + :clojure.error/symbol - symbol being expanded/compiled/invoked + :clojure.error/class - cause exception class symbol + :clojure.error/cause - cause exception message + :clojure.error/spec - explain-data for spec error" + [datafied-throwable] + (let [{:keys [via trace phase] :or {phase :execution}} datafied-throwable + {:keys [type message data]} (last via) + {:cljs.spec.alpha/keys [problems fn] :cljs.spec.test.alpha/keys [caller]} data + {:keys [:clojure.error/source] :as top-data} (:data (first via))] + (assoc + (case phase + :read-source + (let [{:keys [:clojure.error/line :clojure.error/column]} data] + (cond-> (merge (-> via second :data) top-data) + source (assoc :clojure.error/source source) + (#{"NO_SOURCE_FILE" "NO_SOURCE_PATH"} source) (dissoc :clojure.error/source) + message (assoc :clojure.error/cause message))) + + (:compile-syntax-check :compilation :macro-syntax-check :macroexpansion) + (cond-> top-data + source (assoc :clojure.error/source source) + (#{"NO_SOURCE_FILE" "NO_SOURCE_PATH"} source) (dissoc :clojure.error/source) + type (assoc :clojure.error/class type) + message (assoc :clojure.error/cause message) + problems (assoc :clojure.error/spec data)) + + (:read-eval-result :print-eval-result) + (let [[source method file line] (-> trace first)] + (cond-> top-data + line (assoc :clojure.error/line line) + file (assoc :clojure.error/source file) + (and source method) (assoc :clojure.error/symbol (vector #_java-loc->source source method)) + type (assoc :clojure.error/class type) + message (assoc :clojure.error/cause message))) + + :execution + (let [[source method file line] (->> trace #_(drop-while #(core-class? (name (first %)))) first) + file (first (remove #(or (nil? %) (#{"NO_SOURCE_FILE" "NO_SOURCE_PATH"} %)) [(:file caller) file])) + err-line (or (:line caller) line)] + (cond-> {:clojure.error/class type} + err-line (assoc :clojure.error/line err-line) + message (assoc :clojure.error/cause message) + (or fn (and source method)) (assoc :clojure.error/symbol (or fn (vector #_java-loc->source source method))) + file (assoc :clojure.error/source file) + problems (assoc :clojure.error/spec data)))) + :clojure.error/phase phase))) + +(defn ex-str + "Returns a string from exception data, as produced by ex-triage. + The first line summarizes the exception phase and location. + The subsequent lines describe the cause." + [{:clojure.error/keys [phase source line column symbol class cause spec] :as triage-data}] + (let [loc (str (or source "") ":" (or line 1) (if column (str ":" column) "")) + class-name (name (or class "")) + simple-class class-name + cause-type (if (contains? #{"Exception" "RuntimeException"} simple-class) + "" ;; omit, not useful + (str " (" simple-class ")")) + format gstring/format] + (case phase + :read-source + (format "Syntax error reading source at (%s).\n%s\n" loc cause) + + :macro-syntax-check + (format "Syntax error macroexpanding %sat (%s).\n%s" + (if symbol (str symbol " ") "") + loc + (if spec + (with-out-str + (spec/explain-out + (if true #_(= s/*explain-out* s/explain-printer) + (update spec ::spec/problems + (fn [probs] (map #(dissoc % :in) probs))) + spec))) + (format "%s\n" cause))) + + :macroexpansion + (format "Unexpected error%s macroexpanding %sat (%s).\n%s\n" + cause-type + (if symbol (str symbol " ") "") + loc + cause) + + :compile-syntax-check + (format "Syntax error%s compiling %sat (%s).\n%s\n" + cause-type + (if symbol (str symbol " ") "") + loc + cause) + + :compilation + (format "Unexpected error%s compiling %sat (%s).\n%s\n" + cause-type + (if symbol (str symbol " ") "") + loc + cause) + + :read-eval-result + (format "Error reading eval result%s at %s (%s).\n%s\n" cause-type symbol loc cause) + + :print-eval-result + (format "Error printing return value%s at %s (%s).\n%s\n" cause-type symbol loc cause) + + :execution + (if spec + (format "Execution error - invalid arguments to %s at (%s).\n%s" + symbol + loc + (with-out-str + (spec/explain-out + (if true #_(= s/*explain-out* s/explain-printer) + (update spec ::spec/problems + (fn [probs] (map #(dissoc % :in) probs))) + spec)))) + (format "Execution error%s at %s(%s).\n%s\n" + cause-type + (if symbol (str symbol " ") "") + loc + cause))))) + +(defn error->str [error] + (ex-str (ex-triage (Error->map error)))) diff --git a/src/main/cljs/cljs/source_map.cljs b/src/main/cljs/cljs/source_map.cljs new file mode 100644 index 0000000000..7009bfd758 --- /dev/null +++ b/src/main/cljs/cljs/source_map.cljs @@ -0,0 +1,305 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.source-map + (:require [goog.object :as gobj] + [clojure.string :as string] + [clojure.set :as set] + [cljs.source-map.base64-vlq :as base64-vlq])) + +;; ============================================================================= +;; Source map code in the file assumes the following in memory +;; representation of source map data. +;; +;; { gline[Integer] +;; { gcol[Integer] +;; [{ :line ..., :col ..., :name ..., :source ... }] } } +;; +;; Reverse source map code in the file assumes the following in memory +;; representation of source map data. +;; +;; { file-name[String] +;; { line[Integer] +;; { col[Integer] +;; [{ :gline ..., :gcol ..., :name ... }] } } + +;; ----------------------------------------------------------------------------- +;; Utilities + +(defn indexed-sources + "Take a seq of source file names and return a map from + file number to integer index. For reverse source maps." + [sources] + (->> sources + (map-indexed (fn [a b] [a b])) + (reduce (fn [m [i v]] (assoc m v i)) {}))) + +(defn source-compare + "Take a seq of source file names and return a comparator + that can be used to construct a sorted map. For reverse + source maps." + [sources] + (let [sources (indexed-sources sources)] + (fn [a b] (compare (sources a) (sources b))))) + +;; ----------------------------------------------------------------------------- +;; Decoding + +(defn seg->map + "Take a source map segment represented as a vector + and return a map." + [seg source-map] + (let [[gcol source line col name] seg] + {:gcol gcol + :source (aget (gobj/get source-map "sources") source) + :line line + :col col + :name (when-let [name (-> seg meta :name)] + (aget (gobj/get source-map "names") name))})) + +(defn seg-combine + "Combine a source map segment vector and a relative + source map segment vector and combine them to get + an absolute segment posititon information as a vector." + [seg relseg] + (let [[gcol source line col name] seg + [rgcol rsource rline rcol rname] relseg + nseg [(+ gcol rgcol) + (+ (or source 0) rsource) + (+ (or line 0) rline) + (+ (or col 0) rcol) + (+ (or name 0) rname)]] + (if name + (with-meta nseg {:name (+ name rname)}) + nseg))) + +(defn update-reverse-result + "Helper for decode-reverse. Take a reverse source map and + update it with a segment map." + [result segmap gline] + (let [{:keys [gcol source line col name]} segmap + d {:gline gline + :gcol gcol} + d (if name (assoc d :name name) d)] + (update-in result [source] + (fnil (fn [m] + (update-in m [line] + (fnil (fn [m] + (update-in m [col] + (fnil (fn [v] (conj v d)) + []))) + (sorted-map)))) + (sorted-map))))) + +(defn decode-reverse + "Convert a v3 source map JSON object into a reverse source map + mapping original ClojureScript source locations to the generated + JavaScript." + ([source-map] + (decode-reverse + (gobj/get source-map "mappings") source-map)) + ([mappings source-map] + (let [sources (gobj/get source-map "sources") + relseg-init [0 0 0 0 0] + lines (seq (string/split mappings #";"))] + (loop [gline 0 + lines lines + relseg relseg-init + result (sorted-map-by (source-compare sources))] + (if lines + (let [line (first lines) + [result relseg] + (if (string/blank? line) + [result relseg] + (let [segs (seq (string/split line #","))] + (loop [segs segs relseg relseg result result] + (if segs + (let [seg (first segs) + nrelseg (seg-combine (base64-vlq/decode seg) relseg)] + (recur (next segs) nrelseg + (update-reverse-result result (seg->map nrelseg source-map) gline))) + [result relseg]))))] + (recur (inc gline) (next lines) (assoc relseg 0 0) result)) + result))))) + +(defn update-result + "Helper for decode. Take a source map and update it based on a + segment map." + [result segmap gline] + (let [{:keys [gcol source line col name]} segmap + d {:line line + :col col + :source source} + d (if name (assoc d :name name) d)] + (update-in result [gline] + (fnil (fn [m] + (update-in m [gcol] + (fnil #(conj % d) []))) + (sorted-map))))) + +(defn decode + "Convert a v3 source map JSON object into a source map mapping + generated JavaScript source locations to the original + ClojureScript." + ([source-map] + (decode (gobj/get source-map "mappings") source-map)) + ([mappings source-map] + (let [sources (gobj/get source-map "sources") + relseg-init [0 0 0 0 0] + lines (seq (string/split mappings #";"))] + (loop [gline 0 lines lines relseg relseg-init result {}] + (if lines + (let [line (first lines) + [result relseg] + (if (string/blank? line) + [result relseg] + (let [segs (seq (string/split line #","))] + (loop [segs segs relseg relseg result result] + (if segs + (let [seg (first segs) + nrelseg (seg-combine (base64-vlq/decode seg) relseg)] + (recur (next segs) nrelseg + (update-result result (seg->map nrelseg source-map) gline))) + [result relseg]))))] + (recur (inc gline) (next lines) (assoc relseg 0 0) result)) + result))))) + +;; ----------------------------------------------------------------------------- +;; Encoding + +(defn lines->segs + "Take a nested sorted map encoding line and column information + for a file and return a vector of vectors of encoded segments. + Each vector represents a line, and the internal vectors are segments + representing the contents of the line." + [lines] + (let [relseg (atom [0 0 0 0 0])] + (reduce + (fn [segs cols] + (swap! relseg + (fn [[_ source line col name]] + [0 source line col name])) + (conj segs + (reduce + (fn [cols [gcol sidx line col name :as seg]] + (let [offset (map - seg @relseg)] + (swap! relseg + (fn [[_ _ _ _ lname]] + [gcol sidx line col (or name lname)])) + (conj cols (base64-vlq/encode offset)))) + [] cols))) + [] lines))) + +(defn encode + "Take an internal source map representation represented as nested + sorted maps of file, line, column and return a source map v3 JSON + string." + [m opts] + (let [lines (atom [[]]) + names->idx (atom {}) + name-idx (atom 0) + preamble-lines (take (or (:preamble-line-count opts) 0) (repeat [])) + info->segv (fn [info source-idx line col] + (let [segv [(:gcol info) source-idx line col]] + (if-let [name (:name info)] + (let [idx (if-let [idx (get @names->idx name)] + idx + (let [cidx @name-idx] + (swap! names->idx assoc name cidx) + (swap! name-idx inc) + cidx))] + (conj segv idx)) + segv))) + encode-cols (fn [infos source-idx line col] + (doseq [info infos] + (let [segv (info->segv info source-idx line col) + gline (:gline info) + lc (count @lines)] + (if (> gline (dec lc)) + (swap! lines + (fn [lines] + (conj (into lines (repeat (dec (- gline (dec lc))) [])) [segv]))) + (swap! lines + (fn [lines] + (update-in lines [gline] conj segv)))))))] + (doseq [[source-idx [_ lines]] (map-indexed (fn [i v] [i v]) m)] + (doseq [[line cols] lines] + (doseq [[col infos] cols] + (encode-cols infos source-idx line col)))) + (let [source-map-file-contents + (cond-> #js {"version" 3 + "file" (:file opts) + "sources" (let [paths (keys m) + f (comp + (if (true? (:source-map-timestamp opts)) + #(str % "?rel=" (.valueOf (js/Date.))) + identity) + #(last (string/split % #"/")))] + (->> paths (map f) (into-array))) + "lineCount" (:lines opts) + "mappings" (->> (lines->segs (concat preamble-lines @lines)) + (map #(string/join "," %)) + (string/join ";")) + "names" (into-array + (map (set/map-invert @names->idx) + (range (count @names->idx))))} + (:sources-content opts) + (doto (gobj/set "sourcesContent" (into-array (:sources-content opts)))))] + (.stringify js/JSON source-map-file-contents)))) + +;; ----------------------------------------------------------------------------- +;; Merging + +(defn merge-source-maps + "Merge an internal source map representation of a single + ClojureScript file mapping original to generated with a + second source map mapping original JS to generated JS. + The is to support source maps that work through multiple + compilation steps like Google Closure optimization passes." + [cljs-map js-map] + (loop [line-map-seq (seq cljs-map) new-lines (sorted-map)] + (if line-map-seq + (let [[line col-map] (first line-map-seq) + new-cols + (loop [col-map-seq (seq col-map) new-cols (sorted-map)] + (if col-map-seq + (let [[col infos] (first col-map-seq)] + (recur (next col-map-seq) + (assoc new-cols col + (reduce (fn [v {:keys [gline gcol]}] + (into v (get-in js-map [gline gcol]))) + [] infos)))) + new-cols))] + (recur (next line-map-seq) + (assoc new-lines line new-cols))) + new-lines))) + +;; ----------------------------------------------------------------------------- +;; Reverse Source Map Inversion + +(defn invert-reverse-map + "Given a ClojureScript to JavaScript source map, invert it. Useful when + mapping JavaScript stack traces when environment support is unavailable." + [reverse-map] + (let [inverted (atom (sorted-map))] + (doseq [[line columns] reverse-map] + (doseq [[column column-info] columns] + (doseq [{:keys [gline gcol name]} column-info] + (swap! inverted update-in [gline] + (fnil (fn [columns] + (update-in columns [gcol] (fnil conj []) + {:line line :col column :name name})) + (sorted-map)))))) + @inverted)) + +(comment + (invert-reverse-map + {1 + {1 [{:gcol 0, :gline 0, :name "cljs.core/map"}], + 5 [{:gcol 24, :gline 0, :name "cljs.core/inc"}]}}) + ) \ No newline at end of file diff --git a/src/main/cljs/cljs/source_map/base64.cljs b/src/main/cljs/cljs/source_map/base64.cljs new file mode 100644 index 0000000000..c5dfc2b1ee --- /dev/null +++ b/src/main/cljs/cljs/source_map/base64.cljs @@ -0,0 +1,25 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.source-map.base64) + +(def chars64 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") +(def char->int (zipmap chars64 (range 0 64))) +(def int->char (zipmap (range 0 64) chars64)) + +(defn encode [n] + (let [e (find int->char n)] + (if e + (second e) + (throw (js/Error. (str "Must be between 0 and 63: " n)))))) + +(defn decode [c] + (let [e (find char->int c)] + (if e + (second e) + (throw (js/Error. (str "Not a valid base 64 digit: " c)))))) diff --git a/src/main/cljs/cljs/source_map/base64_vlq.cljs b/src/main/cljs/cljs/source_map/base64_vlq.cljs new file mode 100644 index 0000000000..ff4d02b248 --- /dev/null +++ b/src/main/cljs/cljs/source_map/base64_vlq.cljs @@ -0,0 +1,102 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.source-map.base64-vlq + (:require [clojure.string :as string] + [cljs.source-map.base64 :as base64]) + (:import [goog.string StringBuffer])) + +(def ^:const vlq-base-shift 5) +(def ^:const vlq-base (bit-shift-left 1 vlq-base-shift)) +(def ^:const vlq-base-mask (dec vlq-base)) +(def ^:const vlq-continuation-bit vlq-base) + +(defn to-vlq-signed [v] + (if (neg? v) + (inc (bit-shift-left (- v) 1)) + (+ (bit-shift-left v 1) 0))) + +(defn from-vlq-signed [v] + (let [neg? (= (bit-and v 1) 1) + shifted (bit-shift-right v 1)] + (if neg? + (- shifted) + shifted))) + +(defn encode-val [n] + (let [sb (StringBuffer.) + vlq (to-vlq-signed n)] + (loop [digit (bit-and vlq vlq-base-mask) + vlq (bit-shift-right-zero-fill vlq vlq-base-shift)] + (if (pos? vlq) + (let [digit (bit-or digit vlq-continuation-bit)] + (.append sb (base64/encode digit)) + (recur (bit-and vlq vlq-base-mask) + (bit-shift-right-zero-fill vlq vlq-base-shift))) + (.append sb (base64/encode digit)))) + (str sb))) + +(defn encode [v] + (apply str (map encode-val v))) + +(defn decode [s] + (let [l (.-length s)] + (loop [i 0 result 0 shift 0] + (when (>= i l) + (throw (js/Error. "Expected more digits in base 64 VLQ value."))) + (let [digit (base64/decode (.charAt s i))] + (let [i (inc i) + continuation? (pos? (bit-and digit vlq-continuation-bit)) + digit (bit-and digit vlq-base-mask) + result (+ result (bit-shift-left digit shift)) + shift (+ shift vlq-base-shift)] + (if continuation? + (recur i result shift) + (lazy-seq + (cons (from-vlq-signed result) + (let [s (.substring s i)] + (when-not (string/blank? s) + (decode s))))))))))) + +(comment + ;; tests + + (bit-shift-right-zero-fill 127 1) ;; 63 + (bit-shift-right-zero-fill -127 1) ;; 2147483584 + + (to-vlq-signed 32) ;; 64 + (to-vlq-signed -32) ;; 65 + (from-vlq-signed 64) ;; 32 + (from-vlq-signed 65) ;; -32 + + ;; Base64 VLQ can only represent 32bit values + + (encode-val 32) ; "gC" + (decode "gC") ; {:value 32 :rest ""} + + (decode "AAgBC") ; (0 0 16 1) + + ;; lines kept count by semicolons, segments delimited by commas + ;; the above is gline 0, gcol 0, file 0, line 16, col 1, no name if this was the first segment read + + (decode "AAggBC") ; very clever way to encode large values + (decode "AAggBCA") ; 5 values instead of 4 + + (encode [0 0 16 1]) ; "AAgBC" + + (decode "IAWdD") ; (4 0 11 -14 -1) this is correct + ;; gline N, gcol +4, file +0, line +11, col -14, name -1 + + ;; Notes about format + ;; we always have 1, 4, or 5 values, all zero-based indexes + ;; 1. generated col - relative - reset on every new line in generated source + ;; 2. index into sources list - relative + ;; 3. original line - relative + ;; 4. origin column - relative + ;; 5. name - relative + ) diff --git a/src/main/cljs/cljs/spec/alpha.cljc b/src/main/cljs/cljs/spec/alpha.cljc new file mode 100644 index 0000000000..a181c699b7 --- /dev/null +++ b/src/main/cljs/cljs/spec/alpha.cljc @@ -0,0 +1,564 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.spec.alpha + (:refer-clojure :exclude [+ * and or cat def keys merge resolve assert]) + (:require [cljs.core :as c] + [cljs.analyzer :as ana] + [cljs.env :as env] + [cljs.analyzer.api :refer [resolve]] + [clojure.walk :as walk] + [cljs.spec.gen.alpha :as gen] + [clojure.string :as str])) + +(defonce registry-ref (atom {})) + +(defn- ->sym + "Returns a symbol from a symbol or var" + [x] + (if (map? x) + (:name x) + x)) + +(defn- unfn [expr] + (if (clojure.core/and (seq? expr) + (symbol? (first expr)) + (= "fn*" (name (first expr)))) + (let [[[s] & form] (rest expr)] + (conj (walk/postwalk-replace {s '%} form) '[%] 'cljs.core/fn)) + expr)) + +(defn- res [env form] + (cond + (keyword? form) form + (symbol? form) #?(:clj (clojure.core/or (->> form (resolve env) ->sym) form) + :cljs (let [resolved (clojure.core/or (->> form (resolve env) ->sym) form) + ns-name (namespace resolved)] + (symbol + (if (clojure.core/and ns-name (str/ends-with? ns-name "$macros")) + (subs ns-name 0 (- (count ns-name) 7)) + ns-name) + (name resolved)))) + (sequential? form) (walk/postwalk #(if (symbol? %) (res env %) %) (unfn form)) + :else form)) + +(defmacro ^:private mres + "a compile time res, for use in cljs/spec/alpha.cljs" + [form] + (res &env form)) + +(defn- ns-qualify + "Qualify symbol s by resolving it or using the current *ns*." + [env s] + (if (namespace s) + (->sym (binding [ana/*private-var-access-nowarn* true] + (ana/resolve-var env s))) + (symbol (str ana/*cljs-ns*) (str s)))) + +(defonce ^:private _speced_vars (atom #{})) + +(defn speced-vars [] + @_speced_vars) + +(defmacro def + "Given a namespace-qualified keyword or resolveable symbol k, and a + spec, spec-name, predicate or regex-op makes an entry in the + registry mapping k to the spec. Use nil to remove an entry in + the registry for k." + [k spec-form] + (let [k (if (symbol? k) + (let [sym (ns-qualify &env k)] + (swap! _speced_vars conj + (vary-meta sym assoc :fdef-ns (-> &env :ns :name))) + sym) + k) + form (res &env spec-form)] + (swap! registry-ref (fn [r] + (if (nil? form) + (dissoc r k) + (assoc r k form)))) + `(def-impl '~k '~form ~spec-form))) + +(defmacro spec + "Takes a single predicate form, e.g. can be the name of a predicate, + like even?, or a fn literal like #(< % 42). Note that it is not + generally necessary to wrap predicates in spec when using the rest + of the spec macros, only to attach a unique generator + + Can also be passed the result of one of the regex ops - + cat, alt, *, +, ?, in which case it will return a regex-conforming + spec, useful when nesting an independent regex. + --- + + Optionally takes :gen generator-fn, which must be a fn of no args that + returns a test.check generator. + + Returns a spec." + [form & {:keys [gen]}] + (when form + `(spec-impl '~(res &env form) ~form ~gen nil))) + +(defmacro multi-spec + "Takes the name of a spec/predicate-returning multimethod and a + tag-restoring keyword or fn (retag). Returns a spec that when + conforming or explaining data will pass it to the multimethod to get + an appropriate spec. You can e.g. use multi-spec to dynamically and + extensibly associate specs with 'tagged' data (i.e. data where one + of the fields indicates the shape of the rest of the structure). + + (defmulti mspec :tag) + + The methods should ignore their argument and return a predicate/spec: + (defmethod mspec :int [_] (s/keys :req-un [::tag ::i])) + + retag is used during generation to retag generated values with + matching tags. retag can either be a keyword, at which key the + dispatch-tag will be assoc'ed, or a fn of generated value and + dispatch-tag that should return an appropriately retagged value. + + Note that because the tags themselves comprise an open set, + the tag key spec cannot enumerate the values, but can e.g. + test for keyword?. + + Note also that the dispatch values of the multimethod will be + included in the path, i.e. in reporting and gen overrides, even + though those values are not evident in the spec. +" + [mm retag] + `(multi-spec-impl '~(res &env mm) (var ~mm) ~retag)) + +(defmacro keys + "Creates and returns a map validating spec. :req and :opt are both + vectors of namespaced-qualified keywords. The validator will ensure + the :req keys are present. The :opt keys serve as documentation and + may be used by the generator. + + The :req key vector supports 'and' and 'or' for key groups: + + (s/keys :req [::x ::y (or ::secret (and ::user ::pwd))] :opt [::z]) + + There are also -un versions of :req and :opt. These allow + you to connect unqualified keys to specs. In each case, fully + qualfied keywords are passed, which name the specs, but unqualified + keys (with the same name component) are expected and checked at + conform-time, and generated during gen: + + (s/keys :req-un [:my.ns/x :my.ns/y]) + + The above says keys :x and :y are required, and will be validated + and generated by specs (if they exist) named :my.ns/x :my.ns/y + respectively. + + In addition, the values of *all* namespace-qualified keys will be validated + (and possibly destructured) by any registered specs. Note: there is + no support for inline value specification, by design. + + Optionally takes :gen generator-fn, which must be a fn of no args that + returns a test.check generator." + [& {:keys [req req-un opt opt-un gen]}] + (let [unk #(-> % name keyword) + req-keys (filterv keyword? (flatten req)) + req-un-specs (filterv keyword? (flatten req-un)) + _ (clojure.core/assert (every? #(clojure.core/and (keyword? %) (namespace %)) (concat req-keys req-un-specs opt opt-un)) + "all keys must be namespace-qualified keywords") + req-specs (into req-keys req-un-specs) + req-keys (into req-keys (map unk req-un-specs)) + opt-keys (into (vec opt) (map unk opt-un)) + opt-specs (into (vec opt) opt-un) + gx (gensym) + parse-req (fn [rk f] + (map (fn [x] + (if (keyword? x) + `(contains? ~gx ~(f x)) + (walk/postwalk + (fn [y] (if (keyword? y) `(contains? ~gx ~(f y)) y)) + x))) + rk)) + pred-exprs [`(map? ~gx)] + pred-exprs (into pred-exprs (parse-req req identity)) + pred-exprs (into pred-exprs (parse-req req-un unk)) + keys-pred `(fn* [~gx] (c/and ~@pred-exprs)) + pred-exprs (mapv (fn [e] `(fn* [~gx] ~e)) pred-exprs) + pred-forms (walk/postwalk #(res &env %) pred-exprs)] + ;; `(map-spec-impl ~req-keys '~req ~opt '~pred-forms ~pred-exprs ~gen) + `(map-spec-impl {:req '~req :opt '~opt :req-un '~req-un :opt-un '~opt-un + :req-keys '~req-keys :req-specs '~req-specs + :opt-keys '~opt-keys :opt-specs '~opt-specs + :pred-forms '~pred-forms + :pred-exprs ~pred-exprs + :keys-pred ~keys-pred + :gfn ~gen}))) + +(defmacro or + "Takes key+pred pairs, e.g. + + (s/or :even even? :small #(< % 42)) + + Returns a destructuring spec that returns a map entry containing the + key of the first matching pred and the corresponding value. Thus the + 'key' and 'val' functions can be used to refer generically to the + components of the tagged return." + [& key-pred-forms] + (let [pairs (partition 2 key-pred-forms) + keys (mapv first pairs) + pred-forms (mapv second pairs) + pf (mapv #(res &env %) pred-forms)] + (clojure.core/assert (clojure.core/and (even? (count key-pred-forms)) (every? keyword? keys)) "spec/or expects k1 p1 k2 p2..., where ks are keywords") + `(or-spec-impl ~keys '~pf ~pred-forms nil))) + +(defmacro and + "Takes predicate/spec-forms, e.g. + + (s/and even? #(< % 42)) + + Returns a spec that returns the conformed value. Successive + conformed values propagate through rest of predicates." + [& pred-forms] + `(and-spec-impl '~(mapv #(res &env %) pred-forms) ~(vec pred-forms) nil)) + +(defn- res-kind + [env opts] + (let [{kind :kind :as mopts} opts] + (->> + (if kind + (assoc mopts :kind `~(res env kind)) + mopts) + (mapcat identity)))) + +(defmacro every + "takes a pred and validates collection elements against that pred. + + Note that 'every' does not do exhaustive checking, rather it samples + *coll-check-limit* elements. Nor (as a result) does it do any + conforming of elements. 'explain' will report at most *coll-error-limit* + problems. Thus 'every' should be suitable for potentially large + collections. + + Takes several kwargs options that further constrain the collection: + + :kind - a pred that the collection type must satisfy, e.g. vector? + (default nil) Note that if :kind is specified and :into is + not, this pred must generate in order for every to generate. + :count - specifies coll has exactly this count (default nil) + :min-count, :max-count - coll has count (<= min-count count max-count) (defaults nil) + :distinct - all the elements are distinct (default nil) + + And additional args that control gen + + :gen-max - the maximum coll size to generate (default 20) + :into - one of [], (), {}, #{} - the default collection to generate into + (default same as :kind if supplied, else [] + + Optionally takes :gen generator-fn, which must be a fn of no args that + returns a test.check generator + + See also - coll-of, every-kv +" + [pred & {:keys [into kind count max-count min-count distinct gen-max gen-into gen] :as opts}] + (let [desc (::describe opts) + nopts (-> opts + (dissoc :gen ::describe) + (assoc ::kind-form `'~(res &env (:kind opts)) + ::describe (clojure.core/or desc `'(every ~(res &env pred) ~@(res-kind &env opts))))) + gx (gensym) + cpreds (cond-> [(list (clojure.core/or kind `coll?) gx)] + count (conj `(= ~count (c/bounded-count ~count ~gx))) + + (clojure.core/or min-count max-count) + (conj `(<= (c/or ~min-count 0) + (c/bounded-count (if ~max-count (inc ~max-count) ~min-count) ~gx) + (c/or ~max-count MAX_INT))) + + distinct + (conj `(c/or (empty? ~gx) (apply distinct? ~gx))))] + `(every-impl '~pred ~pred ~(assoc nopts ::cpred `(fn* [~gx] (c/and ~@cpreds))) ~gen))) + +(defmacro every-kv + "like 'every' but takes separate key and val preds and works on associative collections. + + Same options as 'every', :into defaults to {} + + See also - map-of" + + [kpred vpred & opts] + (let [desc `(every-kv ~(res &env kpred) ~(res &env vpred) ~@(res-kind &env opts))] + `(every (tuple ~kpred ~vpred) ::kfn (fn [i# v#] (nth v# 0)) :into {} ::describe '~desc ~@opts))) + +(defmacro coll-of + "Returns a spec for a collection of items satisfying pred. Unlike + generator will fill an empty init-coll. + + Same options as 'every'. conform will produce a collection + corresponding to :into if supplied, else will match the input collection, + avoiding rebuilding when possible. + + Same options as 'every'. + + See also - every, map-of" + [pred & opts] + (let [desc `(coll-of ~(res &env pred) ~@(res-kind &env opts))] + `(every ~pred ::conform-all true ::describe '~desc ~@opts))) + +(defmacro map-of + "Returns a spec for a map whose keys satisfy kpred and vals satisfy + vpred. Unlike 'every-kv', map-of will exhaustively conform every + value. + + Same options as 'every', :kind defaults to map?, with the addition of: + + :conform-keys - conform keys as well as values (default false) + + See also - every-kv" + [kpred vpred & opts] + (let [desc `(map-of ~(res &env kpred) ~(res &env vpred) ~@(res-kind &env opts))] + `(every-kv ~kpred ~vpred ::conform-all true :kind map? ::describe '~desc ~@opts))) + +(defmacro * + "Returns a regex op that matches zero or more values matching + pred. Produces a vector of matches iff there is at least one match" + [pred-form] + `(rep-impl '~(res &env pred-form) ~pred-form)) + +(defmacro + + "Returns a regex op that matches one or more values matching + pred. Produces a vector of matches" + [pred-form] + `(rep+impl '~(res &env pred-form) ~pred-form)) + +(defmacro ? + "Returns a regex op that matches zero or one value matching + pred. Produces a single value (not a collection) if matched." + [pred-form] + `(maybe-impl ~pred-form '~(res &env pred-form))) + +(defmacro alt + "Takes key+pred pairs, e.g. + + (s/alt :even even? :small #(< % 42)) + + Returns a regex op that returns a map entry containing the key of the + first matching pred and the corresponding value. Thus the + 'key' and 'val' functions can be used to refer generically to the + components of the tagged return." + [& key-pred-forms] + (let [pairs (partition 2 key-pred-forms) + keys (mapv first pairs) + pred-forms (mapv second pairs) + pf (mapv #(res &env %) pred-forms)] + (clojure.core/assert (clojure.core/and (even? (count key-pred-forms)) (every? keyword? keys)) "alt expects k1 p1 k2 p2..., where ks are keywords") + `(alt-impl ~keys ~pred-forms '~pf))) + +(defmacro cat + "Takes key+pred pairs, e.g. + + (s/cat :e even? :o odd?) + + Returns a regex op that matches (all) values in sequence, returning a map + containing the keys of each pred and the corresponding value." + [& key-pred-forms] + (let [pairs (partition 2 key-pred-forms) + keys (mapv first pairs) + pred-forms (mapv second pairs) + pf (mapv #(res &env %) pred-forms)] + ;;(prn key-pred-forms) + (clojure.core/assert (clojure.core/and (even? (count key-pred-forms)) (every? keyword? keys)) "cat expects k1 p1 k2 p2..., where ks are keywords") + `(cat-impl ~keys ~pred-forms '~pf))) + +(defmacro & + "takes a regex op re, and predicates. Returns a regex-op that consumes + input as per re but subjects the resulting value to the + conjunction of the predicates, and any conforming they might perform." + [re & preds] + (let [pv (vec preds)] + `(amp-impl ~re '~(res &env re) ~pv '~(mapv #(res &env %) pv)))) + +(defmacro conformer + "takes a predicate function with the semantics of conform i.e. it should return either a + (possibly converted) value or :cljs.spec.alpha/invalid, and returns a + spec that uses it as a predicate/conformer. Optionally takes a + second fn that does unform of result of first" + ([f] `(spec-impl '(conformer ~(res &env f)) ~f nil true)) + ([f unf] `(spec-impl '(conformer ~(res &env f) ~(res &env unf)) ~f nil true ~unf))) + +(defmacro fspec + "takes :args :ret and (optional) :fn kwargs whose values are preds + and returns a spec whose conform/explain take a fn and validates it + using generative testing. The conformed value is always the fn itself. + + See 'fdef' for a single operation that creates an fspec and + registers it, as well as a full description of :args, :ret and :fn + + fspecs can generate functions that validate the arguments and + fabricate a return value compliant with the :ret spec, ignoring + the :fn spec if present. + + Optionally takes :gen generator-fn, which must be a fn of no args + that returns a test.check generator." + [& {:keys [args ret fn gen] :or {ret `cljs.core/any?}}] + (let [env &env] + `(fspec-impl (spec ~args) '~(res env args) + (spec ~ret) '~(res env ret) + (spec ~fn) '~(res env fn) ~gen))) + +(defmacro tuple + "takes one or more preds and returns a spec for a tuple, a vector + where each element conforms to the corresponding pred. Each element + will be referred to in paths using its ordinal." + [& preds] + (clojure.core/assert (not (empty? preds))) + `(tuple-impl '~(mapv #(res &env %) preds) ~(vec preds))) + +(defmacro fdef + "Takes a symbol naming a function, and one or more of the following: + + :args A regex spec for the function arguments as they were a list to be + passed to apply - in this way, a single spec can handle functions with + multiple arities + :ret A spec for the function's return value + :fn A spec of the relationship between args and ret - the + value passed is {:args conformed-args :ret conformed-ret} and is + expected to contain predicates that relate those values + + Qualifies fn-sym with resolve, or using *ns* if no resolution found. + Registers an fspec in the global registry, where it can be retrieved + by calling get-spec with the var or fully-qualified symbol. + + Once registered, function specs are included in doc, checked by + instrument, tested by the runner cljs.spec.test.alpha/check, and (if + a macro) used to explain errors during macroexpansion. + + Note that :fn specs require the presence of :args and :ret specs to + conform values, and so :fn specs will be ignored if :args or :ret + are missing. + + Returns the qualified fn-sym. + + For example, to register function specs for the symbol function: + + (s/fdef cljs.core/symbol + :args (s/alt :separate (s/cat :ns string? :n string?) + :str string? + :sym symbol?) + :ret symbol?)" + [fn-sym & specs] + `(cljs.spec.alpha/def ~fn-sym (fspec ~@specs))) + +(defmacro keys* + "takes the same arguments as spec/keys and returns a regex op that matches sequences of key/values, + converts them into a map, and conforms that map with a corresponding + spec/keys call: + + user=> (s/conform (s/keys :req-un [::a ::c]) {:a 1 :c 2}) + {:a 1, :c 2} + user=> (s/conform (s/keys* :req-un [::a ::c]) [:a 1 :c 2]) + {:a 1, :c 2} + + the resulting regex op can be composed into a larger regex: + + user=> (s/conform (s/cat :i1 integer? :m (s/keys* :req-un [::a ::c]) :i2 integer?) [42 :a 1 :c 2 :d 4 99]) + {:i1 42, :m {:a 1, :c 2, :d 4}, :i2 99}" + [& kspecs] + `(let [mspec# (keys ~@kspecs)] + (with-gen (cljs.spec.alpha/& (* (cat ::k keyword? ::v cljs.core/any?)) ::kvs->map mspec#) + (fn [] (gen/fmap (fn [m#] (apply concat m#)) (gen mspec#)))))) + +(defmacro nilable + "returns a spec that accepts nil and values satisfiying pred" + [pred] + (let [pf (res &env pred)] + `(nilable-impl '~pf ~pred nil))) + +(defmacro inst-in + "Returns a spec that validates insts in the range from start + (inclusive) to end (exclusive)." + [start end] + `(let [st# (cljs.core/inst-ms ~start) + et# (cljs.core/inst-ms ~end) + mkdate# (fn [d#] (js/Date. d#))] + (spec (and cljs.core/inst? #(inst-in-range? ~start ~end %)) + :gen (fn [] + (gen/fmap mkdate# + (gen/large-integer* {:min st# :max et#})))))) + +(defmacro int-in + "Returns a spec that validates fixed precision integers in the + range from start (inclusive) to end (exclusive)." + [start end] + `(spec (and c/int? #(int-in-range? ~start ~end %)) + :gen #(gen/large-integer* {:min ~start :max (dec ~end)}))) + +(defmacro double-in + "Specs a 64-bit floating point number. Options: + + :infinite? - whether +/- infinity allowed (default true) + :NaN? - whether NaN allowed (default true) + :min - minimum value (inclusive, default none) + :max - maximum value (inclusive, default none)" + [& {:keys [infinite? NaN? min max] + :or {infinite? true NaN? true} + :as m}] + `(spec (and c/double? + ~@(when-not infinite? '[#(not (infinite? %))]) + ~@(when-not NaN? '[#(not (js/isNaN %))]) + ~@(when max `[#(<= % ~max)]) + ~@(when min `[#(<= ~min %)])) + :gen #(gen/double* ~m))) + +(defmacro merge + "Takes map-validating specs (e.g. 'keys' specs) and + returns a spec that returns a conformed map satisfying all of the + specs. Successive conformed values propagate through rest of + predicates. Unlike 'and', merge can generate maps satisfying the + union of the predicates." + [& pred-forms] + `(merge-spec-impl '~(mapv #(res &env %) pred-forms) ~(vec pred-forms) nil)) + +(defmacro exercise-fn + "exercises the fn named by sym (a symbol) by applying it to + n (default 10) generated samples of its args spec. When fspec is + supplied its arg spec is used, and sym-or-f can be a fn. Returns a + sequence of tuples of [args ret]. " + ([sym] + `(exercise-fn ~sym 10)) + ([sym n] + `(exercise-fn ~sym ~n nil)) + ([sym n fspec] + (let [sym (cond-> sym + (clojure.core/and (sequential? sym) + (= (first sym) 'quote)) + second)] + `(let [fspec# ~(if-not fspec + `(get-spec '~(:name (resolve &env sym))) + fspec) + f# ~sym] + (if-let [arg-spec# (c/and fspec# (:args fspec#))] + (for [args# (gen/sample (gen arg-spec#) ~n)] + [args# (apply f# args#)]) + (throw (js/Error. "No :args spec found, can't generate"))))))) + +(defmacro ^:private init-compile-asserts [] + (let [compile-asserts (not (-> env/*compiler* deref :options :elide-asserts))] + compile-asserts)) + +(defmacro assert + "spec-checking assert expression. Returns x if x is valid? according +to spec, else throws an error with explain-data plus ::failure of +:assertion-failed. +Can be disabled at either compile time or runtime: +If *compile-asserts* is false at compile time, compiles to x. Defaults +to the negation value of the ':elide-asserts' compiler option, or true if +not set. +If (check-asserts?) is false at runtime, always returns x. Defaults to +value of 'cljs.spec.alpha/*runtime-asserts*', or false if not set. You can +toggle check-asserts? with (check-asserts bool)." + [spec x] + `(if *compile-asserts* + (if @#'*runtime-asserts* + (assert* ~spec ~x) + ~x) + ~x)) diff --git a/src/main/cljs/cljs/spec/alpha.cljs b/src/main/cljs/cljs/spec/alpha.cljs new file mode 100644 index 0000000000..740262aad7 --- /dev/null +++ b/src/main/cljs/cljs/spec/alpha.cljs @@ -0,0 +1,1510 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.spec.alpha + (:refer-clojure :exclude [+ * and or cat def keys merge]) + (:require-macros [cljs.core :as c] + [cljs.spec.alpha :as s]) + (:require [goog.object :as gobj] + [cljs.core :as c] + [clojure.walk :as walk] + [cljs.spec.gen.alpha :as gen] + [clojure.string :as str])) + +(def ^:const MAX_INT 9007199254740991) + +(def ^:dynamic *recursion-limit* + "A soft limit on how many times a branching spec (or/alt/*/opt-keys/multi-spec) + can be recursed through during generation. After this a + non-recursive branch will be chosen." + 4) + +(def ^:dynamic *fspec-iterations* + "The number of times an anonymous fn specified by fspec will be (generatively) tested during conform" + 21) + +(def ^:dynamic *coll-check-limit* + "The number of items validated in a collection spec'ed with 'every'" + 101) + +(def ^:dynamic *coll-error-limit* + "The number of errors reported by explain in a collection spec'ed with 'every'" + 20) + +(defprotocol Spec + (conform* [spec x]) + (unform* [spec y]) + (explain* [spec path via in x]) + (gen* [spec overrides path rmap]) + (with-gen* [spec gfn]) + (describe* [spec])) + +(defonce ^:private registry-ref (atom {})) + +(defn- deep-resolve [reg k] + (loop [spec k] + (if (ident? spec) + (recur (get reg spec)) + spec))) + +(defn- reg-resolve + "returns the spec/regex at end of alias chain starting with k, nil if not found, k if k not ident" + [k] + (if (ident? k) + (let [reg @registry-ref + spec (get reg k)] + (if-not (ident? spec) + spec + (deep-resolve reg spec))) + k)) + +(defn- reg-resolve! + "returns the spec/regex at end of alias chain starting with k, throws if not found, k if k not ident" + [k] + (if (ident? k) + (c/or (reg-resolve k) + (throw (js/Error. (str "Unable to resolve spec: " k)))) + k)) + +(defn spec? + "returns x if x is a spec object, else logical false" + [x] + (when (implements? Spec x) + x)) + +(defn regex? + "returns x if x is a (cljs.spec.alpha) regex op, else logical false" + [x] + (c/and (::op x) x)) + +(defn- with-name [spec name] + (cond + (ident? spec) spec + (regex? spec) (assoc spec ::name name) + + (implements? IMeta spec) + (with-meta spec (assoc (meta spec) ::name name)))) + +(defn- spec-name [spec] + (cond + (ident? spec) spec + + (regex? spec) (::name spec) + + (implements? IMeta spec) + (-> (meta spec) ::name))) + +(declare ^{:arglists '([form pred gfn cpred?] [form pred gfn cpred? unc])} spec-impl) +(declare ^{:arglists '([re gfn])} regex-spec-impl) + +(defn- maybe-spec + "spec-or-k must be a spec, regex or resolvable kw/sym, else returns nil." + [spec-or-k] + (let [s (c/or (c/and (ident? spec-or-k) (reg-resolve spec-or-k)) + (spec? spec-or-k) + (regex? spec-or-k) + nil)] + (if (regex? s) + (with-name (regex-spec-impl s nil) (spec-name s)) + s))) + +(defn- the-spec + "spec-or-k must be a spec, regex or kw/sym, else returns nil. Throws if unresolvable kw/sym" + [spec-or-k] + (c/or (maybe-spec spec-or-k) + (when (ident? spec-or-k) + (throw (js/Error. (str "Unable to resolve spec: " spec-or-k)))))) + +(defn- fn-sym [f-n] + (when-not (str/blank? f-n) + (let [xs (map demunge (str/split f-n "$"))] + (when (c/and (<= 2 (count xs)) + (every? #(not (str/blank? %)) xs)) + (let [[xs y] ((juxt butlast last) xs)] + (symbol (str (str/join "." xs) "/" y))))))) + +(defprotocol Specize + (specize* [_] [_ form])) + +(extend-protocol Specize + Keyword + (specize* ([k] (specize* (reg-resolve! k))) + ([k _] (specize* (reg-resolve! k)))) + + Symbol + (specize* ([s] (specize* (reg-resolve! s))) + ([s _] (specize* (reg-resolve! s)))) + + PersistentHashSet + (specize* ([s] (spec-impl s s nil nil)) + ([s form] (spec-impl form s nil nil))) + + PersistentTreeSet + (specize* ([s] (spec-impl s s nil nil)) + ([s form] (spec-impl form s nil nil))) + + SetLite + (specize* ([s] (spec-impl s s nil nil)) + ([s form] (spec-impl form s nil nil))) + + default + (specize* + ([o] + (if-let [f-n (c/and (fn? o) (fn-sym (.-name o)))] + (spec-impl f-n o nil nil) + (spec-impl ::unknown o nil nil))) + ([o form] (spec-impl form o nil nil)))) + +(defn- specize + ([s] (c/or (spec? s) (specize* s))) + ([s form] (c/or (spec? s) (specize* s form)))) + +(defn invalid? + "tests the validity of a conform return value" + [ret] + (keyword-identical? ::invalid ret)) + +(defn conform + "Given a spec and a value, returns :cljs.spec.alpha/invalid if value does + not match spec, else the (possibly destructured) value." + [spec x] + (conform* (specize spec) x)) + +(defn unform + "Given a spec and a value created by or compliant with a call to + 'conform' with the same spec, returns a value with all conform + destructuring undone." + [spec x] + (unform* (specize spec) x)) + +(defn form + "returns the spec as data" + [spec] + ;;TODO - incorporate gens + (describe* (specize spec))) + +(defn abbrev [form] + (cond + (seq? form) + (walk/postwalk (fn [form] + (cond + (c/and (symbol? form) (namespace form)) + (-> form name symbol) + + (c/and (seq? form) (= 'fn (first form)) (= '[%] (second form))) + (last form) + + :else form)) + form) + + (c/and (symbol? form) (namespace form)) + (-> form name symbol) + + :else form)) + +(defn describe + "returns an abbreviated description of the spec as data" + [spec] + (abbrev (form spec))) + +(defn with-gen + "Takes a spec and a no-arg, generator-returning fn and returns a version of that spec that uses that generator" + [spec gen-fn] + (let [spec (reg-resolve spec)] + (if (regex? spec) + (assoc spec ::gfn gen-fn) + (with-gen* (specize spec) gen-fn)))) + +(defn explain-data* [spec path via in x] + (when-let [probs (explain* (specize spec) path via in x)] + (when-not (empty? probs) + {::problems probs + ::spec spec + ::value x}))) + +(defn explain-data + "Given a spec and a value x which ought to conform, returns nil if x + conforms, else a map with at least the key ::problems whose value is + a collection of problem-maps, where problem-map has at least :path :pred and :val + keys describing the predicate and the value that failed at that + path." + [spec x] + (explain-data* spec [] (if-let [name (spec-name spec)] [name] []) [] x)) + +(defn explain-printer + "Default printer for explain-data. nil indicates a successful validation." + [ed] + (if ed + (let [problems (->> (::problems ed) + (sort-by #(- (count (:in %)))) + (sort-by #(- (count (:path %)))))] + (print + (with-out-str + ;;(prn {:ed ed}) + (doseq [{:keys [path pred val reason via in] :as prob} problems] + (pr val) + (print " - failed: ") + (if reason (print reason) (pr (abbrev pred))) + (when-not (empty? in) + (print (str " in: " (pr-str in)))) + (when-not (empty? path) + (print (str " at: " (pr-str path)))) + (when-not (empty? via) + (print (str " spec: " (pr-str (last via))))) + (doseq [[k v] prob] + (when-not (#{:path :pred :val :reason :via :in} k) + (print "\n\t" (pr-str k) " ") + (pr v))) + (newline))))) + (println "Success!"))) + +(def ^:dynamic *explain-out* explain-printer) + +(defn explain-out + "Prints explanation data (per 'explain-data') to *out* using the printer in *explain-out*, + by default explain-printer." + [ed] + (*explain-out* ed)) + +(defn explain + "Given a spec and a value that fails to conform, prints an explanation to *out*." + [spec x] + (explain-out (explain-data spec x))) + +(defn explain-str + "Given a spec and a value that fails to conform, returns an explanation as a string." + [spec x] + (with-out-str (explain spec x))) + +(declare ^{:arglists '([spec x] [spec x form])} valid?) + +(defn- gensub + [spec overrides path rmap form] + ;;(prn {:spec spec :over overrides :path path :form form}) + (let [spec (specize spec)] + (if-let [g (c/or (when-let [gfn (c/or (get overrides (c/or (spec-name spec) spec)) + (get overrides path))] + (gfn)) + (gen* spec overrides path rmap))] + (gen/such-that #(valid? spec %) g 100) + (throw (js/Error. (str "Unable to construct gen at: " path " for: " (abbrev form))))))) + +(defn gen + "Given a spec, returns the generator for it, or throws if none can + be constructed. Optionally an overrides map can be provided which + should map spec names or paths (vectors of keywords) to no-arg + generator-creating fns. These will be used instead of the generators at those + names/paths. Note that parent generator (in the spec or overrides + map) will supersede those of any subtrees. A generator for a regex + op must always return a sequential collection (i.e. a generator for + s/? should return either an empty sequence/vector or a + sequence/vector with one item in it)" + ([spec] (gen spec nil)) + ([spec overrides] (gensub spec overrides [] {::recursion-limit *recursion-limit*} spec))) + +(defn ^:skip-wiki def-impl + "Do not call this directly, use 'def'" + [k form spec] + (assert (c/and (ident? k) (namespace k)) "k must be namespaced keyword or resolveable symbol") + (if (nil? spec) + (swap! registry-ref dissoc k) + (let [spec (if (c/or (spec? spec) (regex? spec) (get @registry-ref spec)) + spec + (spec-impl form spec nil nil))] + (swap! registry-ref assoc k (with-name spec k)))) + k) + +(defn registry + "returns the registry map, prefer 'get-spec' to lookup a spec by name" + [] + @registry-ref) + +(defn- ->sym + "Returns a symbol from a symbol or var" + [x] + (if (var? x) + (.-sym x) + x)) + +(defn get-spec + "Returns spec registered for keyword/symbol/var k, or nil." + [k] + (get (registry) (if (keyword? k) k (->sym k)))) + +(declare map-spec) + +(defn- macroexpand-check + [v args] + (let [specs (get-spec v)] + (when-let [arg-spec (:args specs)] + (when (invalid? (conform arg-spec args)) + (let [ed (assoc (explain-data* arg-spec [] + (if-let [name (spec-name arg-spec)] [name] []) [] args) + ::args args)] + (throw (ex-info + (str + "Call to " (->sym v) " did not conform to spec.") + ed))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; impl ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn- recur-limit? [rmap id path k] + (c/and (> (get rmap id) (::recursion-limit rmap)) + (contains? (set path) k))) + +(defn- inck [m k] + (assoc m k (inc (c/or (get m k) 0)))) + +(defn- dt + ([pred x form] (dt pred x form nil)) + ([pred x form cpred?] + (if pred + (if-let [spec (the-spec pred)] + (conform spec x) + (if (ifn? pred) + (if cpred? + (pred x) + (if (pred x) x ::invalid)) + (throw (js/Error. (str (pr-str form) " is not a fn, expected predicate fn"))))) + x))) + +(defn valid? + "Helper function that returns true when x is valid for spec." + ([spec x] + (let [spec (specize spec)] + (not (invalid? (conform* spec x))))) + ([spec x form] + (let [spec (specize spec form)] + (not (invalid? (conform* spec x)))))) + +(defn- pvalid? + "internal helper function that returns true when x is valid for spec." + ([pred x] + (not (invalid? (dt pred x ::unknown)))) + ([pred x form] + (not (invalid? (dt pred x form))))) + +(defn- explain-1 [form pred path via in v] + ;;(prn {:form form :pred pred :path path :in in :v v}) + (let [pred (maybe-spec pred)] + (if (spec? pred) + (explain* pred path (if-let [name (spec-name pred)] (conj via name) via) in v) + [{:path path :pred form :val v :via via :in in}]))) + +(declare ^{:arglists '([s] [min-count s])} or-k-gen + ^{:arglists '([s])} and-k-gen) + +(defn- k-gen + "returns a generator for form f, which can be a keyword or a list + starting with 'or or 'and." + [f] + (cond + (keyword? f) (gen/return f) + (= 'or (first f)) (or-k-gen 1 (rest f)) + (= 'and (first f)) (and-k-gen (rest f)))) + +(defn- or-k-gen + "returns a tuple generator made up of generators for a random subset + of min-count (default 0) to all elements in s." + ([s] (or-k-gen 0 s)) + ([min-count s] + (gen/bind (gen/tuple + (gen/choose min-count (count s)) + (gen/shuffle (map k-gen s))) + (fn [[n gens]] + (apply gen/tuple (take n gens)))))) + +(defn- and-k-gen + "returns a tuple generator made up of generators for every element + in s." + [s] + (apply gen/tuple (map k-gen s))) + +(defn ^:skip-wiki map-spec-impl + "Do not call this directly, use 'spec' with a map argument" + [{:keys [req-un opt-un keys-pred pred-exprs opt-keys req-specs req req-keys opt-specs pred-forms opt gfn] + :as argm}] + (let [k->s (zipmap (concat req-keys opt-keys) (concat req-specs opt-specs)) + keys->specnames #(c/or (k->s %) %) + id (random-uuid)] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ m] + (if (keys-pred m) + (let [reg (registry)] + (loop [ret m, [[k v] & ks :as keys] m] + (if keys + (let [sname (keys->specnames k)] + (if-let [s (get reg sname)] + (let [cv (conform s v)] + (if (invalid? cv) + ::invalid + (recur (if (identical? cv v) ret (assoc ret k cv)) + ks))) + (recur ret ks))) + ret))) + ::invalid)) + (unform* [_ m] + (let [reg (registry)] + (loop [ret m, [k & ks :as keys] (c/keys m)] + (if keys + (if (contains? reg (keys->specnames k)) + (let [cv (get m k) + v (unform (keys->specnames k) cv)] + (recur (if (identical? cv v) ret (assoc ret k v)) + ks)) + (recur ret ks)) + ret)))) + (explain* [_ path via in x] + (if-not (map? x) + [{:path path :pred `map? :val x :via via :in in}] + (let [reg (registry)] + (apply concat + (when-let [probs (->> (map (fn [pred form] (when-not (pred x) form)) + pred-exprs pred-forms) + (keep identity) + seq)] + (map + #(identity {:path path :pred % :val x :via via :in in}) + probs)) + (map (fn [[k v]] + (when-not (c/or (not (contains? reg (keys->specnames k))) + (pvalid? (keys->specnames k) v k)) + (explain-1 (keys->specnames k) (keys->specnames k) (conj path k) via (conj in k) v))) + (seq x)))))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [rmap (inck rmap id) + rgen (fn [k s] [k (gensub s overrides (conj path k) rmap k)]) + ogen (fn [k s] + (when-not (recur-limit? rmap id path k) + [k (gen/delay (gensub s overrides (conj path k) rmap k))])) + reqs (map rgen req-keys req-specs) + opts (remove nil? (map ogen opt-keys opt-specs))] + (when (every? identity (concat (map second reqs) (map second opts))) + (gen/bind + (gen/tuple + (and-k-gen req) + (or-k-gen opt) + (and-k-gen req-un) + (or-k-gen opt-un)) + (fn [[req-ks opt-ks req-un-ks opt-un-ks]] + (let [qks (flatten (concat req-ks opt-ks)) + unqks (map (comp keyword name) (flatten (concat req-un-ks opt-un-ks)))] + (->> (into reqs opts) + (filter #((set (concat qks unqks)) (first %))) + (apply concat) + (apply gen/hash-map))))))))) + (with-gen* [_ gfn] (map-spec-impl (assoc argm :gfn gfn))) + (describe* [_] (cons `keys + (cond-> [] + req (conj :req req) + opt (conj :opt opt) + req-un (conj :req-un req-un) + opt-un (conj :opt-un opt-un))))))) + +(defn ^:skip-wiki spec-impl + "Do not call this directly, use 'spec'" + ([form pred gfn cpred?] (spec-impl form pred gfn cpred? nil)) + ([form pred gfn cpred? unc] + (cond + (spec? pred) (cond-> pred gfn (with-gen gfn)) + (regex? pred) (regex-spec-impl pred gfn) + (ident? pred) (cond-> (the-spec pred) gfn (with-gen gfn)) + :else + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (let [ret (pred x)] + (if cpred? + ret + (if ret x ::invalid)))) + (unform* [_ x] (if cpred? + (if unc + (unc x) + (throw (js/Error. "no unform fn for conformer"))) + x)) + (explain* [_ path via in x] + (when (invalid? (dt pred x form cpred?)) + [{:path path :pred form :val x :via via :in in}])) + (gen* [_ _ _ _] (if gfn + (gfn) + (gen/gen-for-pred pred))) + (with-gen* [_ gfn] (spec-impl form pred gfn cpred? unc)) + (describe* [_] form))))) + +(defn ^:skip-wiki multi-spec-impl + "Do not call this directly, use 'multi-spec'" + ([form mmvar retag] (multi-spec-impl form mmvar retag nil)) + ([form mmvar retag gfn] + (let [id (random-uuid) + predx #(let [mm @mmvar] + (c/and (-get-method mm ((-dispatch-fn mm) %)) + (mm %))) + dval #((-dispatch-fn @mmvar) %) + tag (if (keyword? retag) + #(assoc %1 retag %2) + retag)] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (if-let [pred (predx x)] + (dt pred x form) + ::invalid)) + (unform* [_ x] (if-let [pred (predx x)] + (unform pred x) + (throw (js/Error. (str "No method of: " form " for dispatch value: " (dval x)))))) + (explain* [_ path via in x] + (let [dv (dval x) + path (conj path dv)] + (if-let [pred (predx x)] + (explain-1 form pred path via in x) + [{:path path :pred form :val x :reason "no method" :via via :in in}]))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [gen (fn [[k f]] + (let [p (f nil)] + (let [rmap (inck rmap id)] + (when-not (recur-limit? rmap id path k) + (gen/delay + (gen/fmap + #(tag % k) + (gensub p overrides (conj path k) rmap (list 'method form k)))))))) + gs (->> (methods @mmvar) + (remove (fn [[k]] (invalid? k))) + (map gen) + (remove nil?))] + (when (every? identity gs) + (gen/one-of gs))))) + (with-gen* [_ gfn] (multi-spec-impl form mmvar retag gfn)) + (describe* [_] `(multi-spec ~form ~retag)))))) + +(defn ^:skip-wiki tuple-impl + "Do not call this directly, use 'tuple'" + ([forms preds] (tuple-impl forms preds nil)) + ([forms preds gfn] + (let [specs (delay (mapv specize preds forms)) + cnt (count preds)] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] + (let [specs @specs] + (if-not (c/and (vector? x) + (= (count x) cnt)) + ::invalid + (loop [ret x, i 0] + (if (= i cnt) + ret + (let [v (x i) + cv (conform* (specs i) v)] + (if (invalid? cv) + ::invalid + (recur (if (identical? cv v) ret (assoc ret i cv)) + (inc i))))))))) + (unform* [_ x] + (assert (c/and (vector? x) + (= (count x) (count preds)))) + (loop [ret x, i 0] + (if (= i (count x)) + ret + (let [cv (x i) + v (unform (preds i) cv)] + (recur (if (identical? cv v) ret (assoc ret i v)) + (inc i)))))) + (explain* [_ path via in x] + (cond + (not (vector? x)) + [{:path path :pred `vector? :val x :via via :in in}] + + (not= (count x) (count preds)) + [{:path path :pred `(= (count ~'%) ~(count preds)) :val x :via via :in in}] + + :else + (apply concat + (map (fn [i form pred] + (let [v (x i)] + (when-not (pvalid? pred v) + (explain-1 form pred (conj path i) via (conj in i) v)))) + (range (count preds)) forms preds)))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [gen (fn [i p f] + (gensub p overrides (conj path i) rmap f)) + gs (map gen (range (count preds)) preds forms)] + (when (every? identity gs) + (apply gen/tuple gs))))) + (with-gen* [_ gfn] (tuple-impl forms preds gfn)) + (describe* [_] `(tuple ~@forms)))))) + +(defn- tagged-ret [tag ret] + (MapEntry. tag ret nil)) + +(defn ^:skip-wiki or-spec-impl + "Do not call this directly, use 'or'" + [keys forms preds gfn] + (let [id (random-uuid) + kps (zipmap keys preds) + specs (delay (mapv specize preds forms)) + cform (case (count preds) + 2 (fn [x] + (let [specs @specs + ret (conform* (specs 0) x)] + (if (invalid? ret) + (let [ret (conform* (specs 1) x)] + (if (invalid? ret) + ::invalid + (tagged-ret (keys 1) ret))) + (tagged-ret (keys 0) ret)))) + 3 (fn [x] + (let [specs @specs + ret (conform* (specs 0) x)] + (if (invalid? ret) + (let [ret (conform* (specs 1) x)] + (if (invalid? ret) + (let [ret (conform* (specs 2) x)] + (if (invalid? ret) + ::invalid + (tagged-ret (keys 2) ret))) + (tagged-ret (keys 1) ret))) + (tagged-ret (keys 0) ret)))) + (fn [x] + (let [specs @specs] + (loop [i 0] + (if (< i (count specs)) + (let [spec (specs i)] + (let [ret (conform* spec x)] + (if (invalid? ret) + (recur (inc i)) + (tagged-ret (keys i) ret)))) + ::invalid)))))] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (cform x)) + (unform* [_ [k x]] (unform (kps k) x)) + (explain* [this path via in x] + (when-not (pvalid? this x) + (apply concat + (map (fn [k form pred] + (when-not (pvalid? pred x) + (explain-1 form pred (conj path k) via in x))) + keys forms preds)))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [gen (fn [k p f] + (let [rmap (inck rmap id)] + (when-not (recur-limit? rmap id path k) + (gen/delay + (gensub p overrides (conj path k) rmap f))))) + gs (remove nil? (map gen keys preds forms))] + (when-not (empty? gs) + (gen/one-of gs))))) + (with-gen* [_ gfn] (or-spec-impl keys forms preds gfn)) + (describe* [_] `(or ~@(mapcat vector keys forms)))))) + +(defn- and-preds [x preds forms] + (loop [ret x + [pred & preds] preds + [form & forms] forms] + (if pred + (let [nret (dt pred ret form)] + (if (invalid? nret) + ::invalid + ;;propagate conformed values + (recur nret preds forms))) + ret))) + +(defn- explain-pred-list + [forms preds path via in x] + (loop [ret x + [form & forms] forms + [pred & preds] preds] + (when pred + (let [nret (dt pred ret form)] + (if (invalid? nret) + (explain-1 form pred path via in ret) + (recur nret forms preds)))))) + +(defn ^:skip-wiki and-spec-impl + "Do not call this directly, use 'and'" + [forms preds gfn] + (let [specs (delay (mapv specize preds forms)) + cform + (case (count preds) + 2 (fn [x] + (let [specs @specs + ret (conform* (specs 0) x)] + (if (invalid? ret) + ::invalid + (conform* (specs 1) ret)))) + 3 (fn [x] + (let [specs @specs + ret (conform* (specs 0) x)] + (if (invalid? ret) + ::invalid + (let [ret (conform* (specs 1) ret)] + (if (invalid? ret) + ::invalid + (conform* (specs 2) ret)))))) + (fn [x] + (let [specs @specs] + (loop [ret x i 0] + (if (< i (count specs)) + (let [nret (conform* (specs i) ret)] + (if (invalid? nret) + ::invalid + ;;propagate conformed values + (recur nret (inc i)))) + ret)))))] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (cform x)) + (unform* [_ x] (reduce #(unform %2 %1) x (reverse preds))) + (explain* [_ path via in x] (explain-pred-list forms preds path via in x)) + (gen* [_ overrides path rmap] (if gfn (gfn) (gensub (first preds) overrides path rmap (first forms)))) + (with-gen* [_ gfn] (and-spec-impl forms preds gfn)) + (describe* [_] `(and ~@forms))))) + +(defn- coll-prob [x kfn kform distinct count min-count max-count + path via in] + (let [pred (c/or kfn coll?) + kform (c/or kform `coll?)] + (cond + (not (pvalid? pred x)) + (explain-1 kform pred path via in x) + + (c/and count (not= count (bounded-count count x))) + [{:path path :pred `(= ~count (c/count ~'%)) :val x :via via :in in}] + + (c/and (c/or min-count max-count) + (not (<= (c/or min-count 0) + (bounded-count (if max-count (inc max-count) min-count) x) + (c/or max-count MAX_INT)))) + [{:path path :pred `(<= ~(c/or min-count 0) (c/count ~'%) ~(c/or max-count MAX_INT)) :val x :via via :in in}] + + (c/and distinct (not (empty? x)) (not (apply distinct? x))) + [{:path path :pred 'distinct? :val x :via via :in in}]))) + +(defn ^:skip-wiki merge-spec-impl + "Do not call this directly, use 'merge'" + [forms preds gfn] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (let [ms (map #(dt %1 x %2) preds forms)] + (if (some invalid? ms) + ::invalid + (apply c/merge ms)))) + (unform* [_ x] (apply c/merge (map #(unform % x) (reverse preds)))) + (explain* [_ path via in x] + (apply concat + (map #(explain-1 %1 %2 path via in x) + forms preds))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (gen/fmap + #(apply c/merge %) + (apply gen/tuple (map #(gensub %1 overrides path rmap %2) + preds forms))))) + (with-gen* [_ gfn] (merge-spec-impl forms preds gfn)) + (describe* [_] `(merge ~@forms)))) + +(def ^:private empty-coll {`vector? [], `set? #{}, `list? (), `map? {}}) + +(defn ^:skip-wiki every-impl + "Do not call this directly, use 'every', 'every-kv', 'coll-of' or 'map-of'" + ([form pred opts] (every-impl form pred opts nil)) + ([form pred {conform-into :into + describe-form ::describe + :keys [kind ::kind-form count max-count min-count distinct gen-max ::kfn ::cpred + conform-keys ::conform-all] + :or {gen-max 20} + :as opts} + gfn] + (let [gen-into (if conform-into (empty conform-into) (get empty-coll kind-form)) + spec (delay (specize pred)) + check? #(valid? @spec %) + kfn (c/or kfn (fn [i v] i)) + addcv (fn [ret i v cv] (conj ret cv)) + cfns (fn [x] + ;;returns a tuple of [init add complete] fns + (cond + (c/and (vector? x) (c/or (not conform-into) (vector? conform-into))) + [identity + (fn [ret i v cv] + (if (identical? v cv) + ret + (assoc ret i cv))) + identity] + + (c/and (map? x) (c/or (c/and kind (not conform-into)) (map? conform-into))) + [(if conform-keys empty identity) + (fn [ret i v cv] + (if (c/and (identical? v cv) (not conform-keys)) + ret + (assoc ret (nth (if conform-keys cv v) 0) (nth cv 1)))) + identity] + + (c/or (list? conform-into) (seq? conform-into) (c/and (not conform-into) (c/or (list? x) (seq? x)))) + [empty addcv reverse] + + :else [#(empty (c/or conform-into %)) addcv identity]))] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] + (let [spec @spec] + (cond + (not (cpred x)) ::invalid + + conform-all + (let [[init add complete] (cfns x)] + (loop [ret (init x), i 0, [v & vs :as vseq] (seq x)] + (if vseq + (let [cv (conform* spec v)] + (if (invalid? cv) + ::invalid + (recur (add ret i v cv) (inc i) vs))) + (complete ret)))) + + :else + (if (indexed? x) + (let [step (max 1 (long (/ (c/count x) *coll-check-limit*)))] + (loop [i 0] + (if (>= i (c/count x)) + x + (if (valid? spec (nth x i)) + (recur (c/+ i step)) + ::invalid)))) + (let [limit *coll-check-limit*] + (loop [i 0 [v & vs :as vseq] (seq x)] + (cond + (c/or (nil? vseq) (= i limit)) x + (valid? spec v) (recur (inc i) vs) + :else ::invalid))))))) + (unform* [_ x] + (if conform-all + (let [spec @spec + [init add complete] (cfns x)] + (loop [ret (init x), i 0, [v & vs :as vseq] (seq x)] + (if (>= i (c/count x)) + (complete ret) + (recur (add ret i v (unform* spec v)) (inc i) vs)))) + x)) + (explain* [_ path via in x] + (c/or (coll-prob x kind kind-form distinct count min-count max-count + path via in) + (apply concat + ((if conform-all identity (partial take *coll-error-limit*)) + (keep identity + (map (fn [i v] + (let [k (kfn i v)] + (when-not (check? v) + (let [prob (explain-1 form pred path via (conj in k) v)] + prob)))) + (range) x)))))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (let [pgen (gensub pred overrides path rmap form)] + (gen/bind + (cond + gen-into (gen/return gen-into) + kind (gen/fmap #(if (empty? %) % (empty %)) + (gensub kind overrides path rmap form)) + :else (gen/return [])) + (fn [init] + (gen/fmap + #(if (vector? init) % (into init %)) + (cond + distinct + (if count + (gen/vector-distinct pgen {:num-elements count :max-tries 100}) + (gen/vector-distinct pgen {:min-elements (c/or min-count 0) + :max-elements (c/or max-count (max gen-max (c/* 2 (c/or min-count 0)))) + :max-tries 100})) + + count + (gen/vector pgen count) + + (c/or min-count max-count) + (gen/vector pgen (c/or min-count 0) (c/or max-count (max gen-max (c/* 2 (c/or min-count 0))))) + + :else + (gen/vector pgen 0 gen-max)))))))) + + (with-gen* [_ gfn] (every-impl form pred opts gfn)) + (describe* [_] (c/or describe-form `(every ~(s/mres form) ~@(mapcat identity opts)))))))) + +;;;;;;;;;;;;;;;;;;;;;;; regex ;;;;;;;;;;;;;;;;;;; +;;See: +;; http://matt.might.net/articles/implementation-of-regular-expression-matching-in-scheme-with-derivatives/ +;; http://www.ccs.neu.edu/home/turon/re-deriv.pdf + +;;ctors +(defn- accept [x] {::op ::accept :ret x}) + +(defn- accept? [{:keys [::op]}] + (= ::accept op)) + +(defn- pcat* [{[p1 & pr :as ps] :ps, [k1 & kr :as ks] :ks, [f1 & fr :as forms] :forms, ret :ret, rep+ :rep+}] + (when (every? identity ps) + (if (accept? p1) + (let [rp (:ret p1) + ret (conj ret (if ks {k1 rp} rp))] + (if pr + (pcat* {:ps pr :ks kr :forms fr :ret ret}) + (accept ret))) + {::op ::pcat, :ps ps, :ret ret, :ks ks, :forms forms :rep+ rep+}))) + +(defn- pcat [& ps] (pcat* {:ps ps :ret []})) + +(defn ^:skip-wiki cat-impl + "Do not call this directly, use 'cat'" + [ks ps forms] + (pcat* {:ks ks, :ps ps, :forms forms, :ret {}})) + +(defn- rep* [p1 p2 ret splice form] + (when p1 + (let [r {::op ::rep, :p2 p2, :splice splice, :forms form :id (random-uuid)}] + (if (accept? p1) + (assoc r :p1 p2 :ret (conj ret (:ret p1))) + (assoc r :p1 p1, :ret ret))))) + +(defn ^:skip-wiki rep-impl + "Do not call this directly, use '*'" + [form p] (rep* p p [] false form)) + +(defn ^:skip-wiki rep+impl + "Do not call this directly, use '+'" + [form p] + (pcat* {:ps [p (rep* p p [] true form)] :forms `[~form (* ~form)] :ret [] :rep+ form})) + +(defn ^:skip-wiki amp-impl + "Do not call this directly, use '&'" + [re re-form preds pred-forms] + {::op ::amp :p1 re :amp re-form :ps preds :forms pred-forms}) + +(defn- filter-alt [ps ks forms f] + (if (c/or ks forms) + (let [pks (->> (map vector ps + (c/or (seq ks) (repeat nil)) + (c/or (seq forms) (repeat nil))) + (filter #(-> % first f)))] + [(seq (map first pks)) (when ks (seq (map second pks))) (when forms (seq (map #(nth % 2) pks)))]) + [(seq (filter f ps)) ks forms])) + +(defn- alt* [ps ks forms] + (let [[[p1 & pr :as ps] [k1 :as ks] forms] (filter-alt ps ks forms identity)] + (when ps + (let [ret {::op ::alt, :ps ps, :ks ks :forms forms}] + (if (nil? pr) + (if k1 + (if (accept? p1) + (accept (tagged-ret k1 (:ret p1))) + ret) + p1) + ret))))) + +(defn- alts [& ps] (alt* ps nil nil)) +(defn- alt2 [p1 p2] (if (c/and p1 p2) (alts p1 p2) (c/or p1 p2))) + +(defn ^:skip-wiki alt-impl + "Do not call this directly, use 'alt'" + [ks ps forms] (assoc (alt* ps ks forms) :id (random-uuid))) + +(defn ^:skip-wiki maybe-impl + "Do not call this directly, use '?'" + [p form] (assoc (alt* [p (accept ::nil)] nil [form ::nil]) :maybe form)) + +(defn- noret? [p1 pret] + (c/or (= pret ::nil) + (c/and (#{::rep ::pcat} (::op (reg-resolve! p1))) ;;hrm, shouldn't know these + (empty? pret)) + nil)) + +(declare ^{:arglists '([p])} preturn) + +(defn- accept-nil? [p] + (let [{:keys [::op ps p1 p2 forms] :as p} (reg-resolve! p)] + (case op + ::accept true + nil nil + ::amp (c/and (accept-nil? p1) + (let [ret (-> (preturn p1) (and-preds ps (next forms)))] + (not (invalid? ret)))) + ::rep (c/or (identical? p1 p2) (accept-nil? p1)) + ::pcat (every? accept-nil? ps) + ::alt (c/some accept-nil? ps)))) + +(declare ^{:arglists '([p r k])} add-ret) + +(defn- preturn [p] + (let [{[p0 & pr :as ps] :ps, [k :as ks] :ks, :keys [::op p1 ret forms] :as p} (reg-resolve! p)] + (case op + ::accept ret + nil nil + ::amp (let [pret (preturn p1)] + (if (noret? p1 pret) + ::nil + (and-preds pret ps forms))) + ::rep (add-ret p1 ret k) + ::pcat (add-ret p0 ret k) + ::alt (let [[[p0] [k0]] (filter-alt ps ks forms accept-nil?) + r (if (nil? p0) ::nil (preturn p0))] + (if k0 (tagged-ret k0 r) r))))) + +(defn- op-unform [p x] + ;;(prn {:p p :x x}) + (let [{[p0 & pr :as ps] :ps, [k :as ks] :ks, :keys [::op p1 ret forms rep+ maybe] :as p} (reg-resolve! p) + kps (zipmap ks ps)] + (case op + ::accept [ret] + nil [(unform p x)] + ::amp (let [px (reduce #(unform %2 %1) x (reverse ps))] + (op-unform p1 px)) + ::rep (mapcat #(op-unform p1 %) x) + ::pcat (if rep+ + (mapcat #(op-unform p0 %) x) + (mapcat (fn [k] + (when (contains? x k) + (op-unform (kps k) (get x k)))) + ks)) + ::alt (if maybe + [(unform p0 x)] + (let [[k v] x] + (op-unform (kps k) v)))))) + +(defn- add-ret [p r k] + (let [{:keys [::op ps splice] :as p} (reg-resolve! p) + prop #(let [ret (preturn p)] + (if (empty? ret) r ((if splice into conj) r (if k {k ret} ret))))] + (case op + nil r + (::alt ::accept ::amp) + (let [ret (preturn p)] + ;;(prn {:ret ret}) + (if (= ret ::nil) r (conj r (if k {k ret} ret)))) + + (::rep ::pcat) (prop)))) + +(defn- deriv + [p x] + (let [{[p0 & pr :as ps] :ps, [k0 & kr :as ks] :ks, :keys [::op p1 p2 ret splice forms amp] :as p} (reg-resolve! p)] + (when p + (case op + ::accept nil + nil (let [ret (dt p x p)] + (when-not (invalid? ret) (accept ret))) + ::amp (when-let [p1 (deriv p1 x)] + (if (= ::accept (::op p1)) + (let [ret (-> (preturn p1) (and-preds ps (next forms)))] + (when-not (invalid? ret) + (accept ret))) + (amp-impl p1 amp ps forms))) + ::pcat (alt2 (pcat* {:ps (cons (deriv p0 x) pr), :ks ks, :forms forms, :ret ret}) + (when (accept-nil? p0) (deriv (pcat* {:ps pr, :ks kr, :forms (next forms), :ret (add-ret p0 ret k0)}) x))) + ::alt (alt* (map #(deriv % x) ps) ks forms) + ::rep (alt2 (rep* (deriv p1 x) p2 ret splice forms) + (when (accept-nil? p1) (deriv (rep* p2 p2 (add-ret p1 ret nil) splice forms) x))))))) + +(defn- op-describe [p] + (let [{:keys [::op ps ks forms splice p1 rep+ maybe amp] :as p} (reg-resolve! p)] + ;;(prn {:op op :ks ks :forms forms :p p}) + (when p + (case op + ::accept nil + nil p + ::amp (list* 'cljs.spec.alpha/& amp forms) + ::pcat (if rep+ + (list `+ rep+) + (cons `cat (mapcat vector (c/or (seq ks) (repeat :_)) forms))) + ::alt (if maybe + (list `? maybe) + (cons `alt (mapcat vector ks forms))) + ::rep (list (if splice `+ `*) forms))))) + +(defn- op-explain [form p path via in input] + ;;(prn {:form form :p p :path path :input input}) + (let [[x :as input] input + {:keys [::op ps ks forms splice p1 p2] :as p} (reg-resolve! p) + via (if-let [name (spec-name p)] (conj via name) via) + insufficient (fn [path form] + [{:path path + :reason "Insufficient input" + :pred form + :val () + :via via + :in in}])] + (when p + (case op + ::accept nil + nil (if (empty? input) + (insufficient path form) + (explain-1 form p path via in x)) + ::amp (if (empty? input) + (if (accept-nil? p1) + (explain-pred-list forms ps path via in (preturn p1)) + (insufficient path (:amp p))) + (if-let [p1 (deriv p1 x)] + (explain-pred-list forms ps path via in (preturn p1)) + (op-explain (:amp p) p1 path via in input))) + ::pcat (let [pkfs (map vector + ps + (c/or (seq ks) (repeat nil)) + (c/or (seq forms) (repeat nil))) + [pred k form] (if (= 1 (count pkfs)) + (first pkfs) + (first (remove (fn [[p]] (accept-nil? p)) pkfs))) + path (if k (conj path k) path) + form (c/or form (op-describe pred))] + (if (c/and (empty? input) (not pred)) + (insufficient path form) + (op-explain form pred path via in input))) + ::alt (if (empty? input) + (insufficient path (op-describe p)) + (apply concat + (map (fn [k form pred] + (op-explain (c/or form (op-describe pred)) + pred + (if k (conj path k) path) + via + in + input)) + (c/or (seq ks) (repeat nil)) + (c/or (seq forms) (repeat nil)) + ps))) + ::rep (op-explain (if (identical? p1 p2) + forms + (op-describe p1)) + p1 path via in input))))) + +(defn- re-gen [p overrides path rmap f] + ;;(prn {:op op :ks ks :forms forms}) + (let [origp p + {:keys [::op ps ks p1 p2 forms splice ret id ::gfn] :as p} (reg-resolve! p) + rmap (if id (inck rmap id) rmap) + ggens (fn [ps ks forms] + (let [gen (fn [p k f] + ;;(prn {:k k :path path :rmap rmap :op op :id id}) + (when-not (c/and rmap id k (recur-limit? rmap id path k)) + (if id + (gen/delay (re-gen p overrides (if k (conj path k) path) rmap (c/or f p))) + (re-gen p overrides (if k (conj path k) path) rmap (c/or f p)))))] + (map gen ps (c/or (seq ks) (repeat nil)) (c/or (seq forms) (repeat nil)))))] + (c/or (when-let [gfn (c/or (get overrides (spec-name origp)) + (get overrides (spec-name p)) + (get overrides path))] + (case op + (:accept nil) (gen/fmap vector (gfn)) + (gfn))) + (when gfn + (gfn)) + (when p + (case op + ::accept (if (= ret ::nil) + (gen/return []) + (gen/return [ret])) + nil (when-let [g (gensub p overrides path rmap f)] + (gen/fmap vector g)) + ::amp (re-gen p1 overrides path rmap (op-describe p1)) + ::pcat (let [gens (ggens ps ks forms)] + (when (every? identity gens) + (apply gen/cat gens))) + ::alt (let [gens (remove nil? (ggens ps ks forms))] + (when-not (empty? gens) + (gen/one-of gens))) + ::rep (if (recur-limit? rmap id [id] id) + (gen/return []) + (when-let [g (re-gen p2 overrides path rmap forms)] + (gen/fmap #(apply concat %) + (gen/vector g))))))))) + +(defn- re-conform [p [x & xs :as data]] + ;;(prn {:p p :x x :xs xs}) + (if (empty? data) + (if (accept-nil? p) + (let [ret (preturn p)] + (if (= ret ::nil) + nil + ret)) + ::invalid) + (if-let [dp (deriv p x)] + (recur dp xs) + ::invalid))) + +(defn- re-explain [path via in re input] + (loop [p re [x & xs :as data] input i 0] + ;;(prn {:p p :x x :xs xs :re re}) (prn) + (if (empty? data) + (if (accept-nil? p) + nil ;;success + (op-explain (op-describe p) p path via in nil)) + (if-let [dp (deriv p x)] + (recur dp xs (inc i)) + (if (accept? p) + (if (= (::op p) ::pcat) + (op-explain (op-describe p) p path via (conj in i) (seq data)) + [{:path path + :reason "Extra input" + :pred (op-describe re) + :val data + :via via + :in (conj in i)}]) + (c/or (op-explain (op-describe p) p path via (conj in i) (seq data)) + [{:path path + :reason "Extra input" + :pred (op-describe p) + :val data + :via via + :in (conj in i)}])))))) + +(defn ^:skip-wiki regex-spec-impl + "Do not call this directly, use 'spec' with a regex op argument" + [re gfn] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] + (if (c/or (nil? x) (sequential? x)) + (re-conform re (seq x)) + ::invalid)) + (unform* [_ x] (op-unform re x)) + (explain* [_ path via in x] + (if (c/or (nil? x) (sequential? x)) + (re-explain path via in re (seq x)) + [{:path path :pred `(fn [~'%] (c/or (nil? ~'%) (sequential? ~'%))) :val x :via via :in in}])) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (re-gen re overrides path rmap (op-describe re)))) + (with-gen* [_ gfn] (regex-spec-impl re gfn)) + (describe* [_] (op-describe re)))) + +;;;;;;;;;;;;;;;;; HOFs ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- call-valid? + [f specs args] + (let [cargs (conform (:args specs) args)] + (when-not (invalid? cargs) + (let [ret (apply f args) + cret (conform (:ret specs) ret)] + (c/and (not (invalid? cret)) + (if (:fn specs) + (pvalid? (:fn specs) {:args cargs :ret cret}) + true)))))) + +(defn- validate-fn + "returns f if valid, else smallest" + [f specs iters] + (let [g (gen (:args specs)) + prop (gen/for-all* [g] #(call-valid? f specs %))] + (let [ret (gen/quick-check iters prop)] + (if-let [[smallest] (-> ret :shrunk :smallest)] + smallest + f)))) + +(defn ^:skip-wiki fspec-impl + "Do not call this directly, use 'fspec'" + [argspec aform retspec rform fnspec fform gfn] + (let [specs {:args argspec :ret retspec :fn fnspec}] + (reify + ILookup + (-lookup [this k] (get specs k)) + (-lookup [_ k not-found] (get specs k not-found)) + + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ f] (if (ifn? f) + (if (identical? f (validate-fn f specs *fspec-iterations*)) f ::invalid) + ::invalid)) + (unform* [_ f] f) + (explain* [_ path via in f] + (if (ifn? f) + (let [args (validate-fn f specs 100)] + (if (identical? f args) ;;hrm, we might not be able to reproduce + nil + (let [ret (try (apply f args) (catch js/Error t t))] + (if (instance? js/Error ret) + ;;TODO add exception data + [{:path path :pred '(apply fn) :val args :reason (.-message ret) :via via :in in}] + + (let [cret (dt retspec ret rform)] + (if (invalid? cret) + (explain-1 rform retspec (conj path :ret) via in ret) + (when fnspec + (let [cargs (conform argspec args)] + (explain-1 fform fnspec (conj path :fn) via in {:args cargs :ret cret}))))))))) + [{:path path :pred 'ifn? :val f :via via :in in}])) + (gen* [_ overrides _ _] (if gfn + (gfn) + (gen/return + (fn [& args] + (assert (pvalid? argspec args) (with-out-str (explain argspec args))) + (gen/generate (gen retspec overrides)))))) + (with-gen* [_ gfn] (fspec-impl argspec aform retspec rform fnspec fform gfn)) + (describe* [_] `(fspec :args ~aform :ret ~rform :fn ~fform))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; non-primitives ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(cljs.spec.alpha/def ::kvs->map (cljs.spec.alpha/conformer #(zipmap (map ::k %) (map ::v %)) #(map (fn [[k v]] {::k k ::v v}) %))) + +(defn nonconforming + "takes a spec and returns a spec that has the same properties except + 'conform' returns the original (not the conformed) value. Note, will specize regex ops." + [spec] + (let [spec (delay (specize spec))] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (let [ret (conform* @spec x)] + (if (invalid? ret) + ::invalid + x))) + (unform* [_ x] (unform* @spec x)) + (explain* [_ path via in x] (explain* @spec path via in x)) + (gen* [_ overrides path rmap] (gen* @spec overrides path rmap)) + (with-gen* [_ gfn] (nonconforming (with-gen* @spec gfn))) + (describe* [_] `(nonconforming ~(describe* @spec)))))) + +(defn ^:skip-wiki nilable-impl + "Do not call this directly, use 'nilable'" + [form pred gfn] + (let [spec (delay (specize pred form))] + (reify + Specize + (specize* [s] s) + (specize* [s _] s) + + Spec + (conform* [_ x] (if (nil? x) nil (conform* @spec x))) + (unform* [_ x] (if (nil? x) nil (unform* @spec x))) + (explain* [_ path via in x] + (when-not (c/or (pvalid? @spec x) (nil? x)) + (conj + (explain-1 form pred (conj path ::pred) via in x) + {:path (conj path ::nil) :pred 'nil? :val x :via via :in in}))) + (gen* [_ overrides path rmap] + (if gfn + (gfn) + (gen/frequency + [[1 (gen/delay (gen/return nil))] + [9 (gen/delay (gensub pred overrides (conj path ::pred) rmap form))]]))) + (with-gen* [_ gfn] (nilable-impl form pred gfn)) + (describe* [_] `(nilable ~(s/mres form)))))) + +(defn exercise + "generates a number (default 10) of values compatible with spec and maps conform over them, + returning a sequence of [val conformed-val] tuples. Optionally takes + a generator overrides map as per gen" + ([spec] (exercise spec 10)) + ([spec n] (exercise spec n nil)) + ([spec n overrides] + (map #(vector % (conform spec %)) (gen/sample (gen spec overrides) n)))) + +(defn inst-in-range? + "Return true if inst at or after start and before end" + [start end inst] + (c/and (inst? inst) + (let [t (inst-ms inst)] + (c/and (<= (inst-ms start) t) (< t (inst-ms end)))))) + +(defn int-in-range? + "Return true if start <= val, val < end and val is a fixed + precision integer." + [start end val] + (cond + (integer? val) (c/and (<= start val) (< val end)) + + (instance? goog.math.Long val) + (c/and (.lessThanOrEqual start val) + (.lessThan val end)) + + (instance? goog.math.Integer val) + (c/and (.lessThanOrEqual start val) + (.lessThan val end)) + + :else false)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; assert ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defonce + ^{:dynamic true + :doc "If true, compiler will enable spec asserts, which are then +subject to runtime control via check-asserts? If false, compiler +will eliminate all spec assert overhead. See 'assert'. +Initially set to the negation of the ':elide-asserts' compiler option. +Defaults to true."} + *compile-asserts* + (s/init-compile-asserts)) + +(defonce ^{:private true + :dynamic true} + *runtime-asserts* + false) + +(defn ^boolean check-asserts? + "Returns the value set by check-asserts." + [] + *runtime-asserts*) + +(defn check-asserts + "Enable or disable spec asserts that have been compiled +with '*compile-asserts*' true. See 'assert'. +Initially set to boolean value of cljs.spec.alpha/*runtime-asserts*. +Defaults to false." + [^boolean flag] + (set! *runtime-asserts* flag)) + +(defn assert* + "Do not call this directly, use 'assert'." + [spec x] + (if (valid? spec x) + x + (let [ed (c/merge (assoc (explain-data* spec [] [] [] x) + ::failure :assertion-failed))] + (throw (js/Error. + (str "Spec assertion failed\n" (with-out-str (explain-out ed)))))))) diff --git a/src/main/cljs/cljs/spec/gen/alpha.cljc b/src/main/cljs/cljs/spec/gen/alpha.cljc new file mode 100644 index 0000000000..43da3842a5 --- /dev/null +++ b/src/main/cljs/cljs/spec/gen/alpha.cljc @@ -0,0 +1,68 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.spec.gen.alpha + (:refer-clojure :exclude [delay]) + (:require [cljs.core :as c] + [clojure.string :as string])) + +(defmacro dynaload [[quote s]] + `(cljs.spec.gen.alpha/LazyVar. + (fn [] + (if (c/exists? ~s) + ~(vary-meta s assoc :cljs.analyzer/no-resolve true) + (throw + (js/Error. + (str "Var " '~s " does not exist, " + (namespace '~s) " never required"))))) + nil)) + +(defmacro delay + "given body that returns a generator, returns a + generator that delegates to that, but delays + creation until used." + [& body] + `(delay-impl (c/delay ~@body))) + +(defmacro ^:skip-wiki lazy-combinator + "Implementation macro, do not call directly." + [s] + (let [fqn (symbol "clojure.test.check.generators" (name s)) + doc (str "Lazy loaded version of " fqn)] + `(let [g# (dynaload '~fqn)] + (defn ~s + ~doc + [& ~'args] + (apply @g# ~'args))))) + +(defmacro ^:skip-wiki lazy-combinators + "Implementation macro, do not call directly." + [& syms] + `(do + ~@(map + (fn [s] (list `lazy-combinator s)) + syms))) + +(defmacro ^:skip-wiki lazy-prim + "Implementation macro, do not call directly." + [s] + (let [fqn (symbol "clojure.test.check.generators" (name s)) + doc (str "Fn returning " fqn)] + `(let [g# (dynaload '~fqn)] + (defn ~s + ~doc + [& ~'args] + @g#)))) + +(defmacro ^:skip-wiki lazy-prims + "Implementation macro, do not call directly." + [& syms] + `(do + ~@(map + (fn [s] (list `lazy-prim s)) + syms))) \ No newline at end of file diff --git a/src/main/cljs/cljs/spec/gen/alpha.cljs b/src/main/cljs/cljs/spec/gen/alpha.cljs new file mode 100644 index 0000000000..65b0a87a49 --- /dev/null +++ b/src/main/cljs/cljs/spec/gen/alpha.cljs @@ -0,0 +1,183 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.spec.gen.alpha + (:refer-clojure :exclude [boolean cat hash-map list map not-empty set vector + char double int keyword symbol string uuid delay shuffle]) + (:require-macros [cljs.core :as c] + [cljs.spec.gen.alpha :as gen :refer [dynaload lazy-combinators lazy-prims]]) + (:require [cljs.core :as c]) + (:import (goog Uri))) + +(deftype LazyVar [f ^:mutable cached] + IDeref + (-deref [this] + (if-not (nil? cached) + cached + (let [x (f)] + (when-not (nil? x) + (set! cached x)) + x)))) + +(def ^:private quick-check-ref + (dynaload 'clojure.test.check/quick-check)) + +(defn quick-check + [& args] + (apply @quick-check-ref args)) + +(def ^:private for-all*-ref + (dynaload 'clojure.test.check.properties/for-all*)) + +(defn for-all* + "Dynamically loaded clojure.test.check.properties/for-all*." + [& args] + (apply @for-all*-ref args)) + +(let [g? (dynaload 'clojure.test.check.generators/generator?) + g (dynaload 'clojure.test.check.generators/generate) + mkg (dynaload 'clojure.test.check.generators/->Generator)] + (defn- generator? + [x] + (@g? x)) + (defn- generator + [gfn] + (@mkg gfn)) + (defn generate + "Generate a single value using generator." + [generator] + (@g generator))) + +(defn ^:skip-wiki delay-impl + [gfnd] + ;;N.B. depends on test.check impl details + (generator (fn [rnd size] + ((:gen @gfnd) rnd size)))) + +;(defn gen-for-name +; "Dynamically loads test.check generator named s." +; [s] +; (let [g (dynaload s)] +; (if (generator? g) +; g +; (throw (js/Error. (str "Var " s " is not a generator")))))) + +(lazy-combinators hash-map list map not-empty set vector vector-distinct fmap elements + bind choose one-of such-that tuple sample return + large-integer* double* frequency shuffle) + +(lazy-prims any any-printable boolean char char-alpha char-alphanumeric char-ascii double + int keyword keyword-ns large-integer ratio simple-type simple-type-printable + string string-ascii string-alphanumeric symbol symbol-ns uuid) + +(defn cat + "Returns a generator of a sequence catenated from results of +gens, each of which should generate something sequential." + [& gens] + (fmap #(apply concat %) + (apply tuple gens))) + +(defn- ^boolean qualified? [ident] (not (nil? (namespace ident)))) + +(def ^:private +gen-builtins + (c/delay + (let [simple (simple-type-printable)] + {any? (one-of [(return nil) (any-printable)]) + some? (such-that some? (any-printable)) + number? (one-of [(large-integer) (double)]) + integer? (large-integer) + int? (large-integer) + pos-int? (large-integer* {:min 1}) + neg-int? (large-integer* {:max -1}) + nat-int? (large-integer* {:min 0}) + float? (double) + double? (double) + string? (string-alphanumeric) + ident? (one-of [(keyword-ns) (symbol-ns)]) + simple-ident? (one-of [(keyword) (symbol)]) + qualified-ident? (such-that qualified? (one-of [(keyword-ns) (symbol-ns)])) + keyword? (keyword-ns) + simple-keyword? (keyword) + qualified-keyword? (such-that qualified? (keyword-ns)) + symbol? (symbol-ns) + simple-symbol? (symbol) + qualified-symbol? (such-that qualified? (symbol-ns)) + uuid? (uuid) + uri? (fmap #(Uri. (str "http://" % ".com")) (uuid)) + inst? (fmap #(js/Date. %) + (large-integer)) + seqable? (one-of [(return nil) + (list simple) + (vector simple) + (map simple simple) + (set simple) + (string-alphanumeric)]) + indexed? (vector simple) + map? (map simple simple) + vector? (vector simple) + list? (list simple) + seq? (list simple) + char? (char) + set? (set simple) + nil? (return nil) + false? (return false) + true? (return true) + boolean? (boolean) + zero? (return 0) + ;rational? (one-of [(large-integer) (ratio)]) + coll? (one-of [(map simple simple) + (list simple) + (vector simple) + (set simple)]) + empty? (elements [nil '() [] {} #{}]) + associative? (one-of [(map simple simple) (vector simple)]) + sequential? (one-of [(list simple) (vector simple)]) + ;ratio? (such-that ratio? (ratio)) + }))) + +(defn gen-for-pred + "Given a predicate, returns a built-in generator if one exists." + [pred] + (if (set? pred) + (elements pred) + (get @gen-builtins pred))) + +(comment + (require 'clojure.test.check) + (require 'clojure.test.check.properties) + (require 'cljs.spec.gen) + (in-ns 'cljs.spec.gen) + + ;; combinators, see call to lazy-combinators above for complete list + (generate (one-of [(gen-for-pred integer?) (gen-for-pred string?)])) + (generate (such-that #(< 10000 %) (gen-for-pred integer?))) + (let [reqs {:a (gen-for-pred number?) + :b (gen-for-pred keyword?)} + opts {:c (gen-for-pred string?)}] + (generate (bind (choose 0 (count opts)) + #(let [args (concat (seq reqs) (c/shuffle (seq opts)))] + (->> args + (take (+ % (count reqs))) + (mapcat identity) + (apply hash-map)))))) + (generate (cat (list (gen-for-pred string?)) + (list (gen-for-pred integer?)))) + + ;; load your own generator + ;(gen-for-name 'clojure.test.check.generators/int) + + ;; failure modes + ;(gen-for-name 'unqualified) + ;(gen-for-name 'clojure.core/+) + ;(gen-for-name 'clojure.core/name-does-not-exist) + ;(gen-for-name 'ns.does.not.exist/f) + + ) + + diff --git a/src/main/cljs/cljs/spec/test/alpha.cljc b/src/main/cljs/cljs/spec/test/alpha.cljc new file mode 100644 index 0000000000..5c131f6c32 --- /dev/null +++ b/src/main/cljs/cljs/spec/test/alpha.cljc @@ -0,0 +1,329 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.spec.test.alpha + (:require + [cljs.analyzer :as ana] + [cljs.analyzer.api :as ana-api] + [clojure.string :as string] + [cljs.spec.alpha :as s] + [cljs.spec.gen.alpha :as gen])) + +(defonce ^:private instrumented-vars (atom #{})) + +(defn- collectionize + [x] + (if (symbol? x) + (list x) + x)) + +(defn- enumerate-namespace* [sym-or-syms] + (into #{} + (mapcat + (fn [sym] + (->> (vals (ana-api/ns-interns sym)) + (map :name) + (map + (fn [name-sym] + (symbol (name sym) (name name-sym))))))) + (collectionize sym-or-syms))) + +(defmacro enumerate-namespace + "Given a symbol naming an ns, or a collection of such symbols, +returns the set of all symbols naming vars in those nses." + [ns-sym-or-syms] + `'~(enumerate-namespace* (eval ns-sym-or-syms))) + +(defn- fn-spec-name? + [s] + (symbol? s)) + +(defmacro with-instrument-disabled + "Disables instrument's checking of calls, within a scope." + [& body] + ;; Note: In order to read the value of this private var, we employ interop + ;; rather than derefing a var special. This eases specing core functions + ;; (and infinite recursion) by avoiding code generated by the var special, + ;; and also produces more compact / efficient code. + `(let [orig# (.-*instrument-enabled* js/cljs.spec.test.alpha)] + (set! *instrument-enabled* nil) + (try + ~@body + (finally + (set! *instrument-enabled* orig#))))) + +(defmacro instrument-1 + [[quote s] opts] + (when-let [v (ana-api/resolve &env s)] + (let [var-name (:name v)] + (when (and (nil? (:const v)) + #?(:cljs (nil? (:macro v))) + (contains? #?(:clj (s/speced-vars) + :cljs (cljs.spec.alpha$macros/speced-vars)) + var-name)) + (swap! instrumented-vars conj var-name) + `(let [checked# (#'instrument-1* '~s (var ~s) ~opts)] + (when checked# (set! ~s checked#)) + '~var-name))))) + +(defmacro unstrument-1 + [[quote s]] + (when-let [v (ana-api/resolve &env s)] + (when (@instrumented-vars (:name v)) + (swap! instrumented-vars disj (:name v)) + `(let [raw# (#'unstrument-1* '~s (var ~s))] + (when raw# (set! ~s raw#)) + '~(:name v))))) + +(defn- sym-or-syms->syms [sym-or-syms] + (into [] + (mapcat + (fn [sym] + (if (and (string/includes? (str sym) ".") + (ana-api/find-ns sym)) + (->> (vals (ana-api/ns-interns sym)) + (filter #(not (:macro %))) + (map :name) + (map + (fn [name-sym] + (symbol (name sym) (name name-sym))))) + [sym]))) + (collectionize sym-or-syms))) + +(defn- form->sym-or-syms + "Helper for extracting a symbol or symbols from a (potentially + user-supplied) quoted form. In the case that the form has ::no-eval meta, we + know it was generated by us and we directly extract the result, assuming the + shape of the form. This avoids applying eval to extremely large forms in the + latter case." + [sym-or-syms] + (if (::no-eval (meta sym-or-syms)) + (second sym-or-syms) + (eval sym-or-syms))) + +(defmacro instrument + "Instruments the vars named by sym-or-syms, a symbol or collection +of symbols, or all instrumentable vars if sym-or-syms is not +specified. If a symbol identifies a namespace then all symbols in that +namespace will be enumerated. + +If a var has an :args fn-spec, sets the var's root binding to a +fn that checks arg conformance (throwing an exception on failure) +before delegating to the original fn. + +The opts map can be used to override registered specs, and/or to +replace fn implementations entirely. Opts for symbols not included +in sym-or-syms are ignored. This facilitates sharing a common +options map across many different calls to instrument. + +The opts map may have the following keys: + + :spec a map from var-name symbols to override specs + :stub a set of var-name symbols to be replaced by stubs + :gen a map from spec names to generator overrides + :replace a map from var-name symbols to replacement fns + +:spec overrides registered fn-specs with specs your provide. Use +:spec overrides to provide specs for libraries that do not have +them, or to constrain your own use of a fn to a subset of its +spec'ed contract. + +:stub replaces a fn with a stub that checks :args, then uses the +:ret spec to generate a return value. + +:gen overrides are used only for :stub generation. + +:replace replaces a fn with a fn that checks args conformance, then +invokes the fn you provide, enabling arbitrary stubbing and mocking. + +:spec can be used in combination with :stub or :replace. + +Returns a collection of syms naming the vars instrumented." + ([] + `(instrument ^::no-eval '[~@(#?(:clj s/speced-vars + :cljs cljs.spec.alpha$macros/speced-vars))])) + ([xs] + `(instrument ~xs nil)) + ([sym-or-syms opts] + (let [syms (sym-or-syms->syms (form->sym-or-syms sym-or-syms)) + opts-sym (gensym "opts")] + `(let [~opts-sym ~opts] + (reduce + (fn [ret# [_# f#]] + (let [sym# (f#)] + (cond-> ret# sym# (conj sym#)))) + [] + (->> (zipmap '~syms + [~@(map + (fn [sym] + `(fn [] (instrument-1 '~sym ~opts-sym))) + syms)]) + (filter #((instrumentable-syms ~opts-sym) (first %))) + (distinct-by first))))))) + +(defmacro unstrument + "Undoes instrument on the vars named by sym-or-syms, specified +as in instrument. With no args, unstruments all instrumented vars. +Returns a collection of syms naming the vars unstrumented." + ([] + `(unstrument ^::no-eval '[~@(deref instrumented-vars)])) + ([sym-or-syms] + (let [syms (sym-or-syms->syms (form->sym-or-syms sym-or-syms))] + `(reduce + (fn [ret# f#] + (let [sym# (f#)] + (cond-> ret# sym# (conj sym#)))) + [] + [~@(->> syms + (map + (fn [sym] + (when (symbol? sym) + `(fn [] + (unstrument-1 '~sym))))) + (remove nil?))])))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; testing ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro check-1 + [[quote s :as qs] f spec opts] + (let [{:keys [name] :as v} (when qs (ana-api/resolve &env s))] + `(let [s# '~name + v# ~(when v `(var ~name)) + spec# (or ~spec ~(when v `(s/get-spec (var ~name)))) + re-inst?# (and v# (seq (unstrument '~name)) true) + f# (or ~f (when v# @v#)) + opts# ~opts + old-tc-ns# "clojure.test.check" + old-tc-opts-key# (keyword old-tc-ns# "opts") + [tc-ns# opts#] (if-let [old-tc-opts# (get opts# old-tc-opts-key#)] + [old-tc-ns# (assoc opts# :clojure.spec.test.check/opts + old-tc-opts#)] + ["clojure.spec.test.check" opts#])] + (try + (cond + (nil? f#) + {:failure (ex-info "No fn to spec" {::s/failure :no-fn}) + :sym s# :spec spec#} + + (:args spec#) + (let [tcret# (#'quick-check f# spec# opts#)] + (#'make-check-result s# spec# tcret# + (keyword tc-ns# "ret"))) + + :default + {:failure (ex-info "No :args spec" {::s/failure :no-args-spec}) + :sym s# :spec spec#}) + (finally + (when re-inst?# (instrument '~name))))))) + +(defmacro check-fn + "Runs generative tests for fn f using spec and opts. See +'check' for options and return." + ([f spec] + `(check-fn ~f ~spec nil)) + ([f spec opts] + `(let [opts# ~opts] + (validate-check-opts opts#) + (check-1 nil ~f ~spec opts#)))) + +(defn- registry-ref [] + #?(:clj @#'s/registry-ref + :cljs cljs.spec.alpha$macros/registry-ref)) + +(defn checkable-syms* + ([] + (checkable-syms* nil)) + ([opts] + (reduce into #{} + [(filter fn-spec-name? (keys @(registry-ref))) + (keys (:spec opts))]))) + +(defmacro checkable-syms + "Given an opts map as per check, returns the set of syms that +can be checked." + ([] + `(checkable-syms nil)) + ([opts] + `(let [opts# ~opts] + (validate-check-opts opts#) + (reduce conj #{} + '[~@(filter fn-spec-name? (keys @(registry-ref))) + ~@(keys (:spec opts))])))) + +(defmacro check + "Run generative tests for spec conformance on vars named by +sym-or-syms, a symbol or collection of symbols. If sym-or-syms +is not specified, check all checkable vars. If a symbol identifies a +namespace then all symbols in that namespace will be enumerated. + +The opts map includes the following optional keys, where stc +aliases clojure.spec.test.check: + +::stc/opts opts to flow through test.check/quick-check +:gen map from spec names to generator overrides + +The ::stc/opts include :num-tests in addition to the keys +documented by test.check. Generator overrides are passed to +spec/gen when generating function args. + +Returns a lazy sequence of check result maps with the following +keys + +:spec the spec tested +:sym optional symbol naming the var tested +:failure optional test failure +::stc/ret optional value returned by test.check/quick-check + +The value for :failure can be any exception. Exceptions thrown by +spec itself will have an ::s/failure value in ex-data: + +:check-failed at least one checked return did not conform +:no-args-spec no :args spec provided +:no-fn no fn provided +:no-fspec no fspec provided +:no-gen unable to generate :args +:instrument invalid args detected by instrument +" + ([] + `(check ^::no-eval '~(checkable-syms*))) + ([sym-or-syms] + `(check ~sym-or-syms nil)) + ([sym-or-syms opts] + (let [syms (sym-or-syms->syms (form->sym-or-syms sym-or-syms)) + opts-sym (gensym "opts")] + `(if (and (cljs.core/exists? clojure.test.check) + (cljs.core/exists? clojure.test.check.properties)) + (let [~opts-sym ~opts] + [~@(->> syms + (filter (checkable-syms* opts)) + (map + (fn [sym] + (do `(check-1 '~sym nil nil ~opts-sym)))))]) + (throw + (js/Error. (str "Require clojure.test.check and " + "clojure.test.check.properties before calling check."))))))) + +(defmacro ^:private maybe-setup-static-dispatch [f ret conform! arity] + (let [arity-accessor (symbol (str ".-cljs$core$IFn$_invoke$arity$" arity)) + argv (mapv #(symbol (str "arg" %)) (range arity))] + `(when-some [ac# (~arity-accessor ~f)] + (set! (~arity-accessor ~ret) + (fn ~argv + (if *instrument-enabled* + (with-instrument-disabled + (~conform! ~argv) + (binding [*instrument-enabled* true] + (ac# ~@argv))) + (ac# ~@argv))))))) + +(defmacro ^:private setup-static-dispatches [f ret conform! max-arity] + ;; ret is for when we don't have arity info + `(do + ~@(mapv (fn [arity] + `(maybe-setup-static-dispatch ~f ~ret ~conform! ~arity)) + (range (inc max-arity))))) diff --git a/src/main/cljs/cljs/spec/test/alpha.cljs b/src/main/cljs/cljs/spec/test/alpha.cljs new file mode 100644 index 0000000000..6a5a0e5398 --- /dev/null +++ b/src/main/cljs/cljs/spec/test/alpha.cljs @@ -0,0 +1,380 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.spec.test.alpha + (:require-macros [cljs.spec.test.alpha :as m :refer [with-instrument-disabled setup-static-dispatches]]) + (:require + [goog.object :as gobj] + [goog.userAgent.product :as product] + [clojure.string :as string] + [cljs.stacktrace :as st] + [cljs.pprint :as pp] + [cljs.spec.alpha :as s] + [cljs.spec.gen.alpha :as gen])) + +(defn distinct-by + ([f coll] + (let [step (fn step [xs seen] + (lazy-seq + ((fn [[x :as xs] seen] + (when-let [s (seq xs)] + (let [v (f x)] + (if (contains? seen v) + (recur (rest s) seen) + (cons x (step (rest s) (conj seen v))))))) + xs seen)))] + (step coll #{})))) + +(defn ->sym + [x] + (@#'s/->sym x)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; instrument ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^:private ^:dynamic *instrument-enabled* + "if false, instrumented fns call straight through" + true) + +(defn get-host-port [] + (if (not= "browser" *target*) + {} + {:host (.. js/window -location -host) + :port (.. js/window -location -port)})) + +(defn get-ua-product [] + (if (not= "browser" *target*) + (keyword *target*) + (cond + product/SAFARI :safari + product/CHROME :chrome + product/FIREFOX :firefox + product/IE :ie))) + +(defn get-env [] + {:ua-product (get-ua-product)}) + +(defn- fn-spec? + "Fn-spec must include at least :args or :ret specs." + [m] + (or (:args m) (:ret m))) + +;; wrap spec/explain-data until specs always return nil for ok data +(defn- explain-data* + [spec v] + (when-not (s/valid? spec v nil) + (s/explain-data spec v))) + +(defn- find-caller [st] + (letfn [(search-spec-fn [frame] + (when frame + (let [s (:function frame)] + (and (string? s) (not (string/blank? s)) + (re-find #"cljs\.spec\.test\.spec_checking_fn" s)))))] + (->> st + (drop-while #(not (search-spec-fn %))) + (drop-while search-spec-fn) + first))) + +;; TODO: check ::caller result in other browsers - David + +(defn- spec-checking-fn + [v f fn-spec] + (let [fn-spec (@#'s/maybe-spec fn-spec) + args-spec (:args fn-spec) + conform! (fn [v role spec data args] + (let [conformed (s/conform spec data)] + (if (= ::s/invalid conformed) + (let [caller (find-caller + (st/parse-stacktrace + (get-host-port) + (.-stack (js/Error.)) + (get-env) nil)) + ed (merge (assoc (s/explain-data* spec [] [] [] data) + ::s/fn (->sym v) + ::s/args args + ::s/failure :instrument) + (when caller + {::caller caller}))] + (throw (ex-info + (str "Call to " v " did not conform to spec." ) + ed))) + conformed))) + pure-variadic? (and (-> (meta v) :top-fn :variadic?) + (zero? (-> (meta v) :top-fn :max-fixed-arity))) + apply' (fn [f args] + (if (and (nil? args) + pure-variadic?) + (.cljs$core$IFn$_invoke$arity$variadic f) + (apply f args))) + conform!* #(conform! v :args args-spec % %) + ret (if args-spec + (fn [& args] + (if *instrument-enabled* + (with-instrument-disabled + (conform!* args) + (binding [*instrument-enabled* true] + (apply' f args))) + (apply' f args))) + f)] + (when (and (not pure-variadic?) args-spec) + (setup-static-dispatches f ret conform!* 20) + (when-some [variadic (.-cljs$core$IFn$_invoke$arity$variadic f)] + (set! (.-cljs$core$IFn$_invoke$arity$variadic ret) + (fn [& args] + (if *instrument-enabled* + (with-instrument-disabled + (conform!* (apply list* args)) + (binding [*instrument-enabled* true] + (apply' variadic args))) + (apply' variadic args)))))) + ret)) + +(defn- no-fspec + [v spec] + (ex-info (str "Fn at " v " is not spec'ed.") + {:var v :spec spec ::s/failure :no-fspec})) + +(defonce ^:private instrumented-vars (atom {})) + +(defn- instrument-choose-fn + "Helper for instrument." + [f spec sym {over :gen :keys [stub replace]}] + (if (some #{sym} stub) + (-> spec (s/gen over) gen/generate) + (get replace sym f))) + +(defn- instrument-choose-spec + "Helper for instrument" + [spec sym {overrides :spec}] + (get overrides sym spec)) + +(defn- instrument-1* + [s v opts] + (let [spec (s/get-spec v) + {:keys [raw wrapped]} (get @instrumented-vars v) + current @v + to-wrap (if (= wrapped current) raw current) + ospec (or (instrument-choose-spec spec s opts) + (throw (no-fspec v spec))) + ofn (instrument-choose-fn to-wrap ospec s opts) + checked (spec-checking-fn v ofn ospec)] + (swap! instrumented-vars assoc v {:raw to-wrap :wrapped checked}) + checked)) + +(defn- unstrument-1* + [s v] + (when v + (when-let [{:keys [raw wrapped]} (get @instrumented-vars v)] + (swap! instrumented-vars dissoc v) + (let [current @v] + (when (= wrapped current) + raw))))) + +(defn- fn-spec-name? + [s] + (symbol? s)) + +(defn- collectionize + [x] + (if (symbol? x) + (list x) + x)) + +(defn instrumentable-syms + "Given an opts map as per instrument, returns the set of syms +that can be instrumented." + ([] (instrumentable-syms nil)) + ([opts] + (assert (every? ident? (keys (:gen opts))) "instrument :gen expects ident keys") + (reduce into #{} [(filter fn-spec-name? (keys (s/registry))) + (keys (:spec opts)) + (:stub opts) + (keys (:replace opts))]))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; testing ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- explain-check + [args spec v role] + (ex-info + "Specification-based check failed" + (when-not (s/valid? spec v nil) + (assoc (s/explain-data* spec [role] [] [] v) + ::args args + ::val v + ::s/failure :check-failed)))) + +(defn- check-call + "Returns true if call passes specs, otherwise *returns* an exception +with explain-data + ::s/failure." + [f specs args] + (let [cargs (when (:args specs) (s/conform (:args specs) args))] + (if (= cargs ::s/invalid) + (explain-check args (:args specs) args :args) + (let [ret (apply f args) + cret (when (:ret specs) (s/conform (:ret specs) ret))] + (if (= cret ::s/invalid) + (explain-check args (:ret specs) ret :ret) + (if (and (:args specs) (:ret specs) (:fn specs)) + (if (s/valid? (:fn specs) {:args cargs :ret cret}) + true + (explain-check args (:fn specs) {:args cargs :ret cret} :fn)) + true)))))) + +(defn- quick-check + [f specs {gen :gen opts :clojure.spec.test.check/opts}] + (let [{:keys [num-tests] :or {num-tests 1000}} opts + g (try (s/gen (:args specs) gen) (catch js/Error t t))] + (if (instance? js/Error g) + {:result g} + (let [prop (gen/for-all* [g] #(check-call f specs %))] + (apply gen/quick-check num-tests prop (mapcat identity opts)))))) + +(defn- make-check-result + "Builds spec result map." + [check-sym spec test-check-ret tc-ret-key] + (merge {:spec spec + tc-ret-key test-check-ret} + (when check-sym + {:sym check-sym}) + (when-let [result (-> test-check-ret :result)] + (when-not (true? result) {:failure result})) + (when-let [shrunk (-> test-check-ret :shrunk)] + {:failure (:result shrunk)}))) + +(defn validate-check-opts + [opts] + (assert (every? ident? (keys (:gen opts))) "check :gen expects ident keys")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; check reporting ;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- failure-type + [x] + (::s/failure (ex-data x))) + +(defn- unwrap-failure + [x] + (if (failure-type x) + (ex-data x) + x)) + +(defn- result-type + "Returns the type of the check result. This can be any of the +::s/failure keywords documented in 'check', or: + + :check-passed all checked fn returns conformed + :check-threw checked fn threw an exception" + [ret] + (let [failure (:failure ret)] + (cond + (nil? failure) :check-passed + (failure-type failure) (failure-type failure) + :default :check-threw))) + +(defn abbrev-result + "Given a check result, returns an abbreviated version +suitable for summary use." + [x] + (if (:failure x) + (-> (dissoc x :clojure.spec.test.check/ret) + (update :spec s/describe) + (update :failure unwrap-failure)) + (dissoc x :spec :clojure.spec.test.check/opts))) + +(defn summarize-results + "Given a collection of check-results, e.g. from 'check', pretty +prints the summary-result (default abbrev-result) of each. + +Returns a map with :total, the total number of results, plus a +key with a count for each different :type of result." + ([check-results] (summarize-results check-results abbrev-result)) + ([check-results summary-result] + (reduce + (fn [summary result] + (pp/pprint (summary-result result)) + (-> summary + (update :total inc) + (update (result-type result) (fnil inc 0)))) + {:total 0} + check-results))) + +(comment + (require + '[cljs.pprint :as pp] + '[cljs.spec :as s] + '[cljs.spec.gen :as gen] + '[cljs.test :as ctest]) + + (require :reload '[cljs.spec.test :as test]) + + ;; discover speced vars for your own test runner + (s/speced-vars) + + ;; check a single var + (test/check-var #'-) + (test/check-var #'+) + (test/check-var #'clojure.spec.broken-specs/throwing-fn) + + ;; old style example tests + (ctest/run-all-tests) + + (s/speced-vars 'clojure.spec.correct-specs) + ;; new style spec tests return same kind of map + (test/check-var #'subs) + (cljs.spec.test/run-tests 'clojure.core) + (test/run-all-tests) + + ;; example evaluation + (defn ranged-rand + "Returns random int in range start <= rand < end" + [start end] + (+ start (long (rand (- end start))))) + + (s/fdef ranged-rand + :args (s/and (s/cat :start int? :end int?) + #(< (:start %) (:end %))) + :ret int? + :fn (s/and #(>= (:ret %) (-> % :args :start)) + #(< (:ret %) (-> % :args :end)))) + + (instrumentable-syms) + + (m/instrument-1 `ranged-rand {}) + (m/unstrument-1 `ranged-rand) + + (m/instrument) + (m/instrument `ranged-rand) + (m/instrument `[ranged-rand]) + + (m/unstrument) + (m/unstrument `ranged-rand) + (m/unstrument `[ranged-rand]) + + (ranged-rand 8 5) + (defn foo + ([a]) + ([a b] + (ranged-rand 8 5))) + (foo 1 2) + (m/unstrument-1 `ranged-rand) + + (m/check-1 `ranged-rand nil nil {}) + + (m/check-fn inc + (s/fspec + :args (s/cat :x int?) + :ret int?)) + + (m/checkable-syms) + + (m/check `ranged-rand) + ) + + + + + diff --git a/src/main/cljs/cljs/stacktrace.cljc b/src/main/cljs/cljs/stacktrace.cljc new file mode 100644 index 0000000000..4e2d5bbc7e --- /dev/null +++ b/src/main/cljs/cljs/stacktrace.cljc @@ -0,0 +1,578 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.stacktrace + (:require #?@(:clj [[cljs.util :as util] + [clojure.java.io :as io]] + :cljs [[goog.string :as gstring]]) + [clojure.string :as string]) + #?(:clj (:import [java.util.regex Pattern] + [java.io File]))) + +(defmulti parse-stacktrace + "Parse a JavaScript stacktrace string into a canonical data form. The + arguments: + + repl-env - the repl environment, an optional map with :host and :port keys + if the stacktrace includes url, not file references + st - the original stacktrace string to parse + err - an error map. :ua-product key defines the type of stacktrace parser + to use, for example :chrome + opts - additional options. :output-dir maybe given in this argument if + :host and :port do not apply, for example, a file path + + The canonical stacktrace representation can easily be mapped to a + ClojureScript one see mapped-stacktrace and mapped-stacktrace-str" + (fn [repl-env st err opts] (:ua-product err))) + +(defn parse-int [s] + #?(:clj (Long/parseLong s) + :cljs (js/parseInt s 10))) + +(defn starts-with? + #?(:cljs {:tag boolean}) + [^String s0 s1] + #?(:clj (.startsWith s0 s1) + :cljs (gstring/startsWith s0 s1))) + +(defn ends-with? + #?(:cljs {:tag boolean}) + [^String s0 s1] + #?(:clj (.endsWith s0 s1) + :cljs (gstring/endsWith s0 s1))) + +(defn string->regex [s] + #?(:clj (Pattern/compile s) + :cljs (js/RegExp. s))) + +(defn output-directory [opts] + #?(:clj (util/output-directory opts) + :cljs (or (:output-dir opts) "out"))) + +(defmethod parse-stacktrace :default + [repl-env st err opts] st) + +(defn parse-file-line-column [flc] + (if-not (re-find #":" flc) + [flc nil nil] + (let [xs (string/split flc #":") + [pre [line column]] + (reduce + (fn [[pre post] [x i]] + (if (<= i 2) + [pre (conj post x)] + [(conj pre x) post])) + [[] []] (map vector xs (range (count xs) 0 -1))) + file (string/join ":" pre)] + [(cond-> file + (starts-with? file "(") (string/replace "(" "")) + (parse-int + (cond-> line + (ends-with? line ")") (string/replace ")" ""))) + (parse-int + (cond-> column + (ends-with? column ")") (string/replace ")" "")))]))) + +(defn parse-file + "Given a browser file url convert it into a relative path that can be used + to locate the original source." + [{:keys [host host-port port] :as repl-env} file {:keys [asset-path] :as opts}] + (let [urlpat (if host + (string->regex + (str "http://" host ":" (or host-port port) "/")) + "") + match (if host + (re-find urlpat file) + (contains? opts :output-dir))] + (if match + (-> file + (string/replace urlpat "") + (string/replace + (string->regex + ;; if :asset-path specified drop leading slash + (str "^" (or (and asset-path (string/replace asset-path #"^/" "")) + (output-directory opts)) "/")) + "")) + (if-let [asset-root (:asset-root opts)] + (string/replace file asset-root "") + (throw + (ex-info (str "Could not relativize URL " file) + {:type :parse-stacktrace + :reason :relativize-url})))))) + +;; ----------------------------------------------------------------------------- +;; Chrome Stacktrace + +(defn chrome-st-el->frame + [repl-env st-el opts] + (let [xs (-> st-el + (string/replace #"\s+at\s+" "") + (string/split #"\s+")) + [function flc] (if (== 1 (count xs)) + [nil (first xs)] + [(first xs) (last xs)]) + [file line column] (parse-file-line-column flc)] + (if (and file function line column) + {:file (parse-file repl-env file opts) + :function (string/replace function #"Object\." "") + :line line + :column column} + (when-not (string/blank? function) + {:file nil + :function (string/replace function #"Object\." "") + :line nil + :column nil})))) + +(comment + (chrome-st-el->frame {:host "localhost" :port 9000} + "\tat cljs$core$ffirst (http://localhost:9000/out/cljs/core.js:5356:34)" {}) + ) + +(defmethod parse-stacktrace :chrome + [repl-env st err opts] + (->> st + string/split-lines + (drop-while #(starts-with? % "Error")) + (take-while #(not (starts-with? % " at eval"))) + (map #(chrome-st-el->frame repl-env % opts)) + (remove nil?) + vec)) + +(comment + (parse-stacktrace {:host "localhost" :port 9000} + "Error: 1 is not ISeqable + at Object.cljs$core$seq [as seq] (http://localhost:9000/out/cljs/core.js:4258:8) + at Object.cljs$core$first [as first] (http://localhost:9000/out/cljs/core.js:4288:19) + at cljs$core$ffirst (http://localhost:9000/out/cljs/core.js:5356:34) + at http://localhost:9000/out/cljs/core.js:16971:89 + at cljs.core.map.cljs$core$map__2 (http://localhost:9000/out/cljs/core.js:16972:3) + at http://localhost:9000/out/cljs/core.js:10981:129 + at cljs.core.LazySeq.sval (http://localhost:9000/out/cljs/core.js:10982:3) + at cljs.core.LazySeq.cljs$core$ISeqable$_seq$arity$1 (http://localhost:9000/out/cljs/core.js:11073:10) + at Object.cljs$core$seq [as seq] (http://localhost:9000/out/cljs/core.js:4239:13) + at Object.cljs$core$pr_sequential_writer [as pr_sequential_writer] (http://localhost:9000/out/cljs/core.js:28706:14)" + {:ua-product :chrome} + nil) + + (parse-stacktrace {:host "localhost" :port 9000} + "Error: 1 is not ISeqable + at Object.cljs$core$seq [as seq] (http://localhost:9000/js/cljs/core.js:4258:8) + at Object.cljs$core$first [as first] (http://localhost:9000/js/cljs/core.js:4288:19) + at cljs$core$ffirst (http://localhost:9000/js/cljs/core.js:5356:34) + at http://localhost:9000/js/cljs/core.js:16971:89 + at cljs.core.map.cljs$core$map__2 (http://localhost:9000/js/cljs/core.js:16972:3) + at http://localhost:9000/js/cljs/core.js:10981:129 + at cljs.core.LazySeq.sval (http://localhost:9000/js/cljs/core.js:10982:3) + at cljs.core.LazySeq.cljs$core$ISeqable$_seq$arity$1 (http://localhost:9000/js/cljs/core.js:11073:10) + at Object.cljs$core$seq [as seq] (http://localhost:9000/js/cljs/core.js:4239:13) + at Object.cljs$core$pr_sequential_writer [as pr_sequential_writer] (http://localhost:9000/js/cljs/core.js:28706:14)" + {:ua-product :chrome} + {:asset-path "/js"}) + + (parse-stacktrace {:host "localhost" :port 9000} + "Error: 1 is not ISeqable + at Object.cljs$core$seq [as seq] (http://localhost:9000/out/cljs/core.js:4259:8) + at Object.cljs$core$first [as first] (http://localhost:9000/out/cljs/core.js:4289:19) + at cljs$core$ffirst (http://localhost:9000/out/cljs/core.js:5357:18) + at eval (eval at (http://localhost:9000/out/clojure/browser/repl.js:23:272), :1:106) + at eval (eval at (http://localhost:9000/out/clojure/browser/repl.js:23:272), :9:3) + at eval (eval at (http://localhost:9000/out/clojure/browser/repl.js:23:272), :14:4) + at http://localhost:9000/out/clojure/browser/repl.js:23:267 + at clojure$browser$repl$evaluate_javascript (http://localhost:9000/out/clojure/browser/repl.js:26:4) + at Object.callback (http://localhost:9000/out/clojure/browser/repl.js:121:169) + at goog.messaging.AbstractChannel.deliver (http://localhost:9000/out/goog/messaging/abstractchannel.js:142:13)" + {:ua-product :chrome} + nil) + + ;; Node.js example + (parse-stacktrace {} + "Error: 1 is not ISeqable + at Object.cljs$core$seq [as seq] (/home/my/cool/project/.cljs_bootstrap/cljs/core.js:3999:8) + at Object.cljs$core$first [as first] (/home/my/cool/project/.cljs_bootstrap/cljs/core.js:4018:19) + at cljs$core$ffirst (/home/my/cool/project/.cljs_bootstrap/cljs/core.js:5161:34) + at /home/my/cool/project/.cljs_bootstrap/cljs/core.js:16006:88 + at cljs.core.map.cljs$core$IFn$_invoke$arity$2 (/home/my/cool/project/.cljs_bootstrap/cljs/core.js:16007:3) + at cljs.core.LazySeq.sval (/home/my/cool/project/.cljs_bootstrap/cljs/core.js:10244:109) + at cljs.core.LazySeq.cljs$core$ISeqable$_seq$arity$1 (/home/my/cool/project/.cljs_bootstrap/cljs/core.js:10335:10) + at Object.cljs$core$seq [as seq] (/home/my/cool/project/.cljs_bootstrap/cljs/core.js:3980:13) + at Object.cljs$core$pr_sequential_writer [as pr_sequential_writer] (/home/my/cool/project/.cljs_bootstrap/cljs/core.js:28084:14) + at cljs.core.LazySeq.cljs$core$IPrintWithWriter$_pr_writer$arity$3 (/home/my/cool/project/.cljs_bootstrap/cljs/core.js:28812:18)" + {:ua-product :chrome} + {:output-dir "/home/my/cool/project/.cljs_bootstrap"}) + ) + +;; ----------------------------------------------------------------------------- +;; Safari Stacktrace + +(defn safari-st-el->frame + [repl-env st-el opts] + (let [[function flc] (if (re-find #"@" st-el) + (string/split st-el #"@") + [nil st-el]) + [file line column] (parse-file-line-column flc)] + (if (and file function line column) + {:file (parse-file repl-env file opts) + :function (string/trim function) + :line line + :column column} + (when-not (string/blank? function) + {:file nil + :function (string/trim function) + :line nil + :column nil})))) + +(comment + (safari-st-el->frame {:host "localhost" :port 9000} + "cljs$core$seq@http://localhost:9000/out/cljs/core.js:4259:17" {}) + + (safari-st-el->frame {:host "localhost" :port 9000} + "cljs$core$seq@http://localhost:9000/js/cljs/core.js:4259:17" {:asset-path "js"}) + ) + +(defmethod parse-stacktrace :safari + [repl-env st err opts] + (->> st + string/split-lines + (drop-while #(starts-with? % "Error")) + (take-while #(not (starts-with? % "eval code"))) + (remove string/blank?) + (map #(safari-st-el->frame repl-env % opts)) + (remove nil?) + vec)) + +(comment + (parse-stacktrace {} + "cljs$core$seq@out/cljs/core.js:3999:17 + cljs$core$first@out/cljs/core.js:4018:22 + cljs$core$ffirst@out/cljs/core.js:5161:39 + global code" + {:ua-product :safari} + {:output-dir "out"}) + + (parse-stacktrace {:host "localhost" :port 9000} + "cljs$core$seq@http://localhost:9000/out/cljs/core.js:4259:17 +cljs$core$first@http://localhost:9000/out/cljs/core.js:4289:22 +cljs$core$ffirst@http://localhost:9000/out/cljs/core.js:5357:39 +http://localhost:9000/out/cljs/core.js:16972:92 +http://localhost:9000/out/cljs/core.js:16973:3 +http://localhost:9000/out/cljs/core.js:10982:133 +sval@http://localhost:9000/out/cljs/core.js:10983:3 +cljs$core$ISeqable$_seq$arity$1@http://localhost:9000/out/cljs/core.js:11074:14 +cljs$core$seq@http://localhost:9000/out/cljs/core.js:4240:44 +cljs$core$pr_sequential_writer@http://localhost:9000/out/cljs/core.js:28707:17 +cljs$core$IPrintWithWriter$_pr_writer$arity$3@http://localhost:9000/out/cljs/core.js:29386:38 +cljs$core$pr_writer_impl@http://localhost:9000/out/cljs/core.js:28912:57 +cljs$core$pr_writer@http://localhost:9000/out/cljs/core.js:29011:32 +cljs$core$pr_seq_writer@http://localhost:9000/out/cljs/core.js:29015:20 +cljs$core$pr_sb_with_opts@http://localhost:9000/out/cljs/core.js:29078:24 +cljs$core$pr_str_with_opts@http://localhost:9000/out/cljs/core.js:29092:48 +cljs$core$pr_str__delegate@http://localhost:9000/out/cljs/core.js:29130:34 +cljs$core$pr_str@http://localhost:9000/out/cljs/core.js:29139:39 +eval code +eval@[native code] +http://localhost:9000/out/clojure/browser/repl.js:23:271 +clojure$browser$repl$evaluate_javascript@http://localhost:9000/out/clojure/browser/repl.js:26:4 +http://localhost:9000/out/clojure/browser/repl.js:121:173 +deliver@http://localhost:9000/out/goog/messaging/abstractchannel.js:142:21 +xpcDeliver@http://localhost:9000/out/goog/net/xpc/crosspagechannel.js:733:19 +messageReceived_@http://localhost:9000/out/goog/net/xpc/nativemessagingtransport.js:321:23 +fireListener@http://localhost:9000/out/goog/events/events.js:741:25 +handleBrowserEvent_@http://localhost:9000/out/goog/events/events.js:862:34 +http://localhost:9000/out/goog/events/events.js:276:42" + {:ua-product :safari} + nil) + ) + +;; ----------------------------------------------------------------------------- +;; Firefox Stacktrace + +(defn firefox-clean-function [f] + (as-> f f + (cond + (string/blank? f) nil + (not= (.indexOf f " f + (string/replace #"<" "") + (string/replace #?(:clj #"\/" :cljs (js/RegExp. "\\/")) "")))) + +(defn firefox-st-el->frame + [repl-env st-el opts] + (let [[function flc] (if (re-find #"@" st-el) + (string/split st-el #"@") + [nil st-el]) + [file line column] (parse-file-line-column flc)] + (if (and file function line column) + {:file (parse-file repl-env file opts) + :function (firefox-clean-function function) + :line line + :column column} + (when-not (string/blank? function) + {:file nil + :function (firefox-clean-function function) + :line nil + :column nil})))) + +(comment + (firefox-st-el->frame {:host "localhost" :port 9000} + "cljs$core$seq@http://localhost:9000/out/cljs/core.js:4258:8" {}) + + (firefox-st-el->frame {:host "localhost" :port 9000} + "cljs.core.mapframe {:host "localhost" :port 9000} + "cljs.core.mapframe {:host "localhost" :port 9000} + "cljs.core.pr_strframe {:host "localhost" :port 9000} + "cljs.core.pr_str> st + string/split-lines + (drop-while #(starts-with? % "Error")) + (take-while #(= (.indexOf % "> eval") -1)) + (remove string/blank?) + (map #(firefox-st-el->frame repl-env % opts)) + (remove nil?) + vec)) + +(comment + (parse-stacktrace {:host "localhost" :port 9000} + "cljs$core$seq@http://localhost:9000/out/cljs/core.js:4258:8 +cljs$core$first@http://localhost:9000/out/cljs/core.js:4288:9 +cljs$core$ffirst@http://localhost:9000/out/cljs/core.js:5356:24 +cljs.core.map eval:1:25 +@http://localhost:9000/out/clojure/browser/repl.js line 23 > eval:1:2 +clojure$browser$repl$evaluate_javascript/result<@http://localhost:9000/out/clojure/browser/repl.js:23:267 +clojure$browser$repl$evaluate_javascript@http://localhost:9000/out/clojure/browser/repl.js:23:15 +clojure$browser$repl$connect/ file-part + output-dir + (string/replace + (str output-dir + #?(:clj File/separator :cljs "/")) + "")) + file-part) + :function function + :line (parse-source-loc-info line-part) + :column (parse-source-loc-info col-part)})))))] + (->> (string/split st #"\n") + (map process-frame) + (remove nil?) + vec))) + +(comment + (parse-stacktrace {} + "Error: 1 is not ISeqable + at cljs$core$seq (.cljs_node_repl/cljs/core.cljs:1118:20) + at repl:1:65 + at repl:9:4 + at repl:17:3 + at repl:22:4 + at Object.exports.runInThisContext (vm.js:54:17) + at Domain. ([stdin]:41:34) + at Domain.run (domain.js:228:14) + at Socket. ([stdin]:40:25) + at emitOne (events.js:77:13)" + + {:ua-product :nodejs} + {:output-dir ".cljs_node_repl"}) + ) + +;; ----------------------------------------------------------------------------- +;; Stacktrace Mapping + +(defn remove-ext [file] + (-> file + (string/replace #"\.js$" "") + (string/replace #"\.cljs$" "") + (string/replace #"\.cljc$" "") + (string/replace #"\.clj$" ""))) + +(defn mapped-line-column-call + "Given a cljs.source-map source map data structure map a generated line + and column back to the original line, column, and function called." + [sms file line column] + (let [source-map (get sms (symbol (string/replace (remove-ext file) "/" ".")))] + ;; source maps are 0 indexed for columns + ;; multiple segments may exist at column + ;; the last segment seems most accurate + (letfn [(get-best-column [columns column] + (last (or (get columns + (last (filter #(<= % (dec column)) + (sort (keys columns))))) + (second (first columns))))) + (adjust [mapped] + (vec (map #(%1 %2) [inc inc identity] mapped)))] + (let [default [line column nil]] + ;; source maps are 0 indexed for lines + (if-let [columns (get source-map (dec line))] + (adjust (map (get-best-column columns column) [:line :col :name])) + default))))) + +(defn mapped-frame + "Given opts and a canonicalized JavaScript stacktrace frame, return the + ClojureScript frame." + [{:keys [function file line column]} sms opts] + (let [no-source-file? (if-not file true (starts-with? file "<")) + [line' column' call] (if no-source-file? + [line column nil] + (mapped-line-column-call sms file line column)) + file' (when-not no-source-file? + (if (ends-with? file ".js") + (str (subs file 0 (- (count file) 3)) ".cljs") + file))] + {:function function + :call call + :file (if no-source-file? + (str "NO_SOURCE_FILE" (when file (str " " file))) + file') + :line line' + :column column'})) + +(defn mapped-stacktrace + "Given a vector representing the canonicalized JavaScript stacktrace + return the ClojureScript stacktrace. The canonical stacktrace must be + in the form: + + [{:file + :function + :line + :column }*] + + :file must be a URL path (without protocol) relative to :output-dir or a + identifier delimited by angle brackets. The returned mapped stacktrace will + also contain :url entries to the original sources if it can be determined + from the classpath." + ([stacktrace sms] + (mapped-stacktrace stacktrace sms nil)) + ([stacktrace sms opts] + (letfn [(call->function [x] + (if (:call x) + (hash-map :function (:call x)) + {})) + (call-merge [function call] + (merge-with + (fn [munged-fn-name unmunged-call-name] + (if (= munged-fn-name + (string/replace (munge unmunged-call-name) "." "$")) + unmunged-call-name + munged-fn-name)) + function call))] + (let [mapped-frames (map (memoize #(mapped-frame % sms opts)) stacktrace)] + ;; take each non-nil :call and optionally merge it into :function one-level + ;; up to avoid replacing with local symbols, we only replace munged name if + ;; we can munge call symbol back to it + (vec (map call-merge + (map #(dissoc % :call) mapped-frames) + (concat (rest (map call->function mapped-frames)) [{}]))))))) + +(defn mapped-stacktrace-str + "Given a vector representing the canonicalized JavaScript stacktrace and a map + of library names to decoded source maps, print the ClojureScript stacktrace . + See mapped-stacktrace." + ([stacktrace sms] + (mapped-stacktrace-str stacktrace sms nil)) + ([stacktrace sms opts] + (with-out-str + (doseq [{:keys [function file line column]} + (mapped-stacktrace stacktrace sms opts)] + (println "\t" + (str (when function (str function " ")) + "(" file (when line (str ":" line)) + (when column (str ":" column)) ")")))))) + +(comment + (require '[cljs.closure :as cljsc] + '[cljs.vendor.clojure.data.json :as json] + '[cljs.source-map :as sm] + '[clojure.pprint :as pp]) + + (cljsc/build "samples/hello/src" + {:optimizations :none + :output-dir "samples/hello/out" + :output-to "samples/hello/out/hello.js" + :source-map true}) + + (def sms + {'hello.core + (sm/decode + (json/read-str + (slurp "samples/hello/out/hello/core.js.map") + :key-fn keyword))}) + + (pp/pprint sms) + + ;; maps to :line 5 :column 24 + (mapped-stacktrace + [{:file "hello/core.js" + :function "first" + :line 6 + :column 0}] + sms {:output-dir "samples/hello/out"}) + + (mapped-stacktrace-str + [{:file "hello/core.js" + :function "first" + :line 6 + :column 0}] + sms {:output-dir "samples/hello/out"}) + ) diff --git a/src/main/cljs/cljs/test.cljc b/src/main/cljs/cljs/test.cljc new file mode 100644 index 0000000000..27776ad4b9 --- /dev/null +++ b/src/main/cljs/cljs/test.cljc @@ -0,0 +1,442 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.test + #?(:cljs (:require-macros [clojure.template :as temp])) + (:require [cljs.env :as env] + [cljs.analyzer :as ana] + [cljs.analyzer.api :as ana-api] + #?(:clj [clojure.template :as temp]))) + +;; ============================================================================= +;; Utilities for assertions + +(defn function? + "Returns true if argument is a function or a symbol that resolves to + a function (not a macro)." + [menv x] + (and (symbol? x) (:fn-var (ana-api/resolve menv x)))) + +(defn assert-predicate + "Returns generic assertion code for any functional predicate. The + 'expected' argument to 'report' will contains the original form, the + 'actual' argument will contain the form with all its sub-forms + evaluated. If the predicate returns false, the 'actual' form will + be wrapped in (not...)." + [msg form] + (let [args (rest form) + pred (first form) + {:keys [file line end-line column end-column]} (meta form)] + `(let [values# (list ~@args) + result# (apply ~pred values#)] + (if result# + (report + {:type :pass, :message ~msg, + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column + :expected '~form, :actual (cons '~pred values#)}) + (report + {:type :fail, :message ~msg, + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column + :expected '~form, :actual (list '~'not (cons '~pred values#))})) + result#))) + +(defn assert-any + "Returns generic assertion code for any test, including macros, Java + method calls, or isolated symbols." + [msg form] + (let [{:keys [file line end-line column end-column]} (meta form)] + `(let [value# ~form] + (if value# + (report + {:type :pass, :message ~msg, + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column + :expected '~form, :actual value#}) + (report + {:type :fail, :message ~msg, + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column + :expected '~form, :actual value#})) + value#))) + +(defmacro ^:private cljs-output-dir [] + (let [{:keys [output-dir]} (ana-api/get-options)] + (or output-dir "out"))) + +;; ============================================================================= +;; Assertion Methods + +;; You don't call these, but you can add methods to extend the 'is' +;; macro. These define different kinds of tests, based on the first +;; symbol in the test expression. + +(defmulti assert-expr + (fn [menv msg form] + (cond + (nil? form) :always-fail + (seq? form) (first form) + :else :default))) + +(defmethod assert-expr :always-fail [menv msg form] + ;; nil test: always fail + (let [{:keys [file line end-line column end-column]} (meta form)] + `(report {:type :fail, :message ~msg + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column}))) + +(defmethod assert-expr :default [menv msg form] + (if (and (sequential? form) + (function? menv (first form))) + (assert-predicate msg form) + (assert-any msg form))) + +(defmethod assert-expr 'instance? [menv msg form] + ;; Test if x is an instance of y. + (let [{:keys [file line end-line column end-column]} (meta form)] + `(let [klass# ~(nth form 1) + object# ~(nth form 2)] + (let [result# (instance? klass# object#)] + (if result# + (report + {:type :pass, :message ~msg, + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column + :expected '~form, :actual (type object#)}) + (report + {:type :fail, :message ~msg, + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column + :expected '~form, :actual (type object#)})) + result#)))) + +(defmethod assert-expr 'thrown? [menv msg form] + ;; (is (thrown? c expr)) + ;; Asserts that evaluating expr throws an exception of class c. + ;; Returns the exception thrown. + (let [{:keys [file line end-line column end-column]} (meta form) + klass (second form) + body (nthnext form 2)] + `(try + ~@body + (report + {:type :fail, :message ~msg, + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column + :expected '~form, :actual nil}) + (catch ~klass e# + (report + {:type :pass, :message ~msg, + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column + :expected '~form, :actual e#}) + e#)))) + +(defmethod assert-expr 'thrown-with-msg? [menv msg form] + ;; (is (thrown-with-msg? c re expr)) + ;; Asserts that evaluating expr throws an exception of class c. + ;; Also asserts that the message string of the exception matches + ;; (with re-find) the regular expression re. + (let [{:keys [file line end-line column end-column]} (meta form) + klass (nth form 1) + re (nth form 2) + body (nthnext form 3)] + `(try + ~@body + (report {:type :fail, :message ~msg, :expected '~form, :actual nil + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column}) + (catch ~klass e# + (let [m# (.-message e#)] + (if (re-find ~re m#) + (report + {:type :pass, :message ~msg, + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column + :expected '~form, :actual e#}) + (report + {:type :fail, :message ~msg, + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column + :expected '~form, :actual e#})) + e#))))) + +(defmacro try-expr + "Used by the 'is' macro to catch unexpected exceptions. + You don't call this." + [msg form] + (let [{:keys [file line end-line column end-column]} (meta form)] + `(try + ~(assert-expr &env msg form) + (catch :default t# + (report + {:type :error, :message ~msg, + :file ~file :line ~line :end-line ~end-line :column ~column :end-column ~end-column + :expected '~form, :actual t#}))))) + +;; ============================================================================= +;; Assertion Macros + +(defmacro is + "Generic assertion macro. 'form' is any predicate test. + 'msg' is an optional message to attach to the assertion. + + Example: (is (= 4 (+ 2 2)) \"Two plus two should be 4\") + + Special forms: + + (is (thrown? c body)) checks that an instance of c is thrown from + body, fails if not; then returns the thing thrown. + + (is (thrown-with-msg? c re body)) checks that an instance of c is + thrown AND that the message on the exception matches (with + re-find) the regular expression re." + ([form] `(cljs.test/is ~form nil)) + ([form msg] + `(try-expr ~msg ~form))) + +(defmacro are + "Checks multiple assertions with a template expression. + See clojure.template/do-template for an explanation of + templates. + + Example: (are [x y] (= x y) + 2 (+ 1 1) + 4 (* 2 2)) + Expands to: + (do (is (= 2 (+ 1 1))) + (is (= 4 (* 2 2)))) + + Note: This breaks some reporting features, such as line numbers." + [argv expr & args] + (if (or + ;; (are [] true) is meaningless but ok + (and (empty? argv) (empty? args)) + ;; Catch wrong number of args + (and (pos? (count argv)) + (pos? (count args)) + (zero? (mod (count args) (count argv))))) + `(clojure.template/do-template ~argv (is ~expr) ~@args) + (throw (#?(:clj Exception. :cljs js/Error.) "The number of args doesn't match are's argv.")))) + +(defmacro testing + "Adds a new string to the list of testing contexts. May be nested, + but must occur inside a test function (deftest)." + ([string & body] + `(do + (update-current-env! [:testing-contexts] conj ~string) + (try + ~@body + (finally + (update-current-env! [:testing-contexts] rest)))))) + +;; ============================================================================= +;; Defining Tests + +(defmacro deftest + "Defines a test function with no arguments. Test functions may call + other tests, so tests may be composed. If you compose tests, you + should also define a function named test-ns-hook; run-tests will + call test-ns-hook instead of testing all vars. + + Note: Actually, the test body goes in the :test metadata on the var, + and the real function (the value of the var) calls test-var on + itself. + + When cljs.analyzer/*load-tests* is false, deftest is ignored." + [name & body] + (when ana/*load-tests* + `(do + (def ~(vary-meta name assoc :test `(fn [] ~@body)) + (fn [] (cljs.test/test-var (.-cljs$lang$var ~name)))) + (set! (.-cljs$lang$var ~name) (var ~name))))) + +(defmacro async + "Wraps body as a CPS function that can be returned from a test to + continue asynchronously. Binds done to a function that must be + invoked once and from an async context after any assertions. + + (deftest example-with-timeout + (async done + (js/setTimeout (fn [] + ;; make assertions in async context... + (done) ;; ...then call done + ) + 0)))" + [done & body] + `(reify + cljs.test/IAsyncTest + cljs.core/IFn + (~'-invoke [_# ~done] + ((^:async fn [] ~@body))))) + +;; ============================================================================= +;; Running Tests + +(defn ns? [x] + (and (seq? x) (= (first x) 'quote))) + +(defmacro run-tests-block + "Like test-vars, but returns a block for further composition and + later execution." + [env-or-ns & namespaces] + (assert (every? + (fn [[quote ns]] (and (= quote 'quote) (symbol? ns))) + namespaces) + "All arguments to run-tests must be quoted symbols") + (let [is-ns (ns? env-or-ns) + env (gensym "env") + summary (gensym "summary")] + `(let [~env ~(if is-ns + `(empty-env) + env-or-ns) + ~summary (cljs.core/volatile! + {:test 0 :pass 0 :fail 0 :error 0 + :type :summary})] + (concat ~@(map + (fn [ns] + `(concat (test-ns-block ~env ~ns) + [(fn [] + (cljs.core/vswap! + ~summary + (partial merge-with +) + (:report-counters + (get-and-clear-env!))))])) + (if is-ns + (concat [env-or-ns] namespaces) + namespaces)) + [(fn [] + (set-env! ~env) + (do-report (deref ~summary)) + (report (assoc (deref ~summary) :type :end-run-tests)) + (clear-env!))])))) + +(defmacro run-tests + "Runs all tests in the given namespaces; prints results. + Defaults to current namespace if none given. Does not return a meaningful + value due to the possiblity of asynchronous execution. To detect test + completion add a :end-run-tests method case to the cljs.test/report + multimethod." + ([] `(run-tests (empty-env) '~ana/*cljs-ns*)) + ([env-or-ns] + (if (ns? env-or-ns) + `(run-tests (empty-env) ~env-or-ns) + `(run-tests ~env-or-ns '~ana/*cljs-ns*))) + ([env-or-ns & namespaces] + `(run-block (run-tests-block ~env-or-ns ~@namespaces)))) + +(defmacro run-all-tests + "Runs all tests in all namespaces; prints results. + Optional argument is a regular expression; only namespaces with + names matching the regular expression (with re-matches) will be + tested." + ([] `(run-all-tests nil (empty-env))) + ([re] `(run-all-tests ~re (empty-env))) + ([re env] + `(run-tests ~env + ~@(map + (fn [ns] `(quote ~ns)) + (cond->> (ana-api/all-ns) + re (filter #(re-matches re (name %)))))))) + +(defmacro test-all-vars-block + ([[quote ns]] + `(let [env# (get-current-env)] + (concat + [(fn [] + (when (nil? env#) + (set-env! (empty-env))) + ~(when (ana-api/ns-resolve ns 'cljs-test-once-fixtures) + `(update-current-env! [:once-fixtures] assoc '~ns + ~(symbol (name ns) "cljs-test-once-fixtures"))) + ~(when (ana-api/ns-resolve ns 'cljs-test-each-fixtures) + `(update-current-env! [:each-fixtures] assoc '~ns + ~(symbol (name ns) "cljs-test-each-fixtures"))))] + (test-vars-block + [~@(->> (ana-api/ns-interns ns) + (filter (fn [[_ v]] (:test v))) + (sort-by (fn [[_ v]] (:line v))) + (map (fn [[k _]] + `(var ~(symbol (name ns) (name k))))))]) + [(fn [] + (when (nil? env#) + (clear-env!)))])))) + +(defmacro test-all-vars + "Calls test-vars on every var with :test metadata interned in the + namespace, with fixtures." + [[quote ns :as form]] + `(run-block + (concat (test-all-vars-block ~form) + [(fn [] + (report {:type :end-test-all-vars :ns ~form}))]))) + +(defmacro test-ns-block + "Like test-ns, but returns a block for further composition and + later execution. Does not clear the current env." + ([env [quote ns :as form]] + (assert (and (= quote 'quote) (symbol? ns)) "Argument to test-ns must be a quoted symbol") + (assert (ana-api/find-ns ns) (str "Namespace " ns " does not exist")) + `[(fn [] + (set-env! ~env) + (do-report {:type :begin-test-ns, :ns ~form}) + ;; If the namespace has a test-ns-hook function, call that: + ~(if-let [v (ana-api/ns-resolve ns 'test-ns-hook)] + `(~(symbol (name ns) "test-ns-hook")) + ;; Otherwise, just test every var in the namespace. + `(block (test-all-vars-block ~form)))) + (fn [] + (do-report {:type :end-test-ns, :ns ~form}))])) + +(defmacro test-ns + "If the namespace defines a function named test-ns-hook, calls that. + Otherwise, calls test-all-vars on the namespace. 'ns' is a + namespace object or a symbol. + + Internally binds *report-counters* to a ref initialized to + *initial-report-counters*. " + ([ns] `(test-ns (empty-env) ~ns)) + ([env [quote ns :as form]] + `(run-block + (concat (test-ns-block ~env ~form) + [(fn [] + (clear-env!))])))) + +(defmacro run-test + "Runs a single test. + + Because the intent is to run a single test, there is no check for the namespace test-ns-hook." + [test-symbol] + (let [test-var (ana-api/resolve &env test-symbol)] + (cond (nil? test-var) + `(cljs.core/*print-err-fn* "Unable to resolve" ~(str test-symbol) "to a test function.") + (not (:test test-var)) + `(cljs.core/*print-err-fn* ~(str test-symbol) "is not a test") + :else + (let [ns (:ns test-var)] + `(let [env# (get-current-env)] + (run-block + (concat + [(fn [] + (when (nil? env#) + (set-env! (empty-env))) + ~(when (ana-api/resolve &env 'cljs-test-once-fixtures) + `(update-current-env! [:once-fixtures] assoc '~ns + ~(symbol (str ns) "cljs-test-once-fixtures"))) + ~(when (ana-api/resolve &env 'cljs-test-each-fixtures) + `(update-current-env! [:each-fixtures] assoc '~ns + ~(symbol (str ns) "cljs-test-each-fixtures"))))] + (test-vars-block + [(var ~test-symbol)]) + [(fn [] + (when (nil? env#) + (clear-env!)))]))))))) + +;; ============================================================================= +;; Fixes + +(defmacro use-fixtures [type & fns] + (condp = type + :once + `(def ~'cljs-test-once-fixtures + [~@fns]) + :each + `(def ~'cljs-test-each-fixtures + [~@fns]) + :else + (throw + (#?(:clj Exception. :cljs js/Error.) "First argument to cljs.test/use-fixtures must be :once or :each")))) diff --git a/src/main/cljs/cljs/test.cljs b/src/main/cljs/cljs/test.cljs new file mode 100644 index 0000000000..0ff5dff404 --- /dev/null +++ b/src/main/cljs/cljs/test.cljs @@ -0,0 +1,606 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns +^{:author "Stuart Sierra, with contributions and suggestions by + Chas Emerick, Allen Rohner, Stuart Halloway, David Nolen, and + Leon Grapenthin", + :doc "A unit testing framework. + + ASSERTIONS + + The core of the library is the \"is\" macro, which lets you make + assertions of any arbitrary expression: + + (is (= 4 (+ 2 2))) + (is (instance? Integer 256)) + (is (.startsWith \"abcde\" \"ab\")) + + You can type an \"is\" expression directly at the REPL, which will + print a message if it fails. + + user> (is (= 5 (+ 2 2))) + + FAIL in (:1) + expected: (= 5 (+ 2 2)) + actual: (not (= 5 4)) + false + + The \"expected:\" line shows you the original expression, and the + \"actual:\" shows you what actually happened. In this case, it + shows that (+ 2 2) returned 4, which is not = to 5. Finally, the + \"false\" on the last line is the value returned from the + expression. The \"is\" macro always returns the result of the + inner expression. + + There are two special assertions for testing exceptions. The + \"(is (thrown? c ...))\" form tests if an exception of class c is + thrown: + + (is (thrown? ArithmeticException (/ 1 0))) + + \"(is (thrown-with-msg? c re ...))\" does the same thing and also + tests that the message on the exception matches the regular + expression re: + + (is (thrown-with-msg? ArithmeticException #\"Divide by zero\" + (/ 1 0))) + + DOCUMENTING TESTS + + \"is\" takes an optional second argument, a string describing the + assertion. This message will be included in the error report. + + (is (= 5 (+ 2 2)) \"Crazy arithmetic\") + + In addition, you can document groups of assertions with the + \"testing\" macro, which takes a string followed by any number of + assertions. The string will be included in failure reports. + Calls to \"testing\" may be nested, and all of the strings will be + joined together with spaces in the final report, in a style + similar to RSpec + + (testing \"Arithmetic\" + (testing \"with positive integers\" + (is (= 4 (+ 2 2))) + (is (= 7 (+ 3 4)))) + (testing \"with negative integers\" + (is (= -4 (+ -2 -2))) + (is (= -1 (+ 3 -4))))) + + Note that, unlike RSpec, the \"testing\" macro may only be used + INSIDE a \"deftest\" form (see below). + + + DEFINING TESTS + + (deftest addition + (is (= 4 (+ 2 2))) + (is (= 7 (+ 3 4)))) + + (deftest subtraction + (is (= 1 (- 4 3))) + (is (= 3 (- 7 4)))) + + This creates functions named \"addition\" and \"subtraction\", which + can be called like any other function. Therefore, tests can be + grouped and composed, in a style similar to the test framework in + Peter Seibel's \"Practical Common Lisp\" + + + (deftest arithmetic + (addition) + (subtraction)) + + The names of the nested tests will be joined in a list, like + \"(arithmetic addition)\", in failure reports. You can use nested + tests to set up a context shared by several tests. + + DEFINING ASYNC TESTS + + (deftest addition + (async done + (is (= 4 (+ 2 2))) + (is (= 7 (+ 3 4))) + (done))) + + Async tests are constructed with the async macro. The first argument to + the macro is the test completion callback. The body of the async macro may + be any series of expressions. The completion callback must be invoked when + all assertions have run. There is no support for asynchronous coordination - + core.async is recommended for this. Note the body of the async test must be + truly asynchronous to avoid stack overflow. + + RUNNING TESTS + + Run tests with the function \"(run-tests namespaces...)\": + + (run-tests 'your.namespace 'some.other.namespace) + + If you don't specify any namespaces, the current namespace is + used. To run all tests in all namespaces, use \"(run-all-tests)\". + + By default, these functions will search for all tests defined in + a namespace and run them in an undefined order. However, if you + are composing tests, as in the \"arithmetic\" example above, you + probably do not want the \"addition\" and \"subtraction\" tests run + separately. In that case, you must define a special function + named \"test-ns-hook\" that runs your tests in the correct order: + + (defn test-ns-hook [] + (arithmetic)) + + \"run-tests\" also optionally takes a testing enviroment. A default + one is supplied for you by invoking \"empty-env\". The test + environment contains everything needed to run tests including the + report results map. Fixtures must be present here if you want them + to run. Note that code that relies on \"test-ns\" will + automatically be supplied the appropriate defined fixtures. For + example, this is done for you if you use \"run-tests\". + + Note: test-ns-hook prevents execution of fixtures (see below). + + + OMITTING TESTS FROM PRODUCTION CODE + + You can set the ClojureScript compiler build option + \":load-tests\" to false when loading or compiling code in + production. This will prevent any tests from being created by + or \"deftest\". + + + FIXTURES + + Fixtures allow you to run code before and after tests, to set up + the context in which tests should be run. + + A fixture is a map of one or two functions that run code before and + after tests. It looks like this: + + {:before (fn [] + Perform setup, establish bindings, whatever. + ) + :after (fn [] + Tear-down / clean-up code here. + )} + + Both are optional and can be left out. + + Fixtures are attached to namespaces in one of two ways. \"each\" + fixtures are run repeatedly, once for each test function created + with \"deftest\". \"each\" fixtures are useful for + establishing a consistent before/after state for each test, like + clearing out database tables. + + \"each\" fixtures can be attached to the current namespace like this: + (use-fixtures :each fixture1 fixture2 ...) + The fixture1, fixture2 are just maps like the example above. + They can also be passed directly, like this: + (use-fixtures :each + {:before (fn [] setup...), :after (fn [] cleanup...)}) + + The other kind of fixture, a \"once\" fixture, is only run once, + around ALL the tests in the namespace. \"once\" fixtures are useful + for tasks that only need to be performed once, like establishing + database connections, or for time-consuming tasks. + + Attach \"once\" fixtures to the current namespace like this: + (use-fixtures :once fixture1 fixture2 ...) + + Note: Fixtures and test-ns-hook are mutually incompatible. If you + are using test-ns-hook, fixture functions will *never* be run. + + + WRAPPING FIXTURES + + Instead of a map, a fixture can be specified like this: + + (defn my-fixture [f] + Perform setup, establish bindings, whatever. + (f) Then call the function we were passed. + Tear-down / clean-up code here. + ) + + This style is incompatible with async tests. If an async test is + encountered, testing will be aborted. It can't be mixed with + fixtures specified as maps. + + + EXTENDING TEST-IS (ADVANCED) + + You can extend the behavior of the \"is\" macro by defining new + methods for the \"assert-expr\" multimethod. These methods are + called during expansion of the \"is\" macro, so they should return + quoted forms to be evaluated. + + You can plug in your own test-reporting framework by specifying a + :reporter key in the test environment. It is normally set to + :cljs.test/default. Set this to the desired key and supply custom + implementations of the \"report\" multimethod. + + The 'event' argument is a map. It will always have a :type key, + whose value will be a keyword signaling the type of event being + reported. Standard events with :type value of :pass, :fail, and + :error are called when an assertion passes, fails, and throws an + exception, respectively. In that case, the event will also have + the following keys: + + :expected The form that was expected to be true + :actual A form representing what actually occurred + :message The string message given as an argument to 'is' + + The \"testing\" strings will be a list in the :testing-contexts + property of the test environment, and the vars being tested will be + a list in the :testing-vars property of the test environment. + + For additional event types, see the examples in the code. +"} + cljs.test + (:require-macros [clojure.template :as temp] + [cljs.test :as test]) + (:require [clojure.string :as string] + [cljs.pprint :as pprint])) + +;; ============================================================================= +;; Default Reporting + +(defn empty-env + "Generates a testing environment with a reporter. + (empty-env) - uses the :cljs.test/default reporter. + (empty-env :cljs.test/pprint) - pretty prints all data structures. + (empty-env reporter) - uses a reporter of your choosing. + + To create your own reporter see cljs.test/report" + ([] (empty-env ::default)) + ([reporter] + (cond-> {:report-counters {:test 0 :pass 0 :fail 0 :error 0} + :testing-vars () + :testing-contexts () + :formatter pr-str + :reporter reporter} + (= ::pprint reporter) (assoc :reporter ::default + :formatter pprint/pprint)))) + +(def ^:dynamic *current-env* nil) + +(defn get-current-env [] + (or *current-env* (empty-env))) + +(defn update-current-env! [ks f & args] + (set! *current-env* (apply update-in (get-current-env) ks f args))) + +(defn set-env! [new-env] + (set! *current-env* new-env)) + +(defn clear-env! [] + (set! *current-env* nil)) + +(defn get-and-clear-env! [] + "Like get-current-env, but cleans env before returning." + (let [env (cljs.test/get-current-env)] + (clear-env!) + env)) + +(defn testing-vars-str + "Returns a string representation of the current test. Renders names + in *testing-vars* as a list, then the source file and line of + current assertion." + [m] + (let [{:keys [file line column]} m] + (str + (reverse (map #(:name (meta %)) (:testing-vars (get-current-env)))) + " (" file ":" line (when column (str ":" column)) ")"))) + +(defn testing-contexts-str + "Returns a string representation of the current test context. Joins + strings in *testing-contexts* with spaces." + [] + (apply str (interpose " " (reverse (:testing-contexts (get-current-env)))))) + +(defn inc-report-counter! + "Increments the named counter in *report-counters*, a ref to a map. + Does nothing if *report-counters* is nil." + [name] + (if (:report-counters (get-current-env)) + (update-current-env! [:report-counters name] (fnil inc 0)))) + +(defmulti + ^{:doc "Generic reporting function, may be overridden to plug in + different report formats (e.g., TAP, JUnit). Assertions such as + 'is' call 'report' to indicate results. The argument given to + 'report' will be a map with a :type key." + :dynamic true} + report (fn [m] [(:reporter (get-current-env)) (:type m)])) + +(defmethod report :default [m]) + +(defmethod report [::default :pass] [m] + (inc-report-counter! :pass)) + +(defn- print-comparison [m] + (let [formatter-fn (or (:formatter (get-current-env)) pr-str)] + (println "expected:" (formatter-fn (:expected m))) + (println " actual:" (formatter-fn (:actual m))))) + +(defmethod report [::default :fail] [m] + (inc-report-counter! :fail) + (println "\nFAIL in" (testing-vars-str m)) + (when (seq (:testing-contexts (get-current-env))) + (println (testing-contexts-str))) + (when-let [message (:message m)] (println message)) + (print-comparison m)) + +(defmethod report [::default :error] [m] + (inc-report-counter! :error) + (println "\nERROR in" (testing-vars-str m)) + (when (seq (:testing-contexts (get-current-env))) + (println (testing-contexts-str))) + (when-let [message (:message m)] (println message)) + (print-comparison m)) + +(defmethod report [::default :summary] [m] + (println "\nRan" (:test m) "tests containing" + (+ (:pass m) (:fail m) (:error m)) "assertions.") + (println (:fail m) "failures," (:error m) "errors.")) + +(defmethod report [::default :begin-test-ns] [m] + (println "\nTesting" (name (:ns m)))) + +;; Ignore these message types: +(defmethod report [::default :end-test-ns] [m]) +(defmethod report [::default :begin-test-var] [m] + #_(println ":begin-test-var" (testing-vars-str m))) +(defmethod report [::default :end-test-var] [m]) +(defmethod report [::default :end-run-tests] [m]) +(defmethod report [::default :end-test-all-vars] [m]) +(defmethod report [::default :end-test-vars] [m]) + +;; ============================================================================= +;; File, Line, and Column Helpers + +(defn js-line-and-column [stack-element] + "Returns a 2-element vector containing the line and + column encoded at the end of a stack element string. + A line or column will be represented as NaN if not + parsesable." + (let [parts (.split stack-element ":") + cnt (count parts)] + (if (> cnt 1) + [(js/parseInt (nth parts (- cnt 2)) 10) + (js/parseInt (nth parts (dec cnt)) 10)] + [##NaN ##NaN]))) + +(defn js-filename [stack-element] + (let [output-dir (cljs.test/cljs-output-dir) + output-dir (cond-> output-dir + (not (string/ends-with? output-dir "/")) + (str "/"))] + (-> (.split stack-element output-dir) + last + (.split ":") + first))) + +(defn mapped-line-and-column [filename line column] + (let [default [filename line column]] + (if-let [source-map (:source-map (get-current-env))] + ;; source maps are 0 indexed for lines + (if-let [columns (get-in source-map [filename (dec line)])] + (vec + (map + ;; source maps are 0 indexed for columns + ;; multiple segments may exist at column + ;; just take first + (first + (if-let [mapping (get columns (dec column))] + mapping + (second (first columns)))) + [:source :line :col])) + default) + default))) + +(defn file-and-line [exception depth] + ;; TODO: flesh out + (if-let [stack-element (and (string? (.-stack exception)) + (some-> (.-stack exception) + string/split-lines + (get depth) + string/trim))] + (let [fname (js-filename stack-element) + [line column] (js-line-and-column stack-element) + [fname line column] (mapped-line-and-column fname line column)] + {:file fname :line line :column column}) + {:file (.-fileName exception) + :line (.-lineNumber exception)})) + +(defn do-report [m] + (let [m (case (:type m) + :fail (merge (file-and-line (js/Error.) 4) m) + :error (merge (file-and-line (:actual m) 0) m) + m)] + (report m))) + +;; ============================================================================= +;; Async + +(defprotocol IAsyncTest + "Marker protocol denoting CPS function to begin asynchronous + testing.") + +(defn async? + "Returns whether x implements IAsyncTest." + [x] + (satisfies? IAsyncTest x)) + +(defn run-block + "Invoke all functions in fns with no arguments. A fn can optionally + return + + an async test - is invoked with a continuation running left fns + + a seq of fns tagged per block - are invoked immediately after fn" + [fns] + (when-first [f fns] + (let [obj (f)] + (if (async? obj) + (obj (let [d (delay (run-block (rest fns)))] + (fn [] + (if (realized? d) + (println "WARNING: Async test called done more than one time.") + @d)))) + (recur (cond->> (rest fns) + (::block? (meta obj)) (concat obj))))))) + +(defn block + "Tag a seq of fns to be picked up by run-block as injected + continuation. See run-block." + [fns] + (some-> fns + (vary-meta assoc ::block? true))) + +;; ============================================================================= +;; Low-level functions + +(defn- test-var-block* + [v t] + {:pre [(instance? Var v)]} + [(fn [] + (update-current-env! [:testing-vars] conj v) + (update-current-env! [:report-counters :test] inc) + (do-report {:type :begin-test-var :var v}) + (try + (t) + (catch :default e + (case e + ::async-disabled (throw "Async tests require fixtures to be specified as maps. Testing aborted.") + (do-report + {:type :error + :message "Uncaught exception, not in assertion." + :expected nil + :actual e}))))) + (fn [] + (do-report {:type :end-test-var :var v}) + (update-current-env! [:testing-vars] rest))]) + +(defn test-var-block + "Like test-var, but returns a block for further composition and + later execution." + [v] + (if-let [t (:test (meta v))] + (test-var-block* v t))) + +(defn test-var + "If v has a function in its :test metadata, calls that function, + add v to :testing-vars property of env." + [v] + (run-block (test-var-block v))) + +(defn- default-fixture + "The default, empty, fixture function. Just calls its argument. + + NOTE: Incompatible with map fixtures." + [f] + (f)) + +(defn compose-fixtures + "Composes two fixture functions, creating a new fixture function + that combines their behavior. + + NOTE: Incompatible with map fixtures." + [f1 f2] + (fn [g] (f1 (fn [] (f2 g))))) + +(defn join-fixtures + "Composes a collection of fixtures, in order. Always returns a valid + fixture function, even if the collection is empty. + + NOTE: Incompatible with map fixtures." + [fixtures] + (reduce compose-fixtures default-fixture fixtures)) + +(defn- wrap-map-fixtures + "Wraps block in map-fixtures." + [map-fixtures block] + (concat (keep :before map-fixtures) + block + (reverse (keep :after map-fixtures)))) + +(defn- execution-strategy [once each] + (letfn [(fixtures-type [coll] + (cond + (empty? coll) :none + (every? map? coll) :map + (every? fn? coll) :fn)) + (fixtures-types [] + (->> (map fixtures-type [once each]) + (remove #{:none}) + (distinct)))] + (let [[type :as types] (fixtures-types)] + (assert (not-any? nil? types) + "Fixtures may not be of mixed types") + (assert (> 2 (count types)) + "fixtures specified in :once and :each must be of the same type") + ({:map :async :fn :sync} type :async)))) + +(defn- disable-async [f] + (fn [] + (let [obj (f)] + (when (async? obj) + (throw ::async-disabled)) + obj))) + +(defn test-vars-block + "Like test-vars, but returns a block for further composition and + later execution." + [vars] + (map + (fn [[ns vars]] + (fn [] + (block + (let [env (get-current-env) + once-fixtures (get-in env [:once-fixtures ns]) + each-fixtures (get-in env [:each-fixtures ns])] + (case (execution-strategy once-fixtures each-fixtures) + :async + (->> vars + (filter (comp :test meta)) + (mapcat (comp (partial wrap-map-fixtures each-fixtures) + test-var-block)) + (wrap-map-fixtures once-fixtures)) + :sync + (let [each-fixture-fn (join-fixtures each-fixtures)] + [(fn [] + ((join-fixtures once-fixtures) + (fn [] + (doseq [v vars] + (when-let [t (:test (meta v))] + ;; (alter-meta! v update :test disable-async) + (each-fixture-fn + (fn [] + ;; (test-var v) + (run-block + (test-var-block* v (disable-async t))))))))))])))))) + (group-by (comp :ns meta) vars))) + +(defn test-vars + "Groups vars by their namespace and runs test-var on them with + appropriate fixtures assuming they are present in the current + testing environment." + [vars] + (run-block (concat (test-vars-block vars) + [(fn [] + (report {:type :end-test-vars :vars vars}))]))) + +;; ============================================================================= +;; Running Tests, high level functions + +(defn successful? + "Returns true if the given test summary indicates all tests + were successful, false otherwise." + [summary] + (and (zero? (:fail summary 0)) + (zero? (:error summary 0)))) diff --git a/src/cljs/clojure/browser/dom.cljs b/src/main/cljs/clojure/browser/dom.cljs similarity index 98% rename from src/cljs/clojure/browser/dom.cljs rename to src/main/cljs/clojure/browser/dom.cljs index 2b053a3f87..8c6a0a043c 100644 --- a/src/cljs/clojure/browser/dom.cljs +++ b/src/main/cljs/clojure/browser/dom.cljs @@ -49,7 +49,7 @@ (log "v = " v) (when (or (keyword? k) (string? k)) - (doto o (aset (name k) v))))) + (doto o (gobject/set (name k) v))))) (js-obj) attrs) nil)] diff --git a/src/cljs/clojure/browser/event.cljs b/src/main/cljs/clojure/browser/event.cljs similarity index 60% rename from src/cljs/clojure/browser/event.cljs rename to src/main/cljs/clojure/browser/event.cljs index 449ed5bbfa..b04dcd2f43 100644 --- a/src/cljs/clojure/browser/event.cljs +++ b/src/main/cljs/clojure/browser/event.cljs @@ -10,80 +10,82 @@ events. It is based on the Google Closure Library event system." :author "Bobby Calderwood"} clojure.browser.event - (:require [goog.events :as events] - [goog.events.EventTarget :as gevent-target] - [goog.events.EventType :as gevent-type])) + (:require [goog.events :as events]) + (:import (goog.events EventTarget EventType))) -(defprotocol EventType +(defprotocol IEventType (event-types [this])) -(extend-protocol EventType +(extend-protocol IEventType - goog.events.EventTarget + EventTarget (event-types [this] (into {} (map (fn [[k v]] - [(keyword (. k (toLowerCase))) + [(keyword (.toLowerCase k)) v]) (merge - (js->clj goog.events.EventType))))) - - js/Element - (event-types - [this] - (into {} - (map - (fn [[k v]] - [(keyword (. k (toLowerCase))) - v]) - (merge - (js->clj goog.events.EventType)))))) + (js->clj EventType)))))) + +(when (exists? js/Element) + (extend-protocol IEventType + + js/Element + (event-types + [this] + (into {} + (map + (fn [[k v]] + [(keyword (.toLowerCase k)) + v]) + (merge + (js->clj EventType))))))) (defn listen ([src type fn] (listen src type fn false)) ([src type fn capture?] - (goog.events/listen src - (get (event-types src) type type) - fn - capture?))) + (events/listen src + (get (event-types src) type type) + fn + capture?))) (defn listen-once ([src type fn] (listen-once src type fn false)) ([src type fn capture?] - (goog.events/listenOnce src - (get (event-types src) type type) - fn - capture?))) + (events/listenOnce src + (get (event-types src) type type) + fn + capture?))) (defn unlisten ([src type fn] (unlisten src type fn false)) ([src type fn capture?] - (goog.events/unlisten src - (get (event-types src) type type) - fn - capture?))) + (events/unlisten src + (get (event-types src) type type) + fn + capture?))) (defn unlisten-by-key [key] - (goog.events/unlistenByKey key)) + (events/unlistenByKey key)) (defn dispatch-event [src event] - (goog.events/dispatchEvent src event)) + (events/dispatchEvent src event)) (defn expose [e] - (goog.events/expose e)) + (events/expose e)) (defn fire-listeners [obj type capture event]) (defn total-listener-count [] - (goog.events/getTotalListenerCount)) + (events/getTotalListenerCount)) ;; TODO (defn get-listener [src type listener opt_capt opt_handler]); ⇒ ?Listener @@ -97,4 +99,3 @@ events. It is based on the Google Closure Library event system." (defn remove-all [opt_obj opt_type opt_capt]); ⇒ number ;; TODO? (defn unlisten-with-wrapper [src wrapper listener opt_capt opt_handler]) - diff --git a/src/cljs/clojure/browser/net.cljs b/src/main/cljs/clojure/browser/net.cljs similarity index 75% rename from src/cljs/clojure/browser/net.cljs rename to src/main/cljs/clojure/browser/net.cljs index 29769e2d21..016e104620 100644 --- a/src/cljs/clojure/browser/net.cljs +++ b/src/main/cljs/clojure/browser/net.cljs @@ -11,12 +11,11 @@ Includes a common API over XhrIo, CrossPageChannel, and Websockets." :author "Bobby Calderwood and Alex Redington"} clojure.browser.net (:require [clojure.browser.event :as event] - [goog.net.XhrIo :as gxhrio] - [goog.net.EventType :as gnet-event-type] - [goog.net.xpc.CfgFields :as gxpc-config-fields] - [goog.net.xpc.CrossPageChannel :as xpc] - #_[goog.net.WebSocket :as gwebsocket] - [goog.json :as gjson])) + [goog.json :as gjson] + [goog.object :as gobj]) + (:import [goog.net XhrIo EventType WebSocket] + [goog.net.xpc CfgFields CrossPageChannel] + [goog Uri])) (def *timeout* 10000) @@ -24,10 +23,10 @@ Includes a common API over XhrIo, CrossPageChannel, and Websockets." (into {} (map (fn [[k v]] - [(keyword (. k (toLowerCase))) + [(keyword (.toLowerCase k)) v]) (merge - (js->clj goog.net.EventType))))) + (js->clj EventType))))) (defprotocol IConnection (connect @@ -43,7 +42,7 @@ Includes a common API over XhrIo, CrossPageChannel, and Websockets." [this opt opt2 opt3 opt4 opt5]) (close [this])) -(extend-type goog.net.XhrIo +(extend-type XhrIo IConnection (transmit @@ -60,15 +59,15 @@ Includes a common API over XhrIo, CrossPageChannel, and Websockets." (.send this uri method content headers))) - event/EventType + event/IEventType (event-types [this] (into {} (map (fn [[k v]] - [(keyword (. k (toLowerCase))) + [(keyword (.toLowerCase k)) v]) (merge - (js->clj goog.net.EventType)))))) + (js->clj EventType)))))) ;; TODO jQuery/sinatra/RestClient style API: (get [uri]), (post [uri payload]), (put [uri payload]), (delete [uri]) @@ -76,19 +75,19 @@ Includes a common API over XhrIo, CrossPageChannel, and Websockets." (into {} (map (fn [[k v]] - [(keyword (. k (toLowerCase))) + [(keyword (.toLowerCase k)) v]) - (js->clj goog.net.xpc.CfgFields)))) + (js->clj CfgFields)))) (defn xhr-connection "Returns an XhrIo connection" [] - (goog.net.XhrIo.)) + (XhrIo.)) (defprotocol ICrossPageChannel (register-service [this service-name fn] [this service-name fn encode-json?])) -(extend-type goog.net.xpc.CrossPageChannel +(extend-type CrossPageChannel ICrossPageChannel (register-service @@ -113,7 +112,7 @@ Includes a common API over XhrIo, CrossPageChannel, and Websockets." (.send this (name service-name) payload)) (close [this] - (.close this ()))) + (.close this))) (defn xpc-connection "When passed with a config hash-map, returns a parent @@ -127,14 +126,14 @@ Includes a common API over XhrIo, CrossPageChannel, and Websockets." per the CrossPageChannel API." ([] (when-let [config (.getParameterValue - (goog.Uri. (.-href (.-location js/window))) + (Uri. (.-href (.-location js/window))) "xpc")] - (goog.net.xpc.CrossPageChannel. (gjson/parse config)))) + (CrossPageChannel. (gjson/parse config)))) ([config] - (goog.net.xpc.CrossPageChannel. + (CrossPageChannel. (reduce (fn [sum [k v]] (if-let [field (get xpc-config-fields k)] - (doto sum (aset field v)) + (doto sum (gobj/set field v)) sum)) (js-obj) config)))) @@ -142,11 +141,10 @@ Includes a common API over XhrIo, CrossPageChannel, and Websockets." ;; WebSocket is not supported in the 3/23/11 release of Google ;; Closure, but will be included in the next release. -#_(defprotocol IWebSocket - (open? [this])) - -#_(extend-type goog.net.WebSocket +(defprotocol IWebSocket + (open? [this])) +(extend-type WebSocket IWebSocket (open? [this] (.isOpen this ())) @@ -154,9 +152,9 @@ Includes a common API over XhrIo, CrossPageChannel, and Websockets." IConnection (connect ([this url] - (connect this url nil)) + (connect this url nil)) ([this url protocol] - (.open this url protocol))) + (.open this url protocol))) (transmit [this message] (.send this message)) @@ -164,20 +162,20 @@ Includes a common API over XhrIo, CrossPageChannel, and Websockets." (close [this] (.close this ())) - event/EventType + event/IEventType (event-types [this] (into {} - (map - (fn [[k v]] - [(keyword (. k (toLowerCase))) - v]) - (merge - (js->clj goog.net.WebSocket/EventType)))))) - -#_(defn websocket-connection + (map + (fn [[k v]] + [(keyword (. k (toLowerCase))) + v]) + (merge + (js->clj WebSocket.EventType)))))) + +(defn websocket-connection ([] (websocket-connection nil nil)) ([auto-reconnect?] (websocket-connection auto-reconnect? nil)) ([auto-reconnect? next-reconnect-fn] - (goog.net.WebSocket. auto-reconnect? next-reconnect-fn))) \ No newline at end of file + (WebSocket. auto-reconnect? next-reconnect-fn))) diff --git a/src/main/cljs/clojure/browser/repl.cljs b/src/main/cljs/clojure/browser/repl.cljs new file mode 100644 index 0000000000..2010f8c51f --- /dev/null +++ b/src/main/cljs/clojure/browser/repl.cljs @@ -0,0 +1,278 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns ^{:doc "Receive - Eval - Print - Loop + + Receive a block of JS (presumably generated by a ClojureScript compiler) + Evaluate it naively + Print the result of evaluation to a string + Send the resulting string back to the server Loop!" + + :author "Bobby Calderwood and Alex Redington"} + clojure.browser.repl + (:require [goog.dom :as gdom] + [goog.object :as gobj] + [goog.array :as garray] + [goog.json :as json] + [goog.userAgent.product :as product] + [clojure.browser.net :as net] + [clojure.browser.event :as event] + ;; repl-connection callback will receive goog.require('cljs.repl') + ;; and monkey-patched require expects to be able to derive it + ;; via goog.basePath, so this namespace should be compiled together + ;; with clojure.browser.repl: + [cljs.repl])) + +(goog-define HOST "localhost") +(goog-define PORT 9000) + +(def ^:dynamic *repl* nil) + +;; these two defs are top-level so we can use them for printing +(def xpc-connection (atom nil)) +(def parent-connected? (atom false)) + +;; captures any printing that occurs *before* we actually have a connection +(def print-queue (array)) + +(defn flush-print-queue! [conn] + (doseq [str print-queue] + (net/transmit conn :print + (json/serialize + #js {"repl" *repl* + "str" str}))) + (garray/clear print-queue)) + +(defn repl-print [data] + (.push print-queue (pr-str data)) + (when @parent-connected? + (flush-print-queue! @xpc-connection))) + +(set! *print-newline* true) +(set-print-fn! repl-print) +(set-print-err-fn! repl-print) + +(defn get-ua-product [] + (cond + product/SAFARI :safari + product/CHROME :chrome + product/FIREFOX :firefox + product/IE :ie)) + +(defn evaluate-javascript + "Process a single block of JavaScript received from the server" + [conn block] + (let [result + (try + {:status :success + :value (str (js* "eval(~{block})"))} + (catch :default e + {:status :exception + :value (cljs.repl/error->str e)}))] + (pr-str result))) + +(defn send-result [connection url data] + (net/transmit connection url "POST" data nil 0)) + +(defn send-print + "Send data to be printed in the REPL. If there is an error, try again + up to 10 times." + ([url data] + (send-print url data 0)) + ([url data n] + (let [conn (net/xhr-connection)] + (event/listen conn :error + (fn [_] + (if (< n 10) + (send-print url data (inc n)) + (.log js/console (str "Could not send " data " after " n " attempts."))))) + (net/transmit conn url "POST" data nil 0)))) + +(def order (atom 0)) + +(defn wrap-message [repl t data] + (pr-str + {:repl repl + :type t + :content data + :order (swap! order inc)})) + +(defn start-evaluator + "Start the REPL server connection process. This process runs inside the + embedded iframe." + [url] + (if-let [repl-connection (net/xpc-connection)] + (let [connection (net/xhr-connection) + repl-connected? (atom false) + try-handshake (fn try-handshake [] + (when-not @repl-connected? + (net/transmit repl-connection :start-handshake nil)))] + (net/connect repl-connection try-handshake) + + (net/register-service repl-connection + :ack-handshake + (fn [_] + (when-not @repl-connected? + (reset! repl-connected? true) + ;; Now that we're connected to the parent, we can start talking to + ;; the server. + (send-result connection + url (wrap-message nil :ready "ready"))))) + + (event/listen connection + :error + (fn [e] + (reset! repl-connected? false) + (net/transmit repl-connection :reconnect nil) + (js/setTimeout try-handshake 1000))) + + (event/listen connection + :success + (fn [e] + (net/transmit + repl-connection + :evaluate-javascript + (.getResponseText (.-currentTarget e) ())))) + + (net/register-service repl-connection + :send-result + (fn [json] + (let [obj (json/parse json) + repl (gobj/get obj "repl") + result (gobj/get obj "result")] + (send-result connection url + (wrap-message repl :result result))))) + + (net/register-service repl-connection + :print + (fn [json] + (let [obj (json/parse json) + repl (gobj/get obj "repl") + str (gobj/get obj "str")] + (send-print url (wrap-message repl :print str)))))) + (js/alert "No 'xpc' param provided to child iframe."))) + +(def load-queue nil) + +(defn bootstrap + "Reusable browser REPL bootstrapping. Patches the essential functions + in goog.base to support re-loading of namespaces after page load." + [] + ;; Monkey-patch goog.provide if running under optimizations :none - David + (when-not js/COMPILED + (set! (.-require__ js/goog) js/goog.require) + ;; suppress useless Google Closure error about duplicate provides + (set! (.-isProvided_ js/goog) (fn [name] false)) + ;; provide cljs.user + (goog/constructNamespace_ "cljs.user") + (set! (.-writeScriptTag__ js/goog) + (fn [src opt_sourceText] + ;; the page is already loaded, we can no longer leverage document.write + ;; instead construct script tag elements and append them to the body + ;; of the page, to avoid parallel script loading enforce sequential + ;; load with a simple load queue + (let [loaded (atom false) + onload (fn [] + (when (and load-queue (false? @loaded)) + (swap! loaded not) + (if (zero? (alength load-queue)) + (set! load-queue nil) + (.apply js/goog.writeScriptTag__ nil (.shift load-queue)))))] + (.appendChild js/document.body + (as-> (.createElement js/document "script") script + (doto script + (gobj/set "type" "text/javascript") + (gobj/set "onload" onload) + (gobj/set "onreadystatechange" onload)) ;; IE + (if (nil? opt_sourceText) + (doto script (gobj/set "src" src)) + (doto script (gdom/setTextContent opt_sourceText)))))))) + ;; queue or load + (set! (.-writeScriptTag_ js/goog) + (fn [src opt_sourceText] + (if load-queue + (.push load-queue #js [src opt_sourceText]) + (do + (set! load-queue #js []) + (js/goog.writeScriptTag__ src opt_sourceText))))) + ;; In the latest Closure library implementation, there is no goog.writeScriptTag_, + ;; to monkey-patch. The behavior of interest is instead in goog.Dependency.prototype.load, + ;; which first checks and uses CLOSURE_IMPORT_SCRIPT if defined. So we hook our desired + ;; behavior here. + (when goog/debugLoader_ + (set! js/CLOSURE_IMPORT_SCRIPT (.-writeScriptTag_ js/goog))) + ;; we must reuse Closure library dev time dependency management, under namespace + ;; reload scenarios we simply delete entries from the correct private locations + (set! (.-require js/goog) + (fn [src reload] + (when (= reload "reload-all") + (set! (.-cljsReloadAll_ js/goog) true)) + (let [reload? (or reload (.-cljsReloadAll_ js/goog))] + (when reload? + (if (some? goog/debugLoader_) + (let [path (.getPathFromDeps_ goog/debugLoader_ src)] + (gobj/remove (.-written_ goog/debugLoader_) path) + (gobj/remove (.-written_ goog/debugLoader_) + (str js/goog.basePath path))) + (let [path (gobj/get js/goog.dependencies_.nameToPath src)] + (gobj/remove js/goog.dependencies_.visited path) + (gobj/remove js/goog.dependencies_.written path) + (gobj/remove js/goog.dependencies_.written + (str js/goog.basePath path))))) + (let [ret (.require__ js/goog src)] + (when (= reload "reload-all") + (set! (.-cljsReloadAll_ js/goog) false)) + ;; handle requires from Closure Library goog.modules + (if (js/goog.isInModuleLoader_) + (js/goog.module.getInternal_ src) + ret))))))) + +(defn connect + "Connects to a REPL server from an HTML document. After the + connection is made, the REPL will evaluate forms in the context of + the document that called this function." + [repl-server-url] + (let [connected? (atom false) + repl-connection (net/xpc-connection {:peer_uri repl-server-url})] + (swap! xpc-connection (constantly repl-connection)) + (net/register-service repl-connection + :start-handshake + (fn [_] + ;; Child will keep retrying, but we only want + ;; to ack once. + (when-not @connected? + (reset! connected? true) + (reset! parent-connected? true) + (net/transmit repl-connection :ack-handshake nil) + (flush-print-queue! repl-connection)))) + (net/register-service repl-connection + :reconnect + (fn [_] + (reset! connected? false) + (reset! parent-connected? false))) + (net/register-service repl-connection + :evaluate-javascript + (fn [json] + (let [obj (json/parse json) + repl (gobj/get obj "repl") + form (gobj/get obj "form")] + (net/transmit + repl-connection + :send-result + (json/serialize + #js {"repl" repl + "result" + (binding [*repl* repl] + (evaluate-javascript repl-connection form))}))))) + (net/connect repl-connection + (constantly nil) + (fn [iframe] + (set! (.-display (.-style iframe)) + "none"))) + (bootstrap) + repl-connection)) diff --git a/src/main/cljs/clojure/browser/repl/preload.cljs b/src/main/cljs/clojure/browser/repl/preload.cljs new file mode 100644 index 0000000000..8ef31606e7 --- /dev/null +++ b/src/main/cljs/clojure/browser/repl/preload.cljs @@ -0,0 +1,13 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns clojure.browser.repl.preload + (:require [clojure.browser.repl :as repl])) + +(defonce conn + (repl/connect (str "http://" repl/HOST ":" repl/PORT "/repl"))) diff --git a/src/main/cljs/clojure/core/protocols.cljs b/src/main/cljs/clojure/core/protocols.cljs new file mode 100644 index 0000000000..4e7aa75774 --- /dev/null +++ b/src/main/cljs/clojure/core/protocols.cljs @@ -0,0 +1,29 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns clojure.core.protocols) + +(defprotocol Datafiable + :extend-via-metadata true + (datafy [o] "return a representation of o as data (default identity)")) + +(extend-protocol Datafiable + nil + (datafy [_] nil) + + default + (datafy [o] o)) + +(defprotocol Navigable + :extend-via-metadata true + (nav [coll k v] "return (possibly transformed) v in the context of coll and k (a key/index or nil), +defaults to returning v.")) + +(extend-protocol Navigable + default + (nav [_ _ x] x)) diff --git a/src/cljs/clojure/core/reducers.cljs b/src/main/cljs/clojure/core/reducers.cljs similarity index 99% rename from src/cljs/clojure/core/reducers.cljs rename to src/main/cljs/clojure/core/reducers.cljs index a792bbe760..159cb8efdc 100644 --- a/src/cljs/clojure/core/reducers.cljs +++ b/src/main/cljs/clojure/core/reducers.cljs @@ -13,7 +13,7 @@ dependency info." :author "Rich Hickey"} clojure.core.reducers - (:refer-clojure :exclude [reduce map mapcat filter remove take take-while drop flatten]) + (:refer-clojure :exclude [reduce map mapcat filter remove take take-while drop flatten cat]) (:require [cljs.core :as core])) ;;;;;;;;;;;;;; some fj stuff ;;;;;;;;;; @@ -40,7 +40,7 @@ (-kv-reduce coll f init) (cond (nil? coll) init - (array? coll) (array-reduce coll f init) + (array? coll) (#'array-reduce coll f init) :else (-reduce coll f init))))) (defprotocol CollFold diff --git a/src/cljs/clojure/data.cljs b/src/main/cljs/clojure/data.cljs similarity index 99% rename from src/cljs/clojure/data.cljs rename to src/main/cljs/clojure/data.cljs index 11a84af313..80a608cd9b 100644 --- a/src/cljs/clojure/data.cljs +++ b/src/main/cljs/clojure/data.cljs @@ -12,7 +12,7 @@ clojure.data (:require [clojure.set :as set])) -(declare diff) +(declare ^{:arglists '([a b])} diff) (defn- atom-diff "Internal helper for diff." diff --git a/src/main/cljs/clojure/datafy.cljs b/src/main/cljs/clojure/datafy.cljs new file mode 100644 index 0000000000..fa141f0385 --- /dev/null +++ b/src/main/cljs/clojure/datafy.cljs @@ -0,0 +1,63 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns + ^{:doc "Functions to turn objects into data. Alpha, subject to change"} + clojure.datafy + (:require [clojure.core.protocols :as p])) + +(defn datafy + "Attempts to return x as data. + datafy will return the value of clojure.protocols/datafy. If + the value has been transformed and the result supports + metadata, :clojure.datafy/obj will be set on the metadata to the + original value of x." + [x] + (let [v (p/datafy x)] + (if (identical? v x) + v + (if (implements? IWithMeta v) + (vary-meta v assoc ::obj x + ;; Circling back to this at a later date per @dnolen + ;; ::class (-> x .-constructor .-name symbol) + ) + v)))) + +(defn nav + "Returns (possibly transformed) v in the context of coll and k (a + key/index or nil). Callers should attempt to provide the key/index + context k for Indexed/Associative/ILookup colls if possible, but not + to fabricate one e.g. for sequences (pass nil). nav will return the + value of clojure.core.protocols/nav." + [coll k v] + (p/nav coll k v)) + +(defn- datify-ref [r] + (with-meta [(deref r)] (meta r))) + +(extend-protocol p/Datafiable + js/Error + (datafy [x] (Throwable->map x)) + + ExceptionInfo + (datafy [x] (Throwable->map x)) + + Var + (datafy [r] (datify-ref r)) + + Reduced + (datafy [r] (datify-ref r)) + + Atom + (datafy [r] (datify-ref r)) + + Volatile + (datafy [r] (datify-ref r)) + + Delay + (datafy [r] (datify-ref r))) diff --git a/src/main/cljs/clojure/edn.cljs b/src/main/cljs/clojure/edn.cljs new file mode 100644 index 0000000000..9af7076b8c --- /dev/null +++ b/src/main/cljs/clojure/edn.cljs @@ -0,0 +1,55 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns clojure.edn + "edn reading. + + This namespace provides alias for cljs.reader/read and cljs.reader/read-string. + Thus Clojure and ClojureScript source can reference these functions in the same way. + In Clojure, read and read-string may cause evaluation, + but clojure.edn/read and clojure.edn/read-string will not. + In ClojureScript cljs.reader/read and cljs.reader/read-string will not cause evaluation, + they only read edn." + (:require [cljs.reader :as reader])) + +(defn read + "Reads the first object from an cljs.tools.reader.reader-types/IPushbackReader. + Returns the object read. If EOF, throws if eof-error? is true otherwise returns eof. + If no reader is provided, *in* will be used. + + Reads data in the edn format (subset of Clojure data): + http://edn-format.org + + cljs.tools.reader.edn/read doesn't depend on dynamic Vars, all configuration + is done by passing an opt map. + + opts is a map that can include the following keys: + :eof - value to return on end-of-file. When not supplied, eof throws an exception. + :readers - a map of tag symbols to data-reader functions to be considered before default-data-readers. + When not supplied, only the default-data-readers will be used. + :default - A function of two args, that will, if present and no reader is found for a tag, + be called with the tag and the value." + ([reader] + (reader/read reader)) + ([opts reader] + (reader/read opts reader)) + ([reader eof-error? eof opts] + (reader/read reader eof-error? eof opts))) + +(defn read-string + "Reads one object from the string s. + Returns nil when s is nil or empty. + + Reads data in the edn format (subset of Clojure data): + http://edn-format.org + + opts is a map as per cljs.tools.reader.edn/read" + ([s] + (reader/read-string s)) + ([opts s] + (reader/read-string opts s))) diff --git a/src/cljs/clojure/reflect.cljs b/src/main/cljs/clojure/reflect.cljs similarity index 73% rename from src/cljs/clojure/reflect.cljs rename to src/main/cljs/clojure/reflect.cljs index 50589d9e2a..67164f023e 100644 --- a/src/cljs/clojure/reflect.cljs +++ b/src/main/cljs/clojure/reflect.cljs @@ -1,5 +1,14 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + (ns clojure.reflect - (:refer-clojure :exclude [meta]) + ^{:doc "DEPRECATED. Do not use, superceded by REPL enhancements."} + (:refer-clojure :exclude [meta macroexpand]) (:require [clojure.browser.net :as net] [clojure.browser.event :as event])) diff --git a/src/cljs/clojure/set.cljs b/src/main/cljs/clojure/set.cljs similarity index 95% rename from src/cljs/clojure/set.cljs rename to src/main/cljs/clojure/set.cljs index 80f1db4b8a..7eddf4bc73 100644 --- a/src/cljs/clojure/set.cljs +++ b/src/main/cljs/clojure/set.cljs @@ -72,13 +72,12 @@ (defn rename-keys "Returns the map with the keys in kmap renamed to the vals in kmap" [map kmap] - (reduce + (reduce (fn [m [old new]] - (if (and (not= old new) - (contains? m old)) - (-> m (assoc new (get m old)) (dissoc old)) - m)) - map kmap)) + (if (contains? map old) + (assoc m new (get map old)) + m)) + (apply dissoc map (keys kmap)) kmap)) (defn rename "Returns a rel of the maps in xrel with the keys in kmap renamed to the vals in kmap" @@ -97,7 +96,11 @@ (defn map-invert "Returns the map with the vals mapped to the keys." - [m] (reduce (fn [m [k v]] (assoc m v k)) {} m)) + [m] + (persistent! + (reduce-kv (fn [m k v] (assoc! m v k)) + (transient {}) + m))) (defn join "When passed 2 rels, returns the rel corresponding to the natural diff --git a/src/main/cljs/clojure/string.cljs b/src/main/cljs/clojure/string.cljs new file mode 100644 index 0000000000..44e8b4eefe --- /dev/null +++ b/src/main/cljs/clojure/string.cljs @@ -0,0 +1,290 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns clojure.string + (:refer-clojure :exclude [replace reverse]) + (:require [goog.string :as gstring]) + (:import [goog.string StringBuffer])) + +(defn- seq-reverse + [coll] + (reduce conj () coll)) + +(def ^:private re-surrogate-pair + (js/RegExp. "([\\uD800-\\uDBFF])([\\uDC00-\\uDFFF])" "g")) + +(defn ^string reverse + "Returns s with its characters reversed." + [s] + (-> (.replace s re-surrogate-pair "$2$1") + (.. (split "") (reverse) (join "")))) + +(defn- replace-all + [s re replacement] + (let [r (js/RegExp. (.-source re) + (cond-> "g" + (.-ignoreCase re) (str "i") + (.-multiline re) (str "m") + (.-unicode re) (str "u")))] + (.replace s r replacement))) + +(defn- replace-with + [f] + (fn [& args] + (let [matches (drop-last 2 args)] + (if (= (count matches) 1) + (f (first matches)) + (f (vec matches)))))) + +(defn ^string replace + "Replaces all instance of match with replacement in s. + + match/replacement can be: + + string / string + pattern / (string or function of match). + + See also replace-first. + + The replacement is literal (i.e. none of its characters are treated + specially) for all cases above except pattern / string. + + For pattern / string, $1, $2, etc. in the replacement string are + substituted with the string that matched the corresponding + parenthesized group in the pattern. + + Example: + (clojure.string/replace \"Almost Pig Latin\" #\"\\b(\\w)(\\w+)\\b\" \"$2$1ay\") + -> \"lmostAay igPay atinLay\"" + [s match replacement] + (cond + (string? match) + (.replace s (js/RegExp. (gstring/regExpEscape match) "g") replacement) + + (instance? js/RegExp match) + (if (string? replacement) + (replace-all s match replacement) + (replace-all s match (replace-with replacement))) + + :else (throw (str "Invalid match arg: " match)))) + +(defn ^string replace-first + "Replaces the first instance of match with replacement in s. + + match/replacement can be: + + string / string + pattern / (string or function of match). + + See also replace. + + The replacement is literal (i.e. none of its characters are treated + specially) for all cases above except pattern / string. + + For pattern / string, $1, $2, etc. in the replacement string are + substituted with the string that matched the corresponding + parenthesized group in the pattern. + + Example: + (clojure.string/replace-first \"swap first two words\" + #\"(\\w+)(\\s+)(\\w+)\" \"$3$2$1\") + -> \"first swap two words\"" + [s match replacement] + (.replace s match replacement)) + +(defn join + "Returns a string of all elements in coll, as returned by (seq coll), + separated by an optional separator." + ([coll] + (loop [sb (StringBuffer.) coll (seq coll)] + (if-not (nil? coll) + (recur (. sb (append (str (first coll)))) (next coll)) + ^string (.toString sb)))) + ([separator coll] + (loop [sb (StringBuffer.) coll (seq coll)] + (if-not (nil? coll) + (do + (. sb (append (str (first coll)))) + (let [coll (next coll)] + (when-not (nil? coll) + (. sb (append separator))) + (recur sb coll))) + ^string (.toString sb))))) + +(defn ^string upper-case + "Converts string to all upper-case." + [s] + (.toUpperCase s)) + +(defn ^string lower-case + "Converts string to all lower-case." + [s] + (.toLowerCase s)) + +(defn ^string capitalize + "Converts first character of the string to upper-case, all other + characters to lower-case." + [s] + (gstring/capitalize s)) + +;; The JavaScript split function takes a limit argument but the return +;; value is not the same as the Java split function. +;; +;; Java: (.split "a-b-c" #"-" 2) => ["a" "b-c"] +;; JavaScript: (.split "a-b-c" #"-" 2) => ["a" "b"] +;; +;; For consistency, the three arg version has been implemented to +;; mimic Java's behavior. + +(defn- pop-last-while-empty + [v] + (loop [v v] + (if (identical? "" (peek v)) + (recur (pop v)) + v))) + +(defn- discard-trailing-if-needed + [limit v] + (if (and (== 0 limit) (< 1 (count v))) + (pop-last-while-empty v) + v)) + +(defn- split-with-empty-regex + [s limit] + (if (or (<= limit 0) (>= limit (+ 2 (count s)))) + (conj (vec (cons "" (map str (seq s)))) "") + (condp == limit + 1 (vector s) + 2 (vector "" s) + (let [c (- limit 2)] + (conj (vec (cons "" (subvec (vec (map str (seq s))) 0 c))) (subs s c)))))) + +(defn split + "Splits string on a regular expression. Optional argument limit is + the maximum number of parts. Not lazy. Returns vector of the parts. + Trailing empty strings are not returned - pass limit of -1 to return all." + ([s re] + (split s re 0)) + ([s re limit] + (discard-trailing-if-needed limit + (if (identical? "/(?:)/" (str re)) + (split-with-empty-regex s limit) + (if (< limit 1) + (vec (.split (str s) re)) + (loop [s s + limit limit + parts []] + (if (== 1 limit) + (conj parts s) + (let [m (re-find re s)] + (if-not (nil? m) + (let [index (.indexOf s m)] + (recur (.substring s (+ index (count m))) + (dec limit) + (conj parts (.substring s 0 index)))) + (conj parts s)))))))))) + +(defn split-lines + "Splits s on \\n or \\r\\n. Trailing empty lines are not returned." + [s] + (split s #"\n|\r\n")) + +(defn ^string trim + "Removes whitespace from both ends of string." + [s] + (gstring/trim s)) + +(defn ^string triml + "Removes whitespace from the left side of string." + [s] + (gstring/trimLeft s)) + +(defn ^string trimr + "Removes whitespace from the right side of string." + [s] + (gstring/trimRight s)) + +(defn ^string trim-newline + "Removes all trailing newline \\n or return \\r characters from + string. Similar to Perl's chomp." + [s] + (loop [index (.-length s)] + (if (zero? index) + "" + (let [ch (get s (dec index))] + (if (or (identical? \newline ch) + (identical? \return ch)) + (recur (dec index)) + (.substring s 0 index)))))) + +(defn ^boolean blank? + "True if s is nil, empty, or contains only whitespace." + [s] + (gstring/isEmptyOrWhitespace (gstring/makeSafe s))) + +(defn ^string escape + "Return a new string, using cmap to escape each character ch + from s as follows: + + If (cmap ch) is nil, append ch to the new string. + If (cmap ch) is non-nil, append (str (cmap ch)) instead." + [s cmap] + (let [buffer (StringBuffer.) + length (.-length s)] + (loop [index 0] + (if (== length index) + (. buffer (toString)) + (let [ch (.charAt s index) + replacement (cmap ch)] + (if-not (nil? replacement) + (.append buffer (str replacement)) + (.append buffer ch)) + (recur (inc index))))))) + +(defn index-of + "Return index of value (string or char) in s, optionally searching + forward from from-index or nil if not found." + ([s value] + (let [result (.indexOf s value)] + (if (neg? result) + nil + result))) + ([s value from-index] + (let [result (.indexOf s value from-index)] + (if (neg? result) + nil + result)))) + +(defn last-index-of + "Return last index of value (string or char) in s, optionally + searching backward from from-index or nil if not found." + ([s value] + (let [result (.lastIndexOf s value)] + (if (neg? result) + nil + result))) + ([s value from-index] + (let [result (.lastIndexOf s value from-index)] + (if (neg? result) + nil + result)))) + +(defn ^boolean starts-with? + "True if s starts with substr." + [s substr] + (gstring/startsWith s substr)) + +(defn ^boolean ends-with? + "True if s ends with substr." + [s substr] + (gstring/endsWith s substr)) + +(defn ^boolean includes? + "True if s includes substr." + [s substr] + (gstring/contains s substr)) diff --git a/src/cljs/clojure/walk.cljs b/src/main/cljs/clojure/walk.cljs similarity index 89% rename from src/cljs/clojure/walk.cljs rename to src/main/cljs/clojure/walk.cljs index f2ebd8d023..d92d61c91c 100644 --- a/src/cljs/clojure/walk.cljs +++ b/src/main/cljs/clojure/walk.cljs @@ -43,9 +43,13 @@ the sorting function."} {:added "1.1"} [inner outer form] (cond - (seq? form) (outer (doall (map inner form))) - (coll? form) (outer (into (empty form) (map inner form))) - :else (outer form))) + (list? form) (outer (apply list (map inner form))) + (map-entry? form) + (outer (MapEntry. (inner (key form)) (inner (val form)) nil)) + (seq? form) (outer (doall (map inner form))) + (record? form) (outer (reduce (fn [r x] (conj r (inner x))) form form)) + (coll? form) (outer (into (empty form) (map inner form))) + :else (outer form))) (defn postwalk "Performs a depth-first, post-order traversal of form. Calls f on diff --git a/src/cljs/clojure/zip.cljs b/src/main/cljs/clojure/zip.cljs similarity index 100% rename from src/cljs/clojure/zip.cljs rename to src/main/cljs/clojure/zip.cljs diff --git a/src/main/cljs/process/env.cljs b/src/main/cljs/process/env.cljs new file mode 100644 index 0000000000..c8a6909eb1 --- /dev/null +++ b/src/main/cljs/process/env.cljs @@ -0,0 +1,12 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns process.env + "A shim namespace for the Node.js process library") + +(goog-define NODE_ENV "development") diff --git a/src/main/clojure/cljs/analyzer.cljc b/src/main/clojure/cljs/analyzer.cljc new file mode 100644 index 0000000000..c63b857f2d --- /dev/null +++ b/src/main/clojure/cljs/analyzer.cljc @@ -0,0 +1,5165 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer + #?(:clj (:refer-clojure :exclude [ensure macroexpand-1]) + :cljs (:refer-clojure :exclude [ensure js-reserved macroexpand-1 ns-interns])) + #?(:cljs (:require-macros [cljs.analyzer.macros + :refer [allowing-redef disallowing-ns* disallowing-recur + no-warn with-warning-handlers wrapping-errors]] + [cljs.env.macros :refer [ensure]])) + #?(:clj (:require [cljs.analyzer.impl :as impl] + [cljs.analyzer.impl.namespaces :as nses] + [cljs.analyzer.passes.and-or :as and-or] + [cljs.analyzer.passes.lite :as lite] + [cljs.env :as env :refer [ensure]] + [cljs.externs :as externs] + [cljs.js-deps :as deps] + [cljs.tagged-literals :as tags] + [cljs.util :as util :refer [ns->relpath topo-sort]] + [clojure.edn :as edn] + [clojure.java.io :as io] + [clojure.set :as set] + [clojure.string :as string] + [cljs.vendor.clojure.tools.reader :as reader] + [cljs.vendor.clojure.tools.reader.reader-types :as readers]) + :cljs (:require [cljs.analyzer.impl :as impl] + [cljs.analyzer.impl.namespaces :as nses] + [cljs.analyzer.passes.and-or :as and-or] + [cljs.analyzer.passes.lite :as lite] + [cljs.env :as env] + [cljs.reader :as edn] + [cljs.tagged-literals :as tags] + [cljs.tools.reader :as reader] + [cljs.tools.reader.reader-types :as readers] + [clojure.set :as set] + [clojure.string :as string] + [goog.string :as gstring])) + #?(:clj (:import [cljs.tagged_literals JSValue] + [clojure.lang Namespace Var LazySeq ArityException] + [java.io File Reader PushbackReader] + [java.lang Throwable] + [java.net URL] + [java.util.regex Pattern]))) + +#?(:clj (set! *warn-on-reflection* true)) + +;; User file-local compiler flags +#?(:clj (def ^:dynamic *unchecked-if* false)) +#?(:clj (def ^:dynamic *unchecked-arrays* false)) + +;; Compiler dynamic vars +(def ^:dynamic *cljs-ns* 'cljs.user) +(def ^:dynamic *cljs-file* nil) +(def ^:dynamic *checked-arrays* false) +(def ^:dynamic *check-alias-dupes* true) +(def ^:dynamic *cljs-static-fns* false) +(def ^:dynamic *fn-invoke-direct* false) +(def ^:dynamic *cljs-macros-path* "/cljs/core") +(def ^:dynamic *cljs-macros-is-classpath* true) +(def ^:dynamic *cljs-dep-set* (with-meta #{} {:dep-path []})) +(def ^:dynamic *analyze-deps* true) +(def ^:dynamic *load-tests* true) +(def ^:dynamic *load-macros* true) +(def ^:dynamic *reload-macros* false) +(def ^:dynamic *macro-infer* true) +(def ^:dynamic *passes* nil) +(def ^:dynamic *file-defs* nil) +(def ^:dynamic *private-var-access-nowarn* false) + +(def constants-ns-sym + "The namespace of the constants table as a symbol." + 'cljs.core.constants) + +#?(:clj + (def transit-read-opts + (try + (require '[cljs.vendor.cognitect.transit]) + (when-some [ns (find-ns 'cljs.vendor.cognitect.transit)] + (let [read-handler @(ns-resolve ns 'read-handler) + read-handler-map @(ns-resolve ns 'read-handler-map)] + {:handlers + (read-handler-map + {"cljs/js" (read-handler (fn [v] (JSValue. v))) + "cljs/regex" (read-handler (fn [v] (Pattern/compile v)))})})) + (catch Throwable t + nil)))) + +#?(:clj + (def transit-write-opts + (try + (require '[cljs.vendor.cognitect.transit]) + (when-some [ns (find-ns 'cljs.vendor.cognitect.transit)] + (let [write-handler @(ns-resolve ns 'write-handler) + write-handler-map @(ns-resolve ns 'write-handler-map)] + {:handlers + (write-handler-map + {JSValue + (write-handler + (fn [_] "cljs/js") + (fn [js] (.val ^JSValue js))) + Pattern + (write-handler + (fn [_] "cljs/regex") + (fn [pat] (.pattern ^Pattern pat)))})})) + (catch Throwable t + nil)))) + +#?(:clj + (def transit + (delay + (try + (require '[cljs.vendor.cognitect.transit]) + (when-some [ns (find-ns 'cljs.vendor.cognitect.transit)] + {:writer @(ns-resolve ns 'writer) + :reader @(ns-resolve ns 'reader) + :write @(ns-resolve ns 'write) + :read @(ns-resolve ns 'read)}) + (catch Throwable t + nil))))) + +;; log compiler activities +(def ^:dynamic *verbose* false) + +(def -cljs-macros-loaded (atom false)) + +(def ^:dynamic *cljs-warnings* + {:preamble-missing true + :unprovided true + :undeclared-var true + :private-var-access true + :undeclared-ns true + :undeclared-ns-form true + :redef true + :redef-in-file true + :dynamic true + :fn-var true + :fn-arity true + :fn-deprecated true + :declared-arglists-mismatch true + :protocol-deprecated true + :undeclared-protocol-symbol true + :invalid-protocol-symbol true + :multiple-variadic-overloads true + :variadic-max-arity true + :overload-arity true + :extending-base-js-type true + :invoke-ctor true + :invalid-arithmetic true + :invalid-array-access true + :protocol-invalid-method true + :protocol-duped-method true + :protocol-multiple-impls true + :protocol-with-variadic-method true + :protocol-with-overwriting-method true + :protocol-impl-with-variadic-method true + :protocol-impl-recur-with-target true + :single-segment-namespace true + :munged-namespace true + :js-used-as-alias true + :ns-var-clash true + :non-dynamic-earmuffed-var true + :extend-type-invalid-method-shape true + :unsupported-js-module-type true + :unsupported-preprocess-value true + :js-shadowed-by-local true + :infer-warning false}) + +(defn unchecked-arrays? [] + *unchecked-arrays*) + +(defn compiler-options [] + (get @env/*compiler* :options)) + +(defn get-externs [] + (::externs @env/*compiler*)) + +(defn checked-arrays + "Returns false-y, :warn, or :error based on configuration and the + current value of *unchecked-arrays*." + [] + (when (and (not= :advanced (:optimizations (compiler-options))) + (not *unchecked-arrays*)) + *checked-arrays*)) + +(def js-reserved + #{"arguments" "abstract" "await" "boolean" "break" "byte" "case" + "catch" "char" "class" "const" "continue" + "debugger" "default" "delete" "do" "double" + "else" "enum" "export" "extends" "final" + "finally" "float" "for" "function" "goto" "if" + "implements" "import" "in" "instanceof" "int" + "interface" "let" "long" "native" "new" + "package" "private" "protected" "public" + "return" "short" "static" "super" "switch" + "synchronized" "this" "throw" "throws" + "transient" "try" "typeof" "var" "void" + "volatile" "while" "with" "yield" "methods" + "null" "constructor"}) + +(def es5-allowed + #{"default"}) + +#?(:clj (def SENTINEL (Object.)) + :cljs (def SENTINEL (js-obj))) + +(defn gets + ([m k0 k1] + (let [m (get m k0 SENTINEL)] + (when-not (identical? m SENTINEL) + (get m k1)))) + ([m k0 k1 k2] + (let [m (get m k0 SENTINEL)] + (when-not (identical? m SENTINEL) + (let [m (get m k1 SENTINEL)] + (when-not (identical? m SENTINEL) + (get m k2)))))) + ([m k0 k1 k2 k3] + (let [m (get m k0 SENTINEL)] + (when-not (identical? m SENTINEL) + (let [m (get m k1 SENTINEL)] + (when-not (identical? m SENTINEL) + (let [m (get m k2 SENTINEL)] + (when-not (identical? m SENTINEL) + (get m k3))))))))) + +#?(:cljs + (defn munge-path [ss] + (munge (str ss)))) + +#?(:cljs + (defn ns->relpath + "Given a namespace as a symbol return the relative path. May optionally + provide the file extension, defaults to :cljs." + ([ns] (ns->relpath ns :cljs)) + ([ns ext] + (str (string/replace (munge-path ns) \. \/) "." (name ext))))) + +#?(:cljs + (defn topo-sort + ([x get-deps] + (topo-sort x 0 (atom (sorted-map)) (memoize get-deps))) + ([x depth state memo-get-deps] + (let [deps (memo-get-deps x)] + (swap! state update-in [depth] (fnil into #{}) deps) + (doseq [dep deps] + (topo-sort dep (inc depth) state memo-get-deps)) + (doseq [[relpath ns-sym :cljs) + ", " (ns->relpath ns-sym :cljc) + ", or JavaScript source providing \"" js-provide "\"" + (when (string/includes? (ns->relpath ns-sym) "_") + " (Please check that namespaces with dashes use underscores in the ClojureScript file name)"))) + +(defmethod error-message :undeclared-macros-ns + [warning-type {:keys [ns-sym js-provide] :as info}] + (str "No such macros namespace: " ns-sym + ", could not locate " (ns->relpath ns-sym :clj) + " or " (ns->relpath ns-sym :cljc))) + +(defmethod error-message :dynamic + [warning-type info] + (str (:name info) " not declared ^:dynamic")) + +(defmethod error-message :redef + [warning-type info] + (str (:sym info) " already refers to: " (symbol (str (:ns info)) (str (:sym info))) + " being replaced by: " (symbol (str (:ns-name info)) (str (:sym info))))) + +(defmethod error-message :redef-in-file + [warning-type info] + (str (:sym info) " at line " (:line info) " is being replaced")) + +(defmethod error-message :fn-var + [warning-type info] + (str (symbol (str (:ns-name info)) (str (:sym info))) + " no longer fn, references are stale")) + +(defmethod error-message :fn-arity + [warning-type info] + (str "Wrong number of args (" (:argc info) ") passed to " + (or (:ctor info) + (:name info)))) + +(defmethod error-message :fn-deprecated + [warning-type info] + (str (-> info :fexpr :info :name) " is deprecated")) + +(defmethod error-message :declared-arglists-mismatch + [warning-type info] + (str (symbol (str (:ns-name info)) (str (:sym info))) + " declared arglists " (:declared info) + " mismatch defined arglists " (:defined info))) + +(defmethod error-message :undeclared-ns-form + [warning-type info] + (str "Invalid :refer, " (:type info) " " (:lib info) "/" (:sym info) " does not exist")) + +(defmethod error-message :protocol-deprecated + [warning-type info] + (str "Protocol " (:protocol info) " is deprecated")) + +(defmethod error-message :undeclared-protocol-symbol + [warning-type info] + (str "Can't resolve protocol symbol " (:protocol info))) + +(defmethod error-message :invalid-protocol-symbol + [warning-type info] + (str "Symbol " (:protocol info) " is not a protocol")) + +(defmethod error-message :protocol-invalid-method + [warning-type info] + (if (:no-such-method info) + (str "Bad method signature in protocol implementation, " + (:protocol info) " does not declare method called " (:fname info)) + (str "Bad method signature in protocol implementation, " + (:protocol info) " " (:fname info) " does not declare arity " (:invalid-arity info)))) + +(defmethod error-message :protocol-duped-method + [warning-type info] + (str "Duplicated methods in protocol implementation " (:protocol info) " " (:fname info))) + +(defmethod error-message :protocol-multiple-impls + [warning-type info] + (str "Protocol " (:protocol info) " implemented multiple times")) + +(defmethod error-message :protocol-with-variadic-method + [warning-type info] + (str "Protocol " (:protocol info) " declares method " + (:name info) " with variadic signature (&)")) + +(defmethod error-message :protocol-with-overwriting-method + [warning-type info] + (let [overwritten-protocol (-> info :existing :protocol)] + (str "Protocol " (:protocol info) " is overwriting " + (if overwritten-protocol "method" "function") + " " (:name info) + (when overwritten-protocol (str " of protocol " (name overwritten-protocol)))))) + +(defmethod error-message :protocol-impl-with-variadic-method + [warning-type info] + (str "Protocol " (:protocol info) " implements method " + (:name info) " with variadic signature (&)")) + +(defmethod error-message :protocol-impl-recur-with-target + [warning-type info] + (str "Ignoring target object \"" (pr-str (:form info)) "\" passed in recur to protocol method head")) + +(defmethod error-message :multiple-variadic-overloads + [warning-type info] + (str (:name info) ": Can't have more than 1 variadic overload")) + +(defmethod error-message :variadic-max-arity + [warning-type info] + (str (:name info) ": Can't have fixed arity function with more params than variadic function")) + +(defmethod error-message :overload-arity + [warning-type info] + (str (:name info) ": Can't have 2 overloads with same arity")) + +(defmethod error-message :extending-base-js-type + [warning-type info] + (str "Extending an existing JavaScript type - use a different symbol name " + "instead of " (:current-symbol info) " e.g " (:suggested-symbol info))) + +(defmethod error-message :invalid-arithmetic + [warning-type info] + (str (:js-op info) ", all arguments must be numbers, got " (:types info) " instead")) + +(defmethod error-message :invalid-array-access + [warning-type {:keys [name types]}] + (case name + (cljs.core/checked-aget cljs.core/checked-aget') + (str "cljs.core/aget, arguments must be an array followed by numeric indices, got " types " instead" + (when (or (= 'object (first types)) + (every? #{'string} (rest types))) + (str " (consider " + (if (== 2 (count types)) + "goog.object/get" + "goog.object/getValueByKeys") + " for object access)"))) + + (cljs.core/checked-aset cljs.core/checked-aset') + (str "cljs.core/aset, arguments must be an array, followed by numeric indices, followed by a value, got " types " instead" + (when (or (= 'object (first types)) + (every? #{'string} (butlast (rest types)))) + " (consider goog.object/set for object access)")))) + +(defmethod error-message :invoke-ctor + [warning-type info] + (str "Cannot invoke type constructor " (-> info :fexpr :info :name) " as function ")) + +(defmethod error-message :single-segment-namespace + [warning-type info] + (str (:name info) " is a single segment namespace")) + +(defmethod error-message :munged-namespace + [warning-type {:keys [name] :as info}] + (let [munged (->> (string/split (clojure.core/name name) #"\.") + (map #(if (js-reserved %) (str % "$") %)) + (string/join ".") + (munge))] + (str "Namespace " name " contains a reserved JavaScript keyword," + " the corresponding Google Closure namespace will be munged to " munged))) + +(defmethod error-message :js-used-as-alias + [warning-type {:keys [spec] :as info}] + (str "In " (pr-str spec) ", the alias name js is reserved for JavaScript interop")) + +(defmethod error-message :ns-var-clash + [warning-type {:keys [ns var] :as info}] + (str "Namespace " ns " clashes with var " var)) + +(defmethod error-message :non-dynamic-earmuffed-var + [warning-type {:keys [var] :as info}] + (str var " not declared dynamic and thus is not dynamically rebindable, but its name " + "suggests otherwise. Please either indicate ^:dynamic " var " or change the name")) + +(defmethod error-message :extend-type-invalid-method-shape + [warning-type {:keys [protocol method] :as info}] + (str "Bad extend-type method shape for protocol " protocol " method " method + ", method arities must be grouped together")) + +(defmethod error-message :unsupported-js-module-type + [warning-type {:keys [module-type file] :as info}] + (str "Unsupported JavaScript module type " module-type " for foreign library " + file ".")) + +(defmethod error-message :unsupported-preprocess-value + [warning-type {:keys [preprocess file]}] + (str "Unsupported preprocess value " preprocess " for foreign library " + file ".")) + +(defmethod error-message :js-shadowed-by-local + [warning-type {:keys [name]}] + (str name " is shadowed by a local")) + +(defmethod error-message :infer-warning + [warning-type {:keys [warn-type form type property]}] + (case warn-type + :target (str "Cannot infer target type in expression " form "") + :property (str "Cannot resolve property " property + " for inferred type " type " in expression " form) + :object (str "Adding extern to Object for property " property " due to " + "ambiguous expression " form))) + +(defn default-warning-handler [warning-type env extra] + (when (warning-type *cljs-warnings*) + (when-let [s (error-message warning-type extra)] + #?(:clj (binding [*out* *err*] + (println (message env (str "WARNING: " s)))) + :cljs (binding [*print-fn* *print-err-fn*] + (println (message env (str "WARNING: " s)))))))) + +(def ^:dynamic *cljs-warning-handlers* + [default-warning-handler]) + +(defn lite-mode? [] + (get-in @env/*compiler* [:options :lite-mode])) + +(defn elide-to-string? [] + (get-in @env/*compiler* [:options :elide-to-string])) + +#?(:clj + (defmacro with-warning-handlers [handlers & body] + `(binding [*cljs-warning-handlers* ~handlers] + ~@body))) + +(defn- repeat-char [c n] + (loop [ret c n n] + (if (pos? n) + (recur (str ret c) (dec n)) + ret))) + +(defn- hex-format [s pad] + #?(:clj (str "_u" (format (str "%0" pad "x") (int (first s))) "_") + :cljs (let [hex (.toString (.charCodeAt s 0) 16) + len (. hex -length) + hex (if (< len pad) + (str (repeat-char "0" (- pad len)) hex) + hex)] + (str "_u" hex "_")))) + +(defn gen-constant-id [value] + (let [prefix (cond + (keyword? value) "cst$kw$" + (symbol? value) "cst$sym$" + :else + (throw + #?(:clj (Exception. (str "constant type " (type value) " not supported")) + :cljs (js/Error. (str "constant type " (type value) " not supported"))))) + name (if (keyword? value) + (subs (str value) 1) + (str value)) + name (if (= "." name) + "_DOT_" + (-> name + (string/replace "_" "__") + (string/replace "$" "$$") + (string/replace "-" "_DASH_") + (munge) + (string/replace "." "$") + (string/replace #"(?i)[^a-z0-9$_]" #(hex-format % 4))))] + (symbol (str prefix name)))) + +(defn- register-constant! + ([val] (register-constant! nil val)) + ([env val] + (swap! env/*compiler* + (fn [cenv] + (cond-> + (-> cenv + (update-in [::constant-table] + (fn [table] + (if (get table val) + table + (assoc table val (gen-constant-id val)))))) + env (update-in [::namespaces (-> env :ns :name) ::constants] + (fn [{:keys [seen order] :or {seen #{} order []} :as constants}] + (cond-> constants + (not (contains? seen val)) + (assoc + :seen (conj seen val) + :order (conj order val)))))))))) + +(def default-namespaces '{cljs.core {:name cljs.core} + cljs.user {:name cljs.user}}) + +;; this exists solely to support read-only namespace access from macros. +;; External tools should look at the authoritative ::namespaces slot in the +;; compiler-env atoms/maps they're using already; this value will yield only +;; `default-namespaces` when accessed outside the scope of a +;; compilation/analysis call +(def namespaces + #?(:clj + (reify clojure.lang.IDeref + (deref [_] + (if (some? env/*compiler*) + (::namespaces @env/*compiler*) + default-namespaces))) + :cljs + (reify IDeref + (-deref [_] + (if (some? env/*compiler*) + (::namespaces @env/*compiler*) + default-namespaces))))) + +(defn get-namespace + ([key] + (get-namespace env/*compiler* key)) + ([cenv key] + (if-some [ns (get-in @cenv [::namespaces key])] + ns + (when (= 'cljs.user key) + {:name 'cljs.user})))) + +#?(:clj + (defmacro no-warn [& body] + (let [no-warnings (zipmap (keys *cljs-warnings*) (repeat false))] + `(binding [*cljs-warnings* ~no-warnings] + ~@body)))) + +#?(:clj + (defmacro all-warn [& body] + (let [all-warnings (zipmap (keys *cljs-warnings*) (repeat true))] + `(binding [*cljs-warnings* ~all-warnings] + ~@body)))) + +(defn get-line [x env] + (or (-> x meta :line) (:line env))) + +(defn get-col [x env] + (or (-> x meta :column) (:column env))) + +(defn intern-macros + "Given a Clojure namespace intern all macros into the ambient ClojureScript + analysis environment." + ([ns] (intern-macros ns false)) + ([ns reload] + (when (or (nil? (gets @env/*compiler* ::namespaces ns :macros)) + reload) + (swap! env/*compiler* assoc-in [::namespaces ns :macros] + (->> #?(:clj (ns-interns ns) :cljs (ns-interns* ns)) + (filter (fn [[_ ^Var v]] (.isMacro v))) + (map (fn [[k v]] + [k (as-> (meta v) vm + (let [ns (.getName ^Namespace (:ns vm))] + (assoc vm + :ns ns + :name (symbol (str ns) (str k)) + :macro true)))])) + (into {})))))) + +#?(:clj + (def load-mutex (Object.))) + +#?(:clj + (defn- load-data-reader-file [mappings ^java.net.URL url] + (with-open [rdr (readers/input-stream-push-back-reader (.openStream url))] + (binding [*file* (.getFile url)] + (let [new-mappings (reader/read {:eof nil :read-cond :allow :features #{:cljs}} rdr)] + (when (not (map? new-mappings)) + (throw (ex-info (str "Not a valid data-reader map") + {:url url + :clojure.error/phase :compilation}))) + (reduce + (fn [m [k v]] + (when (not (symbol? k)) + (throw (ex-info (str "Invalid form in data-reader file") + {:url url + :form k + :clojure.error/phase :compilation}))) + (when (and (contains? mappings k) + (not= (mappings k) v)) + (throw (ex-info "Conflicting data-reader mapping" + {:url url + :conflict k + :mappings m + :clojure.error/phase :compilation}))) + (assoc m k v)) + mappings + new-mappings)))))) + +#?(:clj + (defn get-data-readers* + "returns a merged map containing all data readers defined by libraries + on the classpath." + ([] + (get-data-readers* (. (Thread/currentThread) (getContextClassLoader)))) + ([^ClassLoader classloader] + (let [data-reader-urls (enumeration-seq (. classloader (getResources "data_readers.cljc")))] + (reduce load-data-reader-file {} data-reader-urls))))) + +#?(:clj + (def get-data-readers (memoize get-data-readers*))) + +#?(:clj + (defn load-data-readers* [] + (let [data-readers (get-data-readers) + nses (map (comp symbol namespace) (vals data-readers))] + (doseq [ns nses] + (try + (locking load-mutex + (require ns)) + (catch Throwable _))) + (->> data-readers + (map (fn [[tag reader-fn]] + [tag + (-> reader-fn find-var var-get + (with-meta {:sym reader-fn}))])) + (into {}))))) + +#?(:clj + (def load-data-readers (memoize load-data-readers*))) + +#?(:clj + (defn load-core [] + (when (not @-cljs-macros-loaded) + (reset! -cljs-macros-loaded true) + (if *cljs-macros-is-classpath* + (locking load-mutex + (load *cljs-macros-path*)) + (locking load-mutex + (load-file *cljs-macros-path*)))) + (intern-macros 'cljs.core))) + +#?(:clj + (defmacro with-core-macros + [path & body] + `(do + (when (not= *cljs-macros-path* ~path) + (reset! -cljs-macros-loaded false)) + (binding [*cljs-macros-path* ~path] + ~@body)))) + +#?(:clj + (defmacro with-core-macros-file + [path & body] + `(do + (when (not= *cljs-macros-path* ~path) + (reset! -cljs-macros-loaded false)) + (binding [*cljs-macros-path* ~path + *cljs-macros-is-classpath* false] + ~@body)))) + +(defn empty-env + "Construct an empty analysis environment. Required to analyze forms." + [] + (ensure + {:ns (get-namespace *cljs-ns*) + :context :statement + :locals {} + :fn-scope [] + :js-globals (into {} + (map #(vector % {:op :js-var :name % :ns 'js}) + '(alert window document console escape unescape + screen location navigator history location + global process require module exports)))})) + +(defn- source-info->error-data + [{:keys [file line column]}] + {:clojure.error/source file + :clojure.error/line line + :clojure.error/column column}) + +(defn source-info + ([env] + (when (:line env) + (source-info nil env))) + ([name env] + (cond-> {:file (if (= (-> env :ns :name) 'cljs.core) + "cljs/core.cljs" + *cljs-file*) + :line (get-line name env) + :column (get-col name env)} + (:root-source-info env) + (merge (select-keys env [:root-source-info]))))) + +(defn message [env s] + (str s + (if (:line env) + (str " at line " (:line env) " " *cljs-file*) + (when *cljs-file* + (str " in file " *cljs-file*))))) + +(defn warning [warning-type env extra] + (doseq [handler *cljs-warning-handlers*] + (handler warning-type env extra))) + +(defn- accumulating-warning-handler [warn-acc] + (fn [warning-type env extra] + (when (warning-type *cljs-warnings*) + (swap! warn-acc conj [warning-type env extra])))) + +(defn- replay-accumulated-warnings [warn-acc] + (run! #(apply warning %) @warn-acc)) + +(defn- error-data + ([env phase] + (error-data env phase nil)) + ([env phase symbol] + (merge (-> (source-info env) source-info->error-data) + {:clojure.error/phase phase} + (when symbol + {:clojure.error/symbol symbol})))) + +(defn- compile-syntax-error + [env msg symbol] + (ex-info nil (error-data env :compile-syntax-check symbol) + #?(:clj (RuntimeException. ^String msg) :cljs (js/Error. msg)))) + +(defn error + ([env msg] + (error env msg nil)) + ([env msg cause] + (ex-info (message env msg) + (assoc (source-info env) :tag :cljs/analysis-error) + cause))) + +(defn analysis-error? + #?(:cljs {:tag boolean}) + [ex] + (= :cljs/analysis-error (:tag (ex-data ex)))) + +(defn has-error-data? + #?(:cljs {:tag boolean}) + [ex] + (contains? (ex-data ex) :clojure.error/phase)) + +#?(:clj + (defmacro wrapping-errors [env & body] + `(try + ~@body + (catch Throwable err# + (cond + (has-error-data? err#) (throw err#) + (analysis-error? err#) (throw (ex-info nil (error-data ~env :compilation) err#)) + :else (throw (ex-info nil (error-data ~env :compilation) (error ~env (.getMessage err#) err#)))))))) + +;; namespaces implicit to the inclusion of cljs.core +(def implicit-nses '#{goog goog.object goog.string goog.array Math String}) + +(defn implicit-import? + #?(:cljs {:tag boolean}) + [env prefix suffix] + (contains? implicit-nses prefix)) + +(declare get-expander) + +(defn confirm-var-exist-warning [env prefix suffix] + (fn [env prefix suffix] + (warning :undeclared-var env + {:prefix prefix + :suffix suffix + :macro-present? (not (nil? (get-expander (symbol (str prefix) (str suffix)) env)))}))) + +(defn lib&sublib + "If a library name has the form foo$bar, return a vector of the library and + the sublibrary property." + [lib] + (if-let [xs (re-matches #"(.*)\$(.*)" (str lib))] + (drop 1 xs) + [lib nil])) + +(defn loaded-js-ns? + "Check if a JavaScript namespace has been loaded. JavaScript vars are + not currently checked." + #?(:cljs {:tag boolean}) + [env prefix] + (when-not (gets @env/*compiler* ::namespaces prefix) + (let [ns (:ns env)] + (or (some? (get (:requires ns) prefix)) + (some? (get (:imports ns) prefix)))))) + +(defn- internal-js-module-exists? + [js-module-index module] + ;; we need to check both keys and values of the JS module index, because + ;; macroexpansion will be looking for the provided name - António Monteiro + (contains? + (into #{} + (mapcat (fn [[k v]] + [k (:name v)])) + js-module-index) + (str module))) + +(def js-module-exists?* (memoize internal-js-module-exists?)) + +(defn js-module-exists? + [module] + (js-module-exists?* (get-in @env/*compiler* [:js-module-index]) module)) + +(defn node-module-dep? + #?(:cljs {:tag boolean}) + [module] + #?(:clj (let [idx (get @env/*compiler* :node-module-index)] + (contains? idx (str (-> module lib&sublib first)))) + :cljs (try + (and (= *target* "nodejs") + (boolean + (or (js/require.resolve (str module)) + (js/require.resolve (-> module lib&sublib first))))) + (catch :default _ + false)))) + +(defn dep-has-global-exports? + [module] + (let [[module _] (lib&sublib module) + global-exports (get-in @env/*compiler* [:js-dependency-index (str module) :global-exports])] + (or (contains? global-exports (symbol module)) + (contains? global-exports (name module))))) + +(defn goog-module-dep? + [module] + (let [[module _] (lib&sublib module) + module-str (str module) + options (compiler-options)] + ;; CLJS-3330: flag for loading some old things in the old way to give time + ;; for library authors to migrate + (if (and (:global-goog-object&array options) + (#{"goog.object" "goog.array"} module-str)) + false + (= :goog (get-in @env/*compiler* [:js-dependency-index module-str :module]))))) + +(defn confirm-var-exists + ([env prefix suffix] + (let [warn (confirm-var-exist-warning env prefix suffix)] + (confirm-var-exists env prefix suffix warn))) + ([env prefix suffix missing-fn] + (let [sufstr (str suffix) + suffix-str (if (and #?(:clj (not= ".." sufstr) + :cljs (not (identical? ".." sufstr))) ;; leave cljs.core$macros/.. alone + #?(:clj (re-find #"\." sufstr) + :cljs ^boolean (.test #"\." sufstr))) + (first (string/split sufstr #"\.")) + suffix) + suffix (symbol suffix-str)] + (when (and (not (implicit-import? env prefix suffix)) + (not (loaded-js-ns? env prefix)) + (not (and (= 'cljs.core prefix) (= 'unquote suffix))) + (nil? (gets @env/*compiler* ::namespaces prefix :defs suffix)) + (not (js-module-exists? prefix))) + (missing-fn env prefix suffix))))) + +(defn confirm-var-exists-throw [] + (fn [env prefix suffix] + (confirm-var-exists env prefix suffix + (fn [env prefix suffix] + (throw (error env (str "Unable to resolve var: " suffix " in this context"))))))) + +(defn resolve-ns-alias + ([env name] + (resolve-ns-alias env name (symbol name))) + ([env name not-found] + (let [sym (symbol name) + {:keys [requires as-aliases]} (:ns env)] + (or (get requires sym) + (get as-aliases sym) + not-found)))) + +(defn resolve-macro-ns-alias + ([env name] + (resolve-macro-ns-alias env name (symbol name))) + ([env name not-found] + (let [sym (symbol name)] + (get (:require-macros (:ns env)) sym not-found)))) + +(defn confirm-ns + "Given env, an analysis environment, and ns-sym, a symbol identifying a + namespace, confirm that the namespace exists. Warn if not found." + [env ns-sym] + (when (and (not= 'cljs.core ns-sym) + (nil? (get implicit-nses ns-sym)) + (nil? (get (-> env :ns :requires) ns-sym)) + ;; something else may have loaded the namespace, i.e. load-file + (nil? (gets @env/*compiler* ::namespaces ns-sym)) + ;; macros may refer to namespaces never explicitly required + ;; confirm that the library at least exists + #?(:clj (nil? (util/ns->source ns-sym))) + (not (js-module-exists? ns-sym))) + (warning :undeclared-ns env {:ns-sym ns-sym :js-provide ns-sym}))) + +(defn core-name? + "Is sym visible from core in the current compilation namespace?" + #?(:cljs {:tag boolean}) + [env sym] + (and (or (some? (gets @env/*compiler* ::namespaces 'cljs.core :defs sym)) + (if-some [mac (get-expander sym env)] + (let [^Namespace ns (-> mac meta :ns)] + (= (.getName ns) #?(:clj 'cljs.core :cljs 'cljs.core$macros))) + false)) + (not (contains? (-> env :ns :excludes) sym)))) + +(defn public-name? + "Is sym public?" + #?(:cljs {:tag boolean}) + [ns sym] + (let [var-ast (or (gets @env/*compiler* ::namespaces ns :defs sym) + #?(:clj (gets @env/*compiler* ::namespaces ns :macros sym) + :cljs (gets @env/*compiler* ::namespaces (symbol (str (name ns) "$macros")) :defs sym)))] + (and (some? var-ast) + (not (or (:private var-ast) + (:anonymous var-ast)))))) + +(defn js-tag? [x] + (and (symbol? x) + (or (= 'js x) + (= "js" (namespace x))))) + +(defn ->pre [x] + (->> (string/split (name x) #"\.") (map symbol))) + +(defn normalize-js-tag [x] + ;; if not 'js, assume constructor + (if-not (= 'js x) + (let [props (->pre x) + [xs y] ((juxt butlast last) props)] + (with-meta 'js + {:prefix (vec (concat xs [(with-meta y {:ctor true})]))})) + x)) + +(defn ->type-set + "Ensures that a type tag is a set." + [t] + (if #?(:clj (set? t) + :cljs (impl/cljs-set? t)) + t + #{t})) + +(defn canonicalize-type [t] + "Ensures that a type tag is either nil, a type symbol, or a non-singleton + set of type symbols, absorbing clj-nil into seq and all types into any." + (cond + (symbol? t) t + (empty? t) nil + (== 1 (count t)) (first t) + (contains? t 'any) 'any + (contains? t 'seq) (let [res (disj t 'clj-nil)] + (if (== 1 (count res)) + 'seq + res)) + :else t)) + +(defn add-types + "Produces a union of types." + ([] 'any) + ([t1] t1) + ([t1 t2] + (if (or (nil? t1) + (nil? t2)) + 'any + (-> (set/union (->type-set t1) (->type-set t2)) + canonicalize-type))) + ([t1 t2 & ts] + (apply add-types (add-types t1 t2) ts))) + +(def alias->type + '{object Object + string String + number Number + array Array + function Function + boolean Boolean + symbol Symbol}) + +(defn resolve-extern + "Given a foreign js property list, return a resolved js property list and the + extern var info" + ([pre] + (resolve-extern pre (get-externs))) + ([pre externs] + (resolve-extern pre externs externs {:resolved []})) + ([pre externs top ret] + (cond + (empty? pre) ret + :else + (let [x (first pre) + me (find externs x)] + (cond + (not me) nil + :else + (let [[x' externs'] me + info' (meta x') + ret (cond-> ret + ;; we only care about var info for the last property + ;; also if we already added it, don't override it + ;; because we're now resolving type information + ;; not instance information anymore + ;; i.e. [console] -> [Console] but :tag is Console _not_ Function vs. + ;; [console log] -> [Console prototype log] where :tag is Function + (and (empty? (next pre)) +x (not (contains? ret :info))) + (assoc :info info'))] + ;; handle actual occurrences of types, i.e. `Console` + (if (and (or (:ctor info') (:iface info')) (= 'Function (:tag info'))) + (or + ;; then check for "static" property + (resolve-extern (next pre) externs' top + (update ret :resolved conj x)) + + ;; first look for a property on the prototype + (resolve-extern (into '[prototype] (next pre)) externs' top + (update ret :resolved conj x)) + + ;; finally check the super class if there is one + (when-let [super (:super info')] + (resolve-extern (into [super] (next pre)) externs top + (assoc ret :resolved [])))) + + (or + ;; If the tag of the property isn't Function or undefined, + ;; try to resolve it similar to the super case above, + ;; this handles singleton cases like `console` + (let [tag (:tag info')] + (when (and tag (not (contains? '#{Function undefined} tag))) + ;; check prefix first, during cljs.externs parsing we always generate prefixes + ;; for tags because of types like webCrypto.Crypto + (resolve-extern (into (or (-> tag meta :prefix) [tag]) (next pre)) externs top + (assoc ret :resolved [])))) + + ;; assume static property + (recur (next pre) externs' top + (update ret :resolved conj x)))))))))) + +(defn normalize-unresolved-prefix + [pre] + (cond-> pre + (< 1 (count pre)) + (cond-> + (-> pre pop peek meta :ctor) + (-> pop + (conj 'prototype) + (conj (peek pre)))))) + +(defn has-extern?* + [pre externs] + (boolean (resolve-extern pre externs))) + +(defn has-extern? + ([pre] + (has-extern? pre (get-externs))) + ([pre externs] + (or (has-extern?* pre externs) + (-> (last pre) str (string/starts-with? "cljs$"))))) + +(defn lift-tag-to-js [tag] + (symbol "js" (str (alias->type tag tag)))) + +(defn js-tag + ([pre] + (js-tag pre :tag)) + ([pre tag-type] + (js-tag pre tag-type (get-externs))) + ([pre tag-type externs] + (js-tag pre tag-type externs externs)) + ([pre tag-type externs top] + (when-let [tag (get-in (resolve-extern pre externs) [:info tag-type])] + (case tag + ;; don't lift these, analyze-dot will raise them for analysis + ;; representing these types as js/Foo is a hassle as it widens the + ;; return types unnecessarily i.e. #{boolean js/Boolean} + (boolean number string) tag + (lift-tag-to-js tag))))) + +(defn dotted-symbol? [sym] + (let [s (str sym)] + #?(:clj (and (.contains s ".") + (not (.contains s ".."))) + :cljs (and ^boolean (goog.string/contains s ".") + (not ^boolean (goog.string/contains s "..")))))) + +(defn munge-node-lib [name] + (str "node$module$" (munge (string/replace (str name) #"[.\/]" #?(:clj "\\$" + :cljs "$$"))))) + +(defn munge-goog-module-lib + ([name] + (str "goog$module$" (munge (string/replace (str name) #"[.\/]" #?(:clj "\\$" :cljs "$$"))))) + ([ns name] + (str (munge ns) "." (munge-goog-module-lib name)))) + +(defn munge-global-export [name] + (str "global$module$" (munge (string/replace (str name) #"[.\/]" #?(:clj "\\$" + :cljs "$$"))))) + +(defn resolve-alias + "Takes a namespace and an unqualified symbol and potentially returns a new + symbol to be used in lieu of the original." + [ns sym] + ;; Conditionally alias aget/aset fns to checked variants + (if (and (= 'cljs.core ns) + ('#{aget aset} sym) + (checked-arrays)) + (get-in '{:warn {aget checked-aget + aset checked-aset} + :error {aget checked-aget' + aset checked-aset'}} + [(checked-arrays) sym]) + sym)) + +(defn ns->module-type [ns] + (cond + (goog-module-dep? ns) :goog-module + (js-module-exists? ns) :js + (node-module-dep? ns) :node + (dep-has-global-exports? ns) :global)) + +(defmulti resolve* (fn [env sym full-ns current-ns] (ns->module-type full-ns))) + +(defmethod resolve* :js + [env sym full-ns current-ns] + {:name (symbol (str full-ns) (str (name sym))) + :op :js-var + :ns full-ns}) + +(defn extern-pre [sym current-ns] + (let [pre (into '[Object] (->> (string/split (name sym) #"\.") (map symbol) vec))] + (when-not (has-extern? pre) + (swap! env/*compiler* update-in + (into [::namespaces current-ns :externs] pre) merge {})) + pre)) + +(defn node-like? + ([] + (node-like? (compiler-options))) + ([opts] + (and (= :nodejs (:target opts)) + (false? (:nodejs-rt opts))))) + +(defmethod resolve* :node + [env sym full-ns current-ns] + ;; not actually targeting Node.js, we need to generate externs + (if (node-like?) + (let [pre (extern-pre sym current-ns)] + {:ns current-ns + :name (symbol (str current-ns) (str (munge-node-lib full-ns) "." (name sym))) + :op :js-var + :tag (with-meta 'js {:prefix pre}) + :foreign true}) + {:ns current-ns + :name (symbol (str current-ns) (str (munge-node-lib full-ns) "." (name sym))) + :op :js-var + :foreign true})) + +(defmethod resolve* :goog-module + [env sym full-ns current-ns] + (let [sym-ast (gets @env/*compiler* ::namespaces full-ns :defs (symbol (name sym)))] + (merge sym-ast + {:name (symbol (str current-ns) (str (munge-goog-module-lib full-ns) "." (name sym))) + :ns current-ns + :op :var + :unaliased-name (symbol (str full-ns) (name sym))}))) + +(defmethod resolve* :global + [env sym full-ns current-ns] + (let [pre (extern-pre sym current-ns)] + {:ns current-ns + :name (symbol (str current-ns) (str (munge-global-export full-ns) "." (name sym))) + :op :js-var + :tag (with-meta 'js {:prefix pre}) + :foreign true})) + +(def ^:private private-var-access-exceptions + "Specially-treated symbols for which we don't trigger :private-var-access warnings." + '#{cljs.core/checked-aget + cljs.core/checked-aset + cljs.core/checked-aget' + cljs.core/checked-aset'}) + +(defmethod resolve* :default + [env sym full-ns current-ns] + (let [sym-ast (gets @env/*compiler* ::namespaces full-ns :defs (symbol (name sym))) + sym-name (symbol (str full-ns) (str (name sym)))] + (when (and (not= current-ns full-ns) + (:private sym-ast) + (not *private-var-access-nowarn*) + (not (contains? private-var-access-exceptions sym-name))) + (warning :private-var-access env + {:sym sym-name})) + (merge sym-ast + {:name sym-name + :op :var + :ns full-ns}))) + +(defn required? [ns env] + (or (contains? (set (vals (gets env :ns :requires))) ns) + (contains? (set (vals (gets env :ns :uses))) ns))) + +(defn invokeable-ns? + "Returns true if ns is a required namespace and a JavaScript module that + might be invokeable as a function." + [ns env] + (let [ns (resolve-ns-alias env ns)] + (and (required? ns env) + (or (js-module-exists? ns) + (node-module-dep? ns) + (dep-has-global-exports? ns))))) + +(defn resolve-invokeable-ns [ns current-ns env] + (let [ns (resolve-ns-alias env ns) + module-type (ns->module-type ns)] + (case module-type + :js {:name (symbol + (or (gets @env/*compiler* :js-module-index ns :name) + (resolve-ns-alias env ns))) + :op :js-var + :ns 'js} + :node {:name (symbol (str current-ns) + (munge-node-lib (resolve-ns-alias env ns))) + :op :js-var + :ns current-ns + :tag 'js} + :global {:name (symbol (str current-ns) + (munge-global-export (resolve-ns-alias env ns))) + :op :js-var + :ns current-ns + :tag 'js}))) + +(defn resolve-import + "goog.modules are deterministically assigned to a property of the namespace, + we cannot expect the reference will be globally available, so we resolve to + namespace local reference." + [env import] + (if (goog-module-dep? import) + (symbol (munge-goog-module-lib (-> env :ns :name) import)) + import)) + +;; core.async calls `macroexpand-1` manually with an ill-formed +;; :locals map. Normally :locals maps symbols maps, but +;; core.async adds entries mapping symbols to symbols. We work +;; around that specific case here. This is called defensively +;; every time we lookup the :locals map. +(defn handle-symbol-local [sym lb] + (if (symbol? lb) + {:name sym} + lb)) + +(defn qualified->dotted + [sym] + (symbol (str (namespace sym) "." (name sym)))) + +(defn resolve-var + "Resolve a var. Accepts a side-effecting confirm fn for producing + warnings about unresolved vars." + ([env sym] + (resolve-var env sym nil)) + ([env sym confirm] + (resolve-var env sym confirm true)) + ([env sym confirm default?] + (let [locals (:locals env)] + (if #?(:clj (= "js" (namespace sym)) + :cljs (identical? "js" (namespace sym))) + (let [symn (-> sym name symbol) + shadowed-by-local (handle-symbol-local symn (get locals symn))] + (cond + (some? shadowed-by-local) + (do (warning :js-shadowed-by-local env {:name sym}) + (assoc shadowed-by-local :op :local)) + + :else + (let [pre (->> (string/split (name sym) #"\.") (map symbol) vec) + res (resolve-extern (->> (string/split (name sym) #"\.") (map symbol) vec))] + (when (and (not res) + ;; ignore exists? usage + (not (-> sym meta ::no-resolve))) + (swap! env/*compiler* update-in + (into [::namespaces (-> env :ns :name) :externs] pre) merge {})) + (merge + {:name sym + :op :js-var + :ns 'js + :tag (with-meta (or (js-tag pre) (:tag (meta sym)) 'js) + {:prefix pre + :ctor (-> res :info :ctor)})} + (when-let [ret-tag (js-tag pre :ret-tag)] + {:js-fn-var true + :ret-tag ret-tag}))))) + (let [s (str sym) + lb (handle-symbol-local sym (get locals sym)) + current-ns (-> env :ns :name)] + (cond + (some? lb) (assoc lb :op :local) + + (some? (namespace sym)) + (let [ns (namespace sym)] + (if-let [resolved (and (nil? (resolve-ns-alias env ns nil)) + (not (dotted-symbol? ns)) + (resolve-var env (symbol ns) nil false) + (resolve-var env (qualified->dotted sym) nil false))] + resolved + (let [ns (if #?(:clj (= "clojure.core" ns) + :cljs (identical? "clojure.core" ns)) + "cljs.core" + ns) + full-ns (resolve-ns-alias env ns + (or (and (js-module-exists? ns) + (gets @env/*compiler* :js-module-index ns :name)) + (symbol ns)))] + (when (some? confirm) + (when (not= current-ns full-ns) + (confirm-ns env full-ns)) + (confirm env full-ns (symbol (name sym)))) + (resolve* env sym full-ns current-ns)))) + + (dotted-symbol? sym) + (let [idx (.indexOf s ".") + prefix (symbol (subs s 0 idx)) + suffix (subs s (inc idx))] + ;; check if prefix is some existing def + (if-let [resolved (resolve-var env prefix nil false)] + (update resolved :name #(symbol (str % "." suffix))) + ;; glib imports (i.e. (:import [goog.module ModuleLoader]) + ;; are always just dotted symbols after the recursion + (let [s (str + (cond->> s + (goog-module-dep? sym) + (resolve-import env))) + idx (.lastIndexOf (str s) ".") + pre (subs s 0 idx) + suf (subs s (inc idx))] + {:op :var + :name (symbol pre suf) + :ns (symbol pre)}))) + + (some? (gets @env/*compiler* ::namespaces current-ns :uses sym)) + (let [full-ns (gets @env/*compiler* ::namespaces current-ns :uses sym)] + (resolve* env sym full-ns current-ns)) + + (some? (gets @env/*compiler* ::namespaces current-ns :renames sym)) + (let [qualified-symbol (gets @env/*compiler* ::namespaces current-ns :renames sym) + full-ns (symbol (namespace qualified-symbol)) + sym (symbol (name qualified-symbol))] + (resolve* env sym full-ns current-ns)) + + (some? (gets @env/*compiler* ::namespaces current-ns :imports sym)) + (recur env (gets @env/*compiler* ::namespaces current-ns :imports sym) confirm default?) + + (some? (gets @env/*compiler* ::namespaces current-ns :defs sym)) + (do + (when (some? confirm) + (confirm env current-ns sym)) + (merge (gets @env/*compiler* ::namespaces current-ns :defs sym) + {:name (symbol (str current-ns) (str sym)) + :op :var + :ns current-ns})) + + (core-name? env sym) + (let [sym (resolve-alias 'cljs.core sym)] + (when (some? confirm) + (confirm env 'cljs.core sym)) + (merge (gets @env/*compiler* ::namespaces 'cljs.core :defs sym) + {:name (symbol "cljs.core" (str sym)) + :op :var + :ns 'cljs.core})) + + (invokeable-ns? s env) + (resolve-invokeable-ns s current-ns env) + + :else + (when default? + (when (some? confirm) + (confirm env current-ns sym)) + (merge (gets @env/*compiler* ::namespaces current-ns :defs sym) + {:name (symbol (str current-ns) (str sym)) + :op :var + :ns current-ns})))))))) + +(defn resolve-existing-var + "Given env, an analysis environment, and sym, a symbol, resolve an existing var. + Emits a warning if no such var exists." + [env sym] + (if-not (-> sym meta ::no-resolve) + (resolve-var env sym confirm-var-exists) + (resolve-var env sym))) + +(defn confirm-bindings + "Given env, an analysis environment env, and names, a list of symbols, confirm + that all correspond to declared dynamic vars." + [env names] + (doseq [name names] + (let [env (assoc env :ns (get-namespace *cljs-ns*)) + ev (resolve-existing-var env name)] + (when (and ev (not (-> ev :dynamic))) + (warning :dynamic env {:ev ev :name (:name ev)}))))) + +(defn resolve-macro-var + "Given env, an analysis environment, and sym, a symbol, resolve a macro." + [env sym] + (let [ns (-> env :ns :name) + namespaces (get @env/*compiler* ::namespaces)] + (cond + (some? (namespace sym)) + (let [ns (namespace sym) + ns (if (= "clojure.core" ns) "cljs.core" ns) + full-ns (resolve-macro-ns-alias env ns) + #?@(:cljs [full-ns (if-not (string/ends-with? (str full-ns) "$macros") + (symbol (str full-ns "$macros")) + full-ns)])] + #?(:clj (get-in namespaces [full-ns :macros (symbol (name sym))]) + :cljs (get-in namespaces [full-ns :defs (symbol (name sym))]))) + + (some? (get-in namespaces [ns :use-macros sym])) + (let [full-ns (get-in namespaces [ns :use-macros sym])] + (get-in namespaces [full-ns :macros sym])) + + (some? (get-in namespaces [ns :rename-macros sym])) + (let [qualified-symbol (get-in namespaces [ns :rename-macros sym]) + full-ns (symbol (namespace qualified-symbol)) + sym (symbol (name qualified-symbol))] + (get-in namespaces [full-ns :macros sym])) + + :else + (let [ns (cond + (some? (get-in namespaces [ns :macros sym])) ns + (core-name? env sym) #?(:clj 'cljs.core + :cljs impl/CLJS_CORE_MACROS_SYM))] + (when (some? ns) + #?(:clj (get-in namespaces [ns :macros sym]) + :cljs (get-in namespaces [ns :defs sym]))))))) + +(declare analyze analyze-symbol analyze-seq) + +;; Note: This is the set of parse multimethod dispatch values, +;; along with '&, and differs from cljs.core/special-symbol? +(def specials '#{if def fn* do let* loop* letfn* throw try recur new set! + ns deftype* defrecord* . js* & quote case* var ns*}) + +(def ^:dynamic *recur-frames* nil) +(def ^:dynamic *loop-lets* ()) +(def ^:dynamic *allow-redef* false) +(def ^:dynamic *allow-ns* true) + +#?(:clj + (defmacro disallowing-recur [& body] + `(binding [*recur-frames* (cons nil *recur-frames*)] ~@body))) + +#?(:clj + (defmacro allowing-redef [& body] + `(binding [*allow-redef* true] ~@body))) + +#?(:clj + (defmacro disallowing-ns* [& body] + `(binding [*allow-ns* false] ~@body))) + +;; TODO: move this logic out - David +(defn analyze-keyword + [env sym] + (register-constant! env sym) + {:op :const :val sym :env env :form sym :tag 'cljs.core/Keyword}) + +(defn get-tag [ast] + (if-some [tag (-> ast :form meta :tag)] + tag + (if-some [tag (-> ast :tag)] + tag + (-> ast :info :tag)))) + +(defn find-matching-method [fn-ast params] + ;; if local fn, need to look in :info + (let [methods (or (:methods fn-ast) (-> fn-ast :info :methods)) + c (count params)] + (some + (fn [m] + (and (or (== (:fixed-arity m) c) + (:variadic? m)) + m)) + methods))) + +(defn type? + #?(:cljs {:tag boolean}) + [env t] + ;; don't use resolve-existing-var to avoid warnings + (when (and (some? t) (symbol? t)) + (let [var (binding [*private-var-access-nowarn* true] + (resolve-var env t))] + (if-some [type (:type var)] + type + (if-some [type (-> var :info :type)] + type + (if-some [proto (:protocol-symbol var)] + proto + (get '#{cljs.core/PersistentHashMap cljs.core/List} t))))))) + +(declare infer-tag) + +(defn unwrap-quote [{:keys [op] :as ast}] + (if #?(:clj (= op :quote) + :cljs (keyword-identical? op :quote)) + (:expr ast) + ast)) + +(defn infer-if [env ast] + (let [{:keys [op form]} (unwrap-quote (:test ast)) + then-tag (infer-tag env (:then ast))] + (if (and #?(:clj (= op :const) + :cljs (keyword-identical? op :const)) + (not (nil? form)) + (not (false? form))) + then-tag + (let [else-tag (infer-tag env (:else ast))] + (cond + (or #?(:clj (= then-tag else-tag) + :cljs (symbol-identical? then-tag else-tag)) + #?(:clj (= else-tag impl/IGNORE_SYM) + :cljs (symbol-identical? else-tag impl/IGNORE_SYM))) then-tag + #?(:clj (= then-tag impl/IGNORE_SYM) + :cljs (symbol-identical? then-tag impl/IGNORE_SYM)) else-tag + ;; TODO: temporary until we move not-native -> clj - David + (and (or (some? (get impl/NOT_NATIVE then-tag)) (type? env then-tag)) + (or (some? (get impl/NOT_NATIVE else-tag)) (type? env else-tag))) + 'clj + :else + (if (and (some? (get impl/BOOLEAN_OR_SEQ then-tag)) + (some? (get impl/BOOLEAN_OR_SEQ else-tag))) + 'seq + (let [then-tag (if #?(:clj (set? then-tag) + :cljs (impl/cljs-set? then-tag)) + then-tag #{then-tag}) + else-tag (if #?(:clj (set? else-tag) + :cljs (impl/cljs-set? else-tag)) + else-tag #{else-tag})] + (into then-tag else-tag)))))))) + +(defn js-var? [ast] + (= :js-var (:op ast))) + +(defn js-var-fn? [fn-ast] + (js-var? (:info fn-ast))) + +(defn fn-ast->tag + [{:keys [info] :as fn-ast}] + (cond + ;; ClojureScript Fn + (:fn-var info) (:ret-tag info) + ;; Global foreign JS Fn inferred via externs + (:js-fn-var info) (:ret-tag info) + ;; Node foreign JS *var*, we cannot distinguish between properties + ;; and functions from such libs at this time, we cannot possibly + ;; know the returns so break the leading prefix (start with raw 'js tag) + (js-var-fn? fn-ast) 'js + :else (when (= 'js (:ns info)) 'js))) + +(defn infer-invoke [env {fn-ast :fn :keys [args] :as ast}] + (let [me (assoc (find-matching-method fn-ast args) :op :fn-method)] + (if-some [ret-tag (infer-tag env me)] + ret-tag + (let [] + (if-some [ret-tag (fn-ast->tag fn-ast)] + ret-tag + impl/ANY_SYM))))) + +(defn infer-tag + "Given env, an analysis environment, and e, an AST node, return the inferred + type of the node" + [env ast] + (if-some [tag (get-tag ast)] + tag + (case (:op ast) + :recur impl/IGNORE_SYM + :throw impl/IGNORE_SYM + :let (infer-tag env (:body ast)) + :loop (infer-tag env (:body ast)) + :try (infer-tag env (:body ast)) + :do (infer-tag env (:ret ast)) + :fn-method (infer-tag env (:body ast)) + :def (infer-tag env (:init ast)) + :invoke (infer-invoke env ast) + :if (infer-if env ast) + :const (case (:form ast) + true impl/BOOLEAN_SYM + false impl/BOOLEAN_SYM + impl/ANY_SYM) + :quote (infer-tag env (:expr ast)) + (:var :local :js-var :binding) + (if-some [init (:init ast)] + (infer-tag env init) + (infer-tag env (:info ast))) + (:host-field :host-call) + impl/ANY_SYM + :js impl/ANY_SYM + nil))) + +(defmulti parse (fn [op & rest] op)) + +(defn var-meta + ([var] + (var-meta var nil)) + ([var expr-env] + (let [sym (:name var) + ks [:ns :doc :file :line :column] + m (merge + (let [user-meta (:meta var) + uks (keys user-meta)] + (zipmap uks + (map #(list 'quote (get user-meta %)) uks))) + (assoc (zipmap ks (map #(list 'quote (get var %)) ks)) + :name `(quote ~(symbol (name (:name var)))) + :test `(when ~sym (.-cljs$lang$test ~sym)) + :arglists (let [arglists (:arglists var) + arglists' (if (= 'quote (first arglists)) + (second arglists) + arglists)] + (list 'quote + (doall (map with-meta arglists' + (:arglists-meta var)))))))] + (if expr-env + (analyze expr-env m) + m)))) + +(defn var-ast + [env sym] + ;; we need to dissoc locals for the `(let [x 1] (def x x))` case, because we + ;; want the var's AST and `resolve-var` will check locals first. - António Monteiro + (binding [*private-var-access-nowarn* true] + (let [env (dissoc env :locals) + var (resolve-var env sym (confirm-var-exists-throw)) + expr-env (assoc env :context :expr)] + (when-some [var-ns (:ns var)] + {:var (analyze expr-env sym) + :sym (analyze expr-env `(quote ~(symbol (name var-ns) (name (:name var))))) + :meta (var-meta var expr-env)})))) + +(defmethod parse 'var + [op env [_ sym :as form] _ _] + (when (not= 2 (count form)) + (throw (error env "Wrong number of args to var"))) + (when-not (symbol? sym) + (throw (error env "Argument to var must be symbol"))) + (merge + {:env env + :op :the-var + :children [:var :sym :meta] + :form form} + (var-ast env sym))) + +(def ^:private predicate->tag + '{ + ;; Base values + cljs.core/nil? clj-nil + cljs.core/undefined? clj-nil + cljs.core/false? boolean + cljs.core/true? boolean + cljs.core/zero? number + cljs.core/infinite? number + + ;; Base types + cljs.core/boolean? boolean + cljs.core/string? string + cljs.core/char? string + cljs.core/number? number + cljs.core/integer? number + cljs.core/float? number + cljs.core/double? number + cljs.core/array? array + cljs.core/seq? seq + + ;; JavaScript types + cljs.core/regexp? js/RegExp + + ;; Types + cljs.core/keyword? cljs.core/Keyword + cljs.core/var? cljs.core/Var + cljs.core/symbol? cljs.core/Symbol + cljs.core/volatile? cljs.core/Volatile + cljs.core/delay? cljs.core/Delay + cljs.core/reduced? cljs.core/Reduced + + ;; Subtypes + cljs.core/simple-keyword? cljs.core/Keyword + cljs.core/qualified-keyword? cljs.core/Keyword + cljs.core/simple-symbol? cljs.core/Symbol + cljs.core/qualified-symbol? cljs.core/Symbol + + ;;; Note: For non-marker protocol entries below, we + ;;; omit predicates that are based on satisfies? because + ;;; we cannot safely apply the fast-path optimization + ;;; which is enabled when the protocol type is inferred. + ;;; If adding a non-marker entry here, also add a test to + ;;; cljs.extend-to-native-test/test-extend-to-protocols. + + ;; Protocols + cljs.core/map-entry? cljs.core/IMapEntry + cljs.core/uuid? cljs.core/IUUID + cljs.core/tagged-literal? cljs.core/ITaggedLiteral + cljs.core/inst? cljs.core/Inst + cljs.core/sequential? cljs.core/ISequential + cljs.core/list? cljs.core/IList + cljs.core/record? cljs.core/IRecord + cljs.core/chunked-seq? cljs.core/IChunkedSeq + + ;; Composites + cljs.core/seqable? #{cljs.core/ISeqable array string} + cljs.core/ident? #{cljs.core/Keyword cljs.core/Symbol} + + ;; Composite subtypes + cljs.core/simple-ident? #{cljs.core/Keyword cljs.core/Symbol} + cljs.core/qualified-ident? #{cljs.core/Keyword cljs.core/Symbol} + }) + +(defn- simple-predicate-induced-tag + "Look for a predicate-induced tag when the test expression is a simple + application of a predicate to a local, as in (string? x)." + [env test] + (when (and (list? test) + (== 2 (count test)) + (every? symbol? test)) + (let [analyzed-fn (no-warn (analyze (assoc env :context :expr) (first test)))] + (when (= :var (:op analyzed-fn)) + (when-let [tag (predicate->tag (:name analyzed-fn))] + (let [sym (last test)] + (when (and (nil? (namespace sym)) + (get-in env [:locals sym])) + [sym tag]))))))) + +(declare specials) + +(defn- type-check-induced-tag + "Look for a type-check-induced tag when the test expression is the use of + instance? on a local, as in (instance? UUID x) or implements? on a local, as + in (implements? ICounted x)." + [env test] + (when (and (list? test) + (== 3 (count test)) + (every? symbol? test) + (not (contains? specials (first test)))) + (let [analyzed-fn (no-warn (analyze (assoc env :context :expr) (first test)))] + (when (= :var (:op analyzed-fn)) + (when ('#{cljs.core/instance? cljs.core/implements?} (:name analyzed-fn)) + (let [analyzed-type (no-warn (analyze (assoc env :context :expr) (second test))) + tag (:name analyzed-type) + sym (last test)] + (when (and (= :var (:op analyzed-type)) + (nil? (namespace sym)) + (get-in env [:locals sym])) + [sym tag]))))))) + +(defn- truth-induced-tag + "Refine a tag to exclude clj-nil if the test is a local." + [env test] + (when (and (symbol? test) + (nil? (namespace test)) + (get-in env [:locals test])) + (let [analyzed-symbol (no-warn (analyze (assoc env :context :expr) test))] + (when-let [tag (:tag analyzed-symbol)] + (when (and (set? tag) + (contains? tag 'clj-nil)) + [test (canonicalize-type (disj tag 'clj-nil))]))))) + +(defn- set-test-induced-tags + "Looks at the test and sets any tags which are induced by virtue + of the test being truthy. For example in (if (string? x) x :bar) + the local x in the then branch must be of string type." + [env test] + (let [[local tag] (or (simple-predicate-induced-tag env test) + (type-check-induced-tag env test) + (truth-induced-tag env test))] + (cond-> env + local (assoc-in [:locals local :tag] tag)))) + +(defmethod parse 'if + [op env [_ test then else :as form] name _] + (when (< (count form) 3) + (throw (compile-syntax-error env "Too few arguments to if" 'if))) + (when (> (count form) 4) + (throw (compile-syntax-error env "Too many arguments to if" 'if))) + (let [test-expr (disallowing-recur (analyze (assoc env :context :expr) test)) + then-expr (allowing-redef (analyze (set-test-induced-tags env test) then)) + else-expr (allowing-redef (analyze env else))] + {:env env :op :if :form form + :test test-expr :then then-expr :else else-expr + :unchecked *unchecked-if* + :children [:test :then :else]})) + +(defmethod parse 'case* + [op env [_ sym tests thens default :as form] name _] + (assert (symbol? sym) "case* must switch on symbol") + (assert (every? vector? tests) "case* tests must be grouped in vectors") + (let [expr-env (assoc env :context :expr) + v (disallowing-recur (analyze expr-env sym)) + tests (mapv #(mapv (fn [t] (analyze expr-env t)) %) tests) + thens (mapv #(analyze env %) thens) + nodes (mapv (fn [tests then] + {:op :case-node + ;synthetic node, no :form + :env env + :tests (mapv (fn [test] + {:op :case-test + :form (:form test) + :env expr-env + :test test + :children [:test]}) + tests) + :then {:op :case-then + :form (:form then) + :env env + :then then + :children [:then]} + :children [:tests :then]}) + tests + thens) + default (analyze env default)] + (assert (every? (fn [t] + (or + (-> t :info :const) + (and (= :const (:op t)) + ((some-fn number? string? char?) (:form t))))) + (apply concat tests)) + "case* tests must be numbers, strings, or constants") + {:env env :op :case :form form + :test v :nodes nodes :default default + :children [:test :nodes :default]})) + +(defmethod parse 'throw + [op env [_ throw-form :as form] name _] + (cond + (= 1 (count form)) + (throw + (error env "Too few arguments to throw, throw expects a single Error instance")) + (< 2 (count form)) + (throw + (error env "Too many arguments to throw, throw expects a single Error instance"))) + (let [throw-expr (disallowing-recur (analyze (assoc env :context :expr) throw-form))] + {:env env :op :throw :form form + :exception throw-expr + :children [:exception]})) + +(defmethod parse 'try + [op env [_ & body :as form] name _] + (let [catchenv (update-in env [:context] #(if (= :expr %) :return %)) + catch? (every-pred seq? #(= (first %) 'catch)) + default? (every-pred catch? #(= (second %) :default)) + finally? (every-pred seq? #(= (first %) 'finally)) + + {:keys [body cblocks dblock fblock]} + (loop [parser {:state :start :forms body + :body [] :cblocks [] :dblock nil :fblock nil}] + (if (seq? (:forms parser)) + (let [[form & forms*] (:forms parser) + parser* (assoc parser :forms forms*)] + (case (:state parser) + :start (cond + (catch? form) (recur (assoc parser :state :catches)) + (finally? form) (recur (assoc parser :state :finally)) + :else (recur (update-in parser* [:body] conj form))) + :catches (cond + (default? form) (recur (assoc parser* :dblock form :state :finally)) + (catch? form) (recur (update-in parser* [:cblocks] conj form)) + (finally? form) (recur (assoc parser :state :finally)) + :else (throw (error env "Invalid try form"))) + :finally (recur (assoc parser* :fblock form :state :done)) + :done (throw (error env "Unexpected form after finally")))) + parser)) + + finally (when (seq fblock) + (-> (disallowing-recur (analyze (assoc env :context :statement) `(do ~@(rest fblock)))) + (assoc :body? true))) + e (when (or (seq cblocks) dblock) (gensym "e")) + default (if-let [[_ _ name & cb] dblock] + `(cljs.core/let [~name ~e] ~@cb) + `(throw ~e)) + cblock (if (seq cblocks) + `(cljs.core/cond + ~@(mapcat + (fn [[_ type name & cb]] + (when name (assert (not (namespace name)) "Can't qualify symbol in catch")) + `[(cljs.core/instance? ~type ~e) + (cljs.core/let [~name ~e] ~@cb)]) + cblocks) + :else ~default) + default) + locals (:locals catchenv) + locals (if e + (assoc locals e + {:name e + :line (get-line e env) + :column (get-col e env) + ;; :local is required for {:op :local ...} nodes + ;; but previously we had no way to figure this out + ;; for `catch` locals, by adding it here we can recover + ;; it later + :local :catch}) + locals) + catch (when cblock + (disallowing-recur (analyze (assoc catchenv :locals locals) cblock))) + try (disallowing-recur (analyze (if (or e finally) catchenv env) `(do ~@body)))] + + {:env env :op :try :form form + :body (assoc try :body? true) + :finally finally + :name e + :catch catch + :children (vec + (concat [:body] + (when catch + [:catch]) + (when finally + [:finally])))})) + +(defn valid-proto [x] + (when (symbol? x) x)) + +(defn elide-env [env ast opts] + (dissoc ast :env)) + +(defn replace-env-pass [new-env] + (fn [env ast opts] + (assoc ast :env new-env))) + +(defn ast-children [ast] + (mapcat (fn [c] + (let [g (get ast c)] + (cond + (vector? g) g + g [g]))) + (:children ast))) + +(defn constant-value? + [{:keys [op] :as ast}] + (or (#{:quote :const} op) + (and (#{:map :set :vector} op) + (every? constant-value? (ast-children ast))))) + +(defn const-expr->constant-value [{:keys [op] :as e}] + (case op + :quote (const-expr->constant-value (:expr e)) + :const (:val e) + :map (zipmap (map const-expr->constant-value (:keys e)) + (map const-expr->constant-value (:vals e))) + :set (into #{} (map const-expr->constant-value (:items e))) + :vector (into [] (map const-expr->constant-value (:items e))))) + +(defn- earmuffed? [sym] + (let [s (name sym)] + (and (> (count s) 2) + (string/starts-with? s "*") + (string/ends-with? s "*")))) + +(defn- core-ns? [ns-sym] + (let [s (name ns-sym)] + (and (not= 'cljs.user ns-sym) + (or (string/starts-with? s "cljs.") + (string/starts-with? s "clojure."))))) + +(defmethod parse 'def + [op env form _ _] + (when (> (count form) 4) + (throw (error env "Too many arguments to def"))) + (let [pfn (fn + ([_ sym] {:sym sym}) + ([_ sym init] {:sym sym :init init}) + ([_ sym doc init] {:sym sym :doc doc :init init})) + args (apply pfn form) + sym (:sym args) + const? (-> sym meta :const) + sym-meta (meta sym) + tag (-> sym meta :tag) + protocol (-> sym meta :protocol valid-proto) + dynamic (-> sym meta :dynamic) + ns-name (-> env :ns :name) + locals (:locals env) + clash-ns (symbol (str ns-name "." sym)) + sym-ns (namespace sym) + sym (cond + (and sym-ns (not #?(:clj (= (symbol sym-ns) ns-name) + :cljs (symbol-identical? (symbol sym-ns) ns-name)))) + (throw (error env (str "Can't def ns-qualified name in namespace " sym-ns))) + + (some? sym-ns) + (symbol (name sym)) + + :else sym)] + (when (some? (get-in @env/*compiler* [::namespaces clash-ns])) + (warning :ns-var-clash env + {:ns (symbol (str ns-name "." sym)) + :var (symbol (str ns-name) (str sym))})) + (when (some? (:const (resolve-var (dissoc env :locals) sym))) + (throw (error env "Can't redefine a constant"))) + (when-some [doc (:doc args)] + (when-not (string? doc) + (throw (error env "Too many arguments to def")))) + (when (and (not dynamic) + (earmuffed? sym) + (not (core-ns? ns-name))) + (warning :non-dynamic-earmuffed-var env + {:var (str sym)})) + (when-some [v (get-in @env/*compiler* [::namespaces ns-name :defs sym])] + (when (and (not *allow-redef*) + (not (:declared v)) + (not (:declared sym-meta)) + *file-defs* + (get @*file-defs* sym)) + (warning :redef-in-file env {:sym sym :line (:line v)})) + (when (and (:declared v) + (:arglists v) + (not= (:arglists v) (:arglists sym-meta))) + (warning :declared-arglists-mismatch env {:ns-name ns-name :sym sym + :declared (second (:arglists v)) + :defined (second (:arglists sym-meta))}))) + (let [env (if (or (and (not= ns-name 'cljs.core) + (core-name? env sym)) + (some? (get-in @env/*compiler* [::namespaces ns-name :uses sym]))) + (let [ev (resolve-existing-var (dissoc env :locals) + ;; ::no-resolve true is to suppress "can't take value + ;; of macro warning" when sym resolves to a macro + (with-meta sym {::no-resolve true})) + conj-to-set (fnil conj #{})] + (when (public-name? (:ns ev) sym) + (warning :redef env {:sym sym :ns (:ns ev) :ns-name ns-name})) + (swap! env/*compiler* update-in [::namespaces ns-name :excludes] + conj-to-set sym) + (update-in env [:ns :excludes] conj-to-set sym)) + env) + var-name (:name (resolve-var (dissoc env :locals) sym)) + init-expr (when (contains? args :init) + (swap! env/*compiler* assoc-in [::namespaces ns-name :defs sym] + (merge + {:name var-name} + sym-meta + (when (true? dynamic) {:dynamic true}) + (source-info var-name env))) + (disallowing-recur + (disallowing-ns* + (analyze (assoc env :context :expr) (:init args) sym)))) + fn-var? (and (some? init-expr) (= (:op init-expr) :fn)) + tag (cond + fn-var? (or (:ret-tag init-expr) tag (:inferred-ret-tag init-expr)) + tag tag + dynamic impl/ANY_SYM + :else (:tag init-expr)) + export-as (when-let [export-val (-> sym meta :export)] + (if (= true export-val) var-name export-val)) + doc (or (:doc args) (-> sym meta :doc))] + (when-some [v (get-in @env/*compiler* [::namespaces ns-name :defs sym])] + (when (and (not (-> sym meta :declared)) + (and (true? (:fn-var v)) (not fn-var?))) + (warning :fn-var env {:ns-name ns-name :sym sym}))) + + ;; declare must not replace any analyzer data of an already def'd sym + (when (or (nil? (get-in @env/*compiler* [::namespaces ns-name :defs sym])) + (not (:declared sym-meta))) + (when *file-defs* + (swap! *file-defs* conj sym)) + + (swap! env/*compiler* assoc-in [::namespaces ns-name :defs sym] + (merge + {:name var-name} + ;; remove actual test metadata, as it includes non-valid EDN and + ;; cannot be present in analysis cached to disk - David + (cond-> sym-meta + (:test sym-meta) (assoc :test true)) + {:meta (-> sym-meta + (dissoc :test) + (update-in [:file] + (fn [f] + (if (= (-> env :ns :name) 'cljs.core) + "cljs/core.cljs" + f))))} + (when doc {:doc doc}) + (when const? + (let [const-expr + (binding [*passes* (conj *passes* (replace-env-pass {:context :expr}))] + (analyze env (:init args)))] + (when (constant-value? const-expr) + {:const-expr const-expr}))) + (when (true? dynamic) {:dynamic true}) + (source-info var-name env) + ;; the protocol a protocol fn belongs to + (when protocol + {:protocol protocol}) + ;; symbol for reified protocol + (when-let [protocol-symbol (-> sym meta :protocol-symbol)] + {:protocol-symbol protocol-symbol + :info (-> protocol-symbol meta :protocol-info) + :impls #{}}) + (when fn-var? + (let [params (map #(vec (map :name (:params %))) (:methods init-expr))] + (merge + {:fn-var (not (:macro sym-meta)) + ;; protocol implementation context + :protocol-impl (:protocol-impl init-expr) + ;; inline protocol implementation context + :protocol-inline (:protocol-inline init-expr)} + (if-some [top-fn-meta (:top-fn sym-meta)] + top-fn-meta + {:variadic? (:variadic? init-expr) + :max-fixed-arity (:max-fixed-arity init-expr) + :method-params params + :arglists (:arglists sym-meta) + :arglists-meta (doall (map meta (:arglists sym-meta)))})))) + (when (and (:declared sym-meta) + (:arglists sym-meta)) + {:declared true + :fn-var true + :method-params (second (:arglists sym-meta))}) + (if (and fn-var? (some? tag)) + {:ret-tag tag} + (when tag {:tag tag}))))) + (merge + {:env env + :op :def + :form form + :ns ns-name + :name var-name + :var (assoc + (analyze + (-> env (dissoc :locals) + (assoc :context :expr) + (assoc :def-var true)) + sym) + :op :var) + :doc doc + :jsdoc (:jsdoc sym-meta)} + (when-let [goog-type (:goog-define sym-meta)] + {:goog-define goog-type}) + (when (true? (:def-emits-var env)) + {:var-ast (var-ast env sym)}) + (when-some [test (:test sym-meta)] + {:test (analyze (assoc env :context :expr) test)}) + (when (some? tag) + (if fn-var? + {:ret-tag tag} + {:tag tag})) + (when (true? dynamic) {:dynamic true}) + (when (some? export-as) {:export export-as}) + (if (some? init-expr) + {:init init-expr + :children [:var :init]} + {:children [:var]}))))) + +(defn analyze-fn-method-param [env] + (fn [[locals params] [arg-id name]] + (when (namespace name) + (throw (error env (str "Can't use qualified name as parameter: " name)))) + (let [line (get-line name env) + column (get-col name env) + nmeta (meta name) + tag (:tag nmeta) + shadow (when (some? locals) + (handle-symbol-local name (locals name))) + env (merge (select-keys env [:context]) + {:line line :column column}) + param {:op :binding + :name name + :form name + :line line + :column column + :tag tag + :shadow shadow + :local :arg + :arg-id arg-id + ;; Give the fn params the same shape + ;; as a :var, so it gets routed + ;; correctly in the compiler + :env env + :info {:name name :shadow shadow} + :binding-form? true}] + [(assoc locals name param) (conj params param)]))) + +(defn analyze-fn-method-body [env form recur-frames] + (binding [*recur-frames* recur-frames] + (analyze env form))) + +(defn- analyze-fn-method [env locals form type analyze-body?] + (let [param-names (first form) + variadic (boolean (some '#{&} param-names)) + param-names (vec (remove '#{&} param-names)) + body (next form) + step (analyze-fn-method-param env) + step-init [locals []] + [locals params] (reduce step step-init (map-indexed vector param-names)) + params' (if (true? variadic) + (butlast params) + params) + fixed-arity (count params') + recur-frame {:protocol-impl (:protocol-impl env) + :params params + :flag (atom nil) + :tags (atom [])} + recur-frames (cons recur-frame *recur-frames*) + body-env (assoc env :context :return :locals locals) + body-form `(do ~@body) + expr (when analyze-body? + (analyze-fn-method-body body-env body-form recur-frames)) + recurs @(:flag recur-frame)] + (merge + {:env env + :op :fn-method + :variadic? variadic + :params params + :fixed-arity fixed-arity + :type type + :form form + :recurs recurs} + (if (some? expr) + {:body (assoc expr :body? true) + :children [:params :body]} + {:children [:params]})))) + +(declare analyze-wrap-meta) + +(defn fn-name-var [env locals name] + (when (some? name) + (let [ns (-> env :ns :name) + shadow (or (handle-symbol-local name (get locals name)) + (get-in env [:js-globals name])) + fn-scope (:fn-scope env) + name-var {:op :binding + :env env + :form name + :name name + :local :fn + :info {:fn-self-name true + :fn-scope fn-scope + :ns ns + :shadow shadow}} + tag (-> name meta :tag) + ret-tag (when (some? tag) + {:ret-tag tag})] + (merge name-var ret-tag)))) + +(defn analyze-fn-methods-pass2* [menv locals type meths] + (mapv #(analyze-fn-method menv locals % type true) meths)) + +(defn analyze-fn-methods-pass2 [menv locals type meths] + (analyze-fn-methods-pass2* menv locals type meths)) + +(defmethod parse 'fn* + [op env [_ & args :as form] name _] + (let [named-fn? (symbol? (first args)) + [name meths] (if named-fn? + [(first args) (next args)] + [name (seq args)]) + ;; turn (fn [] ...) into (fn ([]...)) + meths (if (vector? (first meths)) + (list meths) + meths) + locals (:locals env) + name-var (fn-name-var env locals name) + async (or + ;; NOTE: adding async on fn form turns it into a MetaFn which isn't great for interop, let's discourage it - Michiel Borkent + #_(:async (meta form)) + (:async (meta name)) + (:async (meta (first form)))) + env (assoc env :async async) + env (if (some? name) + (update-in env [:fn-scope] conj name-var) + env) + locals (if (and (some? locals) + named-fn?) + (assoc locals name name-var) + locals) + form-meta (meta form) + type (::type form-meta) + proto-impl (::protocol-impl form-meta) + proto-inline (::protocol-inline form-meta) + menv (-> env + (cond-> + (> (count meths) 1) + (assoc :context :expr)) + ;; clear loop flag since method bodies won't be in a loop at first + ;; only tracking this to keep track of locals we need to capture + (dissoc :in-loop) + (merge {:protocol-impl proto-impl + :protocol-inline proto-inline})) + methods (map #(disallowing-ns* (analyze-fn-method menv locals % type (nil? name))) meths) + mfa (transduce (map :fixed-arity) max 0 methods) + variadic (boolean (some :variadic? methods)) + locals (if named-fn? + (update-in locals [name] assoc + ;; TODO: can we simplify? - David + :fn-var true + :variadic? variadic + :max-fixed-arity mfa + :method-params (map :params methods)) + locals) + methods (if (some? name) + ;; a second pass with knowledge of our function-ness/arity + ;; lets us optimize self calls + (disallowing-ns* (analyze-fn-methods-pass2 menv locals type meths)) + (vec methods)) + form (vary-meta form dissoc ::protocol-impl ::protocol-inline ::type) + js-doc (when (true? variadic) + "@param {...*} var_args") + children (if (some? name-var) + [:local :methods] + [:methods]) + inferred-ret-tag (let [inferred-tags (map (partial infer-tag env) (map :body methods))] + (when (apply = inferred-tags) + (first inferred-tags))) + ast (merge {:op :fn + :env env + :form form + :name name-var + :methods methods + :variadic? variadic + :tag 'function + :inferred-ret-tag inferred-ret-tag + :recur-frames *recur-frames* + :in-loop (:in-loop env) + :loop-lets *loop-lets* + :jsdoc [js-doc] + :max-fixed-arity mfa + :protocol-impl proto-impl + :protocol-inline proto-inline + :children children} + (when (some? name-var) + {:local name-var}))] + (let [variadic-methods (into [] + (comp (filter :variadic?) (take 1)) + methods) + variadic-params (if (pos? (count variadic-methods)) + (count (:params (nth variadic-methods 0))) + 0) + param-counts (into [] (map (comp count :params)) methods)] + (when (< 1 (count variadic-methods)) + (warning :multiple-variadic-overloads env {:name name-var})) + (when (not (or (zero? variadic-params) (== variadic-params (+ 1 mfa)))) + (warning :variadic-max-arity env {:name name-var})) + (when (not= (distinct param-counts) param-counts) + (warning :overload-arity env {:name name-var}))) + (analyze-wrap-meta ast))) + +(defmethod parse 'letfn* + [op env [_ bindings & exprs :as form] name _] + (when-not (and (vector? bindings) (even? (count bindings))) + (throw (error env "bindings must be vector of even number of elements"))) + (let [n->fexpr (into {} (map (juxt first second) (partition 2 bindings))) + names (keys n->fexpr) + context (:context env) + ;; first pass to collect information for recursive references + [meth-env bes] + (reduce (fn [[{:keys [locals] :as env} bes] n] + (let [ret-tag (-> n meta :tag) + fexpr (no-warn (analyze env (n->fexpr n))) + be (cond-> + {:op :binding + :name n + :form n + :env env + :fn-var true + :line (get-line n env) + :column (get-col n env) + :local :letfn + :shadow (handle-symbol-local n (locals n)) + :variadic? (:variadic? fexpr) + :max-fixed-arity (:max-fixed-arity fexpr) + :method-params (map :params (:methods fexpr))} + ret-tag (assoc :ret-tag ret-tag))] + [(assoc-in env [:locals n] be) + (conj bes be)])) + [env []] names) + meth-env (assoc meth-env :context :expr) + ;; the real pass + [meth-env bes] + (reduce (fn [[meth-env bes] {:keys [name shadow] :as be}] + (let [env (assoc-in meth-env [:locals name] shadow) + fexpr (analyze env (n->fexpr name)) + be' (assoc be + :init fexpr + :variadic? (:variadic? fexpr) + :max-fixed-arity (:max-fixed-arity fexpr) + :method-params (map :params (:methods fexpr)) + :children [:init])] + [(assoc-in env [:locals name] be') + (conj bes be')])) + [meth-env []] bes) + expr (-> (analyze (assoc meth-env :context (if (= :expr context) :return context)) `(do ~@exprs)) + (assoc :body? true))] + {:env env :op :letfn :bindings bes :body expr :form form + :children [:bindings :body]})) + +(defn analyze-do-statements* [env exprs] + (mapv #(analyze (assoc env :context :statement) %) (butlast exprs))) + +(defn analyze-do-statements [env exprs] + (disallowing-recur (analyze-do-statements* env exprs))) + +(defmethod parse 'do + [op env [_ & exprs :as form] _ _] + (let [statements (analyze-do-statements env exprs)] + (if (<= (count exprs) 1) + (let [ret (analyze env (first exprs)) + children [:statements :ret]] + {:op :do + :env env + :form form + :statements statements :ret ret + :children children}) + (let [ret-env (if (= :statement (:context env)) + (assoc env :context :statement) + (assoc env :context :return)) + ret (analyze ret-env (last exprs)) + children [:statements :ret]] + {:op :do + :env env + :form form + :statements statements + :ret ret + :children children})))) + +(defn analyze-let-binding-init [env init loop-lets] + (binding [*loop-lets* loop-lets] + (analyze env init))) + +(defn get-let-tag [name init-expr] + (if-some [tag (-> name meta :tag)] + tag + (if-some [tag (-> init-expr :tag)] + tag + (-> init-expr :info :tag)))) + +(defn analyze-let-bindings* [encl-env bindings op] + (loop [bes [] + env (assoc encl-env :context :expr) + bindings (seq (partition 2 bindings))] + + (if-some [[name init] (first bindings)] + (let [] + (when (or (some? (namespace name)) + #?(:clj (.contains (str name) ".") + :cljs ^boolean (goog.string/contains (str name) "."))) + (throw (error encl-env (str "Invalid local name: " name)))) + (let [init-expr (analyze-let-binding-init env init (cons {:params bes} *loop-lets*)) + line (get-line name env) + col (get-col name env) + shadow (or (handle-symbol-local name (get-in env [:locals name])) + (get-in env [:js-globals name])) + be {:op :binding + :name name + :form name + :line line + :column col + :init init-expr + :tag (get-let-tag name init-expr) + :local op + :shadow shadow + ;; Give let* bindings same shape as var so + ;; they get routed correctly in the compiler + :env {:line line :column col} + :info {:name name + :shadow shadow} + :binding-form? true + :children [:init]} + be (if (= :fn (:op init-expr)) + ;; TODO: can we simplify - David + (merge be + {:fn-var true + ;; copy over the :fn-method information we need for invoke type inference + :methods (into [] (map #(select-keys % [:tag :fixed-arity :variadic?]) (:methods init-expr))) + :variadic? (:variadic? init-expr) + :max-fixed-arity (:max-fixed-arity init-expr) + :method-params (map :params (:methods init-expr))}) + be)] + (recur (conj bes be) + (assoc-in env [:locals name] be) + (next bindings)))) + [bes env]))) + +(defn analyze-let-bindings [encl-env bindings op] + (disallowing-recur (analyze-let-bindings* encl-env bindings op))) + +(defn analyze-let-body* [env context exprs] + (analyze (assoc env :context (if (= :expr context) :return context)) `(do ~@exprs))) + +(defn analyze-let-body [env context exprs recur-frames loop-lets] + (binding [*recur-frames* recur-frames + *loop-lets* loop-lets] + (analyze-let-body* env context exprs))) + +(defn analyze-let + [encl-env [_ bindings & exprs :as form] is-loop widened-tags] + (when-not (and (vector? bindings) (even? (count bindings))) + (throw (error encl-env "bindings must be vector of even number of elements"))) + (let [context (:context encl-env) + op (if (true? is-loop) :loop :let) + bindings (if widened-tags + (vec (mapcat + (fn [[name init] widened-tag] + [(vary-meta name assoc :tag widened-tag) init]) + (partition 2 bindings) + widened-tags)) + bindings) + [bes env] (-> encl-env + (cond-> + (true? is-loop) (assoc :in-loop true)) + (analyze-let-bindings bindings op)) + recur-frame (when (true? is-loop) + {:params bes + :flag (atom nil) + :tags (atom (mapv :tag bes))}) + recur-frames (if recur-frame + (cons recur-frame *recur-frames*) + *recur-frames*) + loop-lets (cond + (true? is-loop) *loop-lets* + (some? *loop-lets*) (cons {:params bes} *loop-lets*)) + ;; Accumulate warnings for deferred replay iff there's a possibility of re-analyzing + warn-acc (when (and is-loop + (not widened-tags)) + (atom [])) + expr (if warn-acc + (with-warning-handlers [(accumulating-warning-handler warn-acc)] + (analyze-let-body env context exprs recur-frames loop-lets)) + (analyze-let-body env context exprs recur-frames loop-lets)) + children [:bindings :body] + nil->any (fnil identity 'any)] + (if (and is-loop + (not widened-tags) + (not= (mapv nil->any @(:tags recur-frame)) + (mapv (comp nil->any :tag) bes))) + (recur encl-env form is-loop @(:tags recur-frame)) + (do + (when warn-acc + (replay-accumulated-warnings warn-acc)) + {:op op + :env encl-env + :bindings bes + :body (assoc expr :body? true) + :form form + :children children})))) + +(defmethod parse 'let* + [op encl-env form _ _] + (analyze-let encl-env form false nil)) + +(defmethod parse 'loop* + [op encl-env form _ _] + (analyze-let encl-env form true nil)) + +(defmethod parse 'recur + [op env [_ & exprs :as form] _ _] + (let [context (:context env) + frame (first *recur-frames*) + ;; Add dummy implicit target object if recuring to proto impl method head + add-implicit-target-object? (and (:protocol-impl frame) + (= (count exprs) (dec (count (:params frame))))) + exprs (cond->> exprs add-implicit-target-object? (cons nil)) + exprs (disallowing-recur (vec (map #(analyze (assoc env :context :expr) %) exprs)))] + (when-not frame + (throw (error env "Can't recur here"))) + (when-not (= (count exprs) (count (:params frame))) + (throw (error env (str "recur argument count mismatch, expected: " + (count (:params frame)) " args, got: " (count exprs))))) + (when (and (:protocol-impl frame) + (not add-implicit-target-object?)) + (warning :protocol-impl-recur-with-target env {:form (:form (first exprs))})) + (reset! (:flag frame) true) + (swap! (:tags frame) (fn [tags] + (mapv (fn [tag expr] + ;; Widen by adding the type of the recur expression, except when recurring with a + ;; loop local: Since its final widened type is unknown, conservatively assume 'any. + (if (= :loop (:local expr)) + 'any + (add-types tag (:tag expr)))) + tags exprs))) + (assoc {:env env :op :recur :form form} + :frame frame + :exprs exprs + :children [:exprs]))) + +(defn analyze-const + [env form] + (let [;; register constants + {:keys [tag]} (analyze (assoc env :quoted? true) form)] + {:op :const + :env env + :literal? true + :val form + :tag tag + :form form})) + +(defmethod parse 'quote + [_ env [_ x :as form] _ _] + (when (not= 2 (count form)) + (throw (error env "Wrong number of args to quote"))) + (let [expr (analyze-const env x)] + {:op :quote + :literal? true + :expr expr + :env env + :form form + :tag (:tag expr) + :children [:expr]})) + +(def js-prim-ctor->tag + '{js/Object object + js/String string + js/Array array + js/Number number + js/Function function + js/Boolean boolean}) + +(defn prim-ctor? + "Test whether a tag is a constructor for a JS primitive" + [t] + (contains? js-prim-ctor->tag t)) + +(defmethod parse 'new + [_ env [_ ctor & args :as form] _ _] + (disallowing-recur + (let [enve (assoc env :context :expr) + ctorexpr (analyze enve ctor) + ctor-var (when (#{:var :local :js-var} (:op ctorexpr)) + (resolve-existing-var env ctor)) + record-args + (when (and (:record ctor-var) (not (-> ctor meta :internal-ctor))) + (repeat 3 (analyze enve nil))) + argexprs (into (vec (map #(analyze enve %) args)) record-args) + known-num-fields (:num-fields ctor-var) + argc (count args)] + (when (and (not (-> ctor meta :internal-ctor)) + (some? known-num-fields) + (not (or (= known-num-fields argc) + (and (:record ctor-var) + (= (+ 2 known-num-fields) argc))))) + (warning :fn-arity env {:argc argc :ctor ctor})) + {:env env :op :new :form form :class ctorexpr :args argexprs + :children [:class :args] + :tag (let [tag (-> ctorexpr :info :tag)] + (if (and (js-tag? tag) + (not (prim-ctor? tag))) + 'js ; some foreign thing, drop the prefix + (let [name (-> ctorexpr :info :name)] + (or (js-prim-ctor->tag name) name))))}))) + +(defmethod parse 'set! + [_ env [_ target val alt :as form] _ _] + (let [[target val] (if (= 4 (count form)) + ;; (set! o -prop val) + [`(. ~target ~val) alt] + [target val])] + (disallowing-recur + (binding [*private-var-access-nowarn* true] + (let [enve (assoc env :context :expr) + texpr (cond + (symbol? target) + (do + (cond + (and (= target '*unchecked-if*) ;; TODO: proper resolve + (or (true? val) (false? val))) + (set! *unchecked-if* val) + + (and (= target '*unchecked-arrays*) ;; TODO: proper resolve + (or (true? val) (false? val))) + (set! *unchecked-arrays* val) + + (and (= target '*warn-on-infer*) + (or (true? val) (false? val))) + (set! *cljs-warnings* (assoc *cljs-warnings* :infer-warning val))) + (when (some? (:const (resolve-var (dissoc env :locals) target))) + (throw (error env "Can't set! a constant"))) + (let [local (handle-symbol-local target (-> env :locals target))] + (when-not (or (nil? local) + (and (:field local) + (or (:mutable local) + (:unsynchronized-mutable local) + (:volatile-mutable local)))) + (throw (error env "Can't set! local var or non-mutable field")))) + (analyze-symbol enve target)) + + :else + (when (seq? target) + (let [texpr (if (-> target meta :extend-type) + ;; we're setting a prototype via extend-type macro + ;; nothing to warn + (binding [*cljs-warnings* + (assoc *cljs-warnings* :infer-warning false)] + (analyze-seq enve target nil)) + (analyze-seq enve target nil))] + (when (:field texpr) + texpr)))) + vexpr (analyze enve val)] + ;; as top level fns are decomposed for Closure cross-module code motion, we need to + ;; restore their :methods information + (when (seq? target) + (let [sym (some-> target second) + meta (meta sym)] + (when-let [info (and (= :fn (:op vexpr)) (:top-fn meta))] + (swap! env/*compiler* update-in + [::namespaces (-> env :ns :name) :defs sym :methods] + (fnil conj []) + ;; just use original fn meta, as the fn method is already desugared + ;; only get tag from analysis + (merge + (select-keys info [:fixed-arity :variadic?]) + (select-keys (-> vexpr :methods first) [:tag])))))) + (when-not texpr + (throw (error env "set! target must be a field or a symbol naming a var"))) + (cond + (and (not (:def-emits-var env)) ;; non-REPL context + (some? ('#{*unchecked-if* *unchecked-arrays* *warn-on-infer*} target))) + {:env env :op :no-op} + + :else + {:env env :op :set! :form form :target texpr :val vexpr + :children [:target :val]})))))) + +#?(:clj (declare analyze-file)) + +#?(:clj + (defn locate-src + "Given a namespace return the corresponding ClojureScript (.cljs or .cljc) + resource on the classpath or file from the root of the build." + [ns] + (or (util/ns->source ns) + ;; Find sources available in inputs given to cljs.closure/build - Juho Teperi + (some (fn [source] + (if (= ns (:ns source)) + (:source-file source))) + (:sources @env/*compiler*)) + ;; Find sources in directory given to cljs.compiler/compile-root - Juho Teperi + (let [rootp (when-let [root (:root @env/*compiler*)] + (.getPath ^File root)) + cljsf (io/file rootp (ns->relpath ns :cljs)) + cljcf (io/file rootp (ns->relpath ns :cljc))] + (if (and (.exists cljsf) (.isFile cljsf)) + cljsf + (if (and (.exists cljcf) (.isFile cljcf)) + cljcf)))))) + +(defn external-dep? + "Returns true if the library is an :external? foreign dep. This means no source is provided + for the library, i.e. it will be provided by some script tag on the page, or loaded by some + other means into the JS execution environment." + #?(:cljs {:tag boolean}) + [dep] + (let [js-index (:js-dependency-index @env/*compiler*)] + (if-some [[_ {:keys [foreign external?]}] (find js-index (name (-> dep lib&sublib first)))] + (and foreign external?) + false))) + +(defn foreign-dep? + #?(:cljs {:tag boolean}) + [dep] + (let [js-index (:js-dependency-index @env/*compiler*)] + (if-some [[_ {:keys [foreign]}] (find js-index (name (-> dep lib&sublib first)))] + foreign + false))) + +(defn analyze-deps + "Given a lib, a namespace, deps, its dependencies, env, an analysis environment + and opts, compiler options - analyze all of the dependencies. Required to + correctly analyze usage of other namespaces." + ([lib deps env] + (analyze-deps lib deps env + (when env/*compiler* + (:options @env/*compiler*)))) + ([lib deps env opts] + (let [compiler @env/*compiler*] + (binding [*cljs-dep-set* (vary-meta (conj *cljs-dep-set* lib) update-in [:dep-path] conj lib)] + (assert (every? #(not (contains? *cljs-dep-set* %)) deps) + (str "Circular dependency detected, " + (apply str + (interpose " -> " + (conj (-> *cljs-dep-set* meta :dep-path) + (some *cljs-dep-set* deps)))))) + (doseq [dep deps] + (when-not (or (some? (get-in compiler [::namespaces dep :defs])) + (node-module-dep? dep) + (js-module-exists? (name dep)) + #?(:clj (deps/find-classpath-lib dep))) + (let [idx (:js-dependency-index compiler) + dep (-> dep lib&sublib first)] + (if (contains? idx (name dep)) + (let [dep-name (name dep)] + (when (string/starts-with? dep-name "goog.") + #?(:clj (let [js-lib (get idx dep-name) + ns (externs/analyze-goog-file (:file js-lib) (symbol dep-name))] + (swap! env/*compiler* update-in [::namespaces dep] merge ns))))) + #?(:clj (if-some [src (locate-src dep)] + (analyze-file src opts) + (throw + (error env + (error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)})))) + :cljs (throw + (error env + (error-message :undeclared-ns {:ns-sym dep :js-provide (name dep)})))))))))))) + +(defn global-ns? [x] + (and (symbol? x) + (or (= 'js x) + (= "js" (namespace x))))) + +(defn missing-use? [lib sym cenv] + ;; ignore globals referred via :refer-global + (when-not (global-ns? lib) + (let [js-lib (get-in cenv [:js-dependency-index (name lib)])] + (and (= (get-in cenv [::namespaces lib :defs sym] ::not-found) ::not-found) + (not (= (get js-lib :group) :goog)) + (not (get js-lib :closure-lib)) + (not (node-module-dep? lib)) + (not (dep-has-global-exports? lib)))))) + +(defn missing-rename? [sym cenv] + (let [lib (symbol (namespace sym)) + sym (symbol (name sym))] + (missing-use? lib sym cenv))) + +(defn missing-use-macro? [lib sym] + ;; guard against string requires + (when (symbol? lib) + (let [the-ns #?(:clj (find-ns lib) :cljs (find-macros-ns lib))] + (or (nil? the-ns) (nil? (.findInternedVar ^clojure.lang.Namespace the-ns sym)))))) + +(defn missing-rename-macro? [sym] + (let [lib (symbol (namespace sym)) + sym (symbol (name sym)) + the-ns #?(:clj (find-ns lib) :cljs (find-macros-ns lib))] + (or (nil? the-ns) (nil? (.findInternedVar ^clojure.lang.Namespace the-ns sym))))) + +;; returns (s/map-of symbol? symbol?) +(defn missing-uses + [uses env] + (let [cenv @env/*compiler*] + (into {} (filter (fn [[sym lib]] (missing-use? lib sym cenv)) uses)))) + +;; returns (s/map-of symbol? qualified-symbol?) +(defn missing-renames [renames env] + (let [cenv @env/*compiler*] + (into {} (filter (fn [[_ qualified-sym]] (missing-rename? qualified-sym cenv)) renames)))) + +;; returns (s/map-of symbol? symbol?) +(defn missing-use-macros [use-macros env] + (let [cenv @env/*compiler*] + (into {} (filter (fn [[sym lib]] (missing-use-macro? lib sym)) use-macros)))) + +;; returns (s/map-of symbol? symbol?) +(defn inferred-use-macros [use-macros env] + (let [cenv @env/*compiler*] + (into {} (filter (fn [[sym lib]] (not (missing-use-macro? lib sym))) use-macros)))) + +;; returns (s/map-of symbol? symbol?) +(defn inferred-rename-macros [rename-macros env] + (into {} (filter (fn [[_ qualified-sym]] (not (missing-rename-macro? qualified-sym))) rename-macros))) + +(defn check-uses [uses env] + (let [cenv @env/*compiler*] + (doseq [[sym lib] uses] + (when (missing-use? lib sym cenv) + (throw + (error env + (error-message :undeclared-ns-form {:type "var" :lib lib :sym sym}))))))) + +(defn check-use-macros + ([use-macros env] + (check-use-macros use-macros nil env)) + ([use-macros missing-uses env] + (let [cenv @env/*compiler*] + (doseq [[sym lib] use-macros] + (when (missing-use-macro? lib sym) + (throw + (error env + (error-message :undeclared-ns-form {:type "macro" :lib lib :sym sym}))))) + (check-uses (missing-use-macros missing-uses env) env) + (inferred-use-macros missing-uses env)))) + +(defn check-use-macros-inferring-missing + [{:keys [name uses use-macros] :as ast} env] + (let [missing-uses (when (and *analyze-deps* (seq uses)) + (missing-uses uses env)) + maybe-macros (apply dissoc uses (keys missing-uses)) + remove-missing-uses #(apply dissoc % (keys missing-uses)) + ast' (-> ast + (update-in [:use-macros] + #(-> % + (merge (check-use-macros use-macros missing-uses env)) + (merge (inferred-use-macros maybe-macros env)))) + (update-in [:uses] remove-missing-uses))] + (swap! env/*compiler* + #(-> % + (update-in [::namespaces name :use-macros] merge (:use-macros ast')) + (update-in [::namespaces name :uses] remove-missing-uses))) + ast')) + +(defn check-rename-macros-inferring-missing + [{:keys [name renames] :as ast} env] + (let [missing-renames (when (and *analyze-deps* (seq renames)) + (missing-renames renames env)) + maybe-macros (apply dissoc renames (keys missing-renames)) + missing-rename-macros (inferred-rename-macros missing-renames env) + remove-missing-renames #(apply dissoc % (keys missing-renames)) + ast' (-> ast + (update-in [:rename-macros] + #(-> % + (merge missing-rename-macros) + (merge (inferred-rename-macros maybe-macros env)))) + (update-in [:renames] remove-missing-renames))] + (swap! env/*compiler* + #(-> % + (update-in [::namespaces name :rename-macros] merge (:rename-macros ast')) + (update-in [::namespaces name :renames] remove-missing-renames))) + ast')) + +(defn parse-ns-error-msg [spec msg] + (str msg "; offending spec: " (pr-str spec))) + +(defn basic-validate-ns-spec [env macros? spec] + (when-not (or (symbol? spec) (string? spec) (sequential? spec)) + (throw + (error env + (parse-ns-error-msg spec + "Only [lib.ns & options] and lib.ns specs supported in :require / :require-macros")))) + (when (sequential? spec) + (when-not (or (symbol? (first spec)) (string? (first spec))) + (throw + (error env + (parse-ns-error-msg spec + "Library name must be specified as a symbol in :require / :require-macros")))) + (when-not (odd? (count spec)) + (throw + (error env + (parse-ns-error-msg spec + "Only :as alias, :refer (names) and :rename {from to} options supported in :require")))) + (when-not (every? #{:as :refer :rename} (map first (partition 2 (next spec)))) + (throw + (error env + (parse-ns-error-msg spec + "Only :as, :refer and :rename options supported in :require / :require-macros")))) + (when-not (let [fs (frequencies (next spec))] + (and (<= (fs :as 0) 1) + (<= (fs :refer 0) 1))) + (throw + (error env + (parse-ns-error-msg spec + "Each of :as and :refer options may only be specified once in :require / :require-macros")))))) + +(defn- parse-ns-excludes-impl [env args] + (reduce + (fn [s [k & filters]] + (if (= k :refer-clojure) + (do + (when (seq (:excludes s)) + (throw (error env "Only one :refer-clojure form is allowed per namespace definition"))) + (let [valid-kws #{:exclude :rename} + xs + (loop [fs (seq filters) + ret {:excludes #{} + :renames {}} + err (not (even? (count filters)))] + (cond + (true? err) + (throw + (error env "Only [:refer-clojure :exclude (names)] and optionally `:rename {from to}` specs supported")) + + (some? fs) + (let [kw (first fs)] + (if (valid-kws kw) + (let [refs (second fs)] + (cond + (not (or (and (= kw :exclude) (sequential? refs) (every? symbol? refs)) + (and (= kw :rename) (map? refs) (every? #(every? symbol? %) refs)))) + (recur fs ret true) + + (= kw :exclude) + (recur (nnext fs) (update-in ret [:excludes] into refs) false) + + (= kw :rename) + (recur (nnext fs) (update-in ret [:renames] merge refs) false))) + (recur fs ret true))) + + :else ret))] + (merge-with into s xs))) + s)) + {:excludes #{} + :renames {}} args)) + +(defn parse-ns-excludes [env args] + (let [s (parse-ns-excludes-impl env args)] + (update s :excludes into (keys (:renames s))))) + +(defn use->require [env [lib & filters :as spec]] + (when-not (and (symbol? lib) (odd? (count spec))) + (throw + (error env + (parse-ns-error-msg spec + "Only [lib.ns :only (names)] and optionally `:rename {from to}` specs supported in :use / :use-macros")))) + (loop [fs (seq filters) ret [lib] err false] + (cond + (true? err) + (throw + (error env + (parse-ns-error-msg spec + "Only [lib.ns :only (names)] and optionally `:rename {from to}` specs supported in :use / :use-macros"))) + + (some? fs) + (let [kw (first fs) + only? (= kw :only)] + (if (or only? (= kw :rename)) + (if (some? (some #{(if only? :refer kw)} ret)) + (throw + (error env + (parse-ns-error-msg spec + "Each of :only and :rename options may only be specified once in :use / :use-macros"))) + (let [refs (second fs)] + (if-not (or (and only? (sequential? refs) (every? symbol? refs)) + (and (= kw :rename) (map? refs) (every? #(every? symbol? %) refs))) + (recur fs ret true) + (recur (nnext fs) (into ret [(if only? :refer kw) refs]) false)))) + (recur fs ret true ))) + + :else (if (some? (some #{:refer} ret)) + ret + (recur fs ret true))))) + +(defn parse-global-refer-spec + [env args] + (let [xs (filter #(-> % first (= :refer-global)) args) + cnt (count xs)] + (cond + (> cnt 1) + (throw (error env "Only one :refer-global form is allowed per namespace definition")) + + (== cnt 1) + (let [[_ & {:keys [only rename] :as parsed-spec}] (first xs) + only-set (set only) + err-str "Only (:refer-global :only [names]) and optionally `:rename {from to}` specs supported. + :rename symbols must be present in :only"] + (when-not (or (empty? only) + (and (vector? only) + (every? symbol only))) + (throw (error env err-str))) + (when-not (or (empty? rename) + (and (map? rename) + (every? symbol (mapcat identity rename)) + (every? only-set (keys rename)))) + (throw (error env (str err-str (pr-str parsed-spec))))) + (when-not (every? #{:only :rename} (keys parsed-spec)) + (throw (error env (str err-str (pr-str parsed-spec))))) + {:use (zipmap (if rename (remove rename only) + only) (repeat 'js)) + :rename (into {} + (map (fn [[orig new-name]] + [new-name (symbol "js" (str orig))])) + rename)})))) + +(defn parse-global-require-spec + [env cenv deps aliases spec] + (if (or (symbol? spec) (string? spec)) + (recur env cenv deps aliases [spec]) + (do + (basic-validate-ns-spec env false spec) + (let [[lib & opts] spec + {alias :as referred :refer renamed :rename + :or {alias (if (string? lib) + (symbol (munge lib)) + lib)}} + (apply hash-map opts) + referred-without-renamed (seq (remove (set (keys renamed)) referred)) + [rk uk renk] [:require :use :rename]] + (when-not (or (symbol? alias) (nil? alias)) + (throw + (error env + (parse-ns-error-msg spec + ":as must be followed by a symbol in :require / :require-macros")))) + (when (some? alias) + (let [lib' ((:fns @aliases) alias)] + (when (and (some? lib') (not= lib lib')) + (throw (error env (parse-ns-error-msg spec ":as alias must be unique")))) + (when (= alias 'js) + (when-not (= lib (get-in @aliases [:fns 'js])) ; warn only once + (warning :js-used-as-alias env {:spec spec}))) + (swap! aliases update-in [:fns] conj [alias lib]))) + (when-not (or (and (sequential? referred) + (every? symbol? referred)) + (nil? referred)) + (throw + (error env + (parse-ns-error-msg spec + ":refer must be followed by a sequence of symbols in :require / :require-macros")))) + (swap! deps conj lib) + (let [ret (merge + (when (some? alias) + {rk (merge {alias lib} {lib lib})}) + (when (some? referred-without-renamed) + {uk (apply hash-map (interleave referred-without-renamed (repeat lib)))}) + (when (some? renamed) + {renk (reduce (fn [m [original renamed]] + (when-not (some #{original} referred) + (throw (error env + (str "Renamed symbol " original " not referred")))) + (assoc m renamed (symbol (str lib) (str original)))) + {} renamed)}))] + (swap! cenv assoc-in [:js-dependency-index (str lib)] + {:external? true + :foreign true + :provides [(str lib)] + :global-exports {lib lib}}) + ret))))) + +(defn parse-require-spec [env macros? deps aliases spec] + (if (or (symbol? spec) (string? spec)) + (recur env macros? deps aliases [spec]) + (do + (basic-validate-ns-spec env macros? spec) + (let [[lib & opts] spec + ;; We need to load JS modules by the name that has been created by the + ;; Google Closure compiler, e.g. module$resources$libs$calculator. + ;; This means that we need to create an alias from the module name + ;; given with :provides to the new name. + [lib js-module-provides] (if-some [js-module-name (gets @env/*compiler* :js-module-index (str lib) :name)] + [(symbol js-module-name) lib] + [lib nil]) + {alias :as referred :refer renamed :rename + :or {alias (if (string? lib) + (symbol (munge lib)) + lib)}} + (apply hash-map opts) + referred-without-renamed (seq (remove (set (keys renamed)) referred)) + [rk uk renk] (if macros? [:require-macros :use-macros :rename-macros] [:require :use :rename])] + (when-not (or (symbol? alias) (nil? alias)) + (throw + (error env + (parse-ns-error-msg spec + ":as must be followed by a symbol in :require / :require-macros")))) + (when (some? alias) + (let [alias-type (if macros? :macros :fns) + lib' ((alias-type @aliases) alias)] + (when (and (some? lib') (not= lib lib')) + (throw (error env (parse-ns-error-msg spec ":as alias must be unique")))) + (when (= alias 'js) + (when-not (= lib (get-in @aliases [(if macros? :fns :macros) 'js])) ; warn only once + (warning :js-used-as-alias env {:spec spec}))) + (swap! aliases + update-in [alias-type] + conj [alias lib] (when js-module-provides [js-module-provides lib])))) + (when-not (or (and (sequential? referred) + (every? symbol? referred)) + (nil? referred)) + (throw + (error env + (parse-ns-error-msg spec + ":refer must be followed by a sequence of symbols in :require / :require-macros")))) + (when-not macros? + (swap! deps conj lib)) + (merge + (when (some? alias) + {rk (merge {alias lib} {lib lib} + (when js-module-provides {js-module-provides lib}))}) + (when (some? referred-without-renamed) + {uk (apply hash-map (interleave referred-without-renamed (repeat lib)))}) + (when (some? renamed) + {renk (reduce (fn [m [original renamed]] + (when-not (some #{original} referred) + (throw (error env + (str "Renamed symbol " original " not referred")))) + (assoc m renamed (symbol (str lib) (str original)))) + {} renamed)})))))) + +(defn parse-import-spec [env deps spec] + (when-not (or (and (sequential? spec) + (every? symbol? spec)) + (and (symbol? spec) (nil? (namespace spec)))) + (throw (error env (parse-ns-error-msg spec "Only lib.ns.Ctor or [lib.ns Ctor*] spec supported in :import")))) + (let [import-map (cond + (sequential? spec) + (->> (rest spec) + (map #(vector % (symbol (str (first spec) "." %)))) + (into {})) + + (not (== -1 (.indexOf (str spec) "."))) + {(symbol (last (string/split (str spec) #"\."))) spec} + + :else {})] + (doseq [[_ spec] import-map] + (swap! deps conj spec)) + {:import import-map + :require import-map})) + +#?(:clj (declare parse-ns)) + +(defn macro-autoload-ns? + "Given a spec form check whether the spec namespace requires a macro file + of the same name. If so return true." + #?(:cljs {:tag boolean}) + [form] + (when *macro-infer* + (let [ns (if (sequential? form) (first form) form) + {:keys [use-macros require-macros]} + (or (get-in @env/*compiler* [::namespaces ns]) + #?(:clj + (when-let [res (util/ns->source ns)] + (:ast (parse-ns res)))))] + (or (some #{ns} (vals use-macros)) + (some #{ns} (vals require-macros)))))) + +(defn clj-ns->cljs-ns + "Given a symbol that starts with clojure as the first segment return the + same symbol with the first segment replaced with cljs" + [sym] + (let [segs (string/split (clojure.core/name sym) #"\.")] + (if (= "clojure" (first segs)) + (symbol (string/join "." (cons "cljs" (next segs)))) + sym))) + +#?(:clj + (defn aliasable-clj-ns? + "Predicate for testing with a symbol represents an aliasable clojure namespace." + [sym] + (when-not (util/ns->source sym) + (let [[seg1 :as segs] (string/split (clojure.core/name sym) #"\.")] + (when (= "clojure" seg1) + (let [sym' (clj-ns->cljs-ns sym)] + (util/ns->source sym'))))))) + +#?(:clj + (defn process-rewrite-form [[k & specs :as form]] + (letfn [(process-spec [maybe-spec] + (let [[lib & xs] (if (sequential? maybe-spec) + maybe-spec + [maybe-spec])] + (if (and (symbol? lib) (aliasable-clj-ns? lib)) + (let [lib' (clj-ns->cljs-ns lib) + spec (cons lib' xs)] + (into (if xs [spec] []) [(list lib' :as lib)])) + [maybe-spec])))] + (if (#{:use :require} k) + (cons k (mapcat process-spec specs)) + form)))) + +#?(:clj + (defn rewrite-cljs-aliases + "Alias non-existing clojure.* namespaces to existing cljs.* namespaces if + possible." + [args] + (map process-rewrite-form args))) + +(defn canonicalize-specs [specs] + (letfn [(canonicalize [quoted-spec-or-kw] + (if (keyword? quoted-spec-or-kw) + quoted-spec-or-kw + (as-> (second quoted-spec-or-kw) spec + (if (or (vector? spec) (map? spec)) spec [spec]))))] + (map canonicalize specs))) + +(defn canonicalize-import-specs [specs] + (letfn [(canonicalize [quoted-spec-or-kw] + (if (keyword? quoted-spec-or-kw) + quoted-spec-or-kw + (second quoted-spec-or-kw)))] + (map canonicalize specs))) + +(defn desugar-ns-specs + "Given an original set of ns specs desugar :include-macros and :refer-macros + usage into only primitive spec forms - :use, :require, :use-macros, + :require-macros. If a library includes a macro file of with the same name + as the namespace will also be desugared." + [args] + (let [{:keys [require] :as indexed} + (->> args + (map (fn [[k & specs]] [k (into [] specs)])) + (into {})) + sugar-keys #{:include-macros :refer-macros} + ;; drop spec k and value from spec for generated :require-macros + remove-from-spec + (fn [pred spec] + (if-not (and (sequential? spec) (some pred spec)) + spec + (let [[l r] (split-with (complement pred) spec)] + (recur pred (concat l (drop 2 r)))))) + ;; rewrite :refer-macros to :refer for generated :require-macros + replace-refer-macros + (fn [spec] + (if-not (sequential? spec) + spec + (map (fn [x] (if (= x :refer-macros) :refer x)) spec))) + reload-spec? #(#{:reload :reload-all} %) + to-macro-specs + (fn [specs] + (->> specs + (filter + (fn [x] + (or (and (sequential? x) + (some sugar-keys x)) + (reload-spec? x) + (macro-autoload-ns? x)))) + (map (fn [x] + (if-not (reload-spec? x) + (->> x (remove-from-spec #{:include-macros}) + (remove-from-spec #{:refer}) + (remove-from-spec #{:rename}) + (replace-refer-macros)) + x))))) + remove-sugar (partial remove-from-spec sugar-keys)] + (if-some [require-specs (seq (to-macro-specs require))] + (map (fn [x] + (if-not (reload-spec? x) + (let [[k v] x] + (cons k (map remove-sugar v))) + x)) + (update-in indexed [:require-macros] (fnil into []) require-specs)) + args))) + +(defn find-def-clash [env ns segments] + (let [to-check (map (fn [xs] + [(symbol (string/join "." (butlast xs))) + (symbol (last xs))]) + (drop 2 (reductions conj [] segments)))] + (doseq [[clash-ns name] to-check] + (when (get-in @env/*compiler* [::namespaces clash-ns :defs name]) + (warning :ns-var-clash env + {:ns ns + :var (symbol (str clash-ns) (str name))}))))) + +(defn macro-ns-name [name] + (let [name-str (str name)] + (if-not #?(:clj (.endsWith name-str "$macros") + :cljs (gstring/endsWith name-str "$macros")) + (symbol (str name-str "$macros")) + name))) + +(defn- check-duplicate-aliases + [env old new] + (let [ns-name (:name old)] + (doseq [k [:requires :require-macros]] + (let [old-aliases (get old k) + new-aliases (get new k)] + (when-some [alias (some (set (keys new-aliases)) + (->> old-aliases + (remove (fn [[k v :as entry]] + (or (= k v) + (= entry (find new-aliases k))))) + keys))] + (throw (error env + (str "Alias " alias " already exists in namespace " ns-name + ", aliasing " (get old-aliases alias))))))))) + +(defn- merge-ns-info [old new env] + (if (pos? (count old)) + (let [deep-merge-keys + [:use-macros :require-macros :rename-macros + :uses :requires :renames :imports :as-aliases]] + #?(:clj + (when *check-alias-dupes* + (check-duplicate-aliases env old new))) + (merge + old + (select-keys new [:excludes]) + (merge-with merge + (select-keys old deep-merge-keys) + (select-keys new deep-merge-keys)))) + new)) + +(def ns-spec-cases + #{:use :use-macros :require :require-macros + :import :refer-global :require-global}) + +(defmethod parse 'ns + [_ env [_ name & args :as form] _ opts] + (when-not *allow-ns* + (throw (error env "Namespace declarations must appear at the top-level."))) + (when-not (symbol? name) + (throw (error env "Namespaces must be named by a symbol."))) + (let [name (cond-> name (:macros-ns opts) macro-ns-name)] + (let [segments (string/split (clojure.core/name name) #"\.")] + (when (= 1 (count segments)) + (warning :single-segment-namespace env {:name name})) + (let [segment (some js-reserved segments)] + (when (some? segment) + (warning :munged-namespace env {:name name}))) + (find-def-clash env name segments) + #?(:clj + (when (some (complement util/valid-js-id-start?) segments) + (throw + (AssertionError. + (str "Namespace " name " has a segment starting with an invaild " + "JavaScript identifier")))))) + (let [docstring (when (string? (first args)) (first args)) + mdocstr (-> name meta :doc) + args (if (some? docstring) (next args) args) + metadata (when (map? (first args)) (first args)) + args (desugar-ns-specs + #?(:clj (rewrite-cljs-aliases + (if metadata (next args) args)) + :cljs (if (some? metadata) (next args) args))) + {:keys [as-aliases] args :libspecs} (nses/elide-aliases-from-ns-specs args) + name (vary-meta name merge metadata) + {excludes :excludes core-renames :renames} (parse-ns-excludes env args) + core-renames (reduce (fn [m [original renamed]] + (assoc m renamed (symbol "cljs.core" (str original)))) + {} core-renames) + {global-uses :use global-renames :rename} (parse-global-refer-spec env args) + deps (atom []) + ;; as-aliases can only be used *once* because they are about the reader + aliases (atom {:fns as-aliases :macros as-aliases}) + spec-parsers {:require (partial parse-require-spec env false deps aliases) + :require-macros (partial parse-require-spec env true deps aliases) + :use (comp (partial parse-require-spec env false deps aliases) + (partial use->require env)) + :use-macros (comp (partial parse-require-spec env true deps aliases) + (partial use->require env)) + :import (partial parse-import-spec env deps) + :require-global #(parse-global-require-spec env env/*compiler* deps aliases %)} + valid-forms (atom #{:use :use-macros :require :require-macros :require-global :import}) + reload (atom {:use nil :require nil :use-macros nil :require-macros nil}) + reloads (atom {}) + {uses :use requires :require renames :rename + use-macros :use-macros require-macros :require-macros + rename-macros :rename-macros imports :import :as params} + (reduce + (fn [m [k & libs :as libspec]] + (when-not (#{:use :use-macros :require :require-macros :require-global :import} k) + (throw (error env (str "Only :refer-clojure, :require, :require-macros, :use, :use-macros, :require-global and :import libspecs supported. Got " libspec " instead.")))) + (when-not (@valid-forms k) + (throw (error env (str "Only one " k " form is allowed per namespace definition")))) + (swap! valid-forms disj k) + ;; check for spec type reloads + (when-not (= :import k) + (when (some? (some #{:reload} libs)) + (swap! reload assoc k :reload)) + (when (some? (some #{:reload-all} libs)) + (swap! reload assoc k :reload-all))) + ;; check for individual ns reloads from REPL interactions + (when-let [xs (seq (filter #(-> % meta :reload) libs))] + (swap! reloads assoc k + (zipmap (map first xs) (map #(-> % meta :reload) xs)))) + (apply merge-with merge m + (map (spec-parsers k) + (remove #{:reload :reload-all} libs)))) + {} (remove (fn [[r]] (#{:refer-clojure :refer-global} r)) args)) + ;; patch `require-macros` and `use-macros` in Bootstrap for namespaces + ;; that require their own macros + #?@(:cljs [[require-macros use-macros] + (map (fn [spec-map] + (if (:macros-ns opts) + (let [ns (symbol (subs (str name) 0 (- (count (str name)) 7)))] + (reduce (fn [m [k v]] + (cond-> m + (not (symbol-identical? v ns)) + (assoc k v))) + {} spec-map)) + spec-map)) [require-macros use-macros])])] + (set! *cljs-ns* name) + (let [ns-info + {:as-aliases as-aliases + :name name + :doc (or docstring mdocstr) + :excludes excludes + :use-macros use-macros + :require-macros require-macros + :rename-macros rename-macros + :uses (merge uses global-uses) + :requires requires + :renames (merge renames core-renames global-renames) + :imports imports}] + (swap! env/*compiler* update-in [::namespaces name] merge ns-info) + (merge {:op :ns + :env env + :form form + :deps (into [] (distinct @deps)) + :reload @reload + :reloads @reloads} + (cond-> ns-info + (@reload :use) + (update-in [:uses] + (fn [m] (with-meta m {(@reload :use) true}))) + (@reload :require) + (update-in [:requires] + (fn [m] (with-meta m {(@reload :require) true}))))))))) + +(defmethod parse 'ns* + [_ env [_ quoted-specs :as form] _ opts] + (when-let [not-quoted (->> (remove keyword? quoted-specs) + (remove #(and (seq? %) (= 'quote (first %))) ) + first)] + (throw (error env (str "Arguments to " (name (first quoted-specs)) + " must be quoted. Offending spec: " not-quoted)))) + (when-not *allow-ns* + (throw (error env (str "Calls to `" (name (first quoted-specs)) + "` must appear at the top-level.")))) + (let [specs (if (= :import (first quoted-specs)) + (canonicalize-import-specs quoted-specs) + (canonicalize-specs quoted-specs)) + name (-> env :ns :name) + args (desugar-ns-specs + #?(:clj (list (process-rewrite-form + specs)) + :cljs (list specs))) + {:keys [as-aliases] args :libspecs} (nses/elide-aliases-from-ns-specs args) + {excludes :excludes core-renames :renames} (parse-ns-excludes env args) + core-renames (reduce (fn [m [original renamed]] + (assoc m renamed (symbol "cljs.core" (str original)))) + {} core-renames) + {global-uses :use global-renames :rename} (parse-global-refer-spec env args) + deps (atom []) + ;; as-aliases can only be used *once* because they are about the reader + aliases (atom {:fns as-aliases :macros as-aliases}) + spec-parsers {:require (partial parse-require-spec env false deps aliases) + :require-macros (partial parse-require-spec env true deps aliases) + :use (comp (partial parse-require-spec env false deps aliases) + (partial use->require env)) + :use-macros (comp (partial parse-require-spec env true deps aliases) + (partial use->require env)) + :import (partial parse-import-spec env deps) + :require-global #(parse-global-require-spec env env/*compiler* deps aliases %)} + reload (atom {:use nil :require nil :use-macros nil :require-macros nil}) + reloads (atom {}) + {uses :use requires :require renames :rename + use-macros :use-macros require-macros :require-macros + rename-macros :rename-macros imports :import :as params} + (reduce + (fn [m [k & libs]] + ;; check for spec type reloads + (when-not (= :import k) + (when (some? (some #{:reload} libs)) + (swap! reload assoc k :reload)) + (when (some? (some #{:reload-all} libs)) + (swap! reload assoc k :reload-all))) + ;; check for individual ns reloads from REPL interactions + (when-some [xs (seq (filter #(-> % meta :reload) libs))] + (swap! reloads assoc k + (zipmap (map first xs) (map #(-> % meta :reload) xs)))) + (apply merge-with merge m + (map (spec-parsers k) + (remove #{:reload :reload-all} libs)))) + {} (remove (fn [[r]] (#{:refer-clojure :refer-global} r)) args))] + (set! *cljs-ns* name) + (let [require-info + {:as-aliases as-aliases + :name name + :excludes excludes + :use-macros use-macros + :require-macros require-macros + :rename-macros rename-macros + :uses (merge uses global-uses) + :requires requires + :renames (merge renames core-renames global-renames) + :imports imports}] + (swap! env/*compiler* update-in [::namespaces name] merge-ns-info require-info env) + (merge {:op :ns* + :env env + :form form + :deps (into [] (distinct @deps)) + :reload @reload + :reloads @reloads} + (cond-> require-info + (@reload :use) + (update-in [:uses] + (fn [m] (with-meta m {(@reload :use) true}))) + (@reload :require) + (update-in [:requires] + (fn [m] (with-meta m {(@reload :require) true})))))))) + +(defn parse-type + [op env [_ tsym fields pmasks body :as form]] + (let [t (:name (resolve-var (dissoc env :locals) tsym)) + locals (reduce (fn [m fld] + (assoc m fld + {:name fld + :line (get-line fld env) + :column (get-col fld env) + :local :field + :field true + :mutable (-> fld meta :mutable) + :unsynchronized-mutable (-> fld meta :unsynchronized-mutable) + :volatile-mutable (-> fld meta :volatile-mutable) + :tag (-> fld meta :tag) + :shadow (m fld)})) + {} (if (= :defrecord op) + (concat fields '[__meta __extmap ^:mutable __hash]) + fields)) + protocols (-> tsym meta :protocols)] + (swap! env/*compiler* update-in [::namespaces (-> env :ns :name) :defs tsym] + (fn [m] + (let [m (assoc (or m {}) + :name t + :tag 'function + :type true + :num-fields (count fields) + :record (= :defrecord op))] + (merge m + (dissoc (meta tsym) :protocols) + {:protocols protocols} + (source-info tsym env))))) + {:op op :env env :form form :t t :fields fields :pmasks pmasks + :tag 'function + :protocols (disj protocols 'cljs.core/Object) + :children [#_:fields :body] + :body (analyze (assoc env :locals locals) body)})) + +(defmethod parse 'deftype* + [_ env form _ _] + (parse-type :deftype env form)) + +(defmethod parse 'defrecord* + [_ env form _ _] + (parse-type :defrecord env form) ) + +;; dot accessor code + +(def ^:private property-symbol? #(boolean (and (symbol? %) (re-matches #"^-.*" (name %))))) + +(defn- classify-dot-form + [[target member args]] + [(cond (nil? target) ::error + :default ::expr) + (cond (property-symbol? member) ::property + (symbol? member) ::symbol + (seq? member) ::list + :default ::error) + (cond (nil? args) () + :default ::expr)]) + +(defmulti build-dot-form #(classify-dot-form %)) + +;; (. o -p) +;; (. (...) -p) +(defmethod build-dot-form [::expr ::property ()] + [[target prop _]] + {:dot-action ::access :target target + :field (with-meta (-> prop name (.substring 1) symbol) (meta prop))}) + +;; (. o -p ) +(defmethod build-dot-form [::expr ::property ::list] + [[target prop args]] + #?(:clj (throw (Error. (str "Cannot provide arguments " args " on property access " prop))) + :cljs (throw (js/Error. (str "Cannot provide arguments " args " on property access " prop))))) + +(defn- build-method-call + "Builds the intermediate method call map used to reason about the parsed form during + compilation." + [target meth args] + (if (symbol? meth) + {:dot-action ::call :target target :method meth :args args} + {:dot-action ::call :target target :method (first meth) :args args})) + +;; (. o m 1 2) +(defmethod build-dot-form [::expr ::symbol ::expr] + [[target meth args]] + (build-method-call target meth args)) + +;; (. o m) +(defmethod build-dot-form [::expr ::symbol ()] + [[target meth args]] + (build-method-call target meth args)) + +;; (. o (m)) +;; (. o (m 1 2)) +(defmethod build-dot-form [::expr ::list ()] + [[target meth-expr _]] + (build-method-call target (first meth-expr) (rest meth-expr))) + +(defmethod build-dot-form :default + [dot-form] + #?(:clj (throw + (Error. + (str "Unknown dot form of " + (list* '. dot-form) " with classification " + (classify-dot-form dot-form)))) + :cljs (throw + (js/Error. + (str "Unknown dot form of " + (list* '. dot-form) " with classification " + (classify-dot-form dot-form)))))) + +;; this only for a smaller set of types that we want to infer +;; we don't generally want to consider function for example, these +;; specific cases are ones we either try to optimize or validate +(def ^{:private true} + tag->js-prim-ctor + '{string js/String + array js/Array + number js/Number + boolean js/Boolean}) + +(defn analyze-dot [env target field member+ form] + (let [v [target field member+] + {:keys [dot-action target method field args]} (build-dot-form v) + enve (assoc env :context :expr) + targetexpr (analyze enve target) + form-meta (meta form) + target-tag (as-> (:tag targetexpr) $ + (or (some-> $ meta :ctor lift-tag-to-js) + (tag->js-prim-ctor $ $))) + prop (or field method) + tag (or (:tag form-meta) + (and (js-tag? target-tag) + (vary-meta (normalize-js-tag target-tag) + update-in [:prefix] (fnil conj '[Object]) prop)) + nil)] + (when (and (not= 'constructor prop) + (not (string/starts-with? (str prop) "cljs$")) + (not (-> prop meta :protocol-prop))) + ;; Adding to Object + (when (= 'Object (first (-> tag meta :prefix))) + (warning :infer-warning env + {:warn-type :object :form form :property prop})) + (when (not= 'js target-tag) + ;; Cannot determine type of the target + (when (or (nil? target-tag) ('#{any} target-tag)) + (warning :infer-warning env + {:warn-type :target :form form :property prop})) + ;; Unresolveable property on existing extern + (let [[pre' pre] ((juxt butlast identity) (-> tag meta :prefix))] + (when (and (has-extern? pre') (not (has-extern? pre))) + (warning :infer-warning env + {:warn-type :property :form form + :type (symbol "js" + (string/join "." + (cond-> pre' (= 'prototype (last pre')) butlast))) + :property prop}))))) + (when (js-tag? tag) + (let [pre (-> tag meta :prefix)] + (when-not (has-extern? pre) + (swap! env/*compiler* update-in + (into [::namespaces (-> env :ns :name) :externs] + (normalize-unresolved-prefix pre)) merge {})))) + (case dot-action + ::access (let [children [:target]] + {:op :host-field + :env env + :form form + :target targetexpr + :field field + :children children + :tag (if (js-tag? tag) + (or (js-tag (-> tag meta :prefix) :tag) tag) + tag)}) + ::call (let [argexprs (mapv #(analyze enve %) args) + children [:target :args]] + {:op :host-call + :env env + :form form + :target targetexpr + :method method + :args argexprs + :children children + :tag (if (js-tag? tag) + (or (js-tag (-> tag meta :prefix) :ret-tag) 'js) + tag)})))) + +(defmethod parse '. + [_ env [_ target & [field & member+] :as form] _ _] + (disallowing-recur (analyze-dot env target field member+ form))) + +(defn get-js-tag [form] + (let [form-meta (meta form)] + (if-some [tag (:tag form-meta)] + tag + (when (true? (:numeric form-meta)) + 'number)))) + +(defn js-star-interp + [env ^String s] + (let [idx (.indexOf s "~{")] + (if (== -1 idx) + (list s) + (let [end (.indexOf s "}" idx) + inner (:name (resolve-existing-var env (symbol (subs s (+ 2 idx) end))))] + (lazy-seq + (cons (subs s 0 idx) + (cons inner + (js-star-interp env (subs s (inc end)))))))))) + +(defn js-star-seg + [^String s] + (let [idx (.indexOf s "~{")] + (if (== -1 idx) + (list s) + (let [end (.indexOf s "}" idx)] + (lazy-seq + (cons (subs s 0 idx) + (js-star-seg (subs s (inc end))))))))) + +(def NUMERIC_SET '#{any number long double}) + +(defn numeric-type? + #?(:cljs {:tag boolean}) + [t] + ;; TODO: type inference is not strong enough to detect that + ;; when functions like first won't return nil, so variadic + ;; numeric functions like cljs.core/< would produce a spurious + ;; warning without this - David + (cond + (nil? t) true + (= 'clj-nil t) true + (js-tag? t) true ;; TODO: revisit + :else + (if (and (symbol? t) (some? (get NUMERIC_SET t))) + true + (when #?(:clj (set? t) + :cljs (impl/cljs-set? t)) + (or (contains? t 'number) + (contains? t 'long) + (contains? t 'double) + (contains? t 'any) + (contains? t 'js)))))) + +(def array-types + '#{array objects ints longs floats doubles chars shorts bytes boolean}) + +(defn array-type? + #?(:cljs {:tag boolean}) + [t] + ;; TODO same inference caveats as the numeric-type? fn above + (cond + (nil? t) true + (= 'clj-nil t) true + (js-tag? t) true ;; TODO: revisit + (= 'any t) true + (contains? array-types t) true + :else + (boolean + (when #?(:clj (set? t) + :cljs (impl/cljs-set? t)) + (or (contains? t 'any) + (contains? t 'js) + (some array-types t)))))) + +(defn- analyze-js-star-args [js-op env args] + (first (reduce + (fn [[argexprs env] arg] + [(conj argexprs (analyze env arg)) + (if (= js-op 'cljs.core/and) + (set-test-induced-tags env arg) + env)]) + [[] env] + args))) + +(defn analyze-js-star* [env jsform args form] + (let [enve (assoc env :context :expr) + form-meta (meta form) + segs (js-star-seg jsform) + tag (get-js-tag form) + js-op (:js-op form-meta) + argexprs (analyze-js-star-args js-op enve args) + numeric (:numeric form-meta) + validate (fn [warning-type valid-types?] + (let [types (map #(infer-tag env %) argexprs)] + (when-not (valid-types? types) + (warning warning-type env + {:js-op js-op + :types (into [] types)})))) + op-match? (fn [sym] + #?(:clj (= sym (:js-op form-meta)) + :cljs (symbol-identical? sym (:js-op form-meta))))] + (when (true? numeric) + (validate :invalid-arithmetic #(every? numeric-type? %))) + {:op :js + :env env + :segs segs + :args argexprs + :tag tag + :form form + :children [:args] + :js-op js-op + :numeric numeric})) + +(defn analyze-js-star [env jsform args form] + (disallowing-recur (analyze-js-star* env jsform args form))) + +(defmethod parse 'js* + [op env [_ jsform & args :as form] _ _] + (when-not (string? jsform) + (throw (error env "Invalid js* form"))) + (if (some? args) + (analyze-js-star env jsform args form) + (let [code (apply str (js-star-interp env jsform)) + tag (get-js-tag form) + form-meta (meta form) + js-op (:js-op form-meta) + numeric (:numeric form-meta)] + {:op :js + :env env + :form form + :code code + :tag tag + :js-op js-op + :numeric numeric}))) + +;; TODO: analyzed analyzed? should take pass name as qualified keyword arg +;; then compiler passes can mark/check individually - David + +(defn- unsorted-map? [x] + (and (map? x) + (not (sorted? x)))) + +(defn analyzed + "Mark a form as being analyzed. Assumes x satisfies IMeta. Useful to suppress + warnings that will have been caught by a first compiler pass." + [x] + (cond + (unsorted-map? x) (assoc x ::analyzed true) + :else (vary-meta x assoc ::analyzed true))) + +(defn analyzed? + "Returns boolean if the form has already been marked as analyzed." + #?(:cljs {:tag boolean}) + [x] + (boolean + (cond + (unsorted-map? x) (::analyzed x) + :else (::analyzed (meta x))))) + +(defn- all-values? + #?(:cljs {:tag boolean}) + [exprs] + (every? #(or (nil? %) (symbol? %) (string? %) (number? %) (true? %) (false? %)) exprs)) + +(defn- valid-arity? + #?(:cljs {:tag boolean}) + [argc method-params] + (or (nil? method-params) ; Assume valid if method-params unavailable + (boolean (some #{argc} (map count method-params))))) + +(defn- record-tag? + [tag] + (boolean (and (symbol? tag) + (some? (namespace tag)) + (get-in @env/*compiler* [::namespaces (symbol (namespace tag)) :defs (symbol (name tag)) :record])))) + +(defn- record-basis + [tag] + (let [positional-factory (symbol (str "->" (name tag))) + fields (first (get-in @env/*compiler* [::namespaces (symbol (namespace tag)) :defs positional-factory :method-params]))] + (into #{} fields))) + +(defn- record-with-field? + [tag field] + (and (record-tag? tag) + (contains? (record-basis tag) field))) + +(defn- invalid-arity? [argc method-params variadic max-fixed-arity] + (and (not (valid-arity? argc method-params)) + (or (not variadic) + (and variadic (< argc max-fixed-arity))))) + +(defn parse-invoke* + [env [f & args :as form]] + (let [enve (assoc env :context :expr) + fexpr (analyze enve f) + argc (count args) + fn-var? (or (-> fexpr :info :fn-var) + (-> fexpr :info :js-fn-var)) + kw? (= 'cljs.core/Keyword (:tag fexpr)) + cur-ns (-> env :ns :name) + HO-invoke? (and (boolean *cljs-static-fns*) + (not fn-var?) + (not (js-tag? f)) + (not kw?) + (not (analyzed? f))) + ;; function expressions, eg: ((deref m) x) or ((:x m) :a) + bind-f-expr? (and HO-invoke? + (not (symbol? f))) + ;; Higher order invokes with (some) argument expressions. Bind the arguments + ;; to avoid exponential complexity that is created by the IFn arity check branch. + bind-args? (and HO-invoke? + (not (all-values? args)))] + (when ^boolean fn-var? + (let [{^boolean variadic :variadic? :keys [max-fixed-arity method-params name unaliased-name ns macro]} (:info fexpr)] + ;; don't warn about invalid arity when compiling a macros namespace + ;; that requires itself, as that code is not meant to be executed in the + ;; `$macros` ns - António Monteiro + (when (and #?(:cljs (not (and (gstring/endsWith (str cur-ns) "$macros") + (symbol-identical? cur-ns ns) + (true? macro)))) + (invalid-arity? argc method-params variadic max-fixed-arity)) + (warning :fn-arity env {:name (or unaliased-name name) :argc argc})))) + (when (and kw? (not (or (== 1 argc) (== 2 argc)))) + (warning :fn-arity env {:name (first form) :argc argc})) + (let [deprecated? (-> fexpr :info :deprecated) + no-warn? (-> form meta :deprecation-nowarn)] + (when (and (boolean deprecated?) + (not (boolean no-warn?))) + (warning :fn-deprecated env {:fexpr fexpr}))) + (when (some? (-> fexpr :info :type)) + (warning :invoke-ctor env {:fexpr fexpr})) + (if (or bind-args? bind-f-expr?) + (let [arg-syms (when bind-args? (take argc (repeatedly gensym))) + f-sym (when bind-f-expr? (gensym "fexpr__")) + bindings (cond-> [] + bind-args? (into (interleave arg-syms args)) + bind-f-expr? (conj f-sym (analyzed f))) + tag (:tag (meta form))] + (analyze env + `(let [~@bindings] + ~(with-meta + `(~(analyzed (if bind-f-expr? f-sym f)) + ~@(if bind-args? arg-syms args)) + {:tag tag})))) + (let [ana-expr #(analyze enve %) + argexprs (mapv ana-expr args)] + (if (and (and (keyword? f) + (nil? (namespace f))) + (== 1 (count args)) + (record-with-field? (:tag (first argexprs)) (symbol (name f)))) + (let [field-access-form (list* (symbol (str ".-" (name f))) args)] + (no-warn (analyze env field-access-form))) + {:env env :op :invoke :form form :fn fexpr :args argexprs + :children [:fn :args]}))))) + +(defn parse-invoke + [env form] + (disallowing-recur (parse-invoke* env form))) + +(defn desugar-dotted-expr [{:keys [op] :as expr}] + (case op + (:var :local) (if (dotted-symbol? (symbol (name (:name expr)))) + (let [s (name (:name expr)) + idx (.lastIndexOf s ".") + _ (assert (not= (inc idx) (count s))) + prefix (with-meta (symbol (namespace (:name expr)) (subs s 0 idx)) + (meta (:form expr))) + field (symbol (subs s (inc idx)))] + (assert (not (:const-expr expr))) + {:op :host-field + :env (:env expr) + :form (list '. prefix field) + ;; goog.module vars get converted to the form of + ;; current.ns/goog$module.theDef, we need to dissoc + ;; actual extern var info so we get something well-formed + :target (desugar-dotted-expr (-> (dissoc expr :info) + (assoc :name prefix + :form prefix) + (dissoc :tag) + (assoc-in [:info :name] prefix) + (assoc-in [:env :context] :expr))) + :field field + :tag (:tag expr) + ;; in the case of goog.module var if there is :info, + ;; we need to adopt it now as this is where :ret-tag info lives + :info (:info expr) + :children [:target]}) + expr) + ;:var + expr)) + + +(defn analyze-symbol + "Finds the var associated with sym" + [env sym] + (if ^boolean (:quoted? env) + (do + (register-constant! env sym) + (analyze-wrap-meta {:op :const :val sym :env env :form sym :tag 'cljs.core/Symbol})) + (let [{:keys [line column]} (meta sym) + env (if-not (nil? line) + (assoc env :line line) + env) + env (if-not (nil? column) + (assoc env :column column) + env) + ret {:env env :form sym} + lcls (:locals env)] + (if-some [lb (handle-symbol-local sym (get lcls sym))] + (merge + (assoc ret :op :local :info lb) + ;; this is a temporary workaround for core.async see CLJS-3030 - David + (when (map? lb) + (select-keys lb [:name :local :arg-id :variadic? :init]))) + (let [sym-meta (meta sym) + sym-ns (namespace sym) + sym-name (name sym) + cur-ns (str (-> env :ns :name)) + ;; when compiling a macros namespace that requires itself, we need + ;; to resolve calls to `my-ns.core/foo` to `my-ns.core$macros/foo` + ;; to avoid undeclared variable warnings - António Monteiro + #?@(:cljs [sym (if (and sym-ns + (not= sym-ns "cljs.core") + (gstring/endsWith cur-ns "$macros") + (not (gstring/endsWith sym-ns "$macros")) + (= sym-ns (subs cur-ns 0 (- (count cur-ns) 7)))) + (symbol (str sym-ns "$macros") (name sym)) + sym)])] + (if (and sym-ns + (nil? (resolve-ns-alias env sym-ns nil)) + (not= ".." sym-name) ;; special case `..` macro in self-hosted + (or (= "new" sym-name) + (string/starts-with? sym-name "."))) + (merge + {:op :qualified-method + :env env + :form sym + :class (analyze-symbol (assoc env :context :expr) (symbol sym-ns))} + (if (= "new" sym-name) + {:kind :new + :name (symbol sym-name)} + {:kind :method + :name (symbol (subs sym-name 1))})) + (let [info (if-not (contains? sym-meta ::analyzed) + (resolve-existing-var env sym) + (resolve-var env sym))] + (assert (:op info) (:op info)) + (desugar-dotted-expr + (if-not (true? (:def-var env)) + (merge + (assoc ret :info info) + (select-keys info [:op :name :ns :tag]) + (when-let [const-expr (:const-expr info)] + {:const-expr const-expr})) + (let [info (resolve-var env sym)] + (merge (assoc ret :op :var :info info) + (select-keys info [:op :name :ns :tag])))))))))))) + +(defn excluded? + #?(:cljs {:tag boolean}) + [env sym] + (or (some? (gets env :ns :excludes sym)) + (some? (gets @env/*compiler* ::namespaces (gets env :ns :name) :excludes sym)))) + +(defn used? + #?(:cljs {:tag boolean}) + [env sym] + (or (some? (gets env :ns :use-macros sym)) + (some? (gets @env/*compiler* ::namespaces (gets env :ns :name) :use-macros sym)))) + +(defn get-expander-ns [env ^String nstr] + ;; first check for clojure.* -> cljs.* cases + (let [res (or (resolve-macro-ns-alias env nstr nil) + (resolve-ns-alias env nstr nil)) + nstr (if (some? res) (str res) nstr)] + (cond + #?@(:clj [(= "clojure.core" nstr) (find-ns 'cljs.core)] + :cljs [(identical? "clojure.core" nstr) (find-macros-ns impl/CLJS_CORE_MACROS_SYM)]) + #?@(:clj [(= "clojure.repl" nstr) (find-ns 'cljs.repl)] + :cljs [(identical? "clojure.repl" nstr) (find-macros-ns 'cljs.repl)]) + #?@(:clj [(.contains nstr ".") (find-ns (symbol nstr))] + :cljs [(goog.string/contains nstr ".") (find-macros-ns (symbol nstr))]) + :else + (or (some-> env :ns :require-macros (get (symbol nstr)) #?(:clj find-ns + :cljs find-macros-ns)) + ;; single segment namespace case + #?(:clj (find-ns (symbol nstr)) + :cljs (find-macros-ns (symbol nstr))))))) + +(defn get-expander* [sym env] + (when-not (or (some? (gets env :locals sym)) ; locals hide macros + (and (excluded? env sym) (not (used? env sym)))) + (let [nstr (namespace sym)] + (cond + (some? nstr) + (let [ns (get-expander-ns env nstr)] + (when (some? ns) + (.findInternedVar ^clojure.lang.Namespace ns (symbol (name sym))))) + + (some? (gets env :ns :rename-macros sym)) + (let [qualified-symbol (gets env :ns :rename-macros sym) + nsym (symbol (namespace qualified-symbol)) + sym (symbol (name qualified-symbol))] + (.findInternedVar ^clojure.lang.Namespace + #?(:clj (find-ns nsym) :cljs (find-macros-ns nsym)) sym)) + + :else + (let [nsym (gets env :ns :use-macros sym)] + (if (and (some? nsym) (symbol? nsym)) + (.findInternedVar ^clojure.lang.Namespace + #?(:clj (find-ns nsym) :cljs (find-macros-ns nsym)) sym) + ;; can't be done as compiler pass because macros get to run first + (when-not (and (lite-mode?) (= 'vector sym)) + (.findInternedVar ^clojure.lang.Namespace + #?(:clj (find-ns 'cljs.core) :cljs (find-macros-ns impl/CLJS_CORE_MACROS_SYM)) sym)))))))) + +(defn get-expander + "Given a sym, a symbol identifying a macro, and env, an analysis environment + return the corresponding Clojure macroexpander." + [sym env] + (let [mvar (get-expander* sym env)] + (when (and (some? mvar) + #?(:clj (.isMacro ^clojure.lang.Var mvar) + :cljs ^boolean (.isMacro mvar))) + mvar))) + +#?(:cljs + (let [cached-var (delay (get (ns-interns* 'cljs.spec.alpha) 'macroexpand-check))] + (defn get-macroexpand-check-var [] + (when (some? (find-ns-obj 'cljs.spec.alpha)) + @cached-var)))) + +(defn- var->sym [var] + #?(:clj (symbol (str (.-ns ^clojure.lang.Var var)) (str (.-sym ^clojure.lang.Var var))) + :cljs (.-sym var))) + +(defn- do-macroexpand-check + [env form mac-var] + (when (not (-> @env/*compiler* :options :spec-skip-macros)) + (let [mchk #?(:clj (some-> (find-ns 'clojure.spec.alpha) + (ns-resolve 'macroexpand-check)) + :cljs (get-macroexpand-check-var))] + (when (some? mchk) + (try + (mchk mac-var (next form)) + (catch #?(:clj Throwable :cljs :default) e + (throw (ex-info nil (error-data env :macro-syntax-check (var->sym mac-var)) e)))))))) + +#?(:cljs + (defn- check-macro-arity [mac-var form] + (let [mac-sym (.-sym mac-var)] + (when-let [{:keys [variadic? max-fixed-arity method-params]} + (get-in @env/*compiler* [::namespaces (symbol (namespace mac-sym)) :defs (symbol (name mac-sym))])] + (let [argc (count (rest form)) + offset (if (= '&form (ffirst method-params)) 2 0)] + (when (invalid-arity? argc (map #(nthrest %1 offset) method-params) + variadic? (when max-fixed-arity (- max-fixed-arity offset))) + (throw (js/Error. (error-message :fn-arity {:argc argc, :name mac-sym}))))))))) + +(defn macroexpand-1* + [env form] + (if (seq? form) + (let [op (first form)] + (if (contains? specials op) + (do + (when (= 'ns op) + (do-macroexpand-check env form (get-expander 'cljs.core/ns-special-form env))) + form) + ;else + (if-some [mac-var (when (symbol? op) (get-expander op env))] + (#?@(:clj [binding [*ns* (create-ns *cljs-ns*)]] + :cljs [do]) + (do-macroexpand-check env form mac-var) + (let [form' (try + #?(:cljs (check-macro-arity mac-var form)) + (apply @mac-var form env (rest form)) + #?(:clj (catch ArityException e + (throw (ArityException. (- (.actual e) 2) (.name e))))) + (catch #?(:clj Throwable :cljs :default) e + (throw (ex-info nil (error-data env :macroexpansion (var->sym mac-var)) e))))] + (if #?(:clj (seq? form') :cljs (impl/cljs-seq? form')) + (let [sym' (first form') + sym (first form)] + (if #?(:clj (= sym' 'js*) + :cljs (symbol-identical? sym' impl/JS_STAR_SYM)) + (let [sym (if (some? (namespace sym)) + sym + (symbol "cljs.core" (str sym))) + js-op {:js-op sym} + numeric #?(:clj (-> mac-var meta ::numeric) + :cljs (let [mac-var-ns (symbol (namespace (.-sym mac-var))) + mac-var-name (symbol (name (.-sym mac-var)))] + (get-in @env/*compiler* + [::namespaces mac-var-ns :defs mac-var-name :meta ::numeric]))) + js-op (if (true? numeric) + (assoc js-op :numeric true) + js-op)] + (vary-meta form' merge js-op)) + form')) + form'))) + (if (symbol? op) + (let [opname (str op)] + (cond + (identical? \. + #?(:clj (first opname) + :cljs (.charAt opname 0))) + (let [[target & args] (next form)] + (with-meta (list* #?(:clj '. :cljs impl/DOT_SYM) target (symbol (subs opname 1)) args) + (meta form))) + + (identical? \. + #?(:clj (last opname) + :cljs (.charAt opname (dec (. opname -length))))) + (with-meta + (list* #?(:clj 'new :cljs impl/NEW_SYM) (symbol (subs opname 0 (dec (count opname)))) (next form)) + (meta form)) + + :else form)) + form)))) + form)) + +(defn macroexpand-1 + "Given a env, an analysis environment, and form, a ClojureScript form, + macroexpand the form once." + [env form] + (wrapping-errors env (macroexpand-1* env form))) + +(declare analyze-list) + +(defn analyze-seq* [op env form name opts] + (if (contains? specials op) + (parse op env form name opts) + (parse-invoke env form))) + +(defn analyze-seq*-wrap [op env form name opts] + (wrapping-errors env + (analyze-seq* op env form name opts))) + +(defn analyze-seq + ([env form name] + (analyze-seq env form name + (when env/*compiler* + (:options @env/*compiler*)))) + ([env form name opts] + (if ^boolean (:quoted? env) + (analyze-list env form) + (let [line (-> form meta :line) + line (if (nil? line) + (:line env) + line) + col (-> form meta :column) + col (if (nil? col) + (:column env) + col) + env (assoc env :line line :column col)] + (let [op (first form)] + (when (nil? op) + (throw (error env "Can't call nil"))) + (let [mform (macroexpand-1 env form)] + (if (identical? form mform) + (analyze-seq*-wrap op env form name opts) + (analyze env mform name opts)))))))) + +(defn analyze-map + [env form] + (let [expr-env (assoc env :context :expr) + ks (disallowing-recur (mapv #(analyze expr-env %) (keys form))) + vs (disallowing-recur (mapv #(analyze expr-env %) (vals form)))] + (analyze-wrap-meta {:op :map :env env :form form + :keys ks :vals vs + :children [:keys :vals] + :tag 'cljs.core/IMap}))) + +;; :list is not used in the emitter any more, but analyze-list is called from analyze-const +;; to hit the `register-constant!` cases for symbols and keywords. +(defn analyze-list + [env form] + (let [expr-env (assoc env :context :expr) + items (disallowing-recur (mapv #(analyze expr-env %) form))] + (analyze-wrap-meta {:op :list :env env :form form :items items :children [:items] :tag 'cljs.core/IList}))) + +(defn analyze-vector + [env form] + (let [expr-env (assoc env :context :expr) + items (disallowing-recur (mapv #(analyze expr-env %) form))] + (analyze-wrap-meta {:op :vector :env env :form form :items items :children [:items] :tag 'cljs.core/IVector}))) + +(defn analyze-set + [env form] + (let [expr-env (assoc env :context :expr) + items (disallowing-recur (mapv #(analyze expr-env %) form))] + (analyze-wrap-meta {:op :set :env env :form form :items items :children [:items] :tag 'cljs.core/ISet}))) + +(defn analyze-js-value + [env ^JSValue form] + (let [val (.-val form) + expr-env (assoc env :context :expr)] + (if (map? val) + (let [keys (vec (keys val)) + vals (disallowing-recur + (mapv #(analyze expr-env %) (vals val)))] + {:op :js-object + :env env + :form form + :keys keys + :vals vals + :children [:vals] + :tag 'object}) + (let [items (disallowing-recur + (mapv #(analyze expr-env %) val))] + {:op :js-array + :env env + :form form + :items items + :children [:items] + :tag 'array})))) + +(defn record-ns+name [x] + (map symbol + #?(:clj + ((juxt (comp #(string/join "." %) butlast) last) + (string/split (.getName ^Class (type x)) #"\.")) + :cljs + (string/split (pr-str (type x)) #"/")))) + +(defn analyze-record + [env x] + (let [;; register constansts + _items_ (disallowing-recur + (analyze (assoc env :context :expr) (into {} x))) + [ns name] (record-ns+name x)] + {:op :const + :val x + :env env + :form x + :tag (symbol (str ns) (str name))})) + +(defn elide-reader-meta [m] + (dissoc m :file :line :column :end-column :end-line :source)) + +(defn elide-analyzer-meta [m] + (dissoc m ::analyzed)) + +(defn elide-irrelevant-meta [m] + (-> m elide-reader-meta elide-analyzer-meta)) + +(defn analyze-wrap-meta [expr] + (let [form (:form expr) + m (elide-irrelevant-meta (meta form))] + (if (some? (seq m)) + (let [env (:env expr) ; take on expr's context ourselves + expr (assoc-in expr [:env :context] :expr) ; change expr to :expr + meta-expr (analyze-map (:env expr) m)] + {:op :with-meta :env env :form form + :meta meta-expr :expr expr :children [:meta :expr]}) + expr))) + +(defn infer-type [env {:keys [tag] :as ast} _] + (if (or (nil? tag) (= 'function tag)) + ;; infer-type won't get a chance to process :methods + ;; so treat :fn as a special case for now, could probably + ;; fix up to use :children to walk child nodes + (if (= :fn (:op ast)) + (update ast :methods + (fn [ms] (into [] (map #(infer-type env % _)) ms))) + (if-some [tag (infer-tag env ast)] + (assoc ast :tag tag) + ast)) + ast)) + +(defn- repl-self-require? [env deps] + (and (:repl-env env) (some #{*cljs-ns*} deps))) + +#?(:clj + (defn ns-side-effects + [env {:keys [op] :as ast} opts] + (if (#{:ns :ns*} op) + (let [{:keys [name deps uses require-macros use-macros reload reloads]} ast] + (when (and *analyze-deps* (seq deps)) + (analyze-deps + (if (repl-self-require? env deps) 'cljs.user name) + deps env (dissoc opts :macros-ns))) + (if *load-macros* + (do + (load-core) + (doseq [nsym (vals use-macros)] + (let [k (or (:use-macros reload) + (get-in reloads [:use-macros nsym]) + (and (= nsym name) *reload-macros* :reload))] + (if k + (locking load-mutex + (clojure.core/require nsym k)) + (locking load-mutex + (clojure.core/require nsym))) + (intern-macros nsym k))) + (doseq [nsym (vals require-macros)] + (let [k (or (:require-macros reload) + (get-in reloads [:require-macros nsym]) + (and (= nsym name) *reload-macros* :reload))] + (if k + (locking load-mutex + (clojure.core/require nsym k)) + (locking load-mutex + (clojure.core/require nsym))) + (intern-macros nsym k))) + (-> ast + (check-use-macros-inferring-missing env) + (check-rename-macros-inferring-missing env))) + (do + (check-uses + (when (and *analyze-deps* (seq uses)) + (missing-uses uses env)) + env) + ast))) + ast))) + +;; A set of validators that can be used to do static type +;; checking of runtime fns based on inferred argument types. +(def invoke-arg-type-validators + (let [aget-validator {:valid? #(and (array-type? (first %)) + (every? numeric-type? (rest %))) + :warning-type :invalid-array-access} + aset-validator {:valid? #(and (array-type? (first %)) + (every? numeric-type? (butlast (rest %)))) + :warning-type :invalid-array-access}] + {'cljs.core/checked-aget aget-validator + 'cljs.core/checked-aset aset-validator + 'cljs.core/checked-aget' aget-validator + 'cljs.core/checked-aset' aset-validator})) + +(defn check-invoke-arg-types + [env {:keys [op] :as ast} opts] + (when (and (not (analyzed? ast)) + #?(:clj (= :invoke op) + :cljs (keyword-identical? :invoke op))) + (when-some [[name {:keys [valid? warning-type]}] (find invoke-arg-type-validators (-> ast :fn :info :name))] + (let [types (mapv :tag (:args ast))] + (when-not (valid? types) + (warning warning-type env + {:name name + :types types}))))) + (analyzed ast)) + +#?(:clj + (defn analyze-form [env form name opts] + (cond + (symbol? form) (analyze-symbol env form) + (and (seq? form) (seq form)) (analyze-seq env form name opts) + (record? form) (analyze-record env form) + (map? form) (analyze-map env form) + (vector? form) (analyze-vector env form) + (set? form) (analyze-set env form) + (keyword? form) (analyze-keyword env form) + (instance? JSValue form) (analyze-js-value env form) + :else + (let [tag (cond + (nil? form) 'clj-nil + (number? form) 'number + (string? form) 'string + (instance? Character form) 'string + (true? form) 'boolean + (false? form) 'boolean + (= () form) 'cljs.core/IList)] + (cond-> {:op :const :val form :env env :form form} + tag (assoc :tag tag)))))) + +#?(:cljs + (defn analyze-form [env form name opts] + (cond + (symbol? form) (analyze-symbol env form) + (and (impl/cljs-seq? form) (some? (seq form))) (analyze-seq env form name opts) + (record? form) (analyze-record env form) + (impl/cljs-map? form) (analyze-map env form) + (impl/cljs-vector? form) (analyze-vector env form) + (impl/cljs-set? form) (analyze-set env form) + (keyword? form) (analyze-keyword env form) + (instance? cljs.tagged-literals/JSValue form) (analyze-js-value env form) + :else + (let [tag (cond + (nil? form) impl/CLJ_NIL_SYM + (number? form) impl/NUMBER_SYM + (string? form) impl/STRING_SYM + (true? form) impl/BOOLEAN_SYM + (false? form) impl/BOOLEAN_SYM + (= () form) 'cljs.core/IList)] + (cond-> {:op :const :val form :env env :form form} + tag (assoc :tag tag)))))) + +(def default-passes + #?(:clj [infer-type and-or/optimize check-invoke-arg-types ns-side-effects] + :cljs [infer-type and-or/optimize check-invoke-arg-types])) + +(defn analyze* [env form name opts] + (let [passes (cond-> (or *passes* default-passes) + (lite-mode?) (conj lite/use-lite-types)) + form (if (instance? LazySeq form) + (if (seq form) form ()) + form) + ast (analyze-form env form name opts)] + (reduce (fn [ast pass] (pass env ast opts)) ast passes))) + +(defn analyze + "Given an environment, a map containing {:locals (mapping of names to bindings), :context + (one of :statement, :expr, :return), :ns (a symbol naming the + compilation ns)}, and form, returns an expression object (a map + containing at least :form, :op and :env keys). If expr has any (immediately) + nested exprs, must have a :children entry. This must be a vector of keywords naming + the immediately nested fields mapped to an expr or vector of exprs. This will + facilitate code walking without knowing the details of the op set." + ([env form] (analyze env form nil)) + ([env form name] + (analyze env form name + (when env/*compiler* + (:options @env/*compiler*)))) + ([env form name opts] + (wrapping-errors env + (if (analyzed? form) + (no-warn (analyze* env form name opts)) + (analyze* env form name opts))))) + +(defn add-consts + "Given a compiler state and a map from fully qualified symbols to constant + EDN values, update the compiler state marking these vars as const to support + direct substitution of these vars in source." + [compiler-state constants-map] + (reduce-kv + (fn [compiler-state sym value] + (let [ns (symbol (namespace sym))] + (update-in compiler-state + [::namespaces ns :defs (symbol (name sym))] merge + {:const-expr + (binding [*passes* (conj *passes* (replace-env-pass {:context :expr}))] + (analyze (empty-env) value))}))) + compiler-state constants-map)) + +#?(:clj + (defn- source-path + "Returns a path suitable for providing to tools.reader as a 'filename'." + [x] + (cond + (instance? File x) (.getAbsolutePath ^File x) + :default (str x)))) + +(defn resolve-symbol [sym] + (if (and (not (namespace sym)) + (dotted-symbol? sym)) + sym + (:name (binding [*private-var-access-nowarn* true] + (resolve-var (assoc @env/*compiler* :ns (get-namespace *cljs-ns*)) + sym))))) + +(defn get-aliases + "Get all alias maps for a namespace." + [ns] + (apply merge + ((juxt :requires :require-macros :as-aliases) + (get-namespace ns)))) + +#?(:clj + (defn get-bridged-alias-map + "Returns clojure.tools.reader/*alias-map* for bridging" + [] + (try + @(ns-resolve 'clojure.tools.reader '*alias-map*) + (catch Throwable t + nil)))) + +#?(:clj + (defn forms-seq* + "Seq of Clojure/ClojureScript forms from rdr, a java.io.Reader. Optionally + accepts a filename argument which will be used in any emitted errors." + ([^Reader rdr] (forms-seq* rdr nil)) + ([^Reader rdr filename] + {:pre [(instance? Reader rdr)]} + (let [eof-sentinel (Object.) + opts (merge + {:eof eof-sentinel} + (if (and filename (= (util/ext filename) "cljc")) + {:read-cond :allow :features #{:cljs}})) + pbr (readers/indexing-push-back-reader + (PushbackReader. rdr) 1 filename) + data-readers (merge tags/*cljs-data-readers* + (load-data-readers)) + forms-seq_ + (fn forms-seq_ [] + (lazy-seq + (let [form (binding [*ns* (create-ns *cljs-ns*) + reader/*data-readers* data-readers + reader/*alias-map* (get-aliases *cljs-ns*) + reader/resolve-symbol resolve-symbol] + (reader/read opts pbr))] + (if (identical? form eof-sentinel) + (.close rdr) + (cons form (forms-seq_))))))] + (forms-seq_))))) + +#?(:clj + (defn forms-seq + "DEPRECATED: Seq of Clojure/ClojureScript forms from [f], which can be anything + for which `clojure.java.io/reader` can produce a `java.io.Reader`. Optionally + accepts a [filename] argument, which the reader will use in any emitted errors." + ([f] (forms-seq f (source-path f))) + ([f filename] (forms-seq f filename false)) + ([f filename return-reader?] + (let [rdr (io/reader f) + pbr (readers/indexing-push-back-reader + (PushbackReader. rdr) 1 filename) + data-readers (merge tags/*cljs-data-readers* + (load-data-readers)) + forms-seq* + (fn forms-seq* [] + (lazy-seq + (let [eof-sentinel (Object.) + form (binding [*ns* (create-ns *cljs-ns*) + reader/*data-readers* data-readers + reader/*alias-map* + (apply merge + ((juxt :requires :require-macros) + (get-namespace *cljs-ns*)))] + (reader/read pbr nil eof-sentinel))] + (if (identical? form eof-sentinel) + (.close rdr) + (cons form (forms-seq*))))))] + (if (true? return-reader?) + [(forms-seq*) rdr] + (forms-seq*)))))) + +#?(:clj + (defn gen-user-ns + [src] + (if (sequential? src) + (symbol (str "cljs.user.source$form$" (util/content-sha (pr-str src) 7))) + (let [full-name (str src) + name (.substring full-name + (inc (.lastIndexOf full-name "/")) + (.lastIndexOf full-name "."))] + (symbol (str "cljs.user." name (util/content-sha full-name 7))))))) + +#?(:clj + (defn ^:dynamic parse-ns + "Helper for parsing only the essential namespace information from a + ClojureScript source file and returning a cljs.closure/IJavaScript compatible + map _not_ a namespace AST node. + + By default does not load macros or perform any analysis of dependencies. If + opts parameter provided :analyze-deps and :load-macros keys their values will + be used for *analyze-deps* and *load-macros* bindings respectively. This + function does _not_ side-effect the ambient compilation environment unless + requested via opts where :restore is false." + ([src] + (parse-ns src nil + (when env/*compiler* + (:options @env/*compiler*)))) + ([src opts] (parse-ns src nil opts)) + ([src dest opts] + (ensure + (let [src (if (symbol? src) + (util/ns->source src) + src) + ijs + (binding [env/*compiler* (if (false? (:restore opts)) + env/*compiler* + (atom @env/*compiler*)) + *cljs-ns* 'cljs.user + *cljs-file* src + *macro-infer* + (or (when (contains? opts :macro-infer) + (:macro-infer opts)) + false) + *analyze-deps* + (or (when (contains? opts :analyze-deps) + (:analyze-deps opts)) + false) + *load-macros* + (or (when (contains? opts :load-macros) + (:load-macros opts)) + false)] + (let [rdr (when-not (sequential? src) (io/reader src))] + (try + (loop [forms (if rdr + (forms-seq* rdr (source-path src)) + src) + ret (merge + {:file dest + :source-file (when rdr src) + :source-forms (when-not rdr src) + :macros-ns (:macros-ns opts) + :requires (cond-> #{'cljs.core} + (get-in @env/*compiler* [:options :emit-constants]) + (conj constants-ns-sym))} + (when (and dest (.exists ^File dest)) + {:lines (with-open [reader (io/reader dest)] + (-> reader line-seq count))}))] + (if (seq forms) + (let [env (empty-env) + ast (no-warn (analyze env (first forms) nil opts))] + (cond + (= :ns (:op ast)) + (let [ns-name (:name ast) + ns-name (if (and (= 'cljs.core ns-name) + (= "cljc" (util/ext src))) + 'cljs.core$macros + ns-name) + deps (merge (:uses ast) (:requires ast))] + (merge + {:ns (or ns-name 'cljs.user) + :provides [ns-name] + :requires (if (= 'cljs.core ns-name) + (set (vals deps)) + (cond-> (conj (set (vals deps)) 'cljs.core) + (get-in @env/*compiler* [:options :emit-constants]) + (conj constants-ns-sym))) + :file dest + :source-file (when rdr src) + :source-forms (when-not rdr src) + :ast ast + :macros-ns (or (:macros-ns opts) + (= 'cljs.core$macros ns-name))} + (when (and dest (.exists ^File dest)) + {:lines (with-open [reader (io/reader dest)] + (-> reader line-seq count))}))) + + (= :ns* (:op ast)) + (let [deps (merge (:uses ast) (:requires ast))] + (recur (rest forms) + (cond-> (update-in ret [:requires] into (set (vals deps))) + ;; we need to defer generating the user namespace + ;; until we actually need or it will break when + ;; `src` is a sequence of forms - António Monteiro + (not (:ns ret)) + (assoc :ns (gen-user-ns src) :provides [(gen-user-ns src)])))) + + :else ret)) + ret)) + (finally + (when rdr + (.close ^Reader rdr))))))] + (cond-> ijs + (not (contains? ijs :ns)) + (merge + {:ns (gen-user-ns src) + :provides [(gen-user-ns src)]}))))))) + +#?(:clj + (defn- cache-analysis-ext + ([] (cache-analysis-ext (get-in @env/*compiler* [:options :cache-analysis-format] :transit))) + ([format] + (if (and (= format :transit) @transit) "json" "edn")))) + +#?(:clj + (defn build-affecting-options [opts] + (select-keys opts + [:static-fns :fn-invoke-direct :optimize-constants :elide-asserts :target :nodejs-rt + :cache-key :checked-arrays :language-out :optimizations :lite-mode :elide-to-string]))) + +#?(:clj + (defn build-affecting-options-sha [path opts] + (let [m (assoc (build-affecting-options opts) :path path)] + (util/content-sha (pr-str m) 7)))) + +#?(:clj + (defn ^File cache-base-path + ([path] + (cache-base-path path nil)) + ([path opts] + (io/file (System/getProperty "user.home") + ".cljs" ".aot_cache" (util/clojurescript-version) + (build-affecting-options-sha path opts))))) + +#?(:clj + (defn cacheable-files + ([rsrc ext] + (cacheable-files rsrc ext nil)) + ([rsrc ext opts] + (let [{:keys [ns]} (parse-ns rsrc) + path (cache-base-path (util/path rsrc) opts) + name (util/ns->relpath ns nil File/separatorChar)] + (into {} + (map + (fn [[k v]] + [k (io/file path + (if (and (= (str "cljs" File/separatorChar "core$macros") name) + (= :source k)) + (str "cljs" File/separatorChar "core.cljc") + (str name v)))])) + {:source (str "." ext) + :output-file ".js" + :source-map ".js.map" + :analysis-cache-edn (str "." ext ".cache.edn") + :analysis-cache-json (str "." ext ".cache.json")}))))) + +#?(:clj + (defn cache-file + "Given a ClojureScript source file returns the read/write path to the analysis + cache file. Defaults to the read path which is usually also the write path." + ([src] (cache-file src "out")) + ([src output-dir] (cache-file src (parse-ns src) output-dir)) + ([src ns-info output-dir] + (cache-file src ns-info output-dir :read nil)) + ([src ns-info output-dir mode] + (cache-file src ns-info output-dir mode nil)) + ([src ns-info output-dir mode opts] + {:pre [(map? ns-info)]} + (let [ext (cache-analysis-ext)] + (if-let [core-cache + (and (= mode :read) + (= (:ns ns-info) 'cljs.core) + (io/resource (str "cljs/core.cljs.cache.aot." ext)))] + core-cache + (let [aot-cache-file + (when (util/url? src) + ((keyword (str "analysis-cache-" ext)) + (cacheable-files src (util/ext src) opts)))] + (if (and aot-cache-file (.exists ^File aot-cache-file)) + aot-cache-file + (let [target-file (util/to-target-file output-dir ns-info + (util/ext (:source-file ns-info)))] + (io/file (str target-file ".cache." ext)))))))))) + +#?(:clj + (defn requires-analysis? + "Given a src, a resource, and output-dir, a compilation output directory + return true or false depending on whether src needs to be (re-)analyzed. + Can optionally pass cache, the analysis cache file." + ([src] (requires-analysis? src "out")) + ([src output-dir] + (let [cache (cache-file src output-dir)] + (requires-analysis? src cache output-dir nil))) + ([src cache output-dir] + (requires-analysis? src cache output-dir nil)) + ([src cache output-dir opts] + (cond + (util/url? cache) + (let [path (.getPath ^URL cache)] + (if (or (.endsWith path "cljs/core.cljs.cache.aot.edn") + (.endsWith path "cljs/core.cljs.cache.aot.json")) + false + (throw (Exception. (str "Invalid anlaysis cache, must be file not URL " cache))))) + + (and (util/file? cache) + (not (.exists ^File cache))) + true + + :else + (let [out-src (util/to-target-file output-dir (parse-ns src)) + cache-src (:output-file (cacheable-files src (util/ext src) opts))] + (if (and (not (.exists out-src)) + (not (.exists ^File cache-src))) + true + (or (not cache) (util/changed? src cache)))))))) + +#?(:clj + (defn- get-spec-vars + [] + (when-let [spec-ns (find-ns 'cljs.spec.alpha)] + (locking load-mutex + {:registry-ref (ns-resolve spec-ns 'registry-ref) + :speced-vars (ns-resolve spec-ns '_speced_vars)}))) + :cljs + (let [registry-ref (delay (get (ns-interns* 'cljs.spec.alpha$macros) 'registry-ref)) + ;; Here, we look up the symbol '-speced-vars because ns-interns* + ;; is implemented by invoking demunge on the result of js-keys. + speced-vars (delay (get (ns-interns* 'cljs.spec.alpha$macros) '-speced-vars))] + (defn- get-spec-vars [] + (when (some? (find-ns-obj 'cljs.spec.alpha$macros)) + {:registry-ref @registry-ref + :speced-vars @speced-vars})))) + +(defn dump-specs + "Dumps registered speced vars for a given namespace into the compiler + environment." + [ns] + (let [spec-vars (get-spec-vars) + ns-str (str ns)] + (swap! env/*compiler* update-in [::namespaces ns] + merge + (when-let [registry-ref (:registry-ref spec-vars)] + {:cljs.spec/registry-ref + (into [] + (filter (fn [[k _]] (= ns-str (namespace k)))) + @@registry-ref)}) + (when-let [speced-vars (:speced-vars spec-vars)] + {:cljs.spec/speced-vars + (into [] + (filter + (fn [v] + (or (= ns-str (namespace v)) + (= ns (-> v meta :fdef-ns))))) + @@speced-vars)})))) + +(defn register-specs + "Registers speced vars found in a namespace analysis cache." + [cached-ns] + #?(:clj (try + (locking load-mutex + (clojure.core/require 'cljs.spec.alpha)) + (catch Throwable t))) + (let [{:keys [registry-ref speced-vars]} (get-spec-vars)] + (when-let [registry (seq (:cljs.spec/registry-ref cached-ns))] + (when registry-ref + (swap! @registry-ref into registry))) + (when-let [vars (seq (:cljs.spec/speced-vars cached-ns))] + (when speced-vars + (swap! @speced-vars into vars))))) + +#?(:clj + (defn write-analysis-cache + ([ns cache-file] + (write-analysis-cache ns cache-file nil)) + ([ns ^File cache-file src] + (util/mkdirs cache-file) + (dump-specs ns) + (let [ext (util/ext cache-file) + analysis (dissoc (get-in @env/*compiler* [::namespaces ns]) :macros)] + (case ext + "edn" (spit cache-file + (str ";; Analyzed by ClojureScript " (util/clojurescript-version) "\n" + (pr-str analysis))) + "json" (when-let [{:keys [writer write]} @transit] + (with-open [os (io/output-stream cache-file)] + (write (writer os :json transit-write-opts) analysis))))) + (when src + (.setLastModified ^File cache-file (util/last-modified src)))))) + +#?(:clj + (defn read-analysis-cache + ([cache-file src] + (read-analysis-cache cache-file src nil)) + ([^File cache-file src opts] + ;; we want want to keep dependency analysis information + ;; don't revert the environment - David + (let [{:keys [ns]} (parse-ns src + (merge opts + {:restore false + :analyze-deps true + :load-macros true})) + ext (util/ext cache-file) + cached-ns (case ext + "edn" (edn/read-string (slurp cache-file)) + "json" (let [{:keys [reader read]} @transit] + (with-open [is (io/input-stream cache-file)] + (read (reader is :json transit-read-opts)))))] + (when (or *verbose* (:verbose opts)) + (util/debug-prn "Reading analysis cache for" (str src))) + (swap! env/*compiler* + (fn [cenv] + (do + (register-specs cached-ns) + (doseq [x (get-in cached-ns [::constants :order])] + (register-constant! x)) + (-> cenv + (assoc-in [::namespaces ns] cached-ns))))))))) + +(defn analyze-form-seq + ([forms] + (analyze-form-seq forms + (when env/*compiler* + (:options @env/*compiler*)))) + ([forms opts] + (analyze-form-seq forms opts false)) + ([forms opts return-last?] + (let [env (assoc (empty-env) :build-options opts)] + (binding [*file-defs* nil + #?@(:clj [*unchecked-if* false + *unchecked-arrays* false]) + *cljs-ns* 'cljs.user + *cljs-file* nil + reader/*alias-map* (or #?(:clj (get-bridged-alias-map)) reader/*alias-map* {})] + (loop [ns nil forms forms last-ast nil] + (if (some? forms) + (let [form (first forms) + env (assoc env :ns (get-namespace *cljs-ns*)) + ast (analyze env form nil opts)] + (if (= (:op ast) :ns) + (recur (:name ast) (next forms) ast) + (recur ns (next forms) ast))) + (if return-last? + last-ast + ns))))))) + +(defn ensure-defs + "Ensures that a non-nil defs map exists in the compiler state for a given + ns. (A non-nil defs map signifies that the namespace has been analyzed.)" + [ns] + (swap! env/*compiler* update-in [::namespaces ns :defs] #(or % {}))) + +#?(:clj + (defn analyze-file + "Given a java.io.File, java.net.URL or a string identifying a resource on the + classpath attempt to analyze it. + + This function side-effects the ambient compilation environment + `cljs.env/*compiler*` to aggregate analysis information. opts argument is + compiler options, if :cache-analysis true will cache analysis to + \":output-dir/some/ns/foo.cljs.cache.edn\". This function does not return a + meaningful value." + ([f] + (analyze-file f + (when env/*compiler* + (:options @env/*compiler*)))) + ([f opts] + (analyze-file f false opts)) + ([f skip-cache opts] + (binding [*file-defs* (atom #{}) + *unchecked-if* false + *unchecked-arrays* false + *cljs-warnings* *cljs-warnings*] + (let [output-dir (util/output-directory opts) + res (cond + (instance? File f) f + (instance? URL f) f + (re-find #"^file://" f) (URL. f) + :else (io/resource f))] + (assert res (str "Can't find " f " in classpath")) + (ensure + (let [ns-info (parse-ns res) + path (if (instance? File res) + (.getPath ^File res) + (.getPath ^URL res)) + cache (when (:cache-analysis opts) + (cache-file res ns-info output-dir :read opts))] + (when-not (get-in @env/*compiler* [::namespaces (:ns ns-info) :defs]) + (if (or skip-cache (not cache) (requires-analysis? res cache output-dir opts)) + (binding [*cljs-ns* 'cljs.user + *cljs-file* path + reader/*alias-map* (or (get-bridged-alias-map) reader/*alias-map* {})] + (when (or *verbose* (:verbose opts)) + (util/debug-prn "Analyzing" (str res))) + (let [env (assoc (empty-env) :build-options opts) + ns (with-open [rdr (io/reader res)] + (loop [ns nil forms (seq (forms-seq* rdr (util/path res)))] + (if forms + (let [form (first forms) + env (assoc env :ns (get-namespace *cljs-ns*)) + ast (analyze env form nil opts)] + (cond + (= (:op ast) :ns) + (recur (:name ast) (next forms)) + + (and (nil? ns) (= (:op ast) :ns*)) + (recur (gen-user-ns res) (next forms)) + + :else + (recur ns (next forms)))) + ns)))] + (ensure-defs ns) + (when (and cache (true? (:cache-analysis opts))) + (write-analysis-cache ns cache res)))) + (try + (read-analysis-cache cache res opts) + (catch Throwable e + (analyze-file f true opts)))))))))))) diff --git a/src/main/clojure/cljs/analyzer/api.cljc b/src/main/clojure/cljs/analyzer/api.cljc new file mode 100644 index 0000000000..2fa4f2a134 --- /dev/null +++ b/src/main/clojure/cljs/analyzer/api.cljc @@ -0,0 +1,295 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer.api + "This is intended to be a stable api for those who need programmatic access + to the analyzer." + (:refer-clojure :exclude [all-ns ns-interns ns-resolve resolve find-ns + ns-publics remove-ns the-ns]) + #?(:clj (:require [cljs.analyzer :as ana] + [cljs.env :as env] + [cljs.util :as util] + [clojure.edn :as edn] + [clojure.java.io :as io]) + :cljs (:require [cljs.analyzer :as ana] + [cljs.env :as env]))) + +;; ============================================================================= +;; Useful Utilities + +(defn empty-state + "Creates an empty compilation state Atom. The optional opts arg is a map + representing the compiler configuration. See the documentation + for details: https://clojurescript.org/reference/compiler-options" + ([] + (if-not (nil? env/*compiler*) + env/*compiler* + (env/default-compiler-env))) + ([opts] + (env/default-compiler-env opts))) + +(defn current-state + "Return the current compiler state atom." + [] + env/*compiler*) + +(defn current-file + "Return the current file under analysis or compilation." + [] + ana/*cljs-file*) + +(defn current-ns + "Return the current ns under analysis or compilation." + [] + ana/*cljs-ns*) + +(defmacro with-state + "Run the body with the given compilation state Atom." + [state & body] + `(env/with-compiler-env ~state + ~@body)) + +(defn empty-env + "Creates an empty analysis environment." + [] + (ana/empty-env)) + +(defmacro no-warn + "Disable analyzer warnings for any analysis executed in body." + [& body] + (let [no-warnings (zipmap (keys ana/*cljs-warnings*) (repeat false))] + `(binding [ana/*cljs-warnings* ~no-warnings] + ~@body))) + +(defn warning-enabled? + "Test if the given warning-type is enabled." + [warning-type] + (ana/*cljs-warnings* warning-type)) + +(defn default-warning-handler + "The default warning handler. + + Outputs the warning messages to *err*." + [warning-type env extra] + (ana/default-warning-handler warning-type env extra)) + +(defmacro with-warning-handlers + "Helper macro for custom handling of emitted warnings. Handlers should be + a vector of functions. The signature of these functions is + [warn-type env warn-info]. warn-type is a keyword describing the warning, + env is the analysis environment, and warn-info is a map of extra useful + information for a particular warning type." + [handlers & body] + `(ana/with-warning-handlers ~handlers + ~@body)) + +(defn warning-message + "Helper for generating the standard analyzer messages for warnings. Should be + passed warn-type and warn-info. See with-warning-handlers." + [warn-type warn-info] + (ana/error-message warn-type warn-info)) + +(defn enabled-warnings + "Get the enabled warning types." + [] + ana/*cljs-warnings*) + +(defn get-options + "Return the compiler options from compiler state." + ([] (get-options (current-state))) + ([state] + (get @state :options))) + +(defn get-js-index + "Return the currently computed Google Closure js dependency index from the + compiler state." + ([] (get-js-index (current-state))) + ([state] + (get @state :js-dependency-index))) + +(def + ^{:doc "ClojureScript's default analysis passes."} + default-passes ana/default-passes) + +(defmacro with-passes + "Evaluate the body with the provided sequence of compiler passes." + [passes & body] + `(binding [ana/*passes* ~passes] + ~@body)) + +#?(:clj + (defn analyze + "Given an environment, a map containing {:locals (mapping of names to bindings), :context + (one of :statement, :expr, :return), :ns (a symbol naming the + compilation ns)}, and form, returns an expression object (a map + containing at least :form, :op and :env keys). If expr has any (immediately) + nested exprs, must have :children entry. This must be a vector of keywords naming + the immediately nested fields mapped to an expr or vector of exprs. This will + facilitate code walking without knowing the details of the op set." + ([env form] (analyze env form nil)) + ([env form name] (analyze env form name nil)) + ([env form name opts] + (analyze (or (current-state) (empty-state opts)) env form name opts)) + ([state env form name opts] + (env/with-compiler-env state + (binding [ana/*cljs-warning-handlers* (:warning-handlers opts ana/*cljs-warning-handlers*)] + (ana/analyze env form name opts)))))) + +#?(:clj + (defn forms-seq + "Seq of Clojure/ClojureScript forms from rdr, a java.io.Reader. Optionally + accepts a filename argument which will be used in any emitted errors." + ([rdr] (ana/forms-seq* rdr nil)) + ([rdr filename] (ana/forms-seq* rdr filename)))) + +#?(:clj + (defn parse-ns + "Helper for parsing only the essential namespace information from a + ClojureScript source file and returning a cljs.closure/IJavaScript compatible + map _not_ a namespace AST node. + + By default does not load macros or perform any analysis of dependencies. If + opts parameter provided :analyze-deps and :load-macros keys their values will + be used for *analyze-deps* and *load-macros* bindings respectively. This + function does _not_ side-effect the ambient compilation environment unless + requested via opts where :restore is false." + ([src] (parse-ns src nil nil)) + ([src opts] (parse-ns src nil opts)) + ([src dest opts] + (parse-ns (or (current-state) (empty-state opts)) src dest opts)) + ([state src dest opts] + (env/with-compiler-env state + (binding [ana/*cljs-warning-handlers* (:warning-handlers opts ana/*cljs-warning-handlers*)] + (ana/parse-ns src dest opts)))))) + +#?(:clj + (defn analyze-file + "Given a java.io.File, java.net.URL or a string identifying a resource on the + classpath attempt to analyze it. + + This function side-effects the ambient compilation environment + `cljs.env/*compiler*` to aggregate analysis information. opts argument is + compiler options, if :cache-analysis true will cache analysis to + \":output-dir/some/ns/foo.cljs.cache.edn\". This function does not return a + meaningful value." + ([f] (analyze-file f nil)) + ([f opts] + (analyze-file (or (current-state) (empty-state opts)) f opts)) + ([state f opts] + (env/with-compiler-env state + (binding [ana/*cljs-warning-handlers* (:warning-handlers opts ana/*cljs-warning-handlers*)] + (ana/analyze-file f opts)))))) + +#?(:clj + (defn read-analysis-cache + "Read an analysis cache." + [cache-file] + (case (util/ext cache-file) + "edn" (edn/read-string (slurp cache-file)) + "json" (let [{:keys [reader read]} @ana/transit] + (with-open [is (io/input-stream cache-file)] + (read (reader is :json ana/transit-read-opts))))))) + +;; ============================================================================= +;; Main API + +(defn resolve + "Given an analysis environment resolve a var. Analogous to + clojure.core/resolve" + [env sym] + {:pre [(map? env) (symbol? sym)]} + (try + (binding [ana/*private-var-access-nowarn* true] + (ana/resolve-var env sym + (ana/confirm-var-exists-throw))) + (catch #?(:clj Exception :cljs :default) e + (ana/resolve-macro-var env sym)))) + +(defn all-ns + "Return all namespaces. Analagous to clojure.core/all-ns but + returns symbols identifying namespaces not Namespace instances." + ([] + (all-ns env/*compiler*)) + ([state] + (keys (get @state ::ana/namespaces)))) + +(defn resolve-extern + "Given a symbol attempt to look it up in the provided externs" + ([sym] + (resolve-extern env/*compiler* sym)) + ([state sym] + (let [pre (ana/->pre sym)] + (env/with-compiler-env state + (:info (ana/resolve-extern pre)))))) + +(defn find-ns + "Given a namespace return the corresponding namespace analysis map. Analagous + to clojure.core/find-ns." + ([sym] + (find-ns env/*compiler* sym)) + ([state sym] + {:pre [(symbol? sym)]} + (get-in @state [::ana/namespaces sym]))) + +(defn the-ns + "Given a namespace return the corresponding namespace analysis map, throwing an + exception if not found. Analagous to clojure.core/the-ns." + ([ns] + (the-ns env/*compiler* ns)) + ([state sym] + {:pre [(symbol? sym)]} + (or (find-ns state sym) + (throw (ex-info (str "No namespace found: " sym) {:ns sym}))))) + +(defn ns-interns + "Given a namespace return all the var analysis maps. Analagous to + clojure.core/ns-interns but returns var analysis maps not vars." + ([ns] + (ns-interns env/*compiler* ns)) + ([state ns] + {:pre [(symbol? ns)]} + (let [ns (the-ns state ns)] + (merge + (:macros ns) + (:defs ns))))) + +(defn ns-publics + "Given a namespace return all the public var analysis maps. Analagous to + clojure.core/ns-publics but returns var analysis maps not vars." + ([ns] + (ns-publics env/*compiler* ns)) + ([state ns] + {:pre [(symbol? ns)]} + (->> (ns-interns state ns) + (remove (fn [[k v]] (:private v))) + (into {})))) + +(defn ns-resolve + "Given a namespace and a symbol return the corresponding var analysis map. + Analagous to clojure.core/ns-resolve but returns var analysis map not Var." + ([ns sym] + (ns-resolve env/*compiler* ns sym)) + ([state ns sym] + {:pre [(symbol? ns) (symbol? sym)]} + (get-in @state [::ana/namespaces ns :defs sym]))) + +(defn remove-ns + "Removes the namespace named by the symbol." + ([ns] + (remove-ns env/*compiler* ns)) + ([state ns] + {:pre [(symbol? ns)]} + (swap! state update-in [::ana/namespaces] dissoc ns))) + +(defmacro in-cljs-user + "Binds cljs.analyzer/*cljs-ns* to 'cljs.user and uses the given compilation + environment atom and runs body." + [env & body] + `(binding [cljs.analyzer/*cljs-ns* 'cljs.user] + (cljs.env/with-compiler-env ~env + ~@body))) diff --git a/src/main/clojure/cljs/analyzer/impl.cljc b/src/main/clojure/cljs/analyzer/impl.cljc new file mode 100644 index 0000000000..75ebfa7d50 --- /dev/null +++ b/src/main/clojure/cljs/analyzer/impl.cljc @@ -0,0 +1,59 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer.impl) + +(def ANY_SYM 'any) + +(def BOOLEAN_OR_SEQ '#{boolean seq}) + +(def BOOLEAN_SYM 'boolean) + +#?(:cljs + (def CLJ_NIL_SYM 'clj-nil)) + +#?(:cljs + (def CLJS_CORE_MACROS_SYM 'cljs.core$macros)) + +#?(:cljs + (def CLJS_CORE_SYM 'cljs.core)) + +#?(:cljs + (def DOT_SYM '.)) + +(def IGNORE_SYM 'ignore) + +#?(:cljs + (def JS_STAR_SYM 'js*)) + +#?(:cljs + (def NEW_SYM 'new)) + +(def NOT_NATIVE '#{clj not-native}) + +#?(:cljs + (def NUMBER_SYM 'number)) + +#?(:cljs + (def STRING_SYM 'string)) + +#?(:cljs + (defn ^boolean cljs-map? [x] + (implements? IMap x))) + +#?(:cljs + (defn ^boolean cljs-seq? [x] + (implements? ISeq x))) + +#?(:cljs + (defn ^boolean cljs-vector? [x] + (implements? IVector x))) + +#?(:cljs + (defn ^boolean cljs-set? [x] + (implements? ISet x))) diff --git a/src/main/clojure/cljs/analyzer/impl/namespaces.cljc b/src/main/clojure/cljs/analyzer/impl/namespaces.cljc new file mode 100644 index 0000000000..a97c6646a1 --- /dev/null +++ b/src/main/clojure/cljs/analyzer/impl/namespaces.cljc @@ -0,0 +1,68 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer.impl.namespaces) + +(defn check-and-remove-as-alias + "Given a libspec return a map of :as-alias alias, if was present. Return the + libspec with :as-alias elided. If the libspec was *only* :as-alias do not + return it." + [libspec] + ;; ignore simple requires (symbols) and + ;; REPL stuff (keywords, i.e. :reload) + (if (or (symbol? libspec) + (keyword? libspec)) + {:libspec libspec} + (let [[lib & spec :as libspec] libspec + [pre-spec [_ alias & post-spec :as post]] (split-with (complement #{:as-alias}) spec)] + (if (seq post) + (let [libspec' (into [lib] (concat pre-spec post-spec))] + (assert (symbol? alias) + (str ":as-alias must be followed by a symbol, got: " alias)) + (cond-> {:as-alias {alias lib}} + (> (count libspec') 1) (assoc :libspec libspec'))) + {:libspec libspec})))) + +(defn check-as-alias-duplicates + [as-aliases new-as-aliases] + (doseq [[alias _] new-as-aliases] + (assert (not (contains? as-aliases alias)) + (str "Duplicate :as-alias " alias ", already in use for lib " + (get as-aliases alias))))) + +(defn elide-aliases-from-libspecs + "Given libspecs, elide all :as-alias. Return a map of :libspecs (filtered) + and :as-aliases." + ([libspecs] + (elide-aliases-from-libspecs libspecs {})) + ([libspecs as-aliases] + (let [ret {:as-aliases as-aliases + :libspecs []}] + (reduce + (fn [ret libspec] + (let [{:keys [as-alias libspec]} (check-and-remove-as-alias libspec)] + (check-as-alias-duplicates (:as-aliases ret) as-alias) + (cond-> ret + libspec (update :libspecs conj libspec) + as-alias (update :as-aliases merge as-alias)))) + ret libspecs)))) + +(defn elide-aliases-from-ns-specs [ns-specs] + "Given ns specs, elide all :as-alias. Return a map of :libspecs (filtered) + and :as-aliases." + (let [ret {:as-aliases {} + :libspecs []}] + (reduce + (fn [{:keys [as-aliases] :as ret} [spec-key & libspecs]] + (if-not (= :refer-clojure spec-key) + (let [{:keys [as-aliases libspecs]} (elide-aliases-from-libspecs libspecs as-aliases)] + (cond-> ret + (not (empty? as-aliases)) (update :as-aliases merge as-aliases) + (not (empty? libspecs)) (update :libspecs conj (list* spec-key libspecs)))) + (update ret :libspecs conj (list* spec-key libspecs)))) + ret ns-specs))) diff --git a/src/main/clojure/cljs/analyzer/macros.clj b/src/main/clojure/cljs/analyzer/macros.clj new file mode 100644 index 0000000000..2cdb67ccf1 --- /dev/null +++ b/src/main/clojure/cljs/analyzer/macros.clj @@ -0,0 +1,57 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer.macros + (:refer-clojure :exclude [binding])) + +(defmacro with-warning-handlers [handlers & body] + `(cljs.core/binding [cljs.analyzer/*cljs-warning-handlers* ~handlers] + ~@body)) + +(defmacro no-warn [& body] + `(cljs.core/binding [cljs.analyzer/*cljs-warnings* + (zipmap (keys cljs.analyzer/*cljs-warnings*) (repeat false))] + ~@body)) + +(defmacro with-core-macros + [path & body] + `(do + (when (not= cljs.analyzer/*cljs-macros-path* ~path) + (reset! cljs.analyzer/-cljs-macros-loaded false)) + (cljs.core/binding [cljs.analyzer/*cljs-macros-path* ~path] + ~@body))) + +(defmacro with-core-macros-file + [path & body] + `(do + (when (not= cljs.analyzer/*cljs-macros-path* ~path) + (reset! cljs.analyzer/-cljs-macros-loaded false)) + (cljs.core/binding [cljs.analyzer/*cljs-macros-path* ~path + cljs.analyzer/*cljs-macros-is-classpath* false] + ~@body))) + +(defmacro wrapping-errors [env & body] + `(try + ~@body + (catch :default err# + (cond + (cljs.analyzer/has-error-data? err#) (throw err#) + (cljs.analyzer/analysis-error? err#) (throw (ex-info nil (cljs.analyzer/error-data ~env :compilation) err#)) + :else (throw (ex-info nil (cljs.analyzer/error-data ~env :compilation) (cljs.analyzer/error ~env (.-message err#) err#))))))) + +(defmacro disallowing-recur [& body] + `(cljs.core/binding [cljs.analyzer/*recur-frames* + (cons nil cljs.analyzer/*recur-frames*)] + ~@body)) + +(defmacro allowing-redef [& body] + `(cljs.core/binding [cljs.analyzer/*allow-redef* true] + ~@body)) + +(defmacro disallowing-ns* [& body] + `(cljs.core/binding [cljs.analyzer/*allow-ns* false] ~@body)) diff --git a/src/clj/cljs/analyzer/utils.clj b/src/main/clojure/cljs/analyzer/utils.clj similarity index 57% rename from src/clj/cljs/analyzer/utils.clj rename to src/main/clojure/cljs/analyzer/utils.clj index ce266b4f74..f61f201dc3 100644 --- a/src/clj/cljs/analyzer/utils.clj +++ b/src/main/clojure/cljs/analyzer/utils.clj @@ -1,3 +1,11 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + (ns cljs.analyzer.utils (:require [cljs.analyzer :as ana])) @@ -5,7 +13,7 @@ (let [env (:env ast) ast (if (= op :fn) (assoc ast :methods - (map #(simplify-env nil %) (:methods ast))) + (mapv #(simplify-env nil %) (:methods ast))) ast)] (assoc (dissoc ast :env) :env {:context (:context env)}))) diff --git a/src/main/clojure/cljs/build/api.clj b/src/main/clojure/cljs/build/api.clj new file mode 100644 index 0000000000..43f451c18a --- /dev/null +++ b/src/main/clojure/cljs/build/api.clj @@ -0,0 +1,309 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software + +(ns cljs.build.api + "This is intended to be a stable api for those who need programmatic access + to ClojureScript's project building facilities. + + For example: a build script may need to how to invalidate compiled + files so that they will be recompiled." + (:refer-clojure :exclude [compile]) + (:require [clojure.java.io :as io] + [cljs.util :as util] + [cljs.analyzer :as ana] + [cljs.analyzer.api :as ana-api] + [cljs.closure :as closure] + [cljs.js-deps :as deps]) + (:import [java.io File])) + +;; ============================================================================= +;; Useful Utilities + +(defn ^File target-file-for-cljs-ns + "Given an output directory and a clojurescript namespace return the + compilation target file for that namespace. + + For example: + (target-file-from-cljs-ns \"resources/out\" 'example.core) -> + " + ([ns-sym] (closure/target-file-for-cljs-ns ns-sym nil)) + ([ns-sym output-dir] (closure/target-file-for-cljs-ns ns-sym output-dir))) + +(defn mark-cljs-ns-for-recompile! + "Backdates a cljs target file so that it the cljs compiler will recompile it." + ([ns-sym] (closure/mark-cljs-ns-for-recompile! ns-sym nil)) + ([ns-sym output-dir] (closure/mark-cljs-ns-for-recompile! ns-sym output-dir))) + +(defn cljs-dependents-for-macro-namespaces + "Takes a list of Clojure (.clj) namespaces that define macros and + returns a list ClojureScript (.cljs) namespaces that depend on those macro + namespaces. + + For example where example.macros is defined in the clojure file + \"example/macros.clj\" and both 'example.core and 'example.util are + ClojureScript namespaces that require and use the macros from + 'example.macros : + (cljs-dependents-for-macro-namespaces 'example.macros) -> + ('example.core 'example.util)" + ([namespaces] + (closure/cljs-dependents-for-macro-namespaces + (or (ana-api/current-state) (ana-api/empty-state)) namespaces)) + ([state namespaces] + (closure/cljs-dependents-for-macro-namespaces state namespaces))) + +(defn parse-js-ns + "Given a Google Closure style JavaScript file or resource return the namespace + information for the given file. Only returns the value extracted from the + first provide statement." + [f] + (closure/parse-js-ns f)) + +(defn ^File src-file->target-file + "Given a ClojureScript source file return the target file. May optionally + provide build options with :output-dir specified." + ([src] (src-file->target-file src nil)) + ([src opts] + (src-file->target-file + (or (ana-api/current-state) (ana-api/empty-state opts)) src opts)) + ([state src opts] + (ana-api/with-state state + (binding [ana/*cljs-warning-handlers* (:warning-handlers opts ana/*cljs-warning-handlers*)] + (closure/src-file->target-file src opts))))) + +(defn ^String src-file->goog-require + "Given a ClojureScript or Google Closure style JavaScript source file return + the goog.require statement for it." + ([src] (src-file->goog-require src nil)) + ([src opts] + (src-file->goog-require + (or (ana-api/current-state) (ana-api/empty-state opts)) src opts)) + ([state src opts] + (ana-api/with-state state + (binding [ana/*cljs-warning-handlers* (:warning-handlers opts ana/*cljs-warning-handlers*)] + (closure/src-file->goog-require src opts))))) + +(defn index-ijs + "Given a sequence of cljs.closure/IJavaScript values, create an index using + :provides. The original values will appear under each :provide." + [xs] + (reduce + (fn [index x] + (merge index + (zipmap (:provides x) (repeat x)))) + {} xs)) + +;; ============================================================================= +;; Main API + +(defn goog-dep-string + "Given compiler options and a IJavaScript instance return the corresponding + goog.addDependency string" + [opts ijs] + (closure/add-dep-string opts ijs)) + +(defn source-on-disk + "Ensure that the given IJavaScript exists on disk in the output directory. + Return updated IJavaScript with the new location if necessary." + [opts ijs] + (closure/source-on-disk opts ijs)) + +(defn ns->source + "Given a namespace as a symbol return the corresponding resource if it exists." + [ns] + (util/ns->source ns)) + +(defn ns->location + "Given a namespace and compilation environment return the relative path and + uri of the corresponding source regardless of the source language extension: + .cljs, .cljc, .js. Returns a map containing :relative-path a string, and + :uri a URL." + ([ns] + (ns->location ns (or (ana-api/current-state) (ana-api/empty-state)))) + ([ns compiler-env] + (closure/source-for-namespace ns compiler-env))) + +(defn compilable->ijs + "Given a cljs.closure/Compilable value, return the corresponding + cljs.closure/IJavaScript value." + ([x] + (compilable->ijs x {})) + ([x opts] + (closure/-find-sources x opts))) + +(defn add-dependency-sources + "Given a sequence of cljs.closure/IJavaScript values, return a set that includes + all dependencies." + ([xs] + (add-dependency-sources xs {})) + ([xs opts] + (add-dependency-sources (or (ana-api/current-state) (ana-api/empty-state opts)) xs opts)) + ([state xs opts] + (ana-api/with-state state + (closure/add-dependency-sources xs opts)))) + +(defn add-dependencies + "DEPRECATED: Given one or more IJavaScript objects in dependency order, produce + a new sequence of IJavaScript objects which includes the input list + plus all dependencies in dependency order." + [opts & ijss] + (closure/add-dependencies opts ijss)) + +(defn handle-js-modules + "Given a collection of IJavaScript values representing a build, index all + node modules, convert all JS modules (ES6 etc), and store the updated + js-dependency-index (likely changed due to modules) in compiler state." + [state xs opts] + (closure/handle-js-modules opts xs state)) + +(defn dependency-order + "Topologically sort a collection of IJavaScript values." + [xs] + (deps/dependency-order xs)) + +(defn add-implicit-options + "Given a valid map of build options add any standard implicit options. For + example :optimizations :none implies :cache-analysis true and :source-map + true." + [opts] + (closure/add-implicit-options opts)) + +(defn inputs + "Given a list of directories and files, return a compilable object that may + be passed to build or watch." + [& xs] + (reify + closure/Inputs + (-paths [_] + (map io/file xs)) + closure/Compilable + (-compile [_ opts] + (letfn [(compile-input [x] + (let [compiled (closure/-compile x opts)] + (if (sequential? compiled) + compiled + [compiled])))] + (mapcat compile-input xs))) + (-find-sources [_ opts] + (mapcat #(closure/-find-sources % opts) xs)))) + +(defn compile + "Given a Compilable, compile it and return an IJavaScript." + ([opts compilable] + (compile (or (ana-api/current-state) (ana-api/empty-state opts)) opts compilable)) + ([state opts compilable] + (ana-api/with-state state + (closure/compile compilable opts)))) + +(defn output-unoptimized + "Ensure that all JavaScript source files are on disk (not in jars), + write the goog deps file including only the libraries that are being + used and write the deps file for the current project. + + The deps file for the current project will include third-party + libraries." + [opts & sources] + (apply closure/output-unoptimized opts sources)) + +(defn build + "Given compiler options, produce runnable JavaScript. An optional source + parameter may be provided." + ([opts] + (build nil opts)) + ([source opts] + (build source opts + (or + (ana-api/current-state) + (ana-api/empty-state + ;; need to dissoc :foreign-libs since we won't know what overriding + ;; foreign libspecs are referring to until after add-implicit-options + ;; - David + (closure/add-externs-sources (dissoc opts :foreign-libs)))))) + ([source opts compiler-env] + (doseq [[unknown-opt suggested-opt] (util/unknown-opts (set (keys opts)) closure/known-opts)] + (when suggested-opt + (println (str "WARNING: Unknown compiler option '" unknown-opt "'. Did you mean '" suggested-opt "'?")))) + (binding [ana/*cljs-warning-handlers* (:warning-handlers opts ana/*cljs-warning-handlers*)] + (closure/build source opts compiler-env)))) + +(defn watch + "Given a source which can be compiled, watch it for changes to produce." + ([source opts] + (watch source opts + (or (ana-api/current-state) + (ana-api/empty-state + (closure/add-externs-sources opts))))) + ([source opts compiler-env] + (watch source opts compiler-env nil)) + ([source opts compiler-env stop] + (binding [ana/*cljs-warning-handlers* (:warning-handlers opts ana/*cljs-warning-handlers*)] + (closure/watch source opts compiler-env stop)))) + +;; ============================================================================= +;; Node.js / NPM dependencies + +(defn compiler-opts? [m] + (and (map? m) + (or (contains? m :output-to) + (contains? m :modules) + (contains? m :npm-deps) + (contains? m :main) + (contains? m :optimizations) + (contains? m :foreign-libs)))) + +(defn install-node-deps! + "EXPERIMENTAL: Install the supplied dependencies via NPM. dependencies must be + a map of name to version or a valid compiler options map." + ([dependencies] + (if (compiler-opts? dependencies) + (install-node-deps! (:npm-deps dependencies) dependencies) + (install-node-deps! dependencies + (when-let [state (ana-api/current-state)] + (:options @state))))) + ([dependencies opts] + {:pre [(map? dependencies)]} + (closure/check-npm-deps opts) + (closure/maybe-install-node-deps! + (update-in opts [:npm-deps] merge dependencies)))) + +(defn get-node-deps + "EXPERIMENTAL: Get the Node.js dependency graph of the supplied dependencies. + Dependencies must be a sequence of strings or symbols naming packages or paths + within packages (e.g. [react \"react-dom/server\"] or a valid compiler options + map. Assumes dependencies have been been previously installed, either by + `cljs.build.api/install-node-deps!` or by an NPM client, and reside in the + `node_modules` directory." + ([dependencies] + (if (compiler-opts? dependencies) + (get-node-deps (keys (:npm-deps dependencies)) dependencies) + (get-node-deps dependencies + (when-let [state (ana-api/current-state)] + (:options @state))))) + ([dependencies opts] + {:pre [(sequential? dependencies)]} + (closure/index-node-modules + (distinct (concat (keys (:npm-deps opts)) (map str dependencies))) + opts))) + +(defn node-inputs + "EXPERIMENTAL: return the foreign libs entries as computed by running + the module-deps package on the supplied JavaScript entry points. Assumes + that the `@cljs-oss/module-deps` NPM package is either locally or globally + installed." + ([entries] + (node-inputs entries + (:options (or (ana-api/current-state) (ana-api/empty-state))))) + ([entries opts] + (closure/node-inputs entries opts))) + +(defn node-modules + "Return a sequence of requirable libraries found under node_modules." + ([] + (node-modules {})) + ([opts] + (ana-api/with-state (or (ana-api/current-state) (ana-api/empty-state opts)) + (filter :provides (closure/index-node-modules-dir))))) diff --git a/src/main/clojure/cljs/cli.clj b/src/main/clojure/cljs/cli.clj new file mode 100644 index 0000000000..9087151c95 --- /dev/null +++ b/src/main/clojure/cljs/cli.clj @@ -0,0 +1,749 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.cli + (:require [clojure.java.io :as io] + [clojure.string :as string] + [clojure.edn :as edn] + [cljs.util :as util] + [cljs.env :as env] + [cljs.analyzer :as ana] + [cljs.analyzer.api :as ana-api] + [cljs.compiler.api :as comp] + [cljs.build.api :as build] + [cljs.closure :as closure] + [cljs.repl :as repl]) + (:import [java.io File StringReader FileWriter] + [java.text BreakIterator] + [java.util Locale])) + +(declare main) + +;; ----------------------------------------------------------------------------- +;; Help String formatting + +(def ^{:private true} help-template + "Usage: java -cp cljs.jar cljs.main [init-opt*] [main-opt] [arg*] + +With no options or args, runs an interactive Read-Eval-Print Loop + +%s +For --main and --repl: + + - Enters the cljs.user namespace + - Binds *command-line-args* to a seq of strings containing command line + args that appear after any main option + - Runs all init options in order + - Calls a -main function or runs a repl or script if requested + +The init options may be repeated and mixed freely, but must appear before +any main option. + +In the case of --compile you may supply --repl or --serve (if applicable) +options afterwards. + +Paths may be absolute or relative in the filesystem or relative to +classpath. Classpath-relative paths have prefix of @ or @/") + +(defn- auto-fill + ([ws] + (auto-fill ws 50)) + ([^String ws max-len] + (let [b (BreakIterator/getLineInstance Locale/ENGLISH)] + (.setText b ws) + (loop [s (.first b) e (.next b) line-len 0 line "" ret []] + (if (not= e BreakIterator/DONE) + (let [w (.substring ws s e) + word-len (.length w) + line-len (+ line-len word-len)] + (if (= w "--") ; long-form options are single tokens (i.e. --repl) + (recur s (.next b) (- line-len 2) line ret) + (if (> line-len max-len) + (recur e (.next b) word-len w (conj ret line)) + (recur e (.next b) line-len (str line w) ret)))) + (conj ret (str line (.substring ws s (.length ws))))))))) + +(defn- opt->str [cs {:keys [arg doc]}] + (letfn [(desc-string [filled] + (string/join "\n" + (map #(apply str (concat (repeat 6 " ") [%])) + filled)))] + (let [[f & r] cs + + fstr (cond-> (if (= 1 (count cs)) + (str " " f) + (format "%1$5s" f)) + (not (empty? r)) (str ", " (string/join ", " r)) + arg (str " " arg)) + filled (auto-fill doc)] + (if (< (.length fstr) 30) + (cond-> (str (format "%1$-30s" fstr) (first filled) "\n") + (seq (rest filled)) (str (desc-string (rest filled)) "\n")) + (str + fstr "\n" + (desc-string fstr) "\n"))))) + +(defn- group->str [options group] + (let [{:keys [desc pseudos]} (get-in options [:groups group])] + (apply str + desc ":\n" + (->> (:init options) + (filter (fn [[k v]] (= (:group v) group))) + (concat pseudos) + (sort-by ffirst) + (map (fn [[k v]] (opt->str k v))))))) + +(defn- primary-groups-str [options] + (str + (group->str options ::main&compile) "\n" + (group->str options ::main) "\n" + (group->str options ::compile) "\n")) + +(defn- all-groups-str [{:keys [groups] :as options}] + (let [custom-groups + (disj (set (keys groups)) + ::main&compile ::main ::compile)] + (apply str + (primary-groups-str options) + (map + (fn [group] + (str (group->str options group) "\n")) + custom-groups)))) + +(defn- main-str [options] + (let [pseudos {["path"] {:doc "Run a script from a file or resource"} + ["-"] {:doc "Run a script from standard input"}}] + (apply str + "main options:\n" + (->> (:main options) + (concat pseudos) + (sort-by ffirst) + (remove (fn [[k v]] (nil? (ffirst k)))) + (map (fn [[k v]] (opt->str k v))))))) + +(defn- options-str [options] + (str + (all-groups-str options) + (main-str options))) + +(declare merged-commands) + +(defn help-str [repl-env] + (format help-template + (options-str (merged-commands repl-env)))) + +;; ----------------------------------------------------------------------------- +;; Main + +(defn- output-dir-opt + [cfg output-dir] + (assoc-in cfg [:options :output-dir] output-dir)) + +(defn- verbose-opt + [cfg value] + (assoc-in cfg [:options :verbose] (= value "true"))) + +(defn- validate-watch-paths [[path :as paths]] + (when (or (nil? path) + (and (not (.exists (io/file path))) + (or (string/blank? path) + (string/starts-with? path "-")))) + (throw + (ex-info + (str "Missing watch path(s)") + {:cljs.main/error :invalid-arg}))) + (when-let [non-existent (seq (remove #(.exists (io/file %)) paths))] + (throw + (ex-info + (if (== 1 (count non-existent)) + (str "Watch path " + (first non-existent) + " does not exist") + (str "Watch paths " + (string/join ", " (butlast non-existent)) + " and " + (last non-existent) + " does not exist")) + {:cljs.main/error :invalid-arg})))) + +(defn- watch-opt + [cfg paths] + (let [paths (util/split-paths paths)] + (validate-watch-paths paths) + (update-in cfg [:options :watch] (fnil into []) paths))) + +(defn- optimize-opt + [cfg level] + (assoc-in cfg [:options :optimizations] (keyword level))) + +(defn- output-to-opt + [cfg path] + (assoc-in cfg [:options :output-to] path)) + +(defn- deps-cmd-opt + [cfg deps-cmd] + (assoc-in cfg [:options :deps-cmd] deps-cmd)) + +(defn- target-opt + [cfg target] + (let [target (if (= "node" target) "nodejs" target)] + (assoc-in cfg [:options :target] (keyword target)))) + +(defn missing-file [x] + (throw + (ex-info + (str "File " x " does not exist") + {:cljs.main/error :invalid-arg}))) + +(defn missing-resource [x] + (throw + (ex-info + (str "Resource " + (if (string/starts-with? x "@/") + (subs x 2) + (subs x 1)) + " does not exist") + {:cljs.main/error :invalid-arg}))) + +(defn read-edn-opts [str] + (letfn [(read-rsrc [rsrc-str orig-str] + (if-let [rsrc (io/resource rsrc-str)] + (edn/read-string (slurp rsrc)) + (missing-resource orig-str)))] + (cond + (string/starts-with? str "@/") (read-rsrc (subs str 2) str) + (string/starts-with? str "@") (read-rsrc (subs str 1) str) + :else + (let [f (io/file str)] + (if (.exists f) + (edn/read-string (slurp f)) + (missing-file str)))))) + +(defn load-edn-opts [str] + (reduce merge {} (map read-edn-opts (util/split-paths str)))) + +(defn- repl-env-opts-opt + [cfg ropts] + (let [ropts (string/trim ropts) + edn (if (string/starts-with? ropts "{") + (edn/read-string ropts) + (load-edn-opts ropts))] + (update cfg :repl-env-options merge edn))) + +(defn- compile-opts-opt + [cfg copts] + (let [copts (string/trim copts) + edn (if (string/starts-with? copts "{") + (edn/read-string copts) + (load-edn-opts copts))] + (update cfg :options merge edn))) + +(defn- init-opt + [cfg file] + (let [file' (cond + (string/starts-with? file "@/") + (io/resource (subs file 2)) + (string/starts-with? file "@") + (io/resource (subs file 1)) + :else + (let [f (io/file file)] + (if (.exists f) + f + (missing-file file))))] + (when-not file' + (missing-resource file)) + (update-in cfg [:inits] + (fnil conj []) + {:type :init-script + :script file'}))) + +(defn- eval-opt + [cfg form-str] + (update-in cfg [:inits] + (fnil conj []) + {:type :eval-forms + :forms (ana-api/forms-seq (StringReader. form-str))})) + +(defn get-dispatch + ([commands k opt] + (get-dispatch commands k opt nil)) + ([commands k opt default] + (let [k' (keyword (str (name k) "-dispatch"))] + (or (get-in commands [k' opt]) default)))) + +(defn initialize + "Common initialize routine for repl, script, and null opts" + [inits commands] + (reduce + (fn [ret [opt arg]] + ((get-dispatch commands :init opt) ret arg)) + {} inits)) + +(defn temp-out-dir [] + (let [f (File/createTempFile "out" (Long/toString (System/nanoTime)))] + (.delete f) + (util/mkdirs f) + (util/path f))) + +(defn- repl-name [repl-env] + (let [repl-ns (-> repl-env meta :ns str)] + (when (string/starts-with? repl-ns "cljs.repl.") + (subs repl-ns (count "cljs.repl."))))) + +(defn- fast-initial-prompt? [repl-env options inits] + (boolean + (and (empty? inits) + (not (:verbose options)) + (not (:repl-verbose options)) + (contains? #{"node"} (repl-name repl-env))))) + +(defn target->repl-env [target default] + (if (= :nodejs target) + (do + (require 'cljs.repl.node) + (resolve 'cljs.repl.node/repl-env)) + default)) + +(defn- repl-opt + "Start a repl with args and inits. Print greeting if no eval options were +present" + [repl-env [_ & args] {:keys [repl-env-options options inits] :as cfg}] + (let [opts (cond-> options + (not (:output-dir options)) + (assoc :output-dir (temp-out-dir) :temp-output-dir? true) + (not (contains? options :aot-cache)) + (assoc :aot-cache true)) + reopts (merge repl-env-options (select-keys opts [:main :output-dir])) + _ (when (or ana/*verbose* (:verbose opts)) + (util/debug-prn "REPL env options:" (pr-str reopts))) + renv (-> (apply (target->repl-env (:target options) repl-env) (mapcat identity reopts)) + (assoc :compiler-opts opts))] + (repl/repl* renv + (assoc opts + ::repl/fast-initial-prompt? + (or (fast-initial-prompt? repl-env options inits) + (::repl/fast-initial-prompt? (repl/repl-options renv))) + + :quit-prompt + (if (empty? inits) + repl/repl-title + (constantly nil)) + + :inits + (into + [{:type :init-forms + :forms (when-not (empty? args) + [`(set! *command-line-args* (list ~@args))])}] + inits))))) + +(defn default-main + "Default handler for the --main flag. Will start REPL, invoke -main with the + supplied arguments." + [repl-env {:keys [main script args repl-env-options options inits] :as cfg}] + (let [opts (cond-> options + (not (:output-dir options)) + (assoc :output-dir (temp-out-dir) :temp-output-dir? true) + (not (contains? options :aot-cache)) + (assoc :aot-cache true)) + reopts (merge repl-env-options + (select-keys opts [:output-to :output-dir])) + _ (when (or ana/*verbose* (:verbose opts)) + (util/debug-prn "REPL env options:" (pr-str reopts))) + renv (apply (target->repl-env (:target options) repl-env) (mapcat identity reopts)) + coptsf (when-let [od (:output-dir opts)] + (io/file od "cljsc_opts.edn")) + copts (when (and coptsf (.exists coptsf)) + (edn/read-string (slurp coptsf))) + opts (merge copts + (build/add-implicit-options + (merge (repl/repl-options renv) opts)))] + (binding [env/*compiler* (env/default-compiler-env opts) + ana/*cljs-ns* 'cljs.user + repl/*repl-opts* opts + ana/*verbose* (:verbose opts) + repl/*repl-env* renv] + (when ana/*verbose* + (util/debug-prn "Compiler options:" (pr-str repl/*repl-opts*))) + (comp/with-core-cljs repl/*repl-opts* + (fn [] + (try + (repl/setup renv repl/*repl-opts*) + ;; Load cljs.repl runtime (so ex-str, ex-triage, etc. are available) + (repl/evaluate-form renv (ana-api/empty-env) "" + `(~'require ~''cljs.repl)) + ;; REPLs don't normally load cljs_deps.js + (when (and coptsf (.exists coptsf)) + (let [depsf (io/file (:output-dir opts) "cljs_deps.js")] + (when (.exists depsf) + (repl/evaluate renv "cljs_deps.js" 1 (slurp depsf))))) + (repl/evaluate-form renv (ana-api/empty-env) "" + (when-not (empty? args) + `(set! *command-line-args* (list ~@args)))) + (repl/evaluate-form renv (ana-api/empty-env) "" + `(~'ns ~'cljs.user)) + (repl/maybe-install-npm-deps opts) + (repl/run-inits renv inits) + (when script + (cond + (= "-" script) + (repl/load-stream renv "" *in*) + + (.exists (io/file script)) + (with-open [stream (io/reader script)] + (repl/load-stream renv script stream)) + + (string/starts-with? script "@/") + (if-let [rsrc (io/resource (subs script 2))] + (repl/load-stream renv (util/get-name rsrc) rsrc) + (missing-resource script)) + + (string/starts-with? script "@") + (if-let [rsrc (io/resource (subs script 1))] + (repl/load-stream renv (util/get-name rsrc) rsrc) + (missing-resource script)) + + (string/starts-with? script "-") + (throw + (ex-info + (str "Expected script or -, got flag " script " instead") + {:cljs.main/error :invalid-arg})) + + :else + (throw + (ex-info + (str "Script " script " does not exist") + {:cljs.main/error :invalid-arg})))) + (when main + (let [src (build/ns->source main)] + (when-not src + (throw + (ex-info + (str "Namespace " main " does not exist." + (when (string/includes? main "-") + " Please check that namespaces with dashes use underscores in the ClojureScript file name.")) + {:cljs.main/error :invalid-arg}))) + (repl/load-stream renv (util/get-name src) src) + (repl/evaluate-form renv (ana-api/empty-env) "" + `(~(symbol (name main) "-main") ~@args)))) + (finally + (repl/tear-down renv)))))))) + +(defn- main-opt + "Call the -main function from a namespace with string arguments from + the command line. Can be customized with ::cljs.cli/main fn entry in + the map returned by cljs.repl/IReplEnvOptions. For default behavior + see default-main." + [repl-env [_ ns & args] cfg] + ((::main (repl/repl-options (repl-env)) default-main) + repl-env (merge cfg {:main ns :args args}))) + +(defn- null-opt + "No repl or script opt present, just bind args and run inits" + [repl-env args cfg] + ((::main (repl/repl-options (repl-env)) default-main) + repl-env (merge cfg {:args args}))) + +(defn- help-opt + [repl-env _ _] + (println (help-str repl-env))) + +(defn- script-opt + "If no main option was given (compile, repl, main), handles running in + 'script' mode. Can be customized with ::cljs.cli/main fn entry in + the map returned by cljs.repl/IReplEnvOptions. For default behavior see + default-main." + [repl-env [path & args] cfg] + ((::main (repl/repl-options (repl-env)) default-main) + repl-env (merge cfg {:script path :args args}))) + +(defn watch-proc [cenv path opts] + (let [log-file (io/file (util/output-directory opts) "watch.log")] + (util/mkdirs log-file) + (#'repl/err-out (println "Watch compilation log available at:" (str log-file))) + (let [log-out (FileWriter. log-file)] + (binding [*err* log-out + *out* log-out] + (build/watch path (dissoc opts :watch) cenv))))) + +(defn- serve-opt + [_ [_ address-port & args] {:keys [options] :as cfg}] + (let [[host port] (if address-port + (string/split address-port #":") + ["localhost" 9000])] + (require 'cljs.repl.browser) + ((ns-resolve 'cljs.repl.browser 'serve) + {:host host + :port (if port + (cond-> port (string? port) Integer/parseInt) + 9000) + :output-dir (:output-dir options "out")}))) + +(defn- install-deps-opt + [_ _ {:keys [options] :as cfg}] + (closure/maybe-install-node-deps! options)) + +(defn get-main-ns [{:keys [ns options] :as cfg}] + (if (and ns (not (#{"-r" "--repl" "-s" "--serve"} ns))) + (symbol ns) + (:main options))) + +(defn default-compile + [repl-env {:keys [ns args options post-compile-fn] :as cfg}] + (let [rfs #{"-r" "--repl"} + sfs #{"-s" "--serve"} + env-opts (repl/repl-options ((target->repl-env (:target options) repl-env))) + repl? (boolean (or (rfs ns) (rfs (first args)))) + serve? (boolean (or (sfs ns) (sfs (first args)))) + main-ns (get-main-ns cfg) + opts (as-> + (merge + (select-keys env-opts + (cond-> closure/known-opts + repl? (conj :browser-repl))) + options + (when main-ns + {:main main-ns})) opts + (cond-> opts + (not (:output-to opts)) + (assoc :output-to + (.getPath (io/file (:output-dir opts "out") "main.js"))) + + (= :advanced (:optimizations opts)) + (dissoc :browser-repl) + + (not (:output-dir opts)) + (assoc :output-dir "out") + + (not (contains? opts :aot-cache)) + (assoc :aot-cache true) + + (sequential? (:watch opts)) + (update :watch cljs.closure/compilable-input-paths))) + convey (into [:output-dir] repl/known-repl-opts) + cfg (update cfg :options merge (select-keys opts convey)) + source (when (and (= :none (:optimizations opts :none)) main-ns) + (closure/check-main opts) + (:uri (build/ns->location main-ns))) + cenv (env/default-compiler-env + (closure/add-externs-sources (dissoc opts :foreign-libs)))] + (env/with-compiler-env cenv + (if-let [path (:watch opts)] + (if repl? + (build/build source opts cenv) + (build/watch path opts cenv)) + (build/build source opts cenv)) + (when (fn? post-compile-fn) + (post-compile-fn)) + (when repl? + (repl-opt repl-env args + (cond-> (assoc-in cfg [:options :compiler-env] cenv) + main-ns (update :options merge {:main main-ns})))) + (when serve? + (serve-opt repl-env args cfg))))) + +(defn- compile-opt + "Handle the compile flag. Custom compilation is possible by providing + :cljs.cli/compile fn in the map returned by cljs.repl/IReplEnvOptions. + For default behavior see default-compile." + [repl-env [_ ns & args] cfg] + ((::compile (repl/-repl-options (repl-env)) default-compile) + repl-env (merge cfg {:args args :ns ns}))) + +(defn get-options + "Given a commands map and a phase (:init or :main), return all flags + which can be handled as a set. If phase is :all will return the entire + flag set (:init + :main)." + [commands phase] + (if (= :all phase) + (into (get-options commands :main) (get-options commands :init)) + (-> (get commands (keyword (str (name phase) "-dispatch"))) + keys set))) + +(defn get-flags-set + "See get-options, this just provides a better name." + [commands phase] + (get-options commands phase)) + +(defn bool-init-options + [commands] + (reduce + (fn [ret [flags config]] + (cond-> ret + (= "bool" (:arg config)) + (into flags))) + #{} (:init commands))) + +(defn dispatch? + "Given a commands map, a phase (:init or :main) and a command line flag, + return true if the flag has a handler." + [commands phase opt] + (contains? (get-flags-set commands phase) opt)) + +(defn add-commands + "Given commands map (see below), create a commands map with :init-dispatch + and :main-dispatch keys where short and long arguments are mapped individually + to their processing fn." + ([commands] + (add-commands {:main-dispatch nil :init-dispatch nil} commands)) + ([commands {:keys [groups main init]}] + (letfn [(merge-dispatch [commands dispatch-key options] + (update-in commands [dispatch-key] + (fn [m] + (reduce + (fn [ret [flag-names flag-config]] + (merge ret + (zipmap flag-names (repeat (:fn flag-config))))) + m options))))] + (-> commands + (update-in [:groups] merge groups) + (update-in [:main] merge main) + (update-in [:init] merge init) + (merge-dispatch :init-dispatch init) + (merge-dispatch :main-dispatch main))))) + +(def ^{:doc "Default commands for ClojureScript REPLs. :groups are to support +printing organized output for --help. a :main option must come at the end, they +specify things like running a -main fn, compile, repl, or web serving. Sometimes +:main options can be used together (i.e. --compile --repl), but this is not +generic - the combinations must be explicitly supported"} + default-commands + (add-commands + {:groups {::main&compile {:desc "init options" + :pseudos + {["-re" "--repl-env"] + {:arg "env" + :doc (str "The REPL environment to use. Built-in " + "supported values: node, browser. " + "Defaults to browser. If given a " + "non-single-segment namespace, will " + "use the repl-env fn found there.")}}} + ::main {:desc "init options only for --main and --repl"} + ::compile {:desc "init options only for --compile"}} + :init + {["-i" "--init"] {:group ::main :fn init-opt + :arg "path" + :doc "Load a file or resource"} + ["-e" "--eval"] {:group ::main :fn eval-opt + :arg "string" + :doc "Evaluate expressions in string; print non-nil values"} + ["-v" "--verbose"] {:group ::main :fn verbose-opt + :arg "bool" + :doc "If true, will enable ClojureScript verbose logging"} + ["-d" "--output-dir"] {:group ::main&compile :fn output-dir-opt + :arg "path" + :doc (str "Set the output directory to use. If " + "supplied, cljsc_opts.edn in that directory " + "will be used to set ClojureScript compiler " + "options") } + ["-w" "--watch"] {:group ::compile :fn watch-opt + :arg "paths" + :doc (str "Continuously build, only effective with the " + "--compile main option. Specifies a system-dependent " + "path-separated list of directories to watch.")} + ["-o" "--output-to"] {:group ::compile :fn output-to-opt + :arg "file" + :doc "Set the output compiled file"} + ["--deps-cmd"] {:group ::compile :fn deps-cmd-opt + :arg "string" + :doc "Set the node dependency manager. Only npm or yarn supported"} + ["-O" "--optimizations"] {:group ::compile :fn optimize-opt + :arg "level" + :doc + (str "Set optimization level, only effective with " + "--compile main option. Valid values are: none, " + "whitespace, simple, advanced")} + ["-t" "--target"] {:group ::main&compile :fn target-opt + :arg "name" + :doc + (str "The JavaScript target. Configures environment bootstrap and " + "defaults to browser. Supported values: node or nodejs, " + "webworker, bundle, none") } + ["-ro" "--repl-opts"] {:group ::main&compile :fn repl-env-opts-opt + :arg "edn" + :doc (str "Options to configure the repl-env, can be an EDN string or " + "system-dependent path-separated list of EDN files / classpath resources. Options " + "will be merged left to right.")} + ["-co" "--compile-opts"] {:group ::main&compile :fn compile-opts-opt + :arg "edn" + :doc (str "Options to configure the build, can be an EDN string or " + "system-dependent path-separated list of EDN files / classpath resources. Options " + "will be merged left to right.")}} + :main + {["--install-deps"] {:fn install-deps-opt + :doc "Install all :npm-deps found upstream and in supplied compiler options"} + ["-r" "--repl"] {:fn repl-opt + :doc "Run a repl"} + ["-m" "--main"] {:fn main-opt + :arg "ns" + :doc "Call the -main function from a namespace with args"} + ["-c" "--compile"] {:fn compile-opt + :arg "[ns]" + :doc (str "Run a compile. If optional namespace specified, use as " + "the main entry point. If --repl follows, " + "will launch a REPL after the compile completes. " + "If --serve follows, will start a web server that serves " + "the current directory after the compile completes.")} + ["-s" "--serve"] {:fn serve-opt + :arg "host:port" + :doc (str "Start a simple web server to serve the current directory")} + [nil] {:fn null-opt} + ["-h" "--help" "-?"] {:fn help-opt + :doc "Print this help message and exit"}}})) + +(defn normalize + "Given a commands map (flag + value -> option processor fn) and the sequence of + command line arguments passed to the process, normalize it. Boolean flags don't + need to specify anything, insert the implied trues and return the normalized + command line arguments." + [commands args] + (letfn [(normalize* [args*] + (if (not (contains? (get-flags-set commands :main) (first args*))) + (let [pred (complement (bool-init-options commands)) + [pre post] ((juxt #(take-while pred %) + #(drop-while pred %)) + args*)] + (cond + (= pre args*) pre + + (not (#{"true" "false"} (fnext post))) + (concat pre [(first post) "true"] + (normalize commands (next post))) + + :else + (concat pre [(first post) (fnext post)] + (normalize commands (nnext post))))) + args*))] + (loop [args args args' (normalize* args)] + (if (= args args') + args' + (recur args' (normalize* args')))))) + +(defn merged-commands + "Given a repl environment combine the default commands with the custom + REPL commands. Commands are a mapping from a command line argument + (flag + value) to a function to handle that particular flag + value." + [repl-env] + (add-commands default-commands + (::commands (repl/repl-options (repl-env))))) + +(defn main + "A generic runner for ClojureScript. repl-env must satisfy + cljs.repl/IReplEnvOptions and cljs.repl/IJavaScriptEnv protocols. args is a + sequence of command line flags." + [repl-env & args] + (try + (let [commands (merged-commands repl-env)] + (if args + (loop [[opt arg & more :as args] (normalize commands args) inits []] + (if (dispatch? commands :init opt) + (recur more (conj inits [opt arg])) + ((get-dispatch commands :main opt script-opt) + repl-env args (initialize inits commands)))) + (repl-opt repl-env nil nil))) + (finally + (flush)))) diff --git a/src/main/clojure/cljs/closure.clj b/src/main/clojure/cljs/closure.clj new file mode 100644 index 0000000000..55e7181f0b --- /dev/null +++ b/src/main/clojure/cljs/closure.clj @@ -0,0 +1,3436 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.closure + (:refer-clojure :exclude [compile]) + (:require [cljs.externs :as externs] + [cljs.util :as util :refer [distinct-by]] + [cljs.core :as cljsm] + [cljs.compiler :as comp] + [cljs.analyzer :as ana] + [cljs.source-map :as sm] + [cljs.env :as env] + [cljs.foreign.node :refer [package-json-entries node-file-seq->libs-spec*]] + [cljs.js-deps :as deps] + [clojure.java.io :as io] + [clojure.java.shell :as sh] + [clojure.reflect] + [clojure.set :as set] + [clojure.string :as string] + [cljs.vendor.clojure.data.json :as json] + [cljs.module-graph :as module-graph]) + (:import [java.lang ProcessBuilder] + [java.io + File BufferedReader BufferedInputStream + Writer InputStreamReader IOException StringWriter ByteArrayInputStream] + [java.net URI URL] + [java.util.logging Level] + [java.util List Random HashMap] + [java.util.concurrent + TimeUnit LinkedBlockingDeque Executors CountDownLatch] + [com.google.javascript.jscomp CompilerOptions CompilationLevel + CompilerInput CompilerInput$ModuleType DependencyOptions + CompilerOptions$LanguageMode SourceMap$Format + SourceMap$DetailLevel ClosureCodingConvention SourceFile + Result JSError CheckLevel DiagnosticGroup DiagnosticGroups + CommandLineRunner + JSChunk SourceMap VariableMap PrintStreamErrorManager DiagnosticType + VariableRenamingPolicy PropertyRenamingPolicy] + [com.google.javascript.jscomp.deps ClosureBundler ModuleLoader$ResolutionMode ModuleNames + SimpleDependencyInfo] + [com.google.javascript.rhino Node] + [java.nio.file Path Paths Files StandardWatchEventKinds WatchKey + WatchEvent FileVisitor FileVisitResult FileSystems] + [java.nio.charset Charset StandardCharsets] + [com.sun.nio.file SensitivityWatchEventModifier])) + +;; Copied from clojure.tools.gitlibs + +(def ^:private GITLIBS-CACHE-DIR + (delay + (.getCanonicalPath + (let [env (System/getenv "GITLIBS")] + (if (string/blank? env) + (io/file (System/getProperty "user.home") ".gitlibs") + (io/file env)))))) + +(defn- gitlibs-cache-dir + "Returns the gitlibs cache dir, a string." + [] + @GITLIBS-CACHE-DIR) + +(defn- gitlibs-src? + "Returns true if the file comes from the gitlibs cache." + [file] + #_(string/starts-with? (util/path file) (gitlibs-cache-dir)) + ;; NOTE: does not work on first build see CLJS-2765 + false) + +(def name-chars (map char (concat (range 48 57) (range 65 90) (range 97 122)))) + +(defn random-char [] + (nth name-chars (.nextInt (Random.) (count name-chars)))) + +(defn random-string [length] + (apply str (take length (repeatedly random-char)))) + +(defn- sym->var + "Converts a namespaced symbol to a var, loading the requisite namespace if + needed. For use with a function defined under a keyword in opts. The kw and + ex-data arguments are used to form exceptions." + ([sym kw] + (sym->var sym kw nil)) + ([sym kw ex-data] + (let [ns (namespace sym) + _ (when (nil? ns) + (throw + (ex-info (str kw " symbol " sym " is not fully qualified") + (merge ex-data {kw sym + :clojure.error/phase :compilation})))) + var-ns (symbol ns)] + (when (not (find-ns var-ns)) + (try + (locking ana/load-mutex + (require var-ns)) + (catch Throwable t + (throw (ex-info (str "Cannot require namespace referred by " kw " value " sym) + (merge ex-data {kw sym + :clojure.error/phase :compilation}) + t))))) + + (find-var sym)))) + +(defn- opts-fn + "Extracts a function from opts, by default expecting a function value, but + converting from a namespaced symbol if needed." + [kw opts] + (when-let [fn-or-sym (kw opts)] + (cond-> fn-or-sym (symbol? fn-or-sym) (sym->var kw {})))) + +;; Closure API +;; =========== + +(defmulti js-source-file (fn [_ source] (class source))) + +(defmethod js-source-file String [^String name ^String source] + (-> (SourceFile/builder) + (.withPath name) + (.withContent source) + (.build))) + +(defmethod js-source-file File [_ ^File source] + (-> (SourceFile/builder) + (.withPath (.toPath source)) + (.withCharset StandardCharsets/UTF_8) + (.build))) + +(defmethod js-source-file URL [_ ^URL source] + (js-source-file _ (io/file (.getPath source)))) + +(defmethod js-source-file BufferedInputStream [^String name ^BufferedInputStream source] + (-> (SourceFile/builder) + (.withPath name) + (.withContent source) + (.build))) + +(def check-level + {:error CheckLevel/ERROR + :warning CheckLevel/WARNING + :off CheckLevel/OFF}) + +(def warning-types + {:access-controls DiagnosticGroups/ACCESS_CONTROLS + :analyzer-checks DiagnosticGroups/ANALYZER_CHECKS + :check-regexp DiagnosticGroups/CHECK_REGEXP + :check-types DiagnosticGroups/CHECK_TYPES + :check-useless-code DiagnosticGroups/CHECK_USELESS_CODE + :check-variables DiagnosticGroups/CHECK_VARIABLES + :closure-dep-method-usage-checks DiagnosticGroups/CLOSURE_DEP_METHOD_USAGE_CHECKS + :conformance-violations DiagnosticGroups/CONFORMANCE_VIOLATIONS + :const DiagnosticGroups/CONST + :constant-property DiagnosticGroups/CONSTANT_PROPERTY + :debugger-statement-present DiagnosticGroups/DEBUGGER_STATEMENT_PRESENT + :deprecated DiagnosticGroups/DEPRECATED + :deprecated-annotations DiagnosticGroups/DEPRECATED_ANNOTATIONS + :duplicate-message DiagnosticGroups/DUPLICATE_MESSAGE + :duplicate-vars DiagnosticGroups/DUPLICATE_VARS + :es5-strict DiagnosticGroups/ES5_STRICT + :externs-validation DiagnosticGroups/EXTERNS_VALIDATION + :extra-require DiagnosticGroups/EXTRA_REQUIRE + :function-params DiagnosticGroups/FUNCTION_PARAMS + :global-this DiagnosticGroups/GLOBAL_THIS + :invalid-casts DiagnosticGroups/INVALID_CASTS + :j2cl-checks DiagnosticGroups/J2CL_CHECKS + :jsdoc-missing-type DiagnosticGroups/JSDOC_MISSING_TYPE + :late-provide DiagnosticGroups/LATE_PROVIDE + :lint-checks DiagnosticGroups/LINT_CHECKS + :message-descriptions DiagnosticGroups/MESSAGE_DESCRIPTIONS + :misplaced-msg-annotation DiagnosticGroups/MISPLACED_MSG_ANNOTATION + :misplaced-type-annotation DiagnosticGroups/MISPLACED_TYPE_ANNOTATION + :missing-override DiagnosticGroups/MISSING_OVERRIDE + :missing-polyfill DiagnosticGroups/MISSING_POLYFILL + :missing-properties DiagnosticGroups/MISSING_PROPERTIES + :missing-provide DiagnosticGroups/MISSING_PROVIDE + :missing-require DiagnosticGroups/MISSING_REQUIRE + :missing-return DiagnosticGroups/MISSING_RETURN + :missing-sources-warnings DiagnosticGroups/MISSING_SOURCES_WARNINGS + :module-load DiagnosticGroups/MODULE_LOAD + :msg-conventions DiagnosticGroups/MSG_CONVENTIONS + :non-standard-jsdoc DiagnosticGroups/NON_STANDARD_JSDOC + :report-unknown-types DiagnosticGroups/REPORT_UNKNOWN_TYPES + :strict-missing-properties DiagnosticGroups/STRICT_MISSING_PROPERTIES + :strict-module-dep-check DiagnosticGroups/STRICT_MODULE_DEP_CHECK + :suspicious-code DiagnosticGroups/SUSPICIOUS_CODE + :too-many-type-params DiagnosticGroups/TOO_MANY_TYPE_PARAMS + :tweaks DiagnosticGroups/TWEAKS + :type-invalidation DiagnosticGroups/TYPE_INVALIDATION + :undefined-variables DiagnosticGroups/UNDEFINED_VARIABLES + :underscore DiagnosticGroups/UNDERSCORE + :unknown-defines DiagnosticGroups/UNKNOWN_DEFINES + :unused-local-variable DiagnosticGroups/UNUSED_LOCAL_VARIABLE + :violated-module-dep DiagnosticGroups/VIOLATED_MODULE_DEP + :visibility DiagnosticGroups/VISIBILITY}) + +(def known-opts + "Set of all known compiler options." + #{:anon-fn-naming-policy :asset-path :cache-analysis :closure-defines :closure-extra-annotations + :closure-warnings :compiler-stats :dump-core :elide-asserts :externs :foreign-libs + :hashbang :language-in :language-out :libs :main :modules :source-map-path :source-map-asset-path + :optimizations :optimize-constants :output-dir :output-to :output-wrapper :parallel-build :preamble + :pretty-print :print-input-delimiter :pseudo-names :recompile-dependents :source-map + :source-map-inline :source-map-timestamp :static-fns :target :verbose :warnings + :emit-constants :ups-externs :ups-foreign-libs :ups-libs :warning-handlers :preloads + :browser-repl :cache-analysis-format :infer-externs :closure-generate-exports :npm-deps + :fn-invoke-direct :checked-arrays :closure-module-roots :rewrite-polyfills :use-only-custom-externs + :watch :watch-error-fn :watch-fn :install-deps :process-shim :rename-prefix :rename-prefix-namespace + :closure-variable-map-in :closure-property-map-in :closure-variable-map-out :closure-property-map-out + :stable-names :ignore-js-module-exts :opts-cache :aot-cache :elide-strict :fingerprint :spec-skip-macros + :nodejs-rt :target-fn :deps-cmd :bundle-cmd :global-goog-object&array :node-modules-dirs :lite-mode + :elide-to-string}) + +(def string->charset + {"iso-8859-1" StandardCharsets/ISO_8859_1 + "us-ascii" StandardCharsets/US_ASCII + "utf-16" StandardCharsets/UTF_16 + "utf-16be" StandardCharsets/UTF_16BE + "utf-16le" StandardCharsets/UTF_16LE + "utf-8" StandardCharsets/UTF_8}) + +(defn to-charset [charset] + (cond + (instance? Charset charset) charset + (and (string? charset) + (contains? string->charset (string/lower-case charset))) + (get string->charset (string/lower-case charset)) + :else + (throw + (ex-info + (str "Invalid :closure-output-charset " charset " given, only " + (string/join ", " (keys string->charset)) " supported ") + {:clojure.error/phase :compilation})))) + +(def lang-level + [:ecmascript3 :ecmascript5 :ecmascript5-strict :ecmascript6 :ecmascript6-strict + :ecmascript8 + :ecmascript-2015 :ecmascript-2016 :ecmascript-2017 :ecmascript-2018 + :ecmascript-2019 :ecmascript-2020 :ecmascript-2021 :ecmascript-next + :no-transpile]) + +(defn expand-lang-key [key] + (keyword (string/replace (name key) #"^es" "ecmascript"))) + +(defn ^CompilerOptions$LanguageMode lang-key->lang-mode [key] + (case (expand-lang-key key) + :no-transpile CompilerOptions$LanguageMode/NO_TRANSPILE ;; same mode as input (for language-out only) + :ecmascript3 CompilerOptions$LanguageMode/ECMASCRIPT3 + :ecmascript5 CompilerOptions$LanguageMode/ECMASCRIPT5 + :ecmascript5-strict CompilerOptions$LanguageMode/ECMASCRIPT5_STRICT + :ecmascript6 CompilerOptions$LanguageMode/ECMASCRIPT_2015 ;; (deprecated and remapped) + :ecmascript6-strict CompilerOptions$LanguageMode/ECMASCRIPT_2015 ;; (deprecated and remapped) + :ecmascript8 CompilerOptions$LanguageMode/ECMASCRIPT_2017 + :ecmascript-2015 CompilerOptions$LanguageMode/ECMASCRIPT_2015 + :ecmascript-2016 CompilerOptions$LanguageMode/ECMASCRIPT_2016 + :ecmascript-2017 CompilerOptions$LanguageMode/ECMASCRIPT_2017 + :ecmascript-2018 CompilerOptions$LanguageMode/ECMASCRIPT_2018 + :ecmascript-2019 CompilerOptions$LanguageMode/ECMASCRIPT_2019 + :ecmascript-2020 CompilerOptions$LanguageMode/ECMASCRIPT_2020 + :ecmascript-2021 CompilerOptions$LanguageMode/ECMASCRIPT_2021 + :ecmascript-next CompilerOptions$LanguageMode/ECMASCRIPT_NEXT)) + +(defn set-options + "TODO: Add any other options that we would like to support." + [opts ^CompilerOptions compiler-options] + (.setModuleResolutionMode compiler-options ModuleLoader$ResolutionMode/NODE) + + (when (contains? opts :pretty-print) + (.setPrettyPrint compiler-options (:pretty-print opts))) + + (when (contains? opts :pseudo-names) + (set! (.generatePseudoNames compiler-options) (:pseudo-names opts))) + + (when-let [lang-key (:language-in opts :ecmascript-next)] + (.setLanguageIn compiler-options (lang-key->lang-mode lang-key))) + + (when-let [lang-key (:language-out opts)] + (.setLanguageOut compiler-options (lang-key->lang-mode lang-key))) + + (when (contains? opts :print-input-delimiter) + (set! (.printInputDelimiter compiler-options) + (:print-input-delimiter opts))) + + (when (contains? opts :closure-warnings) + (doseq [[type level] (:closure-warnings opts)] + (. compiler-options + (setWarningLevel (type warning-types) (level check-level))))) + + (when (contains? opts :closure-extra-annotations) + (. compiler-options + (setExtraAnnotationNames (map name (:closure-extra-annotations opts))))) + + (when (contains? opts :closure-module-roots) + (. compiler-options + (setModuleRoots (:closure-module-roots opts)))) + + (when (contains? opts :closure-generate-exports) + (. compiler-options + (setGenerateExports (:closure-generate-exports opts)))) + + (when (contains? opts :rewrite-polyfills) + (. compiler-options + (setRewritePolyfills (:rewrite-polyfills opts)))) + + (when (contains? opts :rename-prefix) + (. compiler-options + (setRenamePrefix (:rename-prefix opts)))) + + (when (contains? opts :rename-prefix-namespace) + (. compiler-options + (setRenamePrefixNamespace (:rename-prefix-namespace opts)))) + + (when (contains? opts :closure-variable-map-in) + (let [var-in (io/file (:closure-variable-map-in opts))] + (when (.exists var-in) + (.setInputVariableMap compiler-options + (VariableMap/load (.getAbsolutePath var-in)))))) + + (when (contains? opts :closure-property-map-in) + (let [prop-in (io/file (:closure-property-map-in opts))] + (when (.exists prop-in) + (.setInputPropertyMap compiler-options + (VariableMap/load (.getAbsolutePath prop-in)))))) + + (. compiler-options + (setOutputCharset (to-charset (:closure-output-charset opts "UTF-8"))) ;; only works > 20160125 Closure Compiler + ) + + compiler-options) + +(defn ^CompilerOptions make-options + "Create a CompilerOptions object and set options from opts map." + [opts] + (let [level (case (:optimizations opts) + :advanced CompilationLevel/ADVANCED_OPTIMIZATIONS + :whitespace CompilationLevel/WHITESPACE_ONLY + :simple CompilationLevel/SIMPLE_OPTIMIZATIONS) + compiler-options (doto (CompilerOptions.) + (.setCodingConvention (ClosureCodingConvention.)))] + (doseq [[key val] (:closure-defines opts)] + (let [key (name key)] + (cond + (string? val) (.setDefineToStringLiteral compiler-options key val) + (number? val) (.setDefineToDoubleLiteral compiler-options key val) + (or (true? val) + (false? val)) (.setDefineToBooleanLiteral compiler-options key val) + :else (println "value for" key "must be string, int, float, or bool")))) + (if-let [extra-annotations (:closure-extra-annotations opts)] + (. compiler-options (setExtraAnnotationNames (map name extra-annotations)))) + (when (:source-map opts) + (if (:modules opts) + ;; name is not actually used by Closure in :modules case, + ;; but we need to provide _something_ for Closure to not + ;; complain + (.setSourceMapOutputPath compiler-options + (str (io/file (util/output-directory opts) + "cljs_modules.map"))) + (.setSourceMapOutputPath compiler-options + (:source-map opts))) + (.setSourceMapDetailLevel compiler-options SourceMap$DetailLevel/ALL) + (.setSourceMapFormat compiler-options SourceMap$Format/V3)) + (do + (.setOptionsForCompilationLevel level compiler-options) + (set-options opts compiler-options) + compiler-options))) + +(defn load-externs + "Externs are JavaScript files which contain empty definitions of + functions which will be provided by the environment. Any function in + an extern file will not be renamed during optimization. + + Options may contain an :externs key with a list of file paths to + load. The :use-only-custom-externs flag may be used to indicate that + the default externs should be excluded." + [{:keys [externs use-only-custom-externs target ups-externs infer-externs] :as opts}] + (let [validate (fn validate [p us] + (if (empty? us) + (throw (util/compilation-error (IllegalArgumentException. + (str "Extern " p " does not exist")))) + us)) + filter-cp-js (fn [paths] + (for [p paths + u (deps/find-js-classpath p)] + u)) + filter-js (fn [paths] + (for [p paths + u (deps/find-js-resources p)] + u)) + add-target (fn [ext] + (cons (io/resource "cljs/externs.js") + (if (= :nodejs target) + (cons (io/resource "cljs/nodejs_externs.js") + (or ext [])) + ext))) + load-js (fn [ext] + (map #(js-source-file (.getFile %) (slurp %)) ext))] + (let [js-sources (-> externs filter-js add-target load-js) + ups-sources (-> ups-externs filter-cp-js load-js) + all-sources (vec (concat js-sources ups-sources))] + (cond-> + (if use-only-custom-externs + all-sources + (into all-sources (externs/default-externs))) + infer-externs + (conj (js-source-file nil + (io/file (util/output-directory opts) "inferred_externs.js"))))))) + +(defn ^com.google.javascript.jscomp.Compiler make-closure-compiler [] + (let [compiler (com.google.javascript.jscomp.Compiler.)] + (com.google.javascript.jscomp.Compiler/setLoggingLevel Level/WARNING) + compiler)) + +(defn report-failure [^Result result] + (let [errors (.errors result) + warnings (.warnings result)] + (binding [*out* *err*] + (doseq [next (seq errors)] + (println "ERROR:" (.toString ^JSError next))) + (doseq [next (seq warnings)] + (println "WARNING:" (.toString ^JSError next))) + (when (seq errors) + (throw (util/compilation-error (Exception. "Closure compilation failed"))))))) + +;; Protocols for IJavaScript and Compilable +;; ======================================== + + + +(defprotocol ISourceMap + (-source-url [this] "Return the CLJS source url") + (-source-map [this] "Return the CLJS compiler generated JS source mapping")) + +(extend-protocol deps/IJavaScript + + String + (-foreign? [this] false) + (-closure-lib? [this] false) + (-url + ([this] nil) + ([this _] nil)) + (-relative-path + ([this] nil) + ([this _] nil)) + (-provides [this] + (let [{:keys [provides]} (deps/parse-js-ns (string/split-lines this))] + (cond-> provides + (empty? provides) + (conj (util/content-sha this 7))))) + (-requires [this] (:requires (deps/parse-js-ns (string/split-lines this)))) + (-source + ([this] this) + ([this _] this)) + + clojure.lang.IPersistentMap + (-foreign? [this] (:foreign this)) + (-closure-lib? [this] (:closure-lib this)) + (-url + ([this] (deps/-url this nil)) + ([this opts] + (let [[url file] (if-let [url-min (and (#{:advanced :simple} (:optimizations opts)) + (:url-min this))] + [url-min (:file-min this)] + [(:url this) (:file this)])] + (or url (deps/to-url file))))) + (-relative-path + ([this] (deps/-relative-path this nil)) + ([this opts] + (let [file (if-let [file-min (and (#{:advanced :simple} (:optimizations opts)) + (:file-min this))] + file-min + (:file this)) + as-file (io/as-file file)] + (when (and as-file (not (.isAbsolute as-file))) + file)))) + (-provides [this] (map name (:provides this))) + (-requires [this] (map name (:requires this))) + (-source + ([this] (deps/-source this nil)) + ([this opts] + (if-let [s (:source this)] + s + (with-open [reader (io/reader (deps/-url this opts))] + (slurp reader)))))) + +(defrecord JavaScriptFile [foreign ^URL url ^URL source-url provides requires lines source-map] + deps/IJavaScript + (-foreign? [this] foreign) + (-closure-lib? [this] (:closure-lib this)) + (-url [this] url) + (-url [this opts] url) + (-relative-path [this] nil) + (-relative-path [this opts] nil) + (-provides [this] provides) + (-requires [this] requires) + (-source [this] (deps/-source this nil)) + (-source [this opts] + (with-open [reader (io/reader url)] + (slurp reader))) + ISourceMap + (-source-url [this] source-url) + (-source-map [this] source-map)) + +(defn javascript-file + ([foreign ^URL url provides requires] + (javascript-file foreign url nil provides requires nil nil)) + ([foreign ^URL url source-url provides requires lines source-map] + (assert (first provides) (str source-url " does not provide a namespace")) + (JavaScriptFile. foreign url source-url (map name provides) (map name requires) lines source-map))) + +(defn map->javascript-file [m] + (merge + (javascript-file + (:foreign m) + (when-let [f (or (:file m) (:url m))] + (deps/to-url f)) + (when-let [sf (:source-file m)] + (deps/to-url sf)) + (:provides m) + (:requires m) + (:lines m) + (:source-map m)) + (when-let [source-file (:source-file m)] + {:source-file source-file}) + (when-let [out-file (:out-file m)] + {:out-file out-file}) + (when (:closure-lib m) + {:closure-lib true}) + (when-let [module (:module m)] + {:module module}) + (when-let [lang (:lang m)] + {:lang lang}) + (when-let [ns (:ns m)] + {:ns ns}) + (when (:macros-ns m) + {:macros-ns true}))) + +(defn read-js + "Read a JavaScript file returning a map of file information." + [f] + (let [source (slurp f) + m (deps/parse-js-ns (string/split-lines source))] + (map->javascript-file (assoc m :file f)))) + + +;; Compile +;; ======= + +(defprotocol Inputs + (-paths [this] "Returns the file paths to the source inputs")) + +(extend-protocol Inputs + String + (-paths [this] [(io/file this)]) + File + (-paths [this] [this])) + +(defprotocol Compilable + (-compile [this opts] "Returns one or more IJavaScripts.") + (-find-sources [this opts] "Returns one or more IJavascripts, without compiling them.")) + +(defn compilable-input-paths + "Takes a coll of inputs as strings or files and returns a + single Inputs and Compilable object." + [paths] + (reify + cljs.closure/Inputs + (-paths [_] + (mapcat cljs.closure/-paths paths)) + cljs.closure/Compilable + (-compile [_ opts] + (mapcat #(cljs.closure/-compile % opts) + paths)) + (-find-sources [_ opts] + (mapcat #(cljs.closure/-find-sources % opts) + paths)))) + +(defn compile-form-seq + "Compile a sequence of forms to a JavaScript source string." + ([forms] + (compile-form-seq forms + (when env/*compiler* + (:options @env/*compiler*)))) + ([forms opts] + (comp/with-core-cljs opts + (fn [] + (with-out-str + (binding [ana/*cljs-ns* 'cljs.user] + (doseq [form forms] + (comp/emit (ana/analyze (ana/empty-env) form))))))))) + +(defn compiled-file + "Given a map with at least a :file key, return a map with + {:file .. :provides .. :requires ..}. + + Compiled files are cached so they will only be read once." + [m] + (let [path (.getPath (.toURL ^File (:file m))) + js (if (:provides m) + (map->javascript-file m) + (if-let [js (get-in @env/*compiler* [::compiled-cljs path])] + js + (read-js (:file m))))] + (swap! env/*compiler* update-in [::compiled-cljs] assoc path js) + js)) + +(defn compile + "Given a Compilable, compile it and return an IJavaScript." + [compilable opts] + (-compile compilable opts)) + +(def ^:private USER-HOME-WRITABLE + (delay (.canWrite (io/file (System/getProperty "user.home"))))) + +(defn- aot-cache? [opts] + "Returns true if compilation artifacts shuold be placed in the + shared AOT cache." + (and (:aot-cache opts) + @USER-HOME-WRITABLE)) + +(defn- copy-from-cache + [cache-path cacheable source-file opts] + (doseq [[k ^File f] cacheable] + (when (.exists f) + (let [target (io/file (util/output-directory opts) + (-> (.getAbsolutePath f) + (string/replace (.getAbsolutePath cache-path) "") + (subs 1)))] + (when (and (or ana/*verbose* (:verbose opts)) (= :output-file k)) + (util/debug-prn (str "Copying cached " f " to " target))) + (util/mkdirs target) + (spit target (slurp f)) + (.setLastModified target (util/last-modified source-file)))))) + +(defn find-sources + "Given a Compilable, find sources and return a sequence of IJavaScript." + [compilable opts] + (-find-sources compilable opts)) + +(defn compile-file + "Compile a single cljs file. If no output-file is specified, returns + a string of compiled JavaScript. With an output-file option, the + compiled JavaScript will written to this location and the function + returns a JavaScriptFile. In either case the return value satisfies + IJavaScript." + [^File file {:keys [output-file] :as opts}] + (if output-file + (let [out-file (io/file (util/output-directory opts) output-file)] + (if (and (aot-cache? opts) + (gitlibs-src? file)) + (let [cacheable (ana/cacheable-files file (util/ext file) opts) + cache-path (ana/cache-base-path (util/path file) opts)] + (if (not (.exists (:output-file cacheable))) + (let [ret (compiled-file (comp/compile-file file (:output-file cacheable) + (assoc opts :output-dir (util/path cache-path))))] + (copy-from-cache cache-path cacheable file opts) + ret) + (do + (when-not (.exists out-file) + (copy-from-cache cache-path cacheable file opts)) + (compiled-file (comp/compile-file file (.toString out-file) opts))))) + (compiled-file (comp/compile-file file (.toString out-file) opts)))) + (let [path (.getPath ^File file)] + (binding [ana/*cljs-file* path] + (with-open [rdr (io/reader file)] + (compile-form-seq (ana/forms-seq* rdr path))))))) + +(defn compile-dir + "Recursively compile all cljs files under the given source + directory. Return a list of JavaScriptFiles." + [^File src-dir opts] + (let [out-dir (util/output-directory opts)] + (map compiled-file + (comp/compile-root src-dir out-dir opts)))) + +(defn ^String path-from-jarfile + "Given the URL of a file within a jar, return the path of the file + from the root of the jar." + [^URL url] + (last (string/split (.getFile url) #"\.jar!/"))) + +(defn jar-file-to-disk + "Copy a file contained within a jar to disk. Return the created file." + ([url out-dir] + (jar-file-to-disk url out-dir + (when env/*compiler* + (:options @env/*compiler*)))) + ([url out-dir opts] + (let [out-file (io/file out-dir (path-from-jarfile url)) + content (with-open [reader (io/reader url)] + (slurp reader))] + (when (and url (or ana/*verbose* (:verbose opts))) + (util/debug-prn "Copying" (str url) "to" (str out-file))) + (util/mkdirs out-file) + (spit out-file content) + (.setLastModified ^File out-file (util/last-modified url)) + out-file))) + +(defn compile-from-jar + "Compile a file from a jar if necessary. Returns IJavaScript." + [jar-file {:keys [output-file] :as opts}] + (let [out-file (when output-file + (io/file (util/output-directory opts) output-file)) + cacheable (ana/cacheable-files jar-file (util/ext jar-file) opts)] + (when (or (nil? out-file) + (comp/requires-compilation? jar-file out-file opts)) + ;; actually compile from JAR + (if (not (aot-cache? opts)) + (-compile (jar-file-to-disk jar-file (util/output-directory opts) opts) opts) + (let [cache-path (ana/cache-base-path (util/path jar-file) opts)] + (when (comp/requires-compilation? jar-file (:output-file cacheable) opts) + (-compile (jar-file-to-disk jar-file cache-path opts) + (assoc opts :output-dir (util/path cache-path)))) + (copy-from-cache cache-path cacheable jar-file opts)))) + ;; Files that don't require compilation (cljs.loader for example) + ;; need to be copied from JAR to disk. + (when (or (nil? out-file) + (not (.exists out-file))) + (jar-file-to-disk jar-file (util/output-directory opts) opts)) + ;; have to call compile-file as it includes more IJavaScript + ;; information than ana/parse-ns for now + (compile-file + (io/file (util/output-directory opts) + (last (string/split (.getPath ^URL jar-file) #"\.jar!/"))) + opts))) + +(defn find-jar-sources [this opts] + [(comp/find-source this)]) + +(extend-protocol Compilable + + File + (-compile [this opts] + (if (.isDirectory this) + (compile-dir this opts) + (compile-file this opts))) + (-find-sources [this _] + (if (.isDirectory this) + (comp/find-root-sources this) + [(comp/find-source this)])) + + URL + (-compile [this opts] + (case (.getProtocol this) + "file" (-compile (io/file this) opts) + "jar" (compile-from-jar this opts))) + (-find-sources [this opts] + (case (.getProtocol this) + "file" (-find-sources (io/file this) opts) + "jar" (find-jar-sources this opts))) + + clojure.lang.PersistentList + (-compile [this opts] + (compile-form-seq [this])) + (-find-sources [this opts] + [(ana/parse-ns [this] opts)]) + + String + (-compile [this opts] (-compile (io/file this) opts)) + (-find-sources [this opts] (-find-sources (io/file this) opts)) + + clojure.lang.Symbol + (-compile [this opts] + (-compile (util/ns->source this) opts)) + (-find-sources [this opts] + (-find-sources (util/ns->source this) opts)) + + clojure.lang.PersistentVector + (-compile [this opts] (compile-form-seq this)) + (-find-sources [this opts] + [(ana/parse-ns this opts)]) + + clojure.lang.IPersistentSet + (-compile [this opts] + (doall (map (comp #(-compile % opts) util/ns->source) this))) + (-find-sources [this opts] + (into [] (mapcat #(-find-sources % opts)) this)) + ) + +(comment + ;; compile a file in memory + (-compile "samples/hello/src/hello/core.cljs" {}) + (-find-sources "samples/hello/src/hello/core.cljs" {}) + ;; compile a file to disk - see file @ 'out/clojure/set.js' + (-compile (io/resource "clojure/set.cljs") {:output-file "clojure/set.js"}) + (-find-sources (io/resource "clojure/set.cljs") {:output-file "clojure/set.js"}) + ;; compile a project + (-compile (io/file "samples/hello/src") {}) + (-find-sources (io/file "samples/hello/src") {}) + ;; compile a project with a custom output directory + (-compile (io/file "samples/hello/src") {:output-dir "my-output"}) + (-find-sources (io/file "samples/hello/src") {:output-dir "my-output"}) + ;; compile a form + (-compile '(defn plus-one [x] (inc x)) {}) + ;; compile a vector of forms + (-compile '[(ns test.app (:require [goog.array :as array])) + (defn plus-one [x] (inc x))] + {}) + + (-find-sources 'cljs.core {}) + ) + +(defn js-dependencies + "Given a sequence of Closure namespace strings, return the list of + all dependencies. The returned list includes all Google and + third-party library dependencies. + + Third-party libraries are configured using the :libs option where + the value is a list of directories containing third-party + libraries." + [opts requires] + (loop [requires requires + visited (set requires) + deps #{}] + (if (seq requires) + (let [node (or (get (@env/*compiler* :js-dependency-index) (first requires)) + (deps/find-classpath-lib (first requires))) + new-req (remove #(contains? visited %) + (into (:requires node) (:require-types node)))] + (recur (into (rest requires) new-req) + (into visited new-req) + (conj deps node))) + (remove nil? deps)))) + +(comment + ;; find dependencies + (binding [env/*compiler* (env/default-compiler-env)] + (js-dependencies {} ["goog.array"])) + + ;; find dependencies in an external library + (binding [env/*compiler* (env/default-compiler-env)] + (js-dependencies {:libs ["closure/library/third_party/closure"]} ["goog.dom.query"])) + + (binding [env/*compiler* (env/default-compiler-env)] + (js-dependencies {} ["goog.math.Long"])) + + (binding [env/*compiler* (env/default-compiler-env)] + (js-dependencies {} ["goog.string.StringBuffer"])) + ) + +(defn add-core-macros-if-cljs-js + "If a compiled entity is the cljs.js namespace, explicitly + add the cljs.core macros namespace dependency to it." + [compiled] + (cond-> compiled + ;; TODO: IJavaScript :provides :requires should really + ;; always be Vector - David + (= ["cljs.js"] (into [] (map str) (deps/-provides compiled))) + (update-in [:requires] concat ["cljs.core$macros"]))) + +(defn get-compiled-cljs + "Return an IJavaScript for this file. Compiled output will be + written to the working directory." + [opts {:keys [relative-path uri]}] + (let [js-file (comp/rename-to-js relative-path) + compiled (-compile uri (merge opts {:output-file js-file}))] + (add-core-macros-if-cljs-js compiled))) + +(defn cljs-source-for-namespace + "Given a namespace return the corresponding source with either a .cljs or + .cljc extension." + [ns] + (if (= "cljs.core$macros" (str ns)) + (let [relpath "cljs/core.cljc"] + {:relative-path relpath :uri (io/resource relpath) :ext :cljc}) + (let [path (-> (munge ns) (string/replace \. \/)) + relpath (str path ".cljs")] + (if-let [res (io/resource relpath)] + {:relative-path relpath :uri res :ext :cljs} + (let [relpath (str path ".cljc")] + (if-let [res (io/resource relpath)] + {:relative-path relpath :uri res :ext :cljc})))))) + +(defn source-for-namespace + "Given a namespace and compilation environment return the relative path and + uri of the corresponding source regardless of the source language extension: + .cljs, .cljc, .js" + [ns compiler-env] + (let [ns-str (str (comp/munge ns {})) + path (string/replace ns-str \. \/) + relpath (str path ".cljs")] + (if-let [cljs-res (io/resource relpath)] + {:relative-path relpath :uri cljs-res :ext :cljs} + (let [relpath (str path ".cljc")] + (if-let [cljc-res (io/resource relpath)] + {:relative-path relpath :uri cljc-res :ext :cljc} + (let [relpath (str path ".js")] + (if-let [js-res (io/resource relpath)] + {:relative-path relpath :uri js-res :ext :js} + (let [ijs (get-in @compiler-env [:js-dependency-index (str ns)]) + relpath (or (:file ijs) (:url ijs))] + (if-let [js-res (and relpath + ;; try to parse URL, otherwise just return local + ;; resource + (or (and (util/url? relpath) relpath) + (try (URL. relpath) (catch Throwable t)) + (io/resource relpath)))] + {:relative-path relpath :uri js-res :ext :js} + (throw + (util/compilation-error + (IllegalArgumentException. + (str "Namespace " ns " does not exist." + (when (string/includes? ns "-") + " Please check that namespaces with dashes use underscores in the ClojureScript file name.")))))))))))))) + +(defn cljs-dependencies + "Given a list of all required namespaces, return a list of + IJavaScripts which are the cljs dependencies. The returned list will + not only include the explicitly required files but any transitive + dependencies as well. JavaScript files will be compiled to the + working directory if they do not already exist. + + Only load dependencies from the classpath." + [opts requires] + (letfn [(cljs-deps [lib-names] + (->> lib-names + (remove #(or ((@env/*compiler* :js-dependency-index) %) + (deps/find-classpath-lib %))) + (map cljs-source-for-namespace) + (remove (comp nil? :uri))))] + (loop [required-files (cljs-deps requires) + visited (set required-files) + js-deps #{}] + (if (seq required-files) + (let [next-file (first required-files) + js (get-compiled-cljs opts next-file) + new-req (remove #(contains? visited %) (cljs-deps (deps/-requires js)))] + (recur (into (rest required-files) new-req) + (into visited new-req) + (conj js-deps js))) + (disj js-deps nil))))) + +(comment + ;; only get cljs deps + (cljs-dependencies {} ["goog.string" "cljs.core"]) + ;; get transitive deps + (cljs-dependencies {} ["clojure.string"]) + ;; don't get cljs.core twice + (cljs-dependencies {} ["cljs.core" "clojure.string"]) + ) + +(defn find-cljs-dependencies + "Given set of cljs namespace symbols, find IJavaScript objects for the namespaces." + [requires] + (letfn [(cljs-deps [namespaces] + (->> namespaces + (remove #(or ((@env/*compiler* :js-dependency-index) %) + (deps/find-classpath-lib %))) + (map cljs-source-for-namespace) + (remove (comp nil? :uri))))] + (loop [required-files (cljs-deps requires) + visited (set required-files) + cljs-namespaces #{}] + (if (seq required-files) + (let [next-file (first required-files) + ns-info (ana/parse-ns (:uri next-file)) + new-req (remove #(contains? visited %) (cljs-deps (cond-> (deps/-requires ns-info) + (= 'cljs.js (:ns ns-info)) (conj "cljs.core$macros"))))] + (recur (into (rest required-files) new-req) + (into visited new-req) + (conj cljs-namespaces ns-info))) + (disj cljs-namespaces nil))))) + +(defn- constants-filename + "Returns the filename of the constants table." + [opts] + (str (util/output-directory opts) File/separator + (string/replace (str ana/constants-ns-sym) "." File/separator) ".js")) + +(defn- constants-javascript-file + "Returns the constants table as a JavaScriptFile." + [opts] + (let [url (deps/to-url (constants-filename opts))] + (javascript-file nil url [(str ana/constants-ns-sym)] ["cljs.core"]))) + +(defn add-dependencies + "DEPRECATED: Given one or more IJavaScript objects in dependency order, produce + a new sequence of IJavaScript objects which includes the input list + plus all dependencies in dependency order." + [opts & inputs] + (let [inputs (set inputs) + requires (set (mapcat deps/-requires inputs)) + required-cljs (clojure.set/difference (cljs-dependencies opts requires) inputs) + required-js (js-dependencies opts + (into (set (mapcat deps/-requires required-cljs)) requires))] + (cons + (javascript-file nil (io/resource "goog/base.js") ["goog"] nil) + (deps/dependency-order + (concat + (map + (fn [{:keys [type foreign url file provides requires] :as js-map}] + ;; ignore :seed inputs, only for REPL - David + (if (not= :seed type) + (let [url (or url (io/resource file))] + (merge + (javascript-file foreign url provides requires) + js-map)) + js-map)) + required-js) + (when (-> @env/*compiler* :options :emit-constants) + [(constants-javascript-file opts)]) + required-cljs + inputs))))) + +(comment + (alter-var-root #'env/*compiler* (constantly (env/default-compiler-env))) + ;; only get cljs deps + (find-cljs-dependencies ["goog.string" "cljs.core"]) + ;; get transitive deps + (find-cljs-dependencies ["clojure.string"]) + ;; don't get cljs.core twice + (find-cljs-dependencies ["cljs.core" "clojure.string"]) + ) + +(defn- module-entries + "Return the module entries of `compile-opts` as a set." + [compile-opts] + (->> compile-opts :modules vals + (map :entries) + (remove nil?) + (apply concat) + (set))) + +(defn add-dependency-sources + "Given list of IJavaScript objects, produce a new sequence of IJavaScript objects + of all dependencies of inputs." + ([inputs] + (add-dependency-sources inputs + (when env/*compiler* + (:options @env/*compiler*)))) + ([inputs compile-opts] + (let [inputs (set inputs) + requires (set (mapcat deps/-requires inputs)) + module-entries (module-entries compile-opts)] + (into inputs (find-cljs-dependencies (set/union requires module-entries)))))) + +(defn check-unprovided + [inputs] + (let [requires (set (mapcat deps/-requires inputs)) + provided (set (mapcat deps/-provides inputs)) + unprovided (clojure.set/difference requires provided)] + (when (seq unprovided) + (ana/warning :unprovided @env/*compiler* {:unprovided (sort unprovided)})) + inputs)) + +(defn compile-task [^LinkedBlockingDeque deque input-set compiled opts failed] + (loop [ns-info (.pollFirst deque)] + (when (and ns-info (not @failed)) + (let [{:keys [requires]} ns-info + input-set' @input-set + {:keys [compiler-stats verbose]} opts] + (if (every? #(not (contains? input-set' %)) requires) + (do + (try + (swap! compiled conj + (-compile (or (:source-file ns-info) + (:source-forms ns-info)) + ; - ns-info -> ns -> cljs file relpath -> js relpath + (merge opts + {:output-file (comp/rename-to-js + (util/ns->relpath (:ns ns-info)))}))) + (catch Throwable e + (reset! failed e))) + (when-not @failed + (when-let [ns (:ns ns-info)] + (swap! input-set disj ns)) + (recur (.pollFirst deque)))) + (do + (Thread/sleep 10) + (recur ns-info))))))) + +(defn parallel-compile-sources [inputs compiler-stats opts] + (module-graph/validate-inputs inputs) + (let [deque (LinkedBlockingDeque. inputs) + input-set (atom (into #{} (comp (remove nil?) (map :ns)) inputs)) + cnt (+ 2 (int (* 0.6 (.. Runtime getRuntime availableProcessors)))) + latch (CountDownLatch. cnt) + es (Executors/newFixedThreadPool cnt) + compiled (atom []) + failed (atom false)] + (dotimes [_ cnt] + (.execute es + (bound-fn [] + (compile-task deque input-set compiled opts failed) + (.countDown latch)))) + (util/measure compiler-stats "Compile sources" (.await latch)) + (.shutdown es) + (when @failed + (throw @failed)) + @compiled)) + +(defn compile-sources + "Takes dependency ordered list of IJavaScript compatible maps from parse-ns + and compiles them." + ([inputs opts] + (compile-sources inputs (:compiler-stats opts) opts)) + ([inputs compiler-stats opts] + (if (:parallel-build opts) + (parallel-compile-sources inputs compiler-stats opts) + (util/measure compiler-stats + "Compile sources" + (binding [comp/*inputs* (zipmap (map :ns inputs) inputs)] + (doall + (for [ns-info inputs] + ; TODO: compile-file calls parse-ns unnecessarily to get ns-info + ; TODO: we could mark dependent namespaces for recompile here + (-compile (or (:source-file ns-info) + (:source-forms ns-info)) + ; - ns-info -> ns -> cljs file relpath -> js relpath + (merge opts {:output-file (comp/rename-to-js (util/ns->relpath (:ns ns-info)))}))))))))) + +(defn remove-goog-base + [inputs] + (remove #(= (:provides %) ["goog"]) inputs)) + +(defn add-goog-base + [inputs] + (cons (javascript-file nil (io/resource "goog/base.js") ["goog"] nil) + inputs)) + +(defn add-js-sources + "Given list of IJavaScript objects, add foreign-deps, constants-table + IJavaScript objects to the list." + [inputs opts] + (let [requires (set (mapcat deps/-requires inputs)) + required-js (js-dependencies opts requires)] + (concat + (->> required-js + ;; :foreign-libs which declare :external? have no sources (they are included + ;; on the page via some script tag we'll never see). :require-global libs are + ;; implicit :foreign-libs where :external? is true + (remove :external?) + (map + (fn [{:keys [foreign url file provides requires] :as js-map}] + (let [url (or url (io/resource file))] + (merge + (javascript-file foreign url provides requires) + js-map))))) + (when (-> @env/*compiler* :options :emit-constants) + [(constants-javascript-file opts)]) + inputs))) + +(defn add-preloads + "Add :preloads to a given set of inputs (IJavaScript). Returns a new + list of inputs where the preloaded namespaces and their deps come immediately after + cljs.core or the constants table depending on the optimization setting. Any + files needing copying or compilation will be compiled and/or copied to the + appropiate location." + [inputs opts] + (if-not (:preloads opts) + inputs + (let [pred (fn [x] + (if (:emit-constants opts) + (not= [(str ana/constants-ns-sym)] (:provides x)) + (not= ["cljs.core"] (:provides x)))) + pre (take-while pred inputs) + post (drop-while pred inputs) + preloads (remove nil? + (map + (fn [preload] + (try + (comp/find-source preload) + (catch Throwable t + (util/debug-prn "WARNING: preload namespace" preload "does not exist")))) + (:preloads opts)))] + (distinct-by :provides + (concat pre [(first post)] + (-> (add-dependency-sources preloads opts) + deps/dependency-order + (compile-sources opts) + (add-js-sources opts) + deps/dependency-order) + (next post)))))) + +(comment + (comp/find-sources-root "samples/hello/src") + (find-dependency-sources (find-sources-root "samples/hello/src")) + (find-sources "samples/hello/src")) + +(defn preamble-from-paths [paths] + (when-let [missing (seq (remove io/resource paths))] + (ana/warning :preamble-missing @env/*compiler* {:missing (sort missing)})) + (let [resources (remove nil? (map io/resource paths))] + (str (string/join "\n" (map slurp resources)) "\n"))) + +(defn make-preamble [{:keys [target preamble hashbang]}] + (str (when (and (= :nodejs target) (not (false? hashbang))) + (str "#!" (or hashbang "/usr/bin/env node") "\n")) + (when preamble (preamble-from-paths preamble)))) + +;; Optimize +;; ======== + +(defmulti javascript-name class) + +(defmethod javascript-name URL [^URL url] + (if url (.getPath url) "cljs/user.js")) + +(defmethod javascript-name String [s] + (if-let [name (first (deps/-provides s))] name "cljs/user.js")) + +(defmethod javascript-name JavaScriptFile [js] + (when-let [url (deps/-url js)] + (javascript-name url))) + +(defn build-provides + "Given a vector of provides, builds required goog.provide statements" + [provides] + (apply str (map #(str "goog.provide('" % "');\n") provides))) + +(defmethod js-source-file JavaScriptFile [_ js] + (if-let [url (deps/-url js)] + (js-source-file (javascript-name url) (io/input-stream url)) + (when-let [source (:source js)] + (js-source-file (javascript-name source) source)))) + +(defn ensure-cljs-base-module + "Ensure that compiler :modules map has :cljs-base module with defined + :output-to. If :output-to not provided will default to :output-dir location + and the name of the file will be \"cljs_base.js.\"" + ([modules] + (ensure-cljs-base-module modules + (when env/*compiler* + (:options @env/*compiler*)))) + ([modules opts] + (update-in modules [:cljs-base :output-to] + (fnil io/file + (io/file + (util/output-directory opts) + "cljs_base.js"))))) + +(comment + (ensure-cljs-base-module + {:cljs-base + {:output-to "out/modules/base.js"} + :core + {:output-to "out/modules/core.js" + :entries '#{cljs.core}} + :landing + {:output-to "out/modules/reader.js" + :entries '#{cljs.reader} + :depends-on #{:core}}}) + ) + +(defn- const-expr-form + "Returns the :const-expr form for `sym` from `compiler-state`." + [compiler-state sym] + (let [const-expr (get-in compiler-state [::ana/namespaces (symbol (namespace sym)) :defs (symbol (name sym)) :const-expr])] + (some-> const-expr ana/const-expr->constant-value))) + +(defn compile-loader + "Special compilation pass for cljs.loader namespace. cljs.loader must be + compiled last after all inputs. This is because all inputs must be known and + they must already be sorted in dependency order." + [inputs {:keys [modules] :as opts}] + (when-let [loader (->> inputs + (filter + (fn [input] + (some '#{"cljs.loader" cljs.loader} + (:provides input)))) + first)] + (let [module-uris (when (seq modules) + (module-graph/modules->module-uris modules inputs opts)) + module-infos (when (seq modules) + (module-graph/modules->module-infos modules))] + (swap! env/*compiler* ana/add-consts + {'cljs.core/MODULE_INFOS + (merge (const-expr-form @env/*compiler* 'cljs.core/MODULE_INFOS) module-infos) + 'cljs.core/MODULE_URIS + (merge (const-expr-form @env/*compiler* 'cljs.core/MODULE_URIS) module-uris)}) + (-compile (:source-file loader) + (merge opts + {:cache-key (util/content-sha (pr-str module-uris)) + :output-file (comp/rename-to-js (util/ns->relpath (:ns loader)))})))) + inputs) + +(defn build-modules + "Given a list of IJavaScript sources in dependency order and compiler options + return a dependency sorted list of module name / description tuples. The + module descriptions will be augmented with a :closure-module entry holding + the Closure JSChunk. Each module description will also be augmented with + a :foreign-deps vector containing foreign IJavaScript sources in dependency + order." + [sources opts] + (let [sources (map + (fn [js] + (cond + (instance? JavaScriptFile js) + js + (map? js) + (map->JavaScriptFile js) + (string? js) + (merge + (map->javascript-file {:provides (deps/-provides js)}) + {:source js}) + :else js)) + sources) + used (atom #{}) ;; track used inputs to avoid dupes + modules + (reduce + (fn [ret [name {:keys [entries depends-on] :as module-desc}]] + (assert (or (= name :cljs-base) (not (empty? entries))) + (str "Module " name " does not define any :entries")) + (when (:verbose opts) + (util/debug-prn "Building module" name)) + (let [js-module (JSChunk. (clojure.core/name name)) + module-sources + (reduce + (fn [ret entry-sym] + (if-let [entries (module-graph/find-sources-for-module-entry entry-sym sources)] + (let [unused (set/difference entries @used)] + (swap! used into entries) + (into ret unused)) + (throw + (util/compilation-error (IllegalArgumentException. + (str "Could not find matching namespace for " entry-sym)))))) + [] entries) + foreign-deps (atom [])] + ;; add inputs to module + (doseq [ijs module-sources] + (when (:verbose opts) + (util/debug-prn " adding entry" (:provides ijs))) + (if-not (deps/-foreign? ijs) + (.add js-module + ^SourceFile (js-source-file (javascript-name ijs) ijs)) + (swap! foreign-deps conj ijs))) + ;; add module dependencies, will always work + ;; since modules are already in dependency order + (doseq [dep depends-on] + (if-let [parent-module (get-in (into {} ret) [dep :closure-module])] + (do + (when (:verbose opts) + (util/debug-prn " module" name "depends on" dep)) + (.addDependency js-module ^JSChunk parent-module)) + (throw (util/compilation-error (IllegalArgumentException. + (str "Parent module " dep " does not exist")))))) + (conj ret + [name (assoc module-desc + :closure-module js-module + :foreign-deps @foreign-deps)]))) + [] (module-graph/sort-modules + (ensure-cljs-base-module + (module-graph/expand-modules (:modules opts) sources) opts)))] + modules)) + +(comment + (build "samples/hello/src" + {:optimizations :none + :output-dir "out" + :output-to "out/hello.js" + :source-map true}) + + (let [modules + (build-modules + [(map->javascript-file + (ana/parse-ns 'cljs.core (io/file "out/cljs/core.js") nil)) + (map->javascript-file + (ana/parse-ns 'cljs.reader (io/file "out/cljs/reader.js") nil))] + {:optimizations :advanced + :output-dir "out" + :cache-analysis true + :modules {:core + {:output-to "out/modules/core.js" + :entries '#{cljs.core}} + :landing + {:output-to "out/modules/reader.js" + :entries '#{cljs.reader} + :depends-on #{:core}}}})] + modules) + ) + +(defn emit-optimized-source-map + "Given a JSON parsed Google Closure JavaScript to JavaScript source map, + the entire list of original IJavaScript sources output a merged JavaScript + to ClojureScript source map file with the given file name. opts should + supply :preamble-line-count and :foreign-deps-line-count if they are + relevant." + [sm-json sources name opts] + (let [closure-source-map (sm/decode-reverse sm-json)] + (loop [sources (seq sources) + relpaths {} + merged (sorted-map-by + (sm/source-compare + (remove nil? + (map (fn [source] + (if-let [^URL source-url (:source-url source)] + (.getPath source-url) + (if-let [^URL url (:url source)] + (.getPath url)))) + sources))))] + (if sources + (let [source (first sources)] + (recur + (next sources) + (let [{:keys [provides]} source + url (or (:source-url source) (:url source))] + (if (and provides url) + (assoc relpaths + (.getPath ^URL url) + (util/ns->relpath (first provides) (util/ext url))) + relpaths)) + (if-let [url (:url source)] + (let [path (.getPath ^URL url)] + (if-let [compiled (get-in @env/*compiler* [::comp/compiled-cljs path])] + (if-let [source-url (:source-url source)] + (assoc merged + (.getPath ^URL source-url) + (sm/merge-source-maps + (:source-map compiled) + (get closure-source-map path))) + merged) + (assoc merged path (get closure-source-map path)))) + merged))) + (spit + (io/file name) + (sm/encode merged + {:preamble-line-count (+ (:preamble-line-count opts 0) + (:foreign-deps-line-count opts 0)) + :lines (+ (:lineCount sm-json) + (:preamble-line-count opts 0) + (:foreign-deps-line-count opts 0) + 2) + :file name + :output-dir (util/output-directory opts) + :source-map (:source-map opts) + :source-map-path (:source-map-path opts) + :source-map-timestamp (:source-map-timestamp opts) + :source-map-pretty-print (:source-map-pretty-print opts) + :relpaths relpaths})))))) + +(defn write-variable-maps [^Result result opts] + (let [var-out (:closure-variable-map-out opts)] + (when-let [var-map (and var-out (.-variableMap result))] + (util/mkdirs var-out) + (io/copy (ByteArrayInputStream. (.toBytes var-map)) + (io/file var-out)))) + (let [prop-out (:closure-property-map-out opts)] + (when-let [prop-map (and prop-out (.-propertyMap result))] + (util/mkdirs prop-out) + (io/copy (ByteArrayInputStream. (.toBytes prop-map)) + (io/file prop-out))))) + +(defn optimize-modules + "Use the Closure Compiler to optimize one or more Closure JSChunks. Returns + a dependency sorted list of module name and description tuples." + [opts & sources] + ;; the following pre-condition can't be enabled + ;; lein-cljsbuild adds :output-to? + #_{:pre [(and (contains? opts :modules) + (not (contains? opts :output-to)))]} + (assert (= (count (:modules opts)) + (count (into #{} + (map (comp :output-to second) + (:modules opts))))) + "Each :output-to of :modules must be unique") + (let [closure-compiler (make-closure-compiler) + ^List externs (load-externs opts) + compiler-options (make-options opts) + _ (.initOptions closure-compiler compiler-options) + sources (if (= :whitespace (:optimizations opts)) + (cons "var CLOSURE_NO_DEPS = true;" sources) + sources) + modules (build-modules sources opts) + ^List inputs (map (comp :closure-module second) modules) + _ (doseq [^JSChunk input inputs] + (.sortInputsByDeps input closure-compiler)) + _ (when (or ana/*verbose* (:verbose opts)) + (util/debug-prn "Applying optimizations" (:optimizations opts) "to" (count sources) "sources")) + ^Result result (.compileChunks closure-compiler externs inputs compiler-options) + ^SourceMap source-map (when (:source-map opts) + (.getSourceMap closure-compiler))] + (assert (or (nil? (:source-map opts)) source-map) + "Could not create source maps for modules") + (if (.success result) + (do + (write-variable-maps result opts) + (vec + (for [[name {:keys [output-to closure-module] :as module}] modules] + [name + (merge + (assoc module + :source + (do + (when source-map (.reset source-map)) + (.toSource closure-compiler ^JSChunk closure-module))) + (when source-map + (let [sw (StringWriter.) + source-map-name (str output-to ".map.closure")] + (.appendTo source-map sw source-map-name) + {:source-map-json (.toString sw) + :source-map-name source-map-name})))]))) + (report-failure result)))) + +(defn ->js-source-files [sources] + (doall + (map (fn [src] + (let [src' (cond-> src + (and (not (record? src)) (map? src)) + map->javascript-file)] + (js-source-file (javascript-name src') src'))) + sources))) + +(defn optimize + "Use the Closure Compiler to optimize one or more JavaScript files." + [opts & sources] + (when (or ana/*verbose* (:verbose opts)) + (util/debug-prn "Applying optimizations" (:optimizations opts) "to" (count sources) "sources")) + (let [closure-compiler (make-closure-compiler) + ^List externs (load-externs opts) + compiler-options (make-options opts) + sources (if (= :whitespace (:optimizations opts)) + (cons "var CLOSURE_NO_DEPS = true;" sources) + sources) + ^List inputs (->js-source-files sources) + ^Result result (util/measure (:compiler-stats opts) + "Optimizing with Google Closure Compiler" + (.compile closure-compiler externs inputs compiler-options))] + (if (.success result) + ;; compiler.getSourceMap().reset() + (do + (write-variable-maps result opts) + (let [source (.toSource closure-compiler)] + (when-let [name (:source-map opts)] + (let [name' (str name ".closure") + sw (StringWriter.) + sm-json-str (do + (.appendTo (.getSourceMap closure-compiler) sw name') + (.toString sw))] + (when (true? (:closure-source-map opts)) + (spit (io/file name') sm-json-str)) + (emit-optimized-source-map + (json/read-str sm-json-str :key-fn keyword) + sources name + (assoc opts + :preamble-line-count + (+ (- (count (.split #"\r?\n" (make-preamble opts) -1)) 1) + (if (:output-wrapper opts) 1 0)))))) + source)) + (report-failure result)))) + +(comment + ;; optimize JavaScript strings + (optimize {:optimizations :whitespace} "var x = 3 + 2; alert(x);") + ;; => "var x=3+2;alert(x);" + (optimize {:optimizations :simple} "var x = 3 + 2; alert(x);") + ;; => "var x=5;alert(x);" + (optimize {:optimizations :advanced} "var x = 3 + 2; alert(x);") + ;; => "alert(5);" + + ;; optimize a ClojureScript form + (optimize {:optimizations :simple} (-compile '(def x 3) {})) + ) + +;; Output +;; ====== +;; +;; The result of a build is always a single string of JavaScript. The +;; build process may produce files on disk but a single string is +;; always output. What this string contains depends on whether the +;; input has been optimized or not. If the :output-to option is set +;; then this string will be written to the specified file. If not, it +;; will be returned. +;; +;; The :output-dir option can be used to set the working directory +;; where any files will be written to disk. By default this directory +;; is 'out'. +;; +;; If inputs are optimized then the output string will be the complete +;; application with all dependencies included. +;; +;; For unoptimized output, the string will be a Closure deps file +;; describing where the JavaScript files are on disk and their +;; dependencies. All JavaScript files will be located in the working +;; directory, including any dependencies from the Closure library. +;; +;; Unoptimized mode is faster because the Closure Compiler is not +;; run. It also makes debugging much simpler because each file is +;; loaded in its own script tag. +;; +;; When working with uncompiled files, you will need to add additional +;; script tags to the hosting HTML file: one which pulls in Closure +;; library's base.js and one which calls goog.require to load your +;; code. See samples/hello/hello-dev.html for an example. + +(defn ^String path-relative-to + "Generate a string which is the path to the input IJavaScript relative + to the specified base file." + [^File base input] + (let [base-path (util/path-seq (.getCanonicalPath base)) + input-path (util/path-seq (.getCanonicalPath (io/file (deps/-url input)))) + count-base (count base-path) + common (count (take-while true? (map #(= %1 %2) base-path input-path))) + prefix (repeat (- count-base common 1) "..")] + (if (= count-base common) + (last input-path) ;; same file + (util/to-path (concat prefix (drop common input-path)) "/")))) + +(defn add-dep-string + "Return a goog.addDependency string for an input." + [opts input] + (letfn [(ns-list [coll] (when (seq coll) (apply str (interpose ", " (map #(str "'" (comp/munge %) "'") coll)))))] + (str "goog.addDependency(\"" + (path-relative-to + (io/file (util/output-directory opts) "goog" "base.js") input) + "\", [" + (ns-list (deps/-provides input)) + "], [" + ;; even under Node.js where runtime require is possible + ;; this is necessary - see CLJS-2151 + (ns-list (cond->> + ;; remove the global js namespace, it's not real + ;; comes from :refer-global + ;; remove external? foreign deps - they are already loaded + ;; in the environment, there is nothing to do. + ;; :require-global is the typical case here + (->> (deps/-requires input) ;; returns nses as strings, not symbols + (remove #{"js"}) + (remove ana/external-dep?)) + ;; under Node.js we emit native `require`s for these + (= :nodejs (:target opts)) + (filter (complement ana/node-module-dep?)))) + "]" + (if (deps/-foreign? input) ", {'foreign-lib': true}") + ");\n"))) + +(defn deps-file + "Return a deps file string for a sequence of inputs." + [opts sources] + (apply str (map #(add-dep-string opts %) sources))) + +(comment + (path-relative-to (io/file "out/goog/base.js") {:url (deps/to-url "out/cljs/core.js")}) + (add-dep-string {} {:url (deps/to-url "out/cljs/core.js") :requires ["goog.string"] :provides ["cljs.core"]}) + (deps-file {:output-dir "pubic/js"} [{:url (deps/to-url "out/cljs/core.js") :requires ["goog.string"] :provides ["cljs.core"]}]) + ) + +(defn elide-strict [js {:keys [elide-strict] :as opts}] + (cond-> js + (not (false? elide-strict)) (string/replace #"(?m)^['\"]use strict['\"]" " "))) + +(defn ^File fingerprint-out-file + [content ^File out-file] + (let [dir (.getParent out-file) + fn (.getName out-file) + idx (.lastIndexOf fn ".") + ext (subs fn (inc idx)) + name (subs fn 0 idx)] + (io/file dir + (str name "-" + (string/lower-case + (util/content-sha content 7)) "." ext)))) + +(defn output-one-file [{:keys [output-to fingerprint] :as opts} js] + (let [js (elide-strict js opts)] + (cond + (nil? output-to) js + + (or (string? output-to) + (util/file? output-to)) + (let [f (io/file output-to)] + (util/mkdirs f) + (spit f js) + (when fingerprint + (let [dir (.getParent f) + mf (io/file dir "manifest.edn") + g (fingerprint-out-file js f)] + (.renameTo f g) + (spit mf (pr-str {(.toString f) (.toString g)}))))) + + :else (println js)))) + +(defn output-deps-file [opts sources] + (output-one-file opts (deps-file opts sources))) + +(declare foreign-deps-str add-header add-source-map-link) + +(defn preloads + ([syms] + (preloads syms nil)) + ([syms mode] + (letfn [(preload-str [sym] + (str (when (= :browser mode) "document.write('');\n" "\n")))] + (map preload-str syms)))) + +(defn bundle? [opts] + (false? (:nodejs-rt opts))) + +(defn export-dep [dep] + (str "\""dep "\": require('" dep "')" )) + +(defn npm-deps-js + "Returns the JavaScript code to support runtime require of bundled modules." + [node-requires] + (str + "module.exports = {\n" + " npmDeps: {\n" + (string/join ",\n" (map (comp #(str " " %) export-dep) node-requires)) + " }\n" + "};\n")) + +(defn output-main-file + "Output an entry point. In the non-modules case, opts is simply compiler + options. When emitting a module entry point, opts must contain :module-name." + [opts] + (assert (or (not (contains? opts :module-name)) + (get (:modules opts) (:module-name opts))) + (str "Module " (:module-name opts) " does not exist")) + (let [module (get (:modules opts) (:module-name opts))] + (output-one-file + (merge opts + (when module + {:output-to (:output-to module)})) + (if-let [target-fn (opts-fn :target-fn opts)] + (target-fn opts) + (let [asset-path (or (:asset-path opts) + (util/output-directory opts)) + closure-defines (json/write-str (:closure-defines opts))] + (case (:target (cond-> opts (bundle? opts) (dissoc :target))) + :nodejs + (add-header opts + (str (when (or (not module) (= :cljs-base (:module-name opts))) + (str "var path = require(\"path\");\n" + "try {\n" + " require(\"source-map-support\").install();\n" + "} catch(err) {\n" + "}\n" + "require(path.join(path.resolve(\".\"),\"" asset-path "\",\"goog\",\"bootstrap\",\"nodejs.js\"));\n" + "require(path.join(path.resolve(\".\"),\"" asset-path "\",\"cljs_deps.js\"));\n" + "goog.global.CLOSURE_UNCOMPILED_DEFINES = " closure-defines ";\n" + (apply str (preloads (:preloads opts))))) + (apply str + (map (fn [entry] + (str "goog.require(\"" (comp/munge entry) "\");\n")) + (if-let [entries (when module (:entries module))] + entries + [(:main opts)]))) + (when (:nodejs-rt opts) + "goog.require(\"cljs.nodejscli\");\n"))) + + :webworker + (str (when (or (not module) (= :cljs-base (:module-name opts))) + (str "var CLOSURE_BASE_PATH = \"" asset-path "/goog/\";\n" + "var CLOSURE_UNCOMPILED_DEFINES = " closure-defines ";\n" + "var CLOSURE_IMPORT_SCRIPT = (function(global) { return function(src) {global['importScripts'](src); return true;};})(this);\n" + "if(typeof goog == 'undefined') importScripts(\"" asset-path "/goog/base.js\");\n" + "importScripts(\"" asset-path "/cljs_deps.js\");\n" + (apply str (preloads (:preloads opts))))) + (apply str + (map (fn [entry] + (when-not (= "goog" entry) + (str "goog.require(\"" (comp/munge entry) "\");\n"))) + (if-let [entries (when module (:entries module))] + entries + (when-let [main (:main opts)] + [main]))))) + + (str + (when (bundle? opts) + "import {npmDeps} from \"./npm_deps.js\";\n") + (when (or (not module) (= :cljs-base (:module-name opts))) + (str + "window.CLOSURE_UNCOMPILED_DEFINES = " closure-defines ";\n" + "window.CLOSURE_NO_DEPS = true;\n" + "if(typeof goog == \"undefined\") document.write('');\n" + "document.write('');\n" + "document.write('');\n" + "document.write('');\n" + (apply str (preloads (:preloads opts) :browser)))) + (apply str + (map (fn [entry] + (when-not (= "goog" entry) + (str "document.write('');\n"))) + (if-let [entries (when module (:entries module))] + entries + (when-let [main (:main opts)] + [main])))) + (when (bundle? opts) + (str + "window.require = function(lib) {\n" + " return npmDeps[lib];\n" + "}\n"))))))))) + +(defn fingerprinted-modules [modules fingerprint-info] + (into {} + (map + (fn [[module-name module-info]] + (let [module-info' + (assoc module-info :output-to + (get-in fingerprint-info + [module-name :output-to-fingerprint]))] + [module-name module-info']))) + modules)) + +(defn output-modules + "Given compiler options, original IJavaScript sources and a sequence of + module name and module description tuples output module sources to disk. + Modules description must define :output-to and supply :source entry with + the JavaScript source to write to disk." + [opts js-sources modules] + (let [fingerprint-info (atom {})] + (doseq [[name {:keys [output-to source foreign-deps] :as module-desc}] modules] + (assert (not (nil? output-to)) + (str "Module " name " does not define :output-to")) + (assert (not (nil? source)) + (str "Module " name " did not supply :source")) + (let [fdeps-str (when-not (empty? foreign-deps) + (foreign-deps-str opts foreign-deps)) + sm-name (when (:source-map opts) + (str output-to ".map")) + out-file (io/file output-to) + _ (util/mkdirs out-file) + js (as-> source source + (if (= name :cljs-base) + (add-header opts source) + source) + (if fdeps-str + (str fdeps-str "\n" source) + source) + (elide-strict source opts) + (if sm-name + (add-source-map-link + (assoc opts + :output-to output-to + :source-map sm-name) + source) + source)) + fingerprint-base? (and (:fingerprint opts) (= :cljs-base name))] + (when-not fingerprint-base? + (spit out-file js)) + (when (:fingerprint opts) + (let [out-file' (fingerprint-out-file js out-file)] + (when-not fingerprint-base? + (.renameTo out-file out-file')) + (swap! fingerprint-info update name merge + (when fingerprint-base? {:source js}) + {:output-to (.toString output-to) + :output-to-fingerprint (.toString out-file')}))) + (when (:source-map opts) + (let [sm-json-str (:source-map-json module-desc) + sm-json (json/read-str sm-json-str :key-fn keyword)] + (when (true? (:closure-source-map opts)) + (spit (io/file (:source-map-name module-desc)) sm-json-str)) + (emit-optimized-source-map sm-json js-sources sm-name + (merge opts + {:source-map sm-name + :preamble-line-count + (if (= name :cljs-base) + (+ (- (count (.split #"\r?\n" (make-preamble opts) -1)) 1) + (if (:output-wrapper opts) 1 0) + (if (:fingerprint opts) 1 0)) + 0) + :foreign-deps-line-count + (if fdeps-str + (- (count (.split #"\r?\n" fdeps-str -1)) 1) + 0)})))))) + (when (:fingerprint opts) + (let [fi @fingerprint-info + g (get-in fi [:cljs-base :output-to-fingerprint]) + out (io/file g) + dir (.getParent out) + mnf (io/file dir "manifest.edn") + uris (module-graph/modules->module-uris + (fingerprinted-modules modules fi) js-sources opts)] + (spit mnf + (pr-str + (into {} + (map (juxt :output-to :output-to-fingerprint)) + (vals fi)))) + (spit out + (str "var COMPILED_MODULE_URIS = " + (json/write-str + (into {} + (map (fn [[k v]] [(-> k name munge) v])) uris)) + ";\n" + (get-in fi [:cljs-base :source]))))))) + +(defn lib-rel-path [{:keys [lib-path url provides] :as ijs}] + (if (nil? lib-path) + (util/ns->relpath (first provides) "js") + (if (.endsWith lib-path ".js") + (util/get-name url) + (let [path (util/path url) + lib-path (util/normalize-path lib-path)] + (subs path (+ (inc (.lastIndexOf path lib-path)) (.length lib-path))))))) + +(defn ^String rel-output-path + "Given a IJavaScript which points to a .js file either in memory, in a jar file, + or is a foreign lib, return the path relative to the output directory." + ([js] + (rel-output-path js + (when env/*compiler* + (:options @env/*compiler*)))) + ([js opts] + (let [url (deps/-url js opts)] + (cond + url + (cond + (deps/-closure-lib? js) (lib-rel-path js) + (deps/-foreign? js) (or (deps/-relative-path js opts) + (util/relative-name url)) + :else (path-from-jarfile url)) + + (string? js) + (str (util/content-sha js 7) ".js") + + :else (str (random-string 5) ".js"))))) + +(defn get-source-files [js-modules opts] + (map (fn [lib] + (let [file (if-let [file-min (and (#{:advanced :simple} (:optimizations opts)) + (:file-min lib))] + file-min + (:file lib))] + (js-source-file file (deps/-source lib)))) + js-modules)) + +(defn make-convert-js-module-options [opts] + (-> opts + (select-keys + [:closure-warnings :closure-extra-annotations :pretty-print + :language-in :language-out :closure-module-roots :rewrite-polyfills]) + (assoc-in [:closure-warnings :non-standard-jsdoc] :off) + (set-options (CompilerOptions.)))) + +(defn module-type->keyword [^CompilerInput$ModuleType module-type] + (case (.name module-type) + "NONE" :none + "GOOG" :goog + "ES6" :es6 + "COMMONJS" :commonjs + "JSON" :json + "IMPORTED_SCRIPT" :imported-script)) + +(defn add-converted-source + [closure-compiler inputs-by-name opts {:keys [file-min file provides requires] :as ijs}] + (let [processed-file (if-let [min (and (#{:advanced :simple} (:optimizations opts)) + file-min)] + min + file) + processed-file (string/replace processed-file "\\" "/") + ^CompilerInput input (get inputs-by-name processed-file) + ^Node ast-root (.getAstRoot input closure-compiler) + provides (distinct (map #(ModuleNames/fileToModuleName %) + (cons processed-file provides))) + ;; getJsModuleType returns NONE for ES6 files, but getLoadsFlags module returns es6 for those + module-type (or (some-> (.get (.getLoadFlags input) "module") keyword) + (module-type->keyword (.getJsModuleType input)))] + (assoc ijs + :module-type module-type + :source + ;; Add goog.provide/require calls ourselves, not emited by Closure since + ;; https://github.com/google/closure-compiler/pull/2641 + (str + (apply str (map (fn [n] + (str "goog.provide(\"" n "\");\n")) + provides)) + (->> (.getRequires input) + ;; v20180204 returns string + ;; next Closure returns DependencyInfo.Require object + (map (fn [i] + (if (string? i) + i + (.getSymbol i)))) + ;; If CJS/ES6 module uses goog.require, goog is added to requires + ;; but this would cause problems with Cljs. + (remove #{"goog"}) + (map (fn [n] + (str "goog.require(\"" n "\");\n"))) + (apply str)) + (.toSource closure-compiler ast-root))))) + +(defn- sorting-dependency-options [] + (DependencyOptions/sortOnly)) + +(defn convert-js-modules + "Takes a list JavaScript modules as an IJavaScript and rewrites them into a Google + Closure-compatible form. Returns list IJavaScript with the converted module + code set as source." + [js-modules opts] + (let [^List externs '() + ^List source-files (get-source-files js-modules opts) + ^CompilerOptions options (doto (make-convert-js-module-options opts) + (.setProcessCommonJSModules true) + (.setLanguageIn (lang-key->lang-mode :ecmascript6)) + (.setLanguageOut (lang-key->lang-mode (:language-out opts :ecmascript3))) + (.setDependencyOptions (sorting-dependency-options)) + (.setPackageJsonEntryNames ^List (package-json-entries opts))) + closure-compiler (doto (make-closure-compiler) + (.init externs source-files options)) + _ (.parse closure-compiler) + _ (report-failure (.getResult closure-compiler)) + inputs-by-name (into {} (map (juxt #(.getName %) identity) (vals (.getInputsById closure-compiler))))] + + ;; This will take care of converting ES6 to CJS + ;; Based on language-in setting, this could also handle ES7/8/TypeScript transpilation. + (.transpileAndDontCheck closure-compiler) + ;; This will rewrite CommonJS modules + (.whitespaceOnlyPasses closure-compiler) + + (map (partial add-converted-source + closure-compiler inputs-by-name opts) + js-modules))) + +(defmulti js-transforms + "Takes an IJavaScript with the source code set as source, transforms the + source code and returns an IJavascript with the new code set as source." + (fn [ijs opts] + (:preprocess ijs))) + +(defmethod js-transforms :default [ijs opts] + (ana/warning :unsupported-preprocess-value @env/*compiler* ijs) + ijs) + +(defn url->nio-path [url] + (let [raw-uri (.toURI url) + arr (-> raw-uri .toString (.split "!")) + uri (-> arr (aget 0) URI/create) + fs (try + (FileSystems/getFileSystem uri) + (catch Throwable t + (FileSystems/newFileSystem uri (HashMap.))))] + (.getPath fs ^String (.toString raw-uri) (make-array String 0)))) + +(defn add-goog-load [source] + (let [sb (StringBuilder.) + module (-> (SimpleDependencyInfo/builder "" "") + (.setGoogModule true) .build) + bundler (ClosureBundler.)] + (.appendTo bundler sb module source) + (.toString sb))) + +(defn ^DiagnosticGroup es5-warnings [] + (DiagnosticGroup. + (into-array DiagnosticType + [(DiagnosticType/error "JSC_CANNOT_CONVERT" "")]))) + +(defn ^CompilerOptions transpile-options [] + (doto (CompilerOptions.) + (.setQuoteKeywordProperties true) + (.setSkipNonTranspilationPasses true) + (.setVariableRenaming VariableRenamingPolicy/OFF) + (.setPropertyRenaming PropertyRenamingPolicy/OFF) + (.setWrapGoogModulesForWhitespaceOnly false) + (.setPrettyPrint true) + (.setSourceMapOutputPath "/dev/null") + (.setSourceMapIncludeSourcesContent true) + (.setWarningLevel (es5-warnings) CheckLevel/OFF))) + +(defn closure-transpile + "Transpile a single JavaScript file to JavaScript. Used to lower Closure + Library files written in more recent versions of the JavaScript standard." + ([rsc opts] + (closure-transpile (util/path rsc) (slurp rsc) opts)) + ([path source opts] + (let [cc (make-closure-compiler) + cc-opts (set-options opts (transpile-options)) + externs (SourceFile/fromCode "externs.js" "function Symbol() {}") + source (SourceFile/fromCode path source) + result (.compile cc externs source cc-opts)] + ;; TODO: error handling + (.toSource cc)))) + +;; TODO: better error handling +(defn transpile + [{:keys [language-out] :or {language-out :es3} :as opts} rsc {:keys [module lang] :as js}] + (let [source (slurp rsc) + source' (if (and lang + (< (.indexOf lang-level (expand-lang-key language-out)) + (.indexOf lang-level (expand-lang-key lang)))) + (closure-transpile (util/path rsc) source opts) + source)] + (str "/*TRANSPILED*/" + (cond-> source' + (= :goog module) add-goog-load)))) + +(defn requires-transpile? [out-file] + (let [line (first (line-seq (io/reader out-file)))] + (not (string/starts-with? line "/*TRANSPILED*/")))) + +(comment + (println (slurp (io/resource "goog/math/long.js"))) + + (deps/parse-js-ns (-> (io/resource "goog/math/long.js") io/reader line-seq)) + (deps/parse-js-ns (-> (io/resource "goog/string/stringbuffer.js") io/reader line-seq)) + + (url->nio-path (io/resource "goog/math/long.js")) + + (println + (maybe-transpile {} (io/resource "goog/math/long.js") {:module :goog :lang :es6})) + ) + +(defn transpile? [opts {:keys [module lang]}] + (or module lang)) + +(defn write-javascript + "Write or copy a JavaScript file to output directory. Only write if the file + does not already exist. Return IJavaScript for the file on disk at the new + location." + [{:keys [optimizations] :as opts} js] + (let [out-dir (io/file (util/output-directory opts)) + out-name (rel-output-path js opts) + out-file (io/file out-dir out-name) + res (or (:url js) (:source-file js)) + js-module? (and res out-dir + (.startsWith (util/path res) (util/path out-dir))) ;; We already Closure processed it and wrote it out + transpile? (transpile? opts js) + ijs (merge + {:requires (deps/-requires js) + :provides (deps/-provides js) + :group (:group js)} + (when-not js-module? + {:url (deps/to-url out-file) + :out-file (.toString out-file)}))] + (when (and (not js-module?) + (or (not (.exists out-file)) + (and res (util/changed? out-file res)) + ;; always re-emit GCL libs under optimizations higher than :none + ;; :none will just use the cached transpiled result + (and transpile? + (or (not= :none optimizations) + (requires-transpile? out-file))))) + (when (and res (or ana/*verbose* (:verbose opts))) + (util/debug-prn "Copying" (str res) "to" (str out-file))) + (util/mkdirs out-file) + (if (and transpile? (= :none optimizations)) + (spit out-file (transpile opts res js)) + (spit out-file (deps/-source js))) + (when res + (.setLastModified ^File out-file (util/last-modified res)))) + (if (map? js) + (merge js ijs) + ijs))) + +(defn write-js? + "Returns true if IJavaScript instance needs to be written/copied to output + directory. True when in memory, in a JAR, or if foreign library." + [js] + (try + (let [url ^URL (deps/-url js)] + (or (not url) + (= (.getProtocol url) "jar") + (deps/-closure-lib? js) + (deps/-foreign? js))) + (catch Throwable t + (throw (util/compilation-error (Exception. (str "Could not write JavaScript " (pr-str js)))))))) + +(defn source-on-disk + "Ensure that the given IJavaScript exists on disk in the output directory. + Return updated IJavaScript with the new location if necessary." + [opts js] + (if (write-js? js) + (write-javascript opts js) + ;; always copy original ClojureScript sources to the output directory + ;; when source maps enabled + (let [source-url (:source-url js) + out-file (when-let [ns (and (:source-map opts) + source-url + (first (:provides js)))] + (io/file (io/file (util/output-directory opts)) + (util/ns->relpath ns (util/ext source-url))))] + (when (and out-file source-url + (or (not (.exists ^File out-file)) + (util/changed? (io/file source-url) out-file))) + (do + (when (or ana/*verbose* (:verbose opts)) + (util/debug-prn "Copying" (str source-url) "to" (str out-file))) + (util/mkdirs out-file) + (spit out-file (slurp source-url)) + (.setLastModified ^File out-file (util/last-modified source-url)))) + js))) + +(comment + (write-javascript {} "goog.provide('demo');\nalert('hello');\n") + ;; write something from a jar file to disk + (source-on-disk {} + {:url (io/resource "goog/base.js") + :source (with-open [reader (io/reader (io/resource "goog/base.js"))] + (slurp reader))}) + ;; doesn't write a file that is already on disk + (source-on-disk {} {:url (io/resource "cljs/core.cljs")}) + ) + +(defn output-unoptimized + "Ensure that all JavaScript source files are on disk (not in jars), + write the goog deps file including only the libraries that are being + used and write the deps file for the current project. + + The deps file for the current project will include third-party + libraries." + [{:keys [modules] :as opts} & sources] + ;; this source-on-disk call is currently necessary for REPLs - David + (let [disk-sources (doall (map #(source-on-disk opts %) sources)) + goog-deps (io/file (util/output-directory opts) "goog" "deps.js") + main (:main opts) + output-deps #(output-deps-file + (assoc opts :output-to + (str (util/output-directory opts) + File/separator "cljs_deps.js")) + disk-sources)] + (util/mkdirs goog-deps) + (spit goog-deps (slurp (io/resource "goog/deps.js"))) + (when (:debug-inputs opts) + (util/debug-prn "DEBUG: all compiler inputs") + (util/debug-prn (pr-str sources))) + (cond + modules + (let [modules' (module-graph/expand-modules modules sources)] + (output-deps) + (doall + (map + (fn [[module-name _]] + (output-main-file + (merge opts + {:module-name module-name + :modules modules'}))) + modules))) + + (and main (not= :none (:target opts))) + (do + (output-deps) + (output-main-file opts)) + + :else (output-deps-file opts disk-sources)))) + +(defn get-upstream-deps* + "returns a merged map containing all upstream dependencies defined + by libraries on the classpath." + ([] + (get-upstream-deps* (. (Thread/currentThread) (getContextClassLoader)))) + ([classloader] + (let [upstream-deps (map #(read-string (slurp %)) + (enumeration-seq (. classloader (getResources "deps.cljs"))))] + (apply merge-with + (fn [a b] + (if (map? a) + (merge-with #(into #{%1} #{%2}) a b) + (concat a b))) + upstream-deps)))) + +(def get-upstream-deps (memoize get-upstream-deps*)) + +(defn add-header [opts js] + (str (make-preamble opts) js)) + +(defn foreign-deps-str [opts sources] + (letfn [(to-js-str [ijs] + (if-let [url (or (and (#{:advanced :simple} (:optimizations opts)) + (:url-min ijs)) + (:url ijs))] + (slurp url) + (throw (util/compilation-error (IllegalArgumentException. + (str "Foreign lib " ijs " does not exist"))))))] + (str (string/join "\n" (map to-js-str sources)) "\n"))) + +(defn add-wrapper [{:keys [output-wrapper] :as opts} js] + (if output-wrapper + (cond + (fn? output-wrapper) (output-wrapper js) + (string? output-wrapper) (format output-wrapper js) + :else (str ";(function(){\n" js "\n})();\n")) + js)) + +(defn add-source-map-link [{:keys [source-map output-to] :as opts} js] + (if source-map + (if (= output-to :print) + (str js "\n//# sourceMappingURL=" source-map "\n\n") + (str js "\n//# sourceMappingURL=" (path-relative-to (io/file output-to) {:url source-map}) "\n\n")) + js)) + +(defn absolute-path? [path] + (.isAbsolute (io/file path))) + +(defn absolute-parent [path] + (.getParent (.getAbsoluteFile (io/file path)))) + +(defn in-same-dir? + "Checks that path-1 and path-2 are siblings in the same logical directory." + [path-1 path-2] + (= (absolute-parent path-1) + (absolute-parent path-2))) + +(defn same-or-subdirectory-of? + "Checks that path names a file or directory that is the dir or a subdirectory there of." + [dir path] + (let [dir-path (.getAbsolutePath (io/file dir)) + path-path (.getAbsolutePath (io/file path))] + (.startsWith path-path dir-path))) + +(defn check-output-to [{:keys [output-to] :as opts}] + (when (contains? opts :output-to) + (assert (or (string? output-to) + (= :print output-to)) + (format ":output-to %s must specify a file or be :print" + (pr-str output-to)))) + true) + +(defn check-output-dir [{:keys [output-dir] :as opts}] + (when (contains? opts :output-dir) + (assert (string? output-dir) + (format ":output-dir %s must specify a directory" + (pr-str output-dir)))) + true) + +(defn check-source-map + "When :source-map is specified in opts, " + [{:keys [output-to source-map output-dir optimizations] :as opts}] + (when (and (contains? opts :source-map) + (:source-map opts) + (not (= optimizations :none))) + (assert (and (or (contains? opts :output-to) + (contains? opts :modules)) + (contains? opts :output-dir)) + (str ":source-map cannot be specified without also specifying :output-dir " + "and either :output-to or :modules if optimization setting applied")) + (assert (or (nil? (:output-to opts)) (:modules opts) (string? source-map)) + (format (str ":source-map %s must specify a file in the same directory " + "as :output-to %s if optimization setting applied") + (pr-str source-map) + (pr-str output-to))) + (assert (or (nil? (:output-to opts)) (:modules opts) (in-same-dir? source-map output-to)) + (format (str ":source-map %s must specify a file in the same directory as " + ":output-to %s if optimization setting applied") + (pr-str source-map) + (pr-str output-to))) + (assert (or (nil? (:output-to opts)) (:modules opts) (same-or-subdirectory-of? (absolute-parent output-to) output-dir)) + (format (str ":output-dir %s must specify a directory in :output-to's " + "parent %s if optimization setting applied") + (pr-str output-dir) + (pr-str (absolute-parent output-to))))) + (when (and (contains? opts :source-map) + (= optimizations :none)) + (assert (util/boolean? source-map) + (format ":source-map must be true or false when compiling with :optimizations :none but it is: %s" + (pr-str source-map)))) + true) + +(defn check-source-map-path [{:keys [source-map-path] :as opts}] + (when (contains? opts :source-map-path) + (assert (string? source-map-path) + (format ":source-map-path %s must be a directory" + source-map-path)) + (when-not (= (:optimizations opts) :none) + (assert (and (contains? opts :output-to) + (contains? opts :source-map)) + (str ":source-map-path cannot be specified without also specifying " + ":output-to and :source-map if optimization setting applied")))) + true) + +(defn check-output-wrapper [{:keys [output-wrapper optimizations]}] + (assert (not (and output-wrapper (= :whitespace optimizations))) + ":output-wrapper cannot be combined with :optimizations :whitespace")) + +(defn check-node-target [{:keys [nodejs-rt optimizations] :as opts}] + (assert (not (and nodejs-rt (= optimizations :whitespace))) + (format ":nodejs target not compatible with :whitespace optimizations")) + (assert (not (and nodejs-rt (= optimizations :none) (not (contains? opts :main)))) + (format ":nodejs target with :none optimizations requires a :main entry"))) + +(defn check-main [{:keys [main] :as opts}] + (when main + (assert (or (symbol? main) (string? main)) + (format ":main must be a symbol or string, got %s instead" main)) + (when (symbol? main) + (assert (not (string/starts-with? (str main) "'")) + (format ":main must be an unquoted symbol, got %s instead" main))))) + +(defn check-preloads [{:keys [preloads optimizations] :as opts}] + (when (and (some? preloads) + (not= preloads '[process.env]) + (not= optimizations :none)) + (binding [*out* *err*] + (println "WARNING: :preloads should only be specified with :none optimizations")))) + +(defn check-cache-analysis-format [{:keys [cache-analysis cache-analysis-format] :as opts}] + (assert (not (and cache-analysis + ((complement #{:edn :transit}) cache-analysis-format) + (not (nil? cache-analysis-format)))) + (format ":cache-analysis format must be :edn or :transit but it is: %s" + (pr-str cache-analysis-format)))) + +(defn check-npm-deps [{:keys [npm-deps]}] + (let [npm-deps (if (true? npm-deps) {} npm-deps) + {ups-npm-deps :npm-deps} (get-upstream-deps) + conflicts (filter (fn [[dep v]] + (and (coll? v) (not (contains? npm-deps dep)))) + ups-npm-deps)] + (binding [*out* *err*] + (doseq [[dep versions] conflicts] + (println (str "WARNING: NPM dependency " (name dep) + " conflicts between versions " + (util/conjunction-str versions) + ". Specify a version in :npm-deps or the latest will be installed.")))))) + +(defn foreign-source? [js] + (and (satisfies? deps/IJavaScript js) + (deps/-foreign? js))) + +(defn expand-libs + "EXPERIMENTAL. Given a set of libs expand any entries which only name + directories into a sequence of lib entries for all JS files recursively + found in that directory. All other options will be shared with the original + entry. The computed :provides assumes the specified directory is on the + classpath." + [libs] + (letfn [(prep-path [p root] + (subs (string/replace (subs p 0 (- (count p) 3)) root "") 1)) + (path->provides [p] + (let [p' (string/replace p File/separator ".")] + (cond-> [p'] + (string/includes? p' "_") + (conj (string/replace p' "_" "-"))))) + (expand-lib* [{:keys [file] :as lib}] + (if-not file + [lib] ;; foreign-lib override case - David + (let [root (.getAbsolutePath (io/file file)) + dir (io/file file)] + (if (.isDirectory dir) + (into [] + (comp + (filter #(.endsWith (.getName ^File %) ".js")) + (filter #(not (.isHidden ^File %))) + (map + (fn [^File f] + (let [p (.getPath f) + ap (.getAbsolutePath f)] + (merge lib + {:file p :provides (path->provides (prep-path ap root))}))))) + (file-seq dir)) + [lib]))))] + (into [] (mapcat expand-lib* libs)))) + +(declare index-node-modules) + +(defn compute-upstream-npm-deps + ([] + (compute-upstream-npm-deps + (when env/*compiler* + (:options @env/*compiler*)))) + ([{:keys [npm-deps]}] + (let [{ups-npm-deps :npm-deps} (get-upstream-deps)] + (reduce + (fn [m [dep v]] + (cond-> m + (and (or (nil? npm-deps) (map? npm-deps)) + (not (contains? npm-deps dep))) + (assoc dep (if (coll? v) + (last (sort v)) + v)))) + {} ups-npm-deps)))) + +(defn ensure-module-opts [opts] + (update opts :modules + #(ensure-cljs-base-module % opts))) + +(defn shim-process? + [{:keys [target process-shim] :as opts}] + (if (= :nodejs target) + (true? process-shim) + (not (false? process-shim)))) + +(defn normalize-closure-defines [defines] + (into {} + (map (fn [[k v]] + [(if (symbol? k) (str (comp/munge k)) k) v]) + defines))) + +(defn resolve-warning-handlers [fns] + (reduce + (fn [ret afn] + (cond + (fn? afn) (conj ret afn) + + (symbol? afn) + (let [afn' (sym->var afn :warning-handlers)] + (when-not afn' + (throw + (ex-info (str "Could not resolve warning handler: " afn) + {:warning-handlers fns + :clojure.error/phase :compilation}))) + (conj ret afn')) + + :else + (throw + (ex-info (str "Invalid warning handler " afn " of type " (type afn)) + {:warning-handlers fns + :clojure.error/phase :compilation})))) + [] fns)) + +(defn add-implicit-options + [{:keys [optimizations output-dir] + :or {optimizations :none + output-dir "out"} + :as opts}] + (let [opts (cond-> opts + (shim-process? opts) + (-> (update-in [:preloads] (fnil conj []) 'process.env) + (cond-> + (not= :none optimizations) + (update-in [:closure-defines 'process.env/NODE_ENV] (fnil str "production")))) + + (or (:closure-defines opts) (shim-process? opts)) + (update :closure-defines normalize-closure-defines) + + (:browser-repl opts) + (update-in [:preloads] (fnil conj []) 'clojure.browser.repl.preload) + + (and (contains? opts :modules) + (not (contains? opts :stable-names))) + (assoc :stable-names true)) + {:keys [libs foreign-libs externs]} (get-upstream-deps) + emit-constants (or (and (= optimizations :advanced) + (not (false? (:optimize-constants opts)))) + (:optimize-constants opts))] + (cond-> + (-> opts + (assoc + :optimizations optimizations + :output-dir output-dir + :ups-libs libs + :ups-foreign-libs (expand-libs foreign-libs) + :ups-externs externs + :emit-constants emit-constants + :cache-analysis-format (:cache-analysis-format opts :transit))) + + (not (:lite-mode opts)) + (update-in [:preamble] #(into (or % []) ["cljs/imul.js"])) + + (:lite-mode opts) + (assoc-in [:closure-defines (str (comp/munge 'cljs.core/LITE_MODE))] + (:lite-mode opts)) + + (:target opts) + (assoc-in [:closure-defines (str (comp/munge 'cljs.core/*target*))] + (name (:target opts))) + + (= :nodejs (:target opts)) + (merge + (when (nil? (:nodejs-rt opts)) + {:nodejs-rt true})) + + ;; :bundle is just sugar + (= :bundle (:target opts)) + (merge + {:hashbang false + :infer-externs true + :nodejs-rt false + :target :nodejs} + (when-not (:npm-deps opts) + {:npm-deps true})) + + (= optimizations :none) + (assoc + :cache-analysis (:cache-analysis opts true) + :source-map (:source-map opts true)) + + (:aot-cache opts) + (assoc :cache-analysis true) + + (= optimizations :advanced) + (cond-> + (not (false? (:static-fns opts))) (assoc :static-fns true) + (not (false? (:optimize-constants opts))) (assoc :optimize-constants true)) + + (nil? (find (:closure-warnings opts) :check-types)) + (assoc-in [:closure-warnings :check-types] :off) + + (nil? (find (:closure-warnings opts) :check-variables)) + (assoc-in [:closure-warnings :check-variables] :off) + + (nil? (:closure-module-roots opts)) + (assoc :closure-module-roots []) + + (nil? (:opts-cache opts)) + (assoc :opts-cache "cljsc_opts.edn") + + (not (contains? opts :aot-cache)) + (assoc :aot-cache false) + + (contains? opts :modules) + (ensure-module-opts) + + (nil? (:language-in opts)) + (assoc :language-in :ecmascript-next) + + (:stable-names opts) + (as-> opts + (let [out-dir (if (true? (:stable-names opts)) + output-dir + (:stable-names opts))] + (merge + {:closure-variable-map-in (io/file out-dir "closure_var.map") + :closure-variable-map-out (io/file out-dir "closure_var.map") + :closure-property-map-in (io/file out-dir "closure_prop.map") + :closure-property-map-out (io/file out-dir "closure_prop.map")} + opts))) + + (nil? (:ignore-js-module-exts opts)) + (assoc :ignore-js-module-exts [".css"]) + + (:warning-handlers opts) + (update :warning-handlers resolve-warning-handlers)))) + +(defn- alive? [proc] + (try (.exitValue proc) false (catch IllegalThreadStateException _ true))) + +(defn- pipe [^Process proc in ^Writer out] + ;; we really do want system-default encoding here + (with-open [^java.io.Reader in (-> in InputStreamReader. BufferedReader.)] + (loop [buf (char-array 1024)] + (when (alive? proc) + (try + (let [len (.read in buf)] + (when-not (neg? len) + (.write out buf 0 len) + (.flush out))) + (catch IOException e + (when (and (alive? proc) (not (.contains (.getMessage e) "Stream closed"))) + (.printStackTrace e *err*)))) + (recur buf))))) + +(defn maybe-install-node-deps! + [{:keys [deps-cmd npm-deps verbose] :or {deps-cmd "npm"} :as opts}] + (let [npm-deps (merge (if (map? npm-deps) + npm-deps + {}) + (compute-upstream-npm-deps opts))] + (when-not (empty? npm-deps) + (let [pkg-json (io/file "package.json")] + (when (or ana/*verbose* verbose) + (util/debug-prn "Installing Node.js dependencies")) + (when-not (.exists pkg-json) + (spit pkg-json "{}")) + (let [proc (-> (ProcessBuilder. + (into (cond->> + [deps-cmd + ({"npm" "install" "yarn" "add"} deps-cmd) + "@cljs-oss/module-deps"] + util/windows? (into ["cmd" "/c"])) + (map (fn [[dep version]] (str (name dep) "@" version))) + npm-deps)) + .start) + is (.getInputStream proc) + iw (StringWriter. (* 16 1024 1024)) + es (.getErrorStream proc) + ew (StringWriter. (* 1024 1024)) + _ (do (.start + (Thread. + (bound-fn [] (pipe proc is iw)))) + (.start + (Thread. + (bound-fn [] (pipe proc es ew))))) + err (.waitFor proc)] + (when (and (not (zero? err)) (not (.isAlive proc))) + (println (str ew))))) + true))) + +(defn node-module-deps + "EXPERIMENTAL: return the foreign libs entries as computed by running + the module-deps package on the supplied JavaScript entry point. Assumes + that the `@cljs-oss/module-deps` NPM package is either locally or globally + installed." + ([entry] + (node-module-deps entry + (when env/*compiler* + (:options @env/*compiler*)))) + ([{:keys [file]} {:keys [target] :as opts}] + ;; NOTE: The code value should only employ single quotes for strings. + ;; If double quotes are used, then when the contents of this file + ;; are passed to node via --eval on Windows, the double quotes + ;; will be elided, leading to syntactically incorrect JavaScript. + (let [main-entries (str "[" (->> (package-json-entries opts) + (map #(str "'" % "'")) + (string/join ",")) "]") + escape-backslashes #(string/replace % "\\" "\\\\") + code (-> (slurp (io/resource "cljs/module_deps.js")) + (string/replace "JS_FILE" (escape-backslashes file)) + (string/replace "CLJS_TARGET" (str "" (when target (name target)))) + (string/replace "MAIN_ENTRIES" main-entries) + (string/replace "FILE_SEPARATOR" (escape-backslashes File/separator))) + proc (-> (ProcessBuilder. ["node" "--eval" code]) + .start) + is (.getInputStream proc) + iw (StringWriter. (* 16 1024 1024)) + es (.getErrorStream proc) + ew (StringWriter. (* 1024 1024)) + _ (do (.start + (Thread. + (bound-fn [] (pipe proc is iw)))) + (.start + (Thread. + (bound-fn [] (pipe proc es ew))))) + err (.waitFor proc)] + (if (zero? err) + (into [] + (map (fn [{:strs [file provides]}] file + (merge + {:file file + ;; Just tag everything es6 here, add-converted-source will + ;; ask the real type, CJS/ES6, from Closure. + :module-type :es6} + (when provides + {:provides provides})))) + (next (json/read-str (str iw)))) + (do + (when-not (.isAlive proc) + (binding [*out* *err*] + (println (str ew)))) + []))))) + +(defn node-inputs + "EXPERIMENTAL: return the foreign libs entries as computed by running + the module-deps package on the supplied JavaScript entry points. Assumes + that the `@cljs-oss/module-deps` NPM package is either locally or globally + installed." + ([entries] + (node-inputs entries + (when env/*compiler* + (:options @env/*compiler*)))) + ([entries opts] + (into [] (distinct (mapcat #(node-module-deps % opts) entries))))) + +(defn index-node-modules + ([modules] + (index-node-modules + modules + (when env/*compiler* + (:options @env/*compiler*)))) + ([modules opts] + (->> (or (:node-modules-dirs opts) ["node_modules"]) + (map io/file) + (mapcat (fn [dir] + (when (and (seq modules) (.exists dir) (.isDirectory dir)) + (let [modules (into #{} (map name) modules) + deps-file (io/file (util/output-directory opts) "cljs$node_modules.js") + old-contents (when (.exists deps-file) + (slurp deps-file)) + new-contents (let [sb (StringBuffer.)] + (run! #(.append sb (str "require('" % "');\n")) modules) + (str sb))] + (util/mkdirs deps-file) + (if (or (not= old-contents new-contents) + (nil? env/*compiler*) + (nil? (::transitive-dep-set @env/*compiler*))) + (do + (spit deps-file new-contents) + (let [transitive-js (node-inputs [{:file (.getAbsolutePath deps-file)}] opts)] + (when-not (nil? env/*compiler*) + (swap! env/*compiler* update-in [::transitive-dep-set] + assoc modules transitive-js)) + transitive-js)) + (when-not (nil? env/*compiler*) + (get-in @env/*compiler* [::transitive-dep-set modules]))))))) + (filterv identity)))) + +(def node-file-seq->libs-spec (memoize node-file-seq->libs-spec*)) + +(defn index-node-modules-dir + ([] + (index-node-modules-dir + (when env/*compiler* + (:options @env/*compiler*)))) + ([opts] + (let [module-fseq (util/module-file-seq opts)] + (node-file-seq->libs-spec module-fseq opts)))) + +(defn preprocess-js + "Given js-module map, apply preprocessing defined by :preprocess value in the map." + [{:keys [preprocess] :as js-module} opts] + (cond + (keyword? preprocess) + (js-transforms js-module opts) + + (symbol? preprocess) + (let [preprocess-var (sym->var preprocess :preprocess {:file (:file js-module)})] + (try + (preprocess-var js-module opts) + (catch Throwable t + (throw (ex-info (str "Error running preprocessing function " preprocess) + {:file (:file js-module) + :preprocess preprocess + :clojure.error/phase :compilation} + t))))) + + :else + (do + (ana/warning :unsupported-preprocess-value @env/*compiler* js-module) + js-module))) + +(defn- to-absolute-path [^String file-str] + (.getAbsolutePath (io/file file-str))) + +(defn process-js-modules + "Given the current compiler options, converts JavaScript modules to Google + Closure modules and writes them to disk. Adds mapping from original module + namespace to new module namespace to compiler env. Returns modified compiler + options where new modules are passed with :libs option." + [opts] + (let [;; Modules from both :foreign-libs (compiler options) and :ups-foreign-libs (deps.cljs) + ;; are processed together, so that files from both sources can depend on each other. + ;; e.g. commonjs module in :foreign-libs can depend on commonjs module from :ups-foreign-libs. + js-modules (filter :module-type (concat (:foreign-libs opts) (:ups-foreign-libs opts)))] + (if (seq js-modules) + (util/measure (:compiler-stats opts) + "Process JS modules" + (let [_ (when-let [unsupported (first (filter (complement #{:es6 :commonjs}) + (map :module-type js-modules)))] + (ana/warning :unsupported-js-module-type @env/*compiler* unsupported)) + ;; Load all modules - add :source so preprocessing and conversion can access it + js-modules (into [] + (comp + (map (fn [lib] + (let [js (deps/load-foreign-library lib) + url (str (deps/-url js opts))] + (if (and url (some (fn [ext] + (.endsWith url ext)) + (:ignore-js-module-exts opts))) + (do + (when (or ana/*verbose* (:verbose opts)) + (util/debug-prn "Ignoring JS module" url "based on the file extension")) + (assoc js :source "")) + (if-let [src (deps/-source js opts)] + (assoc js :source src) + (throw + (ex-info (str "Could not get source for JS module") + {:js-module lib + :clojure.error/phase :compilation}))))))) + (map (fn [js] + (if (:preprocess js) + (preprocess-js js opts) + js))) + (map (fn [js] + (cond-> (update-in js [:file] to-absolute-path) + (some? (:file-min js)) + (update-in [:file-min] to-absolute-path))))) + js-modules) + js-modules (convert-js-modules js-modules opts)] + ;; Write modules to disk, update compiler state and build new options + (reduce (fn [new-opts {:keys [file module-type] :as ijs}] + (let [ijs (write-javascript opts ijs) + module-name (-> (deps/load-library (:out-file ijs)) first :provides first)] + (swap! env/*compiler* + #(assoc-in % [:js-namespaces module-name] {:module-type module-type})) + (doseq [provide (:provides ijs)] + (swap! env/*compiler* + #(update-in % [:js-module-index] assoc provide {:name module-name + :module-type module-type}))) + (-> new-opts + (update-in [:libs] (comp vec conj) (:out-file ijs)) + ;; js-module might be defined in either, so update both + (update-in [:foreign-libs] + (fn [libs] + (into [] + (remove #(= (to-absolute-path (:file %)) file)) + libs))) + (update-in [:ups-foreign-libs] + (fn [libs] + (into [] + (remove #(= (to-absolute-path (:file %)) (to-absolute-path file))) + libs)))))) + opts js-modules))) + opts))) + +(defn load-data-readers! [compiler] + (swap! compiler update-in [:cljs.analyzer/data-readers] merge + (ana/load-data-readers))) + +(defn add-externs-sources [opts] + (cond-> opts + (:infer-externs opts) + (assoc :externs-sources (load-externs (dissoc opts :infer-externs))))) + +(defn handle-js-modules + "Given all Cljs sources (build inputs and dependencies in classpath) + + - index all the node modules + - process the JS modules (preprocess + convert to Closure JS) + - save js-dependency-index for compilation" + [{:keys [npm-deps target] :as opts} js-sources compiler-env] + ;; Find all the top-level Node packages and their files + (let [top-level (reduce + (fn [acc m] + (reduce (fn [acc p] (assoc acc p m)) acc (:provides m))) + {} + ;; if :npm-deps option is false, node_modules/ dir shouldn't be indexed + (if (not (false? npm-deps)) + (index-node-modules-dir))) + requires (->> (mapcat deps/-requires js-sources) + ;; fixup foo$default cases, foo is the lib, default is a property + (map #(-> % ana/lib&sublib first)) + set) + ;; Select Node files that are required by Cljs code, + ;; and create list of all their dependencies + node-required (set/intersection (set (keys top-level)) requires) + expanded-libs (expand-libs (:foreign-libs opts)) + output-dir (util/output-directory opts) + opts (update opts :foreign-libs + (fn [libs] + (into (if (= target :nodejs) + [] + (index-node-modules node-required)) + (into expanded-libs + (node-inputs (filter (fn [{:keys [module-type]}] + (some? module-type)) + expanded-libs)))))) + ;; If compiler-env doesn't contain JS module info we need to process + ;; modules even if files haven't changed since last compile. + opts (if (or (nil? (:js-namespaces @compiler-env)) + (nil? (:js-module-index @compiler-env)) + (some + (fn [ijs] + (let [dest (io/file output-dir (rel-output-path (assoc ijs :foreign true) opts))] + (util/changed? (deps/-url ijs opts) dest))) + (:foreign-libs opts))) + (process-js-modules opts) + (:options @compiler-env))] + (swap! compiler-env + (fn [cenv] + (-> cenv + ;; we need to also track the whole top level - this is to support + ;; cljs.analyze/analyze-deps, particularly in REPL contexts - David + (merge {:js-dependency-index (deps/js-dependency-index opts)}) + (update-in [:options] merge opts) + (update-in [:node-module-index] (fnil into #{}) + (if (= target :nodejs) + (map str node-required) + (map str (keys top-level))))))) + opts)) + +(defn output-bootstrap [{:keys [target target-fn] :as opts}] + (when (or (and (#{:nodejs} target) + (not= (:optimizations opts) :whitespace)) + target-fn) + (let [target-str (name target) + outfile (io/file (util/output-directory opts) + "goog" "bootstrap" (str target-str ".js"))] + ;; not all targets using :target-fn might provide a bootstrap file to include + (when-let [bootstrap-file (io/resource (str "cljs/bootstrap_" target-str ".js"))] + (util/mkdirs outfile) + (spit outfile (slurp bootstrap-file)))))) + +(defn compile-inputs + "Compile inputs and all of their transitive dependencies including JS modules, + libs, and foreign libs. Duplicates the pipeline of build." + [inputs opts] + (env/ensure + (let [sources (-> inputs + (#(map add-core-macros-if-cljs-js %)) + (add-dependency-sources opts)) + opts (handle-js-modules opts sources env/*compiler*) + sources (-> sources + deps/dependency-order + (compile-sources false opts) + (add-js-sources opts) deps/dependency-order + (->> (map #(source-on-disk opts %)) doall))] + sources))) + +(defn compile-ns + "Compiles a namespace and all of its transitive dependencies. + See compile-inputs." + [ns opts] + (compile-inputs (find-sources ns opts) opts)) + +(defn validate-opts [opts] + (check-output-to opts) + (check-output-dir opts) + (check-source-map opts) + (check-source-map-path opts) + (check-output-wrapper opts) + (check-node-target opts) + (check-preloads opts) + (check-cache-analysis-format opts) + (check-main opts) + opts) + +(defn run-bundle-cmd [opts] + (let [cmd-type (or (#{:none} (:optimizations opts)) :default)] + (when-let [cmd (get-in opts [:bundle-cmd cmd-type])] + (let [{:keys [exit out err]} + (try + (apply sh/sh cmd) + (catch Throwable t + (throw + (ex-info (str ":build-cmd " cmd-type " failed") + {:cmd cmd} t))))] + (when-not (== 0 exit) + (throw + (ex-info (str ":bundle-cmd " cmd-type " failed") + {:cmd cmd :exit-code exit :stdout out :stderr err}))))))) + +(defn build + "Given compiler options, produce runnable JavaScript. An optional source + parameter may be provided." + ([opts] + (build nil opts)) + ([source opts] + (build source opts + (if-not (nil? env/*compiler*) + env/*compiler* + (env/default-compiler-env + ;; need to dissoc :foreign-libs since we won't know what overriding + ;; foreign libspecs are referring to until after add-implicit-options + ;; - David + (add-externs-sources (dissoc opts :foreign-libs)))))) + ([source opts compiler-env] + (env/with-compiler-env compiler-env + (let [orig-opts opts + opts (add-implicit-options opts) + ;; we want to warn about NPM dep conflicts before installing the modules + _ (when (:install-deps opts) + (check-npm-deps opts) + (swap! compiler-env update-in [:npm-deps-installed?] + (fn [installed?] + (if-not installed? + (maybe-install-node-deps! opts) + installed?)))) + + compiler-stats (:compiler-stats opts) + checked-arrays (or (:checked-arrays opts) + ana/*checked-arrays*) + static-fns? (or (and (= (:optimizations opts) :advanced) + (not (false? (:static-fns opts)))) + (:static-fns opts) + ana/*cljs-static-fns*) + sources (when source + (-find-sources source opts))] + (validate-opts opts) + (swap! compiler-env + #(-> % + (update-in [:options] merge opts) + (assoc :target (:target opts)) + ;; Save the current js-dependency index once we have computed opts + ;; or the analyzer won't be able to find upstream dependencies - Antonio + (assoc :js-dependency-index (deps/js-dependency-index opts)) + ;; Save list of sources for cljs.analyzer/locate-src - Juho Teperi + (assoc :sources sources))) + (binding [comp/*recompiled* (when-not (false? (:recompile-dependents opts)) + (atom #{})) + ana/*checked-arrays* checked-arrays + ana/parse-ns (memoize ana/parse-ns) + ana/*cljs-static-fns* static-fns? + ana/*fn-invoke-direct* (or (and static-fns? + (:fn-invoke-direct opts)) + ana/*fn-invoke-direct*) + *assert* (not= (:elide-asserts opts) true) + ana/*load-tests* (not= (:load-tests opts) false) + ana/*cljs-warnings* + (let [warnings (opts :warnings true)] + (merge + ana/*cljs-warnings* + (if (or (true? warnings) + (false? warnings)) + (zipmap + [:unprovided :undeclared-var + :undeclared-ns :undeclared-ns-form] + (repeat warnings)) + warnings))) + ana/*verbose* (:verbose opts)] + (when (and ana/*verbose* (not (::watch-triggered-build? opts))) + (util/debug-prn "Options passed to ClojureScript compiler:" (pr-str opts))) + (let [one-file? (and (:main opts) + (#{:advanced :simple :whitespace} (:optimizations opts))) + source (if (or one-file? + ;; if source is nil, :main is supplied, :optimizations :none, + ;; fix up source for the user, see CLJS-3255 + (and (nil? source) (:main opts) (= :none (:optimizations opts)))) + (let [main (:main opts) + uri (:uri (cljs-source-for-namespace main))] + (assert uri (str "No file for namespace " main " exists")) + uri) + ;; old compile directory behavior, or code-splitting + source) + compile-opts (if one-file? + (assoc opts :output-file (:output-to opts)) + opts) + _ (load-data-readers! compiler-env) + ;; reset :js-module-index so that ana/parse-ns called by -find-sources + ;; can find the missing JS modules + js-sources (env/with-compiler-env (dissoc @compiler-env :js-module-index) + (-> (if source + (-find-sources source opts) + (-find-sources (reduce into #{} (map (comp :entries val) (:modules opts))) opts)) + (add-dependency-sources compile-opts))) + opts (handle-js-modules opts js-sources compiler-env) + js-sources (-> js-sources + deps/dependency-order + (compile-sources compiler-stats compile-opts) + (#(map add-core-macros-if-cljs-js %)) + (add-js-sources opts) + (cond-> + (and (= :nodejs (:target opts)) + (:nodejs-rt opts)) + (concat + [(-compile (io/resource "cljs/nodejs.cljs") + (assoc opts :output-file "nodejs.js"))])) + deps/dependency-order + ;; NOTE: :preloads are compiled *after* + ;; user specified inputs. Thus user code cannot + ;; depend on anything (i.e. fn/macros) defined + ;; in preloads via global access pattern + (add-preloads opts) + remove-goog-base + add-goog-base + (cond-> + (and (= :nodejs (:target opts)) + (:nodejs-rt opts)) + (concat + [(-compile (io/resource "cljs/nodejscli.cljs") + (assoc opts :output-file "nodejscli.js"))])) + (->> (map #(source-on-disk opts %)) doall) + (compile-loader opts)) + _ (when (:emit-constants opts) + (comp/emit-constants-table-to-file + (::ana/constant-table @env/*compiler*) + (constants-filename opts))) + _ (when (:infer-externs opts) + (comp/emit-inferred-externs-to-file + (reduce util/map-merge {} + (map (comp :externs second) + (get @compiler-env ::ana/namespaces))) + (str (util/output-directory opts) "/inferred_externs.js"))) + _ (spit (io/file (util/output-directory opts) (:opts-cache opts)) (pr-str orig-opts)) + optim (:optimizations opts) + ret (if (and optim (not= optim :none)) + (do + (when-let [fname (:source-map opts)] + (assert (or (nil? (:output-to opts)) (:modules opts) (string? fname)) + (str ":source-map must name a file when using :whitespace, " + ":simple, or :advanced optimizations with :output-to"))) + (if (:modules opts) + (->> + (util/measure compiler-stats + (str "Optimizing " (count js-sources) " sources") + (apply optimize-modules opts js-sources)) + (output-modules opts js-sources)) + (let [fdeps-str (foreign-deps-str opts + (filter foreign-source? js-sources)) + opts (assoc opts + :foreign-deps-line-count + (- (count (.split #"\r?\n" fdeps-str -1)) 1))] + (->> + (util/measure compiler-stats + (str "Optimizing " (count js-sources) " sources") + (apply optimize opts + (remove foreign-source? js-sources))) + (add-wrapper opts) + (add-source-map-link opts) + (str fdeps-str) + (add-header opts) + (output-one-file opts))))) + (do + (when (bundle? opts) + (spit (io/file (util/output-directory opts) "npm_deps.js") + (npm-deps-js (:node-module-index @env/*compiler*)))) + (apply output-unoptimized opts js-sources)))] + (output-bootstrap opts) + (when (bundle? opts) (run-bundle-cmd opts)) + ret)))))) + +(comment + ;; testing modules + (build "samples/hello/src" + {:optimizations :advanced + :output-dir "samples/hello/out" + :source-map true + :modules + {:hello + {:output-to "samples/hello/out/hello.js" + :entries '#{cljs.reader hello.core}}}}) + + (require '[cljs.externs :as externs]) + + (externs/parse-externs + (js-source-file "cljs/externs.js" (io/file "src/main/cljs/cljs/externs.js"))) + ) + +(defn ^File target-file-for-cljs-ns + [ns-sym output-dir] + (util/to-target-file + (util/output-directory {:output-dir output-dir}) + {:ns ns-sym})) + +(defn mark-cljs-ns-for-recompile! + [ns-sym output-dir] + (let [s (target-file-for-cljs-ns ns-sym output-dir)] + (when (.exists s) + (.setLastModified s 5000)))) + +(defn cljs-dependents-for-macro-namespaces + [state namespaces] + (map :name + (let [namespaces-set (set namespaces)] + (filter (fn [x] (not-empty + (set/intersection namespaces-set (-> x :require-macros vals set)))) + (vals (:cljs.analyzer/namespaces @state)))))) + +(defn watch + "Given a source directory, produce runnable JavaScript. Watch the source + directory for changes rebuilding when necessary. Takes the same arguments as + cljs.closure/build in addition to some watch-specific options: + - :watch-fn, a function of no arguments to run after a successful build. May + be a function value or a namespaced symbol identifying a function, + in which case the associated namespace willl be loaded and the + symbol resolved. + - :watch-error-fn, a function receiving the exception of a failed build. May + be a function value or a namespaced symbol, loaded as + with :watch-fn." + ([source opts] + (watch source opts + (if-not (nil? env/*compiler*) + env/*compiler* + (env/default-compiler-env opts)))) + ([source opts compiler-env] + (watch source opts compiler-env nil)) + ([source opts compiler-env quit] + (let [opts (cond-> opts + (= (:verbose opts :not-found) :not-found) + (assoc :verbose true)) + paths (map #(Paths/get (.toURI %)) (-paths source)) + path (first paths) + fs (.getFileSystem path) + srvc (.newWatchService fs)] + (letfn [(buildf [] + (try + (let [start (System/nanoTime) + watch-opts (assoc opts ::watch-triggered-build? true)] + (build source watch-opts compiler-env) + (println "... done. Elapsed" + (/ (unchecked-subtract (System/nanoTime) start) 1e9) "seconds") + (flush)) + (when-let [f (opts-fn :watch-fn opts)] + (f)) + (catch Throwable e + (if-let [f (opts-fn :watch-error-fn opts)] + (f e) + (binding [*out* *err*] + (println e)))))) + (watch-all [^Path root] + (Files/walkFileTree root + (reify + FileVisitor + (preVisitDirectory [_ dir _] + (let [^Path dir dir] + (. dir + (register srvc + (into-array [StandardWatchEventKinds/ENTRY_CREATE + StandardWatchEventKinds/ENTRY_DELETE + StandardWatchEventKinds/ENTRY_MODIFY]) + (into-array [SensitivityWatchEventModifier/HIGH])))) + FileVisitResult/CONTINUE) + (postVisitDirectory [_ dir exc] + FileVisitResult/CONTINUE) + (visitFile [_ file attrs] + FileVisitResult/CONTINUE) + (visitFileFailed [_ file exc] + FileVisitResult/CONTINUE))))] + (println "Building ...") + (flush) + (buildf) + (println "Watching paths:" (apply str (interpose ", " paths))) + (doseq [path paths] + (watch-all path)) + (loop [key nil] + (when (and (or (nil? quit) (not @quit)) + (or (nil? key) (. ^WatchKey key reset))) + (let [key (. srvc (poll 300 TimeUnit/MILLISECONDS)) + poll-events-seq (when key (seq (.pollEvents key)))] + (when (and key + (some + (fn [^WatchEvent e] + (let [fstr (.. e context toString)] + (and (or (. fstr (endsWith "cljc")) + (. fstr (endsWith "cljs")) + (. fstr (endsWith "clj")) + (. fstr (endsWith "js"))) + (not (. fstr (startsWith ".#")))))) + poll-events-seq)) + (when-let [clj-files (seq (keep (fn [^WatchEvent e] + (let [ctx (.context e) + fstr (.toString ctx)] + (when (and (or (. fstr (endsWith "cljc")) + (. fstr (endsWith "clj"))) + (not (. fstr (startsWith ".#")))) + ctx))) + poll-events-seq))] + (let [^Path dir (.watchable key) + file-seq (map #(.toFile (.resolve dir %)) clj-files) + nses (map (comp :ns ana/parse-ns) file-seq)] + (doseq [ns nses] + (require ns :reload)) + (doseq [ns (cljs-dependents-for-macro-namespaces compiler-env nses)] + (mark-cljs-ns-for-recompile! ns (:output-dir opts))))) + (println "Change detected, recompiling ...") + (flush) + (buildf)) + (recur key)))))))) + +(comment + (watch "samples/hello/src" + {:optimizations :none + :output-to "samples/hello/out/hello.js" + :output-dir "samples/hello/out" + :cache-analysis true + :source-map true + :verbose true + :watch-fn + (fn [] + (println "Success!"))}) + ) + +;; ============================================================================= +;; Utilities + +;; for backwards compatibility +(defn output-directory [opts] + (util/output-directory opts)) + +(defn parse-js-ns [f] + (deps/parse-js-ns (line-seq (io/reader f)))) + +(defn ^File src-file->target-file + ([src] + (src-file->target-file src + (when env/*compiler* + (:options @env/*compiler*)))) + ([src opts] + (util/to-target-file + (when (:output-dir opts) + (util/output-directory opts)) + (ana/parse-ns src)))) + +(defn ^String src-file->goog-require + ([src] (src-file->goog-require src {:wrap true})) + ([src {:keys [wrap all-provides macros-ns] :as options}] + (let [goog-ns + (case (util/ext src) + ("cljs" "cljc") (let [ns-str (str (comp/munge (:ns (ana/parse-ns src))))] + (cond-> ns-str + (and macros-ns (not (.endsWith ns-str "$macros"))) + (str "$macros"))) + "js" (cond-> (:provides (parse-js-ns src)) + (not all-provides) first) + (throw + (util/compilation-error (IllegalArgumentException. + (str "Can't create goog.require expression for " src)))))] + (if (and (not all-provides) wrap) + (if (:reload options) + (str "goog.require(\"" goog-ns "\", true);") + (str "goog.require(\"" goog-ns "\");")) + (if (vector? goog-ns) + goog-ns + (str goog-ns)))))) + +;; Browser REPL client stuff + +(defn compile-client-js [opts] + (let [copts (select-keys opts [:optimizations :output-dir :language-in])] + ;; we're inside the REPL process where cljs.env/*compiler* is already + ;; established, need to construct a new one to avoid mutating the one + ;; the REPL uses + (build + '[(ns clojure.browser.repl.client + (:require [goog.events :as event] + [clojure.browser.repl :as repl])) + (defn start [url] + (event/listen js/window + "load" + (fn [] + (repl/start-evaluator url))))] + copts (env/default-compiler-env copts)))) + +(defn create-client-js-file [opts file-path] + (if-let [cached (io/resource "brepl_client.js")] + cached + (let [file (io/file file-path)] + (when (not (.exists file)) + (spit file (compile-client-js opts))) + file))) + +;; AOTed resources + +(defn aot-cache-core [] + (let [base-path (io/file "src" "main" "cljs" "cljs") + src (io/file base-path "core.cljs") + dest (io/file base-path "core.aot.js") + cache (io/file base-path "core.cljs.cache.aot.edn") + tcache (io/file base-path "core.cljs.cache.aot.json")] + (util/mkdirs dest) + (env/with-compiler-env (env/default-compiler-env {:infer-externs true}) + (comp/compile-file src dest + {:static-fns true + :source-map true + :source-map-url "core.js.map" + :output-dir (str "src" File/separator "main" File/separator "cljs")}) + (ana/write-analysis-cache 'cljs.core cache src) + (ana/write-analysis-cache 'cljs.core tcache src)) + (create-client-js-file + {:language-in :ecmascript-next + :optimizations :simple + :output-dir "aot_out"} + (io/file "resources" "brepl_client.js")) + (doseq [f (file-seq (io/file "aot_out")) + :when (.isFile f)] + (.delete f)))) + +(comment + (time + (do (aot-cache-core) nil)) + + (time + (do (ana/analyze-file "cljs/core.cljs") nil)) + + (println (build '[(ns hello.core) + (defn ^{:export greet} greet [n] (str "Hola " n)) + (defn ^:export sum [xs] 42)] + {:optimizations :simple :pretty-print true})) + + ;; build a project with optimizations + (build "samples/hello/src" {:optimizations :advanced}) + (build "samples/hello/src" {:optimizations :advanced :output-to "samples/hello/hello.js"}) + ;; open 'samples/hello/hello.html' to see the result in action + + ;; build a project without optimizations + (build "samples/hello/src" {:output-dir "samples/hello/out" :output-to "samples/hello/hello.js"}) + ;; open 'samples/hello/hello-dev.html' to see the result in action + ;; notice how each script was loaded individually + + ;; build unoptimized from raw ClojureScript + (build '[(ns hello.core) + (defn ^{:export greet} greet [n] (str "Hola " n)) + (defn ^:export sum [xs] 42)] + {:output-dir "samples/hello/out" :output-to "samples/hello/hello.js"}) + ;; open 'samples/hello/hello-dev.html' to see the result in action + ) diff --git a/src/main/clojure/cljs/compiler.cljc b/src/main/clojure/cljs/compiler.cljc new file mode 100644 index 0000000000..1fbf54ec20 --- /dev/null +++ b/src/main/clojure/cljs/compiler.cljc @@ -0,0 +1,1964 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.compiler + #?(:clj (:refer-clojure :exclude [ensure macroexpand-1 munge]) + :cljs (:refer-clojure :exclude [ensure js-reserved macroexpand-1 munge])) + #?(:cljs (:require-macros [cljs.compiler.macros :refer [emit-wrap]] + [cljs.env.macros :refer [ensure]])) + #?(:clj (:require [cljs.analyzer :as ana] + [cljs.env :as env :refer [ensure]] + [cljs.js-deps :as deps] + [cljs.source-map :as sm] + [cljs.tagged-literals :as tags] + [cljs.util :as util] + [cljs.vendor.clojure.data.json :as json] + [clojure.java.io :as io] + [clojure.set :as set] + [clojure.string :as string] + [cljs.vendor.clojure.tools.reader :as reader]) + :cljs (:require [cljs.analyzer :as ana] + [cljs.analyzer.impl :as ana.impl] + [cljs.env :as env] + [cljs.source-map :as sm] + [cljs.tools.reader :as reader] + [clojure.set :as set] + [clojure.string :as string] + [goog.string :as gstring])) + #?(:clj (:import [cljs.tagged_literals JSValue] + java.lang.StringBuilder + [java.io File Writer] + [java.time Instant] + [java.util.concurrent Executors ExecutorService TimeUnit] + [java.util.concurrent.atomic AtomicLong]) + :cljs (:import [goog.string StringBuffer]))) + +#?(:clj (set! *warn-on-reflection* true)) + +(def js-reserved ana/js-reserved) + +(def ^:private es5>= + (into #{} + (comp + (mapcat (fn [lang] + [lang (keyword (string/replace (name lang) #"^ecmascript" "es"))]))) + [:ecmascript5 :ecmascript5-strict :ecmascript6 :ecmascript6-strict + :ecmascript-2015 :ecmascript6-typed :ecmascript-2016 :ecmascript-2017 + :ecmascript-2018 :ecmascript-2019 :ecmascript-2020 :ecmascript-2021 + :ecmascript-next])) + +(def ^:dynamic *recompiled* nil) +(def ^:dynamic *inputs* nil) +(def ^:dynamic *source-map-data* nil) +(def ^:dynamic *source-map-data-gen-col* nil) +(def ^:dynamic *lexical-renames* {}) + +(def cljs-reserved-file-names #{"deps.cljs"}) + +(defn get-first-ns-segment + "Gets the part up to the first `.` of a namespace. + Returns the empty string for nil. + Returns the entire string if no `.` in namespace" + [ns] + (let [ns (str ns) + idx (.indexOf ns ".")] + (if (== -1 idx) + ns + (subs ns 0 idx)))) + +(defn ^:dynamic find-ns-starts-with [needle] + (reduce-kv + (fn [xs ns _] + (when (= needle (get-first-ns-segment ns)) + (reduced needle))) + nil + (::ana/namespaces @env/*compiler*))) + +; Helper fn +(defn shadow-depth [s] + (let [{:keys [name info]} s] + (loop [d 0, {:keys [shadow]} info] + (cond + shadow (recur (inc d) shadow) + (find-ns-starts-with (str name)) (inc d) + :else d)))) + +(defn hash-scope [s] + (hash-combine #?(:clj (hash (:name s)) + :cljs (-hash ^not-native (:name s))) + (shadow-depth s))) + +(declare munge) + +(defn fn-self-name [{:keys [name info] :as name-var}] + (let [name (string/replace (str name) ".." "_DOT__DOT_") + {:keys [ns fn-scope]} info + scoped-name (apply str + (interpose "_$_" + (concat (map (comp str :name) fn-scope) [name])))] + (symbol + (munge + (str (string/replace (str ns) "." "$") "$" scoped-name))))) + +(defn munge-reserved [reserved] + (fn [s] + (if-not (nil? (get reserved s)) + (str s "$") + s))) + +(defn munge + ([s] (munge s js-reserved)) + ([s reserved] + (if #?(:clj (map? s) + :cljs (ana.impl/cljs-map? s)) + (let [name-var s + name (:name name-var) + field (:field name-var) + info (:info name-var)] + (if-not (nil? (:fn-self-name info)) + (fn-self-name s) + ;; Unshadowing + (let [depth (shadow-depth s) + code (hash-scope s) + renamed (get *lexical-renames* code) + name (cond + (true? field) (str "self__." name) + (not (nil? renamed)) renamed + :else name) + munged-name (munge name reserved)] + (if (or (true? field) (zero? depth)) + munged-name + (symbol (str munged-name "__$" depth)))))) + ;; String munging + (let [ss (string/replace (str s) ".." "_DOT__DOT_") + ss (string/replace ss + #?(:clj #"\/(.)" :cljs (js/RegExp. "\\/(.)")) ".$1") ; Division is special + rf (munge-reserved reserved) + ss (map rf (string/split ss #"\.")) + ss (string/join "." ss) + ms #?(:clj (clojure.lang.Compiler/munge ss) + :cljs (munge-str ss))] + (if (symbol? s) + (symbol ms) + ms))))) + +(defn- comma-sep [xs] + (interpose "," xs)) + +(defn- escape-char [^Character c] + (let [cp #?(:clj (.hashCode c) + :cljs (gstring/hashCode c))] + (case cp + ; Handle printable escapes before ASCII + 34 "\\\"" + 92 "\\\\" + ; Handle non-printable escapes + 8 "\\b" + 12 "\\f" + 10 "\\n" + 13 "\\r" + 9 "\\t" + (if (< 31 cp 127) + c ; Print simple ASCII characters + #?(:clj (format "\\u%04X" cp) ; Any other character is Unicode + :cljs (let [unpadded (.toString cp 16) + pad (subs "0000" (.-length unpadded))] + (str "\\u" pad unpadded))))))) + +(defn- escape-string [^CharSequence s] + (let [sb #?(:clj (StringBuilder. (count s)) + :cljs (StringBuffer.))] + (doseq [c s] + (.append sb (escape-char c))) + (.toString sb))) + +(defn- wrap-in-double-quotes [x] + (str \" x \")) + +(defmulti emit* :op) + +(defn emit [ast] + (when *source-map-data* + (let [{:keys [env]} ast] + (when (:line env) + (let [{:keys [line column]} env] + (swap! *source-map-data* + (fn [m] + (let [minfo (cond-> {:gcol #?(:clj (.get ^AtomicLong *source-map-data-gen-col*) + :cljs (:gen-col m)) + :gline (:gen-line m)} + (#{:var :local :js-var :binding} (:op ast)) + (assoc :name (str (-> ast :info :name))))] + ; Dec the line/column numbers for 0-indexing. + ; tools.reader uses 1-indexed sources, chrome + ; expects 0-indexed source maps. + (update-in m [:source-map (dec line)] + (fnil (fn [line] + (update-in line [(if column (dec column) 0)] + (fnil (fn [column] (conj column minfo)) []))) + (sorted-map)))))))))) + (emit* ast)) + +(defn emits + ([]) + ([^Object a] + (cond + (nil? a) nil + #?(:clj (map? a) :cljs (ana.impl/cljs-map? a)) (emit a) + #?(:clj (seq? a) :cljs (ana.impl/cljs-seq? a)) (apply emits a) + #?(:clj (fn? a) :cljs (js-fn? a)) (a) + :else (let [^String s (cond-> a (not (string? a)) .toString)] + #?(:clj (when-some [^AtomicLong gen-col *source-map-data-gen-col*] + (.addAndGet gen-col (.length s))) + :cljs (when-some [sm-data *source-map-data*] + (swap! sm-data update :gen-col #(+ % (.-length s))))) + #?(:clj (.write ^Writer *out* s) + :cljs (print s)))) + nil) + ([a b] + (emits a) (emits b)) + ([a b c] + (emits a) (emits b) (emits c)) + ([a b c d] + (emits a) (emits b) (emits c) (emits d)) + ([a b c d e] + (emits a) (emits b) (emits c) (emits d) (emits e)) + ([a b c d e & xs] + (emits a) (emits b) (emits c) (emits d) (emits e) + (doseq [x xs] (emits x)))) + +(defn ^:private _emitln [] + (newline) + (when *source-map-data* + #?(:clj (.set ^AtomicLong *source-map-data-gen-col* 0)) + (swap! *source-map-data* + (fn [{:keys [gen-line] :as m}] + (assoc m + :gen-line (inc gen-line) + #?@(:cljs [:gen-col 0]))))) + nil) + +(defn emitln + ([] (_emitln)) + ([a] + (emits a) (_emitln)) + ([a b] + (emits a) (emits b) (_emitln)) + ([a b c] + (emits a) (emits b) (emits c) (_emitln)) + ([a b c d] + (emits a) (emits b) (emits c) (emits d) (_emitln)) + ([a b c d e] + (emits a) (emits b) (emits c) (emits d) (emits e) (_emitln)) + ([a b c d e & xs] + (emits a) (emits b) (emits c) (emits d) (emits e) + (doseq [x xs] (emits x)) + (_emitln))) + +(defn ^String emit-str [expr] + (with-out-str (emit expr))) + +#?(:clj + (defmulti emit-constant* class) + :cljs + (defmulti emit-constant* type)) + +(declare emit-map emit-list emit-vector emit-set emit-js-object emit-js-array + emit-with-meta emit-constants-comma-sep emit-constant emit-record-value) + +(defn all-distinct? [xs] + (apply distinct? xs)) + +#?(:clj + (defn emit-constant-no-meta [x] + (cond + (seq? x) (emit-list x emit-constants-comma-sep) + (record? x) (let [[ns name] (ana/record-ns+name x)] + (emit-record-value ns name #(emit-constant (into {} x)))) + (map? x) (emit-map (keys x) (vals x) emit-constants-comma-sep all-distinct?) + (vector? x) (emit-vector x emit-constants-comma-sep) + (set? x) (emit-set x emit-constants-comma-sep all-distinct?) + :else (emit-constant* x))) + :cljs + (defn emit-constant-no-meta [x] + (cond + (ana.impl/cljs-seq? x) (emit-list x emit-constants-comma-sep) + (record? x) (let [[ns name] (ana/record-ns+name x)] + (emit-record-value ns name #(emit-constant (into {} x)))) + (ana.impl/cljs-map? x) (emit-map (keys x) (vals x) emit-constants-comma-sep all-distinct?) + (ana.impl/cljs-vector? x) (emit-vector x emit-constants-comma-sep) + (ana.impl/cljs-set? x) (emit-set x emit-constants-comma-sep all-distinct?) + :else (emit-constant* x)))) + +(defn emit-constant [v] + (let [m (ana/elide-irrelevant-meta (meta v))] + (if (some? (seq m)) + (emit-with-meta #(emit-constant-no-meta v) #(emit-constant-no-meta m)) + (emit-constant-no-meta v)))) + +(defmethod emit-constant* :default + [x] + (throw + (ex-info (str "failed compiling constant: " x "; " + (pr-str (type x)) " is not a valid ClojureScript constant.") + {:constant x + :type (type x) + :clojure.error/phase :compilation}))) + +(defmethod emit-constant* nil [x] (emits "null")) + +#?(:clj + (defmethod emit-constant* Long [x] (emits "(" x ")"))) + +#?(:clj + (defmethod emit-constant* Integer [x] (emits x))) ; reader puts Integers in metadata + +#?(:clj + (defmethod emit-constant* Double [x] + (let [x (double x)] + (cond (Double/isNaN x) + (emits "NaN") + + (Double/isInfinite x) + (emits (if (pos? x) "Infinity" "-Infinity")) + + :else (emits x)))) + :cljs + (defmethod emit-constant* js/Number [x] + (cond (js/isNaN x) + (emits "NaN") + + (not (js/isFinite x)) + (emits (if (pos? x) "Infinity" "-Infinity")) + + (and (zero? x) (neg? (/ x))) + (emits "(-0)") + + :else (emits "(" x ")")))) + +#?(:clj + (defmethod emit-constant* BigDecimal [x] (emits (.doubleValue ^BigDecimal x)))) + +#?(:clj + (defmethod emit-constant* clojure.lang.BigInt [x] (emits (.doubleValue ^clojure.lang.BigInt x)))) + +(defmethod emit-constant* #?(:clj String :cljs js/String) [x] + (emits (wrap-in-double-quotes (escape-string x)))) + +(defmethod emit-constant* #?(:clj Boolean :cljs js/Boolean) [x] (emits (if x "true" "false"))) + +#?(:clj + (defmethod emit-constant* Character [x] + (emits (wrap-in-double-quotes (escape-char x))))) + +(defmethod emit-constant* #?(:clj java.util.regex.Pattern :cljs js/RegExp) [x] + (if (= "" (str x)) + (emits "(new RegExp(\"\"))") + (let [[_ flags pattern] (re-find #"^(?:\(\?([idmsux]*)\))?(.*)" (str x))] + #?(:clj (emits \/ + (.replaceAll (re-matcher #"/" pattern) "\\\\/") + \/ flags) + :cljs (emits pattern))))) + +(defn emits-keyword [kw] + (let [ns (namespace kw) + name (name kw)] + (emits "new cljs.core.Keyword(") + (emit-constant ns) + (emits ",") + (emit-constant name) + (emits ",") + (emit-constant (if ns + (str ns "/" name) + name)) + (emits ",") + (emit-constant (hash kw)) + (emits ")"))) + +(defn emits-symbol [sym] + (let [ns (namespace sym) + name (name sym) + symstr (if-not (nil? ns) + (str ns "/" name) + name)] + (emits "new cljs.core.Symbol(") + (emit-constant ns) + (emits ",") + (emit-constant name) + (emits ",") + (emit-constant symstr) + (emits ",") + (emit-constant (hash sym)) + (emits ",") + (emit-constant nil) + (emits ")"))) + +(defmethod emit-constant* #?(:clj clojure.lang.Keyword :cljs Keyword) [x] + (if-let [value (and (-> @env/*compiler* :options :emit-constants) + (-> @env/*compiler* ::ana/constant-table x))] + (emits "cljs.core." value) + (emits-keyword x))) + +(defmethod emit-constant* #?(:clj clojure.lang.Symbol :cljs Symbol) [x] + (if-let [value (and (-> @env/*compiler* :options :emit-constants) + (-> @env/*compiler* ::ana/constant-table x))] + (emits "cljs.core." value) + (emits-symbol x))) + +(defn emit-constants-comma-sep [cs] + (fn [] + (doall + (map-indexed (fn [i m] + (if (even? i) + (emit-constant m) + (emits m))) + (comma-sep cs))))) + +(def ^:private array-map-threshold 8) + +;; tagged literal support + +(defn- emit-inst [inst-ms] + (emits "new Date(" inst-ms ")")) + +(defmethod emit-constant* #?(:clj java.util.Date :cljs js/Date) [^java.util.Date date] + (emit-inst (.getTime date))) + +#?(:clj + (defmethod emit-constant* java.time.Instant [^java.time.Instant inst] + (emit-inst (.toEpochMilli inst)))) + +(defmethod emit-constant* #?(:clj java.util.UUID :cljs UUID) [^java.util.UUID uuid] + (let [uuid-str (.toString uuid)] + (emits "new cljs.core.UUID(\"" uuid-str "\", " (hash uuid-str) ")"))) + +(defmethod emit-constant* #?(:clj JSValue :cljs cljs.tagged-literals/JSValue) [^JSValue v] + (let [items (.-val v)] + (if (map? items) + (emit-js-object items #(fn [] (emit-constant %))) + (emit-js-array items emit-constants-comma-sep)))) + +#?(:clj + (defmacro emit-wrap [env & body] + `(let [env# ~env] + (when (= :return (:context env#)) (emits "return ")) + ~@body + (when-not (= :expr (:context env#)) (emitln ";"))))) + +(defmethod emit* :no-op [m]) + +(defn emit-var + [{:keys [info env form] :as ast}] + (if-let [const-expr (:const-expr ast)] + (emit (assoc const-expr :env env)) + (let [{:keys [options] :as cenv} @env/*compiler* + var-name (:name info) + info (if (= (namespace var-name) "js") + (let [js-module-name (get-in cenv [:js-module-index (name var-name) :name])] + (or js-module-name (name var-name))) + info)] + ;; We need a way to write bindings out to source maps and javascript + ;; without getting wrapped in an emit-wrap calls, otherwise we get + ;; e.g. (function greet(return x, return y) {}). + (if (:binding-form? ast) + ;; Emit the arg map so shadowing is properly handled when munging + ;; (prevents duplicate fn-param-names) + (emits (munge ast)) + (when-not (= :statement (:context env)) + (let [reserved (cond-> js-reserved + (and (es5>= (:language-out options)) + ;; we can skip munging things like `my.ns.default` + ;; but not standalone `default` variable names + ;; as they're not valid ES5 - Antonio + (some? (namespace var-name))) + (set/difference ana/es5-allowed)) + js-module (get-in cenv [:js-namespaces (or (namespace var-name) (name var-name))]) + info (cond-> info + (not= form 'js/-Infinity) (munge reserved))] + (emit-wrap env + (case (:module-type js-module) + ;; Closure exports CJS exports through default property + :commonjs + (if (namespace var-name) + (emits (munge (namespace var-name) reserved) "[\"default\"]." (munge (name var-name) reserved)) + (emits (munge (name var-name) reserved) "[\"default\"]")) + + ;; Emit bracket notation for default prop access instead of dot notation + :es6 + (if (and (namespace var-name) (= "default" (name var-name))) + (emits (munge (namespace var-name) reserved) "[\"default\"]") + (emits info)) + + (emits info))))))))) + +(defmethod emit* :var [expr] (emit-var expr)) +(defmethod emit* :binding [expr] (emit-var expr)) +(defmethod emit* :js-var [expr] (emit-var expr)) +(defmethod emit* :local [expr] (emit-var expr)) + +(defmethod emit* :the-var + [{:keys [env var sym meta] :as arg}] + {:pre [(ana/ast? sym) (ana/ast? meta)]} + (let [{:keys [name]} (:info var)] + (emit-wrap env + (emits "new cljs.core.Var(function(){return " (munge name) ";}," + sym "," meta ")")))) + +(defn emit-with-meta [expr meta] + (emits "cljs.core.with_meta(" expr "," meta ")")) + +(defmethod emit* :with-meta + [{:keys [expr meta env]}] + (emit-wrap env + (emit-with-meta expr meta))) + +(defn distinct-keys? [keys] + (let [keys (map ana/unwrap-quote keys)] + (and (every? #(= (:op %) :const) keys) + (= (count (into #{} keys)) (count keys))))) + +(defn obj-map-key [x] + (if (keyword? x) + (str \" "\\uFDD0" \' + (if (namespace x) + (str (namespace x) "/") "") + (name x) + \") + (str \" x \"))) + +(defn emit-obj-map [str-keys vals comma-sep distinct-keys?] + (if (zero? (count str-keys)) + (emits "cljs.core.ObjMap.EMPTY") + (emits "cljs.core.ObjMap.fromObject([" (comma-sep str-keys) "], {" + (comma-sep (map (fn [k v] (str k ":" (emit-str v))) str-keys vals)) + "})"))) + +(defn emit-lite-map [keys vals comma-sep distinct-keys?] + (if (zero? (count keys)) + (emits "cljs.core.HashMapLite.EMPTY") + (emits "cljs.core.HashMapLite.fromArrays([" (comma-sep keys) "], [" (comma-sep vals) "])"))) + +(defn emit-map [keys vals comma-sep distinct-keys?] + (cond + (zero? (count keys)) + (emits "cljs.core.PersistentArrayMap.EMPTY") + + (<= (count keys) array-map-threshold) + (if (distinct-keys? keys) + (emits "new cljs.core.PersistentArrayMap(null, " (count keys) ", [" + (comma-sep (interleave keys vals)) + "], null)") + (emits "cljs.core.PersistentArrayMap.createAsIfByAssoc([" + (comma-sep (interleave keys vals)) + "])")) + + :else + (emits "cljs.core.PersistentHashMap.fromArrays([" + (comma-sep keys) + "],[" + (comma-sep vals) + "])"))) + +(defmethod emit* :map + [{:keys [env form keys vals]}] + (emit-wrap env + (if (ana/lite-mode?) + (let [form-keys (clojure.core/keys form)] + (if (every? #(or (string? %) (keyword? %)) form-keys) + (emit-obj-map (map obj-map-key form-keys) vals comma-sep distinct-keys?) + (emit-lite-map keys vals comma-sep distinct-keys?))) + (emit-map keys vals comma-sep distinct-keys?)))) + +(defn emit-list [items comma-sep] + (if (empty? items) + (emits "cljs.core.List.EMPTY") + (emits "cljs.core.list(" (comma-sep items) ")"))) + +(defn emit-vector [items comma-sep] + (if (empty? items) + (emits "cljs.core.PersistentVector.EMPTY") + (let [cnt (count items)] + (if (< cnt 32) + (emits "new cljs.core.PersistentVector(null, " cnt + ", 5, cljs.core.PersistentVector.EMPTY_NODE, [" (comma-sep items) "], null)") + (emits "cljs.core.PersistentVector.fromArray([" (comma-sep items) "], true)"))))) + +(defn emit-lite-vector [items comma-sep] + (if (empty? items) + (emits "cljs.core.VectorLite.EMPTY") + (emits "new cljs.core.VectorLite(null, [" (comma-sep items) "], null)"))) + +(defmethod emit* :vector + [{:keys [items env]}] + (emit-wrap env + (if (ana/lite-mode?) + (emit-lite-vector items comma-sep) + (emit-vector items comma-sep)))) + +(defn distinct-constants? [items] + (let [items (map ana/unwrap-quote items)] + (and (every? #(= (:op %) :const) items) + (= (count (into #{} items)) (count items))))) + +(defn emit-set [items comma-sep distinct-constants?] + (cond + (empty? items) + (emits "cljs.core.PersistentHashSet.EMPTY") + + (distinct-constants? items) + (emits "new cljs.core.PersistentHashSet(null, new cljs.core.PersistentArrayMap(null, " (count items) ", [" + (comma-sep (interleave items (repeat "null"))) "], null), null)") + + :else (emits "cljs.core.PersistentHashSet.createAsIfByAssoc([" (comma-sep items) "])"))) + +(defn emit-lite-set [items comma-sep distinct-constants?] + (if (empty? items) + (emits "cljs.core.SetLite.EMPTY") + (emits "cljs.core.set_lite([" (comma-sep items) "])"))) + +(defmethod emit* :set + [{:keys [items env]}] + (emit-wrap env + (if (ana/lite-mode?) + (emit-lite-set items comma-sep distinct-constants?) + (emit-set items comma-sep distinct-constants?)))) + +(defn emit-js-object [items emit-js-object-val] + (emits "({") + (when-let [items (seq items)] + (let [[[k v] & r] items] + (emits "\"" (name k) "\": " (emit-js-object-val v)) + (doseq [[k v] r] + (emits ", \"" (name k) "\": " (emit-js-object-val v))))) + (emits "})")) + +(defn emit-js-array [items comma-sep] + (emits "[" (comma-sep items) "]")) + +(defmethod emit* :js-object + [{:keys [keys vals env]}] + (emit-wrap env + (emit-js-object (map vector keys vals) identity))) + +(defmethod emit* :js-array + [{:keys [items env]}] + (emit-wrap env + (emit-js-array items comma-sep))) + +(defn emit-record-value + [ns name items] + (emits ns ".map__GT_" name "(" items ")")) + +(defmethod emit* :quote + [{:keys [expr]}] + (emit expr)) + +(defmethod emit* :const + [{:keys [form env]}] + (when-not (= :statement (:context env)) + (emit-wrap env (emit-constant form)))) + +(defn truthy-constant? [expr] + (let [{:keys [op form const-expr]} (ana/unwrap-quote expr)] + (or (and (= op :const) + form + (not (or (and (string? form) (= form "")) + (and (number? form) (zero? form))))) + (and (some? const-expr) + (truthy-constant? const-expr))))) + +(defn falsey-constant? [expr] + (let [{:keys [op form const-expr]} (ana/unwrap-quote expr)] + (or (and (= op :const) + (or (false? form) (nil? form))) + (and (some? const-expr) + (falsey-constant? const-expr))))) + +(defn safe-test? [env e] + (let [tag (ana/infer-tag env e)] + (or ('#{boolean seq} (ana/js-prim-ctor->tag tag tag)) + (truthy-constant? e)))) + +(defmethod emit* :if + [{:keys [test then else env unchecked]}] + (let [context (:context env) + checked (not (or unchecked (safe-test? env test)))] + (cond + (truthy-constant? test) (emitln then) + (falsey-constant? test) (emitln else) + :else + (if (= :expr context) + (emits "(" (when checked "cljs.core.truth_") "(" test ")?" then ":" else ")") + (do + (if checked + (emitln "if(cljs.core.truth_(" test ")){") + (emitln "if(" test "){")) + (emitln then "} else {") + (emitln else "}")))))) + +(defn iife-open [{:keys [async]}] + (str (when async "(await ") "(" (when async "async ") "function (){")) + +(defn iife-close [{:keys [async]}] + (str "})()" (when async ")"))) + +(defmethod emit* :case + [{v :test :keys [nodes default env]}] + (when (= (:context env) :expr) + (emitln (iife-open env))) + (let [gs (gensym "caseval__")] + (when (= :expr (:context env)) + (emitln "var " gs ";")) + (emitln "switch (" v ") {") + (doseq [{ts :tests {:keys [then]} :then} nodes] + (doseq [test (map :test ts)] + (emitln "case " test ":")) + (if (= :expr (:context env)) + (emitln gs "=" then) + (emitln then)) + (emitln "break;")) + (when default + (emitln "default:") + (if (= :expr (:context env)) + (emitln gs "=" default) + (emitln default))) + (emitln "}") + (when (= :expr (:context env)) + (emitln "return " gs ";" + (iife-close env))))) + +(defmethod emit* :throw + [{throw :exception :keys [env]}] + (if (= :expr (:context env)) + (emits (iife-open env) "throw " throw (iife-close env)) + (emitln "throw " throw ";"))) + +(def base-types + #{"null" "*" "...*" + "boolean" "Boolean" + "string" "String" + "number" "Number" + "array" "Array" + "object" "Object" + "RegExp" + "Date"}) + +(def mapped-types + {"nil" "null"}) + +(defn resolve-type [env ^String t] + (cond + (get base-types t) t + + (get mapped-types t) (get mapped-types t) + + #?(:clj (.startsWith t "!") + :cljs (gstring/startsWith t "!")) + (str "!" (resolve-type env (subs t 1))) + + #?(:clj (.startsWith t "{") + :cljs (gstring/startsWith t "{")) t + + #?(:clj (.startsWith t "function") + :cljs (gstring/startsWith t "function")) + (let [idx (.lastIndexOf t ":") + [fstr rstr] (if-not (== -1 idx) + [(subs t 0 idx) (subs t (inc idx) (count t))] + [t nil]) + ret-t (when rstr (resolve-type env rstr)) + axstr (subs fstr 9 (dec (count fstr))) + args-ts (when-not (string/blank? axstr) + (map (comp #(resolve-type env %) string/trim) + (string/split axstr #",")))] + (cond-> (str "function(" (string/join "," args-ts) ")") + ret-t (str ":" ret-t))) + + #?(:clj (.endsWith t "=") + :cljs (gstring/endsWith t "=")) + (str (resolve-type env (subs t 0 (dec (count t)))) "=") + + :else + (munge (str (:name (ana/resolve-var env (symbol t))))))) + +(defn resolve-types [env ts] + (let [ts (-> ts string/trim (subs 1 (dec (count ts)))) + xs (string/split ts #"\|")] + (str "{" (string/join "|" (map #(resolve-type env %) xs)) "}"))) + +(defn munge-param-return [env line] + (cond + (re-find #"@param" line) + (let [[p ts n & xs] (map string/trim + (string/split (string/trim line) #" "))] + (if (and (= "@param" p) + ts #?(:clj (.startsWith ^String ts "{") + :cljs (gstring/startsWith ts "{"))) + (string/join " " (concat [p (resolve-types env ts) (munge n)] xs)) + line)) + + (re-find #"@return" line) + (let [[p ts & xs] (map string/trim + (string/split (string/trim line) #" "))] + (if (and (= "@return" p) + ts #?(:clj (.startsWith ^String ts "{") + :cljs (gstring/startsWith ts "{"))) + (string/join " " (concat [p (resolve-types env ts)] xs)) + line)) + + :else line)) + +(defn checking-types? [] + (#{:error :warning} + (get-in @env/*compiler* + [:options :closure-warnings :check-types]))) + +(defn emit-comment + "Emit a nicely formatted comment string." + ([doc jsdoc] + (emit-comment nil doc jsdoc)) + ([env doc jsdoc] + (let [docs (when doc [doc]) + docs (if jsdoc (concat docs jsdoc) docs) + docs (remove nil? docs)] + (letfn [(print-comment-lines [e] + (let [[x & ys] + (map #(if (checking-types?) (munge-param-return env %) %) + (string/split-lines e))] + (emitln " * " (string/replace x "*/" "* /")) + (doseq [next-line ys] + (emitln " * " + (-> next-line + (string/replace #"^ " "") + (string/replace "*/" "* /"))))))] + (when (seq docs) + (emitln "/**") + (doseq [e docs] + (when e + (print-comment-lines e))) + (emitln " */")))))) + +(defn valid-define-value? [x] + (or (string? x) + (true? x) + (false? x) + (number? x))) + +(defn get-define [mname jsdoc] + (let [opts (get @env/*compiler* :options)] + (and (some #?(:clj #(.startsWith ^String % "@define") + :cljs #(gstring/startsWith % "@define")) + jsdoc) + opts + (= (:optimizations opts) :none) + (let [define (get-in opts [:closure-defines (str mname)])] + (when (valid-define-value? define) + (pr-str define)))))) + +(defmethod emit* :def + [{:keys [name var init env doc goog-define jsdoc export test var-ast]}] + ;; We only want to emit if an init is supplied, this is to avoid dead code + ;; elimination issues. The REPL is the exception to this rule. + (when (or init (:def-emits-var env)) + (let [mname (munge name)] + (emit-comment env doc (concat + (when goog-define + [(str "@define {" goog-define "}")]) + jsdoc (:jsdoc init))) + (when (= :return (:context env)) + (emitln "return (")) + (when (:def-emits-var env) + (emitln (iife-open env))) + (emits var) + (when init + (emits " = " + (if-let [define (get-define mname jsdoc)] + define + init))) + (when (:def-emits-var env) + (emitln "; return (") + (emits (merge + {:op :the-var + :env (assoc env :context :expr)} + var-ast)) + (emitln ");" + (iife-close env))) + (when (= :return (:context env)) + (emitln ")")) + ;; NOTE: JavaScriptCore does not like this under advanced compilation + ;; this change was primarily for REPL interactions - David + ;(emits " = (typeof " mname " != 'undefined') ? " mname " : undefined") + (when-not (= :expr (:context env)) (emitln ";")) + (when export + (emitln "goog.exportSymbol('" (munge export) "', " mname ");")) + (when (and ana/*load-tests* test) + (when (= :expr (:context env)) + (emitln ";")) + (emitln var ".cljs$lang$test = " test ";"))))) + +(defn emit-apply-to + [{:keys [name params env]}] + (let [arglist (gensym "arglist__") + delegate-name (str (munge name) "__delegate")] + (emitln "(function (" arglist "){") + (doseq [[i param] (map-indexed vector (drop-last 2 params))] + (emits "var ") + (emit param) + (emits " = cljs.core.first(") + (emitln arglist ");") + (emitln arglist " = cljs.core.next(" arglist ");")) + (if (< 1 (count params)) + (do + (emits "var ") + (emit (last (butlast params))) + (emitln " = cljs.core.first(" arglist ");") + (emits "var ") + (emit (last params)) + (emitln " = cljs.core.rest(" arglist ");") + (emits "return " delegate-name "(") + (doseq [param params] + (emit param) + (when-not (= param (last params)) (emits ","))) + (emitln ");")) + (do + (emits "var ") + (emit (last params)) + (emitln " = cljs.core.seq(" arglist ");") + (emits "return " delegate-name "(") + (doseq [param params] + (emit param) + (when-not (= param (last params)) (emits ","))) + (emitln ");"))) + (emits "})"))) + +(defn emit-fn-params [params] + (doseq [param params] + (emit param) + ; Avoid extraneous comma (function greet(x, y, z,) + (when-not (= param (last params)) + (emits ",")))) + +(defn emit-fn-method + [{expr :body :keys [type name params env recurs]}] + (let [async (:async env)] + (emit-wrap env + (emits "(" (when async "async ") "function " (munge name) "(") + (emit-fn-params params) + (emitln "){") + (when type + (emitln "var self__ = this;")) + (when recurs (emitln "while(true){")) + (emits expr) + (when recurs + (emitln "break;") + (emitln "}")) + (emits "})")))) + +(defn emit-arguments-to-array + "Emit code that copies function arguments into an array starting at an index. + Returns name of var holding the array." + [startslice] + (assert (and (>= startslice 0) (integer? startslice))) + (let [mname (munge (gensym)) + i (str mname "__i") + a (str mname "__a")] + (emitln "var " i " = 0, " + a " = new Array(arguments.length - " startslice ");") + (emitln "while (" i " < " a ".length) {" + a "[" i "] = arguments[" i " + " startslice "]; ++" i ";}") + a)) + +(defn emit-variadic-fn-method + [{expr :body max-fixed-arity :fixed-arity variadic :variadic? :keys [type name params env recurs] :as f}] + (emit-wrap env + (let [name (or name (gensym)) + mname (munge name) + delegate-name (str mname "__delegate") + async (:async env)] + (emitln "(function() { ") + (emits "var " delegate-name " = " (when async "async ") "function (") + (doseq [param params] + (emit param) + (when-not (= param (last params)) (emits ","))) + (emitln "){") + (when type + (emitln "var self__ = this;")) + (when recurs (emitln "while(true){")) + (emits expr) + (when recurs + (emitln "break;") + (emitln "}")) + (emitln "};") + + (emitln "var " mname " = " (when async "async ") "function (" + (comma-sep + (if variadic + (concat (butlast params) ['var_args]) + params)) "){") + (when type + (emitln "var self__ = this;")) + (when variadic + (emits "var ") + (emit (last params)) + (emitln " = null;") + (emitln "if (arguments.length > " (dec (count params)) ") {") + (let [a (emit-arguments-to-array (dec (count params)))] + (emitln " " (last params) " = new cljs.core.IndexedSeq(" a ",0,null);")) + (emitln "} ")) + (emits "return " delegate-name ".call(this,") + (doseq [param params] + (emit param) + (when-not (= param (last params)) (emits ","))) + (emits ");") + (emitln "};") + + (emitln mname ".cljs$lang$maxFixedArity = " max-fixed-arity ";") + (emits mname ".cljs$lang$applyTo = ") + (emit-apply-to (assoc f :name name)) + (emitln ";") + (emitln mname ".cljs$core$IFn$_invoke$arity$variadic = " delegate-name ";") + (emitln "return " mname ";") + (emitln "})()")))) + +(defmethod emit* :fn + [{variadic :variadic? :keys [name env methods max-fixed-arity recur-frames in-loop loop-lets]}] + ;;fn statements get erased, serve no purpose and can pollute scope if named + (when-not (= :statement (:context env)) + (let [recur-params (mapcat :params (filter #(and % @(:flag %)) recur-frames)) + loop-locals + (->> (concat recur-params + ;; need to capture locals only if in recur fn or loop + (when (or in-loop (seq recur-params)) + (mapcat :params loop-lets))) + (map munge) + seq) + async (:async env)] + (when loop-locals + (when (= :return (:context env)) + (emits "return ")) + (emitln "((function (" (comma-sep (map munge loop-locals)) "){") + (when-not (= :return (:context env)) + (emits "return "))) + (if (= 1 (count methods)) + (if variadic + (emit-variadic-fn-method (assoc (first methods) :name name)) + (emit-fn-method (assoc (first methods) :name name))) + (let [name (or name (gensym)) + mname (munge name) + maxparams (apply max-key count (map :params methods)) + mmap (into {} + (map (fn [method] + [(munge (symbol (str mname "__" (count (:params method))))) + method]) + methods)) + ms (sort-by #(-> % second :params count) (seq mmap))] + (when (= :return (:context env)) + (emits "return ")) + (emitln "(function() {") + (emitln "var " mname " = null;") + (doseq [[n meth] ms] + (emits "var " n " = ") + (if (:variadic? meth) + (emit-variadic-fn-method meth) + (emit-fn-method meth)) + (emitln ";")) + (emitln mname " = " (when async "async ") "function(" + (comma-sep (if variadic + (concat (butlast maxparams) ['var_args]) + maxparams)) "){") + (when variadic + (emits "var ") + (emit (last maxparams)) + (emitln " = var_args;")) + (emitln "switch(arguments.length){") + (doseq [[n meth] ms] + (if (:variadic? meth) + (do (emitln "default:") + (let [restarg (munge (gensym))] + (emitln "var " restarg " = null;") + (emitln "if (arguments.length > " max-fixed-arity ") {") + (let [a (emit-arguments-to-array max-fixed-arity)] + (emitln restarg " = new cljs.core.IndexedSeq(" a ",0,null);")) + (emitln "}") + (emitln "return " n ".cljs$core$IFn$_invoke$arity$variadic(" + (comma-sep (butlast maxparams)) + (when (> (count maxparams) 1) ", ") + restarg ");"))) + (let [pcnt (count (:params meth))] + (emitln "case " pcnt ":") + (emitln "return " n ".call(this" (if (zero? pcnt) nil + (list "," (comma-sep (take pcnt maxparams)))) ");")))) + (emitln "}") + (let [arg-count-js (if (= 'self__ (-> ms first val :params first :name)) + "(arguments.length - 1)" + "arguments.length")] + (emitln "throw(new Error('Invalid arity: ' + " arg-count-js "));")) + (emitln "};") + (when variadic + (emitln mname ".cljs$lang$maxFixedArity = " max-fixed-arity ";") + (emitln mname ".cljs$lang$applyTo = " (some #(let [[n m] %] (when (:variadic? m) n)) ms) ".cljs$lang$applyTo;")) + (doseq [[n meth] ms] + (let [c (count (:params meth))] + (if (:variadic? meth) + (emitln mname ".cljs$core$IFn$_invoke$arity$variadic = " n ".cljs$core$IFn$_invoke$arity$variadic;") + (emitln mname ".cljs$core$IFn$_invoke$arity$" c " = " n ";")))) + (emitln "return " mname ";") + (emitln "})()"))) + (when loop-locals + (emitln ";})(" (comma-sep loop-locals) "))"))))) + +(defmethod emit* :do + [{:keys [statements ret env]}] + (let [context (:context env)] + (when (and (seq statements) (= :expr context)) (emitln (iife-open env))) + (doseq [s statements] (emitln s)) + (emit ret) + (when (and (seq statements) (= :expr context)) (emitln (iife-close env))))) + +(defmethod emit* :try + [{try :body :keys [env catch name finally]}] + (let [context (:context env)] + (if (or name finally) + (do + (when (= :expr context) + (emits (iife-open env))) + (emits "try{" try "}") + (when name + (emits "catch (" (munge name) "){" catch "}")) + (when finally + (assert (not= :const (:op (ana/unwrap-quote finally))) "finally block cannot contain constant") + (emits "finally {" finally "}")) + (when (= :expr context) + (emits (iife-close env)))) + (emits try)))) + +(defn emit-let + [{expr :body :keys [bindings env]} is-loop] + (let [context (:context env)] + (when (= :expr context) + (emits (iife-open env))) + (binding [*lexical-renames* + (into *lexical-renames* + (when (= :statement context) + (map + (fn [binding] + (let [name (:name binding)] + (vector (hash-scope binding) + (gensym (str name "-"))))) + bindings)))] + (doseq [{:keys [init] :as binding} bindings] + (emits "var ") + (emit binding) ; Binding will be treated as a var + (emitln " = " init ";")) + (when is-loop (emitln "while(true){")) + (emits expr) + (when is-loop + (emitln "break;") + (emitln "}"))) + (when (= :expr context) (emits (iife-close env))))) + +(defmethod emit* :let [ast] + (emit-let ast false)) + +(defmethod emit* :loop [ast] + (emit-let ast true)) + +(defmethod emit* :recur + [{:keys [frame exprs env]}] + (let [temps (vec (take (count exprs) (repeatedly gensym))) + params (:params frame)] + (dotimes [i (count exprs)] + (emitln "var " (temps i) " = " (exprs i) ";")) + (dotimes [i (count exprs)] + (emitln (munge (params i)) " = " (temps i) ";")) + (emitln "continue;"))) + +(defmethod emit* :letfn + [{expr :body :keys [bindings env]}] + (let [context (:context env)] + (when (= :expr context) (emits (iife-open env))) + (doseq [{:keys [init] :as binding} bindings] + (emitln "var " (munge binding) " = " init ";")) + (emits expr) + (when (= :expr context) (emits (iife-close env))))) + +(defn protocol-prefix [psym] + (symbol (str (-> (str psym) + (.replace #?(:clj \. :cljs (js/RegExp. "\\." "g")) \$) + (.replace \/ \$)) + "$"))) + +(defmethod emit* :invoke + [{f :fn :keys [args env] :as expr}] + (let [info (:info f) + fn? (and ana/*cljs-static-fns* + (not (:dynamic info)) + (:fn-var info)) + protocol (:protocol info) + tag (ana/infer-tag env (first (:args expr))) + proto? (and protocol tag + (or (and ana/*cljs-static-fns* protocol (= tag 'not-native)) + (and + (or ana/*cljs-static-fns* + (:protocol-inline env)) + (or (= protocol tag) + ;; ignore new type hints for now - David + (and (not (set? tag)) + (not ('#{any clj clj-or-nil clj-nil number string boolean function object array js} tag)) + (when-let [ps (:protocols + (ana/resolve-existing-var env + ;; we're just checking for protocol methods, + ;; an internal optimization, don't emit warnings + (vary-meta tag assoc ::ana/no-resolve true)))] + (ps protocol))))))) + first-arg-tag (ana/infer-tag env (first (:args expr))) + opt-not? (and (= (:name info) 'cljs.core/not) + (= first-arg-tag 'boolean)) + opt-count? (and (= (:name info) 'cljs.core/count) + (boolean ('#{string array} first-arg-tag))) + ns (:ns info) + ftag (ana/infer-tag env f) + js? (or (= ns 'js) (= ns 'Math) (:foreign info)) ;; foreign - i.e. global / Node.js library + goog? (when ns + (or (= ns 'goog) + (when-let [ns-str (str ns)] + (= (get (string/split ns-str #"\.") 0 nil) "goog")) + (not (contains? (::ana/namespaces @env/*compiler*) ns)))) + + keyword? (or (= 'cljs.core/Keyword ftag) + (let [f (ana/unwrap-quote f)] + (and (= (-> f :op) :const) + (keyword? (-> f :form))))) + [f variadic-invoke] + (if fn? + (let [arity (count args) + variadic? (:variadic? info) + mps (:method-params info) + mfa (:max-fixed-arity info)] + (cond + ;; if only one method, no renaming needed + (and (not variadic?) + (= (count mps) 1)) + [f nil] + + ;; direct dispatch to variadic case + (and variadic? (> arity mfa)) + [(update-in f [:info] + (fn [info] + (-> info + (assoc :name (symbol (str (munge info) ".cljs$core$IFn$_invoke$arity$variadic"))) + ;; bypass local fn-self-name munging, we're emitting direct + ;; shadowing already applied + (update-in [:info] + #(-> % (dissoc :shadow) (dissoc :fn-self-name)))))) + {:max-fixed-arity mfa}] + + ;; direct dispatch to specific arity case + :else + (let [arities (map count mps)] + (if (some #{arity} arities) + [(update-in f [:info] + (fn [info] + (-> info + (assoc :name (symbol (str (munge info) ".cljs$core$IFn$_invoke$arity$" arity))) + ;; bypass local fn-self-name munging, we're emitting direct + ;; shadowing already applied + (update-in [:info] + #(-> % (dissoc :shadow) (dissoc :fn-self-name)))))) nil] + [f nil])))) + [f nil])] + (emit-wrap env + (cond + opt-not? + (emits "(!(" (first args) "))") + + opt-count? + (emits "((" (first args) ").length)") + + proto? + (let [pimpl (str (munge (protocol-prefix protocol)) + (munge (name (:name info))) "$arity$" (count args))] + (emits (first args) "." pimpl "(" (comma-sep (cons "null" (rest args))) ")")) + + keyword? + (emits f ".cljs$core$IFn$_invoke$arity$" (count args) "(" (comma-sep args) ")") + + variadic-invoke + (let [mfa (:max-fixed-arity variadic-invoke)] + (emits f "(" (comma-sep (take mfa args)) + (when-not (zero? mfa) ",") + "cljs.core.prim_seq.cljs$core$IFn$_invoke$arity$2([" + (comma-sep (drop mfa args)) "], 0))")) + + (or fn? js? goog?) + (emits f "(" (comma-sep args) ")") + + :else + (if (and ana/*cljs-static-fns* (#{:var :local :js-var} (:op f))) + ;; higher order case, static information missing + (let [fprop (str ".cljs$core$IFn$_invoke$arity$" (count args))] + (if ana/*fn-invoke-direct* + (emits "(" f fprop " ? " f fprop "(" (comma-sep args) ") : " + f "(" (comma-sep args) "))") + (emits "(" f fprop " ? " f fprop "(" (comma-sep args) ") : " + f ".call(" (comma-sep (cons "null" args)) "))"))) + (emits f ".call(" (comma-sep (cons "null" args)) ")")))))) + +(defmethod emit* :new + [{ctor :class :keys [args env]}] + (emit-wrap env + (emits "(new " ctor "(" + (comma-sep args) + "))"))) + +(defmethod emit* :qualified-method + [{ctor :class :keys [args env kind name]}] + (if (= :new kind) + (emit-wrap env + (emits "(function (...args) { return Reflect.construct(" ctor ", args) })")) + (emit-wrap env + (emits "(function (x, ...args) { return Reflect.apply(" ctor ".prototype." name ", x, args) })")))) + +(defmethod emit* :set! + [{:keys [target val env]}] + (emit-wrap env (emits "(" target " = " val ")"))) + +(defn sublib-select + [sublib] + (when sublib + (let [xs (string/split sublib #"\.")] + (apply str + (map #(str "['" % "']") xs))))) + +(defn emit-global-export [ns-name global-exports lib opts] + (let [[lib' sublib] (ana/lib&sublib lib) + ref (str "goog.global" + ;; Convert object dot access to bracket access + (->> (string/split (name (or (get global-exports (symbol lib')) + (get global-exports (name lib')))) + #"\.") + (map (fn [prop] (str "[\"" prop "\"]"))) + (apply str)))] + (when (and (ana/external-dep? lib') + (= :none (:optimizations opts))) + (emitln + "if(!" ref ") throw new Error(\"External library, " lib' ", never provided\");")) + (emitln + (munge ns-name) "." + (ana/munge-global-export lib) + " = " + ref + (sublib-select sublib) + ";"))) + +(defn load-libs + [libs seen reloads deps ns-name] + (let [{:keys [options js-dependency-index]} @env/*compiler* + {:keys [target nodejs-rt optimizations]} options + loaded-libs (munge 'cljs.core.*loaded-libs*) + loaded-libs-temp (munge (gensym 'cljs.core.*loaded-libs*)) + [node-libs libs-to-load] (let [libs (remove (set (vals seen)) (filter (set (vals libs)) deps))] + (if (= :nodejs target) + (let [{node-libs true libs-to-load false} (group-by ana/node-module-dep? libs)] + [node-libs libs-to-load]) + [nil libs])) + [goog-modules libs-to-load] (let [{goog-modules true libs-to-load false} + (group-by ana/goog-module-dep? libs-to-load)] + [goog-modules libs-to-load]) + global-exports-libs (filter ana/dep-has-global-exports? libs-to-load)] + (when (-> libs meta :reload-all) + (emitln "if(!COMPILED) " loaded-libs-temp " = " loaded-libs " || cljs.core.set([\"cljs.core\"]);") + (emitln "if(!COMPILED) " loaded-libs " = cljs.core.set([\"cljs.core\"]);")) + (doseq [lib libs-to-load] + (cond + #?@(:clj + [(ana/foreign-dep? lib) + ;; we only load foreign libraries under optimizations :none + ;; under :modules we also elide loads, as the module loader will + ;; have handled it - David + (when (and (= :none optimizations) + (not (contains? options :modules))) + (let [[lib _] (ana/lib&sublib lib)] + (if nodejs-rt + ;; under node.js we load foreign libs globally + (let [ijs (get js-dependency-index (name lib))] + (emitln "cljs.core.load_file(" + (-> (io/file (util/output-directory options) + (or (deps/-relative-path ijs) + (util/relative-name (:url ijs)))) + str + escape-string + wrap-in-double-quotes) + ");")) + (if-not (ana/external-dep? lib) + (emitln "goog.require('" (munge lib) "');") + ;; TODO: validate the lib exists + ))))] + :cljs + [(and (ana/foreign-dep? lib) + (not (keyword-identical? optimizations :none))) + nil]) + + (or (-> libs meta :reload) + (= (get reloads lib) :reload)) + (emitln "goog.require('" (munge lib) "', 'reload');") + + (or (-> libs meta :reload-all) + (= (get reloads lib) :reload-all)) + (emitln "goog.require('" (munge lib) "', 'reload-all');") + + :else + (when-not (= lib 'goog) + (emitln "goog.require('" (munge lib) "');")))) + ;; Node Libraries + (doseq [lib node-libs] + (let [[lib' sublib] (ana/lib&sublib lib)] + (emitln (munge ns-name) "." + (ana/munge-node-lib lib) + " = require('" lib' "')" (sublib-select sublib) ";"))) + ;; Google Closure Library Modules (i.e. goog.module(...)) + ;; these must be assigned to vars + (doseq [lib goog-modules] + (let [[lib' sublib] (ana/lib&sublib lib)] + (emitln "goog.require('" lib' "');") + ;; we emit goog.scope here to suppress a Closure error about + ;; goog.module.get when compiling - meant to discourage incorrect + ;; usage by hand written code - not applicable here + (emitln "goog.scope(function(){") + (emitln (munge ns-name) "." + (ana/munge-goog-module-lib lib) + " = goog.module.get('" lib' "')" (sublib-select sublib) ";") + (emitln "});"))) + ;; Global Exports + (doseq [lib global-exports-libs] + (let [{:keys [global-exports]} (get js-dependency-index (name (-> lib ana/lib&sublib first)))] + (emit-global-export ns-name global-exports lib options))) + (when (-> libs meta :reload-all) + (emitln "if(!COMPILED) " loaded-libs " = cljs.core.into(" loaded-libs-temp ", " loaded-libs ");")))) + +(defmethod emit* :ns* + [{:keys [name requires uses require-macros reloads env deps]}] + (load-libs requires nil (:require reloads) deps name) + (load-libs uses requires (:use reloads) deps name) + (when (:repl-env env) + (emitln "'nil';"))) + +(defmethod emit* :ns + [{:keys [name requires uses require-macros reloads env deps]}] + (emitln "goog.provide('" (munge name) "');") + (when-not (= name 'cljs.core) + (emitln "goog.require('cljs.core');") + (when (-> @env/*compiler* :options :emit-constants) + (emitln "goog.require('" (munge ana/constants-ns-sym) "');"))) + (load-libs requires nil (:require reloads) deps name) + (load-libs uses requires (:use reloads) deps name)) + +(defmethod emit* :deftype + [{:keys [t fields pmasks body protocols]}] + (let [fields (map munge fields)] + (emitln "") + (emitln "/**") + (emitln "* @constructor") + (doseq [protocol protocols] + (emitln " * @implements {" (munge (str protocol)) "}")) + (emitln "*/") + (emitln (munge t) " = (function (" (comma-sep fields) "){") + (doseq [fld fields] + (emitln "this." fld " = " fld ";")) + (doseq [[pno pmask] pmasks] + (emitln "this.cljs$lang$protocol_mask$partition" pno "$ = " pmask ";")) + (emitln "});") + (emit body))) + +(defmethod emit* :defrecord + [{:keys [t fields pmasks body protocols]}] + (let [fields (concat (map munge fields) '[__meta __extmap __hash])] + (emitln "") + (emitln "/**") + (emitln "* @constructor") + (doseq [protocol protocols] + (emitln " * @implements {" (munge (str protocol)) "}")) + (emitln "*/") + (emitln (munge t) " = (function (" (comma-sep fields) "){") + (doseq [fld fields] + (emitln "this." fld " = " fld ";")) + (doseq [[pno pmask] pmasks] + (emitln "this.cljs$lang$protocol_mask$partition" pno "$ = " pmask ";")) + (emitln "});") + (emit body))) + +(defn emit-dot + [{:keys [target field method args env]}] + (emit-wrap env + (if field + (emits target "." (munge field #{})) + (emits target "." (munge method #{}) "(" + (comma-sep args) + ")")))) + +(defmethod emit* :host-field [ast] (emit-dot ast)) +(defmethod emit* :host-call [ast] (emit-dot ast)) + +(defmethod emit* :js + [{:keys [op env code segs args]}] + (if (and code #?(:clj (.startsWith ^String (string/trim code) "/*") + :cljs (gstring/startsWith (string/trim code) "/*"))) + (emits code) + (emit-wrap env + (if code + (emits code) + (emits (interleave (concat segs (repeat nil)) + (concat args [nil]))))))) + +;; TODO: unify renaming helpers - this one was hard to find - David + +#?(:clj + (defn rename-to-js + "Change the file extension from .cljs to .js. Takes a File or a + String. Always returns a String." + [^String file-str] + (cond + (.endsWith file-str ".cljs") + (clojure.string/replace file-str #"\.cljs$" ".js") + + (.endsWith file-str ".cljc") + (if (= "cljs/core.cljc" file-str) + "cljs/core$macros.js" + (clojure.string/replace file-str #"\.cljc$" ".js")) + + :else + (throw (util/compilation-error (IllegalArgumentException. + (str "Invalid source file extension " file-str))))))) + +#?(:clj + (defn with-core-cljs + "Ensure that core.cljs has been loaded." + ([] (with-core-cljs + (when env/*compiler* + (:options @env/*compiler*)))) + ([opts] (with-core-cljs opts (fn []))) + ([opts body] + {:pre [(or (nil? opts) (map? opts)) + (fn? body)]} + (when-not (get-in @env/*compiler* [::ana/namespaces 'cljs.core :defs]) + (ana/analyze-file "cljs/core.cljs" opts)) + (body)))) + +#?(:clj + (defn url-path [^File f] + (.getPath (.toURL (.toURI f))))) + +#?(:clj + (defn compiled-by-string + ([] + (compiled-by-string + (when env/*compiler* + (:options @env/*compiler*)))) + ([opts] + (str "// Compiled by ClojureScript " + (util/clojurescript-version) + (when opts + (str " " (pr-str (ana/build-affecting-options opts)))))))) + +#?(:clj + (defn cached-core [ns ext opts] + (and (= :none (:optimizations opts)) + (not= "cljc" ext) + (= 'cljs.core ns) + (io/resource "cljs/core.aot.js")))) + +#?(:clj + (defn macro-ns? [ns ext opts] + (or (= "clj" ext) + (= 'cljs.core$macros ns) + (and (= ns 'cljs.core) (= "cljc" ext)) + (:macros-ns opts)))) + +#?(:clj + (defn emit-cached-core [src dest cached opts] + ;; no need to bother with analysis cache reading, handled by + ;; with-core-cljs + (when (or ana/*verbose* (:verbose opts)) + (util/debug-prn "Using cached cljs.core" (str src))) + (spit dest (slurp cached)) + (.setLastModified ^File dest (util/last-modified src)) + (when (true? (:source-map opts)) + (spit (io/file (str dest ".map")) + (json/write-str + (assoc + (json/read-str (slurp (io/resource "cljs/core.aot.js.map"))) + "file" + (str (io/file (util/output-directory opts) "cljs" "core.js")))))) + (merge + (ana/parse-ns src dest nil) + {:out-file dest}))) + +#?(:clj + (defn emit-source-map [src dest sm-data opts] + (let [sm-file (io/file (str (.getPath ^File dest) ".map"))] + (if-let [smap (:source-map-asset-path opts)] + (emitln "\n//# sourceMappingURL=" smap + (string/replace (util/path sm-file) + (str (util/path (io/file (:output-dir opts)))) + "") + (if (true? (:source-map-timestamp opts)) + (str + (if-not (string/index-of smap "?") "?" "&") + "rel=" (System/currentTimeMillis)) + "")) + (emitln "\n//# sourceMappingURL=" + (or (:source-map-url opts) (.getName sm-file)) + (if (true? (:source-map-timestamp opts)) + (str "?rel=" (System/currentTimeMillis)) + ""))) + (spit sm-file + (sm/encode {(url-path src) (:source-map sm-data)} + {:lines (+ (:gen-line sm-data) 2) + :file (url-path dest) + :source-map-path (:source-map-path opts) + :source-map-timestamp (:source-map-timestamp opts) + :source-map-pretty-print (:source-map-pretty-print opts) + :relpaths {(util/path src) + (util/ns->relpath (first (:provides opts)) (:ext opts))}}))))) + +#?(:clj + (defn emit-source [src dest ext opts] + (with-open [out ^java.io.Writer (io/make-writer dest {})] + (binding [*out* out + ana/*cljs-ns* 'cljs.user + ana/*cljs-file* (.getPath ^File src) + reader/*alias-map* (or (ana/get-bridged-alias-map) reader/*alias-map* {}) + ana/*checked-arrays* (or ana/*checked-arrays* (:checked-arrays opts)) + ana/*cljs-static-fns* (or ana/*cljs-static-fns* (:static-fns opts)) + *source-map-data* (when (:source-map opts) + (atom + {:source-map (sorted-map) + :gen-line 0})) + *source-map-data-gen-col* (AtomicLong.) + find-ns-starts-with (memoize find-ns-starts-with)] + (emitln (compiled-by-string opts)) + (with-open [rdr (io/reader src)] + (let [env (ana/empty-env) + emitter (when (:parallel-build opts) + (Executors/newSingleThreadExecutor)) + emit (if emitter + #(.execute emitter ^Runnable (bound-fn [] (emit %))) + emit)] + (loop [forms (ana/forms-seq* rdr (util/path src)) + ns-name nil + deps []] + (if (seq forms) + (let [env (assoc env :ns (ana/get-namespace ana/*cljs-ns*)) + {:keys [op] :as ast} (ana/analyze env (first forms) nil opts)] + (cond + (= op :ns) + (let [ns-name (:name ast) + ns-name (if (and (= 'cljs.core ns-name) + (= "cljc" ext)) + 'cljs.core$macros + ns-name)] + (emit ast) + (recur (rest forms) ns-name (into deps (:deps ast)))) + + (= :ns* (:op ast)) + (let [ns-emitted? (some? ns-name) + ns-name (if-not ns-emitted? + (ana/gen-user-ns src) + ns-name)] + (if-not ns-emitted? + (emit (assoc ast :name ns-name :op :ns)) + (emit ast)) + (recur (rest forms) ns-name (into deps (:deps ast)))) + + :else + (let [ns-emitted? (some? ns-name) + ns-name (if-not ns-emitted? + (ana/gen-user-ns src) + ns-name)] + (when-not ns-emitted? + (emit {:op :ns + :name ns-name})) + (emit ast) + (recur (rest forms) ns-name deps)))) + (let [_ (when emitter + (.shutdown emitter) + (.awaitTermination emitter 1000 TimeUnit/HOURS)) + sm-data (when *source-map-data* (assoc @*source-map-data* + :gen-col (.get ^AtomicLong *source-map-data-gen-col*))) + ret (merge + {:ns (or ns-name 'cljs.user) + :macros-ns (:macros-ns opts) + :provides [ns-name] + :requires (if (= ns-name 'cljs.core) + (vec (distinct deps)) + (cond-> (conj (vec (distinct deps)) 'cljs.core) + (get-in @env/*compiler* [:options :emit-constants]) + (conj ana/constants-ns-sym))) + :file dest + :out-file (.toString ^File dest) + :source-file src} + (when sm-data + {:source-map (:source-map sm-data)}))] + (when (and sm-data (= :none (:optimizations opts))) + (emit-source-map src dest sm-data + (merge opts {:ext ext :provides [ns-name]}))) + (let [path (.getPath (.toURL ^File dest))] + (swap! env/*compiler* assoc-in [::compiled-cljs path] ret)) + (ana/ensure-defs ns-name) + (let [{:keys [output-dir cache-analysis]} opts] + (when (and (true? cache-analysis) output-dir) + (ana/write-analysis-cache ns-name + (ana/cache-file src (ana/parse-ns src) output-dir :write) + src)) + ret)))))))))) + +#?(:clj + (defn compile-file* + ([^File src ^File dest] + (compile-file* src dest + (when env/*compiler* + (:options @env/*compiler*)))) + ([^File src ^File dest opts] + (ensure + (with-core-cljs opts + (fn [] + (when (and (or ana/*verbose* (:verbose opts)) + (not (:compiler-stats opts))) + (util/debug-prn "Compiling" (str src) "to" (str dest))) + (util/measure (and (or ana/*verbose* (:verbose opts)) + (:compiler-stats opts)) + (str "Compiling " (str src) " to " (str dest)) + (let [ext (util/ext src) + {:keys [ns] :as ns-info} (ana/parse-ns src)] + (if-let [cached (cached-core ns ext opts)] + [(emit-cached-core src dest cached opts) false] + (let [opts (if (macro-ns? ns ext opts) + (assoc opts :macros-ns true) + opts) + dest-exists? (.exists dest) + ret [(emit-source src dest ext opts) dest-exists?]] + (.setLastModified ^File dest (util/last-modified src)) + ret)))))))))) + +#?(:clj + (defn requires-compilation? + "Return true if the src file requires compilation." + ([src dest] + (requires-compilation? src dest + (when env/*compiler* + (:options @env/*compiler*)))) + ([^File src ^File dest opts] + (let [{:keys [ns requires]} (ana/parse-ns src)] + (if (and (= 'cljs.loader ns) (not (contains? opts :cache-key))) + false + (ensure + (or (not (.exists dest)) + (util/changed? src dest) + (let [version' (util/compiled-by-version dest) + version (util/clojurescript-version)] + (and version (not= version version'))) + (and opts + (not (and (io/resource "cljs/core.aot.js") (= 'cljs.core ns))) + (not= (ana/build-affecting-options opts) + (ana/build-affecting-options (util/build-options dest)))) + (and opts (:source-map opts) + (if (= (:optimizations opts) :none) + (not (.exists (io/file (str (.getPath dest) ".map")))) + (not (get-in @env/*compiler* [::compiled-cljs (.getAbsolutePath dest)])))) + (when-let [recompiled' (and *recompiled* @*recompiled*)] + (some requires recompiled'))))))))) + +#?(:clj + (defn compile-file + "Compiles src to a file of the same name, but with a .js extension, + in the src file's directory. + + With dest argument, write file to provided location. If the dest + argument is a file outside the source tree, missing parent + directories will be created. The src file will only be compiled if + the dest file has an older modification time. + + Both src and dest may be either a String or a File. + + Returns a map containing {:ns .. :provides .. :requires .. :file ..}. + If the file was not compiled returns only {:file ...}" + ([src] + (let [dest (rename-to-js src)] + (compile-file src dest + (when env/*compiler* + (:options @env/*compiler*))))) + ([src dest] + (compile-file src dest + (when env/*compiler* + (:options @env/*compiler*)))) + ([src dest opts] + {:post [map?]} + (binding [ana/*file-defs* (atom #{}) + ana/*unchecked-if* false + ana/*unchecked-arrays* false + ana/*cljs-warnings* ana/*cljs-warnings*] + (let [nses (get @env/*compiler* ::ana/namespaces) + src-file (io/file src) + dest-file (io/file dest) + opts (merge {:optimizations :none} opts)] + (if (.exists src-file) + (try + (let [{ns :ns :as ns-info} (ana/parse-ns src-file dest-file opts) + opts (if (and (not= (util/ext src) "clj") ;; skip cljs.core macro-ns + (= ns 'cljs.core)) + (cond-> opts + (not (false? (:static-fns opts))) (assoc :static-fns true) + true (dissoc :checked-arrays)) + opts)] + (if (or (requires-compilation? src-file dest-file opts) + (:force opts)) + (do + (util/mkdirs dest-file) + (when (and (get-in nses [ns :defs]) + (not= 'cljs.core ns) + (not= :interactive (:mode opts))) + (swap! env/*compiler* update-in [::ana/namespaces] dissoc ns)) + (let [[ret recompiled?] (compile-file* src-file dest-file opts)] + (when (and *recompiled* + recompiled?) + (swap! *recompiled* conj ns)) + ret)) + (do + ;; populate compilation environment with analysis information + ;; when constants are optimized + (when (or (and (= ns 'cljs.loader) + (not (contains? opts :cache-key))) + (and (true? (:optimize-constants opts)) + (nil? (get-in nses [ns :defs])))) + (with-core-cljs opts (fn [] (ana/analyze-file src-file opts)))) + (assoc ns-info :out-file (.toString dest-file))))) + (catch Exception e + (throw (ex-info (str "failed compiling file:" src) {:file src :clojure.error/phase :compilation} e)))) + (throw (util/compilation-error (java.io.FileNotFoundException. (str "The file " src " does not exist.")))))))))) + +#?(:clj + (defn cljs-files-in + "Return a sequence of all .cljs and .cljc files in the given directory." + [dir] + (map io/file + (reduce + (fn [m x] + (if (.endsWith ^String x ".cljs") + (cond-> (conj m x) + (contains? m (str (subs x 0 (dec (count x))) "c")) + (set/difference #{(str (subs x 0 (dec (count x))) "c")})) + ;; ends with .cljc + (cond-> m + (not (contains? m (str (subs x 0 (dec (count x))) "s"))) + (conj x)))) + #{} + (into [] + (comp + (filter + #(let [name (.getName ^File %)] + (and (or (.endsWith name ".cljs") + (.endsWith name ".cljc")) + (not= \. (first name)) + (not (contains? cljs-reserved-file-names name))))) + (map #(.getPath ^File %))) + (file-seq dir)))))) + +#?(:clj + (defn compile-root + "Looks recursively in src-dir for .cljs files and compiles them to + .js files. If target-dir is provided, output will go into this + directory mirroring the source directory structure. Returns a list + of maps containing information about each file which was compiled + in dependency order." + ([src-dir] + (compile-root src-dir "out")) + ([src-dir target-dir] + (compile-root src-dir target-dir + (when env/*compiler* + (:options @env/*compiler*)))) + ([src-dir target-dir opts] + (swap! env/*compiler* assoc :root src-dir) + (let [src-dir-file (io/file src-dir) + inputs (deps/dependency-order + (map #(ana/parse-ns %) + (cljs-files-in src-dir-file)))] + (binding [*inputs* (zipmap (map :ns inputs) inputs)] + (loop [inputs (seq inputs) compiled []] + (if inputs + (let [{:keys [source-file] :as ns-info} (first inputs) + output-file (util/to-target-file target-dir ns-info) + ijs (compile-file source-file output-file opts)] + (recur + (next inputs) + (conj compiled + (assoc ijs :file-name (.getPath output-file))))) + compiled))))))) + +#?(:clj + (defn find-source [file] + (ana/parse-ns file))) + +#?(:clj + (defn find-root-sources + [src-dir] + (let [src-dir-file (io/file src-dir)] + (map find-source (cljs-files-in src-dir-file))))) + +;; TODO: needs fixing, table will include other things than keywords - David + +(defn emit-constants-table [table] + (emitln "goog.provide('" (munge ana/constants-ns-sym) "');") + (emitln "goog.require('cljs.core');") + (doseq [[sym value] table] + (let [ns (namespace sym) + name (name sym)] + (emits "cljs.core." value " = ") + (cond + (keyword? sym) (emits-keyword sym) + (symbol? sym) (emits-symbol sym) + :else (throw + (ex-info + (str "Cannot emit constant for type " (type sym)) + {:error :invalid-constant-type + :clojure.error/phase :compilation}))) + (emits ";\n")))) + +#?(:clj + (defn emit-constants-table-to-file [table dest] + (io/make-parents dest) + (with-open [out ^java.io.Writer (io/make-writer dest {})] + (binding [*out* out] + (emit-constants-table table))))) + +(defn emit-externs + ([externs] + (emit-externs [] externs (atom #{}) + (when env/*compiler* (ana/get-externs)))) + ([prefix externs top-level known-externs] + (loop [ks (seq (keys externs))] + (when ks + (let [k (first ks) + [top :as prefix'] (conj prefix k)] + (when (and (not= 'prototype k) + (nil? (get-in known-externs prefix'))) + (if-not (or (contains? @top-level top) + (contains? known-externs top)) + (do + (emitln "var " (string/join "." (map munge prefix')) ";") + (swap! top-level conj top)) + (emitln (string/join "." (map munge prefix')) ";"))) + (let [m (get externs k)] + (when-not (empty? m) + (emit-externs prefix' m top-level known-externs)))) + (recur (next ks)))))) + +#?(:clj + (defn emit-inferred-externs-to-file [externs dest] + (io/make-parents dest) + (with-open [out ^java.io.Writer (io/make-writer dest {})] + (binding [*out* out] + (emit-externs externs))))) diff --git a/src/main/clojure/cljs/compiler/api.clj b/src/main/clojure/cljs/compiler/api.clj new file mode 100644 index 0000000000..b268ac6ed2 --- /dev/null +++ b/src/main/clojure/cljs/compiler/api.clj @@ -0,0 +1,101 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software + +(ns cljs.compiler.api + "This is intended to be a stable api for those who need programmatic access + to the compiler." + (:refer-clojure :exclude [munge]) + (:require [cljs.analyzer :as ana] + [cljs.analyzer.api :as ana-api] + [cljs.compiler :as comp] + [cljs.closure :as closure])) + +;; ============================================================================= +;; Main API + +(defn munge + "Munge a symbol or string. Preserves the original type." + [s] + (comp/munge s)) + +(defn emit + "Given an AST node generated by the analyzer emit JavaScript as a string." + ([ast] + (emit (or (ana-api/current-state) (ana-api/empty-state)) ast)) + ([state ast] + (ana-api/with-state state + (with-out-str + (comp/emit ast))))) + +(defn with-core-cljs + "Ensure that core.cljs has been loaded." + ([] + (comp/with-core-cljs + (when-let [state (ana-api/current-state)] + (:options @state)))) + ([opts] (with-core-cljs opts (fn []))) + ([opts body] + (with-core-cljs (or (ana-api/current-state) (ana-api/empty-state opts)) opts body)) + ([state opts body] + (ana-api/with-state state + (binding [ana/*cljs-warning-handlers* (:warning-handlers opts ana/*cljs-warning-handlers*)] + (comp/with-core-cljs opts body))))) + +(defn requires-compilation? + "Return true if the src file requires compilation." + ([src dest] (requires-compilation? src dest nil)) + ([src dest opts] + (requires-compilation? (or (ana-api/current-state)(ana-api/empty-state opts)) src dest opts)) + ([state src dest opts] + (ana-api/with-state state + (binding [ana/*cljs-warning-handlers* (:warning-handlers opts ana/*cljs-warning-handlers*)] + (comp/requires-compilation? src dest opts))))) + +(defn compile-file + "Compiles src to a file of the same name, but with a .js extension, + in the src file's directory. + + With dest argument, write file to provided location. If the dest + argument is a file outside the source tree, missing parent + directories will be created. The src file will only be compiled if + the dest file has an older modification time. + + Both src and dest may be either a String or a File. + + Returns a map containing {:ns .. :provides .. :requires .. :file ..}. + If the file was not compiled returns only {:file ...}" + ([src] + (compile-file src (closure/src-file->target-file src))) + ([src dest] + (compile-file src dest nil)) + ([src dest opts] + (compile-file (or (ana-api/current-state) (ana-api/empty-state opts)) src dest opts)) + ([state src dest opts] + (ana-api/with-state state + (binding [ana/*cljs-warning-handlers* (:warning-handlers opts ana/*cljs-warning-handlers*)] + (comp/compile-file src dest opts))))) + +(defn cljs-files-in + "Return a sequence of all .cljs and .cljc files in the given directory." + [dir] + (comp/cljs-files-in dir)) + +(defn compile-root + "Looks recursively in src-dir for .cljs files and compiles them to + .js files. If target-dir is provided, output will go into this + directory mirroring the source directory structure. Returns a list + of maps containing information about each file which was compiled + in dependency order." + ([src-dir] (compile-root src-dir "out")) + ([src-dir target-dir] (compile-root src-dir target-dir nil)) + ([src-dir target-dir opts] + (compile-root (or (ana-api/current-state) (ana-api/empty-state opts)) src-dir target-dir opts)) + ([state src-dir target-dir opts] + (ana-api/with-state state + (binding [ana/*cljs-warning-handlers* (:warning-handlers opts ana/*cljs-warning-handlers*)] + (comp/compile-root src-dir target-dir opts))))) diff --git a/src/main/clojure/cljs/compiler/macros.clj b/src/main/clojure/cljs/compiler/macros.clj new file mode 100644 index 0000000000..5ff36b50e7 --- /dev/null +++ b/src/main/clojure/cljs/compiler/macros.clj @@ -0,0 +1,16 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.compiler.macros + (:refer-clojure :exclude [let])) + +(defmacro emit-wrap [env & body] + `(cljs.core/let [env# ~env] + (when (= :return (:context env#)) (cljs.compiler/emits "return ")) + ~@body + (when-not (= :expr (:context env#)) (cljs.compiler/emitln ";")))) diff --git a/src/main/clojure/cljs/core.cljc b/src/main/clojure/cljs/core.cljc new file mode 100644 index 0000000000..d8a2d0fd59 --- /dev/null +++ b/src/main/clojure/cljs/core.cljc @@ -0,0 +1,3504 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.core + (:refer-clojure :exclude [-> ->> .. amap and areduce alength aclone assert await binding bound-fn case comment + cond condp declare definline definterface defmethod defmulti defn defn- defonce + defprotocol defrecord defstruct deftype delay destructure doseq dosync dotimes doto + extend-protocol extend-type fn for future gen-class gen-interface + if-let if-not import io! lazy-cat lazy-seq let letfn locking loop + memfn ns or proxy proxy-super pvalues reify sync time + when when-first when-let when-not while with-bindings with-in-str + with-loading-context with-local-vars with-open with-out-str with-precision with-redefs + satisfies? identical? true? false? number? nil? instance? symbol? keyword? string? str get + make-array vector list hash-map array-map hash-set + + aget aset + + - * / < <= > >= == zero? pos? neg? inc dec max min mod + byte char short int long float double + unchecked-byte unchecked-char unchecked-short unchecked-int + unchecked-long unchecked-float unchecked-double + unchecked-add unchecked-add-int unchecked-dec unchecked-dec-int + unchecked-divide unchecked-divide-int unchecked-inc unchecked-inc-int + unchecked-multiply unchecked-multiply-int unchecked-negate unchecked-negate-int + unchecked-subtract unchecked-subtract-int unchecked-remainder-int + unsigned-bit-shift-right + + bit-and bit-and-not bit-clear bit-flip bit-not bit-or bit-set + bit-test bit-shift-left bit-shift-right bit-xor defmacro + + cond-> cond->> as-> some-> some->> + + require use refer-clojure + + if-some when-some test ns-publics ns-imports ns-interns + ns-unmap var vswap! macroexpand-1 macroexpand + some? resolve + #?@(:cljs [alias coercive-not coercive-not= coercive-= coercive-boolean + truth_ js-arguments js-delete js-in js-debugger exists? divide js-mod + unsafe-bit-and bit-shift-right-zero-fill mask bitpos caching-hash + defcurried rfn specify! js-this this-as implements? array js-obj js-fn? + simple-benchmark gen-apply-to js-str es6-iterable load-file* undefined? + specify copy-arguments goog-define js-comment js-inline-comment + unsafe-cast require-macros use-macros gen-apply-to-simple unchecked-get unchecked-set])]) + #?(:cljs (:require-macros [cljs.core :as core] + [cljs.support :refer [assert-args]])) + (:require clojure.walk + clojure.set + [clojure.string :as string] + [cljs.compiler :as comp] + [cljs.env :as env] + #?(:clj [cljs.support :refer [assert-args]]) + #?(:cljs [cljs.core :as core]) + #?(:cljs [cljs.analyzer :as ana]))) + +#?(:clj (alias 'core 'clojure.core)) +#?(:clj (alias 'ana 'cljs.analyzer)) + +#?(:clj + (core/defmacro import-macros [ns [& vars]] + (core/let [ns (find-ns ns) + vars (map #(ns-resolve ns %) vars) + syms (map + (core/fn [^clojure.lang.Var v] + (core/-> v .sym + (with-meta + (merge + {:macro true} + (update-in (select-keys (meta v) [:arglists :doc :file :line]) + [:arglists] (core/fn [arglists] `(quote ~arglists))))))) + vars) + defs (map + (core/fn [sym var] + (core/let [{:keys [arglists doc file line]} (meta sym)] + `(do + (def ~sym (deref ~var)) + ;for AOT compilation + (alter-meta! (var ~sym) assoc + :macro true + :arglists ~arglists + :doc ~doc + :file ~file + :line ~line)))) + syms vars)] + `(do ~@defs + :imported)))) + +#?(:clj + (import-macros clojure.core + [-> ->> .. assert comment cond + declare defn- + extend-protocol fn for + if-let if-not letfn + memfn + when when-first when-let when-not while + cond-> cond->> as-> some-> some->> + if-some when-some])) + +#?(:cljs + (core/defmacro -> + "Threads the expr through the forms. Inserts x as the + second item in the first form, making a list of it if it is not a + list already. If there are more forms, inserts the first form as the + second item in second form, etc." + [x & forms] + (core/loop [x x, forms forms] + (if forms + (core/let [form (first forms) + threaded (if (seq? form) + (with-meta `(~(first form) ~x ~@(next form)) (meta form)) + (core/list form x))] + (recur threaded (next forms))) + x)))) + +#?(:cljs + (core/defmacro ->> + "Threads the expr through the forms. Inserts x as the + last item in the first form, making a list of it if it is not a + list already. If there are more forms, inserts the first form as the + last item in second form, etc." + [x & forms] + (core/loop [x x, forms forms] + (if forms + (core/let [form (first forms) + threaded (if (seq? form) + (with-meta `(~(first form) ~@(next form) ~x) (meta form)) + (core/list form x))] + (recur threaded (next forms))) + x)))) + +#?(:cljs + (core/defmacro .. + "form => fieldName-symbol or (instanceMethodName-symbol args*) + + Expands into a member access (.) of the first member on the first + argument, followed by the next member on the result, etc. For + instance: + + (.. System (getProperties) (get \"os.name\")) + + expands to: + + (. (. System (getProperties)) (get \"os.name\")) + + but is easier to write, read, and understand." + ([x form] `(. ~x ~form)) + ([x form & more] `(.. (. ~x ~form) ~@more)))) + +#?(:cljs + (core/defmacro comment + "Ignores body, yields nil" + [& body])) + +#?(:cljs + (core/defmacro cond + "Takes a set of test/expr pairs. It evaluates each test one at a + time. If a test returns logical true, cond evaluates and returns + the value of the corresponding expr and doesn't evaluate any of the + other tests or exprs. (cond) returns nil." + {:added "1.0"} + [& clauses] + (core/when clauses + (core/list 'if (first clauses) + (if (next clauses) + (second clauses) + (throw (js/Error. "cond requires an even number of forms"))) + (cons 'cljs.core/cond (next (next clauses))))))) + +#?(:cljs + (core/defmacro declare + "defs the supplied var names with no bindings, useful for making forward declarations." + [& names] `(do ~@(map #(core/list 'def (vary-meta % assoc :declared true)) names)))) + +(core/defmacro doto + "Evaluates x then calls all of the methods and functions with the + value of x supplied at the front of the given arguments. The forms + are evaluated in order. Returns x. + + (doto (new js/Map) (.set \"a\" 1) (.set \"b\" 2))" + [x & forms] + (core/let [gx (gensym)] + `(let [~gx ~x] + ~@(map (core/fn [f] + (if (seq? f) + `(~(first f) ~gx ~@(next f)) + `(~f ~gx))) + forms) + ~gx))) + +#?(:cljs + (core/defn- parse-impls [specs] + (core/loop [ret {} s specs] + (if (seq s) + (recur (assoc ret (first s) (take-while seq? (next s))) + (drop-while seq? (next s))) + ret)))) + +#?(:cljs + (core/defn- emit-extend-protocol [p specs] + (core/let [impls (parse-impls specs)] + `(do + ~@(map (core/fn [[t fs]] + `(extend-type ~t ~p ~@fs)) + impls))))) + +#?(:cljs + (core/defmacro extend-protocol + "Useful when you want to provide several implementations of the same + protocol all at once. Takes a single protocol and the implementation + of that protocol for one or more types. Expands into calls to + extend-type: + + (extend-protocol Protocol + AType + (foo [x] ...) + (bar [x y] ...) + BType + (foo [x] ...) + (bar [x y] ...) + AClass + (foo [x] ...) + (bar [x y] ...) + nil + (foo [x] ...) + (bar [x y] ...)) + + expands into: + + (do + (clojure.core/extend-type AType Protocol + (foo [x] ...) + (bar [x y] ...)) + (clojure.core/extend-type BType Protocol + (foo [x] ...) + (bar [x y] ...)) + (clojure.core/extend-type AClass Protocol + (foo [x] ...) + (bar [x y] ...)) + (clojure.core/extend-type nil Protocol + (foo [x] ...) + (bar [x y] ...)))" + [p & specs] + (emit-extend-protocol p specs))) + +(core/defn ^{:private true} + maybe-destructured + [params body] + (if (every? core/symbol? params) + (cons params body) + (core/loop [params params + new-params (with-meta [] (meta params)) + lets []] + (if params + (if (core/symbol? (first params)) + (recur (next params) (conj new-params (first params)) lets) + (core/let [gparam (gensym "p__")] + (recur (next params) (conj new-params gparam) + (core/-> lets (conj (first params)) (conj gparam))))) + `(~new-params + (let ~lets + ~@body)))))) + +(core/defmacro fn + "params => positional-params* , or positional-params* & rest-param + positional-param => binding-form + rest-param => binding-form + binding-form => name, or destructuring-form + + Defines a function + + See https://clojure.org/reference/special_forms#fn for more information" + {:forms '[(fn name? [params*] exprs*) (fn name? ([params*] exprs*) +)]} + [& sigs] + (core/let [name (if (core/symbol? (first sigs)) (first sigs) nil) + sigs (if name (next sigs) sigs) + sigs (if (vector? (first sigs)) + (core/list sigs) + (if (seq? (first sigs)) + sigs + ;; Assume single arity syntax + (throw (#?(:clj Exception. :cljs js/Error.) + (if (seq sigs) + (core/str "Parameter declaration " + (core/first sigs) + " should be a vector") + (core/str "Parameter declaration missing")))))) + psig (fn* [sig] + ;; Ensure correct type before destructuring sig + (core/when (not (seq? sig)) + (throw (#?(:clj Exception. :cljs js/Error.) + (core/str "Invalid signature " sig + " should be a list")))) + (core/let [[params & body] sig + _ (core/when (not (vector? params)) + (throw (#?(:clj Exception. :cljs js/Error.) + (if (seq? (first sigs)) + (core/str "Parameter declaration " params + " should be a vector") + (core/str "Invalid signature " sig + " should be a list"))))) + conds (core/when (core/and (next body) (map? (first body))) + (first body)) + body (if conds (next body) body) + conds (core/or conds (meta params)) + pre (:pre conds) + post (:post conds) + body (if post + `((let [~'% ~(if (core/< 1 (count body)) + `(do ~@body) + (first body))] + ~@(map (fn* [c] `(assert ~c)) post) + ~'%)) + body) + body (if pre + (concat (map (fn* [c] `(assert ~c)) pre) + body) + body)] + (maybe-destructured params body))) + new-sigs (map psig sigs) + fn-sym-meta (meta (first &form)) + fn*-sym (with-meta 'fn* fn-sym-meta)] + (with-meta + (if name + (list* fn*-sym name new-sigs) + (cons fn*-sym new-sigs)) + (meta &form)))) + +#?(:cljs + (core/defmacro defn- + "same as defn, yielding non-public def" + [name & decls] + (list* `defn (with-meta name (assoc (meta name) :private true)) decls))) + +#?(:cljs + (core/defmacro if-let + "bindings => binding-form test + + If test is true, evaluates then with binding-form bound to the value of + test, if not, yields else" + ([bindings then] + `(if-let ~bindings ~then nil)) + ([bindings then else & oldform] + (assert-args if-let + (vector? bindings) "a vector for its binding" + (empty? oldform) "1 or 2 forms after binding vector" + (= 2 (count bindings)) "exactly 2 forms in binding vector") + (core/let [form (bindings 0) tst (bindings 1)] + `(let [temp# ~tst] + (if temp# + (let [~form temp#] + ~then) + ~else)))))) + +#?(:cljs + (core/defmacro if-not + "Evaluates test. If logical false, evaluates and returns then expr, + otherwise else expr, if supplied, else nil." + ([test then] `(if-not ~test ~then nil)) + ([test then else] + `(if (not ~test) ~then ~else)))) + +#?(:cljs + (core/defmacro letfn + "fnspec ==> (fname [params*] exprs) or (fname ([params*] exprs)+) + + Takes a vector of function specs and a body, and generates a set of + bindings of functions to their names. All of the names are available + in all of the definitions of the functions, as well as the body." + {:forms '[(letfn [fnspecs*] exprs*)], + :special-form true, :url nil} + [fnspecs & body] + `(letfn* ~(vec (interleave (map first fnspecs) + (map #(cons `fn %) fnspecs))) + ~@body))) + +(core/defmacro memfn + "Expands into code that creates a fn that expects to be passed an + object and any args and calls the named instance method on the + object passing the args. Use when you want to treat a JavaScript + method as a first-class fn." + [name & args] + (core/let [t (with-meta (gensym "target") + (meta name))] + `(fn [~t ~@args] + (. ~t (~name ~@args))))) + +#?(:cljs + (core/defmacro when + "Evaluates test. If logical true, evaluates body in an implicit do." + [test & body] + (core/list 'if test (cons 'do body)))) + +#?(:cljs + (core/defmacro when-first + "bindings => x xs + + Roughly the same as (when (seq xs) (let [x (first xs)] body)) but xs is evaluated only once" + [bindings & body] + (assert-args when-first + (vector? bindings) "a vector for its binding" + (= 2 (count bindings)) "exactly 2 forms in binding vector") + (core/let [[x xs] bindings] + `(when-let [xs# (seq ~xs)] + (let [~x (first xs#)] + ~@body))))) + +#?(:cljs + (core/defmacro when-let + "bindings => binding-form test + + When test is true, evaluates body with binding-form bound to the value of test" + [bindings & body] + (assert-args when-let + (vector? bindings) "a vector for its binding" + (= 2 (count bindings)) "exactly 2 forms in binding vector") + (core/let [form (bindings 0) tst (bindings 1)] + `(let [temp# ~tst] + (when temp# + (let [~form temp#] + ~@body)))))) + +#?(:cljs + (core/defmacro when-not + "Evaluates test. If logical false, evaluates body in an implicit do." + [test & body] + (core/list 'if test nil (cons 'do body)))) + +#?(:cljs + (core/defmacro while + "Repeatedly executes body while test expression is true. Presumes + some side-effect will cause test to become false/nil. Returns nil" + [test & body] + `(loop [] + (when ~test + ~@body + (recur))))) + +#?(:cljs + (core/defmacro cond-> + "Takes an expression and a set of test/form pairs. Threads expr (via ->) + through each form for which the corresponding test + expression is true. Note that, unlike cond branching, cond-> threading does + not short circuit after the first true test expression." + [expr & clauses] + (core/assert (even? (count clauses))) + (core/let [g (gensym) + steps (map (core/fn [[test step]] `(if ~test (-> ~g ~step) ~g)) + (partition 2 clauses))] + `(let [~g ~expr + ~@(interleave (repeat g) (butlast steps))] + ~(if (empty? steps) + g + (last steps)))))) + +#?(:cljs + (core/defmacro cond->> + "Takes an expression and a set of test/form pairs. Threads expr (via ->>) + through each form for which the corresponding test expression + is true. Note that, unlike cond branching, cond->> threading does not short circuit + after the first true test expression." + [expr & clauses] + (core/assert (even? (count clauses))) + (core/let [g (gensym) + steps (map (core/fn [[test step]] `(if ~test (->> ~g ~step) ~g)) + (partition 2 clauses))] + `(let [~g ~expr + ~@(interleave (repeat g) (butlast steps))] + ~(if (empty? steps) + g + (last steps)))))) + +#?(:cljs + (core/defmacro as-> + "Binds name to expr, evaluates the first form in the lexical context + of that binding, then binds name to that result, repeating for each + successive form, returning the result of the last form." + [expr name & forms] + `(let [~name ~expr + ~@(interleave (repeat name) (butlast forms))] + ~(if (empty? forms) + name + (last forms))))) + +#?(:cljs + (core/defmacro some-> + "When expr is not nil, threads it into the first form (via ->), + and when that result is not nil, through the next etc" + [expr & forms] + (core/let [g (gensym) + steps (map (core/fn [step] `(if (nil? ~g) nil (-> ~g ~step))) + forms)] + `(let [~g ~expr + ~@(interleave (repeat g) (butlast steps))] + ~(if (empty? steps) + g + (last steps)))))) + +#?(:cljs + (core/defmacro some->> + "When expr is not nil, threads it into the first form (via ->>), + and when that result is not nil, through the next etc" + [expr & forms] + (core/let [g (gensym) + steps (map (core/fn [step] `(if (nil? ~g) nil (->> ~g ~step))) + forms)] + `(let [~g ~expr + ~@(interleave (repeat g) (butlast steps))] + ~(if (empty? steps) + g + (last steps)))))) + +#?(:cljs + (core/defmacro if-some + "bindings => binding-form test + + If test is not nil, evaluates then with binding-form bound to the + value of test, if not, yields else" + ([bindings then] + `(if-some ~bindings ~then nil)) + ([bindings then else & oldform] + (assert-args if-some + (vector? bindings) "a vector for its binding" + (empty? oldform) "1 or 2 forms after binding vector" + (= 2 (count bindings)) "exactly 2 forms in binding vector") + (core/let [form (bindings 0) tst (bindings 1)] + `(let [temp# ~tst] + (if (nil? temp#) + ~else + (let [~form temp#] + ~then))))))) + +#?(:cljs + (core/defmacro when-some + "bindings => binding-form test + + When test is not nil, evaluates body with binding-form bound to the + value of test" + [bindings & body] + (assert-args when-some + (vector? bindings) "a vector for its binding" + (= 2 (count bindings)) "exactly 2 forms in binding vector") + (core/let [form (bindings 0) tst (bindings 1)] + `(let [temp# ~tst] + (if (nil? temp#) + nil + (let [~form temp#] + ~@body)))))) + +(core/defn- ^{:dynamic true} assert-valid-fdecl + "A good fdecl looks like (([a] ...) ([a b] ...)) near the end of defn." + [fdecl] + (core/when (empty? fdecl) + (throw + #?(:clj (IllegalArgumentException. "Parameter declaration missing") + :cljs (js/Error. "Parameter declaration missing")))) + (core/let [argdecls + (map + #(if (seq? %) + (first %) + (throw + #?(:clj (IllegalArgumentException. + (if (seq? (first fdecl)) + (core/str "Invalid signature \"" + % + "\" should be a list") + (core/str "Parameter declaration \"" + % + "\" should be a vector"))) + :cljs (js/Error. + (if (seq? (first fdecl)) + (core/str "Invalid signature \"" + % + "\" should be a list") + (core/str "Parameter declaration \"" + % + "\" should be a vector")))))) + fdecl) + bad-args (seq (remove #(vector? %) argdecls))] + (core/when bad-args + (throw + #?(:clj (IllegalArgumentException. + (core/str "Parameter declaration \"" (first bad-args) + "\" should be a vector")) + :cljs (js/Error. + (core/str "Parameter declaration \"" (first bad-args) + "\" should be a vector"))))))) + +(def + ^{:private true} + sigs + (core/fn [fdecl] + (assert-valid-fdecl fdecl) + (core/let [asig + (core/fn [fdecl] + (core/let [arglist (first fdecl) + ;elide implicit macro args + arglist (if #?(:clj (clojure.lang.Util/equals '&form (first arglist)) + :cljs (= '&form (first arglist))) + #?(:clj (clojure.lang.RT/subvec arglist 2 (clojure.lang.RT/count arglist)) + :cljs (subvec arglist 2 (count arglist))) + arglist) + body (next fdecl)] + (if (map? (first body)) + (if (next body) + (with-meta arglist (conj (if (meta arglist) (meta arglist) {}) (first body))) + arglist) + arglist)))] + (if (seq? (first fdecl)) + (core/loop [ret [] fdecls fdecl] + (if fdecls + (recur (conj ret (asig (first fdecls))) (next fdecls)) + (seq ret))) + (core/list (asig fdecl)))))) + +(core/defmacro defonce + "defs name to have the root value of init iff the named var has no root value, + else init is unevaluated" + [x init] + (core/let [qualified (if (namespace x) + x + (symbol (core/str (core/-> &env :ns :name)) (name x)))] + `(when-not (exists? ~qualified) + (def ~x ~init)))) + +(core/defn destructure [bindings] + (core/let [bents (partition 2 bindings) + pb (core/fn pb [bvec b v] + (core/let [pvec + (core/fn [bvec b val] + (core/let [gvec (gensym "vec__") + gseq (gensym "seq__") + gfirst (gensym "first__") + has-rest (some #{'&} b)] + (core/loop [ret (core/let [ret (conj bvec gvec val)] + (if has-rest + (conj ret gseq (core/list `seq gvec)) + ret)) + n 0 + bs b + seen-rest? false] + (if (seq bs) + (core/let [firstb (first bs)] + (core/cond + (= firstb '&) (recur (pb ret (second bs) gseq) + n + (nnext bs) + true) + (= firstb :as) (pb ret (second bs) gvec) + :else (if seen-rest? + (throw #?(:clj (new Exception "Unsupported binding form, only :as can follow & parameter") + :cljs (new js/Error "Unsupported binding form, only :as can follow & parameter"))) + (recur (pb (if has-rest + (conj ret + gfirst `(first ~gseq) + gseq `(next ~gseq)) + ret) + firstb + (if has-rest + gfirst + (core/list `nth gvec n nil))) + (core/inc n) + (next bs) + seen-rest?)))) + ret)))) + pmap + (core/fn [bvec b v] + (core/let [gmap (gensym "map__") + defaults (:or b)] + (core/loop [ret (core/-> bvec (conj gmap) (conj v) + (conj gmap) (conj `(--destructure-map ~gmap)) + ((core/fn [ret] + (if (:as b) + (conj ret (:as b) gmap) + ret)))) + bes (core/let [transforms + (reduce + (core/fn [transforms mk] + (if (core/keyword? mk) + (core/let [mkns (namespace mk) + mkn (name mk)] + (core/cond (= mkn "keys") (assoc transforms mk #(keyword (core/or mkns (namespace %)) (name %))) + (= mkn "syms") (assoc transforms mk #(core/list `quote (symbol (core/or mkns (namespace %)) (name %)))) + (= mkn "strs") (assoc transforms mk core/str) + :else transforms)) + transforms)) + {} + (keys b))] + (reduce + (core/fn [bes entry] + (reduce #(assoc %1 %2 ((val entry) %2)) + (dissoc bes (key entry)) + ((key entry) bes))) + (dissoc b :as :or) + transforms))] + (if (seq bes) + (core/let [bb (key (first bes)) + bk (val (first bes)) + local (if #?(:clj (core/instance? clojure.lang.Named bb) + :cljs (cljs.core/implements? INamed bb)) + (with-meta (symbol nil (name bb)) (meta bb)) + bb) + bv (if (contains? defaults local) + (core/list 'cljs.core/get gmap bk (defaults local)) + (core/list 'cljs.core/get gmap bk))] + (recur + (if (core/or (core/keyword? bb) (core/symbol? bb)) ;(ident? bb) + (core/-> ret (conj local bv)) + (pb ret bb bv)) + (next bes))) + ret))))] + (core/cond + (core/symbol? b) (core/-> bvec (conj (if (namespace b) (symbol (name b)) b)) (conj v)) + (core/keyword? b) (core/-> bvec (conj (symbol (name b))) (conj v)) + (vector? b) (pvec bvec b v) + (map? b) (pmap bvec b v) + :else (throw + #?(:clj (new Exception (core/str "Unsupported binding form: " b)) + :cljs (new js/Error (core/str "Unsupported binding form: " b))))))) + process-entry (core/fn [bvec b] (pb bvec (first b) (second b)))] + (if (every? core/symbol? (map first bents)) + bindings + (core/if-let [kwbs (seq (filter #(core/keyword? (first %)) bents))] + (throw + #?(:clj (new Exception (core/str "Unsupported binding key: " (ffirst kwbs))) + :cljs (new js/Error (core/str "Unsupported binding key: " (ffirst kwbs))))) + (reduce process-entry [] bents))))) + +(core/defmacro ^:private return-first + [& body] + `(let [ret# ~(first body)] + ~@(rest body) + ret#)) + +(core/defmacro goog-define + "Defines a var using `goog.define`. Passed default value must be + string, number or boolean. + + Default value can be overridden at compile time using the + compiler option `:closure-defines`. + + Example: + (ns your-app.core) + (goog-define DEBUG! false) + ;; can be overridden with + :closure-defines {\"your_app.core.DEBUG_BANG_\" true} + or + :closure-defines {your-app.core/DEBUG! true}" + [sym default] + (assert-args goog-define + (core/or (core/string? default) + (core/number? default) + (core/true? default) + (core/false? default)) "a string, number or boolean as default value") + (core/let [defname (comp/munge (core/str *ns* "/" sym)) + type (core/cond + (core/string? default) "string" + (core/number? default) "number" + (core/or (core/true? default) (core/false? default)) "boolean")] + `(~(if (:def-emits-var &env) `return-first `do) + (declare ~(core/vary-meta sym + (core/fn [m] + (core/cond-> m + (core/not (core/contains? m :tag)) + (core/assoc :tag (core/symbol type)) + )))) + (def ~(vary-meta sym assoc :goog-define type) (goog/define ~defname ~default))))) + +(core/defmacro let + "binding => binding-form init-expr + binding-form => name, or destructuring-form + destructuring-form => map-destructure-form, or seq-destructure-form + + Evaluates the exprs in a lexical context in which the symbols in + the binding-forms are bound to their respective init-exprs or parts + therein. + + See https://clojure.org/reference/special_forms#binding-forms for + more information about destructuring." + [bindings & body] + (assert-args let + (vector? bindings) "a vector for its binding" + (even? (count bindings)) "an even number of forms in binding vector") + `(let* ~(destructure bindings) ~@body)) + +(core/defmacro loop + "Evaluates the exprs in a lexical context in which the symbols in + the binding-forms are bound to their respective init-exprs or parts + therein. Acts as a recur target." + [bindings & body] + (assert-args loop + (vector? bindings) "a vector for its binding" + (even? (count bindings)) "an even number of forms in binding vector") + (core/let [db (destructure bindings)] + (if (= db bindings) + `(loop* ~bindings ~@body) + (core/let [vs (take-nth 2 (drop 1 bindings)) + bs (take-nth 2 bindings) + gs (map (core/fn [b] (if (core/symbol? b) b (gensym))) bs) + bfs (reduce (core/fn [ret [b v g]] + (if (core/symbol? b) + (conj ret g v) + (conj ret g v b g))) + [] (map core/vector bs vs gs))] + `(let ~bfs + (loop* ~(vec (interleave gs gs)) + (let ~(vec (interleave bs gs)) + ~@body))))))) + +(def fast-path-protocols + "protocol fqn -> [partition number, bit]" + (zipmap (map #(symbol "cljs.core" (core/str %)) + '[IFn ICounted IEmptyableCollection ICollection IIndexed ASeq ISeq INext + ILookup IAssociative IMap IMapEntry ISet IStack IVector IDeref + IDerefWithTimeout IMeta IWithMeta IReduce IKVReduce IEquiv IHash + ISeqable ISequential IList IRecord IReversible ISorted IPrintWithWriter IWriter + IPrintWithWriter IPending IWatchable IEditableCollection ITransientCollection + ITransientAssociative ITransientMap ITransientVector ITransientSet + IMultiFn IChunkedSeq IChunkedNext IComparable INamed ICloneable IAtom + IReset ISwap IIterable IDrop]) + (iterate (core/fn [[p b]] + (if (core/== 2147483648 b) + [(core/inc p) 1] + [p #?(:clj (core/bit-shift-left b 1) + :cljs (core/* 2 b))])) + [0 1]))) + +(def fast-path-protocol-partitions-count + "total number of partitions" + (core/let [c (count fast-path-protocols) + m (core/mod c 32)] + (if (core/zero? m) + (core/quot c 32) + (core/inc (core/quot c 32))))) + +(core/defn- compatible? [inferred-tag allowed-tags] + (if (set? inferred-tag) + (clojure.set/subset? inferred-tag allowed-tags) + (contains? allowed-tags inferred-tag))) + +(core/defn- typed-expr? [env form allowed-tags] + (compatible? (cljs.analyzer/infer-tag env + (cljs.analyzer/no-warn (cljs.analyzer/analyze env form))) + allowed-tags)) + +(core/defn- string-expr [e] + (vary-meta e assoc :tag 'string)) + +(core/defmacro str_ + ([] "") + ([x] + (if (typed-expr? &env x '#{string}) + x + (string-expr (core/list 'js* "cljs.core.str_(~{})" x)))) + ([x & ys] + (core/let [interpolate (core/fn [x] + (if (typed-expr? &env x '#{string clj-nil}) + "~{}" + "cljs.core.str_(~{})")) + strs (core/->> (core/list* x ys) + (map interpolate) + (interpose ",") + (apply core/str))] + (string-expr (list* 'js* (core/str "[" strs "].join('')") x ys))))) + +(core/defn- compile-time-constant? [x] + (core/or + (core/string? x) + (core/keyword? x) + (core/boolean? x) + (core/number? x))) + +;; TODO: should probably be a compiler pass to avoid the code duplication +(core/defmacro str + [& xs] + (core/let [interpolate (core/fn [x] + (core/cond + (compile-time-constant? x) + ["+~{}" x] + :else + ;; Note: can't assume non-nil despite tag here, so we go through str 1-arity + ["+cljs.core.str.cljs$core$IFn$_invoke$arity$1(~{})" x])) + strs+args (keep interpolate xs) + strs (string/join (map first strs+args)) + args (map second strs+args)] + (string-expr (list* 'js* (core/str "(\"\"" strs ")") args)))) + +(core/defn- bool-expr [e] + (vary-meta e assoc :tag 'boolean)) + +(core/defn- simple-test-expr? [env ast] + (core/and + (#{:var :js-var :local :invoke :const :host-field :host-call :js :quote} (:op ast)) + ('#{boolean seq} (cljs.analyzer/infer-tag env ast)))) + +(core/defmacro and + "Evaluates exprs one at a time, from left to right. If a form + returns logical false (nil or false), and returns that value and + doesn't evaluate any of the other expressions, otherwise it returns + the value of the last expr. (and) returns true." + ([] true) + ([x] x) + ([x & next] + `(let [and# ~x] + (if and# (and ~@next) and#)))) + +(core/defmacro or + "Evaluates exprs one at a time, from left to right. If a form + returns a logical true value, or returns that value and doesn't + evaluate any of the other expressions, otherwise it returns the + value of the last expression. (or) returns nil." + ([] nil) + ([x] x) + ([x & next] + `(let [or# ~x] + (if or# or# (or ~@next))))) + +(core/defmacro nil? [x] + `(coercive-= ~x nil)) + +(core/defmacro some? [x] + `(not (nil? ~x))) + +(core/defmacro coercive-not [x] + (bool-expr (core/list 'js* "(!~{})" x))) + +(core/defmacro coercive-not= [x y] + (bool-expr (core/list 'js* "(~{} != ~{})" x y))) + +(core/defmacro coercive-= [x y] + (bool-expr (core/list 'js* "(~{} == ~{})" x y))) + +(core/defmacro coercive-boolean [x] + (with-meta (core/list 'js* "~{}" x) + {:tag 'boolean})) + +;; internal - do not use. +(core/defmacro truth_ [x] + (core/assert (core/symbol? x) "x is substituted twice") + (core/list 'js* "(~{} != null && ~{} !== false)" x x)) + +(core/defmacro js-arguments [] + (core/list 'js* "arguments")) + +(core/defmacro js-delete [obj key] + (core/list 'js* "delete ~{}[~{}]" obj key)) + +(core/defmacro js-in [key obj] + (core/list 'js* "~{} in ~{}" key obj)) + +(core/defmacro js-debugger + "Emit JavaScript \"debugger;\" statement" + [] + (core/list 'do + (core/list 'js* "debugger") + nil)) + +(core/defmacro js-comment + "Emit a top-level JavaScript multi-line comment. New lines will create a + new comment line. Comment block will be preceded and followed by a newline" + [comment] + (core/let [[x & ys] (string/split comment #"\n")] + (core/list 'js* + (core/str + "\n/**\n" + (core/str " * " x "\n") + (core/->> ys + (map #(core/str " * " (string/replace % #"^ " "") "\n")) + (reduce core/str "")) + " */\n")))) + +(core/defmacro await [expr] + (core/assert (:async &env) "await can only be used in async contexts") + (core/list 'js* "(await ~{})" expr)) + +(core/defmacro unsafe-cast + "EXPERIMENTAL: Subject to change. Unsafely cast a value to a different type." + [t x] + (core/let [cast-expr (core/str "~{} = /** @type {" t "} */ (~{})")] + (core/list 'js* cast-expr x x))) + +(core/defmacro js-inline-comment + "Emit an inline JavaScript comment." + [comment] + (core/list 'js* (core/str "/**" comment "*/"))) + +(core/defmacro true? [x] + (bool-expr (core/list 'js* "~{} === true" x))) + +(core/defmacro false? [x] + (bool-expr (core/list 'js* "~{} === false" x))) + +(core/defmacro string? [x] + (bool-expr (core/list 'js* "typeof ~{} === 'string'" x))) + +(core/defmacro js-fn? [x] + (bool-expr (core/list 'js* "typeof ~{} === 'function'" x))) + +(core/defmacro exists? + "Return true if argument exists, analogous to usage of typeof operator + in JavaScript." + [x] + (if (core/symbol? x) + (core/let [x (core/cond-> (:name (cljs.analyzer/resolve-var &env x)) + (= "js" (namespace x)) name) + segs (string/split (core/str (string/replace-first (core/str x) "/" ".")) #"\.") + n (count segs) + syms (map + #(vary-meta (symbol "js" (string/join "." %)) + assoc :cljs.analyzer/no-resolve true) + (reverse (take n (iterate butlast segs)))) + js (string/join " && " (repeat n "(typeof ~{} !== 'undefined')"))] + (bool-expr (concat (core/list 'js* js) syms))) + `(some? ~x))) + +(core/defmacro undefined? + "Return true if argument is identical to the JavaScript undefined value." + [x] + (bool-expr (core/list 'js* "(void 0 === ~{})" x))) + +(core/defmacro identical? [a b] + (bool-expr (core/list 'js* "(~{} === ~{})" a b))) + +(core/defmacro instance? [c x] + ;; Google Closure warns about some references to RegExp, so + ;; (instance? RegExp ...) needs to be inlined, but the expansion + ;; should preserve the order of argument evaluation. + (bool-expr (if (clojure.core/symbol? c) + (core/list 'js* "(~{} instanceof ~{})" x c) + `(let [c# ~c x# ~x] + (~'js* "(~{} instanceof ~{})" x# c#))))) + +(core/defmacro number? [x] + (bool-expr (core/list 'js* "typeof ~{} === 'number'" x))) + +(core/defmacro symbol? [x] + (bool-expr `(instance? Symbol ~x))) + +(core/defmacro keyword? [x] + (bool-expr `(instance? Keyword ~x))) + +(core/defmacro aget + ([array idx] + (core/case (ana/checked-arrays) + :warn `(checked-aget ~array ~idx) + :error `(checked-aget' ~array ~idx) + (core/list 'js* "(~{}[~{}])" array idx))) + ([array idx & idxs] + (core/case (ana/checked-arrays) + :warn `(checked-aget ~array ~idx ~@idxs) + :error `(checked-aget' ~array ~idx ~@idxs) + (core/let [astr (apply core/str (repeat (count idxs) "[~{}]"))] + `(~'js* ~(core/str "(~{}[~{}]" astr ")") ~array ~idx ~@idxs))))) + +(core/defmacro aset + ([array idx val] + (core/case (ana/checked-arrays) + :warn `(checked-aset ~array ~idx ~val) + :error `(checked-aset' ~array ~idx ~val) + (core/list 'js* "(~{}[~{}] = ~{})" array idx val))) + ([array idx idx2 & idxv] + (core/case (ana/checked-arrays) + :warn `(checked-aset ~array ~idx ~idx2 ~@idxv) + :error `(checked-aset' ~array ~idx ~idx2 ~@idxv) + (core/let [n (core/dec (count idxv)) + astr (apply core/str (repeat n "[~{}]"))] + `(~'js* ~(core/str "(~{}[~{}][~{}]" astr " = ~{})") ~array ~idx ~idx2 ~@idxv))))) + +(core/defmacro unchecked-get + "INTERNAL. Compiles to JavaScript property access using bracket notation. Does + not distinguish between object and array types and not subject to compiler + static analysis." + [obj key] + (core/list 'js* "(~{}[~{}])" obj key)) + +(core/defmacro unchecked-set + "INTERNAL. Compiles to JavaScript property access using bracket notation. Does + not distinguish between object and array types and not subject to compiler + static analysis." + [obj key val] + (core/list 'js* "(~{}[~{}] = ~{})" obj key val)) + +(core/defmacro ^::ana/numeric + + ([] 0) + ([x] (core/list 'js* "(~{})" x)) + ([x y] (core/list 'js* "(~{} + ~{})" x y)) + ([x y & more] `(+ (+ ~x ~y) ~@more))) + +(core/defmacro byte [x] x) +(core/defmacro short [x] x) +(core/defmacro float [x] x) +(core/defmacro double [x] x) + +(core/defmacro unchecked-byte [x] x) +(core/defmacro unchecked-char [x] x) +(core/defmacro unchecked-short [x] x) +(core/defmacro unchecked-float [x] x) +(core/defmacro unchecked-double [x] x) + +(core/defmacro ^::ana/numeric unchecked-add + ([& xs] `(+ ~@xs))) + +(core/defmacro ^::ana/numeric unchecked-add-int + ([& xs] `(+ ~@xs))) + +(core/defmacro ^::ana/numeric unchecked-dec + ([x] `(dec ~x))) + +(core/defmacro ^::ana/numeric unchecked-dec-int + ([x] `(dec ~x))) + +(core/defmacro ^::ana/numeric unchecked-divide-int + ([& xs] `(/ ~@xs))) + +(core/defmacro ^::ana/numeric unchecked-inc + ([x] `(inc ~x))) + +(core/defmacro ^::ana/numeric unchecked-inc-int + ([x] `(inc ~x))) + +(core/defmacro ^::ana/numeric unchecked-multiply + ([& xs] `(* ~@xs))) + +(core/defmacro ^::ana/numeric unchecked-multiply-int + ([& xs] `(* ~@xs))) + +(core/defmacro ^::ana/numeric unchecked-negate + ([x] `(- ~x))) + +(core/defmacro ^::ana/numeric unchecked-negate-int + ([x] `(- ~x))) + +(core/defmacro ^::ana/numeric unchecked-remainder-int + ([x n] `(core/mod ~x ~n))) + +(core/defmacro ^::ana/numeric unchecked-subtract + ([& xs] `(- ~@xs))) + +(core/defmacro ^::ana/numeric unchecked-subtract-int + ([& xs] `(- ~@xs))) + +(core/defmacro ^::ana/numeric - + ([x] (core/list 'js* "(- ~{})" x)) + ([x y] (core/list 'js* "(~{} - ~{})" x y)) + ([x y & more] `(- (- ~x ~y) ~@more))) + +(core/defmacro ^::ana/numeric * + ([] 1) + ([x] (core/list 'js* "(~{})" x)) + ([x y] (core/list 'js* "(~{} * ~{})" x y)) + ([x y & more] `(* (* ~x ~y) ~@more))) + +(core/defmacro ^::ana/numeric / + ([x] `(/ 1 ~x)) + ([x y] (core/list 'js* "(~{} / ~{})" x y)) + ([x y & more] `(/ (/ ~x ~y) ~@more))) + +(core/defmacro ^::ana/numeric divide + ([x] `(/ 1 ~x)) + ([x y] (core/list 'js* "(~{} / ~{})" x y)) + ([x y & more] `(/ (/ ~x ~y) ~@more))) + +(core/defmacro ^::ana/numeric < + ([x] true) + ([x y] (bool-expr (core/list 'js* "(~{} < ~{})" x y))) + ([x y & more] `(and (< ~x ~y) (< ~y ~@more)))) + +(core/defmacro ^::ana/numeric <= + ([x] true) + ([x y] (bool-expr (core/list 'js* "(~{} <= ~{})" x y))) + ([x y & more] `(and (<= ~x ~y) (<= ~y ~@more)))) + +(core/defmacro ^::ana/numeric > + ([x] true) + ([x y] (bool-expr (core/list 'js* "(~{} > ~{})" x y))) + ([x y & more] `(and (> ~x ~y) (> ~y ~@more)))) + +(core/defmacro ^::ana/numeric >= + ([x] true) + ([x y] (bool-expr (core/list 'js* "(~{} >= ~{})" x y))) + ([x y & more] `(and (>= ~x ~y) (>= ~y ~@more)))) + +(core/defmacro ^::ana/numeric == + ([x] true) + ([x y] (bool-expr (core/list 'js* "(~{} === ~{})" x y))) + ([x y & more] `(and (== ~x ~y) (== ~y ~@more)))) + +(core/defmacro ^::ana/numeric dec [x] + `(- ~x 1)) + +(core/defmacro ^::ana/numeric inc [x] + `(+ ~x 1)) + +(core/defmacro ^::ana/numeric zero? [x] + `(== ~x 0)) + +(core/defmacro ^::ana/numeric pos? [x] + `(> ~x 0)) + +(core/defmacro ^::ana/numeric neg? [x] + `(< ~x 0)) + +(core/defmacro ^::ana/numeric unchecked-max + ([x] x) + ([x y] + `(let [x# ~x, y# ~y] + (if (> x# y#) x# y#))) + ([x y & more] + `(max (max ~x ~y) ~@more))) + +(core/defmacro ^::ana/numeric unchecked-min + ([x] x) + ([x y] + `(let [x# ~x, y# ~y] + (if (< x# y#) x# y#))) + ([x y & more] + `(min (min ~x ~y) ~@more))) + +(core/defmacro ^::ana/numeric js-mod [num div] + (core/list 'js* "(~{} % ~{})" num div)) + +(core/defmacro ^::ana/numeric bit-not [x] + (core/list 'js* "(~ ~{})" x)) + +(core/defmacro ^::ana/numeric bit-and + ([x y] (core/list 'js* "(~{} & ~{})" x y)) + ([x y & more] `(bit-and (bit-and ~x ~y) ~@more))) + +;; internal do not use +(core/defmacro ^::ana/numeric unsafe-bit-and + ([x y] (bool-expr (core/list 'js* "(~{} & ~{})" x y))) + ([x y & more] `(unsafe-bit-and (unsafe-bit-and ~x ~y) ~@more))) + +(core/defmacro ^::ana/numeric bit-or + ([x y] (core/list 'js* "(~{} | ~{})" x y)) + ([x y & more] `(bit-or (bit-or ~x ~y) ~@more))) + +(core/defmacro int + [x] + (core/list 'js* "(~{} | 0)" x)) + +(core/defmacro ^::ana/numeric bit-xor + ([x y] (core/list 'js* "(~{} ^ ~{})" x y)) + ([x y & more] `(bit-xor (bit-xor ~x ~y) ~@more))) + +(core/defmacro ^::ana/numeric bit-and-not + ([x y] (core/list 'js* "(~{} & ~~{})" x y)) + ([x y & more] `(bit-and-not (bit-and-not ~x ~y) ~@more))) + +(core/defmacro ^::ana/numeric bit-clear [x n] + (core/list 'js* "(~{} & ~(1 << ~{}))" x n)) + +(core/defmacro ^::ana/numeric bit-flip [x n] + (core/list 'js* "(~{} ^ (1 << ~{}))" x n)) + +(core/defmacro bit-test [x n] + (bool-expr (core/list 'js* "((~{} & (1 << ~{})) != 0)" x n))) + +(core/defmacro ^::ana/numeric bit-shift-left [x n] + (core/list 'js* "(~{} << ~{})" x n)) + +(core/defmacro ^::ana/numeric bit-shift-right [x n] + (core/list 'js* "(~{} >> ~{})" x n)) + +(core/defmacro ^::ana/numeric bit-shift-right-zero-fill [x n] + (core/list 'js* "(~{} >>> ~{})" x n)) + +(core/defmacro ^::ana/numeric unsigned-bit-shift-right [x n] + (core/list 'js* "(~{} >>> ~{})" x n)) + +(core/defmacro ^::ana/numeric bit-set [x n] + (core/list 'js* "(~{} | (1 << ~{}))" x n)) + +;; internal +(core/defmacro mask [hash shift] + (core/list 'js* "((~{} >>> ~{}) & 0x01f)" hash shift)) + +;; internal +(core/defmacro bitpos [hash shift] + (core/list 'js* "(1 << ~{})" `(mask ~hash ~shift))) + +;; internal +(core/defmacro caching-hash [coll hash-fn hash-key] + (core/assert (clojure.core/symbol? hash-key) "hash-key is substituted twice") + `(let [h# ~hash-key] + (if-not (nil? h#) + h# + (let [h# (~hash-fn ~coll)] + (set! ~hash-key h#) + h#)))) + +;;; internal -- reducers-related macros + +(core/defn- do-curried + [name doc meta args body] + (core/let [cargs (vec (butlast args))] + `(defn ~name ~doc ~meta + (~cargs (fn [x#] (~name ~@cargs x#))) + (~args ~@body)))) + +(core/defmacro ^:private defcurried + "Builds another arity of the fn that returns a fn awaiting the last + param" + [name doc meta args & body] + (do-curried name doc meta args body)) + +(core/defn- do-rfn [f1 k fkv] + `(fn + ([] (~f1)) + ~(clojure.walk/postwalk + #(if (sequential? %) + ((if (vector? %) vec identity) + (core/remove #{k} %)) + %) + fkv) + ~fkv)) + +(core/defmacro ^:private rfn + "Builds 3-arity reducing fn given names of wrapped fn and key, and k/v impl." + [[f1 k] fkv] + (do-rfn f1 k fkv)) + +;;; end of reducers macros + +(core/defn- protocol-prefix [psym] + (core/str (core/-> (core/str psym) + (.replace #?(:clj \. :cljs (js/RegExp. "\\." "g")) \$) + (.replace \/ \$)) + "$")) + +(def ^:private base-type + {nil "null" + 'object "object" + 'string "string" + 'symbol "symbol" + 'number "number" + 'bigint "bigint" + 'array "array" + 'function "function" + 'boolean "boolean" + 'default "_"}) + +;; only used for generating warnings when extending fundamental JS types +(def ^:private js-base-type + {'js/Boolean "boolean" + 'js/String "string" + 'js/Symbol "symbol" + 'js/Array "array" + 'js/Object "object" + 'js/Number "number" + 'js/BigInt "bigint" + 'js/Function "function"}) + +(core/defmacro reify + "reify creates an object implementing a protocol. + reify is a macro with the following structure: + + (reify options* specs*) + + Currently there are no options. + + Each spec consists of the protocol name followed by zero + or more method bodies: + + protocol + (methodName [args+] body)* + + Methods should be supplied for all methods of the desired + protocol(s). You can also define overrides for Object methods. Note that + the first parameter must be supplied to correspond to the target object + ('this' in JavaScript parlance). Note also that recur calls + to the method head should *not* pass the target object, it will be supplied + automatically and can not be substituted. + + recur works to method heads The method bodies of reify are lexical + closures, and can refer to the surrounding local scope: + + (str (let [f \"foo\"] + (reify Object + (toString [this] f)))) + == \"foo\" + + (seq (let [f \"foo\"] + (reify ISeqable + (-seq [this] (seq f))))) + == (\"f\" \"o\" \"o\")) + + reify always implements IMeta and IWithMeta and transfers meta + data of the form to the created object. + + (meta ^{:k :v} (reify Object (toString [this] \"foo\"))) + == {:k :v}" + [& impls] + (core/let [t (with-meta + (gensym + (core/str "t_reify_" + (string/replace (core/str (munge ana/*cljs-ns*)) "." "$"))) + {:anonymous true}) + meta-sym (gensym "meta") + this-sym (gensym "_") + locals (keys (:locals &env)) + ns (core/-> &env :ns :name) + munge comp/munge] + `(do + (when-not (exists? ~(symbol (core/str ns) (core/str t))) + (deftype ~t [~@locals ~meta-sym] + IWithMeta + (~'-with-meta [~this-sym ~meta-sym] + (new ~t ~@locals ~meta-sym)) + IMeta + (~'-meta [~this-sym] ~meta-sym) + ~@impls)) + (new ~t ~@locals + ;; if the form meta is empty, emit nil + ~(core/let [form-meta (ana/elide-reader-meta (meta &form))] + (core/when-not (empty? form-meta) + form-meta)))))) + +(core/defmacro specify! + "Identical to reify but mutates its first argument." + [expr & impls] + (core/let [x (with-meta (gensym "x") {:extend :instance})] + `(let [~x ~expr] + (extend-type ~x ~@impls) + ~x))) + +(core/defmacro specify + "Identical to specify! but does not mutate its first argument. The first + argument must be an ICloneable instance." + [expr & impls] + `(cljs.core/specify! (cljs.core/clone ~expr) + ~@impls)) + +(core/defmacro ^:private js-this [] + (core/list 'js* "this")) + +(core/defmacro this-as + "Defines a scope where JavaScript's implicit \"this\" is bound to the name provided." + [name & body] + `(let [~name (js-this)] + ~@body)) + +(core/defn- to-property [sym] + (symbol (core/str "-" sym))) + +(core/defn- update-protocol-var [p type env] + (core/when-not (= 'Object p) + (core/if-let [var (cljs.analyzer/resolve-existing-var (dissoc env :locals) p)] + (do + (core/when-not (:protocol-symbol var) + (cljs.analyzer/warning :invalid-protocol-symbol env {:protocol p})) + (core/when (core/and (:protocol-deprecated cljs.analyzer/*cljs-warnings*) + (core/-> var :deprecated) + (not (core/-> p meta :deprecation-nowarn))) + (cljs.analyzer/warning :protocol-deprecated env {:protocol p})) + (core/when (:protocol-symbol var) + (swap! env/*compiler* update-in [:cljs.analyzer/namespaces] + (core/fn [ns] + (update-in ns [(:ns var) :defs (symbol (name p)) :impls] + conj type))))) + (core/when (:undeclared cljs.analyzer/*cljs-warnings*) + (cljs.analyzer/warning :undeclared-protocol-symbol env {:protocol p}))))) + +(core/defn- resolve-var [env sym] + (core/let [ret (:name (cljs.analyzer/resolve-var env sym))] + (core/assert ret (core/str "Can't resolve: " sym)) + ret)) + +(core/defn- ->impl-map [impls] + (core/loop [ret {} s impls] + (if (seq s) + (recur (assoc ret (first s) (take-while seq? (next s))) + (drop-while seq? (next s))) + ret))) + +(core/defn- base-assign-impls [env resolve tsym type [p sigs]] + (update-protocol-var p tsym env) + (core/let [psym (resolve p) + pfn-prefix (subs (core/str psym) 0 + (clojure.core/inc (.indexOf (core/str psym) "/")))] + (cons `(unchecked-set ~psym ~type true) + (map (core/fn [[f & meths :as form]] + `(unchecked-set ~(symbol (core/str pfn-prefix f)) + ~type ~(with-meta `(fn ~@meths) (meta form)))) + sigs)))) + +(core/defmulti ^:private extend-prefix (core/fn [tsym sym] (core/-> tsym meta :extend))) + +(core/defmethod extend-prefix :instance + [tsym sym] `(.. ~tsym ~(to-property sym))) + +(core/defmethod extend-prefix :default + [tsym sym] + (with-meta `(.. ~tsym ~'-prototype ~(to-property sym)) {:extend-type true})) + +(core/defn- adapt-obj-params [type [[this & args :as sig] & body]] + (core/list (vec args) + (list* 'this-as (vary-meta this assoc :tag type) body))) + +(core/defn- adapt-ifn-params [type [[this & args :as sig] & body]] + (core/let [self-sym (with-meta 'self__ {:tag type})] + `(~(vec (cons self-sym args)) + (this-as ~self-sym + (let [~this ~self-sym] + ~@body))))) + +;; for IFn invoke implementations, we need to drop first arg +(core/defn- adapt-ifn-invoke-params [type [[this & args :as sig] & body]] + `(~(vec args) + (this-as ~(vary-meta this assoc :tag type) + ~@body))) + +(core/defn- adapt-proto-params [type [[this & args :as sig] & body]] + (core/let [this' (vary-meta this assoc :tag type)] + `(~(vec (cons this' args)) + (this-as ~this' + ~@body)))) + +(core/defn- add-obj-methods [type type-sym sigs] + (core/->> sigs + ;; Elide all toString methods in :lite-mode + (remove + (core/fn [[f]] + (core/and (ana/elide-to-string?) (core/= 'toString f)))) + (map + (core/fn [[f & meths :as form]] + (core/let [[f meths] (if (vector? (first meths)) + [f [(rest form)]] + [f meths])] + `(set! ~(extend-prefix type-sym f) + ~(with-meta `(fn ~@(map #(adapt-obj-params type %) meths)) (meta form)))))))) + +(core/defn- ifn-invoke-methods [type type-sym [f & meths :as form]] + (map + (core/fn [meth] + (core/let [arity (count (first meth))] + `(set! ~(extend-prefix type-sym (symbol (core/str "cljs$core$IFn$_invoke$arity$" arity))) + ~(with-meta `(fn ~meth) (meta form))))) + (map #(adapt-ifn-invoke-params type %) meths))) + +(core/defn- add-ifn-methods [type type-sym [f & meths :as form]] + (core/let [meths (map #(adapt-ifn-params type %) meths) + this-sym (with-meta 'self__ {:tag type}) + argsym (gensym "args") + max-ifn-arity 20] + (concat + [`(set! ~(extend-prefix type-sym 'call) ~(with-meta `(fn ~@meths) (meta form))) + `(set! ~(extend-prefix type-sym 'apply) + ~(with-meta + `(fn ~[this-sym argsym] + (this-as ~this-sym + (let [args# (cljs.core/aclone ~argsym)] + (.apply (.-call ~this-sym) ~this-sym + (.concat (array ~this-sym) + (if (> (.-length args#) ~max-ifn-arity) + (doto (.slice args# 0 ~max-ifn-arity) + (.push (.slice args# ~max-ifn-arity))) + args#)))))) + (meta form)))] + (ifn-invoke-methods type type-sym form)))) + +(core/defn- add-proto-methods* [pprefix type type-sym [f & meths :as form]] + (core/let [pf (core/str pprefix (munge (name f)))] + (if (vector? (first meths)) + ;; single method case + (core/let [meth meths] + [`(set! ~(extend-prefix type-sym (core/str pf "$arity$" (count (first meth)))) + ~(with-meta `(fn ~@(adapt-proto-params type meth)) (meta form)))]) + (map (core/fn [[sig & body :as meth]] + `(set! ~(extend-prefix type-sym (core/str pf "$arity$" (count sig))) + ~(with-meta `(fn ~(adapt-proto-params type meth)) (meta form)))) + meths)))) + +(core/defn- proto-assign-impls [env resolve type-sym type [p sigs]] + (update-protocol-var p type env) + (core/let [psym (resolve p) + pprefix (protocol-prefix psym) + skip-flag (set (core/-> type-sym meta :skip-protocol-flag))] + (if (= p 'Object) + (add-obj-methods type type-sym sigs) + (concat + (core/when-not (skip-flag psym) + [`(set! ~(extend-prefix type-sym pprefix) cljs.core/PROTOCOL_SENTINEL)]) + (mapcat + (core/fn [sig] + (if (= psym 'cljs.core/IFn) + (add-ifn-methods type type-sym sig) + (add-proto-methods* pprefix type type-sym sig))) + sigs))))) + +(core/defn- validate-impl-sigs [env p method] + (core/when-not (= p 'Object) + (core/let [var (ana/resolve-var (dissoc env :locals) p) + minfo (core/-> var :protocol-info :methods) + method-name (first method) + ->name (comp symbol name) + [fname sigs] (if (core/vector? (second method)) + [(->name method-name) [(second method)]] + [(->name method-name) (map first (rest method))]) + decmeths (core/get minfo fname ::not-found)] + (core/when (= decmeths ::not-found) + (ana/warning :protocol-invalid-method env {:protocol p :fname fname :no-such-method true})) + (core/when (namespace method-name) + (core/let [method-var (ana/resolve-var (dissoc env :locals) method-name + ana/confirm-var-exist-warning)] + (core/when-not (= (:name var) (:protocol method-var)) + (ana/warning :protocol-invalid-method env + {:protocol p :fname method-name :no-such-method true})))) + (core/loop [sigs sigs seen #{}] + (core/when (seq sigs) + (core/let [sig (first sigs) + c (count sig)] + (core/when (contains? seen c) + (ana/warning :protocol-duped-method env {:protocol p :fname fname})) + (core/when (some '#{&} sig) + (ana/warning :protocol-impl-with-variadic-method env {:protocol p :name fname})) + (core/when (core/and (not= decmeths ::not-found) (not (some #{c} (map count decmeths)))) + (ana/warning :protocol-invalid-method env {:protocol p :fname fname :invalid-arity c})) + (recur (next sigs) (conj seen c)))))))) + +(core/defn- validate-impls [env impls] + (core/loop [protos #{} impls impls] + (core/when (seq impls) + (core/let [proto (first impls) + methods (take-while seq? (next impls)) + impls (drop-while seq? (next impls))] + (core/when (contains? protos proto) + (ana/warning :protocol-multiple-impls env {:protocol proto})) + (core/loop [seen #{} methods methods] + (core/when (seq methods) + (core/let [[fname :as method] (first methods)] + (core/when (contains? seen fname) + (ana/warning :extend-type-invalid-method-shape env + {:protocol proto :method fname})) + (validate-impl-sigs env proto method) + (recur (conj seen fname) (next methods))))) + (recur (conj protos proto) impls))))) + +(core/defn- type-hint-first-arg + [type-sym argv] + (assoc argv 0 (vary-meta (argv 0) assoc :tag type-sym))) + +(core/defn- type-hint-single-arity-sig + [type-sym sig] + (list* (first sig) (type-hint-first-arg type-sym (second sig)) (nnext sig))) + +(core/defn- type-hint-multi-arity-sig + [type-sym sig] + (list* (type-hint-first-arg type-sym (first sig)) (next sig))) + +(core/defn- type-hint-multi-arity-sigs + [type-sym sigs] + (list* (first sigs) (map (partial type-hint-multi-arity-sig type-sym) (rest sigs)))) + +(core/defn- type-hint-sigs + [type-sym sig] + (if (vector? (second sig)) + (type-hint-single-arity-sig type-sym sig) + (type-hint-multi-arity-sigs type-sym sig))) + +(core/defn- type-hint-impl-map + [type-sym impl-map] + (reduce-kv (core/fn [m proto sigs] + (assoc m proto (map (partial type-hint-sigs type-sym) sigs))) + {} impl-map)) + +(core/defmacro extend-type + "Extend a type to a series of protocols. Useful when you are + supplying the definitions explicitly inline. Propagates the + type as a type hint on the first argument of all fns. + + type-sym may be + + * default, meaning the definitions will apply for any value, + unless an extend-type exists for one of the more specific + cases below. + * nil, meaning the definitions will apply for the nil value. + * any of object, boolean, number, string, array, or function, + indicating the definitions will apply for values of the + associated base JavaScript types. Note that, for example, + string should be used instead of js/String. + * a JavaScript type not covered by the previous list, such + as js/RegExp. + * a type defined by deftype or defrecord. + + (extend-type MyType + ICounted + (-count [c] ...) + Foo + (bar [x y] ...) + (baz ([x] ...) ([x y] ...) ...)" + [type-sym & impls] + (core/let [env &env + _ (validate-impls env impls) + resolve (partial resolve-var env) + impl-map (->impl-map impls) + impl-map (if ('#{boolean number} type-sym) + (type-hint-impl-map type-sym impl-map) + impl-map) + [type assign-impls] (core/if-let [type (base-type type-sym)] + [type base-assign-impls] + [(resolve type-sym) proto-assign-impls])] + (core/when (core/and (:extending-base-js-type cljs.analyzer/*cljs-warnings*) + (js-base-type type-sym)) + (cljs.analyzer/warning :extending-base-js-type env + {:current-symbol type-sym :suggested-symbol (js-base-type type-sym)})) + `(do ~@(mapcat #(assign-impls env resolve type-sym type %) impl-map)))) + +(core/defn- prepare-protocol-masks [env impls] + (core/let [resolve (partial resolve-var env) + impl-map (->impl-map impls) + fpp-pbs (seq + (keep fast-path-protocols + (map resolve + (keys impl-map))))] + (if fpp-pbs + (core/let [fpps (into #{} + (filter (partial contains? fast-path-protocols) + (map resolve (keys impl-map)))) + parts (core/as-> (group-by first fpp-pbs) parts + (into {} + (map (juxt key (comp (partial map peek) val)) + parts)) + (into {} + (map (juxt key (comp (partial reduce core/bit-or) val)) + parts)))] + [fpps (reduce (core/fn [ps p] (update-in ps [p] (core/fnil identity 0))) + parts + (range fast-path-protocol-partitions-count))])))) + +(core/defn- annotate-specs [annots v [f sigs]] + (conj v + (vary-meta (cons f (map #(cons (second %) (nnext %)) sigs)) + merge annots))) + +(core/defn dt->et + ([type specs fields] + (dt->et type specs fields false)) + ([type specs fields inline] + (core/let [annots {:cljs.analyzer/type type + :cljs.analyzer/protocol-impl true + :cljs.analyzer/protocol-inline inline}] + (core/loop [ret [] specs specs] + (if (seq specs) + (core/let [p (first specs) + ret (core/-> (conj ret p) + (into (reduce (partial annotate-specs annots) [] + (group-by first (take-while seq? (next specs)))))) + specs (drop-while seq? (next specs))] + (recur ret specs)) + ret))))) + +(core/defn- collect-protocols [impls env] + (core/->> impls + (filter core/symbol?) + (map #(:name (cljs.analyzer/resolve-var (dissoc env :locals) %))) + (into #{}))) + +(core/defn- build-positional-factory + [rsym rname fields] + (core/let [fn-name (with-meta (symbol (core/str '-> rsym)) + (assoc (meta rsym) :factory :positional)) + docstring (core/str "Positional factory function for " rname ".") + field-values (if (core/-> rsym meta :internal-ctor) (conj fields nil nil nil) fields)] + `(defn ~fn-name + ~docstring + [~@fields] + (new ~rname ~@field-values)))) + +(core/defn- validate-fields + [case name fields] + (core/when-not (vector? fields) + (throw + #?(:clj (AssertionError. (core/str case " " name ", no fields vector given.")) + :cljs (js/Error. (core/str case " " name ", no fields vector given.")))))) + +(core/defmacro deftype + "(deftype name [fields*] options* specs*) + + Currently there are no options. + + Each spec consists of a protocol or interface name followed by zero + or more method bodies: + + protocol-or-Object + (methodName [args*] body)* + + The type will have the (by default, immutable) fields named by + fields, which can have type hints. Protocols and methods + are optional. The only methods that can be supplied are those + declared in the protocols/interfaces. Note that method bodies are + not closures, the local environment includes only the named fields, + and those fields can be accessed directly. Fields can be qualified + with the metadata :mutable true at which point (set! afield aval) will be + supported in method bodies. Note well that mutable fields are extremely + difficult to use correctly, and are present only to facilitate the building + of higherlevel constructs, such as ClojureScript's reference types, in + ClojureScript itself. They are for experts only - if the semantics and + implications of :mutable are not immediately apparent to you, you should not + be using them. + + Method definitions take the form: + + (methodname [args*] body) + + The argument and return types can be hinted on the arg and + methodname symbols. If not supplied, they will be inferred, so type + hints should be reserved for disambiguation. + + Methods should be supplied for all methods of the desired + protocol(s). You can also define overrides for methods of Object. Note that + a parameter must be supplied to correspond to the target object + ('this' in JavaScript parlance). Note also that recur calls to the method + head should *not* pass the target object, it will be supplied + automatically and can not be substituted. + + In the method bodies, the (unqualified) name can be used to name the + class (for calls to new, instance? etc). + + One constructor will be defined, taking the designated fields. Note + that the field names __meta and __extmap are currently reserved and + should not be used when defining your own types. + + Given (deftype TypeName ...), a factory function called ->TypeName + will be defined, taking positional parameters for the fields" + [t fields & impls] + (validate-fields "deftype" t fields) + (core/let [env &env + v (cljs.analyzer/resolve-var (dissoc env :locals) t) + r (:name v) + [fpps pmasks] (prepare-protocol-masks env impls) + protocols (collect-protocols impls env) + t (vary-meta t assoc + :protocols protocols + :skip-protocol-flag fpps)] + `(do + (deftype* ~t ~fields ~pmasks + ~(if (seq impls) + `(extend-type ~t ~@(dt->et t impls fields)))) + ;; don't emit static basis method w/ reify + ;; nor for core types + ~@(core/when-not (core/or (string/starts-with? (name t) "t_reify") + (= 'cljs.core (:ns v))) + [`(set! (.-getBasis ~t) (fn [] '[~@fields]))]) + (set! (.-cljs$lang$type ~t) true) + (set! (.-cljs$lang$ctorStr ~t) ~(core/str r)) + (set! (.-cljs$lang$ctorPrWriter ~t) (fn [this# writer# opt#] (-write writer# ~(core/str r)))) + + ~(build-positional-factory t r fields) + ~t))) + +(core/defn- emit-defrecord + "Do not use this directly - use defrecord" + [env tagname rname fields impls] + (core/let [hinted-fields fields + fields (vec (map #(with-meta % nil) fields)) + base-fields fields + pr-open (core/str "#" #?(:clj (.getNamespace rname) + :cljs (namespace rname)) + "." #?(:clj (.getName rname) + :cljs (name rname)) + "{") + fields (conj fields '__meta '__extmap (with-meta '__hash {:mutable true}))] + (core/let [gs (gensym) + ksym (gensym "k") + impls (concat + impls + ['IRecord + 'ICloneable + `(~'-clone [this#] (new ~tagname ~@fields)) + 'IHash + `(~'-hash [this#] + (caching-hash this# + (fn [coll#] + (bit-xor + ~(hash (core/-> rname comp/munge core/str)) + (hash-unordered-coll coll#))) + ~'__hash)) + 'IEquiv + (core/let [this (gensym 'this) other (gensym 'other)] + `(~'-equiv [~this ~other] + (and (some? ~other) + (identical? (.-constructor ~this) + (.-constructor ~other)) + ~@(map (core/fn [field] + `(= (.. ~this ~(to-property field)) + (.. ~(with-meta other {:tag tagname}) ~(to-property field)))) + base-fields) + (= (.-__extmap ~this) + (.-__extmap ~(with-meta other {:tag tagname})))))) + 'IMeta + `(~'-meta [this#] ~'__meta) + 'IWithMeta + `(~'-with-meta [this# ~gs] (new ~tagname ~@(replace {'__meta gs} fields))) + 'ILookup + `(~'-lookup [this# k#] (-lookup this# k# nil)) + `(~'-lookup [this# ~ksym else#] + (case ~ksym + ~@(mapcat (core/fn [f] [(keyword f) f]) base-fields) + (cljs.core/get ~'__extmap ~ksym else#))) + 'ICounted + `(~'-count [this#] (+ ~(count base-fields) (count ~'__extmap))) + 'ICollection + `(~'-conj [this# entry#] + (if (vector? entry#) + (-assoc this# (-nth entry# 0) (-nth entry# 1)) + (reduce -conj + this# + entry#))) + 'IAssociative + `(~'-contains-key? [this# ~ksym] + ~(if (seq base-fields) + `(case ~ksym + (~@(map keyword base-fields)) true + (cljs.core/contains? ~'__extmap ~ksym)) + `(cljs.core/contains? ~'__extmap ~ksym))) + `(~'-assoc [this# k# ~gs] + (condp keyword-identical? k# + ~@(mapcat (core/fn [fld] + [(keyword fld) (list* `new tagname (replace {fld gs '__hash nil} fields))]) + base-fields) + (new ~tagname ~@(remove #{'__extmap '__hash} fields) (assoc ~'__extmap k# ~gs) nil))) + 'IMap + `(~'-dissoc [this# k#] (if (contains? #{~@(map keyword base-fields)} k#) + (dissoc (-with-meta (into {} this#) ~'__meta) k#) + (new ~tagname ~@(remove #{'__extmap '__hash} fields) + (not-empty (dissoc ~'__extmap k#)) + nil))) + 'ISeqable + `(~'-seq [this#] (seq (concat [~@(map #(core/list 'cljs.core/MapEntry. (keyword %) % nil) base-fields)] + ~'__extmap))) + + 'IIterable + `(~'-iterator [~gs] + (RecordIter. 0 ~gs ~(count base-fields) [~@(map keyword base-fields)] (if ~'__extmap + (-iterator ~'__extmap) + (core/nil-iter)))) + + 'IPrintWithWriter + `(~'-pr-writer [this# writer# opts#] + (let [pr-pair# (fn [keyval#] (pr-sequential-writer writer# (~'js* "cljs.core.pr_writer") "" " " "" opts# keyval#))] + (pr-sequential-writer + writer# pr-pair# ~pr-open ", " "}" opts# + (concat [~@(map #(core/list `vector (keyword %) %) base-fields)] + ~'__extmap)))) + 'IKVReduce + `(~'-kv-reduce [this# f# init#] + (reduce (fn [ret# [k# v#]] (f# ret# k# v#)) init# this#)) + ]) + [fpps pmasks] (prepare-protocol-masks env impls) + protocols (collect-protocols impls env) + tagname (vary-meta tagname assoc + :protocols protocols + :skip-protocol-flag fpps)] + `(do + (~'defrecord* ~tagname ~hinted-fields ~pmasks + (extend-type ~tagname ~@(dt->et tagname impls fields true))))))) + +(core/defn- build-map-factory [rsym rname fields] + (core/let [fn-name (with-meta (symbol (core/str 'map-> rsym)) + (assoc (meta rsym) :factory :map)) + docstring (core/str "Factory function for " rname ", taking a map of keywords to field values.") + ms (gensym) + ks (map keyword fields) + getters (map (core/fn [k] `(~k ~ms)) ks)] + `(defn ~fn-name ~docstring [~ms] + (let [extmap# (cond->> (dissoc ~ms ~@ks) + (record? ~ms) (into {}))] + (new ~rname ~@getters nil (not-empty extmap#) nil))))) + +(core/defmacro defrecord + "(defrecord name [fields*] options* specs*) + + Currently there are no options. + + Each spec consists of a protocol or interface name followed by zero + or more method bodies: + + protocol-or-Object + (methodName [args*] body)* + + The record will have the (immutable) fields named by + fields, which can have type hints. Protocols and methods + are optional. The only methods that can be supplied are those + declared in the protocols. Note that method bodies are + not closures, the local environment includes only the named fields, + and those fields can be accessed directly. + + Method definitions take the form: + + (methodname [args*] body) + + The argument and return types can be hinted on the arg and + methodname symbols. If not supplied, they will be inferred, so type + hints should be reserved for disambiguation. + + Methods should be supplied for all methods of the desired + protocol(s). You can also define overrides for + methods of Object. Note that a parameter must be supplied to + correspond to the target object ('this' in JavaScript parlance). Note also + that recur calls to the method head should *not* pass the target object, it + will be supplied automatically and can not be substituted. + + In the method bodies, the (unqualified) name can be used to name the + class (for calls to new, instance? etc). + + The type will have implementations of several ClojureScript + protocol generated automatically: IMeta/IWithMeta (metadata support) and + IMap, etc. + + In addition, defrecord will define type-and-value-based =, + and will define ClojureScript IHash and IEquiv. + + Two constructors will be defined, one taking the designated fields + followed by a metadata map (nil for none) and an extension field + map (nil for none), and one taking only the fields (using nil for + meta and extension fields). Note that the field names __meta + and __extmap are currently reserved and should not be used when + defining your own records. + + Given (defrecord TypeName ...), two factory functions will be + defined: ->TypeName, taking positional parameters for the fields, + and map->TypeName, taking a map of keywords to field values." + [rsym fields & impls] + (validate-fields "defrecord" rsym fields) + (core/let [rsym (vary-meta rsym assoc :internal-ctor true) + r (vary-meta + (:name (cljs.analyzer/resolve-var (dissoc &env :locals) rsym)) + assoc :internal-ctor true)] + `(let [] + ~(emit-defrecord &env rsym r fields impls) + (set! (.-getBasis ~r) (fn [] '[~@fields])) + (set! (.-cljs$lang$type ~r) true) + (set! (.-cljs$lang$ctorPrSeq ~r) (fn [this#] (cljs.core/list ~(core/str r)))) + (set! (.-cljs$lang$ctorPrWriter ~r) (fn [this# writer#] (-write writer# ~(core/str r)))) + ~(build-positional-factory rsym r fields) + ~(build-map-factory rsym r fields) + ~r))) + +(core/defmacro defprotocol + "A protocol is a named set of named methods and their signatures: + + (defprotocol AProtocolName + ;optional doc string + \"A doc string for AProtocol abstraction\" + + ;method signatures + (bar [this a b] \"bar docs\") + (baz [this a] [this a b] [this a b c] \"baz docs\")) + + No implementations are provided. Docs can be specified for the + protocol overall and for each method. The above yields a set of + polymorphic functions and a protocol object. All are + namespace-qualified by the ns enclosing the definition The resulting + functions dispatch on the type of their first argument, which is + required and corresponds to the implicit target object ('this' in + JavaScript parlance). defprotocol is dynamic, has no special compile-time + effect, and defines no new types. + + (defprotocol P + (foo [this]) + (bar-me [this] [this y])) + + (deftype Foo [a b c] + P + (foo [this] a) + (bar-me [this] b) + (bar-me [this y] (+ c y))) + + (bar-me (Foo. 1 2 3) 42) + => 45 + + (foo + (let [x 42] + (reify P + (foo [this] 17) + (bar-me [this] x) + (bar-me [this y] x)))) + => 17" + [psym & doc+methods] + (core/let [p (:name (cljs.analyzer/resolve-var (dissoc &env :locals) psym)) + [opts methods] + (core/loop [opts {:protocol-symbol true} + methods [] + sigs doc+methods] + (core/if-not (seq sigs) + [opts methods] + (core/let [[head & tail] sigs] + (core/cond + (core/string? head) + (recur (assoc opts :doc head) methods tail) + (core/keyword? head) + (recur (assoc opts head (first tail)) methods (rest tail)) + (core/seq? head) + (recur opts (conj methods head) tail) + :else + (throw #?(:clj (Exception. + (core/str "Invalid protocol, " psym " received unexpected argument")) + :cljs (js/Error. + (core/str "Invalid protocol, " psym " received unexpected argument")))) + )))) + psym (vary-meta psym merge opts) + ns-name (core/-> &env :ns :name) + fqn (core/fn [n] (symbol (core/str ns-name) (core/str n))) + prefix (protocol-prefix p) + _ (core/doseq [[mname & arities] methods] + (core/when (some #{0} (map count (filter vector? arities))) + (throw + #?(:clj (Exception. + (core/str "Invalid protocol, " psym + " defines method " mname " with arity 0")) + :cljs (js/Error. + (core/str "Invalid protocol, " psym + " defines method " mname " with arity 0")))))) + sig->syms (core/fn [sig] + (core/if-not (every? core/symbol? sig) + (mapv (core/fn [arg] + (core/cond + (core/symbol? arg) arg + (core/and (map? arg) (core/some? (:as arg))) (:as arg) + :else (gensym))) sig) + sig)) + expand-dyn (core/fn [fname sig] + (core/let [sig (sig->syms sig) + + fqn-fname (with-meta (fqn fname) {:cljs.analyzer/no-resolve true}) + fsig (first sig) + + ;; construct protocol checks in reverse order + ;; check the.protocol/fn["_"] for default impl last + check + `(let [m# (unchecked-get ~fqn-fname "_")] + (if-not (nil? m#) + (m# ~@sig) + (throw + (missing-protocol + ~(core/str psym "." fname) ~fsig)))) + + ;; then check protocol on js string,function,array,object (first dynamic check actually executed) + check + `(let [x# (if (nil? ~fsig) nil ~fsig) + m# (unchecked-get ~fqn-fname (goog/typeOf x#))] + (if-not (nil? m#) + (m# ~@sig) + ~check))] + `(~sig ~check))) + expand-sig (core/fn [fname dyn-name slot sig] + (core/let [sig (sig->syms sig) + + fqn-fname (with-meta (fqn fname) {:cljs.analyzer/no-resolve true}) + fsig (first sig) + + ;; check protocol property on object (first check executed) + check + `(if (and (not (nil? ~fsig)) + ;; Property access needed here. + (not (nil? (. ~fsig ~(with-meta (symbol (core/str "-" slot)) {:protocol-prop true}))))) + (. ~fsig ~slot ~@sig) + (~dyn-name ~@sig)) + + ;; then check protocol fn in metadata (only when protocol is marked with :extend-via-metadata true) + check + (core/if-not (:extend-via-metadata opts) + check + `(if-let [meta-impl# (-> ~fsig (core/meta) (core/get '~fqn-fname))] + (meta-impl# ~@sig) + ~check))] + `(~sig ~check))) + psym (core/-> psym + (vary-meta update-in [:jsdoc] conj "@interface") + (vary-meta assoc-in [:protocol-info :methods] + (into {} + (map + (core/fn [[fname & sigs]] + (core/let [doc (core/as-> (last sigs) doc + (core/when (core/string? doc) doc)) + sigs (take-while vector? sigs)] + [(vary-meta fname assoc :doc doc) + (vec sigs)])) + methods))) + ;; for compatibility with Clojure + (vary-meta assoc-in [:sigs] + (into {} + (map + (core/fn [[fname & sigs]] + (core/let [doc (core/as-> (last sigs) doc + (core/when (core/string? doc) doc)) + sigs (take-while vector? sigs)] + [(keyword fname) {:name fname :arglists (list* sigs) :doc doc}])) + methods)))) + method (core/fn [[fname & sigs]] + (core/let [doc (core/as-> (last sigs) doc + (core/when (core/string? doc) doc)) + sigs (take-while vector? sigs) + amp (core/when (some #{'&} (apply concat sigs)) + (cljs.analyzer/warning + :protocol-with-variadic-method + &env {:protocol psym :name fname})) + _ (core/when-some [existing (core/get (-> &env :ns :defs) fname)] + (core/when-not (= p (:protocol existing)) + (cljs.analyzer/warning + :protocol-with-overwriting-method + {} {:protocol psym :name fname :existing existing}))) + slot (symbol (core/str prefix (munge (name fname)))) + dyn-name (symbol (core/str slot "$dyn")) + fname (vary-meta fname assoc + :protocol p + :doc doc)] + `(let [~dyn-name (core/fn + ~@(map (core/fn [sig] + (expand-dyn fname sig)) + sigs))] + (defn ~fname + ~@(map (core/fn [sig] + (expand-sig fname dyn-name + (with-meta (symbol (core/str slot "$arity$" (count sig))) + {:protocol-prop true}) + sig)) + sigs)))))] + `(do + (set! ~'*unchecked-if* true) + (def ~psym (~'js* "function(){}")) + ~@(map method methods) + (set! ~'*unchecked-if* false)))) + +(core/defmacro implements? + "EXPERIMENTAL" + [psym x] + (core/let [p (:name + (cljs.analyzer/resolve-var + (dissoc &env :locals) psym)) + prefix (protocol-prefix p) + xsym (bool-expr (gensym)) + [part bit] (fast-path-protocols p) + msym (symbol + (core/str "-cljs$lang$protocol_mask$partition" part "$"))] + (core/if-not (core/symbol? x) + `(let [~xsym ~x] + (if ~xsym + (if (or ~(if bit `(unsafe-bit-and (. ~xsym ~msym) ~bit) false) + (identical? cljs.core/PROTOCOL_SENTINEL (. ~xsym ~(symbol (core/str "-" prefix))))) + true + false) + false)) + `(if-not (nil? ~x) + (if (or ~(if bit `(unsafe-bit-and (. ~x ~msym) ~bit) false) + (identical? cljs.core/PROTOCOL_SENTINEL (. ~x ~(symbol (core/str "-" prefix))))) + true + false) + false)))) + +(core/defmacro satisfies? + "Returns true if x satisfies the protocol" + [psym x] + (core/let [p (:name + (cljs.analyzer/resolve-var + (dissoc &env :locals) psym)) + prefix (protocol-prefix p) + xsym (bool-expr (gensym)) + [part bit] (fast-path-protocols p) + msym (symbol + (core/str "-cljs$lang$protocol_mask$partition" part "$"))] + (core/if-not (core/symbol? x) + `(let [~xsym ~x] + (if-not (nil? ~xsym) + (if (or ~(if bit `(unsafe-bit-and (. ~xsym ~msym) ~bit) false) + (identical? cljs.core/PROTOCOL_SENTINEL + (. ~xsym ~(with-meta (symbol (core/str "-" prefix)) {:protocol-prop true})))) + true + (if (coercive-not (. ~xsym ~msym)) + (cljs.core/native-satisfies? ~psym ~xsym) + false)) + (cljs.core/native-satisfies? ~psym ~xsym))) + `(if-not (nil? ~x) + (if (or ~(if bit `(unsafe-bit-and (. ~x ~msym) ~bit) false) + (identical? cljs.core/PROTOCOL_SENTINEL + (. ~x ~(with-meta (symbol (core/str "-" prefix)) {:protocol-prop true})))) + true + (if (coercive-not (. ~x ~msym)) + (cljs.core/native-satisfies? ~psym ~x) + false)) + (cljs.core/native-satisfies? ~psym ~x))))) + +(core/defmacro lazy-seq + "Takes a body of expressions that returns an ISeq or nil, and yields + a ISeqable object that will invoke the body only the first time seq + is called, and will cache the result and return it on all subsequent + seq calls." + [& body] + `(new cljs.core/LazySeq nil (fn [] ~@body) nil nil)) + +(core/defmacro delay + "Takes a body of expressions and yields a Delay object that will + invoke the body only the first time it is forced (with force or deref/@), and + will cache the result and return it on all subsequent force + calls." + [& body] + `(new cljs.core/Delay (fn [] ~@body) nil)) + +(core/defmacro with-redefs + "binding => var-symbol temp-value-expr + + Temporarily redefines vars while executing the body. The + temp-value-exprs will be evaluated and each resulting value will + replace in parallel the root value of its var. After the body is + executed, the root values of all the vars will be set back to their + old values. Useful for mocking out functions during testing. + + Note that under advanced compilation vars are statically resolved, + preventing with-redef usage. If var redefinition is desired in a production + setting then the var to be redefined must be declared ^:dynamic." + [bindings & body] + (core/let [names (take-nth 2 bindings) + vals (take-nth 2 (drop 1 bindings)) + orig-val-syms (map (comp gensym #(core/str % "-orig-val__") name) names) + temp-val-syms (map (comp gensym #(core/str % "-temp-val__") name) names) + binds (map core/vector names temp-val-syms) + resets (reverse (map core/vector names orig-val-syms)) + bind-value (core/fn [[k v]] (core/list 'set! k v))] + `(let [~@(interleave orig-val-syms names) + ~@(interleave temp-val-syms vals)] + ~@(map bind-value binds) + (try + ~@body + (finally + ~@(map bind-value resets)))))) + +(core/defmacro binding + "binding => var-symbol init-expr + + Creates new bindings for the (already-existing) vars, with the + supplied initial values, executes the exprs in an implicit do, then + re-establishes the bindings that existed before. The new bindings + are made in parallel (unlike let); all init-exprs are evaluated + before the vars are bound to their new values." + [bindings & body] + (core/let [names (take-nth 2 bindings)] + (cljs.analyzer/confirm-bindings &env names) + `(with-redefs ~bindings ~@body))) + +(core/defmacro condp + "Takes a binary predicate, an expression, and a set of clauses. + Each clause can take the form of either: + + test-expr result-expr + + test-expr :>> result-fn + + Note :>> is an ordinary keyword. + + For each clause, (pred test-expr expr) is evaluated. If it returns + logical true, the clause is a match. If a binary clause matches, the + result-expr is returned, if a ternary clause matches, its result-fn, + which must be a unary function, is called with the result of the + predicate as its argument, the result of that call being the return + value of condp. A single default expression can follow the clauses, + and its value will be returned if no clause matches. If no default + expression is provided and no clause matches, an Error is thrown." + {:added "1.0"} + + [pred expr & clauses] + (core/let [gpred (gensym "pred__") + gexpr (gensym "expr__") + emit (core/fn emit [pred expr args] + (core/let [[[a b c :as clause] more] + (split-at (if (= :>> (second args)) 3 2) args) + n (count clause)] + (core/cond + (= 0 n) `(throw (js/Error. (cljs.core/str "No matching clause: " ~expr))) + (= 1 n) a + (= 2 n) `(if (~pred ~a ~expr) + ~b + ~(emit pred expr more)) + :else `(if-let [p# (~pred ~a ~expr)] + (~c p#) + ~(emit pred expr more))))) + gres (gensym "res__")] + `(let [~gpred ~pred + ~gexpr ~expr] + ~(emit gpred gexpr clauses)))) + +(core/defn- assoc-test [m test expr env] + (if (contains? m test) + (throw + #?(:clj (clojure.core/IllegalArgumentException. + (core/str "Duplicate case test constant '" + test "'" + (core/when (:line env) + (core/str " on line " (:line env) " " + cljs.analyzer/*cljs-file*)))) + :cljs (js/Error. + (core/str "Duplicate case test constant '" + test "'" + (core/when (:line env) + (core/str " on line " (:line env) " " + cljs.analyzer/*cljs-file*)))))) + (assoc m test expr))) + +(core/defn- const? [env x] + (core/let [m (core/and (core/list? x) + (ana/resolve-var env (last x)))] + (core/when m (core/get m :const)))) + +(core/defmacro case + "Takes an expression, and a set of clauses. + + Each clause can take the form of either: + + test-constant result-expr + + (test-constant1 ... test-constantN) result-expr + + The test-constants are not evaluated. They must be compile-time + literals, and need not be quoted. If the expression is equal to a + test-constant, the corresponding result-expr is returned. A single + default expression can follow the clauses, and its value will be + returned if no clause matches. If no default expression is provided + and no clause matches, an Error is thrown. + + Unlike cond and condp, case does a constant-time dispatch, the + clauses are not considered sequentially. All manner of constant + expressions are acceptable in case, including numbers, strings, + symbols, keywords, and (ClojureScript) composites thereof. Note that since + lists are used to group multiple constants that map to the same + expression, a vector can be used to match a list if needed. The + test-constants need not be all of the same type." + [e & clauses] + (core/let [esym (gensym) + default (if (odd? (count clauses)) + (last clauses) + `(throw + (js/Error. + (cljs.core/str "No matching clause: " ~esym)))) + env &env + pairs (reduce + (core/fn [m [test expr]] + (core/cond + (seq? test) + (reduce + (core/fn [m test] + (core/let [test (if (core/symbol? test) + (core/list 'quote test) + test)] + (assoc-test m test expr env))) + m test) + (core/symbol? test) + (assoc-test m (core/list 'quote test) expr env) + :else + (assoc-test m test expr env))) + {} (partition 2 clauses)) + tests (keys pairs)] + (core/cond + (every? (some-fn core/number? core/string? #?(:clj core/char? :cljs (core/fnil core/char? :nonchar)) #(const? env %)) tests) + (core/let [no-default (if (odd? (count clauses)) (butlast clauses) clauses) + tests (mapv #(if (seq? %) (vec %) [%]) (take-nth 2 no-default)) + thens (vec (take-nth 2 (drop 1 no-default)))] + `(let [~esym ~e] (case* ~esym ~tests ~thens ~default))) + + (every? core/keyword? tests) + (core/let [no-default (if (odd? (count clauses)) (butlast clauses) clauses) + kw-str #(.substring (core/str %) 1) + tests (mapv #(if (seq? %) (mapv kw-str %) [(kw-str %)]) (take-nth 2 no-default)) + thens (vec (take-nth 2 (drop 1 no-default)))] + `(let [~esym ~e + ~esym (if (keyword? ~esym) (.-fqn ~(vary-meta esym assoc :tag 'cljs.core/Keyword)) nil)] + (case* ~esym ~tests ~thens ~default))) + + ;; equality + :else + `(let [~esym ~e] + (cond + ~@(mapcat (core/fn [[m c]] `((cljs.core/= ~m ~esym) ~c)) pairs) + :else ~default))))) + +(core/defmacro ^:private when-assert [x] + (core/when *assert* x)) + +(core/defmacro assert + "Evaluates expr and throws an exception if it does not evaluate to + logical true." + ([x] + (core/when *assert* + `(when-not ~x + (throw (js/Error. ~(core/str "Assert failed: " (core/pr-str x))))))) + ([x message] + (core/when *assert* + `(when-not ~x + (throw (js/Error. + (cljs.core/str "Assert failed: " ~message "\n" ~(core/pr-str x)))))))) + +(core/defmacro for + "List comprehension. Takes a vector of one or more + binding-form/collection-expr pairs, each followed by zero or more + modifiers, and yields a lazy sequence of evaluations of expr. + Collections are iterated in a nested fashion, rightmost fastest, + and nested coll-exprs can refer to bindings created in prior + binding-forms. Supported modifiers are: :let [binding-form expr ...], + :while test, :when test. + + (take 100 (for [x (range 100000000) y (range 1000000) :while (< y x)] [x y]))" + [seq-exprs body-expr] + (assert-args for + (vector? seq-exprs) "a vector for its binding" + (even? (count seq-exprs)) "an even number of forms in binding vector") + (core/let [to-groups (core/fn [seq-exprs] + (reduce (core/fn [groups [k v]] + (if (core/keyword? k) + (conj (pop groups) (conj (peek groups) [k v])) + (conj groups [k v]))) + [] (partition 2 seq-exprs))) + err (core/fn [& msg] (throw (ex-info (apply core/str msg) {}))) + emit-bind (core/fn emit-bind [[[bind expr & mod-pairs] + & [[_ next-expr] :as next-groups]]] + (core/let [giter (gensym "iter__") + gxs (gensym "s__") + do-mod (core/fn do-mod [[[k v :as pair] & etc]] + (core/cond + (= k :let) `(let ~v ~(do-mod etc)) + (= k :while) `(when ~v ~(do-mod etc)) + (= k :when) `(if ~v + ~(do-mod etc) + (recur (rest ~gxs))) + (core/keyword? k) (err "Invalid 'for' keyword " k) + next-groups + `(let [iterys# ~(emit-bind next-groups) + fs# (seq (iterys# ~next-expr))] + (if fs# + (concat fs# (~giter (rest ~gxs))) + (recur (rest ~gxs)))) + :else `(cons ~body-expr + (~giter (rest ~gxs)))))] + (if next-groups + #_ "not the inner-most loop" + `(fn ~giter [~gxs] + (lazy-seq + (loop [~gxs ~gxs] + (when-first [~bind ~gxs] + ~(do-mod mod-pairs))))) + #_"inner-most loop" + (core/let [gi (gensym "i__") + gb (gensym "b__") + do-cmod (core/fn do-cmod [[[k v :as pair] & etc]] + (core/cond + (= k :let) `(let ~v ~(do-cmod etc)) + (= k :while) `(when ~v ~(do-cmod etc)) + (= k :when) `(if ~v + ~(do-cmod etc) + (recur + (unchecked-inc ~gi))) + (core/keyword? k) + (err "Invalid 'for' keyword " k) + :else + `(do (chunk-append ~gb ~body-expr) + (recur (unchecked-inc ~gi)))))] + `(fn ~giter [~gxs] + (lazy-seq + (loop [~gxs ~gxs] + (when-let [~gxs (seq ~gxs)] + (if (chunked-seq? ~gxs) + (let [c# ^not-native (chunk-first ~gxs) + size# (count c#) + ~gb (chunk-buffer size#)] + (if (coercive-boolean + (loop [~gi 0] + (if (< ~gi size#) + (let [~bind (-nth c# ~gi)] + ~(do-cmod mod-pairs)) + true))) + (chunk-cons + (chunk ~gb) + (~giter (chunk-rest ~gxs))) + (chunk-cons (chunk ~gb) nil))) + (let [~bind (first ~gxs)] + ~(do-mod mod-pairs)))))))))))] + `(let [iter# ~(emit-bind (to-groups seq-exprs))] + (iter# ~(second seq-exprs))))) + +(core/defmacro doseq + "Repeatedly executes body (presumably for side-effects) with + bindings and filtering as provided by \"for\". Does not retain + the head of the sequence. Returns nil." + [seq-exprs & body] + (assert-args doseq + (vector? seq-exprs) "a vector for its binding" + (even? (count seq-exprs)) "an even number of forms in binding vector") + (core/let [err (core/fn [& msg] (throw (ex-info (apply core/str msg) {}))) + step (core/fn step [recform exprs] + (core/if-not exprs + [true `(do ~@body nil)] + (core/let [k (first exprs) + v (second exprs) + + seqsym (gensym "seq__") + recform (if (core/keyword? k) recform `(recur (next ~seqsym) nil 0 0)) + steppair (step recform (nnext exprs)) + needrec (steppair 0) + subform (steppair 1)] + (core/cond + (= k :let) [needrec `(let ~v ~subform)] + (= k :while) [false `(when ~v + ~subform + ~@(core/when needrec [recform]))] + (= k :when) [false `(if ~v + (do + ~subform + ~@(core/when needrec [recform])) + ~recform)] + (core/keyword? k) (err "Invalid 'doseq' keyword" k) + :else (core/let [chunksym (with-meta (gensym "chunk__") + {:tag 'not-native}) + countsym (gensym "count__") + isym (gensym "i__") + recform-chunk `(recur ~seqsym ~chunksym ~countsym (unchecked-inc ~isym)) + steppair-chunk (step recform-chunk (nnext exprs)) + subform-chunk (steppair-chunk 1)] + [true `(loop [~seqsym (seq ~v) + ~chunksym nil + ~countsym 0 + ~isym 0] + (if (coercive-boolean (< ~isym ~countsym)) + (let [~k (-nth ~chunksym ~isym)] + ~subform-chunk + ~@(core/when needrec [recform-chunk])) + (when-let [~seqsym (seq ~seqsym)] + (if (chunked-seq? ~seqsym) + (let [c# (chunk-first ~seqsym)] + (recur (chunk-rest ~seqsym) c# + (count c#) 0)) + (let [~k (first ~seqsym)] + ~subform + ~@(core/when needrec [recform]))))))])))))] + (nth (step nil (seq seq-exprs)) 1))) + +(core/defmacro array [& rest] + (core/let [xs-str (core/->> (repeat "~{}") + (take (count rest)) + (interpose ",") + (apply core/str))] + (vary-meta + (list* 'js* (core/str "[" xs-str "]") rest) + assoc :tag 'array))) + +(core/defmacro make-array + ([size] + (vary-meta + (if (core/number? size) + `(array ~@(take size (repeat nil))) + `(js/Array. ~size)) + assoc :tag 'array)) + ([type size] + `(cljs.core/make-array ~size)) + ([type size & more-sizes] + (vary-meta + `(let [dims# (list ~@more-sizes) + dimarray# (cljs.core/make-array ~size)] + (dotimes [i# (alength dimarray#)] + (aset dimarray# i# (apply cljs.core/make-array nil dims#))) + dimarray#) + assoc :tag 'array))) + +(core/defmacro list + ([] + '(.-EMPTY cljs.core/List)) + ([x] + `(cljs.core/List. nil ~x nil 1 nil)) + ([x & xs] + (core/let [cnt (core/inc (count xs))] + `(cljs.core/List. nil ~x (list ~@xs) ~cnt nil)))) + +(core/defmacro vector + ([] '(.-EMPTY cljs.core/PersistentVector)) + ([& xs] + (core/let [cnt (count xs)] + (if (core/< cnt 32) + `(cljs.core/PersistentVector. nil ~cnt 5 + (.-EMPTY-NODE cljs.core/PersistentVector) (array ~@xs) nil) + (vary-meta + `(.fromArray cljs.core/PersistentVector (array ~@xs) true) + assoc :tag 'cljs.core/PersistentVector))))) + +(core/defmacro array-map + ([] '(.-EMPTY cljs.core/PersistentArrayMap)) + ([& kvs] + (core/let [keys (map first (partition 2 kvs))] + (if (core/and (every? #(= (:op (cljs.analyzer/unwrap-quote %)) :const) + (map #(cljs.analyzer/no-warn (cljs.analyzer/analyze &env %)) keys)) + (= (count (into #{} keys)) (count keys))) + `(cljs.core/PersistentArrayMap. nil ~(clojure.core// (count kvs) 2) (array ~@kvs) nil) + `(.createAsIfByAssoc cljs.core/PersistentArrayMap (array ~@kvs)))))) + +(core/defmacro hash-map + ([] `(.-EMPTY cljs.core/PersistentHashMap)) + ([& kvs] + (core/let [pairs (map + (core/fn [pair] + (remove #{::missing} pair)) + (partition 2 2 (repeat ::missing) kvs)) + ks (map first pairs) + vs (map second (take-while #(= 2 (count %)) pairs))] + (vary-meta + `(.fromArrays cljs.core/PersistentHashMap (array ~@ks) (array ~@vs)) + assoc :tag 'cljs.core/PersistentHashMap)))) + +(core/defmacro hash-set + ([] `(.-EMPTY cljs.core/PersistentHashSet)) + ([& xs] + (if (core/and (core/<= (count xs) 8) + (every? #(= (:op (cljs.analyzer/unwrap-quote %)) :const) + (map #(cljs.analyzer/no-warn (cljs.analyzer/analyze &env %)) xs)) + (= (count (into #{} xs)) (count xs))) + `(cljs.core/PersistentHashSet. nil + (cljs.core/PersistentArrayMap. nil ~(count xs) (array ~@(interleave xs (repeat nil))) nil) + nil) + (vary-meta + `(.createAsIfByAssoc cljs.core/PersistentHashSet (array ~@xs)) + assoc :tag 'cljs.core/PersistentHashSet)))) + +(core/defn- js-obj* [kvs] + (core/let [kvs-str (core/->> (repeat "~{}:~{}") + (take (count kvs)) + (interpose ",") + (apply core/str))] + (vary-meta + (list* 'js* (core/str "({" kvs-str "})") (apply concat kvs)) + assoc :tag 'object))) + +(core/defmacro js-obj [& rest] + (core/let [sym-or-str? (core/fn [x] (core/or (core/symbol? x) (core/string? x))) + filter-on-keys (core/fn [f coll] + (core/->> coll + (filter (core/fn [[k _]] (f k))) + (into {}))) + kvs (into {} (map vec (partition 2 rest))) + sym-pairs (filter-on-keys core/symbol? kvs) + expr->local (zipmap + (filter (complement sym-or-str?) (keys kvs)) + (repeatedly gensym)) + obj (gensym "obj")] + (if (empty? rest) + (js-obj* '()) + `(let [~@(apply concat (clojure.set/map-invert expr->local)) + ~obj ~(js-obj* (filter-on-keys core/string? kvs))] + ~@(map (core/fn [[k v]] `(unchecked-set ~obj ~k ~v)) sym-pairs) + ~@(map (core/fn [[k v]] `(unchecked-set ~obj ~v ~(core/get kvs k))) expr->local) + ~obj)))) + +(core/defmacro alength [a] + (vary-meta + (core/list 'js* "~{}.length" a) + assoc :tag 'number)) + +(core/defmacro amap + "Maps an expression across an array a, using an index named idx, and + return value named ret, initialized to a clone of a, then setting + each element of ret to the evaluation of expr, returning the new + array ret." + [a idx ret expr] + `(let [a# ~a + l# (alength a#) + ~ret (cljs.core/aclone a#)] + (loop [~idx 0] + (if (< ~idx l#) + (do + (aset ~ret ~idx ~expr) + (recur (inc ~idx))) + ~ret)))) + +(core/defmacro areduce + "Reduces an expression across an array a, using an index named idx, + and return value named ret, initialized to init, setting ret to the + evaluation of expr at each step, returning ret." + [a idx ret init expr] + `(let [a# ~a + l# (alength a#)] + (loop [~idx 0 ~ret ~init] + (if (< ~idx l#) + (recur (inc ~idx) ~expr) + ~ret)))) + +(core/defmacro dotimes + "bindings => name n + + Repeatedly executes body (presumably for side-effects) with name + bound to integers from 0 through n-1." + [bindings & body] + (core/let [i (first bindings) + n (second bindings)] + `(let [n# ~n] + (loop [~i 0] + (when (< ~i n#) + ~@body + (recur (inc ~i))))))) + +(core/defn- check-valid-options + "Throws an exception if the given option map contains keys not listed + as valid, else returns nil." + [options & valid-keys] + (core/when (seq (apply disj (apply core/hash-set (keys options)) valid-keys)) + (throw + (apply core/str "Only these options are valid: " + (first valid-keys) + (map #(core/str ", " %) (rest valid-keys)))))) + +(core/defmacro defmulti + "Creates a new multimethod with the associated dispatch function. + The docstring and attribute-map are optional. + + Options are key-value pairs and may be one of: + :default the default dispatch value, defaults to :default + :hierarchy the isa? hierarchy to use for dispatching + defaults to the global hierarchy" + {:arglists '([name docstring? attr-map? dispatch-fn & options])} + [mm-name & options] + (core/let [docstring (if (core/string? (first options)) + (first options) + nil) + options (if (core/string? (first options)) + (next options) + options) + m (if (map? (first options)) + (first options) + {}) + options (if (map? (first options)) + (next options) + options) + dispatch-fn (first options) + options (next options) + m (if docstring + (assoc m :doc docstring) + m) + m (if (meta mm-name) + (conj (meta mm-name) m) + m) + mm-ns (core/-> &env :ns :name core/str)] + (core/when (= (count options) 1) + (throw + #?(:clj (Exception. "The syntax for defmulti has changed. Example: (defmulti name dispatch-fn :default dispatch-value)") + :cljs (js/Error. "The syntax for defmulti has changed. Example: (defmulti name dispatch-fn :default dispatch-value)")))) + (core/let [options (apply core/hash-map options) + default (core/get options :default :default)] + (check-valid-options options :default :hierarchy) + `(defonce ~(with-meta mm-name m) + (let [method-table# (atom {}) + prefer-table# (atom {}) + method-cache# (atom {}) + cached-hierarchy# (atom {}) + hierarchy# (cljs.core/get ~options :hierarchy ((~'js* "cljs.core.get_global_hierarchy")))] + (cljs.core/MultiFn. (cljs.core/symbol ~mm-ns ~(name mm-name)) ~dispatch-fn ~default hierarchy# + method-table# prefer-table# method-cache# cached-hierarchy#)))))) + +(core/defmacro defmethod + "Creates and installs a new method of multimethod associated with dispatch-value. " + [multifn dispatch-val & fn-tail] + `(-add-method ~(with-meta multifn {:tag 'cljs.core/MultiFn}) ~dispatch-val (fn ~@fn-tail))) + +(core/defmacro time + "Evaluates expr and prints the time it took. Returns the value of expr." + [expr] + `(let [start# (system-time) + ret# ~expr] + (prn (cljs.core/str "Elapsed time: " + (.toFixed (- (system-time) start#) 6) + " msecs")) + ret#)) + +(core/defmacro simple-benchmark + "Runs expr iterations times in the context of a let expression with + the given bindings, then prints out the bindings and the expr + followed by number of iterations and total time. The optional + argument print-fn, defaulting to println, sets function used to + print the result. expr's string representation will be produced + using pr-str in any case." + [bindings expr iterations & {:keys [print-fn] :or {print-fn 'println}}] + (core/let [bs-str (pr-str bindings) + expr-str (pr-str expr)] + `(let ~bindings + (let [start# (.getTime (js/Date.)) + ret# (dotimes [_# ~iterations] ~expr) + end# (.getTime (js/Date.)) + elapsed# (- end# start#)] + (~print-fn (str ~bs-str ", " ~expr-str ", " + ~iterations " runs, " elapsed# " msecs")))))) + +(def ^:private cs (into [] (map (comp gensym core/str core/char) (range 97 118)))) + +(core/defn- gen-apply-to-helper + ([] (gen-apply-to-helper 1)) + ([n] + (if (core/<= n 20) + `(let [~(cs (core/dec n)) (-first ~'args) + ~'args (-rest ~'args)] + (if (== ~'argc ~n) + (~'f ~@(take n cs)) + ~(gen-apply-to-helper (core/inc n)))) + `(throw (js/Error. "Only up to 20 arguments supported on functions"))))) + +(core/defmacro gen-apply-to [] + `(do + (set! ~'*unchecked-if* true) + (defn ~'apply-to [~'f ~'argc ~'args] + (let [~'args (seq ~'args)] + (if (zero? ~'argc) + (~'f) + ~(gen-apply-to-helper)))) + (set! ~'*unchecked-if* false))) + +(core/defn- gen-apply-to-simple-helper + [f num-args args] + (core/let [new-arg-sym (symbol (core/str "a" num-args)) + proto-name (core/str "cljs$core$IFn$_invoke$arity$" (core/inc num-args)) + proto-prop (symbol (core/str ".-" proto-name)) + proto-inv (symbol (core/str "." proto-name)) + next-sym (symbol (core/str "next_" num-args)) + all-args (mapv #(symbol (core/str "a" %)) (range (core/inc num-args)))] + `(let [~new-arg-sym (cljs.core/-first ~args) + ~next-sym (cljs.core/next ~args)] + (if (nil? ~next-sym) + (if (~proto-prop ~f) + (~proto-inv ~f ~@all-args) + (.call ~f ~f ~@all-args)) + ~(if (core/<= 19 num-args) + ;; We've exhausted all protocols, fallback to .apply: + `(let [arr# (cljs.core/array ~@all-args)] + (loop [s# ~next-sym] + (when s# + (do (.push arr# (cljs.core/-first s#)) + (recur (cljs.core/next s#))))) + (.apply ~f ~f arr#)) + (gen-apply-to-simple-helper f (core/inc num-args) next-sym)))))) + +(core/defmacro gen-apply-to-simple + [f num-args args] + (gen-apply-to-simple-helper f num-args args)) + +(core/defmacro with-out-str + "Evaluates exprs in a context in which *print-fn* is bound to .append + on a fresh StringBuffer. Returns the string created by any nested + printing calls." + [& body] + `(let [sb# (goog.string/StringBuffer.)] + (binding [cljs.core/*print-newline* true + cljs.core/*print-fn* (fn [x#] (.append sb# x#))] + ~@body) + (cljs.core/str sb#))) + +(core/defmacro lazy-cat + "Expands to code which yields a lazy sequence of the concatenation + of the supplied colls. Each coll expr is not evaluated until it is + needed. + + (lazy-cat xs ys zs) === (concat (lazy-seq xs) (lazy-seq ys) (lazy-seq zs))" + [& colls] + `(concat ~@(map #(core/list `lazy-seq %) colls))) + +(core/defmacro js-str [s] + (core/list 'js* "''+~{}" s)) + +(core/defmacro es6-iterable [ty] + `(unchecked-set (.-prototype ~ty) cljs.core/ITER_SYMBOL + (fn [] + (this-as this# + (cljs.core/es6-iterator this#))))) + +(core/defmacro ns-publics + "Returns a map of the public intern mappings for the namespace." + [quoted-ns] + (core/assert (core/and (seq? quoted-ns) + (= (first quoted-ns) 'quote) + (core/symbol? (second quoted-ns))) + "Argument to ns-publics must be a quoted symbol") + (core/let [ns (second quoted-ns)] + `(into {} + [~@(map + (core/fn [[sym _]] + `[(symbol ~(name sym)) (var ~(symbol (name ns) (name sym)))]) + (filter (core/fn [[_ info]] + (not (core/-> info :meta :private))) + (get-in @env/*compiler* [:cljs.analyzer/namespaces ns :defs])))]))) + +(core/defmacro ns-imports + "Returns a map of the import mappings for the namespace." + [quoted-ns] + (core/assert (core/and (seq? quoted-ns) + (= (first quoted-ns) 'quote) + (core/symbol? (second quoted-ns))) + "Argument to ns-imports must be a quoted symbol") + (core/let [ns (second quoted-ns)] + `(into {} + [~@(map + (core/fn [[ctor qualified-ctor]] + `[(symbol ~(name ctor)) ~(symbol qualified-ctor)]) + (get-in @env/*compiler* [:cljs.analyzer/namespaces ns :imports]))]))) + +(core/defmacro ns-interns + "Returns a map of the intern mappings for the namespace." + [quoted-ns] + (core/assert (core/and (seq? quoted-ns) + (= (first quoted-ns) 'quote) + (core/symbol? (second quoted-ns))) + "Argument to ns-interns must be a quoted symbol") + (core/let [ns (second quoted-ns)] + `(into {} + [~@(map + (core/fn [[sym _]] + `[(symbol ~(name sym)) (var ~(symbol (name ns) (name sym)))]) + (get-in @env/*compiler* [:cljs.analyzer/namespaces ns :defs]))]))) + +(core/defmacro ns-unmap + "Removes the mappings for the symbol from the namespace." + [quoted-ns quoted-sym] + (core/assert + (core/and (seq? quoted-ns) (= (first quoted-ns) 'quote) (core/symbol? (second quoted-ns)) + (seq? quoted-sym) (= (first quoted-sym) 'quote) (core/symbol? (second quoted-sym))) + "Arguments to ns-unmap must be quoted symbols") + (core/let [ns (second quoted-ns) + sym (second quoted-sym)] + (swap! env/*compiler* update-in [::ana/namespaces ns :defs] dissoc sym) + `(js-delete ~(comp/munge ns) ~(comp/munge (core/str sym))))) + +(core/defmacro vswap! + "Non-atomically swaps the value of the volatile as if: + (apply f current-value-of-vol args). Returns the value that + was swapped in." + [vol f & args] + `(-vreset! ~vol (~f (-deref ~vol) ~@args))) + +(core/defmacro locking + [x & forms] + `(do ~@forms)) + +;; An internal-use Var for defining specs on the ns special form +(core/defmacro ^:private ns-special-form []) + +(core/defmacro require + "Loads libs, skipping any that are already loaded. Each argument is + either a libspec that identifies a lib or a flag that modifies how all the identified + libs are loaded. Use :require in the ns macro in preference to calling this + directly. + + Libs + + A 'lib' is a named set of resources in classpath whose contents define a + library of ClojureScript code. Lib names are symbols and each lib is associated + with a ClojureScript namespace. A lib's name also locates its root directory + within classpath using Java's package name to classpath-relative path mapping. + All resources in a lib should be contained in the directory structure under its + root directory. All definitions a lib makes should be in its associated namespace. + + 'require loads a lib by loading its root resource. The root resource path + is derived from the lib name in the following manner: + Consider a lib named by the symbol 'x.y.z; it has the root directory + /x/y/, and its root resource is /x/y/z.clj. The root + resource should contain code to create the lib's namespace (usually by using + the ns macro) and load any additional lib resources. + + Libspecs + + A libspec is a lib name or a vector containing a lib name followed by + options expressed as sequential keywords and arguments. + + Recognized options: + :as takes a symbol as its argument and makes that symbol an alias to the + lib's namespace in the current namespace. + :refer takes a list of symbols to refer from the namespace. + :refer-macros takes a list of macro symbols to refer from the namespace. + :include-macros true causes macros from the namespace to be required. + :rename specifies a map from referred var names to different + symbols (and can be used to prevent clashes) + + + Flags + + A flag is a keyword. + Recognized flags: :reload, :reload-all, :verbose + :reload forces loading of all the identified libs even if they are + already loaded + :reload-all implies :reload and also forces loading of all libs that the + identified libs directly or indirectly load via require or use + :verbose triggers printing information about each load, alias, and refer + + Example: + + The following would load the library clojure.string :as string. + + (require '[clojure.string :as string])" + [& args] + `(~'ns* ~(cons :require args))) + +(core/defmacro require-macros + "Similar to require but only for macros." + [& args] + `(~'ns* ~(cons :require-macros args))) + +(core/defmacro use + "Like require, but referring vars specified by the mandatory + :only option. + + Example: + + The following would load the library clojure.set while referring + the intersection var. + + (use '[clojure.set :only [intersection]])" + [& args] + `(~'ns* ~(cons :use args))) + +(core/defmacro use-macros + "Similar to use but only for macros." + [& args] + `(~'ns* ~(cons :use-macros args))) + +(core/defmacro import + "import-list => (closure-namespace constructor-name-symbols*) + + For each name in constructor-name-symbols, adds a mapping from name to the + constructor named by closure-namespace to the current namespace. Use :import in the ns + macro in preference to calling this directly." + [& import-symbols-or-lists] + `(~'ns* ~(cons :import import-symbols-or-lists))) + +(core/defmacro refer-clojure + "Refers to all the public vars of `cljs.core`, subject to + filters. + Filters can include at most one each of: + + :exclude list-of-symbols + :rename map-of-fromsymbol-tosymbol + + Filters can be used to select a subset, via exclusion, or to provide a mapping + to a symbol different from the var's name, in order to prevent clashes." + [& args] + `(~'ns* ~(cons :refer-clojure args))) + +(core/defmacro refer-global + "Refer global js vars. Supports renaming via :rename. + + (refer-global :only '[Date Symbol] :rename '{Symbol Sym})" + [& args] + `(~'ns* ~(cons :refer-global args))) + +(core/defmacro require-global + "Require libraries in the global JS environment. + + (require-global '[SomeLib :as lib :refer [foo]])" + [& args] + `(~'ns* ~(cons :require-global args))) + +;; INTERNAL - do not use, only for Node.js +(core/defmacro load-file* [f] + `(goog/nodeGlobalRequire ~f)) + +(core/defmacro macroexpand-1 + "If form represents a macro form, returns its expansion, + else returns form." + [quoted] + (core/assert (core/= (core/first quoted) 'quote) + "Argument to macroexpand-1 must be quoted") + (core/let [form (second quoted)] + (if (seq? form) + `(quote ~(ana/macroexpand-1 &env form)) + form))) + +(core/defmacro macroexpand + "Repeatedly calls macroexpand-1 on form until it no longer + represents a macro form, then returns it. Note neither + macroexpand-1 nor macroexpand expand macros in subforms." + [quoted] + (core/assert (core/= (core/first quoted) 'quote) + "Argument to macroexpand must be quoted") + (core/let [form (second quoted) + env &env] + (if (seq? form) + (core/loop [form form form' (ana/macroexpand-1 env form)] + (core/if-not (core/identical? form form') + (recur form' (ana/macroexpand-1 env form')) + `(quote ~form'))) + form))) + +(core/defn- multi-arity-fn? [fdecl] + (core/< 1 (count fdecl))) + +(core/defn- variadic-fn? [fdecl] + (core/and (= 1 (count fdecl)) + (some '#{&} (ffirst fdecl)))) + +(core/defn- variadic-fn* + ([sym method] + (variadic-fn* sym method true)) + ([sym [arglist & body :as method] solo] + (core/let [sig (remove '#{&} arglist) + restarg (gensym "seq")] + (core/letfn [(get-delegate [] + 'cljs$core$IFn$_invoke$arity$variadic) + (get-delegate-prop [] + (symbol (core/str "-" (get-delegate)))) + (param-bind [param] + `[~param (^::ana/no-resolve first ~restarg) + ~restarg (^::ana/no-resolve next ~restarg)]) + (apply-to [] + (if (core/< 1 (count sig)) + (core/let [params (repeatedly (core/dec (count sig)) gensym)] + `(fn + ([~restarg] + (let [~@(mapcat param-bind params)] + (this-as self# + (. self# (~(get-delegate) ~@params ~restarg))))))) + `(fn + ([~restarg] + (this-as self# + (. self# (~(get-delegate) (seq ~restarg))))))))] + `(do + (set! (. ~sym ~(get-delegate-prop)) + (~(with-meta `fn (meta sym)) (~(vec sig) ~@body))) + ~@(core/when solo + `[(set! (. ~sym ~'-cljs$lang$maxFixedArity) + ~(core/dec (count sig)))]) + (js-inline-comment " @this {Function} ") + ;; dissoc :top-fn so this helper gets ignored in cljs.analyzer/parse 'set! + (set! (. ~(vary-meta sym dissoc :top-fn) ~'-cljs$lang$applyTo) + ~(apply-to))))))) + +(core/defmacro copy-arguments [dest] + `(let [len# (alength (js-arguments))] + (loop [i# 0] + (when (< i# len#) + (.push ~dest (unchecked-get (js-arguments) i#)) + (recur (inc i#)))))) + +(core/defn- elide-implicit-macro-args [arglists] + (core/map (core/fn [arglist] + (if (core/vector? arglist) + (core/subvec arglist 2) + (core/drop 2 arglist))) + arglists)) + +(core/defn- variadic-fn [name meta [[arglist & body :as method] :as fdecl] emit-var?] + (core/letfn [(dest-args [c] + (map (core/fn [n] `(unchecked-get (js-arguments) ~n)) + (range c)))] + (core/let [rname (symbol (core/str ana/*cljs-ns*) (core/str name)) + sig (remove '#{&} arglist) + c-1 (core/dec (count sig)) + macro? (:macro meta) + mfa (core/cond-> c-1 macro? (core/- 2)) + meta (assoc meta + :top-fn + {:variadic? true + :fixed-arity mfa + :max-fixed-arity mfa + :method-params (core/cond-> [sig] macro? elide-implicit-macro-args) + :arglists (core/cond-> (core/list arglist) macro? elide-implicit-macro-args) + :arglists-meta (doall (map meta [arglist]))}) + name (with-meta name meta)] + `(do + (def ~name + (fn [~'var_args] + (let [args# (array)] + (copy-arguments args#) + (let [argseq# (when (< ~c-1 (alength args#)) + (new ^::ana/no-resolve cljs.core/IndexedSeq + (.slice args# ~c-1) 0 nil))] + (. ~rname (~'cljs$core$IFn$_invoke$arity$variadic ~@(dest-args c-1) argseq#)))))) + ~(variadic-fn* name method) + ~(core/when emit-var? `(var ~name)))))) + +(core/comment + (require '[clojure.pprint :as pp]) + (pp/pprint (variadic-fn 'foo {} '(([& xs])))) + (pp/pprint (variadic-fn 'foo {} '(([a & xs] xs)))) + (pp/pprint (variadic-fn 'foo {} '(([a b & xs] xs)))) + (pp/pprint (variadic-fn 'foo {} '(([a [b & cs] & xs] xs)))) + ) + +(core/defn- multi-arity-fn [name meta fdecl emit-var?] + (core/letfn [(dest-args [c] + (map (core/fn [n] `(unchecked-get (js-arguments) ~n)) + (range c))) + (fixed-arity [rname sig] + (core/let [c (count sig)] + [c `(. ~rname + (~(symbol + (core/str "cljs$core$IFn$_invoke$arity$" c)) + ~@(dest-args c)))])) + (fn-method [name [sig & body :as method]] + (if (some '#{&} sig) + (variadic-fn* name method false) + ;; fix up individual :fn-method meta for + ;; cljs.analyzer/parse 'set! :top-fn handling + `(set! + (. ~(vary-meta name update :top-fn merge + {:variadic? false :fixed-arity (count sig)}) + ~(symbol (core/str "-cljs$core$IFn$_invoke$arity$" + (count sig)))) + (~(with-meta `fn (core/meta name)) ~method))))] + (core/let [rname (symbol (core/str ana/*cljs-ns*) (core/str name)) + arglists (map first fdecl) + macro? (:macro meta) + varsig? #(boolean (some '#{&} %)) + {sigs false var-sigs true} (group-by varsig? arglists) + variadic? (core/pos? (core/count var-sigs)) + variadic-params (if variadic? + (core/cond-> (remove '#{&} (first var-sigs)) + true core/count + macro? (core/- 2)) + 0) + maxfa (apply core/max + (concat + (map count sigs) + [(core/- (count (first var-sigs)) 2)])) + mfa (core/cond-> maxfa macro? (core/- 2)) + meta (assoc meta + :top-fn + {:variadic? variadic? + :fixed-arity mfa + :max-fixed-arity mfa + :method-params (core/cond-> sigs macro? elide-implicit-macro-args) + :arglists (core/cond-> arglists macro? elide-implicit-macro-args) + :arglists-meta (doall (map meta arglists))}) + args-sym (gensym "args") + param-counts (map count arglists) + name (with-meta name meta)] + (core/when (core/< 1 (count var-sigs)) + (ana/warning :multiple-variadic-overloads {} {:name name})) + (core/when (core/and (core/pos? variadic-params) + (not (core/== variadic-params (core/+ 1 mfa)))) + (ana/warning :variadic-max-arity {} {:name name})) + (core/when (not= (distinct param-counts) param-counts) + (ana/warning :overload-arity {} {:name name})) + `(do + (def ~name + (fn [~'var_args] + (case (alength (js-arguments)) + ~@(mapcat #(fixed-arity rname %) sigs) + ~(if variadic? + `(let [args-arr# (array)] + (copy-arguments args-arr#) + (let [argseq# (when (< ~maxfa (alength args-arr#)) + (new ^::ana/no-resolve cljs.core/IndexedSeq + (.slice args-arr# ~maxfa) 0 nil))] + (. ~rname + (~'cljs$core$IFn$_invoke$arity$variadic + ~@(dest-args maxfa) + argseq#)))) + (if (:macro meta) + `(throw (js/Error. + (.join (array "Invalid arity: " (- (alength (js-arguments)) 2)) ""))) + `(throw (js/Error. + (.join (array "Invalid arity: " (alength (js-arguments))) "")))))))) + ~@(map #(fn-method name %) fdecl) + ;; optimization properties + (set! (. ~name ~'-cljs$lang$maxFixedArity) ~maxfa) + ~(core/when emit-var? `(var ~name)))))) + +(core/comment + (require '[clojure.pprint :as pp]) + (pp/pprint (multi-arity-fn 'foo {} '(([a]) ([a b])))) + (pp/pprint (multi-arity-fn 'foo {} '(([a]) ([a & xs])))) + (pp/pprint (multi-arity-fn 'foo {} '(([a]) ([a [b & cs] & xs])))) + ;; CLJS-1216 + (pp/pprint (multi-arity-fn 'foo {} '(([a]) ([a b & xs])))) + ) + +(def + ^{:doc "Same as (def name (core/fn [params* ] exprs*)) or (def + name (core/fn ([params* ] exprs*)+)) with any doc-string or attrs added + to the var metadata. prepost-map defines a map with optional keys + :pre and :post that contain collections of pre or post conditions." + :arglists '([name doc-string? attr-map? [params*] prepost-map? body] + [name doc-string? attr-map? ([params*] prepost-map? body)+ attr-map?]) + :macro true} + defn (core/fn defn [&form &env name & fdecl] + ;; Note: Cannot delegate this check to def because of the call to (with-meta name ..) + (if (core/instance? #?(:clj clojure.lang.Symbol :cljs Symbol) name) + nil + (throw + #?(:clj (IllegalArgumentException. "First argument to defn must be a symbol") + :cljs (js/Error. "First argument to defn must be a symbol")))) + (core/let [m (if (core/string? (first fdecl)) + {:doc (first fdecl)} + {}) + fdecl (if (core/string? (first fdecl)) + (next fdecl) + fdecl) + m (if (map? (first fdecl)) + (conj m (first fdecl)) + m) + fdecl (if (map? (first fdecl)) + (next fdecl) + fdecl) + fdecl (if (vector? (first fdecl)) + (core/list fdecl) + fdecl) + m (if (map? (last fdecl)) + (conj m (last fdecl)) + m) + fdecl (if (map? (last fdecl)) + (butlast fdecl) + fdecl) + m (conj {:arglists (core/list 'quote (sigs fdecl))} m) + ;; no support for :inline + ;m (core/let [inline (:inline m) + ; ifn (first inline) + ; iname (second inline)] + ; ;; same as: (if (and (= 'fn ifn) (not (symbol? iname))) ...) + ; (if (if #?(:clj (clojure.lang.Util/equiv 'fn ifn) + ; :cljs (= 'fn ifn)) + ; (if #?(:clj (core/instance? clojure.lang.Symbol iname) + ; :cljs (core/instance? Symbol iname)) false true)) + ; ;; inserts the same fn name to the inline fn if it does not have one + ; (assoc m + ; :inline (cons ifn + ; (cons (clojure.lang.Symbol/intern + ; (.concat (.getName ^clojure.lang.Symbol name) "__inliner")) + ; (next inline)))) + ; m)) + m (conj (if (meta name) (meta name) {}) m)] + (core/cond + (multi-arity-fn? fdecl) + (multi-arity-fn name + (if (comp/checking-types?) + (update-in m [:jsdoc] conj "@param {...*} var_args") + m) fdecl (:def-emits-var &env)) + + (variadic-fn? fdecl) + (variadic-fn name + (if (comp/checking-types?) + (update-in m [:jsdoc] conj "@param {...*} var_args") + m) fdecl (:def-emits-var &env)) + + :else + (core/list 'def (with-meta name m) + ;;todo - restore propagation of fn name + ;;must figure out how to convey primitive hints to self calls first + (cons `fn fdecl)))))) + +#?(:clj (. (var defn) (setMacro)) + :cljs (set! (. defn -cljs$lang$macro) true)) + +(core/defn defmacro + "Like defn, but the resulting function name is declared as a + macro and will be used as a macro by the compiler when it is + called." + {:arglists '([name doc-string? attr-map? [params*] body] + [name doc-string? attr-map? ([params*] body)+ attr-map?]) + :macro true} + [&form &env name & args] + (core/let [prefix (core/loop [p (core/list (vary-meta name assoc :macro true)) args args] + (core/let [f (first args)] + (if (core/string? f) + (recur (cons f p) (next args)) + (if (map? f) + (recur (cons f p) (next args)) + p)))) + fdecl (core/loop [fd args] + (if (core/string? (first fd)) + (recur (next fd)) + (if (map? (first fd)) + (recur (next fd)) + fd))) + fdecl (if (vector? (first fdecl)) + (core/list fdecl) + fdecl) + add-implicit-args (core/fn [fd] + (core/let [args (first fd)] + (cons (vec (cons '&form (cons '&env args))) (next fd)))) + add-args (core/fn [acc ds] + (if (core/nil? ds) + acc + (core/let [d (first ds)] + (if (map? d) + (conj acc d) + (recur (conj acc (add-implicit-args d)) (next ds)))))) + fdecl (seq (add-args [] fdecl)) + decl (core/loop [p prefix d fdecl] + (if p + (recur (next p) (cons (first p) d)) + d))] + `(let [ret# ~(cons `defn decl)] + (set! (. ~name ~'-cljs$lang$macro) true) + ret#))) + +#?(:clj (. (var defmacro) (setMacro)) + :cljs (set! (. defmacro -cljs$lang$macro) true)) + +(core/defmacro resolve + "Returns the var to which a symbol will be resolved in the namespace else nil." + [quoted-sym] + (core/assert + (core/and (seq? quoted-sym) + (= 'quote (first quoted-sym))) + "Argument to resolve must be a quoted symbol") + (core/let [sym (second quoted-sym) + env &env + [var meta] (try + (core/let [var (ana/resolve-var env sym (ana/confirm-var-exists-throw)) ] + [var (ana/var-meta var)]) + (catch #?@(:clj [Throwable t] :cljs [:default e]) + [(ana/resolve-var env sym) nil])) + resolved (vary-meta (:name var) assoc ::ana/no-resolve true)] + `(when (exists? ~resolved) + (cljs.core/Var. (fn [] ~resolved) '~resolved ~meta)))) diff --git a/src/main/clojure/cljs/core/macros.clj b/src/main/clojure/cljs/core/macros.clj new file mode 100644 index 0000000000..1b0e117b1a --- /dev/null +++ b/src/main/clojure/cljs/core/macros.clj @@ -0,0 +1,43 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.core.macros + (:refer-clojure :exclude [alias]) + (:require [clojure.java.io :as io] + [cljs.vendor.clojure.tools.reader :as reader] + [cljs.vendor.clojure.tools.reader.reader-types :as readers] + [cljs.env :as env] + [cljs.analyzer :as ana] + [cljs.repl :refer [source]]) + (:import [java.io PushbackReader])) + +(defn source-fn + [x] + (when-let [m (-> x resolve meta)] + (when-let [filepath (:file m)] + (let [f (io/file filepath) + f (if (.exists f) + f + (io/resource filepath))] + (when f + (with-open [pbr (PushbackReader. (io/reader f))] + (let [rdr (readers/source-logging-push-back-reader pbr)] + (dotimes [_ (dec (:line m))] (readers/read-line rdr)) + (reader/read {:read-cond :allow :features #{:clj}} rdr)))))))) + +(defmacro import-macros [ns [& vars]] + (letfn [(->cljs-macro [[_ & rest]] + `(cljs.core/defmacro ~@rest))] + `(do + ~@(binding [*ns* (find-ns ns)] + (doall (map (comp ->cljs-macro source-fn) vars)))))) + +(defmacro alias [[_ ns] [_ alias]] + (swap! env/*compiler* assoc-in + [::namespaces (.getName *ns*) :requires alias] ns) + nil) diff --git a/src/main/clojure/cljs/core/server.clj b/src/main/clojure/cljs/core/server.clj new file mode 100644 index 0000000000..0c8923b9e4 --- /dev/null +++ b/src/main/clojure/cljs/core/server.clj @@ -0,0 +1,167 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.core.server + (:refer-clojure :exclude [with-bindings resolve-fn prepl io-prepl]) + (:require [cljs.vendor.clojure.tools.reader.reader-types :as readers] + [cljs.vendor.clojure.tools.reader :as reader] + [cljs.env :as env] + [cljs.closure :as closure] + [cljs.analyzer :as ana] + [cljs.analyzer.api :as ana-api] + [cljs.repl :as repl] + [cljs.compiler :as comp] + [cljs.tagged-literals :as tags])) + +(defmacro with-bindings [& body] + `(binding [ana/*cljs-ns* ana/*cljs-ns* + ana/*unchecked-if* ana/*unchecked-if* + ana/*unchecked-arrays* ana/*unchecked-arrays*] + ~@body)) + +(defn- resolve-fn [valf] + (if (symbol? valf) + (or (resolve valf) + (when-let [nsname (namespace valf)] + (require (symbol nsname)) + (resolve valf)) + (throw (Exception. (str "can't resolve: " valf)))) + valf)) + +(defn repl-quit? [v] + (#{":repl/quit" ":cljs/quit"} v)) + +(defn prepl + "A REPL with structured output (for programs) + reads forms to eval from in-reader (a LineNumberingPushbackReader) + Closing the input or passing the form :cljs/quit or :repl/quit will cause it + to return + + Calls out-fn with data, one of: + {:tag :ret + :val string ;;eval result + :ns ns-name-string + :ms long ;;eval time in milliseconds + :form string ;;iff successfully read + } + {:tag :out + :val string} ;chars from during-eval *out* + {:tag :err + :val string} ;chars from during-eval *err* + {:tag :tap + :val string} ;values from tap> + + You might get more than one :out or :err per eval, but exactly one :ret + tap output can happen at any time (i.e. between evals) + If during eval an attempt is made to read *in* it will read from in-reader unless :stdin is supplied +" + [repl-env {:keys [special-fns] :as opts} in-reader out-fn & {:keys [stdin]}] + (let [repl-opts (repl/repl-options repl-env) + opts (merge + {:def-emits-var true} + (closure/add-implicit-options + (merge-with (fn [a b] (if (nil? b) a b)) + repl-opts opts))) + EOF (Object.) + tapfn #(out-fn {:tag :tap :val %1}) + env (ana-api/empty-env) + special-fns (merge repl/default-special-fns special-fns) + is-special-fn? (set (keys special-fns))] + (env/ensure + (repl/maybe-install-npm-deps opts) + (comp/with-core-cljs opts + (fn [] + (with-bindings + (binding [*in* (or stdin in-reader) + *out* (PrintWriter-on #(out-fn {:tag :out :val %1}) nil) + *err* (PrintWriter-on #(out-fn {:tag :err :val %1}) nil) + repl/*repl-env* repl-env] + (let [opts (merge opts (:merge-opts (repl/setup repl-env opts)))] + (binding [repl/*repl-opts* opts] + (repl/evaluate-form repl-env env "" + (with-meta `(~'ns ~'cljs.user) {:line 1 :column 1}) identity opts) + (try + (add-tap tapfn) + (loop [] + (when (try + (let [[form s] (binding [*ns* (create-ns ana/*cljs-ns*) + reader/resolve-symbol ana/resolve-symbol + reader/*data-readers* (merge tags/*cljs-data-readers* + (ana/load-data-readers)) + reader/*alias-map* (ana/get-aliases ana/*cljs-ns*)] + (reader/read+string {:eof EOF :read-cond :allow :features #{:cljs}} + in-reader))] + (try + (when-not (identical? form EOF) + (let [start (System/nanoTime) + ret (if (and (seq? form) (is-special-fn? (first form))) + (do + ((get special-fns (first form)) repl-env env form opts) + "nil") + (repl/eval-cljs repl-env env form opts)) + ms (quot (- (System/nanoTime) start) 1000000)] + (when-not (repl-quit? ret) + (out-fn {:tag :ret + :val (if (instance? Throwable ret) + (Throwable->map ret) + ret) + :ns (name ana/*cljs-ns*) + :ms ms + :form s}) + true))) + (catch Throwable ex + (out-fn {:tag :ret :val (Throwable->map ex) + :ns (name ana/*cljs-ns*) :form s + :exception true}) + true))) + (catch Throwable ex + (out-fn {:tag :ret :val (Throwable->map ex) + :ns (name ana/*cljs-ns*) + :exception true}) + true)) + (recur))) + (finally + (remove-tap tapfn) + (repl/tear-down repl-env)))))))))))) + +(defn io-prepl + "prepl bound to *in* and *out*, suitable for use with e.g. server/repl (socket-repl). + :ret and :tap vals will be processed by valf, a fn of one argument + or a symbol naming same (default identity)" + [& {:keys [valf repl-env opts] :or {valf #(if (string? %) % (pr-str %))}}] + (let [valf (resolve-fn valf) + out *out* + lock (Object.)] + (prepl repl-env opts + (readers/source-logging-push-back-reader *in* 1 "NO_SOURCE_FILE") + #(binding [*out* out, *flush-on-newline* true, *print-readably* true] + (locking lock + (prn (cond-> %1 + (#{:ret :tap} (:tag %1)) + (assoc :val (valf (:val %1)))))))))) + +(comment + + ;; eval in order + + (defmacro clj-eval [form] + `(quote ~(eval form))) + + (require '[cljs.repl.node :as node]) + + (io-prepl :repl-env (node/repl-env)) + + ;; wait a moment for Node REPL to be ready, then eval the following + + (cljs.core.server/clj-eval + (cljs.analyzer.api/ns-resolve 'cljs.core 'first)) + + (require '[clojure.string :as string]) + (string/includes? "foo" "o") + + ) diff --git a/src/clj/cljs/env.clj b/src/main/clojure/cljs/env.cljc similarity index 51% rename from src/clj/cljs/env.clj rename to src/main/clojure/cljs/env.cljc index 09ed531d02..3aecc4c851 100644 --- a/src/clj/cljs/env.clj +++ b/src/main/clojure/cljs/env.cljc @@ -9,7 +9,8 @@ (ns ^{:doc "A namespace that exists solely to provide a place for \"compiler\" state that is accessed/maintained by many different components."} cljs.env - (:require [cljs.js-deps :refer (js-dependency-index)]) + #?(:clj (:require [cljs.js-deps :as deps] + [cljs.externs :as externs])) (:refer-clojure :exclude [ensure])) ;; bit of a misnomer, but: an atom containing a map that serves as the bag of @@ -29,6 +30,10 @@ state that is accessed/maintained by many different components."} ;; * :cljs.analyzer/constant-table - map of (currently only keyword) constant ;; values to fixed ids ;; * :cljs.analyzer/namespaces - map of symbols to "namespace" maps +;; * :cljs.analyzer/data-readers - literal map of symbols, where the first +;; symbol in each pair is a tag that will be recognized by the reader. The +;; second symbol in the pair is the fully-qualified name of a Var which will +;; be invoked by the reader to parse the form following the tag. ;; * :cljs.compiler/compiled-cljs - cache of intermediate compilation results ;; that speeds incremental builds in conjunction with source map generation ;; * :cljs.closure/compiled-cljs - cache from js file path to map of @@ -38,34 +43,48 @@ state that is accessed/maintained by many different components."} ;; implementation-dependent data. (def ^:dynamic *compiler* nil) +(defn default-compiler-env* [options] + (merge + {:cljs.analyzer/namespaces {'cljs.user {:name 'cljs.user}} + :cljs.analyzer/constant-table {} + :cljs.analyzer/data-readers {} + :cljs.analyzer/externs #?(:clj (externs/externs-map (:externs-sources options)) + :cljs nil) + :options options} + #?@(:clj [(when (and (= :nodejs (:target options)) + (not (false? (:nodejs-rt options)))) + {:node-module-index deps/native-node-modules}) + {:js-dependency-index (deps/js-dependency-index options)}]))) + (defn default-compiler-env ([] (default-compiler-env {})) ([options] - (atom {:options options - :js-dependency-index (js-dependency-index options)}))) + (atom (default-compiler-env* options)))) -(defmacro with-compiler-env - "Evaluates [body] with [env] bound as the value of the `*compiler*` var in -this namespace." - [env & body] - `(let [env# ~env - env# (cond - (map? env#) (atom env#) - (and (instance? clojure.lang.Atom env#) - (map? @env#)) env# - :default (throw (IllegalArgumentException. - (str "Compiler environment must be a map or atom containing a map, not " - (class env#)))))] - (binding [*compiler* env#] ~@body))) +#?(:clj + (defmacro with-compiler-env + "Evaluates [body] with [env] bound as the value of the `*compiler*` var in + this namespace." + [env & body] + `(let [env# ~env + env# (cond + (map? env#) (atom env#) + (and (instance? clojure.lang.Atom env#) + (map? @env#)) env# + :default (throw (IllegalArgumentException. + (str "Compiler environment must be a map or atom containing a map, not " + (class env#)))))] + (binding [*compiler* env#] ~@body)))) -(defmacro ensure - [& body] - `(let [val# *compiler*] - (if (nil? val#) - (push-thread-bindings - (hash-map (var *compiler*) (default-compiler-env)))) - (try - ~@body - (finally - (if (nil? val#) - (pop-thread-bindings)))))) +#?(:clj + (defmacro ensure + [& body] + `(let [val# *compiler*] + (if (nil? val#) + (push-thread-bindings + (hash-map (var *compiler*) (default-compiler-env)))) + (try + ~@body + (finally + (if (nil? val#) + (pop-thread-bindings))))))) diff --git a/src/main/clojure/cljs/env/macros.clj b/src/main/clojure/cljs/env/macros.clj new file mode 100644 index 0000000000..863490848e --- /dev/null +++ b/src/main/clojure/cljs/env/macros.clj @@ -0,0 +1,37 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.env.macros + (:refer-clojure :exclude [binding ensure])) + +(defmacro with-compiler-env + "Evaluates [body] with [env] bound as the value of the `*compiler*` var in + this namespace." + [env & body] + `(let [env# ~env + env# (cond + (map? env#) (atom env#) + (and (instance? cljs.core/Atom env#) (map? @env#)) env# + :default + (throw + (js/Error. + (str "Compiler environment must be a map or atom containing a map, not " + (type env#)))))] + (cljs.core/binding [cljs.env/*compiler* env#] + ~@body))) + +(defmacro ensure + [& body] + `(let [val# cljs.env/*compiler*] + (when (nil? val#) + (set! cljs.env/*compiler* (cljs.env/default-compiler-env))) + (try + ~@body + (finally + (when (nil? val#) + (set! cljs.env/*compiler* nil)))))) diff --git a/src/main/clojure/cljs/externs.clj b/src/main/clojure/cljs/externs.clj new file mode 100644 index 0000000000..e354aac745 --- /dev/null +++ b/src/main/clojure/cljs/externs.clj @@ -0,0 +1,479 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.externs + (:require [cljs.util :as util] + [cljs.js-deps :as js-deps] + [clojure.java.io :as io] + [clojure.string :as string]) + (:import [com.google.javascript.jscomp + CompilerOptions CompilerOptions$Environment SourceFile CompilerInput CommandLineRunner] + [com.google.javascript.jscomp.parsing Config$JsDocParsing] + [com.google.javascript.rhino + Node Token JSTypeExpression JSDocInfo JSDocInfo$Visibility] + [java.util.logging Level] + [java.net URL])) + +(def ^:dynamic *ignore-var* false) +(def ^:dynamic *source-file* nil) +(def ^:dynamic *goog-ns* nil) + +(defn default-externs [] + (CommandLineRunner/getBuiltinExterns CompilerOptions$Environment/BROWSER)) + +;; ------------------------------------------------------------------------------ +;; Externs Parsing + +(defn annotate + "Given a sequential list of properties [foo core baz] representing segments + of the namespace, annotate the last symbol with the type information." + [props ty] + (when (seq props) + (conj + (into [] (butlast props)) + (with-meta (last props) ty)))) + +(def token->kw + {Token/BANG :bang + Token/BLOCK :block + Token/PIPE :pipe + Token/STRINGLIT :string-lit + Token/QMARK :qmark + Token/STAR :star}) + +(defn parse-texpr [^Node root] + (when-let [token (get token->kw (.getToken root))] + (let [children (.children root)] + (merge + {:type token} + (when-not (empty? children) + {:children (vec (map parse-texpr (.children root)))}) + (when (= :string-lit token) + {:value (.getString root)}))))) + +(defn undefined? + [{:keys [type value] :as texpr}] + (and (= type :string-lit) + (= "undefined" value))) + +(defn add-prefix + "Externs inference uses :prefix meta to both resolve externs as well as generate + missing externs information. Google Closure Compiler default externs includes + nested types like webCrypto.Crypto. Add prefix information to the returned symbol to + simplify resolution later." + [type-str] + (with-meta (symbol type-str) + {:prefix (->> (string/split (name type-str) #"\.") + (map symbol) vec)})) + +(defn simplify-texpr + [texpr] + (case (:type texpr) + :string-lit (-> texpr :value add-prefix) + :star 'any + ;; TODO: qmark should probably be #{nil T} + (:qmark :bang) (simplify-texpr (-> texpr :children first)) + :pipe (let [[x y] (:children texpr)] + (if (undefined? y) + (simplify-texpr x) + 'any)) + 'any)) + +(defn get-tag [^JSTypeExpression texpr] + (some-> (.getRoot texpr) parse-texpr simplify-texpr)) + +(defn params->method-params [xs] + (let [not-opt? (complement :optional?) + required (into [] (map :name (take-while not-opt? xs))) + opts (map :name (drop-while not-opt? xs))] + (loop [ret [required] opts opts] + (if-let [opt (first opts)] + (recur (conj ret (conj (last ret) opt)) (drop 1 opts)) + (seq ret))))) + +(defn generic? [t] + (let [s (name t)] + (boolean (re-matches #"[A-Z]" s)))) + +(defn gtype->cljs-type [t] + (when t + (cond + (generic? t) 'any + (= t 'Array) 'array + :else t))) + +(defn get-params + "Return param information in JSDoc appearance order. GCL is relatively + civilized, so this isn't really a problem." + [^JSDocInfo info] + (map + (fn [n] + (let [t (.getParameterType info n)] + {:name (symbol n) + :optional? (.isOptionalArg t) + :var-args? (.isVarArgs t)})) + (.getParameterNames info))) + +(defn get-var-info [^Node node] + (when node + (let [info (.getJSDocInfo node)] + (when info + (merge + (if-let [^JSTypeExpression ty (.getType info)] + {:tag (get-tag ty)} + (if (or (.isConstructor info) (.isInterface info)) + (let [qname (symbol (.. node getFirstChild getQualifiedName))] + (cond-> {:tag 'Function} + (.isConstructor info) (merge {:ctor qname}) + (.isInterface info) (merge {:iface qname}) + (.hasBaseType info) (merge {:super (get-tag (.getBaseType info))}))) + (if (or (.hasReturnType info) + (as-> (.getParameterCount info) c + (and c (pos? c)))) + (let [arglist (get-params info) + arglists (params->method-params arglist)] + {:tag 'Function + :js-fn-var true + :ret-tag (or (some-> (.getReturnType info) + get-tag gtype->cljs-type) + 'clj-nil) + :variadic? (boolean (some :var-args? arglist)) + :max-fixed-arity (count (take-while (complement :var-args?) arglist)) + :method-params arglists + :arglists arglists})))) + {:file *source-file* + :line (.getLineno node)} + (when-let [doc (.getOriginalCommentString info)] + {:doc doc}) + (when (= JSDocInfo$Visibility/PRIVATE (.getVisibility info)) + {:private true})))))) + +(defmulti parse-extern-node + (fn [^Node node] + (.getToken node))) + +;; handle named function case (i.e. goog.modules) +;; function foo {}, the entire function is the node +(defmethod parse-extern-node Token/FUNCTION [^Node node] + (when (> (.getChildCount node) 0) + (let [ty (get-var-info node)] + (doto + (cond-> (parse-extern-node (.getFirstChild node)) + ty (-> first (annotate ty) vector)))))) + +(defmethod parse-extern-node Token/VAR [^Node node] + (when (> (.getChildCount node) 0) + (let [ty (get-var-info node)] + (cond-> (parse-extern-node (.getFirstChild node)) + ty (-> first (annotate ty) vector))))) + +(defmethod parse-extern-node Token/EXPR_RESULT [^Node node] + (when (> (.getChildCount node) 0) + (parse-extern-node (.getFirstChild node)))) + +(defmethod parse-extern-node Token/ASSIGN [^Node node] + (when (> (.getChildCount node) 0) + (let [ty (get-var-info node) + lhs (cond-> (first (parse-extern-node (.getFirstChild node))) + ty (annotate ty))] + (if (> (.getChildCount node) 1) + (let [externs + (binding [*ignore-var* true] + (parse-extern-node (.getChildAtIndex node 1)))] + (conj (map (fn [ext] (concat lhs ext)) externs) + lhs)) + [lhs])))) + +;; JavaScript name +;; function foo {}, in this case the `foo` name node +;; {"foo": bar}, in this case the `bar` name node +(defmethod parse-extern-node Token/NAME [^Node node] + (if (= Token/STRING_KEY (-> node .getParent .getToken)) + ;; if we are inside an object literal we are done + [] + ;; also check .getString - goog.module defs won't have qualified names + (let [name (or (.getQualifiedName node) (.getString node)) + lhs (when-not (string/blank? name) + (map symbol (string/split name #"\.")))] + (if (seq lhs) + (if (> (.getChildCount node) 0) + (let [externs (parse-extern-node (.getFirstChild node))] + (conj (map (fn [ext] (concat lhs ext)) externs) + lhs)) + [lhs]) + [])))) + +(defmethod parse-extern-node Token/GETPROP [^Node node] + (when-not *ignore-var* + (let [props (map symbol (string/split (.getQualifiedName node) #"\."))] + [(if-let [ty (get-var-info node)] + (annotate props ty) + props)]))) + +;; JavaScript Object literal +;; { ... } +(defmethod parse-extern-node Token/OBJECTLIT [^Node node] + (when (> (.getChildCount node) 0) + (loop [nodes (.children node) + externs []] + (if (empty? nodes) + externs + (recur (rest nodes) + (concat externs (parse-extern-node (first nodes)))))))) + +;; Object literal string key node +;; {"foo": bar} - the key and value together +(defmethod parse-extern-node Token/STRING_KEY [^Node node] + (let [lhs [(-> node .getString symbol)]] + (if (> (.getChildCount node) 0) + (let [externs (parse-extern-node (.getFirstChild node))] + (conj (map (fn [ext] (concat lhs ext)) externs) + lhs)) + [lhs]))) + +(defmethod parse-extern-node :default [node]) + +(defn parse-externs + "Returns a sequential collection of the form: + + [[foo core first] + [foo core next] + [foo core baz last] ...] + + Where the last symbol is annotated with var info via metadata. This simple + structure captures the nested form of Closure namespaces and aids + direct indexing." + [^SourceFile source-file] + (binding [*source-file* (.getName source-file)] + (let [^CompilerOptions compiler-options + (doto (CompilerOptions.) + (.setParseJsDocDocumentation + Config$JsDocParsing/INCLUDE_DESCRIPTIONS_WITH_WHITESPACE)) + closure-compiler + (doto + (let [compiler (com.google.javascript.jscomp.Compiler.)] + (com.google.javascript.jscomp.Compiler/setLoggingLevel Level/WARNING) + compiler) + (.init (list source-file) '() compiler-options)) + js-ast (CompilerInput. source-file) + ^Node root (.getAstRoot js-ast closure-compiler) + ;; TODO: switch to getFirstChild + getNext in the loop + nodes (.children root)] + (loop [nodes (cond-> nodes + ;; handle goog.modules which won't have top-levels + ;; need to look at internal children + (= Token/MODULE_BODY (some-> nodes ^Node (first) .getToken)) + (-> ^Node (first) .children)) + externs []] + (if (empty? nodes) + externs + (let [node (first nodes) + new-extern (parse-extern-node node)] + (recur (rest nodes) (concat externs new-extern)))))))) + +(defn index-externs [externs] + (reduce + (fn [m xs] + (cond-> m + (seq xs) (update-in xs merge {}))) + {} externs)) + +(defn externs-map* + ([] + (externs-map* (default-externs))) + ([sources] + (externs-map* sources + '{eval {} + global {} + goog {nodeGlobalRequire {}} + COMPILED {} + TypeError {} + Error {prototype {number {} columnNumber {}}} + ReferenceError {}})) + ([sources defaults] + (let [sources (if-not (empty? sources) + sources + (default-externs))] + (reduce + (fn [externs externs-file] + (util/map-merge + externs (index-externs (parse-externs externs-file)))) + defaults sources)))) + +(def externs-map (memoize externs-map*)) + +(defn ns-match? [ns-segs var-segs] + (or + ;; exact match (i.e. ctors) + (= ns-segs var-segs) + (and + (= (inc (count ns-segs)) (count var-segs)) + (= ns-segs (take (count ns-segs) var-segs))))) + +(defmulti parsed->defs (fn [_ module-type] module-type)) + +(defmethod parsed->defs :goog + ([externs _] + (let [grouped (group-by #(= 'exports (first %)) externs) + exports (->> (get grouped true) + (map (comp vec rest)) + (remove empty?) + set) + exported (filter exports (get grouped false))] + (reduce + (fn [m xs] + (let [sym (last xs)] + (cond-> m + (seq xs) (assoc sym (merge (meta sym) {:ns *goog-ns* :name sym}))))) + {} exported)))) + +(defmethod parsed->defs :default + ([externs _] + (let [ns-segs (into [] (map symbol (string/split (str *goog-ns*) #"\.")))] + (reduce + (fn [m xs] + ;; ignore definitions from other provided namespaces not under consideration + (if (ns-match? ns-segs xs) + (let [sym (last xs)] + (cond-> m + (seq xs) (assoc sym (merge (meta sym) {:ns *goog-ns* :name sym})))) + m)) + {} externs)))) + +(defn resource->source-file + [^URL resource] + (-> (SourceFile/builder) + (.withPath (.getPath resource)) + (.withContent (io/input-stream resource)) + (.build))) + +(defn analyze-goog-file + ([f] + (analyze-goog-file f nil)) + ([f ns] + (let [rsrc (io/resource f) + desc (js-deps/parse-js-ns (line-seq (io/reader rsrc))) + ns (or ns (-> (:provides desc) first symbol))] + (binding [*goog-ns* ns] + {:name ns + :defs (parsed->defs + (parse-externs (resource->source-file rsrc)) + (:module desc))})))) + +(defn info + "Helper for grabbing var info from an externs map. + Example: + (info externs '[Number isNaN]) + See `externs-map`" + [externs props] + (-> externs + (get-in (butlast props)) + (find (last props)) + first meta)) + +(defn filtered-externs [f] + (->> + (filter + #(= f (.getName %)) + (default-externs)) + first parse-externs index-externs)) + +(comment + (require '[clojure.java.io :as io] + '[cljs.closure :as closure] + '[clojure.pprint :refer [pprint]] + '[cljs.js-deps :as js-deps]) + + (resource->source-file (io/resource "goog/dom/dom.js")) + + (pprint + (get-in (analyze-goog-file "goog/dom/dom.js") + [:defs 'setTextContent])) + + (pprint (analyze-goog-file "goog/string/string.js")) + + (get (js-deps/js-dependency-index {}) "goog.string") + + ;; {:tag Function :ret-tag boolean} + (-> + (nth + (parse-externs + (closure/js-source-file "goog/string/string.js" + (io/input-stream (io/resource "goog/string/string.js")))) + 2) + last meta) + + (parse-externs + (closure/js-source-file "goog/string/string.js" + (io/input-stream (io/resource "goog/string/string.js")))) + + (-> (externs-map + [(closure/js-source-file "goog/string/string.js" + (io/input-stream (io/resource "goog/string/string.js")))] + {}) + (get-in '[goog string]) + (find 'numberAwareCompare_) + first meta) + + (-> (externs-map + [(closure/js-source-file "goog/date/date.js" + (io/input-stream (io/resource "goog/date/date.js")))] + {}) + (get-in '[goog date month]) + ) + + (pprint (analyze-goog-file "goog/date/date.js" 'goog.date.month)) + + (externs-map) + + (-> (externs-map) + (find 'console) first meta) + + (get (externs-map) 'Function) + + (get (externs-map) 'Error) + + ;; values are not on the prototype + (get (externs-map) 'Symbol) + (get (externs-map) 'Number) + + (-> (get-in (externs-map) '[Window prototype]) + (find 'performance) first meta) + + ;; webkit_dom.js defines Console and Window.prototype.console + (filter + (fn [s] + (let [m (-> s parse-externs index-externs)] + (get-in m '[Window prototype console]))) + (default-externs)) + + (-> + (filter + (fn [s] + (= "externs.zip//webkit_dom.js" (.getName s))) + (default-externs)) + first parse-externs index-externs + (find 'console) first meta) + + (-> + (filter + (fn [s] + (= "externs.zip//webkit_dom.js" (.getName s))) + (default-externs)) + first parse-externs index-externs + (get-in '[Console prototype]) + (find 'log) first meta) + + (require '[clojure.java.io :as io] + '[cljs.closure :as cc]) + + (-> (cc/js-source-file nil (io/file "react.ext.js")) + parse-externs index-externs + (get 'React) + (find 'Component) first meta) + ) diff --git a/src/main/clojure/cljs/foreign/node.clj b/src/main/clojure/cljs/foreign/node.clj new file mode 100644 index 0000000000..e37794de27 --- /dev/null +++ b/src/main/clojure/cljs/foreign/node.clj @@ -0,0 +1,207 @@ +(ns cljs.foreign.node + (:require [cljs.vendor.clojure.data.json :as json] + [clojure.java.io :as io] + [clojure.string :as string])) + +(defn package-json-entries + "Takes options and returns a sequence with the desired order of package.json + entries for the given :package-json-resolution mode. If no mode is provided, + defaults to :webpack (if no target is set) and :nodejs (if the target is + :nodejs)." + [opts] + {:pre [(or (= (:package-json-resolution opts) :webpack) + (= (:package-json-resolution opts) :nodejs) + (and (sequential? (:package-json-resolution opts)) + (every? string? (:package-json-resolution opts))) + (not (contains? opts :package-json-resolution)))]} + (let [modes {:nodejs ["main"] + :webpack ["browser" "module" "main"]}] + (if-let [mode (:package-json-resolution opts)] + (if (sequential? mode) mode (get modes mode)) + (case (:target opts) + :nodejs (:nodejs modes) + (:webpack modes))))) + +(comment + (= (package-json-entries {}) ["browser" "module" "main"]) + (= (package-json-entries {:package-json-resolution :nodejs}) ["main"]) + (= (package-json-entries {:package-json-resolution :webpack}) ["browser" "module" "main"]) + (= (package-json-entries {:package-json-resolution ["foo" "bar" "baz"]}) ["foo" "bar" "baz"]) + (= (package-json-entries {:target :nodejs}) ["main"]) + (= (package-json-entries {:target :nodejs :package-json-resolution :nodejs}) ["main"]) + (= (package-json-entries {:target :nodejs :package-json-resolution :webpack}) ["browser" "module" "main"]) + (= (package-json-entries {:target :nodejs :package-json-resolution ["foo" "bar"]}) ["foo" "bar"])) + +(defn- package-json? [path] + (= "package.json" (.getName (io/file path)))) + +(defn- top-level-package-json? [path] + (boolean (re-find #"node_modules[/\\](@[^/\\]+?[/\\])?[^/\\]+?[/\\]package\.json$" path))) + +;; the path sans the package.json part +;; i.e. some_lib/package.json -> some_lib +(defn- trim-package-json [s] + (if (string/ends-with? s "package.json") + (subs s 0 (- (count s) 12)) + s)) + +(defn- trim-relative [path] + (cond-> path + (string/starts-with? path "./") + (subs 2))) + +(defn- ->export-pkg-json [package-path export] + (io/file + (trim-package-json package-path) + (trim-relative export) + "package.json")) + +(defn resolve-export + "Given an export value, find the entry point based on the + :package-json-resolution value, defaults to :nodejs. Returns nil + if we couldn't resolve it." + [export opts] + (if (string? export) + export + ;; we check for require to attempt to filter out + ;; strange cases, i.e. import but no require etc. + (when (and (map? export) (contains? export "require")) + (let [resolve (:package-json-resolution opts :nodejs) + lookup (if (sequential? resolve) + (or (some #{"import" "require"} resolve) "require") + ({:webpack "import" :nodejs "require"} resolve)) + entry (get export lookup)] + (if (map? entry) + (get entry "default") + entry))))) + +(defn- export-subpaths + "Examine the export subpaths to compute subpackages. Add them to pkg-json + parameter (this is a reduce-kv helper)." + [pkg-jsons export-subpath export package-path pkg-name opts] + ;; NOTE: ignore "." exports for now + (if (= "." export-subpath) + (if-let [resolved (resolve-export export opts)] + (assoc-in pkg-jsons [package-path "main"] resolved) + pkg-jsons) + ;; technically the following logic is a bit brittle since `exports` is + ;; supposed to be used to hide the package structure. + ;; instead, here we assume the export subpath does match the library structure + ;; on disk, if we find a package.json we add it to pkg-jsons map + ;; and we synthesize "name" key based on subpath + (let [export-pkg-json-file (->export-pkg-json package-path export-subpath)] + ;; note this will ignore export wildcards etc. + (cond-> pkg-jsons + (.exists export-pkg-json-file) + (-> (assoc + (.getAbsolutePath export-pkg-json-file) + (merge + (json/read-str (slurp export-pkg-json-file)) + ;; add the name field so that path->main-name works later + (when (and (map? export) + (contains? export "require")) + {"name" (str pkg-name (string/replace export-subpath "./" "/"))})))))))) + +(defn- add-exports + "Given a list of pkg-jsons examine them for the `exports` field. `exports` + is now the preferred way to declare an entrypoint to a Node.js library. However, + for backwards compatibility it is often combined with `main`. + + `export` can also be a JS object - if so, it can define subpaths. `.` points + to main and other subpaths can be defined relative to that. + + See https://nodejs.org/api/packages.html#main-entry-point-export for more + detailed information." + [pkg-jsons opts] + (reduce-kv + (fn [pkg-jsons package-path {:strs [exports] :as pkg-json}] + (if (string? exports) + pkg-jsons + ;; map case + (reduce-kv + (fn [pkg-jsons export-subpath export] + (export-subpaths pkg-jsons export-subpath + export package-path (get pkg-json "name") opts)) + pkg-jsons exports))) + pkg-jsons pkg-jsons)) + +(defn path->main-name + "Determine whether a path is a main entrypoint in the provided package.json. + If so return the name entry provided in the package.json file." + [path [pkg-json-path {:as pkg-json :strs [name]}] opts] + (let [entries (package-json-entries opts) + entry (first (keep (partial get pkg-json) entries))] + (when-not (nil? entry) + ;; should be the only edge case in + ;; the package.json main field - Antonio + (let [entry (trim-relative entry) + entry-path (-> pkg-json-path (string/replace \\ \/) + trim-package-json (str entry))] + ;; find a package.json entry point that matches + ;; the `path` + (some (fn [candidate] + (when (= candidate (string/replace path \\ \/)) name)) + (cond-> [entry-path] + ;; if we have an entry point that doesn't end in .js or .json + ;; try to match some alternatives + (not (or (string/ends-with? entry-path ".js") + (string/ends-with? entry-path ".json"))) + (into [(str entry-path ".js") (str entry-path "/index.js") (str entry-path ".json") + (string/replace entry-path #"\.cjs$" ".js")]))))))) + +(defn- path->rel-name [path] + (-> (subs path (.lastIndexOf path "node_modules")) + (string/replace \\ \/) + (string/replace #"node_modules[\\\/]" ""))) + +(defn path->provides + "For a given path in node_modules, determine what namespaces that file would + provide to ClojureScript. Note it is assumed that we *already* processed all + package.json files and they are present via pkg-jsons parameter as we need them + to figure out the provides." + [path pkg-jsons opts] + (merge + {:file path + :module-type :es6} + ;; if the file is *not* a package.json, then compute what + ;; namespaces it :provides to ClojureScript + (when-not (package-json? path) + ;; given some path search the package.json to determine whether it is a + ;; main entry point or not + (let [pkg-json-main (some #(path->main-name path % opts) pkg-jsons)] + {:provides (let [module-rel-name (path->rel-name path) + provides (cond-> [module-rel-name (string/replace module-rel-name #"\.js(on)?$" "")] + (some? pkg-json-main) (conj pkg-json-main)) + index-replaced (string/replace module-rel-name #"[\\\/]index\.js(on)?$" "")] + (cond-> provides + (and (boolean (re-find #"[\\\/]index\.js(on)?$" module-rel-name)) + (not (some #{index-replaced} provides))) + (conj index-replaced)))})))) + +(defn get-pkg-jsons + "Given all a seq of files in node_modules return a map of all package.json + files indexed by path. Includes any `export` package.json files as well" + ([module-fseq] + (get-pkg-jsons module-fseq nil)) + ([module-fseq opts] + (add-exports + (into {} + (comp (map #(.getAbsolutePath %)) + (filter top-level-package-json?) + (map (fn [path] [path (json/read-str (slurp path))]))) + module-fseq) opts))) + +(defn node-file-seq->libs-spec* + "Given a sequence of non-nested node_module paths where the extension ends in + `.js/.json`, return lib-spec maps for each path containing at least :file, + :module-type, and :provides." + [module-fseq opts] + (let [;; a map of all the *top-level* package.json paths and their exports + ;; to the package.json contents as EDN + pkg-jsons (get-pkg-jsons module-fseq opts)] + (into [] + (comp + (map #(.getAbsolutePath %)) + ;; for each file, figure out what it will provide to ClojureScript + (map #(path->provides % pkg-jsons opts))) + module-fseq))) diff --git a/src/main/clojure/cljs/instant.clj b/src/main/clojure/cljs/instant.clj new file mode 100644 index 0000000000..81df024d1f --- /dev/null +++ b/src/main/clojure/cljs/instant.clj @@ -0,0 +1,54 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.instant + (:require [clojure.instant :as inst]) + (:import [java.time Instant OffsetDateTime ZoneOffset] + [java.time.format DateTimeFormatter DateTimeFormatterBuilder] + [java.util Locale Locale$Category])) + +(set! *warn-on-reflection* true) + +(def ^:private ^java.time.format.DateTimeFormatter utc-format + (-> (DateTimeFormatterBuilder.) + (.appendInstant 9) + (.toFormatter (Locale/getDefault Locale$Category/FORMAT)))) + +(defn- remove-last-char ^String [s] + (subs s 0 (dec (count s)))) + +(defn- print-instant + "Print a java.time.Instant as RFC3339 timestamp, always in UTC." + [^java.time.Instant instant, ^java.io.Writer w] + (.write w "#inst \"") + (.write w (remove-last-char (.format utc-format instant))) + (.write w "-00:00\"")) + +(defmethod print-method java.time.Instant + [^java.time.Instant instant, ^java.io.Writer w] + (print-instant instant w)) + +(defmethod print-dup java.time.Instant + [^java.time.Instant instant, ^java.io.Writer w] + (print-instant instant w)) + +(defn- construct-instant + "Construct a java.time.Instant, which has nanosecond precision." + [years months days hours minutes seconds nanoseconds + offset-sign offset-hours offset-minutes] + (Instant/from + (OffsetDateTime/of years months days hours minutes seconds nanoseconds + (ZoneOffset/ofHoursMinutes (* offset-sign offset-hours) (* offset-sign offset-minutes))))) + +(defn read-instant-instant + "To read an instant as a java.time.Instant, bind *data-readers* to a + map with this var as the value for the 'inst key. Instant preserves + fractional seconds with nanosecond precision. The timezone offset will + be used to convert into UTC." + [^CharSequence cs] + (inst/parse-timestamp (inst/validated construct-instant) cs)) diff --git a/src/main/clojure/cljs/js_deps.cljc b/src/main/clojure/cljs/js_deps.cljc new file mode 100644 index 0000000000..e13c8ada8a --- /dev/null +++ b/src/main/clojure/cljs/js_deps.cljc @@ -0,0 +1,400 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.js-deps + (:require [cljs.util :as util :refer [distinct-by]] + [cljs.vendor.clojure.data.json :as json] + [clojure.java.io :as io] + [clojure.string :as string]) + (:import [java.io File] + [java.net URL URLClassLoader] + [java.util.zip ZipFile ZipEntry])) + +(def ^:private java-8? (-> (System/getProperty "java.version") (string/starts-with? "1.8."))) + +(defn- classpath-files + "Returns a list of classpath files. Under Java 8, walks up the parentage + chain of RT/baseLoader, concatenating any URLs it retrieves. Under Java 9 and + later, builds file list from the java.class.path system property." + [] + (->> + (if java-8? + ; taken from pomegranate/dynapath + ; https://github.com/tobias/dynapath/blob/master/src/dynapath/util.clj + (->> (clojure.lang.RT/baseLoader) + (iterate #(.getParent ^ClassLoader %)) + (take-while identity) + reverse + (filter (partial instance? URLClassLoader)) + (mapcat #(.getURLs ^URLClassLoader %))) + (-> (System/getProperty "java.class.path") + util/split-paths)) + distinct + (map io/file))) + +(defn ^ZipFile zip-file [jar-path] + (try + (cond + (instance? File jar-path) (ZipFile. ^File jar-path) + (string? jar-path) (ZipFile. ^String jar-path)) + (catch Exception _ + nil))) + +(defn jar-entry-names* [jar-path] + (when-let [zf (zip-file jar-path)] + (with-open [z zf] + (doall (map #(.getName ^ZipEntry %) (enumeration-seq (.entries ^ZipFile z))))))) + +(def jar-entry-names (memoize jar-entry-names*)) + +(defn find-js-jar + "Returns a seq of URLs of all JavaScript resources in the given jar" + [jar-path lib-path] + (map io/resource + (filter #(and + (.endsWith ^String % ".js") + (.startsWith ^String % lib-path)) + (jar-entry-names jar-path)))) + +(defmulti to-url class) + +(defmethod to-url File [^File f] (.toURL (.toURI f))) + +(defmethod to-url URL [^URL url] url) + +(defmethod to-url String [s] (to-url (io/file s))) + +(defn find-js-fs + "finds js resources from a path on the files system" + [path] + (let [file (io/file path)] + (when (.exists file) + (map to-url (filter #(.endsWith ^String (.getName ^File %) ".js") (file-seq (io/file path))))))) + +(defn find-js-classpath + "Returns a seq of URLs of all JavaScript files on the classpath." + [path] + (->> (classpath-files) + (reduce + (fn [files jar-or-dir] + (let [name (.toLowerCase (.getName ^File jar-or-dir)) + ext (.substring name (inc (.lastIndexOf name ".")))] + (->> (when (.exists ^File jar-or-dir) + (cond + (.isDirectory ^File jar-or-dir) + (find-js-fs (str (.getAbsolutePath ^File jar-or-dir) "/" path)) + + (#{"jar" "zip"} ext) + (find-js-jar jar-or-dir path) + + :else nil)) + (remove nil?) + (into files)))) + []))) + +(defn find-js-resources [path] + "Returns a seq of URLs to all JavaScript resources on the classpath or within +a given (directory) path on the filesystem. [path] only applies to the latter +case." + (let [file (io/file path)] + (if (.exists file) + (find-js-fs path) + (find-js-classpath path)))) + +(defn parse-js-ns + "Given the lines from a JavaScript source file, parse the provide + and require statements and return them in a map. Assumes that all + provide and require statements appear before the first function + definition." + [lines] + (letfn [(conj-in [m k v] (update-in m [k] (fn [old] (conj old v))))] + (->> (for [line lines x (string/split line #";")] x) + (map string/trim) + (drop-while #(not (or (string/includes? % "goog.provide(") + (string/includes? % "goog.module(") + (string/includes? % "goog.require(") + (string/includes? % "goog.requireType(")))) + (take-while #(not (re-matches #".*=[\s]*function\(.*\)[\s]*[{].*" %))) + (map #(re-matches #".*goog\.(provide|module|require|requireType)\(['\"](.*)['\"]\)" %)) + (remove nil?) + (map #(drop 1 %)) + (reduce (fn [m ns] + (let [munged-ns (string/replace (last ns) "_" "-")] + (case (first ns) + "provide" (conj-in m :provides munged-ns) + "module" (-> m + (conj-in :provides munged-ns) + (assoc :module :goog)) + "require" (conj-in m :requires munged-ns) + "requireType" (conj-in m :require-types munged-ns)))) + {:requires [] :provides [] :require-types []})))) + +(defprotocol IJavaScript + (-foreign? [this] "Whether the Javascript represents a foreign + library (a js file that not have any goog.provide statement") + (-closure-lib? [this] "Whether the Javascript represents a Closure style + library") + (-url [this] [this opts] "The URL where this JavaScript is located. Returns nil + when JavaScript exists in memory only.") + (-relative-path [this] [this opts] "Relative path for this JavaScript.") + (-provides [this] "A list of namespaces that this JavaScript provides.") + (-requires [this] "A list of namespaces that this JavaScript requires.") + (-source [this] [this opts] "The JavaScript source string.")) + +(defn get-file [lib-spec index] + (or (:file lib-spec) + (some (fn [provide] (get-in index [provide :file])) + (:provides lib-spec)))) + +(defn lib-spec-merge [a b] + (merge a + (cond-> b + (contains? a :provides) (dissoc :provides)))) + +(defn build-index + "Index a list of dependencies by namespace and file name. There can + be zero or more namespaces provided per file. Upstream foreign libraies + will have their options merged with local foreign libraries to support + fine-grained overriding." + [deps] + (reduce + (fn [index dep] + (let [provides (:provides dep) + index' (if (seq provides) + (reduce + (fn [index' provide] + (if (:foreign dep) + (update-in index' [provide] lib-spec-merge dep) + ;; when building the dependency index, we need to + ;; avoid overwriting a CLJS dep with a CLJC dep of + ;; the same namespace - António Monteiro + (let [file (when-let [f (or (:source-file dep) (:file dep))] + (str f)) + ext (when file + (subs file (inc (string/last-index-of file "."))))] + (update-in index' [provide] + (fn [d] + (if (and (= ext "cljc") (some? d)) + d + dep)))))) + index provides) + index)] + (if (:foreign dep) + (if-let [file (get-file dep index')] + (update-in index' [file] lib-spec-merge dep) + (throw + (util/compilation-error (Exception. + (str "No :file provided for :foreign-libs spec " (pr-str dep)))))) + (assoc index' (:file dep) dep)))) + {} deps)) + +(defn dependency-order-visit + ([state ns-name] + (dependency-order-visit state ns-name [])) + ([state ns-name seen] + #_(assert (not (some #{ns-name} seen)) + (str "Circular dependency detected, " + (apply str (interpose " -> " (conj seen ns-name))))) + (if-not (some #{ns-name} seen) + (let [file (get state ns-name)] + (if (or (:visited file) (nil? file)) + state + (let [state (assoc-in state [ns-name :visited] true) + deps (:requires file) + state (reduce #(dependency-order-visit %1 %2 (conj seen ns-name)) state deps)] + (assoc state :order (conj (:order state) file))))) + state))) + +(defn- pack-string [s] + (if (string? s) + {:provides (-provides s) + :requires (-requires s) + :file (str "from_source_" (gensym) ".clj") + ::original s} + s)) + +(defn- unpack-string [m] + (or (::original m) m)) + +(defn dependency-order + "Topologically sort a collection of dependencies." + [coll] + (let [state (build-index (map pack-string coll))] + (map unpack-string + (distinct-by :provides + (:order (reduce dependency-order-visit (assoc state :order []) (keys state))))))) + + +;; Dependencies +;; ============ +;; +;; Find all dependencies from files on the classpath. Eliminates the +;; need for closurebuilder. cljs dependencies will be compiled as +;; needed. + +(defn find-url + "Given a string, returns a URL. Attempts to resolve as a classpath-relative + path, then as a path relative to the working directory or a URL string" + [path-or-url] + (or (io/resource path-or-url) + (try (io/as-url path-or-url) + (catch java.net.MalformedURLException e + false)) + (io/as-url (io/as-file path-or-url)))) + +(defn load-foreign-library* + "Given a library spec (a map containing the keys :file + and :provides), returns a map containing :provides, :requires, :file + and :url" + ([lib-spec] (load-foreign-library* lib-spec false)) + ([lib-spec cp-only?] + (let [find-func (if cp-only? io/resource find-url)] + (cond-> (assoc lib-spec :foreign true) + (:file lib-spec) + (assoc :url (find-func (:file lib-spec))) + + (:file-min lib-spec) + (assoc :url-min (find-func (:file-min lib-spec))))))) + +(def load-foreign-library (memoize load-foreign-library*)) + +(defn- library-graph-node + "Returns a map of :provides, :requires, and :url given a URL to a goog-style +JavaScript library containing provide/require 'declarations'." + ([url] (library-graph-node url nil)) + ([url lib-path] + (with-open [reader (io/reader url)] + (-> reader line-seq parse-js-ns + (merge + {:url url} + (when lib-path + {:closure-lib true :lib-path lib-path})))))) + +(defn load-library* + "Given a path to a JavaScript library, which is a directory + containing Javascript files, return a list of maps + containing :provides, :requires, :file and :url." + [path] + (->> (find-js-resources path) + (map #(library-graph-node % path)) + (filter #(seq (:provides %))))) + +(def load-library (memoize load-library*)) + +(defn library-dependencies + [{libs :libs foreign-libs :foreign-libs + ups-libs :ups-libs ups-flibs :ups-foreign-libs}] + (concat + (mapcat load-library ups-libs) ;upstream deps + ; :libs are constrained to filesystem-only at this point; see + ; `find-classpath-lib` for goog-style JS library lookup + (mapcat load-library (filter #(.exists (io/file %)) libs)) + (map #(load-foreign-library % true) ups-flibs) ;upstream deps + (map load-foreign-library foreign-libs))) + +(comment + ;; load one library + (load-library* "closure/library/third_party/closure") + ;; load all library dependencies + (library-dependencies {:libs ["closure/library/third_party/closure"]}) + (library-dependencies {:foreign-libs [{:file "http://example.com/remote.js" + :provides ["my.example"]}]}) + (library-dependencies {:foreign-libs [{:file "local/file.js" + :provides ["my.example"]}]}) + (library-dependencies {:foreign-libs [{:file "cljs/nodejs_externs.js" + :provides ["my.example"]}]})) + +;; NO LONGER NEEDED, deps.js and base.js now removed from build +;(defn goog-resource +; "Helper to disambiguate Google Closure Library resources from Google +; Closure Library Third Party resoures." +; [path] +; (first +; (filter +; (fn [res] +; (re-find #"(\/google-closure-library-0.0*|\/google-closure-library\/)" (.getPath ^URL res))) +; (enumeration-seq (.getResources (.getContextClassLoader (Thread/currentThread)) path))))) + +;; NOTE: because this looks at deps.js for indexing the Closure Library we +;; don't need to bother parsing files in Closure Library. But it's also a +;; potential source of confusion as *other* Closure style libs will need to be +;; parsed, user won't typically provide a deps.js +(defn goog-dependencies* + "Create an index of Google dependencies by namespace and file name from + goog/deps.js" + [] + (letfn [(parse-list [s] (when (> (count s) 0) + (-> (.substring ^String s 1 (dec (count s))) + (string/split #"'\s*,\s*'"))))] + (with-open [reader (io/reader (io/resource "goog/deps.js"))] + (->> (line-seq reader) + (map #(re-matches #"^goog\.addDependency\(['\"](.*)['\"],\s*\[(.*)\],\s*\[(.*)\],\s*(\{.*\})\);.*" %)) + (remove nil?) + (map #(drop 1 %)) + (remove #(.startsWith ^String (first %) "../../third_party")) + (map + (fn [[file provides requires load-opts-str]] + (let [{:strs [lang module]} + (-> (string/replace load-opts-str "'" "\"") (json/read-str)) + file' (str "goog/" file)] + (merge + {:file file' + :provides (parse-list provides) + :requires (parse-list requires) + :require-types (-> file' io/resource io/reader line-seq + parse-js-ns :require-types) + :group :goog} + (when module + {:module (keyword module)}) + (when lang + {:lang (keyword lang)}))))) + (doall))))) + +(def goog-dependencies (memoize goog-dependencies*)) + +(defn js-dependency-index + "Returns the index for all JavaScript dependencies. Lookup by + namespace or file name." + [opts] + ; (library-dependencies) will find all of the same libs returned by + ; (goog-dependencies), but the latter returns some additional/different + ; information (:file instead of :url, :group), so they're folded in last to + ; take precedence in the returned index. It is likely that + ; (goog-dependencies), special-casing of them, goog/deps.js, etc can be + ; removed entirely, but verifying that can be a fight for another day. + (build-index (concat (library-dependencies opts) (goog-dependencies)))) + +(defn find-classpath-lib + "Given [lib], a string or symbol naming a goog-style JavaScript library + (i.e. one that uses goog.provide and goog.require), look for a resource on the + classpath corresponding to [lib] and return a map via `library-graph-node` + that contains its relevant metadata. The library found on the classpath + _must_ contain a `goog.provide` that matches [lib], or this fn will return nil + and print a warning." + [lib] + (when-let [lib-resource (some-> (name lib) + (.replace \. \/) + (.replace \- \_) + (str ".js") + io/resource)] + (let [{:keys [provides] :as lib-info} (library-graph-node lib-resource)] + (if (some #{(name lib)} provides) + (assoc lib-info :closure-lib true) + (binding [*out* *err*] + (println + (format + (str "WARNING: JavaScript file found on classpath for library `%s`, " + "but does not contain a corresponding `goog.provide` declaration: %s") + lib lib-resource))))))) + +(def native-node-modules + #{"assert" "buffer_ieee754" "buffer" "child_process" "cluster" "console" + "constants" "crypto" "_debugger" "dgram" "dns" "domain" "events" "freelist" + "fs" "http" "https" "_linklist" "module" "net" "os" "path" "punycode" + "querystring" "readline" "repl" "stream" "string_decoder" "sys" "timers" + "tls" "tty" "url" "util" "vm" "zlib" "_http_server" "process" "v8"}) diff --git a/src/main/clojure/cljs/module_graph.cljc b/src/main/clojure/cljs/module_graph.cljc new file mode 100644 index 0000000000..026cad710f --- /dev/null +++ b/src/main/clojure/cljs/module_graph.cljc @@ -0,0 +1,402 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.module-graph + (:require [clojure.string :as string] + [clojure.set :as set] + [clojure.java.io :as io] + [cljs.compiler :as comp] + [cljs.util :as util])) + +(defn find-sources-for-module-entry + "Given an entry as a symbol, find all matching inputs in sources. If the + symbol ends in a *, then the symbol will be treated as a wildcard. This + function returns a set and is not order preserving. If there are no matches + returns nil." + [entry sources] + (let [m (name (comp/munge entry)) + xs (string/split m #"\.")] + (if (= "_STAR_" (last xs)) + (let [matcher (str (string/join "." (butlast xs)) ".") + matches (into #{} + (filter + (fn [source] + (when (some #(.startsWith ^String % matcher) + (map (comp str comp/munge) (:provides source))) + source))) + sources)] + (when-not (empty? matches) + matches)) + (when-let [input (some + (fn [source] + (let [matcher + (into #{} + [(name entry) (name (comp/munge entry))])] + (when (some matcher (map (comp str comp/munge) (:provides source))) + source))) + sources)] + #{input})))) + +;; Passes for constructing complete module information + +(defn normalize + "Normalize compiler :modules. All symbols in a module :entries will be + converted into munged strings." + [modules] + (reduce-kv + (fn [ret module-name module] + (assoc ret module-name + (update module :entries + (fn [es] (into #{} (map (comp str comp/munge)) es))))) + {} modules)) + +(defn add-cljs-base + "Adds :cljs-base module to compiler :modules if not already present." + [modules] + (cond-> modules + (not (contains? modules :cljs-base)) + (assoc :cljs-base {}))) + +(defn add-cljs-base-dep + "Adds :cljs-base to any module in compiler :modules with an empty :depends-on." + [modules] + (reduce-kv + (fn [ret k {:keys [depends-on] :as module-info}] + (assoc ret k + (cond-> module-info + (and (not= :cljs-base k) (empty? depends-on)) + (assoc :depends-on [:cljs-base])))) + {} modules)) + +(defn depth-of + "Compute the depth of module-name based on dependency information in + compiler :modules." + [module-name modules] + (if (= module-name :cljs-base) + 0 + (let [mns (get-in modules [module-name :depends-on])] + (if (empty? mns) + 1 + (apply max + (map (fn [mn] (+ 1 (depth-of mn modules))) mns)))))) + +(defn annotate-depths + "Annotate all modules in compiler :modules with depth information." + [modules] + (reduce-kv + (fn [ret module-name module] + (let [module' (assoc module :depth (depth-of module-name modules))] + (assoc ret module-name module'))) + {} modules)) + +(defn normalize-input [input] + (-> input + (update :provides #(into [] (map (comp str comp/munge)) %)) + (update :requires #(into [] (map (comp str comp/munge)) %)))) + +(defn index-inputs + "Index compiler inputs by :provides. If an input has multiple entries + in :provides will result in multiple entries in the map. The keys will be munged + strings not symbols." + [inputs] + (reduce + (fn [ret {:keys [provides] :as input}] + (into ret + (map + (fn [provide] + (vector + (-> provide comp/munge str) + (-> input normalize-input)))) + provides)) + {} inputs)) + +(defn validate-inputs* + [indexed path seen validated] + (let [ns (peek path) + {:keys [requires]} (get indexed ns)] + (doseq [ns' requires] + (if (contains? seen ns') + (throw + (ex-info + (str "Circular dependency detected " + (apply str (interpose " -> " (conj path ns')))) + {:cljs.closure/error :invalid-inputs + :clojure.error/phase :compilation})) + (when-not (contains? @validated ns) + (validate-inputs* indexed (conj path ns') (conj seen ns') validated)))) + (swap! validated conj ns))) + +(defn validate-inputs + "Throws on the presence of circular dependencies" + ([inputs] + (validate-inputs inputs [] #{})) + ([inputs path seen] + (let [indexed (index-inputs inputs) + validated (atom #{})] + (binding [] + (doseq [{:keys [provides]} (map normalize-input inputs)] + (let [ns (first provides)] + (validate-inputs* indexed (conj path ns) (conj seen ns) validated) + (swap! validated conj ns))))))) + +(defn ^:dynamic deps-for + "Return all dependencies for x in a graph using deps-key." + [x graph deps-key] + (let [requires (get-in graph [x deps-key])] + (try + (-> (mapcat #(deps-for % graph deps-key) requires) + (concat requires) distinct vec) + (catch Throwable t + (throw + (ex-info (str "Failed to compute deps for " x) + {:lib x :requires requires} t)))))) + +(defn deps-for-entry + "Return all dependencies for an entry using a compiler inputs index." + [entry indexed-inputs] + (map #(-> % comp/munge str) (deps-for entry indexed-inputs :requires))) + +(defn deps-for-module + "Return all dependencies of a module using compiler :modules." + [module modules] + (deps-for module modules :depends-on)) + +(defn deepest-common-parent + "Given a set of modules and a compiler :modules graph, compute the deepest + common parent module." + [modules all-modules] + (let [common-parents + (reduce set/intersection + (map #(conj (set (deps-for-module % all-modules)) %) modules))] + (apply max-key + (fn [p] (get-in all-modules [p :depth])) + common-parents))) + +(defn canonical-name + "Given an entry use indexed-inputs to return the canonical name. Throws if + entry cannot be found." + [entry indexed-inputs] + (if-let [entry (get indexed-inputs (-> entry comp/munge str))] + (-> (:provides entry) first comp/munge str) + (throw (util/compilation-error (Exception. (str "No input matching \"" entry "\"")))))) + +(defn validate-modules + "Check that a compiler :modules map does not contain user supplied duplicates. + Throws if modules fails validation." + [modules indexed-inputs] + (let [seen (atom {})] + (doseq [[module-name {:keys [entries] :as module}] modules] + (let [entries (into #{} (map #(canonical-name % indexed-inputs)) entries)] + (doseq [entry entries] + (let [seen' @seen] + (if-some [module-name' (get seen' entry)] + (throw + (util/compilation-error + (Exception. + (str "duplicate entry \"" entry "\", occurs in " module-name + " and " module-name' ". entry :provides is " + (get-in indexed-inputs [entry :provides]))))) + (swap! seen assoc entry module-name)))))))) + +(defn inputs->assigned-modules + "Given compiler inputs assign each to a single module. This is done by first + starting with :entries. Dependencies for every entry in a module are also added + to that module. Inputs may of course be assigned to several modules initially + but we must eventually choose one. User supplied module :entries are respected + but all other input assignments are computed automatically via + deepest-common-parent. This function returns a map assigning all inputs (indexed + by munged name) to a single module. Any orphan inputs will be assigned to + :cljs-base." + [inputs modules] + (let [index (index-inputs inputs) + _ (validate-modules modules index) + deps #(deps-for-entry % index) + assign1 (fn [[entry maybe-assigned]] + [entry + (if (= 1 (count maybe-assigned)) + (first maybe-assigned) + (deepest-common-parent maybe-assigned modules))]) + canon (fn [xs] (into #{} (map #(canonical-name % index)) xs)) + assigns (fn [f ms] + (binding [deps-for (memoize deps-for)] + (reduce-kv + (fn [ret module-name {:keys [entries] :as module}] + (let [entries' (canon entries)] + (reduce + (fn [ret entry] + (update ret entry (fnil conj #{}) module-name)) + ret (canon (f entries'))))) + {} ms))) + e->ms (assigns identity modules) + d->ms (assigns #(distinct (mapcat deps %)) modules) + e&d->ms (merge-with into e->ms d->ms) + orphans {:cljs-base + {:entries + (->> (reduce-kv + (fn [m k _] + (reduce dissoc m (get-in m [k :provides]))) + index e&d->ms) + vals (map (comp str comp/munge first :provides)) set)}} + o->ms (assigns identity orphans) + od->ms (assigns #(distinct (mapcat deps %)) orphans) + all->ms (merge-with into e&d->ms o->ms od->ms)] + (into {} (map assign1) all->ms))) + +(defn expand-modules + "Given compiler :modules and a dependency sorted list of compiler inputs return + a complete :modules map where all depended upon inputs are assigned." + [modules inputs] + (let [order (first + (reduce + (fn [[ret n] {:keys [provides]}] + [(merge ret + (zipmap (map (comp str comp/munge) provides) (repeat n))) + (inc n)]) + [{} 0] inputs)) + modules' (-> modules normalize add-cljs-base add-cljs-base-dep) + assigns (inputs->assigned-modules inputs + (annotate-depths modules')) + um (reduce-kv + (fn [ret entry module-name] + (update-in ret [module-name :entries] + (fnil conj #{}) entry)) + modules' assigns)] + (reduce-kv + (fn [ret module-name {:keys [entries]}] + (update-in ret [module-name :entries] #(vec (sort-by order %)))) + um um))) + +(comment + (inputs->assigned-modules inputs + (-> modules add-cljs-base add-cljs-base-dep annotate-depths)) + + (pprint + (expand-modules modules inputs)) + ) + +(defn topo-sort + "Topologically sort a graph using the given edges-key." + [graph edges-key] + (letfn [(no-incoming-edges [graph edges-key] + (->> graph + (filter + (fn [[k v]] + (every? #(not (contains? graph %)) (edges-key v)))) + (map first)))] + (when-not (empty? graph) + (let [nodes (no-incoming-edges graph edges-key) + graph' (reduce #(dissoc %1 %2) graph nodes)] + (concat nodes (topo-sort graph' edges-key)))))) + +(defn sort-modules [modules-with-base] + (into [] (map (fn [module] [module (module modules-with-base)])) + (topo-sort modules-with-base :depends-on))) + +(comment + (def ms + (sort-modules + (-> + {:cljs-base + {:output-to "out/module/base.js"} + :core + {:output-to "out/modules/core.js" + :entries '#{cljs.core}} + :landing + {:output-to "out/modules/reader.js" + :entries '#{cljs.reader} + :depends-on #{:core}}} + add-cljs-base add-cljs-base-dep))) + ) + +(defn parent? [f0 f1] + (.startsWith + (.getAbsolutePath (io/file f0)) + (.getAbsolutePath (io/file f1)))) + +;; JS modules become Closure libs that exist in the output directory. However in +;; the current indexing pipeline, these will not have an :out-file. Correct these +;; entries for module->module-uris - David + +(defn maybe-add-out-file + [{:keys [lib-path] :as ijs} {:keys [output-dir] :as opts}] + (if-not lib-path + ijs + (if (parent? lib-path output-dir) + (assoc ijs :out-file lib-path) + ijs))) + +(defn modules->module-uris + "Given a :modules map, a dependency sorted list of compiler inputs, and + compiler options return a Closure module uris map. This map will include + all inputs by leveraging expand-modules." + [modules inputs {:keys [optimizations asset-path output-dir] :as opts}] + (assert optimizations "Must supply :optimizations in opts map") + (assert (#{:advanced :simple :none :whitespace} optimizations) "Must supply valid :optimizations in opts map") + (assert output-dir "Must supply :output-dir in opts map") + (letfn [(get-uri [rel-path] + (cond->> rel-path + asset-path (str asset-path))) + (get-rel-path* [output-dir file] + (-> (.. (io/file file) getAbsoluteFile getPath) + (string/replace output-dir "") + (string/replace #"[\\/]" "/")))] + (let [get-rel-path (partial get-rel-path* + (.. (io/file output-dir) + getAbsoluteFile getPath))] + (case optimizations + :none + (into {} + (map + (fn [[module-name {:keys [entries] :as module}]] + [module-name + (into [] + (comp + (mapcat #(find-sources-for-module-entry % inputs)) + (map + (comp get-uri get-rel-path + (fn [{:keys [out-file] :as ijs}] + (if-not out-file + (throw (util/compilation-error (Exception. (str "No :out-file for IJavaScript " (pr-str ijs))))) + out-file)) + #(maybe-add-out-file % opts))) + (distinct)) + entries)])) + (expand-modules modules inputs)) + (:advanced :simple :whitespace) + (reduce-kv + (fn [ret k {:keys [output-to]}] + ;; TODO: move validation + (assert output-to + (str "Module " k " does not specify :output-to")) + (assoc ret k [(-> output-to get-rel-path get-uri)])) + {:cljs-base [(-> (or (get-in modules [:cljs-base :output-to]) + (io/file output-dir "cljs_base.js")) + get-rel-path get-uri)]} + modules))))) + +(defn modules->module-infos + "Given a :modules map return a Closure module info map which maps modules + to depended upon modules." + [modules] + (let [modules (-> modules add-cljs-base add-cljs-base-dep)] + (reduce-kv + (fn [ret module-name {:keys [depends-on] :or {depends-on []} :as module}] + (assoc ret module-name depends-on)) + {} modules))) + +(defn module-for + "Given an entry find the module it belongs to." + [entry modules] + (let [modules' (normalize modules) + entry' (str (comp/munge entry))] + (->> modules' + (some + (fn [[module-name {:keys [entries]} :as me]] + (when (some #{entry'} entries) + me))) + first))) diff --git a/src/main/clojure/cljs/repl.cljc b/src/main/clojure/cljs/repl.cljc new file mode 100644 index 0000000000..c588b4fbff --- /dev/null +++ b/src/main/clojure/cljs/repl.cljc @@ -0,0 +1,1608 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.repl + (:refer-clojure :exclude [load load-file]) + (:require [clojure.java.io :as io] + [clojure.string :as string] + [clojure.set :as set] + [cljs.vendor.clojure.data.json :as json] + [cljs.vendor.clojure.tools.reader :as reader] + [cljs.vendor.clojure.tools.reader.reader-types :as readers] + [cljs.tagged-literals :as tags] + [clojure.edn :as edn] + [cljs.util :as util] + [cljs.compiler :as comp] + [cljs.analyzer :as ana] + [cljs.analyzer.api :as ana-api] + [cljs.env :as env] + [cljs.js-deps :as deps] + [cljs.closure :as cljsc] + [cljs.source-map :as sm]) + (:import [java.io File PushbackReader FileWriter PrintWriter] + [java.net URL] + [java.util Base64] + [java.util.concurrent.atomic AtomicLong] + [clojure.lang IExceptionInfo] + [java.util.regex Pattern])) + +(def ^:dynamic *cljs-verbose* false) +(def ^:dynamic *repl-opts* nil) +(def ^:dynamic *repl-env* nil) + +(def known-repl-opts + "Set of all known REPL options." + #{:analyze-path :bind-err :caught :compiler-env :def-emits-var :eval :flush + :init :inits :need-prompt :print :print-no-newline :prompt :quit-prompt :read + :reader :repl-requires :repl-verbose :source-map-inline :watch :watch-fn + :wrap}) + +(defmacro ^:private err-out [& body] + `(binding [*out* *err*] + ~@body)) + +;; ============================================================================= +;; Copied over from clojure.main + +(defn skip-if-eol + "If the next character on stream s is a newline, skips it, otherwise + leaves the stream untouched. Returns :line-start, :stream-end, or :body + to indicate the relative location of the next character on s. The stream + must either be an instance of LineNumberingPushbackReader or duplicate + its behavior of both supporting .unread and collapsing all of CR, LF, and + CRLF to a single \\newline." + [s] + (let [c (readers/read-char s)] + (case c + \newline :line-start + nil :stream-end + (do (readers/unread s c) :body)))) + +(defn skip-whitespace + "Skips whitespace characters on stream s. Returns :line-start, :stream-end, + or :body to indicate the relative location of the next character on s. + Interprets comma as whitespace and semicolon as comment to end of line. + Does not interpret #! as comment to end of line because only one + character of lookahead is available. The stream must either be an + instance of LineNumberingPushbackReader or duplicate its behavior of both + supporting .unread and collapsing all of CR, LF, and CRLF to a single + \\newline." + [s] + (loop [c (readers/read-char s)] + (case c + \newline :line-start + nil :stream-end + \; (do (readers/read-line s) :line-start) + (if (or (Character/isWhitespace c) (identical? c \,)) + (recur (readers/read-char s)) + (do (readers/unread s c) :body))))) + +(defn repl-read + "Default :read hook for repl. Reads from *in* which must either be an + instance of LineNumberingPushbackReader or duplicate its behavior of both + supporting .unread and collapsing all of CR, LF, and CRLF into a single + \\newline. repl-read: + - skips whitespace, then + - returns request-prompt on start of line, or + - returns request-exit on end of stream, or + - reads an object from the input stream, then + - skips the next input character if it's end of line, then + - returns the object." + ([request-prompt request-exit] + (repl-read request-prompt request-exit *repl-opts*)) + ([request-prompt request-exit opts] + (let [current-in *in* + bind-in? (true? (:source-map-inline opts))] + (binding [*in* (if bind-in? + ((:reader opts)) + *in*)] + (or ({:line-start request-prompt :stream-end request-exit} + (skip-whitespace *in*)) + (let [input (reader/read {:read-cond :allow :features #{:cljs}} *in*)] + ;; Transfer 1-char buffer to original *in* + (readers/unread current-in (readers/read-char *in*)) + (skip-if-eol (if bind-in? current-in *in*)) + input)))))) + +;; ============================================================================= +;; CLJS Specifics + +(defprotocol IReplEnvOptions + (-repl-options [repl-env] "Return default REPL options for a REPL Env")) + +(defn repl-options [repl-env] + (-repl-options repl-env)) + +(defprotocol IJavaScriptEnv + (-setup [repl-env opts] "initialize the environment") + (-evaluate [repl-env filename line js] "evaluate a javascript string") + (-load [repl-env provides url] "load code at url into the environment") + (-tear-down [repl-env] "dispose of the environment")) + +(defn setup [repl-env opts] + (-setup repl-env opts)) + +(defn evaluate [repl-env filename line js] + (-evaluate repl-env filename line js)) + +(defn load [repl-env provides url] + (-load repl-env provides url)) + +(defn tear-down [repl-env] + (-tear-down repl-env)) + +(extend-type + Object + IReplEnvOptions + (-repl-options [_] nil)) + +(defprotocol IParseError + (-parse-error [repl-env error build-options] + "Given the original JavaScript error return the error to actually + use.")) + +(defprotocol IGetError + (-get-error [repl-env name env build-options] + "Given a symbol representing a var holding an error, an analysis + environment, and the REPL/compiler options return the canonical error + representation: + + {:value + :stacktrace } + + :value should be the host environment JavaScript error message string. + :stacktrace should be the host JavaScript environment stacktrace string.")) + +(defprotocol IParseStacktrace + (-parse-stacktrace [repl-env stacktrace error build-options] + "Given the original JavaScript stacktrace string, the entire original error + value and current compiler build options, parse the stacktrace into the + canonical form: + + [{:file + :function + :line + :column }*] + + :file must be a URL path (without protocol) relative to :output-dir. If + no source file can be supplied (such as REPL defs), :file may be a custom + identifier string surrounded by angle brackets, i.e. \"\".")) + +(defprotocol IPrintStacktrace + (-print-stacktrace [repl-env stacktrace error build-options] + "Implementing REPL evaluation environments are given the opportunity to + print the mapped stacktrace themselves. This permits further processing.")) + +(defn- env->opts + "Returns a hash-map containing all of the entries in [repl-env], translating +:working-dir to :output-dir." + ([repl-env] (env->opts repl-env nil)) + ([repl-env opts] + ;; some bits in cljs.closure use the options value as an ifn :-/ + (-> (into {} repl-env) + (assoc :optimizations + (or (:optimizations opts) (get repl-env :optimizations :none))) + (assoc :output-dir + (or (:output-dir opts) (get repl-env :working-dir ".repl")))))) + +(defn add-url [ijs] + (cond-> ijs + (not (contains? ijs :url)) + (assoc :url (io/resource (:file ijs))))) + +(defn ns->input [ns opts] + (or (some-> (util/ns->source ns) (ana/parse-ns opts)) + (some-> (get-in @env/*compiler* [:js-dependency-index (str ns)]) add-url) + (some-> (deps/find-classpath-lib ns)) + (throw + (ex-info (str ns " does not exist") + {::error :invalid-ns})))) + +(defn compilable? [input] + (contains? input :source-file)) + +(defn- load-sources + "Load the compiled `sources` into the REPL." + [repl-env sources opts] + (if (:output-dir opts) + ;; REPLs that read from :output-dir just need to add deps, + ;; environment will handle actual loading - David + (let [sb (StringBuffer.)] + (doseq [source sources] + (with-open [rdr (io/reader (:url source))] + (.append sb (cljsc/add-dep-string opts source)))) + (when (:repl-verbose opts) + (println (.toString sb))) + (-evaluate repl-env "" 1 (.toString sb))) + ;; REPLs that stream must manually load each dep - David + (doseq [{:keys [url provides]} sources] + (-load repl-env provides url)))) + +(defn- load-cljs-loader + "Compile and load the cljs.loader namespace if it's present in `sources`." + [repl-env sources opts] + (when-let [source (first (filter #(= (:ns %) 'cljs.loader) sources))] + (cljsc/compile-loader sources opts) + (load-sources repl-env [source] opts))) + +(defn load-namespace + "Load a namespace and all of its dependencies into the evaluation environment. + The environment is responsible for ensuring that each namespace is + loaded once and only once. Returns the compiled sources." + ([repl-env ns] (load-namespace repl-env ns nil)) + ([repl-env ns opts] + (let [ns (if (and (seq? ns) (= (first ns) 'quote)) (second ns) ns) + sources (seq + (when-not (ana/node-module-dep? ns) + (let [input (ns->input ns opts)] + (if (compilable? input) + (->> (cljsc/compile-inputs [input] + (merge (env->opts repl-env) opts)) + (remove (comp #{["goog"]} :provides))) + (map #(cljsc/source-on-disk opts %) + (cljsc/add-js-sources [input] opts))))))] + (when (:repl-verbose opts) + (println (str "load-namespace " ns " , compiled:") (map :provides sources))) + (load-sources repl-env sources opts) + sources))) + +(defn- load-dependencies + "Compile and load the given `requires` and return the compiled sources." + ([repl-env requires] + (load-dependencies repl-env requires nil)) + ([repl-env requires opts] + (->> requires + distinct + (remove ana/global-ns?) + (remove ana/external-dep?) + (mapcat #(load-namespace repl-env % opts)) + doall))) + +(defn ^File js-src->cljs-src + "Map a JavaScript output file back to the original ClojureScript source + file (.cljs or .cljc)." + [f] + (let [f (io/file f) + dir (.getParentFile f) + base-name (string/replace (.getName f) ".js" "") + cljsf (io/file dir (str base-name ".cljs"))] + (if (.exists cljsf) + cljsf + (let [cljcf (io/file dir (str base-name ".cljc"))] + (if (.exists cljcf) + cljcf))))) + +(defn read-source-map + "Return the source map for the JavaScript source file." + [f] + (when-let [smf (util/file-or-resource (str f ".map"))] + (let [ns (if (= f "cljs/core.aot.js") + 'cljs.core + (some-> (js-src->cljs-src f) ana/parse-ns :ns))] + (when ns + (as-> @env/*compiler* compiler-env + (let [t (util/last-modified smf)] + (if (or (and (= ns 'cljs.core) + (nil? (get-in compiler-env [::source-maps ns]))) + (and (not= ns 'cljs.core) + (> t (get-in compiler-env [::source-maps ns :last-modified] 0)))) + (swap! env/*compiler* assoc-in [::source-maps ns] + {:last-modified t + :source-map (sm/decode (json/read-str (slurp smf) :key-fn keyword))}) + compiler-env)) + (get-in compiler-env [::source-maps ns :source-map])))))) + +(defn ns-info + "Given a path to a js source file return the ns info for the corresponding + ClojureScript file if it exists." + [f] + (let [f' (js-src->cljs-src f)] + (when (and f' (.exists f')) + (ana/parse-ns f')))) + +(defn- mapped-line-column-call + "Given a cljs.source-map source map data structure map a generated line + and column back to the original line, column, and function called." + [source-map line column] + (let [default [line column nil]] + ;; source maps are 0 indexed for lines + (if-let [columns (get source-map (dec line))] + (vec + (map #(%1 %2) + [inc inc identity] + (map + ;; source maps are 0 indexed for columns + ;; multiple segments may exist at column + ;; the last segment seems most accurate + (last + (or + (get columns (last (filter #(<= % (dec column)) (sort (keys columns))))) + (second (first columns)))) + [:line :col :name]))) + default))) + +(defn- mapped-frame + "Given opts and a canonicalized JavaScript stacktrace frame, return the + ClojureScript frame." + [{:keys [function file line column]} opts] + (let [no-source-file? (if-not file + true + (.startsWith file "<")) + rfile (when-not no-source-file? + (io/file (URL. (.toURL (io/file (util/output-directory opts))) file))) + [sm {:keys [ns source-file] :as ns-info}] + (when-not no-source-file? + ((juxt read-source-map ns-info) rfile)) + [line' column' call] (if ns-info + (mapped-line-column-call sm line column) + [line column]) + name' (when (and ns-info function) + function) + file' (if no-source-file? + file + (string/replace + (.getCanonicalFile + (if ns-info + source-file + (io/file rfile))) + (str (System/getProperty "user.dir") File/separator) "")) + url (or (and ns-info (util/ns->source ns)) + (and file (io/resource file)))] + (merge + {:function name' + :call call + :file (if no-source-file? + (str "" + (when file + (str " " file))) + (io/file file')) + :line line' + :column column'} + (when url + {:url url})))) + +(defn mapped-stacktrace + "Given a vector representing the canonicalized JavaScript stacktrace + return the ClojureScript stacktrace. The canonical stacktrace must be + in the form: + + [{:file + :function + :line + :column }*] + + :file must be a URL path (without protocol) relative to :output-dir or a + identifier delimited by angle brackets. The returned mapped stacktrace will + also contain :url entries to the original sources if it can be determined + from the classpath." + ([stacktrace] (mapped-stacktrace stacktrace nil)) + ([stacktrace opts] + (vec + (let [mapped-frames (map (memoize #(mapped-frame % opts)) stacktrace)] + ;; take each non-nil :call and optionally merge it into :function one-level up + ;; to avoid replacing with local symbols, we only replace munged name if we can munge call symbol back to it + (map #(merge-with (fn [munged-fn-name unmunged-call-name] + (if (= munged-fn-name (string/replace (cljs.compiler/munge unmunged-call-name) "." "$")) + unmunged-call-name + munged-fn-name)) %1 %2) + (map #(dissoc % :call) mapped-frames) + (concat (rest (map #(if (:call %) + (hash-map :function (:call %)) + {}) + mapped-frames)) [{}])))))) + +(defn file-display + [file {:keys [output-dir temp-output-dir?]}] + (if temp-output-dir? + (let [canonicalize (fn [file] (.getCanonicalPath (io/file file))) + can-file (canonicalize file) + can-out (canonicalize output-dir)] + (if (.startsWith can-file can-out) + (subs can-file (inc (count can-out))) + (subs can-file (inc (.lastIndexOf can-file java.io.File/separator))))) + file)) + +(defn print-mapped-stacktrace + "Given a vector representing the canonicalized JavaScript stacktrace + print the ClojureScript stacktrace. See mapped-stacktrace." + ([stacktrace] (print-mapped-stacktrace stacktrace *repl-opts*)) + ([stacktrace opts] + (doseq [{:keys [function file line column]} + (mapped-stacktrace stacktrace opts)] + (err-out + (println "\t" + (str (when function (str function " ")) + "(" (file-display file opts) (when line (str ":" line)) (when column (str ":" column)) ")")))))) + +(comment + (def st (env/default-compiler-env)) + + (cljsc/build "samples/hello/src" + {:optimizations :none + :output-dir "samples/hello/out" + :output-to "samples/hello/out/hello.js" + :source-map true} + st) + + (env/with-compiler-env st + (mapped-stacktrace + [{:file "hello/core.js" + :function "first" + :line 6 + :column 0}] + {:output-dir "samples/hello/out"})) + + (env/with-compiler-env st + (print-mapped-stacktrace + [{:file "hello/core.js" + :function "first" + :line 6 + :column 0}] + {:output-dir "samples/hello/out"})) + + ;; URL example + + (cljsc/build "samples/hello/src" + {:optimizations :none + :output-dir "out" + :output-to "out/hello.js" + :source-map true} + st) + + (env/with-compiler-env st + (mapped-stacktrace + [{:file "cljs/core.js" + :function "first" + :line 2 + :column 1}] + {:output-dir "out"})) + + (env/with-compiler-env st + (print-mapped-stacktrace + [{:file "cljs/core.js" + :function "first" + :line 2 + :column 1}] + {:output-dir "out"})) + ) + +(defn- display-error + ([repl-env ret form opts] + (display-error repl-env ret form (constantly nil) opts)) + ([repl-env ret form f opts] + (err-out + (f) + (when-let [value (:value ret)] + (println value)) + (when-let [st (:stacktrace ret)] + (if (and (true? (:source-map opts)) + (satisfies? IParseStacktrace repl-env)) + (let [cst (try + (-parse-stacktrace repl-env st ret opts) + (catch Throwable e + (when (:repl-verbose opts) + (println "Failed to canonicalize stacktrace") + (println e))))] + (if (vector? cst) + (if (satisfies? IPrintStacktrace repl-env) + (-print-stacktrace repl-env cst ret opts) + (print-mapped-stacktrace cst opts)) + (println st))) + (println st)))))) + +(defn- bytes-to-base64-str + "Convert a byte array into a base-64 encoded string." + [^bytes bytes] + (.encodeToString (Base64/getEncoder) bytes)) + +(defn evaluate-form + "Evaluate a ClojureScript form in the JavaScript environment. Returns a + string which is the ClojureScript return value. This string may or may + not be readable by the Clojure reader." + ([repl-env env filename form] + (evaluate-form repl-env env filename form identity)) + ([repl-env env filename form wrap] + (evaluate-form repl-env env filename form wrap *repl-opts*)) + ([repl-env env filename form wrap opts] + (binding [ana/*cljs-file* filename] + (let [env (merge env + {:root-source-info {:source-type :fragment + :source-form form} + :repl-env repl-env}) + def-emits-var (:def-emits-var opts) + backup-comp @env/*compiler* + ->ast (fn [form] + (binding [ana/*analyze-deps* false] + (ana/analyze (assoc env :def-emits-var def-emits-var) + (wrap form) nil opts))) + ast (->ast form) + ast (if-not (#{:ns :ns*} (:op ast)) + ast + (let [ijs (ana/parse-ns [form])] + (cljsc/handle-js-modules opts + (deps/dependency-order + (cljsc/add-dependency-sources [ijs] opts)) + env/*compiler*) + (binding [ana/*check-alias-dupes* false] + (ana/no-warn (->ast form))))) ;; need new AST after we know what the modules are - David + wrap-js + ;; TODO: check opts as well - David + (if (:source-map repl-env) + (binding [comp/*source-map-data* + (atom {:source-map (sorted-map) + :gen-line 0}) + comp/*source-map-data-gen-col* (AtomicLong.)] + (let [js (comp/emit-str ast) + t (System/currentTimeMillis)] + (str js + "\n//# sourceURL=repl-" t ".js" + "\n//# sourceMappingURL=data:application/json;base64," + (bytes-to-base64-str + (.getBytes + (sm/encode + {(str "repl-" t ".cljs") + (:source-map @comp/*source-map-data*)} + {:lines (+ (:gen-line @comp/*source-map-data*) 3) + :file (str "repl-" t ".js") + :sources-content + [(or (:source (meta form)) + ;; handle strings / primitives without metadata + (with-out-str (pr form)))]}) + "UTF-8"))))) + (comp/emit-str ast))] + ;; NOTE: means macros which expand to ns aren't supported for now + ;; when eval'ing individual forms at the REPL - David + (when (#{:ns :ns*} (:op ast)) + (let [ast (try + (ana/no-warn (ana/analyze env form nil opts)) + (catch #?(:clj Exception :cljs js/Error) e + (reset! env/*compiler* backup-comp) + (throw e))) + sources (load-dependencies repl-env + (into (vals (:requires ast)) + (distinct (vals (:uses ast)))) + opts)] + (load-cljs-loader repl-env sources opts))) + (when *cljs-verbose* + (err-out (println wrap-js))) + (let [ret (-evaluate repl-env filename (:line (meta form)) wrap-js)] + (case (:status ret) + :error (throw + (ex-info (:value ret) + {:type :js-eval-error + :error ret + :repl-env repl-env + :form form})) + :exception (throw + (ex-info (:value ret) + {:type :js-eval-exception + :error ret + :repl-env repl-env + :form form + :js wrap-js})) + :success (:value ret))))))) + +(defn load-stream [repl-env filename res] + (let [env (ana/empty-env)] + (with-open [rdr (io/reader res)] + (doseq [form (ana/forms-seq* rdr filename)] + (let [env (assoc env :ns (ana/get-namespace ana/*cljs-ns*))] + (evaluate-form repl-env env filename form)))))) + +(defn load-file + ([repl-env f] (load-file repl-env f *repl-opts*)) + ([repl-env f opts] + (if (:output-dir opts) + (let [src (cond + (util/url? f) f + (.exists (io/file f)) (io/file f) + :else (io/resource f)) + compiled (binding [ana/*reload-macros* true] + (cljsc/handle-js-modules opts + (deps/dependency-order + (cljsc/add-dependency-sources [(ana/parse-ns src)] opts)) + env/*compiler*) + (cljsc/compile src + (assoc opts + ;; need to set opts to nil here so that we don't + ;; double up output-dir + :output-file (cljsc/src-file->target-file src nil) + :force true + :mode :interactive)))] + ;; copy over the original source file if source maps enabled + (when-let [ns (and (:source-map opts) (first (:provides compiled)))] + (spit + (io/file (io/file (util/output-directory opts)) + (util/ns->relpath ns (util/ext (:source-url compiled)))) + (slurp src))) + ;; need to load dependencies first + (let [sources (load-dependencies repl-env (:requires compiled) opts)] + (load-cljs-loader repl-env (conj sources compiled) opts)) + (-evaluate repl-env f 1 (cljsc/add-dep-string opts compiled)) + (-evaluate repl-env f 1 + (cljsc/src-file->goog-require src + {:wrap true :reload true :macros-ns (:macros-ns compiled)}))) + (binding [ana/*cljs-ns* ana/*cljs-ns*] + (let [res (if (= File/separatorChar (first f)) f (io/resource f))] + (assert res (str "Can't find " f " in classpath")) + (load-stream repl-env f res)))))) + +(defn- root-resource + "Returns the root directory path for a lib" + {:tag String} + [lib] + (str \/ + (.. (name lib) + (replace \- \_) + (replace \. \/)))) + +(defn- root-directory + "Returns the root resource path for a lib" + [lib] + (let [d (root-resource lib)] + (subs d 0 (.lastIndexOf d "/")))) + +(defn- load-path->cp-path + [path] + (let [src (if (= File/separatorChar (first path)) + path + (str (root-directory ana/*cljs-ns*) \/ path)) + src (.substring src 1)] + (or (io/resource (str src ".cljs")) + (io/resource (str src ".cljc"))))) + +(defn- wrap-fn [form] + (cond + (and (seq? form) + (#{'ns 'require 'require-macros 'refer-global 'require-global + 'use 'use-macros 'import 'refer-clojure} (first form))) + identity + + ('#{*1 *2 *3 *e} form) (fn [x] `(cljs.core.pr-str ~x)) + :else + (fn [x] + `(try + (cljs.core.pr-str + (let [ret# ~x] + (set! *3 *2) + (set! *2 *1) + (set! *1 ret#) + ret#)) + (catch :default e# + (set! *e e#) + (throw e#)))))) + +(defn- init-wrap-fn [form] + (cond + (and (seq? form) + (#{'ns 'require 'require-macros 'refer-global + 'use 'use-macros 'import 'refer-clojure} (first form))) + identity + + ('#{*1 *2 *3 *e} form) (fn [x] `(cljs.core.pr-str ~x)) + :else + (fn [x] + `(cljs.core.pr-str ~x)))) + +(defn eval-cljs + "Given a REPL evaluation environment, an analysis environment, and a + form, evaluate the form and return the result. The result is always the value + represented as a string." + ([repl-env env form] + (eval-cljs repl-env env form *repl-opts*)) + ([repl-env env form opts] + (evaluate-form repl-env + (assoc env :ns (ana/get-namespace ana/*cljs-ns*)) + "" + form + ;; the pluggability of :wrap is needed for older JS runtimes like Rhino + ;; where catching the error will swallow the original trace + ((or (:wrap opts) wrap-fn) form) + opts))) + +(defn decorate-specs [specs] + (if-let [k (some #{:reload :reload-all} specs)] + (->> specs (remove #{k}) (map #(vary-meta % assoc :reload k))) + specs)) + +(comment + (ana/canonicalize-specs + '['foo.bar '[bar.core :as bar]]) + + (ana/canonicalize-specs + '['foo.bar '[bar.core :as bar] :reload]) + + (map meta + (decorate-specs + (ana/canonicalize-specs + '['foo.bar '[bar.core :as bar] :reload]))) + ) + +;; Special REPL fns, these provide compatiblity with Clojure functions +;; that are not possible to reproduce given ClojureScript's compilation model +;; All functions should have the following signature +;; +;; (fn self +;; ([repl-env env form] +;; (self repl-env env form)) +;; ([repl-env env form opts] +;; ..)) +;; +;; repl-env - IJavaScriptEnv instance +;; env - a cljs.analyzer environment, *not* cljs.env environment +;; form - complete form entered at the repl +;; opts - REPL options, essentially augmented cljs.closure/build options + +(defn- wrap-self + "Takes a self-ish fn and returns it wrapped with exception handling. + Compiler state is restored if self-ish fn fails." + [f] + (fn g + ([a b c] + (g a b c nil)) + ([a b c d] + (let [backup-comp @env/*compiler*] + (try + (apply f [a b c d]) + (catch #?(:clj Exception :cljs js/Error) e ;;Exception + (reset! env/*compiler* backup-comp) + (throw e))))))) + +(defn- wrap-special-fns + [wfn fns] + "Wrap wfn around all (fn) values in fns hashmap." + (into {} (for [[k v] fns] [k (wfn v)]))) + +(def default-special-fns + (let [load-file-fn + (fn self + ([repl-env env form] + (self repl-env env form nil)) + ([repl-env env [_ file :as form] opts] + (load-file repl-env file opts))) + in-ns-fn + (fn self + ([repl-env env form] + (self repl-env env form nil)) + ([repl-env env [_ [quote ns-name] :as form] _] + ;; guard against craziness like '5 which wreaks havoc + (when-not (and (= quote 'quote) (symbol? ns-name)) + (throw (IllegalArgumentException. "Argument to in-ns must be a symbol."))) + (when-not (ana/get-namespace ns-name) + (swap! env/*compiler* assoc-in [::ana/namespaces ns-name] {:name ns-name}) + (-evaluate repl-env "" 1 + (str "goog.provide('" (comp/munge ns-name) "');"))) + (set! ana/*cljs-ns* ns-name))) + load-fn + (fn self + ([repl-env env form] + (self env repl-env form nil)) + ([repl-env env [_ & paths :as form] opts] + (let [cp-paths (map load-path->cp-path paths)] + (run! #(load-file repl-env % opts) cp-paths))))] + (wrap-special-fns wrap-self + {'in-ns in-ns-fn + 'clojure.core/in-ns in-ns-fn + 'load-file load-file-fn + 'clojure.core/load-file load-file-fn + 'load-namespace + (fn self + ([repl-env env form] + (self env repl-env form nil)) + ([repl-env env [_ ns :as form] opts] + (load-namespace repl-env ns opts))) + 'load load-fn + 'clojure.core/load load-fn}))) + +(defn analyze-source + "Given a source directory, analyzes all .cljs files. Used to populate + (:cljs.analyzer/namespaces compiler-env) so as to support code reflection." + ([src-dir] (analyze-source src-dir nil)) + ([src-dir opts] + (if-let [src-dir (and (not (empty? src-dir)) + (File. src-dir))] + (doseq [file (comp/cljs-files-in src-dir)] + (ana/analyze-file (str "file://" (.getAbsolutePath file)) opts))))) + +(defn repl-title [] + (println "ClojureScript" (util/clojurescript-version))) + +(defn repl-quit-prompt [] + (println "To quit, type:" :cljs/quit)) + +(defn repl-prompt [] + (print (str ana/*cljs-ns* "=> "))) + +(defn demunge + "Given a string representation of a fn class, + as in a stack trace element, returns a readable version." + [fn-name] + (clojure.lang.Compiler/demunge fn-name)) + +(def ^:private core-namespaces + #{"clojure.core" "clojure.core.reducers" "clojure.core.protocols" "clojure.data" "clojure.datafy" + "clojure.edn" "clojure.instant" "clojure.java.io" "clojure.main" "clojure.pprint" "clojure.reflect" + "clojure.repl" "clojure.set" "clojure.spec.alpha" "clojure.spec.gen.alpha" "clojure.spec.test.alpha" + "clojure.string" "clojure.template" "clojure.uuid" "clojure.walk" "clojure.xml" "clojure.zip"}) + +(defn- core-class? + [^String class-name] + (and (not (nil? class-name)) + (or (.startsWith class-name "clojure.lang.") + (contains? core-namespaces (second (re-find #"^([^$]+)\$" class-name)))))) + +(defn- file-name + "Helper to get just the file name part of a path or nil" + [^String full-path] + (when full-path + (try + (.getName (java.io.File. full-path)) + (catch Throwable t)))) + +(defn- java-loc->source + "Convert Java class name and method symbol to source symbol, either a + Clojure function or Java class and method." + [clazz method] + (if (#{'invoke 'invokeStatic} method) + (let [degen #(.replaceAll ^String % "--.*$" "") + [ns-name fn-name & nested] (->> (str clazz) (.split #"\$") (map demunge) (map degen))] + (symbol ns-name (String/join "$" ^"[Ljava.lang.String;" (into-array String (cons fn-name nested))))) + (symbol (name clazz) (name method)))) + +(defn ex-triage + "Returns an analysis of the phase, error, cause, and location of an error that occurred + based on Throwable data, as returned by Throwable->map. All attributes other than phase + are optional: + :clojure.error/phase - keyword phase indicator, one of: + :read-source :compile-syntax-check :compilation :macro-syntax-check :macroexpansion + :execution :read-eval-result :print-eval-result + :clojure.error/source - file name (no path) + :clojure.error/line - integer line number + :clojure.error/column - integer column number + :clojure.error/symbol - symbol being expanded/compiled/invoked + :clojure.error/class - cause exception class symbol + :clojure.error/cause - cause exception message + :clojure.error/spec - explain-data for spec error" + [datafied-throwable] + (let [{:keys [via trace phase] :or {phase :execution}} datafied-throwable + {:keys [type message data]} (last via) + {:keys [:clojure.spec.alpha/problems :clojure.spec.alpha/fn :clojure.spec.test.alpha/caller]} data + {:keys [:clojure.error/source] :as top-data} (:data (first via))] + (assoc + (case phase + :read-source + (let [{:keys [:clojure.error/line :clojure.error/column]} data] + (cond-> (merge (-> via second :data) top-data) + source (assoc :clojure.error/source (file-name source)) + (#{"NO_SOURCE_FILE" "NO_SOURCE_PATH"} source) (dissoc :clojure.error/source) + message (assoc :clojure.error/cause message))) + + (:compile-syntax-check :compilation :macro-syntax-check :macroexpansion) + (cond-> top-data + source (assoc :clojure.error/source (file-name source)) + (#{"NO_SOURCE_FILE" "NO_SOURCE_PATH"} source) (dissoc :clojure.error/source) + type (assoc :clojure.error/class type) + message (assoc :clojure.error/cause message) + problems (assoc :clojure.error/spec data)) + + (:read-eval-result :print-eval-result) + (let [[source method file line] (-> trace first)] + (cond-> top-data + line (assoc :clojure.error/line line) + file (assoc :clojure.error/source file) + (and source method) (assoc :clojure.error/symbol (java-loc->source source method)) + type (assoc :clojure.error/class type) + message (assoc :clojure.error/cause message))) + + :execution + (let [[source method file line] (->> trace (drop-while #(core-class? (name (first %)))) first) + file (first (remove #(or (nil? %) (#{"NO_SOURCE_FILE" "NO_SOURCE_PATH"} %)) [(:file caller) file])) + err-line (or (:line caller) line)] + (cond-> {:clojure.error/class type} + err-line (assoc :clojure.error/line err-line) + message (assoc :clojure.error/cause message) + (or fn (and source method)) (assoc :clojure.error/symbol (or fn (java-loc->source source method))) + file (assoc :clojure.error/source file) + problems (assoc :clojure.error/spec data)))) + :clojure.error/phase phase))) + +(defn ex-str + "Returns a string from exception data, as produced by ex-triage. + The first line summarizes the exception phase and location. + The subsequent lines describe the cause." + [{:keys [:clojure.error/phase :clojure.error/source :clojure.error/line :clojure.error/column + :clojure.error/symbol :clojure.error/class :clojure.error/cause :clojure.error/spec] + :as triage-data}] + (let [spec-loaded? (some? (resolve 'clojure.spec.alpha/explain-out)) + loc (str (or source "REPL") ":" (or line 1) (if column (str ":" column) "")) + class-name (name (or class "")) + simple-class (if class (or (first (re-find #"([^.])++$" class-name)) class-name)) + cause-type (if (contains? #{"Exception" "RuntimeException"} simple-class) + "" ;; omit, not useful + (str " (" simple-class ")"))] + (case phase + :read-source + (format "Syntax error reading source at (%s).%n%s%n" loc cause) + + :macro-syntax-check + (format "Syntax error macroexpanding %sat (%s).%n%s" + (if symbol (str symbol " ") "") + loc + (if (and spec spec-loaded?) + (with-out-str + ((resolve 'clojure.spec.alpha/explain-out) + (if (= @(resolve 'clojure.spec.alpha/*explain-out*) @(resolve 'clojure.spec.alpha/explain-printer)) + (update spec :clojure.spec.alpha/problems + (fn [probs] (map #(dissoc % :in) probs))) + spec))) + (format "%s%n" cause))) + + :macroexpansion + (format "Unexpected error%s macroexpanding %sat (%s).%n%s%n" + cause-type + (if symbol (str symbol " ") "") + loc + cause) + + :compile-syntax-check + (format "Syntax error%s compiling %sat (%s).%n%s%n" + cause-type + (if symbol (str symbol " ") "") + loc + cause) + + :compilation + (format "Unexpected error%s compiling %sat (%s).%n%s%n" + cause-type + (if symbol (str symbol " ") "") + loc + cause) + + :read-eval-result + (format "Error reading eval result%s at %s (%s).%n%s%n" cause-type symbol loc cause) + + :print-eval-result + (format "Error printing return value%s at %s (%s).%n%s%n" cause-type symbol loc cause) + + :execution + (if (and spec spec-loaded?) + (format "Execution error - invalid arguments to %s at (%s).%n%s" + symbol + loc + (with-out-str + ((resolve 'clojure.spec.alpha/explain-out) + (if (= @(resolve 'clojure.spec.alpha/*explain-out*) @(resolve 'clojure.spec.alpha/explain-printer)) + (update spec :clojure.spec.alpha/problems + (fn [probs] (map #(dissoc % :in) probs))) + spec)))) + (format "Execution error%s at %s(%s).%n%s%n" + cause-type + (if symbol (str symbol " ") "") + loc + cause))))) + +(defn repl-caught [e repl-env opts] + (if (and (instance? IExceptionInfo e) + (#{:js-eval-error :js-eval-exception} (:type (ex-data e)))) + (let [{:keys [type repl-env error form js]} (ex-data e)] + (case type + :js-eval-error + (display-error repl-env error form opts) + + :js-eval-exception + (display-error repl-env error form + (if (:repl-verbose opts) + #(prn "Error evaluating:" form :as js) + (constantly nil)) + opts))) + (binding [*out* *err*] + (print (-> e Throwable->map ex-triage ex-str)) + (flush)))) + +(defn repl-nil? [x] + (boolean (#{"" "nil"} x))) + +(defn run-inits [renv inits] + (doseq [{:keys [type] :as init} inits] + (case type + :init-forms + (doseq [form (:forms init)] + (eval-cljs renv (ana/empty-env) form)) + :eval-forms + (binding [*repl-opts* (merge *repl-opts* {:def-emits-var true :wrap init-wrap-fn})] + (doseq [form (:forms init)] + (let [value (eval-cljs renv (ana/empty-env) form *repl-opts*)] + (when-not (repl-nil? value) + (println value))))) + :init-script + (let [script (:script init)] + (load-stream renv (util/get-name script) script))))) + +(defn maybe-install-npm-deps [opts] + (when (:install-deps opts) + (cljsc/check-npm-deps opts) + (swap! env/*compiler* update-in [:npm-deps-installed?] + (fn [installed?] + (if-not installed? + (cljsc/maybe-install-node-deps! opts) + installed?))))) + +(defn initial-prompt [quit-prompt prompt] + (quit-prompt) + (prompt) + (flush)) + +(defn repl* + [repl-env {:keys [init inits need-prompt quit-prompt prompt flush read eval print caught reader + print-no-newline source-map-inline wrap repl-requires ::fast-initial-prompt? + compiler-env bind-err] + :or {need-prompt #(if (readers/indexing-reader? *in*) + (== (readers/get-column-number *in*) 1) + (identity true)) + fast-initial-prompt? false + quit-prompt repl-title + prompt repl-prompt + flush flush + read repl-read + eval eval-cljs + print println + caught repl-caught + reader #(readers/source-logging-push-back-reader + *in* + 1 "") + print-no-newline print + source-map-inline true + repl-requires '[[cljs.repl :refer-macros [source doc find-doc apropos dir pst]] + [cljs.pprint :refer [pprint] :refer-macros [pp]]] + bind-err true} + :as opts}] + ;; bridge clojure.tools.reader to satisfy the old contract + (when (and (find-ns 'clojure.tools.reader) + (not (find-ns 'cljs.vendor.bridge))) + (require 'cljs.vendor.bridge)) + (doseq [[unknown-opt suggested-opt] (util/unknown-opts (set (keys opts)) (set/union known-repl-opts cljsc/known-opts))] + (when suggested-opt + (println (str "WARNING: Unknown option '" unknown-opt "'. Did you mean '" suggested-opt "'?")))) + (when (true? fast-initial-prompt?) + (initial-prompt quit-prompt prompt)) + (let [repl-opts (-repl-options repl-env) + repl-requires (into repl-requires (:repl-requires repl-opts)) + {:keys [analyze-path repl-verbose warn-on-undeclared special-fns + checked-arrays static-fns fn-invoke-direct] + :as opts + :or {warn-on-undeclared true}} + (merge + {:def-emits-var true} + (cljsc/add-implicit-options + (merge-with (fn [a b] (if (nil? b) a b)) + repl-opts + opts + {:prompt prompt + :need-prompt need-prompt + :flush flush + :read read + :print print + :caught caught + :reader reader + :print-no-newline print-no-newline + :source-map-inline source-map-inline}))) + done? (atom false)] + (env/with-compiler-env (or compiler-env env/*compiler* (env/default-compiler-env opts)) + (when (:source-map opts) + (.start (Thread. (bound-fn [] (read-source-map "cljs/core.aot.js"))))) + (binding [*repl-env* repl-env + ana/*unchecked-if* false + ana/*unchecked-arrays* false + *err* (if bind-err + (cond-> *out* + (not (instance? PrintWriter *out*)) (PrintWriter.)) + *err*) + ana/*cljs-ns* ana/*cljs-ns* + *cljs-verbose* repl-verbose + ana/*cljs-warnings* + (let [warnings (opts :warnings)] + (merge + ana/*cljs-warnings* + (if (or (true? warnings) + (false? warnings)) + (zipmap (keys ana/*cljs-warnings*) (repeat warnings)) + warnings) + (zipmap + [:unprovided :undeclared-var + :undeclared-ns :undeclared-ns-form] + (repeat (if (false? warnings) + false + warn-on-undeclared))) + {:infer-warning false})) + ana/*checked-arrays* checked-arrays + ana/*cljs-static-fns* static-fns + ana/*fn-invoke-direct* (and static-fns fn-invoke-direct) + *repl-opts* opts] + (try + (let [env (assoc (ana/empty-env) :context :expr) + special-fns (merge default-special-fns special-fns) + is-special-fn? (set (keys special-fns)) + request-prompt (Object.) + request-exit (Object.) + opts (comp/with-core-cljs opts + (fn [] + (if-let [merge-opts (:merge-opts (-setup repl-env opts))] + (merge opts merge-opts) + opts))) + _ (when (= :after-setup fast-initial-prompt?) + (initial-prompt quit-prompt prompt)) + init (do + (evaluate-form repl-env env "" + `(~'set! ~'cljs.core/*print-namespace-maps* true) + identity opts) + (or init + #(evaluate-form repl-env env "" + (with-meta + `(~'ns ~'cljs.user + (:require ~@repl-requires)) + {:line 1 :column 1}) + identity opts))) + maybe-load-user-file #(when-let [user-resource (util/ns->source 'user)] + (when (= "file" (.getProtocol ^URL user-resource)) + (load-file repl-env (io/file user-resource) opts))) + read-eval-print + (fn [] + (let [input (binding [*ns* (create-ns ana/*cljs-ns*) + reader/resolve-symbol ana/resolve-symbol + reader/*data-readers* (merge tags/*cljs-data-readers* + (ana/load-data-readers)) + reader/*alias-map* (ana/get-aliases ana/*cljs-ns*)] + (try + (read request-prompt request-exit) + (catch Throwable e + (throw (ex-info nil {:clojure.error/phase :read-source} e)))))] + (or ({request-exit request-exit + :cljs/quit request-exit + request-prompt request-prompt} input) + (if (and (seq? input) (is-special-fn? (first input))) + (do + ((get special-fns (first input)) repl-env env input opts) + (print nil)) + (let [value (eval repl-env env input opts)] + (try + (print value) + (catch Throwable e + (throw (ex-info nil {:clojure.error/phase :print-eval-result} e)))))))))] + (maybe-install-npm-deps opts) + (comp/with-core-cljs opts + (fn [] + (binding [*repl-opts* opts] + (try + (when analyze-path + (if (vector? analyze-path) + (run! #(analyze-source % opts) analyze-path) + (analyze-source analyze-path opts))) + (when-let [main-ns (:main opts)] + (.start + (Thread. + (bound-fn [] (ana/analyze-file (util/ns->source main-ns)))))) + (init) + (run-inits repl-env inits) + (maybe-load-user-file) + (catch Throwable e + (caught e repl-env opts))) + (when-let [src (:watch opts)] + (.start + (Thread. + ((ns-resolve 'clojure.core 'binding-conveyor-fn) + (fn [] + (let [log-file (io/file (util/output-directory opts) "watch.log")] + (err-out (println "Watch compilation log available at:" (str log-file))) + (try + (let [log-out (FileWriter. log-file)] + (binding [*err* log-out + *out* log-out] + (cljsc/watch src (dissoc opts :watch) + env/*compiler* done?))) + (catch Throwable e + (caught e repl-env opts))))))))) + ;; let any setup async messages flush + (Thread/sleep 50) + (binding [*in* (if (true? (:source-map-inline opts)) + *in* + (reader))] + (when-not fast-initial-prompt? + (initial-prompt quit-prompt prompt)) + (loop [] + (when-not + (try + (identical? (read-eval-print) request-exit) + (catch Throwable e + (caught e repl-env opts) + nil)) + (when (need-prompt) + (prompt) + (flush)) + (recur)))))))) + (catch Throwable t + (throw + (ex-info "Unexpected error during REPL initialization" + {::error :init-failed} t))) + (finally + (reset! done? true) + (-tear-down repl-env))))))) + +(defn repl + "Generic, reusable, read-eval-print loop. By default, reads from *in* using + a c.t.r.reader-types/source-logging-push-back-reader, + writes to *out*, and prints exception summaries to *err*. If you use the + default :read hook, *in* must either be an instance of + c.t.r.reader-types/PushbackReader or duplicate its behavior of both supporting + unread and collapsing CR, LF, and CRLF into a single \\newline. Options + are sequential keyword-value pairs. The first argument is the JavaScript + evaluation environment, the second argument is an extended version of the + standard ClojureScript compiler options. In addition to ClojureScript compiler + build options it also take a set of options similar to clojure.main/repl with + adjustments for ClojureScript evalution and compilation model: + + Available clojure.main/repl style options and their defaults: + + - :init, function of no arguments, initialization hook called with + bindings for set!-able vars in place. + default: #() + + - :need-prompt, function of no arguments, called before each + read-eval-print except the first, the user will be prompted if it + returns true. + default: #(if (c.t.r.readers-types/indexing-reader? *in*) + (== (c.t.r.reader-types/get-column-number *in*) 1) + (identity true)) + + - :prompt, function of no arguments, prompts for more input. + default: repl-prompt + + - :flush, function of no arguments, flushes output + default: flush + + - :read, function of two arguments, reads from *in*: + - returns its first argument to request a fresh prompt + - depending on need-prompt, this may cause the repl to prompt + before reading again + - returns its second argument to request an exit from the repl + - else returns the next object read from the input stream + default: repl-read + + - :eval, function of one argument, returns the evaluation of its + argument. The eval function must take repl-env, the JavaScript evaluation + environment, env, the ClojureScript analysis environment, the form + and opts, the standard ClojureScript REPL/compiler options. + default: eval + + - :print, function of one argument, prints its argument to the output + default: println + + - :caught, function of three arguments, a throwable, called when + read, eval, or print throws an exception or error default. The second + argument is the JavaScript evaluation environment this permits context + sensitive handling if necessary. The third argument is opts, the standard + ClojureScript REPL/compiler options. In the case of errors or exception + in the JavaScript target, these will be thrown as + clojure.lang.IExceptionInfo instances. + default: repl-caught + + - :reader, the c.t.r reader to use. + default: c.t.r.reader-types/source-logging-push-back-reader + + - :print-no-newline, print without a newline. + default: print + + - :source-map-inline, whether inline source maps should be enabled. Most + useful in browser context. Implies using a fresh reader for each form. + default: true" + [repl-env & opts] + (assert (even? (count opts)) + "Arguments after repl-env must be interleaved key value pairs") + (repl* repl-env (apply hash-map opts))) + +;; ============================================================================= +;; ClojureScript REPL interaction support + +(def special-doc-map + '{. {:forms [(.instanceMethod instance args*) + (.-instanceField instance)] + :doc "The instance member form works for methods and fields. + They all expand into calls to the dot operator at macroexpansion time."} + ns {:forms [(name docstring? attr-map? references*)] + :doc "You must currently use the ns form only with the following caveats + + * You must use the :only form of :use + * :require supports :as, :refer, and :rename + - all options can be skipped + - in this case a symbol can be used as a libspec directly + - that is, (:require lib.foo) and (:require [lib.foo]) are both + supported and mean the same thing + - :rename specifies a map from referred var names to different + symbols (and can be used to prevent clashes) + - prefix lists are not supported + * The only options for :refer-clojure are :exclude and :rename + * :import is available for importing Google Closure classes + - ClojureScript types and records should be brought in with :use + or :require :refer, not :import ed + * Macros must be defined in a different compilation stage than the one + from where they are consumed. One way to achieve this is to define + them in one namespace and use them from another. They are referenced + via the :require-macros / :use-macros options to ns + - :require-macros and :use-macros support the same forms that + :require and :use do + + Implicit macro loading: If a namespace is required or used, and that + namespace itself requires or uses macros from its own namespace, then + the macros will be implicitly required or used using the same + specifications. Furthermore, in this case, macro vars may be included + in a :refer or :only spec. This oftentimes leads to simplified library + usage, such that the consuming namespace need not be concerned about + explicitly distinguishing between whether certain vars are functions + or macros. For example: + + (ns testme.core (:require [cljs.test :as test :refer [test-var deftest]])) + + will result in test/is resolving properly, along with the test-var + function and the deftest macro being available unqualified. + + Inline macro specification: As a convenience, :require can be given + either :include-macros true or :refer-macros [syms...]. Both desugar + into forms which explicitly load the matching Clojure file containing + macros. (This works independently of whether the namespace being + required internally requires or uses its own macros.) For example: + + (ns testme.core + (:require [foo.core :as foo :refer [foo-fn] :include-macros true] + [woz.core :as woz :refer [woz-fn] :refer-macros [app jx]])) + + is sugar for + + (ns testme.core + (:require [foo.core :as foo :refer [foo-fn]] + [woz.core :as woz :refer [woz-fn]]) + (:require-macros [foo.core :as foo] + [woz.core :as woz :refer [app jx]])) + + Auto-aliasing clojure namespaces: If a non-existing clojure.* namespace + is required or used and a matching cljs.* namespace exists, the cljs.* + namespace will be loaded and an alias will be automatically established + from the clojure.* namespace to the cljs.* namespace. For example: + + (ns testme.core (:require [clojure.test])) + + will be automatically converted to + + (ns testme.core (:require [cljs.test :as clojure.test]))"} + def {:forms [(def symbol doc-string? init?)] + :doc "Creates and interns a global var with the name + of symbol in the current namespace (*ns*) or locates such a var if + it already exists. If init is supplied, it is evaluated, and the + root binding of the var is set to the resulting value. If init is + not supplied, the root binding of the var is unaffected."} + do {:forms [(do exprs*)] + :doc "Evaluates the expressions in order and returns the value of + the last. If no expressions are supplied, returns nil."} + if {:forms [(if test then else?)] + :doc "Evaluates test. If not the singular values nil or false, + evaluates and yields then, otherwise, evaluates and yields else. If + else is not supplied it defaults to nil."} + new {:forms [(Constructor. args*) (new Constructor args*)] + :url "java_interop#new" + :doc "The args, if any, are evaluated from left to right, and + passed to the JavaScript constructor. The constructed object is + returned."} + quote {:forms [(quote form)] + :doc "Yields the unevaluated form."} + recur {:forms [(recur exprs*)] + :doc "Evaluates the exprs in order, then, in parallel, rebinds + the bindings of the recursion point to the values of the exprs. + Execution then jumps back to the recursion point, a loop or fn method."} + set! {:forms[(set! var-symbol expr) + (set! (.- instance-expr instanceFieldName-symbol) expr)] + :url "vars#set" + :doc "Used to set vars and JavaScript object fields"} + throw {:forms [(throw expr)] + :doc "The expr is evaluated and thrown."} + try {:forms [(try expr* catch-clause* finally-clause?)] + :doc "catch-clause => (catch classname name expr*) + finally-clause => (finally expr*) + Catches and handles JavaScript exceptions."} + var {:forms [(var symbol)] + :doc "The symbol must resolve to a var, and the Var object +itself (not its value) is returned. The reader macro #'x expands to (var x)."}}) + +(defn- special-doc [name-symbol] + (assoc (or (special-doc-map name-symbol) (meta (resolve name-symbol))) + :name name-symbol + :special-form true)) + +(def repl-special-doc-map + '{in-ns {:arglists ([name]) + :doc "Sets *cljs-ns* to the namespace named by the symbol, creating it if needed."} + load-file {:arglists ([name]) + :doc "Sequentially read and evaluate the set of forms contained in the file."} + load {:arglists ([& paths]) + :doc "Loads Clojure code from resources in classpath. A path is interpreted as + classpath-relative if it begins with a slash or relative to the root + directory for the current namespace otherwise."}}) + +(defn- repl-special-doc [name-symbol] + (assoc (repl-special-doc-map name-symbol) + :name name-symbol + :repl-special-function true)) + +(defmacro doc + "Prints documentation for a var or special form given its name, + or for a spec if given a keyword" + [name] + `(print + (binding [cljs.core/*print-newline* true] + (with-out-str + ~(if-let [special-name ('{& fn catch try finally try} name)] + `(doc ~special-name) + (cond + (special-doc-map name) + `(cljs.repl/print-doc (quote ~(special-doc name))) + + (repl-special-doc-map name) + `(cljs.repl/print-doc (quote ~(repl-special-doc name))) + + (keyword? name) + `(cljs.repl/print-doc {:spec ~name :doc (cljs.spec.alpha/describe ~name)}) + + (ana-api/find-ns name) + `(cljs.repl/print-doc + (quote ~(select-keys (ana-api/find-ns name) [:name :doc]))) + + (ana-api/resolve &env name) + `(cljs.repl/print-doc + (quote ~(let [var (ana-api/resolve &env name) + ns (-> var :name namespace) + m (cond-> var + (= "js" ns) + (-> :name ana-api/resolve-extern + (select-keys [:doc :arglists]) + (merge {:name name})) + (not= "js" ns) + (select-keys [:ns :name :doc :forms :arglists :macro :url]))] + (cond-> (update-in m [:name] clojure.core/name) + (:protocol-symbol var) + (assoc :protocol true + :methods + (->> (get-in var [:protocol-info :methods]) + (map (fn [[fname sigs]] + [fname {:doc (:doc + (ana-api/resolve &env + (symbol (str (:ns var)) (str fname)))) + :arglists (seq sigs)}])) + (into {}))))))))))))) + +(defmacro find-doc + "Prints documentation for any var whose documentation or name + contains a match for re-string-or-pattern" + [re-string-or-pattern] + (let [re (re-pattern re-string-or-pattern) + ms (concat + (mapcat + (fn [ns] + (map + (fn [m] + (update-in (select-keys m [:ns :name :doc :forms :arglists :macro :url]) + [:name] #(if-not (nil? %) (clojure.core/name %) %))) + (sort-by :name (vals (ana-api/ns-interns ns))))) + (ana-api/all-ns)) + (map #(select-keys (ana-api/find-ns %) [:name :doc]) (ana-api/all-ns)) + (map special-doc (keys special-doc-map))) + ms (for [m ms + :when (and (:doc m) + (or (re-find (re-matcher re (:doc m))) + (re-find (re-matcher re (str (:name m))))))] + m)] + `(doseq [m# (quote ~ms)] + (cljs.repl/print-doc m#)))) + +(defn source-fn + "Returns a string of the source code for the given symbol, if it can + find it. This requires that the symbol resolve to a Var defined in + a namespace for which the .clj is in the classpath. Returns nil if + it can't find the source. For most REPL usage, 'source' is more + convenient. + + Example: (source-fn 'filter)" + [env x] + (when-let [v (ana-api/resolve env x)] + (when-let [filepath (:file v)] + (let [f (io/file filepath) + f (if (.exists f) + f + (io/resource filepath))] + (when f + (with-open [pbr (PushbackReader. (io/reader f))] + (let [rdr (readers/source-logging-push-back-reader pbr)] + (dotimes [_ (dec (:line v))] (readers/read-line rdr)) + (binding [reader/*alias-map* identity + reader/*data-readers* (merge tags/*cljs-data-readers* + (ana/load-data-readers))] + (-> (reader/read {:read-cond :allow :features #{:cljs}} rdr) + meta :source))))))))) + +(comment + (def cenv (env/default-compiler-env)) + (def aenv (assoc-in (ana/empty-env) [:ns :name] 'cljs.user)) + + (binding [ana/*cljs-ns* 'cljs.user] + (env/with-compiler-env cenv + (comp/with-core-cljs {} + (fn [] + (source-fn aenv 'cljs.core/first))))) + ) + +(defmacro source + "Prints the source code for the given symbol, if it can find it. + This requires that the symbol resolve to a Var defined in a + namespace for which the .cljs is in the classpath. + + Example: (source filter)" + [n] + `(println ~(or (source-fn &env n) (str "Source not found")))) + +(defn- named-publics-vars + "Gets the public vars in a namespace that are not anonymous." + [ns] + (->> (ana-api/ns-publics ns) + (remove (comp :anonymous val)) + (map key))) + +(defmacro apropos + "Given a regular expression or stringable thing, return a seq of all +public definitions in all currently-loaded namespaces that match the +str-or-pattern." + [str-or-pattern] + (let [matches? (if (instance? Pattern str-or-pattern) + #(re-find str-or-pattern (str %)) + #(.contains (str %) (str str-or-pattern)))] + `(quote + ~(sort + (mapcat + (fn [ns] + (let [ns-name (str ns)] + (map #(symbol ns-name (str %)) + (filter matches? (named-publics-vars ns))))) + (ana-api/all-ns)))))) + +(defn- resolve-ns + "Resolves a namespace symbol to a namespace by first checking to see if it + is a namespace alias." + [ns-sym] + (or (get-in @env/*compiler* [::ana/namespaces ana/*cljs-ns* :requires ns-sym]) + (get-in @env/*compiler* [::ana/namespaces ana/*cljs-ns* :require-macros ns-sym]) + ns-sym)) + +(defmacro dir + "Prints a sorted directory of public vars in a namespace" + [ns] + `(doseq [sym# (quote ~(sort (named-publics-vars (resolve-ns ns))))] + (println sym#))) + +(defmacro pst + ([] `(pst *e)) + ([e] + (let [{:keys [repl-env] :as env} &env] + (when (and e repl-env) + (when-let [ret (if (satisfies? IGetError repl-env) + (-get-error repl-env e env *repl-opts*) + (edn/read-string + (evaluate-form repl-env env "" + `(when ~e + (pr-str + {:value (str ~e) + :stacktrace (.-stack ~e)})))))] + (display-error repl-env + (if (satisfies? IParseError repl-env) + (-parse-error repl-env ret *repl-opts*) + ret) + nil *repl-opts*)))))) diff --git a/src/main/clojure/cljs/repl/bootstrap.clj b/src/main/clojure/cljs/repl/bootstrap.clj new file mode 100644 index 0000000000..a4e86270ba --- /dev/null +++ b/src/main/clojure/cljs/repl/bootstrap.clj @@ -0,0 +1,53 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.repl.bootstrap + (:require [cljs.repl :as repl])) + +(defn install-repl-goog-require + "Install a version of goog.require that supports namespace reloading. + IMPORTANT: must be invoked *after* loading cljs.core." + [repl-env env] + ;; monkey patch goog.provide - it throws when namespaces are loaded multiple times + ;; we never care how many times a namespace is loaded it doesn't matter if + ;; Google Closure Library or ClojureScript + (repl/evaluate-form repl-env env "" + '(when-not (.-isProvided__ js/goog) + (set! (.-isProvided__ js/goog) js/goog.isProvided_))) + (repl/evaluate-form repl-env env "" + '(set! (.-isProvided_ js/goog) (fn [x] false))) + ;; monkey-patch goog.require + (repl/evaluate-form repl-env env "" + '(when-not (.-require__ js/goog) + (set! (.-require__ js/goog) js/goog.require))) + (repl/evaluate-form repl-env env "" + '(set! (.-require js/goog) + (fn [src reload] + (when (= reload "reload-all") + (set! (.-cljsReloadAll_ js/goog) true)) + (let [reload? (or reload (.-cljsReloadAll_ js/goog))] + (when reload? + ;; check for new-ish private goog/debugLoader + (if (some? goog/debugLoader_) + (let [path (.getPathFromDeps_ goog/debugLoader_ src)] + (cljs.core/js-delete (.-written_ goog/debugLoader_) path) + (cljs.core/js-delete (.-written_ goog/debugLoader_) + (str js/goog.basePath path))) + ;; legacy approach + (let [path (cljs.core/unchecked-get js/goog.dependencies_.nameToPath src)] + (cljs.core/js-delete js/goog.dependencies_.visited path) + (cljs.core/js-delete js/goog.dependencies_.written path) + (cljs.core/js-delete js/goog.dependencies_.written + (str js/goog.basePath path))))) + (let [ret (.require__ js/goog src)] + (when (= reload "reload-all") + (set! (.-cljsReloadAll_ js/goog) false)) + ;; handle requires from Closure Library goog.modules + (if (js/goog.isInModuleLoader_) + (js/goog.module.getInternal_ src) + ret))))))) diff --git a/src/main/clojure/cljs/repl/browser.clj b/src/main/clojure/cljs/repl/browser.clj new file mode 100644 index 0000000000..9b333127e4 --- /dev/null +++ b/src/main/clojure/cljs/repl/browser.clj @@ -0,0 +1,521 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.repl.browser + (:refer-clojure :exclude [loaded-libs]) + (:require [clojure.java.io :as io] + [clojure.java.browse :as browse] + [clojure.string :as string] + [clojure.edn :as edn] + [cljs.vendor.clojure.data.json :as json] + [cljs.util :as util] + [cljs.closure :as cljsc] + [cljs.repl :as repl] + [cljs.cli :as cli] + [cljs.repl.server :as server] + [cljs.stacktrace :as st] + [cljs.analyzer :as ana] + [cljs.build.api :as build] + [clojure.string :as str]) + (:import [java.util.concurrent Executors ConcurrentHashMap])) + +(def ^:dynamic browser-state nil) +(def ^:dynamic ordering nil) +(def ^:dynamic es nil) +(def outs (ConcurrentHashMap.)) + +(defn thread-name [] + (let [name (.getName (Thread/currentThread))] + (if (string/starts-with? name "nREPL") "main" name))) + +(def ext->mime-type + {".html" "text/html" + ".css" "text/css" + + ".ttf" "font/ttf" + ".otf" "font/otf" + + ".pdf" "application/pdf" + + ".jpg" "image/jpeg" + ".png" "image/png" + ".gif" "image/gif" + ".svg" "image/svg+xml" + + ".mp4" "video/mp4" + ".m4a" "audio/m4a" + ".m4v" "video/mp4" + ".mp3" "audio/mpeg" + ".mpeg" "video/mpeg" + ".wav" "audio/wav" + + ".js" "text/javascript" + ".json" "application/json" + ".clj" "text/x-clojure" + ".cljs" "text/x-clojure" + ".cljc" "text/x-clojure" + ".edn" "text/x-clojure" + ".map" "application/json" + ".wasm" "application/wasm"}) + +(def mime-type->encoding + {"text/html" "UTF-8" + "text/css" "UTF-8" + + "font/ttf" "ISO-8859-1" + "font/otf" "ISO-8859-1" + + "application/pdf" "ISO-8859-1" + + "image/jpeg" "ISO-8859-1" + "image/png" "ISO-8859-1" + "image/gif" "ISO-8859-1" + "image/svg+xml" "UTF-8" + + "video/mp4" "ISO-8859-1" + "audio/m4a" "ISO-8859-1" + "audio/mpeg" "ISO-8859-1" + "video/mpeg" "ISO-8859-1" + "audio/wav" "ISO-8859-1" + + "text/javascript" "UTF-8" + "text/x-clojure" "UTF-8" + "application/json" "UTF-8" + "application/wasm" "ISO-8859-1"}) + +(defn- set-return-value-fn + "Save the return value function which will be called when the next + return value is received." + [f] + (swap! browser-state (fn [old] (assoc old :return-value-fn f)))) + +(defn send-for-eval + "Given a form and a return value function, send the form to the + browser for evaluation. The return value function will be called + when the return value is received." + ([form return-value-fn] + (send-for-eval @(server/connection) form return-value-fn)) + ([conn form return-value-fn] + (set-return-value-fn return-value-fn) + (server/send-and-close conn 200 + (json/write-str + {"repl" (thread-name) + "form" form}) + "application/json"))) + +(defn- return-value + "Called by the server when a return value is received." + [val] + (when-let [f (:return-value-fn @browser-state)] + (f val))) + +(defn repl-client-js [] + (slurp (:client-js @browser-state))) + +(defn send-repl-client-page + [{:keys [path] :as request} conn opts] + (if-not browser-state + (server/send-404 conn path) + (server/send-and-close conn 200 + (str + "" + "" + "" + "") + "text/html"))) + +(defn default-index [output-to] + (str + "" + "" + "" + "" + "" + "" + "" + "
    " + "" + "" + "
    " + "

    Welcome to the ClojureScript browser REPL.

    " + "

    This page hosts your REPL and application evaluation environment. " + "Validate the connection by typing (js/alert \"Hello CLJS!\") in the REPL.

    " + "

    To provide your own custom page, place an index.html file in " + "the REPL launch directory, starting with this template:

    " + "
    "
    +    "<!DOCTYPE html>\n"
    +    "<html>\n"
    +    "  <head>\n"
    +    "    <meta charset=\"UTF-8\">\n"
    +    "  </head>\n"
    +    "  <body>\n"
    +    "    <script src=\"" output-to "\" type=\"text/javascript\"></script>\n"
    +    "  </body>\n"
    +    "</html>\n"
    +    "
    " + "
    " + "" + "")) + +(defn- path->mime-type [ext->mime-type path default] + (let [lc-path (str/lower-case path) + last-dot (.lastIndexOf path ".")] + (if (pos? last-dot) + (-> lc-path + (subs last-dot) + (ext->mime-type default)) + default))) + +(defn send-static + [{path :path :as request} conn + {:keys [static-dir output-dir host port gzip? compiler-opts] :or {output-dir "out"} :as opts}] + (let [output-dir (when-not (.isAbsolute (io/file output-dir)) output-dir)] + (if (and static-dir (not= "/favicon.ico" path)) + (let [path (if (= "/" path) "/index.html" path) + local-path + (cond-> + (seq (for [x (if (string? static-dir) [static-dir] static-dir) + :when (.exists (io/file (str x path)))] + (str x path))) + (complement nil?) first) + local-path + (if (nil? local-path) + (cond + (re-find #".jar" path) + (io/resource (second (string/split path #".jar!/"))) + (string/includes? path (System/getProperty "user.dir")) + (io/file (string/replace path (str (System/getProperty "user.dir") "/") "")) + (#{"/cljs-logo-icon-32.png" "/cljs-logo.svg"} path) + (io/resource (subs path 1)) + :else nil) + local-path)] + (cond + local-path + (let [mime-type (path->mime-type ext->mime-type path "text/plain") + encoding (mime-type->encoding mime-type "UTF-8")] + (server/send-and-close conn 200 (slurp local-path :encoding encoding) + mime-type encoding (and gzip? (or (= "text/javascript" mime-type) + (= "application/wasm" mime-type))))) + + ;; "/index.html" doesn't exist, provide our own + (= path "/index.html") + (server/send-and-close conn 200 + (default-index (str output-dir "/main.js")) + "text/html" "UTF-8") + + ;; "/main.js" doesn't exist, provide our own + (= path (cond->> "/main.js" output-dir (str "/" output-dir ))) + (let [closure-defines (-> `{"goog.json.USE_NATIVE_JSON" true + clojure.browser.repl/HOST ~host + clojure.browser.repl/PORT ~port} + (merge (:closure-defines @browser-state)) + cljsc/normalize-closure-defines + json/write-str) + preloads (when-let [preloads (:preloads compiler-opts)] + (mapv (fn [ns-symb] + (str "document.write('');")) + preloads))] + (server/send-and-close conn 200 + (str "var CLOSURE_UNCOMPILED_DEFINES = " closure-defines ";\n" + "var CLOSURE_NO_DEPS = true;\n" + "document.write('');\n" + "document.write('');\n" + (when (.exists (io/file output-dir "cljs_deps.js")) + (str "document.write('');\n")) + "document.write('');\n" + (when preloads (str/join "\n" preloads)) + "document.write('');\n") + "text/javascript" "UTF-8")) + + :else (server/send-404 conn path))) + (server/send-404 conn path)))) + +(server/dispatch-on :get + (fn [{:keys [path]} _ _] + (.startsWith path "/repl")) + send-repl-client-page) + +(server/dispatch-on :get + (fn [{:keys [path]} _ _] + (or (= path "/") (path->mime-type ext->mime-type path nil))) + send-static) + +(defmulti handle-post (fn [m _ _ ] (:type m))) + +(server/dispatch-on :post (constantly true) handle-post) + +(defmethod handle-post :ready [_ conn _] + (send-via es ordering (fn [_] {:expecting nil :fns {}})) + ;; browser refresh, reset connq + (locking server/lock + (.clear server/connq)) + (send-for-eval conn + (binding [ana/*cljs-warnings* + (assoc ana/*cljs-warnings* + :undeclared-var false)] + (cljsc/-compile + '[(set! *print-fn* clojure.browser.repl/repl-print) + (set! *print-err-fn* clojure.browser.repl/repl-print) + (set! *print-newline* true) + (when (pos? (count clojure.browser.repl/print-queue)) + (clojure.browser.repl/flush-print-queue! + @clojure.browser.repl/xpc-connection))] {})) + identity)) + +(defn add-in-order [{:keys [expecting fns]} order f] + {:expecting (or expecting order) + :fns (assoc fns order f)}) + +(defn run-in-order [{:keys [expecting fns]}] + (loop [order expecting fns fns] + (if-let [f (get fns order)] + (do + (f) + (recur (inc order) (dissoc fns order))) + {:expecting order :fns fns}))) + +(defn constrain-order + "Elements to be printed in the REPL will arrive out of order. Ensure + that they are printed in the correct order." + [order f] + (send-via es ordering add-in-order order f) + (send-via es ordering run-in-order)) + +(defmethod handle-post :print [{:keys [repl content order]} conn _] + (constrain-order order + (fn [] + (binding [*out* (or (and repl (.get outs repl)) *out*)] + (print (read-string content)) + (.flush *out*)))) + (server/send-and-close conn 200 "ignore__")) + +(defmethod handle-post :result [{:keys [content order]} conn _] + (constrain-order order + (fn [] + (return-value content) + (server/set-connection conn)))) + +(defn browser-eval + "Given a string of JavaScript, evaluate it in the browser and return a map representing the + result of the evaluation. The map will contain the keys :type and :value. :type can be + :success, :exception, or :error. :success means that the JavaScript was evaluated without + exception and :value will contain the return value of the evaluation. :exception means that + there was an exception in the browser while evaluating the JavaScript and :value will + contain the error message. :error means that some other error has occured." + [form] + (let [return-value (promise)] + (send-for-eval form + (fn [val] (deliver return-value val))) + (let [ret @return-value] + (try + (read-string ret) + (catch Exception e + {:status :error + :value (str "Could not read return value: " ret)}))))) + +(defn load-javascript + "Accepts a REPL environment, a list of namespaces, and a URL for a + JavaScript file which contains the implementation for the list of + namespaces. Will load the JavaScript file into the REPL environment + if any of the namespaces have not already been loaded from the + ClojureScript REPL." + [repl-env provides url] + (browser-eval (slurp url))) + +(defn serve [{:keys [host port output-dir] :as opts}] + (println "Serving HTTP on" host "port" port) + (binding [ordering (agent {:expecting nil :fns {}}) + es (Executors/newFixedThreadPool 16) + server/state (atom {:socket nil})] + (server/start + (merge opts + {:static-dir (cond-> ["." "out/"] output-dir (conj output-dir)) + :gzip? true})))) + +;; ============================================================================= +;; BrowserEnv + +(def lock (Object.)) + +(defn- waiting-to-connect-message [url] + (print-str "Waiting for browser to connect to" url "...")) + +(defn- maybe-browse-url + ([base-url] + (maybe-browse-url base-url false)) + ([base-url new-window] + (try + (browse/browse-url + (cond-> base-url + new-window (str "?rel=" (System/currentTimeMillis)))) + (catch Throwable t + (if-some [error-message (not-empty (.getMessage t))] + (println "Failed to launch a browser:\n" error-message "\n") + (println "Could not launch a browser.\n")) + (println "You can instead launch a non-browser REPL (Node or Nashorn).\n") + (println "You can disable automatic browser launch with this REPL option") + (println " :launch-browser false") + (println "and you can specify the listen IP address with this REPL option") + (println " :host \"127.0.0.1\"\n") + (println (waiting-to-connect-message base-url))))) +) + +(defn setup [{:keys [working-dir launch-browser new-window server-state] :as repl-env} {:keys [output-dir] :as opts}] + (locking lock + (when-not (:socket @server-state) + (binding [browser-state (:browser-state repl-env) + ordering (:ordering repl-env) + es (:es repl-env) + server/state (:server-state repl-env)] + (swap! browser-state + (fn [old] + (assoc old :client-js + (cljsc/create-client-js-file + {:optimizations :simple + :output-dir working-dir} + (io/file working-dir "brepl_client.js")) + :closure-defines (:closure-defines opts)))) + ;; TODO: this could be cleaner if compiling forms resulted in a + ;; :output-to file with the result of compiling those forms - David + (when (and output-dir (not (.exists (io/file output-dir "clojure" "browser" "repl" "preload.js")))) + (let [target (io/file output-dir "brepl_deps.js")] + (util/mkdirs target) + (spit target + (build/build + '[(require '[clojure.browser.repl.preload])] + (merge (dissoc (select-keys opts cljsc/known-opts) :modules) + {:opts-cache "brepl_opts.edn"}))))) + (server/start repl-env) + (let [base-url (str "http://" (:host repl-env) ":" (:port repl-env))] + (if launch-browser + (maybe-browse-url base-url new-window) + (println (waiting-to-connect-message base-url))))))) + (.put outs (thread-name) *out*) + (swap! server-state update :listeners inc)) + +(defrecord BrowserEnv [] + repl/IJavaScriptEnv + (-setup [this opts] + (setup this opts)) + (-evaluate [this _ _ js] + (binding [browser-state (:browser-state this) + ordering (:ordering this) + es (:es this) + server/state (:server-state this)] + (browser-eval js))) + (-load [this provides url] + (load-javascript this provides url)) + (-tear-down [this] + (.remove outs (thread-name)) + (let [server-state (:server-state this)] + (when (zero? (:listeners (swap! server-state update :listeners dec))) + (binding [server/state server-state] (server/stop)) + (when-not (.isShutdown (:es this)) + (.shutdownNow (:es this)))))) + repl/IReplEnvOptions + (-repl-options [this] + {:browser-repl true + :repl-requires + '[[clojure.browser.repl] [clojure.browser.repl.preload]] + ::repl/fast-initial-prompt? :after-setup + :cljs.cli/commands + {:groups {::repl {:desc "browser REPL options"}} + :init + {["-H" "--host"] + {:group ::repl + :fn #(-> %1 + (assoc-in [:repl-env-options :host] %2) + (assoc-in [:options :closure-defines 'clojure.browser.repl/HOST] %2)) + :arg "address" + :doc "Address to bind"} + ["-p" "--port"] + {:group ::repl + :fn #(let [port (Integer/parseInt %2)] + (-> %1 + (assoc-in [:repl-env-options :port] port) + (assoc-in [:options :closure-defines 'clojure.browser.repl/PORT] port))) + :arg "number" + :doc "Port to bind"}}}}) + repl/IParseStacktrace + (-parse-stacktrace [this st err opts] + (st/parse-stacktrace this st err opts)) + repl/IGetError + (-get-error [this e env opts] + (edn/read-string + (repl/evaluate-form this env "" + `(when ~e + (pr-str + {:ua-product (clojure.browser.repl/get-ua-product) + :value (str ~e) + :stacktrace (.-stack ~e)})))))) + +(defn repl-env* + [{:keys [output-dir host port] :or {host "localhost" port 9000} :as opts}] + (merge (BrowserEnv.) + {:host host + :port port + :launch-browser true + :new-window false + :working-dir (->> [".repl" (util/clojurescript-version)] + (remove empty?) (string/join "-")) + :static-dir (cond-> ["." "out/"] output-dir (conj output-dir)) + :preloaded-libs [] + :src "src/" + :browser-state (atom {:return-value-fn nil + :client-js nil}) + :ordering (agent {:expecting nil :fns {}}) + :es (Executors/newFixedThreadPool 16) + :server-state + (atom + {:socket nil + :listeners 0})} + opts)) + +(defn repl-env + "Create a browser-connected REPL environment. + + Options: + + port: The port on which the REPL server will run. Defaults to 9000. + launch-browser: A Boolean indicating whether a browser should be automatically + launched connecting back to the terminal REPL. Defaults to true. + working-dir: The directory where the compiled REPL client JavaScript will + be stored. Defaults to \".repl\" with a ClojureScript version + suffix, eg. \".repl-0.0-2138\". + static-dir: List of directories to search for static content. Defaults to + [\".\" \"out/\"]. + src: The source directory containing user-defined cljs files. Used to + support reflection. Defaults to \"src/\". + " + [& {:as opts}] + (repl-env* opts)) + +(defn -main [& args] + (apply cli/main repl-env args)) + +(comment + + (require '[cljs.repl :as repl]) + (require '[cljs.repl.browser :as browser]) + (def env (browser/repl-env)) + (repl/repl env) + ;; simulate the browser with curl + ;; curl -v -d "ready" http://127.0.0.1:9000 + ClojureScript:> (+ 1 1) + ;; curl -v -d "2" http://127.0.0.1:9000 + + ) diff --git a/src/main/clojure/cljs/repl/node.clj b/src/main/clojure/cljs/repl/node.clj new file mode 100644 index 0000000000..a147808bdb --- /dev/null +++ b/src/main/clojure/cljs/repl/node.clj @@ -0,0 +1,253 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.repl.node + (:require [clojure.string :as string] + [clojure.java.io :as io] + [cljs.util :as util] + [cljs.analyzer :as ana] + [cljs.compiler :as comp] + [cljs.repl :as repl] + [cljs.repl.bootstrap :as bootstrap] + [cljs.cli :as cli] + [cljs.closure :as closure] + [cljs.vendor.clojure.data.json :as json]) + (:import [java.net Socket] + [java.lang StringBuilder] + [java.io File BufferedReader BufferedWriter IOException] + [java.lang ProcessBuilder Process] + [java.util.concurrent ConcurrentHashMap LinkedBlockingQueue])) + +(def lock (Object.)) +(def results (ConcurrentHashMap.)) +(def outs (ConcurrentHashMap.)) +(def errs (ConcurrentHashMap.)) + +(defn thread-name [] + (let [name (.getName (Thread/currentThread))] + (if (string/starts-with? name "nREPL") "main" name))) + +(defn create-socket [^String host port] + (let [socket (Socket. host (int port)) + in (io/reader socket) + out (io/writer socket)] + {:socket socket :in in :out out})) + +(defn close-socket [s] + (.close (:in s)) + (.close (:out s)) + (.close (:socket s))) + +(defn write [^BufferedWriter out ^String js] + (.write out js) + (.write out (int 0)) ;; terminator + (.flush out)) + +(defn ^String read-response [^BufferedReader in] + (let [sb (StringBuilder.)] + (loop [sb sb c (.read in)] + (case c + -1 (throw (IOException. "Stream closed")) + 0 (str sb) + (do + (.append sb (char c)) + (recur sb (.read in))))))) + +(defn node-eval + "Evaluate a JavaScript string in the Node REPL process." + [repl-env js] + (let [tname (thread-name) + {:keys [out]} @(:socket repl-env)] + (write out (json/write-str {:type "eval" :repl tname :form js})) + (let [result (.take ^LinkedBlockingQueue (.get results tname))] + (condp = (:status result) + "success" + {:status :success + :value (:value result)} + + "exception" + {:status :exception + :value (:value result)})))) + +(defn load-javascript + "Load a Closure JavaScript file into the Node REPL process." + [repl-env provides url] + (node-eval repl-env + (str "goog.require('" (comp/munge (first provides)) "')"))) + +(defn seq->js-array [v] + (str "[" (apply str (interpose ", " (map pr-str v))) "]")) + +(defn platform-path [v] + (str "path.join.apply(null, " (seq->js-array v) ")")) + +(defn- alive? [proc] + (try (.exitValue proc) false (catch IllegalThreadStateException _ true))) + +(defn- event-loop [^Process proc in] + ;; we really do want system-default encoding here + (while (alive? proc) + (try + (let [res (read-response in)] + (try + (let [{:keys [type repl value] :or {repl "main"} :as event} + (json/read-str res :key-fn keyword)] + (case type + "result" + (.offer (.get results repl) event) + (when-let [stream (.get (if (= type "out") outs errs) repl)] + (.write stream value 0 (.length ^String value)) + (.flush stream)))) + (catch Throwable _ + (.write *out* res 0 (.length res)) + (.flush *out*)))) + (catch IOException e + (when (and (alive? proc) (not (.contains (.getMessage e) "Stream closed"))) + (.printStackTrace e *err*)))))) + +(defn- build-process + [opts repl-env input-src] + (let [xs (cond-> [(get opts :node-command "node")] + (:debug-port repl-env) (conj (str "--inspect=" (:debug-port repl-env)))) + proc (-> (ProcessBuilder. (into-array xs)) (.redirectInput input-src))] + (when-let [path-fs (:path repl-env)] + (.put (.environment proc) + "NODE_PATH" + (string/join File/pathSeparator + (map #(.getAbsolutePath (io/as-file %)) path-fs)))) + proc)) + +(defn setup + ([repl-env] (setup repl-env nil)) + ([{:keys [host port socket state] :as repl-env} opts] + (let [tname (thread-name)] + (.put results tname (LinkedBlockingQueue.)) + (.put outs tname *out*) + (.put errs tname *err*)) + (locking lock + (when-not @socket + (let [output-dir (io/file (util/output-directory opts)) + _ (.mkdirs output-dir) + of (io/file output-dir "node_repl.js") + _ (spit of + (string/replace (slurp (io/resource "cljs/repl/node_repl.js")) + "var PORT = 5001;" + (str "var PORT = " (:port repl-env) ";"))) + proc (.start (build-process opts repl-env of)) + env (ana/empty-env) + core (io/resource "cljs/core.cljs") + ;; represent paths as vectors so we can emit JS arrays, this is to + ;; paper over Windows issues with minimum hassle - David + path (.getPath (.getCanonicalFile output-dir)) + [fc & cs] (rest (util/path-seq path)) ;; remove leading empty string + root (.substring path 0 (+ (.indexOf path fc) (count fc))) + root-path (vec (cons root cs)) + rewrite-path (conj root-path "goog")] + (reset! (:proc repl-env) proc) + (loop [r nil] + (when-not (= r "ready") + (Thread/sleep 50) + (try + (reset! socket (create-socket host port)) + (catch Exception e)) + (if @socket + (recur (read-response (:in @socket))) + (recur nil)))) + (.start (Thread. (bound-fn [] (event-loop proc (:in @socket))))) + ;; compile cljs.core & its dependencies, goog/base.js must be available + ;; for bootstrap to load, use new closure/compile as it can handle + ;; resources in JARs + (let [core-js (closure/compile core + (assoc opts :output-file + (closure/src-file->target-file + core (dissoc opts :output-dir)))) + deps (closure/add-dependencies opts core-js)] + ;; output unoptimized code and only the deps file for all compiled + ;; namespaces, we don't need the bootstrap target file + (apply closure/output-unoptimized + (assoc (assoc opts :target :none) + :output-to (.getPath (io/file output-dir "node_repl_deps.js"))) + deps)) + ;; bootstrap, replace __dirname as __dirname won't be set + ;; properly due to how we are running it - David + (node-eval repl-env + (-> (slurp (io/resource "cljs/bootstrap_nodejs.js")) + (string/replace "path.resolve(__dirname, \"..\", \"base.js\")" + (platform-path (conj rewrite-path "bootstrap" ".." "base.js"))) + (string/replace + "path.join(\".\", \"..\", src)" + (str "path.join(" (platform-path rewrite-path) ", src)")) + (string/replace "path.resolve(__dirname, \"..\", src)" + (str "path.join(" (platform-path rewrite-path) ", src)")) + (string/replace + "var CLJS_ROOT = \".\";" + (str "var CLJS_ROOT = " (platform-path root-path) ";")))) + ;; load the deps file so we can goog.require cljs.core etc. + (node-eval repl-env + (str "require(" + (platform-path (conj root-path "node_repl_deps.js")) + ")")) + ;; load cljs.core, setup printing + (repl/evaluate-form repl-env env "" + '(do + (.require js/goog "cljs.core") + (enable-console-print!))) + (bootstrap/install-repl-goog-require repl-env env) + (node-eval repl-env + (str "goog.global.CLOSURE_UNCOMPILED_DEFINES = " + (json/write-str (:closure-defines opts)) ";"))))) + (swap! state update :listeners inc))) + +(defrecord NodeEnv [host port path socket proc state] + repl/IReplEnvOptions + (-repl-options [this] + {:output-dir ".cljs_node_repl" + :target :nodejs}) + repl/IParseError + (-parse-error [_ err _] + (assoc err :value nil)) + repl/IJavaScriptEnv + (-setup [this opts] + (setup this opts)) + (-evaluate [this filename line js] + (node-eval this js)) + (-load [this provides url] + (load-javascript this provides url)) + (-tear-down [this] + (swap! state update :listeners dec) + (locking lock + (when (zero? (:listeners @state)) + (let [sock @socket] + (when-not (.isClosed (:socket sock)) + (write (:out sock) ":cljs/quit") + (while (alive? @proc) (Thread/sleep 50)) + (close-socket sock))))) + (let [tname (thread-name)] + (.remove results tname) + (.remove outs tname) + (.remove errs tname)))) + +(defn repl-env* [options] + (let [{:keys [host port path debug-port]} + (merge + {:host "localhost" + :port (+ 49000 (rand-int 10000))} + options)] + (assoc + (NodeEnv. host port path + (atom nil) (atom nil) (atom {:listeners 0})) + :debug-port debug-port))) + +(defn repl-env + "Construct a Node.js evalution environment. Can supply :host, :port + and :path (a vector used as the NODE_PATH)." + [& {:as options}] + (repl-env* options)) + +(defn -main [& args] + (apply cli/main repl-env args)) diff --git a/src/main/clojure/cljs/repl/node_repl.js b/src/main/clojure/cljs/repl/node_repl.js new file mode 100644 index 0000000000..8a8c6ef0d7 --- /dev/null +++ b/src/main/clojure/cljs/repl/node_repl.js @@ -0,0 +1,114 @@ +/** + * Copyright (c) Rich Hickey. All rights reserved. + * The use and distribution terms for this software are covered by the + * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) + * which can be found in the file epl-v10.html at the root of this distribution. + * By using this software in any fashion, you are agreeing to be bound by + * the terms of this license. + * You must not remove this notice, or any other, from this software. + */ + +process.env.NODE_DISABLE_COLORS = true; +var net = require("net"); +var vm = require("vm"); +var dom = require("domain").create(); +var PORT = 5001; +var repl = null; + +try { + require("source-map-support").install(); +} catch(err) { +} + +var server = net.createServer(function (socket) { + var buffer = "", + ret = null, + err = null; + + socket.write("ready"); + socket.write("\0"); + + socket.setEncoding("utf8"); + + process.stdout.write = function(chunk, encoding, fd) { + var args = Array.prototype.slice.call(arguments, 0); + args[0] = JSON.stringify({type: "out", repl: repl, value: chunk}); + socket.write.apply(socket, args); + socket.write("\0"); + }; + + process.stderr.write = (function(write) { + return function(chunk, encoding, fd) { + var args = Array.prototype.slice.call(arguments, 0); + args[0] = JSON.stringify({type: "err", repl: repl, value: chunk}); + socket.write.apply(socket, args); + socket.write("\0"); + }; + })(process.stderr.write); + + + dom.on("error", function(ue) { + console.error(ue.stack); + }); + + socket.on("data", function(data) { + if(data[data.length-1] != "\0") { + buffer += data; + } else { + if(buffer.length > 0) { + data = buffer + data; + buffer = ""; + } + + if(data) { + // not sure how \0's are getting through - David + data = data.replace(/\0/g, ""); + + if(":cljs/quit" == data) { + server.close(); + socket.unref(); + return; + } else { + try { + dom.run(function () { + var obj = JSON.parse(data); + repl = obj.repl; + ret = vm.runInThisContext(obj.form, "repl"); + }); + } catch (e) { + err = e; + } + } + } + + if(err) { + socket.write(JSON.stringify({ + type: "result", + repl: repl, + status: "exception", + value: cljs.repl.error__GT_str(err) + })); + } else if(ret !== undefined && ret !== null) { + socket.write(JSON.stringify({ + type: "result", + repl: repl, + status: "success", + value: ret.toString() + })); + } else { + socket.write(JSON.stringify({ + type: "result", + repl: repl, + status: "success", + value: null + })); + } + + ret = null; + err = null; + + socket.write("\0"); + } + }); + +}).listen(PORT); diff --git a/src/clj/cljs/repl/reflect.clj b/src/main/clojure/cljs/repl/reflect.clj similarity index 83% rename from src/clj/cljs/repl/reflect.clj rename to src/main/clojure/cljs/repl/reflect.clj index fb96707774..48302db567 100644 --- a/src/clj/cljs/repl/reflect.clj +++ b/src/main/clojure/cljs/repl/reflect.clj @@ -1,3 +1,11 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + (ns cljs.repl.reflect (:refer-clojure :exclude [macroexpand]) (:require [cljs.repl.server :as server] @@ -29,8 +37,9 @@ (update-in [:name] str) (update-in [:method-params] #(str (vec %))))))) -(defn macroexpand [form] +(defn macroexpand "Fully expands a cljs macro form." + [form] (let [mform (analyzer/macroexpand-1 {} form)] (if (identical? form mform) mform diff --git a/src/main/clojure/cljs/repl/server.clj b/src/main/clojure/cljs/repl/server.clj new file mode 100644 index 0000000000..18aacfd561 --- /dev/null +++ b/src/main/clojure/cljs/repl/server.clj @@ -0,0 +1,235 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.repl.server + (:refer-clojure :exclude [loaded-libs]) + (:require [clojure.string :as str]) + (:import java.io.BufferedReader + java.io.InputStreamReader + java.io.ByteArrayOutputStream + java.util.zip.GZIPOutputStream + java.net.ServerSocket + [java.util LinkedList])) + +(def ^:dynamic state nil) +(def connq (LinkedList.)) +(def promiseq (LinkedList.)) +(def lock (Object.)) + +(defn connection + "Promise to return a connection when one is available. If no connection is + available put the promise into a FIFO queue to get the next available + connection." + [] + (locking lock + (let [p (promise) + conn (.poll connq)] + (if (and conn (not (.isClosed conn))) + (deliver p conn) + (.offer promiseq p)) + p))) + +(defn set-connection + "Given a new available connection, poll the promise queue for and deliver + the connection. Otherwise put the connection into a FIFO queue." + [conn] + (locking lock + (if-let [p (.poll promiseq)] + (deliver p conn) + (.offer connq conn)))) + +(defonce handlers (atom {})) + +(defn dispatch-on + "Registers a handler to be dispatched based on a request method and a + predicate. + + pred should be a function that accepts an options map, a connection, + and a request map and returns a boolean value based on whether or not + that request should be dispatched to the related handler." + ([method pred handler] + (dispatch-on method {:pred pred :handler handler})) + ([method {:as m}] + (swap! handlers + (fn [old] + (update-in old [method] #(conj (vec %) m)))))) + +(defn parse-file-parts [file] + ;; This is a port of java.net.URL.Parts, which is package private. + (let [ref-idx (str/index-of file "#") + [file ref] (if ref-idx + [(subs file 0 ref-idx) (subs file (inc ref-idx))] + [file nil]) + q-idx (str/last-index-of file \?)] + (merge {:ref ref} + (if q-idx + {:path (subs file 0 q-idx) + :query-str (subs file (inc q-idx))} + {:path file})))) + +;;; assumes first line already consumed +(defn parse-headers + "Parse the headers of an HTTP POST request." + [header-lines] + (apply hash-map + (mapcat + (fn [line] + (let [[k v] (str/split line #":" 2)] + [(keyword (str/lower-case k)) (str/triml v)])) + header-lines))) + +(defn read-headers [rdr] + (loop [next-line (.readLine rdr) header-lines []] + (if (= "" next-line) + header-lines ;; we're done reading headers + (recur + (.readLine rdr) + (conj header-lines next-line))))) + +(defn read-post [line rdr] + (let [[_ file _] (str/split line #" ") + {:keys [path ref query-str]} (parse-file-parts file) + headers (parse-headers (read-headers rdr)) + content-length (Integer/parseInt (:content-length headers)) + content (char-array content-length)] + (io! (.read rdr content 0 content-length) + {:method :post + :path path + :ref ref + :query-str query-str + :headers headers + :content (String. content)}))) + +(defn read-get [line rdr] + (let [[_ file _] (str/split line #" ") + {:keys [path ref query-str]} (parse-file-parts file) + headers (parse-headers (read-headers rdr))] + {:method :get + :path path + :ref ref + :query-str query-str + :headers headers})) + +(defn read-request [rdr] + (if-let [line (.readLine rdr)] + (cond + (.startsWith line "POST") (read-post line rdr) + (.startsWith line "GET") (read-get line rdr) + :else {:method :unknown :content line}) + {:method :unknown :content nil})) + +(defn- status-line [status] + (case status + 200 "HTTP/1.1 200 OK" + 404 "HTTP/1.1 404 Not Found" + "HTTP/1.1 500 Error")) + +(defn ^bytes gzip [^bytes bytes] + (let [baos (ByteArrayOutputStream. (count bytes))] + (try + (let [gzos (GZIPOutputStream. baos)] + (try + (.write gzos bytes) + (finally + (.close gzos)))) + (finally + (.close baos))) + (.toByteArray baos))) + +(defn send-and-close + "Use the passed connection to send a form to the browser. Send a + proper HTTP response." + ([conn status form] + (send-and-close conn status form "text/html")) + ([conn status form content-type] + (send-and-close conn status form content-type "UTF-8")) + ([conn status form content-type encoding] + (send-and-close conn status form content-type encoding false)) + ([conn status form content-type encoding gzip?] + (let [byte-form (cond-> (.getBytes form encoding) gzip? gzip) + content-length (count byte-form) + headers (map #(.getBytes (str % "\r\n")) + (cond-> + [(status-line status) + "Server: ClojureScript REPL" + (if (not= content-type "application/wasm") + (str "Content-Type: " + content-type + "; charset=" encoding) + (str "Content-Type: " + content-type)) + (str "Content-Length: " content-length)] + gzip? (conj "Content-Encoding: gzip") + true (conj "")))] + (with-open [os (.getOutputStream conn)] + (doseq [header headers] + (.write os header 0 (count header))) + (.write os byte-form 0 content-length) + (.flush os) + (.close conn))))) + +(defn send-404 [conn path] + (send-and-close conn 404 + (str + "" + "

    Page not found

    " + "No page " path " found on this server." + "") + "text/html")) + +(defn- dispatch-request [request conn opts] + (try + (if-let [handlers ((:method request) @handlers)] + (if-let [handler + (some (fn [{:keys [pred handler]}] + (when (pred request conn opts) + handler)) + handlers)] + (if (= :post (:method request)) + (handler (read-string (:content request)) conn opts ) + (handler request conn opts)) + (send-404 conn (:path request)))) + (catch Throwable t + (try + (send-and-close conn 500 (str "" + "

    Exception while handling

    " + "")) + (catch Throwable _)) + (throw t)))) + +(defn- handle-connection + [opts conn] + (let [rdr (BufferedReader. (InputStreamReader. (.getInputStream conn)))] + (if-let [request (read-request rdr)] + (dispatch-request request conn opts) + (.close conn)))) + +(defn- server-loop + [opts server-socket] + (when-let [conn (try (.accept server-socket) (catch Throwable _))] + (.setKeepAlive conn true) + (.start + (Thread. + ((ns-resolve 'clojure.core 'binding-conveyor-fn) + (fn [] (handle-connection opts conn))))) + (recur opts server-socket))) + +(defn start + "Start the server on the specified port." + [opts] + (let [ss (ServerSocket. (:port opts))] + (.start + (Thread. + ((ns-resolve 'clojure.core 'binding-conveyor-fn) + (fn [] (server-loop opts ss))))) + (swap! state (fn [old] (assoc old :socket ss :port (:port opts)))))) + +(defn stop [] + (when-let [sock (:socket @state)] + (when-not (.isClosed sock) + (.close sock)))) diff --git a/src/main/clojure/cljs/server/browser.clj b/src/main/clojure/cljs/server/browser.clj new file mode 100644 index 0000000000..1c429d7035 --- /dev/null +++ b/src/main/clojure/cljs/server/browser.clj @@ -0,0 +1,54 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.server.browser + (:require [cljs.env :as env] + [cljs.repl :as repl] + [cljs.repl.browser :as browser] + [cljs.core.server :as server]) + (:import [java.net ServerSocket])) + +(defonce envs (atom {})) + +(defn env-opts->key [{:keys [host port]}] + [host port]) + +(defn stale? [{:keys [server-state] :as repl-env}] + (if-let [sock (:socket @server-state)] + (.isClosed ^ServerSocket sock) + false)) + +(defn get-envs [env-opts] + (let [env-opts (merge {:host "localhost" :port 9000} env-opts) + k (env-opts->key env-opts)] + (swap! envs + #(cond-> % + (or (not (contains? % k)) + (stale? (get-in % [k 0]))) + (assoc k + [(browser/repl-env* env-opts) + (env/default-compiler-env)]))) + (get @envs k))) + +(defn repl + ([] + (repl nil)) + ([{:keys [opts env-opts]}] + (let [[env cenv] (get-envs env-opts)] + (env/with-compiler-env cenv + (repl/repl* env opts))))) + +(defn prepl + ([] + (prepl nil)) + ([{:keys [opts env-opts]}] + (let [[env cenv] (get-envs env-opts)] + (env/with-compiler-env cenv + (apply server/io-prepl + (mapcat identity + {:repl-env env :opts opts})))))) \ No newline at end of file diff --git a/src/main/clojure/cljs/server/node.clj b/src/main/clojure/cljs/server/node.clj new file mode 100644 index 0000000000..41b8fb426c --- /dev/null +++ b/src/main/clojure/cljs/server/node.clj @@ -0,0 +1,54 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.server.node + (:require [cljs.env :as env] + [cljs.repl :as repl] + [cljs.repl.node :as node] + [cljs.core.server :as server]) + (:import [java.net Socket])) + +(defonce envs (atom {})) + +(defn env-opts->key [{:keys [host port]}] + [host port]) + +(defn stale? [{:keys [socket] :as repl-env}] + (if-let [sock (:socket @socket)] + (.isClosed ^Socket sock) + false)) + +(defn get-envs [env-opts] + (let [env-opts (merge {:host "localhost" :port 49001} env-opts) + k (env-opts->key env-opts)] + (swap! envs + #(cond-> % + (or (not (contains? % k)) + (stale? (get-in % [k 0]))) + (assoc k + [(node/repl-env* env-opts) + (env/default-compiler-env)]))) + (get @envs k))) + +(defn repl + ([] + (repl nil)) + ([{:keys [opts env-opts]}] + (let [[env cenv] (get-envs env-opts)] + (env/with-compiler-env cenv + (repl/repl* env opts))))) + +(defn prepl + ([] + (prepl nil)) + ([{:keys [opts env-opts]}] + (let [[env cenv] (get-envs env-opts)] + (env/with-compiler-env cenv + (apply server/io-prepl + (mapcat identity + {:repl-env env :opts opts})))))) diff --git a/src/clj/cljs/source_map.clj b/src/main/clojure/cljs/source_map.clj similarity index 55% rename from src/clj/cljs/source_map.clj rename to src/main/clojure/cljs/source_map.clj index 2c3344b076..ed33d4ef0a 100644 --- a/src/clj/cljs/source_map.clj +++ b/src/main/clojure/cljs/source_map.clj @@ -1,9 +1,16 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + (ns cljs.source-map (:require [clojure.java.io :as io] [clojure.string :as string] - [clojure.data.json :as json] + [cljs.vendor.clojure.data.json :as json] [clojure.set :as set] - [clojure.pprint :as pp] [cljs.source-map.base64-vlq :as base64-vlq])) ;; ============================================================================= @@ -78,10 +85,9 @@ (with-meta nseg {:name (+ name rname)}) nseg))) -(defn update-result - "Helper for decode. Take an internal source map representation - organized as nested sorted maps mapping file, line, and column - and update it based on a segment map and generated line number." +(defn update-reverse-result + "Helper for decode-reverse. Take a source map and update it + based on a segment map." [result segmap gline] (let [{:keys [gcol source line col name]} segmap d {:gline gline @@ -97,11 +103,12 @@ (sorted-map)))) (sorted-map))))) -(defn decode - "Convert a v3 source map JSON object into a nested sorted map - organized as file, line, and column." +(defn decode-reverse + "Convert a v3 source map JSON object into a nested sorted map + organized as file, line, and column. Note this source map + maps from *original* source location to generated source location." ([source-map] - (decode (:mappings source-map) source-map)) + (decode-reverse (:mappings source-map) source-map)) ([mappings source-map] (let [{:keys [sources]} source-map relseg-init [0 0 0 0 0] @@ -121,11 +128,53 @@ (let [seg (first segs) nrelseg (seg-combine (base64-vlq/decode seg) relseg)] (recur (next segs) nrelseg - (update-result result (seg->map nrelseg source-map) gline))) + (update-reverse-result result (seg->map nrelseg source-map) gline))) [result relseg]))))] (recur (inc gline) (next lines) (assoc relseg 0 0) result)) result))))) +(defn update-result + "Helper for decode. Take a source map and update it based on a + segment map." + [result segmap gline] + (let [{:keys [gcol source line col name]} segmap + d {:line line + :col col + :source source} + d (if name (assoc d :name name) d)] + (update-in result [gline] + (fnil (fn [m] + (update-in m [gcol] + (fnil #(conj % d) []))) + (sorted-map))))) + +(defn decode + "Convert a v3 source map JSON object into a nested sorted map + organized as file, line, and column. Note this source map + maps from *generated* source location to original source + location." + ([source-map] + (decode (:mappings source-map) source-map)) + ([mappings source-map] + (let [relseg-init [0 0 0 0 0] + lines (seq (string/split mappings #";"))] + (loop [gline 0 lines lines relseg relseg-init result {}] + (if lines + (let [line (first lines) + [result relseg] + (if (string/blank? line) + [result relseg] + (let [segs (seq (string/split line #","))] + (loop [segs segs relseg relseg result result] + (if segs + (let [seg (first segs) + nrelseg (seg-combine (base64-vlq/decode seg) relseg)] + (recur (next segs) nrelseg + (update-result result (seg->map nrelseg source-map) gline))) + [result relseg]))))] + (recur (inc gline) (next lines) (assoc relseg 0 0) result)) + result))))) + ;; ----------------------------------------------------------------------------- ;; Encoding @@ -152,31 +201,28 @@ [] cols))) [] lines))) -(defn relativize-path [path {:keys [output-dir source-map-path source-map relpaths]}] - (let [bare-munged-path (cond - (re-find #"\.jar!/" path) - (str (or source-map-path output-dir) (second (string/split path #"\.jar!"))) - - :else - (str (or source-map-path output-dir) "/" (get relpaths path)))] - (cond source-map-path - bare-munged-path +(defn relativize-path + "Relativize a path using :source-map-path if provided or the parent directory + otherwise." + [path {:keys [output-dir source-map-path source-map relpaths] :as opts}] + (let [bare-munged-path + (cond + (re-find #"\.jar!/" path) + (str (or source-map-path output-dir) + (second (string/split path #"\.jar!"))) + :else + (str (or source-map-path output-dir) + "/" (get relpaths path)))] + (cond + source-map-path bare-munged-path + :else + (let [unrel-uri (-> bare-munged-path io/file .toURI) + sm-parent-uri (-> source-map io/file .getAbsoluteFile .getParentFile .toURI)] + (str (.relativize sm-parent-uri unrel-uri)))))) - :default - (let [unrelativized-juri (-> bare-munged-path - io/file - .toURI) - source-map-parent-juri (-> source-map - io/file - .getAbsoluteFile - .getParentFile - .toURI)] - (str (.relativize source-map-parent-juri unrelativized-juri)))))) - -(defn encode +(defn encode* "Take an internal source map representation represented as nested - sorted maps of file, line, column and return a source map v3 JSON - string." + sorted maps of file, line, column and return a v3 representation." [m opts] (let [lines (atom [[]]) names->idx (atom {}) @@ -211,39 +257,57 @@ (doseq [[line cols] lines] (doseq [[col infos] cols] (encode-cols infos source-idx line col)))) - (let [source-map-file-contents - (cond-> {"version" 3 - "file" (:file opts) - "sources" (into [] - (let [paths (keys m) - f (if (or (:output-dir opts) - (:source-map-path opts)) - #(relativize-path % opts) - #(last (string/split % #"/")))] - (map f paths))) - "lineCount" (:lines opts) - "mappings" (->> (lines->segs (concat preamble-lines @lines)) - (map #(string/join "," %)) - (string/join ";")) - "names" (into [] - (map (set/map-invert @names->idx) - (range (count @names->idx))))} - (:sources-content opts) - (assoc "sourcesContent" (:sources-content opts)))] + + (cond-> {"version" 3 + "file" (:file opts) + "sources" (into [] + (let [paths (keys m) + f (comp + (if (true? (:source-map-timestamp opts)) + (fn [uri] + (if-not (string/index-of uri "?") + (str uri "?rel=" (System/currentTimeMillis)) + (str uri "&rel=" (System/currentTimeMillis)))) + identity) + (if (or (:output-dir opts) + (:source-map-path opts)) + #(relativize-path % opts) + #(last (string/split % #"/"))))] + (map f paths))) + "lineCount" (:lines opts) + "mappings" (->> (lines->segs (concat preamble-lines @lines)) + (map #(string/join "," %)) + (string/join ";")) + "names" (into [] + (map (set/map-invert @names->idx) + (range (count @names->idx))))} + + (:sources-content opts) + (assoc "sourcesContent" (:sources-content opts))))) + +(defn encode + "Take an internal source map representation represented as nested + sorted maps of file, line, column and return a source map v3 JSON + string." + [m opts] + (let [source-map-file-contents (encode* m opts)] + (if (true? (:source-map-pretty-print opts)) (with-out-str (json/pprint source-map-file-contents - :escape-slash false))))) + :escape-slash false)) + (json/write-str source-map-file-contents)))) ;; ----------------------------------------------------------------------------- ;; Merging (defn merge-source-maps "Merge an internal source map representation of a single - ClojureScript file with an internal source map representation of - the generated JavaScript file that underwent Google Closure - Compiler optimization." - [cljs-map closure-map] + ClojureScript file mapping original to generated with a + second source map mapping original JS to generated JS. + The is to support source maps that work through multiple + compilation steps like Google Closure optimization passes." + [cljs-map js-map] (loop [line-map-seq (seq cljs-map) new-lines (sorted-map)] (if line-map-seq (let [[line col-map] (first line-map-seq) @@ -254,30 +318,52 @@ (recur (next col-map-seq) (assoc new-cols col (reduce (fn [v {:keys [gline gcol]}] - (into v (get-in closure-map [gline gcol]))) + (into v (get-in js-map [gline gcol]))) [] infos)))) new-cols))] (recur (next line-map-seq) (assoc new-lines line new-cols))) new-lines))) +;; ----------------------------------------------------------------------------- +;; Reverse Source Map Inversion + +(defn invert-reverse-map + "Given a ClojureScript to JavaScript source map, invert it. Useful when + mapping JavaScript stack traces when environment support is unavailable." + [reverse-map] + (let [inverted (atom (sorted-map))] + (doseq [[line columns] reverse-map] + (doseq [[column column-info] columns] + (doseq [{:keys [gline gcol name]} column-info] + (swap! inverted update-in [gline] + (fnil (fn [columns] + (update-in columns [column] (fnil conj []) + {:line line :col column :name name})) + (sorted-map)))))) + @inverted)) + (comment ;; INSTRUCTIONS: - + ;; switch into samples/hello ;; run repl to start clojure ;; build with - + (require '[cljs.closure :as cljsc]) - (cljsc/build "src" {:optimizations :simple :output-to "hello.js" :source-map "hello.js.map"}) + (cljsc/build "src" + {:optimizations :simple + :output-to "hello.js" + :source-map "hello.js.map" + :output-dir "out"}) ;; load source map (def raw-source-map (json/read-str (slurp (io/file "hello.js.map")) :key-fn keyword)) ;; test it out - (first (decode raw-source-map)) + (first (decode-reverse raw-source-map)) ;; decoded source map preserves file order - (= (keys (decode raw-source-map)) (:sources raw-source-map)) + (= (keys (decode-reverse raw-source-map)) (:sources raw-source-map)) ) diff --git a/src/main/clojure/cljs/source_map/base64.clj b/src/main/clojure/cljs/source_map/base64.clj new file mode 100644 index 0000000000..a53f8f96bc --- /dev/null +++ b/src/main/clojure/cljs/source_map/base64.clj @@ -0,0 +1,28 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.source-map.base64) + +(def chars64 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") +(def char->int (zipmap chars64 (range 0 64))) +(def int->char (zipmap (range 0 64) chars64)) + +(defn encode [^long n] + (case n + 0 \A 1 \B 2 \C 3 \D 4 \E 5 \F 6 \G 7 \H 8 \I 9 \J 10 \K 11 \L 12 \M + 13 \N 14 \O 15 \P 16 \Q 17 \R 18 \S 19 \T 20 \U 21 \V 22 \W 23 \X 24 \Y 25 \Z + 26 \a 27 \b 28 \c 29 \d 30 \e 31 \f 32 \g 33 \h 34 \i 35 \j 36 \k 37 \l 38 \m + 39 \n 40 \o 41 \p 42 \q 43 \r 44 \s 45 \t 46 \u 47 \v 48 \w 49 \x 50 \y 51 \z + 52 \0 53 \1 54 \2 55 \3 56 \4 57 \5 58 \6 59 \7 60 \8 61 \9 62 \+ 63 \/ + (throw (Error. (str "Must be between 0 and 63: " n))))) + +(defn ^Character decode [c] + (let [e (find char->int c)] + (if e + (second e) + (throw (Error. (str "Not a valid base 64 digit: " c)))))) \ No newline at end of file diff --git a/src/clj/cljs/source_map/base64_vlq.clj b/src/main/clojure/cljs/source_map/base64_vlq.clj similarity index 85% rename from src/clj/cljs/source_map/base64_vlq.clj rename to src/main/clojure/cljs/source_map/base64_vlq.clj index ea3625f367..b6d33cca00 100644 --- a/src/clj/cljs/source_map/base64_vlq.clj +++ b/src/main/clojure/cljs/source_map/base64_vlq.clj @@ -1,5 +1,13 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + (ns cljs.source-map.base64-vlq - (require [clojure.string :as string] + (:require [clojure.string :as string] [cljs.source-map.base64 :as base64])) (def ^:const vlq-base-shift 5) diff --git a/src/main/clojure/cljs/support.cljc b/src/main/clojure/cljs/support.cljc new file mode 100644 index 0000000000..918cce7f52 --- /dev/null +++ b/src/main/clojure/cljs/support.cljc @@ -0,0 +1,18 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.support) + +(defmacro assert-args + "Internal - do not use!" + [fnname & pairs] + `(do (when-not ~(first pairs) + (throw (ex-info ~(str fnname " requires " (second pairs)) {:clojure.error/phase :macro-syntax-check}))) + ~(let [more (nnext pairs)] + (when more + (list* `assert-args fnname more))))) diff --git a/src/main/clojure/cljs/tagged_literals.cljc b/src/main/clojure/cljs/tagged_literals.cljc new file mode 100644 index 0000000000..09d53cbb11 --- /dev/null +++ b/src/main/clojure/cljs/tagged_literals.cljc @@ -0,0 +1,93 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.tagged-literals + #?(:clj (:require [cljs.instant :as inst]) + :cljs (:require [cljs.reader :as reader]))) + +(defn read-queue + [form] + (when-not (vector? form) + (throw + #?(:clj (RuntimeException. + "Queue literal expects a vector for its elements.") + :cljs (js/Error. + "Queue literal expects a vector for its elements.")))) + (list 'cljs.core/into 'cljs.core.PersistentQueue.EMPTY form)) + +#?(:clj + (defn read-uuid + [form] + (when-not (string? form) + (throw (RuntimeException. "UUID literal expects a string as its representation."))) + (try + (java.util.UUID/fromString form) + (catch Throwable e + (throw (RuntimeException. (.getMessage e))))))) + +#?(:cljs + (defn read-uuid + [form] + (when-not (string? form) + (throw (js/Error. "UUID literal expects a string as its representation."))) + (try + (uuid form) + (catch :default e + (throw (js/Error. (. e -message))))))) + +#?(:clj + (defn read-inst + [form] + (when-not (string? form) + (throw (RuntimeException. "Instance literal expects a string for its timestamp."))) + (try + (inst/read-instant-instant form) + (catch Throwable e + (throw (RuntimeException. (.getMessage e))))))) + +#?(:cljs + (defn read-inst + [form] + (when-not (string? form) + (throw (js/Error. "Instance literal expects a string for its timestamp."))) + (try + (#'reader/read-date form) + (catch :default e + (throw (js/Error. (. e -message))))))) + +(defn valid-js-literal-key? [k] + (or (string? k) + (and (keyword? k) + (nil? (namespace k))))) + +(deftype JSValue [val]) + +(defn read-js + [form] + (when-not (or (vector? form) (map? form)) + (throw + #?(:clj (RuntimeException. + "JavaScript literal must use map or vector notation") + :cljs (js/Error. + "JavaScript literal must use map or vector notation")))) + (when-not (or (not (map? form)) + (every? valid-js-literal-key? (keys form))) + (throw + #?(:clj (RuntimeException. + "JavaScript literal keys must be strings or unqualified keywords") + :cljs (js/Error. + "JavaScript literal keys must be strings or unqualified keywords")))) + (JSValue. form)) + +(def ^:dynamic *cljs-data-readers* + (merge ;; assumes we can read all data_readers + #?(:clj *data-readers*) + {'queue read-queue + 'uuid read-uuid + 'inst read-inst + 'js read-js})) diff --git a/src/main/clojure/cljs/util.cljc b/src/main/clojure/cljs/util.cljc new file mode 100644 index 0000000000..0964fec074 --- /dev/null +++ b/src/main/clojure/cljs/util.cljc @@ -0,0 +1,416 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.util + (:refer-clojure :exclude [boolean?]) + (:require [clojure.java.io :as io] + [clojure.string :as string] + [clojure.set :as set] + [clojure.edn :as edn]) + (:import [java.io File] + [java.net URL URLDecoder] + [java.security MessageDigest])) + +;; next line is auto-generated by the build-script - Do not edit! +(def ^:dynamic *clojurescript-version*) + +(defn compilation-error [cause] + (ex-info nil {:clojure.error/phase :compilation} cause)) + +(defn- main-src-directory [] + (some (fn [file] + (when (= "main" (.getName file)) + file)) + (iterate (memfn getParentFile) (io/as-file (io/resource "cljs/util.cljc"))))) + +(defn- file-hash [file] + (if (.isDirectory file) + 0 + (hash (slurp file)))) + +(def ^:private synthethetic-version-prefix "0.0.") + +(def ^:private synthetic-clojurescript-version + (delay (let [qualifier (fn [n] + (if (== n Integer/MIN_VALUE) + 0 + (Math/abs n)))] + (str synthethetic-version-prefix + (qualifier (reduce unchecked-add-int (map file-hash (file-seq (main-src-directory))))))))) + +(defn ^String clojurescript-version + "Returns clojurescript version as a printable string." + [] + (if (bound? #'*clojurescript-version*) + (str + (:major *clojurescript-version*) + "." + (:minor *clojurescript-version*) + (when-let [i (:incremental *clojurescript-version*)] + (str "." i)) + (when-let [q (:qualifier *clojurescript-version*)] + (str "." q)) + (when (:interim *clojurescript-version*) + "-SNAPSHOT")) + @synthetic-clojurescript-version)) + +(defn synthetic-version? + "Returns true if clojurescript-version returns a synthetically-generated + version." + [] + (string/starts-with? (clojurescript-version) synthethetic-version-prefix)) + +(defn cljs-built-dep? + "Returns true if ClojureScript itself is a built dep." + [] + (not (synthetic-version?))) + +(defn ^String compiled-by-version [f] + (with-open [reader (io/reader f)] + (let [match (some->> reader line-seq first + (re-matches #".*ClojureScript (\d+\.\d+\.\d+).*$"))] + (or (and match (second match)) "0.0.0000")))) + +(defn build-options [^File f] + (with-open [reader (io/reader f)] + (let [match (some->> reader line-seq first + (re-matches #".*ClojureScript \d+\.\d+\.\d+ (.*)$"))] + (and match (edn/read-string (second match)))))) + +(defn munge-path [ss] + (clojure.lang.Compiler/munge (str ss))) + +(defn ns->relpath + "Given a namespace as a symbol return the relative path. May optionally + provide the file extension, defaults to :cljs." + ([ns] (ns->relpath ns :cljs)) + ([ns ext] + (ns->relpath ns ext \/)) + ([ns ext sep] + (cond-> (string/replace (munge-path ns) \. sep) + ext (str "." (name ext))))) + +(defn ns->source + "Given a namespace as a symbol return the corresponding resource if it exists." + [ns] + (or (io/resource (ns->relpath ns :cljs)) + (io/resource (ns->relpath ns :cljc)))) + +(defn path-seq + [file-str] + (->> File/separator + java.util.regex.Pattern/quote + re-pattern + (string/split file-str))) + +(defn to-path + ([parts] + (to-path parts File/separator)) + ([parts sep] + (apply str (interpose sep parts)))) + +(defn split-paths + [paths-str] + (string/split paths-str (re-pattern File/pathSeparator))) + +(declare ext) + +(defn ^File to-target-file + ([target-dir ns-info] + (to-target-file target-dir ns-info "js")) + ([target-dir {:keys [ns source-file] :as ns-info} ext] + (let [src-ext (if source-file + (cljs.util/ext source-file) + "cljs") + ns (if (or (= src-ext "clj") + (and (= ns 'cljs.core) (= src-ext "cljc"))) + (symbol (str ns "$macros")) + ns) + relpath (string/split (munge-path (str ns)) #"\.") + parents (cond-> (butlast relpath) + target-dir (conj target-dir))] + (cond->> (io/file (str (last relpath) (str "." ext))) + (seq parents) + (io/file (to-path parents)))))) + +(defn mkdirs + "Create all parent directories for the passed file." + [^File f] + (.mkdirs (.getParentFile (.getCanonicalFile f)))) + +(defn output-directory + ([opts] (output-directory opts "out")) + ([opts default] + {:pre [(or (nil? opts) (map? opts))]} + (or (:output-dir opts) default))) + +(def windows? + (.startsWith (.toLowerCase (System/getProperty "os.name")) "windows")) + +(defn file? [f] + (instance? File f)) + +(defn url? [f] + (instance? URL f)) + +(defn ^String filename [^File f] + (.getName f)) + +;; on Windows, URLs end up having forward slashes like +;; /C:/Users/... - Antonio +(defn ^String normalize-path [^String x] + (-> (cond-> x + windows? (string/replace #"^[\\/]" "")) + (string/replace "\\" File/separator) + (string/replace "/" File/separator))) + +(defn ^String path [x] + (cond + (file? x) (.getAbsolutePath ^File x) + (url? x) (if windows? + (let [f (URLDecoder/decode (.getFile x))] + (normalize-path f)) + (.getPath ^URL x)) + (string? x) x + :else (throw (Exception. (str "Expected file, url, or string. Got " (pr-str x)))))) + +(defn ^String ext + "Given a file, url or string return the file extension." + [x] + (let [s (cond + (file? x) (filename x) + (url? x) (path x) + (string? x) x + :else (throw (Exception. (str "Expected file, url, or string. Got " (pr-str x)))))] + (last (string/split s #"\.")))) + +(defn ^String get-name + "Given a file or url return the last component of the path." + [x] + {:pre [(or (file? x) (url? x))]} + (if (file? x) + (filename x) + (last (string/split (path x) #"[\\\/]")))) + +(defn ^String relative-name + "Given a file return a path relative to the working directory. Given a + URL return the JAR relative path of the resource." + [x] + {:pre [(or (file? x) (url? x))]} + (letfn [(strip-user-dir [s] + (let [user-path (.toPath (io/file (System/getProperty "user.dir"))) + base-count (.getNameCount user-path) + file-path (.toPath (io/file s))] + (if (.startsWith file-path user-path) + (str (.subpath file-path base-count (.getNameCount file-path))) + s)))] + (if (file? x) + (strip-user-dir (.getAbsolutePath x)) + (let [f (URLDecoder/decode (.getFile x))] + (if (string/includes? f ".jar!/") + (last (string/split f #"\.jar!/")) + (strip-user-dir f)))))) + +(defn last-modified [src] + (cond + (file? src) (.lastModified ^File src) + (url? src) + (let [conn (.openConnection ^URL src)] + (try + (.getLastModified conn) + (finally + (let [ins (.getInputStream conn)] + (when ins + (.close ins)))))) + :else + (throw + (IllegalArgumentException. (str "Cannot get last modified for " src))))) + +(defn changed? [a b] + (not (== (last-modified a) (last-modified b)))) + +(defn file-or-resource [s] + (or (and (.exists (io/file s)) (io/file s)) + (io/resource s))) + +(defn topo-sort + ([x get-deps] + (topo-sort x 0 (atom (sorted-map)) (memoize get-deps))) + ([x depth state memo-get-deps] + (let [deps (memo-get-deps x)] + (swap! state update-in [depth] (fnil into #{}) deps) + (doseq [dep deps] + (topo-sort dep (inc depth) state memo-get-deps)) + (doseq [[ (suggestion 3 (str unknown) (map str knowns)) + (subs 1) + keyword)])) + +(defn distinct-by + ([f coll] + (let [step (fn step [xs seen] + (lazy-seq + ((fn [[x :as xs] seen] + (when-let [s (seq xs)] + (let [v (f x)] + (if (contains? seen v) + (recur (rest s) seen) + (cons x (step (rest s) (conj seen v))))))) + xs seen)))] + (step coll #{})))) + +(def ^:private hex-digits (char-array "0123456789ABCDEF")) + +(defn- bytes-to-hex-str + "Convert an array of bytes into a hex encoded string." + [^bytes bytes] + (loop [index (int 0) + buffer (StringBuilder. (int (* 2 (alength bytes))))] + (if (== (alength bytes) index) + (.toString buffer) + (let [byte (aget bytes index)] + (.append buffer (aget ^chars hex-digits (bit-and (bit-shift-right byte 4) 0xF))) + (.append buffer (aget ^chars hex-digits (bit-and byte 0xF))) + (recur (inc index) buffer))))) + +(defn content-sha + ([^String s] + (content-sha s nil)) + ([^String s ^Long n] + (let [digest (MessageDigest/getInstance "SHA-1") + _ (.reset digest) + _ (.update digest (.getBytes s "utf8")) + sha (bytes-to-hex-str (.digest digest))] + (if-not (nil? n) + (apply str (take n sha)) + sha)))) + +(defn map-merge [a b] + (if (and (map? a) (map? b)) + (loop [ks (seq (keys a)) ret a b' b] + (if ks + (let [k (first ks)] + (if (contains? b' k) + (recur + (next ks) + (assoc ret k (map-merge (get ret k) (get b' k))) + (dissoc b' k)) + (recur (next ks) ret b'))) + (merge ret b'))) + a)) + +(defn conjunction-str [xs] + (let [xs (vec xs)] + (case (count xs) + 1 (first xs) + 2 (str (first xs) " and " (second xs)) + (str (string/join ", " (pop xs)) " and " (peek xs))))) + +(defn module-file-seq-1 [dir] + [dir] + (let [fseq (tree-seq + (fn [^File f] + (and (. f (isDirectory)) + (not (boolean + (re-find #"node_modules[\\\/].*[\\\/]node_modules" + (.getPath f)))))) + (fn [^File d] + (seq (. d (listFiles)))) + dir)] + (filter (fn [^File f] + (let [path (.getPath f)] + (or (.endsWith path ".json") + (.endsWith path ".js") + (.endsWith path ".cjs")))) + fseq))) + +(defn node-path-modules [opts] + (map io/file (or (:node-modules-dirs opts) [(io/file "node_modules")]))) + +(defn module-file-seq [opts] + "Return a seq of all files in `node_modules` ending in `.js` or `.json` that are + not in an internally nested `node_modules` dir." + (mapcat module-file-seq-1 (node-path-modules opts))) diff --git a/src/main/clojure/cljs/vendor/bridge.clj b/src/main/clojure/cljs/vendor/bridge.clj new file mode 100644 index 0000000000..4a2e24c098 --- /dev/null +++ b/src/main/clojure/cljs/vendor/bridge.clj @@ -0,0 +1,42 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.vendor.bridge + (:require [cljs.vendor.clojure.tools.reader.reader-types :as vendor] + [clojure.tools.reader.reader-types :as readers])) + +(extend-protocol vendor/Reader + clojure.tools.reader.reader_types.Reader + (read-char [reader] + (readers/read-char reader)) + (peek-char [reader] + (readers/peek-char reader))) + +(extend-protocol vendor/IPushbackReader + clojure.tools.reader.reader_types.IPushbackReader + (unread [reader ch] + (readers/unread reader ch))) + +(extend-protocol vendor/IndexingReader + clojure.tools.reader.reader_types.IndexingReader + (get-line-number [reader] + (readers/get-line-number reader)) + (get-column-number [reader] + (readers/get-column-number reader)) + (get-file-name [reader] + (readers/get-file-name reader))) + +(extend-protocol vendor/ReaderCoercer + clojure.tools.reader.reader_types.ReaderCoercer + (to-rdr [reader] + (readers/to-rdr reader))) + +(extend-protocol vendor/PushbackReaderCoercer + clojure.tools.reader.reader_types.PushbackReaderCoercer + (to-pbr [reader buflen] + (readers/to-pbr reader buflen))) diff --git a/src/main/clojure/cljs/vendor/clojure/data/json.clj b/src/main/clojure/cljs/vendor/clojure/data/json.clj new file mode 100644 index 0000000000..361a306e2a --- /dev/null +++ b/src/main/clojure/cljs/vendor/clojure/data/json.clj @@ -0,0 +1,809 @@ +;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use +;; and distribution terms for this software are covered by the Eclipse +;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this +;; distribution. By using this software in any fashion, you are +;; agreeing to be bound by the terms of this license. You must not +;; remove this notice, or any other, from this software. + +(ns ^{:author "Stuart Sierra" + :doc "JavaScript Object Notation (JSON) parser/generator. + See http://www.json.org/"} + cljs.vendor.clojure.data.json + (:refer-clojure :exclude (read)) + (:require [clojure.pprint :as pprint]) + (:import (java.io PrintWriter PushbackReader StringWriter + Writer StringReader EOFException))) + +;;; JSON READER + +(set! *warn-on-reflection* true) + +(defn- default-write-key-fn + [x] + (cond (instance? clojure.lang.Named x) + (name x) + (nil? x) + (throw (Exception. "JSON object properties may not be nil")) + :else (str x))) + +(defn- default-value-fn [k v] v) + +(declare -read) + +(defmacro ^:private codepoint [c] + (int c)) + +(defn- codepoint-clause [[test result]] + (cond (list? test) + [(map int test) result] + (= test :whitespace) + ['(9 10 13 32) result] + (= test :js-separators) + ['(16r2028 16r2029) result] + :else + [(int test) result])) + +(defmacro ^:private codepoint-case [e & clauses] + `(case ~e + ~@(mapcat codepoint-clause (partition 2 clauses)) + ~@(when (odd? (count clauses)) + [(last clauses)]))) + +(defn- read-hex-char [^PushbackReader stream] + ;; Expects to be called with the head of the stream AFTER the + ;; initial "\u". Reads the next four characters from the stream. + (let [a (.read stream) + b (.read stream) + c (.read stream) + d (.read stream)] + (when (or (neg? a) (neg? b) (neg? c) (neg? d)) + (throw (EOFException. + "JSON error (end-of-file inside Unicode character escape)"))) + (let [s (str (char a) (char b) (char c) (char d))] + (char (Integer/parseInt s 16))))) + +(defn- read-escaped-char [^PushbackReader stream] + ;; Expects to be called with the head of the stream AFTER the + ;; initial backslash. + (let [c (.read stream)] + (when (neg? c) + (throw (EOFException. "JSON error (end-of-file inside escaped char)"))) + (codepoint-case c + (\" \\ \/) (char c) + \b \backspace + \f \formfeed + \n \newline + \r \return + \t \tab + \u (read-hex-char stream)))) + +(defn- slow-read-string [^PushbackReader stream ^String already-read] + (let [buffer (StringBuilder. already-read)] + (loop [] + (let [c (.read stream)] + (when (neg? c) + (throw (EOFException. "JSON error (end-of-file inside string)"))) + (codepoint-case c + \" (str buffer) + \\ (do (.append buffer (read-escaped-char stream)) + (recur)) + (do (.append buffer (char c)) + (recur))))))) + +(defn- read-quoted-string [^PushbackReader stream] + ;; Expects to be called with the head of the stream AFTER the + ;; opening quotation mark. + (let [buffer ^chars (char-array 64) + read (.read stream buffer 0 64) + end-index (unchecked-dec-int read)] + (when (neg? read) + (throw (EOFException. "JSON error (end-of-file inside string)"))) + (loop [i (int 0)] + (let [c (int (aget buffer i))] + (codepoint-case c + \" (let [off (unchecked-inc-int i) + len (unchecked-subtract-int read off)] + (.unread stream buffer off len) + (String. buffer 0 i)) + \\ (let [off i + len (unchecked-subtract-int read off)] + (.unread stream buffer off len) + (slow-read-string stream (String. buffer 0 i))) + (if (= i end-index) + (do (.unread stream c) + (slow-read-string stream (String. buffer 0 i))) + (recur (unchecked-inc-int i)))))))) + +(defn- read-integer [^String string] + (if (< (count string) 18) ; definitely fits in a Long + (Long/valueOf string) + (or (try (Long/valueOf string) + (catch NumberFormatException e nil)) + (bigint string)))) + +(defn- read-decimal [^String string bigdec?] + (if bigdec? + (bigdec string) + (Double/valueOf string))) + +(defn- read-number [^PushbackReader stream bigdec?] + (let [buffer (StringBuilder.) + decimal? (loop [stage :minus] + (let [c (.read stream)] + (case stage + :minus + (codepoint-case c + \- + (do (.append buffer (char c)) + (recur :int-zero)) + \0 + (do (.append buffer (char c)) + (recur :frac-point)) + (\1 \2 \3 \4 \5 \6 \7 \8 \9) + (do (.append buffer (char c)) + (recur :int-digit)) + (throw (Exception. "JSON error (invalid number literal)"))) + ;; Number must either be a single 0 or 1-9 followed by 0-9 + :int-zero + (codepoint-case c + \0 + (do (.append buffer (char c)) + (recur :frac-point)) + (\1 \2 \3 \4 \5 \6 \7 \8 \9) + (do (.append buffer (char c)) + (recur :int-digit)) + (throw (Exception. "JSON error (invalid number literal)"))) + ;; at this point, there is at least one digit + :int-digit + (codepoint-case c + (\0 \1 \2 \3 \4 \5 \6 \7 \8 \9) + (do (.append buffer (char c)) + (recur :int-digit)) + \. + (do (.append buffer (char c)) + (recur :frac-first)) + (\e \E) + (do (.append buffer (char c)) + (recur :exp-symbol)) + ;; early exit + :whitespace + (do (.unread stream c) + false) + (\, \] \} -1) + (do (.unread stream c) + false) + (throw (Exception. "JSON error (invalid number literal)"))) + ;; previous character is a "0" + :frac-point + (codepoint-case c + \. + (do (.append buffer (char c)) + (recur :frac-first)) + (\e \E) + (do (.append buffer (char c)) + (recur :exp-symbol)) + ;; early exit + :whitespace + (do (.unread stream c) + false) + (\, \] \} -1) + (do (.unread stream c) + false) + ;; Disallow zero-padded numbers or invalid characters + (throw (Exception. "JSON error (invalid number literal)"))) + ;; previous character is a "." + :frac-first + (codepoint-case c + (\0 \1 \2 \3 \4 \5 \6 \7 \8 \9) + (do (.append buffer (char c)) + (recur :frac-digit)) + (throw (Exception. "JSON error (invalid number literal)"))) + ;; any number of following digits + :frac-digit + (codepoint-case c + (\0 \1 \2 \3 \4 \5 \6 \7 \8 \9) + (do (.append buffer (char c)) + (recur :frac-digit)) + (\e \E) + (do (.append buffer (char c)) + (recur :exp-symbol)) + ;; early exit + :whitespace + (do (.unread stream c) + true) + (\, \] \} -1) + (do (.unread stream c) + true) + (throw (Exception. "JSON error (invalid number literal)"))) + ;; previous character is a "e" or "E" + :exp-symbol + (codepoint-case c + (\- \+) + (do (.append buffer (char c)) + (recur :exp-first)) + (\0 \1 \2 \3 \4 \5 \6 \7 \8 \9) + (do (.append buffer (char c)) + (recur :exp-digit))) + ;; previous character is a "-" or "+" + ;; must have at least one digit + :exp-first + (codepoint-case c + (\0 \1 \2 \3 \4 \5 \6 \7 \8 \9) + (do (.append buffer (char c)) + (recur :exp-digit)) + (throw (Exception. "JSON error (invalid number literal)"))) + ;; any number of following digits + :exp-digit + (codepoint-case c + (\0 \1 \2 \3 \4 \5 \6 \7 \8 \9) + (do (.append buffer (char c)) + (recur :exp-digit)) + :whitespace + (do (.unread stream c) + true) + (\, \] \} -1) + (do (.unread stream c) + true) + (throw (Exception. "JSON error (invalid number literal)"))))))] + (if decimal? + (read-decimal (str buffer) bigdec?) + (read-integer (str buffer))))) + +(defn- next-token [^PushbackReader stream] + (loop [c (.read stream)] + (if (< 32 c) + (int c) + (codepoint-case (int c) + :whitespace (recur (.read stream)) + -1 -1)))) + +(defn invalid-array-exception [] + (Exception. "JSON error (invalid array)")) + +(defn- read-array* [^PushbackReader stream options] + ;; Handles all array values after the first. + (loop [result (transient [])] + (let [r (conj! result (-read stream true nil options))] + (codepoint-case (int (next-token stream)) + \] (persistent! r) + \, (recur r) + (throw (invalid-array-exception)))))) + +(defn- read-array [^PushbackReader stream options] + ;; Expects to be called with the head of the stream AFTER the + ;; opening bracket. + ;; Only handles array value. + (let [c (int (next-token stream))] + (codepoint-case c + \] [] + \, (throw (invalid-array-exception)) + (do (.unread stream c) + (read-array* stream options))))) + +(defn- read-key [^PushbackReader stream] + (let [c (int (next-token stream))] + (if (= c (codepoint \")) + (let [key (read-quoted-string stream)] + (if (= (codepoint \:) (int (next-token stream))) + key + (throw (Exception. "JSON error (missing `:` in object)")))) + (if (= c (codepoint \})) + nil + (throw (Exception. (str "JSON error (non-string key in object), found `" (char c) "`, expected `\"`"))))))) + +(defn- read-object [^PushbackReader stream options] + ;; Expects to be called with the head of the stream AFTER the + ;; opening bracket. + (let [key-fn (get options :key-fn) + value-fn (get options :value-fn)] + (loop [result (transient {})] + (if-let [key (read-key stream)] + (let [key (cond-> key key-fn key-fn) + value (-read stream true nil options) + r (if value-fn + (let [out-value (value-fn key value)] + (if-not (= value-fn out-value) + (assoc! result key out-value) + result)) + (assoc! result key value))] + (codepoint-case (int (next-token stream)) + \, (recur r) + \} (persistent! r) + (throw (Exception. "JSON error (missing entry in object)")))) + (let [r (persistent! result)] + (if (empty? r) + r + (throw (Exception. "JSON error empty entry in object is not allowed")))))))) + +(defn- -read + [^PushbackReader stream eof-error? eof-value options] + (let [c (int (next-token stream))] + (codepoint-case c + ;; Read numbers + (\- \0 \1 \2 \3 \4 \5 \6 \7 \8 \9) + (do (.unread stream c) + (read-number stream (:bigdec options))) + + ;; Read strings + \" (read-quoted-string stream) + + ;; Read null as nil + \n (if (and (= (codepoint \u) (.read stream)) + (= (codepoint \l) (.read stream)) + (= (codepoint \l) (.read stream))) + nil + (throw (Exception. "JSON error (expected null)"))) + + ;; Read true + \t (if (and (= (codepoint \r) (.read stream)) + (= (codepoint \u) (.read stream)) + (= (codepoint \e) (.read stream))) + true + (throw (Exception. "JSON error (expected true)"))) + + ;; Read false + \f (if (and (= (codepoint \a) (.read stream)) + (= (codepoint \l) (.read stream)) + (= (codepoint \s) (.read stream)) + (= (codepoint \e) (.read stream))) + false + (throw (Exception. "JSON error (expected false)"))) + + ;; Read JSON objects + \{ (read-object stream options) + + ;; Read JSON arrays + \[ (read-array stream options) + + (if (neg? c) ;; Handle end-of-stream + (if eof-error? + (throw (EOFException. "JSON error (end-of-file)")) + eof-value) + (throw (Exception. + (str "JSON error (unexpected character): " (char c)))))))) + +(def default-read-options {:bigdec false + :key-fn nil + :value-fn nil}) +(defn read + "Reads a single item of JSON data from a java.io.Reader. Options are + key-value pairs, valid options are: + + :eof-error? boolean + + If true (default) will throw exception if the stream is empty. + + :eof-value Object + + Object to return if the stream is empty and eof-error? is + false. Default is nil. + + :bigdec boolean + + If true use BigDecimal for decimal numbers instead of Double. + Default is false. + + :key-fn function + + Single-argument function called on JSON property names; return + value will replace the property names in the output. Default + is clojure.core/identity, use clojure.core/keyword to get + keyword properties. + + :value-fn function + + Function to transform values in maps (\"objects\" in JSON) in + the output. For each JSON property, value-fn is called with + two arguments: the property name (transformed by key-fn) and + the value. The return value of value-fn will replace the value + in the output. If value-fn returns itself, the property will + be omitted from the output. The default value-fn returns the + value unchanged. This option does not apply to non-map + collections." + [reader & {:as options}] + (let [{:keys [eof-error? eof-value] + :or {eof-error? true}} options] + (->> options + (merge default-read-options) + (-read (PushbackReader. reader 64) eof-error? eof-value)))) + +(defn read-str + "Reads one JSON value from input String. Options are the same as for + read." + [string & {:as options}] + (let [{:keys [eof-error? eof-value] + :or {eof-error? true}} options] + (->> options + (merge default-read-options) + (-read (PushbackReader. (StringReader. string) 64) eof-error? eof-value)))) + +;;; JSON WRITER + + +(defprotocol JSONWriter + (-write [object out options] + "Print object to Appendable out as JSON")) + +(defn- ->hex-string [^Appendable out cp] + (let [cpl (long cp)] + (.append out "\\u") + (cond + (< cpl 16) + (.append out "000") + (< cpl 256) + (.append out "00") + (< cpl 4096) + (.append out "0")) + (.append out (Integer/toHexString cp)))) + +(def ^{:tag "[S"} codepoint-decoder + (let [shorts (short-array 128)] + (dotimes [i 128] + (codepoint-case i + \" (aset shorts i (short 1)) + \\ (aset shorts i (short 1)) + \/ (aset shorts i (short 2)) + \backspace (aset shorts i (short 3)) + \formfeed (aset shorts i (short 4)) + \newline (aset shorts i (short 5)) + \return (aset shorts i (short 6)) + \tab (aset shorts i (short 7)) + (if (< i 32) + (aset shorts i (short 8)) + (aset shorts i (short 0))))) + shorts)) + +(defn- write-string [^CharSequence s ^Appendable out options] + (let [decoder codepoint-decoder] + (.append out \") + (dotimes [i (.length s)] + (let [cp (int (.charAt s i))] + (if (< cp 128) + (case (aget decoder cp) + 0 (.append out (char cp)) + 1 (do (.append out (char (codepoint \\))) (.append out (char cp))) + 2 (.append out (if (get options :escape-slash) "\\/" "/")) + 3 (.append out "\\b") + 4 (.append out "\\f") + 5 (.append out "\\n") + 6 (.append out "\\r") + 7 (.append out "\\t") + 8 (->hex-string out cp)) + (codepoint-case cp + :js-separators (if (get options :escape-js-separators) + (->hex-string out cp) + (.append out (char cp))) + (if (get options :escape-unicode) + (->hex-string out cp) ; Hexadecimal-escaped + (.append out (char cp))))))) + (.append out \"))) + +(defn- write-indent [^Appendable out options] + (let [indent-depth (:indent-depth options)] + (.append out \newline) + (loop [i indent-depth] + (when (pos? i) + (.append out " ") + (recur (dec i)))))) + +(defn- write-object [m ^Appendable out options] + (let [key-fn (get options :key-fn) + value-fn (get options :value-fn) + indent (get options :indent) + opts (cond-> options + indent (update :indent-depth inc))] + (.append out \{) + (when (and indent (seq m)) + (write-indent out opts)) + (loop [x m, have-printed-kv false] + (when (seq x) + (let [[k v] (first x) + out-key (key-fn k) + out-value (value-fn k v) + nxt (next x)] + (when-not (string? out-key) + (throw (Exception. "JSON object keys must be strings"))) + (if-not (= value-fn out-value) + (do + (when have-printed-kv + (.append out \,) + (when indent + (write-indent out opts))) + (write-string out-key out opts) + (.append out \:) + (when indent + (.append out \space)) + (-write out-value out opts) + (when (seq nxt) + (recur nxt true))) + (when (seq nxt) + (recur nxt have-printed-kv)))))) + (when (and indent (seq m)) + (write-indent out options))) + (.append out \})) + +(defn- write-array [s ^Appendable out options] + (let [indent (get options :indent) + opts (cond-> options + indent (update :indent-depth inc))] + (.append out \[) + (when (and indent (seq s)) + (write-indent out opts)) + (loop [x s] + (when (seq x) + (let [fst (first x) + nxt (next x)] + (-write fst out opts) + (when (seq nxt) + (.append out \,) + (when indent + (write-indent out opts)) + (recur nxt))))) + (when (and indent (seq s)) + (write-indent out options))) + (.append out \])) + +(defn- write-bignum [x ^Appendable out options] + (.append out (str x))) + +(defn- write-float [^Float x ^Appendable out options] + (cond (.isInfinite x) + (throw (Exception. "JSON error: cannot write infinite Float")) + (.isNaN x) + (throw (Exception. "JSON error: cannot write Float NaN")) + :else + (.append out (str x)))) + +(defn- write-double [^Double x ^Appendable out options] + (cond (.isInfinite x) + (throw (Exception. "JSON error: cannot write infinite Double")) + (.isNaN x) + (throw (Exception. "JSON error: cannot write Double NaN")) + :else + (.append out (str x)))) + +(defn- write-plain [x ^Appendable out options] + (.append out (str x))) + +(defn- write-uuid [^java.util.UUID x ^Appendable out options] + (.append out \") + (.append out (.toString x)) + (.append out \")) + +(defn- write-instant [^java.time.Instant x ^Appendable out options] + (let [formatter ^java.time.format.DateTimeFormatter (:date-formatter options)] + (.append out \") + (.append out (.format formatter x)) + (.append out \"))) + +(defn- write-date [^java.util.Date x ^Appendable out options] + (write-instant (.toInstant x) out options)) + +(defn- default-sql-date->instant-fn [^java.sql.Date d] + (.toInstant (.atStartOfDay (.toLocalDate d) (java.time.ZoneId/systemDefault)))) + +(defn- write-sql-date [^java.sql.Date x ^Appendable out options] + (let [->instant (:sql-date-converter options)] + (write-instant (->instant x) out options))) + +(defn- write-null [x ^Appendable out options] + (.append out "null")) + +(defn- write-named [x out options] + (write-string (name x) out options)) + +(defn- write-generic [x out options] + (if (.isArray (class x)) + (-write (seq x) out options) + (throw (Exception. (str "Don't know how to write JSON of " (class x)))))) + +(defn- write-ratio [x out options] + (-write (double x) out options)) + +;; nil, true, false +(extend nil JSONWriter {:-write write-null}) +(extend java.lang.Boolean JSONWriter {:-write write-plain}) + +;; Numbers +(extend java.lang.Byte JSONWriter {:-write write-plain}) +(extend java.lang.Short JSONWriter {:-write write-plain}) +(extend java.lang.Integer JSONWriter {:-write write-plain}) +(extend java.lang.Long JSONWriter {:-write write-plain}) +(extend java.lang.Float JSONWriter {:-write write-float}) +(extend java.lang.Double JSONWriter {:-write write-double}) +(extend clojure.lang.Ratio JSONWriter {:-write write-ratio}) +(extend java.math.BigInteger JSONWriter {:-write write-bignum}) +(extend java.math.BigDecimal JSONWriter {:-write write-bignum}) +(extend java.util.concurrent.atomic.AtomicInteger JSONWriter {:-write write-plain}) +(extend java.util.concurrent.atomic.AtomicLong JSONWriter {:-write write-plain}) +(extend java.util.UUID JSONWriter {:-write write-uuid}) +(extend java.time.Instant JSONWriter {:-write write-instant}) +(extend java.util.Date JSONWriter {:-write write-date}) +(extend java.sql.Date JSONWriter {:-write write-sql-date}) +(extend clojure.lang.BigInt JSONWriter {:-write write-bignum}) + +;; Symbols, Keywords, and Strings +(extend clojure.lang.Named JSONWriter {:-write write-named}) +(extend java.lang.CharSequence JSONWriter {:-write write-string}) + +;; Collections +(extend java.util.Map JSONWriter {:-write write-object}) +(extend java.util.Collection JSONWriter {:-write write-array}) + +;; Maybe a Java array, otherwise fail +(extend java.lang.Object JSONWriter {:-write write-generic}) + +(def default-write-options {:escape-unicode true + :escape-js-separators true + :escape-slash true + :sql-date-converter default-sql-date->instant-fn + :date-formatter java.time.format.DateTimeFormatter/ISO_INSTANT + :key-fn default-write-key-fn + :value-fn default-value-fn + :indent false + :indent-depth 0}) +(defn write + "Write JSON-formatted output to a java.io.Writer. Options are + key-value pairs, valid options are: + + :escape-unicode boolean + + If true (default) non-ASCII characters are escaped as \\uXXXX + + :escape-js-separators boolean + + If true (default) the Unicode characters U+2028 and U+2029 will + be escaped as \\u2028 and \\u2029 even if :escape-unicode is + false. (These two characters are valid in pure JSON but are not + valid in JavaScript strings.) + + :escape-slash boolean + + If true (default) the slash / is escaped as \\/ + + :sql-date-converter function + + Single-argument function used to convert a java.sql.Date to + a java.time.Instant. As java.sql.Date does not have a + time-component (which is required by java.time.Instant), it needs + to be computed. The default implementation, `default-sql-date->instant-fn` + uses + ``` + (.toInstant (.atStartOfDay (.toLocalDate sql-date) (java.time.ZoneId/systemDefault))) + ``` + + :date-formatter + + A java.time.DateTimeFormatter instance, defaults to DateTimeFormatter/ISO_INSTANT + + :key-fn function + + Single-argument function called on map keys; return value will + replace the property names in the output. Must return a + string. Default calls clojure.core/name on symbols and + keywords and clojure.core/str on everything else. + + :value-fn function + + Function to transform values in maps before writing. For each + key-value pair in an input map, called with two arguments: the + key (BEFORE transformation by key-fn) and the value. The + return value of value-fn will replace the value in the output. + If the return value is a number, boolean, string, or nil it + will be included literally in the output. If the return value + is a non-map collection, it will be processed recursively. If + the return value is a map, it will be processed recursively, + calling value-fn again on its key-value pairs. If value-fn + returns itself, the key-value pair will be omitted from the + output. This option does not apply to non-map collections." + [x ^Writer writer & {:as options}] + (-write x writer (merge default-write-options options))) + +(defn write-str + "Converts x to a JSON-formatted string. Options are the same as + write." + ^String [x & {:as options}] + (let [sw (StringWriter.)] + (-write x sw (merge default-write-options options)) + (.toString sw))) + +;;; JSON PRETTY-PRINTER + +;; Based on code by Tom Faulhaber + +(defn- pprint-array [s] + ((pprint/formatter-out "~<[~;~@{~w~^, ~:_~}~;]~:>") s)) + +(defn- pprint-object [m options] + (let [key-fn (:key-fn options)] + ((pprint/formatter-out "~<{~;~@{~<~w:~_~w~:>~^, ~_~}~;}~:>") + (for [[k v] m] [(key-fn k) v])))) + +(defn- pprint-generic [x options] + (if (.isArray (class x)) + (pprint-array (seq x)) + ;; pprint proxies Writer, so we can't just wrap it + (print (with-out-str (-write x (PrintWriter. *out*) options))))) + +(defn- pprint-dispatch [x options] + (cond (nil? x) (print "null") + (instance? java.util.Map x) (pprint-object x options) + (instance? java.util.Collection x) (pprint-array x) + (instance? clojure.lang.ISeq x) (pprint-array x) + :else (pprint-generic x options))) + +(defn pprint + "Pretty-prints JSON representation of x to *out*. Options are the + same as for write except :value-fn, which is not supported." + [x & {:as options}] + (let [opts (merge default-write-options options)] + (pprint/with-pprint-dispatch #(pprint-dispatch % opts) + (pprint/pprint x)))) + +;; DEPRECATED APIs from 0.1.x + +(defn read-json + "DEPRECATED; replaced by read-str. + + Reads one JSON value from input String or Reader. If keywordize? is + true (default), object keys will be converted to keywords. If + eof-error? is true (default), empty input will throw an + EOFException; if false EOF will return eof-value." + ([input] + (read-json input true true nil)) + ([input keywordize?] + (read-json input keywordize? true nil)) + ([input keywordize? eof-error? eof-value] + (let [key-fn (if keywordize? keyword identity)] + (condp instance? input + String + (read-str input + :key-fn key-fn + :eof-error? eof-error? + :eof-value eof-value) + java.io.Reader + (read input + :key-fn key-fn + :eof-error? eof-error? + :eof-value eof-value))))) + +(defn write-json + "DEPRECATED; replaced by 'write'. + + Print object to PrintWriter out as JSON" + [x out escape-unicode?] + (write x out :escape-unicode escape-unicode?)) + +(defn json-str + "DEPRECATED; replaced by 'write-str'. + + Converts x to a JSON-formatted string. + + Valid options are: + :escape-unicode false + to turn of \\uXXXX escapes of Unicode characters." + [x & options] + (apply write-str x options)) + +(defn print-json + "DEPRECATED; replaced by 'write' to *out*. + + Write JSON-formatted output to *out*. + + Valid options are: + :escape-unicode false + to turn off \\uXXXX escapes of Unicode characters." + [x & options] + (apply write x *out* options)) + +(defn pprint-json + "DEPRECATED; replaced by 'pprint'. + + Pretty-prints JSON representation of x to *out*. + + Valid options are: + :escape-unicode false + to turn off \\uXXXX escapes of Unicode characters." + [x & options] + (apply pprint x options)) \ No newline at end of file diff --git a/src/main/clojure/cljs/vendor/clojure/tools/reader.clj b/src/main/clojure/cljs/vendor/clojure/tools/reader.clj new file mode 100644 index 0000000000..f5f3afd9e8 --- /dev/null +++ b/src/main/clojure/cljs/vendor/clojure/tools/reader.clj @@ -0,0 +1,1035 @@ +;; Copyright (c) Nicola Mometto, Rich Hickey & contributors. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns ^{:doc "A clojure reader in clojure" + :author "Bronsa"} + cljs.vendor.clojure.tools.reader + (:refer-clojure :exclude [read read-line read-string char read+string + default-data-readers *default-data-reader-fn* + *read-eval* *data-readers* *suppress-read*]) + (:require [cljs.vendor.clojure.tools.reader.reader-types :refer + [read-char unread peek-char indexing-reader? source-logging-push-back-reader source-logging-reader? + get-line-number get-column-number get-file-name string-push-back-reader log-source]] + [cljs.vendor.clojure.tools.reader.impl.utils :refer :all] ;; [char ex-info? whitespace? numeric? desugar-meta] + [cljs.vendor.clojure.tools.reader.impl.errors :as err] + [cljs.vendor.clojure.tools.reader.impl.commons :refer :all] + [cljs.vendor.clojure.tools.reader.default-data-readers :as data-readers]) + (:import (clojure.lang PersistentHashSet IMeta + RT Symbol Reflector Var IObj + PersistentVector IRecord Namespace) + cljs.vendor.clojure.tools.reader.reader_types.SourceLoggingPushbackReader + java.lang.reflect.Constructor + java.util.regex.Pattern + (java.util List LinkedList))) + +(set! *warn-on-reflection* true) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; helpers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(declare ^:private read* + macros dispatch-macros + ^:dynamic *read-eval* + ^:dynamic *data-readers* + ^:dynamic *default-data-reader-fn* + ^:dynamic *suppress-read* + default-data-readers) + +(defn ^:private ns-name* [x] + (if (instance? Namespace x) + (name (ns-name x)) + (name x))) + +(defn- macro-terminating? [ch] + (case ch + (\" \; \@ \^ \` \~ \( \) \[ \] \{ \} \\) true + false)) + +(defn- ^String read-token + "Read in a single logical token from the reader" + [rdr kind initch] + (if-not initch + (err/throw-eof-at-start rdr kind) + (loop [sb (StringBuilder.) ch initch] + (if (or (whitespace? ch) + (macro-terminating? ch) + (nil? ch)) + (do (when ch + (unread rdr ch)) + (str sb)) + (recur (.append sb ch) (read-char rdr)))))) + +(declare read-tagged) + +(defn- read-dispatch + [rdr _ opts pending-forms] + (if-let [ch (read-char rdr)] + (if-let [dm (dispatch-macros ch)] + (dm rdr ch opts pending-forms) + (read-tagged (doto rdr (unread ch)) ch opts pending-forms)) ;; ctor reader is implemented as a tagged literal + (err/throw-eof-at-dispatch rdr))) + +(defn- read-unmatched-delimiter + [rdr ch opts pending-forms] + (err/throw-unmatch-delimiter rdr ch)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; readers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn read-regex + [rdr ch opts pending-forms] + (let [sb (StringBuilder.)] + (loop [ch (read-char rdr)] + (if (identical? \" ch) + (Pattern/compile (str sb)) + (if (nil? ch) + (err/throw-eof-reading rdr :regex sb) + (do + (.append sb ch ) + (when (identical? \\ ch) + (let [ch (read-char rdr)] + (if (nil? ch) + (err/throw-eof-reading rdr :regex sb)) + (.append sb ch))) + (recur (read-char rdr)))))))) + +(defn- read-unicode-char + ([^String token ^long offset ^long length ^long base] + (let [l (+ offset length)] + (when-not (== (count token) l) + (err/throw-invalid-unicode-literal nil token)) + (loop [i offset uc 0] + (if (== i l) + (char uc) + (let [d (Character/digit (int (nth token i)) (int base))] + (if (== d -1) + (err/throw-invalid-unicode-digit-in-token nil (nth token i) token) + (recur (inc i) (long (+ d (* uc base)))))))))) + + ([rdr initch base length exact?] + (let [base (long base) + length (long length)] + (loop [i 1 uc (long (Character/digit (int initch) (int base)))] + (if (== uc -1) + (err/throw-invalid-unicode-digit rdr initch) + (if-not (== i length) + (let [ch (peek-char rdr)] + (if (or (whitespace? ch) + (macros ch) + (nil? ch)) + (if exact? + (err/throw-invalid-unicode-len rdr i length) + (char uc)) + (let [d (Character/digit (int ch) (int base))] + (read-char rdr) + (if (== d -1) + (err/throw-invalid-unicode-digit rdr ch) + (recur (inc i) (long (+ d (* uc base)))))))) + (char uc))))))) + +(def ^:private ^:const upper-limit (int \uD7ff)) +(def ^:private ^:const lower-limit (int \uE000)) + +(defn- read-char* + "Read in a character literal" + [rdr backslash opts pending-forms] + (let [ch (read-char rdr)] + (if-not (nil? ch) + (let [token (if (or (macro-terminating? ch) + (whitespace? ch)) + (str ch) + (read-token rdr :character ch)) + token-len (count token)] + (cond + + (== 1 token-len) (Character/valueOf (nth token 0)) + + (= token "newline") \newline + (= token "space") \space + (= token "tab") \tab + (= token "backspace") \backspace + (= token "formfeed") \formfeed + (= token "return") \return + + (.startsWith token "u") + (let [c (read-unicode-char token 1 4 16) + ic (int c)] + (if (and (> ic upper-limit) + (< ic lower-limit)) + (err/throw-invalid-character-literal rdr (Integer/toString ic 16)) + c)) + + (.startsWith token "o") + (let [len (dec token-len)] + (if (> len 3) + (err/throw-invalid-octal-len rdr token) + (let [uc (read-unicode-char token 1 len 8)] + (if (> (int uc) 0377) + (err/throw-bad-octal-number rdr) + uc)))) + + :else (err/throw-unsupported-character rdr token))) + (err/throw-eof-in-character rdr)))) + +(defn ^:private starting-line-col-info [rdr] + (when (indexing-reader? rdr) + [(get-line-number rdr) (int (dec (int (get-column-number rdr))))])) + +(defn ^:private ending-line-col-info [rdr] + (when (indexing-reader? rdr) + [(get-line-number rdr) (get-column-number rdr)])) + +(defonce ^:private READ_EOF (Object.)) +(defonce ^:private READ_FINISHED (Object.)) + +(def ^:dynamic *read-delim* false) +(defn- ^PersistentVector read-delimited + "Reads and returns a collection ended with delim" + [kind delim rdr opts pending-forms] + (let [[start-line start-column] (starting-line-col-info rdr) + delim (char delim)] + (binding [*read-delim* true] + (loop [a (transient [])] + (let [form (read* rdr false READ_EOF delim opts pending-forms)] + (if (identical? form READ_FINISHED) + (persistent! a) + (if (identical? form READ_EOF) + (err/throw-eof-delimited rdr kind start-line start-column (count a)) + (recur (conj! a form))))))))) + +(defn- read-list + "Read in a list, including its location if the reader is an indexing reader" + [rdr _ opts pending-forms] + (let [[start-line start-column] (starting-line-col-info rdr) + the-list (read-delimited :list \) rdr opts pending-forms) + [end-line end-column] (ending-line-col-info rdr)] + (with-meta (if (empty? the-list) + '() + (clojure.lang.PersistentList/create the-list)) + (when start-line + (merge + (when-let [file (get-file-name rdr)] + {:file file}) + {:line start-line + :column start-column + :end-line end-line + :end-column end-column}))))) + +(defn- read-vector + "Read in a vector, including its location if the reader is an indexing reader" + [rdr _ opts pending-forms] + (let [[start-line start-column] (starting-line-col-info rdr) + the-vector (read-delimited :vector \] rdr opts pending-forms) + [end-line end-column] (ending-line-col-info rdr)] + (with-meta the-vector + (when start-line + (merge + (when-let [file (get-file-name rdr)] + {:file file}) + {:line start-line + :column start-column + :end-line end-line + :end-column end-column}))))) + +(defn- read-map + "Read in a map, including its location if the reader is an indexing reader" + [rdr _ opts pending-forms] + (let [[start-line start-column] (starting-line-col-info rdr) + the-map (read-delimited :map \} rdr opts pending-forms) + map-count (count the-map) + [end-line end-column] (ending-line-col-info rdr)] + (when (odd? map-count) + (err/throw-odd-map rdr start-line start-column the-map)) + (with-meta + (if (zero? map-count) + {} + (RT/map (to-array the-map))) + (when start-line + (merge + (when-let [file (get-file-name rdr)] + {:file file}) + {:line start-line + :column start-column + :end-line end-line + :end-column end-column}))))) + +(defn- read-number + [rdr initch] + (loop [sb (doto (StringBuilder.) (.append initch)) + ch (read-char rdr)] + (if (or (whitespace? ch) (macros ch) (nil? ch)) + (let [s (str sb)] + (unread rdr ch) + (or (match-number s) + (err/throw-invalid-number rdr s))) + (recur (doto sb (.append ch)) (read-char rdr))))) + +(defn- escape-char [sb rdr] + (let [ch (read-char rdr)] + (case ch + \t "\t" + \r "\r" + \n "\n" + \\ "\\" + \" "\"" + \b "\b" + \f "\f" + \u (let [ch (read-char rdr)] + (if (== -1 (Character/digit (int ch) 16)) + (err/throw-invalid-unicode-escape rdr ch) + (read-unicode-char rdr ch 16 4 true))) + (if (numeric? ch) + (let [ch (read-unicode-char rdr ch 8 3 false)] + (if (> (int ch) 0377) + (err/throw-bad-octal-number rdr) + ch)) + (err/throw-bad-escape-char rdr ch))))) + +(defn- read-string* + [reader _ opts pending-forms] + (loop [sb (StringBuilder.) + ch (read-char reader)] + (case ch + nil (err/throw-eof-reading reader :string sb) + \\ (recur (doto sb (.append (escape-char sb reader))) + (read-char reader)) + \" (str sb) + (recur (doto sb (.append ch)) (read-char reader))))) + +(defn- read-symbol + [rdr initch] + (let [[line column] (starting-line-col-info rdr)] + (when-let [token (read-token rdr :symbol initch)] + (case token + + ;; special symbols + "nil" nil + "true" true + "false" false + "/" '/ + + (or (when-let [p (parse-symbol token)] + (with-meta (symbol (p 0) (p 1)) + (when line + (merge + (when-let [file (get-file-name rdr)] + {:file file}) + (let [[end-line end-column] (ending-line-col-info rdr)] + {:line line + :column column + :end-line end-line + :end-column end-column}))))) + (err/throw-invalid rdr :symbol token)))))) + +(def ^:dynamic *alias-map* + "Map from ns alias to ns, if non-nil, it will be used to resolve read-time + ns aliases instead of (ns-aliases *ns*). + + Defaults to nil" + nil) + +(defn- resolve-alias [sym] + ((or *alias-map* + (ns-aliases *ns*)) sym)) + +(defn- resolve-ns [sym] + (or (resolve-alias sym) + (find-ns sym))) + +(defn- read-keyword + [reader initch opts pending-forms] + (let [ch (read-char reader)] + (if-not (whitespace? ch) + (let [token (read-token reader :keyword ch) + s (parse-symbol token)] + (if s + (let [^String ns (s 0) + ^String name (s 1)] + (if (identical? \: (nth token 0)) + (if ns + (let [ns (resolve-alias (symbol (subs ns 1)))] + (if ns + (keyword (str ns) name) + (err/throw-invalid reader :keyword (str \: token)))) + (keyword (str *ns*) (subs name 1))) + (keyword ns name))) + (err/throw-invalid reader :keyword (str \: token)))) + (err/throw-single-colon reader)))) + +(defn- wrapping-reader + "Returns a function which wraps a reader in a call to sym" + [sym] + (fn [rdr _ opts pending-forms] + (list sym (read* rdr true nil opts pending-forms)))) + +(defn- read-meta + "Read metadata and return the following object with the metadata applied" + [rdr _ opts pending-forms] + (log-source rdr + (let [[line column] (starting-line-col-info rdr) + m (desugar-meta (read* rdr true nil opts pending-forms))] + (when-not (map? m) + (err/throw-bad-metadata rdr m)) + (let [o (read* rdr true nil opts pending-forms)] + (if (instance? IMeta o) + (let [m (if (and line (seq? o)) + (merge {:line line :column column} m) + m)] + (if (instance? IObj o) + (with-meta o (merge (meta o) m)) + (reset-meta! o m))) + (err/throw-bad-metadata-target rdr o)))))) + +(defn- read-set + [rdr _ opts pending-forms] + (let [[start-line start-column] (starting-line-col-info rdr) + ;; subtract 1 from start-column so it includes the # in the leading #{ + start-column (if start-column (int (dec (int start-column)))) + the-set (PersistentHashSet/createWithCheck + (read-delimited :set \} rdr opts pending-forms)) + [end-line end-column] (ending-line-col-info rdr)] + (with-meta the-set + (when start-line + (merge + (when-let [file (get-file-name rdr)] + {:file file}) + {:line start-line + :column start-column + :end-line end-line + :end-column end-column}))))) + +(defn- read-discard + "Read and discard the first object from rdr" + [rdr _ opts pending-forms] + (doto rdr + (read* true nil opts pending-forms))) + +(defn- read-symbolic-value + [rdr _ opts pending-forms] + (let [sym (read* rdr true nil opts pending-forms)] + (case sym + Inf Double/POSITIVE_INFINITY + -Inf Double/NEGATIVE_INFINITY + NaN Double/NaN + (err/reader-error rdr (str "Invalid token: ##" sym))))) + +(def ^:private RESERVED_FEATURES #{:else :none}) + +(defn- has-feature? + [rdr feature opts] + (if (keyword? feature) + (or (= :default feature) (contains? (get opts :features) feature)) + (err/throw-feature-not-keyword rdr feature))) + +;; WIP, move to errors in the future +(defn- check-eof-error + [form rdr ^long first-line] + (when (identical? form READ_EOF) + (err/throw-eof-error rdr (and (< first-line 0) first-line)))) + +(defn- check-reserved-features + [rdr form] + (when (get RESERVED_FEATURES form) + (err/reader-error rdr "Feature name " form " is reserved"))) + +(defn- check-invalid-read-cond + [form rdr ^long first-line] + (when (identical? form READ_FINISHED) + (if (< first-line 0) + (err/reader-error rdr "read-cond requires an even number of forms") + (err/reader-error rdr "read-cond starting on line " first-line " requires an even number of forms")))) + +(defn- read-suppress + "Read next form and suppress. Return nil or READ_FINISHED." + [first-line rdr opts pending-forms] + (binding [*suppress-read* true] + (let [form (read* rdr false READ_EOF \) opts pending-forms)] + (check-eof-error form rdr first-line) + (when (identical? form READ_FINISHED) + READ_FINISHED)))) + +(def ^:private NO_MATCH (Object.)) + +(defn- match-feature + "Read next feature. If matched, read next form and return. + Otherwise, read and skip next form, returning READ_FINISHED or nil." + [first-line rdr opts pending-forms] + (let [feature (read* rdr false READ_EOF \) opts pending-forms)] + (check-eof-error feature rdr first-line) + (if (= feature READ_FINISHED) + READ_FINISHED + (do + (check-reserved-features rdr feature) + (if (has-feature? rdr feature opts) + ;; feature matched, read selected form + (doto (read* rdr false READ_EOF \) opts pending-forms) + (check-eof-error rdr first-line) + (check-invalid-read-cond rdr first-line)) + ;; feature not matched, ignore next form + (or (read-suppress first-line rdr opts pending-forms) + NO_MATCH)))))) + +(defn- read-cond-delimited + [rdr splicing opts pending-forms] + (let [first-line (if (indexing-reader? rdr) (get-line-number rdr) -1) + result (loop [matched NO_MATCH + finished nil] + (cond + ;; still looking for match, read feature+form + (identical? matched NO_MATCH) + (let [match (match-feature first-line rdr opts pending-forms)] + (if (identical? match READ_FINISHED) + READ_FINISHED + (recur match nil))) + + ;; found match, just read and ignore the rest + (not (identical? finished READ_FINISHED)) + (recur matched (read-suppress first-line rdr opts pending-forms)) + + :else + matched))] + (if (identical? result READ_FINISHED) + rdr + (if splicing + (if (instance? List result) + (do + (.addAll ^List pending-forms 0 ^List result) + rdr) + (err/reader-error rdr "Spliced form list in read-cond-splicing must implement java.util.List.")) + result)))) + +(defn- read-cond + [rdr _ opts pending-forms] + (when (not (and opts (#{:allow :preserve} (:read-cond opts)))) + (throw (RuntimeException. "Conditional read not allowed"))) + (if-let [ch (read-char rdr)] + (let [splicing (= ch \@) + ch (if splicing (read-char rdr) ch)] + (when splicing + (when-not *read-delim* + (err/reader-error rdr "cond-splice not in list"))) + (if-let [ch (if (whitespace? ch) (read-past whitespace? rdr) ch)] + (if (not= ch \() + (throw (RuntimeException. "read-cond body must be a list")) + (binding [*suppress-read* (or *suppress-read* (= :preserve (:read-cond opts)))] + (if *suppress-read* + (reader-conditional (read-list rdr ch opts pending-forms) splicing) + (read-cond-delimited rdr splicing opts pending-forms)))) + (err/throw-eof-in-character rdr))) + (err/throw-eof-in-character rdr))) + +(def ^:private ^:dynamic arg-env) + +(defn- garg + "Get a symbol for an anonymous ?argument?" + [^long n] + (symbol (str (if (== -1 n) "rest" (str "p" n)) + "__" (RT/nextID) "#"))) + +(defn- read-fn + [rdr _ opts pending-forms] + (if (thread-bound? #'arg-env) + (throw (IllegalStateException. "Nested #()s are not allowed"))) + (binding [arg-env (sorted-map)] + (let [form (read* (doto rdr (unread \()) true nil opts pending-forms) ;; this sets bindings + rargs (rseq arg-env) + args (if rargs + (let [higharg (long (key ( first rargs)))] + (let [args (loop [i 1 args (transient [])] + (if (> i higharg) + (persistent! args) + (recur (inc i) (conj! args (or (get arg-env i) + (garg i)))))) + args (if (arg-env -1) + (conj args '& (arg-env -1)) + args)] + args)) + [])] + (list 'fn* args form)))) + +(defn- register-arg + "Registers an argument to the arg-env" + [n] + (if (thread-bound? #'arg-env) + (if-let [ret (arg-env n)] + ret + (let [g (garg n)] + (set! arg-env (assoc arg-env n g)) + g)) + (throw (IllegalStateException. "Arg literal not in #()")))) ;; should never hit this + +(declare read-symbol) + +(defn- read-arg + [rdr pct opts pending-forms] + (if-not (thread-bound? #'arg-env) + (read-symbol rdr pct) + (let [ch (peek-char rdr)] + (cond + (or (whitespace? ch) + (macro-terminating? ch) + (nil? ch)) + (register-arg 1) + + (identical? ch \&) + (do (read-char rdr) + (register-arg -1)) + + :else + (let [n (read* rdr true nil opts pending-forms)] + (if-not (integer? n) + (throw (IllegalStateException. "Arg literal must be %, %& or %integer")) + (register-arg n))))))) + +(defn- read-eval + "Evaluate a reader literal" + [rdr _ opts pending-forms] + (when-not *read-eval* + (err/reader-error rdr "#= not allowed when *read-eval* is false")) + (eval (read* rdr true nil opts pending-forms))) + +(def ^:private ^:dynamic gensym-env nil) + +(defn- read-unquote + [rdr comma opts pending-forms] + (if-let [ch (peek-char rdr)] + (if (identical? \@ ch) + ((wrapping-reader 'clojure.core/unquote-splicing) (doto rdr read-char) \@ opts pending-forms) + ((wrapping-reader 'clojure.core/unquote) rdr \~ opts pending-forms)))) + +(declare syntax-quote*) +(defn- unquote-splicing? [form] + (and (seq? form) + (= (first form) 'clojure.core/unquote-splicing))) + +(defn- unquote? [form] + (and (seq? form) + (= (first form) 'clojure.core/unquote))) + +(defn- expand-list + "Expand a list by resolving its syntax quotes and unquotes" + [s] + (loop [s (seq s) r (transient [])] + (if s + (let [item (first s) + ret (conj! r + (cond + (unquote? item) (list 'clojure.core/list (second item)) + (unquote-splicing? item) (second item) + :else (list 'clojure.core/list (syntax-quote* item))))] + (recur (next s) ret)) + (seq (persistent! r))))) + +(defn- flatten-map + "Flatten a map into a seq of alternate keys and values" + [form] + (loop [s (seq form) key-vals (transient [])] + (if s + (let [e (first s)] + (recur (next s) (-> key-vals + (conj! (key e)) + (conj! (val e))))) + (seq (persistent! key-vals))))) + +(defn- register-gensym [sym] + (if-not gensym-env + (throw (IllegalStateException. "Gensym literal not in syntax-quote"))) + (or (get gensym-env sym) + (let [gs (symbol (str (subs (name sym) + 0 (dec (count (name sym)))) + "__" (RT/nextID) "__auto__"))] + (set! gensym-env (assoc gensym-env sym gs)) + gs))) + +(defn ^:dynamic resolve-symbol + "Resolve a symbol s into its fully qualified namespace version" + [s] + (if (pos? (.indexOf (name s) ".")) + (if (.endsWith (name s) ".") + (let [csym (symbol (subs (name s) 0 (dec (count (name s)))))] + (symbol (str (name (resolve-symbol csym)) "."))) + s) + (if-let [ns-str (namespace s)] + (let [ns (resolve-ns (symbol ns-str))] + (if (or (nil? ns) + (= (ns-name* ns) ns-str)) ;; not an alias + s + (symbol (ns-name* ns) (name s)))) + (if-let [o ((ns-map *ns*) s)] + (if (class? o) + (symbol (.getName ^Class o)) + (if (var? o) + (symbol (-> ^Var o .ns ns-name*) (-> ^Var o .sym name)))) + (symbol (ns-name* *ns*) (name s)))))) + +(defn- add-meta [form ret] + (if (and (instance? IObj form) + (seq (dissoc (meta form) :line :column :end-line :end-column :file :source))) + (list 'clojure.core/with-meta ret (syntax-quote* (meta form))) + ret)) + +(defn- syntax-quote-coll [type coll] + ;; We use sequence rather than seq here to fix https://clojure.atlassian.net/browse/CLJ-1444 + ;; But because of https://clojure.atlassian.net/browse/CLJ-1586 we still need to call seq on the form + (let [res (list 'clojure.core/sequence + (list 'clojure.core/seq + (cons 'clojure.core/concat + (expand-list coll))))] + (if type + (list 'clojure.core/apply type res) + res))) + +(defn map-func + "Decide which map type to use, array-map if less than 16 elements" + [coll] + (if (>= (count coll) 16) + 'clojure.core/hash-map + 'clojure.core/array-map)) + +(defn- syntax-quote* [form] + (->> + (cond + (special-symbol? form) (list 'quote form) + + (symbol? form) + (list 'quote + (if (namespace form) + (let [maybe-class ((ns-map *ns*) + (symbol (namespace form)))] + (if (class? maybe-class) + (symbol (.getName ^Class maybe-class) (name form)) + (resolve-symbol form))) + (let [sym (str form)] + (cond + (.endsWith sym "#") + (register-gensym form) + + (.startsWith sym ".") + form + + :else (resolve-symbol form))))) + + (unquote? form) (second form) + (unquote-splicing? form) (throw (IllegalStateException. "unquote-splice not in list")) + + (coll? form) + (cond + + (instance? IRecord form) form + (map? form) (syntax-quote-coll (map-func form) (flatten-map form)) + (vector? form) (list 'clojure.core/vec (syntax-quote-coll nil form)) + (set? form) (syntax-quote-coll 'clojure.core/hash-set form) + (or (seq? form) (list? form)) + (let [seq (seq form)] + (if seq + (syntax-quote-coll nil seq) + '(clojure.core/list))) + + :else (throw (UnsupportedOperationException. "Unknown Collection type"))) + + (or (keyword? form) + (number? form) + (char? form) + (string? form) + (nil? form) + (instance? Boolean form) + (instance? Pattern form)) + form + + :else (list 'quote form)) + (add-meta form))) + +(defn- read-syntax-quote + [rdr backquote opts pending-forms] + (binding [gensym-env {}] + (-> (read* rdr true nil opts pending-forms) + syntax-quote*))) + +(defn- read-namespaced-map + [rdr _ opts pending-forms] + (let [[start-line start-column] (starting-line-col-info rdr) + token (read-token rdr :namespaced-map (read-char rdr))] + (if-let [ns (cond + (= token ":") + (ns-name *ns*) + + (= \: (first token)) + (some-> token (subs 1) parse-symbol second' symbol resolve-ns) + + :else + (some-> token parse-symbol second'))] + + (let [ch (read-past whitespace? rdr)] + (if (identical? ch \{) + (let [items (read-delimited :namespaced-map \} rdr opts pending-forms) + [end-line end-column] (ending-line-col-info rdr)] + (when (odd? (count items)) + (err/throw-odd-map rdr nil nil items)) + (let [keys (take-nth 2 items) + vals (take-nth 2 (rest items))] + (with-meta + (RT/map (to-array (mapcat list (namespace-keys (str ns) keys) vals))) + (when start-line + (merge + (when-let [file (get-file-name rdr)] + {:file file}) + {:line start-line + :column start-column + :end-line end-line + :end-column end-column}))))) + (err/throw-ns-map-no-map rdr token))) + (err/throw-bad-ns rdr token)))) + +(defn- macros [ch] + (case ch + \" read-string* + \: read-keyword + \; read-comment + \' (wrapping-reader 'quote) + \@ (wrapping-reader 'clojure.core/deref) + \^ read-meta + \` read-syntax-quote ;;(wrapping-reader 'syntax-quote) + \~ read-unquote + \( read-list + \) read-unmatched-delimiter + \[ read-vector + \] read-unmatched-delimiter + \{ read-map + \} read-unmatched-delimiter + \\ read-char* + \% read-arg + \# read-dispatch + nil)) + +(defn- dispatch-macros [ch] + (case ch + \^ read-meta ;deprecated + \' (wrapping-reader 'var) + \( read-fn + \= read-eval + \{ read-set + \< (throwing-reader "Unreadable form") + \" read-regex + \! read-comment + \_ read-discard + \? read-cond + \: read-namespaced-map + \# read-symbolic-value + nil)) + +(defn- read-ctor [rdr class-name opts pending-forms] + (when-not *read-eval* + (err/reader-error rdr "Record construction syntax can only be used when *read-eval* == true")) + (let [class (Class/forName (name class-name) false (RT/baseLoader)) + ch (read-past whitespace? rdr)] ;; differs from clojure + (if-let [[end-ch form] (case ch + \[ [\] :short] + \{ [\} :extended] + nil)] + (let [entries (to-array (read-delimited :record-ctor end-ch rdr opts pending-forms)) + numargs (count entries) + all-ctors (.getConstructors class) + ctors-num (count all-ctors)] + (case form + :short + (loop [i 0] + (if (>= i ctors-num) + (err/reader-error rdr "Unexpected number of constructor arguments to " (str class) + ": got " numargs) + (if (== (count (.getParameterTypes ^Constructor (aget all-ctors i))) + numargs) + (Reflector/invokeConstructor class entries) + (recur (inc i))))) + :extended + (let [vals (RT/map entries)] + (loop [s (keys vals)] + (if s + (if-not (keyword? (first s)) + (err/reader-error rdr "Unreadable ctor form: key must be of type clojure.lang.Keyword") + (recur (next s))))) + (Reflector/invokeStaticMethod class "create" (object-array [vals]))))) + (err/reader-error rdr "Invalid reader constructor form")))) + +(defn- read-tagged [rdr initch opts pending-forms] + (let [tag (read* rdr true nil opts pending-forms)] + (if-not (symbol? tag) + (err/throw-bad-reader-tag rdr tag)) + (if *suppress-read* + (tagged-literal tag (read* rdr true nil opts pending-forms)) + (if-let [f (or (*data-readers* tag) + (default-data-readers tag))] + (f (read* rdr true nil opts pending-forms)) + (if (.contains (name tag) ".") + (read-ctor rdr tag opts pending-forms) + (if-let [f *default-data-reader-fn*] + (f tag (read* rdr true nil opts pending-forms)) + (err/throw-unknown-reader-tag rdr tag))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Public API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def ^:dynamic *read-eval* + "Defaults to true. + + ***WARNING*** + This setting implies that the full power of the reader is in play, + including syntax that can cause code to execute. It should never be + used with untrusted sources. See also: cljs.vendor.clojure.tools.reader.edn/read. + + When set to logical false in the thread-local binding, + the eval reader (#=) and *record/type literal syntax* are disabled in read/load. + Example (will fail): (binding [*read-eval* false] (read-string \"#=(* 2 21)\")) + + When set to :unknown all reads will fail in contexts where *read-eval* + has not been explicitly bound to either true or false. This setting + can be a useful diagnostic tool to ensure that all of your reads + occur in considered contexts." + true) + +(def ^:dynamic *data-readers* + "Map from reader tag symbols to data reader Vars. + Reader tags without namespace qualifiers are reserved for Clojure. + Default reader tags are defined in cljs.vendor.clojure.tools.reader/default-data-readers + and may be overridden by binding this Var." + {}) + +(def ^:dynamic *default-data-reader-fn* + "When no data reader is found for a tag and *default-data-reader-fn* + is non-nil, it will be called with two arguments, the tag and the value. + If *default-data-reader-fn* is nil (the default value), an exception + will be thrown for the unknown tag." + nil) + +(def ^:dynamic *suppress-read* false) + +(def default-data-readers + "Default map of data reader functions provided by Clojure. + May be overridden by binding *data-readers*" + {'inst #'data-readers/read-instant-date + 'uuid #'data-readers/default-uuid-reader}) + +(defn ^:private read* + ([reader eof-error? sentinel opts pending-forms] + (read* reader eof-error? sentinel nil opts pending-forms)) + ([reader eof-error? sentinel return-on opts pending-forms] + (when (= :unknown *read-eval*) + (err/reader-error "Reading disallowed - *read-eval* bound to :unknown")) + (try + (loop [] + (let [ret (log-source reader + (if (seq pending-forms) + (.remove ^List pending-forms 0) + (let [ch (read-char reader)] + (cond + (whitespace? ch) reader + (nil? ch) (if eof-error? (err/throw-eof-error reader nil) sentinel) + (= ch return-on) READ_FINISHED + (number-literal? reader ch) (read-number reader ch) + :else (if-let [f (macros ch)] + (f reader ch opts pending-forms) + (read-symbol reader ch))))))] + (if (identical? ret reader) + (recur) + ret))) + (catch Exception e + (if (ex-info? e) + (let [d (ex-data e)] + (if (= :reader-exception (:type d)) + (throw e) + (throw (ex-info (.getMessage e) + (merge {:type :reader-exception} + d + (if (indexing-reader? reader) + {:line (get-line-number reader) + :column (get-column-number reader) + :file (get-file-name reader)})) + e)))) + (throw (ex-info (.getMessage e) + (merge {:type :reader-exception} + (if (indexing-reader? reader) + {:line (get-line-number reader) + :column (get-column-number reader) + :file (get-file-name reader)})) + e))))))) + +(defn read + "Reads the first object from an IPushbackReader or a java.io.PushbackReader. + Returns the object read. If EOF, throws if eof-error? is true. + Otherwise returns sentinel. If no stream is provided, *in* will be used. + + Opts is a persistent map with valid keys: + :read-cond - :allow to process reader conditionals, or + :preserve to keep all branches + :features - persistent set of feature keywords for reader conditionals + :eof - on eof, return value unless :eofthrow, then throw. + if not specified, will throw + + ***WARNING*** + Note that read can execute code (controlled by *read-eval*), + and as such should be used only with trusted sources. + + To read data structures only, use cljs.vendor.clojure.tools.reader.edn/read + + Note that the function signature of cljs.vendor.clojure.tools.reader/read and + cljs.vendor.clojure.tools.reader.edn/read is not the same for eof-handling" + {:arglists '([] [reader] [opts reader] [reader eof-error? eof-value])} + ([] (read *in* true nil)) + ([reader] (read reader true nil)) + ([{eof :eof :as opts :or {eof :eofthrow}} reader] + (when (source-logging-reader? reader) + (let [^StringBuilder buf (:buffer @(.source-log-frames ^SourceLoggingPushbackReader reader))] + (.setLength buf 0))) + (read* reader (= eof :eofthrow) eof nil opts (LinkedList.))) + ([reader eof-error? sentinel] + (when (source-logging-reader? reader) + (let [^StringBuilder buf (:buffer @(.source-log-frames ^SourceLoggingPushbackReader reader))] + (.setLength buf 0))) + (read* reader eof-error? sentinel nil {} (LinkedList.)))) + +(defn read-string + "Reads one object from the string s. + Returns nil when s is nil or empty. + + ***WARNING*** + Note that read-string can execute code (controlled by *read-eval*), + and as such should be used only with trusted sources. + + To read data structures only, use cljs.vendor.clojure.tools.reader.edn/read-string + + Note that the function signature of cljs.vendor.clojure.tools.reader/read-string and + cljs.vendor.clojure.tools.reader.edn/read-string is not the same for eof-handling" + ([s] + (read-string {} s)) + ([opts s] + (when (and s (not (identical? s ""))) + (read opts (string-push-back-reader s))))) + +(defmacro syntax-quote + "Macro equivalent to the syntax-quote reader macro (`)." + [form] + (binding [gensym-env {}] + (syntax-quote* form))) + +(defn read+string + "Like read, and taking the same args. reader must be a SourceLoggingPushbackReader. + Returns a vector containing the object read and the (whitespace-trimmed) string read." + ([] (read+string (source-logging-push-back-reader *in*))) + ([stream] (read+string stream true nil)) + ([^SourceLoggingPushbackReader stream eof-error? eof-value] + (let [^StringBuilder buf (doto ^StringBuilder (:buffer @(.source-log-frames stream)) (.setLength 0)) + o (log-source stream (read stream eof-error? eof-value)) + s (.trim (str buf))] + [o s])) + ([opts ^SourceLoggingPushbackReader stream] + (let [^StringBuilder buf (doto ^StringBuilder (:buffer @(.source-log-frames stream)) (.setLength 0)) + o (log-source stream (read opts stream)) + s (.trim (str buf))] + [o s]))) diff --git a/src/main/clojure/cljs/vendor/clojure/tools/reader/default_data_readers.clj b/src/main/clojure/cljs/vendor/clojure/tools/reader/default_data_readers.clj new file mode 100644 index 0000000000..3a61f97759 --- /dev/null +++ b/src/main/clojure/cljs/vendor/clojure/tools/reader/default_data_readers.clj @@ -0,0 +1,303 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +;;; copied from clojure.instant and clojure.uuid ;;; + +(ns ^:skip-wiki cljs.vendor.clojure.tools.reader.default-data-readers + (:import [java.util Calendar Date GregorianCalendar TimeZone] + [java.sql Timestamp])) + +;;; clojure.instant ;;; + +;;; ------------------------------------------------------------------------ +;;; convenience macros + +(defmacro ^:private fail + [msg] + `(throw (RuntimeException. ~msg))) + +(defmacro ^:private verify + ([test msg] `(when-not ~test (fail ~msg))) + ([test] `(verify ~test ~(str "failed: " (pr-str test))))) + +(defn- divisible? + [num div] + (zero? (mod num div))) + +(defn- indivisible? + [num div] + (not (divisible? num div))) + + +;;; ------------------------------------------------------------------------ +;;; parser implementation + +(defn- parse-int [^String s] + (Long/parseLong s)) + +(defn- zero-fill-right [^String s width] + (cond (= width (count s)) s + (< width (count s)) (.substring s 0 width) + :else (loop [b (StringBuilder. s)] + (if (< (.length b) width) + (recur (.append b \0)) + (.toString b))))) + +(def parse-timestamp + "Parse a string containing an RFC3339-like like timestamp. + +The function new-instant is called with the following arguments. + + min max default + --- ------------ ------- + years 0 9999 N/A (s must provide years) + months 1 12 1 + days 1 31 1 (actual max days depends + hours 0 23 0 on month and year) + minutes 0 59 0 + seconds 0 60 0 (though 60 is only valid + nanoseconds 0 999999999 0 when minutes is 59) + offset-sign -1 1 0 + offset-hours 0 23 0 + offset-minutes 0 59 0 + +These are all integers and will be non-nil. (The listed defaults +will be passed if the corresponding field is not present in s.) + +Grammar (of s): + + date-fullyear = 4DIGIT + date-month = 2DIGIT ; 01-12 + date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on + ; month/year + time-hour = 2DIGIT ; 00-23 + time-minute = 2DIGIT ; 00-59 + time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second + ; rules + time-secfrac = '.' 1*DIGIT + time-numoffset = ('+' / '-') time-hour ':' time-minute + time-offset = 'Z' / time-numoffset + + time-part = time-hour [ ':' time-minute [ ':' time-second + [time-secfrac] [time-offset] ] ] + + timestamp = date-year [ '-' date-month [ '-' date-mday + [ 'T' time-part ] ] ] + +Unlike RFC3339: + + - we only parse the timestamp format + - timestamp can elide trailing components + - time-offset is optional (defaults to +00:00) + +Though time-offset is syntactically optional, a missing time-offset +will be treated as if the time-offset zero (+00:00) had been +specified. +" + (let [timestamp #"(\d\d\d\d)(?:-(\d\d)(?:-(\d\d)(?:[T](\d\d)(?::(\d\d)(?::(\d\d)(?:[.](\d+))?)?)?)?)?)?(?:[Z]|([-+])(\d\d):(\d\d))?"] + + (fn [new-instant ^CharSequence cs] + (if-let [[_ years months days hours minutes seconds fraction + offset-sign offset-hours offset-minutes] + (re-matches timestamp cs)] + (new-instant + (parse-int years) + (if-not months 1 (parse-int months)) + (if-not days 1 (parse-int days)) + (if-not hours 0 (parse-int hours)) + (if-not minutes 0 (parse-int minutes)) + (if-not seconds 0 (parse-int seconds)) + (if-not fraction 0 (parse-int (zero-fill-right fraction 9))) + (cond (= "-" offset-sign) -1 + (= "+" offset-sign) 1 + :else 0) + (if-not offset-hours 0 (parse-int offset-hours)) + (if-not offset-minutes 0 (parse-int offset-minutes))) + (fail (str "Unrecognized date/time syntax: " cs)))))) + + +;;; ------------------------------------------------------------------------ +;;; Verification of Extra-Grammatical Restrictions from RFC3339 + +(defn- leap-year? + [year] + (and (divisible? year 4) + (or (indivisible? year 100) + (divisible? year 400)))) + +(def ^:private days-in-month + (let [dim-norm [nil 31 28 31 30 31 30 31 31 30 31 30 31] + dim-leap [nil 31 29 31 30 31 30 31 31 30 31 30 31]] + (fn [month leap-year?] + ((if leap-year? dim-leap dim-norm) month)))) + +(defn validated + "Return a function which constructs and instant by calling constructor +after first validating that those arguments are in range and otherwise +plausible. The resulting function will throw an exception if called +with invalid arguments." + [new-instance] + (fn [years months days hours minutes seconds nanoseconds + offset-sign offset-hours offset-minutes] + (verify (<= 1 months 12)) + (verify (<= 1 days (days-in-month months (leap-year? years)))) + (verify (<= 0 hours 23)) + (verify (<= 0 minutes 59)) + (verify (<= 0 seconds (if (= minutes 59) 60 59))) + (verify (<= 0 nanoseconds 999999999)) + (verify (<= -1 offset-sign 1)) + (verify (<= 0 offset-hours 23)) + (verify (<= 0 offset-minutes 59)) + (new-instance years months days hours minutes seconds nanoseconds + offset-sign offset-hours offset-minutes))) + + +;;; ------------------------------------------------------------------------ +;;; print integration + +(def ^:private ^ThreadLocal thread-local-utc-date-format + ;; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access. + ;; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335 + (proxy [ThreadLocal] [] + (initialValue [] + (doto (java.text.SimpleDateFormat. "yyyy-MM-dd'T'HH:mm:ss.SSS-00:00") + ;; RFC3339 says to use -00:00 when the timezone is unknown (+00:00 implies a known GMT) + (.setTimeZone (java.util.TimeZone/getTimeZone "GMT")))))) + +(defn- print-date + "Print a java.util.Date as RFC3339 timestamp, always in UTC." + [^java.util.Date d, ^java.io.Writer w] + (let [utc-format (.get thread-local-utc-date-format)] + (.write w "#inst \"") + (.write w ^String (.format ^java.text.SimpleDateFormat utc-format d)) + (.write w "\""))) + +(defmethod print-method java.util.Date + [^java.util.Date d, ^java.io.Writer w] + (print-date d w)) + +(defmethod print-dup java.util.Date + [^java.util.Date d, ^java.io.Writer w] + (print-date d w)) + +(defn- print-calendar + "Print a java.util.Calendar as RFC3339 timestamp, preserving timezone." + [^java.util.Calendar c, ^java.io.Writer w] + (let [calstr (format "%1$tFT%1$tT.%1$tL%1$tz" c) + offset-minutes (- (.length calstr) 2)] + ;; calstr is almost right, but is missing the colon in the offset + (.write w "#inst \"") + (.write w calstr 0 offset-minutes) + (.write w ":") + (.write w calstr offset-minutes 2) + (.write w "\""))) + +(defmethod print-method java.util.Calendar + [^java.util.Calendar c, ^java.io.Writer w] + (print-calendar c w)) + +(defmethod print-dup java.util.Calendar + [^java.util.Calendar c, ^java.io.Writer w] + (print-calendar c w)) + + +(def ^:private ^ThreadLocal thread-local-utc-timestamp-format + ;; SimpleDateFormat is not thread-safe, so we use a ThreadLocal proxy for access. + ;; http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335 + (proxy [ThreadLocal] [] + (initialValue [] + (doto (java.text.SimpleDateFormat. "yyyy-MM-dd'T'HH:mm:ss") + (.setTimeZone (java.util.TimeZone/getTimeZone "GMT")))))) + +(defn- print-timestamp + "Print a java.sql.Timestamp as RFC3339 timestamp, always in UTC." + [^java.sql.Timestamp ts, ^java.io.Writer w] + (let [utc-format (.get thread-local-utc-timestamp-format)] + (.write w "#inst \"") + (.write w ^String (.format ^java.text.SimpleDateFormat utc-format ts)) + ;; add on nanos and offset + ;; RFC3339 says to use -00:00 when the timezone is unknown (+00:00 implies a known GMT) + (.write w (format ".%09d-00:00" (.getNanos ts))) + (.write w "\""))) + +(defmethod print-method java.sql.Timestamp + [^java.sql.Timestamp ts, ^java.io.Writer w] + (print-timestamp ts w)) + +(defmethod print-dup java.sql.Timestamp + [^java.sql.Timestamp ts, ^java.io.Writer w] + (print-timestamp ts w)) + + +;;; ------------------------------------------------------------------------ +;;; reader integration + +(defn- construct-calendar + "Construct a java.util.Calendar, preserving the timezone +offset, but truncating the subsecond fraction to milliseconds." + ^GregorianCalendar + [years months days hours minutes seconds nanoseconds + offset-sign offset-hours offset-minutes] + (doto (GregorianCalendar. years (dec months) days hours minutes seconds) + (.set Calendar/MILLISECOND (quot nanoseconds 1000000)) + (.setTimeZone (TimeZone/getTimeZone + (format "GMT%s%02d:%02d" + (if (neg? offset-sign) "-" "+") + offset-hours offset-minutes))))) + +(defn- construct-date + "Construct a java.util.Date, which expresses the original instant as +milliseconds since the epoch, UTC." + [years months days hours minutes seconds nanoseconds + offset-sign offset-hours offset-minutes] + (.getTime (construct-calendar years months days + hours minutes seconds nanoseconds + offset-sign offset-hours offset-minutes))) + +(defn- construct-timestamp + "Construct a java.sql.Timestamp, which has nanosecond precision." + [years months days hours minutes seconds nanoseconds + offset-sign offset-hours offset-minutes] + (doto (Timestamp. + (.getTimeInMillis + (construct-calendar years months days + hours minutes seconds 0 + offset-sign offset-hours offset-minutes))) + ;; nanos must be set separately, pass 0 above for the base calendar + (.setNanos nanoseconds))) + +(def read-instant-date + "To read an instant as a java.util.Date, bind *data-readers* to a map with +this var as the value for the 'inst key. The timezone offset will be used +to convert into UTC." + (partial parse-timestamp (validated construct-date))) + +(def read-instant-calendar + "To read an instant as a java.util.Calendar, bind *data-readers* to a map with +this var as the value for the 'inst key. Calendar preserves the timezone +offset." + (partial parse-timestamp (validated construct-calendar))) + +(def read-instant-timestamp + "To read an instant as a java.sql.Timestamp, bind *data-readers* to a +map with this var as the value for the 'inst key. Timestamp preserves +fractional seconds with nanosecond precision. The timezone offset will +be used to convert into UTC." + (partial parse-timestamp (validated construct-timestamp))) + +;;; clojure.uuid ;;; + +(defn default-uuid-reader [form] + {:pre [(string? form)]} + (java.util.UUID/fromString form)) + +(defmethod print-method java.util.UUID [uuid ^java.io.Writer w] + (.write w (str "#uuid \"" (str uuid) "\""))) + +(defmethod print-dup java.util.UUID [o w] + (print-method o w)) diff --git a/src/main/clojure/cljs/vendor/clojure/tools/reader/edn.clj b/src/main/clojure/cljs/vendor/clojure/tools/reader/edn.clj new file mode 100644 index 0000000000..2de4ad94af --- /dev/null +++ b/src/main/clojure/cljs/vendor/clojure/tools/reader/edn.clj @@ -0,0 +1,440 @@ +;; Copyright (c) Nicola Mometto, Rich Hickey & contributors. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns ^{:doc "An EDN reader in clojure" + :author "Bronsa"} + cljs.vendor.clojure.tools.reader.edn + (:refer-clojure :exclude [read read-string char default-data-readers]) + (:require [cljs.vendor.clojure.tools.reader.reader-types :refer + [read-char unread peek-char indexing-reader? + get-line-number get-column-number get-file-name string-push-back-reader]] + [cljs.vendor.clojure.tools.reader.impl.utils :refer + [char ex-info? whitespace? numeric? desugar-meta namespace-keys second']] + [cljs.vendor.clojure.tools.reader.impl.commons :refer :all] + [cljs.vendor.clojure.tools.reader.impl.errors :as err] + [cljs.vendor.clojure.tools.reader :refer [default-data-readers]]) + (:import (clojure.lang PersistentHashSet IMeta RT PersistentVector))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; helpers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(declare read macros dispatch-macros) + +(defn- macro-terminating? [ch] + (and (not (identical? \# ch)) + (not (identical? \' ch)) + (not (identical? \: ch)) + (macros ch))) + +(defn- not-constituent? [ch] + (or (identical? \@ ch) + (identical? \` ch) + (identical? \~ ch))) + +(defn- ^String read-token + ([rdr kind initch] + (read-token rdr kind initch true)) + + ([rdr kind initch validate-leading?] + (cond + (not initch) + (err/throw-eof-at-start rdr kind) + + (and validate-leading? + (not-constituent? initch)) + (err/throw-bad-char rdr kind initch) + + :else + (loop [sb (StringBuilder.) + ch initch] + (if (or (whitespace? ch) + (macro-terminating? ch) + (nil? ch)) + (do (unread rdr ch) + (str sb)) + (if (not-constituent? ch) + (err/throw-bad-char rdr kind ch) + (recur (doto sb (.append ch)) (read-char rdr)))))))) + + + +(declare read-tagged) + +(defn- read-dispatch + [rdr _ opts] + (if-let [ch (read-char rdr)] + (if-let [dm (dispatch-macros ch)] + (dm rdr ch opts) + (read-tagged (doto rdr (unread ch)) ch opts)) + (err/throw-eof-at-dispatch rdr))) + +(defn- read-unmatched-delimiter + [rdr ch opts] + (err/throw-unmatch-delimiter rdr ch)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; readers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +(defn- read-unicode-char + ([^String token ^long offset ^long length ^long base] + (let [l (+ offset length)] + (when-not (== (count token) l) + (err/throw-invalid-unicode-literal nil token)) + (loop [i offset uc 0] + (if (== i l) + (char uc) + (let [d (Character/digit (int (nth token i)) (int base))] + (if (== d -1) + (err/throw-invalid-unicode-digit-in-token nil (nth token i) token) + (recur (inc i) (long (+ d (* uc base)))))))))) + + ([rdr initch base length exact?] + (let [length (long length) + base (long base)] + (loop [i 1 uc (Character/digit (int initch) (int base))] + (if (== uc -1) + (err/throw-invalid-unicode-digit rdr initch) + (if-not (== i length) + (let [ch (peek-char rdr)] + (if (or (whitespace? ch) + (macros ch) + (nil? ch)) + (if exact? + (err/throw-invalid-unicode-len rdr i length) + (char uc)) + (let [d (Character/digit (int ch) (int base))] + (read-char rdr) + (if (== d -1) + (err/throw-invalid-unicode-digit rdr ch) + (recur (inc i) (long (+ d (* uc base)))))))) + (char uc))))))) + +(def ^:private ^:const upper-limit (int \uD7ff)) +(def ^:private ^:const lower-limit (int \uE000)) + +(defn- read-char* + [rdr backslash opts] + (let [ch (read-char rdr)] + (if-not (nil? ch) + (let [token (if (or (macro-terminating? ch) + (not-constituent? ch) + (whitespace? ch)) + (str ch) + (read-token rdr :character ch false)) + token-len (count token)] + (cond + + (== 1 token-len) (Character/valueOf (nth token 0)) + + (= token "newline") \newline + (= token "space") \space + (= token "tab") \tab + (= token "backspace") \backspace + (= token "formfeed") \formfeed + (= token "return") \return + + (.startsWith token "u") + (let [c (read-unicode-char token 1 4 16) + ic (int c)] + (if (and (> ic upper-limit) + (< ic lower-limit)) + (err/throw-invalid-character-literal rdr (Integer/toString ic 16)) + c)) + + (.startsWith token "o") + (let [len (dec token-len)] + (if (> len 3) + (err/throw-invalid-octal-len rdr token) + (let [uc (read-unicode-char token 1 len 8)] + (if (> (int uc) 0377) + (err/throw-bad-octal-number rdr) + uc)))) + + :else (err/throw-unsupported-character rdr token))) + (err/throw-eof-in-character rdr)))) + +(defn ^:private starting-line-col-info [rdr] + (when (indexing-reader? rdr) + [(get-line-number rdr) (int (dec (int (get-column-number rdr))))])) + +(defn- ^PersistentVector read-delimited + [kind delim rdr opts] + (let [[start-line start-column] (starting-line-col-info rdr) + delim (char delim)] + (loop [a (transient [])] + (let [ch (read-past whitespace? rdr)] + (when-not ch + (err/throw-eof-delimited rdr kind start-line start-column (count a))) + + (if (identical? delim (char ch)) + (persistent! a) + (if-let [macrofn (macros ch)] + (let [mret (macrofn rdr ch opts)] + (recur (if-not (identical? mret rdr) (conj! a mret) a))) + (let [o (read (doto rdr (unread ch)) true nil opts)] + (recur (if-not (identical? o rdr) (conj! a o) a))))))))) + +(defn- read-list + [rdr _ opts] + (let [the-list (read-delimited :list \) rdr opts)] + (if (empty? the-list) + '() + (clojure.lang.PersistentList/create the-list)))) + +(defn- read-vector + [rdr _ opts] + (read-delimited :vector \] rdr opts)) + +(defn- read-map + [rdr _ opts] + (let [[start-line start-column] (starting-line-col-info rdr) + coll (read-delimited :map \} rdr opts) + l (to-array coll)] + (when (== 1 (bit-and (alength l) 1)) + (err/throw-odd-map rdr start-line start-column coll)) + (RT/map l))) + +(defn- read-number + [rdr initch opts] + (loop [sb (doto (StringBuilder.) (.append initch)) + ch (read-char rdr)] + (if (or (whitespace? ch) (macros ch) (nil? ch)) + (let [s (str sb)] + (unread rdr ch) + (or (match-number s) + (err/throw-invalid-number rdr s))) + (recur (doto sb (.append ch)) (read-char rdr))))) + + +(defn- escape-char [sb rdr] + (let [ch (read-char rdr)] + (case ch + \t "\t" + \r "\r" + \n "\n" + \\ "\\" + \" "\"" + \b "\b" + \f "\f" + \u (let [ch (read-char rdr)] + (if (== -1 (Character/digit (int ch) 16)) + (err/throw-invalid-unicode-escape rdr ch) + (read-unicode-char rdr ch 16 4 true))) + (if (numeric? ch) + (let [ch (read-unicode-char rdr ch 8 3 false)] + (if (> (int ch) 0377) + (err/throw-bad-octal-number rdr) + ch)) + (err/throw-bad-escape-char rdr ch))))) + +(defn- read-string* + [rdr _ opts] + (loop [sb (StringBuilder.) + ch (read-char rdr)] + (case ch + nil (err/throw-eof-reading rdr :string \" sb) + \\ (recur (doto sb (.append (escape-char sb rdr))) + (read-char rdr)) + \" (str sb) + (recur (doto sb (.append ch)) (read-char rdr))))) + +(defn- read-symbol + [rdr initch] + (when-let [token (read-token rdr :symbol initch)] + (case token + + ;; special symbols + "nil" nil + "true" true + "false" false + "/" '/ + + (or (when-let [p (parse-symbol token)] + (symbol (p 0) (p 1))) + (err/throw-invalid rdr :symbol token))))) + +(defn- read-keyword + [reader initch opts] + (let [ch (read-char reader)] + (if-not (whitespace? ch) + (let [token (read-token reader :keyword ch) + s (parse-symbol token)] + (if (and s (== -1 (.indexOf token "::"))) + (let [^String ns (s 0) + ^String name (s 1)] + (if (identical? \: (nth token 0)) + (err/throw-invalid reader :keyword (str \: token)) ; No ::kw in edn. + (keyword ns name))) + (err/throw-invalid reader :keyword (str \: token)))) + (err/throw-single-colon reader)))) + +(defn- wrapping-reader + [sym] + (fn [rdr _ opts] + (list sym (read rdr true nil opts)))) + +(defn- read-meta + [rdr _ opts] + (let [m (desugar-meta (read rdr true nil opts))] + (when-not (map? m) + (err/throw-bad-metadata rdr m)) + + (let [o (read rdr true nil opts)] + (if (instance? IMeta o) + (with-meta o (merge (meta o) m)) + (err/throw-bad-metadata-target rdr o))))) + +(defn- read-set + [rdr _ opts] + (PersistentHashSet/createWithCheck (read-delimited :set \} rdr opts))) + +(defn- read-discard + [rdr _ opts] + (doto rdr + (read true nil true))) + +(defn- read-namespaced-map + [rdr _ opts] + (let [token (read-token rdr :namespaced-map (read-char rdr))] + (if-let [ns (some-> token parse-symbol second)] + (let [ch (read-past whitespace? rdr)] + (if (identical? ch \{) + (let [items (read-delimited :namespaced-map \} rdr opts)] + (when (odd? (count items)) + (err/throw-odd-map rdr nil nil items)) + (let [keys (take-nth 2 items) + vals (take-nth 2 (rest items))] + (RT/map (to-array (mapcat list (namespace-keys (str ns) keys) vals))))) + (err/throw-ns-map-no-map rdr token))) + (err/throw-bad-ns rdr token)))) + +(defn- read-symbolic-value + [rdr _ opts] + (let [sym (read rdr true nil opts)] + (case sym + Inf Double/POSITIVE_INFINITY + -Inf Double/NEGATIVE_INFINITY + NaN Double/NaN + (err/reader-error rdr (str "Invalid token: ##" sym))))) + +(defn- macros [ch] + (case ch + \" read-string* + \: read-keyword + \; read-comment + \^ read-meta + \( read-list + \) read-unmatched-delimiter + \[ read-vector + \] read-unmatched-delimiter + \{ read-map + \} read-unmatched-delimiter + \\ read-char* + \# read-dispatch + nil)) + +(defn- dispatch-macros [ch] + (case ch + \^ read-meta ;deprecated + \{ read-set + \< (throwing-reader "Unreadable form") + \! read-comment + \_ read-discard + \: read-namespaced-map + \# read-symbolic-value + nil)) + +(defn- read-tagged [rdr initch opts] + (let [tag (read rdr true nil opts) + object (read rdr true nil opts)] + (if-not (symbol? tag) + (err/throw-bad-reader-tag rdr "Reader tag must be a symbol")) + (if-let [f (or (get (:readers opts) tag) + (default-data-readers tag))] + (f object) + (if-let [d (:default opts)] + (d tag object) + (err/throw-unknown-reader-tag rdr tag))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Public API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn read + "Reads the first object from an IPushbackReader or a java.io.PushbackReader. + Returns the object read. If EOF, throws if eof-error? is true otherwise returns eof. + If no reader is provided, *in* will be used. + + Reads data in the edn format (subset of Clojure data): + http://edn-format.org + + cljs.vendor.clojure.tools.reader.edn/read doesn't depend on dynamic Vars, all configuration + is done by passing an opt map. + + opts is a map that can include the following keys: + :eof - value to return on end-of-file. When not supplied, eof throws an exception. + :readers - a map of tag symbols to data-reader functions to be considered before default-data-readers. + When not supplied, only the default-data-readers will be used. + :default - A function of two args, that will, if present and no reader is found for a tag, + be called with the tag and the value." + ([] (read *in*)) + ([reader] (read {} reader)) + ([{:keys [eof] :as opts} reader] + (let [eof-error? (not (contains? opts :eof))] + (read reader eof-error? eof opts))) + ([reader eof-error? eof opts] + (try + (loop [] + (let [ch (read-char reader)] + (cond + (whitespace? ch) (recur) + (nil? ch) (if eof-error? (err/throw-eof-error reader nil) eof) + (number-literal? reader ch) (read-number reader ch opts) + :else (let [f (macros ch)] + (if f + (let [res (f reader ch opts)] + (if (identical? res reader) + (recur) + res)) + (read-symbol reader ch)))))) + (catch Exception e + (if (ex-info? e) + (let [d (ex-data e)] + (if (= :reader-exception (:type d)) + (throw e) + (throw (ex-info (.getMessage e) + (merge {:type :reader-exception} + d + (if (indexing-reader? reader) + {:line (get-line-number reader) + :column (get-column-number reader) + :file (get-file-name reader)})) + e)))) + (throw (ex-info (.getMessage e) + (merge {:type :reader-exception} + (if (indexing-reader? reader) + {:line (get-line-number reader) + :column (get-column-number reader) + :file (get-file-name reader)})) + e))))))) + +(defn read-string + "Reads one object from the string s. + Returns nil when s is nil or empty. + + Reads data in the edn format (subset of Clojure data): + http://edn-format.org + + opts is a map as per cljs.vendor.clojure.tools.reader.edn/read" + ([s] (read-string {:eof nil} s)) + ([opts s] + (when (and s (not (identical? s ""))) + (read opts (string-push-back-reader s))))) diff --git a/src/main/clojure/cljs/vendor/clojure/tools/reader/impl/commons.clj b/src/main/clojure/cljs/vendor/clojure/tools/reader/impl/commons.clj new file mode 100644 index 0000000000..8162909c2b --- /dev/null +++ b/src/main/clojure/cljs/vendor/clojure/tools/reader/impl/commons.clj @@ -0,0 +1,131 @@ +;; Copyright (c) Nicola Mometto, Rich Hickey & contributors. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.vendor.clojure.tools.reader.impl.commons + (:refer-clojure :exclude [char]) + (:require [cljs.vendor.clojure.tools.reader.reader-types :refer [peek-char read-char]] + [cljs.vendor.clojure.tools.reader.impl.errors :refer [reader-error]] + [cljs.vendor.clojure.tools.reader.impl.utils :refer [numeric? newline? char]]) + (:import (clojure.lang BigInt Numbers) + (java.util.regex Pattern Matcher) + java.lang.reflect.Constructor)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; helpers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn number-literal? + "Checks whether the reader is at the start of a number literal" + [reader initch] + (or (numeric? initch) + (and (or (identical? \+ initch) (identical? \- initch)) + (numeric? (peek-char reader))))) + +(defn read-past + "Read until first character that doesn't match pred, returning + char." + [pred rdr] + (loop [ch (read-char rdr)] + (if (pred ch) + (recur (read-char rdr)) + ch))) + +(defn skip-line + "Advances the reader to the end of a line. Returns the reader" + [reader] + (loop [] + (when-not (newline? (read-char reader)) + (recur))) + reader) + +(def ^Pattern int-pattern #"([-+]?)(?:(0)|([1-9][0-9]*)|0[xX]([0-9A-Fa-f]+)|0([0-7]+)|([1-9][0-9]?)[rR]([0-9A-Za-z]+)|0[0-9]+)(N)?") +(def ^Pattern ratio-pattern #"([-+]?[0-9]+)/([0-9]+)") +(def ^Pattern float-pattern #"([-+]?[0-9]+(\.[0-9]*)?([eE][-+]?[0-9]+)?)(M)?") + +(defn- match-int + [^Matcher m] + (if (.group m 2) + (if (.group m 8) 0N 0) + (let [negate? (= "-" (.group m 1)) + a (cond + (.group m 3) [(.group m 3) 10] + (.group m 4) [(.group m 4) 16] + (.group m 5) [(.group m 5) 8] + (.group m 7) [(.group m 7) (Integer/parseInt (.group m 6))] + :else [nil nil]) + ^String n (a 0)] + (when n + (let [bn (BigInteger. n (int (a 1))) + bn (if negate? (.negate bn) bn)] + (if (.group m 8) + (BigInt/fromBigInteger bn) + (if (< (.bitLength bn) 64) + (.longValue bn) + (BigInt/fromBigInteger bn)))))))) + +(defn- match-ratio + [^Matcher m] + (let [^String numerator (.group m 1) + ^String denominator (.group m 2) + numerator (if (.startsWith numerator "+") + (subs numerator 1) + numerator)] + (/ (-> numerator BigInteger. BigInt/fromBigInteger Numbers/reduceBigInt) + (-> denominator BigInteger. BigInt/fromBigInteger Numbers/reduceBigInt)))) + +(defn- match-float + [^String s ^Matcher m] + (if (.group m 4) + (BigDecimal. ^String (.group m 1)) + (Double/parseDouble s))) + +(defn match-number [^String s] + (let [int-matcher (.matcher int-pattern s)] + (if (.matches int-matcher) + (match-int int-matcher) + (let [float-matcher (.matcher float-pattern s)] + (if (.matches float-matcher) + (match-float s float-matcher) + (let [ratio-matcher (.matcher ratio-pattern s)] + (when (.matches ratio-matcher) + (match-ratio ratio-matcher)))))))) + +(defn parse-symbol + "Parses a string into a vector of the namespace and symbol" + [^String token] + (when-not (or (= "" token) + (.endsWith token ":") + (.startsWith token "::")) + (let [ns-idx (.indexOf token "/")] + (if-let [^String ns (and (pos? ns-idx) + (subs token 0 ns-idx))] + (let [ns-idx (inc ns-idx)] + (when-not (== ns-idx (count token)) + (let [sym (subs token ns-idx)] + (when (and (not (numeric? (nth sym 0))) + (not (= "" sym)) + (not (.endsWith ns ":")) + (or (= sym "/") + (== -1 (.indexOf sym "/")))) + [ns sym])))) + (when (or (= token "/") + (== -1 (.indexOf token "/"))) + [nil token]))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; readers +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn read-comment + [rdr & _] + (skip-line rdr)) + +(defn throwing-reader + [msg] + (fn [rdr & _] + (reader-error rdr msg))) diff --git a/src/main/clojure/cljs/vendor/clojure/tools/reader/impl/errors.clj b/src/main/clojure/cljs/vendor/clojure/tools/reader/impl/errors.clj new file mode 100644 index 0000000000..862982882a --- /dev/null +++ b/src/main/clojure/cljs/vendor/clojure/tools/reader/impl/errors.clj @@ -0,0 +1,214 @@ +;; Copyright (c) Russ Olsen, Nicola Mometto, Rich Hickey & contributors. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.vendor.clojure.tools.reader.impl.errors + (:require [cljs.vendor.clojure.tools.reader.reader-types :as types] + [cljs.vendor.clojure.tools.reader.impl.inspect :as i])) + +(defn- location-details [rdr ex-type] + (let [details {:type :reader-exception + :ex-kind ex-type}] + (if (types/indexing-reader? rdr) + (assoc + details + :file (types/get-file-name rdr) + :line (types/get-line-number rdr) + :col (types/get-column-number rdr)) + details))) + +(defn ^:private throw-ex + [rdr ex-type & msg] + (let [details (location-details rdr ex-type) + file (:file details) + line (:line details) + col (:col details) + msg1 (if file (str file " ")) + msg2 (if line (str "[line " line ", col " col "]")) + msg3 (if (or msg1 msg2) " ") + full-msg (apply str msg1 msg2 msg3 msg)] + (throw (ex-info full-msg details)))) + +(defn reader-error + "Throws an ExceptionInfo with the given message. + If rdr is an IndexingReader, additional information about column and line number is provided" + [rdr & msgs] + (throw-ex rdr :reader-error (apply str msgs))) + +(defn eof-error + "Throws an ExceptionInfo with the given message. + If rdr is an IndexingReader, additional information about column and line number is provided" + [rdr & msgs] + (throw-ex rdr :eof (apply str msgs))) + +(defn illegal-arg-error + "Throws an ExceptionInfo with the given message. + If rdr is an IndexingReader, additional information about column and line number is provided" + [rdr & msgs] + (throw-ex rdr :illegal-argument (apply str msgs))) + +(defn throw-eof-delimited + ([rdr kind line column] (throw-eof-delimited rdr kind line column nil)) + ([rdr kind line column n] + (eof-error + rdr + "Unexpected EOF while reading " + (if n + (str "item " n " of ")) + (name kind) + (if line + (str ", starting at line " line " and column " column)) + "."))) + +(defn throw-odd-map [rdr line col elements] + (reader-error + rdr + "The map literal starting with " + (i/inspect (first elements)) + (if line (str " on line " line " column " col)) + " contains " + (count elements) + " form(s). Map literals must contain an even number of forms.")) + +(defn throw-invalid-number [rdr token] + (reader-error + rdr + "Invalid number: " + token + ".")) + +(defn throw-invalid-unicode-literal [rdr token] + (throw + (illegal-arg-error rdr + "Invalid unicode literal: \\" token "."))) + +(defn throw-invalid-unicode-escape [rdr ch] + (reader-error + rdr + "Invalid unicode escape: \\u" + ch + ".")) + +(defn throw-invalid [rdr kind token] + (reader-error rdr "Invalid " (name kind) ": " token ".")) + +(defn throw-eof-at-start [rdr kind] + (eof-error rdr "Unexpected EOF while reading start of " (name kind) ".")) + +(defn throw-bad-char [rdr kind ch] + (reader-error rdr "Invalid character: " ch " found while reading " (name kind) ".")) + +(defn throw-eof-at-dispatch [rdr] + (eof-error rdr "Unexpected EOF while reading dispatch character.")) + +(defn throw-unmatch-delimiter [rdr ch] + (reader-error rdr "Unmatched delimiter " ch ".")) + +(defn throw-eof-reading [rdr kind & start] + (let [init (case kind :regex "#\"" :string \")] + (eof-error rdr "Unexpected EOF reading " (name kind) " starting " (apply str init start) "."))) + +(defn throw-invalid-unicode-char[rdr token] + (throw + (illegal-arg-error rdr + "Invalid unicode character \\" token "."))) + +(defn throw-invalid-unicode-digit-in-token [rdr ch token] + (throw + (illegal-arg-error rdr + "Invalid digit " ch " in unicode character \\" token "."))) + +(defn throw-invalid-unicode-digit[rdr ch] + (throw + (illegal-arg-error rdr + "Invalid digit " ch " in unicode character."))) + +(defn throw-invalid-unicode-len[rdr actual expected] + (throw + (illegal-arg-error rdr + "Invalid unicode literal. Unicode literals should be " + expected + " characters long. " + "Value supplied is " + actual + " characters long."))) + +(defn throw-invalid-character-literal[rdr token] + (reader-error rdr "Invalid character literal \\u" token ".")) + +(defn throw-invalid-octal-len[rdr token] + (reader-error + rdr + "Invalid octal escape sequence in a character literal: " + token + ". Octal escape sequences must be 3 or fewer digits.")) + +(defn throw-bad-octal-number [rdr] + (reader-error rdr "Octal escape sequence must be in range [0, 377].")) + +(defn throw-unsupported-character[rdr token] + (reader-error + rdr + "Unsupported character: " + token + ".")) + +(defn throw-eof-in-character[rdr] + (eof-error rdr "Unexpected EOF while reading character.")) + +(defn throw-bad-escape-char [rdr ch] + (reader-error rdr "Unsupported escape character: \\" ch ".")) + +(defn throw-single-colon [rdr] + (reader-error rdr "A single colon is not a valid keyword.")) + +(defn throw-bad-metadata [rdr x] + (reader-error + rdr + "Metadata cannot be " + (i/inspect x) + ". Metadata must be a Symbol, Keyword, String or Map.")) + +(defn throw-bad-metadata-target [rdr target] + (reader-error + rdr + "Metadata can not be applied to " + (i/inspect target) + ". " + "Metadata can only be applied to IMetas.")) + +(defn throw-feature-not-keyword [rdr feature] + (reader-error + rdr + "Feature cannot be " + (i/inspect feature) + ". Features must be keywords.")) + +(defn throw-ns-map-no-map [rdr ns-name] + (reader-error rdr "Namespaced map with namespace " ns-name " does not specify a map.")) + +(defn throw-bad-ns [rdr ns-name] + (reader-error rdr "Invalid value used as namespace in namespaced map: " ns-name ".")) + +(defn throw-bad-reader-tag [rdr tag] + (reader-error + rdr + "Invalid reader tag: " + (i/inspect tag) + ". Reader tags must be symbols.")) + +(defn throw-unknown-reader-tag [rdr tag] + (reader-error + rdr + "No reader function for tag " + (i/inspect tag) + ".")) + +(defn throw-eof-error [rdr line] + (if line + (eof-error rdr "EOF while reading, starting at line " line ".") + (eof-error rdr "EOF while reading."))) diff --git a/src/main/clojure/cljs/vendor/clojure/tools/reader/impl/inspect.clj b/src/main/clojure/cljs/vendor/clojure/tools/reader/impl/inspect.clj new file mode 100644 index 0000000000..cd7be56418 --- /dev/null +++ b/src/main/clojure/cljs/vendor/clojure/tools/reader/impl/inspect.clj @@ -0,0 +1,91 @@ +;; Copyright (c) Russ Olsen, Nicola Mometto, Rich Hickey & contributors. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.vendor.clojure.tools.reader.impl.inspect) + +(declare inspect*) + +(defn- inspect*-col [truncate col start end] + (let [n (count col) + l (if truncate 0 (min 10 n)) + elements (map (partial inspect* true) (take l col)) + content (apply str (interpose " " elements)) + suffix (if (< l n) "...")] + (str start content suffix end))) + +(defn- dispatch-inspect + [_ x] + (cond + (nil? x) :nil + (string? x) :string + (keyword? x) :strable + (number? x) :strable + (symbol? x) :strable + (vector? x) :vector + (list? x) :list + (map? x) :map + (set? x) :set + (= x true) :strable + (= x false) :strable + :default (class x))) + +(defmulti inspect* dispatch-inspect) + +(defmethod inspect* :string [truncate ^String x] + (let [n (if truncate 5 20) + suffix (if (> (.length x) n) "...\"" "\"")] + (str + \" + (.substring ^String x 0 (min n (.length x))) + suffix))) + +(defmethod inspect* :strable [truncate x] (str x)) + +(defmethod inspect* clojure.lang.PersistentVector$ChunkedSeq [truncate x] + "") + +(defmethod inspect* clojure.lang.PersistentArrayMap$Seq [truncate x] + "") + +(defmethod inspect* clojure.lang.PersistentHashMap$NodeSeq [truncate x] + "") + +(defmethod inspect* clojure.lang.Cons [truncate x] "") + +(defmethod inspect* clojure.lang.LazySeq [truncate x] "") + +(defmethod inspect* :nil [_ _] "nil") + +(defmethod inspect* :list [truncate col] + (inspect*-col truncate col \( \))) + +(defmethod inspect* :map [truncate m] + (let [len (count m) + n-shown (if truncate 0 len) + contents (apply concat (take n-shown m)) + suffix (if (> len n-shown) "...}" \})] + (inspect*-col truncate contents \{ suffix))) + +(defmethod inspect* :set [truncate col] + (inspect*-col truncate col "#{" \})) + +(defmethod inspect* :vector [truncate col] + (inspect*-col truncate col \[ \])) + +(defmethod inspect* :default [truncate x] + (let [classname (if (nil? x) "nil" (.getName (class x)))] + (str "<" classname ">"))) + +(defn inspect + "Return a string description of the value supplied. + May be the a string version of the value itself (e.g. \"true\") + or it may be a description (e.g. \"an instance of Foo\"). + If truncate is true then return a very terse version of + the inspection." + ([x] (inspect* false x)) + ([truncate x] (inspect* truncate x))) diff --git a/src/main/clojure/cljs/vendor/clojure/tools/reader/impl/utils.clj b/src/main/clojure/cljs/vendor/clojure/tools/reader/impl/utils.clj new file mode 100644 index 0000000000..0b814e8e7e --- /dev/null +++ b/src/main/clojure/cljs/vendor/clojure/tools/reader/impl/utils.clj @@ -0,0 +1,127 @@ +;; Copyright (c) Nicola Mometto, Rich Hickey & contributors. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns ^:skip-wiki cljs.vendor.clojure.tools.reader.impl.utils + (:refer-clojure :exclude [char reader-conditional tagged-literal])) + +(defn char [x] + (when x + (clojure.core/char x))) + +(def <=clojure-1-7-alpha5 + (let [{:keys [minor qualifier]} *clojure-version*] + (or (< minor 7) + (and (= minor 7) + (= "alpha" + (when qualifier + (subs qualifier 0 (dec (count qualifier))))) + (<= (read-string (subs qualifier (dec (count qualifier)))) + 5))))) + +(defmacro compile-when [cond & then] + (when (eval cond) + `(do ~@then))) + +(defn ex-info? [ex] + (instance? clojure.lang.ExceptionInfo ex)) + +(compile-when <=clojure-1-7-alpha5 + (defrecord TaggedLiteral [tag form]) + + (defn tagged-literal? + "Return true if the value is the data representation of a tagged literal" + [value] + (instance? cljs.vendor.clojure.tools.reader.impl.utils.TaggedLiteral value)) + + (defn tagged-literal + "Construct a data representation of a tagged literal from a + tag symbol and a form." + [tag form] + (cljs.vendor.clojure.tools.reader.impl.utils.TaggedLiteral. tag form)) + + (ns-unmap *ns* '->TaggedLiteral) + (ns-unmap *ns* 'map->TaggedLiteral) + + (defmethod print-method cljs.vendor.clojure.tools.reader.impl.utils.TaggedLiteral [o ^java.io.Writer w] + (.write w "#") + (print-method (:tag o) w) + (.write w " ") + (print-method (:form o) w)) + + (defrecord ReaderConditional [splicing? form]) + (ns-unmap *ns* '->ReaderConditional) + (ns-unmap *ns* 'map->ReaderConditional) + + (defn reader-conditional? + "Return true if the value is the data representation of a reader conditional" + [value] + (instance? cljs.vendor.clojure.tools.reader.impl.utils.ReaderConditional value)) + + (defn reader-conditional + "Construct a data representation of a reader conditional. + If true, splicing? indicates read-cond-splicing." + [form splicing?] + (cljs.vendor.clojure.tools.reader.impl.utils.ReaderConditional. splicing? form)) + + (defmethod print-method cljs.vendor.clojure.tools.reader.impl.utils.ReaderConditional [o ^java.io.Writer w] + (.write w "#?") + (when (:splicing? o) (.write w "@")) + (print-method (:form o) w))) + +(defn whitespace? + "Checks whether a given character is whitespace" + [ch] + (when ch + (or (Character/isWhitespace ^Character ch) + (identical? \, ch)))) + +(defn numeric? + "Checks whether a given character is numeric" + [^Character ch] + (when ch + (Character/isDigit ch))) + +(defn newline? + "Checks whether the character is a newline" + [c] + (or (identical? \newline c) + (nil? c))) + +(defn desugar-meta + "Resolves syntactical sugar in metadata" ;; could be combined with some other desugar? + [f] + (cond + (keyword? f) {f true} + (symbol? f) {:tag f} + (string? f) {:tag f} + :else f)) + +(defn make-var + "Returns an anonymous unbound Var" + [] + (with-local-vars [x nil] x)) + +(defn namespace-keys [ns keys] + (for [key keys] + (if (or (symbol? key) + (keyword? key)) + (let [[key-ns key-name] ((juxt namespace name) key) + ->key (if (symbol? key) symbol keyword)] + (cond + (nil? key-ns) + (->key ns key-name) + + (= "_" key-ns) + (->key key-name) + + :else + key)) + key))) + +(defn second' [[a b]] + (when-not a b)) diff --git a/src/main/clojure/cljs/vendor/clojure/tools/reader/reader_types.clj b/src/main/clojure/cljs/vendor/clojure/tools/reader/reader_types.clj new file mode 100644 index 0000000000..dab408fd4f --- /dev/null +++ b/src/main/clojure/cljs/vendor/clojure/tools/reader/reader_types.clj @@ -0,0 +1,431 @@ +;; Copyright (c) Nicola Mometto, Rich Hickey & contributors. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns ^{:doc "Protocols and default Reader types implementation" + :author "Bronsa"} + cljs.vendor.clojure.tools.reader.reader-types + (:refer-clojure :exclude [char read-line]) + (:require [cljs.vendor.clojure.tools.reader.impl.utils :refer [char whitespace? newline? make-var]]) + (:import clojure.lang.LineNumberingPushbackReader + (java.io InputStream BufferedReader Closeable))) + +(defmacro ^:private update! [what f] + (list 'set! what (list f what))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; reader protocols +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defprotocol Reader + (read-char [reader] + "Returns the next char from the Reader, nil if the end of stream has been reached") + (peek-char [reader] + "Returns the next char from the Reader without removing it from the reader stream")) + +(defprotocol IPushbackReader + (unread [reader ch] + "Pushes back a single character on to the stream")) + +(defprotocol IndexingReader + (get-line-number [reader] + "Returns the line number of the next character to be read from the stream") + (get-column-number [reader] + "Returns the column number of the next character to be read from the stream") + (get-file-name [reader] + "Returns the file name the reader is reading from, or nil")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; reader deftypes +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(deftype StringReader + [^String s ^long s-len ^:unsynchronized-mutable ^long s-pos] + Reader + (read-char [reader] + (when (> s-len s-pos) + (let [r (nth s s-pos)] + (update! s-pos inc) + r))) + (peek-char [reader] + (when (> s-len s-pos) + (nth s s-pos)))) + +(deftype InputStreamReader [^InputStream is ^:unsynchronized-mutable ^"[B" buf] + Reader + (read-char [reader] + (if buf + (let [c (aget buf 0)] + (set! buf nil) + (char c)) + (let [c (.read is)] + (when (>= c 0) + (char c))))) + (peek-char [reader] + (when-not buf + (set! buf (byte-array 1)) + (when (== -1 (.read is buf)) + (set! buf nil))) + (when buf + (char (aget buf 0)))) + Closeable + (close [this] + (.close is))) + +(deftype PushbackReader + [rdr ^"[Ljava.lang.Object;" buf ^long buf-len ^:unsynchronized-mutable ^long buf-pos] + Reader + (read-char [reader] + (char + (if (< buf-pos buf-len) + (let [r (aget buf buf-pos)] + (update! buf-pos inc) + r) + (read-char rdr)))) + (peek-char [reader] + (char + (if (< buf-pos buf-len) + (aget buf buf-pos) + (peek-char rdr)))) + IPushbackReader + (unread [reader ch] + (when ch + (if (zero? buf-pos) (throw (RuntimeException. "Pushback buffer is full"))) + (update! buf-pos dec) + (aset buf buf-pos ch))) + Closeable + (close [this] + (when (instance? Closeable rdr) + (.close ^Closeable rdr)))) + +(deftype IndexingPushbackReader + [rdr ^:unsynchronized-mutable ^long line ^:unsynchronized-mutable ^long column + ^:unsynchronized-mutable line-start? ^:unsynchronized-mutable prev + ^:unsynchronized-mutable ^long prev-column file-name + ^:unsynchronized-mutable normalize?] + Reader + (read-char [reader] + (when-let [ch (read-char rdr)] + (let [ch (if normalize? + (do (set! normalize? false) + (if (or (identical? \newline ch) + (identical? \formfeed ch)) + (read-char rdr) + ch)) + ch) + ch (if (identical? \return ch) + (do (set! normalize? true) + \newline) + ch)] + (set! prev line-start?) + (set! line-start? (newline? ch)) + (when line-start? + (set! prev-column column) + (set! column 0) + (update! line inc)) + (update! column inc) + ch))) + + (peek-char [reader] + (peek-char rdr)) + + IPushbackReader + (unread [reader ch] + (if line-start? + (do (update! line dec) + (set! column prev-column)) + (update! column dec)) + (set! line-start? prev) + ;; This may look a bit convoluted, but it helps in the following + ;; scenario: + ;; + The underlying reader is about to return \return from the + ;; next read-char, and then \newline after that. + ;; + read-char gets \return, sets normalize? to true, returns + ;; \newline instead. + ;; + Caller calls unread on the \newline it just got. If we + ;; unread the \newline to the underlying reader, now it is ready + ;; to return two \newline chars in a row, which will throw off + ;; the tracked line numbers. + (let [ch (if normalize? + (do (set! normalize? false) + (if (identical? \newline ch) + \return + ch)) + ch)] + (unread rdr ch))) + + IndexingReader + (get-line-number [reader] (int line)) + (get-column-number [reader] (int column)) + (get-file-name [reader] file-name) + + Closeable + (close [this] + (when (instance? Closeable rdr) + (.close ^Closeable rdr)))) + +;; Java interop + +(extend-type java.io.PushbackReader + Reader + (read-char [rdr] + (let [c (.read ^java.io.PushbackReader rdr)] + (when (>= c 0) + (char c)))) + + (peek-char [rdr] + (when-let [c (read-char rdr)] + (unread rdr c) + c)) + + IPushbackReader + (unread [rdr c] + (when c + (.unread ^java.io.PushbackReader rdr (int c))))) + +(extend LineNumberingPushbackReader + IndexingReader + {:get-line-number (fn [rdr] (.getLineNumber ^LineNumberingPushbackReader rdr)) + :get-column-number (fn [rdr] + (.getColumnNumber ^LineNumberingPushbackReader rdr)) + :get-file-name (constantly nil)}) + +(defprotocol ReaderCoercer + (to-rdr [rdr])) + +(declare string-reader push-back-reader) + +(extend-protocol ReaderCoercer + Object + (to-rdr [rdr] + (if (satisfies? Reader rdr) + rdr + (throw (IllegalArgumentException. (str "Argument of type: " (class rdr) " cannot be converted to Reader"))))) + cljs.vendor.clojure.tools.reader.reader_types.Reader + (to-rdr [rdr] rdr) + String + (to-rdr [str] (string-reader str)) + java.io.Reader + (to-rdr [rdr] (java.io.PushbackReader. rdr))) + +(defprotocol PushbackReaderCoercer + (to-pbr [rdr buf-len])) + +(extend-protocol PushbackReaderCoercer + Object + (to-pbr [rdr buf-len] + (if (satisfies? Reader rdr) + (push-back-reader rdr buf-len) + (throw (IllegalArgumentException. (str "Argument of type: " (class rdr) " cannot be converted to IPushbackReader"))))) + cljs.vendor.clojure.tools.reader.reader_types.Reader + (to-pbr [rdr buf-len] (push-back-reader rdr buf-len)) + cljs.vendor.clojure.tools.reader.reader_types.PushbackReader + (to-pbr [rdr buf-len] (push-back-reader rdr buf-len)) + String + (to-pbr [str buf-len] (push-back-reader str buf-len)) + java.io.Reader + (to-pbr [rdr buf-len] (java.io.PushbackReader. rdr buf-len))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Source Logging support +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn merge-meta + "Returns an object of the same type and value as `obj`, with its + metadata merged over `m`." + [obj m] + (let [orig-meta (meta obj)] + (with-meta obj (merge m (dissoc orig-meta :source))))) + +(defn- peek-source-log + "Returns a string containing the contents of the top most source + logging frame." + [source-log-frames] + (let [current-frame @source-log-frames] + (.substring ^StringBuilder (:buffer current-frame) (:offset current-frame)))) + +(defn- log-source-char + "Logs `char` to all currently active source logging frames." + [source-log-frames char] + (when-let [^StringBuilder buffer (:buffer @source-log-frames)] + (.append buffer char))) + +(defn- drop-last-logged-char + "Removes the last logged character from all currently active source + logging frames. Called when pushing a character back." + [source-log-frames] + (when-let [^StringBuilder buffer (:buffer @source-log-frames)] + (.deleteCharAt buffer (dec (.length buffer))))) + +(deftype SourceLoggingPushbackReader + [rdr ^:unsynchronized-mutable ^long line ^:unsynchronized-mutable ^long column + ^:unsynchronized-mutable line-start? ^:unsynchronized-mutable prev + ^:unsynchronized-mutable ^long prev-column file-name source-log-frames + ^:unsynchronized-mutable normalize?] + Reader + (read-char [reader] + (when-let [ch (read-char rdr)] + (let [ch (if normalize? + (do (set! normalize? false) + (if (or (identical? \newline ch) + (identical? \formfeed ch)) + (read-char rdr) + ch)) + ch) + ch (if (identical? \return ch) + (do (set! normalize? true) + \newline) + ch)] + (set! prev line-start?) + (set! line-start? (newline? ch)) + (when line-start? + (set! prev-column column) + (set! column 0) + (update! line inc)) + (update! column inc) + (log-source-char source-log-frames ch) + ch))) + + (peek-char [reader] + (peek-char rdr)) + + IPushbackReader + (unread [reader ch] + (if line-start? + (do (update! line dec) + (set! column prev-column)) + (update! column dec)) + (set! line-start? prev) + (when ch + (drop-last-logged-char source-log-frames)) + (unread rdr ch)) + + IndexingReader + (get-line-number [reader] (int line)) + (get-column-number [reader] (int column)) + (get-file-name [reader] file-name) + + Closeable + (close [this] + (when (instance? Closeable rdr) + (.close ^Closeable rdr)))) + +(defn log-source* + [reader f] + (let [frame (.source-log-frames ^SourceLoggingPushbackReader reader) + ^StringBuilder buffer (:buffer @frame) + new-frame (assoc @frame :offset (.length buffer))] + (with-bindings {frame new-frame} + (let [ret (f)] + (if (instance? clojure.lang.IObj ret) + (merge-meta ret {:source (peek-source-log frame)}) + ret))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Public API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; fast check for provided implementations +(defn indexing-reader? + "Returns true if the reader satisfies IndexingReader" + [rdr] + (or (instance? cljs.vendor.clojure.tools.reader.reader_types.IndexingReader rdr) + (instance? LineNumberingPushbackReader rdr) + (and (not (instance? cljs.vendor.clojure.tools.reader.reader_types.PushbackReader rdr)) + (not (instance? cljs.vendor.clojure.tools.reader.reader_types.StringReader rdr)) + (not (instance? cljs.vendor.clojure.tools.reader.reader_types.InputStreamReader rdr)) + (get (:impls IndexingReader) (class rdr))))) + +(defn string-reader + "Creates a StringReader from a given string" + ([^String s] + (StringReader. s (count s) 0))) + +(defn ^Closeable push-back-reader + "Creates a PushbackReader from a given reader or string" + ([rdr] (push-back-reader rdr 1)) + ([rdr buf-len] (PushbackReader. (to-rdr rdr) (object-array buf-len) buf-len buf-len))) + +(defn ^Closeable string-push-back-reader + "Creates a PushbackReader from a given string" + ([s] + (string-push-back-reader s 1)) + ([^String s buf-len] + (push-back-reader (string-reader s) buf-len))) + +(defn ^Closeable input-stream-reader + "Creates an InputStreamReader from an InputStream" + [is] + (InputStreamReader. is nil)) + +(defn ^Closeable input-stream-push-back-reader + "Creates a PushbackReader from a given InputStream" + ([is] + (input-stream-push-back-reader is 1)) + ([^InputStream is buf-len] + (push-back-reader (input-stream-reader is) buf-len))) + +(defn ^Closeable indexing-push-back-reader + "Creates an IndexingPushbackReader from a given string or PushbackReader" + ([s-or-rdr] + (indexing-push-back-reader s-or-rdr 1)) + ([s-or-rdr buf-len] + (indexing-push-back-reader s-or-rdr buf-len nil)) + ([s-or-rdr buf-len file-name] + (IndexingPushbackReader. + (to-pbr s-or-rdr buf-len) 1 1 true nil 0 file-name false))) + +(defn ^Closeable source-logging-push-back-reader + "Creates a SourceLoggingPushbackReader from a given string or PushbackReader" + ([s-or-rdr] + (source-logging-push-back-reader s-or-rdr 1)) + ([s-or-rdr buf-len] + (source-logging-push-back-reader s-or-rdr buf-len nil)) + ([s-or-rdr buf-len file-name] + (SourceLoggingPushbackReader. + (to-pbr s-or-rdr buf-len) + 1 + 1 + true + nil + 0 + file-name + (doto (make-var) + (alter-var-root (constantly {:buffer (StringBuilder.) + :offset 0}))) + false))) + +(defn read-line + "Reads a line from the reader or from *in* if no reader is specified" + ([] (read-line *in*)) + ([rdr] + (if (or (instance? LineNumberingPushbackReader rdr) + (instance? BufferedReader rdr)) + (binding [*in* rdr] + (clojure.core/read-line)) + (loop [c (read-char rdr) s (StringBuilder.)] + (if (newline? c) + (str s) + (recur (read-char rdr) (.append s c))))))) + +(defn source-logging-reader? + [rdr] + (instance? SourceLoggingPushbackReader rdr)) + +(defmacro log-source + "If reader is a SourceLoggingPushbackReader, execute body in a source + logging context. Otherwise, execute body, returning the result." + [reader & body] + `(if (and (source-logging-reader? ~reader) + (not (whitespace? (peek-char ~reader)))) + (log-source* ~reader (^:once fn* [] ~@body)) + (do ~@body))) + +(defn line-start? + "Returns true if rdr is an IndexingReader and the current char starts a new line" + [rdr] + (when (indexing-reader? rdr) + (== 1 (int (get-column-number rdr))))) diff --git a/src/main/clojure/cljs/vendor/cognitect/transit.clj b/src/main/clojure/cljs/vendor/cognitect/transit.clj new file mode 100644 index 0000000000..43fa0da963 --- /dev/null +++ b/src/main/clojure/cljs/vendor/cognitect/transit.clj @@ -0,0 +1,479 @@ +;; Copyright 2014 Rich Hickey. All Rights Reserved. +;; +;; Licensed under the Apache License, Version 2.0 (the "License"); +;; you may not use this file except in compliance with the License. +;; You may obtain a copy of the License at +;; +;; http://www.apache.org/licenses/LICENSE-2.0 +;; +;; Unless required by applicable law or agreed to in writing, software +;; distributed under the License is distributed on an "AS-IS" BASIS, +;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +;; See the License for the specific language governing permissions and +;; limitations under the License. + +(ns cljs.vendor.cognitect.transit + "An implementation of the transit-format for Clojure built + on top of the transit-java library." + (:refer-clojure :exclude [read]) + (:require [clojure.string :as str]) + (:import [com.cognitect.transit WriteHandler ReadHandler ArrayReadHandler MapReadHandler + ArrayReader TransitFactory TransitFactory$Format MapReader] + [com.cognitect.transit.SPI ReaderSPI] + [java.io InputStream OutputStream] + [java.util.function Function])) + +(defprotocol HandlerMapProvider + (handler-map [this])) + +(deftype HandlerMapContainer [m] + HandlerMapProvider + (handler-map [this] m)) + +;; writing + +(set! *warn-on-reflection* true) + +(defn- transit-format + "Converts a keyword to a TransitFactory$Format value." + [kw] + (TransitFactory$Format/valueOf + (str/join "_" (-> kw + name + str/upper-case + (str/split #"-"))))) + +(defn tagged-value + "Creates a TaggedValue object." + [tag rep] (TransitFactory/taggedValue tag rep)) + +(defn nsed-name + "Convert a keyword or symbol to a string in + namespace/name format." + [^clojure.lang.Named kw-or-sym] + (if-let [ns (.getNamespace kw-or-sym)] + (str ns "/" (.getName kw-or-sym)) + (.getName kw-or-sym))) + +(defn- fn-or-val + [f] + (if (fn? f) f (constantly f))) + +(defn write-handler + "Creates a transit WriteHandler whose tag, rep, + stringRep, and verboseWriteHandler methods + invoke the provided fns. + + If a non-fn is passed as an argument, implemented + handler method returns the value unaltered." + ([tag-fn rep-fn] + (write-handler tag-fn rep-fn nil nil)) + ([tag-fn rep-fn str-rep-fn] + (write-handler tag-fn rep-fn str-rep-fn nil)) + ([tag-fn rep-fn str-rep-fn verbose-handler-fn] + (let [tag-fn (fn-or-val tag-fn) + rep-fn (fn-or-val rep-fn) + str-rep-fn (fn-or-val str-rep-fn) + verbose-handler-fn (fn-or-val verbose-handler-fn)] + (reify WriteHandler + (tag [_ o] (tag-fn o)) + (rep [_ o] (rep-fn o)) + (stringRep [_ o] (when str-rep-fn (str-rep-fn o))) + (getVerboseHandler [_] (when verbose-handler-fn (verbose-handler-fn))))))) + +(deftype WithMeta [value meta]) + +(def default-write-handlers + "Returns a map of default WriteHandlers for + Clojure types. Java types are handled + by the default WriteHandlers provided by the + transit-java library." + { + java.util.List + (reify WriteHandler + (tag [_ l] (if (seq? l) "list" "array")) + (rep [_ l] (if (seq? l) (TransitFactory/taggedValue "array" l) l)) + (stringRep [_ _] nil) + (getVerboseHandler [_] nil)) + + clojure.lang.BigInt + (reify WriteHandler + (tag [_ _] "n") + (rep [_ bi] (str (biginteger bi))) + (stringRep [this bi] (.rep this bi)) + (getVerboseHandler [_] nil)) + + clojure.lang.Keyword + (reify WriteHandler + (tag [_ _] ":") + (rep [_ kw] (nsed-name kw)) + (stringRep [_ kw] (nsed-name kw)) + (getVerboseHandler [_] nil)) + + clojure.lang.Ratio + (reify WriteHandler + (tag [_ _] "ratio") + (rep [_ r] (TransitFactory/taggedValue "array" [(numerator r) (denominator r)])) + (stringRep [_ _] nil) + (getVerboseHandler [_] nil)) + + clojure.lang.Symbol + (reify WriteHandler + (tag [_ _] "$") + (rep [_ sym] (nsed-name sym)) + (stringRep [_ sym] (nsed-name sym)) + (getVerboseHandler [_] nil)) + + cljs.vendor.cognitect.transit.WithMeta + (reify WriteHandler + (tag [_ _] "with-meta") + (rep [_ o] + (TransitFactory/taggedValue "array" + [(.-value ^cljs.vendor.cognitect.transit.WithMeta o) + (.-meta ^cljs.vendor.cognitect.transit.WithMeta o)])) + (stringRep [_ _] nil) + (getVerboseHandler [_] nil))}) + +(deftype Writer [w]) + +(defn writer + "Creates a writer over the provided destination `out` using + the specified format, one of: :msgpack, :json or :json-verbose. + + An optional opts map may be passed. Supported options are: + + :handlers - a map of types to WriteHandler instances, they are merged + with the default-handlers and then with the default handlers + provided by transit-java. + + :default-handler - a default WriteHandler to use if NO handler is + found for a type. If no default is specified, an error will be + thrown for an unknown type. + + :transform - a function of one argument that will transform values before + they are written." + ([out type] (writer out type {})) + ([^OutputStream out type {:keys [handlers default-handler transform]}] + (if (#{:json :json-verbose :msgpack} type) + (let [handler-map (if (instance? HandlerMapContainer handlers) + (handler-map handlers) + (merge default-write-handlers handlers))] + (Writer. (TransitFactory/writer (transit-format type) out handler-map default-handler + (when transform + (reify Function + (apply [_ x] + (transform x))))))) + (throw (ex-info "Type must be :json, :json-verbose or :msgpack" {:type type}))))) + +(defn write + "Writes a value to a transit writer." + [^Writer writer o] + (.write ^com.cognitect.transit.Writer (.w writer) o)) + + +;; reading + +(defn read-handler + "Creates a transit ReadHandler whose fromRep + method invokes the provided fn." + [from-rep] + (reify ReadHandler + (fromRep [_ o] (from-rep o)))) + +(defn read-map-handler + "Creates a Transit MapReadHandler whose fromRep + and mapReader methods invoke the provided fns." + [from-rep map-reader] + (reify MapReadHandler + (fromRep [_ o] (from-rep o)) + (mapReader [_] (map-reader)))) + +(defn read-array-handler + "Creates a Transit ArrayReadHandler whose fromRep + and arrayReader methods invoke the provided fns." + [from-rep array-reader] + (reify ArrayReadHandler + (fromRep [_ o] (from-rep o)) + (arrayReader [_] (array-reader)))) + + +(def default-read-handlers + "Returns a map of default ReadHandlers for + Clojure types. Java types are handled + by the default ReadHandlers provided by the + transit-java library." + {":" + (reify ReadHandler + (fromRep [_ o] (keyword o))) + + "$" + (reify ReadHandler + (fromRep [_ o] (symbol o))) + + "ratio" + (reify ReadHandler + (fromRep [_ o] (/ (.get ^java.util.List o 0) + (.get ^java.util.List o 1)))) + + "n" + (reify ReadHandler + (fromRep [_ o] (clojure.lang.BigInt/fromBigInteger + (BigInteger. ^String o)))) + + "set" + (reify ArrayReadHandler + (fromRep [_ o] o) + (arrayReader [_] + (reify ArrayReader + (init [_] (transient #{})) + (init [_ ^int size] (transient #{})) + (add [_ s item] (conj! s item)) + (complete [_ s] (persistent! s))))) + + "list" + (reify ArrayReadHandler + (fromRep [_ o] o) + (arrayReader [_] + (reify ArrayReader + (init [_] (java.util.ArrayList.)) + (init [_ ^int size] (java.util.ArrayList. size)) + (add [_ l item] (.add ^java.util.List l item) l) + (complete [_ l] (or (seq l) '()))))) + + "cmap" + (reify ArrayReadHandler + (fromRep [_ o]) + (arrayReader [_] + (let [marker (Object.) + ^objects next-key (object-array [marker])] + (reify ArrayReader + (init [_] (transient {})) + (init [_ ^int size] (transient {})) + (add [_ m item] + (let [k (aget next-key 0)] + (if (identical? k marker) + (do + (aset next-key 0 item) + m) + (do + (aset next-key 0 marker) + (assoc! m k item))))) + (complete [_ m] (persistent! m)))))) + + "with-meta" + (reify ReadHandler + (fromRep [_ o] + (with-meta (get ^java.util.List o 0) (get ^java.util.List o 1))))}) + +(defn map-builder + "Creates a MapBuilder that makes Clojure- + compatible maps." + [] + (reify MapReader + (init [_] (transient {})) + (init [_ ^int size] (transient {})) + (add [_ m k v] (assoc! m k v)) + (complete [_ m] (persistent! m)))) + +(defn list-builder + "Creates an ArrayBuilder that makes Clojure- + compatible lists." + [] + (reify ArrayReader + (init [_] (transient [])) + (init [_ ^int size] (transient [])) + (add [_ v item] (conj! v item)) + (complete [_ v] (persistent! v)))) + +(deftype Reader [r]) + +(defn reader + "Creates a reader over the provided source `in` using + the specified format, one of: :msgpack, :json or :json-verbose. + + An optional opts map may be passed. Supported options are: + + :handlers - a map of tags to ReadHandler instances, they are merged + with the Clojure default-read-handlers and then with the default ReadHandlers + provided by transit-java. + + :default-handler - an instance of DefaultReadHandler, used to process + transit encoded values for which there is no other ReadHandler; if + :default-handler is not specified, non-readable values are returned + as TaggedValues." + ([in type] (reader in type {})) + ([^InputStream in type {:keys [handlers default-handler]}] + (if (#{:json :json-verbose :msgpack} type) + (let [handler-map (if (instance? HandlerMapContainer handlers) + (handler-map handlers) + (merge default-read-handlers handlers)) + reader (TransitFactory/reader (transit-format type) + in + handler-map + default-handler)] + (Reader. (.setBuilders ^ReaderSPI reader + (map-builder) + (list-builder)))) + (throw (ex-info "Type must be :json, :json-verbose or :msgpack" {:type type}))))) + +(defn read + "Reads a value from a reader. Throws a RuntimeException when + the reader's InputStream is empty." + [^Reader reader] + (.read ^com.cognitect.transit.Reader (.r reader))) + +(defn record-write-handler + "Creates a WriteHandler for a record type" + [^Class type] + (reify WriteHandler + (tag [_ _] (.getName type)) + (rep [_ rec] (tagged-value "map" rec)) + (stringRep [_ _] nil) + (getVerboseHandler [_] nil))) + +(defn record-write-handlers + "Creates a map of record types to WriteHandlers" + [& types] + (reduce (fn [h t] (assoc h t (record-write-handler t))) + {} + types)) + +(defn record-read-handler + "Creates a ReadHandler for a record type" + [^Class type] + (let [type-name (map #(str/replace % "_" "-") (str/split (.getName type) #"\.")) + map-ctor (-> (str (str/join "." (butlast type-name)) "/map->" (last type-name)) + symbol + resolve)] + (reify ReadHandler + (fromRep [_ m] (map-ctor m))))) + +(defn record-read-handlers + "Creates a map of record type tags to ReadHandlers" + [& types] + (reduce (fn [d ^Class t] (assoc d (.getName t) (record-read-handler t))) + {} + types)) + +(defn read-handler-map + "Returns a HandlerMapContainer containing a ReadHandlerMap + containing all the default handlers for Clojure and Java and any + custom handlers that you supply, letting you store the return value + and pass it to multiple invocations of reader. This can be more + efficient than repeatedly handing the same raw map of tags -> custom + handlers to reader." + [custom-handlers] + (HandlerMapContainer. + (TransitFactory/readHandlerMap (merge default-read-handlers custom-handlers)))) + +(defn write-handler-map + "Returns a HandlerMapContainer containing a WriteHandlerMap + containing all the default handlers for Clojure and Java and any + custom handlers that you supply, letting you store the return value + and pass it to multiple invocations of writer. This can be more + efficient than repeatedly handing the same raw map of types -> custom + handlers to writer." + [custom-handlers] + (HandlerMapContainer. + (TransitFactory/writeHandlerMap (merge default-write-handlers custom-handlers)))) + +(defn write-meta + "For :transform. Will write any metadata present on the value." + [x] + (if (instance? clojure.lang.IObj x) + (if-let [m (meta x)] + (WithMeta. (with-meta x nil) m) + x) + x)) + +(comment + (require 'cognitect.transit) + (in-ns 'cognitect.transit) + + (import [java.io File ByteArrayInputStream ByteArrayOutputStream OutputStreamWriter]) + + (def out (ByteArrayOutputStream. 2000)) + + (def w (writer out :json)) + (def w (writer out :json-verbose)) + (def w (writer out :msgpack)) + (def w (writer out :msgpack {:transform write-meta})) + (def w (writer out :json {:transform write-meta})) + + (write w "foo") + (write w 10) + (write w [1 2 3]) + (write w (with-meta [1 2 3] {:foo 'bar})) + (String. (.toByteArray out)) + + (write w {:a-key 1 :b-key 2}) + (write w {"a" "1" "b" "2"}) + (write w {:a-key [1 2]}) + (write w #{1 2}) + (write w [{:a-key 1} {:a-key 2}]) + (write w [#{1 2} #{1 2}]) + (write w (int-array (range 10))) + (write w {[:a :b] 2}) + (write w [123N]) + (write w 1/3) + (write w {false 10 [] 20}) + + (def in (ByteArrayInputStream. (.toByteArray out))) + + (def r (reader in :json)) + + (def r (reader in :msgpack)) + + (def x (read r)) + (meta x) + + (type (read r)) + + ;; extensibility + + (defrecord Point [x y]) + + (defrecord Circle [c r]) + + (def ext-write-handlers + {Point + (write-handler "point" (fn [p] [(.x p) (.y p)])) + Circle + (write-handler "circle" (fn [c] [(.c c) (.r c)]))}) + + (def ext-read-handlers + {"point" + (read-handler (fn [[x y]] (prn "making a point") (Point. x y))) + "circle" + (read-handler (fn [[c r]] (prn "making a circle") (Circle. c r)))}) + + (def ext-write-handlers + (record-write-handlers Point Circle)) + + (def ext-read-handlers + (record-read-handlers Point Circle)) + + (def out (ByteArrayOutputStream. 2000)) + (def w (writer out :json {:handlers ext-write-handlers})) + (write w (Point. 10 20)) + (write w (Circle. (Point. 10 20) 30)) + (write w [(Point. 10 20) (Point. 20 40) (Point. 0 0)]) + + (def in (ByteArrayInputStream. (.toByteArray out))) + (def r (reader in :json {:handlers ext-read-handlers})) + (read r) + + ;; write and read handler maps + + (def custom-write-handler-map (write-handler-map ext-write-handlers)) + (def custom-read-handler-map (read-handler-map ext-read-handlers)) + + (def out (ByteArrayOutputStream. 2000)) + (def w (writer out :json {:handlers custom-write-handler-map})) + + (write w (Point. 10 20)) + + (def in (ByteArrayInputStream. (.toByteArray out))) + (def r (reader in :json {:handlers custom-read-handler-map})) + (read r) + ) diff --git a/src/test/cljs/Circle-min.js b/src/test/cljs/Circle-min.js new file mode 100644 index 0000000000..dfe8e799c6 --- /dev/null +++ b/src/test/cljs/Circle-min.js @@ -0,0 +1,14 @@ +var React = require('./react-min'); + +var Circle = React.createClass({ + render: function() { + return( + + + + + ); + } +}); + +module.exports = Circle; diff --git a/src/test/cljs/Circle.js b/src/test/cljs/Circle.js new file mode 100644 index 0000000000..898a6f6325 --- /dev/null +++ b/src/test/cljs/Circle.js @@ -0,0 +1,14 @@ +var React = require('./reactJS'); + +var Circle = React.createClass({ + render: function() { + return( + + + + + ); + } +}); + +module.exports = Circle; diff --git a/src/test/cljs/baz.cljs b/src/test/cljs/baz.cljs new file mode 100644 index 0000000000..09b1ad76f7 --- /dev/null +++ b/src/test/cljs/baz.cljs @@ -0,0 +1,11 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns baz) + +(defn f [x] x) diff --git a/src/test/cljs/calculator.js b/src/test/cljs/calculator.js new file mode 100644 index 0000000000..c69a6960c0 --- /dev/null +++ b/src/test/cljs/calculator.js @@ -0,0 +1,10 @@ +var calculator = { + add: function (a, b) { + return a + b; + }, + subtract: function (a, b) { + return a - b; + } +}; + +module.exports = calculator; diff --git a/src/test/cljs/calculator_global.js b/src/test/cljs/calculator_global.js new file mode 100644 index 0000000000..d1bcf38afc --- /dev/null +++ b/src/test/cljs/calculator_global.js @@ -0,0 +1,8 @@ +Calculator = { + add: function (a, b) { + return a + b; + }, + subtract: function (a, b) { + return a - b; + } +}; diff --git a/src/test/cljs/cljs/apply_test.cljs b/src/test/cljs/cljs/apply_test.cljs new file mode 100644 index 0000000000..9379ffb53f --- /dev/null +++ b/src/test/cljs/cljs/apply_test.cljs @@ -0,0 +1,130 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.apply-test + (:require [clojure.test :refer [deftest is]])) + +(defn fn-returning-this + [x] + (this-as this + this)) + +(deftest js-fns-test + (is (= 1 (apply js/parseInt ["1"]))) + (is (= 1 (apply js/parseInt "1" nil))) + (is (= 1 (apply js/parseInt "1" []))) + (is (= 15 (apply js/parseInt "F" [16]))) + (is (identical? fn-returning-this (apply fn-returning-this [0])) + "apply should supply the this object to be the function itself")) + +(deftest data-structures-test + (is (= 1 (apply #{1} [1]))) + (is (= nil (apply #{1} [2]))) + (is (= 1 (apply #{1} 1 [2]))) + (is (= 2 (apply #{} 1 [2]))) + (is (thrown? js/Error (apply #{} [])) + "We should still get wrong arity errors")) + +(def meta-f (with-meta (fn [& a] a) {})) + +;; more data structure test: +(deftest meta-fn-test + (is (= nil (apply meta-f []))) + (is (= '(1)) (apply meta-f [1])) + (is (= '(1)) (apply meta-f 1 [])) + (is (= '(1 2)) (apply meta-f 1 2 [])) + (is (= '(1 2 3)) (apply meta-f 1 2 3 [])) + (is (= '(1 2 3 4)) (apply meta-f 1 2 3 4 [])) + (is (= '(1 2 3 4 5)) (apply meta-f 1 2 3 4 5 [])) + (is (= (range 1 8)) (apply meta-f 1 2 3 4 5 [6 7])) + (is (= (range 21) (apply meta-f (range 21))) + "Should properly call the last IFn arity with 20 args with last being a seq") + (is (= (range 22) (apply meta-f (range 22))) + "Should properly call the last IFn arity with 20 args with last being a seq") + (is (= (range 22) (.apply meta-f nil (to-array (range 22)))) + ".apply should also handle >20 arguments") + (let [ctor #(.apply meta-f nil (js-arguments))] ; CLJS-3382 + (is (= '(1 2 3) (.apply ctor nil #js [1 2 3]))) + (is (= (range 30) (.apply ctor nil (to-array (range 30))))))) + +(deftest multi-arity-test + (is (= 2 (apply (fn ([a] a) ([a b] b)) 1 [2]))) + (is (= 1 (apply (fn ([a] a) ([a b] b)) 1 []))) + (is (= 1 (apply (fn ([a] a) ([a b] b)) 1 nil))) + (is (thrown? js/Error (apply (fn ([a] a) ([a b] b)) 1 2 3 nil))) + (is (thrown? js/Error (apply (fn ([a b] a) + ([a b c] a)) [1])))) + +(deftest single-arity-variadic-test + (doseq [f [(fn [& r] r) + (fn [a & r] (list* a r)) + (fn [a b & r] (list* a b r)) + (fn [a b c & r] (list* a b c r)) + (fn [a b c d & r] (list* a b c d r)) + (fn [a b c d e & r] (list* a b c d e r))]] + (is (= (range 10) (apply f (range 10)))) + (is (= (range 10) (apply f 0 (range 1 10)))) + (is (= (range 10) (apply f 0 1 (range 2 10)))) + (is (= (range 10) (apply f 0 1 2 (range 3 10)))) + (is (= (range 10) (apply f 0 1 2 3 (range 4 10)))) + (is (= (range 10) (apply f 0 1 2 3 4 (range 5 10)))) + (is (= (range 10) (apply f 0 1 2 3 4 5 (range 6 10))))) + (is (nil? (apply (fn [a & b] b) [1])) + "rest should stay nil") + (is (nil? (apply (fn [a & b] b) 1 [])) + "rest should be nil'd") + (is (= '(2) (apply (fn [a & b] b) 1 [2])) + "rest should be nil'd") + (is (= (range 30) + (apply (fn [_ _ _ _ _ a b c d e _ _ _ _ _ f g h i j k _ _ _ _ _ & R] + (let [a (array)] + (copy-arguments a) + (concat (.slice a 0 26) R))) + (range 30))) + "Variadic function are not limited to 20 params")) + +(deftest multi-arity-variadic-test + (doseq [f [(fn ([]) ([& r] r)) + (fn ([a]) ([a & r] (list* a r))) + (fn ([a]) ([a b & r] (list* a b r))) + (fn ([a]) ([a b c & r] (list* a b c r))) + (fn ([a]) ([a b c d & r] (list* a b c d r))) + (fn ([a]) ([a b c d e & r] (list* a b c d e r)))]] + (is (= (range 10) (apply f (range 10)))) + (is (= (range 10) (apply f 0 (range 1 10)))) + (is (= (range 10) (apply f 0 1 (range 2 10)))) + (is (= (range 10) (apply f 0 1 2 (range 3 10)))) + (is (= (range 10) (apply f 0 1 2 3 (range 4 10)))) + (is (= (range 10) (apply f 0 1 2 3 4 (range 5 10)))) + (is (= (range 10) (apply f 0 1 2 3 4 5 (range 6 10))))) + (is (= 1 (apply (fn ([a] a) ([a & b] b)) [1]))) + (is (= '(2) (apply (fn ([a] a) ([a & b] b)) 1 [2]))) + (is (= 1 (apply (fn ([a] a) ([a & b] b)) 1 []))) + (is (= 1 (apply (fn ([a] a) ([a & b] b)) [1]))) + (is (= '(2 3 4 5 6) (apply (fn ([a] a) ([a & b] b)) 1 2 3 4 5 [6]))) + (is (= '(2 3 4 5) (apply (fn ([a] a) ([a & b] b)) 1 2 3 4 5 []))) + (is (= (range 30) + (apply (fn ([a]) + ([_ _ _ _ _ a b c d e _ _ _ _ _ f g h i j k _ _ _ _ _ & R] + (let [a (array)] + (copy-arguments a) + (concat (.slice a 0 26) R)))) + (range 30))) + "Variadic function are not limited to 20 params")) + +(deftest incorrect-invokes-m-arity-test + ;; This is the fault of .call currently not throwing. Can't be caught by apply: + #_(is (thrown? js/Error + (apply (fn ([a b] a) + ([a b & c] a)) [1])) + "Apply should throw on wrong arity.")) + +(deftest incorrect-invokes-dispatcher-test + ;; The dispatcher needs to look at arguments.length: + #_(is (thrown? js/Error (.call (fn [a b & b] a) nil 1)) + "Dispatcher should complain about wrong arity")) diff --git a/src/test/cljs/cljs/array_access/alpha.cljs b/src/test/cljs/cljs/array_access/alpha.cljs new file mode 100644 index 0000000000..318b31bd58 --- /dev/null +++ b/src/test/cljs/cljs/array_access/alpha.cljs @@ -0,0 +1,14 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.array-access.alpha + (:require-macros [cljs.array-access.helper :as helper]) + (:require [cljs.array-access.beta])) + +(defn unchecked-arrays? [] + (helper/unchecked-arrays?)) diff --git a/src/test/cljs/cljs/array_access/beta.cljs b/src/test/cljs/cljs/array_access/beta.cljs new file mode 100644 index 0000000000..867b3e477a --- /dev/null +++ b/src/test/cljs/cljs/array_access/beta.cljs @@ -0,0 +1,12 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.array-access.beta + (:require-macros [cljs.array-access.helper :as helper])) + +(set! *unchecked-arrays* true) diff --git a/src/test/cljs/cljs/array_access/helper.clj b/src/test/cljs/cljs/array_access/helper.clj new file mode 100644 index 0000000000..70c9600472 --- /dev/null +++ b/src/test/cljs/cljs/array_access/helper.clj @@ -0,0 +1,13 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.array-access.helper + (:require [cljs.analyzer :as ana])) + +(defmacro unchecked-arrays? [] + (ana/unchecked-arrays?)) diff --git a/src/test/cljs/cljs/array_access_test.cljc b/src/test/cljs/cljs/array_access_test.cljc new file mode 100644 index 0000000000..2cabd60a10 --- /dev/null +++ b/src/test/cljs/cljs/array_access_test.cljc @@ -0,0 +1,13 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.array-access-test) + +(defmacro suppress-errs [& forms] + `(cljs.core/with-redefs [cljs.core/*print-err-fn* nil] + ~@forms)) \ No newline at end of file diff --git a/src/test/cljs/cljs/array_access_test.cljs b/src/test/cljs/cljs/array_access_test.cljs new file mode 100644 index 0000000000..a98898fa36 --- /dev/null +++ b/src/test/cljs/cljs/array_access_test.cljs @@ -0,0 +1,186 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.array-access-test + (:require-macros [cljs.array-access-test :refer [suppress-errs]]) + (:require [cljs.test :as test :refer [deftest is]] + [cljs.array-access.alpha :as alpha])) + +(deftest cljs-2861-test + ;; With cljs-2718, a typo led to (set! *unchecked-arrays* true) as + ;; not being treated as a no-op generating intrisic, which we can + ;; detect here when this test is run in JVM ClojureScript. + (is (false? *unchecked-arrays*))) + +(deftest unchecked-arrays-file-scope-test + (is (not (alpha/unchecked-arrays?)))) + +(deftest aget-test + (is (thrown? js/Error (aget nil 1))) + (is (nil? (aget #js {} 1))) + (is (nil? (aget #js [] 0))) + (is (nil? (aget #js [1] -1))) + (is (nil? (aget #js [1] 1))) + (is (== 1 (aget #js [1] "0"))) + (is (nil? (aget [1] 0))) + (is (== 1 (aget #js [1] 0))) + (is (== 1 (aget #js {:foo 1} "foo"))) + (is (nil? (aget #js [#js {}] 0 0))) + (is (nil? (aget #js [#js []] 0 0))) + (is (nil? (aget #js [#js [1]] 0 -1))) + (is (nil? (aget #js [#js [1]] 0 1))) + (is (== 1 (aget #js [#js [1]] 0 "0"))) + (is (== 1 (aget #js [#js [1]] 0 0)))) + +(deftest aset-test + (is (thrown? js/Error (aset nil 1 "x"))) + (is (= "x" (aset #js {} 1 "x"))) + (is (= "x" (aset #js [] 0 "x"))) + (is (= "x" (aset #js [1] -1 "x"))) + (is (= "x" (aset #js [1] 1 "x"))) + (is (= "x" (aset #js [1] "0" "x"))) + (is (= "x" (aset [1] 0 "x"))) + (is (= "x" (aset #js [1] 0 "x"))) + (let [v #js [1]] + (aset v 0 "x") + (is (= "x" (aget v 0)))) + (let [v #js {:foo 1}] + (aset v "foo" "x") + (is (= "x" (aget v "foo")))) + (is (= "x" (aset #js [#js {}] 0 0 "x"))) + (is (= "x" (aset #js [#js []] 0 0 "x"))) + (is (= "x" (aset #js [#js [1]] 0 -1 "x"))) + (is (= "x" (aset #js [#js [1]] 0 1 "x"))) + (is (= "x" (aset #js [#js [1]] 0 "0" "x"))) + (is (= "x" (aset #js [#js [1]] 0 0 "x"))) + (let [v #js [#js [1]]] + (aset v 0 0 "x") + (is (= "x" (aget v 0 0))))) + +(deftest unchecked-aget-test + (is (thrown? js/Error (unchecked-get nil 1))) + (is (nil? (unchecked-get #js {} 1))) + (is (nil? (unchecked-get #js [] 0))) + (is (nil? (unchecked-get #js [1] -1))) + (is (nil? (unchecked-get #js [1] 1))) + (is (== 1 (unchecked-get #js [1] "0"))) + (is (nil? (unchecked-get [1] 0))) + (is (== 1 (unchecked-get #js [1] 0))) + (is (== 1 (unchecked-get #js {:foo 1} "foo")))) + +(deftest unchecked-set-test + (is (thrown? js/Error (unchecked-set nil 1 "x"))) + (is (= "x" (unchecked-set #js {} 1 "x"))) + (is (= "x" (unchecked-set #js [] 0 "x"))) + (is (= "x" (unchecked-set #js [1] -1 "x"))) + (is (= "x" (unchecked-set #js [1] 1 "x"))) + (is (= "x" (unchecked-set #js [1] "0" "x"))) + (is (= "x" (unchecked-set [1] 0 "x"))) + (is (= "x" (unchecked-set #js [1] 0 "x"))) + (let [v #js [1]] + (unchecked-set v 0 "x") + (is (= "x" (aget v 0)))) + (let [v #js {:foo 1}] + (unchecked-set v "foo" "x") + (is (= "x" (aget v "foo"))))) + +;; to suppress compile time warnings +(defn checked-aget-alias [& args] + (apply checked-aget args)) + +(defn checked-aset-alias [& args] + (apply checked-aset args)) + +(deftest checked-aget-test + (suppress-errs + (is (thrown? js/Error (checked-aget-alias nil 1))) + (is (nil? (checked-aget-alias #js {} 1))) + (is (nil? (checked-aget-alias #js [] 0))) + (is (nil? (checked-aget-alias #js [1] -1))) + (is (nil? (checked-aget-alias #js [1] 1))) + (is (== 1 (checked-aget-alias #js [1] "0"))) + (is (nil? (checked-aget-alias [1] 0))) + (is (== 1 (checked-aget-alias #js [1] 0))) + (is (== 1 (checked-aget-alias #js {:foo 1} "foo"))) + (is (nil? (checked-aget-alias #js [#js {}] 0 0))) + (is (nil? (checked-aget-alias #js [#js []] 0 0))) + (is (nil? (checked-aget-alias #js [#js [1]] 0 -1))) + (is (nil? (checked-aget-alias #js [#js [1]] 0 1))) + (is (== 1 (checked-aget-alias #js [#js [1]] 0 "0"))) + (is (== 1 (checked-aget-alias #js [#js [1]] 0 0))))) + +(deftest checked-aset-test + (suppress-errs + (is (thrown? js/Error (checked-aset-alias nil 1 "x"))) + (is (= "x" (checked-aset-alias #js {} 1 "x"))) + (is (= "x" (checked-aset-alias #js [] 0 "x"))) + (is (= "x" (checked-aset-alias #js [1] -1 "x"))) + (is (= "x" (checked-aset-alias #js [1] 1 "x"))) + (is (= "x" (checked-aset-alias #js [1] "0" "x"))) + (is (= "x" (checked-aset-alias [1] 0 "x"))) + (is (= "x" (checked-aset-alias #js [1] 0 "x"))) + (let [v #js [1]] + (checked-aset-alias v 0 "x") + (is (= "x" (aget v 0)))) + (let [v #js {:foo 1}] + (checked-aset-alias v "foo" "x") + (is (= "x" (aget v "foo")))) + (is (= "x" (checked-aset-alias #js [#js {}] 0 0 "x"))) + (is (= "x" (checked-aset-alias #js [#js []] 0 0 "x"))) + (is (= "x" (checked-aset-alias #js [#js [1]] 0 -1 "x"))) + (is (= "x" (checked-aset-alias #js [#js [1]] 0 1 "x"))) + (is (= "x" (checked-aset-alias #js [#js [1]] 0 "0" "x"))) + (is (= "x" (checked-aset-alias #js [#js [1]] 0 0 "x"))) + (let [v #js [#js [1]]] + (checked-aset-alias v 0 0 "x") + (is (= "x" (aget v 0 0)))))) + +;; to suppress compile time warnings +(defn checked-aget'-alias [& args] + (apply checked-aget' args)) + +(defn checked-aset'-alias [& args] + (apply checked-aset' args)) + +(deftest checked-aget'-test + (is (thrown? js/Error (checked-aget'-alias nil 1))) + (is (thrown? js/Error (checked-aget'-alias #js {} 1))) + (is (thrown? js/Error (checked-aget'-alias #js [] 0))) + (is (thrown? js/Error (checked-aget'-alias #js [1] -1))) + (is (thrown? js/Error (checked-aget'-alias #js [1] 1))) + (is (thrown? js/Error (checked-aget'-alias #js [1] "0"))) + (is (thrown? js/Error (checked-aget'-alias [1] 0))) + (is (== 1 (checked-aget'-alias #js [1] 0))) + (is (thrown? js/Error (checked-aget'-alias #js [#js {}] 0 0))) + (is (thrown? js/Error (checked-aget'-alias #js [#js []] 0 0))) + (is (thrown? js/Error (checked-aget'-alias #js [#js [1]] 0 -1))) + (is (thrown? js/Error (checked-aget'-alias #js [#js [1]] 0 1))) + (is (thrown? js/Error (checked-aget'-alias #js [#js [1]] 0 "0"))) + (is (== 1 (checked-aget'-alias #js [#js [1]] 0 0)))) + +(deftest checked-aset'-test + (is (thrown? js/Error (checked-aset'-alias nil 1 "x"))) + (is (thrown? js/Error (checked-aset'-alias #js {} 1 "x"))) + (is (thrown? js/Error (checked-aset'-alias #js [] 0 "x"))) + (is (thrown? js/Error (checked-aset'-alias #js [1] -1 "x"))) + (is (thrown? js/Error (checked-aset'-alias #js [1] 1 "x"))) + (is (thrown? js/Error (checked-aset'-alias #js [1] "0" "x"))) + (is (thrown? js/Error (checked-aset'-alias [1] 0 "x"))) + (is (= "x" (checked-aset'-alias #js [1] 0 "x"))) + (let [v #js [1]] + (checked-aset'-alias v 0 "x") + (is (= "x" (aget v 0)))) + (is (thrown? js/Error (checked-aset'-alias #js [#js {}] 0 0 "x"))) + (is (thrown? js/Error (checked-aset'-alias #js [#js []] 0 0 "x"))) + (is (thrown? js/Error (checked-aset'-alias #js [#js [1]] 0 -1 "x"))) + (is (thrown? js/Error (checked-aset'-alias #js [#js [1]] 0 1 "x"))) + (is (thrown? js/Error (checked-aset'-alias #js [#js [1]] 0 "0" "x"))) + (is (= "x" (checked-aset'-alias #js [#js [1]] 0 0 "x"))) + (let [v #js [#js [1]]] + (checked-aset'-alias v 0 0 "x") + (is (= "x" (aget v 0 0))))) diff --git a/src/test/cljs/cljs/async_await_test.cljs b/src/test/cljs/cljs/async_await_test.cljs new file mode 100644 index 0000000000..c3d9d447a3 --- /dev/null +++ b/src/test/cljs/cljs/async_await_test.cljs @@ -0,0 +1,276 @@ +(ns cljs.async-await-test + (:refer-global :only [Date Promise]) + (:require [clojure.test :refer [deftest is async]] + [cljs.core :as cc :refer [await] :rename {await aw}] + [goog.object :as gobj]) + (:require-macros [cljs.macro-test.macros :refer [await!] :as macros])) + +(defn ^:async foo [n] + (let [x (await (js/Promise.resolve 10)) + y (let [y (await (js/Promise.resolve 20))] + (inc y)) + ;; not async + f (fn [] 20)] + (+ n x y (f)))) + +(deftest defn-test + (async done + (try + (let [v (await (foo 10))] + (is (= 61 v))) + (let [v (await (apply foo [10]))] + (is (= 61 v))) + (catch :default _ (is false)) + (finally (done))))) + +(defn ^:async variadic-foo [n & ns] + (let [x (await (js/Promise.resolve n)) + y (let [y (await (js/Promise.resolve (apply + ns)))] + (inc y)) + ;; not async + f (fn [] 20)] + (+ n x y (f)))) + +(deftest variadic-defn-test + (async done + (try + (let [v (await (variadic-foo 10))] + (is (= 41 v))) + (let [v (await (variadic-foo 10 1 2 3))] + (is (= 47 v))) + (let [v (await (apply variadic-foo [10 1 2 3]))] + (is (= 47 v))) + (catch :default _ (is false)) + (finally (done))))) + +(defn ^:async multi-arity-foo + ([n] (await n)) + ([n x] (+ (await n) x))) + +(deftest multi-arity-defn-test + (async done + (try + (let [v (await (multi-arity-foo 10))] + (is (= 10 v))) + (let [v (await (multi-arity-foo 10 20))] + (is (= 30 v))) + (let [v (await (apply multi-arity-foo [10]))] + (is (= 10 v))) + (let [v (await (apply multi-arity-foo [10 20]))] + (is (= 30 v))) + (catch :default _ (is false)) + (finally (done))))) + +(defn ^:async multi-arity-variadic-foo + ([n] (await n)) + ([n & xs] (apply + (await n) xs))) + +(deftest multi-arity-variadic-test + (async done + (try + (let [v (await (multi-arity-variadic-foo 10))] + (is (= 10 v))) + (let [v (await (multi-arity-variadic-foo 10 20))] + (is (= 30 v))) + (let [v (await (apply multi-arity-variadic-foo [10]))] + (is (= 10 v))) + (let [v (await (apply multi-arity-variadic-foo [10 20]))] + (is (= 30 v))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest fn-test + (async done + (try + (let [f (^:async fn [x] (+ x (await (js/Promise.resolve 20)))) + v (await (f 10)) + v2 (await (apply f [10]))] + (is (= 30 v v2))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest varargs-fn-test + (async done + (try + (let [f (^:async fn [x & xs] (apply + x (await (js/Promise.resolve 20)) xs)) + v (await (f 10)) + v2 (await (apply f [10])) + v3 (await (f 5 5)) + v4 (await (apply f [5 5]))] + (is (= 30 v v2 v3 v4))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest variadic-fn-test + (async done + (try (let [f (^:async fn + ([x] (await (js/Promise.resolve x))) + ([x y] (cons (await (js/Promise.resolve x)) [y])))] + (is (= [1 1 [1 2] [1 2]] + [(await (f 1)) + (await (apply f [1])) + (await (f 1 2)) + (await (apply f [1 2]))]))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest variadic-varargs-fn-test + (async done + (try (let [f (^:async fn + ([x] (await (js/Promise.resolve x))) + ([x & xs] (cons (await (js/Promise.resolve x)) xs)))] + (is (= [1 1 [1 2 3] [1 2 3]] + [(await (f 1)) + (await (apply f [1])) + (await (f 1 2 3)) + (await (apply f [1 2 3]))]))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest await-in-throw-test + (async done + (let [f (^:async fn [x] (inc (if (odd? x) (throw (await (js/Promise.resolve "dude"))) x)))] + (try + (let [x (await (f 2))] + (is (= 3 x))) + (let [x (try (await (f 1)) + (catch :default e e))] + (is (= "dude" x))) + (catch :default _ (is false)) + (finally (done)))))) + +(deftest await-in-do-test + (async done + (try + (let [a (atom 0) + f (^:async fn [] (let [_ (do (swap! a inc) + (swap! a + (await (js/Promise.resolve 2))))] + @a)) + v (await (f))] + (is (= 3 v))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest await-let-fn-test + (async done + (try + (let [f (^:async fn [] (let [v + ;; force letfn in expr position + (letfn [(^:async f [] (inc (await (js/Promise.resolve 10))))] + (inc (await (f))))] + (identity v))) + v (await (f))] + (is (= 12 v))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest await-in-loop-test + (async done + (try + (let [f (^:async fn [] (let [x + ;; force loop in expr position + (loop [xs (map #(js/Promise.resolve %) [1 2 3]) + ys []] + (if (seq xs) + (let [x (first xs) + v (await x)] + (recur (rest xs) (conj ys v))) + ys))] + (identity x))) + v (await (f))] + (is (= [1 2 3] v))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest await-in-nested + (async done + (try + (let [f (^:async fn [] + (let [b1 1 + b2 (let [x 2] + (+ x + ;; outer let doesn't have awaits + ;; but inner let does, so outer let should become async + (let [x (await (js/Promise.resolve 1))] x))) + b3 (case :foo :foo (case :foo :foo (await (js/Promise.resolve 1)))) + b4 (int ;; wrapped in int to avoid false positive warning: + ;; all arguments must be numbers, got [number + ;; ignore] instead + (try (throw (throw (await (js/Promise.resolve 1)))) (catch :default _ 1 ))) + a (atom 0) + b5 (do (swap! a inc) (swap! a inc) + ;; do with single expr, wrapped in identity to avoid merging with upper do + (identity (do (swap! a (await (js/Promise.resolve inc))))) + ;; do with multiple exprs, wrapped identity to avoid merging with upper do + (identity (do (swap! a inc) (swap! a (await (js/Promise.resolve inc))))) + @a) + b6 (try (identity (try 1 (finally (await nil)))) + (finally nil)) + b7 (letfn [(f [x] x)] + (f (letfn [(f [x] x)] + (f (await 1)))))] + (await (+ b1 b2 b3 b4 b5 b6 b7))))] + (is (= 13 (await (f))))) + (catch :default _ (is false)) + (finally (done))))) + +(deftest await-with-aliases-or-renamed-and-via-macros-test + (async done + (try + (let [a (await! (js/Promise.resolve 1)) + b (macros/await! (js/Promise.resolve 1)) + c (cc/await (js/Promise.resolve 1)) + d (aw (js/Promise.resolve 1)) + e (cljs.core/await (js/Promise.resolve 1)) + f (clojure.core/await (js/Promise.resolve 1))] + (is (= 1 a)) + (is (= 1 b)) + (is (= 1 c)) + (is (= 1 d)) + (is (= 1 e)) + (is (= 1 f)) + (done)) + (catch :default _ (is false))))) + +(deftest await-with-ctor + (async done + (let [f (^:async fn [] (Date. (await (Promise/resolve 0))))] + (is (= (Date. 0) (await (f))))) + (done))) + +(deftest await-with-literals + (async done + (let [objf (^:async fn [] #js {:foo (await (Promise/resolve "bar"))})] + (is (gobj/equals #js {:foo "bar"} (await (objf))))) + (let [arrayf (^:async fn [] #js [0 (await (Promise/resolve 1 )) 2])] + (is (= [0 1 2] (seq (await (arrayf)))))) + (let [listf (^:async fn [] (list 0 1 2))] + (is (= '(0 1 2) (await (listf))))) + (let [vectorf (^:async fn [] [0 (await (Promise/resolve 1 )) 2])] + (is (= [0 1 2] (await (vectorf))))) + (let [mapf (^:async fn [] {:foo (await (Promise/resolve :bar))})] + (is (= {:foo :bar} (await (mapf))))) + (let [setf (^:async fn [] #{:foo (await (Promise/resolve :bar)) :baz})] + (is (= #{:foo :bar :baz} (await (setf))))) + (done))) + +(deftest await-with-if-test + (async done + (let [f (^:async fn [] (if (await (Promise/resolve true)) :success :fail))] + (is (= :success (await (f))))) + (done))) + +(defn ^:async async-destructure + [{:keys [foo bar] + :or {bar (await (Promise/resolve "hello!"))}}] + [foo bar]) + +(deftest await-in-async-destructure + (async done + (let [res (await (async-destructure {:foo 1}))] + (is (= [1 "hello!"] res))) + (done))) + +(comment + (clojure.test/run-tests) + ) diff --git a/src/test/cljs/cljs/baz.cljs b/src/test/cljs/cljs/baz.cljs new file mode 100644 index 0000000000..09b1ad76f7 --- /dev/null +++ b/src/test/cljs/cljs/baz.cljs @@ -0,0 +1,11 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns baz) + +(defn f [x] x) diff --git a/src/test/cljs/cljs/binding_test.cljs b/src/test/cljs/cljs/binding_test.cljs new file mode 100644 index 0000000000..c162777074 --- /dev/null +++ b/src/test/cljs/cljs/binding_test.cljs @@ -0,0 +1,56 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.binding-test + (:require [cljs.test :refer-macros [deftest is]] + [cljs.binding-test-other-ns :as o])) + +(deftest test-binding + (is (binding [o/*foo* 2] + (= o/*foo* 2))) + (is (= o/*foo* 1))) + +(deftest test-with-redefs + (is (with-redefs [o/bar 2] + (= o/bar 2))) + (is (= o/bar 10))) + +(def ^:dynamic *a* 1) +(def ^:dynamic *b* nil) + +(deftest test-binding-parallel + (is (= 2 (binding [*a* 10 + *b* (inc *a*)] + *b*)))) + +(def a 1) +(def b nil) + +(deftest test-redefs-parallel + (is (= 2 (with-redefs [a 10 + b (inc a)] + b)))) + +(def ^:dynamic *foo* false) +(def ^:dynamic ^boolean *foo-tagged* false) + +(defn bar [] (if *foo* 1 2)) +(defn bar-tagged [] (if *foo-tagged* 1 2)) + +(deftest test-tag-inference + (is (= 2 (bar))) + (binding [*foo* "abc"] + (is (= 1 (bar)))) + (binding [*foo* ""] + (is (= 1 (bar)))) + + (is (= 2 (bar-tagged))) + (binding [*foo-tagged* "abc"] + (is (= 1 (bar-tagged)))) + (binding [*foo-tagged* ""] + (is (= 2 (bar-tagged))))) diff --git a/src/test/cljs/cljs/binding_test_other_ns.cljs b/src/test/cljs/cljs/binding_test_other_ns.cljs new file mode 100644 index 0000000000..962b00b5b2 --- /dev/null +++ b/src/test/cljs/cljs/binding_test_other_ns.cljs @@ -0,0 +1,13 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.binding-test-other-ns) + +(def ^:dynamic *foo* 1) + +(def bar 10) diff --git a/src/test/cljs/cljs/chunked_seq.cljs b/src/test/cljs/cljs/chunked_seq.cljs new file mode 100644 index 0000000000..73abf707f5 --- /dev/null +++ b/src/test/cljs/cljs/chunked_seq.cljs @@ -0,0 +1,27 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.chunked-seq + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is are]])) + +(deftest test-cljs-2693 + (is (chunked-seq? (range 5))) + (is (satisfies? IChunk (chunk-first (range 5)))) + (is (nil? (chunk-next (range 32)))) + (is (not (chunked-seq? (range 2 -2 0)))) + (is (chunked-seq? (range))) + (is (= 5 (count (chunk-first (range 5))))) + (is (= 32 (count (chunk-first (range))))) + (is (= 17 (nth (chunk-first (range 100)) 17))) + (is (= 35 (nth (chunk-first (range 100)) 35))) + (is (= 32 (count (chunk-first (range 100))))) + (is (= 0 (first (range 5)))) + (is (= 1 (second (range 5)))) + (is (= (range 1 5) (rest (range 5)))) + (is (= (range 1 5) (next (range 5))))) \ No newline at end of file diff --git a/src/test/cljs/cljs/clojure_alias_test.cljs b/src/test/cljs/cljs/clojure_alias_test.cljs new file mode 100644 index 0000000000..aa5ca71500 --- /dev/null +++ b/src/test/cljs/cljs/clojure_alias_test.cljs @@ -0,0 +1,26 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.clojure-alias-test + "Tests requiring via `clojure.*` instead of `cljs.*`" + (:refer-clojure :exclude [use-macros]) + (:require-macros clojure.spec.gen.alpha) + ;(:use-macros [clojure.analyzer.macros :only [no-warn]]) + (:require [clojure.test :refer [deftest is] :rename {is is?}] + [clojure.spec.alpha :as s :refer [spec? spec] :rename {spec foo}])) + +(deftest normal-test + (is? (= 1 1))) + +(deftest aliases-test + (is? (= spec? clojure.spec.alpha/spec? cljs.spec.alpha/spec?)) + (is? (foo number?))) + +(deftest use-macros + (s/def ::even? (s/and number? even?)) + (is? (s/valid? ::even? 2))) diff --git a/src/test/cljs/cljs/collections_test.cljs b/src/test/cljs/cljs/collections_test.cljs new file mode 100644 index 0000000000..8607d30c28 --- /dev/null +++ b/src/test/cljs/cljs/collections_test.cljs @@ -0,0 +1,1223 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.collections-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is are run-tests]] + [clojure.test.check.clojure-test :refer-macros [defspec]] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as prop :include-macros true])) + +(deftest test-map-operations + (testing "Test basic map collection operations" + (is (= {:a :b} (get {[1 2 3] {:a :b}, 4 5} [1 2 3]))) + (is (not (= {:a :b :c nil} {:a :b :d nil}))) + (is (= {:a :b} (dissoc {:a :b :c :d} :c))) + #_(is (= (hash-map :foo 5) + (assoc (ObjMap. nil (array) (js-obj)) :foo 5)))) + (testing "Testing assoc dissoc" + (is (= {1 2 3 4} (assoc {} 1 2 3 4))) + (is (= {1 2} (assoc {} 1 2))) + (is (= [42 2] (assoc [1 2] 0 42))) + (is (= {} (dissoc {1 2 3 4} 1 3))) + (is (= {1 2} (dissoc {1 2 3 4} 3))) + (is (nil? (dissoc nil :foo)))) + (testing "Testing find" + (is (= (find {} :a) nil)) + (is (= (find {:a 1} :a) [:a 1])) + (is (= (find {:a 1} :b) nil)) + (is (= (find {:a 1 :b 2} :a) [:a 1])) + (is (= (find {:a 1 :b 2} :b) [:b 2])) + (is (= (find {:a 1 :b 2} :c) nil)) + (is (= (find {} nil) nil)) + (is (= (find {:a 1} nil) nil)) + (is (= (find {:a 1 :b 2} nil) nil)) + (is (= (find [1 2 3] 0) [0 1])) + (is (= (find [1 2 3] -1) nil)) + (is (= (find [1 2 3] js/NaN) nil)) + (is (= (find [1 2 3] :a) nil)) + (is (= (find [1 2 3] 10) nil))) + ) + +(deftest test-map + (testing "IDrop" + (let [am (apply array-map (interleave (range 7) (range 7)))] + (is (satisfies? IDrop am)) + (is (= [[3 3] [4 4] [5 5] [6 6]] (drop 3 am))) + (is (satisfies? IDrop (drop 3 am))) + (is (= [[5 5] [6 6]] (drop 2 (drop 3 am))))))) + +(deftest test-vectors + (testing "Testing vectors" + (is (= :a (nth [:a :b :c :d] 0))) + (is (= :a (nth [:a :b :c :d] 0.1))) + (let [pv (vec (range 97))] + (testing "basic ops" + (is (= (nth pv 96) 96)) + (is (= (nth pv 97 nil) nil)) + (is (= (pv 96) 96)) + (is (= (reverse pv) (rseq pv))) + (is (nil? (rseq []))))) + (let [pv (vec (range 33))] + (testing "pop" + (is (= pv (-> pv pop pop (conj 31) (conj 32)))))) + (let [stack1 (pop (vec (range 97))) + stack2 (pop stack1)] + (testing "stack operations" + (is (= 95 (peek stack1))) + (is (= 94 (peek stack2))))) + (testing "IDrop" + (is (satisfies? IDrop (vec (range 39)))) + (is (= (range 3 39) (drop 3 (vec (range 39))))) + (is (= (range 31 39) (drop 31 (vec (range 39))))) + (is (= (range 32 39) (drop 32 (vec (range 39))))) + (is (= (range 33 39) (drop 33 (vec (range 39))))) + (is (satisfies? IDrop (drop 3 (vec (range 39))))) + (is (= (range 31 39) (drop 28 (drop 3 (vec (range 39)))))) + (is (= (range 32 39) (drop 29 (drop 3 (vec (range 39)))))) + (is (= (range 33 39) (drop 30 (drop 3 (vec (range 39))))))) + (let [v1 (vec (range 10)) + v2 (vec (range 5)) + s (subvec v1 2 8)] + (testing "subvec" + (is (= s (-> v1 (subvec 2) (subvec 0 6)) (->> v1 (drop 2) (take 6)))) + (is (= 6 (count s))) + (is (= [2 3 4 5 6] (pop s))) + (is (= 7 (peek s))) + (is (= [2 3 4 5 6 7 1] + (assoc s 6 1) + (conj s 1))) + (is (= 27 (reduce + s))) + (is (= s (vec s))) ; pour into plain vector + ;; go outside ranges + (is (= :fail (try (subvec v2 0 6) (catch js/Error e :fail)))) + (is (= :fail (try (subvec v2 6 10) (catch js/Error e :fail)))) + (is (= :fail (try (subvec v2 6 10) (catch js/Error e :fail)))) + (is (= :fail (try (subvec v2 3 6) (catch js/Error e :fail)))) + ;; no layered subvecs + (is (identical? v1 (.-v (subvec s 1 4)))) + (let [m {:x 1}] + (is (= m (meta (with-meta s m))))) + ;; CLJS-997 + (is (= (reduce-kv + 123 (vec (range 10 50))) + (reduce-kv + 123 (subvec (vec (range 100)) 10 50))))) + ;; CLJS-513 + (let [sentinel (js-obj) + s (subvec [0 1 2 3] 1 2)] + (testing "bounds checking" + (is (identical? sentinel (try (s -1) (catch js/Error _ sentinel)))) + (is (identical? sentinel (try (s 1) (catch js/Error _ sentinel)))))) + ;; CLJS-765 + (let [sv1 (subvec [0 1 2 3] 1 2) + sv2 (subvec [0 1 2 3] 1 1)] + (testing "rseq equality" + (is (= (rseq sv1) '(1))) + (is (nil? (rseq sv2))))) + (let [sv1 (subvec [0 1 2 3] 0 2) + sv2 (subvec [0 1 2 3] 1 3)] + (testing "IFind" + (is (= (find sv1 0) [0 0])) + (is (= (find sv1 1) [1 1])) + (is (= (find sv1 2) nil)) + (is (= (find sv1 -1) nil)) + (is (= (find sv2 0) [0 1])) + (is (= (find sv2 1) [1 2])) + (is (= (find sv2 2) nil)) + (is (= (find sv2 -1) nil)))) + ) + )) + +(deftest test-sets + (testing "Testing persistent sets" + (is (set [])) + (is (= #{} (set []))) + (is (= #{} (hash-set))) + (is (identical? cljs.core.PersistentHashSet (type (hash-set)))) + + (is (= #{"foo"} (set ["foo"]))) + (is (= #{"foo"} (hash-set "foo"))) + (is (= #{1 2 3} #{1 3 2})) + (is (= #{#{1 2 3} [4 5 6] {7 8} 9 10} + #{10 9 [4 5 6] {7 8} #{1 2 3}})) + (is (not (= #{nil [] {} 0 #{}} #{}))) + (is (= (count #{nil [] {} 0 #{}}) 5)) + (is (= (conj #{1} 1) #{1})) + (is (= (conj #{1} 2) #{2 1})) + (is (= #{} (-empty #{1 2 3 4}))) + (is (= (reduce + #{1 2 3 4 5}) 15)) + (is (= 4 (get #{1 2 3 4} 4))) + (is (contains? #{1 2 3 4} 4)) + (is (contains? #{[] nil 0 {} #{}} {})) + (is (contains? #{[1 2 3]} [1 2 3])) + (is (not (contains? (-disjoin #{1 2 3} 3) 3))) + (is (= #{1 2 3} (disj #{1 2 3}))) + (is (= #{1 2} (disj #{1 2 3} 3))) + (is (= #{1} (disj #{1 2 3} 2 3))) + (is (nil? (disj nil :foo))))) + +(defspec integerrange-equals-range 100 + (prop/for-all [start gen/int + end gen/int + step gen/s-pos-int] + (= (Range. nil start end step nil nil nil) + (IntegerRange. nil start end step nil nil nil)))) + +(deftest test-range + (testing "Testing Range" + ;; Range + (is (= (range 0 10 3) (list 0 3 6 9))) + (is (= (range 2.5) '(0 1 2))) + (is (= (count (range 0 10 3)) 4)) + (is (= (range 0 -10 -3) (list 0 -3 -6 -9))) + (is (= (count (range 0 -10 -3)) 4)) + (is (= (range -10 10 3) (list -10 -7 -4 -1 2 5 8))) + (is (= (count (range -10 10 3)) 7)) + (is (= (range 0 1 1) (list 0))) + (is (= (range 0 -3 -1) (list 0 -1 -2))) + (is (= (range 3 0 -1) (list 3 2 1))) + (is (= (range 0 10 -1) (list))) + (is (= (take 3 (range 0 1 0)) (list 0 0 0))) + (is (= (range 10 0 1) (list))) + (is (= (range 0 0 0) (list))) + (is (= (count (range 0 10 -1)) 0)) + (is (= (count (take 3 (range 0 2 0))) 3)) + (is (= (count (range 10 0 1)) 0)) + (is (= (count (range 0 0 0)) 0)) + (is (= (take 3 (range 1 0 0)) (list 1 1 1))) + (is (= (take 3 (range 3 1 0)) (list 3 3 3))) + (is (not (counted? (range)))) + (is (counted? (range 0 10 1))) + (is (not (counted? (range 0.1 10 1)))) + ;; no chunked seqs in :lite-mode + (when-not ^boolean LITE_MODE + (is (chunked-seq? (range 0 10 1))) + (is (chunked-seq? (range 0.1 10 1)))) + (is (= (range 0.5 8 1.2) '(0.5 1.7 2.9 4.1 5.3 6.5 7.7))) + (is (= (range 0.5 -4 -2) '(0.5 -1.5 -3.5))) + (testing "IDrop" + (is (satisfies? IDrop (range 10))) + (is (= [5 6 7 8 9] (drop 5 (range 10)))) + (is (satisfies? IDrop (drop 5 (range 10)))) + (is (= [8 9] (drop 3 (drop 5 (range 10)))))) + (is (= (reduce + (range 0 100)) 4950)) + (is (= (reduce + 0 (range 0 100)) 4950)) + (is (= (reduce + (range 0.1 100)) 4960)) + (is (= (reduce + 0 (range 0.1 100)) 4960)) + (is (= (reduce + (map inc (range 0 100 1))) 5050)) + (is (= (reduce + 0 (map inc (range 0 100 1))) 5050)) + (is (= (reduce + (map inc (range 0 100 0.1))) 51051)) + (is (= (reduce + 0 (map inc (range 0 100 0.1))) 51051)) + (is (= (reduce + (range 0 3.1 0.1)) 46.500000000000014)) + (is (= (reduce + 0 (range 0 3.1 0.1)) 46.500000000000014)) + (is (= (reduce + (range 0 3.2 0.1)) 49.600000000000016)) + (is (= (reduce + 0 (range 0 3.2 0.1)) 49.600000000000016)) + (is (= (reduce + (range 0 3.3 0.1)) 52.80000000000002)) + (is (= (reduce + 0 (range 0 3.3 0.1)) 52.80000000000002)) + (is (= (reduce + (range 0 -3.1 -0.1)) -46.500000000000014)) + (is (= (reduce + 0 (range 0 -3.1 -0.1)) -46.500000000000014)) + (is (= (reduce + (range 0 -3.2 -0.1)) -49.600000000000016)) + (is (= (reduce + 0 (range 0 -3.2 -0.1)) -49.600000000000016)) + (is (= (reduce + (range 0 -3.3 -0.1)) -52.80000000000002)) + (is (= (reduce + 0 (range 0 -3.3 -0.1)) -52.80000000000002)) + (is (= (reduce + (map inc (range 0 3.1 0.1))) 77.50000000000001)) + (is (= (reduce + 0 (map inc (range 0 3.1 0.1))) 77.50000000000001)) + (is (= (reduce + (map inc (range 0 3.2 0.1))) 81.60000000000002)) + (is (= (reduce + 0 (map inc (range 0 3.2 0.1))) 81.60000000000002)) + (is (= (reduce + (map inc (range 0 3.3 0.1))) 85.80000000000003)) + (is (= (reduce + 0 (map inc (range 0 3.3 0.1))) 85.80000000000003)) + (is (= (reduce + (map inc (range 0 -3.1 -0.1))) -15.500000000000012)) + (is (= (reduce + 0 (map inc (range 0 -3.1 -0.1))) -15.500000000000012)) + (is (= (reduce + (map inc (range 0 -3.2 -0.1))) -17.600000000000016)) + (is (= (reduce + 0 (map inc (range 0 -3.2 -0.1))) -17.600000000000016)) + (is (= (reduce + (map inc (range 0 -3.3 -0.1))) -19.80000000000002)) + (is (= (reduce + 0 (map inc (range 0 -3.3 -0.1))) -19.80000000000002)) + )) + +(deftest test-cycle + (testing "Testing Cycle" + + (is (= "(1 2 3 1 2 ...)" (binding [*print-length* 5] (pr-str (cycle [1 2 3]))))) + + (are [x y] (= x y) + (cycle []) () + (cycle nil) () + + (take 3 (cycle [1])) '(1 1 1) + (take 5 (cycle [1 2 3])) '(1 2 3 1 2) + + (take 3 (cycle [nil])) '(nil nil nil) + + (transduce (take 5) + (cycle [1])) 5 + (transduce (take 5) + 2 (cycle [1])) 7 + (transduce (take 5) + (cycle [3 7])) 23 + (transduce (take 5) + 2 (cycle [3 7])) 25 + + (take 2 (cycle (map #(/ 42 %) '(2 1 0)))) '(21 42) + (first (cycle [1 2 3])) 1 + (first (rest (cycle [1 2 3]))) 2 + (first (next (cycle [1 2 3]))) 2 + (first (conj (cycle [1 2 3]) :hi)) :hi + (empty (cycle [1 2 3])) () + (first (next (cycle (map #(/ 42 %) '(2 1 0))))) 42 + (into [] (take 2) (cycle (map #(/ 42 %) '(2 1 0)))) '(21 42) + + (first (seq (cycle [1 2 3]))) 1) + + ; indexOf fns work on the finite cycle + (is (= -1 (.indexOf (cycle []) 19))) + (is (= -1 (.indexOf (cycle []) 19 2))) + (is (= -1 (.lastIndexOf (cycle []) 19))) + (is (= -1 (.lastIndexOf (cycle []) 19 2))) + + (is (= {:a 1} (meta (with-meta (cycle [1 2 3]) {:a 1})))) + (is (nil? (meta (empty (with-meta (cycle [1 2 3]) {:a 1}))))) + (is (= (take 7 (with-meta (cycle [1 2 3]) {:a 1})) (take 7 (cycle [1 2 3])))) + + (is (realized? (cycle [1 2 3]))) + + (are [x y] (= (transduce (take x) conj (cycle [1 2 3])) y) + 0 [] + 1 [1] + 2 [1 2] + 3 [1 2 3] + 4 [1 2 3 1] + 5 [1 2 3 1 2] + 6 [1 2 3 1 2 3] + 7 [1 2 3 1 2 3 1]) + + (are [x y] (= (transduce (take x) conj [:x] (cycle [1 2 3])) y) + 0 [:x] + 1 [:x 1] + 2 [:x 1 2] + 3 [:x 1 2 3] + 4 [:x 1 2 3 1] + 5 [:x 1 2 3 1 2] + 6 [:x 1 2 3 1 2 3] + 7 [:x 1 2 3 1 2 3 1]))) + +(deftest test-repeat + (testing "Testing Repeat" + (is (= (repeat 5 :x) (repeat 5 :x))) + (is (= (repeat 5 :x) '(:x :x :x :x :x))) + (is (= (hash (repeat 5 :x)) (hash '(:x :x :x :x :x)))) + (is (= (assoc (array-map (repeat 1 :x) :y) '(:x) :z) {'(:x) :z})) + (is (= (assoc (hash-map (repeat 1 :x) :y) '(:x) :z) {'(:x) :z})) + + (is (= "(7 7 7 ...)" (binding [*print-length* 3] (pr-str (repeat 7))))) + (is (= "(7 7 7)" (pr-str (repeat 3 7)))) + + (are [x y] (= x y) + (take 0 (repeat 7)) () + (take 1 (repeat 7)) '(7) + (take 2 (repeat 7)) '(7 7) + (take 5 (repeat 7)) '(7 7 7 7 7)) + + ; limited sequence + (are [x y] (= x y) + (repeat 0 7) () + (repeat 1 7) '(7) + (repeat 2 7) '(7 7) + (repeat 5 7) '(7 7 7 7 7) + + (repeat -1 7) () + (repeat -3 7) ()) + + ;; counts + (are [x y] (= (count x) y) + (repeat 0 7) 0 + (repeat 1 7) 1 + (repeat 2 7) 2 + (repeat 5 7) 5 + + (repeat -1 7) 0 + (repeat -3 7) 0) + + ; test different data types + (are [x] (= (repeat 3 x) (list x x x)) + nil + false true + 0 42 + 0.0 3.14 + 0M 1M + \c + "" "abc" + 'sym + :kw + () '(1 2) + [] [1 2] + {} {:a 1 :b 2} + #{} #{1 2}) + + ; indexOf / lastIndexOf work on finite repeats + (is (= -1 (.indexOf (repeat 7 5) 19))) + (is (= -1 (.indexOf (repeat 7 5) 19 2))) + (is (= -1 (.lastIndexOf (repeat 7 5) 19))) + (is (= -1 (.lastIndexOf (repeat 7 5) 19 2))) + (is (= 0 (.indexOf (repeat 7 5) 5))) + (is (= 6 (.lastIndexOf (repeat 7 5) 5))) + (is (= 3 (.indexOf (repeat 7 5) 5 3))) + (is (= 3 (.lastIndexOf (repeat 7 5) 5 3))) + + (is (= {:a 1} (meta (with-meta (repeat 5 7) {:a 1})))) + (is (nil? (meta (empty (with-meta (repeat 5 7) {:a 1}))))) + (is (= (with-meta (repeat 5 7) {:a 1}) (repeat 5 7))) + + (is (not (realized? (repeat 5 7)))) + + (is (= [1 1] (into [] (drop 98 (repeat 100 1))))) + (is (= [1 1] (into [] (drop 98) (repeat 100 1)))) + (is (= [1 1] (into [] (take 2 (drop 98 (repeat 1)))))) + + (is (= [1] (drop 0 (repeat 1 1)))) + (is (= '(:a) (drop 1 (repeat 2 :a)))) + (is (= () (drop 2 (repeat 2 :a)))) + (is (= () (drop 3 (repeat 2 :a)))) + + (testing "IDrop" + (is (satisfies? IDrop (repeat 10 0))) + (is (= [0 0 0 0 0] (drop 5 (repeat 10 0)))) + (is (satisfies? IDrop (drop 5 (repeat 10 0)))) + (is (= [0 0] (drop 3 (drop 5 (repeat 10 0)))))) + + (is (= () (empty (repeat 100 1)))) + (is (= () (empty (repeat 7)))) + + (are [x y] (= (transduce (take x) conj (repeat 1)) y) + 0 [] + 1 [1] + 2 [1 1] + 3 [1 1 1]) + + (are [x y] (= (transduce (take x) conj [:x] (repeat 1)) y) + 0 [:x] + 1 [:x 1] + 2 [:x 1 1] + 3 [:x 1 1 1]) + + (are [x y] (= (transduce (take x) conj (repeat 2 1)) y) + 0 [] + 1 [1] + 2 [1 1] + 3 [1 1]) + + (are [x y] (= (transduce (take x) conj [:x] (repeat 2 1)) y) + 0 [:x] + 1 [:x 1] + 2 [:x 1 1] + 3 [:x 1 1]))) + +(deftest test-string + (testing "IDrop" + (is (satisfies? IDrop (seq "aaaaaaaaaa"))) + (is (= [\a \a \a \a \a] (drop 5 "aaaaaaaaaa"))) + (is (= [\a \a \a \a \a] (drop 5 (seq "aaaaaaaaaa")))) + (is (not (satisfies? IDrop (drop 5 "aaaaaaaaaa")))) + (is (satisfies? IDrop (drop 5 (seq "aaaaaaaaaa")))) + (is (= [\a \a] (drop 3 (drop 5 "aaaaaaaaaa")))) + (is (= [\a \a] (drop 3 (drop 5 (seq "aaaaaaaaaa"))))))) + +(deftest test-iterate + (testing "Testing Iterate" + (are [x y] (= x y) + (take 0 (iterate inc 0)) () + (take 1 (iterate inc 0)) '(0) + (take 2 (iterate inc 0)) '(0 1) + (take 5 (iterate inc 0)) '(0 1 2 3 4)) + + (is (= "(0 1 2 ...)" (binding [*print-length* 3] (pr-str (iterate inc 0))))) + + (is (not (realized? (rest (iterate inc 0))))) + + (is (= {:a 1} (meta (with-meta (iterate inc 0) {:a 1})))) + (is (nil? (meta (empty (with-meta (iterate inc 0) {:a 1}))))) + (is (= (take 20 (with-meta (iterate inc 0) {:a 1})) (take 20 (iterate inc 0)))) + + (is (= [:first 0 1] (take 3 (conj (iterate inc 0) :first)))) + + (is (= () (empty (iterate inc 0)))) + + (let [v (iterate inc 0)] + (is (identical? v (seq v)))) + + (is (= 0 (first (iterate inc 0)))) + (is (= 1 (first (rest (iterate inc 0))))) + (is (= 1 (first (next (iterate inc 0))))) + + ;; test other fns + (is (= '(:foo 42 :foo 42) (take 4 (iterate #(if (= % :foo) 42 :foo) :foo)))) + (is (= '(1 false true true) (take 4 (iterate boolean? 1)))) + (is (= '(256 128 64 32 16 8 4 2 1 0) (take 10 (iterate #(quot % 2) 256)))) + + ;; reduce via transduce + (is (= (transduce (take 5) + (iterate #(* 2 %) 2)) 62)) + (is (= (transduce (take 5) + 1 (iterate #(* 2 %) 2)) 63)) + + (are [x y] (= (transduce (take x) conj (iterate inc 0)) y) + 0 [] + 1 [0] + 2 [0 1] + 3 [0 1 2]) + + (are [x y] (= (transduce (take x) conj [:x] (iterate inc 0)) y) + 0 [:x] + 1 [:x 0] + 2 [:x 0 1] + 3 [:x 0 1 2]))) + +(deftest test-split-at + (is (vector? (split-at 2 []))) + (is (vector? (split-at 2 [1 2 3]))) + + (are [x y] (= x y) + (split-at 2 []) [() ()] + (split-at 2 [1 2 3 4 5]) [(list 1 2) (list 3 4 5)] + + (split-at 5 [1 2 3]) [(list 1 2 3) ()] + (split-at 0 [1 2 3]) [() (list 1 2 3)] + (split-at -1 [1 2 3]) [() (list 1 2 3)] + (split-at -5 [1 2 3]) [() (list 1 2 3)] )) + +(deftest test-splitv-at + (is (vector? (splitv-at 2 []))) + (is (vector? (first (splitv-at 2 [])))) + (is (vector? (splitv-at 2 [1 2 3]))) + (is (vector? (first (splitv-at 2 [1 2 3]))))) + +(defspec splitv-at-equals-split-at 100 + (prop/for-all [n gen/nat + coll (gen/vector gen/nat)] + (= (splitv-at n coll) (split-at n coll)))) + +(deftest test-rseq + (testing "Testing RSeq" + (is (= '(3 2 1) (reverse (seq (array 1 2 3))))) + (is (= '(3 2 1) (reverse [1 2 3]))) + (is (= '(4 3 2 1) (cons 4 (reverse [1 2 3])))) + (is (= 6 (reduce + (reverse [1 2 3])))) + (is (= '(4 3 2) (map inc (reverse [1 2 3])))) + (is (= '(4 2) (filter even? (reverse [1 2 3 4])))) + )) + +(deftest test-sorted-map + (testing "Testing sorted maps" + ;; PersistentTreeMap + (let [m1 (sorted-map) + c2 (comp - compare) + m2 (sorted-map-by c2)] + (testing "basic ops 1" + (is (identical? cljs.core.PersistentTreeMap (type m1))) + (is (identical? cljs.core.PersistentTreeMap (type m2))) + (is (identical? compare (.-comp m1))) + (is (zero? (count m1))) + (is (zero? (count m2))) + (is (nil? (rseq m1))) + (let [m1 (assoc m1 :foo 1 :bar 2 :quux 3) + m2 (assoc m2 :foo 1 :bar 2 :quux 3)] + (testing "basic ops 2" + (is (= (count m1) 3)) + (is (= (count m2) 3)) + (is (= (seq m1) (list [:bar 2] [:foo 1] [:quux 3]))) + (is (= (seq m2) (list [:quux 3] [:foo 1] [:bar 2]))) + (is (= (seq m1) (rseq m2))) + (is (= (seq m2) (rseq m1))) + (is (= (conj m1 [:wibble 4]) {:foo 1 :bar 2 :quux 3 :wibble 4})) + (is (= (count (conj m1 [:wibble 4])) 4)) + (is (= (conj m2 [:wibble 4]) {:foo 1 :bar 2 :quux 3 :wibble 4})) + (is (= (count (conj m2 [:wibble 4])) 4)) + (is (= (map key (assoc m1 nil 4)) (list nil :bar :foo :quux))) + (is (= (map key (assoc m2 nil 4)) (list :quux :foo :bar nil))))))) + (let [m (->> [[0 10] [20 30] [10 20] [50 60] [30 40] [40 50]] + (mapcat (partial apply range)) + (mapcat #(list % %)) + (apply sorted-map)) + s1 (map #(vector % %) (range 60)) + s2 (map #(vector % %) (range 59 -1 -1))] + (testing "edge cases 1" + (is (= (count m) 60)) + (is (= (seq m) s1)) + (is (= (rseq m) s2)))) + (let [m (sorted-map :foo 1 :bar 2 :quux 3)] + (testing "edge cases 2" + (is (= (dissoc m :foo) (hash-map :bar 2 :quux 3))) + (is (= (count (dissoc m :foo)) 2)) + (is (= (hash m) (hash (hash-map :foo 1 :bar 2 :quux 3)))) + (is (= (subseq m < :foo) (list [:bar 2]))) + (is (= (subseq m <= :foo) (list [:bar 2] [:foo 1]))) + (is (= (subseq m > :foo) (list [:quux 3]))) + (is (= (subseq m >= :foo) (list [:foo 1] [:quux 3]))) + (is (= (map #(reduce (fn [_ x] x) %) m) (list 2 1 3))) + (is (= (map #(reduce (fn [x _] x) 7 %) m) (list 7 7 7))))) + )) + +(deftest test-sorted-sets + (let [s1 (sorted-set) + c2 (comp - compare) + s2 (sorted-set-by c2) + c3 #(compare (quot %1 2) (quot %2 2)) + s3 (sorted-set-by c3) + s4 (sorted-set-by <)] + (testing "Testing sorted set" + (is (identical? cljs.core.PersistentTreeSet (type s1))) + (is (identical? cljs.core.PersistentTreeSet (type s2))) + (is (identical? compare (-comparator s1))) + (is (zero? (count s1))) + (is (zero? (count s2))) + (is (nil? (rseq s1))) + (let [s1 (conj s1 1 2 3) + s2 (conj s2 1 2 3) + s3 (conj s3 1 2 3 7 8 9) + s4 (conj s4 1 2 3)] + (testing "basic ops" + (is (= (hash s1) (hash s2))) + (is (= (hash s1) (hash #{1 2 3}))) + (is (= (seq s1) (list 1 2 3))) + (is (= (rseq s1) (list 3 2 1))) + (is (= (seq s2) (list 3 2 1))) + (is (= (rseq s2) (list 1 2 3))) + (is (= (count s1) 3)) + (is (= (count s2) 3)) + (is (= (count s3) 4)) + (is (= (get s3 0) 1)) + (is (= (subseq s3 > 5) (list 7 8))) + (is (= (subseq s3 > 6) (list 8))) + (is (= (subseq s3 >= 6) (list 7 8))) + (is (= (subseq s3 >= 12) nil)) + (is (= (subseq s3 < 0) (list))) + (is (= (subseq s3 < 5) (list 1 2))) + (is (= (subseq s3 < 6) (list 1 2))) + (is (= (subseq s3 <= 6) (list 1 2 7))) + (is (= (subseq s3 >= 2 <= 6) (list 2 7))) + (is (= (subseq s4 >= 2 < 3) (list 2))) + (let [s1 (disj s1 2) + s2 (disj s2 2)] + (testing "edge cases" + (is (= (seq s1) (list 1 3))) + (is (= (rseq s1) (list 3 1))) + (is (= (seq s2) (list 3 1))) + (is (= (rseq s2) (list 1 3))) + (is (= (count s1) 2)) + (is (= (count s2) 2))))))))) + +(deftest test-lazy-seq-realized? + (testing "Testing LazySeq IPending" + (let [xs (lazy-seq + (cons 1 + (lazy-seq + (cons 2 + (lazy-seq (cons 3 nil))))))] + (is (not (realized? xs))) + (is (not (realized? (rest xs)))) + (is (realized? xs)) + (is (not (realized? (nthrest xs 2)))) + (is (realized? (rest xs)))))) + +(deftest test-784 + (testing "Testing CLJS-784, conj on maps" + (doseq [m [(array-map) (hash-map) (sorted-map)]] + (is (= :ok + (try + (conj m "foo") + (catch js/Error _ + :ok)))) + (is (= {:foo 1} (conj m [:foo 1]))) + (is (= {:foo 1} (conj m {:foo 1}))) + (is (= {:foo 1} (conj m (list [:foo 1]))))) + (doseq [mt [array-map hash-map sorted-map]] + (is (= {:foo 1 :bar 2 :baz 3} + (conj (mt :foo 1) + ((fn make-seq [from-seq] + ;; this tests specifically for user defined seq's, that implement the bare minimum, i.e. no INext + (when (seq from-seq) + (reify + ISeqable + (-seq [this] this) + ISeq + (-first [this] (first from-seq)) + (-rest [this] (make-seq (rest from-seq)))))) + [[:bar 2] [:baz 3]])))))) + ) + +(deftest test-849 + (let [xs [44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24]] + (testing "Testing CLJS-849, transient contains?" + (is (loop [m (transient (zipmap xs (repeat 1))) + xs xs] + (if-let [x (first xs)] + (if (contains? m x) + (recur (dissoc! m x) (next xs)) + false) + true)))))) + +(deftest test-large-array-map + (let [m (array-map 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11 11 12 12 13 13 14 14 15 15)] + (testing "Testing large array maps" + (is (instance? cljs.core/PersistentArrayMap m)) + (is (= (seq m) [[0 0] [1 1] [2 2] [3 3] [4 4] [5 5] [6 6] [7 7] [8 8] [9 9] [10 10] [11 11] [12 12] [13 13] [14 14] [15 15]]))))) + +(def test-map + {:a 1 + :b 2 + #inst "2013-12-19T05:00:00.000-00:00" 3 + :d 4 + :e 5 + #inst "2013-12-06T05:00:00.000-00:00" 6 + :g 7 + :h 8 + :i 9 + :j 10}) + +(deftest test-716 + (testing "Testing CLJS-716, date as keys in maps" + (is (= (test-map #inst "2013-12-19T05:00:00.000-00:00") 3)) + (is (= (test-map #inst "2013-12-06T05:00:00.000-00:00") 6)))) + +(deftest test-transient-edge-case-1 + (let [v1 (vec (range 15 48)) + v2 (vec (range 40 57)) + v1 (persistent! (assoc! (conj! (pop! (transient v1)) :foo) 0 :quux)) + v2 (persistent! (assoc! (conj! (transient v2) :bar) 0 :quux)) + v (into v1 v2)] + (is (= v (vec (concat [:quux] (range 16 47) [:foo] + [:quux] (range 41 57) [:bar])))))) + +(deftest test-transient-edge-case-2 + (is (loop [v (transient []) + xs (range 100)] + (if-let [x (first xs)] + (recur + (condp #(%1 (mod %2 3)) x + #{0 2} (conj! v x) + #{1} (assoc! v (count v) x)) + (next xs)) + (= (vec (range 100)) (persistent! v)))))) + +(deftest test-phm + ;; PersistentHashMap & TransientHashMap + (loop [m1 cljs.core.PersistentHashMap.EMPTY + m2 (transient cljs.core.PersistentHashMap.EMPTY) + i 0] + (if (< i 100) + (recur (assoc m1 i i) (assoc! m2 i i) (inc i)) + (let [m2 (persistent! m2)] + (is (= (count m1) 100)) + (is (= (count m2) 100)) + (is (= m1 m2)) + (loop [i 0] + (if (< i 100) + (do (is (= (m1 i) i)) + (is (= (m2 i) i)) + (is (= (get m1 i) i)) + (is (= (get m2 i) i)) + (is (contains? m1 i)) + (is (contains? m2 i)) + (recur (inc i))))) + (is (= (map vector (range 100) (range 100)) (sort-by first (seq m1)))) + (is (= (map vector (range 100) (range 100)) (sort-by first (seq m2)))) + (is (not (contains? (dissoc m1 3) 3)))))) + (let [tm (-> (->> (interleave (range 10) (range 10)) + (apply assoc cljs.core.PersistentHashMap.EMPTY)) + (dissoc 3 5 7) + transient)] + (doseq [k [0 1 2 4 6 8 9]] + (is (= k (get tm k)))) + (let [m (persistent! tm)] + (is (= 2 (try (dissoc! tm 1) 1 (catch js/Error e 2)))) + (is (= 2 (try (assoc! tm 10 10) 1 (catch js/Error e 2)))) + (is (= 2 (try (persistent! tm) 1 (catch js/Error e 2)))) + (is (= 2 (try (count tm) 1 (catch js/Error e 2)))) + (is (= m {0 0 1 1 2 2 4 4 6 6 8 8 9 9})))) + (let [m (-> (->> (interleave (range 10) (range 10)) + (apply assoc cljs.core.PersistentHashMap.EMPTY)) + (dissoc 3 5 7))] + (testing "Testing PHM dissoc" + (is (= (count m) 7)) + (is (= m {0 0 1 1 2 2 4 4 6 6 8 8 9 9})))) + (let [m (-> (->> (interleave (range 10) (range 10)) + (apply assoc cljs.core.PersistentHashMap.EMPTY)) + (conj [:foo 1]))] + (testing "Testing PHM conj" + (is (= (count m) 11)) + (is (= m {0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 :foo 1})))) + (let [m (-> (->> (interleave (range 10) (range 10)) + (apply assoc cljs.core.PersistentHashMap.EMPTY) + transient) + (conj! [:foo 1]) + persistent!)] + (testing "Testing PHM conj!" + (is (= (count m) 11)) + (is (= m {0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 :foo 1})))) + (let [tm (->> (interleave (range 10) (range 10)) + (apply assoc cljs.core.PersistentHashMap.EMPTY) + transient)] + (testing "Testing transient PHM" + (is (loop [tm tm ks [3 5 7]] + (if-let [k (first ks)] + (recur (dissoc! tm k) (next ks)) + (let [m (persistent! tm)] + (and (= (count m) 7) + (= m {0 0 1 1 2 2 4 4 6 6 8 8 9 9})))))))) + ) + +(deftype FixedHash [h v] + IHash + (-hash [this] h) + IEquiv + (-equiv [this other] + (and (instance? FixedHash other) (= v (.-v other))))) + +(def fixed-hash-foo (FixedHash. 0 :foo)) +(def fixed-hash-bar (FixedHash. 0 :bar)) + +(deftest test-phm-fixed-hash + (let [m (assoc cljs.core.PersistentHashMap.EMPTY + fixed-hash-foo 1 + fixed-hash-bar 2)] + (is (= (get m fixed-hash-foo) 1)) + (is (= (get m fixed-hash-bar) 2)) + (is (= (count m) 2)) + (let [m (dissoc m fixed-hash-foo)] + (is (= (get m fixed-hash-bar) 2)) + (is (not (contains? m fixed-hash-foo))) + (is (= (count m) 1)))) + + (let [m (into cljs.core.PersistentHashMap.EMPTY ; make sure we're testing + (zipmap (range 100) (range 100))) ; the correct map type + m (assoc m fixed-hash-foo 1 fixed-hash-bar 2)] + (is (= (count m) 102)) + (is (= (get m fixed-hash-foo) 1)) + (is (= (get m fixed-hash-bar) 2)) + (let [m (dissoc m 3 5 7 fixed-hash-foo)] + (is (= (get m fixed-hash-bar) 2)) + (is (not (contains? m fixed-hash-foo))) + (is (= (count m) 98)))) + + (let [m (into cljs.core.PersistentHashMap.EMPTY ; make sure we're testing + (zipmap (range 100) (range 100))) ; the correct map type + m (transient m) + m (assoc! m fixed-hash-foo 1) + m (assoc! m fixed-hash-bar 2) + m (persistent! m)] + (is (= (count m) 102)) + (is (= (get m fixed-hash-foo) 1)) + (is (= (get m fixed-hash-bar) 2)) + (let [m (dissoc m 3 5 7 fixed-hash-foo)] + (is (= (get m fixed-hash-bar) 2)) + (is (not (contains? m fixed-hash-foo))) + (is (= (count m) 98))))) + +(def array-map-conversion-threshold + cljs.core.PersistentArrayMap.HASHMAP_THRESHOLD) + +(deftest test-pam + (let [m (-> (->> (interleave (range 10) (range 10)) + (apply assoc cljs.core.PersistentArrayMap.EMPTY)) + (dissoc 3 5 7))] + (is (= (count m) 7)) + (is (= m {0 0 1 1 2 2 4 4 6 6 8 8 9 9}))) + (let [m (-> (->> (interleave (range 10) (range 10)) + (apply assoc cljs.core.PersistentArrayMap.EMPTY)) + (conj [:foo 1]))] + (is (= (count m) 11)) + (is (= m {0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 :foo 1}))) + (let [m (-> (->> (interleave (range 10) (range 10)) + (apply assoc cljs.core.PersistentArrayMap.EMPTY) + transient) + (conj! [:foo 1]) + persistent!)] + (is (= (count m) 11)) + (is (= m {0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 :foo 1}))) + (let [tm (->> (interleave (range 10) (range 10)) + (apply assoc cljs.core.PersistentArrayMap.EMPTY) + transient)] + (loop [tm tm ks [3 5 7]] + (if-let [k (first ks)] + (recur (dissoc! tm k) (next ks)) + (let [m (persistent! tm)] + (is (= (count m) 7)) + (is (= m {0 0 1 1 2 2 4 4 6 6 8 8 9 9})))))) + (let [tm (-> (->> (interleave (range 10) (range 10)) + (apply assoc cljs.core.PersistentArrayMap.EMPTY)) + (dissoc 3 5 7) + transient)] + (doseq [k [0 1 2 4 6 8 9]] + (is (= k (get tm k)))) + (let [m (persistent! tm)] + (is (= 2 (try (dissoc! tm 1) 1 (catch js/Error e 2)))) + (is (= 2 (try (assoc! tm 10 10) 1 (catch js/Error e 2)))) + (is (= 2 (try (persistent! tm) 1 (catch js/Error e 2)))) + (is (= 2 (try (count tm) 1 (catch js/Error e 2)))) + (is (= m {0 0 1 1 2 2 4 4 6 6 8 8 9 9})))) + (let [m (apply assoc cljs.core.PersistentArrayMap.EMPTY + (interleave (range (* 2 array-map-conversion-threshold)) + (range (* 2 array-map-conversion-threshold))))] + (is (= (count m) (* 2 array-map-conversion-threshold))) + (is (= (m array-map-conversion-threshold) array-map-conversion-threshold)) + (is (= m (into cljs.core.PersistentHashMap.EMPTY + (map #(vector % %) + (range (* 2 array-map-conversion-threshold))))))) + ) + +(deftest test-literal-maps + (loop [m1 {} m2 {} i 0] + (if (< i 100) + (recur (assoc m1 i i) (assoc m2 (str "foo" i) i) (inc i)) + (do (is (= m1 (into cljs.core.PersistentHashMap.EMPTY + (map vector (range 100) (range 100))))) + (is (= m2 (into cljs.core.PersistentHashMap.EMPTY + (map vector + (map (partial str "foo") (range 100)) + (range 100))))) + (is (= (count m1) 100)) + (is (= (count m2) 100))))) + ) + +(deftest test-461 + ;; CLJS-461: automatic map conversions + (loop [i 0 m (with-meta {-1 :quux} {:foo :bar}) result []] + (if (<= i (+ cljs.core.PersistentArrayMap.HASHMAP_THRESHOLD 2)) + (recur (inc i) (assoc m i i) (conj result (meta m))) + (let [n (inc (+ cljs.core.PersistentArrayMap.HASHMAP_THRESHOLD 2)) + expected (repeat n {:foo :bar})] + (is (= result expected)))))) + +(deftest test-transient-hash-set + ;; TransientHashSet + (loop [s (transient #{}) + i 0] + (if (< i 100) + (recur (conj! s i) (inc i)) + (loop [s s i 0] + (if (< i 100) + (if (zero? (mod i 3)) + (recur (disj! s i) (inc i)) + (recur s (inc i))) + (let [s (persistent! s)] + (is (= s (loop [s #{} xs (remove #(zero? (mod % 3)) (range 100))] + (if-let [x (first xs)] + (recur (conj s x) (next xs)) + s)))) + (is (= s (set (remove #(zero? (mod % 3)) (range 100)))))))))) + ) + +(deftest test-cljs-1189 + (testing "array-map should always return array maps" + (let [am (apply array-map (range 100))] + (is (== (count am) 50)) + (is (instance? PersistentArrayMap am))))) + +(deftest test-cljs-1199 + (testing "array-map should skip dropped elements of IndexedSeq" + (is (= {:a 1} (apply array-map (drop 1 [0 :a 1])))))) + +(deftest test-cljs-1212 + (is (= (set {:a 0 :b 0 :c 0 :d 0 :e 0 :f 0 :g 0 :h 0 :i 0}) + #{[:a 0] [:b 0] [:c 0] [:d 0] [:e 0] [:f 0] [:g 0] [:h 0] [:i 0]}))) + +(deftest test-count-hash-set + (is + (== (count (hash-set [1 4] [2 4] [3 4] [0 3] [1 3] [2 3] [3 3] + [0 2] [1 2] [2 2] [3 2] [4 2] [0 1] [1 1] + [2 1] [3 1] [1 0] [2 0] [3 0])) + (count (list [1 4] [2 4] [3 4] [0 3] [1 3] [2 3] [3 3] + [0 2] [1 2] [2 2] [3 2] [4 2] [0 1] [1 1] + [2 1] [3 1] [1 0] [2 0] [3 0]))))) + +(deftest test-734 + (testing "Testing CLJS-734, transient operations" + (is (= (-> (transient []) (conj! 1 2) persistent!) [1 2])) + (is (= (-> (transient #{1 2 3}) (disj! 1 2) persistent!) #{3})) + (is (= (-> (transient {}) (assoc! :a 1 :b 2) persistent!) {:a 1 :b 2})) + (is (= (-> (transient {:a 1 :b 2 :c 3}) (dissoc! :a :b) persistent!) {:c 3})))) + +(deftest test-vec + (let [v (vec #js [1 2 3 4])] + (is (= (count v) 4)) + (is (= (first v) 1)) + (is (= (last v) 4)) + (is (= v [1 2 3 4])))) + +(deftest test-phm-from-array + (let [m (.fromArray PersistentHashMap #js [1 2 3 4] true)] + (is (= (count m) 2)) + (is (contains? m 1)) + (is (contains? m 3)) + (is (= (get m 1) 2)) + (is (= (get m 3) 4)) + (is (= m {1 2 3 4})))) + +(deftest test-cljs-1809 + (is (= (into) [])) + (is (= (into [1 2]) [1 2]))) + +(deftest test-cljs-1951 + (is (= () (interleave))) + (is (= '(1 2 3) (interleave [1 2 3])))) + +(deftest test-cljs-1497 + (testing "PersistentArrayMap" + (let [metadata {:a 1} + k [1 2 3] + v 1 + map (array-map (with-meta k metadata) v) + [k' v'] (find map k)] + (is (= k k')) + (is (= v v')) + (is (= metadata (meta k'))))) + (testing "PersistentHashMap" + (let [metadata {:a 1} + k [1 2 3] + v 1 + map (hash-map (with-meta k metadata) v) + [k' v'] (find map k)] + (is (= k k')) + (is (= v v')) + (is (= metadata (meta k')))) + (let [map (hash-map nil :foo)] + (is (= (find map nil) [nil :foo]))) + (let [metadata {:a 1} + k [1 2 3] + v 1 + map (hash-map (with-meta k metadata) v nil 2) + [k' v'] (find map k)] + (is (= k k')) + (is (= v v')) + (is (= metadata (meta k'))))) + (testing "PersistentTreeMap" + (let [metadata {:a 1} + k [1 2 3] + v 1 + map (sorted-map (with-meta k metadata) v) + [k' v'] (find map k)] + (is (= k k')) + (is (= v v')) + (is (= metadata (meta k')))) + (let [map (sorted-map nil :foo)] + (is (= (find map nil) [nil :foo]))))) + +(deftest cljs-2460 + (is (= "[:a 1]" (pr-str (->MapEntry :a 1 nil)))) + (binding [*print-length* 1] + (is (= "[:a ...]" (pr-str (->MapEntry :a 1 nil)))))) + +(deftype CustomVectorThing [v] + ;; Subvec expects its argument to implement IVector. + ;; Note, that this method is never actually called. + IVector + (-assoc-n [coll i val] nil) + + IIndexed + (-nth [coll i] (nth v i)) + (-nth [coll i not-found] (nth v i not-found)) + + ICounted + (-count [coll] (count v))) + +(deftest test-cljs-2128 + (testing "Subvec iteration" + (when-not ^boolean LITE_MODE + (testing "Subvec over PersistentVector uses RangedIterator" + (is (instance? RangedIterator (-iterator (subvec [0 1 2 3] 1 3)))))) + (testing "Subvec over other vectors uses naive SeqIter" + (is (instance? SeqIter (-iterator (subvec (->CustomVectorThing [0 1 2 3]) 1 3)))))) + (testing "Subvec reduce" + (testing "Subvec over PersistentVector reduces as expected" + (is (= [1 2] (reduce conj [] (subvec [0 1 2 3] 1 3))))) + (testing "Subvec over other vectors reduces as expected" + (is (= [1 2] (reduce conj [] (subvec (->CustomVectorThing [0 1 2 3]) 1 3))))))) + +(deftest test-cljs-2145 + (testing "PersistentHashMap -find implementation" + (is (= (find (hash-map) :a) nil)) + (is (= (find (hash-map :a 1) :a) [:a 1])) + (is (= (find (hash-map :a false) :a) [:a false])) + (is (= (find (zipmap (range 1000) (repeat :foo)) 999) [999 :foo])) + (is (= (find (zipmap (range 1000) (repeat :foo)) 1000) nil)))) + +(deftest test-cljs-2452 + (is (= (reverse []) ()))) + +(deftest test-cljs-2462 + (is (= 1 (count (subvec [1 2] 0 1.5)))) + (is (= [1 2 3] (subvec [0 1 2 3 4 5] 1.2 4.7)))) + +(deftest test-cljs-2478 + (is (not (map-entry? [:a 1]))) + (is (= {:a 1 :b 2 :c 3} (into (hash-map :a 1) [[:b 2] [:c 3]]))) + (is (= {:a 1 :b 2 :c 3} (into (hash-map :a 1) {:b 2 :c 3}))) + (is (= {:a 1 :b 2 :c 3} (into (hash-map :a 1) (seq {:b 2 :c 3})))) + (is (= {:a 1 :b 2 :c 3} (into (array-map :a 1) [[:b 2] [:c 3]]))) + (is (= {:a 1 :b 2 :c 3} (into (array-map :a 1) {:b 2 :c 3}))) + (is (= {:a 1 :b 2 :c 3} (into (array-map :a 1) (seq {:b 2 :c 3}))))) + +(deftest test-cljs-1743 + (testing "TransientArrayMap as an invokable function" + (let [tam (transient (array-map :a 1 :b 2))] + (is (= (tam :a) 1)) + (is (= (tam :a :not-found) 1)) + (is (= (tam :x) nil)) + (is (= (tam :x :not-found) :not-found)))) + (testing "TransientHashMap as an invokable function" + (let [thm (transient (hash-map :a 1 :b 2))] + (is (= (thm :a) 1)) + (is (= (thm :a :not-found) 1)) + (is (= (thm :x) nil)) + (is (= (thm :x :not-found) :not-found))))) + +(deftest test-cljs-2456 + (testing "Maps" + (testing "PersistentArrayMap" + (let [pam (array-map :a 1 :b 2 :c 3)] + (is (map-entry? (first pam))) + (is (every? map-entry? pam)) + (is (map-entry? (find pam :a))) + (is (map-entry? (.next (-iterator pam)))))) + (testing "PersistentHashMap" + (let [phm (hash-map :a 1 :b 2 :c 3)] + (is (map-entry? (first phm))) + (is (every? map-entry? phm)) + (is (map-entry? (find phm :a))) + (is (map-entry? (.next (-iterator phm))))))) + (testing "Vectors" + (testing "PersistentVector" + (is (map-entry? (find [0 1 2] 0)))) + (testing "MapEntry" + (is (map-entry? (find (MapEntry. :key :val nil) 0)))))) + +(deftest test-cljs-2474 + (let [rand-seq (fn rand-seq [] (lazy-seq (cons (rand) (rand-seq)))) + xs (rand-seq) + ys (with-meta xs {:foo 1})] + (is (not (realized? xs))) + (is (not (realized? ys))) + (is (= (take 3 xs) (take 3 ys)))) + (let [xs (lazy-seq) + ys (with-meta xs {:foo 1})] + (is (not (realized? xs))) + (is (not (realized? ys))) + (is (= () xs ys)))) + +(deftest test-cljs-2736 + (let [s #{(with-meta [:a] {:n 42})}] + (is (= {:n 42} (meta (s [:a])))))) + +(deftest test-cljs-2442 + (testing "set ctor" + (let [coll #{1 2}] + (is (identical? coll (set coll)))) + (is (= #{1 2} (set #{1 2}))) + (is (nil? (meta (set ^:a #{1 2}))))) + (testing "vec ctor" + (let [coll [1 2]] + (is (identical? coll (vec coll)))) + (is (= [1 2] (vec [1 2]))) + (is (nil? (meta (vec ^:a [1 2])))) + (let [coll (vec (first {:a 1}))] + (is (vector? coll)) + (is (not (map-entry? coll))) + (is (= [:a 1] coll))))) + +(deftest test-cljs-2798 + (is (nil? (let [b (chunk-buffer 1)] + (chunk-append b 0) + (next (chunk-cons (chunk b) nil)))))) + +(deftest test-cljs-3124 + ;; Doesn't work under :lite-mode because there are not + ;; separate transient types for now + (when-not ^boolean LITE_MODE + (let [t (assoc! (transient []) 0 1)] + (persistent! t) + (is (= :fail (try (get t :a :not-found) (catch js/Error e :fail))))))) + +(deftest test-cljs-3317 + (testing "persistent vector invoke matches clojure" + (is (thrown-with-msg? js/Error #"Key must be integer" ([1 2] nil))))) + +(deftest test-cljs-3324 + (testing "hash-map behavior with missing values matches clojure" + (is (thrown-with-msg? js/Error #"No value supplied for key: :a" + (hash-map :a))) + (is (thrown-with-msg? js/Error #"No value supplied for key: :a" + (apply hash-map [:a]))))) + +(deftest test-cljs-3393 + (is (= '(0 2 4) (take 3 (filter even? (range 100000000)))))) + +#_(deftest test-cljs-3420-lazy-seq-caching-bug + (testing "LazySeq should realize seq once" + (let [a (atom 0) + x (eduction (map (fn [_] (swap! a inc))) [nil]) + l (lazy-seq x)] + (dotimes [_ 10] + (is (= [1] l)))))) + +(deftest test-cljs-3240-overflow-regress + (let [things (zipmap (range 15000) (repeat 0))] + (is (zero? (count (filter #(-> % key string?) things)))))) + +(deftest test-obj-map + (let [a (obj-map)] + (is (empty? a)) + (is (zero? (count a)))) + (let [b (obj-map :a 1)] + (is (not (empty? b))) + (is (== 1 (count b)))) + (let [c (obj-map :a 1 :b 2 :c 3)] + (is (== 3 (count c))) + (is (= 1 (get c :a))) + (is (= 1 (:a c))) + (is (every? keyword? (keys c))) + (is (= (set [:a :b :c]) (set (keys c))))) + (is (= (obj-map :a 1 :b 2 :c 3) + (obj-map :a 1 :b 2 :c 3))) + (is (= (obj-map :a 1 :b 2) + (into (obj-map) [[:a 1] [:b 2]]))) + (is (= (merge-with + + (obj-map :a 1 :b 2) + (obj-map :a 1 :b 2)) + (into (obj-map) [[:a 2] [:b 4]]))) + (is (= (transient (obj-map :a 1 :b 2)) + (obj-map :a 1 :b 2)))) + +(deftest test-hash-map-lite + (let [a (hash-map-lite)] + (is (empty? a)) + (is (zero? (count a)))) + (let [b (hash-map-lite :a 1)] + (is (not (empty? b))) + (is (== 1 (count b)))) + (let [c (hash-map-lite :a 1 :b 2 :c 3)] + (is (== 3 (count c))) + (is (= 1 (get c :a))) + (is (= 1 (:a c))) + (is (every? keyword? (keys c))) + (is (= (set [:a :b :c]) (set (keys c))))) + (is (= (hash-map-lite :a 1 :b 2 :c 3) + (hash-map-lite :a 1 :b 2 :c 3))) + (is (= (hash-map-lite :a 1 :b 2) + (into (hash-map-lite) [[:a 1] [:b 2]]))) + (is (= (merge-with + + (hash-map-lite :a 1 :b 2) + (hash-map-lite :a 1 :b 2)) + (into (hash-map-lite) [[:a 2] [:b 4]]))) + (is (= (transient (hash-map-lite :a 1 :b 2)) + (hash-map-lite :a 1 :b 2)))) + +(deftest test-set-lite + (is (= #{1 2 3} #{1 2 3})) + (is (= 3 (count #{1 2 3}))) + (let [x #{1 2 3}] + (is (every? #(contains? x %) [1 2 3]))) + (is (= (set-lite [[3 4] [1 2] [5 6]]) + (into #{} [[3 4] [1 2] [5 6]])))) + +(comment + + (run-tests) + + ) diff --git a/src/test/cljs/cljs/core_test.cljs b/src/test/cljs/cljs/core_test.cljs new file mode 100644 index 0000000000..01f6f0b527 --- /dev/null +++ b/src/test/cljs/cljs/core_test.cljs @@ -0,0 +1,2014 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.core-test + (:refer-global :only [Object String]) + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is are]] + [clojure.test.check :as tc] + [clojure.test.check.clojure-test :refer-macros [defspec]] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as prop :include-macros true] + [clojure.string :as s] + [clojure.set :as set] + [goog.object :as gobject])) + +(deftest test-metadata + (testing "Testing metadata" + (is (= {"x" "y"} (meta ^{"x" "y"} []))) + )) + +(deftest test-fn-with-metadata + (let [f (fn [x] (* x 2)) + m {:foo "bar"} + mf (with-meta f m)] + (testing "Testing functions with metadata" + (is (nil? (meta f))) + (is (fn? mf)) + (is (= 4 (mf 2))) + (is (= 4 (apply mf [2]))) + (is (= (meta mf) m))))) + +(deftest test-atoms-and-volatile + (let [a (atom 0)] + (testing "Testing basic atom operations" + (is (= 0 (deref a))) + (is (= 1 (swap! a inc))) + (is (= false (compare-and-set! a 0 42))) + (is (= true (compare-and-set! a 1 7))) + (is (nil? (meta a))) + (is (nil? (get-validator a))))) + (let [a (atom 0)] + (testing "Testing swap!" + (is (= 1 (swap! a + 1))) + (is (= 4 (swap! a + 1 2))) + (is (= 10 (swap! a + 1 2 3))) + (is (= 20 (swap! a + 1 2 3 4))))) + (let [a (atom [1] :validator coll? :meta {:a 1})] + (testing "Testing atom validators" + (is (= coll? (get-validator a))) + (is (thrown? js/Error (reset! a 1))) + (is (thrown? js/Error (set-validator! a number?))) + (is (some? (get-validator a))) + (set-validator! a nil) + (is (nil? (get-validator a))) + (let [e1 (ex-info "" {})] + (try + (set-validator! a (fn [_] (throw e1))) + (catch :default e2 + (is (identical? e1 e2))))) + (is (= {:a 1} (meta a))) + (alter-meta! a assoc :b 2) + (is (= {:a 1 :b 2} (meta a))))) + (let [v (volatile! 1)] + (testing "Testing volatile" + (is (volatile? v)) + (is (not (volatile? (atom 1)))) + (is (= 2 (vreset! v 2))) + (is (= 3 (vswap! v inc))) + (is (= 3 @v))))) + +(deftest test-contains? + (testing "Testing contains?" + (is (contains? {:a 1 :b 2} :a)) + (is (not (contains? {:a 1 :b 2} :z))) + (is (contains? [5 6 7] 1)) + (is (contains? [5 6 7] 2)) + (is (not (contains? [5 6 7] 3))) + (is (contains? (to-array [5 6 7]) 1)) + (is (contains? (to-array [5 6 7]) 2)) + (is (not (contains? (to-array [5 6 7]) 3))) + (is (not (contains? nil 42))) + (is (contains? "f" 0)) + (is (not (contains? "f" 55)))) + + (testing "Testing contains? with IAssociative protocol" + (let [ds (reify + IAssociative + (-contains-key? [_ k] (= k :valid)))] + (is (contains? ds :valid)) + (is (not (contains? ds :invalid)))))) + +(deftest test-run! + (testing "Testing run!" + (let [a (atom 0)] + (run! (fn [n] + (swap! a + n)) + (range 5)) + (is (= 10 @a))) + (is (nil? (run! identity [1]))) + (is (nil? (run! reduced (range)))))) + +(deftest test-in-operations + (testing "Testing update-in" + (is (= {:foo {:bar {:baz 1}}} + (update-in {:foo {:bar {:baz 0}}} [:foo :bar :baz] inc))) + (is (= {:foo 1 :bar 2 :baz 10} + (update-in {:foo 1 :bar 2 :baz 3} [:baz] + 7))) + (is (= [{:foo 1, :bar 2} {:foo 1, :bar 3}] + (update-in [{:foo 1 :bar 2}, {:foo 1 :bar 2}] [1 :bar] inc))) + (is (= [{:foo {:bar 2}} {:foo {:bar 3}}] + (update-in [{:foo {:bar 2}}, {:foo {:bar 2}}] [1 :foo :bar] inc)))) + (testing "Testing assoc-in" + (is (= {:foo {:bar {:baz 100}}} + (assoc-in {:foo {:bar {:baz 0}}} [:foo :bar :baz] 100))) + (is (= {:foo 1 :bar 2 :baz 100} + (assoc-in {:foo 1 :bar 2 :baz 3} [:baz] 100))) + (is (= [{:foo [{:bar 2} {:baz 3}]} {:foo [{:bar 2} {:baz 100}]}] + (assoc-in [{:foo [{:bar 2} {:baz 3}]}, {:foo [{:bar 2} {:baz 3}]}] + [1 :foo 1 :baz] 100))) + (is (= [{:foo 1, :bar 2} {:foo 1, :bar 100}] + (assoc-in [{:foo 1 :bar 2}, {:foo 1 :bar 2}] [1 :bar] 100)))) + (testing "Testing get-in" + (is (= 1 (get-in {:foo 1 :bar 2} [:foo]))) + (is (= 2 (get-in {:foo {:bar 2}} [:foo :bar]))) + (is (= 1 (get-in [{:foo 1}, {:foo 2}] [0 :foo]))) + (let [v (reduced 42)] + (is (= v (get-in {:foo v} [:foo])))) + (is (= 4 (get-in [{:foo 1 :bar [{:baz 1}, {:buzz 2}]}, {:foo 3 :bar [{:baz 3}, {:buzz 4}]}] + [1 :bar 1 :buzz])))) + ) + +(deftest test-js-clj-conversions + (testing "Testing JS / CLJS data conversions" + (testing "js->clj" + (is (= {"a" 1, "b" 2} (js->clj (js* "{\"a\":1,\"b\":2}")))) + (is (= {"a" nil} (js->clj (js* "{\"a\":null}")))) + (is (= {} (js->clj (js* "{}")))) + (is (= {"a" true, "b" false} (js->clj (js* "{\"a\":true,\"b\":false}")))) + (is (= {:a 1, :b 2} (js->clj (js* "{\"a\":1,\"b\":2}") :keywordize-keys true))) + (is (= [[{:a 1, :b 2} {:a 1, :b 2}]] + (js->clj (js* "[[{\"a\":1,\"b\":2}, {\"a\":1,\"b\":2}]]") :keywordize-keys true))) + (is (= [[{:a 1, :b 2} {:a 1, :b 2}]] + (js->clj [[{:a 1, :b 2} {:a 1, :b 2}]]))) + (is (= (js->clj nil) nil)) + (let [map-entry (->MapEntry #js {:foo 1} #js [1 2] nil)] + (is (= (->MapEntry {"foo" 1} [1 2] nil) (js->clj map-entry))))) + (testing "clj->js" + (is (= (clj->js 'a) "a")) + (is (= (clj->js :a) "a")) + (is (= (clj->js "a") "a")) + (is (= (clj->js 1) 1)) + (is (= (clj->js nil) (js* "null"))) + (is (= (clj->js true) (js* "true"))) + (is (goog/typeOf "array" (clj->js []))) + (is (goog/typeOf "array" (clj->js #{}))) + (is (goog/typeOf "array" (clj->js '()))) + (is (goog/isObject (clj->js {}))) + (is (= (gobject/get (clj->js {:a 1}) "a") 1)) + (is (= (-> (clj->js {:a {:b {{:k :ey} :d}}}) + (gobject/get "a") + (gobject/get "b") + (gobject/get "{:k :ey}")) + "d"))) + (is (= (-> (clj->js {:foo/bar "a"}) + (gobject/get "bar")) + "a")) + (is (= (-> (clj->js {:foo/bar "a"} :keyword-fn namespace) + (gobject/get "foo")) + "a")))) + +(deftest test-delay + (let [a (atom 0) + d (delay (swap! a inc))] + (testing "Testing delay" + (is (false? (realized? d))) + (is (zero? @a)) ;; delay hasn't triggered yet + (is (= 1 @d)) ;; trigger it + (is (= 1 @a)) ;; make sure side effect has happened + (is (true? (realized? d))) + (is (= 1 @d)) ;; body doesn't happen again + (is (= 1 @a)) ;; atom hasn't changed either + (is (= (force d) @d)) + (is (= 1 (force 1))))) ;; you can safely force non-delays + ) + +(deftest test-hierarchy + (testing "Testing hierarchy operations" + (derive ::rect ::shape) + (derive ::square ::rect) + (is (= #{:cljs.core-test/shape} (parents ::rect))) + (is (= #{:cljs.core-test/rect :cljs.core-test/shape} (ancestors ::square))) + (is (= #{:cljs.core-test/rect :cljs.core-test/square} (descendants ::shape))) + (is (true? (isa? 42 42))) + (is (true? (isa? ::square ::shape))) + (derive ObjMap ::collection) + (derive cljs.core.PersistentHashSet ::collection) + (is (true? (isa? ObjMap ::collection))) + (is (true? (isa? cljs.core.PersistentHashSet ::collection))) + (is (false? (isa? cljs.core.IndexedSeq ::collection))) + (isa? js/String js/Object) + (is (true? (isa? [::square ::rect] [::shape ::shape]))) + ;; ?? (ancestors java.util.ArrayList) + ;; ?? isa? based dispatch tests + )) + +(defmulti bar (fn [x y] [x y])) +(defmethod bar [::rect ::shape] [x y] :rect-shape) +(defmethod bar [::shape ::rect] [x y] :shape-rect) + +;;(bar ::rect ::rect) +;; -> java.lang.IllegalArgumentException: +;; Multiple methods match dispatch value: +;; [:cljs.core-test/rect :cljs.core-test/rect] -> [:cljs.core-test/rect :cljs.core-test/shape] +;; and [:cljs.core-test/shape :cljs.core-test/rect], +;; and neither is preferred + +(deftest test-multimethods-1 + (testing "Testing basic multimethod usage" + (is (zero? (count (prefers bar)))) + (prefer-method bar [::rect ::shape] [::shape ::rect]) + (is (= 1 (count (prefers bar)))) + (is (= :rect-shape (bar ::rect ::rect))) + (is (= :rect-shape (apply (-get-method bar [::rect ::shape]) [::rect ::shape]))) + )) + +(defmulti nested-dispatch (fn [m] (-> m :a :b))) +(defmethod nested-dispatch :c [m] :nested-a) +(defmethod nested-dispatch :default [m] :nested-default) + +(defmulti nested-dispatch2 ffirst) +(defmethod nested-dispatch2 :a [m] :nested-a) +(defmethod nested-dispatch2 :default [m] :nested-default) + +(defmulti foo1 (fn [& args] (first args))) +(defmethod foo1 :a [& args] :a-return) +(defmethod foo1 :default [& args] :default-return) + +(defmulti area :Shape) +(defn rect [wd ht] {:Shape :Rect :wd wd :ht ht}) +(defn circle [radius] {:Shape :Circle :radius radius}) +(defmethod area :Rect [r] + (* (:wd r) (:ht r))) +(defmethod area :Circle [c] + (* Math/PI (* (:radius c) (:radius c)))) +(defmethod area :default [x] :oops) +(defmulti foo2 (fn [])) +(defmethod foo2 :default [] :foo) + +(defmulti apply-multi-test (fn ([_] 0) ([_ _] 0) ([_ _ _] 0))) +(defmethod apply-multi-test 0 + ([x] :one) + ([x y] :two) + ([x y & r] [:three r])) + +;; CLJS-469, helpful exception message on bad dispatch +(defmulti no-dispatch-value :test) + +;; custom hierarchy +(def my-map-hierarchy + (atom (-> (make-hierarchy) + (derive (type (obj-map)) ::map) + (derive (type (array-map)) ::map) + (derive (type (hash-map)) ::map) + (derive (type (sorted-map)) ::map)))) + +(defmulti my-map? type :hierarchy my-map-hierarchy) +(defmethod my-map? ::map [_] true) +(defmethod my-map? :default [_] false) + +(defmulti foo2' identity) +(defmethod foo2' 0 [x] x) + +(def three-levels-h (-> (make-hierarchy) + (derive :parent :gparent) + (derive :child :parent))) + +(defmulti multi-with-h (fn [v] v) :hierarchy #'three-levels-h) +(defmethod multi-with-h :gparent [_] :gparent) +(defmethod multi-with-h :parent [_] :parent) + +(deftest test-multimethods-2 + (let [r (rect 4 13) + c (circle 12)] + (testing "Testing multimethod edge cases" + (is (= :nested-a (nested-dispatch {:a {:b :c}}))) + (is (= :nested-a (nested-dispatch2 [[:a :b]]))) + (is (= :a-return (foo1 :a))) + (is (= :default-return (foo1 1))) + (is (= 52 (area r))) + (is (= :oops (area {}))) + ;; CLJS-863 + (is (= :foo (foo2))) + ;; remove method tests + (is (= 2 (count (methods bar)))) + (remove-method bar [::rect ::shape]) + (is (= 1 (count (methods bar)))) + (remove-all-methods bar) + (is (zero? (count (methods bar)))) + (is (= [:three '(2)] (apply apply-multi-test [0 1 2]))) + (is (try + (no-dispatch-value {:test :test}) + (catch js/Error e + (not= -1 (.indexOf (.-message e) "cljs.core-test/no-dispatch-value"))))) + (doseq [m [(obj-map) (array-map) (hash-map) (sorted-map)]] + (is (my-map? m))) + (doseq [not-m [[] 1 "asdf" :foo]] + (is (not (my-map? not-m)))) + ;; multimethod hashing + (is (= foo2' (ffirst {foo2' 1}))) + (is (= :parent (multi-with-h :child))) +))) + +(def tmph (make-hierarchy)) +(defmulti fooz (fn [a b] (keyword b)) :hierarchy #'tmph) +(defmethod fooz :a [a b] a) +(defmethod fooz :b [a b] b) +(prefer-method fooz :a :b) + +(deftest test-cljs-3367-backward-conflict-prefers + (testing "CLJS-3367: Verify no backward conflict in prefer-method" + (is (some? (prefer-method fooz :a :b))))) + +(deftest test-transducers + (testing "Testing transducers" + (is (= (sequence (map inc) (array 1 2 3)) '(2 3 4))) + (is (= (apply str (sequence (map #(.toUpperCase %)) "foo")) "FOO")) + (is (== (hash [1 2 3]) (hash (sequence (map inc) (range 3))))) + (is (= [1 2 3] (sequence (map inc) (range 3)))) + (is (= (sequence (map inc) (range 3)) [1 2 3])) + (is (= (sequence (remove even?) (range 10)) '(1 3 5 7 9))) + (is (= (sequence (take 5) (range 10)) + '(0 1 2 3 4))) + (is (= (sequence (take-while #(< % 5)) (range 10)) + '(0 1 2 3 4))) + (is (= (sequence (drop 5) (range 10)) + '(5 6 7 8 9))) + (is (= (sequence (drop-while #(< % 5)) (range 10)) + '(5 6 7 8 9))) + (is (= (sequence (take-nth 2) (range 10)) + '(0 2 4 6 8))) + (is (= (sequence (replace {:foo :bar}) '(:foo 1 :foo 2)) + '(:bar 1 :bar 2))) + (let [ret (into [] (map inc) (range 3))] + (is (and (vector? ret) (= ret '(1 2 3))))) + (let [ret (into [] (filter even?) (range 10))] + (is (and (vector? ret) (= ret '(0 2 4 6 8))))) + (is (= (map inc (sequence (map inc) (range 3))) + '(2 3 4))) + (is (= (sequence (dedupe) [1 1 2 2 3 3]) + '(1 2 3))) + (is (= (mapcat reverse [[3 2 1 0] [6 5 4] [9 8 7]]) + (range 10))) + (is (= (sequence (mapcat reverse) [[3 2 1 0] [6 5 4] [9 8 7]]) + (range 10))) + (is (= (seq (eduction (map inc) [1 2 3])) '(2 3 4))) + (is (= (seq (eduction (map inc) (map inc) [1 2 3])) '(3 4 5))) + (is (= (sequence (partition-by #{:split}) [1 2 3 :split 4 5 6]) + '([1 2 3] [:split] [4 5 6]))) + (is (= (sequence (partition-all 3) '(1 2 3 4 5)) + '([1 2 3] [4 5]))) + (is (= (sequence (keep identity) [1 nil 2 nil 3]) + '(1 2 3))) + (is (= (keep-indexed identity [:foo nil :bar nil :baz]) + (sequence (keep-indexed identity) [:foo nil :bar nil :baz]))) + (let [xform (comp (map inc) + (filter even?) + (dedupe) + (mapcat range) + (partition-all 3) + (partition-by #(< (apply + %) 7)) + (mapcat flatten) + (random-sample 1.0) + (take-nth 1) + (keep #(when (odd? %) (* % %))) + (keep-indexed #(when (even? %1) (* %1 %2))) + (replace {2 "two" 6 "six" 18 "eighteen"}) + (take 11) + (take-while #(not= 300 %)) + (drop 1) + (drop-while string?) + (remove string?)) + data (vec (interleave (range 18) (range 20)))] + (is (= (sequence xform data) '(36 200 10)))) + (let [xf (map #(+ %1 %2))] + (is (= (sequence xf [0 0] [1 2]) [1 2]))) + (is (= (-> (sequence (map inc) [1 2 3]) + (with-meta {:a 1}) + meta) {:a 1})) + (let [xf (fn [rf] + (fn + ([] (rf)) + ([result] (rf result :foo)) + ([result input] (rf result input))))] + (is (= (sequence xf [1 2 3]) [1 2 3 :foo])))) + (testing "CLJS-2258" + (is (= ["1"] (sequence (map str) (eduction [1])))))) + +(deftest test-into+halt-when + (is (= :anomaly (into [] (comp (filter some?) (halt-when #{:anomaly})) + [1 2 3 :anomaly 4]))) + (is (= {:anomaly :oh-no!, + :partial-results [1 2]} + (into [] + (halt-when :anomaly #(assoc %2 :partial-results %1)) + [1 2 {:anomaly :oh-no!} 3 4])))) + +(deftest test-reader-literals + (testing "Testing reader literals" + (is (= #queue [1] (into cljs.core.PersistentQueue.EMPTY [1]))) + (is (not= #queue [1 2] (into cljs.core.PersistentQueue.EMPTY [1]))) + (is (= #inst "2010-11-12T18:14:15.666-00:00" + #inst "2010-11-12T13:14:15.666-05:00")) + (is (= #uuid "550e8400-e29b-41d4-a716-446655440000" + #uuid "550e8400-e29b-41d4-a716-446655440000")) + (is (= 42 + (get {#uuid "550e8400-e29b-41d4-a716-446655440000" 42} + #uuid "550e8400-e29b-41d4-a716-446655440000"))) + )) + +(deftest test-uuid + (testing "Testing UUID" + (is (= (cljs.core/uuid "550e8400-e29b-41d4-a716-446655440000") + (cljs.core/uuid "550e8400-e29b-41d4-a716-446655440000"))) + (is (not (identical? (cljs.core/uuid "550e8400-e29b-41d4-a716-446655440000") + (cljs.core/uuid "550e8400-e29b-41d4-a716-446655440000")))) + (is (= 42 (get {(cljs.core/uuid "550e8400-e29b-41d4-a716-446655440000") 42} + (cljs.core/uuid "550e8400-e29b-41d4-a716-446655440000") + :not-at-all-found))) + (is (= :not-at-all-found + (get {(cljs.core/uuid "550e8400-e29b-41d4-a716-446655440000") 42} + (cljs.core/uuid "666e8400-e29b-41d4-a716-446655440000") + :not-at-all-found))) + (is (= -1 (compare (cljs.core/uuid "550e8400-e29b-41d4-a716-446655440000") + (cljs.core/uuid "666e8400-e29b-41d4-a716-446655440000")))) + (is (= 1 (compare (cljs.core/uuid "550e8400-e29b-41d4-a716-446655440000") + (cljs.core/uuid "550e8400-a29b-41d4-a716-446655440000")))) + (is (= 0 (compare (cljs.core/uuid "550e8400-e29b-41d4-a716-446655440000") + (cljs.core/uuid "550e8400-e29b-41d4-a716-446655440000"))))) + (testing "UUID hashing" + (let [id "550e8400-e29b-41d4-a716-446655440000" + uuid (cljs.core/uuid id) + expected (hash id)] + (is (= expected (hash uuid))) + (is (= expected (.-__hash uuid)))))) + +(def constantly-nil (constantly nil)) + +(deftest some->test + (is (nil? (some-> nil))) + (is (= 0 (some-> 0))) + (is (= -1 (some-> 1 (- 2)))) + (is (nil? (some-> 1 constantly-nil (- 2))))) + +(deftest some->>test + (is (nil? (some->> nil))) + (is (= 0 (some->> 0))) + (is (= 1 (some->> 1 (- 2)))) + (is (nil? (some->> 1 constantly-nil (- 2))))) + +(deftest cond->test + (is (= 0 (cond-> 0))) + (is (= -1 (cond-> 0 true inc true (- 2)))) + (is (= 0 (cond-> 0 false inc))) + (is (= -1 (cond-> 1 true (- 2) false inc)))) + +(deftest cond->>test + (is (= 0 (cond->> 0))) + (is (= 1 (cond->> 0 true inc true (- 2)))) + (is (= 0 (cond->> 0 false inc))) + (is (= 1 (cond->> 1 true (- 2) false inc)))) + +(deftest as->test + (is (= 0 (as-> 0 x))) + (is (= 1 (as-> 0 x (inc x)))) + (is (= 2 (as-> [0 1] x + (map inc x) + (reverse x) + (first x))))) + +(deftest threading-loop-recur + (is (nil? (loop [] + (as-> 0 x + (when-not (zero? x) + (recur)))))) + (is (nil? (loop [x nil] (some-> x recur)))) + (is (nil? (loop [x nil] (some->> x recur)))) + (is (= 0 (loop [x 0] (cond-> x false recur)))) + (is (= 0 (loop [x 0] (cond->> x false recur))))) + +(defspec boolean-test 10 + (prop/for-all [b gen/boolean] + (boolean? b))) + +(deftest aget-test + (is (= 11 (aget #js [10 11 12] 1))) + (is (= 11 (apply aget [#js [10 11 12] 1]))) + (is (= 3 (aget #js [1 2 #js [3 4]] 2 0))) + (is (= 3 (apply aget [#js [1 2 #js [3 4]] 2 0])))) + +(deftest aset-test + (let [array #js [10 11 12]] + (is (= 13 (aset array 1 13))) + (is (= 13 (aget array 1)))) + (let [array #js [10 11 12]] + (is (= 13 (apply aset [array 1 13]))) + (is (= 13 (aget array 1)))) + (let [array #js [1 2 #js [3 4]]] + (is (= 13 (aset array 2 0 13))) + (is (= 13 (aget array 2 0)))) + (let [array #js [1 2 #js [3 4]]] + (is (= 13 (apply aset [array 2 0 13]))) + (is (= 13 (aget array 2 0))))) + +(deftest unchecked-get-test + (is (= 1 (unchecked-get #js {:a 1} "a"))) + (is (nil? (unchecked-get #js {:a 1} "b"))) + (is (nil? (unchecked-get #js {:a 1} nil)))) + +(deftest js-invoke-test + (let [o (doto (js-obj) (gobject/set "my sum" (fn [a b] (+ a b))))] + (is (= 5 (js-invoke o "my sum" 2 3))))) + +(deftest memfn-test + (let [substr (memfn substr start length)] + (is (= "cde" (substr "abcdefg" 2 3)))) + (let [trim (memfn trim)] + (is (= ["abc" "def"] (map trim [" abc " " def "]))))) + +;; ============================================================================= +;; Tickets + +(deftest test-383 + (testing "Testing CLJS-383" + (let [f1 (fn f1 ([] 0) ([a] 1) ([a b] 2) ([a b c & more] 3)) + f2 (fn f2 ([x] :foo) ([x y & more] (apply f1 y more)))] + (is (= 1 (f2 1 2)))) + (let [f (fn ([]) ([a & more] more))] + (is (nil? (f :foo)))) + (is (nil? (array-seq (array 1) 1)))) ) + +(deftest test-513 + (testing "Testing CLJS-513" + (let [sentinel (js-obj)] + (is (identical? sentinel (try ([] 0) (catch js/Error _ sentinel))))))) + +(defprotocol IFoo (foo [this])) + +(deftest test-reify-meta + (is (= (meta (with-meta (reify IFoo (foo [this] :foo)) {:foo :bar})) + {:foo :bar}))) + +(let [x "original"] + (defn original-closure-stmt [] x)) + +(deftest test-401-411 + (let [x "overwritten"] + (is (= "original" (original-closure-stmt)))) + (is (= "original" (let [x "original" + oce (fn [] x) + x "overwritten"] + (oce))))) + +(deftest test-letfn-shadowing + (letfn [(x [] "original") + (y [] (x))] + (let [x (fn [] "overwritten")] + (is (= "original" (y)))))) + +(deftest test-459 + (is (= (reduce-kv conj [] (sorted-map :foo 1 :bar 2)) + [:bar 2 :foo 1]))) + +(deftest test-kv-reduce + (letfn [(kvr-test [data expect] + (and + (= :reduced + (reduce-kv + (fn [_ _ _] (reduced :reduced)) + [] data)) + (= (sort expect) + (sort + (reduce-kv + (fn [r k v] (-> r (conj [k v]))) + [] data)))))] + (testing "Testing IKVReduce" + (doseq [[data expect] [[(obj-map :k0 :v0 :k1 :v1) [[:k0 :v0] [:k1 :v1]]] + [(hash-map :k0 :v0 :k1 :v1) [[:k0 :v0] [:k1 :v1]]] + [(array-map :k0 :v0 :k1 :v1) [[:k0 :v0] [:k1 :v1]]] + [[:v0 :v1] [[0 :v0] [1 :v1]]]]] + (is (kvr-test data expect))) + (is (= {:init :val} (reduce-kv assoc {:init :val} nil)))))) + +(deftest test-data-conveying-exceptions + (is (= {:foo 1} + (try (throw (ex-info "asdf" {:foo 1})) + (catch ExceptionInfo e + (ex-data e))))) + (is (instance? js/Error (ex-info "asdf" {:foo 1}))) + (is (= (pr-str (ex-info "abc" {:x 1})) "#error {:message \"abc\", :data {:x 1}}")) + (is (= (pr-str (ex-info "abc" {:x 1} "def")) "#error {:message \"abc\", :data {:x 1}, :cause \"def\"}")) + (is (= (.toString (ex-info "abc" {:x 1} "def")) "#error {:message \"abc\", :data {:x 1}, :cause \"def\"}")) + (is (= (str (ex-info "abc" {:x 1} "def")) "#error {:message \"abc\", :data {:x 1}, :cause \"def\"}")) + (is (not (instance? cljs.core.ExceptionInfo (js/Error.))))) + +(deftest test-Throwable->map + (let [msg-0 "message-0" + data-0 {:a 0} + msg-1 "message-1" + data-1 {:b 1} + msg-2 "message-2"] + ;; Check ex-info style error + (let [ex (ex-info msg-0 data-0) + m (Throwable->map ex)] + (is (= msg-0 (:cause m))) + (is (= data-0 (:data m))) + (is (nil? (:trace m))) + (let [via (:via m)] + (is (== 1 (count via))) + ;; Check via 0 + (is (= `ExceptionInfo (:type (nth via 0)))) + (is (= msg-0 (:message (nth via 0)))) + (is (= data-0 (:data (nth via 0)))))) + ;; Check plain js/Error style error + (let [ex (js/Error. msg-0) + m (Throwable->map ex)] + (is (= msg-0 (:cause m))) + (is (nil? (:data m))) + (is (nil? (:trace m))) + (let [via (:via m)] + (is (== 1 (count via))) + ;; Check via 0 + (is (= 'js/Error (:type (nth via 0)))) + (is (= msg-0 (:message (nth via 0)))) + (is (nil? (:data (nth via 0)))))) + ;; Check ex-info style with chain ending in js/Error + (let [ex (ex-info msg-0 data-0 + (ex-info msg-1 data-1 + (js/Error. msg-2))) + m (Throwable->map ex)] + (is (= msg-2 (:cause m))) + (is (nil? (:data m))) + (is (nil? (:trace m))) + (let [via (:via m)] + (is (== 3 (count via))) + ;; Check via 0 + (is (= `ExceptionInfo (:type (nth via 0)))) + (is (= msg-0 (:message (nth via 0)))) + (is (= data-0 (:data (nth via 0)))) + ;; Check via 1 + (is (= `ExceptionInfo (:type (nth via 1)))) + (is (= msg-1 (:message (nth via 1)))) + (is (= data-1 (:data (nth via 1)))) + ;; Check via 2 + (is (= 'js/Error (:type (nth via 2)))) + (is (= msg-2 (:message (nth via 2)))) + (is (nil? (:data (nth via 2)))))))) + +(deftest test-2067 + (is (= 0 (reduce-kv + (fn [x k _] + (when (zero? k) + (reduced k))) + nil (zipmap (range 17) (repeat 0)))))) + +(deftest test-435 + (is (= (assoc {} 154618822656 1 261993005056 1) + {154618822656 1 261993005056 1}))) + +(deftest test-458 + (is (= (get-in {:a {:b 1}} [:a :b :c] :nothing-there) + :nothing-there))) + +(deftest test-464 + (is (nil? (get-in {:foo {:bar 2}} [:foo :bar :baz])))) + +(deftest test-symbol-meta + (is (= (meta (with-meta 'foo {:tag 'int})) {:tag 'int})) + (is (= (meta (quote ^{:bar true} foo)) {:bar true})) + (is (= (meta (quote ^:bar foo)) {:bar true})) + (is (= (meta (first '[^:bar x])) {:bar true}))) + +(deftest test-467 + (is (= (reduce-kv + 0 (apply hash-map (range 1000))) + (reduce + (range 1000))))) + +(deftest test-477 + (is (= [js/undefined 1 2] ((fn [& more] more) js/undefined 1 2))) + (is (= [js/undefined 4 5] ((fn [a b & more] more) 1 2 js/undefined 4 5)))) + +(deftest test-493 + (is (nil? (get 42 :anything))) + (is (= (get 42 :anything :not-found) :not-found)) + (is (nil? (first (map get [42] [:anything])))) + (is (= (first (map get [42] [:anything] [:not-found])) :not-found))) + +(deftest test-481 + (let [fs (atom [])] + (doseq [x (range 4) + :let [y (inc x) + f (fn [] y)]] + (swap! fs conj f)) + (is (= (map #(%) @fs) '(1 2 3 4))))) + +(def exists?-test-val 'foo) + +(deftest test-495 + (testing "Testing CLJS-495, exists?" + (is (false? (exists? js/jQuery))) + (is (exists? exists?-test-val)))) + +(deftest test-2764 + (testing "Testing CLJS-2764, exists? on multi-segment symbols" + (is (false? (exists? this.ns.does.not.exist))) + (is (true? (exists? cljs.core.first))) + (is (true? (exists? cljs.core/first))) + (is (true? (exists? (:foo {:foo 1})))) + (is (false? (exists? (:foo {})))))) + +(deftest test-518 + (is (nil? (:test "test")))) + +(deftest test-541 + (letfn [(f! [x] (print \f) x) + (g! [x] (print \g) x)] + (is (= "ffgfg" + (with-out-str + (instance? Symbol (f! 'foo)) + (max (f! 5) (g! 10)) + (min (f! 5) (g! 10))))))) + +(deftest test-582 + (is (= #{1 2} (set [1 2 2]))) + (is (= #{1 2} (hash-set 1 2 2))) + (is (= #{1 2} (apply hash-set [1 2 2])))) + +(deftest test-ordered-set + (is (= #{1 2} (sorted-set 1 2 2))) + (is (= [1 2 3] (seq (sorted-set 2 3 1)))) + (is (= #{1 2} (apply sorted-set [1 2 2])))) + +(deftest test-3454-conj + (is (= #{1 2 3} (conj #{1 2} 3))) + (is (= #{1 2 3} (conj (sorted-set 1 2) 3))) + (let [s #{1 2} + ss (sorted-set 1 2)] + (is (identical? s (conj s 2))) + (is (identical? ss (conj ss 2))))) + +(deftest test-3454-disj + (is (= #{1 2} (disj #{1 2 3} 3))) + (is (= #{1 2} (disj (sorted-set 1 2 3) 3))) + (let [s #{1 2} + ss (sorted-set 1 2)] + (is (identical? s (disj s 3))) + (is (identical? ss (disj ss 3))))) + +(deftest test-585 + (is (= (last (map identity (into [] (range 32)))) 31)) + (is (= (into #{} (range 32)) + (set (map identity (into [] (range 32))))))) + +(def foo580) +(def foo580 {:a (fn []) :b (fn [] (foo580 :a))}) + +(deftest test-580 + (is (nil? (((:b foo580)))))) + +(deftest test-587 + (is (== (first (filter #(== % 9999) (range))) 9999))) + +(deftest test-604 + (is (= () (concat nil []))) + (is (= () (concat [] [])))) + +(deftest test-600 + (is (= "foobar" (apply str (concat "foo" "bar"))))) + +(deftest test-608 + (is (= '("") (re-seq #"\s*" "")))) + +(deftype KeywordTest [] + ILookup + (-lookup [o k] :nothing) + (-lookup [o k not-found] not-found)) + +(deftest tset-638 + (is (= (:a (KeywordTest.)) :nothing))) + +(deftest test-648 + (let [a (reify IHash (-hash [_] 42)) + b (reify IHash (-hash [_] 42)) + s (set (range 128))] + (testing "Testing CLJS-648 (CLJ-1285)" + (is (= (-> (conj s a b) transient (disj! a) persistent! (conj a)) + (-> (conj s a b) transient (disj! a) persistent! (conj a))))))) + +(deftest test-660 + (testing "Testing CLJS-660, namespace handling" + (is (= (-> 'a.b keyword ((juxt namespace name))) [nil "a.b"])) + (is (= (-> 'a.b/c keyword ((juxt namespace name))) ["a.b" "c"])) + (is (= (-> "a.b" keyword ((juxt namespace name))) [nil "a.b"])) + (is (= (-> "a.b/c" keyword ((juxt namespace name))) ["a.b" "c"])))) + +(deftest test-663 + (testing "Testing CLJS-663, invalid keywords" + (is (= (keyword 123) nil)) + (is (= (keyword (js/Date.)) nil)))) + +(deftest test-647 + (let [keys #(vec (js-keys %)) + z "x"] + (testing "Testing CLJS-647, js-keys" + (assert (= ["x"] + (keys (js-obj "x" "y")) + (keys (js-obj (identity "x") "y")) + (keys (js-obj z "y"))))))) + + +(def some-x 1) +(def some-y 1) + +(deftest test-583 + (is (= (count #{some-x some-y}) 1))) + +(deftest test-584 + (is (= (count {some-x :foo some-y :bar}) 1))) + +(deftest test-725 + (testing "Testing CLJS-725, drop" + (is (= (apply vector (drop-while (partial = 1) [1 2 3])) [2 3])) + (is (= (apply list (drop-while (partial = 1) [1 2 3])) '(2 3))) + (is (= (set (drop 1 #js [1 2 3])) #{2 3})))) + +(deftest test-724 + (is (nil? (first (rest (rest (rest (range 3)))))))) + +(deftest test-730 + (testing "Testing CLJS-730, object? predicate" + (is (true? (object? #js {}))) + (is (false? (object? nil))))) + +(deftest test-767 + (testing "Testing CLJS-767, invalid assoc" + (doseq [n [nil "-1" "" "0" "1" false true (js-obj)]] + (is (= :fail (try (assoc [1 2] n 4) + (catch js/Error e :fail)))) + (is (= :fail (try (assoc (subvec [1 2 3] 2) n 4) + (catch js/Error e :fail)))) + (is (= :fail (try (assoc (range 1 3) n 4) + (catch js/Error e :fail))))))) + +(deftest test-768 + (testing "Testing CLJS-768, invalid assoc!" + (doseq [n [nil "-1" "" "0" "1" false true (js-obj)]] + (is (= :fail (try (assoc! (transient [1 2]) n 4) + (catch js/Error e :fail))))))) + +(defn cljs-739 [arr names] + (let [name (first names)] + (if name + (recur (conj arr (fn [] (println name))) + (rest names)) + arr))) + +(deftest test-739 + (testing "Testing CLJS-739, with-out-str" + (binding [*print-newline* true] + (is (= (with-out-str (doseq [fn (cljs-739 [] [:a :b :c :d])] (fn))) + ":a\n:b\n:c\n:d\n"))))) + +(deftest print-ns-maps + (testing "Testing CLJS-1786, *print-namespace-maps*" + (is (= "#:user{:a 1}" (binding [*print-namespace-maps* true] (pr-str {:user/a 1})))) + (is (= "{:user/a 1}" (binding [*print-namespace-maps* false] (pr-str {:user/a 1})))))) + +(deftest test-728 + (testing "Testing CLJS-728, lookup with default" + (doseq [n [nil "-1" "" "0" "1" false true (js-obj)]] + (is (nil? (get [1 2] n))) + (is (= :fail (try (nth [1 2] n) (catch js/Error e :fail)))) + (is (= 4 (get [1 2] n 4))) + (is (= :fail (try (nth [1 2] n 4) (catch js/Error e :fail)))) + + (is (nil? (get (subvec [1 2] 1) n))) + (is (= :fail (try (nth (subvec [1 2] 1) n) (catch js/Error e :fail)))) + (is (= 4 (get (subvec [1 2] 1) n 4))) + (is (= :fail (try (nth (subvec [1 2] 1) n 4) (catch js/Error e :fail)))) + + (is (nil? (get (transient [1 2]) n))) + (is (= :fail (try (nth (transient [1 2]) n) (catch js/Error e :fail)))) + (is (= 4 (get (transient [1 2]) n 4))) + (is (= :fail (try (nth (transient [1 2]) n 4) (catch js/Error e :fail)))) + + (is (nil? (get (range 1 3) n))) + (is (= :fail (try (nth (range 1 3) n) (catch js/Error e :fail)))) + (is (= 4 (get (range 1 3) n 4))) + (is (= :fail (try (nth (range 1 3) n 4) (catch js/Error e :fail)))))) + ) + +(def cljs-780 (atom {:foo (with-meta [] {:bar '(1 2 3)})})) + +(deftest test-780 + (let [_ (swap! cljs-780 update-in [:foo] vary-meta update-in [:bar] vec) + x (-> @cljs-780 :foo meta :bar)] + (testing "Testing CLJS-780, update-in + vary-meta" + (is (vector? x)) + (is (= x [1 2 3])))) ) + +(deftest test-782 + (testing "Testing CLJS-782, UUID toString" + (is (= (.toString #uuid "550e8400-e29b-41d4-a716-446655440000") + "550e8400-e29b-41d4-a716-446655440000")))) + +(deftest test-case-keyword + (is (= (let [x "a"] (case x :a 1 "a")) "a"))) + +(deftest test-801 + (testing "Testing CLJS-801, str" + (is (= "0atrue:key/wordsymb/olfalse[1 2 3 4]1234.56789" + (str 0 "a" true nil :key/word 'symb/ol false [1 2 3 4] 1234.5678 0x09))))) + +(defn case-recur [value] + (case value + :a (recur :b) + :b 0)) + +(deftest test-812 + (testing "Testing CLJS-812, case with recur" + (is (= (case-recur :a) 0)))) + +(deftest test-816 + (testing "Testing CLJS-816, rename-keys" + (is (= (set/rename-keys {:a "one" :b "two"} {:a :z}) {:z "one" :b "two"})) + (is (= (set/rename-keys {:a "one" :b "two"} {:a :z :c :y}) {:z "one" :b "two"})) + (is (= (set/rename-keys {:a "one" :b "two" :c "three"} {:a :b :b :a}) + {:a "two" :b "one" :c "three"}))) ) + +(deftest test-881 + (testing "Testing CLJS-881, duplicate keys in array maps" + (is (= [:foo] (keys (apply array-map [:foo 1 :foo 2])))))) + +(deftest test-810 + (let [not-strings [true false nil 1 (fn [])]] + (testing "Testing CLJS-810, exception on bad input to regex fns" + (is (every? #(= :failed (try (re-find #"." %) + (catch js/TypeError _ :failed))) not-strings)) + (is (every? #(= :failed (try (re-matches #"." %) + (catch js/TypeError _ :failed))) not-strings)) + (is (every? #(= :failed (try (re-find #"nomatch" %) + (catch js/TypeError _ :failed))) not-strings)) + (is (every? #(= :failed (try (re-matches #"nomatch" %) + (catch js/TypeError _ :failed))) not-strings)) + (is (every? #(= :failed (try (re-seq #"." %) + (catch js/TypeError _ :failed))) not-strings)) + (is (every? #(= :failed (try (re-seq #"nomatch" %) + (catch js/TypeError _ :failed))) not-strings))))) + +(deftest test-853 + (testing "Testing CLJS-853, function metadata" + (is (= {:foo true} (meta ^:foo (fn [])))))) + +(deftest test-807 + (testing "Testing CLJS-807, big int, float, big dec literals" + (is (= -1 -1N)) + (is (= 9.007199254740996E15 9007199254740995N)) + (is (= 1.5 1.5M)) + (is (= 4.9E-324 5E-324M)))) + +(deftest test-921-var-meta-name + (testing "testing CLJS-921, :name var metadata should be unqualified" + (is (= (-> (var first) meta :name) 'first)))) + +(deftype MyWatchable [] + IWatchable + (-notify-watches [this oldval newval]) + (-add-watch [this key f]) + (-remove-watch [this key])) + +(deftest test-920-watch-ops-return-ref + (testing "tesing CLJS-920, add-watch/return-watch should return reference" + (let [w (MyWatchable.)] + (is (identical? (add-watch w :foo (fn [])) w)) + (is (identical? (remove-watch w :foo) w))))) + +(deftype MyCustomAtom [^:mutable state] + IDeref + (-deref [_] state) + IReset + (-reset! [_ newval] + (set! state newval))) + +(deftest test-919-generic-cas + (testing "testing CLJS-919, CAS should on custom atom types" + (let [a0 (MyCustomAtom. 10) + a1 (MyCustomAtom. 0)] + (compare-and-set! a0 0 20) + (compare-and-set! a1 0 20) + (is (== @a0 10)) + (is (== @a1 20))))) + +(deftest test-map-new-transducers + (testing "Test distinct, interpose, map-indexed transducers" + (is (= [1 2 3] + (transduce (distinct) conj [] [1 1 2 2 3 3]))) + (is (= [1 :foo 2 :foo 3] + (transduce (interpose :foo) conj [] [1 2 3]))) + (is (= [[0 1] [1 2] [2 3]] + (transduce (map-indexed (fn [i x] [i x])) conj [] [1 2 3]))))) + +(defn foo-var [f] + (fn [x] + (f x))) + +(defn foo-set [x] + (first x)) + +(deftest test-cljs-982-var-deref + (let [f (foo-var #'foo-set)] + (is (= (f [1 2 3]) 1)) + (set! foo-set (fn [x] :oops)) + (is (= (f [1 2 3]) :oops)))) + +(deftest test-cljs-993 + (is (nil? (binding [*print-level* 4]))) + (is (= (binding [*print-level* 4] *print-level*) 4)) + (is (nil? (try + (binding [*print-level* 4] + (throw (js/Error.))) + (catch js/Error e + *print-level*))))) + +(defn meta-test-fn + "A docstring" + {:foo :bar, :baz 12345, :whatever "String Metadata"} + [a b] + (+ a b)) + +(deftest test-cljs-1046 + (let [m (meta #'meta-test-fn)] + (is (= "A docstring" (:doc m))) + (is (= :bar (:foo m))) + (is (= 12345 (:baz m))) + (is (= "String Metadata" (:whatever m))))) + +(defmulti cljs-1144 identity :default ::default) + +(deftest test-cljs-1144 + (is (not= map (dispatch-fn cljs-1144))) + (is (= identity (dispatch-fn cljs-1144))) + (is (= ::default (default-dispatch-val cljs-1144)))) + +(defn foo-1187 [] (print "foo!")) + +(defn bar-1187 [] (print "bar!")) + +(defn print-foo-1187 [fb] + (apply + (case fb + :foo #'foo-1187 + :bar #'bar-1187) [])) + +(deftest test-var? + (is (var? #'inc)) + (is (not (var? 1)))) + +(deftest test-cljs-1187 + (testing "Internal var nodes analyzed in expression context" + (is (= (with-out-str (print-foo-1187 :foo)) + "foo!")))) + +(deftest test-var-arglists + (is (= (-> #'first meta :arglists) '([coll]))) + (is (= (-> #'hash-map meta :arglists) '([& keyvals]))) + (is (= (-> #'map meta :arglists) + '([f] [f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])))) + +(deftest tagged-literals + (let [tl (tagged-literal 'x "y")] + (is (tagged-literal? tl)) + (is (not (tagged-literal? {:tag 'x :form "y"}))) + (is (= (:tag tl) 'x)) + (is (= (:form tl) "y")) + (is (= tl (tagged-literal 'x "y"))) + (is (not= tl (tagged-literal 'z "y"))) + (is (not= tl (tagged-literal 'x "z"))) + (is (= (hash tl) (hash (tagged-literal 'x "y")))) + (is (= "#foo [1]" (str (tagged-literal 'foo [1])))))) + +(defn- incme [] + (let [incme (fn [a queue & args] (inc a))] + (incme 1 [1] :color "#fff"))) + +(deftest test-cljs-1225 + (is (= (incme) 2))) + +(defn my-conj + [acc x] + (conj acc x)) + +(deftest test-cljs-1209 + (is (= (reduce my-conj [] (eduction (map identity) [1 2 3])) + [1 2 3]))) + +(deftest test-get-with-float + (is (= (get #js [\h \i] 1.7) \i)) + (is (= (get "hi" 1.7) \i))) + +(defn foo-1284 + ([] nil) + ([x0 & xs] [x0 xs])) + +(deftest test-cljs-1284 + (let [xs (IndexedSeq. #js [] 0 nil) + ys (IndexedSeq. #js [1] 3 nil)] + (is (nil? (first xs))) + (is (nil? (seq xs))) + (is (= (rest xs) ())) + (is (= (pr-str xs) "()")) + (is (= (foo-1284 0) [0 nil])) + (is (= (pr-str (foo-1284 0)) "[0 nil]")) + (is (zero? (count ys))) + (is (= (transduce (map inc) conj [] ys) [])))) + +(deftest test-symbol-from-string + (let [x (symbol "js/Array")] + (is (= x 'js/Array)) + (is (= (hash x) (hash 'js/Array))) + (is (= (namespace x) "js")) + (is (= (name x) "Array")))) + +(deftest test-1276 + (is (= #'first #'first)) + (is (not= #'first #'last))) + +(deftest test-1248 + (let [v (vary-meta #'first assoc :foo 'bar)] + (is (= (-> v meta :foo) 'bar)))) + +(deftest test-1210 + (is (= ((fn [] + (let [{:keys [arguments]} {} + arguments (or arguments [])] + arguments))) + []))) + +(deftest test-munge-demunge + (is (= 'cljs.core/first? + (demunge (munge 'cljs.core/first?))))) + +(deftest test-munge + (is (= "a_b" (munge "a-b"))) + (is (= "a_SLASH_b" (munge "a/b"))) + (is (= "_DOT__DOT_" (munge ".."))) + (is (= "abstract$" (munge "abstract"))) + (is (= 'abc (munge 'abc))) + (is (= "toString" (munge "toString"))) + (is (= "function$" (munge "function")))) + +(deftest test-munge-str + (is (= "function" (munge-str "function")))) + +(defprotocol IFooBar + (a-method [t])) + +(deftest test-cljs-1451 + (is (= "foobar" (a-method (reify + IFooBar + (cljs.core-test/a-method [_] "foobar")))))) + +(deftest test-cljs-1569 + (is (= (meta (with-meta (seq [1 2 3]) {:a 1})) {:a 1}))) + +(deftest test-cljs-1420 + (is (= :2-arity + (get-in + (reify + ILookup + (-lookup [o k] :2-arity) + (-lookup [o k not-found] :3-arity)) + [:foo])))) + +(deftest test-cljs-1594 + (is (not (js/isNaN (hash js/Infinity)))) + (is (not (js/isNaN (hash js/-Infinity)))) + (is (not (js/isNaN (hash js/NaN)))) + (is (= (hash-set js/Infinity js/-Infinity 0 1 2 3 4 5 6 7 8) + (set (keys (zipmap [js/Infinity js/-Infinity 0 1 2 3 4 5 6 7 8] (repeat nil))))))) + +(deftest test-cljs-1590 + (is (= [""] (s/split "" #"\n"))) + (is (= [] (s/split "\n\n\n" #"\n"))) + (is (= [""] (s/split-lines ""))) + (is (= [] (s/split-lines "\n\n\n")))) + +(deftest test-reductions-obeys-reduced + (is (= [0 :x] + (reductions (constantly (reduced :x)) + (range)))) + (is (= [:x] + (reductions (fn [acc x] x) + (reduced :x) + (range)))) + (is (= [2 6 12 12] + (reductions (fn [acc x] + (if (= x :stop) + (reduced acc) + (+ acc x))) + [2 4 6 :stop 8 10])))) + +(deftest test-cljs-1721 + (is (= 1 (get-in {:a (array 1 2 3 4)} [:a 0] :not-found))) + (is (= :not-found (get-in {:a (array 1 2 3 4)} [:a 4] :not-found))) + (is (= "d" (get-in {:a "data"} [:a 0] :not-found))) + (is (= :not-found (get-in {:a "data"} [:a 4] :not-found)))) + +(deftest test-cljs-1739 + (is (= (-> {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9} + rest rest rest rest rest rest rest rest rest) + ()))) + +(deftest test-cljs-1744 + (doseq [i (range 1 64)] + (let [m (zipmap (range i) (range i))] + (is (= () (last (take (inc i) (iterate rest m)))))))) + +(def cljs.core-test/foo-1274 42) + +(deftest test-cljs-1274 + (is (= foo-1274 42)) + (is (= cljs.core-test/foo-1274 42))) + +(defrecord CLJS1780 [a b c]) + +(deftest test-cljs-1780 + (let [record (->CLJS1780 1 2 3)] + (is (= (into #{} (sequence (map identity) + record)) + #{[:a 1] [:b 2] [:c 3]})) + (is (= (into #{} (sequence (map identity) + (assoc record :d 4 :e 5)) ) + #{[:a 1] [:b 2] [:c 3] [:d 4] [:e 5]})))) + +(deftest test-cljs-1775 + (is (nil? (get "foo" nil))) + (is (= 42 (get {nil 42} nil) 42)) + (is (= (get #js [\h \i] 1.7 :not-found) \i)) + (is (= (get "hi" 1.7 :not-found) \i))) + +(deftest test-cljs-1748 + (is (thrown? js/Error (nth (array 0 1 2) 3))) + (is (thrown? js/Error (nth (array 0 1 2) -1))) + (is (= (nth (array 0 1 2) 3 :not-found) :not-found)) + (is (= (nth (array 0 1 2) -1 :not-found) :not-found)) + + (is (thrown? js/Error (nth "012" 3))) + (is (thrown? js/Error (nth "012" -1))) + (is (= (nth "012" 3 :not-found) :not-found)) + (is (= (nth "012" -1 :not-found) :not-found))) + +(let [foo-1536 2] + (def foo-1536 foo-1536)) + +(let [foo-1536-2 1] + (defn foo-1536-2 [] + foo-1536-2)) + +(deftest test-cljs-1536 + (is (= foo-1536 2)) + (is (= (foo-1536-2) 1)) + ;; these two lines generate a `:redef-in-file` warning, which is caused by `cljs.test/is` + (is (= ((let [z 1] (defn z [] z))) 1)) + (is (= (let [w 1] ((defn w [] w))) 1))) + +(deftest test-cljs-1837 + (testing "halt-when transducer" + (is (= (transduce (halt-when #{1}) conj [] [5 4 1 2 3]) + 1)) + (is (= (transduce (halt-when #{1} (fn [ret input] input)) conj [] [5 4 1 2 3]) + 1)) + (is (= (transduce (halt-when #{1} (fn [ret input] ret)) conj [] [5 4 1 2 3]) + [5 4])) + (is (= (transduce (halt-when #{1} (fn [ret input] (conj ret input))) conj [] [5 4 1 2 3]) + [5 4 1])) + (is (= (into [] (halt-when #{1} (fn [ret in] (conj ret in))) [2 3 1]) + [2 3 1])))) + +(deftest test-cljs-1839 + (let [x #js {:foo (fn [])} + foo (.-foo x)] + (is (instance? foo (new foo))) + (is (instance? foo (foo.))) + (is (instance? foo (new (.-foo x)))))) + +(deftest test-cljs-1845 + (let [sv (subvec [0 1 2 3 4 5 7 8 9] 2 6)] + (is (= [2 3 4 5] sv)) + (is (= [2 3 0 5] (assoc sv 2 0))) + (is (= [2 3 4 0] (assoc sv 3 0))) + (is (= [2 3 4 5 0] (assoc sv 4 0))) + (is (thrown? js/Error (assoc sv 5 0))) + (is (thrown? js/Error (assoc sv -1 0))))) + +(deftest test-cljs-1829 + (is (= (get "0123" -1 :not-found) :not-found)) + (is (= (get #js [0 1 2 3] -1 :not-found) :not-found)) + (is (= (get "0123" nil :not-found) :not-found)) + (is (= (get #js [0 1 2 3] nil :not-found) :not-found))) + +(deftest test-cljs-2028 + (let [x (sequence (filter pos?) [1 2 -1])] + (is (not (realized? x))) + (is (= x [1 2])) + (is (realized? x)))) + +(deftest test-1518 + (testing "Test evaluate expression once - keyword tests" + (let [m {:a :b + :b :c} + x (atom :a)] + (case (swap! x m) :a 0 :default) + (is (= :b @x))))) + +(deftest test-cljs-2021 + (let [check-if-throws #(try (%) (catch js/Error e :fail))] + (is (= :fail (check-if-throws #(subvec nil 0 0)))) + (is (= :fail (check-if-throws #(subvec {:foo :bar} 0 1)))) + (is (= :fail (check-if-throws #(subvec '(:foo) 0 1)))) + (is (= :fail (check-if-throws #(subvec #{:foo} 0 1)))))) + +(deftest test-cljs-2075 + (testing "PersistentTreeMap kv-reduce should honor reduced" + (let [sm (sorted-map 1 1, 2 2, 3 3, 4 4, 5 5, 6 6, 7 7)] + (is (= [1 2 3 4] (reduce-kv (fn [m k v] (if (= 5 k) (reduced m) (conj m k))) [] sm)))))) + +(defrecord CLJS2079 [a b]) + +(deftest test-cljs-2079 + (testing "Records and maps should not be equal" + (let [am (array-map :a 1 :b 2) + hm (hash-map :a 1 :b 2) + sm (sorted-map :a 1 :b 2) + r (->CLJS2079 1 2)] + (is (= am hm sm)) + + (is (not= r am)) + (is (not= am r)) + + (is (not= r hm)) + (is (not= hm r)) + + (is (not= r sm)) + (is (not= sm r))))) + +(deftype MapWithNoIKVReduce [backing-map] + IMap + (-dissoc [_ _] nil) + + ISeqable + (-seq [_] (seq backing-map))) + +(deftest test-cljs-2083 + (testing "maps which do not implement IKVReduce can be compared" + (is (true? (equiv-map (MapWithNoIKVReduce. {:a 1 :b 2 :c 3}) {:a 1 :b 2 :c 3}))) + (is (false? (equiv-map (MapWithNoIKVReduce. {:a 1 :b 2 :c 3}) {:a 1 :b 2 :c 4}))))) + +(deftest test-cljs-1685 + (testing "nil start or end param throws error" + (is (= :fail (try (subvec nil nil) + (catch js/Error e :fail)))) + (is (= :fail (try (subvec nil 1 nil) + (catch js/Error e :fail)))))) + +(def ^:const cljs-2104 "cljs-2104") + +(deftest test-const-emission + (testing "const exprs emission context, not definition context (CLJS-2104)" + (is (= cljs-2104 "cljs-2104")) + (is (= (if-some [x true] + cljs-2104 + "unreachable") + "cljs-2104")))) + +(deftest test-cljs-2113 + (is (thrown? js/Error (nth (range 2) -2))) + (is (thrown? js/Error (nth (range 2 1 0) -2))) + (is (= ::not-found (nth (range 2) -2 ::not-found))) + (is (= ::not-found (nth (range 2 1 0) -2 ::not-found)))) + +(deftest test-cljs-2109 + (testing "Syntax quoted dotted symbol without namespace should resolve to itself" + (is (= 'clojure.core `clojure.core)))) + +(deftype Partial [f args] + IFn + (-invoke [_ & a] + (apply (apply partial f args) a))) + +(deftest test-cljs-2133 + (testing "Invalid variadic IFn implementation should work" + (let [p (Partial. + [1])] + (p 2)))) + +(deftest test-resolve + (testing "Resolve should return valid var" + (is (= 1 ((resolve 'first) [1 2 3]))))) + +(deftest test-cljs-1998 + (testing "printing an Object with a null constructor" + (is (= "#object[Object]" (pr-str (.create js/Object nil)))))) + +(deftest test-cljs-2184 + (testing "ns-publics" + (is (contains? (ns-publics 'clojure.string) 'join)) + (is (not (contains? (ns-publics 'clojure.string) 'replace-all))) + (is (= (find (ns-publics 'clojure.string) 'join) + ['join #'clojure.string/join]))) + (testing "ns-imports" + (is (contains? (ns-imports 'clojure.string) 'StringBuffer)) + (is (= (find (ns-imports 'clojure.string) 'StringBuffer) + ['StringBuffer goog.string.StringBuffer])))) + +(deftest test-cljs-2190 + (binding [*print-namespace-maps* true] + (testing "printing a javascript map with a slash on keyword" + (is (= "#js {\"foo/bar\" 33}" (pr-str (doto (js-obj) (gobject/set "foo/bar" 33))))) + (is (= "#js {\"foo/bar\" #:var{:quux 66}}" (pr-str (doto (js-obj) (gobject/set "foo/bar" {:var/quux 66})))))))) + +(def ^:const true-2267 true) +(def ^:const false-2267 false) +(def ^:const nil-2267 nil) +(def ^:const empty-string-2267 "") +(def ^:const non-empty-string-2267 "x") +(def ^:const zero-2267 0) +(def ^:const non-zero-2267 1) + +(deftest test-cljs-2267 + (is (= :then (if true-2267 :then :else))) + (is (= :else (if false-2267 :then :else))) + (is (= :else (if nil-2267 :then :else))) + (is (= :then (if empty-string-2267 :then :else))) + (is (= :then (if non-empty-string-2267 :then :else))) + (is (= :then (if zero-2267 :then :else))) + (is (= :then (if non-zero-2267 :then :else)))) + +(deftest test-cljs-2278 + (is (= "#js {:alpha 1, \"beta gamma\" 2, \"delta/epsilon\" 3}" (pr-str #js {"alpha" 1 "beta gamma" 2 "delta/epsilon" 3}))) + (is (= "#js {\":abc\" 1}" (pr-str #js {":abc" 1}))) + (is (= "#js {\"0abc\" 1}" (pr-str #js {"0abc" 1}))) + (is (= "#js {:abc-def 1}" (pr-str #js {"abc-def" 1}))) + (is (= "#js {:x*+?!-' 1}" (pr-str #js {"x*+?!-'" 1})))) + +(deftest test-cljs-2282 + (is (= "#js {:_abc 1}" (pr-str #js {"_abc" 1}))) + (is (= "#js {:*compiler* 1}" (pr-str #js {"*compiler*" 1})))) + +(deftest test-cljs-2403 + (are [f k coll expected] (= expected (apply f k coll)) + min-key :x [{:x 1000} {:x 1001} {:x 1002} {:x 1000 :second true}] {:x 1000 :second true} + max-key :x [{:x 1000} {:x 999} {:x 998} {:x 1000 :second true}] {:x 1000 :second true})) + +(deftest swap-vals-returns-old-value + (let [a (atom 0)] + (is (= [0 1] (swap-vals! a inc))) + (is (= [1 2] (swap-vals! a inc))) + (is (= 2 @a)))) + +(deftest deref-swap-arities + (let [a (atom 0)] + (is (= [0 1] (swap-vals! a + 1))) + (is (= [1 3] (swap-vals! a + 1 1))) + (is (= [3 6] (swap-vals! a + 1 1 1))) + (is (= [6 10] (swap-vals! a + 1 1 1 1))) + (is (= 10 @a)))) + +(deftest deref-reset-returns-old-value + (let [a (atom 0)] + (is (= [0 :b] (reset-vals! a :b))) + (is (= [:b 45M] (reset-vals! a 45M))) + (is (= 45M @a)))) + +(deftest reset-on-deref-reset-equality + (let [a (atom :usual-value)] + (is (= :usual-value (reset! a (first (reset-vals! a :almost-never-seen-value))))))) + +(deftest test-cljs-2374 + (is (= "##NaN" (pr-str js/NaN))) + (is (= "##Inf" (pr-str js/Infinity))) + (is (= "##-Inf" (pr-str js/-Infinity)))) + +(deftest test-cljs-2449 + (is (= 1 (let [catch identity] (catch 1)))) + (is (= 1 (let [finally identity] (finally 1))))) + +(deftype Foo2407 [x y]) +(defrecord Bar2407 [x y]) + +(deftest test-cljs-2407 + (is (= "Positional factory function for cljs.core-test/Foo2407." (:doc (meta #'->Foo2407)))) + (is (= "Positional factory function for cljs.core-test/Bar2407." (:doc (meta #'->Bar2407)))) + (is (= "Factory function for cljs.core-test/Bar2407, taking a map of keywords to field values." (:doc (meta #'map->Bar2407))))) + +(deftest test-cljs-2283 + (is (nil? (doseq [])))) + +(deftest test-cljs-2453 + (is (= (re-seq #"[Bc]?" "aBcD") '("" "B" "c" "" ""))) + (is (= (re-seq #"[BcD]?$" "aBcD") '("D" ""))) + (is (= (map first (re-seq #"(\d+)" "ClojureScript 1.9.222")) '("1" "9" "222"))) + (is (= (re-seq #"\d+" "a1b2c3d") '("1" "2" "3"))) + (is (= (re-seq #"\d?" "a1b2c3d") '("" "1" "" "2" "" "3" "" ""))) + (is (= (re-seq #"\d*" "a1b2c3d") '("" "1" "" "2" "" "3" "" ""))) + (is (= (re-seq #"\d+" "a1b22c333d") '("1" "22" "333"))) + (is (= (re-seq #"\d?" "a1b22c333d") '("" "1" "" "2" "2" "" "3" "3" "3" "" ""))) + (is (= (re-seq #"\d*" "a1b22c333d") '("" "1" "" "22" "" "333" "" ""))) + (is (= (re-seq #"\w+" "once upon a time") '("once" "upon" "a" "time"))) + (is (nil? (re-seq #"\w+" "")))) + +(deftest test-cljs-2001 + (is (map-entry? (MapEntry. :key :val 0))) + (is (not (map-entry? [:key :val])))) + +(deftype Foo2455 [] + ISequential) + +(deftest test-cljs-2455 + (is (= :x (nth (eduction [:x]) 0))) + (is (thrown-with-msg? js/Error #"Index out of bounds" (nth (eduction [:x]) 1))) + (is (= :x (nth (eduction [:x]) 0 :not-found))) + (is (= :not-found (nth (eduction [:x]) 1 :not-found))) + ;; Calling nth on a type satisfying ISequential should attempt coercion + (is (thrown-with-msg? js/Error #".* is not ISeqable" (nth (->Foo2455) 0)))) + +(deftest test-cljs-2457 + (is (thrown-with-msg? js/Error #".* is not ISeqable" (seq #js {:a 1 :b 2})))) + +(deftest test-cljs-2537 + (is (true? (contains? (to-array [7 13 41]) -0.5))) + (is (== 7 (get (to-array [7 13 41]) -0.5))) + (is (== 7 (get (to-array [7 13 41]) -0.5 :not-found))) + (is (true? (contains? "ab" -0.5))) + (is (= \a (get "ab" -0.5))) + (is (= \a (get "ab" -0.5 :not-found)))) + +(deftest test-cljs-2538 + (testing "fractional indices in nth on arrays" + (is (thrown-with-msg? js/Error #"Index out of bounds" (nth (to-array [1 2]) -1))) + (is (= :not-found (nth (to-array [1 2]) -1 :not-found))) + (is (== 1 (nth (to-array [1 2]) -0.5))) + (is (== 1 (nth (to-array [1 2]) -0.5 :not-found))) + (is (== 1 (nth (to-array [1 2]) 0))) + (is (== 1 (nth (to-array [1 2]) 0 :not-found))) + (is (== 1 (nth (to-array [1 2]) 0.5))) + (is (== 1 (nth (to-array [1 2]) 0.5 :not-found))) + (is (== 2 (nth (to-array [1 2]) 1))) + (is (== 2 (nth (to-array [1 2]) 1 :not-found))) + (is (== 2 (nth (to-array [1 2]) 1.5))) + (is (== 2 (nth (to-array [1 2]) 1.5 :not-found))) + (is (thrown-with-msg? js/Error #"Index out of bounds" (nth (to-array [1 2]) 2))) + (is (= :not-found (nth (to-array [1 2]) 2 :not-found)))) + (testing "fractional indices in nth on strings" + (is (thrown-with-msg? js/Error #"Index out of bounds" (nth "ab" -1))) + (is (= :not-found (nth "ab" -1 :not-found))) + (is (== \a (nth "ab" -0.5))) + (is (== \a (nth "ab" -0.5 :not-found))) + (is (== \a (nth "ab" 0))) + (is (== \a (nth "ab" 0 :not-found))) + (is (== \a (nth "ab" 0.5))) + (is (== \a (nth "ab" 0.5 :not-found))) + (is (== \b (nth "ab" 1))) + (is (== \b (nth "ab" 1 :not-found))) + (is (== \b (nth "ab" 1.5))) + (is (== \b (nth "ab" 1.5 :not-found))) + (is (thrown-with-msg? js/Error #"Index out of bounds" (nth "ab" 2))) + (is (= :not-found (nth "ab" 2 :not-found))))) + +(deftest test-cljs-2549 + (let [tap (fn [_])] + (add-tap tap) + (is (set? @tapset)) + (is (contains? @tapset tap)) + (remove-tap tap))) + +(deftest test-cljs-2552 + (is (boolean? (tap> nil)))) + +;; Delete a bogus property from the beta? fn +;; Without the fix this js-delete form code-gens to code that deletes the alpha? fn: +;; delete (cljs.core_test.alpha_2585_QMARK_) && (cljs.core_test.beta_2585_QMARK_)["bogus-property"] +(defn ^boolean alpha-2585? [] true) +(defn ^boolean beta-2585? [] true) +(js-delete (and alpha-2585? beta-2585?) "bogus-property") + +(deftest test-cljs-2585 + (is (= true ((or int? string?) 1))) + ;; Make sure we didn't delete the alpha? fn + (is (some? alpha-2585?))) + +(defn fn-2741* ([x]) ([x y])) +(def fn-2741 fn-2741*) + +(deftest test-cljs-2741 + (is (thrown-with-msg? js/Error #".*Invalid arity: 0" ((fn ([x]) ([x y]))))) + (is (thrown-with-msg? js/Error #".*Invalid arity: 3" ((fn ([x]) ([x y])) 1 2 3))) + (is (thrown-with-msg? js/Error #".*Invalid arity: 0" (fn-2741))) + (is (thrown-with-msg? js/Error #".*Invalid arity: 3" (fn-2741 1 2 3))) + (is (thrown-with-msg? js/Error #".*Invalid arity: 0" ({}))) + (is (thrown-with-msg? js/Error #".*Invalid arity: 3" ({} 1 2 3)))) + +(deftest test-cljs-2799 + (is (thrown? js/Error (nth (repeat :x) -1))) + (is (= ::not-found (nth (repeat :x) -1 ::not-found)))) + +(comment + ;; ObjMap + ;; (let [ks (map (partial str "foo") (range 500)) + ;; m (apply obj-map (interleave ks (range 500)))] + ;; (assert (instance? cljs.core.ObjMap m)) + ;; (assert (= 500 (count m))) + ;; (assert (= 123 (m "foo123")))) + + ;; vars + + ;; (defn var-test + ;; "A docstring" + ;; [a b] + ;; (+ a b)) + + ;; (let [var-meta (meta #'var-test)] + ;; (assert (= (:doc var-meta) "A docstring")) + ;; (assert (= (:arglists var-meta) '([a b])))) + + ) + +(deftest uri-predicate + (testing "Testing uri?" + (is (not (uri? "http://clojurescript.org"))) + (is (not (uri? 42))) + (is (not (uri? []))) + (is (not (uri? {}))) + (is (uri? (goog.Uri. ""))) + (is (uri? (goog.Uri. "http://clojurescript.org"))) + (is (uri? (goog.Uri. "some string"))))) + +(defrecord CLJS-2787 []) + +(deftest test-cljs-2787 + (let [x (map->CLJS-2787 {1 2}) + y (map->CLJS-2787 x)] + (is (= x y)))) + +(deftest test-cljs-2807 + (testing "Quoted sets should work" + (is (macroexpand '(fn [x] #{(into [] x)}))))) + +(deftest var-desugar-test + (testing "dotted variable in return position" + (= cljs.core.PersistentQueue.EMPTY + ((fn [] cljs.core.PersistentQueue.EMPTY))) + (= 1 + (let [a #js {:b 1}] + ((fn [] a.b)))))) + +(deftest test-cljs-2832 + (is (true? ((comp not empty?) "foo"))) + (is (false? ((comp not empty?) ""))) + (is (thrown? js/Error ((not empty?) "foo"))) + (is (thrown? js/Error ((not empty?) "")))) + +(deftest test-cljs-2864 + (is (= "" (str))) + (is (= "a" (str "a"))) + (is (= "1" (str 1))) + (is (= "xyzzy" (str "x" "y" "z" "z" "y"))) + (is (= "a1b2c3" (str "a" 1 "b" 2 "c" 3)))) + +(defn str-fn-2865 [] + "hello") + +(deftest test-cljs-2865 + (is (= "ab" (str "a" (let [x true] (when x "b"))))) + (is (= "ab" (str "a" js/undefined "b"))) + (is (= "ab" (str "a" nil "b"))) + (is (= "ahellob" (str "a" (str-fn-2865) "b")))) + +(deftest test-cljs-2886 + (is (zero? (count ""))) + (is (== 1 (count "a"))) + (is (zero? (count #js []))) + (is (== 1 (count #js [1]))) + (is (zero? (count []))) + (is (== 1 (count [1])))) + +(deftest test-cljs-2934 + (let [x (delay 1)] + (is (= "#object[cljs.core.Delay {:status :pending, :val nil}]" (pr-str x))) + (force x) + (is (= "#object[cljs.core.Delay {:status :ready, :val 1}]" (pr-str x))))) + +(deftest test-cljs-2943 + (let [m1 {:a 2, :b 3, :c 5} + m2 {:a 7, :b 11, :d 13, :e 17} + m3 {:a 19, :d 23, :f 29} + m4 {:a 28, :b 14, :c 5, :d 36, :e 17, :f 29} + sorted (fn [m] (into (sorted-map) m))] + (is (= m4 (merge-with + m1 m2 m3))) + (is (= m4 (merge-with + (sorted m1) m2 m3))) + (is (= m4 (merge-with + (sorted m1) (sorted m2) m3))) + (is (= m4 (merge-with + m1 (sorted m2) m3))) + (is (= m4 (merge-with + m1 (sorted m2) (sorted m3)))))) + +(deftest test-cljs-2933 + (is (= "#object[cljs.core.Atom {:val 1}]" (pr-str (atom 1)))) + (is (= "#object[cljs.core.Volatile {:val 2}]" (pr-str (volatile! 2))))) + +(deftest test-cljs-2944 + (is (= (symbol :foo/bar) 'foo/bar)) + (is (= (symbol (->Var nil 'bar/foo nil)) 'bar/foo)) + (is (thrown? js/Error (symbol 1)))) + +(deftest test-cljs-2959 + (is (= {:a true} (meta (sort (with-meta (range 10) {:a true}))))) + (is (= {:a true} (meta (sort-by :a (with-meta (seq [{:a 5} {:a 2} {:a 3}]) {:a true})))))) + +(deftest test-cljs-2991 + (let [o (js-obj)] + (is (object? o)) + (is (empty? (js-keys o)))) + (let [o (js-obj "a" 17)] + (is (object? o)) + (is (== 1 (count (js-keys o)))) + (is (= "a" (aget (js-keys o) 0))) + (is (== 17 (gobject/get o "a")))) + (let [o (js-obj "a" 17 "b" 27)] + (is (object? o)) + (is (== 2 (count (js-keys o)))) + (is (== 17 (gobject/get o "a"))) + (is (== 27 (gobject/get o "b"))))) + +(defprotocol ExtMetaProtocol + :extend-via-metadata true + (ext-meta-protocol [x])) + +(defprotocol NonMetaProtocol + (non-meta-protocol [x])) + +(defrecord SomeMetaImpl [x] + ExtMetaProtocol + (ext-meta-protocol [_] x) + NonMetaProtocol + (non-meta-protocol [_] x)) + +(deftest test-cljs-2960 + ;; protocol impl via metadata + (is (= 1 (ext-meta-protocol (with-meta {} {`ext-meta-protocol (fn [_] 1)})))) + ;; metadata before actual impl + (is (= 1 (ext-meta-protocol (with-meta (SomeMetaImpl. 2) {`ext-meta-protocol (fn [_] 1)})))) + ;; protocol not marked as :extend-via-metadata so fallthrough to no impl + (is (thrown? js/Error (non-meta-protocol (with-meta {} {`non-meta-protocol (fn [_] 1)})))) + ;; normal impl call just in case + (is (= 2 (non-meta-protocol (with-meta (SomeMetaImpl. 2) {`non-meta-protocol (fn [_] 1)}))))) + +(extend-type PersistentArrayMap + ExtMetaProtocol + (ext-meta-protocol [m] 2)) + +(deftest test-cljs-3313 + (testing "metadata protocol fn takes precedence over direct implementation" + (= 1 (ext-meta-protocol (with-meta (array-map) {`ext-meta-protocol (fn [_] 1)}))))) + +(deftest test-cljs-3054 + (testing "`into` behaves the same as Clojure" + (is (nil? (into nil #{}))) + (is (= '(3 2 1) (into nil [1 2 3])))) + (testing "calling `set/union` with nilable sets returns a nilable set" + (is (nil? (set/union #{} nil nil))))) + +(deftest test-cljs-3092 + (is (nil? (peek (subvec [] 0)))) + (is (nil? (peek (subvec [1] 1)))) + (is (nil? (peek (subvec [1 2] 0 0)))) + (is (nil? (peek (subvec [1 2] 1 1)))) + (is (nil? (peek (subvec [1 2] 2 2))))) + +(deftest test-cljs-3093 + (is (thrown-with-msg? js/Error #"Index out of bounds" (subvec [1 2 3 4] -1))) + (is (= [1 2 3 4] (subvec [1 2 3 4] -0.9))) + (is (thrown-with-msg? js/Error #"Index out of bounds" (subvec [1 2 3 4] 2 1))) + (is (= [] (subvec [1 2 3 4] 1.7 1.3))) + (is (thrown-with-msg? js/Error #"Index out of bounds" (subvec [1 2 3 4] 0 5))) + (is (= [1 2 3 4] (subvec [1 2 3 4] 0 4.9)))) + +(deftest test-cljs-3095 + (let [a #js [:original] + v (apply vector a)] + (aset a 0 :modified) + (is (= :original (v 0))))) + +(deftest test-cljs-3119 + (is (= "a" (get "abc" -0.5))) + (is (nil? (get "abc" -1)))) + +(deftest test-cljs-3130 + (is (thrown-with-msg? js/Error #"Cannot compare f151d12d-7bd5-4409-9352-5900ee07baf7 to a" + (compare (uuid "f151d12d-7bd5-4409-9352-5900ee07baf7") "a")))) + +(deftest test-cljs-3202 + (is (= :/ (keyword "/"))) + (is (= (hash :/) (hash (keyword "/"))))) + +(deftest test-cljs-3263 + (is (= "#inst \"0985-04-12T23:20:50.520-00:00\"" (pr-str #inst "0985-04-12T23:20:50.520-00:00"))) + (is (= "#inst \"1970-12-18T23:20:50.520-00:00\"" (pr-str #inst "1970-12-18T23:20:50.520-00:00")))) + +(deftest test-cljs-3270 + (is (== 10 (count (range 0 (+ 1 (/ 9)) (/ 9)))))) + +(deftest test-cljs-3271 + (is (== 0.6 (nth (range 0 1 0.1) 6)))) + +(defrecord CLJS3305A []) +(defrecord CLJS3305B [a b]) + +(deftest test-cljs-3305 + (let [empty-basis (->CLJS3305A) + nonempty-basis (->CLJS3305B 1 2) + empty-extended (assoc empty-basis :y 1) + nonempty-extended (assoc nonempty-basis :y 1)] + (is (false? (contains? empty-basis :a))) + (is (true? (contains? nonempty-basis :a))) + (is (false? (contains? nonempty-basis :c))) + (is (true? (contains? empty-extended :y))) + (is (false? (contains? empty-extended :z))) + (is (true? (contains? nonempty-extended :a))) + (is (false? (contains? nonempty-extended :c))) + (is (true? (contains? nonempty-extended :y))) + (is (false? (contains? nonempty-extended :z))))) + +(deftest test-cljs-3306 + (let [sv (subvec [0 1 2 3 4] 2 4)] + (is (true? (contains? sv 0))) + (is (false? (contains? sv 0.5))) + (is (true? (contains? sv 1))) + (is (false? (contains? sv 1.5))) + (is (false? (contains? sv :kw)))) + (let [sv (subvec [0 1 2 3 4] 2 2)] + (is (false? (contains? sv 0))))) + +(deftest test-cljs-3309 + (is (= :ok + (loop [x 4] + (if (or (< x 4) (not-any? (fn [y] x) [1])) + (recur 5) + :ok)))) + (is (= '([]) + ((fn [s] + (for [e s :when (and (sequential? e) (every? (fn [x] x) e))] + e)) + [[]])))) + +(deftest test-cljs-3333 + (defonce not-native 17) ;; Intentionally matching a core name + (is (== 17 not-native))) + +(deftest test-cljs-3334 + (is (exists? /)) + (is (exists? cljs.core//)) + (is (not (exists? cljs.core-test//)))) + +(deftest test-update-vals + (let [inm (with-meta {:a 1 :b 2} {:has :meta})] + (are [result expr] (= result expr) + {:a 2 :b 3} (update-vals inm inc) + {:has :meta} (meta (update-vals inm inc)) + {0 2 2 4} (update-vals (hash-map 0 1 2 3) inc) + {0 2 2 4} (update-vals (array-map 0 1 2 3) inc) + {0 2 2 4} (update-vals (sorted-map 2 3 0 1) inc)))) + +(deftest test-update-keys + (let [inm (with-meta {:a 1 :b 2} {:has :meta})] + (are [result expr] (= result expr) + {"a" 1 "b" 2} (update-keys inm name) + {:has :meta} (meta (update-keys inm name)) + {1 1 3 3} (update-keys (hash-map 0 1 2 3) inc) + {1 1 3 3} (update-keys (array-map 0 1 2 3) inc) + {1 1 3 3} (update-keys (sorted-map 2 3 0 1) inc)))) + +(deftest test-cljs-3363 + (is (= {} + (reduce-kv #(assoc %1 %3 %2) {} nil))) + (is (= {1 :a 2 :b} + (reduce-kv #(assoc %1 %3 %2) {} (seq {:a 1 :b 2}))))) + +(defn cljs-3386-test-fn + ([x] x) ([_ _ & zs] zs)) + +(deftest test-cljs-3386 + (is (nil? (cljs-3386-test-fn 1 2))) + (is (= '(3 4) (cljs-3386-test-fn 1 2 3 4)))) + +(deftest test-cljs-3400 + (testing "macroexpanding non-seqs should work" + (is (true? (macroexpand '(and)))) + (is (nil? (macroexpand '(or)))))) + +(deftest test-cljs-3395 + (testing "(set! foo -bar baz) pattern" + (let [a #js {}] + (set! a -x false) + (is (false? (.-x a)))))) + +(deftest test-cljs-3406 + (testing "ISwap/IReset protocols" + (let [a (atom {:x 0}) + c (reify + IDeref + (-deref [_] + (:x @a)) + + ISwap + (-swap! [o f] + (:x (swap! a update :x f))) + (-swap! [o f x] + (:x (swap! a update :x f x))) + (-swap! [o f x y] + (:x (swap! a update :x f x y))) + (-swap! [o f x y zs] + (:x (swap! a #(apply update % :x f x y zs)))) + + IReset + (-reset! [o new-value] + (:x (swap! a assoc :x new-value))))] + (is (= 0 @c)) + (is (= 1 (swap! c inc))) + (is (= 1 @c)) + (is (= 2 (swap! c + 1))) + (is (= 2 @c)) + (is (= 5 (swap! c + 1 2))) + (is (= 5 @c)) + (is (= 11 (swap! c + 1 2 3))) + (is (= 11 @c)) + (is (= 0 (reset! c 0))) + (is (= 0 @c)) + + (is (= [0 1] (swap-vals! c inc))) + (is (= 1 @c)) + (is (= [1 2] (swap-vals! c + 1))) + (is (= 2 @c)) + (is (= [2 5] (swap-vals! c + 1 2))) + (is (= 5 @c)) + (is (= [5 11] (swap-vals! c + 1 2 3))) + (is (= 11 @c)) + (is (= [11 0] (reset-vals! c 0))) + (is (= 0 @c))))) + +(defn test-keys [& {:as opts, :keys [a b]}] + [a b opts]) + +(deftest test-cljs-3299-trailing-keys + (testing "verify proper handling of trailing keys" + (is (= (test-keys :a 1, :b 2) + [1 2 {:a 1, :b 2}])) + (is (= (test-keys {:a 1, :b 2}) + [1 2 {:a 1, :b 2}])) + (is (= (test-keys {:a 1, :b 2, :c 3}) + [1 2 {:a 1, :b 2, :c 3}])) + (is (= (test-keys :d 4 {:a 1, :b 2, :c 3}) + [1 2 {:d 4, :a 1, :b 2, :c 3}])))) + +(deftest test-str_ + (is (= "" (apply cljs.core/str_ nil))) + (is (= "" (apply cljs.core/str_ []))) + (is (= "1" (apply cljs.core/str_ 1 []))) + (is (= "12" (apply cljs.core/str_ 1 [2]))) + (is (= "1two:threefour#{:five}[:six]#{:seven}{:eight :nine}" + (apply cljs.core/str_ 1 ["two" :three 'four #{:five} [:six] #{:seven} {:eight :nine}]))) + (is (= "1234" (apply cljs.core/str_ 1 2 [3 4])))) + +(deftest test-cljs-3452 + (let [obj #js {:valueOf (fn [] "dude") + :toString (fn [] "correct")} + str-fn (fn [x y] + (str x obj y "\"foobar\"" 1 :foo nil))] + (testing "object is stringified using toString" + (is (= "correct6\"foobar\"1:foo" (str-fn nil (+ 1 2 3))))))) + +(def test-cljs-3472-var nil) +(deftest test-cljs-3472 + (set! test-cljs-3472-var "dude") + (is (= "dude" (str test-cljs-3472-var)))) + +(deftest test-cljs-3425 + (testing "Incorrect min/max handling of ##NaN" + (is (NaN? (min ##NaN 1))) + (is (NaN? (min 1 ##NaN))) + (is (NaN? (max ##NaN 1))) + (is (NaN? (max 1 ##NaN))))) + +(deftest test-static-props-methods + (is (= [] PersistentVector/EMPTY)) + (let [f String/fromCharCode] + (is (= "A" (f 65))))) + +(deftest test-new-method + (let [f Object/new] + (some? (f)))) + +(deftest test-instance-method-new + (is (= ["FOO" "BAR" "BAZ"] + (map String/.toUpperCase ["foo" "bar" "baz"])))) diff --git a/src/test/cljs/cljs/destructuring_test.cljs b/src/test/cljs/cljs/destructuring_test.cljs new file mode 100644 index 0000000000..d8caf73878 --- /dev/null +++ b/src/test/cljs/cljs/destructuring_test.cljs @@ -0,0 +1,231 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.destructuring-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is]] + [clojure.string :as s] + [clojure.set :as set])) + +(deftest test-destructuring + (testing "Testing destructuring" + (is (= [2 1] (let [[a b] [1 2]] [b a]))) + (is (= #{1 2} (let [[a b] [1 2]] #{a b}))) + (is (= [1 2] (let [{a :a b :b} {:a 1 :b 2}] [a b]))) + (is (= [1 2] (let [{:keys [a b]} {:a 1 :b 2}] [a b]))) + (is (= [1 2 [1 2]] (let [[a b :as v] [1 2]] [a b v]))) + (is (= [1 42] (let [{:keys [a b] :or {b 42}} {:a 1}] [a b]))) + (is (= [1 nil] (let [{:keys [a b] :or {c 42}} {:a 1}] [a b]))) + (is (= [2 1] (let [[a b] '(1 2)] [b a]))) + (is (= {1 2} (let [[a b] [1 2]] {a b}))) + (is (= [2 1] (let [[a b] (seq [1 2])] [b a]))) + (testing "namespaced keys" + (let [{:keys [:a :b]} {:a 1 :b 2}] + (testing "basic" + (is (= 1 a)) + (is (= 2 b)))) + (let [{:keys [:a/b :c/d]} {:a/b 1 :c/d 2}] + (testing "keyword syntax" + (is (= 1 b)) + (is (= 2 d)))) + (let [{:keys [a/b c/d]} {:a/b 1 :c/d 2}] + (testing "symbol syntax" + (is (= 1 b)) + (is (= 2 d)))) + (let [{:syms [a/b c/d]} {'a/b 1 'c/d 2}] + (testing ":syms" + (is (= 1 b)) + (is (= 2 d)))) + (let [{:keys [::s/x ::s/y]} {:clojure.string/x 1 :clojure.string/y 2}] + (testing ":keys" + (is (= x 1)) + (is (= y 2)))) + ))) + +(deftest keywords-in-destructuring + (let [m {:a 1 :b 2}] + (let [{:keys [:a :b]} m] + (is (= [1 2] [a b]))) + (let [{:keys [:a :b :c] :or {c 3}} m] + (is (= [1 2 3] [a b c]))))) + +(deftest namespaced-keywords-in-destructuring + (let [m {:a/b 1 :c/d 2}] + (let [{:keys [:a/b :c/d]} m] + (is (= [1 2] [b d]))) + (let [{:keys [:a/b :c/d :e/f] :or {f 3}} m] + (is (= [1 2 3] [b d f]))))) + +(deftest namespaced-keys-in-destructuring + (let [m {:a/b 1 :c/d 2}] + (let [{:keys [a/b c/d]} m] + (is (= [1 2] [b d]))) + (let [{:keys [a/b c/d e/f] :or {f 3}} m] + (is (= [1 2 3] [b d f]))))) + +(deftest namespaced-syms-in-destructuring + (let [{:syms [a/b c/d e/f] :or {f 3}} {'a/b 1 'c/d 2}] + (is (= [1 2 3] [b d f])))) + +(deftest namespaced-keys-syntax + (let [{:a/keys [b c d] :or {d 3}} {:a/b 1 :a/c 2}] + (is (= [1 2 3] [b c d])))) + +(deftest namespaced-syms-syntax + (let [{:a/syms [b c d] :or {d 3}} {'a/b 1 'a/c 2}] + (is (= [1 2 3] [b c d])))) + +(deftest resolve-keyword-ns-alias-in-destructuring + (let [{:keys [::s/x ::s/y ::s/z] :or {z 3}} {:clojure.string/x 1 :clojure.string/y 2}] + (is (= [1 2 3] [x y z])))) + +(defprotocol IHasFirst + (-get-first [this])) + +(defprotocol IFindsFirst + (-find-first [this other])) + +(deftype First [xs] + ISeqable + (-seq [this] (seq xs)) + IIndexed + (-nth [this i] (nth xs i)) + (-nth [this i not-found] (nth xs i not-found)) + IFn + (-invoke [[x]] x) + (-invoke [this x] this) + Object + (toString [[x]] (str x)) + IHasFirst + (-get-first [[x]] x) + IFindsFirst + (-find-first [_ [x]] x)) + +(deftype DestructuringWithLocals [a] + IFindsFirst + (-find-first [_ [x y]] + [x y a])) + +(deftest test-protocol-method-destructuring + (testing "Testing protocol method destructuring" + (let [fv (First. [1 2 3]) + fs (First. "asdf")] + (testing "basic operations" + (is (= (fv) 1)) + (is (= (fs) \a)) + (is (= (str fs) \a)) + (is (= (-get-first fv) 1)) + (is (= (-get-first fs) \a)) + (is (= (-find-first fv [1]) 1)) + (is (identical? (fv 1) fv)))) + (let [t (DestructuringWithLocals. 1)] + (testing "with locals" + (is (= [2 3 1] (-find-first t [2 3]))))))) + +(defn destructure-1216 + ([kvs] kvs) + ([k v & args] [k v args])) + +(defn foo-1216 + ([a] (foo-1216 a 10)) + ([a b & [c]] [a b c])) + +(deftest test-cljs-1216 + (testing "varargs regression" + (is (= (foo-1216 1) [1 10 nil])) + (is (= (foo-1216 1 2) [1 2 nil])) + (is (= (foo-1216 1 2 3) [1 2 3])) + (is (= [1 2 [3 4]] + (destructure-1216 1 2 3 4))) + (is (= [1 2 [3 4]] + (apply destructure-1216 [1 2 3 4]))) + (is (= (destructure-1216 1 2 3 4)[1 2 [3 4]] + (apply destructure-1216 [1 2 3 4]))))) + +(defprotocol CLJS-1600-IFoo + (foo-fn [_ {:keys [a b] :as x}])) + +(defrecord CLJS-1600-Foo [] + CLJS-1600-IFoo + (foo-fn [_ {:keys [a b] :as args}] + args)) + +(deftest test-cljs-1600 + (let [foo (reify + CLJS-1600-IFoo + (foo-fn [_ {:keys [a b] :as args}] + args))] + (is (= (foo-fn (->CLJS-1600-Foo) {:a 1 :b 2}) + {:a 1 :b 2})) + (is (= (foo-fn foo {:a 1 :b 2}) + {:a 1 :b 2}))) + ;; test that the destructuring works + (let [foo (reify + CLJS-1600-IFoo + (foo-fn [_ {:keys [a b] :as args}] + {:a a :b b}))] + (is (= (foo-fn foo {:a 1 :b 2}) + {:a 1 :b 2}))) + (let [foo (reify + CLJS-1600-IFoo + (foo-fn [_ {:keys [a b c] :or {c 3}}] + {:c c}))] + (is (= (foo-fn foo {:a 1 :b 2}) + {:c 3})))) + +(deftest test-cljs-3076 + (let [f (fn [& [a _]] + a)] + (is (nil? (f nil))) + (is (= 1 (f 1))) + (is (= 1 (f 1 2)))) + (let [])) + +(deftest test-pam-dupes? + (is (false? (#'pam-dupes? #js [:a 1 :b 2 :c 3]))) + (is (true? (#'pam-dupes? #js [:a 1 :b 2 :a 3])))) + +(deftest test-pam-new-size + (is (== 6 (#'pam-new-size #js [:a 1 :b 2 :c 3]))) + (is (== 4 (#'pam-new-size #js [:a 1 :b 2 :a 3])))) + +(deftest singleton-map-in-destructure-context + (let [sample-map {:a 1 :b 2} + {:keys [a] :as m1} (list sample-map)] + (is (= m1 sample-map)) + (is (= a 1)))) + +(deftest trailing-map-destructuring + (let [add (fn [& {:keys [a b]}] (+ a b)) + addn (fn [n & {:keys [a b]}] (+ n a b))] + (testing "that kwargs are applied properly given a map in place of the key/val pairs" + (is (= 3 (add :a 1 :b 2))) + (is (= 3 (add {:a 1 :b 2}))) + (is (= 13 (addn 10 :a 1 :b 2))) + (is (= 13 (addn 10 {:a 1 :b 2}))) + (is (= 103 ((partial addn 100) :a 1 {:b 2}))) + (is (= 103 ((partial addn 100 :a 1) {:b 2}))) + (is (= 107 ((partial addn 100 :a 1) {:a 5 :b 2})))) + (testing "built maps" + (let [{:as m1} (list :a 1 :b 2) + {:as m2} (list :a 1 :b 2 {:c 3}) + {:as m3} (list :a 1 :b 2 {:a 0}) + {:keys [a4] :as m4} (list nil)] + (= m1 {:a 1 :b 2}) + (= m2 {:a 1 :b 2 :c 3}) + (= m3 {:a 0 :b 2}) + (= m1 (seq-to-map-for-destructuring (list :a 1 :b 2))) + (= m2 (seq-to-map-for-destructuring (list :a 1 :b 2 {:c 3}))) + (= m3 (seq-to-map-for-destructuring (list :a 1 :b 2 {:a 0}))) + (= a4 nil))))) + +(comment + + (cljs.test/run-tests) + + ) diff --git a/src/test/cljs/cljs/eval_test.cljs b/src/test/cljs/cljs/eval_test.cljs new file mode 100644 index 0000000000..ea93b21267 --- /dev/null +++ b/src/test/cljs/cljs/eval_test.cljs @@ -0,0 +1,34 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.eval-test + (:require [cljs.test :refer [deftest is]])) + +;;; This test namespace should only be loaded by environments that set up cljs.core/*eval* + +(def addition-list-1 (list + 1 2)) +(def addition-list-2 (list + 1 'a)) +(def addition-list-3 (list (fn [a b] (+ a b)) 1 2)) +(defn square [x] (* x x)) +(defn cube [x] (* x x x)) + +(deftest test-eval + (is (== 1 (eval 1))) + (is (== 3 (eval '(+ 1 2)))) + (is (== 17 (eval '(let [a 10] (+ 3 4 a))))) + (is (= 'a (:name (meta (eval '(def a 3)))))) + (is (== 3 (eval 'a))) + (is (== 3 (eval addition-list-1))) + (is (== 4 (eval addition-list-2))) + (is (== 13 (eval (concat addition-list-1 [10])))) + (is (= 'lucky-number (:name (meta (eval (list 'def 'lucky-number (concat addition-list-1 [20]))))))) + (is (== 23 (eval 'lucky-number))) + (is (== 64 ((eval (list comp square cube)) 2))) + (is (== 5 ((eval (eval +)) 2 3))) + (is (== 3 (eval addition-list-3))) + (is (== 4 (eval (list #'inc 3))))) diff --git a/src/test/cljs/cljs/extend_to_native_test.cljs b/src/test/cljs/cljs/extend_to_native_test.cljs new file mode 100644 index 0000000000..0513f2f9a6 --- /dev/null +++ b/src/test/cljs/cljs/extend_to_native_test.cljs @@ -0,0 +1,154 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.extend-to-native-test + (:require [cljs.test :refer-macros [deftest is]])) + +;;; Note: The tests in this namespace manipulate native types (at +;;; test run time) and this namespace should be loaded last by test +;;; runners so as to not affect other tests. + +;; r1798 core fn protocol regression +(deftest test-extend-to-object + (extend-type object + ISeqable + (-seq [coll] + (map #(vector % (aget coll %)) (js-keys coll))) + + ILookup + (-lookup + ([coll k] + (-lookup coll k nil)) + ([coll k not-found] + (if-let [v (aget coll k)] + v + not-found)))) + (is (= (seq (js-obj "foo" 1 "bar" 2)) '(["foo" 1] ["bar" 2]))) + (is (= (get (js-obj "foo" 1) "foo") 1)) + (is (= (get (js-obj "foo" 1) "bar" ::not-found) ::not-found)) + (is (= (reduce (fn [s [k v]] (+ s v)) 0 (js-obj "foo" 1 "bar" 2)) 3))) + +(deftest test-cljs-2812 + (extend-protocol IPrintWithWriter + object + (-pr-writer [obj writer _] + (write-all writer "#object[custom-print-cljs-2812]")) + boolean + (-pr-writer [obj writer _] + (write-all writer "#boolean[" (str obj) "]")) + number + (-pr-writer [obj writer _] + (write-all writer "#number[" (str obj) "]")) + string + (-pr-writer [obj writer _] + (write-all writer "#string[" obj "]")) + array + (-pr-writer [obj writer _] + (write-all writer "#array[" (count obj) "]")) + function + (-pr-writer [obj writer _] + (write-all writer "#function[custom-print-cljs-2812]"))) + (is (= "#object[custom-print-cljs-2812]" (pr-str #js {}))) + (is (= "#boolean[true]" (pr-str true))) + (is (= "#number[11]" (pr-str 11))) + (is (= "#string[hello]" (pr-str "hello"))) + (is (= "#array[3]" (pr-str #js [1 2 3]))) + (is (= "#function[custom-print-cljs-2812]" (pr-str map))) + ;; Restore basic native types so that test summary output looks correct + (extend-protocol IPrintWithWriter + object + (-pr-writer [obj writer _] + (write-all writer (str obj))) + boolean + (-pr-writer [obj writer _] + (write-all writer (str obj))) + number + (-pr-writer [obj writer _] + (write-all writer (str obj))) + string + (-pr-writer [obj writer _] + (write-all writer obj)))) + +(deftest test-cljs-2974 + (extend-protocol IEmptyableCollection + array + (-empty [_] #js [])) + (let [empty-array (empty #js [1 2 3])] + (is (and (array? empty-array) + (empty? empty-array))))) + +(defn test-map-entry [x] (when (map-entry? x) (-key x))) +(defn test-coll [x] (when (coll? x) (-conj x 1))) +(defn test-set [x] (when (set? x) (-disjoin x 1))) +(defn test-associative [x] (when (associative? x) (-assoc x 1 2))) +(defn test-find [x] (when (ifind? x) (-find x 1))) +(defn test-sorted [x] (when (sorted? x) (-sorted-seq x true))) +(defn test-map [x] (when (map? x) (-dissoc x 1))) +(defn test-vector [x] (when (vector? x) (-assoc-n x 1 2))) +(defn test-chunked-seq [x] (when (chunked-seq? x) (-chunked-first x))) +(defn test-ifn [x] (when (ifn? x) (-invoke x))) +(defn test-reversible [x] (when (reversible? x) (-rseq x))) +(defn test-iterable [x] (when (iterable? x) (-iterator x))) +(defn test-cloneable [x] (when (cloneable? x) (-clone x))) +(defn test-counted [x] (when (counted? x) (-count x))) +(defn test-indexed [x] (when (indexed? x) (-nth x 0))) +(defn test-seqable [x] (when (seqable? x) (-seq x))) +(defn test-reduceable [x] (when (reduceable? x) (-reduce x inc))) + +(deftest test-extend-to-protocols + (extend-type string IMapEntry (-key [_] :a)) + (is (nil? (test-map-entry "a"))) + (extend-type string ICollection (-conj [_ _] :b)) + (is (= :b (test-coll "a"))) + (extend-type string ISet (-disjoin [_ _] :c)) + (is (= :c (test-set "a"))) + (extend-type string IAssociative (-assoc [_ _ _] :d)) + (is (= :d (test-associative "a"))) + (extend-type string IFind (-find [_ _] :e)) + (is (= :e (test-find "a"))) + (extend-type string ISorted (-sorted-seq [_ _] :f)) + (is (= :f (test-sorted "a"))) + (extend-type string IMap (-dissoc [_ _] :g)) + (is (= :g (test-map "a"))) + (extend-type string IVector (-assoc-n [_ _ _] :h)) + (is (= :h (test-vector "a"))) + (extend-type string IChunkedSeq (-chunked-first [_] :i)) + (is (nil? (test-chunked-seq "a"))) + (extend-type string IFn (-invoke [_] :j)) + (is (= :j (test-ifn "a"))) + (extend-type string IReversible (-rseq [_] :k)) + (is (= :k (test-reversible "a"))) + (extend-type string IIterable (-iterator [_] :l)) + (is (= :l (test-iterable "a"))) + (extend-type string ICloneable (-clone [_] :m)) + (is (= :m (test-cloneable "a"))) + (extend-type string ICounted (-count [_] :n)) + (is (= :n (test-counted "a"))) + (extend-type string IIndexed (-nth [_] :o)) + (is (= :o (test-indexed "a"))) + (extend-type number ISeqable (-seq [_] :p)) + (is (= :p (test-seqable 1))) + (extend-type string IReduce (-reduce [_ _] :q)) + (is (= :q (test-reduceable "a")))) + +(defprotocol Slashy (/ [_])) + +(extend-type string + Slashy + (/ [_] "result")) + +(deftest test-protocol-with-slash + (is (= "result" (/ "")))) + +(deftest test-cljs-3307 + (extend-type object + IAssociative + (-contains-key? [_ k] (= k :valid))) + + (is (contains? #js {} :valid)) + (is (not (contains? #js {} :invalid)))) diff --git a/src/test/cljs/cljs/foo/ns_shadow_test.cljs b/src/test/cljs/cljs/foo/ns_shadow_test.cljs new file mode 100644 index 0000000000..1d3d2200b0 --- /dev/null +++ b/src/test/cljs/cljs/foo/ns_shadow_test.cljs @@ -0,0 +1,29 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns foo.ns-shadow-test + (:require [cljs.test :refer-macros [deftest is]] + baz)) + +(defn bar [] 1) + +(defn quux [foo] + (+ (foo.ns-shadow-test/bar) foo)) + +(defn id [x] x) + +(defn foo [] (id 42)) + +(defn baz + ([] (baz 2)) + ([x] (quux 2))) + +(deftest test-shadow + (is (= (quux 2) 3)) + (is (= (foo) 42)) + (is (= (baz) 3))) diff --git a/src/test/cljs/cljs/hash_map_test.cljs b/src/test/cljs/cljs/hash_map_test.cljs new file mode 100644 index 0000000000..3d37e761ca --- /dev/null +++ b/src/test/cljs/cljs/hash_map_test.cljs @@ -0,0 +1,160 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.hash-map-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is]])) + +(defn iter->set + "Return a set of elements in iterator" + [iter] + (loop [entries #{} iter iter] + (if (.hasNext iter) + (recur (conj entries (.next iter)) iter) + entries))) + +(deftest test-cljs-1765 + (is (true? (.hasNext (-iterator (hash-map nil 1))))) + (is (true? (.hasNext (-iterator (hash-map :a 1 :b 2 :c 3 :d 4 nil 5))))) + (is (= #{[nil 1]} + (-> (hash-map nil 1) + (-iterator) + (iter->set)))) + (is (= #{[:a 1] [:b 2] [:c 3] [:d 4] [nil 5]} + (-> (hash-map :a 1 :b 2 :c 3 :d 4 nil 5) + (-iterator) + (iter->set))))) + +(deftest test-cljs-1817 + (let [cljscore-hash hash] + (with-redefs [hash (fn [x] + (if (or (#{:a :b} x) 0) + cljscore-hash))] + (let [x (hash-map :a :a :b -1)] + (is (= (assoc x :b :b) {:a :a :b :b})))))) + +(deftest test-array-map-with-duplicate-keys + (testing "Testing duplicate keys in array maps" + ;; runtime + (is (= [:foo] (keys (apply array-map [:foo 1 :foo 2])))) + (let [sym-a (with-meta 'foo :first) + sym-b (with-meta 'foo :second)] + (is (= {sym-a 2} (apply array-map [sym-a 1 sym-b 2])))) + ;; compile-time + (is (= {:foo 2} (array-map :foo 1 :foo 2))) + (let [sym-a (with-meta 'foo :first) + sym-b (with-meta 'foo :second)] + (is (= {sym-a 2} (array-map sym-a 1 sym-b 2)))))) + +(defrecord T [index a b]) + +(deftest test-cljs-1976 + ;; We must detect hash collisions when two values have different hashes but + ;; still have the same 32-bit hash. Hash producers may be lazy and not + ;; truncate their hash to 32-bits. + (let [bad-record-1 (->T :eavt 17592186192276 nil) + ;; (hash bad-record-1) is 1454955434 + bad-record-2 (->T :avet 10 :fhir.ElementDefinition/minValueDateTime$cr) + ;; (hash bad-record-2) is -2840011862 + ;; But (bit-or (hash bad-record-2) 0) is 1454955434. Collision! + ;; These dates have the same property + bad-date-1 #inst "2017-03-13T22:21:08.666-00:00" + bad-date-2 #inst "2015-11-02T19:53:15.706-00:00"] + (testing "Transient assoc of hash-colliding keys with different hash values" + (is (= :ok (try + (hash-map bad-record-1 nil bad-record-2 nil) + :ok + (catch :default _ :error)))) + (is (= :ok (try + (hash-map bad-date-1 nil bad-date-2 nil) + :ok + (catch :default _ :error))))) + + (testing "Non-transient assoc of hash-colliding keys with different hash values" + (is (= :ok (try + (assoc (hash-map bad-record-1 nil) bad-record-2 nil) + :ok + (catch :default _ :error)))) + + (is (= :ok (try + (assoc (hash-map bad-date-1 nil) bad-date-2 nil) + :ok + (catch :default _ :error))))))) + + +(deftest test-cljs-2496 + (testing "A seq or iterator over a PAM/PHM should be composed of instances of IMapEntry" + (testing "PersistentHashMap" + (let [m (hash-map nil nil 1 1 2 2)] + (is (every? map-entry? m)) + (is (every? map-entry? (iter->set (-iterator m)))))) + (testing "PersistentArrayMap" + (let [m (array-map nil nil 1 1 2 2)] + (is (every? map-entry? m)) + (is (every? map-entry? (iter->set (-iterator m)))))))) + +(deftest test-cljs-1888 + (let [arr-map-seq (seq (array-map :a 1 :b 2)) + ;; small hash map will produce a NodeSeq + node-seq (seq (hash-map :a 1 :b 2 :c 3)) + ;; Large hash map will produce an ArrayNodeSeq + array-node-seq (seq (into {} + (map (fn [e] [e nil])) + (range 1000)))] + (testing "PersistentArrayMapSeq" + (is (= {:has :meta} (-> arr-map-seq + (with-meta {:has :meta}) + (meta)))) + (is (= nil (-> arr-map-seq + (with-meta {:has :meta}) + (rest) + (meta)))) + (is (= nil (-> arr-map-seq + (with-meta {:has :meta}) + (next) + (meta)))) + (is (= nil (-> arr-map-seq + (with-meta {:has :meta}) + (empty) + (meta))))) + + (testing "NodeSeq" + (is (instance? NodeSeq node-seq)) + (is (= {:has :meta} (-> node-seq + (with-meta {:has :meta}) + (meta)))) + (is (= nil (-> node-seq + (with-meta {:has :meta}) + (rest) + (meta)))) + (is (= nil (-> node-seq + (with-meta {:has :meta}) + (next) + (meta)))) + (is (= nil (-> node-seq + (with-meta {:has :meta}) + (empty) + (meta))))) + + (testing "ArrayNodeSeq" + (is (instance? ArrayNodeSeq array-node-seq)) + (is (= {:has :meta} (-> array-node-seq + (with-meta {:has :meta}) + (meta)))) + (is (= nil (-> array-node-seq + (with-meta {:has :meta}) + (rest) + (meta)))) + (is (= nil (-> array-node-seq + (with-meta {:has :meta}) + (next) + (meta)))) + (is (= nil (-> array-node-seq + (with-meta {:has :meta}) + (empty) + (meta))))))) diff --git a/src/test/cljs/cljs/hash_set_test.cljs b/src/test/cljs/cljs/hash_set_test.cljs new file mode 100644 index 0000000000..2648386316 --- /dev/null +++ b/src/test/cljs/cljs/hash_set_test.cljs @@ -0,0 +1,24 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.hash-set-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is]])) + +(deftest test-hash-set-with-duplicate-keys + (testing "Testing duplicate keys in array maps" + ;; runtime + (is (= [:foo] (keys (apply array-map [:foo :foo])))) + (let [sym-a (with-meta 'foo :first) + sym-b (with-meta 'foo :second)] + (is (= #{sym-a} (apply hash-set [sym-a sym-b])))) + ;; compile-time + (is (= {:foo 2} (hash-set :foo :foo))) + (let [sym-a (with-meta 'foo :first) + sym-b (with-meta 'foo :second)] + (is (= #{sym-a} (hash-set sym-a sym-b)))))) diff --git a/src/test/cljs/cljs/hashing_test.cljs b/src/test/cljs/cljs/hashing_test.cljs new file mode 100644 index 0000000000..7bad7169ea --- /dev/null +++ b/src/test/cljs/cljs/hashing_test.cljs @@ -0,0 +1,113 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.hashing-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is]] + [clojure.string :as s] + [clojure.set :as set] + [goog.object :as gobject])) + +(deftest test-hash-null + (is (zero? (hash (gobject/get (js-obj) "foo"))))) + +;; hashing bug in many JS runtimes CLJ-118 +(deftest test-clj-118 + (let [g #{(conj #{:2} :alt)} + h #{#{:2 :alt}}] + (is (= g h))) + (is (= (hash {:a 1 :b 2}) + (hash {:b 2 :a 1}))) + (is (= (hash (hash-map :a 1 :b 2)) + (hash (hash-map :b 2 :a 1)))) + (is (= (hash {:start 133 :end 134}) + (hash (apply hash-map [:start 133 :end 134])))) + (is (= (hash :a) + (hash (keyword "a"))))) + +(deftest test-962-empty-literal-hashes + (testing "CLJS-962: empty literals should produce collections with correct hash codes" + (let [l () + v [] + s #{} + m {}] + (is (== (hash l) (hash v) (hash-ordered-coll ()))) + (is (== (hash s) (hash m) (hash-unordered-coll #{}))))) + (testing "CLJS-962: EMPTY collections should have correct hash codes" + (let [l (.-EMPTY List) + pv (.-EMPTY PersistentVector) + phs (.-EMPTY PersistentHashSet) + pts (.-EMPTY PersistentTreeSet) + pam (.-EMPTY PersistentArrayMap) + phm (.-EMPTY PersistentHashMap) + ptm (.-EMPTY PersistentTreeMap)] + (is (== (hash l) (hash pv) (hash-ordered-coll ()))) + (is (apply == (hash-unordered-coll #{}) (map hash [phs pts pam phm ptm])))))) + +(deftest test-uuid-compile-and-runtime-hash + (is (= (hash (.toString #uuid "0d1f9029-40fc-4728-8bdd-9862172d4370")) + (hash (.toString (UUID. "0d1f9029-40fc-4728-8bdd-9862172d4370" nil)))))) + +(deftest test-cljs-1524 + (let [x0 [] + x1 (conj x0 1) + x2 (conj x1 2) + x3 (remove #{1} x2) + x4 (remove #{2} x3) + x5 (conj x4 3) + x6 (conj x5 4) + x7 (conj x6 5)] + (is (not (== (hash x0) (hash x1) (hash x2) (hash x3) (hash x4) + (hash x5) (hash x6) (hash x7)))))) + +(deftest test-nil-hashing-cljs-1649 + (is (zero? (hash-string nil))) + (is (not (zero? (hash-string "null"))))) + +(deftest test-cljs-1779 + (is (= (hash (keyword 'app "foo")) + (hash (keyword "app" "foo"))))) + +(deftest test-mumur-support + (testing "Testing murmur support" + ;; int-rotate-left + (is (== (int-rotate-left (bit-or 0x87654321 0) 4) (bit-or 0x76543218 0))) + (is (== (int-rotate-left (bit-or 0x87654321 0) 8) (bit-or 0x65432187 0))) + (is (== (int-rotate-left (bit-or 0x80000000 0) 1) 0x1)) + (is (== (int-rotate-left (bit-or 0x78123456 0) 4) (bit-or 0x81234567 0))) + (is (== (int-rotate-left (bit-or 0xffffffff 0) 4) (bit-or 0xffffffff 0))) + + ;; imul + (is (== (imul 3 3) 9)) + (is (== (imul -1 8) -8)) + (is (== (imul -2 -2) 4)) + (is (== (imul 0xffffffff 5) -5)) + (is (== (imul 0xfffffffe 5) -10)) + )) + +(deftest test-cljs-1818 + (is (= (hash true) 1231)) + (is (= (hash false) 1237))) + +(deftest test-cljs-3410 + (testing "Small doubles should not hash the same" + (is (not= (hash-double -0.32553251) (hash-double -0.0000032553251))) + (is (not= (hash -0.32553251) (hash -0.0000032553251)))) + (testing "Same double hashes the same" + (is (= (hash 0.5) (hash 0.5))) + (is (= (hash -0.32553251) (hash -0.32553251))) + (is (= (hash -0.0000032553251) (hash -0.0000032553251))))) + +(deftest test-cljs-3290 + (testing "JS Symbol hash" + (let [s (.for js/Symbol "foo")] + (is (number? (hash s))) + (is (== (hash s) (hash s))) + (is (not (== (hash s) (hash (.for js/Symbol "bar"))))) + (let [m {s 2}] + (is (== 2 (get m s))))))) diff --git a/src/test/cljs/cljs/import_test.cljs b/src/test/cljs/cljs/import_test.cljs new file mode 100644 index 0000000000..8c54e18c0c --- /dev/null +++ b/src/test/cljs/cljs/import_test.cljs @@ -0,0 +1,21 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.import-test + (:require [cljs.test :refer-macros [deftest is]]) + (:import goog.math.Long + [goog.math Vec2 Vec3] + [goog.math Integer])) + +(deftest test-import + (is (fn? Long)) + (is (.equals (Long. 4 6) (.add (Long. 1 2) (Long. 3 4)))) + (is (= "12" (str (Long.fromInt 12)))) + (is (not (nil? (Vec2. 1 2)))) + (is (not (nil? (Vec3. 1 2 3)))) + (is (.equals (Integer.fromString "10") (goog.math.Integer.fromString "10")))) diff --git a/src/test/cljs/cljs/inference_test.cljs b/src/test/cljs/cljs/inference_test.cljs new file mode 100644 index 0000000000..d37db3baf1 --- /dev/null +++ b/src/test/cljs/cljs/inference_test.cljs @@ -0,0 +1,99 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.inference-test + (:require-macros [cljs.inference-util]) + (:require [cljs.test :refer [deftest is]] + [cljs.pprint])) + +(deftest test-cljs-2825 + (cljs.inference-util/truth_-not-called? + (not nil) + (object? nil) + (char? nil) + (any? nil) + (native-satisfies? ICounted nil) + (var? nil) + (iterable? nil) + (cloneable? nil) + (inst? nil) + (reduced? nil) + (counted? nil) + (indexed? nil) + (fn? nil) + (empty? nil) + (coll? nil) + (set? nil) + (associative? nil) + (ifind? nil) + (sequential? nil) + (sorted? nil) + (reduceable? nil) + (map? nil) + (record? nil) + (vector? nil) + (chunked-seq? nil) + (boolean? nil) + (seq? nil) + (seqable? nil) + (boolean nil) + (ifn? nil) + (integer? nil) + (int? nil) + (pos-int? nil) + (nat-int? nil) + (float? nil) + (double? nil) + (infinite? nil) + (contains? [] nil) + (list? nil) + (reversible? nil) + (keyword? nil) + (keyword-identical? :a :a) + (symbol-identical? 'a 'a) + (ident? nil) + (simple-ident? nil) + (qualified-ident? nil) + (simple-symbol? nil) + (qualified-symbol? nil) + (simple-keyword? nil) + (qualified-keyword? nil) + (every? any? []) + (not-every? any? []) + (not-any? any? []) + (even? 0) + (odd? 0) + (volatile? nil) + (equiv-map {} {}) + (map-entry? nil) + (key-test :a :a) + (regexp? nil) + (print-meta? {} nil) + (delay? nil) + (uuid? nil) + (tagged-literal? nil) + (cljs.pprint/float? nil))) + +(deftest cljs-2866-test + ;; Here we are testing that in the JavaScript emitted, + ;; the gensym generated for curr is being passed to dec + (is (zero? ((fn [x] + (while (pos? @x) + (let [curr @x] + (when (number? curr) + (reset! x (dec curr))))) + @x) (atom 1))))) + +(def foo-var-3068 (merge)) + +(deftest cljs-3068-test + ;; Here we are essentially testing that valid JavaScript is + ;; emitted. Without the fix, tests fail with invalid JavaScript + ;; that cannot be parse by GCC, etc. + (if foo-var-3068 foo-var-3068) + (is true)) diff --git a/src/test/cljs/cljs/inference_util.clj b/src/test/cljs/cljs/inference_util.clj new file mode 100644 index 0000000000..b2b2ef9f15 --- /dev/null +++ b/src/test/cljs/cljs/inference_util.clj @@ -0,0 +1,29 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.inference-util) + +(defmacro truth_-called? + "Returns whether cljs.core/truth_ is called when evaluating a form as the + test of an if." + [form] + `(let [called?# (volatile! false)] + (with-redefs [cljs.core/truth_ (fn [x#] + (vreset! called?# true) + (cljs.core/truth_ x#))] + (if ~form 1 2)) + @called?#)) + +(defmacro truth_-not-called? + "Returns whether cljs.core/truth_ is not called when evaluating ecah of + forms as the test of an if." + [& forms] + `(do + ~@(map (fn [form] + `(cljs.test/is (not (truth_-called? ~form)))) + forms))) diff --git a/src/test/cljs/cljs/interop_test.cljs b/src/test/cljs/cljs/interop_test.cljs new file mode 100644 index 0000000000..f736ea15ca --- /dev/null +++ b/src/test/cljs/cljs/interop_test.cljs @@ -0,0 +1,75 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.interop-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is are]])) + +(deftest test-obj-equiv + (testing "Object equiv method" + (is (.equiv :foo :foo)) + (is (.equiv 'foo 'foo)) + ;; .equiv is not a standard thing, primarily for interop + ;; in transit-js, probably not a concern for lite-mode users + (when-not LITE_MODE + (is (.equiv {:foo 1 :bar 2} {:foo 1 :bar 2}))) + (is (.equiv [1 2 3] [1 2 3])) + (is (.equiv '(1 2 3) '(1 2 3))) + (is (.equiv (map inc [1 2 3]) (map inc [1 2 3]))) + ;; .equiv is not a standard thing, primarily for interop + ;; in transit-js, probably not a concern for lite-mode users + (when-not LITE_MODE + (is (.equiv #{:cat :dog :bird} #{:cat :dog :bird}))))) + +(deftest test-es6-interfaces + (testing "ES6 collection interfaces" + (let [iter (es6-iterator [1 2 3])] + (testing "basic iterations" + (is (= (.-value (.next iter)) 1)) + (is (= (.-value (.next iter)) 2)) + (is (= (.-value (.next iter)) 3)) + (is (.-done (.next iter))))) + (is (.has {:foo "bar"} :foo)) + (is (= (.get {:foo "bar"} :foo) "bar")) + (is (= (.get {:foo "bar"} :bar :default) :default)) + (let [iter (.keys {:foo "bar" :baz "woz"})] + (testing "map key iteration" + (is (#{:foo :baz} (.-value (.next iter)))) + (is (#{:foo :baz} (.-value (.next iter)))) + (is (.-done (.next iter))))) + (let [eiter (.entries {:foo "bar" :baz "woz"})] + (testing "map entry iteration" + (let [entries #{(seq #js [:foo "bar"]) (seq #js [:baz "woz"])}] + (is (entries (seq (.-value (.next eiter))))) + (is (entries (seq (.-value (.next eiter)))))) + (is (.-done (.next eiter))))) + (let [iter (.values {:foo "bar" :baz "woz"})] + (testing "map value iteration" + (is (#{"bar" "woz"} (.-value (.next iter)))) + (is (#{"bar" "woz"} (.-value (.next iter)))) + (is (.-done (.next iter))))) + (is (.has #{:cat :bird :dog} :bird)) + (let [iter (.keys #{:cat :bird :dog})] + (testing "set key iteration" + (is (#{:cat :bird :dog} (.-value (.next iter)))) + (is (#{:cat :bird :dog} (.-value (.next iter)))) + (is (#{:cat :bird :dog} (.-value (.next iter)))) + (is (.-done (.next iter))))) + (let [iter (.entries #{:cat :bird :dog})] + (testing "set entry iteration" + (is (#{[:cat :cat] [:bird :bird] [:dog :dog]} (seq (.-value (.next iter))))) + (is (#{[:cat :cat] [:bird :bird] [:dog :dog]} (seq (.-value (.next iter))))) + (is (#{[:cat :cat] [:bird :bird] [:dog :dog]} (seq (.-value (.next iter))))) + (is (.-done (.next iter))))) + (let [iter (.values #{:cat :bird :dog})] + (testing "set value iteration" + (is (#{:cat :bird :dog} (.-value (.next iter)))) + (is (#{:cat :bird :dog} (.-value (.next iter)))) + (is (#{:cat :bird :dog} (.-value (.next iter)))) + (is (.-done (.next iter))))) + )) diff --git a/src/test/cljs/cljs/invoke_test.cljs b/src/test/cljs/cljs/invoke_test.cljs new file mode 100644 index 0000000000..9760b22417 --- /dev/null +++ b/src/test/cljs/cljs/invoke_test.cljs @@ -0,0 +1,50 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.invoke-test + (:require [goog.string :as gstr])) + +(defn variadic-fn [& args]) + +(variadic-fn 1 2 3) + +(defn multi-fn + ([a] a) + ([a b] a)) + +(defn hof-fn-expr-should-be-bound + [funexpr0 normal-arg] + ((complement funexpr0) normal-arg)) + +(defn hof-arg-should-be-bound + [hofinvoke inv-arg0] + (hofinvoke (inv-arg0))) + +(defn hof-fn-expr+args-should-be-bound + [funexpr1 inv-arg1] + ((complement funexpr1) (inv-arg1))) + +;; A keyword should not be bound in a let: +(def foo (delay + (:dont-bind-this js/x))) + +(multi-fn 2) + +(gstr/urlEncode "foo") + +(js/goog.string.urlDecode "bar") + +(declare ^{:arglists '([a b])} declared-fn) + +(declared-fn 1 2) + +(defrecord Foo [foo-field-a foo-field-b]) + +(def foo-record (->Foo 1 2)) + +(:foo-field-a foo-record) diff --git a/src/test/cljs/cljs/iterator_test.cljs b/src/test/cljs/cljs/iterator_test.cljs new file mode 100644 index 0000000000..84e9e6a4d0 --- /dev/null +++ b/src/test/cljs/cljs/iterator_test.cljs @@ -0,0 +1,47 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.iterator-test + (:require [cljs.test :refer-macros [deftest testing is are run-tests]])) + +(defn seq-iter-match + [coll] + (let [i (-iterator coll)] + (loop [s (seq coll) + n 0] + (if (seq s) + (do + (when-not (.hasNext i) + (throw + (js/Error. + (str "Iterator exhausted before seq at(" n ")" )))) + (let [iv (.next i) + sv (first s)] + (when-not (= iv sv) + (throw + (js/Error. + (str "Iterator value " iv " and seq value " sv " did not match at ( " n ")"))))) + (recur (rest s) (inc n))) + (if (.hasNext i) + (throw + (js/Error. + (str "Seq exhausted before iterator at (" n ")"))) + true))))) + +(defrecord TestIterRec [a b]) + +(deftest coll-iter-seq-match + (testing "Direct iterators match sequences" + (let [test-map (apply hash-map (range 200)) + test-set (apply hash-set (range 200)) + test-queue (into cljs.core.PersistentQueue.EMPTY (vec (range 100))) + test-record (into (TestIterRec. 1 2) {:c 3 :d 4})] + (is (= true (seq-iter-match test-map))) + (is (= true (seq-iter-match test-set))) + (is (= true (seq-iter-match test-queue))) + (is (= true (seq-iter-match test-record)))))) \ No newline at end of file diff --git a/src/test/cljs/cljs/keyword_macros.clj b/src/test/cljs/cljs/keyword_macros.clj new file mode 100644 index 0000000000..2047a6614c --- /dev/null +++ b/src/test/cljs/cljs/keyword_macros.clj @@ -0,0 +1,13 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.keyword-macros) + +(defmacro add + [a b] + `(+ ~a ~b)) diff --git a/src/test/cljs/cljs/keyword_other.cljs b/src/test/cljs/cljs/keyword_other.cljs new file mode 100644 index 0000000000..4a4624b95f --- /dev/null +++ b/src/test/cljs/cljs/keyword_other.cljs @@ -0,0 +1,12 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.keyword-other) + +(defn foo [a b] + (+ a b)) diff --git a/src/test/cljs/cljs/keyword_test.cljs b/src/test/cljs/cljs/keyword_test.cljs new file mode 100644 index 0000000000..462281305d --- /dev/null +++ b/src/test/cljs/cljs/keyword_test.cljs @@ -0,0 +1,18 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.keyword-test + (:require-macros [cljs.keyword-macros :as macros] + [cljs.test :refer [deftest is]]) + (:require [cljs.keyword-other :as other] + [cljs.test])) + +(deftest test-keyword + (is (= ::bar :cljs.keyword-test/bar)) + (is (= ::other/foo :cljs.keyword-other/foo)) + (is (= ::macros/foo :cljs.keyword-macros/foo))) diff --git a/src/test/cljs/cljs/letfn_test.cljs b/src/test/cljs/cljs/letfn_test.cljs new file mode 100644 index 0000000000..b66ae6c01b --- /dev/null +++ b/src/test/cljs/cljs/letfn_test.cljs @@ -0,0 +1,28 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.letfn-test + (:require [cljs.test :refer-macros [deftest is]])) + +(deftest test-letfn + (letfn [(ev? [x] + (if (zero? x) + true + (od? (dec x)))) + (od? [x] + (if (zero? x) + false + (ev? (dec x))))] + (is (ev? 0)) + (is (ev? 10)) + (is (not (ev? 1))) + (is (not (ev? 11))) + (is (not (od? 0))) + (is (not (od? 10))) + (is (od? 1)) + (is (od? 11)))) diff --git a/src/test/cljs/cljs/lite_collections_test.cljs b/src/test/cljs/cljs/lite_collections_test.cljs new file mode 100644 index 0000000000..56d44d0ebd --- /dev/null +++ b/src/test/cljs/cljs/lite_collections_test.cljs @@ -0,0 +1,46 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.lite-collections-test + (:require [cljs.test :refer [deftest testing is]])) + +;; NOTE: ** this namespace must be tested with :lite-mode true ** + +(deftest test-obj-map + (let [a (. ObjMap -EMPTY) + b {}] + (is (identical? a b))) + (let [a {:foo 1}] + (is (== 1 (:foo a))))) + +(deftest test-set-lite-with-set + (is (= (set-lite []) (set []))) + (is (= (set []) (set-lite []))) + (is (= (set-lite ["foo" "bar"]) (set-lite ["foo" "bar"]))) + (is (= (set-lite ["foo" "bar"]) (set-lite #js ["foo" "bar"]))) + (is (= (set-lite [(MapEntry. 1 2 nil)]) + (set [(MapEntry. 1 2 nil)])))) + +(deftest test-obj-map-clj->js + (= 1 (aget (clj->js (obj-map :x 1)) "x")) + (= 1 (aget (clj->js {:x 1}) "x"))) + +(deftest test-unchanged-identical? + (let [m (obj-map :foo 1)] + (identical? m (assoc m :foo 1))) + (let [m (hash-map-lite :foo 1)] + (identical? m (assoc m :foo 1))) + (let [s (set-lite [:foo])] + (identical? s (conj s :foo)))) + +(comment + + (require '[cljs.lite-collections-test] :reload) + (cljs.test/run-tests) + + ) diff --git a/src/test/cljs/cljs/macro_test.cljs b/src/test/cljs/cljs/macro_test.cljs new file mode 100644 index 0000000000..6c7354d56c --- /dev/null +++ b/src/test/cljs/cljs/macro_test.cljs @@ -0,0 +1,37 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.macro-test + (:refer-clojure :exclude [==]) + (:require [cljs.test :as test :refer-macros [deftest is]]) + (:use-macros [cljs.macro-test.macros :only [== sm-cljs-3027]]) + (:require-macros [cljs.macro-test.cljs2852] + [single-seg-macros])) + +(deftest test-macros + (is (= (== 1 1) 2))) + +(deftest macroexpansion + (is (= 1 (macroexpand-1 '1))) + (is (= '(if true (do 1)) (macroexpand-1 '(when true 1)))) + (is (= 1 (macroexpand '1))) + (is (= '(if true (do 1)) (macroexpand '(when true 1))))) + +(deftest test-cljs-2283 + (is (= ":a" (first (js-keys (js-obj :a 1)))))) + +(deftest test-cljs-2852 + (is (= '([x])) (cljs.macro-test.cljs2852/beta)) + (is (= '([x] [x y])) (cljs.macro-test.cljs2852/delta)) + (is (= '([x] [x & xs])) (cljs.macro-test.cljs2852/zeta))) + +(deftest test-cljs-3027 + (is (= {"a" "b"} (sm-cljs-3027)))) + +(deftest test-cljs-3413 + (is (= 5 (single-seg-macros/test-macro 2 3)))) diff --git a/src/test/cljs/cljs/macro_test/cljs2261.clj b/src/test/cljs/cljs/macro_test/cljs2261.clj new file mode 100644 index 0000000000..380be3f3a6 --- /dev/null +++ b/src/test/cljs/cljs/macro_test/cljs2261.clj @@ -0,0 +1,12 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.macro-test.cljs2261) + +(defmacro cake [] + `(X.)) diff --git a/src/test/cljs/cljs/macro_test/cljs2261.cljs b/src/test/cljs/cljs/macro_test/cljs2261.cljs new file mode 100644 index 0000000000..64842aa100 --- /dev/null +++ b/src/test/cljs/cljs/macro_test/cljs2261.cljs @@ -0,0 +1,11 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.macro-test.cljs2261) + +(defrecord X []) diff --git a/src/test/cljs/cljs/macro_test/cljs2852.clj b/src/test/cljs/cljs/macro_test/cljs2852.clj new file mode 100644 index 0000000000..e8f0a01441 --- /dev/null +++ b/src/test/cljs/cljs/macro_test/cljs2852.clj @@ -0,0 +1,29 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.macro-test.cljs2852) + +(defmacro alpha + ([x])) + +(defmacro beta [] + `'~(:arglists (meta #'alpha))) + +(defmacro gamma + ([x]) + ([x y])) + +(defmacro delta [] + `'~(:arglists (meta #'gamma))) + +(defmacro epsilon + ([x]) + ([x & xs])) + +(defmacro zeta [] + `'~(:arglists (meta #'epsilon))) diff --git a/src/test/cljs/cljs/macro_test/macros.clj b/src/test/cljs/cljs/macro_test/macros.clj new file mode 100644 index 0000000000..d572503623 --- /dev/null +++ b/src/test/cljs/cljs/macro_test/macros.clj @@ -0,0 +1,20 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.macro-test.macros + (:refer-clojure :exclude [==])) + +(defmacro == [a b] + `(+ ~a ~b)) + +(defmacro sm-cljs-3027 [] + (sorted-map "a" "b")) + +(defmacro await! [x] + ;; resolves as clojure.core/await, not cljs.core/await + `(await ~x)) diff --git a/src/test/cljs/cljs/map_entry_test.cljs b/src/test/cljs/cljs/map_entry_test.cljs new file mode 100644 index 0000000000..809b567e4f --- /dev/null +++ b/src/test/cljs/cljs/map_entry_test.cljs @@ -0,0 +1,147 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.map-entry-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is are]])) + +(defn map-entry-interface-tests + "Tests that a MapEntry implements all the expected interfaces correctly. + Expects a MapEntry type with key `:key` and a val `:val`." + [e] + (testing "map entry interfaces" + (testing "IMapEntry" + (testing "-key" + (is (= :key (-key e)))) + (testing "-val" + (is (= :val (-val e))))) + + (testing "IEquiv" + (testing "-equiv" + (are [x y] (-equiv x y) + e [:key :val] + e '(:key :val)))) + + (testing "ILookup" + (testing "-lookup 2-arity" + (are [x y] (= x y) + :key (-lookup e 0) + :val (-lookup e 1) + nil (-lookup e 2) + nil (-lookup e -1))) + (testing "-lookup 3-arity" + (are [x y] (= x y) + :key (-lookup e 0 :not-found) + :val (-lookup e 1 :not-found) + :not-found (-lookup e 2 :not-found) + :not-found (-lookup e -1 :not-found)))) + + (testing "IStack" + (testing "-peek" + (is (= :val (-peek e)))) + (testing "-pop" + (is (vector? (-pop e))) + (is (= [:key] (-pop e))))) + + (testing "ICollection" + (testing "-conj" + (is (vector? (-conj e :thing))) + (is (= [:key :val :thing] (-conj e :thing))))) + + (testing "IEmptyableCollection" + (testing "-empty" + (is (= (if (instance? PersistentVector e) [] nil) (empty e))))) + + (testing "ISequential" + (is (satisfies? ISequential e))) + + (testing "ISeqable" + (testing "-seq" + (is (= (list :key :val) (-seq e))))) + + (testing "IReversible" + (testing "-rseq" + (is (= (list :val :key) (-rseq e))))) + + (testing "ICounted" + (testing "-count" + (is (= 2 (-count e))))) + + (testing "IIndexed" + (testing "-nth 2-arity" + (are [x y] (= x y) + :key (-nth e 0) + :val (-nth e 1)) + (is (thrown? js/Error (-nth e 2))) + (is (thrown? js/Error (-nth e -1)))) + (testing "-nth 3-arity" + (are [x y] (= x y) + :key (-nth e 0 :not-found) + :val (-nth e 1 :not-found) + :not-found (-nth e 2 :not-found) + :not-found (-nth e -1 :not-found)))) + + (testing "IAssociative" + (testing "-assoc" + (are [x y] (= x y) + [:new :val] (-assoc e 0 :new) + [:key :new] (-assoc e 1 :new) + [:key :val :new] (-assoc e 2 :new))) + (testing "-contains-key?" + (are [x y] (= x y) + true (-contains-key? e 0) + true (-contains-key? e 1) + false (-contains-key? e 2) + false (-contains-key? e -1)))) + + (testing "IVector" + (testing "-assoc-n" + (are [x y] (= x y) + [:new :val] (-assoc-n e 0 :new) + [:key :new] (-assoc-n e 1 :new) + [:key :val :new] (-assoc-n e 2 :new)))) + + (testing "IReduce" + (testing "-reduce 2-arity" + (is (= [:key :val] (-reduce e (fn [r e] [r e]))))) + (testing "-reduce 3-arity" + (is (= [:key :val] (-reduce e conj []))))) + + (testing "IFind" + (testing "-find" + (are [x y] (= x y) + [0 :key] (-find e 0) + [1 :val] (-find e 1) + nil (-find e 2) + nil (-find e -1)))) + + (testing "IFn" + (testing "-invoke 2-arity" + (are [x y] (= x y) + :key (e 0) + :val (e 1)) + (is (thrown? js/Error (e 2))) + (is (thrown? js/Error (e -1)))) + (testing "-invoke 3-arity" + (are [x y] (= x y) + :key (e 0 :not-found) + :val (e 1 :not-found) + :not-found (e 2 :not-found) + :not-found (e -1 :not-found)))) + + (testing "IComparable" + (testing "-compare" + (is (zero? (-compare e [:key :val]))))))) + +(deftest all-map-entry-tests + (testing "BlackNode" + (map-entry-interface-tests (BlackNode. :key :val nil nil nil))) + (testing "RedNode" + (map-entry-interface-tests (RedNode. :key :val nil nil nil))) + (testing "MapEntry" + (map-entry-interface-tests (MapEntry. :key :val nil)))) diff --git a/src/test/cljs/cljs/metadata_test.cljc b/src/test/cljs/cljs/metadata_test.cljc new file mode 100644 index 0000000000..8f5e994113 --- /dev/null +++ b/src/test/cljs/cljs/metadata_test.cljc @@ -0,0 +1,171 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.metadata-test + (:require + #?(:cljs [cljs.test :refer-macros [deftest testing is]] + :clj [clojure.test :refer [deftest testing is]]))) + +(defn seq-interface-tests + "Tests that all seqs handle metadata correctly." + [s] + (when (seq s) + (testing "seqs can have metadata" + (is #?(:clj (instance? clojure.lang.IObj s) + :cljs (satisfies? IMeta s))) + (let [m {:meta :data}] + (is (= m (meta (with-meta s m))))))) + + (when (seq s) + (let [s (with-meta s {:meta :data})] + (testing "next should have nil metadata" + (is (nil? (meta (next s))))) + (testing "rest should have nil metadata" + (is (nil? (meta (rest s))))) + (testing "empty should have nil metadata" + (is (nil? (meta (empty s)))))))) + + +(defn coll-interface-tests + "Tests that all collections handle metadata correctly" + [coll] + (testing "collections can have metadata" + (is #?(:clj (instance? clojure.lang.IObj coll) + :cljs (satisfies? IMeta coll))) + (let [m {:meta :data}] + (is (= coll (with-meta coll m))) + (is (= m (meta (with-meta coll m)))))) + + (testing "conj keeps metadata" + (let [m {:meta :data} + coll (with-meta coll m) + thing (if (map? coll) [:k :v] :x)] + (is (= m (meta (conj coll thing)))))) + + (testing "empty keeps metadata" + (let [m {:meta :data} + coll (with-meta coll m)] + (is (= m (meta (empty coll)))))) + + (testing "seq has no metadata" + (let [m {:meta :data} + coll (with-meta coll m)] + (is (nil? (meta (seq coll))))))) + + +(defn disj-interface-tests + "Tests that collections supporting disj handle metadata correctly" + [coll] + (testing "disj keeps metadata" + (let [m {:meta :data} + coll (with-meta (conj coll :k) m)] + (is (= m (meta (disj coll :k))))))) + + +(deftest metadata-tests + (testing "Collection" + (testing "PersistentVector" + (testing "Empty" + (coll-interface-tests [])) + (testing "Medium" + (coll-interface-tests [0 1 2 3])) + (testing "Large" + (coll-interface-tests (vec (range 100))))) + + (testing "PersistentHashSet" + (testing "Empty" + (coll-interface-tests (hash-set)) + (disj-interface-tests (hash-set))) + (testing "Medium" + (coll-interface-tests (hash-set 0 1 2 3 4 5)) + (disj-interface-tests (hash-set 0 1 2 3 4 5))) + (testing "Large" + (coll-interface-tests (apply hash-set (range 100))) + (disj-interface-tests (apply hash-set (range 100))))) + + (testing "PersistentHashMap" + (testing "Empty" + (coll-interface-tests (hash-map))) + (testing "Medium" + (coll-interface-tests (hash-map 0 1 2 3 4 5))) + (testing "Large" + (coll-interface-tests (apply hash-map (range 100))))) + + (testing "PersistentArrayMap" + (testing "Empty" + (coll-interface-tests (array-map))) + (testing "Medium" + (coll-interface-tests (array-map 0 1 2 3 4 5))))) + + + (testing "Seq over collections" + (testing "PersistentVector" + (testing "Empty" + (seq-interface-tests (seq [])) + (seq-interface-tests (rseq []))) + (testing "Medium" + (testing "seq" + (seq-interface-tests (seq [0 1 2 3]))) + (testing "rseq" + (seq-interface-tests (rseq [0 1 2 3])))) + (testing "Large" + (testing "seq" + (seq-interface-tests (seq (vec (range 100))))) + (testing "rseq" + (seq-interface-tests (rseq (vec (range 100))))))) + + (testing "PersistentHashSet" + (testing "Empty" + (seq-interface-tests (seq (hash-set)))) + (testing "Medium" + (seq-interface-tests (seq (hash-set 0 1 2 3 4 5)))) + (testing "Large" + (seq-interface-tests (seq (apply hash-set (range 100)))))) + + (testing "PersistentHashMap" + (testing "Empty" + (seq-interface-tests (seq (hash-map)))) + (testing "Medium" + (seq-interface-tests (seq (hash-map 0 1 2 3 4 5)))) + (testing "Large" + (seq-interface-tests (seq (apply hash-map (range 100))))) + (testing "KeySeq" + (seq-interface-tests (keys (apply hash-map (range 10))))) + (testing "ValSeq" + (seq-interface-tests (vals (apply hash-map (range 10)))))) + + (testing "PersistentArrayMap" + (testing "Empty" + (seq-interface-tests (seq (array-map)))) + (testing "Medium" + (seq-interface-tests (seq (array-map 0 1 2 3 4 5)))) + (testing "KeySeq" + (seq-interface-tests (keys (apply array-map (range 10))))) + (testing "ValSeq" + (seq-interface-tests (vals (apply array-map (range 10)))))) + + (testing "PersistentTreeMap" + (seq-interface-tests (seq (sorted-map :a 1 :b 2 :c 3))))) + + (testing "generators" + (testing "cycle" + (seq-interface-tests (cycle [1 2 3]))) + (testing "range" + (seq-interface-tests (range 10))) + (testing "repeat" + (seq-interface-tests (repeat 10 :x))) + (testing "iterate" + (seq-interface-tests (iterate inc 0)))) + + (testing "ChunkedCons" + (let [chunked-cons (seq (map inc (vec (range 100))))] + (seq-interface-tests chunked-cons))) + + (testing "Cons" + (seq-interface-tests (cons 'a ())) + (seq-interface-tests (cons 'b (cons 'a ()))))) diff --git a/src/test/cljs/cljs/new_new_test.cljs b/src/test/cljs/cljs/new_new_test.cljs new file mode 100644 index 0000000000..26d6b65180 --- /dev/null +++ b/src/test/cljs/cljs/new_new_test.cljs @@ -0,0 +1,125 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.new-new-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is]] + [clojure.string :as s] + [clojure.set :as set])) + +(defprotocol IProtocolWithDocStrings + (-method1 [this] "some doc") + (-method2 [this] "")) + +(defprotocol IBar (-bar [this x])) + +(defn baz [f] + (reify + IBar + (-bar [_ x] + (f x)))) + +(deftest test-405 + (is (= 2 (-bar (baz inc) 1)))) + +(defprotocol IWoz + (-woz [this])) + +(def noz []) + +(deftest test-414 + (testing "Testing CLJS-414, specify" + (is (= (specify noz IWoz (-woz [_] :boz)) noz)) + (is (not (identical? (specify noz IWoz (-woz [_] :boz)) noz))) + (is (= (-woz (specify noz IWoz (-woz [this] this))) noz)) + (is (= (-woz (specify noz IWoz (-woz [_] :boz))) :boz)))) + +(defrecord Person [firstname lastname]) +(defrecord A []) +(defrecord C [a b c]) +(defrecord A' [x]) +(defrecord B' [x]) +(defrecord FooComparable [x] + IComparable + (-compare [_ o] (compare x (.-x o)))) + +(deftest test-records + (let [fred (Person. "Fred" "Mertz") + fred-too (Person. "Fred" "Mertz") + ethel (with-meta (assoc (Person. "Ethel" "Mertz") :husband :fred) + {:married true}) + ethel-too (with-meta (assoc (Person. "Ethel" "Mertz") :husband :fred) + {:married true}) + letters (C. "a" "b" "c") + more-letters (assoc letters :d "d" :e "e" :f "f")] + (testing "Testing records" + (is (record? fred)) + (is (not (record? {}))) + (is (= (:firstname fred) "Fred")) + (is (= fred fred-too)) + (is (false? (= fred nil))) + (is (false? (= nil fred))) + (is (= (meta ethel) {:married true})) + (is (= ethel ethel-too)) + (is (= (map->Person {:firstname "Fred" :lastname "Mertz"}) fred)) + (is (= (->Person "Fred" "Mertz") fred)) + (is (= (count fred) 2)) + (is (= (count ethel) 3)) + (is (= (conj fred {:wife :ethel :friend :ricky}) + (map->Person {:firstname "Fred" :lastname "Mertz" :wife :ethel :friend :ricky}))) + (is (= (conj fred {:lastname "Flintstone"}) + (map->Person {:firstname "Fred" :lastname "Flintstone"}))) + (is (= (assoc fred :lastname "Flintstone") + (map->Person {:firstname "Fred" :lastname "Flintstone"}))) + (is (= (assoc fred :wife :ethel) + (map->Person {:firstname "Fred" :lastname "Mertz" :wife :ethel}))) + (is (= (dissoc ethel :husband) + (map->Person {:firstname "Ethel" :lastname "Mertz"}))) + (is (= (reduce-kv assoc {:age 30} fred) + {:age 30 :firstname "Fred" :lastname "Mertz"})) + (is (= {:foo 'bar} (meta (with-meta (A.) {:foo 'bar})))) + (is (= 'bar (:foo (assoc (A.) :foo 'bar)))) + (is (= (set (keys letters)) #{:a :b :c})) + (is (= (set (keys more-letters)) #{:a :b :c :d :e :f})) + (is (= (set (keys (dissoc more-letters :d))) #{:a :b :c :e :f})) + (is (= (set (keys (dissoc more-letters :d :e))) #{:a :b :c :f})) + (is (= (set (keys (dissoc more-letters :d :e :f))) #{:a :b :c})) + (is (not= (A'. nil) (B'. nil))) + (is (satisfies? IComparable (->FooComparable 1)))))) + +(deftype FnLike [] + IFn + (-invoke [_] :a) + (-invoke [_ a] :b) + (-invoke [_ a b] :c)) + +(deftype FnLikeB [a] + IFn + (-invoke [_] a)) + +(deftest test-ifn + (testing "Testing IFn implementations" + (is (= :a ((FnLike.)))) + (is (= :b ((FnLike.) 1))) + (is (= :c ((FnLike.) 1 2))) + (is (= [:b :b :b] (map (FnLike.) [0 0 0]))) + (is (= 1 ((FnLikeB. 1)))) + )) + +(deftype TypeBasis [a b]) + +(defrecord RecordBasis [c d e]) + +(deftest test-get-basis + (is (= (.getBasis TypeBasis) '[a b])) + (is (= (.getBasis RecordBasis) '[c d e]))) + +(deftype PositionalFactoryTest [x]) + +(deftest test-515 + (is (== 1 (.-x (->PositionalFactoryTest 1))))) diff --git a/src/test/cljs/cljs/npm_deps_test.cljs b/src/test/cljs/cljs/npm_deps_test.cljs new file mode 100644 index 0000000000..59b3c658cd --- /dev/null +++ b/src/test/cljs/cljs/npm_deps_test.cljs @@ -0,0 +1,39 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.npm-deps-test + (:refer-clojure :exclude [array vector]) + (:require [cljs.test :refer [deftest is]] + ["lodash/array" :as array :refer [slice] :rename {slice slc}] + [calculator :as vector :refer [add] :rename {add plus}] + [es6_calc] + [es6_default_hello :as es6hello])) + +(def array #js [1 2 3]) + +(def vector [1]) + +(deftest test-module-processing + (is (= (array/nth #js [1 2 3] 1) 2)) + ;; rename works + (is (= (array-seq (slc #js [1 2 3] 1)) [2 3]))) + +(deftest test-global-exports + (is (= (plus 1 2) 3))) + +(deftest test-cljs-2224 + ;; array should be correctly resolved in the current NS (shadows module) + (is (= (array-seq array) [1 2 3])) + ;; same should happen with global-exports + (is (= vector [1]))) + +(deftest test-cljs-2286 + (is (= 3 (es6_calc/calculator.add 1 2)))) + +(deftest test-cljs-1620 + (is (= "Hello, world!" (es6hello/default)))) diff --git a/src/test/cljs/cljs/ns_test.cljs b/src/test/cljs/cljs/ns_test.cljs new file mode 100644 index 0000000000..7243eae0a4 --- /dev/null +++ b/src/test/cljs/cljs/ns_test.cljs @@ -0,0 +1,54 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.ns-test + (:refer-clojure :exclude [+ for] :rename {mapv core-mapv}) + (:require-macros [clojure.core :as lang :refer [when when-let] :rename {when always + when-let always-let}] + [cljs.test :refer [deftest is]]) + (:require [goog :as goog-alias] + [cljs.test] + [cljs.ns-test.foo :refer [baz]] + [clojure.set :as s :refer [intersection] :rename {intersection itsc}] + [cljs.analyzer :as ana] + [fake.ns :as-alias fake]) + (:use [cljs.ns-test.bar :only [quux]])) + +(def + -) + +(deftest test-ns + (is (= 4 (clojure.core/+ 2 1 1))) + (is (= 0 (cljs.ns-test/+ 2 1 1))) + (is (= 0 (+ 2 1 1))) + (is (= 123 (baz))) + (is (= 123 (quux))) + + (is (= (range 5) (lang/for [x (range 5)] x))) + (is (= #{1 2 3} (s/union #{1} #{2 3})))) + +(deftest test-cljs-1508 + (is (= (itsc #{1 2 3} #{2}) #{2})) + (is (= #'itsc #'clojure.set/intersection)) + (is (= itsc clojure.set/intersection)) + (is (= (always true 42) 42)) + (is (= (core-mapv inc [1 2]) [2 3])) + (is (= (always-let [foo 42] foo) 42))) + +(deftest test-cljs-1677 + (let [array-like #js {:length 3}] + (is (.isArrayLike js/goog array-like)) + (is (goog/isArrayLike array-like)) + (is (goog-alias/isArrayLike array-like)))) + +(deftest test-cljs-3399 + (is (= ::fake/foo :fake.ns/foo) + (is (= `fake/foo 'fake.ns/foo)))) + +(deftest test-cljs-2292 + (is (= false (exists? mapv))) + (is (= true (exists? core-mapv)))) diff --git a/src/test/cljs/cljs/ns_test/bar.cljs b/src/test/cljs/cljs/ns_test/bar.cljs new file mode 100644 index 0000000000..5a55ea22b6 --- /dev/null +++ b/src/test/cljs/cljs/ns_test/bar.cljs @@ -0,0 +1,11 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.ns-test.bar) + +(defn quux [] 123) diff --git a/src/test/cljs/cljs/ns_test/foo.cljs b/src/test/cljs/cljs/ns_test/foo.cljs new file mode 100644 index 0000000000..2da20eae14 --- /dev/null +++ b/src/test/cljs/cljs/ns_test/foo.cljs @@ -0,0 +1,23 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.ns-test.foo + (:require [cljs.test :refer-macros [deftest is]] + [made.up.lib :as-alias lib])) + +(defn baz [] 123) + +(def kw ::foo) +(def qkw '::foo) + +(deftest test-namespaced-keywords + (is (= (str kw) ":cljs.ns-test.foo/foo")) + (is (= (str qkw) ":cljs.ns-test.foo/foo"))) + +(deftest test-as-alias-keywords + (is (keyword-identical? ::lib/foo :made.up.lib/foo))) diff --git a/src/test/cljs/cljs/other_functions_test.cljs b/src/test/cljs/cljs/other_functions_test.cljs new file mode 100644 index 0000000000..52b2736f6d --- /dev/null +++ b/src/test/cljs/cljs/other_functions_test.cljs @@ -0,0 +1,379 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +; Author: Frantisek Sodomka + + +(ns cljs.other-functions-test + (:require [clojure.test :refer [deftest are is]])) + +(deftest test-identity + ; exactly 1 argument needed + ; (is (thrown? IllegalArgumentException (identity))) + ; (is (thrown? IllegalArgumentException (identity 1 2))) + + (are [x] (= (identity x) x) + nil + false true + 0 42 + 0.0 3.14 + 0M 1M + \c + "" "abc" + 'sym + :kw + () '(1 2) + [] [1 2] + {} {:a 1 :b 2} + #{} #{1 2} ) + + ; evaluation + (are [x y] (= (identity x) y) + (+ 1 2) 3 + (> 5 0) true )) + + +(deftest test-name + (are [x y] (= x (name y)) + "foo" :foo + "bar" 'bar + "quux" "quux")) + +(deftest test-fnil + (let [f1 (fnil vector :a) + f2 (fnil vector :a :b) + f3 (fnil vector :a :b :c)] + (are [result input] (= result [(apply f1 input) (apply f2 input) (apply f3 input)]) + [[1 2 3 4] [1 2 3 4] [1 2 3 4]] [1 2 3 4] + [[:a 2 3 4] [:a 2 3 4] [:a 2 3 4]] [nil 2 3 4] + [[:a nil 3 4] [:a :b 3 4] [:a :b 3 4]] [nil nil 3 4] + [[:a nil nil 4] [:a :b nil 4] [:a :b :c 4]] [nil nil nil 4] + [[:a nil nil nil] [:a :b nil nil] [:a :b :c nil]] [nil nil nil nil])) + (are [x y] (= x y) + ((fnil + 0) nil 42) 42 + ((fnil conj []) nil 42) [42] + (reduce #(update-in %1 [%2] (fnil inc 0)) {} + ["fun" "counting" "words" "fun"]) + {"words" 1, "counting" 1, "fun" 2} + (reduce #(update-in %1 [(first %2)] (fnil conj []) (second %2)) {} + [[:a 1] [:a 2] [:b 3]]) + {:b [3], :a [1 2]})) + +; time assert comment doc + +; partial +; comp + +(deftest test-comp + (let [c0 (comp)] + (are [x] (= (identity x) (c0 x)) + nil + 42 + [1 2 3] + #{} + :foo) + (are [x y] (= (identity x) (c0 y)) + (+ 1 2 3) 6 + (keyword "foo") :foo))) + +; complement + +(deftest test-complement + (let [not-contains? (complement contains?)] + (is (= true (not-contains? [2 3 4] 5))) + (is (= false (not-contains? [2 3 4] 2)))) + (let [first-elem-not-1? (complement (fn [x] (= 1 (first x))))] + (is (= true (first-elem-not-1? [2 3]))) + (is (= false (first-elem-not-1? [1 2]))))) + +; constantly + +(deftest test-constantly + (let [c0 (constantly 10)] + (are [x] (= 10 (c0 x)) + nil + 42 + "foo"))) +;juxt + +(deftest test-juxt + ;; juxt for colls + (let [m0 {:a 1 :b 2} + a0 [1 2]] + (is (= [1 2] ((juxt :a :b) m0))) + (is (= [2 1] ((juxt fnext first) a0)))) + ;; juxt for fns + (let [a1 (fn [a] (+ 2 a)) + b1 (fn [b] (* 2 b))] + (is (= [5 6] ((juxt a1 b1) 3))))) + +;partial + +(deftest test-partial + (let [p0 (partial inc) + p1 (partial + 20) + p2 (partial conj [1 2])] + (is (= 41 (p0 40))) + (is (= 40 (p1 20))) + (is (= [1 2 3] (p2 3))))) + +; every-pred +(deftest test-every-pred + (are [result expr] (= result expr) + ;; 1 pred + true ((every-pred even?)) + true ((every-pred even?) 2) + true ((every-pred even?) 2 4) + true ((every-pred even?) 2 4 6) + true ((every-pred even?) 2 4 6 8) + true ((every-pred even?) 2 4 6 8 10) + false ((every-pred odd?) 2) + false ((every-pred odd?) 2 4) + false ((every-pred odd?) 2 4 6) + false ((every-pred odd?) 2 4 6 8) + false ((every-pred odd?) 2 4 6 8 10) + ;; 2 preds + true ((every-pred even? number?)) + true ((every-pred even? number?) 2) + true ((every-pred even? number?) 2 4) + true ((every-pred even? number?) 2 4 6) + true ((every-pred even? number?) 2 4 6 8) + true ((every-pred even? number?) 2 4 6 8 10) + false ((every-pred number? odd?) 2) + false ((every-pred number? odd?) 2 4) + false ((every-pred number? odd?) 2 4 6) + false ((every-pred number? odd?) 2 4 6 8) + false ((every-pred number? odd?) 2 4 6 8 10) + ;; 2 preds, short-circuiting + false ((every-pred number? odd?) 1 :a) + false ((every-pred number? odd?) 1 3 :a) + false ((every-pred number? odd?) 1 3 5 :a) + false ((every-pred number? odd?) 1 3 5 7 :a) + false ((every-pred number? odd?) 1 :a 3 5 7) + ;; 3 preds + true ((every-pred even? number? #(> % 0))) + true ((every-pred even? number? #(> % 0)) 2) + true ((every-pred even? number? #(> % 0)) 2 4) + true ((every-pred even? number? #(> % 0)) 2 4 6) + true ((every-pred even? number? #(> % 0)) 2 4 6 8) + true ((every-pred even? number? #(> % 0)) 2 4 6 8 10) + true ((every-pred number? even? #(> % 0)) 2 4 6 8 10 12) + false ((every-pred number? odd? #(> % 0)) 2) + false ((every-pred number? odd? #(> % 0)) 2 4) + false ((every-pred number? odd? #(> % 0)) 2 4 6) + false ((every-pred number? odd? #(> % 0)) 2 4 6 8) + false ((every-pred number? odd? #(> % 0)) 2 4 6 8 10) + false ((every-pred number? odd? #(> % 0)) 2 4 6 8 -10) + ;; 3 preds, short-circuiting + false ((every-pred number? odd? #(> % 0)) 1 :a) + false ((every-pred number? odd? #(> % 0)) 1 3 :a) + false ((every-pred number? odd? #(> % 0)) 1 3 5 :a) + false ((every-pred number? odd? #(> % 0)) 1 3 5 7 :a) + false ((every-pred number? odd? #(> % 0)) 1 :a 3 5 7) + ;; 4 preds + true ((every-pred even? number? #(> % 0) #(<= % 12))) + true ((every-pred even? number? #(> % 0) #(<= % 12)) 2) + true ((every-pred even? number? #(> % 0) #(<= % 12)) 2 4) + true ((every-pred even? number? #(> % 0) #(<= % 12)) 2 4 6) + true ((every-pred even? number? #(> % 0) #(<= % 12)) 2 4 6 8) + true ((every-pred even? number? #(> % 0) #(<= % 12)) 2 4 6 8 10) + true ((every-pred number? even? #(> % 0) #(<= % 12)) 2 4 6 8 10 12) + false ((every-pred number? odd? #(> % 0) #(<= % 12)) 2) + false ((every-pred number? odd? #(> % 0) #(<= % 12)) 2 4) + false ((every-pred number? odd? #(> % 0) #(<= % 12)) 2 4 6) + false ((every-pred number? odd? #(> % 0) #(<= % 12)) 2 4 6 8) + false ((every-pred number? odd? #(> % 0) #(<= % 12)) 2 4 6 8 10) + false ((every-pred number? odd? #(> % 0) #(<= % 12)) 2 4 6 8 14) + ;; 4 preds, short-circuiting + false ((every-pred number? odd? #(> % 0) #(<= % 12)) 1 :a) + false ((every-pred number? odd? #(> % 0) #(<= % 12)) 1 3 :a) + false ((every-pred number? odd? #(> % 0) #(<= % 12)) 1 3 5 :a) + false ((every-pred number? odd? #(> % 0) #(<= % 12)) 1 3 5 7 :a) + false ((every-pred number? odd? #(> % 0) #(<= % 12)) 1 :a 3 5 7) + ;; 5 preds + true ((every-pred even? number? #(> % 0) #(<= % 12) #(zero? (rem % 2)))) + true ((every-pred even? number? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2) + true ((every-pred even? number? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4) + true ((every-pred even? number? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6) + true ((every-pred even? number? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6 8) + true ((every-pred even? number? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6 8 10) + true ((every-pred number? even? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6 8 10 12) + false ((every-pred number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2) + false ((every-pred number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4) + false ((every-pred number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6) + false ((every-pred number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6 8) + false ((every-pred number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6 8 10) + false ((every-pred number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6 8 13) + ;; 5 preds, short-circuiting + false ((every-pred number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 1 :a) + false ((every-pred number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 1 3 :a) + false ((every-pred number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 1 3 5 :a) + false ((every-pred number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 1 3 5 7 :a) + false ((every-pred number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 1 :a 3 5 7) + ;; truthiness + true (reduce #(and % %2) + (for [i (range 1 25)] + (apply (apply every-pred (repeat i identity)) + (range i)))))) + +; some-fn + +(deftest test-some-fn + (are [result] (identity result) + ;; 1 pred + (not ((some-fn even?))) + ((some-fn even?) 2) + ((some-fn even?) 2 4) + ((some-fn even?) 2 4 6) + ((some-fn even?) 2 4 6 8) + ((some-fn even?) 2 4 6 8 10) + (not ((some-fn odd?) 2)) + (not ((some-fn odd?) 2 4)) + (not ((some-fn odd?) 2 4 6)) + (not ((some-fn odd?) 2 4 6 8)) + (not ((some-fn odd?) 2 4 6 8 10)) + ;; 2 preds + (not ((some-fn even? number?))) + ((some-fn even? number?) 2) + ((some-fn even? number?) 2 4) + ((some-fn even? number?) 2 4 6) + ((some-fn even? number?) 2 4 6 8) + ((some-fn even? number?) 2 4 6 8 10) + ((some-fn number? odd?) 2) + ((some-fn number? odd?) 2 4) + ((some-fn number? odd?) 2 4 6) + ((some-fn number? odd?) 2 4 6 8) + ((some-fn number? odd?) 2 4 6 8 10) + ;; 2 preds, short-circuiting + ((some-fn number? odd?) 1 :a) + ((some-fn number? odd?) 1 3 :a) + ((some-fn number? odd?) 1 3 5 :a) + ((some-fn number? odd?) 1 3 5 7 :a) + ((some-fn number? odd?) 1 :a 3 5 7) + ;; 3 preds + (not ((some-fn even? number? #(> % 0)))) + ((some-fn even? number? #(> % 0)) 2) + ((some-fn even? number? #(> % 0)) 2 4) + ((some-fn even? number? #(> % 0)) 2 4 6) + ((some-fn even? number? #(> % 0)) 2 4 6 8) + ((some-fn even? number? #(> % 0)) 2 4 6 8 10) + ((some-fn number? even? #(> % 0)) 2 4 6 8 10 12) + ((some-fn number? odd? #(> % 0)) 2) + ((some-fn number? odd? #(> % 0)) 2 4) + ((some-fn number? odd? #(> % 0)) 2 4 6) + ((some-fn number? odd? #(> % 0)) 2 4 6 8) + ((some-fn number? odd? #(> % 0)) 2 4 6 8 10) + ((some-fn number? odd? #(> % 0)) 2 4 6 8 -10) + ;; 3 preds, short-circuiting + ((some-fn number? odd? #(> % 0)) 1 :a) + ((some-fn number? odd? #(> % 0)) :a 1) + ((some-fn number? odd? #(> % 0)) 1 3 :a) + ((some-fn number? odd? #(> % 0)) :a 1 3) + ((some-fn number? odd? #(> % 0)) 1 3 5 :a) + ((some-fn number? odd? #(> % 0)) 1 :a 3 5 7) + ;; 4 preds + (not ((some-fn even? number? #(> % 0) #(<= % 12)))) + ((some-fn even? number? #(> % 0) #(<= % 12)) 2) + ((some-fn even? number? #(> % 0) #(<= % 12)) 2 4) + ((some-fn even? number? #(> % 0) #(<= % 12)) 2 4 6) + ((some-fn even? number? #(> % 0) #(<= % 12)) 2 4 6 8) + ((some-fn even? number? #(> % 0) #(<= % 12)) 2 4 6 8 10) + ((some-fn number? even? #(> % 0) #(<= % 12)) 2 4 6 8 10 12) + ((some-fn number? odd? #(> % 0) #(<= % 12)) 2) + ((some-fn number? odd? #(> % 0) #(<= % 12)) 2 4) + ((some-fn number? odd? #(> % 0) #(<= % 12)) 2 4 6) + ((some-fn number? odd? #(> % 0) #(<= % 12)) 2 4 6 8) + ((some-fn number? odd? #(> % 0) #(<= % 12)) 2 4 6 8 10) + ((some-fn number? odd? #(> % 0) #(<= % 12)) 2 4 6 8 14) + ;; 4 preds, short-circuiting + ((some-fn number? odd? #(> % 0) #(<= % 12)) 1 :a) + ((some-fn number? odd? #(> % 0) #(<= % 12)) 1 3 :a) + ((some-fn number? odd? #(> % 0) #(<= % 12)) 1 3 5 :a) + ((some-fn number? odd? #(> % 0) #(<= % 12)) 1 3 5 7 :a) + ((some-fn number? odd? #(> % 0) #(<= % 12)) 1 :a 3 5 7) + ;; 5 preds + (not ((some-fn even? number? #(> % 0) #(<= % 12) #(zero? (rem % 2))))) + ((some-fn even? number? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2) + ((some-fn even? number? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4) + ((some-fn even? number? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6) + ((some-fn even? number? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6 8) + ((some-fn even? number? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6 8 10) + ((some-fn number? even? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6 8 10 12) + ((some-fn number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2) + ((some-fn number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4) + ((some-fn number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6) + ((some-fn number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6 8) + ((some-fn number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6 8 10) + ((some-fn number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 2 4 6 8 13) + ;; 5 preds, short-circuiting + ((some-fn number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 1 :a) + ((some-fn number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 1 3 :a) + ((some-fn number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 1 3 5 :a) + ((some-fn number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 1 3 5 7 :a) + ((some-fn number? odd? #(> % 0) #(<= % 12) #(zero? (rem % 2))) 1 :a 3 5 7) + ;; truthiness + (reduce #(or % %2) + (conj + (vec + (for [i (range 1 25)] + (apply (apply some-fn (repeat i (comp not boolean))) (range i)))) + true)))) + + +(deftest test-max-min-key + (are [k coll min-item max-item] (and (= min-item (apply min-key k coll)) + (= max-item (apply max-key k coll))) + count ["longest" "a" "xy" "foo" "bar"] "a" "longest" + - [5 10 15 20 25] 25 5 + #(if (neg? %) (- %) %) [-2 -1 0 1 2 3 4] 0 4 + {nil 1 false -1 true 0} [true true false nil] false nil) + (are [f k coll expected] (= expected (apply f k coll)) + min-key :x [{:x 1000} {:x 1001} {:x 1002} {:x 1000 :second true}] {:x 1000 :second true} + max-key :x [{:x 1000} {:x 999} {:x 998} {:x 1000 :second true}] {:x 1000 :second true})) + + +; Printing +; pr prn print println newline +; pr-str prn-str print-str println-str [with-out-str (vars.clj)] + +; update + +(deftest test-update + (are [result expr] (= result expr) + {:a [1 2]} (update {:a [1]} :a conj 2) + [1] (update [0] 0 inc) + ;; higher-order usage + {:a {:b 2}} (update-in {:a {:b 1}} [:a] update :b inc) + ;; missing field = nil + {:a 1 :b nil} (update {:a 1} :b identity) + ;; 4 hard-coded arities + {:a 1} (update {:a 1} :a +) + {:a 2} (update {:a 1} :a + 1) + {:a 3} (update {:a 1} :a + 1 1) + {:a 4} (update {:a 1} :a + 1 1 1) + ;; rest arity + {:a 5} (update {:a 1} :a + 1 1 1 1) + {:a 6} (update {:a 1} :a + 1 1 1 1 1))) + +(deftest test-update-vals + (let [inm (with-meta {:a 1 :b 2} {:has :meta})] + (are [result expr] (= result expr) + {:a 2 :b 3} (update-vals inm inc) + {:has :meta} (meta (update-vals inm inc)) + {0 2 2 4} (update-vals (hash-map 0 1 2 3) inc) + {0 2 2 4} (update-vals (array-map 0 1 2 3) inc) + {0 2 2 4} (update-vals (sorted-map 2 3 0 1) inc)))) + +(deftest test-update-keys + (let [inm (with-meta {:a 1 :b 2} {:has :meta})] + (are [result expr] (= result expr) + {"a" 1 "b" 2} (update-keys inm name) + {:has :meta} (meta (update-keys inm name)) + {1 1 3 3} (update-keys (hash-map 0 1 2 3) inc) + {1 1 3 3} (update-keys (array-map 0 1 2 3) inc) + {1 1 3 3} (update-keys (sorted-map 2 3 0 1) inc)))) diff --git a/src/test/cljs/cljs/parse_test.cljs b/src/test/cljs/cljs/parse_test.cljs new file mode 100644 index 0000000000..582ea58c51 --- /dev/null +++ b/src/test/cljs/cljs/parse_test.cljs @@ -0,0 +1,109 @@ +(ns cljs.parse-test + (:require + [clojure.test :refer [deftest is are]] + [clojure.test.check :as chk] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as prop])) + +(deftest test-parse-long + (are [s expected] + (= expected (parse-long s)) + "100" 100 + "+100" 100 + "0" 0 + "+0" 0 + "-0" 0 + "-42" -42 + "9007199254740991" js/Number.MAX_SAFE_INTEGER ;; largest parsable: 999999999999999934463 + "+9007199254740991" js/Number.MAX_SAFE_INTEGER + "-9007199254740991" js/Number.MIN_SAFE_INTEGER + "077" 77) ;; leading 0s are ignored! (not octal) + + (are [s] ;; do not parse + (nil? (parse-long s)) + "0.3" ;; no float + "9007199254740992" ;; past max long + "-9007199254740992" ;; past min long + "0xA0" ;; no hex + "2r010")) ;; no radix support + +;; generative test - gen long -> str -> parse, compare +(deftest test-gen-parse-long + (let [res (chk/quick-check + 100000 + (prop/for-all* [gen/large-integer] + #(= % (-> % str parse-long))))] + (if (:result res) + (is true) ;; pass + (is (:result res) (pr-str res))))) + +(deftest test-parse-double + (are [s expected] + (= expected (parse-double s)) + "1.234" 1.234 + "+1.234" 1.234 + "-1.234" -1.234 + "+0" +0.0 + "-0.0" -0.0 + "0.0" 0.0 + "5" 5.0 + ".5" 0.5 + "Infinity" ##Inf + "-Infinity" ##-Inf + "1.7976931348623157E308" js/Number.MAX_VALUE + "4.9E-324" js/Number.MIN_VALUE + "1.7976931348623157E309" js/Number.POSITIVE_INFINITY ;; past max double + "2.5e-324" js/Number.MIN_VALUE ;; past min double, above half minimum + "2.4e-324" 0.0) ;; below minimum double + (is (js/isNaN (parse-double "NaN"))) + (are [s] ;; nil on invalid string + (nil? (parse-double s)) + "double" ;; invalid string + "1.7976931348623157G309")) ;; close, but not valid + +;; generative test - gen double -> str -> parse, compare +(deftest test-gen-parse-double + (let [res (chk/quick-check + 100000 + (prop/for-all* [gen/double] + #(let [parsed (-> % str parse-double)] + (if (js/isNaN %) + (js/isNaN parsed) + (= % parsed)))))] + (if (:result res) + (is true) ;; pass + (is (:result res) (pr-str res))))) + +(deftest test-parse-uuid + (is (parse-uuid (str (random-uuid)))) + (is (nil? (parse-uuid "BOGUS"))) ;; nil on invalid uuid string + (are [s] ;; throw on invalid type (not string) + (try (parse-uuid s) (is false) (catch :default _ (is true))) + 123 + nil) + ;; parse the nil uuid + (is (parse-uuid "00000000-0000-0000-0000-000000000000")) + ;; parse a version 1 UUID + (is (parse-uuid "123e4567-e89b-12d3-a456-426614174000")) + ;; parse a version 2 UUID + (is (parse-uuid "123e4567-e89b-22d3-a456-426614174000")) + ;; ensure that bad characters are invalid + (is (nil? (parse-uuid "123e4567-eg9b-12d3-a456-426614174000")))) + +(deftest test-parse-boolean + (is (identical? true (parse-boolean "true"))) + (is (identical? false (parse-boolean "false"))) + + (are [s] ;; nil on invalid string + (nil? (parse-boolean s)) + "abc" + "TRUE" + "FALSE" + " true ") + + (are [s] ;; throw on invalid type (not string) + (try (parse-boolean s) (is false) (catch :default _ (is true))) + nil + false + true + 100)) diff --git a/src/test/cljs/cljs/pprint_test.clj b/src/test/cljs/cljs/pprint_test.clj new file mode 100644 index 0000000000..5112562c41 --- /dev/null +++ b/src/test/cljs/cljs/pprint_test.clj @@ -0,0 +1,30 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.pprint-test + (:require cljs.test)) + +(defmacro simple-tests [name & test-pairs] + `(cljs.test/deftest ~name + ~@(for [[x y] (partition 2 test-pairs)] + `(cond + (cljs.core/regexp? ~y) (cljs.test/is (.exec ~y ~x)) + (cljs.core/string? ~y) (cljs.test/is (= ~x ~y)) + :else (cljs.test/is (= ~x ~y)))))) + +(defmacro code-block + "Read a string then print it with code-dispatch and succeed if it comes out the same" + [test-name & blocks] + `(simple-tests ~test-name + ~@(apply concat + (for [block blocks] + `[(clojure.string/split-lines + (with-out-str + (cljs.pprint/with-pprint-dispatch cljs.pprint/code-dispatch + (cljs.pprint/pprint (cljs.reader/read-string ~block))))) + (clojure.string/split-lines ~block)])))) diff --git a/src/test/cljs/cljs/pprint_test.cljs b/src/test/cljs/cljs/pprint_test.cljs new file mode 100644 index 0000000000..2f95ea50b8 --- /dev/null +++ b/src/test/cljs/cljs/pprint_test.cljs @@ -0,0 +1,1107 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.pprint-test + (:refer-clojure :exclude [prn]) + (:require-macros + [cljs.pprint-test :refer [simple-tests code-block]]) + (:require + [cljs.test :as t :refer-macros [deftest is are]] + [cljs.pprint :refer [pprint cl-format get-pretty-writer prn print-table + *print-pprint-dispatch* simple-dispatch + *print-right-margin* *print-miser-width* + write code-dispatch] + :refer-macros [with-pprint-dispatch]] + [cljs.reader :as reader]) + + (:import [goog.string StringBuffer])) + +(def format cl-format) + +(simple-tests xp-fill-test + (binding [*print-pprint-dispatch* simple-dispatch + *print-right-margin* 38 + *print-miser-width* nil] + (cl-format nil "(let ~:<~@{~:<~w ~_~w~:>~^ ~:_~}~:>~_ ...)~%" + '((x 4) (*print-length* nil) (z 2) (list nil)))) + "(let ((x 4) (*print-length* nil)\n (z 2) (list nil))\n ...)\n" + + (binding [*print-pprint-dispatch* simple-dispatch + *print-right-margin* 22] + (cl-format nil "(let ~:<~@{~:<~w ~_~w~:>~^ ~:_~}~:>~_ ...)~%" + '((x 4) (*print-length* nil) (z 2) (list nil)))) + "(let ((x 4)\n (*print-length*\n nil)\n (z 2)\n (list nil))\n ...)\n" +) + +(simple-tests mandatory-fill-test + (cl-format nil + "
    ~%~~%
    ~%" + [ "hello" "gooodbye" ]) + "
    +Usage: *hello*
    +       *gooodbye*
    +
    +") + +(simple-tests prefix-suffix-test + (binding [*print-pprint-dispatch* simple-dispatch + *print-right-margin* 10, *print-miser-width* 10] + (cl-format nil "~<{~;LIST ~@_~W ~@_~W ~@_~W~;}~:>" '(first second third))) + "{LIST\n first\n second\n third}" +) + +(simple-tests pprint-test + (binding [*print-pprint-dispatch* simple-dispatch] + (write '(defn foo [x y] + (let [result (* x y)] + (if (> result 400) + (cl-format true "That number is too big") + (cl-format true "The result of ~d x ~d is ~d" x y result)))) + :stream nil)) + "(defn + foo + [x y] + (let + [result (* x y)] + (if + (> result 400) + (cl-format true \"That number is too big\") + (cl-format true \"The result of ~d x ~d is ~d\" x y result))))" + + (with-pprint-dispatch code-dispatch ;;fail + (write '(defn foo [x y] + (let [result (* x y)] + (if (> result 400) + (cl-format true "That number is too big") + (cl-format true "The result of ~d x ~d is ~d" x y result)))) + :stream nil)) + "(defn foo [x y] + (let [result (* x y)] + (if (> result 400) + (cl-format true \"That number is too big\") + (cl-format true \"The result of ~d x ~d is ~d\" x y result))))" + + (binding [*print-pprint-dispatch* simple-dispatch + *print-right-margin* 15] + (write '(fn (cons (car x) (cdr y))) :stream nil)) + "(fn\n (cons\n (car x)\n (cdr y)))" + + ;;TODO Fails because of spacing; may be due to the mutating clj difference from cljs + ;;or could simply be a bug in our code + (with-pprint-dispatch code-dispatch + (binding [*print-right-margin* 52] + (write + '(add-to-buffer this (make-buffer-blob (str (char c)) nil)) + :stream nil))) + "(add-to-buffer\n this \n (make-buffer-blob (str (char c)) nil))" + #_"(add-to-buffer\n this\n (make-buffer-blob (str (char c)) nil))" + ) + +;; Not a valid test now that we use tools.reader - David +;(simple-tests pprint-reader-macro-test +; ;;I'm not sure this will work without significant work on cljs. Short story, cljs +; ;;reader only takes valid EDN, so #(* % %) won't work. +; ;;see http://stackoverflow.com/a/25712675/546321 for more details +; #_(with-pprint-dispatch code-dispatch +; (write (reader/read-string "(map #(first %) [[1 2 3] [4 5 6] [7]])") +; :stream nil)) +; #_"(map #(first %) [[1 2 3] [4 5 6] [7]])" +; +; ;;TODO Not sure what to do about this test due to the reader handling of `@` +; (with-pprint-dispatch code-dispatch +; (write (reader/read-string "@@(ref (ref 1))") +; :stream nil)) +; "(deref (deref (ref (ref 1))))" +; #_"@@(ref (ref 1))" +; +; (with-pprint-dispatch code-dispatch +; (write (reader/read-string "'foo") +; :stream nil)) +; "'foo" +; ) + +(simple-tests xp-miser-test + (binding [*print-pprint-dispatch* simple-dispatch + *print-right-margin* 10, *print-miser-width* 9] + (cl-format nil "~:" '(first second third))) + "(LIST\n first\n second\n third)" + + (binding [*print-pprint-dispatch* simple-dispatch + *print-right-margin* 10, *print-miser-width* 8] + (cl-format nil "~:" '(first second third))) + "(LIST first second third)" +) + +(code-block code-block-tests + "(defn cl-format + \"An implementation of a Common Lisp compatible format function\" + [stream format-in & args] + (let [compiled-format (if (string? format-in) + (compile-format format-in) + format-in) + navigator (init-navigator args)] + (execute-format stream compiled-format navigator)))" + + "(defn pprint-defn [writer alis] + (if (next alis) + (let [[defn-sym defn-name & stuff] alis + [doc-str stuff] (if (string? (first stuff)) + [(first stuff) (next stuff)] + [nil stuff]) + [attr-map stuff] (if (map? (first stuff)) + [(first stuff) (next stuff)] + [nil stuff])] + (pprint-logical-block + writer + :prefix + \"(\" + :suffix + \")\" + (cl-format true \"~w ~1I~@_~w\" defn-sym defn-name) + (if doc-str (cl-format true \" ~_~w\" doc-str)) + (if attr-map (cl-format true \" ~_~w\" attr-map)) + (cond + (vector? (first stuff)) (single-defn + stuff + (or doc-str attr-map)) + :else (multi-defn stuff (or doc-str attr-map))))) + (pprint-simple-code-list writer alis)))" +) + +(simple-tests print-length-tests + (binding [*print-length* 1] (with-out-str (pprint '(a b c d e f)))) + "(a ...)\n" + (binding [*print-length* 2] (with-out-str (pprint '(a b c d e f)))) + "(a b ...)\n" + (binding [*print-length* 6] (with-out-str (pprint '(a b c d e f)))) + "(a b c d e f)\n" + (binding [*print-length* 8] (with-out-str (pprint '(a b c d e f)))) + "(a b c d e f)\n" + + (binding [*print-length* 1] (with-out-str (pprint [1 2 3 4 5 6]))) + "[1 ...]\n" + (binding [*print-length* 2] (with-out-str (pprint [1 2 3 4 5 6]))) + "[1 2 ...]\n" + (binding [*print-length* 6] (with-out-str (pprint [1 2 3 4 5 6]))) + "[1 2 3 4 5 6]\n" + (binding [*print-length* 8] (with-out-str (pprint [1 2 3 4 5 6]))) + "[1 2 3 4 5 6]\n" + + (binding [*print-length* 1] (with-out-str (pprint (sorted-set 1 2 3 4 5 6)))) + "#{1 ...}\n" + (binding [*print-length* 2] (with-out-str (pprint (sorted-set 1 2 3 4 5 6)))) + "#{1 2 ...}\n" + (binding [*print-length* 6] (with-out-str (pprint (sorted-set 1 2 3 4 5 6)))) + "#{1 2 3 4 5 6}\n" + (binding [*print-length* 8] (with-out-str (pprint (sorted-set 1 2 3 4 5 6)))) + "#{1 2 3 4 5 6}\n" + + (binding [*print-length* 1] (with-out-str (pprint (sorted-map 1 2, 3 4, 5 6, 7 8, 9 10, 11 12)))) + "{1 2, ...}\n" + (binding [*print-length* 2] (with-out-str (pprint (sorted-map 1 2, 3 4, 5 6, 7 8, 9 10, 11 12)))) + "{1 2, 3 4, ...}\n" + (binding [*print-length* 6] (with-out-str (pprint (sorted-map 1 2, 3 4, 5 6, 7 8, 9 10, 11 12)))) + "{1 2, 3 4, 5 6, 7 8, 9 10, 11 12}\n" + (binding [*print-length* 8] (with-out-str (pprint (sorted-map 1 2, 3 4, 5 6, 7 8, 9 10, 11 12)))) + "{1 2, 3 4, 5 6, 7 8, 9 10, 11 12}\n" + + ;;TODO Not sure if JS objs are handled the way we want; need to further investigate + (binding [*print-length* 1] (with-out-str (pprint (int-array [1 2 3 4 5 6])))) + "#js [1 ...]\n" + (binding [*print-length* 2] (with-out-str (pprint (int-array [1 2 3 4 5 6])))) + "#js [1 2 ...]\n" + (binding [*print-length* 6] (with-out-str (pprint (int-array [1 2 3 4 5 6])))) + "#js [1 2 3 4 5 6]\n" + (binding [*print-length* 8] (with-out-str (pprint (int-array [1 2 3 4 5 6])))) + "#js [1 2 3 4 5 6]\n" + + ) + +(simple-tests print-margin-tests + (binding [cljs.pprint/*print-right-margin* 20] + (with-out-str (pprint (sorted-map 1 (sorted-map 12345 123456), 3 (sorted-map 4 5, 6 7))))) + "{1 {12345 123456},\n 3 {4 5, 6 7}}\n" + + (binding [cljs.pprint/*print-right-margin* 8] + (with-out-str (pprint (sorted-set 123 456 789)))) + "#{123\n 456\n 789}\n" +) + +(simple-tests print-namespace-maps-tests + (binding [*print-namespace-maps* true] (with-out-str (pprint {:user/a 1}))) + "#:user{:a 1}\n" + (binding [*print-namespace-maps* false] (with-out-str (pprint {:user/a 1}))) + "{:user/a 1}\n" + ) + +;;---------------------------------------------------------------------------- +;; clj-format tests +;;---------------------------------------------------------------------------- + +(simple-tests d-tests + (cl-format nil "~D" 0) "0" + (cl-format nil "~D" 2e6) "2000000" + (cl-format nil "~D" 2000000) "2000000" + (cl-format nil "~:D" 2000000) "2,000,000" + ;(cl-format nil "~D" 1/2) "1/2" ;no ratio + (cl-format nil "~D" 'fred) "fred" +) + +(simple-tests cardinal-tests + (cl-format nil "~R" 0) "zero" + (cl-format nil "~R" 4) "four" + (cl-format nil "~R" 15) "fifteen" + (cl-format nil "~R" -15) "minus fifteen" + (cl-format nil "~R" 25) "twenty-five" + (cl-format nil "~R" 20) "twenty" + (cl-format nil "~R" 200) "two hundred" + (cl-format nil "~R" 203) "two hundred three" + + (cl-format nil "~R" 44879032) + "forty-four million, eight hundred seventy-nine thousand, thirty-two" + + (cl-format nil "~R" -44879032) + "minus forty-four million, eight hundred seventy-nine thousand, thirty-two" + + (cl-format nil "~R = ~:*~:D" 44000032) + "forty-four million, thirty-two = 44,000,032" + + ;;js/Number.MAX_SAFE_INTEGER - js starts munging larger values + (cl-format nil "~R = ~:*~:D" 9007199254740991) + "nine quadrillion, seven trillion, one hundred ninety-nine billion, two hundred fifty-four million, seven hundred forty thousand, nine hundred ninety-one = 9,007,199,254,740,991" + + ;;js/Number.MIN_SAFE_INTEGER - js starts munging smaller values + (cl-format nil "~R = ~:*~:D" -9007199254740991) + "minus nine quadrillion, seven trillion, one hundred ninety-nine billion, two hundred fifty-four million, seven hundred forty thousand, nine hundred ninety-one = -9,007,199,254,740,991" + + (cl-format nil "~R = ~:*~:D" 2e6) + "two million = 2,000,000" + + (cl-format nil "~R = ~:*~:D" 200000200000) + "two hundred billion, two hundred thousand = 200,000,200,000" +) + +(simple-tests ordinal-tests + (cl-format nil "~:R" 0) "zeroth" + (cl-format nil "~:R" 4) "fourth" + (cl-format nil "~:R" 15) "fifteenth" + (cl-format nil "~:R" -15) "minus fifteenth" + (cl-format nil "~:R" 25) "twenty-fifth" + (cl-format nil "~:R" 20) "twentieth" + (cl-format nil "~:R" 200) "two hundredth" + (cl-format nil "~:R" 203) "two hundred third" + + (cl-format nil "~:R" 44879032) + "forty-four million, eight hundred seventy-nine thousand, thirty-second" + + (cl-format nil "~:R" -44879032) + "minus forty-four million, eight hundred seventy-nine thousand, thirty-second" + + (cl-format nil "~:R = ~:*~:D" 44000032) + "forty-four million, thirty-second = 44,000,032" + + ;;js/Number.MAX_SAFE_INTEGER - js starts munging larger values + (cl-format nil "~:R = ~:*~:D" 9007199254740991) + "nine quadrillion, seven trillion, one hundred ninety-nine billion, two hundred fifty-four million, seven hundred forty thousand, nine hundred ninety-first = 9,007,199,254,740,991" + + ;;js/Number.MIN_SAFE_INTEGER - js starts munging smaller values + (cl-format nil "~:R = ~:*~:D" -9007199254740991) + "minus nine quadrillion, seven trillion, one hundred ninety-nine billion, two hundred fifty-four million, seven hundred forty thousand, nine hundred ninety-first = -9,007,199,254,740,991" + + (cl-format nil "~:R = ~:*~:D" 2e6) + "two millionth = 2,000,000" +) + +(simple-tests ordinal1-tests + (cl-format nil "~:R" 1) "first" + (cl-format nil "~:R" 11) "eleventh" + (cl-format nil "~:R" 21) "twenty-first" + (cl-format nil "~:R" 20) "twentieth" + (cl-format nil "~:R" 220) "two hundred twentieth" + (cl-format nil "~:R" 200) "two hundredth" + (cl-format nil "~:R" 999) "nine hundred ninety-ninth" +) + +(simple-tests roman-tests + (cl-format nil "~@R" 3) "III" + (cl-format nil "~@R" 4) "IV" + (cl-format nil "~@R" 9) "IX" + (cl-format nil "~@R" 29) "XXIX" + (cl-format nil "~@R" 429) "CDXXIX" + (cl-format nil "~@:R" 429) "CCCCXXVIIII" + (cl-format nil "~@:R" 3429) "MMMCCCCXXVIIII" + (cl-format nil "~@R" 3429) "MMMCDXXIX" + (cl-format nil "~@R" 3479) "MMMCDLXXIX" + (cl-format nil "~@R" 3409) "MMMCDIX" + (cl-format nil "~@R" 300) "CCC" + (cl-format nil "~@R ~D" 300 20) "CCC 20" + (cl-format nil "~@R" 5000) "5,000" + (cl-format nil "~@R ~D" 5000 20) "5,000 20" + (cl-format nil "~@R" "the quick") "the quick" +) + +(simple-tests c-tests + (cl-format nil "~{~c~^, ~}~%" "hello") "h, e, l, l, o\n" + (cl-format nil "~{~:c~^, ~}~%" "hello") "h, e, l, l, o\n" + (cl-format nil "~@C~%" \m) "\\m\n" + (cl-format nil "~@C~%" (char 222)) "\\Þ\n" + + ;;chars that are specially printed in cljs + (cl-format nil "~@C~%" (char 8)) "\\backspace\n" + (cl-format nil "~@C~%" (char 9)) "\\tab\n" + (cl-format nil "~@C~%" (char 10)) "\\newline\n" + (cl-format nil "~@C~%" (char 12)) "\\formfeed\n" + (cl-format nil "~@C~%" (char 13)) "\\return\n" + (cl-format nil "~@C~%" (char 34)) "\\\"\n" + (cl-format nil "~@C~%" (char 92)) "\\\\\n" + + (cl-format nil "~@C~%" (char 3)) "\\\n" +) + +(simple-tests e-tests + (cl-format nil "*~E*" 0.0) "*0.0E+0*" + (cl-format nil "*~6E*" 0.0) "*0.0E+0*" + (cl-format nil "*~6,0E*" 0.0) "* 0.E+0*" + (cl-format nil "*~7,2E*" 0.0) "*0.00E+0*" + (cl-format nil "*~5E*" 0.0) "*0.E+0*" + (cl-format nil "*~10,2,2,,'?E*" 2.8E120) "*??????????*" + (cl-format nil "*~10,2E*" 9.99999) "* 1.00E+1*" + (cl-format nil "*~10,2E*" 9.99999E99) "* 1.00E+100*" + (cl-format nil "*~10,2,2E*" 9.99999E99) "* 1.00E+100*" + (cl-format nil "*~10,2,2,,'?E*" 9.99999E99) "*??????????*" +) + +(simple-tests $-tests + (cl-format nil "~$" 22.3) "22.30" + (cl-format nil "~$" 22.375) "22.38" + (cl-format nil "~3,5$" 22.375) "00022.375" + (cl-format nil "~3,5,8$" 22.375) "00022.375" + (cl-format nil "~3,5,10$" 22.375) " 00022.375" + (cl-format nil "~3,5,14@$" 22.375) " +00022.375" + (cl-format nil "~3,5,14@$" 22.375) " +00022.375" + (cl-format nil "~3,5,14@:$" 22.375) "+ 00022.375" + (cl-format nil "~3,,14@:$" 0.375) "+ 0.375" + (cl-format nil "~1,1$" -12.0) "-12.0" + (cl-format nil "~1,1$" 12.0) "12.0" + (cl-format nil "~1,1$" 12.0) "12.0" + (cl-format nil "~1,1@$" 12.0) "+12.0" + (cl-format nil "~1,1,8,' @:$" 12.0) "+ 12.0" + (cl-format nil "~1,1,8,' @$" 12.0) " +12.0" + (cl-format nil "~1,1,8,' :$" 12.0) " 12.0" + (cl-format nil "~1,1,8,' $" 12.0) " 12.0" + (cl-format nil "~1,1,8,' @:$" -12.0) "- 12.0" + (cl-format nil "~1,1,8,' @$" -12.0) " -12.0" + (cl-format nil "~1,1,8,' :$" -12.0) "- 12.0" + (cl-format nil "~1,1,8,' $" -12.0) " -12.0" + (cl-format nil "~1,1$" 0.001) "0.0" + (cl-format nil "~2,1$" 0.001) "0.00" + (cl-format nil "~1,1,6$" 0.001) " 0.0" + (cl-format nil "~1,1,6$" 0.0015) " 0.0" + (cl-format nil "~2,1,6$" 0.005) " 0.01" + (cl-format nil "~2,1,6$" 0.01) " 0.01" + (cl-format nil "~$" 0.099) "0.10" + (cl-format nil "~1$" 0.099) "0.1" + (cl-format nil "~1$" 0.1) "0.1" + (cl-format nil "~1$" 0.99) "1.0" + (cl-format nil "~1$" -0.99) "-1.0" +) + +(simple-tests f-tests + (cl-format nil "~,1f" -12.0) "-12.0" + (cl-format nil "~,0f" 9.4) "9." + (cl-format nil "~,0f" 9.5) "10." + (cl-format nil "~,0f" -0.99) "-1." + (cl-format nil "~,1f" -0.99) "-1.0" + (cl-format nil "~,2f" -0.99) "-0.99" + (cl-format nil "~,3f" -0.99) "-0.990" + (cl-format nil "~,0f" 0.99) "1." + (cl-format nil "~,1f" 0.99) "1.0" + (cl-format nil "~,2f" 0.99) "0.99" + (cl-format nil "~,3f" 0.99) "0.990" + (cl-format nil "~,3f" -0.099) "-0.099" + (cl-format nil "~,4f" -0.099) "-0.0990" + (cl-format nil "~,5f" -0.099) "-0.09900" + (cl-format nil "~,3f" 0.099) "0.099" + (cl-format nil "~,4f" 0.099) "0.0990" + (cl-format nil "~,5f" 0.099) "0.09900" + (cl-format nil "~f" -1) "-1.0" + (cl-format nil "~2f" -1) "-1." + (cl-format nil "~3f" -1) "-1." + (cl-format nil "~4f" -1) "-1.0" + (cl-format nil "~8f" -1) " -1.0" + (cl-format nil "~2f" -0.0099) "-0." + (cl-format nil "~3f" -0.0099) "-0." + (cl-format nil "~4f" -0.0099) "-.01" + (cl-format nil "~5f" -0.0099) "-0.01" + (cl-format nil "~6f" -0.0099) "-.0099" + (cl-format nil "~1f" 0.0099) "0." + (cl-format nil "~2f" 0.0099) "0." + (cl-format nil "~3f" 0.0099) ".01" + (cl-format nil "~4f" 0.0099) "0.01" + (cl-format nil "~5f" 0.0099) ".0099" + (cl-format nil "~6f" 0.0099) "0.0099" + (cl-format nil "~1f" -0.099) "-.1" + (cl-format nil "~2f" -0.099) "-.1" + (cl-format nil "~3f" -0.099) "-.1" + (cl-format nil "~4f" -0.099) "-0.1" + (cl-format nil "~5f" -0.099) "-.099" + (cl-format nil "~6f" -0.099) "-0.099" + (cl-format nil "~1f" 0.099) ".1" + (cl-format nil "~2f" 0.099) ".1" + (cl-format nil "~3f" 0.099) "0.1" + (cl-format nil "~4f" 0.099) ".099" + (cl-format nil "~5f" 0.099) "0.099" + (cl-format nil "~1f" -0.99) "-1." + (cl-format nil "~2f" -0.99) "-1." + (cl-format nil "~3f" -0.99) "-1." + (cl-format nil "~4f" -0.99) "-.99" + (cl-format nil "~5f" -0.99) "-0.99" + (cl-format nil "~1f" 0.99) "1." + (cl-format nil "~2f" 0.99) "1." + (cl-format nil "~3f" 0.99) ".99" + (cl-format nil "~4f" 0.99) "0.99" + (cl-format nil "~1f" 111.11111) "111." + (cl-format nil "~4f" 111.11111) "111." + (cl-format nil "~5f" 111.11111) "111.1" + (cl-format nil "~1f" -111.11111) "-111." + (cl-format nil "~5f" -111.11111) "-111." + (cl-format nil "~6f" -111.11111) "-111.1" + (cl-format nil "~1f" 555.55555) "556." + (cl-format nil "~4f" 555.55555) "556." + (cl-format nil "~5f" 555.55555) "555.6" + (cl-format nil "~8f" 555.55555) "555.5556" + (cl-format nil "~1f" -555.55555) "-556." + (cl-format nil "~5f" -555.55555) "-556." + (cl-format nil "~6f" -555.55555) "-555.6" + (cl-format nil "~8f" -555.55555) "-555.556" + (cl-format nil "~1f" 999.999) "1000." + (cl-format nil "~5f" 999.999) "1000." + (cl-format nil "~6f" 999.999) "1000.0" + (cl-format nil "~7f" 999.999) "999.999" + (cl-format nil "~8f" 999.999) " 999.999" + (cl-format nil "~1f" -999.999) "-1000." + (cl-format nil "~6f" -999.999) "-1000." + (cl-format nil "~7f" -999.999) "-1000.0" + (cl-format nil "~8f" -999.999) "-999.999" + (cl-format nil "~5,2f" 111.11111) "111.11" + (cl-format nil "~3,1f" -0.0099) "-.0" + (cl-format nil "~6,4f" -0.0099) "-.0099" + (cl-format nil "~6,5f" -0.0099) "-.00990" + (cl-format nil "~6,6f" -0.0099) "-.009900" + (cl-format nil "~6,4f" 0.0099) "0.0099" + (cl-format nil "~6,5f" 0.0099) ".00990" + (cl-format nil "~6,6f" 0.0099) ".009900" + (cl-format nil "~2,1f" 0.0099) ".0" + (cl-format nil "~6,2f" -111.11111) "-111.11" + (cl-format nil "~6,3f" -111.11111) "-111.111" + (cl-format nil "~8,5f" -111.11111) "-111.11111" + (cl-format nil "~12,10f" 1.23456789014) "1.2345678901" + (cl-format nil "~12,10f" 1.23456789016) "1.2345678902" + (cl-format nil "~13,10f" -1.23456789014) "-1.2345678901" + (cl-format nil "~13,10f" -1.23456789016) "-1.2345678902" + (cl-format nil "~1,1f" 0.1) ".1" +) + +(simple-tests ampersand-tests + (cl-format nil "The quick brown ~a jumped over ~d lazy dogs" 'elephant 5) + "The quick brown elephant jumped over 5 lazy dogs" + (cl-format nil "The quick brown ~&~a jumped over ~d lazy dogs" 'elephant 5) + "The quick brown \nelephant jumped over 5 lazy dogs" + (cl-format nil "The quick brown ~&~a jumped\n~& over ~d lazy dogs" 'elephant 5) + "The quick brown \nelephant jumped\n over 5 lazy dogs" + (cl-format nil "~&The quick brown ~&~a jumped\n~& over ~d lazy dogs" 'elephant 5) + "The quick brown \nelephant jumped\n over 5 lazy dogs" + (cl-format nil "~3&The quick brown ~&~a jumped\n~& over ~d lazy dogs" 'elephant 5) + "\n\nThe quick brown \nelephant jumped\n over 5 lazy dogs" + (cl-format nil "~@{~&The quick brown ~a jumped over ~d lazy dogs~}" 'elephant 5 'fox 10) + "The quick brown elephant jumped over 5 lazy dogs\nThe quick brown fox jumped over 10 lazy dogs" + (cl-format nil "I ~[don't ~:;d~&o ~]have one~%" 0) "I don't have one\n" + (cl-format nil "I ~[don't ~:;d~&o ~]have one~%" 1) "I d\no have one\n" +) + +(simple-tests t-tests + (cl-format nil "~@{~&~A~8,4T~:*~A~}" + 'a 'aa 'aaa 'aaaa 'aaaaa 'aaaaaa 'aaaaaaa 'aaaaaaaa 'aaaaaaaaa 'aaaaaaaaaa) + "a a\naa aa\naaa aaa\naaaa aaaa\naaaaa aaaaa\naaaaaa aaaaaa\naaaaaaa aaaaaaa\naaaaaaaa aaaaaaaa\naaaaaaaaa aaaaaaaaa\naaaaaaaaaa aaaaaaaaaa" + (cl-format nil "~@{~&~A~,4T~:*~A~}" + 'a 'aa 'aaa 'aaaa 'aaaaa 'aaaaaa 'aaaaaaa 'aaaaaaaa 'aaaaaaaaa 'aaaaaaaaaa) + "a a\naa aa\naaa aaa\naaaa aaaa\naaaaa aaaaa\naaaaaa aaaaaa\naaaaaaa aaaaaaa\naaaaaaaa aaaaaaaa\naaaaaaaaa aaaaaaaaa\naaaaaaaaaa aaaaaaaaaa" + (cl-format nil "~@{~&~A~2,6@T~:*~A~}" 'a 'aa 'aaa 'aaaa 'aaaaa 'aaaaaa 'aaaaaaa 'aaaaaaaa 'aaaaaaaaa 'aaaaaaaaaa) + "a a\naa aa\naaa aaa\naaaa aaaa\naaaaa aaaaa\naaaaaa aaaaaa\naaaaaaa aaaaaaa\naaaaaaaa aaaaaaaa\naaaaaaaaa aaaaaaaaa\naaaaaaaaaa aaaaaaaaaa" +) + +(simple-tests paren-tests + (cl-format nil "~(PLEASE SPEAK QUIETLY IN HERE~)") "please speak quietly in here" + (cl-format nil "~@(PLEASE SPEAK QUIETLY IN HERE~)") "Please speak quietly in here" + (cl-format nil "~@:(but this Is imporTant~)") "BUT THIS IS IMPORTANT" + (cl-format nil "~:(the greAt gatsby~)!") "The Great Gatsby!" + ;; Test cases from CLtL 18.3 - string-upcase, et al. + (cl-format nil "~@:(~A~)" "Dr. Livingstone, I presume?") "DR. LIVINGSTONE, I PRESUME?" + (cl-format nil "~(~A~)" "Dr. Livingstone, I presume?") "dr. livingstone, i presume?" + (cl-format nil "~:(~A~)" " hello ") " Hello " + (cl-format nil "~:(~A~)" "occlUDeD cASEmenTs FOreSTAll iNADVertent DEFenestraTION") + "Occluded Casements Forestall Inadvertent Defenestration" + (cl-format nil "~:(~A~)" 'kludgy-hash-search) "Kludgy-Hash-Search" + (cl-format nil "~:(~A~)" "DON'T!") "Don'T!" ;not "Don't!" + (cl-format nil "~:(~A~)" "pipe 13a, foo16c") "Pipe 13a, Foo16c" + (cl-format nil "~:(~A~)" nil) "Nil" + (cl-format nil "~:(~A~)" "") "" +) + +(simple-tests square-bracket-tests + ;; Tests for format without modifiers + (cl-format nil "I ~[don't ~]have one~%" 0) "I don't have one\n" + (cl-format nil "I ~[don't ~]have one~%" 1) "I have one\n" + (cl-format nil "I ~[don't ~;do ~]have one~%" 0) "I don't have one\n" + (cl-format nil "I ~[don't ~;do ~]have one~%" 1) "I do have one\n" + (cl-format nil "I ~[don't ~;do ~]have one~%" 2) "I have one\n" + (cl-format nil "I ~[don't ~:;do ~]have one~%" 0) "I don't have one\n" + (cl-format nil "I ~[don't ~:;do ~]have one~%" 1) "I do have one\n" + (cl-format nil "I ~[don't ~:;do ~]have one~%" 2) "I do have one\n" + (cl-format nil "I ~[don't ~:;do ~]have one~%" 700) "I do have one\n" + + ;; Tests for format with a colon + (cl-format nil "I ~:[don't ~;do ~]have one~%" true) "I do have one\n" + (cl-format nil "I ~:[don't ~;do ~]have one~%" 700) "I do have one\n" + (cl-format nil "I ~:[don't ~;do ~]have one~%" '(a b)) "I do have one\n" + (cl-format nil "I ~:[don't ~;do ~]have one~%" nil) "I don't have one\n" + (cl-format nil "I ~:[don't ~;do ~]have one~%" false) "I don't have one\n" + + ;; Tests for format with an at sign + (cl-format nil "We had ~D wins~@[ (out of ~D tries)~].~%" 15 nil) "We had 15 wins.\n" + (cl-format nil "We had ~D wins~@[ (out of ~D tries)~].~%" 15 17) + "We had 15 wins (out of 17 tries).\n" + + ;; Format tests with directives + (cl-format nil "Max ~D: ~[Blue team ~D~;Red team ~D~:;No team ~A~].~%" 15, 0, 7) + "Max 15: Blue team 7.\n" + (cl-format nil "Max ~D: ~[Blue team ~D~;Red team ~D~:;No team ~A~].~%" 15, 1, 12) + "Max 15: Red team 12.\n" + (cl-format nil "Max ~D: ~[Blue team ~D~;Red team ~D~:;No team ~A~].~%" + 15, -1, "(system failure)") + "Max 15: No team (system failure).\n" + + ;; Nested format tests + (cl-format nil "Max ~D: ~[Blue team ~D~:[~; (complete success)~]~;Red team ~D~:;No team ~].~%" + 15, 0, 7, true) + "Max 15: Blue team 7 (complete success).\n" + (cl-format nil "Max ~D: ~[Blue team ~D~:[~; (complete success)~]~;Red team ~D~:;No team ~].~%" + 15, 0, 7, false) + "Max 15: Blue team 7.\n" + + ;; Test the selector as part of the argument + (cl-format nil "The answer is ~#[nothing~;~D~;~D out of ~D~:;something crazy~].") + "The answer is nothing." + (cl-format nil "The answer is ~#[nothing~;~D~;~D out of ~D~:;something crazy~]." 4) + "The answer is 4." + (cl-format nil "The answer is ~#[nothing~;~D~;~D out of ~D~:;something crazy~]." 7 22) + "The answer is 7 out of 22." + (cl-format nil "The answer is ~#[nothing~;~D~;~D out of ~D~:;something crazy~]." 1 2 3 4) + "The answer is something crazy." +) + +(simple-tests curly-brace-plain-tests + ;; Iteration from sublist + (cl-format nil "Coordinates are~{ [~D,~D]~}~%" [ 0, 1, 1, 0, 3, 5, 2, 1 ]) + "Coordinates are [0,1] [1,0] [3,5] [2,1]\n" + + (cl-format nil "Coordinates are~2{ [~D,~D]~}~%" [ 0, 1, 1, 0, 3, 5, 2, 1 ]) + "Coordinates are [0,1] [1,0]\n" + + (cl-format nil "Coordinates are~{ ~#[none~;<~D>~:;[~D,~D]~]~}~%" [ ]) + "Coordinates are\n" + + (cl-format nil "Coordinates are~{ ~#[none~;<~D>~:;[~D,~D]~]~:}~%" [ ]) + "Coordinates are none\n" + + (cl-format nil "Coordinates are~{ ~#[none~;<~D>~:;[~D,~D]~]~:}~%" [2 3 1]) + "Coordinates are [2,3] <1>\n" + + (cl-format nil "Coordinates are~{~:}~%" "" []) + "Coordinates are\n" + + (cl-format nil "Coordinates are~{~:}~%" " ~#[none~;<~D>~:;[~D,~D]~]" [2 3 1]) + "Coordinates are [2,3] <1>\n" + + (cl-format nil "Coordinates are~{~:}~%" " ~#[none~;<~D>~:;[~D,~D]~]" [ ]) + "Coordinates are none\n" +) + +(simple-tests curly-brace-colon-tests + ;; Iteration from list of sublists + (cl-format nil "Coordinates are~:{ [~D,~D]~}~%" [ [0, 1], [1, 0], [3, 5], [2, 1] ]) + "Coordinates are [0,1] [1,0] [3,5] [2,1]\n" + + (cl-format nil "Coordinates are~:{ [~D,~D]~}~%" [ [0, 1, 0], [1, 0, 12], [3, 5], [2, 1] ]) + "Coordinates are [0,1] [1,0] [3,5] [2,1]\n" + + (cl-format nil "Coordinates are~2:{ [~D,~D]~}~%" [ [0, 1], [1, 0], [3, 5], [2, 1] ]) + "Coordinates are [0,1] [1,0]\n" + + (cl-format nil "Coordinates are~:{ ~#[none~;<~D>~:;[~D,~D]~]~}~%" [ ]) + "Coordinates are\n" + + (cl-format nil "Coordinates are~:{ ~#[none~;<~D>~:;[~D,~D]~]~:}~%" [ ]) + "Coordinates are none\n" + + (cl-format nil "Coordinates are~:{ ~#[none~;<~D>~:;[~D,~D]~]~:}~%" [[2 3] [1]]) + "Coordinates are [2,3] <1>\n" + + (cl-format nil "Coordinates are~:{~:}~%" "" []) + "Coordinates are\n" + + (cl-format nil "Coordinates are~:{~:}~%" " ~#[none~;<~D>~:;[~D,~D]~]" [[2 3] [1]]) + "Coordinates are [2,3] <1>\n" + + (cl-format nil "Coordinates are~:{~:}~%" " ~#[none~;<~D>~:;[~D,~D]~]" [ ]) + "Coordinates are none\n" +) + +(simple-tests curly-brace-at-tests + ;; Iteration from main list + (cl-format nil "Coordinates are~@{ [~D,~D]~}~%" 0, 1, 1, 0, 3, 5, 2, 1) + "Coordinates are [0,1] [1,0] [3,5] [2,1]\n" + + (cl-format nil "Coordinates are~2@{ [~D,~D]~}~%" 0, 1, 1, 0, 3, 5, 2, 1) + "Coordinates are [0,1] [1,0]\n" + + (cl-format nil "Coordinates are~@{ ~#[none~;<~D>~:;[~D,~D]~]~}~%") + "Coordinates are\n" + + (cl-format nil "Coordinates are~@{ ~#[none~;<~D>~:;[~D,~D]~]~:}~%") + "Coordinates are none\n" + + (cl-format nil "Coordinates are~@{ ~#[none~;<~D>~:;[~D,~D]~]~:}~%" 2 3 1) + "Coordinates are [2,3] <1>\n" + + (cl-format nil "Coordinates are~@{~:}~%" "") + "Coordinates are\n" + + (cl-format nil "Coordinates are~@{~:}~%" " ~#[none~;<~D>~:;[~D,~D]~]" 2 3 1) + "Coordinates are [2,3] <1>\n" + + (cl-format nil "Coordinates are~@{~:}~%" " ~#[none~;<~D>~:;[~D,~D]~]") + "Coordinates are none\n" +) + +(simple-tests curly-brace-colon-at-tests + ;; Iteration from sublists on the main arg list + (cl-format nil "Coordinates are~@:{ [~D,~D]~}~%" [0, 1], [1, 0], [3, 5], [2, 1] ) + "Coordinates are [0,1] [1,0] [3,5] [2,1]\n" + + (cl-format nil "Coordinates are~@:{ [~D,~D]~}~%" [0, 1, 0], [1, 0, 12], [3, 5], [2, 1] ) + "Coordinates are [0,1] [1,0] [3,5] [2,1]\n" + + (cl-format nil "Coordinates are~2@:{ [~D,~D]~}~%" [0, 1], [1, 0], [3, 5], [2, 1]) + "Coordinates are [0,1] [1,0]\n" + + (cl-format nil "Coordinates are~@:{ ~#[none~;<~D>~:;[~D,~D]~]~}~%") + "Coordinates are\n" + + (cl-format nil "Coordinates are~@:{ ~#[none~;<~D>~:;[~D,~D]~]~:}~%") + "Coordinates are none\n" + + (cl-format nil "Coordinates are~@:{ ~#[none~;<~D>~:;[~D,~D]~]~:}~%" [2 3] [1]) + "Coordinates are [2,3] <1>\n" + + (cl-format nil "Coordinates are~@:{~:}~%" "") + "Coordinates are\n" + + (cl-format nil "Coordinates are~@:{~:}~%" " ~#[none~;<~D>~:;[~D,~D]~]" [2 3] [1]) + "Coordinates are [2,3] <1>\n" + + (cl-format nil "Coordinates are~@:{~:}~%" " ~#[none~;<~D>~:;[~D,~D]~]") + "Coordinates are none\n" +) + +;; TODO tests for ~^ in ~[ constructs and other brackets +;; TODO test ~:^ generates an error when used improperly +;; TODO test ~:^ works in ~@:{...~} +(let [aseq '(a quick brown fox jumped over the lazy dog) + lseq (mapcat identity (for [x aseq] [x (.-length (name x))]))] + (simple-tests up-tests + (cl-format nil "~{~a~^, ~}" aseq) "a, quick, brown, fox, jumped, over, the, lazy, dog" + (cl-format nil "~{~a~0^, ~}" aseq) "a" + (cl-format nil "~{~a~#,3^, ~}" aseq) "a, quick, brown, fox, jumped, over" + (cl-format nil "~{~a~v,3^, ~}" lseq) "a, quick, brown, fox" + (cl-format nil "~{~a~3,v,4^, ~}" lseq) "a, quick, brown, fox" + )) + +(simple-tests angle-bracket-tests + (cl-format nil "~") "foobarbaz" + (cl-format nil "~20") "foo bar baz" + (cl-format nil "~,,2") "foo bar baz" + (cl-format nil "~20<~A~;~A~;~A~>" "foo" "bar" "baz") "foo bar baz" + (cl-format nil "~20:<~A~;~A~;~A~>" "foo" "bar" "baz") " foo bar baz" + (cl-format nil "~20@<~A~;~A~;~A~>" "foo" "bar" "baz") "foo bar baz " + (cl-format nil "~20@:<~A~;~A~;~A~>" "foo" "bar" "baz") " foo bar baz " + (cl-format nil "~10,,2<~A~;~A~;~A~>" "foo" "bar" "baz") "foo bar baz" + (cl-format nil "~10,10,2<~A~;~A~;~A~>" "foo" "bar" "baz") "foo bar baz" + (cl-format nil "~10,10<~A~;~A~;~A~>" "foo" "bar" "baz") "foo barbaz" + (cl-format nil "~20<~A~;~^~A~;~^~A~>" "foo" "bar" "baz") "foo bar baz" + (cl-format nil "~20<~A~;~^~A~;~^~A~>" "foo" "bar") "foo bar" + (cl-format nil "~20@<~A~;~^~A~;~^~A~>" "foo") "foo " + (cl-format nil "~20:<~A~;~^~A~;~^~A~>" "foo") " foo" +) + +(simple-tests angle-bracket-max-column-tests + (cl-format nil "~%;; ~{~<~%;; ~1,50:; ~A~>~}.~%" (into [] (clojure.string/split "This function computes the circular thermodynamic coefficient of the thrombulator angle for use in determining the reaction distance" #"\s"))) + "\n;; This function computes the circular\n;; thermodynamic coefficient of the thrombulator\n;; angle for use in determining the reaction\n;; distance.\n" + (cl-format true "~%;; ~{~<~%;; ~:; ~A~>~}.~%" (into [] (clojure.string/split "This function computes the circular thermodynamic coefficient of the thrombulator angle for use in determining the reaction distance." #"\s"))) +) + +(defn list-to-table-stream [aseq column-width] + (let [sb (StringBuffer.)] + (binding [*out* (get-pretty-writer (StringBufferWriter. sb))] + (doseq [row aseq] + (doseq [col row] + (cl-format *out* "~4D~7,vT" col column-width)) + (#'prn))) + (str sb) + ;;TODO do we need to extend StringBufferWriter to allow access to underlying StringBuffer? + #_(str (:base @@(:base @@stream))))) + +(defn list-to-table-print [aseq column-width] + (let [sb (StringBuffer.)] + (binding [*print-fn* (fn [s] (.append sb (apply str s))) + *print-newline* true] + (doseq [row aseq] + (doseq [col row] + (cl-format true "~4D~7,vT" col column-width)) + (cljs.core/prn))) + (str sb))) + +(simple-tests column-writer-test + (list-to-table-stream (map #(vector % (* % %) (* % % %)) (range 1 21)) 8) + " 1 1 1 \n 2 4 8 \n 3 9 27 \n 4 16 64 \n 5 25 125 \n 6 36 216 \n 7 49 343 \n 8 64 512 \n 9 81 729 \n 10 100 1000 \n 11 121 1331 \n 12 144 1728 \n 13 169 2197 \n 14 196 2744 \n 15 225 3375 \n 16 256 4096 \n 17 289 4913 \n 18 324 5832 \n 19 361 6859 \n 20 400 8000 \n" + + (list-to-table-print (map #(vector % (* % %) (* % % %)) (range 1 21)) 8) + " 1 1 1 \n 2 4 8 \n 3 9 27 \n 4 16 64 \n 5 25 125 \n 6 36 216 \n 7 49 343 \n 8 64 512 \n 9 81 729 \n 10 100 1000 \n 11 121 1331 \n 12 144 1728 \n 13 169 2197 \n 14 196 2744 \n 15 225 3375 \n 16 256 4096 \n 17 289 4913 \n 18 324 5832 \n 19 361 6859 \n 20 400 8000 \n" +) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; The following tests are the various examples from the format +;; documentation in Common Lisp, the Language, 2nd edition, Chapter 22.3 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn expt [base pow] (reduce * (repeat pow base))) + +(let [x 5, y "elephant", n 3] + (simple-tests cltl-intro-tests + (format nil "foo") "foo" + (format nil "The answer is ~D." x) "The answer is 5." + (format nil "The answer is ~3D." x) "The answer is 5." + (format nil "The answer is ~3,'0D." x) "The answer is 005." + (format nil "The answer is ~:D." (expt 47 x)) "The answer is 229,345,007." + (format nil "Look at the ~A!" y) "Look at the elephant!" + (format nil "Type ~:C to ~A." (char 4) "delete all your files") + "Type Control-D to delete all your files." + (format nil "~D item~:P found." n) "3 items found." + (format nil "~R dog~:[s are~; is~] here." n (= n 1)) "three dogs are here." + (format nil "~R dog~:*~[s are~; is~:;s are~] here." n) "three dogs are here." + (format nil "Here ~[are~;is~:;are~] ~:*~R pupp~:@P." n) "Here are three puppies.")) + +(simple-tests cltl-B-tests + ;; CLtL didn't have the colons here, but the spec requires them + (format nil "~,,' ,4:B" 0xFACE) "1111 1010 1100 1110" + (format nil "~,,' ,4:B" 0x1CE) "1 1100 1110" + (format nil "~19,,' ,4:B" 0xFACE) "1111 1010 1100 1110" + ;; This one was a nice idea, but nothing in the spec supports it working this way + ;; (and SBCL doesn't work this way either) + ;(format nil "~19,,' ,4:B" 0x1CE) "0000 0001 1100 1110") +) + +(simple-tests cltl-P-tests + (format nil "~D tr~:@P/~D win~:P" 7 1) "7 tries/1 win" + (format nil "~D tr~:@P/~D win~:P" 1 0) "1 try/0 wins" + (format nil "~D tr~:@P/~D win~:P" 1 3) "1 try/3 wins" +) + +(defn foo [x] + (format nil "~6,2F|~6,2,1,'*F|~6,2,,'?F|~6F|~,2F|~F" + x x x x x x)) + +;; big-pos-ratio is a ratio value that is larger than +;; Double/MAX_VALUE, and has a non-terminating decimal representation +;; if you attempt to represent it exactly. +#_(def big-pos-ratio (/ (* 4 (bigint (. BigDecimal valueOf Double/MAX_VALUE))) 3)) +#_(def big-neg-ratio (- big-pos-ratio)) +;; tiny-pos-ratio is a ratio between 0 and Double/MIN_VALUE. +#_(def tiny-pos-ratio (/ 1 (bigint (apply str (cons "1" (repeat 340 "0")))))) +#_(def tiny-neg-ratio (- tiny-pos-ratio)) + +(simple-tests cltl-F-tests + #_(cl-format false "~10,3f" 4/5) + #_" 0.800" + #_(binding [*math-context* java.math.MathContext/DECIMAL128] + (cl-format false "~10,3f" big-pos-ratio)) + #_"239692417981642093333333333333333300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000" + #_(binding [*math-context* java.math.MathContext/DECIMAL128] + (cl-format false "~10,3f" big-neg-ratio)) + #_"-239692417981642093333333333333333300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.000" + #_(binding [*math-context* java.math.MathContext/DECIMAL128] + (cl-format false "~10,3f" tiny-pos-ratio)) + #_" 0.000" + #_(binding [*math-context* java.math.MathContext/DECIMAL128] + (cl-format false "~10,3f" tiny-neg-ratio)) + #_" -0.000" + (foo 3.14159) " 3.14| 31.42| 3.14|3.1416|3.14|3.14159" + #_(foo 314159/100000) + #_" 3.14| 31.42| 3.14|3.1416|3.14|3.14159" + (foo -3.14159) " -3.14|-31.42| -3.14|-3.142|-3.14|-3.14159" + (foo 100.0) "100.00|******|100.00| 100.0|100.00|100.0" + (foo 1234.0) "1234.00|******|??????|1234.0|1234.00|1234.0" + (foo 0.006) " 0.01| 0.06| 0.01| 0.006|0.01|0.006" +) + +(defn foo-e [x] + (format nil + "~9,2,1,,'*E|~10,3,2,2,'?,,'$E|~9,3,2,-2,'%@E|~9,2E" + x x x x)) + +;; Clojure doesn't support float/double differences in representation +(simple-tests cltl-E-tests + #_(cl-format false "~10,3e" 4/5) + #_" 8.000E-1" + #_(binding [*math-context* java.math.MathContext/DECIMAL128] + (cl-format false "~10,3e" big-pos-ratio)) + #_"2.397E+308" + #_(binding [*math-context* java.math.MathContext/DECIMAL128] + (cl-format false "~10,3e" big-neg-ratio)) + #_"-2.397E+308" + #_(binding [*math-context* java.math.MathContext/DECIMAL128] + (cl-format false "~10,3e" tiny-pos-ratio)) + #_"1.000E-340" + #_(binding [*math-context* java.math.MathContext/DECIMAL128] + (cl-format false "~10,3e" tiny-neg-ratio)) + #_"-1.000E-340" + (foo-e 0.0314159) " 3.14E-2| 31.42$-03|+.003E+01| 3.14E-2" ; Added this one + #_(foo-e 314159/10000000) + #_" 3.14E-2| 31.42$-03|+.003E+01| 3.14E-2" + (foo-e 3.14159) " 3.14E+0| 31.42$-01|+.003E+03| 3.14E+0" + (foo-e -3.14159) " -3.14E+0|-31.42$-01|-.003E+03| -3.14E+0" + (foo-e 1100.0) " 1.10E+3| 11.00$+02|+.001E+06| 1.10E+3" + ; In Clojure, this is identical to the above + ; (foo-e 1100.0L0) " 1.10L+3| 11.00$+02|+.001L+06| 1.10L+3" + (foo-e 1.1E13) "*********| 11.00$+12|+.001E+16| 1.10E+13" + (foo-e 1.1E120) "*********|??????????|%%%%%%%%%|1.10E+120" + ; Clojure doesn't support real numbers this large + ; (foo-e 1.1L1200) "*********|??????????|%%%%%%%%%|1.10L+1200" +) + +(simple-tests cltl-E-scale-tests + (map + (fn [k] (format nil "Scale factor ~2D~:*: |~13,6,2,VE|" + (- k 5) 3.14159)) ;Prints 13 lines + (range 13)) + '("Scale factor -5: | 0.000003E+06|" + "Scale factor -4: | 0.000031E+05|" + "Scale factor -3: | 0.000314E+04|" + "Scale factor -2: | 0.003142E+03|" + "Scale factor -1: | 0.031416E+02|" + "Scale factor 0: | 0.314159E+01|" + "Scale factor 1: | 3.141590E+00|" + "Scale factor 2: | 31.41590E-01|" + "Scale factor 3: | 314.1590E-02|" + "Scale factor 4: | 3141.590E-03|" + "Scale factor 5: | 31415.90E-04|" + "Scale factor 6: | 314159.0E-05|" + "Scale factor 7: | 3141590.E-06|")) + +(defn foo-g [x] + (format nil + "~9,2,1,,'*G|~9,3,2,3,'?,,'$G|~9,3,2,0,'%G|~9,2G" + x x x x)) + +;; Clojure doesn't support float/double differences in representation +(simple-tests cltl-G-tests + #_(cl-format false "~10,3g" 4/5) + #_" 0.800 " + #_(binding [*math-context* java.math.MathContext/DECIMAL128] + (cl-format false "~10,3g" big-pos-ratio)) + #_"2.397E+308" + #_(binding [*math-context* java.math.MathContext/DECIMAL128] + (cl-format false "~10,3g" big-neg-ratio)) + #_"-2.397E+308" + #_(binding [*math-context* java.math.MathContext/DECIMAL128] + (cl-format false "~10,3g" tiny-pos-ratio)) + #_"1.000E-340" + #_(binding [*math-context* java.math.MathContext/DECIMAL128] + (cl-format false "~10,3g" tiny-neg-ratio)) + #_"-1.000E-340" + (foo-g 0.0314159) " 3.14E-2|314.2$-04|0.314E-01| 3.14E-2" + #_(foo-g 314159/10000000) + #_" 3.14E-2|314.2$-04|0.314E-01| 3.14E-2" + (foo-g 0.314159) " 0.31 |0.314 |0.314 | 0.31 " + (foo-g 3.14159) " 3.1 | 3.14 | 3.14 | 3.1 " + (foo-g 31.4159) " 31. | 31.4 | 31.4 | 31. " + (foo-g 314.159) " 3.14E+2| 314. | 314. | 3.14E+2" + (foo-g 3141.59) " 3.14E+3|314.2$+01|0.314E+04| 3.14E+3" + ; In Clojure, this is identical to the above + ; (foo-g 3141.59L0) " 3.14L+3|314.2$+01|0.314L+04| 3.14L+3" + (foo-g 3.14E12) "*********|314.0$+10|0.314E+13| 3.14E+12" + (foo-g 3.14E120) "*********|?????????|%%%%%%%%%|3.14E+120" + ; Clojure doesn't support real numbers this large +) + +(defn type-clash-error [fun nargs argnum right-type wrong-type] + (format nil ;; CLtL has this format string slightly wrong + "~&Function ~S requires its ~:[~:R ~;~*~]~ + argument to be of type ~S,~%but it was called ~ + with an argument of type ~S.~%" + fun (= nargs 1) argnum right-type wrong-type)) + +(simple-tests cltl-Newline-tests + (type-clash-error 'aref nil 2 'integer 'vector) + "Function aref requires its second argument to be of type integer,\nbut it was called with an argument of type vector.\n" + (type-clash-error 'car 1 1 'list 'short-float) + "Function car requires its argument to be of type list,\nbut it was called with an argument of type short-float.\n" +) + +(simple-tests cltl-?-tests + (format nil "~? ~D" "<~A ~D>" '("Foo" 5) 7) " 7" + (format nil "~? ~D" "<~A ~D>" '("Foo" 5 14) 7) " 7" + (format nil "~@? ~D" "<~A ~D>" "Foo" 5 7) " 7" + (format nil "~@? ~D" "<~A ~D>" "Foo" 5 14 7) " 14" +) + +(defn f [n] (format nil "~@(~R~) error~:P detected." n)) + +(simple-tests cltl-paren-tests + (format nil "~@R ~(~@R~)" 14 14) "XIV xiv" + (f 0) "Zero errors detected." + (f 1) "One error detected." + (f 23) "Twenty-three errors detected." +) + +(let [*print-level* nil *print-length* 5] + (simple-tests cltl-bracket-tests + (format nil "~@[ print level = ~D~]~@[ print length = ~D~]" + *print-level* *print-length*) + " print length = 5") +) + +(let [foo "Items:~#[ none~; ~S~; ~S and ~S~ + ~:;~@{~#[~; and~] ~ + ~S~^,~}~]."] + (simple-tests cltl-bracket1-tests + (format nil foo) "Items: none." + (format nil foo 'foo) "Items: foo." + (format nil foo 'foo 'bar) "Items: foo and bar." + (format nil foo 'foo 'bar 'baz) "Items: foo, bar, and baz." + (format nil foo 'foo 'bar 'baz 'quux) "Items: foo, bar, baz, and quux." + )) + +(simple-tests cltl-curly-bracket-tests + (format nil + "The winners are:~{ ~S~}." + '(fred harry jill)) + "The winners are: fred harry jill." + + (format nil "Pairs:~{ <~S,~S>~}." '(a 1 b 2 c 3)) + "Pairs: ." + + (format nil "Pairs:~:{ <~S,~S>~}." '((a 1) (b 2) (c 3))) + "Pairs: ." + + (format nil "Pairs:~@{ <~S,~S>~}." 'a 1 'b 2 'c 3) + "Pairs: ." + + (format nil "Pairs:~:@{ <~S,~S>~}." '(a 1) '(b 2) '(c 3)) + "Pairs: ." +) + +(simple-tests cltl-angle-bracket-tests + (format nil "~10") "foo bar" + (format nil "~10:") " foo bar" + (format nil "~10:@") " foo bar " + (format nil "~10") " foobar" + (format nil "~10:") " foobar" + (format nil "~10@") "foobar " + (format nil "~10:@") " foobar " +) + +(let [donestr "Done.~^ ~D warning~:P.~^ ~D error~:P." + tellstr "~@{~@(~@[~R~^ ~]~A~)~}."] ;; The CLtL example is a little wrong here + + (simple-tests cltl-up-tests + (format nil donestr) "Done." + (format nil donestr 3) "Done. 3 warnings." + (format nil donestr 1 5) "Done. 1 warning. 5 errors." + (format nil tellstr 23) "Twenty-three." + (format nil tellstr nil "losers") "Losers." + (format nil tellstr 23 "losers") "Twenty-three losers." + (format nil "~15<~S~;~^~S~;~^~S~>" 'foo) + " foo" + (format nil "~15<~S~;~^~S~;~^~S~>" 'foo 'bar) + "foo bar" + (format nil "~15<~S~;~^~S~;~^~S~>" 'foo 'bar 'baz) + "foo bar baz" + )) + +(simple-tests cltl-up-x3j13-tests + (format nil + "~:{/~S~^ ...~}" + '((hot dog) (hamburger) (ice cream) (french fries))) + "/hot .../hamburger/ice .../french ..." + (format nil + "~:{/~S~:^ ...~}" + '((hot dog) (hamburger) (ice cream) (french fries))) + "/hot .../hamburger .../ice .../french" + + (format nil + "~:{/~S~#:^ ...~}" ;; This is wrong in CLtL + '((hot dog) (hamburger) (ice cream) (french fries))) + "/hot .../hamburger" +) + +(simple-tests pprint-table-tests + (with-out-str + (print-table [:b :a] + [{:a 1 :b {:a 'is-a} :c ["hi" "there"]} + {:b 5 :a 7 :c "dog" :d -700}])) + " +| :b | :a | +|-----------+----| +| {:a is-a} | 1 | +| 5 | 7 | +" + ;; This test is changed a bit due to the way JS prints large numbers, as well as the + ;; way Nashorn formats floating point output using Java. The number was changed + ;; (54.7e17 to 54.7e21) to make sure JS prints it in E notation (5.47E22) and Nashorn + ;; truncates at the desired precision. + (with-out-str + (print-table [:a :e :d :c] + [{:a 54.7e21 :b {:a 'is-a} :c ["hi" "there"]} + {:b 5 :a -23 :c "dog" :d 'panda}])) + " +| :a | :e | :d | :c | +|----------+----+-------+----------------| +| 5.47e+22 | | | [\"hi\" \"there\"] | +| -23 | | panda | dog | +" + ) + +(deftest test-cljs-2880 + (are [expected format] + (= expected (with-out-str (cl-format true format \a))) + "\\a" "~@c" + "\\o141" "~'o@c" + "\\u0061" "~'u@c")) + +(deftest test-cljs-2881 + (are [expected ch] + (= expected (with-out-str (cl-format true "~@c" ch))) + "\\newline" \newline + "\\space" \space + "\\tab" \tab + "\\backspace" \backspace + "\\return" \return + "\\formfeed" \formfeed + "\\\"" \" + "\\\\" \\)) diff --git a/src/test/cljs/cljs/predicates_test.cljs b/src/test/cljs/cljs/predicates_test.cljs new file mode 100644 index 0000000000..acb50f4b00 --- /dev/null +++ b/src/test/cljs/cljs/predicates_test.cljs @@ -0,0 +1,78 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.predicates-test + (:require [cljs.test :as test :refer-macros [deftest is]]) + (:import [goog.math Integer])) + +(def pred-val-table + (let [uuid (uuid "00000000-0000-0000-0000-000000000000")] + [[identity boolean? indexed? seqable? ident? uuid? inst? simple-ident? qualified-ident? simple-symbol? qualified-symbol? simple-keyword? qualified-keyword?] + [0 false false false false false false false false false false false false] + [1 false false false false false false false false false false false false] + [-1 false false false false false false false false false false false false] + [1.0 false false false false false false false false false false false false] + [true true false false false false false false false false false false false] + [[] false true true false false false false false false false false false] + [nil false false true false false false false false false false false false] + [{} false false true false false false false false false false false false] + [:foo false false false true false false true false false false true false] + [::foo false false false true false false false true false false false true] + ['foo false false false true false false true false true false false false] + ['foo/bar false false false true false false false true false true false false] + [uuid false false false false true false false false false false false false] + [(array) false false true false false false false false false false false false] + ["string" false false true false false false false false false false false false]])) + +(deftest test-preds + (let [[preds & rows] pred-val-table] + (doseq [row rows] + (let [v (first row)] + (dotimes [i (count row)] + (is (= ((nth preds i) v) (nth row i)) + (pr-str (list (nth preds i) v)))))))) + +(def int-val-table + (let [posint 10e10 + negint -10e10 + neg0 (/ ##-Inf) + ;; NOTE: we must go through a var because in self-parity tests + ;; we cannot simply import goog.module namespaces if cljs.core + ;; depends on the type - that's because cljs.core was *separately + ;; compiled* already bundling goog.modules. In many cases this is + ;; not an issue, but it is an issue if internally we use the type + ;; to make instanceof assertions - which we do for Long + natl (.getZero LongImpl) + posl (.fromNumber LongImpl posint) + negl (.fromNumber LongImpl negint) + nati Integer.ZERO + posi (Integer.fromNumber posint) + negi (Integer.fromNumber negint)] + [[identity neg? pos? integer? int? neg-int? pos-int? nat-int?] + [0 false false true true false false true ] + [neg0 false false true true false false true ] + [1 false true true true false true true ] + [-1 true false true true true false false ] + [1.0 false true true true false true true ] + [-1.0 true false true true true false false ] + [posint false true true true false true true ] + [negint true false true true true false false ] + [natl false false false true false false true ] + [posl false true false true false true true ] + [negl true false false true true false false ] + [nati false false false true false false true ] + [posi false true false true false true true ] + [negi true false false true true false false ]])) + +(deftest test-int-preds + (let [[preds & rows] int-val-table] + (doseq [row rows] + (let [v (first row)] + (dotimes [i (count row)] + (is (= ((nth preds i) v) (nth row i)) + (pr-str (list (nth preds i) v)))))))) diff --git a/src/test/cljs/cljs/primitives_test.cljs b/src/test/cljs/cljs/primitives_test.cljs new file mode 100644 index 0000000000..d2ec0b57c4 --- /dev/null +++ b/src/test/cljs/cljs/primitives_test.cljs @@ -0,0 +1,968 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.primitives-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is]] + [clojure.string :as s] + [clojure.set :as set] + [goog.object :as gobject])) + +(deftest test-js-primitives + ;; js primitives + (let [keys #(vec (js-keys %))] + (testing "Testing js primitives" + (is (= [] (keys (js-obj)) (keys (apply js-obj [])))) + (is (= ["x"] (keys (js-obj "x" "y")) (keys (apply js-obj ["x" "y"]))))))) + +(deftest test-equiv + (testing "Testing -equiv" + (is (= 1)) + (is (= 1 1)) + (is (= 1 1 1)) + (is (= 1 1 1 1)) + (is (not (= 1 2))) + (is (not (= 1 2 1))) + (is (not (= 1 1 2))) + (is (not (= 1 1 2 1))) + (is (not (= 1 1 1 2))))) + +(deftest test-arithmetic + (testing "Testing addition" + (is (= (+) 0)) + (is (= (apply + []) 0)) + (is (= (+ 1) 1)) + (is (= (apply + [1]) 1)) + (is (= (+ 1 1) 2)) + (is (= (apply + [1 1]) 2)) + (is (= (+ 1 2 3) 6)) + (is (= (apply + [1 2 3]) 6))) + + (testing "Testing subtraction" + (is (= (- 1) -1)) + (is (= (apply - [1]) -1)) + (is (= (- 1 1) 0)) + (is (= (apply - [1 1]) 0)) + (is (= (- 3 2 1) 0)) + (is (= (apply - [3 2 1]) 0))) + + (testing "Testing multiplication" + (is (= (*) 1)) + (is (= (apply * []) 1)) + (is (= (* 2) 2)) + (is (= (apply * [2]) 2)) + (is (= (* 2 3) 6)) + (is (= (apply * [2 3]) 6))) + + (testing "Testing division" + (is (= (/ 2) 0.5)) + (is (= (apply / [2]) 0.5)) + (is (= (/ 6 2) 3)) + (is (= (apply / [6 2]) 3)) + (is (= (/ 6 3 2) 1)) + (is (= (apply / [6 3 2]) 1))) + + (testing "Testing less than" + (is (= (< 1) true)) + (is (= (apply < [1]) true)) + (is (= (< 1 2) true)) + (is (= (apply < [1 2]) true)) + (is (= (< 1 1) false)) + (is (= (apply < [1 1]) false)) + (is (= (< 2 1) false)) + (is (= (apply < [2 1]) false)) + (is (= (< 1 2 3) true)) + (is (= (apply < [1 2 3]) true)) + (is (= (< 1 1 3) false)) + (is (= (apply < [1 1 3]) false)) + (is (= (< 3 1 1) false)) + (is (= (apply < [3 1 1]) false))) + + (testing "Testing less than or equal to" + (is (= (<= 1) true)) + (is (= (apply <= [1]) true)) + (is (= (<= 1 1) true)) + (is (= (apply <= [1 1]) true)) + (is (= (<= 1 2) true)) + (is (= (apply <= [1 2]) true)) + (is (= (<= 2 1) false)) + (is (= (apply <= [2 1]) false)) + (is (= (<= 1 2 3) true)) + (is (= (apply <= [1 2 3]) true)) + (is (= (<= 1 1 3) true)) + (is (= (apply <= [1 1 3]) true)) + (is (= (<= 3 1 1) false)) + (is (= (apply <= [3 1 1]) false))) + + (testing "Testing greater than" + (is (= (> 1) true)) + (is (= (apply > [1]) true)) + (is (= (> 2 1) true)) + (is (= (apply > [2 1]) true)) + (is (= (> 1 1) false)) + (is (= (apply > [1 1]) false)) + (is (= (> 1 2) false)) + (is (= (apply > [1 2]) false)) + (is (= (> 3 2 1) true)) + (is (= (apply > [3 2 1]) true)) + (is (= (> 3 1 1) false)) + (is (= (apply > [3 1 1]) false)) + (is (= (> 1 1 3) false)) + (is (= (apply > [1 1 3]) false))) + + (testing "Testing greater than or equal to" + (is (= (>= 1) true)) + (is (= (apply >= [1]) true)) + (is (= (>= 2 1) true)) + (is (= (apply >= [2 1]) true)) + (is (= (>= 1 1) true)) + (is (= (apply >= [1 1]) true)) + (is (= (>= 1 2) false)) + (is (= (apply >= [1 2]) false)) + (is (= (>= 3 2 1) true)) + (is (= (apply >= [3 2 1]) true)) + (is (= (>= 3 1 1) true)) + (is (= (apply >= [3 1 1]) true)) + (is (= (>= 3 1 2) false)) + (is (= (apply >= [3 1 2]) false)) + (is (= (>= 1 1 3) false)) + (is (= (apply >= [1 1 3]) false))) + + (testing "Testing dec/inc" + (is (= (dec 1) 0)) + (is (= (apply dec [1]) 0)) + (is (= (inc 0) 1)) + (is (= (apply inc [0]) 1))) + + (testing "Testing zero? pos? neg? even? odd?" + (is (= (zero? 0) true)) + (is (= (apply zero? [0]) true)) + (is (= (zero? 1) false)) + (is (= (apply zero? [1]) false)) + (is (= (zero? -11) false)) + (is (= (apply zero? [-11]) false)) + (is (= (pos? 0) false)) + (is (= (apply pos? [0]) false)) + (is (= (pos? 1) true)) + (is (= (apply pos? [1]) true)) + (is (= (pos? -1) false)) + (is (= (apply pos? [-1]) false)) + (is (= (neg? -1) true)) + (is (= (apply neg? [-1]) true)) + (is (neg? -1)) + (is (not (neg? 1))) + (is (neg? -1.765)) + (is (not (neg? 0))) + (is (= [true false true false true false true false] + (map integer? + [1 1.00001 0x7e7 [] (- 88 1001991881) :foo 0 "0"]))) + (is (= [true false true false true false] + (map odd? [1 2 3 4 -1 0]))) + (is (= [true false true false true true] + (map even? [2 3 4 5 -2 0])))) + + (testing "Testing max / min" + (is (= (max 1) 1)) + (is (= (apply max [1]) 1)) + (is (= (max 1 2) 2)) + (is (= (apply max [1 2]) 2)) + (is (= (max 2 1) 2)) + (is (= (apply max [2 1]) 2)) + (is (= (max 1 2 3) 3)) + (is (= (apply max [1 2 3]) 3)) + (is (= (max 1 3 2) 3)) + (is (= (apply max [1 3 2]) 3)) + + (is (= (min 1) 1)) + (is (= (apply min [1]) 1)) + (is (= (min 1 2) 1)) + (is (= (apply min [1 2]) 1)) + (is (= (min 2 1) 1)) + (is (= (apply min [2 1]) 1)) + (is (= (min 1 2 3) 1)) + (is (= (apply min [1 2 3]) 1)) + (is (= (min 2 1 3) 1)) + (is (= (apply min [3 1 3]) 1))) + + (testing "Testing mod" + (is (= (mod 4 2) 0)) + (is (= (apply mod [4 2]) 0)) + (is (= (mod 3 2) 1)) + (is (= (apply mod [3 2]) 1)) + (is (= (mod -2 5) 3))) + + (testing "Testing numeric equality in collections" + (is (= [4 3 2 1 0] + (loop [i 0 j ()] + (if (< i 5) + (recur (inc i) (conj j (fn [] i))) + (map #(%) j))))) + (is (= [[1 1] [1 2] [1 3] [2 1] [2 2] [2 3]] + (map #(%) (for [i [1 2] j [1 2 3]] (fn [] [i j])))))) + + (testing "Testing integer? predicate" + (is (integer? 0)) + (is (integer? 42)) + (is (integer? -42)) + (is (not (integer? ""))) + (is (not (integer? 1e308))) + (is (not (integer? js/Infinity))) + (is (not (integer? (- js/Infinity)))) + (is (not (integer? js/NaN)))) + + (testing "Testing integer coercions" + (is (= 42 (int 42.5))) + (is (integer? (int 42.5))) + (is (= 42 (long 42.5))) + (is (integer? (long 42.5))) + (is (= -1 (int -1.5))) + (is (= -9 (long -9.8)))) + + (testing "Testing numeric equality from collection" + (is (= 2 (:b {:a 1 :b 2}))) + (is (= 2 ('b '{:a 1 b 2}))) + (is (= 2 ({:a 1 :b 2} :b))) + (is (= 2 ({1 1 2 2} 2))) + (is (= 2 (:a {:b 1} 2))) + (is (= 2 (:a {} 2))) + (is (= 2 ({:b 1} :a 2))) + (is (= 2 ({} :a 2))) + (is (= nil (:a {}))) + (is (= nil (:a ""))) + (is (= 2 (:a "" 2))) + (is (= 2 (#{1 2 3} 2))) + (is (= 1 (apply :a '[{:a 1 a 2}]))) + (is (= 1 (apply 'a '[{a 1 :b 2}]))) + (is (= 1 (apply {:a 1} [:a]))) + (is (= 2 (apply {:a 1} [:b 2])))) + + (testing "Testing quot" + (is (= (quot 4 2) 2)) + (is (= (quot 3 2) 1)) + (is (= (quot 6 4) 1)) + (is (= (quot 0 5) 0)) + (is (= (quot 42 5) 8)) + (is (= (quot 42 -5) -8)) + (is (= (quot -42 -5) 8)) + (is (= (quot 9 3) 3)) + (is (= (quot 9 -3) -3)) + (is (= (quot -9 3) -3)) + (is (= (quot 2 -5) 0)) + (is (= (quot -2 5) 0)) + (is (= (quot 0 3) 0)) + (is (= (quot 0 -3) 0))) + + (testing "Testing mod" + (is (= (mod 4 2) 0)) + (is (= (mod 3 2) 1)) + (is (= (mod 6 4) 2)) + (is (= (mod 0 5) 0)) + (is (= (mod 4.5 2.0) 0.5)) + (is (= (mod 42 5) 2)) + (is (= (mod 9 3) 0)) + (is (= (mod 9 -3) 0)) + (is (= (mod -9 3) 0)) + (is (= (mod -9 -3) 0)) + (is (= (mod 0 3) 0)) + (is (= (mod 3216478362187432 432143214) 120355456))) + + (testing "Testing rem" + (is (= (rem 4 2) 0)) + (is (= (rem 0 5) 0)) + (is (= (rem 4.5 2.0) 0.5)) + (is (= (rem 42 5) 2)) + (is (= (rem 2 5) 2)) + (is (= (rem 2 -5) 2)) + (is (= (rem 0 3) 0))) + ) + +;; See +;; https://github.com/clojure/tools.reader#differences-from-lispreaderjava +;; about why these tests won't pass. Not clear if we should change the reader +;; or the test +;; (assert (= "baz" (name 'foo/bar/baz))) +;; (assert (= "foo/bar" (namespace 'foo/bar/baz))) +;; (assert (= "baz" (name :foo/bar/baz))) +;; (assert (= "foo/bar" (namespace :foo/bar/baz))) +;; TODO: These next two tests need Clojure 1.5 +;; (assert (= "foo" (namespace 'foo//))) +;; (assert (= "/" (name 'foo//))) + +(deftest test-symbols-and-keywords + (testing "Testing name / namespace" + (is (nil? (namespace '/))) + (is (= "/" (name '/))) + (is (= "keyword" (name :keyword)))) + + (testing "Testing str on keywords / symbols" + (is (= ":hello" (str :hello))) + (is (= "hello" (str 'hello))) + (is (= "hello:world" (str "hello" :world))) + (is (= ":helloworld" (str :hello 'world)))) + + (testing "Testing symbol ctor is idempotent" + (is (= 'a (symbol 'a)))) + + (testing "Testing constructed division symbol" + (is (= '/ (symbol "/"))) + (is (= (namespace '/) (namespace (symbol "/")))) + (is (= (hash '/) (hash (symbol "/"))))) + + (testing "Testing keyword ctor" + (is (= :a (keyword "a"))) + (is (= :a (keyword 'a))) + (is (= :a/b (keyword 'a 'b))) + (is (= :a (keyword :a)))) + + (testing "Testing name munging CLJS-1432" + (is (not= :$ :.)) + (is (not= '$ '.)))) + +(deftest test-bit-operations + (testing "Testing bit operations" + (is (= [1 0 0 40 43 49 49]) + [(bit-xor 0 1) + (bit-xor 1 1) + (bit-xor 1 0) + (bit-xor 41 1) + (bit-xor 42 1) + (bit-xor 42 1 26) + (apply bit-xor [42 1 26])]) + (is (= [0 0 1 0 1 1 1] + [(bit-and 1 0) + (bit-and 0 0) + (bit-and 1 1) + (bit-and 42 1) + (bit-and 41 1) + (bit-and 41 1 27) + (apply bit-and [41 1 27])])) + (is (= [1 0 1 43 41 59 59] + [(bit-or 1 0) + (bit-or 0 0) + (bit-or 1 1) + (bit-or 42 1) + (bit-or 41 1) + (bit-or 41 1 26) + (apply bit-or [41 1 26])])) + (is (= [1 0 0 42 32 32] + [(bit-and-not 1 0) + (bit-and-not 0 0) + (bit-and-not 1 1) + (bit-and-not 42 1) + (bit-and-not 41 1 27) + (apply bit-and-not [41 1 27])])) + (is (= [0 2 968 16649 0] + [(bit-clear 1 0) + (bit-clear 2 0) + (bit-clear 1000 5) + (bit-clear 16713 6) + (bit-clear 1024 10)])) + (is (= [0 0 992 18761 0] + [(bit-flip 1 0) + (bit-flip 2 1) + (bit-flip 1000 3) + (bit-flip 16713 11) + (bit-flip 1024 10)])) + (is (= [-2 -3 999 -16714 -1025] + [(bit-not 1) + (bit-not 2) + (bit-not -1000) + (bit-not 16713) + (bit-not 1024)])) + (is (= [1 2 1000 18761 1024] + [(bit-set 1 0) + (bit-set 2 1) + (bit-set 1000 3) + (bit-set 16713 11) + (bit-set 1024 10)])) + (is (= [true true true false true] + [(bit-test 1 0) + (bit-test 2 1) + (bit-test 1000 3) + (bit-test 16713 11) + (bit-test 1024 10)])))) + +(deftest test-apply + (testing "Testing apply" + (is (= 0 (apply + nil))) + (is (= 0 (apply + (list)))) + (is (= 1 (apply + (list 1)))) + (is (= 3 (apply + 1 (list 2)))) + (is (= 7 (apply + 1 2 (list 4)))) + (is (= 15 (apply + 1 2 4 (list 8)))) + (is (= 31 (apply + 1 2 4 8 (list 16)))) + (is (= 63 (apply + 1 2 4 8 16 (list 32)))) + (is (= 127 (apply + 1 2 4 8 16 (list 32 64)))) + (is (= 4950 (apply + (take 100 (iterate inc 0))))) + (is (= () (apply list []))) + (is (= [1 2 3] (apply list [1 2 3]))) + (is (= 6 (apply apply [+ [1 2 3]]))) + ;; apply with infinite sequence + (is (= 3 (apply (fn [& args] + (+ (nth args 0) + (nth args 1) + (nth args 2))) + (iterate inc 0)))) + (is (= [0 1 2 3 4] (take 5 (apply (fn [& m] m) (iterate inc 0))))) + (is (= [1 2 3 4 5] (take 5 (apply (fn [x & m] m) (iterate inc 0))))) + (is (= [2 3 4 5 6] (take 5 (apply (fn [x y & m] m) (iterate inc 0))))) + (is (= [3 4 5 6 7] (take 5 (apply (fn [x y z & m] m) (iterate inc 0))))) + (is (= [4 5 6 7 8] (take 5 (apply (fn [x y z a & m] m) (iterate inc 0))))) + (is (= [5 6 7 8 9] (take 5 (apply (fn [x y z a b & m] m) (iterate inc 0))))) + ;; apply arity tests + (let [single-arity-non-variadic (fn [x y z] [z y x]) + multiple-arity-non-variadic (fn ([x] x) ([x y] [y x]) ([x y z] [z y x])) + single-arity-variadic-fixedargs (fn [x y & more] [more y x]) + single-arity-variadic-nofixedargs (fn [& more] more) + multiple-arity-variadic (fn ([x] x) ([x y] [y x]) ([x y & more] [more y x]))] + (testing "arities" + (is (= [3 2 1] (apply single-arity-non-variadic [1 2 3]))) + (is (= [3 2 1] (apply single-arity-non-variadic 1 [2 3]))) + (is (= [3 2 1] (apply single-arity-non-variadic 1 2 [3]))) + (is (= 42 (apply multiple-arity-non-variadic [42]))) + (is (= [2 1] (apply multiple-arity-non-variadic [1 2]))) + (is (= [2 1] (apply multiple-arity-non-variadic 1 [2]))) + (is (= [3 2 1] (apply multiple-arity-non-variadic [1 2 3]))) + (is (= [3 2 1] (apply multiple-arity-non-variadic 1 [2 3]))) + (is (= [3 2 1] (apply multiple-arity-non-variadic 1 2 [3]))) + (is (= [[3 4 5] 2 1] (apply single-arity-variadic-fixedargs [1 2 3 4 5]))) + (is (= [[3 4 5] 2 1] (apply single-arity-variadic-fixedargs 1 [2 3 4 5]))) + (is (= [[3 4 5] 2 1] (apply single-arity-variadic-fixedargs 1 2 [3 4 5]))) + (is (= [[3 4 5] 2 1] (apply single-arity-variadic-fixedargs 1 2 3 [4 5]))) + (is (= [[3 4 5] 2 1] (apply single-arity-variadic-fixedargs 1 2 3 4 [5]))) + (is (= [3 4 5] (take 3 (first (apply single-arity-variadic-fixedargs (iterate inc 1)))))) + (is (= [2 1] (rest (apply single-arity-variadic-fixedargs (iterate inc 1))))) + (is (= [1 2 3 4 5] (apply single-arity-variadic-nofixedargs [1 2 3 4 5]))) + (is (= [1 2 3 4 5] (apply single-arity-variadic-nofixedargs 1 [2 3 4 5]))) + (is (= [1 2 3 4 5] (apply single-arity-variadic-nofixedargs 1 2 [3 4 5]))) + (is (= [1 2 3 4 5] (apply single-arity-variadic-nofixedargs 1 2 3 [4 5]))) + (is (= [1 2 3 4 5] (apply single-arity-variadic-nofixedargs 1 2 3 4 [5]))) + (is (= [1 2 3 4 5] (take 5 (apply single-arity-variadic-nofixedargs (iterate inc 1))))) + (is (= 42 (apply multiple-arity-variadic [42]))) + (is (= [2 1] (apply multiple-arity-variadic [1 2]))) + (is (= [2 1] (apply multiple-arity-variadic 1 [2]))) + (is (= [[3 4 5] 2 1] (apply multiple-arity-variadic [1 2 3 4 5]))) + (is (= [[3 4 5] 2 1] (apply multiple-arity-variadic 1 [2 3 4 5]))) + (is (= [[3 4 5] 2 1] (apply multiple-arity-variadic 1 2 [3 4 5]))) + (is (= [[3 4 5] 2 1] (apply multiple-arity-variadic 1 2 3 [4 5]))) + (is (= [[3 4 5] 2 1] (apply multiple-arity-variadic 1 2 3 4 [5]))) + (is (= [3 4 5] (take 3 (first (apply multiple-arity-variadic (iterate inc 1)))))) + (is (= [2 1] (rest (apply multiple-arity-variadic (iterate inc 1))))))))) + +(deftest test-booleans + (testing "Testing boolean predicates" + (is (= [true false true false false false true true false false] + [(true? true) + (true? false) + (false? false) + (false? true) + (true? js/undefined) + (false? js/undefined) + (boolean? true) + (boolean? false) + (boolean? nil) + (boolean? js/undefined)])))) + +(deftest test-try-catch + (let [a (atom nil)] + (testing "Testing try/catch" + (is (= 1 (try 1))) + (is (= 2 (try 1 (throw (js/Error.)) (catch js/Error e 2)))) + (is (= 2 (try 1 (throw (js/Error.)) (catch js/Error e 1 2)))) + (is (= 2 (try 1 (throw (js/Error.)) (catch js/Error e 2) (catch :default e 3)))) + (is (= 3 (try 1 (throw true) (catch js/Error e 2) (catch :default e 3)))) + (is (= 2 (try 1 (throw 2) (catch js/Error e 3) (catch :default e e)))) + (is (= 1 (try 1 (finally (reset! a 42))))) + (is (= 42 (deref a)))))) + +(deftest test-list-comprehensions + (let [v [1 2 3]] + (testing "Testing list comprehensions" + (is (= v (for [e v] e))) + (is (= [[1 1] [2 4] [3 9]] (for [e v :let [m (* e e)]] [e m]))) + (is (= [1 2] (for [e v :while (< e 3)] e))) + (is (= [3] (for [e v :when (> e 2)] e))) + (is (= [[1 1] [2 4]] (for [e v :while (< e 3) :let [m (* e e)]] [e m])))))) + +(deftest test-partial-and-comp + (let [a10 (partial + 10) + a20 (partial + 10 10) + a21 (partial + 10 10 1) + a22 (partial + 10 5 4 3) + a23 (partial + 10 5 4 3 1)] + (testing "Testing partial" + (is (= 110 (a10 100))) + (is (= 120 (a20 100))) + (is (= 121 (a21 100))) + (is (= 122 (a22 100))) + (is (= 123 (a23 100))))) + (let [n2 (comp first rest) + n3 (comp first rest rest) + n4 (comp first rest rest rest) + n5 (comp first rest rest rest rest) + n6 (comp first rest rest rest rest rest)] + (testing "Testing comp" + (is (= 2 (n2 [1 2 3 4 5 6 7]))) + (is (= 3 (n3 [1 2 3 4 5 6 7]))) + (is (= 4 (n4 [1 2 3 4 5 6 7]))) + (is (= 5 (n5 [1 2 3 4 5 6 7]))) + (is (= 6 (n6 [1 2 3 4 5 6 7])))))) + +(deftest test-regexps + (testing "Testing regexps" + (let [r1 #"foo", r2 (re-pattern r1)] + (is (= r1 r2))) + (is (= (str (re-pattern "f(.)o")) (str (js* "/f(.)o/")))) + (is (= (re-find (re-pattern "foo") "foo bar foo baz foo zot") "foo")) + (is (= (re-find (re-pattern "f(.)o") "foo bar foo baz foo zot") ["foo" "o"])) + (is (= (re-matches (re-pattern "foo") "foo") "foo")) + (is (= (re-matches (re-pattern "foo") "foo bar foo baz foo zot") nil)) + (is (= (re-matches (re-pattern "foo.*") "foo bar foo baz foo zot") "foo bar foo baz foo zot")) + (is (= (re-seq (re-pattern "foo") "foo bar foo baz foo zot") (list "foo" "foo" "foo"))) + (is (= (re-seq (re-pattern "f(.)o") "foo bar foo baz foo zot") (list ["foo" "o"] ["foo" "o"] ["foo" "o"]))) + (is (= (re-matches (re-pattern "(?i)foo") "Foo") "Foo")) + ; new RegExp("").source => "(?:)" on webkit-family envs, "" elsewhere + (is (#{"#\"\"" "#\"(?:)\""} (pr-str #""))) + (is (= (re-find (re-pattern "[\u2028]") " \u2028 ") "\u2028")))) ; regression test for CLJS-889 + +(deftest test-arrays + (testing "Testing array operations" + (let [a (to-array [1 2 3])] + (testing "basic ops" + (is (= [10 20 30] (seq (amap a i ret (* 10 (aget a i)))))) + (is (= 6 (areduce a i ret 0 (+ ret (aget a i))))) + (is (= (seq a) (seq (to-array [1 2 3])))) + (is (= 42 (aset a 0 42))) + (is (not= (seq a) (seq (to-array [1 2 3])))) + (is (not= a (aclone a))))) + (let [a (array (array 1 2 3) (array 4 5 6))] + (testing "aget" + (is (= (aget a 0 1) 2)) + (is (= (apply aget a [0 1]) 2)) + (is (= (aget a 1 1) 5)) + (is (= (apply aget a [1 1]) 5)) + (aset a 0 0 "foo") + (is (= (aget a 0 0) "foo")) + (apply aset a [0 0 "bar"]) + (is (= (aget a 0 0) "bar")))))) + +(defn- primitive-arrays-equal + [a b] + (= (js->clj a) (js->clj b))) + +(deftest test-make-array + (testing "Testing make-array" + (is (primitive-arrays-equal #js [] (make-array 0))) + (is (primitive-arrays-equal #js [] (apply make-array [0]))) + (is (primitive-arrays-equal #js [nil] (make-array 1))) + (is (primitive-arrays-equal #js [nil] (apply make-array [1]))) + (is (primitive-arrays-equal #js [nil nil] (make-array 2))) + (is (primitive-arrays-equal #js [nil nil] (apply make-array [2]))) + (is (primitive-arrays-equal #js [] (make-array nil 0))) + (is (primitive-arrays-equal #js [] (apply make-array [nil 0]))) + (is (primitive-arrays-equal #js [nil] (make-array nil 1))) + (is (primitive-arrays-equal #js [nil] (apply make-array [nil 1]))) + (is (primitive-arrays-equal #js [nil nil] (make-array nil 2))) + (is (primitive-arrays-equal #js [nil nil] (apply make-array [nil 2]))) + (is (primitive-arrays-equal #js [] (make-array nil 0 0))) + (is (primitive-arrays-equal #js [] (apply make-array [nil 0 0]))) + (is (primitive-arrays-equal #js [] (make-array nil 0 1))) + (is (primitive-arrays-equal #js [] (apply make-array [nil 0 1]))) + (is (primitive-arrays-equal #js [#js []] (make-array nil 1 0))) + (is (primitive-arrays-equal #js [#js []] (apply make-array [nil 1 0]))) + (is (primitive-arrays-equal #js [#js [] #js []] (make-array nil 2 0))) + (is (primitive-arrays-equal #js [#js [] #js []] (apply make-array [nil 2 0]))) + (is (primitive-arrays-equal #js [#js [nil]] (make-array nil 1 1))) + (is (primitive-arrays-equal #js [#js [nil]] (apply make-array [nil 1 1]))) + (is (primitive-arrays-equal #js [#js [nil] #js [nil]] (make-array nil 2 1))) + (is (primitive-arrays-equal #js [#js [nil] #js [nil]] (apply make-array [nil 2 1]))) + (is (primitive-arrays-equal #js [#js [nil nil] #js [nil nil]] (make-array nil 2 2))) + (is (primitive-arrays-equal #js [#js [nil nil] #js [nil nil]] (apply make-array [nil 2 2]))) + (is (primitive-arrays-equal #js [] (make-array nil 0 0 0))) + (is (primitive-arrays-equal #js [] (apply make-array [nil 0 0 0]))) + (is (primitive-arrays-equal #js [] (make-array nil 0 1 1))) + (is (primitive-arrays-equal #js [] (apply make-array [nil 0 1 1]))) + (is (primitive-arrays-equal #js [#js []] (make-array nil 1 0 0))) + (is (primitive-arrays-equal #js [#js []] (apply make-array [nil 1 0 0]))) + (is (primitive-arrays-equal #js [#js [] #js []] (make-array nil 2 0 0))) + (is (primitive-arrays-equal #js [#js [] #js []] (apply make-array [nil 2 0 0]))) + (is (primitive-arrays-equal #js [#js [#js []]] (make-array nil 1 1 0))) + (is (primitive-arrays-equal #js [#js [#js []]] (apply make-array [nil 1 1 0]))) + (is (primitive-arrays-equal #js [#js [#js [nil]]] (make-array nil 1 1 1))) + (is (primitive-arrays-equal #js [#js [#js [nil]]] (apply make-array [nil 1 1 1]))) + (is (primitive-arrays-equal #js [#js [#js [nil nil] #js [nil nil]] #js [#js [nil nil] #js [nil nil]]] + (make-array nil 2 2 2))) + (is (primitive-arrays-equal #js [#js [#js [nil nil] #js [nil nil]] #js [#js [nil nil] #js [nil nil]]] + (apply make-array [nil 2 2 2]))))) + +(deftest test-comparable + (testing "Testing IComparable" + (is (= 0 (compare false false))) + (is (= -1 (compare false true))) + (is (= 1 (compare true false))) + + (is (= -1 (compare 0 1))) + (is (= -1 (compare -1 1))) + (is (= 0 (compare 1 1))) + (is (= 1 (compare 1 0))) + (is (= 1 (compare 1 -1))) + + (is (= 0 (compare "cljs" "cljs"))) + (is (= 0 (compare :cljs :cljs))) + (is (= 0 (compare 'cljs 'cljs))) + (is (= -1 (compare "a" "b"))) + (is (= -1 (compare :a :b))) + (is (= -1 (compare 'a 'b))) + ;; cases involving ns + (is (= -1 (compare :b/a :c/a))) + (is (= -1 (compare :c :a/b))) + (is (= 1 (compare :a/b :c))) + (is (= -1 (compare 'b/a 'c/a))) + (is (= -1 (compare 'c 'a/b))) + (is (= 1 (compare 'a/b 'c))) + + ;; This is different from clj. clj gives -2 next 3 tests + (is (= -1 (compare "a" "c"))) + (is (= -1 (compare :a :c))) + (is (= -1 (compare 'a 'c))) + + (is (= -1 (compare [1 2] [1 1 1]))) + (is (= -1 (compare [1 2] [1 2 1]))) + (is (= -1 (compare [1 1] [1 2]))) + (is (= 0 (compare [1 2] [1 2]))) + (is (= 1 (compare [1 2] [1 1]))) + (is (= 1 (compare [1 1 1] [1 2]))) + (is (= 1 (compare [1 1 2] [1 1 1]))) + (is (= 0 (compare [] []))) + (is (= 0 (compare (vec #js []) []))) + (is (= 0 (compare (with-meta [] {}) []))) + (is (= 0 (compare (pop [1]) []))) + + (is (= -1 (compare (subvec [1 2 3] 1) (subvec [1 2 4] 1)))) + (is (= 0 (compare (subvec [1 2 3] 1) (subvec [1 2 3] 1)))) + (is (= 1 (compare (subvec [1 2 4] 1) (subvec [1 2 3] 1)))) + (is (= 0 (compare (subvec [1] 0 0) (subvec [2] 0 0)))) + + (is (= 0 (compare (js/Date. 2015 2 8 19 13 00 999) + (js/Date. 2015 2 8 19 13 00 999)))) + (is (= -1 (compare (js/Date. 2015 2 8 19 12 00 999) + (js/Date. 2015 2 8 19 13 00 999)))) + (is (= 1 (compare (js/Date. 2015 2 8 19 14 00 999) + (js/Date. 2015 2 8 19 13 00 999)))) + )) + +(deftest test-dot + (let [s "abc"] + (testing "Testing dot operations" + (is (= 3 (.-length s))) + (is (= 3 (. s -length))) + (is (= 3 (. (str 138) -length))) + (is (= 3 (. "abc" -length))) + (is (= "bc" (.substring s 1))) + (is (= "bc" (.substring "abc" 1))) + (is (= "bc" ((memfn substring start) s 1))) + (is (= "bc" (. s substring 1))) + (is (= "bc" (. s (substring 1)))) + (is (= "bc" (. s (substring 1 3)))) + (is (= "bc" (.substring s 1 3))) + (is (= "ABC" (. s (toUpperCase)))) + (is (= "ABC" (. "abc" (toUpperCase)))) + (is (= "ABC" ((memfn toUpperCase) s))) + (is (= "BC" (. (. s (toUpperCase)) substring 1))) + (is (= 2 (.-length (. (. s (toUpperCase)) substring 1)))) + ))) + +(deftest test-type + (is (= nil (type nil))) + (is (= js/Number (type 0))) + (is (= js/Number (type js/NaN))) + (is (= js/Number (type js/Infinity))) + (is (= js/String (type ""))) + (is (= js/Boolean (type true))) + (is (= js/Boolean (type false))) + (is (= js/Function (type identity))) + (is (= js/Function (type (fn [x] x)))) + (is (= js/Object (type (js-obj)))) + (is (= js/Array (type (array)))) + (is (= js/Date (type (js/Date.)))) + (is (= js/Function (type js/Object)))) + +(deftest test-instance? + (is (not (instance? js/Object nil))) + (is (not (instance? js/Number 0))) + (is (not (instance? js/Number js/NaN))) + (is (not (instance? js/Number js/Infinity))) + (is (not (instance? js/String ""))) + (is (not (instance? js/Boolean true))) + (is (not (instance? js/Boolean false))) + (is (instance? js/Number (js/Number. 0))) + (is (instance? js/Object (js/Number. 0))) + (is (instance? js/String (js/String. ""))) + (is (instance? js/Object (js/String. ""))) + (is (instance? js/Boolean (js/Boolean.))) + (is (instance? js/Object (js/Boolean.))) + (is (instance? js/Function identity)) + (is (instance? js/Object identity)) + (is (instance? js/Function (fn [x] x))) + (is (instance? js/Object (js-obj))) + (is (instance? js/Array (array))) + (is (instance? js/Object (array))) + (is (instance? js/Date (js/Date.))) + (is (instance? js/Object (js/Date.))) + (is (instance? js/Function js/Object))) + +(deftest test-case + (testing "Test case expr" + (let [x 1] + (is (= (case x 1 :one) :one))) + (let [x 1] + (is (= (case x 2 :two :default) :default))) + (let [x 1] + (is (= (try + (case x 3 :three) + (catch js/Error e + :fail)) + :fail))) + (let [x 1] + (is (= (case x + (1 2 3) :ok + :fail) + :ok))) + (let [x [:a :b]] + (is (= (case x + [:a :b] :ok) + :ok))) + (let [a 'a] + (is (= (case a + nil nil + & :amp + :none) + :none))) + (let [a '&] + (is (= (case a + nil nil + & :amp + :none) + :amp))) + (let [foo 'a] + (testing "multiple match" + (is (= (case foo + (a b c) :sym + :none) + :sym)) + (is (= (case foo + (b c d) :sym + :none) + :none)))) + )) + +(deftest test-inext + (testing "Testing INext" + (is (= nil (next nil))) + (is (= nil (next (seq (array 1))))) + (is (= '(2 3) (next (seq (array 1 2 3))))) + (is (= nil (next (reverse (seq (array 1)))))) + (is (= '(2 1) (next (reverse (seq (array 1 2 3)))))) + (is (= nil (next (cons 1 nil)))) + (is (= '(2 3) (next (cons 1 (cons 2 (cons 3 nil)))))) + (is (= nil (next (lazy-seq (cons 1 nil))))) + (is (= '(2 3) (next (lazy-seq + (cons 1 + (lazy-seq + (cons 2 + (lazy-seq (cons 3 nil))))))))) + (is (= nil (next (list 1)))) + (is (= '(2 3) (next (list 1 2 3)))) + (is (= nil (next [1]))) + (is (= '(2 3) (next [1 2 3]))) + (is (= nil (next (range 1 2)))) + (is (= '(2 3) (next (range 1 4)))) + )) + +;; this fails in v8 - why? +;; (assert (= "symbol\"'string" (pr-str (str 'symbol \" \' "string")))) +(deftest test-misc + (testing "Testing miscellaneous operations" + (is (= 9 (reduce + (next (seq (array 1 2 3 4)))))) + (is (not (= "one" "two"))) + (is (= 3 (count "abc"))) + (is (= 4 (count (array 1 2 3 4)))) + (is (= "c" (nth "abc" 2))) + (is (= "quux" (nth "abc" 3 "quux"))) + (is (= 1 (nth (array 1 2 3 4) 0))) + (is (= "val" (nth (array 1 2 3 4) 4 "val"))) + (is (= "b" (get "abc" 1))) + (is (= "harriet" (get "abcd" 4 "harriet"))) + (is (= 4 (get (array 1 2 3 4) 3))) + (is (= "zot" (get (array 1 2 3 4) 4 "zot"))) + (is (= 10 (reduce + (array 1 2 3 4)))) + (is (= 20 (reduce + 10 (array 1 2 3 4)))) + (is (= "cabd" (let [jumble (fn [a b] (str (apply str (reverse (str a))) b))] + (reduce jumble "abcd")))) + (is (= "cafrogbd" (let [jumble (fn [a b] (str (apply str (reverse (str a))) b))] + (reduce jumble "frog" "abcd")))) + (is (= [3] (nthnext [1 2 3] 2))) + (assert (not= 1 2)) + (is (not (not= 1 1))) + (is (not (not-empty []))) + (is (boolean (not-empty [1 2 3]))) + (is (= "joel" (min-key count "joel" "tom servo" "crooooooooow"))) + (is (= "crooooooooow" (max-key count "joel" "tom servo" "crooooooooow"))) + (is (= (partition-all 4 [1 2 3 4 5 6 7 8 9]) + [[1 2 3 4] [5 6 7 8] [9]])) + (is (= (partition-all 4 2 [1 2 3 4 5 6 7 8 9]) + [[1 2 3 4] [3 4 5 6] [5 6 7 8] [7 8 9] [9]])) + (is (= [true true] (take-while true? [true true 2 3 4]))) + (is (= [[true true] [false false false] [true true]] + (partition-by true? [true true false false false true true]))) + (is (= [0 2 4 6 8 10] (take-nth 2 [0 1 2 3 4 5 6 7 8 9 10]))) + (let [sf (some-fn number? keyword? symbol?)] + (testing "Testing some-fn" + (is (sf :foo 1)) + (is (sf :foo)) + (is (sf 'bar 1)) + (is (not (sf [] ()))))) + (let [ep (every-pred number? zero?)] + (testing "Testing every-pred" + (is (ep 0 0 0)) + (is (not (ep 1 2 3 0))))) + (is ((complement number?) :foo)) + (is (= [1 [2 3] [1 2 3]] ((juxt first rest seq) [1 2 3]))) + (is (= 5 (max 1 2 3 4 5))) + (is (= 5 (max 5 4 3 2 1))) + (is (= 5.5 (max 1 2 3 4 5 5.5))) + (is (= 1 (min 5 4 3 2 1))) + (is (= 1 (min 1 2 3 4 5))) + (is (= 0.5 (min 5 4 3 0.5 2 1))) + (let [x (array 1 2 3)] + (testing "Testing setting property on JS array" + (set! (.-foo x) :hello) + (is (= (.-foo x) :hello)))) + ;; last + (is (= nil (last nil))) + (is (= 3 (last [1 2 3]))) + ;; dotimes + (let [s (atom [])] + (dotimes [n 5] + (swap! s conj n)) + (is (= [0 1 2 3 4] @s))) + ;; doseq + (let [v [1 2 3 4 5] + s (atom ())] + (doseq [n v] (swap! s conj n)) + (is (= @s (reverse v)))) + ;; memoize + (let [f (memoize (fn [] (rand)))] + (f) + (is (= (f) (f)))) + ;; range + (is (= (range 10) (list 0 1 2 3 4 5 6 7 8 9))) + (is (= (range 10 20) (list 10 11 12 13 14 15 16 17 18 19))) + (is (= (range 10 20 2) (list 10 12 14 16 18))) + (is (= (take 20 (range)) (list 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19))) + ;; group-by + (let [d (group-by second {:a 1 :b 2 :c 1 :d 4 :e 1 :f 2})] + (testing "group-by" + (is (= 3 (count (get d 1)))) + (is (= 2 (count (get d 2)))) + (is (= 1 (count (get d 4)))))) + (is (= {1 2 3 4 5 6} (merge {1 2} {3 4} {5 6}))) + (is (= {1 2 3 4} (merge {1 2} {3 4} nil))) + ;; frequencies + (is (= {:a 3 :b 2} (frequencies [:a :b :a :b :a]))) + ;; reductions + (is (= [1 3 6 10 15] (reductions + [1 2 3 4 5]))) + ;; keep + (is (= [1 3 5 7 9] (keep #(if (odd? %) %) [1 2 3 4 5 6 7 8 9 10]))) + (is (= [2 4 6 8 10] (keep #(if (even? %) %) [1 2 3 4 5 6 7 8 9 10]))) + ;; keep-indexed + (is (= [1 3 5 7 9] (keep-indexed #(if (odd? %1) %2) [0 1 2 3 4 5 6 7 8 9 10]))) + (is (= [2 4 5] (keep-indexed #(if (pos? %2) %1) [-9 0 29 -7 45 3 -8]))) + ;; map-indexed + (is (= [[0 :a] [1 :b] [2 :c]] (map-indexed #(vector % %2) [:a :b :c]))) + ;; merge-with + (is (= '{"Foo" ("foo" "FOO" "fOo"), "Bar" ("bar" "BAR" "BAr"), "Baz" ["baz"], "Qux" ["qux" "quux"]} + (merge-with concat + {"Foo" ["foo" "FOO"] + "Bar" ["bar" "BAR"] + "Baz" ["baz"]} + {"Foo" ["fOo"] + "Bar" ["BAr"] + "Qux" ["qux" "quux"]}))) + (is (= {:a 111, :b 102, :c 13} + (merge-with + + {:a 1 :b 2 :c 3} + {:a 10 :c 10} + {:a 100 :b 100}))) + (is (= {:a 3, :b 102, :c 13} + (apply merge-with [+ + {:a 1 :b 100} + {:a 1 :b 2 :c 3} + {:a 1 :c 10}]))) + (is (= '[a c e] (replace '[a b c d e] [0 2 4]))) + (is (= [:one :zero :two :zero] + (replace {0 :zero 1 :one 2 :two} '(1 0 2 0)))) + ;; split-at + (is (= [[1 2] [3 4 5]] (split-at 2 [1 2 3 4 5]))) + ;; split-with + (is (= [[1 2 3] [4 5]] (split-with (partial >= 3) [1 2 3 4 5]))) + ;; trampoline + (is (= 10000 (trampoline (fn f [n] (if (>= n 10000) n #(f (inc n)))) 0))) + ;; vary-meta + (is (= {:a 1} (meta (vary-meta [] assoc :a 1)))) + (is (= {:a 1 :b 2} (meta (vary-meta (with-meta [] {:b 2}) assoc :a 1)))) + ;; comparator + (is (= [1 1 2 2 3 5] (seq (.sort (to-array [2 3 1 5 2 1]) (comparator <))))) + (is (= [5 3 2 2 1 1] (seq (.sort (to-array [2 3 1 5 2 1]) (comparator >))))) + (is (= (hash 'foo) (hash (symbol "foo")))) + (is (= (hash 'foo/bar) (hash (symbol "foo" "bar")))) + (is (= (lazy-cat [1] [2] [3]) '(1 2 3))) + ;; Make sure take/drop raise an error when given nil as an argument + (is (try (do (take nil [1 2 3]) false) + (catch js/Error e true))) + (is (try (do (drop nil [1 2 3]) false) + (catch js/Error e true))) + (is (try (do (take-nth nil [1 2 3]) false) + (catch js/Error e true))))) + +(deftest test-496 + (is (= (char 65) \A)) + (is (= (char \A) \A))) + +(deftest test-717 + (testing "Testing CLJS-717, JS literals" + (is (array? #js [1 2 3])) + (is (= (alength #js [1 2 3]) 3)) + (is (= (seq #js [1 2 3]) (seq [1 2 3]))) + (is (= (set (js-keys #js {:foo "bar" :baz "woz"})) #{"foo" "baz"})) + (is (= (gobject/get #js {:foo "bar"} "foo") "bar")) + (is (= (gobject/get #js {"foo" "bar"} "foo") "bar")) + (is (array? (gobject/get #js {"foo" #js [1 2 3]} "foo"))) + (is (= (seq (gobject/get #js {"foo" #js [1 2 3]} "foo")) '(1 2 3))))) + +(deftest test-1556 + (testing "Testing CLJS-1556, JS object literal code emission, beginning of statement" + ;; Really testing that this evaluates properly + (is (= 1 (do #js {:a 1} + 1))) + (is (= 1 (gobject/get #js {:a 1} "a"))) + (is (= 1 (.-a #js {:a 1}))))) + +(deftest test-3352 + (is (== ##-Inf (/ -0.0))) + (is (== ##Inf (/ -0)))) + +(deftest test-char? + (is (char? "0")) + (is (char? (String/fromCharCode 13))) + (is (char? (String/fromCharCode 10))) + (is (char? \newline)) + (is (char? \space)) + (is (char? "0")) + (is (char? "\u0080")) + (is (char? "\uFFFD"))) \ No newline at end of file diff --git a/src/test/cljs/cljs/printing_test.cljs b/src/test/cljs/cljs/printing_test.cljs new file mode 100644 index 0000000000..008172ed1f --- /dev/null +++ b/src/test/cljs/cljs/printing_test.cljs @@ -0,0 +1,130 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.printing-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is]] + [clojure.string :as s] + [clojure.set :as set])) + +(deftest test-print-knobs + (testing "Testing printing knobs" + (is (= (binding [*print-length* 0] (str [1 2 3 4 5 6 7 8 9 0])) + "[...]")) + (is (= (binding [*print-length* 1] (str [1 2 3 4 5 6 7 8 9 0])) + "[1 ...]")) + (is (= (binding [*print-length* 2] (str [1 2 3 4 5 6 7 8 9 0])) + "[1 2 ...]")) + (is (= (binding [*print-length* 10] (str [1 2 3 4 5 6 7 8 9 0])) + "[1 2 3 4 5 6 7 8 9 0]")) + ;; CLJS-804 + (is (= (binding [*print-length* 10] (str {:foo "bar"})) + "{:foo \"bar\"}")) + (is (= (binding [*print-length* 0] (str {:foo "bar" :baz "woz"})) + "{...}")) + (is (#{"{:foo \"bar\", ...}" "{:baz \"woz\", ...}"} + (binding [*print-length* 1] (str {:foo "bar" :baz "woz"})))) + (is (#{"{:foo \"bar\", :baz \"woz\"}" "{:baz \"woz\", :foo \"bar\"}"} + (binding [*print-length* 10] (str {:foo "bar" :baz "woz"}))))) + ) + +(deftest test-print-with-opts + (testing "Testing printing with opts - :more-marker" + ; CLJS-1016 + (is (= (pr-str-with-opts [[1 2 3]] {:more-marker "" :print-length 0}) + "[]")) + (is (= (pr-str-with-opts [[1 2 3]] {:more-marker "\u2026" :print-length 1}) + "[1 \u2026]")) + (is (#{"#{1 2 \u2026}" "#{1 3 \u2026}" + "#{2 1 \u2026}" "#{2 3 \u2026}" + "#{3 1 \u2026}" "#{3 2 \u2026}"} + (pr-str-with-opts [#{1 2 3}] {:more-marker "\u2026" :print-length 2}))) + (is (= (pr-str-with-opts ['(1 2 3)] {:more-marker "\u2026" :print-length 2}) + "(1 2 \u2026)")) + (is (#{"{:1 1, :2 2, \u2026}" "{:1 1, :3 3, \u2026}" + "{:2 2, :1 1, \u2026}" "{:2 2, :3 3, \u2026}" + "{:3 3, :1 1, \u2026}" "{:3 3, :2 2, \u2026}"} + (pr-str-with-opts [{:1 1 :2 2 :3 3}] {:more-marker "\u2026" :print-length 2})))) + + (testing "Testing printing with opts - :alt-impl" + ; CLJS-1010 + (is (= (pr-str-with-opts [[1 2 3]] {:alt-impl (fn [obj writer opts] ((:fallback-impl opts) obj writer opts))}) + "[1 2 3]")) + (is (= (pr-str-with-opts [[1 2 3]] {:alt-impl (fn [obj writer opts] (-write writer (str "<" obj ">")))}) + "<[1 2 3]>")) + (is (= (pr-str-with-opts [[:start 1 2 [:middle] 3 4 :end] :standalone] {:alt-impl (fn [obj writer opts] + (if (keyword? obj) + (-write writer (str "|" (name obj) "|")) + ((:fallback-impl opts) obj writer opts)))}) + "[|start| 1 2 [|middle|] 3 4 |end|] |standalone|")) + (is (= (pr-str-with-opts [[1 2 3]] {:alt-impl (fn [obj writer opts])}) + ""))) + ) + +(defrecord PrintMe [a b]) + +(deftest test-printing + (testing "Testing pr-str" + (is (= (pr-str) "")) + (is (= (pr-str 1) "1")) + (is (= (pr-str -1) "-1")) + (is (= (pr-str -1.5) "-1.5")) + (is (= (pr-str -0.0) "-0.0")) + (is (= (pr-str [3 4]) "[3 4]")) + (is (= (pr-str "foo") "\"foo\"")) + (is (= (pr-str :hello) ":hello")) + (is (= (pr-str 'goodbye) "goodbye")) + ;;(is (= (pr-str #{1 2 3}) "#{1 2 3}")) + (is (= (pr-str '(7 8 9)) "(7 8 9)")) + (is (= (pr-str '(deref foo)) "(deref foo)")) + (is (= (pr-str '(quote bar)) "(quote bar)")) + (is (= (pr-str 'foo/bar) "foo/bar")) + (is (= (pr-str \a) "\"a\"")) + (is (= (pr-str :foo/bar) ":foo/bar")) + (is (= (pr-str nil) "nil")) + (is (= (pr-str true) "true")) + (is (= (pr-str false) "false")) + (is (= (pr-str "string") "\"string\"")) + (is (= (pr-str ["üñîçó∂£" :ทดสอบ/你好 'こんにちは]) "[\"üñîçó∂£\" :ทดสอบ/你好 こんにちは]")) + (is (= (pr-str "escape chars \t \r \n \\ \" \b \f") "\"escape chars \\t \\r \\n \\\\ \\\" \\b \\f\"")) + (is (= (pr-str (PrintMe. 1 2)) "#cljs.printing-test.PrintMe{:a 1, :b 2}")) + (is (= (pr-str (js/Date. "2010-11-12T13:14:15.666-05:00")) + "#inst \"2010-11-12T18:14:15.666-00:00\"")) + (doseq [month (range 1 13) + day (range 1 29) + hour (range 1 23)] + (let [pad (fn [n] + (if (< n 10) + (str "0" n) + n)) + inst (str "2010-" (pad month) "-" (pad day) "T" (pad hour) ":14:15.666-00:00")] + (is (= (pr-str (js/Date. inst)) (str "#inst \"" inst "\""))))) + (let [uuid-str "550e8400-e29b-41d4-a716-446655440000" + uuid (cljs.core/uuid uuid-str)] + (is (= (pr-str uuid) (str "#uuid \"" uuid-str "\"")))) + ;; pr-str PersistentQueueSeq - CLJS-800 + (is (= (pr-str (rest (conj cljs.core.PersistentQueue.EMPTY 1 2 3))) "(2 3)")) + (is (= "\"asdf\" \"asdf\"" (pr-str "asdf" "asdf"))) + ;; Different hash map order on self-host + (is (#{"[1 true {:a 2, :b \"x\\\"y\"} #js [3 4]]" + "[1 true {:b \"x\\\"y\", :a 2} #js [3 4]]"} + (pr-str [1 true {:a 2 :b "x\"y"} (array 3 4)])))) + (testing "Testing print-str" + (is (= (print-str "asdf") "asdf"))) + (testing "Testing println-str" + (is (= (println-str "asdf") "asdf\n"))) + (testing "Testing prn-str" + (is (= (prn-str) "\n")) + (is (= (prn-str "asdf") "\"asdf\"\n")) + ;; Different hash map order on self-host + (is (#{"[1 true {:a 2, :b 42} #js [3 4]]\n" + "[1 true {:b 42, :a 2} #js [3 4]]\n"} + (prn-str [1 true {:a 2 :b 42} (array 3 4)])))) + (testing "Testing with-out-str" + (is (= "12" (with-out-str (print 1) (print 2)))) + (is (= "12" (with-out-str (*print-fn* 1) (*print-fn* 2)))))) diff --git a/src/test/cljs/cljs/proxy_test.cljs b/src/test/cljs/cljs/proxy_test.cljs new file mode 100644 index 0000000000..36b53d7cdf --- /dev/null +++ b/src/test/cljs/cljs/proxy_test.cljs @@ -0,0 +1,33 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.proxy-test + (:refer-global :only [Object]) + (:require [cljs.test :as test :refer-macros [deftest testing is are]] + [cljs.proxy :refer [builder]] + [goog.object :as gobj])) + +(def proxy (builder)) + +(deftest map-proxy + (let [proxied (proxy {:foo 1 :bar 2})] + (is (== 1 (gobj/get proxied "foo"))) + (is (== 2 (gobj/get proxied "bar"))) + (is (= #{"foo" "bar"} (into #{} (Object/keys proxied)))))) + +(deftest vector-proxy + (let [proxied (proxy [1 2 3 4])] + (is (== 4 (alength proxied))) + (is (== 1 (aget proxied 0))) + (is (== 4 (aget proxied 3))))) + +(comment + + (test/run-tests) + +) diff --git a/src/test/cljs/cljs/qualified_method_test.cljs b/src/test/cljs/cljs/qualified_method_test.cljs new file mode 100644 index 0000000000..df339b6b86 --- /dev/null +++ b/src/test/cljs/cljs/qualified_method_test.cljs @@ -0,0 +1,17 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.qualified-method-test + (:refer-global :only [String]) + (:require [cljs.test :as test :refer-macros [deftest testing is]])) + +(deftest qualified-method-return-position-test + (testing "qualified method returned from function to force it in return position" + (let [f (fn [] String/.toUpperCase) + m (f)] + (is (= "FOO" (m "foo")))))) diff --git a/src/test/cljs/cljs/reader_test.cljs b/src/test/cljs/cljs/reader_test.cljs new file mode 100644 index 0000000000..e4c01923ff --- /dev/null +++ b/src/test/cljs/cljs/reader_test.cljs @@ -0,0 +1,256 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.reader-test + (:require [cljs.test :refer-macros [deftest testing is] :as test] + [cljs.reader :as reader] + [goog.object :as o])) + +(deftype T [a b]) +(defrecord R [a b]) + +(deftest test-reader + (testing "Test basic reading" + (is (= 1 (reader/read-string "1"))) + (is (= 2 (reader/read-string "#_nope 2"))) + (is (= -1 (reader/read-string "-1"))) + (is (= -1.5 (reader/read-string "-1.5"))) + (is (= [3 4] (reader/read-string "[3 4]"))) + (is (= "foo" (reader/read-string "\"foo\""))) + (is (= :hello (reader/read-string ":hello"))) + (is (= 'goodbye (reader/read-string "goodbye"))) + (is (= '% (reader/read-string "%"))) + (is (= #{1 2 3} (reader/read-string "#{1 2 3}"))) + (is (= '(7 8 9) (reader/read-string "(7 8 9)"))) + ;; Another bad test found by switching to tools.reader - David + ;(is (= '(deref foo) (reader/read-string "@foo"))) + ;; Another bad test found by switching to tools.reader - David + ;;(is (= 'bar (reader/read-string "'bar"))) + (is (= 'foo/bar (reader/read-string "foo/bar"))) + (is (= \a (reader/read-string "\\a"))) + (is (= {:tag 'String} (meta (reader/read-string "^String {:a 1}")))) + (is (= [:a 'b #{'c {:d [:e :f :g]}}] + (reader/read-string "[:a b #{c {:d [:e :f :g]}}]"))) + (is (= :foo/bar (reader/read-string ":foo/bar"))) + (is (= nil (reader/read-string "nil"))) + (is (= true (reader/read-string "true"))) + (is (= false (reader/read-string "false"))) + (is (= "string" (reader/read-string "\"string\""))) + (is (= "escape chars \t \r \n \\ \" \b \f" (reader/read-string "\"escape chars \\t \\r \\n \\\\ \\\" \\b \\f\"")))) + + (testing "Test reading number literals" + (is (apply = 0 (map reader/read-string ["0" "+0" "-0" " 0 "]))) + (is (apply = 42 (map reader/read-string ["052" "0x2a" "2r101010" "8R52" "16r2a" "36r16"]))) + (is (apply = 42 (map reader/read-string ["+052" "+0x2a" "+2r101010" "+8r52" "+16R2a" "+36r16"]))) + (is (apply = -42 (map reader/read-string ["-052" "-0X2a" "-2r101010" "-8r52" "-16r2a" "-36R16"])))) + + (testing "Test reading queue literals" + (is (= cljs.core.PersistentQueue.EMPTY + (reader/read-string "#queue []"))) + (is (= (-> cljs.core.PersistentQueue.EMPTY (conj 1)) + (reader/read-string "#queue [1]"))) + (is (= (into cljs.core.PersistentQueue.EMPTY [1 2]) + (reader/read-string "#queue [1 2]")))) + + (testing "Test reading comments" + ;; Another bad test found by switching to tools.reader - David + ;;(is (nil? (reader/read-string ";foo"))) + (is (= 3 (try + (reader/read-string ";foo\n3") + (catch js/Error e :threw)))) + (is (= 3 (try + (reader/read-string ";foo\n3\n5") + (catch js/Error e :threw))))) + + (let [est-inst (reader/read-string "#inst \"2010-11-12T13:14:15.666-05:00\"") + utc-inst (reader/read-string "#inst \"2010-11-12T18:14:15.666-00:00\"") + pad (fn [n] + (if (< n 10) + (str "0" n) + n))] + (testing "Testing reading instant literals" + (is (= (.valueOf (js/Date. "2010-11-12T13:14:15.666-05:00")) + (.valueOf est-inst))) + (is (= (.valueOf est-inst) + (.valueOf (reader/read-string (pr-str est-inst))))) + (is (= (.valueOf est-inst) + (.valueOf utc-inst))) + (doseq [month (range 1 13) + day (range 1 29) + hour (range 1 23)] + (let [s (str "#inst \"2010-" (pad month) "-" (pad day) "T" (pad hour) ":14:15.666-06:00\"")] + (is (= (-> s reader/read-string .valueOf) + (-> s reader/read-string pr-str reader/read-string .valueOf))))))) + + (let [u (reader/read-string "#uuid \"550e8400-e29b-41d4-a716-446655440000\"")] + (testing "Testing reading UUID literals" + (is (= u (reader/read-string "#uuid \"550e8400-e29b-41d4-a716-446655440000\""))) + (is (not (identical? u (reader/read-string "#uuid \"550e8400-e29b-41d4-a716-446655440000\"")))) + (is (= u (-> u pr-str reader/read-string))))) + + (testing "Testing tag parsers" + (reader/register-tag-parser! 'foo identity) + (is (= [1 2] (reader/read-string "#foo [1 2]"))) + + ;; tag elements with prefix component + (reader/register-tag-parser! 'foo.bar/baz identity) + (is (= [1 2] (reader/read-string "#foo.bar/baz [1 2]"))) + + ;; default tag parser + (reader/register-default-tag-parser! (fn [tag val] val)) + (is (= [1 2] (reader/read-string "#a.b/c [1 2]")))) + + (testing "Character Literals" + (is (= [\tab \return \newline \space \backspace \formfeed \u1234] + (reader/read-string "[\\tab \\return \\newline \\space \\backspace \\formfeed \\u1234]")))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Unicode Tests + + (testing "Test reading unicode - strings, symbols, keywords" + (doseq [unicode + ["اختبار" ; arabic + "ทดสอบ" ; thai + "こんにちは" ; japanese hiragana + "你好" ; chinese traditional + "אַ גוט יאָר" ; yiddish + "cześć" ; polish + "привет" ; russian + + ;; RTL languages skipped below because tricky to insert + ;; ' and : at the "start" + + 'ทดสอบ + 'こんにちは + '你好 + 'cześć + 'привет + + :ทดสอบ + :こんにちは + :你好 + :cześć + :привет + + ;compound data + {:привет :ru "你好" :cn} + ]] + (let [input (pr-str unicode) + read (reader/read-string input)] + (is (= unicode read))))) + + (testing "Testing unicode error cases" + (doseq [unicode-error + ["\"abc \\ua\"" ; truncated + "\"abc \\x0z ...etc\"" ; incorrect code + "\"abc \\u0g00 ..etc\"" ; incorrect code + ]] + (let [r (try + (reader/read-string unicode-error) + :failed-to-throw + (catch js/Error e :ok))] + (is (= r :ok))))) + + (testing "Testing non-string input, CLJS-1342" + (let [r (try + (reader/read-string :foo) + :failed-to-throw + (catch js/Error e :ok))] + (is (= r :ok)))) +) + +(deftest test-717 + (testing "Testing reading, CLJS-717" + (is (array? (reader/read-string "#js [1 2 3]"))) + (is (= (alength (reader/read-string "#js [1 2 3]")) 3)) + (is (= (seq (reader/read-string "#js [1 2 3]")) (seq [1 2 3]))) + (is (= (set (js-keys (reader/read-string "#js {:foo \"bar\" :baz \"woz\"}"))) #{"foo" "baz"})) + (is (= (o/get (reader/read-string "#js {:foo \"bar\"}") "foo") "bar")) + (is (= (o/get (reader/read-string "#js {\"foo\" \"bar\"}") "foo") "bar")) + (is (array? (o/get (reader/read-string "#js {\"foo\" #js [1 2 3]}") "foo"))) + (is (= (seq (o/get (reader/read-string "#js {\"foo\" #js [1 2 3]}") "foo")) '(1 2 3))))) + +(deftest test-787 + (testing "Testing reading, CLS-787" + (is (nil? (reader/read-string ""))))) + +;; Test doesn't seem relevant now that we rely on tools.reader - David +;(deftest test-819 +; (let [re (reader/read-string "#\"\s\u00a1\"") +; m (re-find re " \u00a1 ")] +; (testing "Testing reading, CLJS-819" +; (is (= m " \u00a1"))))) + +(deftest testing-map-type + (let [a (reader/read-string "{:a 1 :b 2 :c 3}") + b (reader/read-string "{:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9}")] + (is (= a {:a 1 :b 2 :c 3})) + ;; Needs fix to cljs.tools.reader.edn - David + (is (instance? PersistentArrayMap a)) + (is (= b {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9})) + (is (instance? PersistentHashMap b)))) + +;; NOTE: issue uncovered by test.check + +(deftest test-slash-reading + (let [x '({/ 0})] + (testing "Testing '/ reading" + (is (= x (reader/read-string (pr-str x)))) + (is (= (reader/read-string (pr-str x)) x))))) + +(deftest testing-cljs-1823 + (let [;; PersistentArrayMap + a (try + (reader/read-string "{:a 1 :b 2 :c 3 :a 1}") + :failed-to-throw + (catch js/Error e (ex-message e))) + ;; PersistentHashMap + b (try + (reader/read-string "{:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :h 7 :i 8 :a 1}") + :failed-to-throw + (catch js/Error e (ex-message e))) + ;; PersistentArrayMap backed PHS + c (try + (reader/read-string "#{:a :b :c :d :a}") + :failed-to-throw + (catch js/Error e (ex-message e))) + ;; PersistentHashMap backed PHS + d (try + (reader/read-string "#{:a :b :c :d :e :f :g :h :i :a}") + :failed-to-throw + (catch js/Error e (ex-message e))) + ] + (is (= "Map literal contains duplicate key: :a" a)) + (is (= "Map literal contains duplicate key: :a" b)) + ;; Waiting on tools.reader fixes - David + (is (= "Set literal contains duplicate key: :a" c)) + (is (= "Set literal contains duplicate key: :a" d)) + )) + +;; Not relevant now that we rely on tools.reader and it duplicates Clojure's behavior - David +;(deftest test-error-messages +; (testing "Leading numbers in keywords" +; (is (thrown-with-msg? js/Error #"Invalid keyword :0s" (reader/read-string ":0s"))))) + +(deftest testing-cljs-3278 + (is (nil? (reader/read-string {:readers {'foo (constantly nil)}} "#foo 1")))) + +(deftest testing-cljs-3291 + (is (= "#inst \"1500-01-01T00:00:00.000-00:00\"" (pr-str #inst "1500"))) + (is (= "#inst \"1582-10-04T00:00:00.000-00:00\"" (pr-str #inst "1582-10-04"))) + (is (= "#inst \"1582-10-04T23:59:59.999-00:00\"" (pr-str #inst "1582-10-04T23:59:59.999"))) + (is (= "#inst \"1582-10-05T00:00:00.000-00:00\"" (pr-str #inst "1582-10-05"))) + (is (= "#inst \"1582-10-07T00:00:00.000-00:00\"" (pr-str #inst "1582-10-07"))) + (is (= "#inst \"1582-10-14T23:59:59.999-00:00\"" (pr-str #inst "1582-10-14T23:59:59.999"))) + (is (= "#inst \"1582-10-15T00:00:00.000-00:00\"" (pr-str #inst "1582-10-15"))) + (is (= "#inst \"1582-10-17T00:00:00.000-00:00\"" (pr-str #inst "1582-10-17"))) + (is (= "#inst \"1700-01-01T00:00:00.000-00:00\"" (pr-str #inst "1700"))) + (is (= "#inst \"1850-01-01T00:00:00.000-00:00\"" (pr-str #inst "1850"))) + (is (= "#inst \"1984-01-01T00:00:00.000-00:00\"" (pr-str #inst "1984"))) + (is (= "#inst \"2000-01-01T00:00:10.123-00:00\"" (pr-str #inst "2000-01-01T00:00:10.123456789-00:00"))) + (is (= "#inst \"2020-01-01T05:00:00.000-00:00\"" (pr-str #inst "2020-01-01T00:00:00.000-05:00")))) diff --git a/src/test/cljs/cljs/recur_test.cljs b/src/test/cljs/cljs/recur_test.cljs new file mode 100644 index 0000000000..9dbd3badba --- /dev/null +++ b/src/test/cljs/cljs/recur_test.cljs @@ -0,0 +1,64 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.recur-test + (:require [cljs.test :refer-macros [deftest is]])) + +;; Setup for CLJS-2085 + +(defprotocol ISearch + (search [this coll])) + +;; Passing this in the recur call here will cause a warning to be emitted +(defrecord Search1 [needle] + ISearch + (search [this coll] + (when (seq coll) + (if (= needle (first coll)) + needle + (recur this (rest coll)))))) + +;; This code will be accepted as is +(defrecord Search2 [needle] + ISearch + (search [_ coll] + (when (seq coll) + (if (= needle (first coll)) + needle + (recur (rest coll)))))) + +;; This code will also be accepted as is; the recur is to a loop +(defrecord Search3 [needle] + ISearch + (search [this coll] + (loop [coll coll] + (when (seq coll) + (if (= needle (first coll)) + needle + (recur (rest coll))))))) + +;; This code should not cause a warning to be emitted +(defrecord Search4 [needle] + ISearch + (search [this coll] + (let [search-fn (fn [coll] + (when (seq coll) + (if (= needle (first coll)) + needle + (recur (rest coll)))))] + (search-fn coll)))) + +(deftest cljs-2085-test + (is (= 1 (-> (->Search1 1) (search [:a 1 "b"])))) + (is (nil? (-> (->Search1 :z) (search [:a 1 "b"])))) + (is (= 1 (-> (->Search2 1) (search [:a 1 "b"])))) + (is (nil? (-> (->Search2 :z) (search [:a 1 "b"])))) + (is (= 1 (-> (->Search3 1) (search [:a 1 "b"])))) + (is (nil? (-> (->Search3 :z) (search [:a 1 "b"])))) + (is (= 1 (-> (->Search4 1) (search [:a 1 "b"])))) + (is (nil? (-> (->Search4 :z) (search [:a 1 "b"]))))) diff --git a/src/test/cljs/cljs/reducers_test.cljs b/src/test/cljs/cljs/reducers_test.cljs new file mode 100644 index 0000000000..06cdf8f1ad --- /dev/null +++ b/src/test/cljs/cljs/reducers_test.cljs @@ -0,0 +1,37 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.reducers-test + (:require [cljs.test :refer-macros [deftest is]] + [clojure.core.reducers :as r])) + +(deftest test-builtin-impls + (is (= 0 (r/fold + nil))) + (is (= [1 2 3 4] (seq (r/reduce r/append! (r/cat) [1 2 3 4])))) + (is (= 10 (r/reduce + (array 1 2 3 4)))) + (is (= 11 (r/reduce + 1 (array 1 2 3 4)))) + (is (= 10 (r/reduce + (list 1 2 3 4)))) + (is (= 11 (r/reduce + 1 (list 1 2 3 4)))) + (is (= (r/fold + + [1 2 3]) + (r/fold + [1 2 3]) + (r/reduce + [1 2 3]) + 6)) + (is (= (r/fold + + (vec (range 2048))) + (r/reduce + (vec (range 2048))))) + (letfn [(f [[ks vs] k v] + [(conj ks k) (conj vs v)]) + (g ([] [#{} #{}]) + ([[ks1 vs1] [ks2 vs2]] + [(into ks1 ks2) (into vs1 vs2)]))] + (is (= (r/reduce f (g) {:a 1 :b 2 :c 3}) + (r/fold g f {:a 1 :b 2 :c 3}) + [#{:a :b :c} #{1 2 3}])) + (let [m (into {} (for [x (range 2048)] [x (- x)]))] + (is (= (r/reduce f (g) m) (r/fold g f m))))) + ;; CLJS-792 + (is (= (into [] (r/map identity {})) []))) diff --git a/src/test/cljs/cljs/repl_test.cljs b/src/test/cljs/cljs/repl_test.cljs new file mode 100644 index 0000000000..4e0083a00f --- /dev/null +++ b/src/test/cljs/cljs/repl_test.cljs @@ -0,0 +1,20 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.repl-test + (:require + [cljs.repl] + [cljs.test :refer [deftest is]])) + +(deftest test-cljs-3017 + (let [m (cljs.repl/Error->map (js/TypeError.))] + (is (= 'js/TypeError (get-in m [:via 0 :type]))))) + +(deftest test-cljs-3019 + (let [m (cljs.repl/Error->map (ex-info "" {}))] + (is (= 'cljs.core/ExceptionInfo (get-in m [:via 0 :type]))))) diff --git a/src/test/cljs/cljs/seqs_test.cljs b/src/test/cljs/cljs/seqs_test.cljs new file mode 100644 index 0000000000..00d648fa17 --- /dev/null +++ b/src/test/cljs/cljs/seqs_test.cljs @@ -0,0 +1,536 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns cljs.seqs-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing are is]] + [clojure.test.check :as tc] + [clojure.test.check.clojure-test :refer-macros [defspec]] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as prop :include-macros true] + [clojure.test.check.random :as random] + [clojure.string :as s] + [clojure.set :as set])) + +(deftest test-sequential-equality + (testing "Testing ISequential equality" + (is (= (list 3 2 1) [3 2 1])) + (is (= [3 2 1] (seq (array 3 2 1)))))) + +(deftest test-seq-operations + (testing "Testing basic seq operations" + (is (= () (rest nil))) + (is (= nil (seq (array)))) + (is (= nil (seq ""))) + (is (= nil (seq []))) + (is (= nil (seq {}))) + (is (= () (rest ()))) + (is (= () (rest [1]))) + (is (= () (rest (array 1)))))) + +(deftest test-empy-and-seq + (testing "Testing empty & seq" + (is (nil? (empty nil))) + (let [e-lazy-seq (empty (with-meta (lazy-seq (cons :a nil)) {:b :c}))] + (testing "lazy seq" + (is (seq? e-lazy-seq)) + (is (empty? e-lazy-seq)) + ;; MAYBE FIXME: this is a bad test, discovered from :lite-mode work + (if-not ^boolean LITE_MODE + (is (= {:b :c} (meta e-lazy-seq))) + ;; LITE_MODE has the correct behavior + (is (nil? (meta e-lazy-seq)))))) + (let [e-list (empty '^{:b :c} (1 2 3))] + (testing "list" + (is (seq? e-list)) + (is (empty? e-list)) + (is (= {:b :c} (meta e-list))))) + (let [e-elist (empty '^{:b :c} ())] + (testing "empty list with metadata" + (is (seq? e-elist)) + (is (empty? e-elist)) + (is (= :c (get (meta e-elist) :b))))) + (let [e-cons (empty (with-meta (cons :a nil) {:b :c}))] + (testing "cons" + (is (seq? e-cons)) + (is (empty? e-cons)) + (is (= {:b :c} (meta e-cons))))) + (let [e-vec (empty ^{:b :c} [:a :d :g])] + (testing "vector" + (is (vector? e-vec)) + (is (empty? e-vec)) + (is (= {:b :c} (meta e-vec))))) + (let [e-omap (empty ^{:b :c} {:a :d :g :h})] + (testing "map" + (is (map? e-omap)) + (is (empty? e-omap)) + (is (= {:b :c} (meta e-omap))))) + (let [e-hmap (empty ^{:b :c} {[1 2] :d :g :h})] + (testing "map with complex keys" + (is (map? e-hmap)) + (is (empty? e-hmap)) + (is (= {:b :c} (meta e-hmap))))) + (let [smap (with-meta (sorted-map-by (comp - compare) 2 :a 1 :b 5 :c) {:b :c}) + e-smap (empty smap)] + (testing "sorted-map-by" + (is (map? e-smap)) + (is (empty? e-smap)) + (is (= {:b :c} (meta e-smap))) + (is (identical? (-comparator smap) (-comparator e-smap))) + (is (= [[5 :c] [2 :a] [1 :b]] (seq (assoc e-smap 2 :a 1 :b 5 :c)))))) + (let [sset (with-meta (sorted-set-by (comp - compare) 5 1 2) {:b :c}) + e-sset (empty sset)] + (testing "sorted-set-by" + (is (set? e-sset)) + (is (empty? e-sset)) + (is (= {:b :c} (meta e-sset))) + (is (identical? (-comparator sset) (-comparator e-sset))) + (is (= [5 2 1] (seq (conj e-sset 5 1 2)))))) + (let [e-queue (empty (with-meta (.-EMPTY PersistentQueue) {:b :c}))] + (testing "queue" + (is (identical? (type e-queue) PersistentQueue)) + (is (empty? e-queue)) + (is (= {:b :c} (meta e-queue))))) + (testing "non-emptyable" + (is (nil? (empty 1))) + (is (nil? (empty "abc"))) + (is (nil? (empty #js [1 2 3])))))) + +(deftest test-empty? + (are [x] (empty? x) + nil + () + (lazy-seq nil) ; => () + [] + {} + #{} + "" + (into-array []) + (transient []) + (transient #{}) + (transient {})) + + (are [x] (not (empty? x)) + '(1 2) + (lazy-seq [1 2]) + [1 2] + {:a 1 :b 2} + #{1 2} + "abc" + (into-array [1 2]) + (transient [1]) + (transient #{1}) + (transient {1 2}))) + +(deftest test-distinct + (testing "Testing distinct? & distinct" + (is (distinct? 1 2 3)) + (is (not (distinct? 1 2 3 1))) + (is (= (distinct ()) ())) + (is (= (distinct '(1)) '(1))) + (is (= (distinct '(1 2 3 1 1 1)) '(1 2 3))) + (is (= (distinct [1 1 1 2]) '(1 2))) + (is (= (distinct [1 2 1 2]) '(1 2))) + (is (= (distinct "a") ["a"])) + (is (= (distinct "abcabab") ["a" "b" "c"])) + (is (= (distinct ["abc" "abc"]) ["abc"])) + (is (= (distinct [nil nil]) [nil])) + (is (= (distinct [0.0 0.0]) [0.0])) + (is (= (distinct ['sym 'sym]) '[sym])) + (is (= (distinct [:kw :kw]) [:kw])) + (is (= (distinct [42 42]) [42])) + (is (= (distinct [[] []]) [[]])) + (is (= (distinct ['(1 2) '(1 2)]) '[(1 2)])) + (is (= (distinct [() ()]) [()])) + (is (= (distinct [[1 2] [1 2]]) [[1 2]])) + (is (= (distinct [{:a 1 :b 2} {:a 1 :b 2}]) [{:a 1 :b 2}])) + (is (= (distinct [{} {}]) [{}])) + (is (= (distinct [#{1 2} #{1 2}]) [#{1 2}])) + (is (= (distinct [#{} #{}]) [#{}])))) + +(deftest test-rearrange-sequential + (testing "Test rearranging sequential collections" + (is (= [1 2 3 4 5] (sort [5 3 1 4 2]))) + (is (= [1 2 3 4 5] (sort < [5 3 1 4 2]))) + (is (= [5 4 3 2 1] (sort > [5 3 1 4 2]))) + (is (= ["a" [ 1 2] "foo"] (sort-by count ["foo" "a" [1 2]]))) + (is (= ["foo" [1 2] "a"] (sort-by count > ["foo" "a" [1 2]]))) + (let [coll [1 2 3 4 5 6 7 8 9 10] + ;; while it is technically possible for this test to fail with a false negative, + ;; it's _extraordinarily_ unlikely. + shuffles (filter #(not= coll %) (take 100 (iterate shuffle coll)))] + (is (not (empty? shuffles)))) + )) + +(deftest test-ISequential-indexOf + (testing "Testing JS .indexOf in ISequential types" + ;; PersistentVector + (is (= (.indexOf [] 2) -1)) + (is (= (.indexOf [] 2 3) -1)) + (is (= (.indexOf [1 2 3 4 5] 2) 1)) + (is (= (.indexOf [1 2 3 4 5] 6) -1)) + (is (= (.indexOf [1 2 3 4 5] -1) -1)) + (is (= (.indexOf [1 2 "x" 4 5 "a"] "a") 5)) + (is (= (.indexOf [1 2 3 4 5] 1 2) -1)) + (is (= (.indexOf [1 2 3 4 5] 2 2) -1)) + (is (= (.indexOf [1 2 3 1 5] 1 2) 3)) + (is (= (.indexOf [1 2 3 4 5] 2) 1)) + (is (= (.indexOf '(1 2 3 4 5) 2) 1)) + (is (= (.indexOf (list 1 2 3) 3) 2)) + (is (= (.indexOf (lazy-seq [1 2 3 4 5]) 3)) 2) + (is (= (.indexOf (sequence (map inc) '(0 1 2 3 4)) 5) 4)))) + +(deftest test-ISequential-lastIndexOf + (testing "Testing JS .lastIndexOf in ISequential types" + ;; PersistentVector + (is (= (.lastIndexOf [] 2) -1)) + (is (= (.lastIndexOf [] 2 3) -1)) + (is (= (.lastIndexOf [1 2 3 4 5] 2) 1)) + (is (= (.lastIndexOf [1 2 3 1 5] 1) 3)) + (is (= (.lastIndexOf [1 2 3 1 5] 1 3) 3)) + (is (= (.lastIndexOf [1 2 3 1 5] 1 2) 0)) + (is (= (.lastIndexOf [1 2 3 1] 1 0) 0)) + (is (= (.lastIndexOf [1 2 3 4 5] 3 100) 2)) + (is (= (.lastIndexOf [1 1 1 1 1] 1) 4)) + (is (= (.lastIndexOf [1 1 1 1 1] 1 6) 4)) + (is (= (.lastIndexOf [1 2 1 1 1] 2) 1)) + (is (= (.lastIndexOf [1 2 3 4 5] 3 -100) -1)) + (is (= (.lastIndexOf [1 2 3 4 5] 3 -2) 2)) + (is (= (.lastIndexOf '(1 2 1 4 5) 1) 2)) + (is (= (.lastIndexOf (list 1 2 3 1 5) 1) 3)) + (is (= (.lastIndexOf (lazy-seq [1 2 1 4 5]) 1)) 2) + (is (= (.lastIndexOf (sequence (map inc) '(0 1 0 3 4)) 1) 2)))) + +(deftest test-chunked + (when-not LITE_MODE + (let [r (range 64) + v (into [] r)] + (testing "Testing Chunked Seqs" + (is (= (hash (seq v)) (hash (seq v)))) + (is (= 6 (reduce + (array-chunk (array 1 2 3))))) + (is (instance? ChunkedSeq (seq v))) + (is (= r (seq v))) + (is (= (map inc r) (map inc v))) + (is (= (filter even? r) (filter even? v))) + (is (= (filter odd? r) (filter odd? v))) + (is (= (concat r r r) (concat v v v))) + (is (satisfies? IReduce (seq v))) + (is (== 2010 (reduce + (nnext (nnext (seq v)))))) + (is (== 2020 (reduce + 10 (nnext (nnext (seq v)))))))))) + +(deftest test-778 + (testing "Testing CLJS-778, -rest, -next RSeq" + (is (= (-rest (rseq [0])) ())) + (is (nil? (-next (rseq [0])))) + (is (= (set (rseq [0])) #{0})))) + +(deftest test-indexed-seqs + (testing "Testing IndexedSeq" + (testing "Sequence equality" + (is (= (list 0 1 2 3 4 5) (seq (array 0 1 2 3 4 5))))) + (testing "nth lookup within bounds" + (is (= 0 (nth (seq (array 0 1 2 3 4 5)) 0))) + (is (= 0 (nth (seq (array 0 1 2 3 4 5)) 0 :not-found))) + (is (= 5 (nth (seq (array 0 1 2 3 4 5)) 5))) + (is (= 5 (nth (seq (array 0 1 2 3 4 5)) 5 :not-found)))) + (testing "nth lookup out of bounds" + (is (thrown? js/Error (nth (seq (array 0 1 2 3 4 5)) 6))) + (is (= :not-found (nth (seq (array 0 1 2 3 4 5)) 6 :not-found))) + (is (thrown? js/Error (nth (seq (array 0 1 2 3 4 5)) -1))) + (is (= :not-found (nth (seq (array 0 1 2 3 4 5)) -1 :not-found)))))) + +(deftest test-cljs-2131 + (testing "calling empty on a ChunkedSeq returns an empty list" + (let [small-vec [1 2 3] + big-vec (into [] (range 1000))] + (is (identical? (empty (seq small-vec)) ())) + (is (identical? (empty (seq big-vec)) ()))))) + +(defrecord Foo [a b]) + +(deftest test-cljs-2482 + (testing "seq on defrecord returns map entries" + (is (every? map-entry? (seq (->Foo 1 2)))))) + +(deftest test-cljs-2911 + (testing "partition-by works correclty with infinite seqs" + (is (= (first (second (partition-by zero? (range)))) 1)))) + +(deftest test-cljs-3230 + (testing "sequence ops on ES6 collections" + (let [s (js/Set.)] + (is (= () (rest s))) + (is (nil? (next s))) + (is (empty? s))))) + +(deftest test-js-iterable? + (testing "test that js-iterable? works on ES6 collections and normal values" + (is (true? (js-iterable? (js/Set.)))) + (is (false? (js-iterable? 1))) + (is (false? (js-iterable? nil))))) + +(deftest test-iteration-opts + (let [genstep (fn [steps] + (fn [k] (swap! steps inc) (inc k))) + test (fn [expect & iteropts] + (is (= expect + (let [nsteps (atom 0) + iter (apply iteration (genstep nsteps) iteropts) + ret (doall (seq iter))] + {:ret ret :steps @nsteps}) + (let [nsteps (atom 0) + iter (apply iteration (genstep nsteps) iteropts) + ret (into [] iter)] + {:ret ret :steps @nsteps}))))] + (test {:ret [1 2 3 4] + :steps 5} + :initk 0 :somef #(< % 5)) + (test {:ret [1 2 3 4 5] + :steps 5} + :initk 0 :kf (fn [ret] (when (< ret 5) ret))) + (test {:ret ["1"] + :steps 2} + :initk 0 :somef #(< % 2) :vf str)) + + ;; kf does not stop on false + (let [iter #(iteration (fn [k] + (if (boolean? k) + [10 :boolean] + [k k])) + :vf second + :kf (fn [[k v]] + (cond + (= k 3) false + (< k 14) (inc k))) + :initk 0)] + (is (= [0 1 2 3 :boolean 11 12 13 14] + (into [] (iter)) + (seq (iter)))))) + +(deftest test-iteration + ;; equivalence to es6-iterator-seq + (let [arr #js [1 nil 3 true false 4 6 nil 7]] + (is (= (let [iter (es6-iterator arr)] + (vec (iteration (fn [_] (.next iter)) + :somef #(not (.-done %)) + :vf #(.-value %)))) + (let [iter (es6-iterator arr)] + (vec (es6-iterator-seq iter)))))) + + ;; paginated API + (let [items 12 pgsize 5 + src (vec (repeatedly items #(random-uuid))) + api (fn [tok] + (let [tok (or tok 0)] + (when (< tok items) + {:tok (+ tok pgsize) + :ret (subvec src tok (min (+ tok pgsize) items))})))] + (is (= src + (mapcat identity (iteration api :kf :tok :vf :ret)) + (into [] cat (iteration api :kf :tok :vf :ret))))) + + (let [src [:a :b :c :d :e] + api (fn [k] + (let [k (or k 0)] + (if (< k (count src)) + {:item (nth src k) + :k (inc k)})))] + (is (= [:a :b :c] + (vec (iteration api + :somef (comp #{:a :b :c} :item) + :kf :k + :vf :item)) + (vec (iteration api + :kf #(some-> % :k #{0 1 2}) + :vf :item)))))) + +(defn- make-rng [seed] + (atom (random/make-random seed))) + +(defn- next-long [rng] + (let [[r1 r2] (random/split @rng)] + (reset! rng r2) + (long (random/rand-long r1)))) + +(deftest test-take + (are [x y] (= x y) + (take 1 [1 2 3 4 5]) '(1) + (take 3 [1 2 3 4 5]) '(1 2 3) + (take 5 [1 2 3 4 5]) '(1 2 3 4 5) + (take 9 [1 2 3 4 5]) '(1 2 3 4 5) + + (take 0 [1 2 3 4 5]) () + (take -1 [1 2 3 4 5]) () + (take -2 [1 2 3 4 5]) () + + (take 0.25 [1 2 3 4 5]) '(1))) + + +(deftest test-drop + (are [x y] (= x y) + (drop 1 [1 2 3 4 5]) '(2 3 4 5) + (drop 3 [1 2 3 4 5]) '(4 5) + (drop 5 [1 2 3 4 5]) () + (drop 9 [1 2 3 4 5]) () + + (drop 0 [1 2 3 4 5]) '(1 2 3 4 5) + (drop -1 [1 2 3 4 5]) '(1 2 3 4 5) + (drop -2 [1 2 3 4 5]) '(1 2 3 4 5) + + (drop 0.25 [1 2 3 4 5]) '(2 3 4 5) ) + + (are [coll] (= (drop 4 coll) (drop -2 (drop 4 coll))) + [0 1 2 3 4 5] + (seq [0 1 2 3 4 5]) + (range 6) + (repeat 6 :x))) + +(deftest test-nthrest + (are [x y] (= x y) + (nthrest [1 2 3 4 5] 1) '(2 3 4 5) + (nthrest [1 2 3 4 5] 3) '(4 5) + (nthrest [1 2 3 4 5] 5) () + (nthrest [1 2 3 4 5] 9) () + + (nthrest [1 2 3 4 5] 0) '(1 2 3 4 5) + (nthrest [1 2 3 4 5] -1) '(1 2 3 4 5) + (nthrest [1 2 3 4 5] -2) '(1 2 3 4 5) + + (nthrest [1 2 3 4 5] 0.25) '(2 3 4 5) + (nthrest [1 2 3 4 5] 1.2) '(3 4 5)) + + ;; (nthrest coll 0) should return coll + (are [coll] (let [r (nthrest coll 0)] (and (= coll r) (= (type coll) (type r)))) + [1 2 3] + (seq [1 2 3]) + (range 10) + (repeat 10 :x) + (seq "abc"))) + +(deftest test-nthnext + (are [x y] (= x y) + (nthnext [1 2 3 4 5] 1) '(2 3 4 5) + (nthnext [1 2 3 4 5] 3) '(4 5) + (nthnext [1 2 3 4 5] 5) nil + (nthnext [1 2 3 4 5] 9) nil + + (nthnext [1 2 3 4 5] 0) '(1 2 3 4 5) + (nthnext [1 2 3 4 5] -1) '(1 2 3 4 5) + (nthnext [1 2 3 4 5] -2) '(1 2 3 4 5) + + (nthnext [1 2 3 4 5] 0.25) '(2 3 4 5) + (nthnext [1 2 3 4 5] 1.2) '(3 4 5) )) + +(deftest test-partitionv-all + (is (= (partitionv-all 4 [1 2 3 4 5 6 7 8 9]) + [[1 2 3 4] [5 6 7 8] [9]])) + (is (= (partitionv-all 4 2 [1 2 3 4 5 6 7 8 9]) + [[1 2 3 4] [3 4 5 6] [5 6 7 8] [7 8 9] [9]]))) + +(deftest test-partition + (are [x y] (= x y) + (partition 2 [1 2 3]) '((1 2)) + (partition 2 [1 2 3 4]) '((1 2) (3 4)) + (partition 2 []) () + + (partition 2 3 [1 2 3 4 5 6 7]) '((1 2) (4 5)) + (partition 2 3 [1 2 3 4 5 6 7 8]) '((1 2) (4 5) (7 8)) + (partition 2 3 []) () + + (partition 1 []) () + (partition 1 [1 2 3]) '((1) (2) (3)) + + (partition 5 [1 2 3]) () + + (partition 4 4 [0 0 0] (range 10)) '((0 1 2 3) (4 5 6 7) (8 9 0 0)) + + (partition -1 [1 2 3]) () + (partition -2 [1 2 3]) ()) + + ;; reduce + (is (= [1 2 4 8 16] (map #(reduce * (repeat % 2)) (range 5)))) + (is (= [3 6 12 24 48] (map #(reduce * 3 (repeat % 2)) (range 5)))) + + ;; equality and hashing + (is (= (repeat 5 :x) (repeat 5 :x))) + (is (= (repeat 5 :x) '(:x :x :x :x :x))) + (is (= (hash (repeat 5 :x)) (hash '(:x :x :x :x :x)))) + (is (= (assoc (array-map (repeat 1 :x) :y) '(:x) :z) {'(:x) :z})) + (is (= (assoc (hash-map (repeat 1 :x) :y) '(:x) :z) {'(:x) :z}))) + +(deftest test-partitionv + (are [x y] (= x y) + (partitionv 2 [1 2 3]) '((1 2)) + (partitionv 2 [1 2 3 4]) '((1 2) (3 4)) + (partitionv 2 []) () + + (partitionv 2 3 [1 2 3 4 5 6 7]) '((1 2) (4 5)) + (partitionv 2 3 [1 2 3 4 5 6 7 8]) '((1 2) (4 5) (7 8)) + (partitionv 2 3 []) () + + (partitionv 1 []) () + (partitionv 1 [1 2 3]) '((1) (2) (3)) + + (partitionv 5 [1 2 3]) () + + (partitionv -1 [1 2 3]) () + (partitionv -2 [1 2 3]) ())) + +(deftest test-reduce-on-coll-seqs + ;; reduce on seq of coll, both with and without an init + (are [coll expected expected-init] + (and + (= expected-init (reduce conj [:init] (seq coll))) + (= expected (reduce conj (seq coll)))) + ;; (seq [ ... ]) + [] [] [:init] + [1] 1 [:init 1] + [[1] 2] [1 2] [:init [1] 2] + + ;; (seq { ... }) + {} [] [:init] + {1 1} [1 1] [:init [1 1]] + {1 1 2 2} [1 1 [2 2]] [:init [1 1] [2 2]] + + ;; (seq (hash-map ... )) + (hash-map) [] [:init] + (hash-map 1 1) [1 1] [:init [1 1]] + (hash-map 1 1 2 2) [1 1 [2 2]] [:init [1 1] [2 2]] + + ;; (seq (sorted-map ... )) + (sorted-map) [] [:init] + (sorted-map 1 1) [1 1] [:init [1 1]] + (sorted-map 1 1 2 2) [1 1 [2 2]] [:init [1 1] [2 2]]) + + (are [coll expected expected-init] + (and + (= expected-init (reduce + 100 (seq coll))) + (= expected (reduce + (seq coll)))) + + ;; (seq (range ...)) + (range 0) 0 100 + (range 1 2) 1 101 + (range 1 3) 3 103)) + +(defspec iteration-seq-equals-reduce 1000 + (prop/for-all [initk gen/small-integer + seed gen/small-integer] + (let [src (fn [] + (let [rng (make-rng seed)] + (iteration #(unchecked-add % (next-long rng)) + :somef (complement #(zero? (mod % 1000))) + :vf str + :initk initk)))] + (= (into [] (src)) + (into [] (seq (src))))))) + +(deftest cljs-3419-seq-js-iterable + (let [js-set (js/Set. #js [1 2 3 4]) + js-map (js/Map. #js [#js [1 2] #js [3 4]])] + (is (seqable? js-set)) + (is (seqable? js-map)))) diff --git a/src/test/cljs/cljs/set_equiv_test.cljs b/src/test/cljs/cljs/set_equiv_test.cljs new file mode 100644 index 0000000000..de9ce88fc8 --- /dev/null +++ b/src/test/cljs/cljs/set_equiv_test.cljs @@ -0,0 +1,26 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.set-equiv-test + (:refer-clojure :exclude [iter]) + (:require [cljs.test :refer-macros [deftest testing is]] + [clojure.string :as s] + [clojure.set :as set])) + +(deftest test-set-equality + (testing "Testing set equality" + (is (= (sorted-set 3 2 1) (sorted-set 1 2 3))) + (is (= (hash-set 3 2 1) (sorted-set 1 2 3))) + (is (= (sorted-set :a :b :c) (hash-set :a :b :c))) + (is (= (hash-set :a :b :c) (hash-set :a :b :c)))) + + (testing "CLJS-2731 uncomparable values" + (is (not= (sorted-set 3 2 1) (sorted-set :a :b :c))) + (is (not= (hash-set 3 2 1) (sorted-set :a :b :c))) + (is (not= (sorted-set 3 2 1) (hash-set :a :b :c))) + (is (not= (hash-set 3 2 1) (hash-set :a :b :c))))) diff --git a/src/test/cljs/cljs/spec/test/test_macros.cljc b/src/test/cljs/cljs/spec/test/test_macros.cljc new file mode 100644 index 0000000000..ce85c37ad6 --- /dev/null +++ b/src/test/cljs/cljs/spec/test/test_macros.cljc @@ -0,0 +1,18 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.spec.test.test-macros + #?(:cljs (:require [cljs.spec.alpha :as s]))) + +(defmacro add + [a b] + `(+ ~a ~b)) + +#?(:cljs + (s/fdef add + :args (s/cat :a number? :b number?))) diff --git a/src/test/cljs/cljs/spec/test/test_ns1.cljs b/src/test/cljs/cljs/spec/test/test_ns1.cljs new file mode 100644 index 0000000000..8893d980af --- /dev/null +++ b/src/test/cljs/cljs/spec/test/test_ns1.cljs @@ -0,0 +1,13 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.spec.test.test-ns1) + +(def x 1) + +(def y 2) diff --git a/src/test/cljs/cljs/spec/test/test_ns2.cljs b/src/test/cljs/cljs/spec/test/test_ns2.cljs new file mode 100644 index 0000000000..17b506ebc9 --- /dev/null +++ b/src/test/cljs/cljs/spec/test/test_ns2.cljs @@ -0,0 +1,11 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.spec.test.test-ns2) + +(def z 3) diff --git a/src/test/cljs/cljs/spec/test_test.cljs b/src/test/cljs/cljs/spec/test_test.cljs new file mode 100644 index 0000000000..14b8e31b53 --- /dev/null +++ b/src/test/cljs/cljs/spec/test_test.cljs @@ -0,0 +1,205 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.spec.test-test + (:require-macros [cljs.spec.test.test-macros]) + (:require [cljs.test :as test :refer-macros [deftest testing + is are run-tests]] + [cljs.spec.alpha :as s] + [cljs.spec.test.alpha :as stest] + [cljs.spec.test.test-ns1] + [cljs.spec.test.test-ns2])) + +(s/fdef clojure.core/symbol + :args (s/alt :separate (s/cat :ns string? :n string?) + :str string? + :sym symbol?) + :ret symbol?) + +(defn h-cljs-1812 [x] true) +(s/fdef h-cljs-1812 :args (s/cat :x int?) :ret true?) + +(deftest test-cljs-1812 + (is (= (stest/unstrument `h-cljs-1812) + [])) + + (stest/check `h-cljs-1812 {:clojure.spec.test.check/opts {:num-tests 1}}) + + ; Calling h-cljs-1812 with an argument of the wrong type shouldn't throw, + ; because the function should not have been instrumented by stest/check. + (h-cljs-1812 "foo")) + +;; Setup for CLJS-2142 +(def ^:const pi 3.14159) +(defn area [r] (* pi r r)) +(s/fdef area :args (s/cat :r number?)) + +(deftest test-cljs-2142 + (is (= `[area] (stest/instrument `[pi area])))) + +(defn f-2391 [] 1) +(s/fdef f-2391 :args (s/cat) :ret #{2}) + +(deftest test-cljs-2391-a + (is (= 1 (f-2391)))) + +(deftest test-cljs-2391-b + (stest/instrument `f-2391 {:stub #{`f-2391}}) + (is (= 2 (f-2391)))) + +(deftest test-cljs-2391-c + (stest/unstrument `f-2391) + (is (= 1 (f-2391)))) + +(deftest test-cljs-2414 + (is (empty? (stest/instrument 'cljs.spec.test.test-macros$macros/add)))) + +(deftest test-cljs-2197 + (stest/instrument `symbol) + (is (thrown? js/Error (symbol 3))) + (is (thrown? js/Error (#'symbol 3))) + (is (thrown? js/Error (apply symbol [3]))) + (stest/unstrument `symbol)) + +(defn arities + ([a] + (inc a)) + ([a b] + (+ a b)) + ([a b c] 0)) + +(s/fdef arities + :args (s/or :arity-1 (s/cat :a number?) + :arity-2 (s/cat :a number? :b number?) + :arity-3 (s/cat :a string? :b boolean? :c map?)) + :ret number?) + +(deftest test-2397 + (stest/instrument `arities) + (is (arities 1)) + (is (thrown? js/Error (arities "bad"))) + (stest/unstrument `arities)) + +(defn foo [& args] args) +(s/fdef foo :args (s/cat :args (s/* int?))) + +(deftest test-2641 + (stest/instrument `foo) + (is (= [1 2 3] (foo 1 2 3))) + (is (thrown? js/Error (foo 1 :hello))) + (stest/unstrument `foo)) + +(deftest test-2755 + (is (uri? (ffirst (s/exercise uri? 1))))) + +(deftest test-cljs-2665 + (is (= '#{cljs.spec.test.test-ns1/x cljs.spec.test.test-ns1/y cljs.spec.test.test-ns2/z} + (stest/enumerate-namespace '[cljs.spec.test.test-ns1 cljs.spec.test.test-ns2]))) + (is (= '#{cljs.spec.test.test-ns1/x cljs.spec.test.test-ns1/y cljs.spec.test.test-ns2/z} + (stest/enumerate-namespace ['cljs.spec.test.test-ns1 'cljs.spec.test.test-ns2]))) + (is (= '#{cljs.spec.test.test-ns1/x cljs.spec.test.test-ns1/y} + (stest/enumerate-namespace 'cljs.spec.test.test-ns1))) + (is (= '#{cljs.spec.test.test-ns2/z} + (stest/enumerate-namespace 'cljs.spec.test.test-ns2)))) + +(defn fn-2953 [x] ::ret-val) + +(s/fdef fn-2953 :args (s/cat :x int?)) + +(deftest test-cljs-2953 + (stest/instrument `fn-2953) + (is @#'stest/*instrument-enabled*) + (is (= ::ret-val (stest/with-instrument-disabled + (is (nil? @#'stest/*instrument-enabled*)) + (fn-2953 "abc")))) + (is @#'stest/*instrument-enabled*)) + +(defn foo-2955 [n] "ret") + +(s/fdef foo-2955 + :args (s/cat :n number?) + :ret string?) + +(deftest test-cljs-2955 + (is (seq (stest/check `foo-2955)))) + +(s/fdef cljs.core/= :args (s/+ any?)) + +(deftest test-cljs-2956 + (is (= '[cljs.core/=] (stest/instrument `=))) + (is (true? (= 1))) + (is (thrown-with-msg? + js/Error #"Call to #'cljs.core/= did not conform to spec\." (=))) + (is (= '[cljs.core/=] (stest/unstrument `=)))) + +(defn fn-2975 [x]) + +(deftest test-2975 + (testing "instrument and unstrument return empty coll when no fdef exists" + (is (empty? (stest/instrument `fn-2975))) + (is (empty? (stest/unstrument `fn-2975))))) + +(defn fn-2995 + ([] (fn-2995 0)) + ([a] (fn-2995 a 1)) + ([a b] [a b])) + +(s/fdef fn-2995 + :args (s/cat :a (s/? number?) + :b (s/? number?))) + +(deftest test-2995 + (stest/instrument `fn-2995) + (testing "instrumented self-calling multi-arity function works" + (is (= [0 1] (fn-2995 0 1))) + (is (= [0 1] (fn-2995 0))) + (is (= [0 1] (fn-2995 0))) + (is (thrown? js/Error (fn-2995 "not a number"))))) + +(defn cljs-2964 [x] true) +(s/fdef cljs-2964 :args (s/cat :x int?) :ret true?) + +(deftest test-cljs-2964 + (let [check-res + (stest/check `cljs-2964 {:clojure.spec.test.check/opts {:num-tests 1}})] + (is (seq check-res)) + (is (every? (fn [res] + (= 1 (-> res + :clojure.spec.test.check/ret + :num-tests))) + check-res)))) + +(defn cljs-3033 [x] true) +(s/fdef cljs-3033 :args (s/cat :x int?) :ret true?) + +(deftest test-cljs-3033 + (let [check-res + (stest/check `cljs-3033 {:clojure.test.check/opts {:num-tests 1}})] + (is (seq check-res)) + (is (every? (fn [res] + (= 1 (-> res + :clojure.test.check/ret + :num-tests))) + check-res)))) + +(s/fdef cljs.core/next :args (s/cat :coll seqable?)) + +(deftest test-3023 + (is (= '[cljs.core/next] (stest/instrument `next))) + (is (= [2 3] (next [1 2 3]))) + (is (thrown-with-msg? js/Error #"Call to #'cljs.core/next did not conform to spec\." (next 1))) + (is (= '[cljs.core/next] (stest/unstrument `next)))) + +(defn cljs-3049 [x] x) +(deftest test-3049 + (s/fdef cljs-3049 :args (s/cat :x number?) :ret number?) + (testing "the spec'ed fn is checkable" + (is (contains? (stest/checkable-syms) `cljs-3049))) + (s/def cljs-3049 nil) + (testing "the spec'ed fn is not checkable anymore" + (is (not (contains? (stest/checkable-syms) `cljs-3049))))) diff --git a/src/test/cljs/cljs/spec_test.cljs b/src/test/cljs/cljs/spec_test.cljs new file mode 100644 index 0000000000..d40ecb37e3 --- /dev/null +++ b/src/test/cljs/cljs/spec_test.cljs @@ -0,0 +1,475 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.spec-test + (:require [cljs.spec.alpha :as s] + [cljs.spec.test.alpha :as st] + [cljs.test :as test :refer-macros [deftest is are run-tests]] + [cljs.spec.gen.alpha :as gen] + [clojure.test.check.generators])) + +(s/def ::even? (s/and number? even?)) +(s/def ::odd? (s/and number? odd?)) + +(def s2 + (s/cat :forty-two #{42} + :odds (s/+ ::odd?) + :m (s/keys :req-un [::a ::b ::c]) + :oes (s/& (s/* (s/cat :o ::odd? :e ::even?)) #(< (count %) 3)) + :ex (s/* (s/alt :odd ::odd? :even ::even?)))) + +(deftest test-roundtrip + (let [xs [42 11 13 15 {:a 1 :b 2 :c 3} 1 2 3 42 43 44 11]] + (is (= xs (s/unform s2 (s/conform s2 xs)))))) + +(defn adder + ([a] a) + ([a b] (+ a b))) + +(s/fdef adder + :args (s/cat :a integer? :b (s/? integer?)) + :ret integer?) + +;;(st/instrument `adder) + +(deftest test-multi-arity-instrument + (is (= 1 (adder 1))) + (is (= 3 (adder 1 2))) + ;;(is (thrown? js/Error (adder "foo"))) + ) + +(defmulti testmm :type) +(defmethod testmm :default [_]) +(defmethod testmm :good [_] "good") + +(s/fdef testmm :args (s/cat :m map?) :ret string?) + +;;(st/instrument `testmm) + +(deftest test-multifn-instrument + (is (= "good" (testmm {:type :good}))) + ;;(is (thrown? js/Error (testmm "foo"))) + ) + +(deftest int-in-test + (is (s/valid? (s/int-in 1 3) 2)) + (is (not (s/valid? (s/int-in 1 3) 0)))) + +(deftest inst-in-test + (is (s/valid? (s/inst-in #inst "1999" #inst "2001") #inst "2000")) + (is (not (s/valid? (s/inst-in #inst "1999" #inst "2001") #inst "1492")))) + +(deftest test-conform-unform + (let [xs [42 11 13 15 {:a 1 :b 2 :c 3} 1 2 3 42 43 44 11]] + (is (= xs (s/unform s2 (s/conform s2 xs)))))) + +(deftest test-assert + (s/def ::even-number (s/and number? even?)) + ;; assertions off by default + (is (= 42 (s/assert ::even-number 42))) + (s/check-asserts true) + (is (= 42 (s/assert ::even-number 42))) + (is (thrown? js/Error (s/assert ::even-number 5)))) + +(deftest test-cljs-1754 + (is (boolean? (gen/generate (s/gen boolean?))))) + +(s/fdef cljs-1757-x :args (s/cat ::first number?) :ret #(= % 2)) +(defn cljs-1757-x [b] 2) + +(deftest test-cljs-1757 + (is (s/exercise-fn `cljs-1757-x))) + +(deftest test-cljs-1788 + (defmulti mm :mm/type) + (s/def ::foo-1788 (s/multi-spec mm :mm/type)) + (is (= (s/form ::foo-1788) + '(cljs.spec.alpha/multi-spec cljs.spec-test/mm :mm/type)))) + +(def h-cljs-1790 (derive (make-hierarchy) :a :b)) +(defmulti spec-type-1790 identity :hierarchy #'h-cljs-1790) +(defmethod spec-type-1790 :b [_] + (s/spec (constantly true))) + +(deftest test-cljs-1790 + (s/def ::multi (s/multi-spec spec-type-1790 identity)) + (is (= :b (s/conform ::multi :b))) + (is (= :a (s/conform ::multi :a)))) + +(deftest test-cljs-1944 + (is (not-empty (s/exercise (s/coll-of string? :kind set?))))) + +;; Copied from Clojure spec tests + +(def even-count? #(even? (count %))) + +(defn submap? + "Is m1 a subset of m2?" + [m1 m2] + (if (and (map? m1) (map? m2)) + (every? (fn [[k v]] (and (contains? m2 k) + (submap? v (get m2 k)))) + m1) + (= m1 m2))) + +(deftest conform-explain + (let [a (s/and #(> % 5) #(< % 10)) + o (s/or :s string? :k keyword?) + c (s/cat :a string? :b keyword?) + either (s/alt :a string? :b keyword?) + star (s/* keyword?) + plus (s/+ keyword?) + opt (s/? keyword?) + andre (s/& (s/* keyword?) even-count?) + andre2 (s/& (s/* keyword?) #{[:a]}) + m (s/map-of keyword? string?) + mkeys (s/map-of (s/and keyword? (s/conformer name)) any?) + mkeys2 (s/map-of (s/and keyword? (s/conformer name)) any? :conform-keys true) + s (s/coll-of (s/spec (s/cat :tag keyword? :val any?)) :kind list?) + v (s/coll-of keyword? :kind vector?) + coll (s/coll-of keyword?) + lrange (s/int-in 7 42) + drange (s/double-in :infinite? false :NaN? false :min 3.1 :max 3.2) + irange (s/inst-in #inst "1939" #inst "1946")] + + (when-not js/COMPILED + ;; CLJS-2483: these won't work with both :advanced and :none optimization settings + (are [spec x conformed ed] + (let [co (s/conform spec x) + e (::s/problems (s/explain-data spec x))] + (when (not= conformed co) (println "conform fail\n\texpect=" conformed "\n\tactual=" co)) + (when (not (every? true? (map submap? ed e))) + (println "explain failures\n\texpect=" ed "\n\tactual failures=" e "\n\tsubmap?=" (map submap? ed e))) + (and (= conformed co) (every? true? (map submap? ed e)))) + keyword? nil ::s/invalid [{:pred `keyword? :val nil}] + keyword? "abc" ::s/invalid [{:pred `keyword? :val "abc"}])) + + (are [spec x conformed ed] + (let [co (s/conform spec x) + e (::s/problems (s/explain-data spec x))] + (when (not= conformed co) (println "conform fail\n\texpect=" conformed "\n\tactual=" co)) + (when (not (every? true? (map submap? ed e))) + (println "explain failures\n\texpect=" ed "\n\tactual failures=" e "\n\tsubmap?=" (map submap? ed e))) + (and (= conformed co) (every? true? (map submap? ed e)))) + + lrange 7 7 nil + lrange 8 8 nil + lrange 42 ::s/invalid [{:pred '(cljs.core/fn [%] (cljs.spec.alpha/int-in-range? 7 42 %)), :val 42}] + + irange #inst "1938" ::s/invalid [{:pred '(cljs.core/fn [%] (cljs.spec.alpha/inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %)), :val #inst "1938"}] + irange #inst "1942" #inst "1942" nil + irange #inst "1946" ::s/invalid [{:pred '(cljs.core/fn [%] (cljs.spec.alpha/inst-in-range? #inst "1939-01-01T00:00:00.000-00:00" #inst "1946-01-01T00:00:00.000-00:00" %)), :val #inst "1946"}] + + drange 3.0 ::s/invalid [{:pred '(cljs.core/fn [%] (cljs.core/<= 3.1 %)), :val 3.0}] + drange 3.1 3.1 nil + drange 3.2 3.2 nil + ;drange Double/POSITIVE_INFINITY ::s/invalid [{:pred '(not (isInfinite %)), :val Double/POSITIVE_INFINITY}] + ;; can't use equality-based test for Double/NaN + ;; drange Double/NaN ::s/invalid {[] {:pred '(not (isNaN %)), :val Double/NaN}} + + keyword? :k :k nil + + a 6 6 nil + a 3 ::s/invalid '[{:pred (cljs.core/fn [%] (cljs.core/> % 5)), :val 3}] + a 20 ::s/invalid '[{:pred (cljs.core/fn [%] (cljs.core/< % 10)), :val 20}] + ;a nil "java.lang.NullPointerException" "java.lang.NullPointerException" + ;a :k "java.lang.ClassCastException" "java.lang.ClassCastException" + + o "a" [:s "a"] nil + o :a [:k :a] nil + o 'a ::s/invalid '[{:pred cljs.core/string?, :val a, :path [:s]} {:pred cljs.core/keyword?, :val a :path [:k]}] + + c nil ::s/invalid '[{:reason "Insufficient input", :pred cljs.core/string?, :val (), :path [:a]}] + c [] ::s/invalid '[{:reason "Insufficient input", :pred cljs.core/string?, :val (), :path [:a]}] + c [:a] ::s/invalid '[{:pred cljs.core/string?, :val :a, :path [:a], :in [0]}] + c ["a"] ::s/invalid '[{:reason "Insufficient input", :pred cljs.core/keyword?, :val (), :path [:b]}] + c ["s" :k] '{:a "s" :b :k} nil + c ["s" :k 5] ::s/invalid '[{:reason "Extra input", :pred (cljs.spec.alpha/cat :a cljs.core/string? :b cljs.core/keyword?), :val (5)}] + + (s/cat) nil {} nil + (s/cat) [5] ::s/invalid '[{:reason "Extra input", :pred (cljs.spec.alpha/cat), :val (5), :in [0]}] + + either nil ::s/invalid '[{:reason "Insufficient input", :pred (cljs.spec.alpha/alt :a cljs.core/string? :b cljs.core/keyword?), :val () :via []}] + either [] ::s/invalid '[{:reason "Insufficient input", :pred (cljs.spec.alpha/alt :a cljs.core/string? :b cljs.core/keyword?), :val () :via []}] + either [:k] [:b :k] nil + either ["s"] [:a "s"] nil + either [:b "s"] ::s/invalid '[{:reason "Extra input", :pred (cljs.spec.alpha/alt :a cljs.core/string? :b cljs.core/keyword?), :val ("s") :via []}] + + star nil [] nil + star [] [] nil + star [:k] [:k] nil + star [:k1 :k2] [:k1 :k2] nil + star [:k1 :k2 "x"] ::s/invalid '[{:pred cljs.core/keyword?, :val "x" :via []}] + star ["a"] ::s/invalid '[{:pred cljs.core/keyword?, :val "a" :via []}] + + plus nil ::s/invalid '[{:reason "Insufficient input", :pred cljs.core/keyword?, :val () :via []}] + plus [] ::s/invalid '[{:reason "Insufficient input", :pred cljs.core/keyword?, :val () :via []}] + plus [:k] [:k] nil + plus [:k1 :k2] [:k1 :k2] nil + plus [:k1 :k2 "x"] ::s/invalid '[{:pred cljs.core/keyword?, :val "x", :in [2]}] + plus ["a"] ::s/invalid '[{:pred cljs.core/keyword?, :val "a" :via []}] + + opt nil nil nil + opt [] nil nil + opt :k ::s/invalid '[{:pred (cljs.core/fn [%] (cljs.core/or (cljs.core/nil? %) (cljs.core/sequential? %))), :val :k}] + opt [:k] :k nil + opt [:k1 :k2] ::s/invalid '[{:reason "Extra input", :pred (cljs.spec.alpha/? cljs.core/keyword?), :val (:k2)}] + opt [:k1 :k2 "x"] ::s/invalid '[{:reason "Extra input", :pred (cljs.spec.alpha/? cljs.core/keyword?), :val (:k2 "x")}] + opt ["a"] ::s/invalid '[{:pred cljs.core/keyword?, :val "a"}] + + andre nil nil nil + andre [] nil nil + andre :k ::s/invalid '[{:pred (cljs.core/fn [%] (cljs.core/or (cljs.core/nil? %) (cljs.core/sequential? %))), :val :k}] + andre [:k] ::s/invalid '[{:pred cljs.spec-test/even-count?, :val [:k]}] + andre [:j :k] [:j :k] nil + + andre2 nil ::s/invalid [{:pred #{[:a]}, :val []}] + andre2 [] ::s/invalid [{:pred #{[:a]}, :val []}] + andre2 [:a] [:a] nil + + m nil ::s/invalid '[{:pred cljs.core/map?, :val nil}] + m {} {} nil + m {:a "b"} {:a "b"} nil + + mkeys nil ::s/invalid '[{:pred cljs.core/map?, :val nil}] + mkeys {} {} nil + mkeys {:a 1 :b 2} {:a 1 :b 2} nil + + mkeys2 nil ::s/invalid '[{:pred cljs.core/map?, :val nil}] + mkeys2 {} {} nil + mkeys2 {:a 1 :b 2} {"a" 1 "b" 2} nil + + s '([:a 1] [:b "2"]) '({:tag :a :val 1} {:tag :b :val "2"}) nil + + v [:a :b] [:a :b] nil + v '(:a :b) ::s/invalid '[{:pred cljs.core/vector? :val (:a :b)}] + + coll nil ::s/invalid '[{:path [], :pred cljs.core/coll?, :val nil, :via [], :in []}] + coll [] [] nil + coll [:a] [:a] nil + coll [:a :b] [:a :b] nil + coll (map identity [:a :b]) '(:a :b) nil + ;;coll [:a "b"] ::s/invalid '[{:pred (coll-checker keyword?), :val [:a b]}] + ))) + +(deftest coll-form + (are [spec form] + (= (s/form spec) form) + (s/map-of int? any?) + '(cljs.spec.alpha/map-of cljs.core/int? cljs.core/any?) + + (s/coll-of int?) + '(cljs.spec.alpha/coll-of cljs.core/int?) + + (s/every-kv int? int?) + '(cljs.spec.alpha/every-kv cljs.core/int? cljs.core/int?) + + (s/every int?) + '(cljs.spec.alpha/every cljs.core/int?) + + (s/coll-of (s/tuple (s/tuple int?))) + '(cljs.spec.alpha/coll-of (cljs.spec.alpha/tuple (cljs.spec.alpha/tuple cljs.core/int?))) + + (s/coll-of int? :kind vector?) + '(cljs.spec.alpha/coll-of cljs.core/int? :kind cljs.core/vector?) + + (s/coll-of int? :gen #(gen/return [1 2])) + '(cljs.spec.alpha/coll-of cljs.core/int? :gen (fn* [] (gen/return [1 2]))))) + +(defn check-conform-unform [spec vals expected-conforms] + (let [actual-conforms (map #(s/conform spec %) vals) + unforms (map #(s/unform spec %) actual-conforms)] + (is (= actual-conforms expected-conforms)) + (is (= vals unforms)))) + +(deftest coll-conform-unform + (check-conform-unform + (s/coll-of (s/or :i int? :s string?)) + [[1 "x"]] + [[[:i 1] [:s "x"]]]) + (check-conform-unform + (s/every (s/or :i int? :s string?)) + [[1 "x"]] + [[1 "x"]]) + (check-conform-unform + (s/map-of int? (s/or :i int? :s string?)) + [{10 10 20 "x"}] + [{10 [:i 10] 20 [:s "x"]}]) + (check-conform-unform + (s/map-of (s/or :i int? :s string?) int? :conform-keys true) + [{10 10 "x" 20}] + [{[:i 10] 10 [:s "x"] 20}]) + (check-conform-unform + (s/every-kv int? (s/or :i int? :s string?)) + [{10 10 20 "x"}] + [{10 10 20 "x"}])) + +(deftest &-explain-pred + (are [val expected] + (= expected (-> (s/explain-data (s/& int? even?) val) ::s/problems first :pred)) + [] 'cljs.core/int? + [0 2] '(cljs.spec.alpha/& cljs.core/int? cljs.core/even?))) + +(deftest keys-explain-pred + (is (= 'cljs.core/map? (-> (s/explain-data (s/keys :req [::x]) :a) ::s/problems first :pred)))) + +(deftest remove-def + (is (= ::ABC (s/def ::ABC string?))) + (is (= ::ABC (s/def ::ABC nil))) + (is (nil? (s/get-spec ::ABC)))) + +;; TODO replace this with a generative test once we have specs for s/keys +(deftest map-spec-generators + (s/def ::a nat-int?) + (s/def ::b boolean?) + (s/def ::c keyword?) + (s/def ::d double?) + (s/def ::e inst?) + (s/def ::f some?) + + (is (= #{[::a] + [::a ::b] + [::a ::b ::c] + [::a ::c]} + (->> (s/exercise (s/keys :req [::a] :opt [::b ::c]) 100) + (map (comp sort keys first)) + (into #{})))) + + (is (= #{[:a] + [:a :b] + [:a :b :c] + [:a :c]} + (->> (s/exercise (s/keys :req-un [::a] :opt-un [::b ::c]) 100) + (map (comp sort keys first)) + (into #{})))) + + (is (= #{[::a ::b] + [::a ::b ::c ::d] + [::a ::b ::c ::d ::e] + [::a ::b ::c ::e] + [::a ::c ::d] + [::a ::c ::d ::e] + [::a ::c ::e]} + (->> (s/exercise (s/keys :req [::a (or ::b (and ::c (or ::d ::e)))]) 200) + (map (comp vec sort keys first)) + (into #{})))) + + (is (= #{[:a :b] + [:a :b :c :d] + [:a :b :c :d :e] + [:a :b :c :e] + [:a :c :d] + [:a :c :d :e] + [:a :c :e]} + (->> (s/exercise (s/keys :req-un [::a (or ::b (and ::c (or ::d ::e)))]) 200) + (map (comp vec sort keys first)) + (into #{})))) + + (is (every? some? (map #(-> % first) (s/exercise ::f 10))))) + +(deftest tuple-explain-pred + (are [val expected] + (= expected (-> (s/explain-data (s/tuple int?) val) ::s/problems first :pred)) + :a 'cljs.core/vector? + [] '(cljs.core/= (cljs.core/count %) 1))) + +(s/fdef foo.bar/cljs-2275 + :args (s/cat :k keyword?) + :ret string?) + +(defn foo-2793 [m & args] + {:m m, :args args}) + +(defn bar-2793 + ([x] {:x x}) + ([x y] {:x x, :y y}) + ([x y & m] {:x x, :y y, :m m})) + +(defn baz-2793 [x & ys]) + +(defn quux-2793 [& numbers]) + +(s/fdef foo-2793) +(s/fdef bar-2793) +(s/fdef baz-2793 :args (s/cat :x number? :ys (s/* number?))) + +(st/instrument `foo-2793) +(st/instrument `bar-2793) +(st/instrument `baz-2793) +(st/instrument `quux-2793) + +(deftest cljs-2793-test + (is (= {:m {:x 1 :y 2} + :args nil} + (foo-2793 {:x 1 :y 2}))) + (is (= {:m {:x 1 :y 2} + :args [1]} + (foo-2793 {:x 1 :y 2} 1))) + (is (= {:m {:x 1 :y 2} + :args [1 2]} + (foo-2793 {:x 1 :y 2} 1 2))) + (is (= {:x 1} + (bar-2793 1))) + (is (= {:x 1 + :y 2} + (bar-2793 1 2))) + (is (= {:x 1 + :y 2 + :m [3]} + (bar-2793 1 2 3))) + (is (= {:x 1 + :y 2 + :m [3 4]} + (bar-2793 1 2 3 4))) + (is (nil? (baz-2793 1))) + (is (nil? (quux-2793)))) + +(s/def ::cljs-2940-foo (s/cat :bar (s/nilable ::cljs-2940-foo))) + +(deftest describing-evaled-specs + (let [sp #{1 2}] + (is (= (s/describe sp) (s/form sp) sp))) + ;; won't work under advanced + (when-not js/COMPILED + (is (= (s/describe odd?) 'odd?)) + (is (= (s/form odd?) 'cljs.core/odd?))) + (is (= (s/describe #(odd? %)) ::s/unknown)) + (is (= (s/form #(odd? %)) ::s/unknown))) + +(defn defk [key & [doc]] + [key doc]) + +(s/fdef defk + :args (s/cat :key keyword? + :doc (s/? string?))) + +(st/instrument `defk) + +(deftest cljs-2977-variadic-fn + (is (thrown? js/Error (defk 1 1))) + (is (thrown? js/Error (defk :foo 1))) + (is (= [:foo "bar"] (defk :foo "bar")))) + +(s/def ::add-spec + (s/fspec :args (s/cat :n pos?) + :ret number?)) + +(s/def add2 ::add-spec) +(defn add2 [n] + (+ n 2)) + +(st/instrument `add2) + +(deftest cljs-3137 + (is (thrown? js/Error (add2 0)))) + +(comment + + (run-tests) + + ) diff --git a/src/test/cljs/cljs/specials_test.cljs b/src/test/cljs/cljs/specials_test.cljs new file mode 100644 index 0000000000..670667e9aa --- /dev/null +++ b/src/test/cljs/cljs/specials_test.cljs @@ -0,0 +1,25 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.specials-test + (:require [cljs.test :refer-macros [deftest is]])) + +(defprotocol IFoo3125 + (-mutate [this])) + +(defrecord Foo3125 [^:mutable x] + IFoo3125 + (-mutate [this] (* 3 (set! x (inc x))))) + +(def ^:dynamic *test-cljs-3125* 4) + +(deftest test-cljs-3125 + (is (== 12 (let [o #js {}] (* 6 (set! (.-a o) 2))))) + (is (== 12 (let [o #js {}] (* 6 (set! o -a 2))))) + (is (== 15 (* 3 (set! *test-cljs-3125* (inc *test-cljs-3125*))))) + (is (== 18 (-mutate (->Foo3125 5))))) diff --git a/src/test/cljs/cljs/syntax_quote_test.cljs b/src/test/cljs/cljs/syntax_quote_test.cljs new file mode 100644 index 0000000000..ea7de9e341 --- /dev/null +++ b/src/test/cljs/cljs/syntax_quote_test.cljs @@ -0,0 +1,16 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.syntax-quote-test + (:require [cljs.test :as test :refer-macros [deftest is]])) + +(deftest test-syntax-quote + (is (= 'cljs.syntax-quote-test/foo `foo)) + (is (= 'cljs.test/test-vars `test/test-vars)) + (is (= 'cljs.test/deftest `test/deftest)) + (is (= 'cljs.test/foo `test/foo))) diff --git a/src/test/cljs/cljs/tagged_literals_test.cljs b/src/test/cljs/cljs/tagged_literals_test.cljs new file mode 100644 index 0000000000..1635381c63 --- /dev/null +++ b/src/test/cljs/cljs/tagged_literals_test.cljs @@ -0,0 +1,20 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.tagged-literals-test + (:require [cljs.test :refer-macros [deftest is]] + [cljs.reader :as reader])) + +(deftest test-identity-custom-literal + (is (= #cljs/tag [1 2 3] [1 2 3]))) + +(deftest test-runtime-reader + (is (object? (reader/read-string "#js {}"))) + (is (= {} (reader/read-string "#cljs/tag {}"))) + (is (= (reader/read-string "#cljs/inc 0") 1)) + (is (= (reader/read-string "#cljs/union #{1}") #{1}))) diff --git a/src/test/cljs/cljs/test_test.cljs b/src/test/cljs/cljs/test_test.cljs new file mode 100644 index 0000000000..db53bfcb3f --- /dev/null +++ b/src/test/cljs/cljs/test_test.cljs @@ -0,0 +1,40 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.test-test + (:require [cljs.test :refer-macros [deftest testing is] :as ct] + [clojure.string :as s] + [clojure.set :as set])) + +(defn- nan? + [x] + (and (number? x) + (js/isNaN x))) + +(deftest js-line-and-column-test + (is (= [2 3] (ct/js-line-and-column "foo:bar:2:3"))) + (is (= [2 3] (ct/js-line-and-column "foo:2:3"))) + (is (= [2 3] (ct/js-line-and-column "2:3"))) + (let [[line column] (ct/js-line-and-column "foo:bogus:3")] + (is (nan? line)) + (is (== 3 column))) + (let [[line column] (ct/js-line-and-column "foo:2:bogus")] + (is (== 2 line)) + (is (nan? column))) + (let [[line column] (ct/js-line-and-column "foo:bogus:bogus")] + (is (nan? line)) + (is (nan? column))) + (let [[line column] (ct/js-line-and-column "foo:3")] + (is (nan? line)) + (is (== 3 column))) + (let [[line column] (ct/js-line-and-column "foo")] + (is (nan? line)) + (is (nan? column)))) + +(deftest test-js-filename + (is (= "core-advanced-test.js" (ct/js-filename (str "nW@" (ct/cljs-output-dir) "/core-advanced-test.js:1191:77"))))) diff --git a/src/test/cljs/cljs/top_level.cljs b/src/test/cljs/cljs/top_level.cljs new file mode 100644 index 0000000000..fc938a4291 --- /dev/null +++ b/src/test/cljs/cljs/top_level.cljs @@ -0,0 +1,23 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.top-level + (:refer-clojure :exclude [test]) + (:require [cljs.test :refer-macros [deftest is]])) + +(let [foo 1] + (defn bar [] + foo)) + +(let [foo 2] + (defn baz [] + foo)) + +(deftest test + (is (= (bar) 1)) + (is (= (baz) 2))) diff --git a/src/test/cljs/cljs/var_test.cljs b/src/test/cljs/cljs/var_test.cljs new file mode 100644 index 0000000000..886326a76a --- /dev/null +++ b/src/test/cljs/cljs/var_test.cljs @@ -0,0 +1,22 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.var-test + (:require [cljs.test :refer-macros [deftest is testing]])) + +(defn cljs-3411-function + "this function adds two numbers" + {:test #(do + (assert (= (cljs-3411-function 2 3) 5)) + (assert (= (cljs-3411-function 4 4) 8)))} + ([x y] (+ x y))) + +(deftest cljs-3411 + (testing "cljs.core/test respects docstring" + (is (= :ok (test cljs-3411-function))) + (is (= :ok (test #'cljs-3411-function))))) diff --git a/src/test/cljs/cljs/walk_test.cljs b/src/test/cljs/cljs/walk_test.cljs new file mode 100644 index 0000000000..fb3476f534 --- /dev/null +++ b/src/test/cljs/cljs/walk_test.cljs @@ -0,0 +1,82 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.walk-test + (:require [cljs.test :refer-macros [deftest testing is]] + [clojure.walk :as w])) + +(deftest t-prewalk-replace + (is (= (w/prewalk-replace {:a :b} [:a {:a :a} (list 3 :c :a)]) + [:b {:b :b} (list 3 :c :b)]))) + +(deftest t-postwalk-replace + (is (= (w/postwalk-replace {:a :b} [:a {:a :a} (list 3 :c :a)]) + [:b {:b :b} (list 3 :c :b)]))) + +(deftest t-stringify-keys + (is (= (w/stringify-keys {:a 1, nil {:b 2 :c 3}, :d 4}) + {"a" 1, nil {"b" 2 "c" 3}, "d" 4}))) + +(deftest t-prewalk-order + (is (= (let [a (atom [])] + (w/prewalk (fn [form] (swap! a conj form) form) + [1 2 {:a 3} (list 4 [5])]) + @a) + [[1 2 {:a 3} (list 4 [5])] + 1 2 {:a 3} [:a 3] :a 3 (list 4 [5]) + 4 [5] 5]))) + +(deftest t-postwalk-order + (is (= (let [a (atom [])] + (w/postwalk (fn [form] (swap! a conj form) form) + [1 2 {:a 3} (list 4 [5])]) + @a) + [1 2 + :a 3 [:a 3] {:a 3} + 4 5 [5] (list 4 [5]) + [1 2 {:a 3} (list 4 [5])]]))) + +(defrecord Foo [a b c]) + +(defmulti get-comparator type) + +(defmethod get-comparator PersistentTreeMap + [o] (.-comp o)) + +(defmethod get-comparator PersistentTreeSet + [o] (get-comparator (.-tree-map o))) + +(deftest test-walk + (testing "Test that walk returns the correct result\n" + (let [colls ['(1 2 3) + [1 2 3] + #{1 2 3} + (sorted-set-by > 1 2 3) + {:a 1, :b 2, :c 3} + (sorted-map-by > 1 10, 2 20, 3 30) + (->Foo 1 2 3) + (map->Foo {:a 1 :b 2 :c 3 :extra 4})]] + (doseq [c colls] + (testing (str "Walking ... " c) + (let [walked (w/walk identity identity c)] + (is (= c walked)) + ;;(is (= (type c) (type walked))) + (if (map? c) + (is (= (w/walk #(update-in % [1] inc) #(reduce + (vals %)) c) + (reduce + (map (comp inc val) c)))) + (is (= (w/walk inc #(reduce + %) c) + (reduce + (map inc c))))) + (when (or (instance? PersistentTreeMap c) + (instance? PersistentTreeSet c)) + (is (= (get-comparator c) (get-comparator walked)))))))))) + +(deftest walk-mapentry + "Checks that walk preserves the MapEntry type. See CLJS-2909." + (let [coll [:html {:a ["b" 1]} ""] + f (fn [e] (if (and (vector? e) (not (map-entry? e))) (apply list e) e))] + (is (= (list :html {:a (list "b" 1)} "") (w/postwalk f coll))))) diff --git a/src/test/cljs/clojure/data_test.cljs b/src/test/cljs/clojure/data_test.cljs new file mode 100644 index 0000000000..0675a6a5b9 --- /dev/null +++ b/src/test/cljs/clojure/data_test.cljs @@ -0,0 +1,31 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns clojure.data-test + (:require [cljs.test :refer-macros [deftest is]] + [clojure.data :refer [diff]])) + +(deftest test-data + (is (= [nil nil nil] (diff nil nil))) + (is (= [1 2 nil] (diff 1 2))) + (is (= [nil nil [1 2 3]] (diff [1 2 3] '(1 2 3)))) + (is (= [1 [:a :b] nil] (diff 1 [:a :b]))) + (is (= [{:a 1} :b nil] (diff {:a 1} :b))) + (is (= [:team #{:p1 :p2} nil] (diff :team #{:p1 :p2}))) + (is (= [{0 :a} [:a] nil] (diff {0 :a} [:a]))) + (is (= [nil [nil 2] [1]] (diff [1] [1 2]))) + (is (= [nil nil [1 2]] (diff [1 2] (into-array [1 2])))) + (is (= [#{:a} #{:b} #{:c :d}] (diff #{:a :c :d} #{:b :c :d}))) + (is (= [nil nil {:a 1}] (diff {:a 1} {:a 1}))) + (is (= [{:a #{2}} {:a #{4}} {:a #{3}}] (diff {:a #{2 3}} {:a #{3 4}}))) + (is (= [nil nil [1 2]] (diff [1 2] (into-array [1 2])))) + (is (= [nil nil [1 2]] (diff (into-array [1 2]) [1 2]))) + (is (= [{:a {:c [1]}} {:a {:c [0]}} {:a {:c [nil 2] :b 1}}] + (diff {:a {:b 1 :c [1 2]}} {:a {:b 1 :c [0 2]}}))) + (is (= [{:a nil} {:a false} {:b nil :c false}] + (diff {:a nil :b nil :c false} {:a false :b nil :c false})))) diff --git a/src/test/cljs/clojure/datafy_test.cljs b/src/test/cljs/clojure/datafy_test.cljs new file mode 100644 index 0000000000..b4a04fb45f --- /dev/null +++ b/src/test/cljs/clojure/datafy_test.cljs @@ -0,0 +1,35 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns clojure.datafy-test + (:require [cljs.test :as test + :refer-macros [deftest is testing]] + [clojure.datafy :as d])) + +(deftest datafy-test + (testing "Datafy works when datafied value is arbitrary JS objects" + (let [datafied #js {} + x (with-meta [1 2 3] {`clojure.core.protocols/datafy (fn [_] datafied)})] + (is (= datafied (d/datafy x))))) + (testing "Datafy adds ::obj metadata when return value != original value and supports metadata" + (let [datafied [2 3 4] + original [1 2 3] + x (with-meta original {`clojure.core.protocols/datafy (fn [_] datafied)})] + (is (= datafied (d/datafy x))) + (is (= {:clojure.datafy/obj original} (meta (d/datafy x))))))) + +(deftest datafy-js-errors-test + (let [x (js/Error. "foo")] + (is (= (Throwable->map x) (d/datafy x)))) + ;; Ensure we can datafy objects that extend js/Error + (let [x (js/RangeError. "x must be between 1 and 5")] + (is (= (Throwable->map x) (d/datafy x))))) + +(deftest datafy-ex-info-test + (let [x (ex-info "foo" {:a 1} (ex-info "bar" {:b 2}))] + (is (= (Throwable->map x) (d/datafy x))))) diff --git a/src/test/cljs/clojure/edn_test.cljs b/src/test/cljs/clojure/edn_test.cljs new file mode 100644 index 0000000000..66d85bff1f --- /dev/null +++ b/src/test/cljs/clojure/edn_test.cljs @@ -0,0 +1,32 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns clojure.edn-test + (:require [cljs.test :refer-macros [deftest is testing]] + [clojure.edn :as edn] + [cljs.reader :as reader] + [cljs.tools.reader.reader-types :as reader-types])) + +(defn- test-reader [] + (reader-types/string-push-back-reader "[1 2 3]")) + +(deftest test-read + (testing "Mirrors cljs.reader/read" + (is (= (edn/read (test-reader)) + (reader/read (test-reader)))) + (is (= (edn/read {} (test-reader)) + (reader/read {} (test-reader)))) + (is (= (edn/read (test-reader) false "EOF" {}) + (reader/read (test-reader) false "EOF" {}))))) + +(deftest test-read-string + (testing "Mirrors cljs.reader/read-string" + (is (= (edn/read-string "(+ 1 2)") + (reader/read-string "(+ 1 2)"))) + (is (= (edn/read-string "{:a #{[1]}}") + (reader/read-string "{:a #{[1]}}"))))) diff --git a/src/test/cljs/clojure/gen_math_test.clj b/src/test/cljs/clojure/gen_math_test.clj new file mode 100644 index 0000000000..ee72d280bf --- /dev/null +++ b/src/test/cljs/clojure/gen_math_test.clj @@ -0,0 +1,285 @@ +(ns ^{:doc "Tests clojure.math to compare between JVM provided functions and the + clojure.math implementations on a ClojureScript instance running on NodeJS. + Tests are generative, but not run through the defspec framework to minimize + i/o to the ClojureScript instance." + :authors ["Michiel Borkent" "Paula Gearon"]} + clojure.gen-math-test + (:require [cljs.core.server] + [cljs.repl.node] + [clojure.core.server :as server] + [clojure.edn :as edn] + [clojure.java.io :as io] + [clojure.test :as t :refer [deftest is]] + [clojure.test.check.clojure-test :refer [defspec]] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as prop])) + +(def ^:const Number-MAX_SAFE_INTEGER 9007199254740991) +(def ^:const Number-MIN_SAFE_INTEGER -9007199254740991) +(defn Number-isSafeInteger + [n] + (and (>= n Number-MIN_SAFE_INTEGER) + (<= n Number-MAX_SAFE_INTEGER))) + +(def gen-small-integer + "Generates a positive or negative integer bounded by the generator's + `size` parameter. Shrinks to zero." + (gen/sized (fn [size] (gen/choose (- size) size)))) + +(def reader (atom nil)) +(def writer (atom nil)) + +(defn cljs-eval [expr] + (-> (binding [*out* @writer + *in* @reader] + (println expr) + (read-line)) + edn/read-string + :val)) + +(t/use-fixtures :once + (fn [f] + (println "Launching test pREPL.") + (let [server (server/start-server {:accept 'cljs.core.server/io-prepl + :address "127.0.0.1" + :port 0 + :name "clojure.math-repl" + :args [:repl-env (cljs.repl.node/repl-env)]}) + port (-> server (.getLocalPort))] + (println "Server opened on port" port) + (with-open [socket (java.net.Socket. "127.0.0.1" port) + rdr (io/reader socket) + wrtr (io/writer socket)] + (reset! reader rdr) + (reset! writer wrtr) + (println "Executing tests") + (cljs-eval "(require 'clojure.math)") + (f) + (println "Tearing down test pREPL."))))) + +(deftest sanity-test + (is (= "6" (cljs-eval "(+ 1 2 3)")))) + +(deftest cljs-match-sanity-test + (is (= "1" (cljs-eval "(clojure.math/cos 0.0)")))) + +(defn n== + [a b] + (or (and (Double/isNaN a) (Double/isNaN b)) + (and (number? a) (number? b) (== a b)) + (= a b))) + +(defn maxi== + [a b] + (or (and (Double/isNaN a) (Double/isNaN b)) + (and (= a Number-MAX_SAFE_INTEGER) (= b Long/MAX_VALUE)) + (and (= a Number-MIN_SAFE_INTEGER) (= b Long/MIN_VALUE)) + (and (number? a) (number? b) (== a b)) + (= a b))) + +(defmacro test-t->t + [n jfn cfn gen & [equals]] + (let [jmfn (symbol "Math" (str jfn)) + cmfn (name cfn) + eq (or equals n==)] + `(let [ds# (gen/sample ~gen ~n)] + (is (every? identity + (map ~eq + (read-string + (cljs-eval (str "(->> '" (pr-str ds#) + " (map double)" + " (map clojure.math/" ~cmfn "))"))) + (map #(~jmfn %) ds#))) + (str "data: " (pr-str ds#)))))) + +(defmacro test-double->double + [n jfn cfn & [equals]] + `(test-t->t ~n ~jfn ~cfn gen/double ~equals)) + +(defmacro test-t-t->double + [n jfn cfn gen1 gen2 & [equals]] + (let [jmfn (symbol "Math" (str jfn)) + cmfn (name cfn) + eq (or equals n==)] + `(let [ds# (gen/sample ~gen1 ~n) + ds2# (gen/sample ~gen2 ~n)] + (is (every? identity + (map ~eq + (read-string + (cljs-eval (str "(->> (map #(vector %1 %2) '" + (pr-str ds#) " '" (pr-str ds2#) ")" + " (map #(try (apply clojure.math/" ~cmfn " %) (catch :default _ :exception))))"))) + (map #(~jmfn %1 %2) ds# ds2#))) + (str "data: " (pr-str (map vector ds# ds2#))))))) + +(defmacro test-double-double->double + [n jfn cfn & [equals]] + `(test-t-t->double ~n ~jfn ~cfn gen/double gen/double ~equals)) + +(def safe-integer (gen/sized (fn [_] (gen/choose Number-MIN_SAFE_INTEGER Number-MAX_SAFE_INTEGER)))) + +(defn e== + [a b] + (or (and (number? a) (number? b) (== a b)) + (= a b))) + +(defmacro test-zlong-long->long + [n jfn cfn] + (let [jmfn (symbol "Math" (str jfn)) + cmfn (name cfn)] + `(let [lzs# (gen/sample safe-integer ~n) + ls# (gen/sample (gen/such-that #(not= % 0) safe-integer) ~n)] + (is (every? identity + (map e== + (read-string + (cljs-eval (str "(->> (map #(vector (long %1) (long %2)) '" + (pr-str lzs#) " '" (pr-str ls#) ")" + " (map #(try (apply clojure.math/" ~cmfn " %) (catch :default _ :exception))))"))) + (map #(~jmfn (long %1) (long %2)) lzs# ls#))) + (str "data: " (pr-str (map vector lzs# ls#))))))) + +;; Tests clojure.core/abs. This function has recently moved to core +(deftest abs-test + (let [ds (gen/sample gen/double 100)] + (is (every? identity + (map #(or (= (double %1) %2) (and (Double/isNaN %1) (Double/isNaN %2))) + (read-string (cljs-eval (str "(->> '" (pr-str ds) + " (map double)" + " (map abs))"))) + (map #(Math/abs %) ds))) ;; This can change to clojure.core/math after Clojure 11 + (str "data: " (pr-str ds))))) + +(def ^:const delta 1E-15) + +(defn nd== + [label a b] + (or (and (Double/isNaN a) (Double/isNaN b)) + (== a b) + (do + (println label "variance:" a "\u2260" b) + (< (Math/abs (- a b)) delta)))) + +(deftest sin-test + (test-double->double 100 sin sin #(nd== "sin()" %1 %2))) + +(deftest to-radians-test + (test-double->double 100 toRadians to-radians)) + +(deftest to-degrees-test + (test-double->double 100 toDegrees to-degrees)) + +(deftest ieee-remainder-test + (test-double-double->double 100 IEEEremainder IEEE-remainder)) + +(deftest ceil-test + (test-double->double 100 ceil ceil)) + +(deftest ceil-null-test + (is (= ":exception" (cljs-eval (str "(try (clojure.math/ceil nil) (catch :default _ :exception))"))))) + +(deftest floor-test + (test-double->double 100 floor floor)) + +(deftest floor-null-test + (is (= ":exception" (cljs-eval (str "(try (clojure.math/floor nil) (catch :default _ :exception))"))))) + +(deftest copy-sign-test + (test-double-double->double 100 copySign copy-sign)) + +(deftest rint-test + (test-double->double 100 rint rint)) + +(deftest round-test + (test-t->t 100 round round (gen/double* {:min Number-MIN_SAFE_INTEGER :max Number-MAX_SAFE_INTEGER}) maxi==)) + +(deftest floor-div-test + (test-zlong-long->long 100 floorDiv floor-div)) + +(deftest floor-mod-test + (test-zlong-long->long 100 floorMod floor-mod)) + +(deftest get-exponent-test + (test-double->double 100 getExponent get-exponent)) + +(deftest ulp-test + (test-double->double 100 ulp ulp)) + +(deftest signum-test + (test-double->double 100 signum signum)) + +(deftest next-after-test + (test-double-double->double 100 nextAfter next-after)) + +(deftest next-up-test + (test-double->double 100 nextUp next-up)) + +(deftest next-down-test + (test-double->double 100 nextDown next-down)) + +(def ^:const MAX-INT 0x7fffffff) + +(deftest scalb-test + (test-t-t->double 100 scalb scalb + gen/double + (gen/such-that + #(<= % MAX-INT) + (gen/resize (inc MAX-INT) gen-small-integer)))) + +;; utililties for the -exact tests +(def safe-integer (gen/choose Number-MIN_SAFE_INTEGER Number-MAX_SAFE_INTEGER)) + +(defn no-overflow? + [f ^long x ^long y] + (try + (Number-isSafeInteger (f x y)) + (catch ArithmeticException _ false))) + +(defmacro test-safe-safe->safe + [n jfn cfn op] + (let [jmfn (symbol "Math" (str jfn)) + cmfn (name cfn)] + `(let [ls1# (gen/sample safe-integer ~n) + ls2# (gen/sample safe-integer ~n)] + (is (every? identity + (map e== + (read-string + (cljs-eval (str "(->> (map #(vector (long %1) (long %2)) '" + (pr-str ls1#) " '" (pr-str ls2#) ")" + " (map (fn [[a b]]" + " (try (clojure.math/" ~cmfn " a b)" + " (catch :default _ :exception)))))"))) + (map #(if (no-overflow? ~op %1 %2) + (~jmfn (long %1) (long %2)) + :exception) ls1# ls2#))) + (str "data: " (pr-str (map vector ls1# ls2#))))))) + +(deftest add-exact-test + (test-safe-safe->safe 100 addExact add-exact +)) + +(deftest subtract-exact + (test-safe-safe->safe 100 subtractExact subtract-exact -)) + +(deftest multiply-exact + (test-safe-safe->safe 100 multiplyExact multiply-exact *)) + +(defmacro test-safe->safe + [n jfn cfn op] + (let [jmfn (symbol "Math" (str jfn)) + cmfn (name cfn)] + `(let [ls# (gen/sample safe-integer ~n)] + (is (every? identity + (map e== + (read-string + (cljs-eval (str "(->> '" (pr-str ls#) + " (map #(try (clojure.math/" ~cmfn " %)" + " (catch :default _ :exception))))"))) + (map #(if (no-overflow? ~op % 1) + (~jmfn (long %)) + :exception) ls#))) + (str "data: " (pr-str (map vector ls#))))))) + +(deftest increment-exact + (test-safe->safe 100 incrementExact increment-exact +)) + +(deftest decrement-exact + (test-safe->safe 100 decrementExact decrement-exact -)) diff --git a/src/test/cljs/clojure/math_test.cljs b/src/test/cljs/clojure/math_test.cljs new file mode 100644 index 0000000000..49a79a58ad --- /dev/null +++ b/src/test/cljs/clojure/math_test.cljs @@ -0,0 +1,318 @@ +; Copyright (c) Rich Hickey. All rights reserved. +; The use and distribution terms for this software are covered by the +; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +; which can be found in the file epl-v10.html at the root of this distribution. +; By using this software in any fashion, you are agreeing to be bound by +; the terms of this license. +; You must not remove this notice, or any other, from this software. + +(ns clojure.math-test + (:require + [clojure.test :refer [deftest is]] + [cljs.math :as m])) + +(defn neg-zero? + [d] + (and (zero? d) (== -1.0 (m/copy-sign 1.0 d)))) + +(defn pos-zero? + [d] + (and (zero? d) (== 1.0 (m/copy-sign 1.0 d)))) + +(defn ulp= + "Tests that y = x +/- m*ulp(x)" + [x y m] + (let [mu (* (m/ulp x) m)] + (<= (- x mu) y (+ x mu)))) + +(deftest test-sin + (is (js/isNaN (m/sin ##NaN))) + (is (js/isNaN (m/sin ##-Inf))) + (is (js/isNaN (m/sin ##Inf))) + (is (pos-zero? (m/sin 0.0))) + (is (neg-zero? (m/sin -0.0))) + (is (ulp= (m/sin m/PI) (- (m/sin (- m/PI))) 1))) + +(deftest test-cos + (is (js/isNaN (m/cos ##NaN))) + (is (js/isNaN (m/cos ##-Inf))) + (is (js/isNaN (m/cos ##Inf))) + (is (= 1.0 (m/cos 0.0) (m/cos -0.0))) + (is (ulp= (m/cos m/PI) (m/cos (- m/PI)) 1))) + +(deftest test-tan + (is (js/isNaN (m/tan ##NaN))) + (is (js/isNaN (m/tan ##-Inf))) + (is (js/isNaN (m/tan ##Inf))) + (is (pos-zero? (m/tan 0.0))) + (is (neg-zero? (m/tan -0.0))) + (is (ulp= (- (m/tan m/PI)) (m/tan (- m/PI)) 1))) + +(deftest test-asin + (is (js/isNaN (m/asin ##NaN))) + (is (js/isNaN (m/asin 2.0))) + (is (js/isNaN (m/asin -2.0))) + (is (zero? (m/asin -0.0)))) + +(deftest test-acos + (is (js/isNaN (m/acos ##NaN))) + (is (js/isNaN (m/acos -2.0))) + (is (js/isNaN (m/acos 2.0))) + (is (ulp= (* 2 (m/acos 0.0)) m/PI 1))) + +(deftest test-atan + (is (js/isNaN (m/atan ##NaN))) + (is (pos-zero? (m/atan 0.0))) + (is (neg-zero? (m/atan -0.0))) + (is (ulp= (m/atan 1) 0.7853981633974483 1))) + +(deftest test-radians-degrees-roundtrip + (doseq [d (range 0.0 360.0 5.0)] + (is (ulp= (m/round d) (m/round (-> d m/to-radians m/to-degrees)) 1)))) + +(deftest test-exp + (is (js/isNaN (m/exp ##NaN))) + (is (= ##Inf (m/exp ##Inf))) + (is (pos-zero? (m/exp ##-Inf))) + (is (ulp= (m/exp 0.0) 1.0 1)) + (is (ulp= (m/exp 1) m/E 1))) + +(deftest test-log + (is (js/isNaN (m/log ##NaN))) + (is (js/isNaN (m/log -1.0))) + (is (= ##Inf (m/log ##Inf))) + (is (= ##-Inf (m/log 0.0))) + (is (ulp= (m/log m/E) 1.0 1))) + +(deftest test-log10 + (is (js/isNaN (m/log10 ##NaN))) + (is (js/isNaN (m/log10 -1.0))) + (is (= ##Inf (m/log10 ##Inf))) + (is (= ##-Inf (m/log10 0.0))) + (is (ulp= (m/log10 10) 1.0 1))) + +(deftest test-sqrt + (is (js/isNaN (m/sqrt ##NaN))) + (is (js/isNaN (m/sqrt -1.0))) + (is (= ##Inf (m/sqrt ##Inf))) + (is (pos-zero? (m/sqrt 0))) + (is (= (m/sqrt 4.0) 2.0))) + +(deftest test-cbrt + (is (js/isNaN (m/cbrt ##NaN))) + (is (= ##-Inf (m/cbrt ##-Inf))) + (is (= ##Inf (m/cbrt ##Inf))) + (is (pos-zero? (m/cbrt 0))) + (is (= 2.0 (m/cbrt 8.0)))) + +(deftest test-IEEE-remainder + (is (js/isNaN (m/IEEE-remainder ##NaN 1.0))) + (is (js/isNaN (m/IEEE-remainder 1.0 ##NaN))) + (is (js/isNaN (m/IEEE-remainder ##Inf 2.0))) + (is (js/isNaN (m/IEEE-remainder ##-Inf 2.0))) + (is (js/isNaN (m/IEEE-remainder 2 0.0))) + (is (= 1.0 (m/IEEE-remainder 5.0 4.0)))) + +(deftest test-ceil + (is (js/isNaN (m/ceil ##NaN))) + (is (= ##Inf (m/ceil ##Inf))) + (is (= ##-Inf (m/ceil ##-Inf))) + (is (= 4.0 (m/ceil m/PI)))) + +(deftest test-floor + (is (js/isNaN (m/floor ##NaN))) + (is (= ##Inf (m/floor ##Inf))) + (is (= ##-Inf (m/floor ##-Inf))) + (is (= 3.0 (m/floor m/PI)))) + +(deftest test-rint + (is (js/isNaN (m/rint ##NaN))) + (is (= ##Inf (m/rint ##Inf))) + (is (= ##-Inf (m/rint ##-Inf))) + (is (= 1.0 (m/rint 1.2))) + (is (neg-zero? (m/rint -0.01)))) + +(deftest test-atan2 + (is (js/isNaN (m/atan2 ##NaN 1.0))) + (is (js/isNaN (m/atan2 1.0 ##NaN))) + (is (pos-zero? (m/atan2 0.0 1.0))) + (is (neg-zero? (m/atan2 -0.0 1.0))) + (is (ulp= (m/atan2 0.0 -1.0) m/PI 2)) + (is (ulp= (m/atan2 -0.0 -1.0) (- m/PI) 2)) + (is (ulp= (* 2.0 (m/atan2 1.0 0.0)) m/PI 2)) + (is (ulp= (* -2.0 (m/atan2 -1.0 0.0)) m/PI 2)) + (is (ulp= (* 4.0 (m/atan2 ##Inf ##Inf)) m/PI 2)) + (is (ulp= (/ (* 4.0 (m/atan2 ##Inf ##-Inf)) 3.0) m/PI 2)) + (is (ulp= (* -4.0 (m/atan2 ##-Inf ##Inf)) m/PI 2)) + (is (ulp= (/ (* -4.0 (m/atan2 ##-Inf ##-Inf)) 3.0) m/PI 2))) + +(deftest test-pow + (is (= 1.0 (m/pow 4.0 0.0))) + (is (= 1.0 (m/pow 4.0 -0.0))) + (is (= 4.2 (m/pow 4.2 1.0))) + (is (js/isNaN (m/pow 4.2 ##NaN))) + (is (js/isNaN (m/pow ##NaN 2.0))) + (is (= ##Inf (m/pow 2.0 ##Inf))) + (is (= ##Inf (m/pow 0.5 ##-Inf))) + (is (= 0.0 (m/pow 2.0 ##-Inf))) + (is (= 0.0 (m/pow 0.5 ##Inf))) + (is (js/isNaN (m/pow 1.0 ##Inf))) + (is (pos-zero? (m/pow 0.0 1.5))) + (is (pos-zero? (m/pow ##Inf -2.0))) + (is (= ##Inf (m/pow 0.0 -2.0))) + (is (= ##Inf (m/pow ##Inf 2.0))) + (is (pos-zero? (m/pow -0.0 1.5))) + (is (pos-zero? (m/pow ##-Inf -1.5))) + (is (neg-zero? (m/pow -0.0 3.0))) + (is (neg-zero? (m/pow ##-Inf -3.0))) + (is (= ##Inf (m/pow -0.0 -1.5))) + (is (= ##Inf (m/pow ##-Inf 2.5))) + (is (= ##-Inf (m/pow -0.0 -3.0))) + (is (= ##-Inf (m/pow ##-Inf 3.0))) + (is (= 4.0 (m/pow -2.0 2.0))) + (is (= -8.0 (m/pow -2.0 3.0))) + (is (= 8.0 (m/pow 2.0 3.0)))) + +(deftest test-round + (is (= 0 (m/round ##NaN))) + (is (= js/Number.MIN_SAFE_INTEGER (m/round ##-Inf))) + (is (= js/Number.MAX_SAFE_INTEGER (m/round ##Inf))) + (is (= 4 (m/round 3.5)))) + +(deftest test-add-exact + (try + (m/add-exact js/Number.MAX_SAFE_INTEGER 1) + (is false) + (catch ExceptionInfo _ + (is true)))) + +(deftest test-subtract-exact + (try + (m/subtract-exact js/Number.MIN_SAFE_INTEGER 1) + (is false) + (catch ExceptionInfo _ + (is true)))) + +(deftest test-multiply-exact + (try + (m/multiply-exact js/Number.MAX_SAFE_INTEGER 2) + (is false) + (catch ExceptionInfo _ + (is true)))) + +(deftest test-increment-exact + (try + (m/increment-exact js/Number.MAX_SAFE_INTEGER) + (is false) + (catch ExceptionInfo _ + (is true)))) + +(deftest test-decrement-exact + (try + (m/decrement-exact js/Number.MIN_SAFE_INTEGER) + (is false) + (catch ExceptionInfo _ + (is true)))) + +(deftest test-negate-exact + (is (= js/Number.MIN_SAFE_INTEGER (m/negate-exact js/Number.MAX_SAFE_INTEGER))) + (is (= js/Number.MAX_SAFE_INTEGER (m/negate-exact js/Number.MIN_SAFE_INTEGER)))) + +(deftest test-floor-div + (is (= js/Number.MAX_SAFE_INTEGER (m/floor-div js/Number.MIN_SAFE_INTEGER -1))) + (is (= -1 (m/floor-div -2 5)))) + +(deftest test-floor-mod + (is (= 3 (m/floor-mod -2 5)))) + +(deftest test-ulp + (is (js/isNaN (m/ulp ##NaN))) + (is (= ##Inf (m/ulp ##Inf))) + (is (= ##Inf (m/ulp ##-Inf))) + (is (= js/Number.MIN_VALUE (m/ulp 0.0))) + (is (= (m/pow 2 971) (m/ulp js/Number.MAX_VALUE))) + (is (= (m/pow 2 971) (m/ulp (- js/Number.MAX_VALUE))))) + +(deftest test-signum + (is (js/isNaN (m/signum ##NaN))) + (is (zero? (m/signum 0.0))) + (is (zero? (m/signum -0.0))) + (is (= 1.0 (m/signum 42.0))) + (is (= -1.0 (m/signum -42.0)))) + +(deftest test-sinh + (is (js/isNaN (m/sinh ##NaN))) + (is (= ##Inf (m/sinh ##Inf))) + (is (= ##-Inf (m/sinh ##-Inf))) + (is (= 0.0 (m/sinh 0.0)))) + +(deftest test-cosh + (is (js/isNaN (m/cosh ##NaN))) + (is (= ##Inf (m/cosh ##Inf))) + (is (= ##Inf (m/cosh ##-Inf))) + (is (= 1.0 (m/cosh 0.0)))) + +(deftest test-tanh + (is (js/isNaN (m/tanh ##NaN))) + (is (= 1.0 (m/tanh ##Inf))) + (is (= -1.0 (m/tanh ##-Inf))) + (is (= 0.0 (m/tanh 0.0)))) + +(deftest test-hypot + (is (= ##Inf (m/hypot 1.0 ##Inf))) + (is (= ##Inf (m/hypot ##Inf 1.0))) + (is (js/isNaN (m/hypot ##NaN 1.0))) + (is (js/isNaN (m/hypot 1.0 ##NaN))) + (is (= 13.0 (m/hypot 5.0 12.0)))) + +(deftest test-expm1 + (is (js/isNaN (m/expm1 ##NaN))) + (is (= ##Inf (m/expm1 ##Inf))) + (is (= -1.0 (m/expm1 ##-Inf))) + (is (= 0.0 (m/expm1 0.0)))) + +(deftest test-log1p + (is (js/isNaN (m/log1p ##NaN))) + (is (= ##Inf (m/log1p ##Inf))) + (is (= ##-Inf (m/log1p -1.0))) + (is (pos-zero? (m/log1p 0.0))) + (is (neg-zero? (m/log1p -0.0)))) + +(deftest test-copy-sign + (is (= 1.0 (m/copy-sign 1.0 42.0))) + (is (= -1.0 (m/copy-sign 1.0 -42.0))) + (is (= -1.0 (m/copy-sign 1.0 ##-Inf)))) + +(deftest test-get-exponent + (is (= (inc @#'cljs.math/EXP-MAX) (m/get-exponent ##NaN))) + (is (= (inc @#'cljs.math/EXP-MAX) (m/get-exponent ##Inf))) + (is (= (inc @#'cljs.math/EXP-MAX) (m/get-exponent ##-Inf))) + (is (= (dec @#'cljs.math/EXP-MIN) (m/get-exponent 0.0))) + (is (= 0 (m/get-exponent 1.0))) + (is (= 13 (m/get-exponent 12345.678)))) + +(deftest test-next-after + (is (js/isNaN (m/next-after ##NaN 1))) + (is (js/isNaN (m/next-after 1 ##NaN))) + (is (pos-zero? (m/next-after 0.0 0.0))) + (is (neg-zero? (m/next-after -0.0 -0.0))) + (is (= js/Number.MAX_VALUE (m/next-after ##Inf 1.0))) + (is (pos-zero? (m/next-after js/Number.MIN_VALUE -1.0)))) + +(deftest test-next-up + (is (js/isNaN (m/next-up ##NaN))) + (is (= ##Inf (m/next-up ##Inf))) + (is (= js/Number.MIN_VALUE (m/next-up 0.0)))) + +(deftest test-next-down + (is (js/isNaN (m/next-down ##NaN))) + (is (= ##-Inf (m/next-down ##-Inf))) + (is (= (- js/Number.MIN_VALUE) (m/next-down 0.0)))) + +(deftest test-scalb + (is (js/isNaN (m/scalb ##NaN 1))) + (is (= ##Inf (m/scalb ##Inf 1))) + (is (= ##-Inf (m/scalb ##-Inf 1))) + (is (pos-zero? (m/scalb 0.0 2))) + (is (neg-zero? (m/scalb -0.0 2))) + (is (= 32.0 (m/scalb 2.0 4)))) diff --git a/src/test/cljs/clojure/set_test.cljs b/src/test/cljs/clojure/set_test.cljs new file mode 100644 index 0000000000..ad81ce743f --- /dev/null +++ b/src/test/cljs/clojure/set_test.cljs @@ -0,0 +1,217 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns clojure.set-test + (:require [clojure.test :refer [are deftest is]] + [clojure.set :as set])) + +(deftest test-union + (are [x y] (= x y) + (set/union) #{} + + ; identity + (set/union #{}) #{} + (set/union #{1}) #{1} + (set/union #{1 2 3}) #{1 2 3} + + ; 2 sets, at least one is empty + (set/union #{} #{}) #{} + (set/union #{} #{1}) #{1} + (set/union #{} #{1 2 3}) #{1 2 3} + (set/union #{1} #{}) #{1} + (set/union #{1 2 3} #{}) #{1 2 3} + + ; 2 sets + (set/union #{1} #{2}) #{1 2} + (set/union #{1} #{1 2}) #{1 2} + (set/union #{2} #{1 2}) #{1 2} + (set/union #{1 2} #{3}) #{1 2 3} + (set/union #{1 2} #{2 3}) #{1 2 3} + + ; 3 sets, some are empty + (set/union #{} #{} #{}) #{} + (set/union #{1} #{} #{}) #{1} + (set/union #{} #{1} #{}) #{1} + (set/union #{} #{} #{1}) #{1} + (set/union #{1 2} #{2 3} #{}) #{1 2 3} + + ; 3 sets + (set/union #{1 2} #{3 4} #{5 6}) #{1 2 3 4 5 6} + (set/union #{1 2} #{2 3} #{1 3 4}) #{1 2 3 4} + + ; different data types + (set/union #{1 2} #{:a :b} #{nil} #{false true} #{\c "abc"} #{[] [1 2]} + #{{} {:a 1}} #{#{} #{1 2}}) + #{1 2 :a :b nil false true \c "abc" [] [1 2] {} {:a 1} #{} #{1 2}} + + ; different types of sets + (set/union (hash-set) (hash-set 1 2) (hash-set 2 3)) + (hash-set 1 2 3) + (set/union (sorted-set) (sorted-set 1 2) (sorted-set 2 3)) + (sorted-set 1 2 3) + (set/union (hash-set) (hash-set 1 2) (hash-set 2 3) + (sorted-set) (sorted-set 4 5) (sorted-set 5 6)) + (hash-set 1 2 3 4 5 6) ; also equals (sorted-set 1 2 3 4 5 6) + )) + +(deftest test-intersection + (are [x y] (= x y) + ; identity + (set/intersection #{}) #{} + (set/intersection #{1}) #{1} + (set/intersection #{1 2 3}) #{1 2 3} + + ; 2 sets, at least one is empty + (set/intersection #{} #{}) #{} + (set/intersection #{} #{1}) #{} + (set/intersection #{} #{1 2 3}) #{} + (set/intersection #{1} #{}) #{} + (set/intersection #{1 2 3} #{}) #{} + + ; 2 sets + (set/intersection #{1 2} #{1 2}) #{1 2} + (set/intersection #{1 2} #{3 4}) #{} + (set/intersection #{1 2} #{1}) #{1} + (set/intersection #{1 2} #{2}) #{2} + (set/intersection #{1 2 4} #{2 3 4 5}) #{2 4} + + ; 3 sets, some are empty + (set/intersection #{} #{} #{}) #{} + (set/intersection #{1} #{} #{}) #{} + (set/intersection #{1} #{1} #{}) #{} + (set/intersection #{1} #{} #{1}) #{} + (set/intersection #{1 2} #{2 3} #{}) #{} + + ; 3 sets + (set/intersection #{1 2} #{2 3} #{5 2}) #{2} + (set/intersection #{1 2 3} #{1 3 4} #{1 3}) #{1 3} + (set/intersection #{1 2 3} #{3 4 5} #{8 2 3}) #{3} + + ; different types of sets + (set/intersection (hash-set 1 2) (hash-set 2 3)) #{2} + (set/intersection (sorted-set 1 2) (sorted-set 2 3)) #{2} + (set/intersection + (hash-set 1 2) (hash-set 2 3) + (sorted-set 1 2) (sorted-set 2 3)) #{2} )) + +(deftest test-difference + (are [x y] (= x y) + ; identity + (set/difference #{}) #{} + (set/difference #{1}) #{1} + (set/difference #{1 2 3}) #{1 2 3} + + ; 2 sets + (set/difference #{1 2} #{1 2}) #{} + (set/difference #{1 2} #{3 4}) #{1 2} + (set/difference #{1 2} #{1}) #{2} + (set/difference #{1 2} #{2}) #{1} + (set/difference #{1 2 4} #{2 3 4 5}) #{1} + + ; 3 sets + (set/difference #{1 2} #{2 3} #{5 2}) #{1} + (set/difference #{1 2 3} #{1 3 4} #{1 3}) #{2} + (set/difference #{1 2 3} #{3 4 5} #{8 2 3}) #{1} )) + +(deftest test-select + (are [x y] (= x y) + (set/select integer? #{}) #{} + (set/select integer? #{1 2}) #{1 2} + (set/select integer? #{1 2 :a :b :c}) #{1 2} + (set/select integer? #{:a :b :c}) #{}) ) + +(def compositions + #{{:name "Art of the Fugue" :composer "J. S. Bach"} + {:name "Musical Offering" :composer "J. S. Bach"} + {:name "Requiem" :composer "Giuseppe Verdi"} + {:name "Requiem" :composer "W. A. Mozart"}}) + +(deftest test-project + (are [x y] (= x y) + (set/project compositions [:name]) #{{:name "Art of the Fugue"} + {:name "Requiem"} + {:name "Musical Offering"}} + (set/project compositions [:composer]) #{{:composer "W. A. Mozart"} + {:composer "Giuseppe Verdi"} + {:composer "J. S. Bach"}} + (set/project compositions [:year]) #{{}} + (set/project #{{}} [:name]) #{{}} )) + +(deftest test-rename + (are [x y] (= x y) + (set/rename compositions {:name :title}) #{{:title "Art of the Fugue" :composer "J. S. Bach"} + {:title "Musical Offering" :composer "J. S. Bach"} + {:title "Requiem" :composer "Giuseppe Verdi"} + {:title "Requiem" :composer "W. A. Mozart"}} + (set/rename compositions {:year :decade}) #{{:name "Art of the Fugue" :composer "J. S. Bach"} + {:name "Musical Offering" :composer "J. S. Bach"} + {:name "Requiem" :composer "Giuseppe Verdi"} + {:name "Requiem" :composer "W. A. Mozart"}} + (set/rename #{{}} {:year :decade}) #{{}})) + +(deftest test-rename-keys + (are [x y] (= x y) + (set/rename-keys {:a "one" :b "two"} {:a :z}) {:z "one" :b "two"} + (set/rename-keys {:a "one" :b "two"} {:a :z :c :y}) {:z "one" :b "two"} + (set/rename-keys {:a "one" :b "two" :c "three"} {:a :b :b :a}) {:a "two" :b "one" :c "three"})) + +(deftest test-index + (are [x y] (= x y) + (set/index #{{:c 2} {:b 1} {:a 1 :b 2}} [:b]) {{:b 2} #{{:a 1 :b 2}}, {:b 1} #{{:b 1}} {} #{{:c 2}}} + )) + +(deftest test-join + (are [x y] (= x y) + (set/join compositions compositions) compositions + (set/join compositions #{{:name "Art of the Fugue" :genre "Classical"}}) + #{{:name "Art of the Fugue" :composer "J. S. Bach" :genre "Classical"}} + )) + +(deftest test-map-invert + (are [x y] (= x y) + (set/map-invert {:a "one" :b "two"}) {"one" :a "two" :b})) + +(deftest test-subset? + (are [sub super] (set/subset? sub super) + #{} #{} + #{} #{1} + #{1} #{1} + #{1 2} #{1 2} + #{1 2} #{1 2 42} + #{false} #{false} + #{nil} #{nil} + #{nil} #{nil false} + #{1 2 nil} #{1 2 nil 4}) + (are [notsub super] (not (set/subset? notsub super)) + #{1} #{} + #{2} #{1} + #{1 3} #{1} + #{nil} #{false} + #{false} #{nil} + #{false nil} #{nil} + #{1 2 nil} #{1 2})) + +(deftest test-superset? + (are [super sub] (set/superset? super sub) + #{} #{} + #{1} #{} + #{1} #{1} + #{1 2} #{1 2} + #{1 2 42} #{1 2} + #{false} #{false} + #{nil} #{nil} + #{false nil} #{false} + #{1 2 4 nil false} #{1 2 nil}) + (are [notsuper sub] (not (set/superset? notsuper sub)) + #{} #{1} + #{2} #{1} + #{1} #{1 3} + #{nil} #{false} + #{false} #{nil} + #{nil} #{false nil} + #{nil 2 3} #{false nil 2 3})) \ No newline at end of file diff --git a/src/test/cljs/clojure/string_test.cljs b/src/test/cljs/clojure/string_test.cljs new file mode 100644 index 0000000000..538d2636bb --- /dev/null +++ b/src/test/cljs/clojure/string_test.cljs @@ -0,0 +1,197 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns clojure.string-test + (:require [cljs.test :as test + :refer-macros [deftest is testing]] + [clojure.test.check :as tc] + [clojure.test.check.clojure-test :refer-macros [defspec]] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as prop :include-macros true] + [clojure.string :as s])) + +(deftest test-api + (testing "Testing string reverse" + (is (= "" (s/reverse ""))) + (is (= "tab" (s/reverse "bat"))) + (is (= "c\uD834\uDD1Ea" (s/reverse "a\uD834\uDD1Ec"))) ;; U+1D11E MUSICAL SYMBOL G CLEF + ) + + (testing "Testing string replace" + (is (= "faabar" (s/replace "foobar" \o \a))) + (is (= "barbarbar" (s/replace "foobarfoo" "foo" "bar"))) + (is (= "foobarfoo" (s/replace "foobarfoo" #"ooo" s/upper-case))) + (is (= "FOObarFOO" (s/replace "foobarfoo" #"foo" s/upper-case))) + (is (= "barbar)foo" (s/replace "foo(bar)foo" "foo(" "bar"))) + (is (= "FOO-ObarFOO-O" + (s/replace "foobarfoo" #"f(o)o" (fn [[m g1]] (s/upper-case (str m "-" g1)))))) + (is (= "faabarfaa" (s/replace "FOObarfoo" #"(?i)foo" "faa"))) + (is (= "aaa\nccc" (s/replace "aaa\nbbb" #"(?m)^bbb" "ccc")))) + + (testing "Testing string join" + (is (= "" (s/join nil))) + (is (= "" (s/join []))) + (is (= "1" (s/join [1]))) + (is (= "12" (s/join [1 2]))) + (is (= "1,2,3" (s/join \, [1 2 3]))) + (is (= "" (s/join \, []))) + (is (= "1 and-a 2 and-a 3" (s/join " and-a " [1 2 3])))) + + (testing "Testing string capitalize" + (is (= "FOOBAR" (s/upper-case "Foobar"))) + (is (= "foobar" (s/lower-case "FooBar"))) + (is (= "Foobar" (s/capitalize "foobar"))) + (is (= "Foobar" (s/capitalize "FOOBAR")))) + + (testing "Testing string split" + (is (= ["a" "b"] (s/split "a-b" #"-"))) + (is (= ["a" "b" "c"] (s/split "a-b-c" #"-" -1))) + (is (= ["a" "b" "c"] (s/split "a-b-c" #"-" 0))) + (is (= ["a-b-c"] (s/split "a-b-c" #"-" 1))) + (is (= ["a" "b-c"] (s/split "a-b-c" #"-" 2))) + (is (= ["a" "b" "c"] (s/split "a-b-c" #"-" 3))) + (is (= ["a" "b" "c"] (s/split "a-b-c" #"-" 4))) + (is (vector? (s/split "abc" #"-"))) + (is (= ["a-b-c"] (s/split "a-b-c" #"x" 2))) + (is (= ["" "a" "b" "c" ""] (s/split "abc" (re-pattern "") 5))) + (is (= ["a"] (s/split "ab" #"b"))) + (is (= [] (s/split "ab" #"ab")))) + + (testing "Testing string split lines" + (let [result (s/split-lines "one\ntwo\r\nthree")] + (is (= ["one" "two" "three"] result)) + (is (vector? result))) + (is (= (list "foo") (s/split-lines "foo")))) + + (testing "Testing string blank?" + (is (s/blank? nil)) + (is (s/blank? "")) + (is (s/blank? " ")) + (is (s/blank? " \t \n \r ")) + (is (not (s/blank? " foo ")))) + + (testing "Testing string escape" + (is (= "<foo&bar>" + (s/escape "" {\& "&" \< "<" \> ">"}))) + (is (= " \\\"foo\\\" " + (s/escape " \"foo\" " {\" "\\\""}))) + (is (= "faabor" + (s/escape "foobar" {\a \o, \o \a}))) + (is (= "aaa" + (s/escape "foo" (fn [c] \a))))) + + (testing "Testing string replace-first" + (is (= "barbarfoo" (s/replace-first "foobarfoo" "foo" "bar"))) + (is (= "barbarfoo" (s/replace-first "foobarfoo" #"foo" "bar"))) + (is (= "z.ology" (s/replace-first "zoology" \o \.))) + (is (= "FOObarfoo" (s/replace-first "foobarfoo" #"foo" s/upper-case)))) + + (testing "Testing string trim" + (is (= "foo " (s/triml " foo "))) + (is (= "" (s/triml " "))) + (is (= " foo" (s/trimr " foo "))) + (is (= "" (s/trimr " "))) + (is (= "foo" (s/trim " foo \r\n")))) + + (testing "Testing string trim-newline" + (is (= "foo" (s/trim-newline "foo\n"))) + (is (= "foo" (s/trim-newline "foo\r\n"))) + (is (= "foo" (s/trim-newline "foo"))) + (is (= "foo\r " (s/trim-newline "foo\r "))) + (is (= "" (s/trim-newline "")))) + + (testing "Testing string trim-newline" + (is (= "foo" (s/trim-newline "foo\n"))) + (is (= "foo" (s/trim-newline "foo\r\n"))) + (is (= "foo" (s/trim-newline "foo"))) + (is (= "foo\r " (s/trim-newline "foo\r "))) + (is (= "" (s/trim-newline "")))) + + (testing "Testing string index-of" + (let [sb "tacos"] + (is (= 2 (s/index-of sb "c"))) + (is (= 2 (s/index-of sb \c))) + (is (= 1 (s/index-of sb "ac"))) + (is (= 3 (s/index-of sb "o" 2))) + (is (= 3 (s/index-of sb \o 2))) + (is (= 3 (s/index-of sb "o" -100))) + (is (= nil (s/index-of sb "z"))) + (is (= nil (s/index-of sb \z))) + (is (= nil (s/index-of sb "z" 2))) + (is (= nil (s/index-of sb \z 2))) + (is (= nil (s/index-of sb "z" 100)) + (is (= nil (s/index-of sb "z" -10)))))) + + (testing "Testing string last-index-of" + (let [sb "banana"] + (is (= 4 (s/last-index-of sb "n"))) + (is (= 4 (s/last-index-of sb \n))) + (is (= 3 (s/last-index-of sb "an"))) + (is (= 4 (s/last-index-of sb "n" ))) + (is (= 4 (s/last-index-of sb "n" 5))) + (is (= 4 (s/last-index-of sb \n 5))) + (is (= 4 (s/last-index-of sb "n" 500))) + (is (= nil (s/last-index-of sb "z"))) + (is (= nil (s/last-index-of sb "z" 1))) + (is (= nil (s/last-index-of sb \z 1))) + (is (= nil (s/last-index-of sb "z" 100))) + (is (= nil (s/last-index-of sb "z" -10))))) + + (testing "Testing string starts-with?" + (is (s/starts-with? "clojure west" "clojure")) + (is (not (s/starts-with? "conj" "clojure")))) + + (testing "Testing string ends-with?" + (is (s/ends-with? "Clojure West" "West")) + (is (not (s/ends-with? "Conj" "West")))) + + (testing "Testing string includes?" + (let [sb "Clojure Applied Book"] + (is (s/includes? sb "Applied")) + (is (not (s/includes? sb "Living")))))) + +(defspec test-cljs-2300 + ;; The reference implementation is the implementation prior to the change. + ;; Since some JavaScript implementations fail to properly change case for + ;; some characters (for example, the upper case of "ß" is "SS"), we limit + ;; this test to strings comprising only printable ASCII characters. + (let [ref-impl (fn [s] + (if (< (count s) 2) + (s/upper-case s) + (str (s/upper-case (subs s 0 1)) + (s/lower-case (subs s 1))))) + char-codes->string (fn [xs] + (apply (.-fromCharCode js/String) xs))] + (prop/for-all [s (gen/fmap char-codes->string + (gen/not-empty (gen/vector (gen/choose 0x20 0x7E))))] + (= (ref-impl s) (s/capitalize s))))) + +(comment + +(deftest char-sequence-handling + (are [result f args] (let [[^CharSequence s & more] args] + (= result (apply f (StringBuffer. s) more))) + "paz" s/reverse ["zap"] + "foo:bar" s/replace ["foo-bar" \- \:] + "ABC" s/replace ["abc" #"\w" s/upper-case] + "faa" s/replace ["foo" #"o" (StringBuffer. "a")] + "baz::quux" s/replace-first ["baz--quux" #"--" "::"] + "baz::quux" s/replace-first ["baz--quux" (StringBuffer. "--") (StringBuffer. "::")] + "zim-zam" s/replace-first ["zim zam" #" " (StringBuffer. "-")] + "Pow" s/capitalize ["POW"] + "BOOM" s/upper-case ["boom"] + "whimper" s/lower-case ["whimPER"] + ["foo" "bar"] s/split ["foo-bar" #"-"] + "calvino" s/trim [" calvino "] + "calvino " s/triml [" calvino "] + " calvino" s/trimr [" calvino "] + "the end" s/trim-newline ["the end\r\n\r\r\n"] + true s/blank? [" "] + ["a" "b"] s/split-lines ["a\nb"] + "fa la la" s/escape ["fo lo lo" {\o \a}])) +) diff --git a/src/test/cljs/clojure/walk_test.cljs b/src/test/cljs/clojure/walk_test.cljs new file mode 100644 index 0000000000..11d41312bc --- /dev/null +++ b/src/test/cljs/clojure/walk_test.cljs @@ -0,0 +1,40 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns clojure.walk-test + (:require [cljs.test :as test + :refer-macros [deftest is testing]] + [clojure.walk :as w])) + +(defrecord Rec1 [a]) + +(defn inc-leaf [x] + (if (number? x) + (inc x) + x)) + +(deftest test-api + (testing "Test walking" + (is (= [2 {1 "1", :two 2}] (w/postwalk inc-leaf [1 {0 "1", :two 1}]))) + (is (= [(Rec1. 2)] (w/postwalk inc-leaf [(Rec1. 1)]))) + + (is (= (map->Rec1 {:a 1, ":a" 1}) + (w/prewalk #(if (keyword? %) (str %) %) (Rec1. 1))) + "Mirror Clojure behavior"))) + +(deftest test-preserves-meta + (testing "Test preserves meta" + (is (= (-> (w/prewalk identity [1 (with-meta [1 2] {:foo 3})]) + (nth 1) meta) + {:foo 3})) + (is (= (-> (w/postwalk identity [1 (with-meta [1 2] {:foo 3})]) + (nth 1) meta) + {:foo 3})))) + +(deftest test-map-entry + (is (= [:a 2] (clojure.walk/postwalk #(cond-> % (number? %) inc) (->MapEntry :a 1 nil))))) diff --git a/src/test/cljs/data_readers.cljc b/src/test/cljs/data_readers.cljc new file mode 100644 index 0000000000..57c306152f --- /dev/null +++ b/src/test/cljs/data_readers.cljc @@ -0,0 +1,13 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +{cljs/tag clojure.core/identity + cljs/inc clojure.core/inc + cljs/union clojure.set/union + test/custom-identity data-readers-test.core/custom-identity + test/custom-form #?(:cljs data-readers-test.core/custom-form-cljs :clj clojure.core/identity)} diff --git a/src/test/cljs/data_readers_test/core.cljc b/src/test/cljs/data_readers_test/core.cljc new file mode 100644 index 0000000000..c45fef7fea --- /dev/null +++ b/src/test/cljs/data_readers_test/core.cljc @@ -0,0 +1,25 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns data-readers-test.core) + +(def custom-identity identity) + +(assert (= 1 #test/custom-identity 1)) + +(defn custom-form-cljs + "a clojure and clojurescript function - in both cases targeting only cljs. + + returns a clojurescript form (from :clj branch, when compiling) + and executes js from :cljs branch when using cljs.reader/read" + [x] + #?(:clj `(js/Array.of ~x) + :cljs (js/Array.of x))) + +#?(:cljs + (def result #test/custom-form"foo")) diff --git a/src/test/cljs/data_readers_test/records.cljc b/src/test/cljs/data_readers_test/records.cljc new file mode 100644 index 0000000000..e75572200f --- /dev/null +++ b/src/test/cljs/data_readers_test/records.cljc @@ -0,0 +1,34 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns data-readers-test.records + #?(:cljs (:require-macros [data-readers-test.records]))) + +(defrecord Foo [a b]) + +(assert (= (Foo. 1 2) #data_readers_test.records.Foo{:a 1 :b 2})) +(assert (= (Foo. 1 2) + '#data_readers_test.records.Foo{:a 1 :b 2})) +(assert (= (Foo. 1 2) + (second ''#data_readers_test.records.Foo{:a 1 :b 2}))) +(assert (= (Foo. 'a 'b) + (let [a 1 + b 2] + #data_readers_test.records.Foo{:a a :b b})) + (pr-str + (let [a 1 + b 2] + #data_readers_test.records.Foo{:a a :b b}))) +(assert (= (Foo. 'a 'b) + (let [a 1 + b 2] + '#data_readers_test.records.Foo{:a a :b b})) + (pr-str + (let [a 1 + b 2] + '#data_readers_test.records.Foo{:a a :b b}))) diff --git a/src/test/cljs/deps.cljs b/src/test/cljs/deps.cljs new file mode 100644 index 0000000000..21cf75ab09 --- /dev/null +++ b/src/test/cljs/deps.cljs @@ -0,0 +1,3 @@ +{:externs ["externs.js"] + :foreign-libs [{:file "calculator_global.js" + :provides ["thirdparty.calculator"]}]} diff --git a/src/test/cljs/es6_default_hello.js b/src/test/cljs/es6_default_hello.js new file mode 100644 index 0000000000..9cc911553a --- /dev/null +++ b/src/test/cljs/es6_default_hello.js @@ -0,0 +1,3 @@ +export default function sayHello () { + return "Hello, world!"; +}; diff --git a/src/test/cljs/es6_dep.js b/src/test/cljs/es6_dep.js new file mode 100644 index 0000000000..da2836fa17 --- /dev/null +++ b/src/test/cljs/es6_dep.js @@ -0,0 +1,3 @@ +import {default as calc} from './calculator'; + +export var calculator = calc; diff --git a/src/test/cljs/es6_hello.js b/src/test/cljs/es6_hello.js new file mode 100644 index 0000000000..cabd03a70d --- /dev/null +++ b/src/test/cljs/es6_hello.js @@ -0,0 +1,3 @@ +export var sayHello = function() { + console.log("Hello, world!"); +}; diff --git a/src/test/cljs/externs.js b/src/test/cljs/externs.js new file mode 100644 index 0000000000..f7224fd6db --- /dev/null +++ b/src/test/cljs/externs.js @@ -0,0 +1,4 @@ +var nth = function(array, n){}; +var Calculator = { + add: function(a, b) {} +}; diff --git a/src/test/cljs/foo/ns_shadow_test.cljs b/src/test/cljs/foo/ns_shadow_test.cljs new file mode 100644 index 0000000000..1d3d2200b0 --- /dev/null +++ b/src/test/cljs/foo/ns_shadow_test.cljs @@ -0,0 +1,29 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns foo.ns-shadow-test + (:require [cljs.test :refer-macros [deftest is]] + baz)) + +(defn bar [] 1) + +(defn quux [foo] + (+ (foo.ns-shadow-test/bar) foo)) + +(defn id [x] x) + +(defn foo [] (id 42)) + +(defn baz + ([] (baz 2)) + ([x] (quux 2))) + +(deftest test-shadow + (is (= (quux 2) 3)) + (is (= (foo) 42)) + (is (= (baz) 3))) diff --git a/src/test/cljs/hello.cljs b/src/test/cljs/hello.cljs new file mode 100644 index 0000000000..b7b12f75e1 --- /dev/null +++ b/src/test/cljs/hello.cljs @@ -0,0 +1,3 @@ +(ns hello) +(defn ^:export greet [n] + (str "Hello " n)) diff --git a/src/test/cljs/js_libs/tabby.js b/src/test/cljs/js_libs/tabby.js new file mode 100644 index 0000000000..b43ff9b575 --- /dev/null +++ b/src/test/cljs/js_libs/tabby.js @@ -0,0 +1,5 @@ +goog.provide("tabby"); + +tabby.hello = function() { + return "hello there from tabby"; +}; diff --git a/src/test/cljs/lite_test_runner.cljs b/src/test/cljs/lite_test_runner.cljs new file mode 100644 index 0000000000..c9f58d677f --- /dev/null +++ b/src/test/cljs/lite_test_runner.cljs @@ -0,0 +1,130 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns lite-test-runner + (:require [cljs.qualified-method-test] + [cljs.proxy-test] + [cljs.test :refer-macros [run-tests]] + [cljs.apply-test] + [cljs.primitives-test] + [cljs.destructuring-test] + [cljs.new-new-test] + [cljs.printing-test] + [cljs.seqs-test] + [cljs.collections-test] + [cljs.hashing-test] + [cljs.core-test] + ;; [cljs.chunked-seq] ;; doesn't exist in :lite-mode + [cljs.interop-test] + [cljs.iterator-test] + [cljs.reader-test] + [cljs.binding-test] + [cljs.parse-test] + [cljs.ns-test] + [clojure.set-test] + [clojure.string-test] + [clojure.data-test] + [clojure.datafy-test] + [clojure.edn-test] + [clojure.walk-test] + [clojure.math-test] + [cljs.macro-test] + [cljs.letfn-test] + [foo.ns-shadow-test] + [cljs.top-level] + [cljs.reducers-test] + [cljs.keyword-test] + [cljs.import-test] + [cljs.ns-test.foo] + [cljs.syntax-quote-test] + [cljs.pprint] + [cljs.pprint-test] + [cljs.spec-test] + [cljs.specials-test] + [cljs.spec.test-test] + [cljs.clojure-alias-test] + ;; [cljs.hash-map-test] + ;; [cljs.map-entry-test] + [cljs.metadata-test] + [cljs.npm-deps-test] + [cljs.other-functions-test] + [cljs.predicates-test] + [cljs.tagged-literals-test] + [cljs.test-test] + [static.core-test] + [cljs.recur-test] + [cljs.array-access-test] + [cljs.inference-test] + [cljs.walk-test] + [cljs.repl-test] + [cljs.lite-collections-test] + [cljs.extend-to-native-test] + [cljs.var-test])) + +(set! *print-newline* false) + +;; When testing Windows we default to Node.js +(if (exists? js/print) + (set-print-fn! js/print) + (enable-console-print!)) + +(run-tests + 'cljs.qualified-method-test + 'cljs.proxy-test + 'cljs.apply-test + 'cljs.primitives-test + 'cljs.destructuring-test + 'cljs.printing-test + 'cljs.new-new-test + 'cljs.seqs-test + 'cljs.collections-test + 'cljs.hashing-test + 'cljs.core-test + 'cljs.interop-test ;; ES6 stuff + 'cljs.iterator-test + 'cljs.reader-test + 'cljs.binding-test + 'cljs.parse-test + 'cljs.ns-test + 'clojure.set-test + 'clojure.string-test + 'clojure.data-test + 'clojure.datafy-test + 'clojure.edn-test + 'clojure.walk-test + 'clojure.math-test + 'cljs.macro-test + 'cljs.letfn-test + 'foo.ns-shadow-test + 'cljs.top-level + 'cljs.reducers-test + 'cljs.keyword-test + 'cljs.import-test + 'cljs.ns-test.foo + 'cljs.syntax-quote-test + 'cljs.pprint + 'cljs.pprint-test + 'cljs.spec-test + 'cljs.specials-test + 'cljs.spec.test-test + 'cljs.clojure-alias-test + 'cljs.metadata-test + 'cljs.npm-deps-test + 'cljs.other-functions-test + 'cljs.predicates-test + 'cljs.tagged-literals-test + 'cljs.test-test + 'static.core-test + 'cljs.recur-test + 'cljs.array-access-test + 'cljs.inference-test + 'cljs.walk-test + 'cljs.repl-test + 'cljs.lite-collections-test + 'cljs.extend-to-native-test + 'cljs.var-test) diff --git a/src/test/cljs/module_test/main.cljs b/src/test/cljs/module_test/main.cljs new file mode 100644 index 0000000000..e1c03f9d6e --- /dev/null +++ b/src/test/cljs/module_test/main.cljs @@ -0,0 +1,12 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns module-test.main) + +(defn ^:export main [] + (println "Loading modules A and B ...")) diff --git a/src/test/cljs/module_test/modules/a.cljs b/src/test/cljs/module_test/modules/a.cljs new file mode 100644 index 0000000000..47cdd95fb1 --- /dev/null +++ b/src/test/cljs/module_test/modules/a.cljs @@ -0,0 +1,12 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns module-test.modules.a) + +(defn ^:export main [] + (println "Module A loaded.")) diff --git a/src/test/cljs/module_test/modules/b.cljs b/src/test/cljs/module_test/modules/b.cljs new file mode 100644 index 0000000000..1fe826668d --- /dev/null +++ b/src/test/cljs/module_test/modules/b.cljs @@ -0,0 +1,12 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns module-test.modules.b) + +(defn ^:export main [] + (println "Module B loaded.")) diff --git a/src/test/cljs/preloads_test/core.cljs b/src/test/cljs/preloads_test/core.cljs new file mode 100644 index 0000000000..cc50ecb117 --- /dev/null +++ b/src/test/cljs/preloads_test/core.cljs @@ -0,0 +1,11 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns preloads-test.core) + +(def foo :foo) diff --git a/src/test/cljs/preloads_test/preload.cljs b/src/test/cljs/preloads_test/preload.cljs new file mode 100644 index 0000000000..7983a61b41 --- /dev/null +++ b/src/test/cljs/preloads_test/preload.cljs @@ -0,0 +1,11 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns preloads-test.preload) + +(def preload-var :foo) diff --git a/src/test/cljs/react-min.js b/src/test/cljs/react-min.js new file mode 100644 index 0000000000..9d2d11fe39 --- /dev/null +++ b/src/test/cljs/react-min.js @@ -0,0 +1,20 @@ +// React minified. +(function(f){ +if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()} +else if(typeof define==="function"&&define.amd){define([],f)} +else{var g;if(typeof window!=="undefined"){g=window} + else if(typeof global!=="undefined"){g=global} + else if(typeof self!=="undefined"){g=self} + else{g=this}g.React = f()} +})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o + +

    Module A

    + + + + + + diff --git a/src/test/cljs_build/code-split/repl.clj b/src/test/cljs_build/code-split/repl.clj new file mode 100644 index 0000000000..bc751f69dc --- /dev/null +++ b/src/test/cljs_build/code-split/repl.clj @@ -0,0 +1,26 @@ +;; Instructions: + +;; ./script/uberjar +;; cd src/test/cljs_build/code-split +;; java -cp ../../../../target/cljs.jar:src clojure.main repl.clj +;; chromium http://localhost:9000/index.html + +(require '[cljs.repl :as r]) +(require '[cljs.build.api :as b]) +(require '[cljs.repl.browser :as rb]) + +(def opts + {:output-dir "out" + :asset-path "/out" + :optimizations :none + :modules {:a {:entries '#{code.split.a} + :output-to "out/a.js"} + :b {:entries '#{code.split.b} + :output-to "out/b.js"} + :c {:entries '#{code.split.c} + :output-to "out/c.js"}} + :browser-repl true + :verbose true}) + +(b/build "src" opts) +(r/repl* (rb/repl-env) (dissoc opts :modules)) diff --git a/src/test/cljs_build/code-split/src/code/split/a.cljs b/src/test/cljs_build/code-split/src/code/split/a.cljs new file mode 100644 index 0000000000..3c15977778 --- /dev/null +++ b/src/test/cljs_build/code-split/src/code/split/a.cljs @@ -0,0 +1,31 @@ +(ns code.split.a + (:require [cljs.loader :as loader] + [clojure.pprint :refer [pprint]] + [goog.dom :as gdom] + [goog.events :as events]) + (:import [goog.debug Console] + [goog.events EventType])) + +(def loader + "The module loader." + (.getLoader loader/*module-manager*)) + +;; Enable logging, to see debug messages. +(.setCapturing (Console.) true) + +(defn print-modules [] + (println "Module Infos:") + (pprint loader/module-infos) + (println "Module URIs:") + (pprint loader/module-uris)) + +(events/listen (gdom/getElement "button-b") EventType.CLICK + (fn [e] (loader/load :b #((resolve 'code.split.b/hello))))) + +(events/listen (gdom/getElement "button-c") EventType.CLICK + (fn [e] (loader/load :c #((resolve 'code.split.c/hello))))) + +(enable-console-print!) +(print-modules) + +(loader/set-loaded! :a) diff --git a/src/test/cljs_build/code-split/src/code/split/b.cljs b/src/test/cljs_build/code-split/src/code/split/b.cljs new file mode 100644 index 0000000000..800ca25e08 --- /dev/null +++ b/src/test/cljs_build/code-split/src/code/split/b.cljs @@ -0,0 +1,9 @@ +(ns code.split.b + (:require [cljs.loader :as loader] + [code.split.d :as d])) + +(defn hello [] + (println "Hello from code.split.b.") + (d/hello)) + +(loader/set-loaded! :b) diff --git a/src/test/cljs_build/code-split/src/code/split/c.cljs b/src/test/cljs_build/code-split/src/code/split/c.cljs new file mode 100644 index 0000000000..e70cd5be23 --- /dev/null +++ b/src/test/cljs_build/code-split/src/code/split/c.cljs @@ -0,0 +1,11 @@ +(ns code.split.c + (:require [cljs.loader :as loader] + [code.split.d :as d])) + +(enable-console-print!) + +(defn hello [] + (println "Hello from code.split.c.") + (d/hello)) + +(loader/set-loaded! :c) diff --git a/src/test/cljs_build/code-split/src/code/split/d.cljs b/src/test/cljs_build/code-split/src/code/split/d.cljs new file mode 100644 index 0000000000..fe481fdb71 --- /dev/null +++ b/src/test/cljs_build/code-split/src/code/split/d.cljs @@ -0,0 +1,4 @@ +(ns code.split.d) + +(defn hello [] + (println "Hello from code.split.d.")) diff --git a/src/test/cljs_build/emit_global_requires_test/core.cljs b/src/test/cljs_build/emit_global_requires_test/core.cljs new file mode 100644 index 0000000000..86205f7922 --- /dev/null +++ b/src/test/cljs_build/emit_global_requires_test/core.cljs @@ -0,0 +1,15 @@ +(ns emit-global-requires-test.core + (:require [react :refer [createElement]] + ["react-dom/server" :as ReactDOMServer] + ["@material-ui/core/styles" :as mui-styles] + ["@material-ui/core/styles/a" :as mui-styles-a])) + +(enable-console-print!) + +(println "ReactDOMServer exists:" ReactDOMServer + (.-renderToString ReactDOMServer)) + +(println "hi" (ReactDOMServer/renderToString (createElement "div" nil "Hello World!"))) + +(mui-styles/createMuiTheme #js {}) +(mui-styles-a/foo) diff --git a/src/test/cljs_build/emit_node_requires_test/core.cljs b/src/test/cljs_build/emit_node_requires_test/core.cljs new file mode 100644 index 0000000000..98ca70ae98 --- /dev/null +++ b/src/test/cljs_build/emit_node_requires_test/core.cljs @@ -0,0 +1,10 @@ +(ns emit-node-requires-test.core + (:require [react :refer [createElement]] + ["react-dom/server" :as ReactDOMServer])) + +(enable-console-print!) + +(println "ReactDOMServer exists:" ReactDOMServer + (.-renderToString ReactDOMServer)) + +(println "hi" (ReactDOMServer/renderToString (createElement "div" nil "Hello World!"))) diff --git a/src/test/cljs_build/emit_node_requires_test/native_modules.cljs b/src/test/cljs_build/emit_node_requires_test/native_modules.cljs new file mode 100644 index 0000000000..1d4a9e5732 --- /dev/null +++ b/src/test/cljs_build/emit_node_requires_test/native_modules.cljs @@ -0,0 +1,6 @@ +(ns emit-node-requires-test.native-modules + (:require [path :refer [isAbsolute]])) + +(enable-console-print!) + +(println (isAbsolute (path/resolve js/__filename))) diff --git a/src/test/cljs_build/firebase/core.cljs b/src/test/cljs_build/firebase/core.cljs new file mode 100644 index 0000000000..ec81c05506 --- /dev/null +++ b/src/test/cljs_build/firebase/core.cljs @@ -0,0 +1,3 @@ +(ns firebase.core + (:require ["firebase/auth" :as auth])) + diff --git a/src/test/cljs_build/foreign-libs-dir/vendor/lib.js b/src/test/cljs_build/foreign-libs-dir/vendor/lib.js new file mode 100644 index 0000000000..ca285d9352 --- /dev/null +++ b/src/test/cljs_build/foreign-libs-dir/vendor/lib.js @@ -0,0 +1,3 @@ +module.exports = { + foo: 'bar', +} diff --git a/src/test/cljs_build/foreign_libs/core.cljs b/src/test/cljs_build/foreign_libs/core.cljs new file mode 100644 index 0000000000..6ad1c01956 --- /dev/null +++ b/src/test/cljs_build/foreign_libs/core.cljs @@ -0,0 +1,13 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns foreign-libs.core + (:require [thirdparty.add])) + +(defn main [] + (println (js/add 1 2))) diff --git a/src/test/cljs_build/foreign_libs_cljs_2249/core.cljs b/src/test/cljs_build/foreign_libs_cljs_2249/core.cljs new file mode 100644 index 0000000000..f7f99d2b12 --- /dev/null +++ b/src/test/cljs_build/foreign_libs_cljs_2249/core.cljs @@ -0,0 +1,13 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns foreign-libs-cljs-2249.core + (:require [thirdparty.calculator])) + +(defn main [] + (println (js/Calculator.add 1 2))) diff --git a/src/test/cljs_build/foreign_libs_cljs_2334/core.cljs b/src/test/cljs_build/foreign_libs_cljs_2334/core.cljs new file mode 100644 index 0000000000..1f6cf737e3 --- /dev/null +++ b/src/test/cljs_build/foreign_libs_cljs_2334/core.cljs @@ -0,0 +1,6 @@ +(ns foreign-libs-cljs-2334.core + (:require [mylib])) + +(enable-console-print!) + +(println "mylib:" mylib) diff --git a/src/test/cljs_build/foreign_libs_cljs_2334/lib.js b/src/test/cljs_build/foreign_libs_cljs_2334/lib.js new file mode 100644 index 0000000000..c78bb04585 --- /dev/null +++ b/src/test/cljs_build/foreign_libs_cljs_2334/lib.js @@ -0,0 +1,5 @@ +import leftPad from 'left-pad'; + +export var lp = function() { + return leftPad(42, 5, 0); +} diff --git a/src/test/cljs_build/foreign_libs_dir_test/core.cljs b/src/test/cljs_build/foreign_libs_dir_test/core.cljs new file mode 100644 index 0000000000..927054fd87 --- /dev/null +++ b/src/test/cljs_build/foreign_libs_dir_test/core.cljs @@ -0,0 +1,15 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns foreign-libs-dir-test.core + (:require [vendor.lib :as lib])) + +(enable-console-print!) + +(defn main [] + (println lib)) diff --git a/src/test/cljs_build/hello-modules/index.html b/src/test/cljs_build/hello-modules/index.html new file mode 100644 index 0000000000..cfa7ff790f --- /dev/null +++ b/src/test/cljs_build/hello-modules/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/test/cljs_build/hello-modules/release.clj b/src/test/cljs_build/hello-modules/release.clj new file mode 100644 index 0000000000..ae675c0dc7 --- /dev/null +++ b/src/test/cljs_build/hello-modules/release.clj @@ -0,0 +1,13 @@ +(require '[cljs.build.api :as b]) + +(b/build "src" + {:output-dir "out" + :asset-path "/out" + :optimizations :advanced + :verbose true + :modules {:foo {:entries '#{foo.core} + :output-to "out/foo.js"} + :bar {:entries '#{bar.core} + :output-to "out/bar.js"}}}) + +(System/exit 0) diff --git a/src/test/cljs_build/hello-modules/repl.clj b/src/test/cljs_build/hello-modules/repl.clj new file mode 100644 index 0000000000..b9130a24b5 --- /dev/null +++ b/src/test/cljs_build/hello-modules/repl.clj @@ -0,0 +1,18 @@ +(require '[cljs.repl :as r]) +(require '[cljs.build.api :as b]) +(require '[cljs.repl.browser :as rb]) + +(def opts + {:watch "src" + :output-dir "out" + :asset-path "/out" + :optimizations :none + :modules {:foo {:entries '#{foo.core} + :output-to "out/foo.js"} + :bar {:entries '#{bar.core} + :output-to "out/bar.js"}} + :browser-repl true + :verbose true}) + +(b/build "src" opts) +(r/repl* (rb/repl-env) opts) diff --git a/src/test/cljs_build/hello-modules/src/bar/core.cljs b/src/test/cljs_build/hello-modules/src/bar/core.cljs new file mode 100644 index 0000000000..ec6f887f86 --- /dev/null +++ b/src/test/cljs_build/hello-modules/src/bar/core.cljs @@ -0,0 +1,11 @@ +(ns bar.core + (:require [cljs.loader :as loader])) + +(enable-console-print!) + +(println "I'm bar!") + +(defn woz [] + (println "WOZ!")) + +(loader/set-loaded! :bar) diff --git a/src/test/cljs_build/hello-modules/src/foo/core.cljs b/src/test/cljs_build/hello-modules/src/foo/core.cljs new file mode 100644 index 0000000000..cef2ffcb2e --- /dev/null +++ b/src/test/cljs_build/hello-modules/src/foo/core.cljs @@ -0,0 +1,17 @@ +(ns foo.core + (:require [goog.dom :as gdom] + [goog.events :as events] + [cljs.loader :as loader]) + (:import [goog.events EventType])) + +(enable-console-print!) + +(println "I'm foo!") + +(events/listen (gdom/getElement "button") EventType.CLICK + (fn [e] + (loader/load :bar + (fn [] + ((resolve 'bar.core/woz)))))) + +(loader/set-loaded! :foo) diff --git a/src/test/cljs_build/json_modules_test/a.js b/src/test/cljs_build/json_modules_test/a.js new file mode 100644 index 0000000000..970187ecd6 --- /dev/null +++ b/src/test/cljs_build/json_modules_test/a.js @@ -0,0 +1,2 @@ +// b is a .json module +var theJSON = require('./b'); diff --git a/src/test/cljs_build/json_modules_test/b.json b/src/test/cljs_build/json_modules_test/b.json new file mode 100644 index 0000000000..f17a4c2957 --- /dev/null +++ b/src/test/cljs_build/json_modules_test/b.json @@ -0,0 +1 @@ +{"foo": 42} diff --git a/src/test/cljs_build/libs_test/core.cljs b/src/test/cljs_build/libs_test/core.cljs new file mode 100644 index 0000000000..8c7a426e7d --- /dev/null +++ b/src/test/cljs_build/libs_test/core.cljs @@ -0,0 +1,6 @@ +(ns libs-test.core + (:require [tabby])) + +(enable-console-print!) + +(println (tabby/hello)) diff --git a/src/test/cljs_build/loader_test/bar.cljs b/src/test/cljs_build/loader_test/bar.cljs new file mode 100644 index 0000000000..fbd164b43b --- /dev/null +++ b/src/test/cljs_build/loader_test/bar.cljs @@ -0,0 +1,9 @@ +(ns loader-test.bar + (:require [cljs.loader :as loader])) + +(enable-console-print!) + +(println "Hello from bar!") + +(defn woz [] + (println "Woz!")) diff --git a/src/test/cljs_build/loader_test/foo.cljs b/src/test/cljs_build/loader_test/foo.cljs new file mode 100644 index 0000000000..232e820508 --- /dev/null +++ b/src/test/cljs_build/loader_test/foo.cljs @@ -0,0 +1,17 @@ +(ns loader-test.foo + (:require [goog.dom :as gdom] + [goog.events :as events] + [cljs.loader :as loader] + [foreign.a] + [foreign.b]) + (:import [goog.events EventType])) + +(enable-console-print!) + +(println "Hello from foo!") + +(events/listen (gdom/getElement "button") EventType.CLICK + (fn [e] + (loader/load :bar + (fn [] + ((resolve 'loader-test.bar/woz)))))) diff --git a/src/test/cljs_build/loader_test/foreignA.js b/src/test/cljs_build/loader_test/foreignA.js new file mode 100644 index 0000000000..5b190303f1 --- /dev/null +++ b/src/test/cljs_build/loader_test/foreignA.js @@ -0,0 +1,3 @@ +global.foreignA = function() { + console.log("I'm foreign!") +}; diff --git a/src/test/cljs_build/loader_test/foreignB.js b/src/test/cljs_build/loader_test/foreignB.js new file mode 100644 index 0000000000..2fc7e3d233 --- /dev/null +++ b/src/test/cljs_build/loader_test/foreignB.js @@ -0,0 +1,3 @@ +global.foreignB = function() { + console.log("I'm foreign too!"); +}; diff --git a/src/test/cljs_build/node_modules_opt_test/core.cljs b/src/test/cljs_build/node_modules_opt_test/core.cljs new file mode 100644 index 0000000000..37848420a6 --- /dev/null +++ b/src/test/cljs_build/node_modules_opt_test/core.cljs @@ -0,0 +1,6 @@ +(ns node-modules-opt-test.core + (:require left-pad)) + +(enable-console-print!) + +(println "Padded:" (left-pad 42 5 0)) diff --git a/src/test/cljs_build/npm_deps_test/core.cljs b/src/test/cljs_build/npm_deps_test/core.cljs new file mode 100644 index 0000000000..1391dadb88 --- /dev/null +++ b/src/test/cljs_build/npm_deps_test/core.cljs @@ -0,0 +1,6 @@ +(ns npm-deps-test.core + (:require left-pad)) + +(enable-console-print!) + +(println "Padded:" (left-pad 42 5 0)) diff --git a/src/test/cljs_build/npm_deps_test/invoke.cljs b/src/test/cljs_build/npm_deps_test/invoke.cljs new file mode 100644 index 0000000000..deb595cfd2 --- /dev/null +++ b/src/test/cljs_build/npm_deps_test/invoke.cljs @@ -0,0 +1,10 @@ +(ns npm-deps-test.invoke + (:require [react :refer [createElement]] + ["react-dom/server" :as ReactDOMServer] + ["lodash-es/array" :as array])) + +(createElement "div") + +(ReactDOMServer/renderToString nil) + +(array/findIndex #js [1 2] 2) diff --git a/src/test/cljs_build/npm_deps_test/string_requires.cljs b/src/test/cljs_build/npm_deps_test/string_requires.cljs new file mode 100644 index 0000000000..b5836dffed --- /dev/null +++ b/src/test/cljs_build/npm_deps_test/string_requires.cljs @@ -0,0 +1,24 @@ +(ns npm-deps-test.string-requires + (:require [react :refer [createElement]] + ["react-dom/server" :as ReactDOMServer] + ["lodash-es/toArray" :refer [default] :rename {default toArray}] + ["lodash-es/toFinite" :as toFinite] + ["lodash-es/array" :as array] + [npm-deps-test.string-requires-in-classpath])) + +(enable-console-print!) + +;; CJS namespace access +(println ReactDOMServer) + +;; CJS method call +(ReactDOMServer/renderToString nil) + +;; es6 default with refer rename +(toArray nil) + +;; es6 :as and default +(toFinite/default nil) + +;; es6 +(array/findIndex #js [1 2] 2) diff --git a/src/test/cljs_build/package_json_resolution_test/core.cljs b/src/test/cljs_build/package_json_resolution_test/core.cljs new file mode 100644 index 0000000000..a232412e1e --- /dev/null +++ b/src/test/cljs_build/package_json_resolution_test/core.cljs @@ -0,0 +1,8 @@ +(ns package-json-resolution-test.core + (:require [iterall] + [graphql])) + +(enable-console-print!) + +(println "Is collection:" (iterall/isCollection #js [1 2])) +(println "GraphQL:" graphql) diff --git a/src/test/cljs_build/thirdparty/add.js b/src/test/cljs_build/thirdparty/add.js new file mode 100644 index 0000000000..32721eab38 --- /dev/null +++ b/src/test/cljs_build/thirdparty/add.js @@ -0,0 +1,11 @@ +// Copyright (c) Rich Hickey. All rights reserved. +// The use and distribution terms for this software are covered by the +// Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +// which can be found in the file epl-v10.html at the root of this distribution. +// By using this software in any fashion, you are agreeing to be bound by +// the terms of this license. +// You must not remove this notice, or any other, from this software. + +function add(a, b) { + return a + b; +} diff --git a/src/test/cljs_build/trivial/core.cljs b/src/test/cljs_build/trivial/core.cljs new file mode 100644 index 0000000000..f96fb7748a --- /dev/null +++ b/src/test/cljs_build/trivial/core.cljs @@ -0,0 +1,3 @@ +(ns trivial.core) + +(. js/console (log "Hello!")) diff --git a/src/test/cljs_build/trivial/core2.cljs b/src/test/cljs_build/trivial/core2.cljs new file mode 100644 index 0000000000..a79e64e807 --- /dev/null +++ b/src/test/cljs_build/trivial/core2.cljs @@ -0,0 +1,3 @@ +(ns trivial.core2) + +(.log js/console (-lookup 1 2)) diff --git a/src/test/cljs_build/trivial/core3.cljs b/src/test/cljs_build/trivial/core3.cljs new file mode 100644 index 0000000000..a66db571c3 --- /dev/null +++ b/src/test/cljs_build/trivial/core3.cljs @@ -0,0 +1,3 @@ +(ns trivial.core3) + +(.log js/console :foo) diff --git a/src/test/cljs_build/trivial/core4.cljs b/src/test/cljs_build/trivial/core4.cljs new file mode 100644 index 0000000000..f8f4c6d25b --- /dev/null +++ b/src/test/cljs_build/trivial/core4.cljs @@ -0,0 +1,3 @@ +(ns trivial.core4) + +(.log js/console []) diff --git a/src/test/cljs_build/trivial/core5.cljs b/src/test/cljs_build/trivial/core5.cljs new file mode 100644 index 0000000000..1e7f877568 --- /dev/null +++ b/src/test/cljs_build/trivial/core5.cljs @@ -0,0 +1,3 @@ +(ns trivial.core5) + +(.log js/console {}) \ No newline at end of file diff --git a/src/test/cljs_build/trivial/core6.cljs b/src/test/cljs_build/trivial/core6.cljs new file mode 100644 index 0000000000..3eed31bc65 --- /dev/null +++ b/src/test/cljs_build/trivial/core6.cljs @@ -0,0 +1,3 @@ +(ns trivial.core6) + +(.log js/console (->> (map inc (range 10)) (filter even?) (partition 2) (drop 1) (mapcat identity) into-array)) diff --git a/src/test/cljs_cli/cljs_cli/test.clj b/src/test/cljs_cli/cljs_cli/test.clj new file mode 100644 index 0000000000..e694638dd1 --- /dev/null +++ b/src/test/cljs_cli/cljs_cli/test.clj @@ -0,0 +1,147 @@ +(ns cljs-cli.test + (:require + [clojure.test :refer [deftest is]] + [clojure.java.io :as io] + [clojure.java.shell :as shell :refer [with-sh-dir]] + [clojure.string :as str] + [cljs-cli.util :refer [cljs-main output-is check-result with-sources with-in with-post-condition with-repl-env-filter repl-title]] + [clojure.string :as string])) + +(deftest eval-test + (-> (cljs-main "-e" 3 "-e" nil "-e" 4) + (output-is 3 4))) + +(deftest init-test + (with-sources {"src/foo/core.cljs" + "(ns foo.core) (def x 3)"} + (-> (cljs-main "-i" "src/foo/core.cljs" "-e" 'foo.core/x) + (output-is 3)))) + +(deftest main-test + (with-sources {"src/foo/core.cljs" + "(ns foo.core) (defn -main [] (prn :hi))"} + (-> (cljs-main "-m" "foo.core") + (output-is :hi)))) + +(deftest command-line-args-test + (with-sources {"src/foo/core.cljs" + "(ns foo.core) (prn *command-line-args*)"} + (-> (cljs-main "src/foo/core.cljs" "alpha" "beta" "gamma") + (output-is (pr-str '("alpha" "beta" "gamma")))))) + +(deftest command-line-args-empty-test + (with-sources {"src/foo/core.cljs" + "(ns foo.core) (prn *command-line-args*)"} + (-> (cljs-main "src/foo/core.cljs") + (output-is nil)))) + +(deftest initial-ns-test + (-> (cljs-main "-e" "::foo") + (output-is ":cljs.user/foo"))) + +(deftest source-test + (with-sources {"src/foo/core.cljs" + "(ns foo.core) (prn :hi)"} + (-> (cljs-main "src/foo/core.cljs") + (output-is :hi)))) + +(deftest compile-test + (with-sources {"src/foo/core.cljs" + "(ns foo.core) (defn -main [] (prn :hi))"} + (with-post-condition (fn [dir] (.exists (io/file dir "out" "main.js"))) + (-> (cljs-main "-o" "out/main.js" "-c" "foo.core") + (output-is))))) + +(deftest run-optimized-node-test + (with-repl-env-filter #{"node"} + (with-sources {"src/foo/core.cljs" + "(ns foo.core) (prn :hello-from-node)"} + (with-post-condition (fn [dir] + (= {:exit 0, :out ":hello-from-node\n", :err ""} + (with-sh-dir dir + (shell/sh "node" (str (io/file dir "out" "main.js")))))) + (-> (cljs-main "-t" "node" "-o" "out/main.js" "-O" "advanced" "-c" "foo.core") + (output-is)))))) + +(deftest test-cljs-2645 + (with-sources {"src/foo/core.cljs" + "(ns foo.core) (goog-define configurable \"default-value\") (defn -main [& args] (println configurable))"} + (-> (cljs-main "-m" "foo.core") + (output-is "default-value")) + (-> (cljs-main "-co" "{:closure-defines {foo.core/configurable \"configured-value\"}}" "-m" "foo.core") + (output-is "configured-value")))) + +(deftest test-cljs-2650-loader-does-not-exists + (doseq [optimizations [:none :advanced]] + (let [src (io/file "src" "test" "cljs_build" "hello-modules" "src") + opts {:output-dir "out" + :asset-path "/out" + :optimizations optimizations + :modules {:foo {:entries '#{foo.core} + :output-to "out/foo.js"} + :bar {:entries '#{bar.core} + :output-to "out/bar.js"}}}] + (with-sources + {"src/foo/core.cljs" (slurp (io/file src "foo" "core.cljs")) + "src/bar/core.cljs" (slurp (io/file src "bar" "core.cljs"))} + (let [result (cljs-main "--compile-opts" (pr-str opts) + "--compile" "foo.core")] + (is (zero? (:exit result))) + (is (str/blank? (:err result)))))))) + +(deftest test-cljs-2673 + (with-repl-env-filter #{"node"} + (-> (cljs-main + "-e" "(require 'cljs.js)" + "-e" "(cljs.js/eval-str (cljs.js/empty-state) \"(+ 1 2)\" nil {:eval cljs.js/js-eval :context :expr} prn)") + (output-is + "{:ns cljs.user, :value 3}")))) + +(deftest test-cljs-2724 + (with-repl-env-filter #{"node"} + (-> (cljs-main + "-e" "(require 'fs)" + "-e" "fs/R_OK") + (output-is + 4)))) + +(deftest test-cljs-2775 + (with-repl-env-filter #{"node"} + (-> (cljs-main + "-co" "{:npm-deps {:left-pad \"1.3.0\"} :install-deps true}" + "-d" "out" + "-e" "(require 'left-pad)" + "-e" "(left-pad 3 10 0)") + (output-is "\"0000000003\"")))) + +(deftest test-cljs-2780 + (with-repl-env-filter #{"node"} + (-> (cljs-main + "-e" "(do (js/setTimeout #(prn :end) 500) nil)" + "-e" ":begin") + (output-is + :begin + :end)))) + +(deftest test-graaljs-polyglot + (with-repl-env-filter #{"graaljs"} + (-> (cljs-main "-e" "(.eval js/Polyglot \"js\" \"1+1\")") + (output-is 2)))) + +(deftest test-cljs-3043 + (with-repl-env-filter identity + (let [check-fn (fn [result] + (is (= 1 (:exit result))) + (is (str/includes? (:err result) "Execution error")) + (is (not (str/includes? (:err result) "error__GT_str"))))] + (-> (cljs-main + "-e" "js/foo") + (check-result check-fn)) + (with-sources {"src/foo/core.cljs" + "(ns foo.core) (prn js/bogus)"} + (-> (cljs-main "-m" "foo.core") + (check-result check-fn))) + (with-sources {"src/foo/core.cljs" + "(ns foo.core) (prn js/bogus)"} + (-> (cljs-main "src/foo/core.cljs") + (check-result check-fn)))))) diff --git a/src/test/cljs_cli/cljs_cli/test_runner.clj b/src/test/cljs_cli/cljs_cli/test_runner.clj new file mode 100644 index 0000000000..f6ab983df2 --- /dev/null +++ b/src/test/cljs_cli/cljs_cli/test_runner.clj @@ -0,0 +1,12 @@ +(ns cljs-cli.test-runner + (:require + [cljs-cli.test] + [cljs-cli.util])) + +(defn -main [& args] + (try + (binding [cljs-cli.util/*repl-env* (or (first args) "node") + cljs-cli.util/*repl-opts* (second args)] + (clojure.test/run-tests 'cljs-cli.test)) + (finally + (shutdown-agents)))) diff --git a/src/test/cljs_cli/cljs_cli/util.clj b/src/test/cljs_cli/cljs_cli/util.clj new file mode 100644 index 0000000000..d272c8444c --- /dev/null +++ b/src/test/cljs_cli/cljs_cli/util.clj @@ -0,0 +1,110 @@ +(ns cljs-cli.util + (:refer-clojure :exclude [*in*]) + (:require + [clojure.string :as string] + [clojure.java.io :as io] + [clojure.java.shell :as shell] + [clojure.test :refer [is]] + [cljs.repl :as repl]) + (:import + (java.io File) + (java.nio.file Files CopyOption) + (java.nio.file.attribute FileAttribute))) + +(def ^:dynamic *repl-env* "node") +(def ^:dynamic *repl-env-filter* (constantly true)) +(def ^:dynamic *repl-opts* nil) +(def ^:dynamic *sources* nil) +(def ^:dynamic *in* nil) +(def ^:dynamic *post-condition* nil) + +(defmacro with-sources + [sources & body] + `(binding [*sources* ~sources] + ~@body)) + + +(defmacro with-in + [in & body] + `(binding [*in* ~in] + ~@body)) + +(defmacro with-post-condition + [post-condition & body] + `(binding [*post-condition* ~post-condition] + ~@body)) + +(defmacro with-repl-env-filter + [repl-env-filter & body] + `(binding [*repl-env-filter* ~repl-env-filter] + ~@body)) + +(defn- ^File make-temp-dir [] + (.toFile (Files/createTempDirectory "cljs-cli-test" (make-array FileAttribute 0)))) + +(defn- delete-recursively [fname] + (doseq [f (reverse (file-seq (io/file fname)))] + (io/delete-file f))) + +(defn- copy-uberjar [^File dest] + (Files/copy (.toPath (io/file "target/cljs.jar")) (.toPath (io/file dest "cljs.jar")) (make-array CopyOption 0))) + +(defn- write-sources [temp-dir] + (let [qualified #(io/file temp-dir %)] + (run! #(io/make-parents (qualified %)) (keys *sources*)) + (run! (fn [[file source]] + (spit (qualified file) source)) + *sources*))) + +(defn- run-in-temp-dir [args] + (let [temp-dir (make-temp-dir)] + (try + (write-sources temp-dir) + (copy-uberjar temp-dir) + (let [result (shell/with-sh-dir temp-dir + #_(apply println "running:" args) + (apply shell/sh (if *in* (concat args [:in *in*]) args)))] + (when *post-condition* + (is (*post-condition* temp-dir))) + result) + (finally + (delete-recursively temp-dir))))) + +(defn form-cp [] + (string/join File/pathSeparator ["cljs.jar" "src"])) + +(defn cljs-main [& args] + (if (*repl-env-filter* *repl-env*) + (let [command-line-args (map str args)] + (run-in-temp-dir + (keep (fn [arg] + (when arg + (str arg))) + (into ["java" "-cp" (form-cp) "cljs.main" + "-re" *repl-env* + (when *repl-opts* "-ro") (when *repl-opts* *repl-opts*)] + command-line-args)))) + {:exit 0 :out "" :err "" :repl-env-filtered true})) + +(def ^:private expected-browser-err + "Compiling client js ...\nServing HTTP on localhost port 9000\nListening for browser REPL connect ...\n") + +(defn- maybe-print-result-err [{:keys [err]}] + (when (and (not (empty? err)) + (not (= expected-browser-err err))) + (binding [*out* *err*] + (println err)))) + +(defn output-is [result & expected-lines] + (is (zero? (:exit result))) + (maybe-print-result-err result) + (when-not (:repl-env-filtered result) + (is (= (string/trim (apply str (map print-str (interleave expected-lines (repeat "\n"))))) + (string/trim (:out result)))))) + +(defn check-result [result pred] + (when-not (:repl-env-filtered result) + (pred result))) + +(defn repl-title [] + (string/trim (with-out-str (repl/repl-title)))) \ No newline at end of file diff --git a/src/test/cljs_cp/npm_deps_test/string_requires_in_classpath.cljs b/src/test/cljs_cp/npm_deps_test/string_requires_in_classpath.cljs new file mode 100644 index 0000000000..322367d845 --- /dev/null +++ b/src/test/cljs_cp/npm_deps_test/string_requires_in_classpath.cljs @@ -0,0 +1,6 @@ +(ns npm-deps-test.string-requires-in-classpath + "This tests string require of a lib that is not loaded + by project local files from a classpath file." + (:require ["lodash/array" :as array])) + +(println "lodash/array is loaded:" (array/nth #js [true] 1)) diff --git a/src/test/clojure/cljs/analyzer/as_alias_test.clj b/src/test/clojure/cljs/analyzer/as_alias_test.clj new file mode 100644 index 0000000000..6ef0458bf3 --- /dev/null +++ b/src/test/clojure/cljs/analyzer/as_alias_test.clj @@ -0,0 +1,90 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer.as-alias-test + (:require [cljs.analyzer.impl.namespaces :as ana-nses] + [cljs.env :as env] + [clojure.test :as test :refer [deftest testing is]])) + +;; ============================================================================= + +(deftest test-check-and-remove-as-alias + (let [cenv (env/default-compiler-env)] + (env/with-compiler-env cenv + (testing "check-and-remove-as-alias basic tests" + (is (= '{:as-alias {bar bar.core}} + (ana-nses/check-and-remove-as-alias '[bar.core :as-alias bar]))) + (is (= '{:as-alias {bar bar.core} + :libspec [bar.core :as boo]} + (ana-nses/check-and-remove-as-alias '[bar.core :as-alias bar :as boo]))) + (is (thrown? Throwable + (ana-nses/check-and-remove-as-alias '[bar.core :as-alias :bar])))) + (testing "check-and-remove-as-alias should not elide simple specs" + (is (= '{:libspec bar.core} + (ana-nses/check-and-remove-as-alias 'bar.core))) + (is (= '{:libspec [bar.core]} + (ana-nses/check-and-remove-as-alias '[bar.core]))))))) + +(deftest test-elide-aliases-from-libspecs + (let [cenv (env/default-compiler-env)] + (env/with-compiler-env cenv + (is (= '{:as-aliases {foo foo.core + bar bar.core + woz woz.core} + :libspecs [[woz.core :as wozc]]} + (ana-nses/elide-aliases-from-libspecs + '([foo.core :as-alias foo] + [bar.core :as-alias bar] + [woz.core :as-alias woz :as wozc])))) + (is (thrown? Throwable + (ana-nses/elide-aliases-from-libspecs + '([foo.core :as-alias foo] + [bar.core :as-alias bar] + [woz.core :as-alias woz :as wozc] + [foo.impl :as-alias foo]))))))) + +(deftest test-elide-aliases-from-ns-specs + (let [cenv (env/default-compiler-env)] + (env/with-compiler-env cenv + (is (= '{:as-aliases {blah blah.core, foo foo.core, bar bar.core}, + :libspecs [(:require-macros [[lala.core :as-lias lala :as tralala]]) + (:require [[woz.core :as woz]])]}) + (ana-nses/elide-aliases-from-ns-specs + '((:require-macros [blah.core :as-alias blah] + [lala.core :as-alias lala :as tralala]) + (:require + [foo.core :as-alias foo] + [bar.core :as-alias bar] + [woz.core :as woz])))) + (testing "Proper handling of ns-spec edgecases" + (is (= '{:as-aliases {} :libspecs [(:require foo.core bar.core woz.core)]} + (ana-nses/elide-aliases-from-ns-specs + '((:require foo.core bar.core woz.core))))) + (is (= '{:as-aliases {} :libspecs [(:require [foo.core] [bar.core] [woz.core])]} + (ana-nses/elide-aliases-from-ns-specs + '((:require [foo.core] [bar.core] [woz.core])))))) + (testing ":refer-clojure is ignored" + (is (= '{:as-aliases {} + :libspecs [(:refer-clojure :exclude [first]) + (:require foo.core bar.core woz.core)]} + (ana-nses/elide-aliases-from-ns-specs + '((:refer-clojure :exclude [first]) + (:require foo.core bar.core woz.core)))))) + (testing ":reload/:reload-all is ignored" + (is (= '{:as-aliases {}, + :libspecs [(:refer-clojure :exclude [first]) + (:require foo.core bar.core woz.core :reload-all)]} + (ana-nses/elide-aliases-from-ns-specs + '((:refer-clojure :exclude [first]) + (:require foo.core bar.core woz.core :reload-all))))))))) + +(comment + + (test/run-tests) + + ) diff --git a/src/test/clojure/cljs/analyzer/glib_module_test.clj b/src/test/clojure/cljs/analyzer/glib_module_test.clj new file mode 100644 index 0000000000..04c2781d34 --- /dev/null +++ b/src/test/clojure/cljs/analyzer/glib_module_test.clj @@ -0,0 +1,62 @@ +(ns cljs.analyzer.glib-module-test + (:require [cljs.analyzer :as ana] + [cljs.analyzer-tests :as ana-tests] + [clojure.test :as test :refer [deftest is testing]] + [cljs.env :as env])) + +(deftest glib-module-detect-test + (testing "Basic glib module detection" + (is (= :goog (get-in @ana-tests/test-cenv [:js-dependency-index (munge "goog.module.ModuleLoader") :module]))))) + +(deftest glib-module-predicate-test + (testing "glib module detection predicate" + (env/with-compiler-env ana-tests/test-cenv + (is (ana/goog-module-dep? 'goog.module.ModuleLoader))))) + +(deftest glib-module-classification-test + (testing "glib module classification" + (env/with-compiler-env ana-tests/test-cenv + (is (= :goog-module (ana/ns->module-type 'goog.module.ModuleLoader)))))) + +(deftest glib-module-resolve-var-test + (testing "glib module var resolution" + (let [cenv (env/default-compiler-env) + ns-ast (ana-tests/analyze-forms cenv + '[(ns foo.core + (:require [goog.module.ModuleLoader :as module-loader]))]) + aenv (assoc (ana/empty-env) :ns (ana/get-namespace cenv 'foo.core))] + (is (= '{:name foo.core/goog$module$goog$module$ModuleLoader.EventType + :ns foo.core + :op :var} + (env/with-compiler-env cenv + (ana/resolve-var aenv 'module-loader/EventType))))))) + +(deftest glib-module-resolve-import-test + (testing "glib module resolve import helper test" + (let [cenv (env/default-compiler-env) + ns-ast (ana-tests/analyze-forms cenv + '[(ns foo.core + (:require [goog.module.ModuleLoader :as module-loader]))]) + aenv (assoc (ana/empty-env) :ns (ana/get-namespace cenv 'foo.core))] + (is (= 'foo.core.goog$module$goog$module$ModuleLoader + (env/with-compiler-env cenv + (ana/resolve-import aenv 'goog.module.ModuleLoader))))))) + +(deftest glib-module-resolve-import-var-test + (testing "glib module :import var resolution" + (let [cenv (env/default-compiler-env) + ns-ast (ana-tests/analyze-forms cenv + '[(ns foo.core + (:import [goog.module ModuleLoader]))]) + aenv (assoc (ana/empty-env) :ns (ana/get-namespace cenv 'foo.core))] + (is (= '{:name foo.core/goog$module$goog$module$ModuleLoader + :ns foo.core + :op :var} + (env/with-compiler-env cenv + (ana/resolve-var aenv 'ModuleLoader))))))) + +(comment + + (test/run-tests) + + ) diff --git a/src/test/clojure/cljs/analyzer/spec_tests.clj b/src/test/clojure/cljs/analyzer/spec_tests.clj new file mode 100644 index 0000000000..57134c7039 --- /dev/null +++ b/src/test/clojure/cljs/analyzer/spec_tests.clj @@ -0,0 +1,288 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer.spec-tests + (:require [cljs.analyzer :as ana] + [cljs.analyzer.api :as ana-api :refer [no-warn]] + [cljs.compiler.api :as comp-api] + [cljs.analyzer-tests :refer [analyze ns-env]] + [cljs.analyzer.specs :as a] + [clojure.test :as test :refer [deftest is]] + [clojure.spec.alpha :as s]) + (:import [java.io StringReader])) + +(deftest test-binding + (let [node (analyze ns-env '(let [x 1] x)) + binding (-> node :bindings first)] + (is (= :binding (:op binding))) + (is (s/valid? ::a/node binding)))) + +(deftest test-case + (let [let-node (no-warn (analyze ns-env '(case x 1 :foo 2 :bar))) + node (-> let-node :body :ret)] + (is (= :case (:op node))) + (is (s/valid? ::a/node node)) + (let [nodes (-> node :nodes) + case-node (first nodes)] + (is (= :case-node (:op case-node))) + (is (s/valid? ::a/node case-node)) + (let [case-tests (:tests case-node) + case-test (first case-tests) + case-then (:then case-node)] + (is (= :case-test (:op case-test))) + (is (s/valid? ::a/node case-test)) + (is (= :case-then (:op case-then))) + (is (s/valid? ::a/node case-then)))))) + +(deftest test-const + (is (s/valid? ::a/node (analyze ns-env 1))) + (is (s/valid? ::a/node (analyze ns-env 1.2))) + (is (s/valid? ::a/node (analyze ns-env true))) + (is (s/valid? ::a/node (analyze ns-env "foo"))) + (let [node (analyze ns-env [])] + (is (= :vector (:op node))) + (is (s/valid? ::a/node node))) + (is (s/valid? ::a/node (analyze ns-env [1 2 3]))) + (is (s/valid? ::a/node (analyze ns-env {}))) + (let [node (analyze ns-env {1 2 3 4})] + (is (= :map (:op node))) + (is (s/valid? ::a/node node))) + (is (s/valid? ::a/node (analyze ns-env #{}))) + (let [node (analyze ns-env #{1 2 3})] + (is (= :set (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-def + (let [node (no-warn (analyze ns-env '(def x)))] + (is (= :def (:op node))) + (is (s/valid? ::a/node node))) + (is (s/valid? ::a/node (analyze ns-env '(def x 1)))) + (is (s/valid? ::a/node (analyze ns-env '(def x (fn []))))) + (is (s/valid? ::a/node (analyze ns-env '(def x (fn [y] y)))))) + +(deftest test-defn + (is (s/valid? ::a/node (analyze ns-env '(defn x [])))) + (is (s/valid? ::a/node (analyze ns-env '(defn x [] 1)))) + (is (s/valid? ::a/node (analyze ns-env '(defn x [y] y))))) + +(deftest test-defrecord + (let [node (no-warn (analyze ns-env '(defrecord A []))) + body (:body node)] + (is (= :defrecord (-> body :statements first :ret :op))) + (is (s/valid? ::a/node node)))) + +(deftest test-deftype + (let [node (no-warn (analyze ns-env '(deftype A [])))] + (is (= :deftype (-> node :statements first :op))) + (is (s/valid? ::a/node node)))) + +(deftest test-do + (let [node (analyze ns-env '(do))] + (is (= :do (:op node))) + (is (s/valid? ::a/node node))) + (is (s/valid? ::a/node (analyze ns-env '(do 1)))) + (is (s/valid? ::a/node (analyze ns-env '(do 1 2 3))))) + +(deftest test-fn + (let [node (no-warn (analyze ns-env '(fn [])))] + (is (= :fn (:op node))) + (is (s/valid? ::a/node node))) + (is (s/valid? ::a/node (analyze ns-env '(fn [] 1)))) + (is (s/valid? ::a/node (analyze ns-env '(fn [x])))) + (is (s/valid? ::a/node (analyze ns-env '(fn [x] 1))))) + +(deftest test-fn-method + (let [node (analyze ns-env '(fn ([]) ([x] x))) + methods (:methods node) + fn0 (first methods) + fn1 (second methods)] + (is (= :fn-method (:op fn0))) + (is (s/valid? ::a/node fn0)) + (is (= :fn-method (:op fn1))) + (is (s/valid? ::a/node fn1)))) + +(deftest test-host-call + (let [node (analyze ns-env '(.substring "foo" 0 1))] + (is (= :host-call (:op node))) + (is (s/valid? ::a/node node))) + (let [node (analyze ns-env '(. "foo" (substring 0 1)))] + (is (= :host-call (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-host-field + (let [node (analyze ns-env '(.-length "foo"))] + (is (= :host-field (:op node))) + (is (s/valid? ::a/node node))) + (let [node (analyze ns-env '(. "foo" -length))] + (is (= :host-field (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-if + (let [node (analyze ns-env '(if true true))] + (is (= :if (:op node))) + (is (s/valid? ::a/node node))) + (is (s/valid? ::a/node (analyze ns-env '(if true true false))))) + +(deftest test-invoke + (let [node (no-warn (analyze ns-env '(count "foo")))] + (is (= :invoke (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-js + (let [node (analyze ns-env '(js* "~{}" 1))] + (is (= :js (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-js-array + (let [node (analyze ns-env + (ana-api/with-state (ana-api/empty-state) + (first (ana-api/forms-seq (StringReader. "#js [1 2 3]")))))] + (is (= :js-array (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-js-object + (let [node (analyze ns-env + (ana-api/with-state (ana-api/empty-state) + (first (ana-api/forms-seq (StringReader. "#js {:foo 1 :bar 2}")))))] + (is (= :js-object (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-js-var + (let [node (analyze ns-env 'js/String)] + (is (= :js-var (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-let + (let [node (analyze ns-env '(let []))] + (is (= :let (:op node))) + (is (s/valid? ::a/node node))) + (is (s/valid? ::a/node (analyze ns-env '(let [x 1])))) + (is (s/valid? ::a/node (analyze ns-env '(let [x 1] x))))) + +(deftest test-letfn + (let [node (analyze ns-env '(letfn [(foo [] (bar)) (bar [] (foo))]))] + (is (= :letfn (:op node))) + (is (s/valid? ::a/node node)))) + +;; list, no longer needed, subsumed by :quote + +(deftest test-local + (let [node (analyze ns-env '(fn [x] x)) + fn-method (-> node :methods first) + body (-> fn-method :body) + ret (:ret body)] + (is (= :local (:op ret))) + (is (s/valid? ::a/node node)))) + +(deftest test-loop + (let [node (analyze ns-env '(loop []))] + (is (= :loop (:op node))) + (is (s/valid? ::a/node node))) + (let [node (analyze ns-env '(loop [x 1] x))] + (is (s/valid? ::a/node node))) + (let [node (analyze ns-env '(loop [x 1] (recur (inc x))))] + (is (s/valid? ::a/node node))) + (let [node (no-warn + (analyze ns-env + '(loop [x 100] + (if (pos? x) + (recur (dec x)) + x))))] + (is (s/valid? ::a/node node)))) + +(deftest test-map + (let [node (no-warn (analyze ns-env '{:foo 1 :bar 2}))] + (is (= :map (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-new + (let [node (no-warn (analyze ns-env '(new String)))] + (is (= :new (:op node))) + (is (s/valid? ::a/node node))) + (is (s/valid? ::a/node (analyze ns-env '(new js/String)))) + (is (s/valid? ::a/node (no-warn (analyze ns-env '(String.))))) + (is (s/valid? ::a/node (analyze ns-env '(js/String.))))) + +(deftest test-no-op + (let [node (binding [ana/*unchecked-if* true] + (no-warn (analyze ns-env '(set! *unchecked-if* false))))] + (is (= :no-op (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-ns + (let [node (no-warn + (binding [ana/*cljs-ns* 'cljs.user] + (analyze ns-env '(ns foo (:require [goog.string])))))] + (is (= :ns (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-ns* + (let [node (no-warn + (binding [ana/*cljs-ns* 'cljs.user] + (analyze ns-env '(ns* (:require '[goog.string])))))] + (is (= :ns* (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-quote + (let [node (analyze ns-env ''(1 2 3))] + (is (= :quote (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-recur + (let [node (no-warn (analyze ns-env '(fn [x] (recur (inc x)))))] + (is (s/valid? ::a/node node)))) + +(deftest test-set + (let [node (analyze ns-env #{1 2 3})] + (is (= :set (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-set! + (let [node (no-warn (analyze ns-env '(set! x 1)))] + (is (= :set! (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-the-var + (let [node (comp-api/with-core-cljs {} + #(analyze ns-env '(var first)))] + (is (= :the-var (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-throw + (let [node (no-warn (analyze ns-env '(throw (js/Error. "foo"))))] + (is (= :throw (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-try + (let [node (no-warn (analyze ns-env '(try 1 (catch :default e) (finally))))] + (is (= :try (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-var + (let [node (no-warn (analyze ns-env '(fn [] x))) + fn-method (-> node :methods first) + body (-> fn-method :body) + ret (:ret body)] + (is (= :var (:op ret))) + (is (s/valid? ::a/node node)))) + +(deftest test-vector + (let [node (no-warn (analyze ns-env '[1 2]))] + (is (= :vector (:op node))) + (is (s/valid? ::a/node node)))) + +(deftest test-with-meta + (let [node (analyze ns-env ^{:meta 2} {:foo 1})] + (is (= :with-meta (:op node))) + (is (s/valid? ::a/node node)))) + +(comment + + (test/run-tests) + + ) diff --git a/src/test/clojure/cljs/analyzer/specs.cljc b/src/test/clojure/cljs/analyzer/specs.cljc new file mode 100644 index 0000000000..ec5079bf98 --- /dev/null +++ b/src/test/clojure/cljs/analyzer/specs.cljc @@ -0,0 +1,328 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer.specs + (:require [clojure.spec.alpha :as s])) + +(s/def ::op keyword?) +(s/def ::form any?) +(s/def ::env map?) +(s/def ::context #{:expr :return :statement}) + +(defmulti node :op) +(s/def ::node (s/multi-spec node :op)) + +(s/def ::test ::node) +(s/def ::then ::node) +(s/def ::else ::node) + +;; TODO: :tag +(s/def ::base + (s/keys + :req-un [::op ::env ::form])) + +(s/def ::name symbol?) +(s/def :cljs.analyzer.specs.binding/local + #{:arg :catch :fn :let :letfn :loop :field}) +(s/def ::variadic? boolean?) +(s/def ::init ::node) +(s/def ::shadow + (s/or :nil nil? + :node ::node)) + +(defmethod node :binding [_] + (s/merge + ::base + (s/keys + :req-un [::name :cljs.analyzer.specs.binding/local] + :opt-un [::variadic? ::init ::shadow]))) + +(s/def ::nodes (s/* ::node)) +(s/def ::default ::node) + +(defmethod node :case [_] + (s/merge ::base + (s/keys + :req-un [::test ::nodes ::default]))) + +(defmethod node :case-node [_] + (s/keys + :req-un [::op ::env ::tests ::then])) + +(defmethod node :case-test [_] + (s/merge ::base + (s/keys + :req-un [::test]))) + +(defmethod node :case-then [_] + (s/merge ::base + (s/keys + :req-un [::then]))) + +(s/def ::literal? boolean?) +(s/def ::val any?) + +(defmethod node :const [_] + (s/merge ::base + (s/keys + :req-un [::val] + ;; ::literal? is required in the AST REF, but we don't actually use it + ;; should check tools.analyzer + :opt-un [::literal?]))) + +(defmethod node :def [_] + (s/merge ::base + (s/keys + :req-un [::name] + :opt-un [::init ::the-var]))) + +(s/def ::body ::node) +(s/def ::t symbol?) + +(defmethod node :defrecord [_] + (s/merge ::base + (s/keys + :req-un [::t ::body]))) + +(defmethod node :deftype [_] + (s/merge ::base + (s/keys + :req-un [::t ::body]))) + +(s/def ::statements (s/* ::node)) +(s/def ::ret ::node) +(s/def ::body? boolean?) + +(defmethod node :do [_] + (s/merge ::base + (s/keys + :req-un [::statements ::ret] + :opt-un [::body?]))) + +(s/def ::local ::node) +(s/def ::max-fixed-arity int?) +(s/def ::methods (s/+ ::node)) + +(defmethod node :fn [_] + (s/merge ::base + (s/keys + :req-un [::variadic? ::max-fixed-arity ::methods] + :opt-un [::local]))) + +(s/def ::fixed-arity int?) +(s/def ::params (s/* ::node)) + +(defmethod node :fn-method [_] + (s/merge ::base + (s/keys + :req-un [::fixed-arity ::params ::body]))) + +(s/def ::method symbol?) +(s/def ::target ::node) +(s/def ::args (s/* ::node)) + +(defmethod node :host-call [_] + (s/merge ::base + (s/keys + :req-un [::method ::target ::args]))) + +(s/def ::field symbol?) + +(defmethod node :host-field [_] + (s/merge ::base + (s/keys + :req-un [::field ::target]))) + +(defmethod node :if [_] + (s/merge ::base + (s/keys + :req-un [::test ::then] + :opt-un [::else]))) + +(s/def ::fn ::node) + +(defmethod node :invoke [_] + (s/merge ::base + (s/keys + :req-un [::fn ::args]))) + +(s/def ::code string?) + +(defmethod node :js [_] + (s/merge ::base + (s/keys + :opt-un [::code]))) + +(defmethod node :js-array [_] + (s/merge ::base + (s/keys + :req-un [::items]))) + +(defmethod node :js-object [_] + (s/merge ::base + (s/keys + :req-un [::vals]))) + +(s/def ::ns symbol?) + +(defmethod node :js-var [_] + (s/merge ::base + (s/keys + :req-un [::ns ::name]))) + +(s/def ::bindings (s/* ::node)) + +(defmethod node :let [_] + (s/merge ::base + (s/keys + :req-un [::bindings ::body]))) + +(defmethod node :letfn [_] + (s/merge ::base + (s/keys + :req-un [::bindings ::body]))) + +(s/def ::items (s/* ::node)) + +;; TODO: not in ast-ref +(defmethod node :list [_] + (s/merge ::base + (s/keys + :req-un [::items]))) + +(defmethod node :local [_] + (s/merge ::base + (s/keys + :req-un [:cljs.analyzer.specs.binding/local ::name]))) + +(defmethod node :loop [_] + (s/merge ::base + (s/keys + :req-un [::bindings ::body]))) + +(s/def ::vals (s/* ::node)) + +(defmethod node :map [_] + (s/merge ::base + (s/keys :req-un [::keys ::vals]))) + +(s/def ::class ::node) + +(defmethod node :new [_] + (s/merge ::base + (s/keys + :req-un [::class ::args]))) + +(defmethod node :no-op [_] + (s/keys + :req-un [::env ::op])) + +(defmethod node :ns [_] + ::base) + +(defmethod node :ns* [_] + ::base) + +(s/def ::expr ::node) + +(defmethod node :quote [_] + (s/merge ::base + (s/keys + :req-un [::expr ::literal?]))) + +(s/def ::exprs (s/* ::node)) + +(defmethod node :recur [_] + (s/merge ::base + (s/keys + :req-un [::exprs]))) + +(defmethod node :set [_] + (s/merge ::base + (s/keys + :req-un [::items]))) + +(defmethod node :set! [_] + (s/merge ::base + (s/keys + :req-un [::target ::val]))) + +(s/def ::var ::node) +(s/def ::sym ::node) +(s/def ::meta map?) + +(defmethod node :the-var [_] + (s/merge ::base + (s/keys + :opt-un [::var ::sym ::meta]))) + +(s/def ::the-var ::node) + +(s/def ::exception ::node) + +(defmethod node :throw [_] + (s/merge ::base + (s/keys + :req-un [::exception]))) + +(s/def ::catch ::node) +(s/def ::finally ::node) + +(defmethod node :try [_] + (s/merge ::base + (s/keys + :req-un [::body ::catch ::name ::finally]))) + +(defmethod node :var [_] + (s/merge ::base + (s/keys + :req-un [::ns ::name]))) + +(s/def ::meta ::node) + +(defmethod node :vector [_] + (s/merge ::base + (s/keys + :req-un [::items]))) + +(defmethod node :with-meta [_] + (s/merge ::base + (s/keys + :req-un [::meta ::expr]))) + +(comment + + (s/valid? ::node 1) + (s/valid? ::node + {:op :const + :env {} + :form 1 + :literal? true + :val 1}) + + (s/explain-data ::node + {:op :if + :env {} + :form '(if true true false) + :test {:op :const + :env {} + :form true + :literal? true + :val true} + :then {:op :const + :env {} + :form true + :literal? true + :val true} + :else {:op :const + :env 1 + :form false + :literal? true + :val false}}) + + ) diff --git a/src/test/clojure/cljs/analyzer_api_tests.clj b/src/test/clojure/cljs/analyzer_api_tests.clj new file mode 100644 index 0000000000..243281ff6b --- /dev/null +++ b/src/test/clojure/cljs/analyzer_api_tests.clj @@ -0,0 +1,60 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer-api-tests + (:require [cljs.analyzer.api :as ana-api]) + (:use clojure.test)) + +(deftest cljs-warning-test + (is (ana-api/warning-enabled? :undeclared-var) + "Undeclared-var warning is enabled by default") + (is (not (ana-api/no-warn (ana-api/warning-enabled? :undeclared-var))) + "Disabled when all warnings are disabled")) + +(def warning-form + '(do (defn x [a b] (+ a b)) + (x 1 2 3 4))) + +(defn warning-handler [counter] + (fn [warning-type env extra] + (when (ana-api/warning-enabled? warning-type) + (swap! counter inc)))) + +(def test-cenv (atom {})) +(def test-env (ana-api/empty-env)) + +(deftest with-warning-handlers-test + (let [counter (atom 0)] + (ana-api/analyze test-cenv test-env warning-form nil + {:warning-handlers [(warning-handler counter)]}) + (is (= 1 @counter)))) + +(deftest vary-warning-handlers-test + (let [counter (atom 0)] + (cljs.analyzer/all-warn + (ana-api/analyze test-cenv test-env warning-form nil + {:warning-handlers [(warning-handler counter)]})) + (is (= 1 @counter)))) + +(deftest get-options-test + (let [state (atom {:options {:a 1}})] + (is (= {:a 1} (ana-api/get-options state))) + (ana-api/with-state state + (is (= {:a 1} (ana-api/get-options)))))) + +(deftest get-js-index-test + (let [state (atom {:js-dependency-index {:a 1}})] + (is (= {:a 1} (ana-api/get-js-index state))) + (ana-api/with-state state + (is (= {:a 1} (ana-api/get-js-index)))))) + +(deftest throw-test + (let [state (atom {})] + (is (thrown? Exception (ana-api/the-ns state 'non.existing))) + (is (thrown? Exception (ana-api/ns-interns state 'non.existing))) + (is (thrown? Exception (ana-api/ns-publics state 'non.existing))))) diff --git a/src/test/clojure/cljs/analyzer_pass_tests.clj b/src/test/clojure/cljs/analyzer_pass_tests.clj new file mode 100644 index 0000000000..643352b7ac --- /dev/null +++ b/src/test/clojure/cljs/analyzer_pass_tests.clj @@ -0,0 +1,204 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer-pass-tests + (:require [cljs.analyzer :as ana] + [cljs.analyzer.passes :as passes] + [cljs.analyzer.passes.and-or :as and-or] + [cljs.analyzer-tests :as ana-tests :refer [analyze]] + [cljs.compiler :as comp] + [cljs.compiler-tests :as comp-tests :refer [compile-form-seq emit]] + [cljs.env :as env] + [cljs.test-util :refer [equiv-modulo-newlines]] + [clojure.string :as string] + [clojure.test :as test :refer [deftest is testing]])) + +(deftest test-walk + (testing "walking visits every node" + (let [expr-env (assoc (ana/empty-env) :context :expr) + ast (->> `(and true false) + (analyze expr-env)) + ast' (passes/walk ast [(fn [_ ast _] (dissoc ast :env))])] + (is (not (contains? ast' :env))) + (is (not (some #(contains? % :env) (:args ast'))))) + (let [expr-env (assoc (ana/empty-env) :context :expr) + ast (->> `(let [x# 1 + y# (fn [] x#) + z# (fn [] y#)] + 'x) + (analyze expr-env)) + ast' (passes/walk ast [(fn [_ ast _] (dissoc ast :env))])] + (is (not (contains? ast' :env))) + (is (= 3 (count (:bindings ast')))) + (is (not (some #(contains? % :env) (:bindings ast'))))))) + +(deftest remove-local + (testing "and/or remove local pass" + (let [ast {:op :fn + :env '{:locals {x {}}} + :loop-lets '[{:params [{:name x}]}]} + pass (and-or/remove-local-pass 'x) + ast' (passes/apply-passes ast [pass])] + (is (contains? (-> ast :env :locals) 'x)) + (is (not (contains? (-> ast' :env :locals) 'x))) + (is (some + (fn [{:keys [params]}] + (some #(= 'x (:name %)) params)) + (:loop-lets ast))) + (is (not (some + (fn [{:keys [params]}] + (some #(= 'x (:name %)) params)) + (:loop-lets ast'))))))) + +(deftest test-and-or-code-gen-pass + (testing "and/or optimization code gen pass" + (let [expr-env (assoc (ana/empty-env) :context :expr) + ast (->> `(and true false) + (analyze expr-env)) + code (with-out-str (emit ast))] + (is (= code "((true) && (false))"))) + (let [expr-env (assoc (ana/empty-env) :context :expr) + ast (analyze expr-env + `(and true (or true false))) + code (with-out-str (emit ast))] + (is (= code "((true) && (((true) || (false))))"))) + (let [expr-env (assoc (ana/empty-env) :context :expr) + ast (analyze expr-env + `(or true (and false true))) + code (with-out-str (emit ast))] + (is (= code "((true) || (((false) && (true))))"))) + (let [expr-env (assoc (ana/empty-env) :context :expr) + local (gensym) + ast (analyze expr-env + `(let [~local true] + (and true (or ~local false)))) + code (with-out-str (emit ast))] + (is (equiv-modulo-newlines code + (string/replace + "(function (){var $SYM = true;\nreturn ((true) && ((($SYM) || (false))));\n})()" + "$SYM" (str local))))))) + +(deftest test-and-or-local + (testing "and/or optimizable with boolean local" + (let [expr-env (assoc (ana/empty-env) :context :expr) + ast (->> `(let [x# true] + (and x# true false)) + (analyze expr-env)) + code (with-out-str (emit ast))] + (is (= 2 (count (re-seq #"&&" code))))))) + +(deftest test-and-or-boolean-fn-arg + (testing "and/or optimizable with boolean fn arg" + (let [arg (with-meta 'x {:tag 'boolean}) + ast (analyze (assoc (ana/empty-env) :context :expr) + `(fn [~arg] + (and ~arg false false))) + code (with-out-str (emit ast))] + (is (= 2 (count (re-seq #"&&" code))))))) + +(deftest test-and-or-boolean-var + (testing "and/or optimizable with boolean var" + (let [code (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(ns foo.bar) + (def baz true) + (defn woz [] + (and baz false))]))] + (is (= 1 (count (re-seq #"&&" code))))))) + +(deftest test-and-or-js-boolean-var + (testing "and/or optimizable with js boolean var" + (let [code (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(ns foo.bar) + (defn baz [] + (and ^boolean js/woz false))]))] + (is (= 1 (count (re-seq #"&&" code))))))) + +(deftest test-and-or-host-call + (testing "and/or optimizable with host call" + (let [code (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(ns foo.bar) + (defn bar [x] + (and ^boolean (.woz x) false))]))] + (is (= 1 (count (re-seq #"&&" code))))))) + +(deftest test-and-or-host-field + (testing "and/or optimizable with host field" + (let [code (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(ns foo.bar) + (defn bar [x] + (and ^boolean (.-woz x) false))]))] + (is (= 1 (count (re-seq #"&&" code))))))) + +(deftest test-core-predicates + (testing "and/or optimizable with core predicates" + (let [code (env/with-compiler-env (env/default-compiler-env) + (comp/with-core-cljs {} + (fn [] + (compile-form-seq + '[(ns foo.bar) + (defn bar [] + (and (even? 1) false))]))))] + (is (= 1 (count (re-seq #"&&" code))))))) + +(deftest test-cljs-3309 + (testing "CLJS-3309: and/or optimization removes discarded local and loop-lets" + (let [code (env/with-compiler-env (env/default-compiler-env) + (comp/with-core-cljs {} + (fn [] + (compile-form-seq + '[(loop [x 4] + (when (or (< x 4) (not-any? (fn [y] x) [1])) + (recur 5)))]))))] + (is (empty? (re-seq #"or_" code)))) + (let [code (env/with-compiler-env (env/default-compiler-env) + (comp/with-core-cljs {} + (fn [] + (compile-form-seq + '[((fn [s] + (for [e s :when (and (sequential? e) (every? (fn [x] x) e))] + e)) + [[]])]))))] + (is (empty? (re-seq #"and_" code)))) + (let [code (env/with-compiler-env (env/default-compiler-env) + (comp/with-core-cljs {} + (fn [] + (compile-form-seq + '[(or false + (boolean + (for [s (range 1)] + (map (fn [x] x) s))))]))))] + (is (empty? (re-seq #"or_" code)))))) + +(deftest test-lite-mode-pass + (let [aenv (assoc (ana/empty-env) :context :expr) + env (env/default-compiler-env {:lite-mode true})] + (let [ast (env/with-compiler-env env + (comp/with-core-cljs {} + (fn [] + (analyze aenv 'cljs.core/vec))))] + (is (= 'cljs.core/vec-lite + (-> ast :name) + (-> ast :info :name)))) + (let [ast (env/with-compiler-env env + (comp/with-core-cljs {} + (fn [] + (analyze aenv 'cljs.core/vector))))] + (is (= 'cljs.core/vector-lite + (-> ast :name) + (-> ast :info :name)))))) + +(comment + (test/run-tests) + + (require '[clojure.pprint :refer [pprint]]) + + ) diff --git a/src/test/clojure/cljs/analyzer_tests.clj b/src/test/clojure/cljs/analyzer_tests.clj new file mode 100644 index 0000000000..07aff9247f --- /dev/null +++ b/src/test/clojure/cljs/analyzer_tests.clj @@ -0,0 +1,1593 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.analyzer-tests + (:require + [cljs.analyzer :as ana] + [cljs.analyzer.api :as ana-api] + [cljs.compiler :as comp] + [cljs.env :as env] + [cljs.test-util :refer [unsplit-lines]] + [cljs.util :as util] + [clojure.java.io :as io] + [clojure.set :as set] + [clojure.string :as string] + [clojure.test :refer [is are deftest testing]])) + +(defn analyze + ([env form] + (env/ensure (ana/analyze env form))) + ([env form name] + (env/ensure (ana/analyze env form name))) + ([env form name opts] + (env/ensure (ana/analyze env form name opts)))) + +(defn collecting-warning-handler [state] + (fn [warning-type env extra] + (when (warning-type ana/*cljs-warnings*) + (when-let [s (ana/error-message warning-type extra)] + (swap! state conj s))))) + +;;****************************************************************************** +;; cljs-warnings tests +;;****************************************************************************** + +(def warning-forms + {:undeclared-var (let [v (gensym)] `(~v 1 2 3)) + :fn-arity '(do (defn x [a b] (+ a b)) + (x 1 2 3 4)) + :keyword-arity '(do (:argumentless-keyword-invocation))}) + +(defn warn-count [form] + (let [counter (atom 0) + tracker (fn [warning-type env & [extra]] + (when (warning-type ana/*cljs-warnings*) + (swap! counter inc)))] + (ana/with-warning-handlers [tracker] + (analyze (ana/empty-env) form)) + @counter)) + +(deftest no-warn + (is (every? zero? (map (fn [[name form]] (ana/no-warn (warn-count form))) warning-forms)))) + +(deftest all-warn + (is (every? #(= 1 %) (map (fn [[name form]] (ana/all-warn (warn-count form))) warning-forms)))) + +;; ============================================================================= +;; NS parsing + +(def ns-env (assoc-in (ana/empty-env) [:ns :name] 'cljs.user)) + +(deftest spec-validation + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:require {:foo :bar}))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only [lib.ns & options] and lib.ns specs supported in :require / :require-macros")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:require [:foo :bar]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Library name must be specified as a symbol in :require / :require-macros")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:require [baz.woz :as woz :refer [] :plop]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only :as alias, :refer (names) and :rename {from to} options supported in :require")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:require [baz.woz :as woz :refer [] :plop true]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only :as, :refer and :rename options supported in :require / :require-macros")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:require [baz.woz :as woz :refer [] :as boz :refer []]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Each of :as and :refer options may only be specified once in :require / :require-macros")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:refer-clojure :refer []))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only [:refer-clojure :exclude (names)] and optionally `:rename {from to}` specs supported")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:refer-clojure :rename [1 2]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only [:refer-clojure :exclude (names)] and optionally `:rename {from to}` specs supported")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:use [baz.woz :exclude []]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only [lib.ns :only (names)] and optionally `:rename {from to}` specs supported in :use / :use-macros")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:use [baz.woz]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only [lib.ns :only (names)] and optionally `:rename {from to}` specs supported in :use / :use-macros")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:use [baz.woz :only]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only [lib.ns :only (names)] and optionally `:rename {from to}` specs supported in :use / :use-macros")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:use [baz.woz :only [1 2 3]]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only [lib.ns :only (names)] and optionally `:rename {from to}` specs supported in :use / :use-macros")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:use [baz.woz :rename [1 2]]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only [lib.ns :only (names)] and optionally `:rename {from to}` specs supported in :use / :use-macros")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:use [foo.bar :rename {baz qux}]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only [lib.ns :only (names)] and optionally `:rename {from to}` specs supported in :use / :use-macros")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:use [baz.woz :only [foo] :only [bar]]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Each of :only and :rename options may only be specified once in :use / :use-macros")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:require [baz.woz :as []]))) + (catch Exception e + (.getMessage (.getCause e)))) + ":as must be followed by a symbol in :require / :require-macros")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:require [baz.woz :as woz] [noz.goz :as woz]))) + (catch Exception e + (.getMessage (.getCause e)))) + ":as alias must be unique")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:require [foo.bar :rename {baz qux}]))) + (catch Exception e + (.getMessage (.getCause e)))) + "Renamed symbol baz not referred")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:unless []))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only :refer-clojure, :require, :require-macros, :use, :use-macros, :require-global and :import libspecs supported. Got (:unless []) instead.")) + (is (.startsWith + (try + (analyze ns-env '(ns foo.bar (:require baz.woz) (:require noz.goz))) + (catch Exception e + (.getMessage (.getCause e)))) + "Only one "))) + +(def test-cenv (env/default-compiler-env)) +(def test-env (assoc-in (ana/empty-env) [:ns :name] 'cljs.core)) +(def test-core-env (env/default-compiler-env)) + +(binding [ana/*unchecked-if* false + ana/*analyze-deps* false] + (env/with-compiler-env test-core-env + (comp/with-core-cljs nil + (fn [])))) + +(defn core-env [] + (atom @test-core-env)) + +(defn analyze-forms [cenv xs] + (binding [ana/*unchecked-if* false + ana/*analyze-deps* false] + (env/with-compiler-env cenv + (ana/analyze-form-seq xs)))) + +(ana/no-warn + (env/with-compiler-env test-cenv + (binding [ana/*analyze-deps* false] + (ana/analyze-file (io/file "src/main/cljs/cljs/core.cljs"))))) + +;; ============================================================================= +;; Catching errors during macroexpansion + +(deftest test-defn-error + (is (.startsWith + (try + (analyze test-env '(defn foo 123)) + (catch Exception e + (.getMessage (.getCause e)))) + "Parameter declaration \"123\" should be a vector"))) + +;; ============================================================================= +;; ns desugaring + +(deftest test-cljs-975 + (let [spec '((:require [bar :refer [baz] :refer-macros [quux]] :reload))] + (is (= (set (ana/desugar-ns-specs spec)) + (set '((:require-macros (bar :refer [quux]) :reload) + (:require (bar :refer [baz]) :reload))))))) + +(deftest test-rewrite-cljs-aliases + (is (= (ana/rewrite-cljs-aliases + '((:require-macros (bar :refer [quux]) :reload) + (:require (clojure.spec.alpha :as s :refer [fdef]) :reload))) + '((:require-macros (bar :refer [quux]) :reload) + (:require (cljs.spec.alpha :as s :refer [fdef]) + (cljs.spec.alpha :as clojure.spec.alpha) :reload)))) + (is (= (ana/rewrite-cljs-aliases + '((:refer-clojure :exclude [first]) + (:require-macros (bar :refer [quux]) :reload) + (:require (clojure.spec.alpha :as s) :reload))) + '((:refer-clojure :exclude [first]) + (:require-macros (bar :refer [quux]) :reload) + (:require (cljs.spec.alpha :as s) (cljs.spec.alpha :as clojure.spec.alpha) :reload)))) + (is (= (ana/rewrite-cljs-aliases + '((:require-macros (bar :refer [quux]) :reload) + (:require clojure.spec.alpha :reload))) + '((:require-macros (bar :refer [quux]) :reload) + (:require (cljs.spec.alpha :as clojure.spec.alpha) :reload))))) + +;; ============================================================================= +;; Namespace metadata + +(deftest test-namespace-metadata + (binding [ana/*cljs-ns* ana/*cljs-ns*] + (is (= (do (analyze ns-env '(ns weeble.ns {:foo bar})) + (meta ana/*cljs-ns*)) + {:foo 'bar})) + + (is (= (do (analyze ns-env '(ns ^{:foo bar} weeble.ns)) + (meta ana/*cljs-ns*)) + {:foo 'bar})) + + (is (= (do (analyze ns-env '(ns ^{:foo bar} weeble.ns {:baz quux})) + (meta ana/*cljs-ns*)) + {:foo 'bar :baz 'quux})) + + (is (= (do (analyze ns-env '(ns ^{:foo bar} weeble.ns {:foo baz})) + (meta ana/*cljs-ns*)) + {:foo 'baz})) + + (is (= (meta (:name (analyze ns-env '(ns weeble.ns {:foo bar})))) + {:foo 'bar})) + + (is (= (meta (:name (analyze ns-env '(ns ^{:foo bar} weeble.ns)))) + {:foo 'bar})) + + (is (= (meta (:name (analyze ns-env '(ns ^{:foo bar} weeble.ns {:baz quux})))) + {:foo 'bar :baz 'quux})) + + (is (= (meta (:name (analyze ns-env '(ns ^{:foo bar} weeble.ns {:foo baz})))) + {:foo 'baz})))) + +(deftest test-cljs-1105 + ;; munge turns - into _, must preserve the dash first + (is (not= (ana/gen-constant-id :test-kw) + (ana/gen-constant-id :test_kw)))) + +(deftest test-symbols-munge-cljs-1432 + (is (not= (ana/gen-constant-id :$) + (ana/gen-constant-id :.))) + (is (not= (ana/gen-constant-id '$) + (ana/gen-constant-id '.)))) + +(deftest test-unicode-munging-cljs-1457 + (is (= (ana/gen-constant-id :C♯) 'cst$kw$C_u266f_) + (= (ana/gen-constant-id 'C♯) 'cst$sym$C_u266f_))) + +;; Constants + +(deftest test-constants + (is (.startsWith + (try + (analyze test-env '(do (def ^:const foo 123) (def foo 246))) + (catch Exception e + (.getMessage (.getCause e)))) + "Can't redefine a constant")) + (is (.startsWith + (try + (analyze test-env '(do (def ^:const foo 123) (set! foo 246))) + (catch Exception e + (.getMessage (.getCause e)))) + "Can't set! a constant"))) + +(deftest test-cljs-1508-rename + (binding [ana/*cljs-ns* ana/*cljs-ns*] + (let [parsed-ns (env/with-compiler-env test-cenv + (analyze test-env + '(ns foo.core + (:require [clojure.set :as set :refer [intersection] :rename {intersection foo}]))))] + (is (nil? (-> parsed-ns :uses (get 'foo)))) + (is (nil? (-> parsed-ns :uses (get 'intersection)))) + (is (some? (-> parsed-ns :renames (get 'foo)))) + (is (= (-> parsed-ns :renames (get 'foo)) + 'clojure.set/intersection))) + (is (env/with-compiler-env test-cenv + (analyze test-env + '(ns foo.core + (:use [clojure.set :only [intersection] :rename {intersection foo}]))))) + (is (= (env/with-compiler-env (atom {::ana/namespaces + {'foo.core {:renames '{foo clojure.set/intersection}}}}) + (select-keys (ana/resolve-var {:ns {:name 'foo.core}} 'foo) + [:name :ns])) + '{:name clojure.set/intersection + :ns clojure.set})) + (let [rwhen (env/with-compiler-env (atom (update-in @test-cenv [::ana/namespaces] + merge {'foo.core {:rename-macros '{always cljs.core/when}}})) + (ana/resolve-macro-var {:ns {:name 'foo.core}} 'always))] + (is (= (-> rwhen :name) + 'cljs.core/when))) + (let [parsed-ns (env/with-compiler-env test-cenv + (analyze test-env + '(ns foo.core + (:refer-clojure :rename {when always + map core-map}))))] + (is (= (-> parsed-ns :excludes) '#{when map})) + (is (= (-> parsed-ns :rename-macros) '{always cljs.core/when})) + (is (= (-> parsed-ns :renames) '{core-map cljs.core/map}))) + (is (thrown? Exception (env/with-compiler-env test-cenv + (analyze test-env + '(ns foo.core + (:require [clojure.set :rename {intersection foo}])))))))) + +(deftest test-cljs-1274 + (let [test-env (assoc-in (ana/empty-env) [:ns :name] 'cljs.user)] + (binding [ana/*cljs-ns* ana/*cljs-ns*] + (is (thrown-with-cause-msg? Exception #"Can't def ns-qualified name in namespace foo.core" + (analyze test-env '(def foo.core/foo 43)))) + (is (analyze test-env '(def cljs.user/foo 43)))))) + +(deftest test-cljs-1702 + (let [ws (atom [])] + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (env/with-compiler-env test-cenv + (ana/analyze-form-seq + '[(ns test.cljs-1702-a) + (def ^:private a 3) + (def ^:private b 3) + (defn- test-fn-a [a] a) + (defn- test-fn-b [a] b)]) + (ana/analyze-form-seq + '[(ns test.cljs-1702-b) + (test.cljs-1702-a/test-fn-a 1) + (#'test.cljs-1702-a/test-fn-b 1) + test.cljs-1702-a/a + @#'test.cljs-1702-a/b])) + (is (= ["var: test.cljs-1702-a/test-fn-a is not public" + "var: test.cljs-1702-a/a is not public"] @ws))))) + +(deftest test-cljs-1763 + (let [parsed (ana/parse-ns-excludes {} '())] + (is (= parsed + {:excludes #{} + :renames {}})) + (is (set? (:excludes parsed))))) + + +(deftest test-cljs-2292 + (let [parsed (ana/parse-ns-excludes {} '((:refer-clojure :rename {map clj-map})))] + (is (= parsed + '{:excludes #{map} + :renames {map clj-map}})) + (is (set? (:excludes parsed))))) + +(deftest test-parse-global-refer + (let [parsed (ana/parse-global-refer-spec {} + '((:refer-global :only [Date Symbol] :rename {Symbol JSSymbol})))] + (is (= parsed + '{:use {Date js} + :rename {JSSymbol js/Symbol}})))) + +(deftest test-parse-require-global + (let [cenv (atom {}) + deps (atom []) + parsed (ana/parse-global-require-spec {} cenv deps (atom {:fns {}}) + '[React :refer [createElement] :as react])] + (println (pr-str @cenv) (pr-str @deps)) + (is (= parsed + '{:require {react React + React React} + :use {createElement React}}))) + (let [cenv (atom {}) + deps (atom []) + parsed (ana/parse-global-require-spec {} cenv deps (atom {:fns {}}) + '[React :refer [createElement] :rename {createElement create} :as react])] + (is (= parsed + '{:require {react React + React React} + :rename {create React/createElement}})))) + +(deftest test-cljs-1785-js-shadowed-by-local + (let [ws (atom [])] + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (analyze ns-env + '(fn [foo] + (let [x js/foo] + (println x))))) + (is (.startsWith (first @ws) "js/foo is shadowed by a local")))) + +(deftest test-cljs-2005 + (let [ws (atom [])] + (try + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (analyze (ana/empty-env) + '(defn myfun + ([x] x) + ([x] x)))) + (catch Exception _)) + (is (.startsWith (first @ws) "myfun: Can't have 2 overloads with same arity")))) + +(deftest test-cljs-2863 + (let [ws (atom [])] + (try + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (analyze (ana/empty-env) + '(defn myfun + ([x] x) + ([& xs] xs)))) + (catch Exception _)) + (is (.startsWith (first @ws) "myfun: Can't have fixed arity function with more params than variadic function"))) + + (let [ws (atom [])] + (try + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (analyze (ana/empty-env) + '(defn myfun + ([& x] x) + ([& xs] xs)))) + (catch Exception _)) + (is (.startsWith (first @ws) "myfun: Can't have more than 1 variadic overload")))) + +(deftest test-canonicalize-specs + (is (= (ana/canonicalize-specs '((quote [clojure.set :as set]))) + '([clojure.set :as set]))) + (is (= (ana/canonicalize-specs '(:exclude (quote [map mapv]))) + '(:exclude [map mapv]))) + (is (= (ana/canonicalize-specs '(:require (quote [clojure.set :as set]))) + '(:require [clojure.set :as set]))) + (is (= (ana/canonicalize-specs '(:require (quote clojure.set))) + '(:require [clojure.set]))) + (is (= (ana/canonicalize-specs '(:refer-clojure :exclude '[map] :rename '{map core-map})) + '(:refer-clojure :exclude [map] :rename {map core-map})))) + +(deftest test-canonicalize-import-specs + (is (= (ana/canonicalize-import-specs '(:import (quote [goog Uri]))) + '(:import [goog Uri]))) + (is (= (ana/canonicalize-import-specs '(:import (quote (goog Uri)))) + '(:import (goog Uri)))) + (is (= (ana/canonicalize-import-specs '(:import (quote goog.Uri))) + '(:import goog.Uri)))) + +(deftest test-cljs-1346 + (testing "`ns*` special form conformance" + (let [test-env (ana/empty-env)] + (is (= (-> (ana/parse-ns '((require '[clojure.set :as set]))) :requires) + '#{cljs.core clojure.set}))) + (binding [ana/*cljs-ns* ana/*cljs-ns* + ana/*cljs-warnings* nil] + (let [test-env (ana/empty-env)] + (is (= (-> (analyze test-env '(require '[clojure.set :as set])) :requires vals set) + '#{clojure.set}))) + (let [test-env (ana/empty-env)] + (is (= (-> (analyze test-env '(require '[clojure.set :as set :refer [union intersection]])) :uses keys set) + '#{union intersection}))) + (let [test-env (ana/empty-env)] + (is (= (-> (analyze test-env '(require '[clojure.set :as set] + '[clojure.string :as str])) + :requires vals set) + '#{clojure.set clojure.string}))) + (let [test-env (ana/empty-env)] + (is (= (-> (analyze test-env '(require-macros '[cljs.test :as test])) :require-macros vals set) + '#{cljs.test}))) + (let [test-env (ana/empty-env) + parsed (analyze test-env '(require-macros '[cljs.test :as test :refer [deftest is]]))] + (is (= (-> parsed :require-macros vals set) + '#{cljs.test})) + (is (= (-> parsed :use-macros keys set) + '#{is deftest}))) + (let [test-env (ana/empty-env) + parsed (analyze test-env '(require '[cljs.test :as test :refer-macros [deftest is]]))] + (is (= (-> parsed :requires vals set) + '#{cljs.test})) + (is (= (-> parsed :require-macros vals set) + '#{cljs.test})) + (is (= (-> parsed :use-macros keys set) + '#{is deftest}))) + (let [test-env (ana/empty-env) + parsed (analyze test-env '(use '[clojure.set :only [intersection]]))] + (is (= (-> parsed :uses keys set) + '#{intersection})) + (is (= (-> parsed :requires) + '{clojure.set clojure.set}))) + (let [test-env (ana/empty-env) + parsed (analyze test-env '(use-macros '[cljs.test :only [deftest is]]))] + (is (= (-> parsed :use-macros keys set) + '#{deftest is})) + (is (= (-> parsed :require-macros) + '{cljs.test cljs.test})) + (is (nil? (-> parsed :requires)))) + (let [test-env (ana/empty-env) + parsed (analyze test-env '(import '[goog.math Long Integer]))] + (is (= (-> parsed :imports) + (-> parsed :requires) + '{Long goog.math.Long + Integer goog.math.Integer}))) + (let [test-env (ana/empty-env) + parsed (analyze test-env '(refer-clojure :exclude '[map mapv]))] + (is (= (-> parsed :excludes) + '#{map mapv}))) + (let [test-env (ana/empty-env) + parsed (analyze test-env '(refer-clojure :exclude '[map mapv] :rename '{mapv core-mapv}))] + (is (= (-> parsed :excludes) + '#{map mapv}))))) + (testing "arguments to require should be quoted" + (binding [ana/*cljs-ns* ana/*cljs-ns* + ana/*cljs-warnings* nil] + (is (thrown-with-cause-msg? Exception #"Arguments to require must be quoted" + (analyze test-env + '(require [clojure.set :as set])))) + (is (thrown-with-cause-msg? Exception #"Arguments to require must be quoted" + (analyze test-env + '(require clojure.set)))))) + (testing "`:ns` and `:ns*` should throw if not `:top-level`" + (binding [ana/*cljs-ns* ana/*cljs-ns* + ana/*cljs-warnings* nil] + (are [analyzed] (thrown-with-cause-msg? Exception + #"Namespace declarations must appear at the top-level." + analyzed) + (analyze test-env + '(def foo + (ns foo.core + (:require [clojure.set :as set])))) + (analyze test-env + '(fn [] + (ns foo.core + (:require [clojure.set :as set])))) + (analyze test-env + '(map #(ns foo.core + (:require [clojure.set :as set])) [1 2]))) + (are [analyzed] (thrown-with-cause-msg? Exception + #"Calls to `require` must appear at the top-level." + analyzed) + (analyze test-env + '(def foo + (require '[clojure.set :as set]))) + (analyze test-env + '(fn [] (require '[clojure.set :as set]))) + (analyze test-env + '(map #(require '[clojure.set :as set]) [1 2])))))) + +(deftest test-analyze-refer-global + (testing "refer-global macro expr return expected AST" + (binding [ana/*cljs-ns* ana/*cljs-ns* + ana/*cljs-warnings* nil] + (let [test-env (ana/empty-env)] + (is (= (-> (analyze test-env '(refer-global :only '[Date])) :uses vals set) + '#{js})))))) + +(deftest test-gen-user-ns + ;; note: can't use `with-redefs` because direct-linking is enabled + (let [s "src/cljs/foo.cljs" + sha (util/content-sha s)] + (is (= (ana/gen-user-ns s) (symbol (str "cljs.user.foo" (apply str (take 7 sha))))))) + (let [a "src/cljs/foo.cljs" + b "src/cljs/foo.cljc"] + ;; namespaces should have different names because the filename hash will be different + (is (not= (ana/gen-user-ns a) (ana/gen-user-ns b))) + ;; specifically, only the hashes should differ + (let [nsa (str (ana/gen-user-ns a)) + nsb (str (ana/gen-user-ns b))] + (is (not= (.substring nsa (- (count nsa) 7)) (.substring nsb (- (count nsb) 7)))) + (is (= (.substring nsa 0 (- (count nsa) 7)) (.substring nsb 0 (- (count nsb) 7))))))) + +(deftest test-cljs-1536 + (let [parsed (env/with-compiler-env test-cenv + (analyze (assoc test-env :def-emits-var true) + '(def x 1)))] + (is (some? (:var-ast parsed)))) + (let [parsed (env/with-compiler-env test-cenv + (analyze (assoc test-env :def-emits-var true) + '(let [y 1] (def y 2))))] + (is (some? (-> parsed :body :ret :var-ast))))) + +(def analyze-ops-cenv (atom @test-cenv)) + +(defn ana' [form] + (env/with-compiler-env analyze-ops-cenv + (analyze test-env form))) + +(defmacro ana [form] + `(ana' '~form)) + +(defn prs-ana [fstr] + (env/with-compiler-env analyze-ops-cenv + (let [[form] (ana/forms-seq* + (java.io.StringReader. fstr))] + (ana' form)))) + +(def juxt-op-val (juxt :op :val)) + +(deftest analyze-ops + ;constants + (is (empty? (-> (ana 1) :children))) + (is (= (-> (ana 1) juxt-op-val) [:const 1])) + (is (empty? (-> (ana :a) :children))) + (is (= (-> (ana :a) juxt-op-val) [:const :a])) + (is (= (-> (ana ::a) juxt-op-val) [:const ::a])) + (is (= (-> (ana "abc") juxt-op-val) [:const "abc"])) + ;variables + ; FIXME deviates from tools.analyzer, :name is always unqualified + (is (= [:var 'cljs.core 'cljs.core/inc 'inc] (-> (ana inc) ((juxt :op :ns :name :form))))) + (is (= [:var 'cljs.core 'cljs.core/inc 'cljs.core/inc] (-> (ana cljs.core/inc) ((juxt :op :ns :name :form))))) + ;; dotted :var + (is (= [:host-field 'bar :host-field 'foo :var 'cljs.core/inc 'cljs.core/inc] + (-> (ana inc.foo.bar) + ((juxt :op + :field + (comp :op :target) + (comp :field :target) + (comp :op :target :target) + (comp :name :target :target) + (comp :name :info :target :target)))))) + ;; dotted :local + (is (= [:host-field 'c :host-field 'b :local 'a 'a] + (-> (ana (let [a 1] a.b.c)) :body :ret + ((juxt :op + :field + (comp :op :target) + (comp :field :target) + (comp :op :target :target) + (comp :name :target :target) + (comp :name :info :target :target)))))) + ;do + (is (= (-> (ana (do 1 2)) :op) :do)) + (is (= (-> (ana (do 1 2)) :children) [:statements :ret])) + ; :statements + (is (vector? (-> (ana (do)) :statements))) + (is (vector? (-> (ana (do 1)) :statements))) + (is (vector? (-> (ana (do 1 2)) :statements))) + (is (= (-> (ana (do 1 2)) :statements first :op) :const)) + ; :ret + (is (= (-> (ana (do)) :ret juxt-op-val) [:const nil])) + (is (= (-> (ana (do nil)) :ret juxt-op-val) [:const nil])) + (is (= (-> (ana (do 1)) :ret juxt-op-val) [:const 1])) + (is (= (-> (ana (do 1 2)) :ret juxt-op-val) [:const 2])) + ;let + (is (= (-> (ana (let [])) :op) :let)) + (is (= (-> (ana (let [a 1] a)) :children) [:bindings :body])) + ; :bindings + (is ((every-pred vector? empty?) (-> (ana (let [])) :bindings))) + (is (vector? (-> (ana (let [a 1] a)) :bindings))) + (is (vector? (-> (ana (let [a 1 b 2] a)) :bindings))) + (is (= (-> (ana (let [a 1] a)) :bindings first :op) :binding)) + (is (= (-> (ana (let [a 1] a)) :bindings first :init :op) :const)) + ; :body + (is (= (-> (ana (let [a 1] a)) :body :op) :do)) + ;local + (is (empty? (-> (ana (let [a 1] a)) :body :ret :children))) + (is (= (-> (ana (let [a 1] a)) :body :ret :op) :local)) + (is (= (-> (ana (let [a 1] a)) :body :ret :name) 'a)) + (is (= (-> (ana (let [a 1] a)) :body :ret :form) 'a)) + (is (map? (-> (ana (let [a 1] a)) :body :ret :env))) + ;; dotted :local + (is (= [:host-field 'c :host-field 'b :local 'a] + (-> (ana (let [a 1] a.b.c)) :body :ret + ((juxt :op + :field + (comp :op :target) + (comp :field :target) + (comp :op :target :target) + (comp :name :target :target)))))) + ;local shadow + (is (= 'alert + (ana/no-warn (-> (ana (let [alert 1] js/alert)) :body + :env :locals + (get 'alert) + :name)))) + (is (= [:local 'alert] + (ana/no-warn (-> (ana (let [alert 1] js/alert)) :body :ret + ((juxt :op :name)))))) + ;loop + (is (= (-> (ana (loop [])) :op) :loop)) + (is (= (-> (ana (loop [a 1])) :bindings first :op) :binding)) + (is (= (-> (ana (loop [a 1] a)) :bindings first :init :op) :const)) + (is (= (-> (ana (loop [a 1] a)) :body :ret :local) :loop)) + (is (= (-> (ana (loop [a 1] (recur 1))) :children) [:bindings :body])) + ;recur + (is (= (-> (ana (loop [a 1] (recur 1))) :body :ret :op) :recur)) + (is (= (-> (ana (loop [a 1] (recur 1))) :body :ret :children) [:exprs])) + ; :exprs + (is ((every-pred vector? empty?) (-> (ana (loop [] (recur))) :body :ret :exprs))) + (is (vector? (-> (ana (loop [a 1] (recur 1))) :body :ret :exprs))) + (is (vector? (-> (ana (loop [a 1 b 2] (recur 1 2))) :body :ret :exprs))) + (is (= (-> (ana (loop [a 1] (recur 1))) :body :ret :exprs first :op) :const)) + (is (= (-> (ana (loop [a 1 b 2] (recur 1 2))) :body :ret :exprs second :op) :const)) + ;try + (is (= (-> (ana (try)) :op) :try)) + (is (= (-> (ana (try)) :children) [:body :catch])) ;; not sure why :catch? + (is (= (-> (ana (try (catch :default e))) :children) [:body :catch])) + (is (= (-> (ana (try (catch :default e) (finally))) :children) [:body :catch :finally])) + (is (= (-> (ana (try (finally))) :children) [:body :catch :finally])) ;; not sure why :catch? + ; :name + (is (symbol? (-> (ana (try (catch :default a))) :name))) + (is (nil? (-> (ana (try)) :name))) + ; :catch + (is (keyword? (-> (ana (try (catch :default a))) :catch :op))) + ; :finally + (is (= (-> (ana (try (finally 1))) :finally :op) :do)) + (is (= (-> (ana (try (finally 1))) :finally :ret :op) :const)) + ;TODO case + (is (= (-> (ana (case 1)) :op) :let)) + (is (= (-> (ana (case 1)) :body :ret :op) :case)) + (is (= (-> (ana (case 1)) :body :ret :children) [:test :nodes :default])) + ;; :test + (is (= (-> (ana (case 1)) :body :ret :test :op) :local)) + ;; :nodes + (is (vector? (-> (ana (case 1)) :body :ret :nodes))) + (is (vector? (-> (ana (case 1 :a 1)) :body :ret :nodes))) + (is (vector? (-> (ana (case 1 (:a :b) 1)) :body :ret :nodes))) + ;; :tests + (is (vector? + (-> (ana (case 1 :a 1)) :body :ret :nodes first :tests))) + (is (vector? + (-> (ana (case 1 :a 1 :b 2)) :body :ret :nodes first :tests))) + (is (= (-> (ana (case 1 :a 1)) :body :ret :nodes first :tests first :op) + :case-test)) + (is (= (-> (ana (case 1 :a 1)) :body :ret :nodes first :tests first :test juxt-op-val) + [:const "a"])) + (is (= (-> (ana (case 1 :a 1 :b 2)) :body :ret :nodes second :tests first :test juxt-op-val) + [:const "b"])) + (is (= (-> (ana (case 1 :a 1 (:b :faz) 2)) :body :ret :nodes (nth 1) :tests second :test juxt-op-val) + [:const "faz"])) + ;; :thens + (is (= (-> (ana (case 1 :a 3)) :body :ret :nodes first :then :op) + :case-then)) + (is (= (-> (ana (case 1 :a 3)) :body :ret :nodes first :then :then juxt-op-val) + [:const 3])) + (is (= (-> (ana (case 1 :a 3 :b 4)) :body :ret :nodes second :then :then juxt-op-val) + [:const 4])) + (is (= (-> (ana (case 1 :a 3 (:b :c) 4)) :body :ret :nodes (nth 1) :then :then juxt-op-val) + [:const 4])) + ;; :default + (is (= :throw (-> (ana (case 1)) :body :ret :default :op))) + (is (= [:const 2] (-> (ana (case 1 2)) :body :ret :default juxt-op-val))) + ;def +;TODO :meta node + (is (= :def (-> (ana (def a)) :op))) + (is (= [:var] (-> (ana (def a)) :children))) + (is (= [:var :init] (-> (ana (def a 1)) :children))) + ; :ns/:name + (is (= ['cljs.core 'cljs.core/a] (-> (ana (def a 1)) ((juxt :ns :name))))) + ; :var + (is (= [:var 'cljs.core 'cljs.core/a 'a] + (-> (ana (def a 1)) :var + ((juxt :op :ns :name :form))))) + ; :init + (is (-> (ana (def a)) (contains? :init) false?)) + (is (= [:const 1] (-> (ana (def a 1)) :init juxt-op-val))) + ;deftype + (is (= :deftype (-> (ana (deftype A [])) :statements first :op))) + (is (= [:body] (-> (ana (deftype A [])) :statements first :children))) + ; :body + (is (= :do (-> (ana (deftype A [a] Object (toString [this] a))) :statements first :body :op))) + ; field reference + (is (= [:local :field] + (-> (ana (deftype A [a] Object (toString [this] a))) + :statements first :body :ret :val :methods + first :body :ret :body :ret + ((juxt :op :local))))) + ;defrecord + (is (= :defrecord (-> (ana (defrecord Ab [])) :body :statements first :ret :op))) + (is (= [:body] (-> (ana (defrecord Ab [])) :body :statements first :ret :children))) + ; :body + (is (= :do (-> (ana (defrecord Ab [] Object (toString [this] "a"))) :body :statements first :ret :body :op))) + ;fn + (is (= :fn (-> (ana (fn [])) :op))) + (is (= [:methods] (-> (ana (fn [])) :children))) + (is (= [:local :methods] (-> (ana (fn a [])) :children))) + ; :local + (is (-> (ana (fn [])) (contains? :local) false?)) + (is (= + [:binding 'b :fn] + (-> (ana (fn b [& a])) + :local + ((juxt :op :name :local))))) + (is (= + [:local 'b :fn] + (-> (ana (fn b [] b)) + :methods + first + :body + :ret + ((juxt :op :name :local))))) + (is (= + [:binding 'b :fn] + (-> (ana (fn b [] b)) + :methods + first + :body + :ret + :env + :locals + (get 'b) + ((juxt :op :name :local))))) + ; :variadic? + (is (true? (-> (ana (fn [& a])) :variadic?))) + (is (false? (-> (ana (fn [])) :variadic?))) + ; :methods + (is (vector? (-> (ana (fn [])) :methods))) + (is (vector? (-> (ana (fn ([]) ([a]))) :methods))) + ;fn-method + (is (= :fn-method (-> (ana (fn [])) :methods first :op))) + (is (= [:params :body] (-> (ana (fn [])) :methods first :children))) + (is (= [:params :body] (-> (ana (fn [a])) :methods first :children))) + ; :fixed-arity + (is (= 0 (-> (ana (fn [])) :methods first :fixed-arity))) + (is (= 1 (-> (ana (fn [a])) :methods first :fixed-arity))) + (is (= 2 (-> (ana (fn [a b & c])) :methods first :fixed-arity))) + ; :variadic? + (is (true? (-> (ana (fn [a b & c])) :variadic?))) + (is (false? (-> (ana (fn [a b])) :variadic?))) + ; :body + (is (= [:const 1] (-> (ana (fn [] 1)) :methods first :body :ret juxt-op-val))) + ; :params + (is (vector? + (-> (ana (fn [])) :methods first :params))) + (is (vector? + (-> (ana (fn [a b])) :methods first :params))) + (is (= [:binding 'a :arg] + (-> (ana (fn [a b])) :methods first :params + first ((juxt :op :name :local))))) + (is (= [:binding 'b :arg] + (-> (ana (fn [a b])) :methods first :params + second ((juxt :op :name :local))))) + ;if + (is (= :if (-> (ana (if 1 2)) :op))) + (is (= :if (-> (ana (if 1 2 3)) :op))) + (is (= [:test :then :else] (-> (ana (if 1 2 3)) :children))) + (is (= [:test :then :else] (-> (ana (if 1 2)) :children))) + ; :test + (is (= [:const 1] (-> (ana (if 1 2)) :test juxt-op-val))) + (is (= [:const 1] (-> (ana (if 1 2 3)) :test juxt-op-val))) + ; :then + (is (= [:const 2] (-> (ana (if 1 2)) :then juxt-op-val))) + (is (= [:const 2] (-> (ana (if 1 2 3)) :then juxt-op-val))) + ; :else + (is (= [:const nil] (-> (ana (if 1 2)) :else juxt-op-val))) + (is (= [:const 3] (-> (ana (if 1 2 3)) :else juxt-op-val))) + ;invoke + (is (= :invoke (-> (ana (:a 1)) :op))) + (is (= [:fn :args] (-> (ana (:a 1)) :children))) + (is ((every-pred vector? empty?) (-> (ana (#'str)) :args))) + (is (vector? (-> (ana (:a 1)) :args))) + (is (vector? (-> (ana (:a 1 2)) :args))) + ; :fn + (is (= :the-var (-> (ana (#'str)) :fn :op))) + ; :invoke + (is (= [:const 1] (-> (ana (:a 1)) :args first juxt-op-val))) + (is (= [:const 2] (-> (ana (:a 1 2)) :args second juxt-op-val))) + ;js-array + (is (= :js-array (-> (prs-ana "#js ['a]") :op))) + (is (= [:items] (-> (prs-ana "#js ['a]") :children))) + (is (vector? (-> (prs-ana "#js ['a]") :items))) + (is (= 'array (-> (prs-ana "#js ['a]") :tag))) + (is (= [:const :a] (-> (prs-ana "#js [:a]") :items first juxt-op-val))) + ;js-object + (is (= :js-object (-> (prs-ana "#js {:a 1}]") :op))) +;; FIXME :keys should be an expression too + (is (= [:vals] (-> (prs-ana "#js {:a 1}") :children))) + (is (vector? (-> (prs-ana "#js {:a 1}") :vals))) + (is (= :a (-> (prs-ana "#js {:a 1}") :keys first))) + (is (vector? (-> (prs-ana "#js {:a 1}") :keys))) + (is (= [:const 1] (-> (prs-ana "#js {:a 1}") :vals first juxt-op-val))) + ;js* + (is (= :js (-> (ana (js* "~{}" 'food)) :op))) + (is (= [:args] (-> (ana (js* "~{}" 'food)) :children))) + (is (vector? (-> (ana (js* "~{}" 'food)) :args))) + (is (= [:const 'food] (-> (ana (js* "~{}" 'food)) :args first :expr juxt-op-val))) +;; FIXME why not a vector? + ;(is (vector? (-> (ana (js* "~{} / ~{}" 1 2)) :segs))) + (is (= ["" " / " ""] (-> (ana (js* "~{} / ~{}" 1 2)) :segs))) + ;letfn + (is (= :letfn + (-> (ana (letfn [(my-inc [a] (inc a))] + (my-inc 1))) + :op))) + (is (= [:bindings :body] + (-> (ana (letfn [(my-inc [a] (inc a))] + (my-inc 1))) + :children))) + ; :bindings + (is (vector? + (-> (ana (letfn [(my-inc [a] (inc a))] + (my-inc 1))) + :bindings))) + (is (vector? + (-> (ana (letfn [(my-inc [a] (inc a))] + (my-inc 1))) + :bindings))) + (is (= :binding + (-> (ana (letfn [(my-inc [a] (inc a))] + (my-inc 1))) + :bindings + first + :op))) + (is (= :fn + (-> (ana (letfn [(my-inc [a] (inc a))] + (my-inc 1))) + :bindings + first + :init + :op))) + (is (= :arg + (-> (ana (letfn [(my-inc [a] a)] + (my-inc 1))) + :bindings + first + :init + :methods + first + :body + :ret + :local))) + ; :body + (is (= :invoke + (-> (ana (letfn [(my-inc [a] (inc a))] + (my-inc 1))) + :body :ret :op))) + (is (= [:local :letfn] + (-> (ana (letfn [(my-inc [a] (inc a))] + (my-inc 1))) + :body :ret :fn ((juxt :op :local))))) + ;map + (is (= :map (-> (ana {:a 1}) :op))) + (is (= [:keys :vals] (-> (ana {:a 1}) :children))) + ; :keys + (is ((every-pred vector? empty?) (-> (ana {}) :keys))) + (is (vector? (-> (ana {:a 1}) :keys))) + (is (= [:const :a] (-> (ana {:a 1}) :keys first juxt-op-val))) + ; :vals + (is ((every-pred vector? empty?) (-> (ana {}) :vals))) + (is (vector? (-> (ana {:a 1}) :vals))) + (is (= [:const 1] (-> (ana {:a 1}) :vals first juxt-op-val))) + ;new + (is (= :new + (-> (ana (do (deftype Person [a]) (Person. 1))) + :ret + :op))) + (is (= [:class :args] + (-> (ana (do (deftype Person [a]) (Person. 1))) + :ret + :children))) + ; :class + (is (= [:var 'cljs.core 'cljs.core/Person] + (-> (ana (do (deftype Person [a]) (Person. 1))) + :ret + :class + ((juxt :op :ns :name))))) + ; :args + (is ((every-pred vector? empty?) + (-> (ana (do (deftype Noarg []) (Noarg.))) + :ret + :args))) + (is (= [:const 1] + (-> (ana (do (deftype Person [a]) (Person. 1))) + :ret + :args + first + juxt-op-val))) + ;set + (is (= :set (-> (ana #{:a :b}) :op))) + (is (= [:items] (-> (ana #{:a :b}) :children))) + ; :items + (is ((every-pred vector? empty?) (-> (ana #{}) :items))) + (is (vector? (-> (ana #{:a}) :items))) + (is (vector? (-> (ana #{:a :c :b}) :items))) + (is (= [:const :a] (-> (ana #{:a}) :items first juxt-op-val))) + ;set! + (is (= :set! + (-> (ana (do (def a 1) (set! a "Hi!"))) + :ret :op))) + (is (= [:target :val] + (-> (ana (do (def a 1) (set! a "Hi!"))) + :ret :children))) + ; :target + (is (= [:var 'cljs.core 'cljs.core/a] + (-> (ana (do (def a 1) (set! a "Hi!"))) + :ret :target ((juxt :op :ns :name))))) + ; :val + (is (= [:const "Hi!"] + (-> (ana (do (def a 1) (set! a "Hi!"))) + :ret :val juxt-op-val))) + ;the-var + (is (= :the-var (-> (ana #'+) :op))) + (is (= [:var :sym :meta] (-> (ana #'+) :children))) + ; :var + (is (= [:var 'cljs.core 'cljs.core/+] + (-> (ana #'+) :var ((juxt :op :ns :name))))) + ; :sym + (is (= 'cljs.core/+ (-> (ana #'+) :sym :expr :val))) + ; :meta + (is (= :map (-> (ana #'+) :meta :op))) + (is (empty? + ; ensure at least these entries are present + (set/difference + #{:name :tag :arglists + :line :column :end-line :end-column + :ns :file :doc :test :top-fn} + (->> (ana #'+) :meta :keys (map :val))))) + (run! + (fn [[k v]] + ; ensure entries map to sane things + (case (:val k) + :name (is (= '+ (-> v :expr :val))) + :tag (is (= 'number (-> v :expr :val))) + :arglists (is (= :quote (:op v))) + ;:row (is (= :quote (:op v))) + (:line :column :end-line :end-column) (number? (:val v)) + :ns (is (symbol? (-> v :expr :val))) + :file (is (string? (-> v :expr :val))) + :doc (is (string? (-> v :expr :val))) + :test (is (= :if (:op v))) + :top-fn (do (is (= :const (:op (:expr v)))) + (is (map? (:val (:expr v))))) + ;default + nil)) + (apply zipmap (-> (ana #'+) :meta ((juxt :keys :vals))))) + ;throw + (is (= :throw (-> (ana (throw (js/Error. "bad"))) :op))) + (is (= [:exception] (-> (ana (throw (js/Error. "bad"))) :children))) + ; :exception + (is (= [:js-var 'js 'js/Error] (-> (ana (throw (js/Error. "bad"))) :exception + :class + ((juxt :op :ns :name))))) + ;vector + (is (= :vector (-> (ana [1]) :op))) + (is (= [:items] (-> (ana [1]) :children))) + ; :items + (is ((every-pred vector? empty?) (-> (ana []) :items))) + (is (vector? (-> (ana [1]) :items))) + (is (= [:const 1] (-> (ana [1]) :items first juxt-op-val))) + ;with-meta + (is (= :with-meta (-> (ana ^:blah (fn [])) :op))) + (is (= [:meta :expr] (-> (ana ^:blah (fn [])) :children))) + (is (= :const (-> (ana '^:foo a) :expr :op))) + ; :meta + (is (= :map (-> (ana ^:blah (fn [])) :meta :op))) + (is (= [:const :blah] (-> (ana ^:blah (fn [])) :meta :keys first juxt-op-val))) + (is (= [:const true] (-> (ana ^:blah (fn [])) :meta :vals first juxt-op-val))) + ;(is (= [:const :foo] (-> (ana '^:foo a) :expr :meta :keys first juxt-op-val))) + ;(is (= [:const true] (-> (ana '^:foo a) :expr :meta :vals first juxt-op-val))) + ; :expr + (is (= :fn (-> (ana ^:blah (fn [])) :expr :op))) + ;(is (= :const (-> (ana '^:foo a) :expr :expr :op))) + ;host-field + (is (= :host-field (-> (ana (.-field 'a)) :op))) + (is (= [:target] (-> (ana (.-field 'a)) :children))) + (is (= 'field (-> (ana (.-field 'a)) :field))) + ; :target + (is (= [:const 'a] (-> (ana (.-field 'a)) :target :expr juxt-op-val))) + ;host-call + (is (= :host-call (-> (ana (.call 'a)) :op))) + (is (= [:target :args] (-> (ana (.call 'a)) :children))) + (is (= 'call (-> (ana (.call 'a)) :method))) + ; :target + (is (= [:const 'a] (-> (ana (.call 'a)) :target :expr juxt-op-val))) + ; :args + (is ((every-pred vector? empty?) (-> (ana (.call 'a)) :args))) + (is (= [:const 1] (-> (ana (.call 'a 1)) :args first juxt-op-val))) + ;ns + (is (binding [ana/*cljs-ns* 'cljs.user] + (= :ns (-> (ana (ns fazz.foo)) :op)))) + ;ns* + (is (binding [ana/*cljs-ns* 'cljs.user] + (= :ns* (-> (ana (refer-clojure :exclude '[locking])) :op)))) + ;quote + (is (= :quote (-> (ana (quote a)) :op))) + (is (= [:expr] (-> (ana (quote a)) :children))) + (is (map? (-> (ana (quote a)) :env))) + (is (= 'quote (-> (ana (quote a)) :form first))) + (is (= (:op (ana '(1 2 3))) :quote)) + ; :expr + (is (= [:const 'a] (-> (ana (quote a)) :expr juxt-op-val))) + ;js-var + (is (= [:js-var 'js/console 'js] (-> (ana js/console) ((juxt :op :name :ns))))) + (is (map? (-> (ana js/console) :env))) + (is (= 'js/-Infinity (-> (ana js/-Infinity) :form))) + ;; TODO dotted :js-var (?) +#_ + (is (= [:js-var 'js/console 'js] + (-> (ana js/console) ((juxt :op :name :ns))))) + ;munging + (is (= + [false 'a] + (-> + (ana (let [a (println 1) + b (println 2)] + [a b])) + :bindings first + ((juxt #(contains? % :ns) :name))))) + ;shadowing + (is (= + 'a + (-> + (ana (let [a (println 1) + a (println 2)] + [a a])) + :bindings second + :shadow + :name))) + (is (= + 'a + (-> + (ana (let [a (println 1) + a (println 2) + a (println 3) + ] + [a a a])) + :bindings (nth 2) + :shadow + :shadow + :name))) + ;ns + (is + (binding [ana/*analyze-deps* false] + (binding [ana/*cljs-ns* 'cljs.user] + (ana + (ns my.ns.foo + (:require [clojure.repl] + [clojure.string] + [goog.string]) + (:import [goog.string StringBuffer])))))) + ;nested metadata + (is (= :baz + (-> (ana ''#{^{:foo :baz} a}) + :expr + :val + second + first + meta + :foo)))) + +(deftest quote-args-error-test + (is (.startsWith + (try + (ana (quote)) + (catch Exception e + (.getMessage (.getCause e)))) + "Wrong number of args to quote")) + (is (.startsWith + (try + (ana (quote a b)) + (catch Exception e + (.getMessage (.getCause e)))) + "Wrong number of args to quote")) + (is (.startsWith + (try + (ana (quote a b c d)) + (catch Exception e + (.getMessage (.getCause e)))) + "Wrong number of args to quote"))) + +(deftest var-args-error-test + (is (.startsWith + (try + (ana (var)) + (catch Exception e + (.getMessage (.getCause e)))) + "Wrong number of args to var")) + (is (.startsWith + (try + (ana (var a b)) + (catch Exception e + (.getMessage (.getCause e)))) + "Wrong number of args to var")) + (is (.startsWith + (try + (ana (var nil)) + (catch Exception e + (.getMessage (.getCause e)))) + "Argument to var must be symbol"))) + +(deftest test-cljs-1871 + (let [ws (atom [])] + (try + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (analyze (ana/empty-env) + '(do (declare ^{:arglists '([x y])} foo) + (defn foo [x])))) + (catch Exception _)) + (is (string/includes? (first @ws) "declared arglists ([x y]) mismatch defined arglists ([x])")))) + +(deftest test-cljs-2023 + (let [form (with-meta 'js/goog.DEBUG {:tag 'boolean})] + (is (= (-> (ana-api/analyze (ana/empty-env) form) :tag) 'boolean)))) + +(deftest test-cljs-1992 ;; declare after def should have no effect + (let [test-cenv (env/default-compiler-env)] + (env/with-compiler-env test-cenv + (ana/analyze-form-seq + '[(ns test.cljs-1992) + (defn test-fn [a b c] :foo) + (declare test-fn)] + )) + + (let [def (get-in @test-cenv [::ana/namespaces 'test.cljs-1992 :defs 'test-fn])] + (is (:fn-var def))))) + +(deftest test-cljs-2101 + (let [test-cenv (env/default-compiler-env)] + (env/with-compiler-env test-cenv + (ana/analyze-form-seq + ['(ns test.cljs-2101) + `(do + ;; Splice in 32 forms in order to consume first chunk in chunked sequence + ~@(range 32) + (def ~'x32 1) + ;; The previous def must be analyzed for subsequent var special to succeed + (def ~'x33 (var ~'x32)))])))) + +(deftest test-cljs-2139 + (let [ws (atom [])] + (try + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (analyze (ana/empty-env) + '(defn foo [] x))) + (catch Exception _)) + (is (= ["Use of undeclared Var cljs.user/x"] @ws)))) + +(deftest test-cljs-2148 + (binding [ana/*checked-arrays* :warn] + (let [ws (atom [])] + (try + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (env/with-compiler-env test-cenv + (analyze (ana/empty-env) + '(aget (js-obj) "a")))) + (catch Exception _)) + (is (= ["cljs.core/aget, arguments must be an array followed by numeric indices, got [object string] instead (consider goog.object/get for object access)"] @ws))) + (let [ws (atom [])] + (try + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (env/with-compiler-env test-cenv + (analyze (ana/empty-env) + '(aget (js-obj) "foo" "bar")))) + (catch Exception _)) + (is (= ["cljs.core/aget, arguments must be an array followed by numeric indices, got [object string string] instead (consider goog.object/getValueByKeys for object access)"] @ws))) + (let [ws (atom [])] + (try + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (env/with-compiler-env test-cenv + (analyze (ana/empty-env) + '(aset (js-obj) "a" 2)))) + (catch Exception _)) + (is (= ["cljs.core/aset, arguments must be an array, followed by numeric indices, followed by a value, got [object string number] instead (consider goog.object/set for object access)"] @ws))) + (let [ws (atom [])] + (try + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (env/with-compiler-env test-cenv + (analyze (ana/empty-env) + '(let [^objects arr (into-array [1 2 3])] + (aget arr 0))))) + (catch Exception _)) + (is (empty? @ws))) + (let [ws (atom [])] + (try + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (env/with-compiler-env test-cenv + (analyze (ana/empty-env) + '(and true (or (aget (js-obj "foo" 1) "foo") 2))))) + (catch Exception _)) + (is (= 1 (count @ws)))))) + +(deftest test-cljs-2037 + (let [test-env (assoc-in (ana/empty-env) [:ns :name] 'cljs.user)] + (binding [ana/*cljs-ns* ana/*cljs-ns* + ana/*analyze-deps* false] + (is (thrown-with-cause-msg? Exception #"Alias str already exists in namespace cljs.user, aliasing clojure.string" + (analyze test-env '(do + (require '[clojure.string :as str]) + (require '[clojure.set :as str]))))) + (is (thrown-with-cause-msg? Exception #"Alias str already exists in namespace cljs.user, aliasing clojure.string" + (analyze test-env '(do + (require-macros '[clojure.string :as str]) + (require-macros '[clojure.set :as str]))))) + (is (analyze test-env '(do + (require '[clojure.string :as str]) + (require '[clojure.string :as str]) + (require 'clojure.string))))))) + +(deftest test-cljs-2182 + (let [cenv (atom @test-cenv)] + (is (thrown-with-cause-msg? Exception + #"Argument to resolve must be a quoted symbol" + (env/with-compiler-env test-cenv + (analyze test-env '(resolve foo.core))))))) + +(deftest test-cljs-2387 + (ana/no-warn + (env/with-compiler-env test-cenv + (ana/analyze-file (io/file "src/test/cljs_build/analyzer_test/no_defs.cljs")))) + (is (= {} (get-in @test-cenv [::ana/namespaces 'analyzer-test.no-defs :defs])))) + +(deftest test-cljs-2475 + (is (thrown-with-cause-msg? Exception #"recur argument count mismatch, expected: 2 args, got: 1" + (analyze test-env '(loop [x 1 y 2] (recur 3)))))) + +(deftest test-cljs-2476 + (doseq [invalid-try-recur-form '[(loop [] (try (recur))) + (loop [] (try (catch js/Error t (recur)))) + (loop [] (try (catch :default t (recur)))) + (loop [] (try (finally (recur))))]] + (is (thrown-with-cause-msg? Exception + #"Can't recur here" + (analyze test-env invalid-try-recur-form))))) + +(deftest test-locals-mapped-to-sym + (testing "analyze should be robust to :locals mapping to symbols" + (is (= [:local 'a] (-> (analyze (assoc-in test-env [:locals 'a] 'foo) 'a) + ((juxt :op :name))))))) + +(deftest test-cljs-2814 + (is (= "global$module$react" (ana/munge-global-export 'react))) + (is (= "global$module$_CIRCA_material_ui$core$styles" (ana/munge-global-export "@material-ui/core/styles"))) + (is (= "node$module$_CIRCA_material_ui$core$styles" (ana/munge-node-lib "@material-ui/core/styles")))) + +(deftest test-cljs-2819 + (let [ws (atom [])] + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (analyze ns-env + '(def *foo* 1))) + (is (string/starts-with? (first @ws) "*foo* not declared dynamic and thus")))) + +(deftest test-cljs-3031 + (let [ws (atom [])] + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (analyze ns-env + '(loop [x "a"] + (if (identical? "a" x) + (recur true) + (+ 3 x))))) + (is (= 1 (count @ws))) + (is (string/starts-with? (first @ws) "cljs.core/+, all arguments must be numbers, got [number #{boolean string}] instead"))) + (let [ws (atom [])] + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (analyze ns-env + '(loop [x "a"] + (if (identical? "a" x) + (recur 1) + (+ 3 x))))) + (is (zero? (count @ws))))) + +(deftest test-cljs-2868 + (is (= 'string + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(subs "duck" 1 1)))))) + (is (= 'string + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(subs "duck" 1)))))) + + (is (= 'string + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(str)))))) + (is (= 'string + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(str 1)))))) + (is (= 'string + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(str 1 2)))))) + + (is (= 'string + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(pr-str 0)))))) + (is (= 'string + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(prn-str 0)))))) + (is (= 'string + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(print-str 0)))))) + (is (= 'string + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(munge-str "")))))) + (is (= 'string + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(demunge-str ""))))))) + +(deftest test-cljs-3120 + (let [cenv (core-env) + _ (analyze-forms cenv + '[(ns goz.core) + (defprotocol IAlpha + (foo [this] "foo fn") + (bar [this x] "bar fn") + (woz [this x y] "baz fn"))]) + sigs (get-in @cenv [::ana/namespaces 'goz.core :defs 'IAlpha :sigs])] + (is (= #{:foo :bar :woz} (set (keys sigs)))) + (is (every? #(set/subset? #{:name :doc :arglists} (set (keys %))) (vals sigs))) + (is #(= '#{foo bar woz} (set (map :name (vals sigs))))) + (is #(= '#{([this] [this x] [this x y])} (set (map :arglists (vals sigs))))) + (is #(= '#{"foo fn" "bar fn" "baz fn"} (set (map :doc (vals sigs))))))) + +(deftest test-cljs-3133 + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (keyword? x) x nil)))))) + '#{cljs.core/Keyword clj-nil})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (simple-keyword? x) x nil)))))) + '#{cljs.core/Keyword clj-nil})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (qualified-keyword? x) x nil)))))) + '#{cljs.core/Keyword clj-nil})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (symbol? x) x nil)))))) + '#{cljs.core/Symbol clj-nil})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (simple-symbol? x) x nil)))))) + '#{cljs.core/Symbol clj-nil})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (qualified-symbol? x) x nil)))))) + '#{cljs.core/Symbol clj-nil})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (ident? x) x nil)))))) + '#{cljs.core/Keyword cljs.core/Symbol clj-nil})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (simple-ident? x) x nil)))))) + '#{cljs.core/Keyword cljs.core/Symbol clj-nil})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (qualified-ident? x) x nil)))))) + '#{cljs.core/Keyword cljs.core/Symbol clj-nil}))) + +(deftest test-cljs-3140 + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (instance? UUID x) x nil)))))) + '#{cljs.core/UUID clj-nil})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (implements? ICounted x) x nil)))))) + '#{cljs.core/ICounted clj-nil}))) + +(deftest test-cljs-3158 + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(loop [a "x" b "y"] + (if (= a 1) + a + (recur b 1))))))) + 'any))) + +(deftest test-cljs-3190 + (let [ws (atom [])] + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (env/with-compiler-env @test-cenv + (analyze (ana/empty-env) + '(do + (defrecord Foo [a]) + (:a (->Foo)))))) + (is (= 1 (count @ws))) + (is (string/starts-with? (first @ws) "Wrong number of args (0) passed to cljs.user/->Foo")))) + +(deftest test-cljs-3210 + (let [ws (atom [])] + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (analyze ns-env + '(do + (+ "a") + (- "a") + (/ "a") + (* "a")))) + (is (= 4 (count @ws))) + (let [[w1 w2 w3 w4] @ws] + (is (= w1 "cljs.core/+, all arguments must be numbers, got [string] instead")) + (is (= w2 "cljs.core/-, all arguments must be numbers, got [string] instead")) + (is (= w3 "cljs.core//, all arguments must be numbers, got [number string] instead")) + (is (= w4 "cljs.core/*, all arguments must be numbers, got [string] instead"))))) + +;; this test does pass, but shows a current problem goog file analysis +;; we only consider the functional API, we don't include information needed +;; to infer usage of classes +(deftest test-analyze-goog-ns + (let [cenv (env/default-compiler-env)] + (env/with-compiler-env cenv + (ana/analyze-form-seq + '[(ns test.foo + (:import [goog.history Html5History]))])) + (is (some? (get-in @cenv [::ana/namespaces 'goog.history.Html5History :defs]))))) + +(deftest test-analyze-goog-ns-ctor + (let [cenv (env/default-compiler-env)] + (env/with-compiler-env cenv + (ana/analyze-form-seq + '[(ns test.foo + (:import [goog.history Html5History]))])) + (is (some? (get-in @cenv [::ana/namespaces 'goog.history.Html5History :defs 'Html5History]))))) + +(deftest test-cljs-3239 + (let [cenv (env/default-compiler-env)] + (env/with-compiler-env cenv + (ana/analyze-form-seq + '[(ns test.foo + (:import goog))])) + (is (= {} (get-in @cenv [::ana/namespaces 'test.foo :imports]))))) + +(deftest test-cljs-3320 + (let [ws (atom [])] + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (binding [ana/*cljs-ns* 'cljs.user] + (analyze ns-env '(ns cljs3320.core (:require [cljs.js :as js]))))) + (is (string/includes? (first @ws) "the alias name js is reserved for JavaScript interop")))) + +(deftest test-cljs-3371 + (let [ws (atom [])] + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (env/with-compiler-env @test-cenv + (analyze (ana/empty-env) + '(do + (defrecord Foo [a]) + (Foo. nil) + (Foo. nil nil nil))))) + (is (empty? @ws))) + (let [ws (atom [])] + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (env/with-compiler-env @test-cenv + (analyze (ana/empty-env) + '(do + (defrecord Foo [a]) + (Foo. nil nil))))) + (is (= 1 (count @ws))) + (is (string/starts-with? (first @ws) "Wrong number of args (2) passed to Foo")))) + +(deftest test-cljs-3401 + (is (not= (ana/gen-constant-id '_PLUS_) + (ana/gen-constant-id '+))) + (is (not= (ana/gen-constant-id 'foo.bar) + (ana/gen-constant-id 'foo$bar)))) + +;; ----------------------------------------------------------------------------- +;; :refer-global / :require-global ns parsing tests + +(deftest test-refer-global + (binding [ana/*cljs-ns* ana/*cljs-ns*] + (let [parsed-ns (env/with-compiler-env test-cenv + (analyze test-env + '(ns foo.core + (:refer-global :only [Date] :rename {Date MyDate}))))] + (= (:renames parsed-ns) + '{MyDate js/Date})))) + +(deftest test-require-global + (binding [ana/*cljs-ns* ana/*cljs-ns*] + (let [parsed-ns (env/with-compiler-env test-cenv + (analyze test-env + '(ns foo.core + (:require-global [React :as react :refer [createElement]]))))] + (is (= (:requires parsed-ns) + '{React React + react React})) + (is (= (:uses parsed-ns) + '{createElement React}))))) diff --git a/src/test/clojure/cljs/build_api_tests.clj b/src/test/clojure/cljs/build_api_tests.clj new file mode 100644 index 0000000000..caa8da41fa --- /dev/null +++ b/src/test/clojure/cljs/build_api_tests.clj @@ -0,0 +1,987 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.build-api-tests + (:refer-clojure :exclude [compile]) + (:import java.io.File) + (:require [cljs.analyzer :as ana] + [cljs.build.api :as build] + [cljs.closure :as closure] + [cljs.env :as env] + [cljs.test-util :as test] + [cljs.util :as util] + [cljs.vendor.clojure.data.json :as json] + [clojure.edn :as edn] + [clojure.java.io :as io] + [clojure.java.shell :as sh] + [clojure.test :refer [deftest is testing]] + [clojure.string :as string])) + +(deftest test-target-file-for-cljs-ns + (is (= (.getPath (build/target-file-for-cljs-ns 'example.core-lib nil)) + (test/platform-path "out/example/core_lib.js"))) + (is (= (.getPath (build/target-file-for-cljs-ns 'example.core-lib "output")) + (test/platform-path "output/example/core_lib.js")))) + +(deftest test-cljs-dependents-for-macro-namespaces + (env/with-compiler-env (env/default-compiler-env) + (swap! env/*compiler* assoc :cljs.analyzer/namespaces + { 'example.core + {:require-macros {'example.macros 'example.macros + 'mac 'example.macros} + :name 'example.core} + 'example.util + {:require-macros {'example.macros 'example.macros + 'mac 'example.macros} + :name 'example.util} + 'example.helpers + {:require-macros {'example.macros-again 'example.macros-again + 'mac 'example.macros-again} + :name 'example.helpers } + 'example.fun + {:require-macros nil + :name 'example.fun }}) + (is (= (set (build/cljs-dependents-for-macro-namespaces ['example.macros])) + #{'example.core 'example.util})) + (is (= (set (build/cljs-dependents-for-macro-namespaces ['example.macros-again])) + #{'example.helpers})) + (is (= (set (build/cljs-dependents-for-macro-namespaces ['example.macros 'example.macros-again])) + #{'example.core 'example.util 'example.helpers})) + (is (= (set (build/cljs-dependents-for-macro-namespaces ['example.not-macros])) + #{})))) + +(def test-cenv (atom {})) +(def test-env (assoc-in (ana/empty-env) [:ns :name] 'cljs.user)) + +;; basic + +(binding [ana/*cljs-ns* 'cljs.user + ana/*analyze-deps* false] + (env/with-compiler-env test-cenv + (ana/no-warn + (ana/analyze test-env + '(ns cljs.user + (:use [clojure.string :only [join]])))))) + +;; linear + +(binding [ana/*cljs-ns* 'cljs.user + ana/*analyze-deps* false] + (env/with-compiler-env test-cenv + (ana/no-warn + (ana/analyze test-env + '(ns foo.core))))) + +(binding [ana/*cljs-ns* 'cljs.user + ana/*analyze-deps* false] + (env/with-compiler-env test-cenv + (ana/no-warn + (ana/analyze test-env + '(ns bar.core + (:require [foo.core :as foo])))))) + +(binding [ana/*cljs-ns* 'cljs.user + ana/*analyze-deps* false] + (env/with-compiler-env test-cenv + (ana/no-warn + (ana/analyze test-env + '(ns baz.core + (:require [bar.core :as bar])))))) + +;; graph + +(binding [ana/*cljs-ns* 'cljs.user + ana/*analyze-deps* false] + (env/with-compiler-env test-cenv + (ana/no-warn + (ana/analyze test-env + '(ns graph.foo.core))))) + +(binding [ana/*cljs-ns* 'cljs.user + ana/*analyze-deps* false] + (env/with-compiler-env test-cenv + (ana/no-warn + (ana/analyze test-env + '(ns graph.bar.core + (:require [graph.foo.core :as foo])))))) + +(binding [ana/*cljs-ns* 'cljs.user + ana/*analyze-deps* false] + (env/with-compiler-env test-cenv + (ana/no-warn + (ana/analyze test-env + '(ns graph.baz.core + (:require [graph.foo.core :as foo] + [graph.bar.core :as bar])))))) + +(deftest cljs-1469 + (let [out (.getPath (io/file (test/tmp-dir) "loader-test-out")) + srcs "samples/hello/src" + [common-tmp app-tmp] (mapv #(File/createTempFile % ".js") + ["common" "app"]) + opts {:optimizations :simple + :output-dir out + :modules {:common {:entries #{"hello.foo.bar"} + :output-to (.getAbsolutePath common-tmp)} + :app {:entries #{"hello.core"} + :output-to (.getAbsolutePath app-tmp)}}}] + (test/delete-out-files out) + (.deleteOnExit common-tmp) + (.deleteOnExit app-tmp) + (is (every? #(zero? (.length %)) [common-tmp app-tmp]) + "The initial files are empty") + (build/build srcs opts) + (is (not (every? #(zero? (.length %)) [common-tmp app-tmp])) + "The files are not empty after compilation"))) + +(deftest cljs-1500-test-modules + (let [out (io/file (test/tmp-dir) "cljs-1500-out") + project (test/project-with-modules (str out)) + modules (-> project :opts :modules)] + (test/delete-out-files out) + (build/build (build/inputs (:inputs project)) (:opts project)) + (is (re-find #"Loading modules A and B" (slurp (-> modules :cljs-base :output-to)))) + (is (re-find #"Module A loaded" (slurp (-> modules :module-a :output-to)))) + (is (re-find #"Module B loaded" (slurp (-> modules :module-b :output-to)))))) + +(deftest cljs-1883-test-foreign-libs-use-relative-path + (let [out (io/file (test/tmp-dir) "cljs-1883-out") + root (io/file "src" "test" "cljs_build") + opts {:foreign-libs + [{:file (str (io/file root "thirdparty" "add.js")) + :provides ["thirdparty.add"]}] + :output-dir (str out) + :main 'foreign-libs.core + :target :nodejs}] + (test/delete-out-files out) + (build/build (build/inputs (io/file root "foreign_libs") (io/file root "thirdparty")) opts) + (let [foreign-lib-file (io/file out (-> opts :foreign-libs first :file))] + (is (.exists foreign-lib-file)) + (is (= (->> (slurp (io/file out "foreign_libs" "core.js")) + (re-matches #"(?s).*cljs\.core\.load_file\(\"([^\"]+)\"\);.*") + (second)) + (str foreign-lib-file)))))) + +(deftest cljs-1537-circular-deps + (let [out (.getPath (io/file (test/tmp-dir) "cljs-1537-test-out")) + out-file (io/file out "main.js") + root "src/test/cljs_build"] + (test/delete-out-files out) + (try + (build/build (build/inputs + (io/file root "circular_deps" "a.cljs") + (io/file root "circular_deps" "b.cljs")) + {:main 'circular-deps.a + :optimizations :none + :output-to out}) + (is false) + (catch Throwable e + (let [cause-message (.getMessage (.getCause (.getCause e)))] + (is (or (re-find #"Circular dependency detected, circular-deps.a -> circular-deps.b -> circular-deps.a" cause-message) + (re-find #"Circular dependency detected, circular-deps.b -> circular-deps.a -> circular-deps.b" cause-message)))))))) + +(defn loader-test-project [output-dir] + {:inputs (str (io/file "src" "test" "cljs_build" "loader_test")) + :opts + {:output-dir output-dir + :optimizations :none + :verbose true + :foreign-libs [{:file "src/test/cljs_build/loader_test/foreignA.js" + :provides ["foreign.a"]} + {:file "src/test/cljs_build/loader_test/foreignB.js" + :provides ["foreign.b"] + :requires ["foreign.a"]}] + :modules + {:foo + {:output-to (str (io/file output-dir "foo.js")) + :entries #{'loader-test.foo}} + :bar + {:output-to (str (io/file output-dir "bar.js")) + :entries #{'loader-test.bar}}}}}) + +(deftest cljs-2077-test-loader + (let [out (.getPath (io/file (test/tmp-dir) "loader-test-out"))] + (test/delete-out-files out) + (let [{:keys [inputs opts]} (loader-test-project out) + loader (io/file out "cljs" "loader.js")] + (build/build (build/inputs inputs) opts) + (is (.exists loader)) + (is (not (nil? (re-find #"[\\/]loader_test[\\/]foo\.js" (slurp loader)))))) + (test/delete-out-files out) + (let [{:keys [inputs opts]} (merge-with merge (loader-test-project out) + {:opts {:optimizations :advanced + :source-map true}})] + (build/build (build/inputs inputs) opts)) + (testing "string inputs in modules" + (test/delete-out-files out) + (let [{:keys [inputs opts]} (merge-with merge (loader-test-project out) + {:opts {:optimizations :whitespace}})] + (build/build (build/inputs inputs) opts))) + (testing "CLJS-2309 foreign libs order preserved" + (test/delete-out-files out) + (let [{:keys [inputs opts]} (merge-with merge (loader-test-project out) + {:opts {:optimizations :advanced}})] + (build/build (build/inputs inputs) opts) + (is (not (nil? (re-find #"foreignA[\s\S]+foreignB" (slurp (io/file out "foo.js")))))))))) + +(deftest test-npm-deps-simple + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (let [out (.getPath (io/file (test/tmp-dir) "npm-deps-test-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'npm-deps-test.core + :output-dir out + :optimizations :none + :install-deps true + :npm-deps {:left-pad "1.1.3"} + :foreign-libs [{:module-type :es6 + :file "src/test/cljs/es6_dep.js" + :provides ["es6_calc"]} + {:module-type :es6 + :file "src/test/cljs/es6_default_hello.js" + :provides ["es6_default_hello"]}] + :closure-warnings {:check-types :off}}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "npm_deps_test/core.cljs")) opts cenv) + (is (.exists (io/file out "node_modules/left-pad/index.js"))) + (is (contains? (:js-module-index @cenv) "left-pad"))) + + (.delete (io/file "package.json")) + (test/delete-node-modules)) + +(deftest test-npm-deps + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (let [cenv (env/default-compiler-env) + out (.getPath (io/file (test/tmp-dir) "npm-deps-test-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'npm-deps-test.string-requires + :output-dir out + :optimizations :none + :install-deps true + :npm-deps {:react "15.6.1" + :react-dom "15.6.1" + :lodash-es "4.17.4" + :lodash "4.17.4"} + :closure-warnings {:check-types :off + :non-standard-jsdoc :off}}}] + (test/delete-out-files out) + (testing "mix of symbol & string-based requires" + (build/build (build/inputs (io/file inputs "npm_deps_test/string_requires.cljs")) opts cenv) + (is (.exists (io/file out "node_modules/react/react.js"))) + (is (contains? (:js-module-index @cenv) "react")) + (is (contains? (:js-module-index @cenv) "react-dom/server"))) + + (testing "builds with string requires are idempotent" + (build/build (build/inputs (io/file inputs "npm_deps_test/string_requires.cljs")) opts cenv) + (is (not (nil? (re-find #"\.\.[\\/]node_modules[\\/]react-dom[\\/]server\.js" (slurp (io/file out "cljs_deps.js")))))))) + + (.delete (io/file "package.json")) + (test/delete-node-modules)) + +(deftest test-npm-deps-invoke-cljs-3144 + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (let [cenv (env/default-compiler-env) + out (.getPath (io/file (test/tmp-dir) "npm-deps-test-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'npm-deps-test.invoke + :output-dir out + :optimizations :none + :install-deps true + :npm-deps {:react "15.6.1" + :react-dom "15.6.1" + :lodash-es "4.17.4" + :lodash "4.17.4"} + :closure-warnings {:check-types :off + :non-standard-jsdoc :off}}}] + (test/delete-out-files out) + (testing "invoking fns from Node.js libraries should not emit .call convention" + (build/build (build/inputs (io/file inputs "npm_deps_test/invoke.cljs")) opts cenv) + (is (.exists (io/file out "node_modules/react/react.js"))) + (is (not (string/includes? (slurp (io/file out "npm_deps_test/invoke.cljs")) "call"))))) + (.delete (io/file "package.json")) + (test/delete-node-modules)) + +(deftest test-preloads + (let [out (.getPath (io/file (test/tmp-dir) "preloads-test-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs")) + :opts {:main 'preloads-test.core + :preloads '[preloads-test.preload] + :output-dir out + :optimizations :none + :closure-warnings {:check-types :off}}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs + (io/file inputs "preloads_test/core.cljs")) + opts cenv) + (is (.exists (io/file out "preloads_test/preload.cljs"))) + (is (contains? (get-in @cenv [::ana/namespaces 'preloads-test.preload :defs]) 'preload-var)))) + +(deftest test-libs-cljs-2152 + (let [out (.getPath (io/file (test/tmp-dir) "libs-test-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'libs-test.core + :output-dir out + :libs ["src/test/cljs/js_libs"] + :optimizations :none + :closure-warnings {:check-types :off}}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs + (io/file "src/test/cljs_build/libs_test/core.cljs") (io/file "src/test/cljs/js_libs") + (io/file inputs "libs_test/core.cljs") + (io/file "src/test/cljs/js_libs")) + opts cenv) + (is (.exists (io/file out "tabby.js"))))) + +(defn collecting-warning-handler [state] + (fn [warning-type env extra] + (when (warning-type ana/*cljs-warnings*) + (when-let [s (ana/error-message warning-type extra)] + (swap! state conj s))))) + +(deftest test-emit-node-requires-cljs-2213 + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (testing "simplest case, require" + (let [ws (atom []) + out (.getPath (io/file (test/tmp-dir) "emit-node-requires-test-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'emit-node-requires-test.core + :output-dir out + :optimizations :none + :target :nodejs + :install-deps true + :npm-deps {:react "15.6.1" + :react-dom "15.6.1"} + :closure-warnings {:check-types :off + :non-standard-jsdoc :off}}} + cenv (env/default-compiler-env opts)] + (test/delete-out-files out) + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (build/build (build/inputs (io/file inputs "emit_node_requires_test/core.cljs")) opts cenv)) + ;; wasn't processed by Closure + (is (not (.exists (io/file out "node_modules/react/react.js")))) + (is (.exists (io/file out "emit_node_requires_test/core.js"))) + (is (true? (boolean (re-find #"emit_node_requires_test\.core\.node\$module\$react_dom\$server = require\('react-dom/server'\);" + (slurp (io/file out "emit_node_requires_test/core.js")))))) + (is (true? (boolean (re-find #"emit_node_requires_test\.core\.node\$module\$react_dom\$server\.renderToString" + (slurp (io/file out "emit_node_requires_test/core.js")))))) + (is (empty? @ws)))) + (testing "Node native modules, CLJS-2218" + (let [ws (atom []) + out (.getPath (io/file (test/tmp-dir) "emit-node-requires-test-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'emit-node-requires-test.native-modules + :output-dir out + :optimizations :none + :target :nodejs + :closure-warnings {:check-types :off}}} + cenv (env/default-compiler-env opts)] + (test/delete-out-files out) + (test/delete-node-modules) + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (build/build (build/inputs (io/file inputs "emit_node_requires_test/native_modules.cljs")) opts cenv)) + (is (.exists (io/file out "emit_node_requires_test/native_modules.js"))) + (is (true? (boolean (re-find #"emit_node_requires_test\.native_modules\.node\$module\$path\.isAbsolute" + (slurp (io/file out "emit_node_requires_test/native_modules.js")))))) + (is (empty? @ws)))) + (.delete (io/file "package.json")) + (test/delete-node-modules)) + +(deftest test-emit-global-requires-cljs-2214 + (testing "simplest case, require" + (let [ws (atom []) + out (.getPath (io/file (test/tmp-dir) "emit-global-requires-test-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'emit-node-requires-test.core + :output-dir out + :optimizations :none + ;; Doesn't matter what :file is used here, as long at it exists + :foreign-libs [{:file "src/test/cljs_build/thirdparty/add.js" + :provides ["react"] + :global-exports '{react React}} + {:file "src/test/cljs_build/thirdparty/add.js" + :provides ["react-dom"] + :requires ["react"] + :global-exports '{react-dom ReactDOM}} + {:file "src/test/cljs_build/thirdparty/add.js" + :provides ["react-dom/server"] + :requires ["react-dom"] + :global-exports '{react-dom/server ReactDOMServer}} + {:file "src/test/cljs_build/thirdparty/add.js" + :provides ["@material-ui/core/styles" + "@material-ui/core/styles/a"] + ;; Key str because contains multiple /, value shouldn't matter + :global-exports {"@material-ui/core/styles" "MaterialUIStyles" + "@material-ui/core/styles/a" "MaterialUIStyles.a"}}]}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (build/build (build/inputs (io/file inputs "emit_global_requires_test/core.cljs")) opts cenv)) + (is (.exists (io/file out "emit_global_requires_test/core.js"))) + (is (true? (boolean (re-find #"emit_global_requires_test\.core\.global\$module\$react_dom\$server = goog\.global\[\"ReactDOMServer\"\];" + (slurp (io/file out "emit_global_requires_test/core.js")))))) + (is (true? (boolean (re-find #"emit_global_requires_test\.core\.global\$module\$react_dom\$server\.renderToString" + (slurp (io/file out "emit_global_requires_test/core.js")))))) + (testing "global exports using string key" + (is (true? (boolean (re-find #"emit_global_requires_test\.core\.global\$module\$_CIRCA_material_ui\$core\$styles = goog\.global\[\"MaterialUIStyles\"\];" + (slurp (io/file out "emit_global_requires_test/core.js")))))) + (is (true? (boolean (re-find #"emit_global_requires_test\.core\.global\$module\$_CIRCA_material_ui\$core\$styles\.createMuiTheme" + (slurp (io/file out "emit_global_requires_test/core.js"))))))) + (testing "global exports points to a sub property" + (is (true? (boolean (re-find #"emit_global_requires_test\.core\.global\$module\$_CIRCA_material_ui\$core\$styles\$a = goog\.global\[\"MaterialUIStyles\"\]\[\"a\"\];" + (slurp (io/file out "emit_global_requires_test/core.js")))))) + (is (true? (boolean (re-find #"emit_global_requires_test\.core\.global\$module\$_CIRCA_material_ui\$core\$styles\$a\.foo" + (slurp (io/file out "emit_global_requires_test/core.js"))))))) + (is (empty? @ws))))) + +(deftest test-data-readers + (let [out (.getPath (io/file (test/tmp-dir) "data-readers-test-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs")) + :opts {:main 'data-readers-test.core + :output-dir out + :optimizations :none + :closure-warnings {:check-types :off}}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "data_readers_test")) opts cenv) + (is (contains? (-> @cenv ::ana/data-readers) 'test/custom-identity)) + (is (true? (boolean (re-find #"Array\.of\(\"foo\"\)" + (slurp (io/file + out ;"data-readers-test-out" + "data_readers_test" "core.js")))))))) + +(deftest test-data-readers-records + (let [out (.getPath (io/file (test/tmp-dir) "data-readers-test-records-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs")) + :opts {:main 'data-readers-test.records + :output-dir out + :optimizations :none + :closure-warnings {:check-types :off}}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "data_readers_test")) opts cenv) + (is (true? (boolean (re-find #"data_readers_test.records.map__GT_Foo\(" + (slurp (io/file out "data_readers_test" "records.js")))))))) + +(deftest test-cljs-2249 + (let [out (io/file (test/tmp-dir) "cljs-2249-out") + root (io/file "src" "test" "cljs_build") + opts {:output-dir (str out) + :main 'foreign-libs-cljs-2249.core + :target :nodejs}] + (test/delete-out-files out) + (build/build (build/inputs (io/file root "foreign_libs_cljs_2249")) opts) + (is (.exists (io/file out "calculator_global.js"))) + (test/delete-out-files out) + (closure/build (build/inputs (io/file root "foreign_libs_cljs_2249")) opts) + (is (.exists (io/file out "calculator_global.js"))))) + +(deftest test-node-modules-cljs-2246 + (test/delete-node-modules) + (.delete (io/file "package-lock.json")) + (spit (io/file "package.json") (json/json-str {:dependencies {:left-pad "1.1.3"} + :devDependencies {"@cljs-oss/module-deps" "*"}})) + (apply sh/sh (cond->> ["npm" "install"] + util/windows? (into ["cmd" "/c"]))) + (let [ws (atom []) + out (.getPath (io/file (test/tmp-dir) "node-modules-opt-test-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'node-modules-opt-test.core + :output-dir out + :npm-deps true + :optimizations :none + :closure-warnings {:check-types :off}}} + cenv (env/default-compiler-env opts)] + (test/delete-out-files out) + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (build/build (build/inputs (io/file inputs "node_modules_opt_test/core.cljs")) opts cenv)) + (is (.exists (io/file out "node_modules/left-pad/index.js"))) + (is (contains? (:js-module-index @cenv) "left-pad")) + (is (empty? @ws))) + (.delete (io/file "package.json")) + (.delete (io/file "package-lock.json")) + (test/delete-node-modules)) + +(deftest test-deps-api-cljs-2255 + (let [out (.getPath (io/file (test/tmp-dir) "cljs-2255-test-out"))] + (test/delete-out-files out) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (build/install-node-deps! {:left-pad "1.1.3"} {:output-dir out}) + (is (.exists (io/file "node_modules/left-pad/package.json"))) + (test/delete-out-files out) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (build/install-node-deps! + {:react "15.6.1" + :react-dom "15.6.1"} + {:output-dir out}) + (let [modules (build/get-node-deps '[react "react-dom/server"] {:output-dir out})] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react/react.js")) + :provides ["react" + "react/react.js" + "react/react"]})) + modules))) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react/lib/React.js")) + :provides ["react/lib/React.js" "react/lib/React"]})) + modules))) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react-dom/server.js")) + :provides ["react-dom/server.js" "react-dom/server"]})) + modules)))) + (test/delete-out-files out) + (test/delete-node-modules) + (.delete (io/file "package.json")))) + +(deftest test-cljs-2296 + (let [out (.getPath (io/file (test/tmp-dir) "cljs-2296-test-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'foreign_libs_dir_test.core + :output-dir out + :optimizations :none + :target :nodejs + ;; :file is a directory + :foreign-libs [{:file "src/test/cljs_build/foreign-libs-dir" + :module-type :commonjs}]}}] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "foreign_libs_dir_test/core.cljs")) opts) + (is (.exists (io/file out "src/test/cljs_build/foreign-libs-dir/vendor/lib.js"))) + (is (re-find #"goog\.provide\(\"module\$[A-Za-z0-9$_]+?src\$test\$cljs_build\$foreign_libs_dir\$vendor\$lib\"\)" + (slurp (io/file out "src/test/cljs_build/foreign-libs-dir/vendor/lib.js")))))) + +(deftest cljs-1883-test-foreign-libs-use-relative-path + (test/delete-node-modules) + (let [out "cljs-2334-out" + root (io/file "src" "test" "cljs_build") + opts {:foreign-libs + [{:file (str (io/file root "foreign_libs_cljs_2334" "lib.js")) + :module-type :es6 + :provides ["mylib"]}] + :npm-deps {:left-pad "1.1.3"} + :install-deps true + :output-dir (str out)}] + (test/delete-out-files out) + (build/build (build/inputs (io/file root "foreign_libs_cljs_2334")) opts) + (let [foreign-lib-file (io/file out (-> opts :foreign-libs first :file)) + index-js (slurp (io/file "cljs-2334-out" "node_modules" "left-pad" "index.js"))] + (is (.exists foreign-lib-file)) + (is (re-find #"module\$.*\$node_modules\$left_pad\$index=" index-js)) + (is (not (re-find #"module\.exports" index-js))) + ;; assert Closure finds and processes the left-pad dep in node_modules + ;; if it can't be found the require will be issued to module$left_pad + ;; so we assert it's of the form module$path$to$node_modules$left_pad$index + (is (re-find #"module\$.*\$node_modules\$left_pad\$index\[\"default\"\]\)\(42,5,0\)" (slurp foreign-lib-file)))) + (test/delete-out-files out) + (test/delete-node-modules))) + +(deftest cljs-2519-test-cljs-base-entries + (let [dir (io/file "src" "test" "cljs_build" "code-split") + out (io/file (test/tmp-dir) "cljs-base-entries") + opts {:output-dir (str out) + :asset-path "/out" + :optimizations :none + :modules {:a {:entries '#{code.split.a} + :output-to (io/file out "a.js")} + :b {:entries '#{code.split.b} + :output-to (io/file out "b.js")} + :c {:entries '#{code.split.c} + :output-to (io/file out "c.js")}}}] + (test/delete-out-files out) + (build/build (build/inputs dir) opts) + (testing "Module :cljs-base" + (let [content (slurp (io/file out "cljs_base.js"))] + (testing "requires code.split.d (used in :b and :c)" + (is (test/document-write? content 'code.split.d))))) + (testing "Module :a" + (let [content (slurp (-> opts :modules :a :output-to))] + (testing "requires code.split.a" + (is (test/document-write? content 'code.split.a))) + (testing "requires cljs.pprint (only used in :a)" + (is (test/document-write? content 'cljs.pprint))))) + (testing "Module :b" + (let [content (slurp (-> opts :modules :b :output-to))] + (testing "requires code.split.b" + (is (test/document-write? content 'code.split.b))))) + (testing "Module :c" + (let [content (slurp (-> opts :modules :c :output-to))] + (testing "requires code.split.c" + (is (test/document-write? content 'code.split.c))))))) + +(deftest test-cljs-2592 + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (let [cenv (env/default-compiler-env) + dir (io/file "src" "test" "cljs_build" "package_json_resolution_test") + out (io/file (test/tmp-dir) "package_json_resolution_test") + opts {:main 'package-json-resolution-test.core + :output-dir (str out) + :output-to (str (io/file out "main.js")) + :optimizations :none + :install-deps true + :npm-deps {:iterall "1.2.2" + :graphql "0.13.1"} + :package-json-resolution :nodejs + :closure-warnings {:check-types :off + :non-standard-jsdoc :off}}] + (test/delete-out-files out) + (build/build (build/inputs dir) opts cenv) + (testing "processes the iterall index.js" + (let [index-js (io/file out "node_modules/iterall/index.js")] + (is (.exists index-js)) + (is (contains? (:js-module-index @cenv) "iterall")) + (is (re-find #"goog\.provide\(\"module\$.*\$node_modules\$iterall\$index\"\)" (slurp index-js))))) + (testing "processes the graphql index.js" + (let [index-js (io/file out "node_modules/graphql/index.js") + execution-index-js (io/file out "node_modules/graphql/execution/index.js") + ast-from-value-js (io/file out "node_modules/grapqhl/utilities/astFromValue.js")] + (is (.exists index-js)) + (is (contains? (:js-module-index @cenv) "graphql")) + (is (re-find #"goog\.provide\(\"module\$.*\$node_modules\$graphql\$index\"\)" (slurp index-js))))) + (testing "processes a nested index.js in graphql" + (let [nested-index-js (io/file out "node_modules/graphql/execution/index.js")] + (is (.exists nested-index-js)) + (is (contains? (:js-module-index @cenv) "graphql/execution")) + (is (re-find #"goog\.provide\(\"module\$.*\$node_modules\$graphql\$execution\$index\"\)" (slurp nested-index-js))))) + (testing "processes cross-package imports" + (let [ast-from-value-js (io/file out "node_modules/graphql/utilities/astFromValue.js")] + (is (.exists ast-from-value-js)) + (is (re-find #"goog.require\(\"module\$.*\$node_modules\$iterall\$index\"\);" (slurp ast-from-value-js))))) + (testing "adds dependencies to cljs_deps.js" + (let [deps-js (io/file out "cljs_deps.js")] + (is (re-find #"goog\.addDependency\(\"..\/node_modules\/iterall\/index.js\"" (slurp deps-js))) + (is (re-find #"goog\.addDependency\(\"..\/node_modules\/graphql\/index.js\"" (slurp deps-js))) + (is (re-find #"goog\.addDependency\(\"..\/node_modules\/graphql\/execution/index.js\"" (slurp deps-js))))) + (testing "adds the right module names to the core.cljs build output" + (let [core-js (io/file out "package_json_resolution_test/core.js")] + (is (re-find #"goog\.require\('module\$.*\$node_modules\$iterall\$index'\);" (slurp core-js))) + (is (re-find #"module\$.+\$node_modules\$iterall\$index\[\"default\"\]\.isCollection" (slurp core-js))) + (is (re-find #"goog\.require\('module\$.*\$node_modules\$graphql\$index'\);" (slurp core-js))) + (is (re-find #"module\$.+\$node_modules\$graphql\$index\[\"default\"\]" (slurp core-js)))))) + (.delete (io/file "package.json")) + (test/delete-node-modules)) + +(deftest test-fingerprint + (let [out (io/file (test/tmp-dir) "cljs-2903-out") + opts {:output-to (.getPath (io/file out "main.js")) + :output-dir (.getPath out) + :fingerprint true + :stable-names true + :optimizations :advanced}] + (test/delete-out-files out) + (build/build "src/test/cljs/hello.cljs" opts) + (let [mf (edn/read-string (slurp (io/file out "manifest.edn"))) + f (io/file (get mf (:output-to opts))) + sha (string/lower-case (util/content-sha (slurp (io/file f)) 7))] + (is (true? (.exists f))) + (is (string/includes? (.getPath f) sha))))) + +(deftest test-fingerprint-modules + (let [out (.getPath (io/file (test/tmp-dir) "cljs-2903-modules-out")) + project (update-in (test/project-with-modules out) + [:opts] merge + {:fingerprint true + :stable-names true + :optimizations :advanced})] + (test/delete-out-files out) + (build/build (build/inputs (:inputs project)) (:opts project)) + (let [mf (edn/read-string (slurp (io/file out "manifest.edn")))] + (doseq [[name {:keys [output-to]}] (get-in project [:opts :modules])] + (when-not (= :cljs-base name) + (let [f (io/file (get mf output-to)) + sha (string/lower-case (util/content-sha (slurp (io/file f)) 7))] + (is (true? (.exists f))) + (is (string/includes? (.getPath f) sha)))))))) + +(deftest cljs-3209-trivial-output-size + (let [out (.getPath (io/file (test/tmp-dir) "3209-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core + :output-dir out + :output-to (.getPath out-file) + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core.cljs")) opts cenv) + (is (< (.length out-file) 10240)))) + +(deftest trivial-output-size-protocol + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-protocol-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core2 + :output-dir out + :output-to (.getPath out-file) + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core2.cljs")) opts cenv) + (is (< (.length out-file) 10240)))) + +(deftest trivial-output-size-keyword + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-keyword-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core3 + :output-dir out + :output-to (.getPath out-file) + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core3.cljs")) opts cenv) + (is (< (.length out-file) 10240)))) + +(deftest trivial-output-size-vector + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-vector-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core4 + :output-dir out + :output-to (.getPath out-file) + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core4.cljs")) opts cenv) + (is (< (.length out-file) 92160)))) + +(deftest lite-mode-vector-code-size-ratchet + (testing ":lite-mode + :elide-to-string, should cut output size for [] in 1/2" + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-vector-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core4 + :output-dir out + :output-to (.getPath out-file) + :lite-mode true + :elide-to-string true + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core4.cljs")) opts cenv) + (is (< (.length out-file) 16384))))) + +(deftest trivial-output-size-map + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-map-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core5 + :output-dir out + :output-to (.getPath out-file) + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core5.cljs")) opts cenv) + (is (< (.length out-file) 92160)))) + +(deftest lite-mode-map-code-size-ratchet + (testing ":lite-mode + :elide-to-string, should cut output size for {} in 1/3" + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-map-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core5 + :output-dir out + :output-to (.getPath out-file) + :lite-mode true + :elide-to-string true + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core5.cljs")) opts cenv) + (is (< (.length out-file) 32768))))) + +(deftest lite-mode-api-code-size-ratchet + (testing ":lite-mode + :elide-to-string, typical cljs.core api usage ~32K" + (let [out (.getPath (io/file (test/tmp-dir) "trivial-output-map-test-out")) + out-file (io/file out "main.js") + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'trivial.core6 + :output-dir out + :output-to (.getPath out-file) + :lite-mode true + :elide-to-string true + :optimizations :advanced}} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build (build/inputs (io/file inputs "trivial/core6.cljs")) opts cenv) + (is (< (.length out-file) 34000))))) + +(deftest cljs-3255-nil-inputs-build + (let [out (.getPath (io/file (test/tmp-dir) "3255-test-out")) + out-file (io/file out "main.js") + opts {:main 'trivial.core + :output-to (.getPath out-file) + :output-dir out + :optimizations :none} + cenv (env/default-compiler-env)] + (test/delete-out-files out) + (build/build nil opts cenv))) + +(deftest test-cljs-3235 + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (testing "Test various require patterns for Node and foreign libraries" + (let [ws (atom []) + out (.getPath (io/file (test/tmp-dir) "cljs-3235-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'cljs-3235.core + :output-dir out + :optimizations :none + :target :nodejs + :install-deps true + :npm-deps {:react "16.13.0" + :react-dom "16.13.0" + :react-select "5.2.1"} + :foreign-libs [{:file (.getPath (io/file "src" "test" "cljs_build" "cljs_3235" "foreign.js")) + :provides ["some-foreign"] + :global-exports '{some-foreign globalLib}}] + :closure-warnings {:check-types :off + :non-standard-jsdoc :off}}} + cenv (env/default-compiler-env opts)] + (test/delete-out-files out) + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (build/build (build/inputs (io/file inputs "cljs_3235/core.cljs")) opts cenv)) + (is (.exists (io/file out "cljs_3235/core.js"))) + (is (true? (boolean (re-find #"cljs_3235\.core\.node\$module\$react_select\$default = require\('react-select'\)\['default'\];" + (slurp (io/file out "cljs_3235/core.js")))))) + (is (true? (boolean (re-find #"cljs_3235\.core\.node\$module\$react_select\$default\$baz = require\('react-select'\)\['default'\]\['baz'\];" + (slurp (io/file out "cljs_3235/core.js")))))) + (is (true? (boolean (re-find #"cljs_3235\.core\.global\$module\$some_foreign\$woz = goog.global\[\"globalLib\"\]\['woz'\];" + (slurp (io/file out "cljs_3235/core.js")))))) + (is (true? (boolean (re-find #"cljs_3235\.core\.global\$module\$some_foreign\$foz\$boz = goog.global\[\"globalLib\"\]\['foz'\]\['boz'\];" + (slurp (io/file out "cljs_3235/core.js")))))) + (is (empty? @ws)))) + (.delete (io/file "package.json")) + (test/delete-node-modules)) + +(deftest test-cljs-3284 + (testing "Type hint warnings don't fire just because of private types" + (let [ws (atom []) + out (.getPath (io/file (test/tmp-dir) "cljs-3235-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'cljs-3284.core + :output-dir out + :optimizations :none}} + cenv (env/default-compiler-env opts)] + (test/delete-out-files out) + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (build/build (build/inputs (io/file inputs "cljs_3284/core.cljs")) opts cenv)) + (is (empty? @ws))))) + +(deftest test-cljs-3311-regress + (testing "Test that CLJS-3311 did not regress" + (let [ws (atom []) + out (.getPath (io/file (test/tmp-dir) "cljs-3311-regress-out")) + {:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'cljs-3311-regress.core + :output-dir out + :optimizations :none}} + cenv (env/default-compiler-env opts)] + (test/delete-out-files out) + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (build/build (build/inputs (io/file inputs "cljs_3311_regress/core.cljs")) opts cenv)) + (is (empty? @ws))))) + +(deftest test-cljs-3332 + (testing "Test that package.json w/ exports work, Firebase as example" + (let [out (.getPath (io/file (test/tmp-dir) "npm-deps-test-out"))] + (test/delete-out-files out) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (let [{:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'firebase.core + :output-dir out + :optimizations :none + :install-deps true + :npm-deps {:firebase "9.3.0"} + :closure-warnings {:check-types :off} + :target :bundle}} + cenv (env/default-compiler-env)] + (build/build (build/inputs (io/file inputs "firebase/core.cljs")) opts cenv) + (is (= #{"firebase/auth"} (:node-module-index @cenv)))) + (.delete (io/file "package.json")) + (test/delete-node-modules) + (test/delete-out-files out)))) + +(deftest test-cljs-3346-as-alias + (testing "Test that using :as-alias does not load the namespace, and that + a namespace that does not exist on file can be used." + (let [out (.getPath (io/file (test/tmp-dir) "cljs-3346-as-alias-out"))] + (test/delete-out-files out) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (let [{:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'cljs-3346-as-alias.core + :output-dir out + :optimizations :none + :closure-warnings {:check-types :off}}} + cenv (env/default-compiler-env)] + (build/build (build/inputs (io/file inputs "cljs_3346_as_alias/core.cljs")) opts cenv)) + (let [source (slurp (io/file out "cljs_3346_as_alias/core.js"))] + (is (true? (boolean (re-find #"goog.require\('cljs.core'\)" source)))) + (is (false? (boolean (re-find #"goog.require\('clojure.set'\)" source)))) + (is (false? (boolean (re-find #"goog.require\('made.up.lib'\)" source)))) + (is (true? (boolean (re-find #"clojure\.set\/foo" source)))) + (is (true? (boolean (re-find #"made\.up\.lib\/bar" source))))) + (.delete (io/file "package.json")) + (test/delete-node-modules) + (test/delete-out-files out)))) + +#_(deftest test-cljs-3452-str-optimizations + (testing "Test that uses compile time optimizations from str macro" + (let [out (.getPath (io/file (test/tmp-dir) "cljs-3452-str-optimizations-out"))] + (test/delete-out-files out) + (let [{:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'cljs-3452-str-optimizations.core + :output-dir out + :optimizations :none + :closure-warnings {:check-types :off}}} + cenv (env/default-compiler-env)] + (build/build (build/inputs (io/file inputs "cljs_3452_str_optimizations/core.cljs")) opts cenv)) + (let [source (slurp (io/file out "cljs_3452_str_optimizations/core.js"))] + (testing "only seven string concats, compile time nil is ignored" + (is (= 7 (count (re-seq #"[\+]" source))))) + (testing "only two 1-arity str calls, compile time constants are optimized" + (is (= 2 (count (re-seq #"\$1\(.*?\)" source)))))) + (test/delete-out-files out)))) + +#_(deftest test-advanced-source-maps + (testing "Test that the `sources` of the final merged source map matches the + one in the original Closure Compiler generated advanced source map" + (let [out (.getPath (io/file (test/tmp-dir) "adv-src-map"))] + (test/delete-out-files out) + (test/delete-node-modules) + (let [{:keys [inputs opts]} {:inputs (str (io/file "src" "test" "cljs_build")) + :opts {:main 'cljs-3346-as-alias.core + :output-to (.getPath (io/file out "main.js")) + :source-map (.getPath (io/file out "main.js.map")) + :output-dir out + :optimizations :advanced + :closure-source-map true}} + cenv (env/default-compiler-env)] + (build/build (build/inputs (io/file inputs "adv_src_map/core.cljs")) opts cenv)) + (let [cljs-src-map (->> (io/file out "main.js.map") slurp json/read-str) + closure-src-map (->> (io/file out "main.js.map.closure") slurp json/read-str)] + (println (get closure-src-map "sources")) + (println (get cljs-src-map "sources"))) + (test/delete-out-files out)))) + +#_(comment + + (clojure.test/test-vars [#'test-advanced-source-maps]) + + ) \ No newline at end of file diff --git a/src/test/clojure/cljs/closure_tests.clj b/src/test/clojure/cljs/closure_tests.clj new file mode 100644 index 0000000000..d373efd9e4 --- /dev/null +++ b/src/test/clojure/cljs/closure_tests.clj @@ -0,0 +1,513 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.closure-tests + (:refer-clojure :exclude [compile]) + (:use cljs.closure clojure.test) + (:require [cljs.build.api :as build] + [cljs.vendor.clojure.data.json :as json] + [clojure.java.shell :as sh] + [cljs.closure :as closure] + [cljs.js-deps :as deps] + [cljs.util :as util] + [cljs.test-util :as test] + [clojure.java.io :as io] + [clojure.string :as string]) + (:import [java.io File] + [com.google.javascript.jscomp JSChunk])) + +(deftest test-make-preamble + (testing "no options" + (is (= "" (make-preamble {})))) + (testing "nodejs" + (testing "with default hashbang" + (is (= "#!/usr/bin/env node\n" (make-preamble {:target :nodejs})))) + (testing "with custom hashbang" + (is (= "#!/bin/env node\n" (make-preamble {:target :nodejs + :hashbang "/bin/env node"})))) + (testing "with no hashbang" + (is (= "" (make-preamble {:target :nodejs + :hashbang false}))) + (testing "and preamble" + (is (= "var preamble1 = require(\"preamble1\");\n" + (make-preamble {:target :nodejs + :hashbang false + :preamble ["cljs/preamble1.js"]}))))) + (testing "with preamble" + (is (= "#!/usr/bin/env node\nvar preamble1 = require(\"preamble1\");\n" + (make-preamble {:target :nodejs + :preamble ["cljs/preamble1.js"]}))))) + (testing "preamble" + (is (= "var preamble1 = require(\"preamble1\");\nvar preamble2 = require(\"preamble2\");\n" + (make-preamble {:preamble ["cljs/preamble1.js" + "cljs/preamble2.js"]}))))) + +(deftest test-check-sourcemap + (testing "optimizations none" + (is (check-source-map {:source-map true :optimizations :none})) + (is (check-source-map {:source-map false :optimizations :none})) + (is (thrown? AssertionError (check-source-map {:source-map "target/build/app.js.map" :optimizations :none}))))) + +(deftest test-cljs-1882-constants-table-is-sorted + (let [out (.getPath (io/file (test/tmp-dir) "cljs-1882-out")) + project (test/project-with-modules out) + modules (-> project :opts :modules)] + (test/delete-out-files out) + (build/build (build/inputs (:inputs project)) (:opts project)) + (let [compiler (closure/make-closure-compiler) + module (JSChunk. "module-c")] + (.initOptions compiler (closure/make-options (:opts project))) + (doseq [file ["cljs/core/constants.js" + "module_test/modules/a.js" + "cljs/core.js"]] + (.add module (closure/js-source-file nil (io/file out file)))) + (.sortInputsByDeps module compiler) + (is (= (->> (.getInputs module) + (map #(string/replace + (.getName %) + (str (string/replace out #"[\\\/]" "/") "/") ""))) + ["cljs/core.js" + "cljs/core/constants.js" + "module_test/modules/a.js"]))))) + +(deftest test-string-provides + (is (= ["CB0BFFB"] (deps/-provides "var x = 42;")))) + +(deftest test-lib-rel-path-cljs-2152 + (let [ijs {:provides ["tabby"] + :url (io/as-url (io/file "src/test/cljs/js_libs/tabby.js")) + :lib-path "src/test/cljs/js_libs"}] + (is (= (closure/lib-rel-path ijs) "tabby.js"))) + (let [ijs {:provides ["tabby"] + :url (io/as-url (io/file "src/test/cljs/js_libs/tabby.js")) + :lib-path (.getAbsolutePath (io/file "src/test/cljs/js_libs/tabby.js"))}] + (is (= (closure/lib-rel-path ijs) "tabby.js")))) + +(deftest test-index-node-modules + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (closure/maybe-install-node-deps! {:npm-deps {:left-pad "1.1.3"}}) + (let [modules (closure/index-node-modules-dir)] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/left-pad/index.js")) + :provides ["left-pad/index.js" + "left-pad/index" + "left-pad"]})) + modules)))) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (closure/maybe-install-node-deps! {:npm-deps {:react "15.6.1" + :react-dom "15.6.1"}}) + (let [modules (closure/index-node-modules-dir)] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react/react.js")) + :provides ["react/react.js" + "react/react" + "react"]})) + modules))) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react/lib/React.js")) + :provides ["react/lib/React.js" "react/lib/React"]})) + modules))) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react-dom/server.js")) + :provides ["react-dom/server.js" "react-dom/server"]})) + modules)))) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (closure/maybe-install-node-deps! {:npm-deps {:node-fetch "1.7.1"}}) + (let [modules (closure/index-node-modules-dir)] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/node-fetch/lib/index.js")) + :provides ["node-fetch/lib/index.js" + "node-fetch/lib/index" + "node-fetch/lib"]})) + modules)))) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (closure/maybe-install-node-deps! {:npm-deps {"@comandeer/css-filter" "1.0.1"}}) + (let [modules (closure/index-node-modules-dir)] + (is (true? (some (fn [module] + (= module + {:file (.getAbsolutePath (io/file "node_modules/@comandeer/css-filter/dist/css-filter.umd.js")) + :module-type :es6 + :provides ["@comandeer/css-filter/dist/css-filter.umd.js" + "@comandeer/css-filter/dist/css-filter.umd" + "@comandeer/css-filter"]})) + modules)))) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (closure/maybe-install-node-deps! {:npm-deps {"jss-extend" "5.0.0"}}) + (let [modules (closure/index-node-modules-dir)] + (is (true? (some (fn [module] + (= module + {:file (.getAbsolutePath (io/file "node_modules/jss-extend/lib/index.js")) + :module-type :es6 + :provides ["jss-extend/lib/index.js" + "jss-extend/lib/index" + "jss-extend" + "jss-extend/lib"]})) + modules)))) + (.delete (io/file "package.json")) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (closure/maybe-install-node-deps! {:npm-deps {"@codemirror/state" "0.17.1"}}) + (let [modules (closure/index-node-modules-dir)] + (is (true? (some (fn [module] + (= module + {:file (.getAbsolutePath (io/file "node_modules/@codemirror/state/dist/index.js")) + :module-type :es6 + :provides ["@codemirror/state/dist/index.js" + "@codemirror/state/dist/index" + "@codemirror/state" + "@codemirror/state/dist"]})) + modules)))) + (.delete (io/file "package.json")) + (test/delete-node-modules)) + +(deftest test-index-node-modules-module-deps-js + (spit (io/file "package.json") "{}") + (let [opts {:npm-deps {:left-pad "1.1.3"}} + out (util/output-directory opts)] + (test/delete-node-modules) + (test/delete-out-files out) + (closure/maybe-install-node-deps! opts) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/left-pad/index.js")) + :provides ["left-pad" + "left-pad/index.js" + "left-pad/index"]})) + (closure/index-node-modules ["left-pad"] opts)))) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (test/delete-out-files out) + (let [opts {:npm-deps {:react "15.6.1" + :react-dom "15.6.1"}} + _ (closure/maybe-install-node-deps! opts) + modules (closure/index-node-modules ["react" "react-dom" "react-dom/server"] opts)] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react/react.js")) + :provides ["react" + "react/react.js" + "react/react"]})) + modules))) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react/lib/React.js")) + :provides ["react/lib/React.js" "react/lib/React"]})) + modules))) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react-dom/server.js")) + :provides ["react-dom/server.js" "react-dom/server"]})) + modules)))) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (test/delete-out-files out) + (let [opts {:npm-deps {:node-fetch "1.7.1"} + :target :nodejs}] + (closure/maybe-install-node-deps! opts) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/node-fetch/lib/index.js")) + :provides ["node-fetch/lib/index.js" + "node-fetch/lib/index" + "node-fetch/lib"]})) + (closure/index-node-modules ["node-fetch/lib"] opts))))) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (test/delete-out-files out) + (let [opts {:npm-deps {"@comandeer/css-filter" "1.0.1"}}] + (closure/maybe-install-node-deps! opts) + (is (true? (some (fn [module] + (= module + {:file (.getAbsolutePath (io/file "node_modules/@comandeer/css-filter/dist/css-filter.umd.js")) + :module-type :es6 + :provides ["@comandeer/css-filter" + "@comandeer/css-filter/dist/css-filter.umd.js" + "@comandeer/css-filter/dist/css-filter.umd"]})) + (closure/index-node-modules ["@comandeer/css-filter"] opts))))) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (test/delete-out-files out) + (let [opts {:npm-deps {"jss-extend" "5.0.0"}}] + (closure/maybe-install-node-deps! opts) + (is (true? (some (fn [module] + (= module + {:file (.getAbsolutePath (io/file "node_modules/jss-extend/lib/index.js")) + :module-type :es6 + :provides ["jss-extend" + "jss-extend/lib/index.js" + "jss-extend/lib/index" + "jss-extend/lib"]})) + (closure/index-node-modules ["jss-extend"] opts))))) + (.delete (io/file "package.json")) + (test/delete-node-modules) + (test/delete-out-files out))) + +(deftest test-cljs-2315 + (spit (io/file "package.json") (json/json-str {:devDependencies {"@cljs-oss/module-deps" "*"}})) + (apply sh/sh (cond->> ["npm" "install"] + util/windows? (into ["cmd" "/c"]))) + (let [file (io/file (test/tmp-dir) "cljs-2315-inputs.js") + _ (spit file "require('./src/test/cljs_build/json_modules_test/a.js');") + node-inputs (closure/node-inputs [{:file (str file)}])] + (is (= node-inputs + [{:file (.getAbsolutePath (io/file "src/test/cljs_build/json_modules_test/a.js")) + :module-type :es6} + {:file (.getAbsolutePath (io/file "src/test/cljs_build/json_modules_test/b.json")) + :module-type :es6}]))) + (.delete (io/file "package.json")) + (test/delete-node-modules)) + +(deftest test-cljs-2318 + (spit (io/file "package.json") "{}") + (let [opts {:npm-deps {:react "15.6.1" + :react-dom "15.6.1" + :react-addons-css-transition-group "15.5.1" + "@blueprintjs/core" "1.24.0"}} + out (util/output-directory opts)] + (test/delete-node-modules) + (test/delete-out-files out) + (closure/maybe-install-node-deps! opts) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/tslib/tslib.es6.js")) + :provides ["tslib" + "tslib/tslib.es6.js" + "tslib/tslib.es6"]})) + (closure/index-node-modules ["tslib"] opts)))) + (.delete (io/file "package.json")) + (test/delete-node-modules) + (test/delete-out-files out))) + +(deftest test-cljs-2327 + (spit (io/file "package.json") "{}") + (let [opts {:npm-deps {:react "16.0.0-beta.5" + :react-dom "16.0.0-beta.5"}} + out (util/output-directory opts)] + (test/delete-node-modules) + (test/delete-out-files out) + (closure/maybe-install-node-deps! opts) + (let [modules (closure/index-node-modules ["react" "react-dom" "react-dom/server"] opts)] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react/index.js")) + :provides ["react" + "react/index.js" + "react/index"]})) + modules))) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react-dom/index.js")) + :provides ["react-dom" + "react-dom/index.js" + "react-dom/index"]})) + modules))) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react-dom/server.browser.js")) + :provides ["react-dom/server.js" + "react-dom/server" + "react-dom/server.browser.js" + "react-dom/server.browser"]})) + modules)))) + (test/delete-node-modules) + (test/delete-out-files out) + (spit (io/file "package.json") "{}") + (let [opts {:npm-deps {:warning "3.0.0"}} + _ (closure/maybe-install-node-deps! opts) + modules (closure/index-node-modules ["warning"] opts)] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/warning/browser.js")) + :provides ["warning" + "warning/browser.js" + "warning/browser"]})) + modules)))) + (test/delete-node-modules) + (test/delete-out-files out) + (spit (io/file "package.json") "{}") + (let [opts {:npm-deps {:react-dom "16.0.0-beta.5" + :react "16.0.0-beta.5"} + :target :nodejs} + _ (closure/maybe-install-node-deps! opts) + modules (closure/index-node-modules ["react-dom/server"] opts)] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/react-dom/server.js")) + :provides ["react-dom/server.js" + "react-dom/server"]})) + modules)))) + (.delete (io/file "package.json")) + (test/delete-node-modules) + (test/delete-out-files out))) + +(deftest test-cljs-2326 + (spit (io/file "package.json") "{}") + (let [opts {:npm-deps {:bootstrap "4.0.0-beta"}} + out (util/output-directory opts)] + (test/delete-node-modules) + (test/delete-out-files out) + (closure/maybe-install-node-deps! opts) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/bootstrap/dist/js/bootstrap.js")) + :provides ["bootstrap" + "bootstrap/dist/js/bootstrap.js" + "bootstrap/dist/js/bootstrap"]})) + (closure/index-node-modules ["bootstrap"] opts)))) + (test/delete-node-modules) + (spit (io/file "package.json") "{}") + (test/delete-out-files out)) + (closure/maybe-install-node-deps! {:npm-deps {:bootstrap "4.0.0-beta"}}) + (let [modules (closure/index-node-modules-dir)] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/bootstrap/dist/js/bootstrap.js")) + :provides ["bootstrap/dist/js/bootstrap.js" + "bootstrap/dist/js/bootstrap" + "bootstrap"]})) + modules)))) + (.delete (io/file "package.json")) + (test/delete-node-modules)) + +(deftest test-cljs-2332 + (spit (io/file "package.json") "{}") + (let [opts {:npm-deps {"@material/drawer" "0.5.4"}} + out (util/output-directory opts)] + (test/delete-node-modules) + (test/delete-out-files out) + (closure/maybe-install-node-deps! opts) + (let [modules (closure/index-node-modules ["@material/drawer"] opts)] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/@material/drawer/slidable/constants.js")) + :provides ["@material/drawer/slidable/constants.js" + "@material/drawer/slidable/constants"]})) + modules)))) + (.delete (io/file "package.json")) + (test/delete-node-modules) + (test/delete-out-files out))) + +(deftest test-cljs-2333 + (spit (io/file "package.json") "{}") + (let [opts {:npm-deps {"asap" "2.0.6"}} + out (util/output-directory opts)] + (test/delete-node-modules) + (test/delete-out-files out) + (closure/maybe-install-node-deps! opts) + (let [modules (closure/index-node-modules ["asap"] opts)] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/asap/browser-asap.js")) + :provides ["asap/asap", + "asap/asap", + "asap/asap.js", + "asap/asap", + "asap", + "asap/browser-asap.js", + "asap/browser-asap"]})) + modules)))) + (.delete (io/file "package.json")) + (test/delete-node-modules) + (test/delete-out-files out))) + +(deftest test-cljs-2580 + (spit (io/file "package.json") "{}") + (let [opts {:npm-deps {"npm-package-with-main-entry-pointing-to-folder" "1.0.0"} + :target :nodejs} + out (util/output-directory opts)] + (test/delete-node-modules) + (test/delete-out-files out) + (closure/maybe-install-node-deps! opts) + (let [modules (closure/index-node-modules-dir)] + (is (true? (some (fn [module] + (= module + {:file (.getAbsolutePath (io/file "node_modules/npm-package-with-main-entry-pointing-to-folder/folder/index.js")) + :module-type :es6 + :provides ["npm-package-with-main-entry-pointing-to-folder/folder/index.js" + "npm-package-with-main-entry-pointing-to-folder/folder/index" + "npm-package-with-main-entry-pointing-to-folder" + "npm-package-with-main-entry-pointing-to-folder/folder"]})) + modules)))) + (let [modules (closure/index-node-modules ["npm-package-with-main-entry-pointing-to-folder"] opts)] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/npm-package-with-main-entry-pointing-to-folder/folder/index.js")) + :provides ["npm-package-with-main-entry-pointing-to-folder" + "npm-package-with-main-entry-pointing-to-folder/folder/index.js" + "npm-package-with-main-entry-pointing-to-folder/folder/index" + "npm-package-with-main-entry-pointing-to-folder/folder"]})) + modules)))) + (.delete (io/file "package.json")) + (test/delete-node-modules) + (test/delete-out-files out))) + +(deftest test-cljs-2592 + (spit (io/file "package.json") "{}") + (let [opts {:npm-deps {:iterall "1.2.2" + :graphql "0.13.1"} + :package-json-resolution :nodejs} + out (util/output-directory opts)] + (test/delete-node-modules) + (test/delete-out-files out) + (closure/maybe-install-node-deps! opts) + (let [modules (closure/index-node-modules ["iterall" "graphql"] opts)] + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/iterall/index.js")) + :provides ["iterall" + "iterall/index.js" + "iterall/index"]})) + modules))) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/graphql/index.js")) + :provides ["graphql" + "graphql/index.js" + "graphql/index"]})) + modules))) + (is (true? (some (fn [module] + (= module {:module-type :es6 + :file (.getAbsolutePath (io/file "node_modules/graphql/execution/index.js")) + :provides ["graphql/execution/index.js" + "graphql/execution/index" + "graphql/execution"]})) + modules)))) + (.delete (io/file "package.json")) + (test/delete-node-modules) + (test/delete-out-files out))) + +(defn empty-handler [warning-type env extra]) + +(deftest test-cljs-3074 + (testing "CLJS-3074: resolve-warning-handlers\n" + (testing "\tfunctions are left alone" + (let [h (fn [warning-type env extra])] + (is (= [h] (closure/resolve-warning-handlers [h]))))) + (testing "\tsymbols are resolved" + (is (= [#'empty-handler] (closure/resolve-warning-handlers [`empty-handler])))) + (testing "\tsymbols and fns can be mixed" + (let [h (fn [warning-type env extra])] + (is (= [h #'empty-handler] (closure/resolve-warning-handlers [h `empty-handler]))))) + (testing "\tinvalid warning handler types are detected" + (is (thrown-with-msg? Throwable + #"Invalid warning handler 1 of type class java.lang.Long" + (closure/resolve-warning-handlers [1])))) + (testing "\tnon-existent handlers are detected" + (is (thrown-with-msg? Throwable + #"Could not resolve warning handler: clojure.core/foo" + (closure/resolve-warning-handlers ['clojure.core/foo])))))) diff --git a/src/test/clojure/cljs/compiler/glib_module_test.clj b/src/test/clojure/cljs/compiler/glib_module_test.clj new file mode 100644 index 0000000000..3e6f12c0f9 --- /dev/null +++ b/src/test/clojure/cljs/compiler/glib_module_test.clj @@ -0,0 +1,33 @@ +(ns cljs.compiler.glib-module-test + (:require [cljs.compiler-tests :as comp-tests] + [cljs.env :as env] + [clojure.test :as test :refer [deftest is testing]])) + +(deftest test-glib-module-compile + (testing "glib modules compiled to Closure Compile expectations" + (let [src (env/with-compiler-env (env/default-compiler-env ) + (comp-tests/compile-form-seq + '[(ns test.foo + (:import [goog.module ModuleLoader])) + (def module-loader (ModuleLoader.))]))] + (is (re-find #"goog\.require\('goog\.module\.ModuleLoader'\)" src)) + (is (re-find #"test\.foo\.goog\$module\$goog\$module\$ModuleLoader = goog\.module\.get\('goog.module.ModuleLoader'\)" src)) + (is (re-find #"test\.foo\.module_loader = \(new test\.foo\.goog\$module\$goog\$module\$ModuleLoader\(\)\)" src))))) + +(deftest cljs-3330-global-goog-object&array + (testing "migration path for goog.module impact on goog.object & goog.array" + (let [src (env/with-compiler-env + (env/default-compiler-env {:global-goog-object&array true}) + (comp-tests/compile-form-seq + '[(ns test.foo + (:require [goog.object :as gobj] + [goog.array :as garray])) + (def module-loader (ModuleLoader.))]))] + (is (re-find #"goog\.require\('goog\.object\'\)" src)) + (is (re-find #"goog\.require\('goog\.array\'\)" src))))) + +(comment + + (test/run-tests) + + ) diff --git a/src/test/clojure/cljs/compiler_tests.clj b/src/test/clojure/cljs/compiler_tests.clj new file mode 100644 index 0000000000..f6f7b560b4 --- /dev/null +++ b/src/test/clojure/cljs/compiler_tests.clj @@ -0,0 +1,427 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.compiler-tests + (:use clojure.test) + (:require [cljs.analyzer :as ana] + [cljs.compiler :as comp] + [cljs.compiler.api :as comp-api] + [cljs.env :as env] + [cljs.util :as util] + [cljs.tagged-literals :as tags] + [clojure.java.io :as io] + [clojure.string :as str] + [clojure.test :as test]) + (:import [java.io File])) + +(defn analyze + ([env form] + (env/ensure (ana/analyze env form))) + ([env form name] + (env/ensure (ana/analyze env form name))) + ([env form name opts] + (env/ensure (ana/analyze env form name opts)))) + +(defn emit [ast] + (env/ensure (comp/emit ast))) + +(def aenv (assoc-in (ana/empty-env) [:ns :name] 'cljs.user)) +(def cenv (env/default-compiler-env)) + +(defn compile-form-seq + ([forms] + (compile-form-seq forms + (when env/*compiler* + (:options @env/*compiler*)))) + ([forms opts] + (with-out-str + (binding [ana/*cljs-ns* 'cljs.user] + (doseq [form forms] + (comp/emit (ana/analyze (ana/empty-env) form))))))) + +#_(deftest should-recompile + (let [src (File. "test/hello.cljs") + dst (File/createTempFile "compilertest" ".cljs") + opt {:optimize-constants true} + optmod {:optimize-constants true :elide-asserts false}] + (with-redefs [util/*clojurescript-version* {:major 0 :minor 0 :qualifier 42}] + (env/with-compiler-env (env/default-compiler-env) + (.setLastModified dst (- (.lastModified src) 100)) + (is (comp/requires-compilation? src dst opt)) + (comp/compile-file src dst opt) + (is (not (comp/requires-compilation? src dst opt))) + (is (comp/requires-compilation? src dst optmod)) + (comp/compile-file src dst optmod) + (is (not (comp/requires-compilation? src dst optmod))))))) + +(deftest fn-scope-munge + (is (= (comp/munge + (get-in + (analyze aenv + '(defn foo [] + (fn bar []))) + [:init :name])) + 'cljs$user$foo)) + (is (= (comp/munge + (get-in + (analyze aenv + '(defn foo [] + (fn bar []))) + [:init :methods 0 :body :ret :local])) + 'cljs$user$foo_$_bar)) + (is (= (comp/munge + (get-in + (analyze aenv + '(fn [] + (fn console []))) + [:methods 0 :body :ret :local])) + 'cljs$user$console))) + +(deftest test-js-negative-infinity + (is (= (with-out-str + (emit + (analyze (assoc aenv :context :expr) 'js/-Infinity))) + "-Infinity"))) + +(deftest test-cljs-2352 + (are [form result] + (= (with-out-str + (emit + (analyze (assoc aenv :context :expr) form))) + result) + Double/NaN "NaN" + Double/POSITIVE_INFINITY "Infinity" + Double/NEGATIVE_INFINITY "-Infinity")) + +(deftest test-munge-dotdot + (is (= 'cljs.core._DOT__DOT_ (comp/munge 'cljs.core/..))) + (is (= "cljs.core._DOT__DOT_" (comp/munge "cljs.core/.."))) + (is (= 'cljs.core._DOT__DOT_ + (ana/no-warn + (env/with-compiler-env cenv + (comp/munge + (:info (analyze {:ns {:name 'cljs.core}} 'cljs.core/..)))))))) + +(deftest test-resolve-dotdot + (is (= '{:name cljs.core/.. + :ns cljs.core} + (ana/no-warn + (env/with-compiler-env cenv + (select-keys + (ana/resolve-var {:ns {:name 'cljs.core}} '..) + [:name :ns])))))) + +(deftest test-cljs-428 + (letfn [(check-docs [docs] + (is (= 1 (count (re-seq #"\*/" docs)))))] + (check-docs (with-out-str + (env/ensure + (comp/emit-comment "/* multiline comments */" nil)))) + (check-docs (with-out-str + (emit + (analyze aenv + '(defn foo "foo is */ like this /*/" [] (+ 1 1)))))))) + +(comment + (env/with-compiler-env cenv + (emit + (analyze aenv + '(defn foo ([a]) ([a b]))))) + ) + +(defn capture-warnings* [f] + (let [capture (atom []) + tracker (fn [warning-type env & [extra]] + (when (warning-type ana/*cljs-warnings*) + (let [err (ana/error-message warning-type extra) + msg (ana/message env (str "WARNING: " err))] + (swap! capture conj [warning-type msg]))))] + (ana/with-warning-handlers [tracker] + (f)) + @capture)) + +(defmacro capture-warnings [& body] + `(capture-warnings* (fn [] ~@body))) + +(deftest or-doesnt-create-bindings + (let [cenv (atom @cenv)] + (binding [ana/*cljs-static-fns* true + ana/*analyze-deps* false] + (env/with-compiler-env cenv + (ana/analyze-file (File. "src/main/cljs/cljs/core.cljs")) + (let [warnings (-> (capture-warnings + (with-out-str + (emit + (analyze aenv + '(let [{:keys [a] :or {b 2}} {:a 1}] [a b]))))))] + (is (= (ffirst warnings) :undeclared-var)) + (is (.startsWith (-> warnings first second) + "WARNING: Use of undeclared Var cljs.user/b"))))))) + +(deftest no-warn-on-emit-invoke-protocol-method + (let [define-foo #(assoc-in % [::ana/namespaces 'cljs.user :defs 'foo] + {:ns 'cljs.user + :name 'cljs.user/foo + :fn-var true + :method-params '([x]) + :protocol 'cljs.user/Foo}) + aenv-with-foo (define-foo aenv) + cenv-with-foo (define-foo @cenv)] + (binding [ana/*cljs-static-fns* true] + (are [form] + (empty? + (capture-warnings + (env/with-compiler-env (atom cenv-with-foo) + (with-out-str + (emit + (analyze aenv-with-foo form)))))) + + '(cljs.user/foo nil) + '(cljs.user/foo 0) + '(cljs.user/foo (inc 0)) + '(cljs.user/foo "") + '(cljs.user/foo true) + '(cljs.user/foo false) + '(cljs.user/foo (nil? nil)) + '(cljs.user/foo (fn [x] x)) + `(cljs.user/foo ~(tags/->JSValue {})) + `(cljs.user/foo ~(tags/->JSValue [])) + '(cljs.user/foo (make-array 0)))))) + +(deftest test-cljs-1643 + (is (thrown-with-msg? Exception #"is not a valid ClojureScript constant." + (comp/emit-constant clojure.core/inc)))) + +(def test-cljs-1925-code + '(do + (defprotocol X + (x [x])) + + (defprotocol Y + (y [y])) + + (extend-protocol X + js/RegExp + (x [x] + (y x))) + + (extend-protocol Y + js/RegExp + (y [y] + :y)))) + +(def specify-test-code + '(do + (defprotocol IBug + (bug [this other] "A sample protocol")) + + (defn MyBug []) + (specify! (.-prototype MyBug) + IBug + (bug [this other] + "bug") + Object + (foo [this] + (bug this 3))))) + +(deftest test-cljs-1925 + (let [opts {:static-fns true} + cenv (env/default-compiler-env opts)] + (is (= [] (binding [ana/*unchecked-if* false + ana/*cljs-static-fns* true] + (capture-warnings + (env/with-compiler-env cenv + (with-out-str + (emit + (comp/with-core-cljs + opts + (fn [] (analyze aenv test-cljs-1925-code nil opts))))))))))) + (let [opts {:static-fns true} + cenv (env/default-compiler-env opts)] + (is (= [] (binding [ana/*unchecked-if* false + ana/*cljs-static-fns* true] + (capture-warnings + (env/with-compiler-env cenv + (with-out-str + (emit + (comp/with-core-cljs + opts + (fn [] (analyze aenv specify-test-code nil opts)))))))))))) + + +(deftest test-optimized-invoke-emit + (let [out-file + (io/file "target/invoke_test.js")] + (comp-api/with-core-cljs + (comp-api/compile-file + (io/file "src/test/cljs/cljs/invoke_test.cljs") + out-file + {:static-fns true})) + + (let [content (slurp out-file)] + ;; test for fn( not fn.call(, omitting arguments in test because they are not relevant + ;; should emit variadic invokes + (is (str/includes? content "cljs.invoke_test.variadic_fn.cljs$core$IFn$_invoke$arity$variadic(")) + ;; should emit optimized invokes + (is (str/includes? content "cljs.invoke_test.multi_fn.cljs$core$IFn$_invoke$arity$1(")) + ;; closure js code must never use .call( + (is (str/includes? content "goog.string.urlEncode(")) + ;; js/goog.string.urlDecode should not use .call + (is (str/includes? content "goog.string.urlDecode(")) + ;; We should NOT emit a let binding for simple (:dont-bind-this js/x) + (is (str/includes? content + (str "new cljs.core.Keyword(null,\"dont-bind-this\",\"dont-bind-this\"," + "-140451389).cljs$core$IFn$_invoke$arity$1(x);"))) + ;; CLJS-2046: Emit bindings for expressions like: (@m a0) or ((:x m) a0) + ;; The test: ((complement funexpr0) normal-arg) + (is (re-find #"(?m)^.*var fexpr.*=.*cljs.core.complement\(funexpr0\);$" + content)) + ;; CLJS-855: Emit binding for expressions like: + ;; (hofinvoke (inv-arg0)) + (is (re-find #"(?m)^.*var .*=.*inv_arg0.cljs.core.IFn._invoke.arity.0 \?.*$" + content)) + + ;; Now test both (855,2046) together: + ;; ((complement funexpr1) (inv-arg1)) + (is (re-find #"(?m)^.*var fexpr.*=.*cljs.core.complement\(funexpr1\);$" + content)) + (is (re-find #"(?m)^.*var .*=.*inv_arg1.cljs.core.IFn._invoke.arity.0 \?.*$" + content)) + ;; CLJS-1871: A declare hinted with :arglists meta should result in static dispatch + (is (str/includes? content "cljs.invoke_test.declared_fn(")) + ;; CLJS-2950: Direct field access for keyword lookup on records + (is (str/includes? content "cljs.invoke_test.foo_record.foo_field_a;"))))) +#_(test-vars [#'test-optimized-invoke-emit]) + +(deftest test-cljs-3077 + (let [opts {} + cenv (env/default-compiler-env opts) + + test-compile + (fn [code] + (env/with-compiler-env cenv + (with-out-str + (emit + (comp/with-core-cljs + opts + (fn [] (analyze aenv code nil opts))))))) + + snippet1 + (test-compile + '(defn wrapper1 [foo] + (let [x 1] + (prn (fn inner [] foo)) + (recur (inc foo))))) + + snippet2 + (test-compile + '(defn wrapper2 [foo] + (loop [x 1] + (prn (fn inner [] x)) + (recur (inc x)) + ))) + + snippet3 + (test-compile + '(defn no-wrapper1 [foo] + (let [x 1] + (prn (fn inner [] foo)))))] + + ;; FIXME: not exactly a clean way to test if function wrappers are created or not + ;; captures foo,x + (is (str/includes? snippet1 "(function (foo,x){")) + ;; captures x + (is (str/includes? snippet2 "(function (x){")) + ;; no capture, no loop or recur + (is (not (str/includes? snippet3 "(function (foo,x){"))) + (is (not (str/includes? snippet3 "(function (foo){"))) + (is (not (str/includes? snippet3 "(function (x){"))) + )) + +(deftest test-goog-ctor-import-gen + (is (true? (str/includes? + (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(ns test.foo + (:import [goog.history Html5History])) + (defn bar [] Html5History)])) + "return goog.history.Html5History;"))) + (is (true? (str/includes? + (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(ns test.foo + (:import [goog.history Html5History])) + (def hist (Html5History.))])) + "(new goog.history.Html5History());")))) + +(deftest emit-source-ns*-retains-ns-name ;; CLJS-3273 + (let [input (java.io.File/createTempFile "foo" ".cljs") + output (java.io.File/createTempFile "foo" ".js") + _ (spit input "(ns foo.foo) (require 'clojure.string)") + ns-info (env/ensure (comp/emit-source input output "cljs" {}))] + (is (= 'foo.foo (:ns ns-info))))) + +(deftest test-3368-global-shadowing + (testing "Let binding which use JS global names should get shadowed" + (let [code (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(defn foo [] + (let [window js/window] + window))]))] + (is (re-find #"window__\$1" code))))) + +(deftest test-externs-infer-is-nan + (testing "Not calls to truth_ if (.isNaN js/Number ...) is used as a test" + (let [code (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(if (.isNaN js/Number 1) true false)]))] + (is (nil? (re-find #"truth_" code)))))) + +(deftest test-goog-lib-infer-boolean + (testing "Can infer goog.string/contains returns boolean" + (let [code (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(ns test.foo + (:require [goog.string :as gstring])) + (if (gstring/contains "foobar" "foo") true false)]))] + (is (nil? (re-find #"truth_" code)))))) + +(deftest test-goog-module-infer-cljs-3438 + (testing "goog.object/containKey requires correct handling of vars from + goog.module namespace" + (let [code (env/with-compiler-env (env/default-compiler-env) + (compile-form-seq + '[(ns test.foo + (:require [goog.object :as gobject])) + (if (gobject/containsKey nil nil) true false)]))] + (is (nil? (re-find #"truth_" code)))))) + +;; CLJS-1225 + +(comment + (binding [ana/*cljs-static-fns* true] + (env/with-compiler-env cenv + (emit + (analyze aenv + '(defn incme [] + (let [incme (fn [a queue & args])] + (println (incme 1 [1] 1 1)))))))) + ) + +(comment + ;; combining boolean hint w/ static fns + + (binding [ana/*cljs-static-fns* true] + (env/with-compiler-env cenv + (emit + (analyze aenv + '(defn foo [x] + (if ^boolean (goog.array/isEmpty x) + true + false)))))) + ) diff --git a/src/test/clojure/cljs/externs_infer_tests.clj b/src/test/clojure/cljs/externs_infer_tests.clj new file mode 100644 index 0000000000..967164d1f2 --- /dev/null +++ b/src/test/clojure/cljs/externs_infer_tests.clj @@ -0,0 +1,638 @@ +(ns cljs.externs-infer-tests + (:require + [cljs.analyzer :as ana] + [cljs.analyzer-tests :refer [analyze collecting-warning-handler test-cenv]] + [cljs.compiler :as comp] + [cljs.closure :as closure] + [cljs.env :as env] + [cljs.externs :as externs] + [cljs.test-util :refer [unsplit-lines]] + [cljs.util :as util] + [clojure.string :as string] + [clojure.test :as test :refer [is are deftest testing]])) + +(def externs-cenv + (atom + {::ana/externs + (externs/externs-map + (closure/load-externs + {:externs ["src/test/externs/test.js"]}))})) + +(def core-inferred + ["var setTimeout;" "var process;" "process.hrtime;" + "goog.isArrayLike;" "Java.type;" "Object.out;" "Object.out.println;" + "Object.error;" "Object.error.println;"]) + +(deftest test-normalize-js-tag + (is (= 'js (ana/normalize-js-tag 'js))) + (is (= '[Foo] (-> 'js/Foo ana/normalize-js-tag meta :prefix))) + (is (true? (-> 'js/Foo ana/normalize-js-tag meta :prefix last meta :ctor))) + (is (= '[Foo Bar] (-> 'js/Foo.Bar ana/normalize-js-tag meta :prefix))) + (is (true? (-> 'js/Foo.Bar ana/normalize-js-tag meta :prefix last meta :ctor)))) + +(deftest test-normalize-unresolved-prefix + (let [pre (-> (ana/normalize-js-tag 'js/Foo) meta :prefix (conj 'bar))] + (is (= '[Foo prototype bar] (ana/normalize-unresolved-prefix pre)))) + (let [pre '[Foo bar]] + (is (= '[Foo bar] (ana/normalize-unresolved-prefix pre))))) + +(comment + + (test/test-vars [#'test-normalize-js-tag]) + (test/test-vars [#'test-normalize-unresolved-prefix]) + + ) + +(deftest test-resolve-extern + (let [externs + (externs/externs-map + (closure/load-externs + {:externs ["src/test/externs/test.js"] + :use-only-custom-externs true}))] + (is (some? (ana/resolve-extern '[baz] externs))) + (is (nil? (ana/resolve-extern '[Foo gozMethod] externs))))) + +(deftest test-has-extern?-basic + (let [externs (externs/externs-map + (closure/load-externs + {:externs ["src/test/externs/test.js"] + :use-only-custom-externs true}))] + (is (true? (ana/has-extern? '[Foo] externs))) + (is (true? (ana/has-extern? '[Foo wozMethod] externs))) + (is (false? (ana/has-extern? '[foo] externs))) + (is (false? (ana/has-extern? '[Foo gozMethod] externs))) + (is (true? (ana/has-extern? '[baz] externs))) + (is (false? (ana/has-extern? '[Baz] externs))))) + +(deftest test-resolve-extern + (let [externs (externs/externs-map)] + (is (= '[Number] + (-> (ana/resolve-extern '[Number] externs) :resolved))) + (is (= '[Number prototype valueOf] + (-> (ana/resolve-extern '[Number valueOf] externs) :resolved))) + (is (= '[Console] + (-> (ana/resolve-extern '[console] externs) :resolved))) + (is (= '[Console prototype log] + (-> (ana/resolve-extern '[console log] externs) :resolved))) + (is (= '[undefined] + (-> (ana/resolve-extern '[undefined] externs) :resolved))) + (is (= '[webCrypto Crypto prototype subtle] + (-> (ana/resolve-extern '[crypto subtle] externs) :resolved))))) + +(comment + (clojure.test/test-vars [#'test-resolve-extern]) + + (def externs (externs/externs-map)) + ;; succeeds + (ana/resolve-extern '[console] externs) + (ana/resolve-extern '[console log] externs) + (ana/resolve-extern '[undefined] externs) + (ana/resolve-extern '[Number] externs) + (ana/resolve-extern '[Number isNaN] externs) + (ana/resolve-extern '[document] externs) + + ) + +(deftest test-has-extern?-defaults + (let [externs (externs/externs-map)] + (is (true? (ana/has-extern? '[console] externs))) + (is (true? (ana/has-extern? '[console log] externs))) + (is (true? (ana/has-extern? '[Number isNaN] externs))))) + +(deftest test-js-tag + (let [externs (externs/externs-map + (closure/load-externs + {:externs ["src/test/externs/test.js"]}))] + (is (= 'js/Console (ana/js-tag '[console] :tag externs))) + (is (= 'js/Function (ana/js-tag '[console log] :tag externs))) + (is (= 'js/undefined (ana/js-tag '[console log] :ret-tag externs))) + (is (= 'boolean (ana/js-tag '[Number isNaN] :ret-tag externs))) + (is (= 'js/Foo (ana/js-tag '[baz] :ret-tag externs))))) + +(comment + + (clojure.test/test-vars [#'test-js-tag]) + + ) + +(defn infer-test-helper + [{:keys [forms externs warnings warn js-dependency-index node-module-index with-core? opts]}] + (let [test-cenv (atom + (cond-> + (if with-core? + (env/default-compiler-env* + (closure/add-externs-sources (merge {:infer-externs true} opts))) + {::ana/externs + (externs/externs-map + (closure/load-externs {:externs (or externs [])}))}) + js-dependency-index (assoc :js-dependency-index js-dependency-index) + node-module-index (assoc :node-module-index node-module-index))) + wrap (if with-core? + #(comp/with-core-cljs nil %) + #(do (%)))] + (ana/with-warning-handlers [(collecting-warning-handler (or warnings (atom [])))] + (binding [ana/*analyze-deps* false + ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env test-cenv + (wrap + (fn [] + (binding [ana/*analyze-deps* true + ana/*cljs-warnings* + (assoc ana/*cljs-warnings* + :infer-warning (if (nil? warn) true warn))] + (ana/analyze-form-seq forms)) + (with-out-str + (comp/emit-externs + (reduce util/map-merge {} + (map (comp :externs second) + (get @test-cenv ::ana/namespaces)))))))))))) + +(deftest test-externs-type-infer + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(.isNaN js/Number 1)))) + :tag))) + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(js/Number.isNaN 1)))) + :tag))) + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(let [x js/Number] + (.isNaN x 1))))) + :tag))) + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(js/isNaN 1)))) + :tag))) + (is (= 'js/Promise + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(.generateKey js/crypto.subtle)))) + :tag))) + (is (= 'string + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(.toUpperCase "foo")))) + :tag))) + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(.isArray js/Array (array))))) + :tag))) + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(.isSafeInteger js/Number 1)))) + :tag))) + (is (= 'boolean + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env (env/default-compiler-env) + (analyze (ana/empty-env) '(js/isFinite 1)))) + :tag)))) + +(deftest test-externs-infer + (is (= 'js/Foo + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env externs-cenv + (analyze (ana/empty-env) 'js/baz))) + :info :ret-tag))) + (is (= 'js/Foo + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env externs-cenv + (analyze (ana/empty-env) '(js/baz)))) + :tag))) + (is (= 'js + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env externs-cenv + (analyze (ana/empty-env) '(js/woz)))) + :tag))) + (is (= 'js + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env externs-cenv + (analyze (ana/empty-env) '(def foo (js/woz))))) + :tag))) + (is (= 'js + (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (env/with-compiler-env externs-cenv + (analyze (ana/empty-env) '(def foo js/boz)))) + :tag))) + (is (nil? (-> (binding [ana/*cljs-ns* ana/*cljs-ns*] + (ana/no-warn + (env/with-compiler-env externs-cenv + (analyze (ana/empty-env) + '(let [z (.baz ^js/Foo.Bar x)] + z))))) + :tag meta :prefix)))) + +(deftest test-basic-infer + (let [res (infer-test-helper + {:forms '[(ns foo.core) + (defn bar [a] (js/parseInt a)) + (def c js/React.Component) + (js/console.log "Hello world!") + (fn [& args] + (.apply (.-log js/console) js/console (into-array args))) + (js/console.log js/Number.MAX_VALUE) + (js/console.log js/Symbol.iterator)]})] + (is (= (unsplit-lines ["var React;" "React.Component;"]) res)))) + +(deftest test-method-infer + (let [res (infer-test-helper + {:forms '[(defn foo [^js/React.Component c] + (.render c))]})] + (is (= (unsplit-lines ["var React;" "React.Component;" "React.Component.prototype.render;"]) + res)))) + +(deftest test-minimal-infer + (let [res (infer-test-helper + {:forms '[(js/console.log (.wozMethod (js/baz)))] + :externs ["src/test/externs/test.js"]})] + (is (string/blank? res)))) + +(deftest test-type-hint-minimal-infer + (let [res (infer-test-helper + {:forms ''[(defn afun [^js/Foo x] + (.wozMethod x))] + :externs ["src/test/externs/test.js"]})] + (is (string/blank? res)))) + +(deftest test-type-hint-infer-unknown-method-in-chain + (let [ws (atom []) + res (infer-test-helper + {:forms '[(defn afun [^js/Foo.Bar x] + (let [z (.baz x)] + (.wozz z)))] + :externs ["src/test/externs/test.js"] + :warnings ws})] + (is (= (unsplit-lines ["Foo.Boo.prototype.wozz;"]) res)) + (is (= 1 (count @ws))) + (is (some-> @ws first + (string/starts-with? + "Cannot resolve property wozz for inferred type js/Foo.Boo"))))) + +(deftest test-type-hint-infer-unknown-property-in-chain + (let [ws (atom []) + res (infer-test-helper + {:forms '[(defn afun [^js/Foo.Bar x] + (let [z (.baz x)] + (.-wozz z)))] + :externs ["src/test/externs/test.js"] + :warnings ws})] + (is (= (unsplit-lines ["Foo.Boo.prototype.wozz;"]) res)) + (is (= 1 (count @ws))) + (is (some-> @ws first + (string/starts-with? + "Cannot resolve property wozz for inferred type js/Foo.Boo"))))) + +(deftest test-type-hint-infer-unknown-method + (let [ws (atom []) + res (infer-test-helper + {:forms '[(defn baz [^js/Foo a] + (.gozMethod a))] + :externs ["src/test/externs/test.js"] + :warnings ws})] + (is (= (unsplit-lines ["Foo.prototype.gozMethod;"]) res)) + (is (= 1 (count @ws))) + (is (some-> @ws first + (string/starts-with? + "Cannot resolve property gozMethod for inferred type js/Foo"))))) + +(comment + + (require '[clojure.java.io :as io] + '[cljs.closure :as cc]) + + (def externs + (-> (cc/js-source-file nil (io/file "src/test/externs/test.js")) + externs/parse-externs externs/index-externs)) + + (ana/resolve-extern '[Foo gozMethod] externs) + + (clojure.test/test-vars [#'test-type-hint-infer-unknown-method]) + + ) + +(deftest test-infer-unknown-method-from-externs + (let [ws (atom []) + res (infer-test-helper + {:forms '[(.gozMethod (js/baz))] + :externs ["src/test/externs/test.js"] + :warnings ws})] + (is (= (unsplit-lines ["Foo.prototype.gozMethod;"]) res)) + (is (= 1 (count @ws))) + (is (some-> @ws first + (string/starts-with? + "Cannot resolve property gozMethod for inferred type js/Foo"))))) + +(deftest test-infer-js-require + (let [ws (atom []) + res (infer-test-helper + {:forms '[(ns foo.core) + (def React (js/require "react")) + (.log js/console (.-Component React))] + :externs ["src/test/externs/test.js"] + :warnings ws})] + (is (= (unsplit-lines ["var require;" "Object.Component;"]) res)) + (is (= 1 (count @ws))) + (is (some-> @ws first + (string/starts-with? + "Adding extern to Object for property Component"))))) + +(deftest test-set-warn-on-infer + (let [ws (atom []) + res (infer-test-helper + {:forms '[(ns warn-on-infer-test.app) + (set! *warn-on-infer* true) + (defn wrap-baz [x] + (.baz x))] + :externs ["src/test/externs/test.js"] + :warnings ws + :warn false + :with-core? true})] + (is (= 1 (count @ws))) + (is (some-> @ws first + (string/starts-with? + "Cannot infer target type"))))) + +(deftest test-cljs-1970-infer-with-cljs-literals + (let [ws (atom []) + res (infer-test-helper + {:forms '[(ns cjls-1970.core) + (set! *warn-on-infer* true) + (defn foo [] (list)) + (defn bar [] (vector))] + :externs ["src/test/externs/test.js"] + :warnings ws + :with-core? true})] + (is (zero? (count @ws))))) + +(deftest test-cljs-1918-infer-with-case-keywords + (let [ws (atom []) + res (infer-test-helper + {:forms '[(ns cjls-1918.core) + (defn foo [x] + (cljs.core/case x + :foo 1 + nil))] + :externs ["src/test/externs/test.js"] + :warnings ws + :with-core? true})] + (is (zero? (count @ws))))) + +(deftest test-cljs-2247 + (let [ws (atom [])] + (try + (ana/with-warning-handlers [(collecting-warning-handler ws)] + (env/with-compiler-env (assoc @test-cenv :repl-env {}) + (ana/analyze (ana/empty-env) + '(defn -foo [])) + (ana/analyze (ana/empty-env) + '(defprotocol IAlpha (-foo [this]))))) + (catch Exception _)) + (is (= ["Protocol IAlpha is overwriting function -foo"] @ws)))) + +(deftest test-cljs-2385-infer-priority + (let [ws (atom []) + res (infer-test-helper + {:forms '[(ns cjls-1918.core) + (defn thing [{:as this}] + (.componentDidUpdate ^js/Thing this))] + :externs ["src/test/externs/test.js"] + :warnings ws + :with-core? true})] + (is (string/includes? res "Thing.prototype.componentDidUpdate;")) + (is (zero? (count @ws))))) + +(deftest test-cljs-2392-broken-inferred-externs + (let [ws (atom []) + res (infer-test-helper + {:forms '[(ns cjls-1918.core + (:require [cljs.nodejs] + [cljs.nodejscli]))] + :warnings ws + :with-core? true + :opts {:target :nodejs}})] + (not (string/includes? res "COMPILED")) + (not (string/includes? res "goog")) + (is (zero? (count @ws))))) + +(deftest test-cljs-2678-global-exports-infer + (let [ws (atom []) + res (infer-test-helper + {:js-dependency-index {"react" {:global-exports '{react React}}} + :forms '[(ns foo.core + (:require [react :as react])) + (.log js/console react/Component)] + :warnings ws + :warn false})] + (is (= (unsplit-lines ["Object.Component;"]) res)))) + +(deftest test-cljs-2767-deftype-defrecord + (let [ws (atom []) + res (infer-test-helper + {:forms '[(ns cjls-2767.core) + (defrecord Foo [])] + :externs ["src/test/externs/test.js"] + :warnings ws + :with-core? true})] + (is (empty? @ws)) + (is (not (string/includes? res "cljs.core")))) + (let [ws (atom []) + res (infer-test-helper + {:forms '[(ns cjls-2767.core) + (deftype Foo [])] + :externs ["src/test/externs/test.js"] + :warnings ws + :with-core? true})] + (is (empty? @ws)) + (is (not (string/includes? res "cljs.core"))))) + +(deftest test-cljs-2790-defrecord-fields + (let [ws (atom []) + res (infer-test-helper + {:forms '[(ns cjls-2790.core) + (defrecord Foo [a b])] + :externs ["src/test/externs/test.js"] + :warnings ws + :with-core? true})] + (is (empty? @ws)) + (is (not (string/includes? res "cljs.core"))))) + +(deftest test-cljs-3181 + (let [ws (atom []) + res (binding [ana/*cljs-static-fns* true] + (infer-test-helper + {:forms '[(ns warn-on-infer-test.app) + (set! *warn-on-infer* true) + (defn f [gfn] + (.then ^js/Promise (gfn (inc 1)) identity))] + :externs ["src/test/externs/test.js"] + :warnings ws + :warn false + :with-core? true}))] + (is (empty? @ws)))) + +(deftest test-cljs-1924 + (let [ws (atom []) + res (binding [ana/*cljs-static-fns* true] + (infer-test-helper + {:forms '[(set! *warn-on-infer* true) + (defrecord Foo [])] + :warnings ws + :warn true + :with-core? true}))] + (is (empty? @ws)))) + +(deftest test-cljs-2862 + (let [ws (atom []) + res (binding [ana/*cljs-static-fns* true] + (infer-test-helper + {:forms '[(ns demo.app) + (set! *warn-on-infer* true) + (deftype Foo [] + Object + (bar [this] :bar))] + :warnings ws + :warn true + :with-core? true}))] + (is (empty? @ws)))) + +(deftest test-cljs-2957 + (let [ws (atom []) + res (binding [ana/*cljs-static-fns* true] + (infer-test-helper + {:forms '[(ns test.foo + (:import [goog.history Html5History])) + (set! *warn-on-infer* true) + (doto (Html5History.) + (.setUseFragment false))] + :warnings ws + :warn true + :with-core? true}))] + (is (empty? @ws)))) + +(deftest test-cljs-3236 + (let [ws (atom []) + res (binding [ana/*cljs-static-fns* true] + (infer-test-helper + {:forms '[(ns test.foo) + (set! *warn-on-infer* true) + (defprotocol IFoo + (bar [this]))] + :warnings ws + :warn true + :with-core? true}))] + (is (empty? @ws)))) + +(deftest test-cljs-3257 + (let [ws (atom []) + res (binding [ana/*cljs-static-fns* true] + (infer-test-helper + {:forms '[(ns app.core) + (set! *warn-on-infer* true) + (defprotocol IFoo + (bar [this])) + (defn not-ok? [v] + (satisfies? IFoo v))] + :warnings ws + :warn true + :with-core? true}))] + (is (empty? @ws)))) + +(deftest test-cljs-3373 + (testing "var from foreign libraries that are invoked as fn should propagate 'js hints" + (let [ws (atom []) + res (infer-test-helper + {:js-dependency-index {"firebase" {:global-exports '{firebase Firebase}}} + :forms '[(ns foo.core + (:require [firebase :refer [getAuth]])) + (def auth + (doto (getAuth) + (.useDeviceLanguage) + (.onAuthStateChanged (fn [user]))))] + :warnings ws + :warn true + :with-core? false})] + (is (= (unsplit-lines + ["Object.getAuth;" + "Object.useDeviceLanguage;" + "Object.onAuthStateChanged;"]) + res))))) + +(deftest test-cljs-3377 + (testing "constructors from foreign libraries that used via `new` should propagate 'js hints" + (let [ws (atom []) + res (infer-test-helper + {:js-dependency-index {"firebase" {:global-exports '{firebase Firebase}}} + :forms '[(ns foo.core + (:require [firebase :refer [GoogleAuthProvider]])) + (def goog-provider + (GoogleAuthProvider.)) + (.someMethod goog-provider) + (.-someProperty goog-provider)] + :warnings ws + :warn true + :with-core? false})] + (is (= (unsplit-lines + ["Object.GoogleAuthProvider;" + "Object.someMethod;" + "Object.someProperty;"]) + res))))) + +(deftest test-cljs-3381 + (testing "invokeable js namespaces not hinted properly" + (let [ws (atom []) + res (infer-test-helper + {:node-module-index #{"markdown-it"} + :forms '[(ns foo.core + (:require [markdown-it])) + (defonce mdi + (doto (new markdown-it + (js-obj + "linkify" true + "typographer" true)) + (.renderInline mdi "hi")))] + :warnings ws + :warn true + :with-core? false + :target :nodejs})] + (is (= (unsplit-lines + ["Object.renderInline;"]) + res))))) + +(deftest test-cljs-3408 + (testing "inheritance of JS Types is inferred" + (let [ws (atom []) + res (infer-test-helper + {:forms '[(ns foo.core) + (.querySelectorAll js/document "div")] + :warnings ws + :warn true + :with-core? true})] + (is (empty? @ws))))) + +(comment + (binding [ana/*cljs-ns* ana/*cljs-ns*] + (ana/no-warn + (env/with-compiler-env externs-cenv + (analyze (ana/empty-env) + '(let [React (js/require "react")] + React))))) + + ;; FIXME: we don't preserve tag information + (binding [ana/*cljs-ns* ana/*cljs-ns*] + (ana/no-warn + (env/with-compiler-env externs-cenv + (let [aenv (ana/empty-env) + _ (analyze aenv '(ns foo.core)) + aenv' (assoc-in aenv [:ns :name] 'foo.core) + _ (ana/analyze aenv' '(def x 1))] + (dissoc (ana/analyze-symbol (assoc-in aenv [:ns :name] 'foo.core) 'x) :env) + ;(get-in @externs-cenv [::ana/namespaces 'foo.core]) + )))) + ) diff --git a/src/test/clojure/cljs/externs_parsing_tests.clj b/src/test/clojure/cljs/externs_parsing_tests.clj new file mode 100644 index 0000000000..e5a399c84f --- /dev/null +++ b/src/test/clojure/cljs/externs_parsing_tests.clj @@ -0,0 +1,91 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.externs-parsing-tests + (:require [cljs.closure :as closure] + [cljs.analyzer :as ana] + [cljs.env :as env] + [cljs.externs :as externs] + [clojure.java.io :as io] + [clojure.test :as test :refer [deftest is testing]]) + (:import [com.google.javascript.jscomp CommandLineRunner])) + +(deftest cljs-3121 + (let [externs (externs/parse-externs + (closure/js-source-file "goog/string/string.js" + (io/input-stream (io/resource "goog/string/string.js"))))] + (is (every? + (fn [xs] + (= (count (distinct xs)) + (count xs))) + externs)))) + +(deftest cljs-3176 + (let [ns (externs/analyze-goog-file "goog/date/date.js") + v (get-in ns [:defs 'getWeekNumber])] + (is (= 3 (-> v :method-params first count)))) + (let [ns (externs/analyze-goog-file "goog/date/date.js" 'goog.date.month)] + (is (= 13 (-> ns :defs count))))) + +(deftest cljs-3170&3189 + (let [ns (externs/analyze-goog-file "goog/object/object.js")] + (is (= 'any (get-in ns [:defs 'get :ret-tag]))) + (is (= 'array (get-in ns [:defs 'getKeys :ret-tag]))))) + +(comment + ;; works + (get-in (externs/analyze-goog-file "goog/object/object.js") + [:defs 'containsKey :ret-tag]) + ) + +(deftest test-parse-super + (let [info (-> + (filter + (fn [s] + (= "externs.zip//w3c_dom2.js" (.getName s))) + (externs/default-externs)) + first externs/parse-externs externs/index-externs + (find 'HTMLDocument) first meta)] + (is (= 'Document (:super info))))) + +(deftest test-parse-closure-type-annotations + (let [externs (::ana/externs @(env/default-compiler-env))] + (testing "JS global console has tag Console" + (let [info (externs/info externs '[console])] + (is (= 'Console (:tag info))))) + (testing "JS global crypto has tag webCrypto.Crypto from: + @type {!webCrypto.Crypto|undefined}" + (let [info (externs/info externs '[crypto])] + (is (= 'webCrypto.Crypto (:tag info))))) + (testing "Generic return type on crypto methods returns ClojureScript relevant + type info:" + (testing "@return {!Promise}" + (let [info (externs/info externs '[webCrypto SubtleCrypto prototype encrypt])] + (is (= 'Promise (:ret-tag info))))) + (testing "@return {!Promise}" + (let [info (externs/info externs '[webCrypto SubtleCrypto prototype deriveKey])] + (is (= 'Promise (:ret-tag info))))) + (testing "@return {!Int8Array|!Uint8Array|!Uint8ClampedArray|!Int16Array|!Uint16Array|!Int32Array|!Uint32Array|!BigInt64Array|!BigUint64Array}" + (let [info (externs/info externs '[webCrypto Crypto prototype getRandomValues])] + (is (= 'any (:ret-tag info)))))))) + +(comment + + (let [externs (::ana/externs @(env/default-compiler-env))] + (externs/info externs '[webCrypto Crypto prototype getRandomValues])) + + (externs/parse-externs + (externs/resource->source-file (io/resource "goog/object/object.js"))) + + (externs/analyze-goog-file "goog/object/object.js") + + (test/run-tests) + + (externs/analyze-goog-file "goog/date/date.js" 'goog.date.month) + + ) diff --git a/src/test/clojure/cljs/foreign/node_test.clj b/src/test/clojure/cljs/foreign/node_test.clj new file mode 100644 index 0000000000..926ccd03ab --- /dev/null +++ b/src/test/clojure/cljs/foreign/node_test.clj @@ -0,0 +1,110 @@ +(ns cljs.foreign.node-test + (:require [cljs.foreign.node :as node] + [cljs.test-util :as test-util] + [cljs.util :as util] + [clojure.java.io :as io] + [clojure.java.shell :as sh] + [clojure.test :as test :refer [deftest is testing]])) + +(defn cleanup + ([] (cleanup #())) + ([f] + (test-util/delete-node-modules) + (doseq [f (map io/file + ["package.json" "package-lock.json" "yarn.lock" + "yarn-error.log"])] + (when (.exists f) + (io/delete-file f))) + (f))) + +(defn install + ([lib version] + (install :npm lib version)) + ([cmd lib version] + (let [action ({:npm "install" :yarn "add"} cmd)] + (sh/sh (name cmd) action (str lib "@" version))))) + +(test/use-fixtures :once cleanup) + +;; ============================================================================= +;; Tests + +(defn pkg-jsons + ([] + (pkg-jsons {})) + ([opts] + (-> (util/module-file-seq opts) + (node/get-pkg-jsons opts)))) + +(defn indexed-lib-specs + ([] + (indexed-lib-specs {})) + ([opts] + (as-> (-> (util/module-file-seq opts) + (node/node-file-seq->libs-spec* opts)) + xs (zipmap (map :file xs) xs)))) + +(defn relpath->data + ([index path] + (relpath->data index path :get)) + ([index path type] + (let [abs-path (.getAbsolutePath (io/file path))] + (case type + :get (get index abs-path) + :find (find index abs-path))))) + +(deftest test-basic + (install "left-pad" "1.3.0") + (testing "Install left-pad, verify that it is indexed and has a sensible lib-spec" + (let [index (indexed-lib-specs)] + (let [left-pad (relpath->data index "node_modules/left-pad/index.js")] + (is (some? (:file left-pad))) + (is (some? (:module-type left-pad))) + (is (= #{"left-pad/index.js" "left-pad/index" "left-pad"} + (into #{} (:provides left-pad)))))))) + +(deftest test-path->main-name + (install :yarn "react-select" "5.7.2") + (testing "Verify that path->main works as expected" + (let [node-opts {:package-json-resolution :nodejs} + webpack-opts {:package-json-resolution :webpack}] + (is (= "react-select" + (node/path->main-name + (.getAbsolutePath (io/file "node_modules/react-select/dist/react-select.cjs.js")) + (relpath->data (pkg-jsons node-opts) + "node_modules/react-select/package.json" :find) + node-opts))) + (is (= "react-select/creatable" + (node/path->main-name + (.getAbsolutePath (io/file "node_modules/react-select/creatable/dist/react-select-creatable.cjs.js")) + (relpath->data (pkg-jsons node-opts) + "node_modules/react-select/creatable/package.json" :find) + node-opts))) + (is (nil? (node/path->main-name + (.getAbsolutePath (io/file "node_modules/react-select/dist/react-select.cjs.js")) + (relpath->data (pkg-jsons webpack-opts) + "node_modules/react-select/package.json" :find) + webpack-opts)))))) + +(deftest test-exports-with-choices + (install :yarn "@mantine/core" "7.0.2") + (testing "Verify that complex exports are handled" + (let [node-opts {:package-json-resolution :nodejs} + webpack-opts {:package-json-resolution :webpack}] + (is (= "@mantine/core" + (node/path->main-name + (.getAbsolutePath (io/file "node_modules/@mantine/core/cjs/index.js")) + (relpath->data (pkg-jsons node-opts) + "node_modules/@mantine/core/package.json" :find) + node-opts))) + (is (= "@mantine/core" + (node/path->main-name + (.getAbsolutePath (io/file "node_modules/@mantine/core/esm/index.mjs")) + (relpath->data (pkg-jsons webpack-opts) + "node_modules/@mantine/core/package.json" :find) + webpack-opts)))))) + +(comment + (test/run-tests) + (cleanup) + ) diff --git a/src/test/clojure/cljs/instant_tests.clj b/src/test/clojure/cljs/instant_tests.clj new file mode 100644 index 0000000000..69b0cfa271 --- /dev/null +++ b/src/test/clojure/cljs/instant_tests.clj @@ -0,0 +1,22 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.instant-tests + (:require + [cljs.instant :as inst] + [clojure.test :refer [deftest is]])) + +(deftest read-instant-instant-test + ;; Clojure uses hybrid Julian / Gregorian, while Instant is proleptic Gregorian + (is (not= #inst "1500" (inst/read-instant-instant "1500"))) + (is (not= (inst-ms #inst "1500") (inst-ms (inst/read-instant-instant "1500")))) + (is (= -14831769600000 (inst-ms (inst/read-instant-instant "1500")))) + (is (= "#inst \"1500-01-01T00:00:00.123456789-00:00\"" + (pr-str (inst/read-instant-instant "1500-01-01T00:00:00.123456789-00:00")))) + (is (= "#inst \"2020-01-01T05:00:00.000000000-00:00\"" + (pr-str (inst/read-instant-instant "2020-01-01T00:00:00.000-05:00"))))) diff --git a/src/test/clojure/cljs/js_deps_tests.clj b/src/test/clojure/cljs/js_deps_tests.clj new file mode 100644 index 0000000000..8678d248de --- /dev/null +++ b/src/test/clojure/cljs/js_deps_tests.clj @@ -0,0 +1,22 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.js-deps-tests + (:require [cljs.js-deps :as js-deps] + [clojure.java.io :as io] + [clojure.test :refer [deftest is run-tests]])) + +(deftest test-parse-js-ns-returns-require-types + (let [ns-info (js-deps/parse-js-ns + (line-seq (io/reader (io/resource "goog/events/eventhandler.js"))))] + (is (true? (contains? ns-info :require-types))))) + +(deftest test-js-dependency-index-has-require-types + (let [deps (js-deps/build-index (js-deps/goog-dependencies*)) + ns-info (get deps "goog.events.EventHandler")] + (is (true? (contains? ns-info :require-types))))) diff --git a/src/test/clojure/cljs/module_graph_tests.clj b/src/test/clojure/cljs/module_graph_tests.clj new file mode 100644 index 0000000000..3ce70375be --- /dev/null +++ b/src/test/clojure/cljs/module_graph_tests.clj @@ -0,0 +1,172 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.module-graph-tests + (:require [clojure.test :as test :refer [deftest is testing]] + [cljs.closure :as closure] + [cljs.util :as util] + [cljs.module-graph :as module-graph]) + (:import [clojure.lang ExceptionInfo])) + +(def opts {:output-dir "out"}) + +(defn modules [{:keys [output-dir] :as opts}] + {:shared {:entries '[shared.a shared.b] + :output-to (str output-dir "/shared.js")} + :page1 {:entries '[page1.a page1.b] + :depends-on [:shared] + :output-to (str output-dir "/page1.js")} + :page2 {:entries '[page2.a page2.b] + :depends-on [:shared] + :output-to (str output-dir "/page2.js")}}) + +(defn inputs + ([] + (inputs {:output-dir "out"})) + ([{:keys [output-dir] :as opts}] + [{:provides '[goog] + :out-file (str output-dir "/goog/base.js")} + {:provides '[cljs.core] + :out-file (str output-dir "/cljs/core.js")} + {:provides ["cljs.reader"] + :requires ["cljs.core"] + :out-file (str output-dir "/cljs/reader.js")} + {:provides '[events "event.types"] + :requires ["cljs.core"] + :out-file (str output-dir "/events.js")} + {:provides '[shared.a] + :requires ["cljs.core"] + :out-file (str output-dir "/shared/a.js")} + {:provides '[shared.b] + :requires '[cljs.core] + :out-file (str output-dir "/shared/b.js")} + {:provides ["page1.a"] + :requires ["cljs.core" "cljs.reader" "events" 'shared.a] + :out-file (str output-dir "/page1/a.js")} + {:provides ["page1.b"] + :requires '[cljs.core shared.b] + :out-file (str output-dir "/page1/b.js")} + {:provides ["page2.a"] + :requires ["cljs.core" "events" 'shared.a] + :out-file (str output-dir "/page2/a.js")} + {:provides ["page2.b"] + :requires ['cljs.core 'shared.b] + :out-file (str output-dir "/page2/b.js")}])) + +(deftest test-add-cljs-base + (is (true? (contains? (module-graph/add-cljs-base (modules opts)) :cljs-base)))) + +(deftest test-add-cljs-base-dep + (let [modules' (-> (modules opts) + module-graph/add-cljs-base + module-graph/add-cljs-base-dep)] + (is (not (some #{:cljs-base} (get-in modules' [:cljs-base :depends-on])))) + (is (some #{:cljs-base} (get-in modules' [:shared :depends-on]))) + (is (not (some #{:cljs-base} (get-in modules' [:page1 :depends-on])))) + (is (not (some #{:cljs-base} (get-in modules' [:page2 :depends-on])))))) + +(deftest test-module-deps + (let [modules (-> (modules opts) + module-graph/add-cljs-base + module-graph/add-cljs-base-dep)] + (is (= (module-graph/deps-for-module :page1 modules) + [:cljs-base :shared])))) + +(deftest test-entry-deps + (let [inputs (module-graph/index-inputs (inputs opts))] + (is (= (module-graph/deps-for-entry "page2.a" inputs) + ["cljs.core" "events" "shared.a"])) + (is (some #{"shared.a"} (module-graph/deps-for-entry "page1.a" inputs))))) + +(deftest test-canonical-name + (let [ins (module-graph/index-inputs (inputs opts))] + (is (= "events" (module-graph/canonical-name 'events ins))) + (is (= "events" (module-graph/canonical-name "events" ins))) + (is (= "events" (module-graph/canonical-name 'event.types ins))) + (is (= "events" (module-graph/canonical-name "event.types" ins))))) + +(deftest test-inputs->assigned-modules + (let [modules (modules opts) + modules' (-> modules + module-graph/add-cljs-base + module-graph/add-cljs-base-dep + module-graph/annotate-depths) + inputs' (inputs opts) + indexed (module-graph/index-inputs inputs') + assigns (module-graph/inputs->assigned-modules inputs' modules') + assigns' (reduce-kv + (fn [ret module-name {:keys [entries]}] + (merge ret + (zipmap + (map #(module-graph/canonical-name % indexed) + entries) + (repeat module-name)))) + {} modules)] + ;; every input assigned, including orphans + (is (every? #(contains? assigns %) + (map #(module-graph/canonical-name % indexed) + (mapcat :provides inputs')))) + ;; every user specified assignment should be respected + (is (every? + (fn [[e m]] + (= m (get assigns e))) + assigns')) + ;; events should not have been moved to :cljs-base as an orphan even though + ;; it provides multiple nses + (is (= (get assigns "events") :shared)))) + +(def bad-modules + {:page1 {:entries '[page1.a page1.b events] + :output-to "out/page1.js"} + :page2 {:entries '[page2.a page2.b event.types] + :output-to "out/page2.js"}}) + +(deftest test-duplicate-entries + (let [modules' (-> bad-modules + module-graph/add-cljs-base + module-graph/add-cljs-base-dep) + index (module-graph/index-inputs (inputs opts))] + (is (= (try + (module-graph/validate-modules modules' index) + (catch Throwable t + :caught)) + :caught)))) + +(deftest test-module->module-uris + (is (= (module-graph/modules->module-uris (modules opts) (inputs opts) + {:output-dir (:output-dir opts) + :asset-path "/asset/js" + :optimizations :none}) + {:shared ["/asset/js/cljs/core.js" "/asset/js/events.js" "/asset/js/shared/a.js" "/asset/js/shared/b.js"], + :page1 ["/asset/js/cljs/reader.js" "/asset/js/page1/a.js" "/asset/js/page1/b.js"], + :page2 ["/asset/js/page2/a.js" "/asset/js/page2/b.js"], + :cljs-base ["/asset/js/goog/base.js"]})) + (is (= (module-graph/modules->module-uris (modules opts) (inputs opts) + {:output-dir (:output-dir opts) + :asset-path "/asset/js" + :optimizations :advanced}) + {:cljs-base ["/asset/js/cljs_base.js"] + :shared ["/asset/js/shared.js"] + :page1 ["/asset/js/page1.js"] + :page2 ["/asset/js/page2.js"]}))) + +(deftest test-module-for + (is (= :page1 (module-graph/module-for 'page1.a (modules opts)))) + (is (= :page1 (module-graph/module-for "page1.a" (modules opts))))) + +(def circular-inputs + [{:provides ["foo.core"] + :requires ["bar.core"]} + {:provides ["bar.core"] + :requires ["baz.core"]} + {:provides ["baz.core"] + :requires ["foo.core"]}]) + +(deftest test-circular-deps + (is (nil? (module-graph/validate-inputs (inputs)))) + (is (thrown? ExceptionInfo (module-graph/validate-inputs circular-inputs)))) diff --git a/src/test/clojure/cljs/module_processing_tests.clj b/src/test/clojure/cljs/module_processing_tests.clj new file mode 100644 index 0000000000..552b821517 --- /dev/null +++ b/src/test/clojure/cljs/module_processing_tests.clj @@ -0,0 +1,205 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.module-processing-tests + (:require [clojure.java.io :as io] + [cljs.closure :as closure] + [clojure.string :as string] + [clojure.test :refer [deftest is]] + [cljs.env :as env] + [cljs.analyzer :as ana] + [cljs.compiler :as comp] + [cljs.js-deps :as deps] + [cljs.util :as util] + [cljs.test-util :as test]) + (:import [java.io File])) + +;; Hard coded JSX transform for the test case +(defn preprocess-jsx [ijs _] + (assoc ijs :source (clojure.string/replace + (:source ijs) + (re-pattern (str "\\(\\R" + "\\s*\\R" + "\\s*\\R" + "\\s*\\R" + "\\s*\\R" + "\\s*\\)")) + (str " React.createElement(\"svg\", {width:\"200px\", height:\"200px\", className:\"center\"}, " + "React.createElement(\"circle\", {cx:\"100px\", cy:\"100px\", r:\"100px\", fill:this.props.color})" + ")")))) + +(defn absolute-module-path + ([relpath] + (absolute-module-path relpath false)) + ([relpath code?] + (let [filename (as-> (subs relpath (inc (.lastIndexOf relpath "/"))) $ + (string/replace $ "_" "-") + (subs $ 0 (.lastIndexOf $ "."))) + dirname (as-> (io/file relpath) $ + (.getAbsolutePath $) + (subs $ 0 (.lastIndexOf $ (str File/separator))) + (string/replace $ "/" "$") + (string/replace $ \. \-) + (cond-> $ code? (string/replace "-" "_")) + ;; Windows + (string/replace $ "\\" "$") + (if code? + (string/replace $ ":" "_") + (string/replace $ ":" "-")))] + (str "module" (when-not (.startsWith dirname "$") "$") dirname "$" filename)))) + +(defmethod closure/js-transforms :jsx [ijs opts] + (preprocess-jsx ijs opts)) + +(deftest commonjs-module-processing + (test/delete-out-files) + (let [cenv (env/default-compiler-env)] + ;; Reset load-library cache so that changes to processed files are noticed + (with-redefs [cljs.js-deps/load-library (memoize cljs.js-deps/load-library*)] + (is (= {:foreign-libs [] + :ups-foreign-libs [] + :libs [(test/platform-path "out/src/test/cljs/reactJS.js") + (test/platform-path "out/src/test/cljs/Circle.js")] + :closure-warnings {:non-standard-jsdoc :off}} + (env/with-compiler-env cenv + (closure/process-js-modules + {:foreign-libs [{:file "src/test/cljs/reactJS.js" + :provides ["React"] + :module-type :commonjs} + {:file "src/test/cljs/Circle.js" + :provides ["Circle"] + :module-type :commonjs + :preprocess :jsx}] + :closure-warnings {:non-standard-jsdoc :off}}))) + "processed modules are added to :libs")) + (is (= {"React" {:name (absolute-module-path "src/test/cljs/reactJS.js") + :module-type :commonjs} + "Circle" {:name (absolute-module-path "src/test/cljs/Circle.js") + :module-type :commonjs}} + (:js-module-index @cenv)) + "Processed modules are added to :js-module-index"))) + +(deftest es6-module-processing + (test/delete-out-files) + (let [cenv (env/default-compiler-env)] + + ;; Reset load-library cache so that changes to processed files are noticed in REPL + (with-redefs [cljs.js-deps/load-library (memoize cljs.js-deps/load-library*)] + + (is (= {:foreign-libs [] + :ups-foreign-libs [] + :libs [(test/platform-path "out/src/test/cljs/es6_hello.js")] + :closure-warnings {:non-standard-jsdoc :off}} + (env/with-compiler-env cenv + (closure/process-js-modules + {:foreign-libs [{:file "src/test/cljs/es6_hello.js" + :provides ["es6-hello"] + :module-type :es6}] + :closure-warnings {:non-standard-jsdoc :off}}))) + "processed modules are added to :libs") + + (is (= {"es6-hello" {:name (absolute-module-path "src/test/cljs/es6_hello.js") + :module-type :es6}} + (:js-module-index @cenv)) + "Processed modules are added to :js-module-index") + + (is (re-find + #"goog.provide\(\"module\$[a-zA-Z0-9$_]+?src\$test\$cljs\$es6_hello\"\);" + (slurp "out/src/test/cljs/es6_hello.js")))))) + +(deftest test-module-name-substitution + (test/delete-out-files) + (let [cenv (env/default-compiler-env)] + ;; Make sure load-library is not cached when developing on REPL + (with-redefs [cljs.js-deps/load-library (memoize cljs.js-deps/load-library*) + cljs.js-deps/load-foreign-library (memoize cljs.js-deps/load-foreign-library*)] + (env/with-compiler-env cenv + (let [opts (closure/process-js-modules {:foreign-libs [{:file "src/test/cljs/calculator.js" + :provides ["calculator"] + :module-type :commonjs}]}) + compile (fn [form] + (with-out-str + (comp/emit (ana/analyze (ana/empty-env) form)))) + crlf (if util/windows? "\r\n" "\n") + output (str (absolute-module-path "src/test/cljs/calculator.js" true) "[\"default\"].add((3),(4));" crlf)] + (swap! cenv + #(assoc % :js-dependency-index (deps/js-dependency-index opts))) + (binding [ana/*cljs-ns* 'cljs.user] + (is (= (str "goog.provide('my_calculator.core');" crlf + "goog.require('cljs.core');" crlf + "goog.require('" (absolute-module-path "src/test/cljs/calculator.js" true) "');" + crlf) + (compile '(ns my-calculator.core (:require [calculator :as calc :refer [subtract add] :rename {subtract sub}]))))) + (is (= output (compile '(calc/add 3 4)))) + (is (= output (compile '(calculator/add 3 4)))) + (is (= output (compile '(add 3 4)))) + (is (= (str (absolute-module-path "src/test/cljs/calculator.js" true) + "[\"default\"].subtract((5),(4));" crlf) + (compile '(sub 5 4)))))))))) + +(deftest test-cljs-1822 + (test/delete-out-files) + (let [cenv (env/default-compiler-env)] + ;; Make sure load-library is not cached when developing on REPL + (with-redefs [cljs.js-deps/load-library (memoize cljs.js-deps/load-library*) + cljs.js-deps/load-foreign-library (memoize cljs.js-deps/load-foreign-library*)] + (is (= {:optimizations :simple + :foreign-libs [] + :ups-foreign-libs [] + :libs [(test/platform-path "out/src/test/cljs/react-min.js") + (test/platform-path "out/src/test/cljs/Circle-min.js")] + :closure-warnings {:non-standard-jsdoc :off}} + (env/with-compiler-env cenv + (closure/process-js-modules + {:optimizations :simple + :foreign-libs [{:file "src/test/cljs/reactJS.js" + :file-min "src/test/cljs/react-min.js" + :provides ["React"] + :module-type :commonjs} + {:file "src/test/cljs/Circle.js" + :file-min "src/test/cljs/Circle-min.js" + :provides ["Circle"] + :module-type :commonjs + :preprocess :jsx}] + :closure-warnings {:non-standard-jsdoc :off}}))) + "processed modules are added to :libs")) + (is (= {"React" {:name (absolute-module-path "src/test/cljs/react-min.js") + :module-type :commonjs} + "Circle" {:name (absolute-module-path "src/test/cljs/Circle-min.js") + :module-type :commonjs}} + (:js-module-index @cenv)) + "Processed modules are added to :js-module-index"))) + +(deftest commonjs-module-processing-preprocess-symbol + (test/delete-out-files) + (let [cenv (env/default-compiler-env)] + ;; Reset load-library cache so that changes to processed files are noticed + (with-redefs [cljs.js-deps/load-library (memoize cljs.js-deps/load-library*)] + (is (= {:foreign-libs [] + :ups-foreign-libs [] + :libs [(test/platform-path "out/src/test/cljs/reactJS.js") + (test/platform-path "out/src/test/cljs/Circle.js")] + :closure-warnings {:non-standard-jsdoc :off}} + (env/with-compiler-env cenv + (closure/process-js-modules + {:foreign-libs [{:file "src/test/cljs/reactJS.js" + :provides ["React"] + :module-type :commonjs} + {:file "src/test/cljs/Circle.js" + :provides ["Circle"] + :module-type :commonjs + :preprocess 'cljs.module-processing-tests/preprocess-jsx}] + :closure-warnings {:non-standard-jsdoc :off}}))) + "processed modules are added to :libs")) + + (is (= {"React" {:name (absolute-module-path "src/test/cljs/reactJS.js") + :module-type :commonjs} + "Circle" {:name (absolute-module-path "src/test/cljs/Circle.js") + :module-type :commonjs}} + (:js-module-index @cenv)) + "Processed modules are added to :js-module-index"))) diff --git a/test/clj/cljs/preamble1.js b/src/test/clojure/cljs/preamble1.js similarity index 100% rename from test/clj/cljs/preamble1.js rename to src/test/clojure/cljs/preamble1.js diff --git a/test/clj/cljs/preamble2.js b/src/test/clojure/cljs/preamble2.js similarity index 100% rename from test/clj/cljs/preamble2.js rename to src/test/clojure/cljs/preamble2.js diff --git a/src/test/clojure/cljs/profile.clj b/src/test/clojure/cljs/profile.clj new file mode 100644 index 0000000000..2d80d20d4d --- /dev/null +++ b/src/test/clojure/cljs/profile.clj @@ -0,0 +1,30 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.profile + (:require [clojure.java.io :as io] + [cljs.env :as env] + [cljs.analyzer :as ana] + [cljs.compiler :as comp])) + +(comment + + ;; ~900ms + (dotimes [_ 20] + (time + (ana/analyze-file (io/resource "cljs/core.cljs")))) + + ;; ~2700ms + ;; after change ~2500 + (dotimes [_ 20] + (time + (env/with-compiler-env (env/default-compiler-env) + (comp/compile-file (.getPath (io/resource "cljs/core.cljs"))) + (.delete (io/file "src/main/cljs/cljs/core.js"))))) + + ) \ No newline at end of file diff --git a/src/test/clojure/cljs/repl_tests.clj b/src/test/clojure/cljs/repl_tests.clj new file mode 100644 index 0000000000..e1a349be5a --- /dev/null +++ b/src/test/clojure/cljs/repl_tests.clj @@ -0,0 +1,45 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.repl-tests + (:require [clojure.java.io :as io] + [cljs.analyzer :as ana] + [cljs.analyzer.api :as ana-api] + [cljs.env :as env] + [cljs.repl :as repl] + [cljs.compiler :as comp]) + (:use clojure.test)) + +(def st (env/default-compiler-env)) + +(env/with-compiler-env st + (ana/analyze-file "cljs/core.cljs") + (ana/load-core)) + +(deftest test-doc + (env/with-compiler-env st + (is (string? (:doc (ana-api/resolve {} '->)))))) + +#_(deftest file-info + (let [repl-env (rhino/repl-env) + compiler-env (env/default-compiler-env) + repl-env (assoc repl-env ::env/compiler compiler-env)] + (env/with-compiler-env compiler-env + (binding [ana/*cljs-ns* 'cljs.user] + (repl/-setup repl-env))) + (let [assoc-info (get-in @compiler-env [:cljs.analyzer/namespaces 'cljs.core :defs 'assoc]) + {:keys [file line]} assoc-info] + + (is assoc-info) + (is (number? line)) + (is file) + (and file + (is (io/resource file)))))) + +(deftest test-bytes-to-base64-str + (is (= "YWJj" (#'repl/bytes-to-base64-str (.getBytes "abc"))))) diff --git a/src/test/clojure/cljs/source_map/base64_tests.clj b/src/test/clojure/cljs/source_map/base64_tests.clj new file mode 100644 index 0000000000..4e82c3127e --- /dev/null +++ b/src/test/clojure/cljs/source_map/base64_tests.clj @@ -0,0 +1,17 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.source-map.base64-tests + (:require + [clojure.test :refer [deftest is]] + [cljs.source-map.base64 :as base64])) + +(deftest encode-test + (doseq [n (range 64)] + (is (= (get base64/int->char n) (base64/encode n)))) + (is (thrown-with-msg? Error #"Must be between 0 and 63: 64" (base64/encode 64)))) diff --git a/src/test/clojure/cljs/test_runner.clj b/src/test/clojure/cljs/test_runner.clj new file mode 100644 index 0000000000..0b0c9f2ca9 --- /dev/null +++ b/src/test/clojure/cljs/test_runner.clj @@ -0,0 +1,46 @@ +(ns cljs.test-runner + (:require [cljs.analyzer-api-tests] + [cljs.analyzer.as-alias-test] + [cljs.analyzer-pass-tests] + [cljs.analyzer-tests] + [cljs.build-api-tests] + [cljs.closure-tests] + [cljs.compiler-tests] + [cljs.compiler.glib-module-test] + [cljs.externs-infer-tests] + [cljs.externs-parsing-tests] + [cljs.instant-tests] + [cljs.js-deps-tests] + [cljs.module-graph-tests] + [cljs.module-processing-tests] + [cljs.source-map.base64-tests] + [cljs.transpile-tests] + [cljs.type-inference-tests] + [cljs.util-tests] + [clojure.test :refer [run-tests]])) + +(defn -main [] + (let [{:keys [fail error]} + (run-tests + 'cljs.analyzer-api-tests + 'cljs.analyzer.as-alias-test + 'cljs.analyzer-pass-tests + 'cljs.analyzer-tests + 'cljs.build-api-tests + 'cljs.closure-tests + 'cljs.compiler-tests + 'cljs.compiler.glib-module-test + 'cljs.externs-infer-tests + 'cljs.externs-parsing-tests + 'cljs.instant-tests + 'cljs.js-deps-tests + 'cljs.module-graph-tests + 'cljs.module-processing-tests + 'cljs.source-map.base64-tests + 'cljs.transpile-tests + 'cljs.type-inference-tests + 'cljs.util-tests)] + (if (or (not (zero? fail)) + (not (zero? error))) + (System/exit 1) + (System/exit 0)))) diff --git a/src/test/clojure/cljs/test_util.clj b/src/test/clojure/cljs/test_util.clj new file mode 100644 index 0000000000..eefe5d82ed --- /dev/null +++ b/src/test/clojure/cljs/test_util.clj @@ -0,0 +1,97 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.test-util + (:require [clojure.java.io :as io] + [clojure.string :as string] + [clojure.test]) + (:import [java.io File])) + +(defn delete-out-files + "Processed files are only copied/written if input has changed. In test case it + makes sense to write files always, in case the processing logic has changed." + ([] + (delete-out-files "out")) + ([directory] + (doseq [f (file-seq (io/file directory)) + :when (.isFile f)] + (.delete f)))) + +(defn delete-node-modules [] + (let [nm (io/file "node_modules")] + (while (.exists nm) + (doseq [f (file-seq nm)] + (.delete f))))) + +(defn document-write? + "Returns true if the string `s` contains a document.write statement to + load the namespace `ns`, otherwise false." + [s ns] + (->> (format "document.write('');" ns) + (string/index-of s) + (some?))) + +(defn project-with-modules + "Returns the build config for a project that uses Google Closure modules." + [output-dir] + {:inputs (str (io/file "src" "test" "cljs")) + :opts + {:main "module-test.main" + :output-dir output-dir + :optimizations :advanced + :verbose true + :modules + {:cljs-base + {:output-to (str (io/file output-dir "module-main.js"))} + :module-a + {:output-to (str (io/file output-dir "module-a.js")) + :entries #{'module-test.modules.a}} + :module-b + {:output-to (str (io/file output-dir "module-b.js")) + :entries #{'module-test.modules.b}}} + :closure-warnings {:check-types :off}}}) + +(defn tmp-dir + "Returns the temporary directory of the system." + [] + (System/getProperty "java.io.tmpdir")) + +(defn platform-path [path] + (.replace path \/ (.charAt (str File/separator) 0))) + +(defn unsplit-lines + "Forms a string wherein each line is followed by a system-dependent newline. + Roughly an inverse of clojure.string/split-lines." + [lines] + (with-out-str + (run! println lines))) + +(defn equiv-modulo-newlines + "Returns whether strings are equivalent, disregarding differences in + embedded system-dependent newlines." + [s & more] + (== 1 (count (group-by string/split-lines (list* s more))))) + +(defmethod clojure.test/assert-expr 'thrown-with-cause-msg? [msg form] + ;; (is (thrown-with-cause-msg? c re expr)) + ;; Asserts that evaluating expr throws an exception of class c. + ;; Also asserts that the message string of the *cause* exception matches + ;; (with re-find) the regular expression re. + (let [klass (nth form 1) + re (nth form 2) + body (nthnext form 3)] + `(try ~@body + (clojure.test/do-report {:type :fail, :message ~msg, :expected '~form, :actual nil}) + (catch ~klass e# + (let [m# (if (.getCause e#) (.. e# getCause getMessage) (.getMessage e#))] + (if (re-find ~re m#) + (clojure.test/do-report {:type :pass, :message ~msg, + :expected '~form, :actual e#}) + (clojure.test/do-report {:type :fail, :message ~msg, + :expected '~form, :actual e#}))) + e#)))) diff --git a/src/test/clojure/cljs/transpile_tests.clj b/src/test/clojure/cljs/transpile_tests.clj new file mode 100644 index 0000000000..d43856eea9 --- /dev/null +++ b/src/test/clojure/cljs/transpile_tests.clj @@ -0,0 +1,22 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.transpile-tests + (:require [cljs.closure :as closure :refer [closure-transpile]] + [clojure.java.io :as io] + [clojure.test :refer [deftest is run-tests]])) + +(deftest test-transpile-lang-in-lang-out + (let [source (closure-transpile + (io/resource "goog/async/throttle.js") + {:language-in :es6 :language-out :es6})] + (is (nil? (re-find #"jscomp" source)))) + (let [source (closure-transpile + (io/resource "goog/async/throttle.js") + {:language-in :es6 :language-out :es5})] + (is (some? (re-find #"jscomp" source))))) diff --git a/src/test/clojure/cljs/type_inference_tests.clj b/src/test/clojure/cljs/type_inference_tests.clj new file mode 100644 index 0000000000..1a9170020d --- /dev/null +++ b/src/test/clojure/cljs/type_inference_tests.clj @@ -0,0 +1,415 @@ +(ns cljs.type-inference-tests + (:require + [cljs.analyzer :as ana] + [cljs.analyzer-tests :refer [analyze test-env test-cenv]] + [cljs.env :as env] + [cljs.test-util :refer [unsplit-lines]] + [clojure.test :refer [is are deftest testing]])) + +(deftest basic-inference + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '1))) + 'number)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '"foo"))) + 'string)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '\a))) + 'string)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(make-array 10)))) + 'array)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(js-obj)))) + 'object)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '[]))) + 'cljs.core/IVector)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '{}))) + 'cljs.core/IMap)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '#{}))) + 'cljs.core/ISet)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env ()))) + 'cljs.core/IList)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(fn [x] x)))) + 'function)) + (is (= (env/with-compiler-env test-cenv + (ana/no-warn + (:tag (analyze test-env '(Foo.))))) + 'cljs.core/Foo))) + +(deftest if-inference + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(if x "foo" 1))))) + '#{number string}))) + +(deftest if-induced-inference + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (nil? x) x :kw)))))) + '#{clj-nil cljs.core/Keyword})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (boolean? x) x :kw)))))) + '#{boolean cljs.core/Keyword})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (number? x) x :kw)))))) + '#{number cljs.core/Keyword})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (double? x) x :kw)))))) + '#{number cljs.core/Keyword})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (float? x) x :kw)))))) + '#{number cljs.core/Keyword})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (integer? x) x :kw)))))) + '#{number cljs.core/Keyword})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (seq? x) x :kw)))))) + '#{seq cljs.core/Keyword})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (array? x) x :kw)))))) + '#{array cljs.core/Keyword})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x ^any []] (if (seqable? x) x :kw)))))) + '#{cljs.core/ISeqable array string cljs.core/Keyword})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (ana/analyze test-env '(let [x (namespace :x)] (if x x :kw)))))) + '#{string cljs.core/Keyword}))) + +(deftest loop-recur-inference + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(loop [x "a"] x))))) + 'string)) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(loop [x 10] + (if (pos? x) + (dec x) + x)))))) + 'number)) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (analyze test-env '((fn [p?] + (loop [x nil] + (if (p? x) + x + (recur (str x))))) + 11))))) + '#{string clj-nil})) + (is (= (ana/no-warn + (env/with-compiler-env test-cenv + (:tag (analyze test-env '((fn [^string x] + (loop [y x] + (if (= "x" y) + y + (recur 1)))) + "a"))))) + '#{number string}))) + +(deftest method-inference + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(.foo js/bar)))) + 'js))) + +(deftest fn-method-inference + ;; should always infer 'function as tag + (is (= 'function + (:tag + (env/with-compiler-env test-cenv + (analyze test-env + '(fn ([a] 1) ([a b] "foo") ([a b & r] ()))))))) + (is (nil? + (:ret-tag + (env/with-compiler-env test-cenv + (analyze test-env + '(fn ([a] 1) ([a b] "foo") ([a b & r] ())))))) ) + ;; methods should have inferred types + (is (= '(number string cljs.core/IList) + (map :tag + (:methods + (env/with-compiler-env test-cenv + (analyze test-env + '(fn ([a] 1) ([a b] "foo") ([a b & r] ()))))))))) + +(deftest fn-inference + (is (= 'number + (env/with-compiler-env test-cenv + (:tag (analyze test-env + '(let [x (fn ([a] 1) ([a b] "foo") ([a b & r] ()))] + (x :one))))))) + (is (= 'string + (env/with-compiler-env test-cenv + (:tag (analyze test-env + '(let [x (fn ([a] 1) ([a b] "foo") ([a b & r] ()))] + (x :one :two))))))) + (is (= 'cljs.core/IList + (env/with-compiler-env test-cenv + (:tag (analyze test-env + '(let [x (fn ([a] 1) ([a b] "foo") ([a b & r] ()))] + (x :one :two :three))))))) + (is (= 'cljs.core/IList + (env/with-compiler-env test-cenv + (:tag (analyze test-env + '(let [x (fn ([a] 1) ([a b] "foo") ([a b & r] ()))] + (x :one :two :three :four)))))))) + +(deftest top-fn-inference + (env/with-compiler-env test-cenv + (ana/analyze-form-seq + '[(ns test.cljs-2901) + (defn foo + ([a] 1) + ([a b] "foo") + ([a b & r] ())) + (foo :one)])) + (is (= '[number string cljs.core/IList] + (map :tag + (get-in @test-cenv [::ana/namespaces 'test.cljs-2901 :defs 'foo :methods])))) + (is (= 'number + (:tag + (env/with-compiler-env test-cenv + (ana/analyze-form-seq + '[(ns test.cljs-2901) + (defn foo + ([a] 1) + ([a b] "foo") + ([a b & r] ())) + (foo :one)] + nil true))))) + (is (= 'string + (:tag + (env/with-compiler-env test-cenv + (ana/analyze-form-seq + '[(ns test.cljs-2901) + (defn foo + ([a] 1) + ([a b] "foo") + ([a b & r] ())) + (foo :one :two)] + nil true))))) + (is (= 'cljs.core/IList + (:tag + (env/with-compiler-env test-cenv + (ana/analyze-form-seq + '[(ns test.cljs-2901) + (defn foo + ([a] 1) + ([a b] "foo") + ([a b & r] ())) + (foo :one :two :three)] + nil true)))))) + +(deftest variadic-fn-inference + (is (= '(cljs.core/IList) + (map :tag + (:methods + (env/with-compiler-env test-cenv + (analyze test-env + '(fn ([a b & r] ())))))))) + (is (= 'cljs.core/IList + (env/with-compiler-env test-cenv + (:tag (analyze test-env + '(let [x (fn ([a b & r] ()))] + (x :one :two))))))) + + (is (= 'cljs.core/IList + (env/with-compiler-env test-cenv + (:tag (analyze test-env + '(let [x (fn ([a b & r] ()))] + (x :one :two :three))))))) + + (is (= 'cljs.core/IList + (env/with-compiler-env test-cenv + (:tag (analyze test-env + '(let [x (fn ([a b & r] ()))] + (x :one :two :three :four))))))) + ) + +(deftest top-variadic-fn-inference + (env/with-compiler-env test-cenv + (ana/analyze-form-seq + '[(ns test.cljs-2901-b) + (defn foo ([a b & r] ())) + (foo :one :two :three :four)] + nil false)) + (is (= '[cljs.core/IList] + (map :tag + (get-in @test-cenv + [::ana/namespaces 'test.cljs-2901-b :defs 'foo :methods])))) + (is (= 'cljs.core/IList + (:tag + (env/with-compiler-env test-cenv + (ana/analyze-form-seq + '[(ns test.cljs-2901-b) + (defn foo ([a b & r] ())) + (foo :one :two)] + nil true))))) + (is (= 'cljs.core/IList + (:tag + (env/with-compiler-env test-cenv + (ana/analyze-form-seq + '[(ns test.cljs-2901-b) + (defn foo ([a b & r] ())) + (foo :one :two :three)] + nil true))))) + (is (= 'cljs.core/IList + (:tag + (env/with-compiler-env test-cenv + (ana/analyze-form-seq + '[(ns test.cljs-2901-b) + (defn foo ([a b & r] ())) + (foo :one :two :three :four)] + nil true)))))) + +(deftest lib-inference + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(+ 1 2)))) + 'number)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(alength (array))))) + 'number)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(aclone (array))))) + 'array)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(-count [1 2 3])))) + 'number)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(count [1 2 3])))) + 'number)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(into-array [1 2 3])))) + 'array)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(js-obj)))) + 'object)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(-conj [] 1)))) + 'clj)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(conj [] 1)))) + 'clj)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(dissoc {:foo :bar} :foo)))) + '#{clj clj-nil})) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(distinct? 1)))) + 'boolean)) + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(special-symbol? 'foo)))) + 'boolean)) + ;; TODO: we can't infer isa?, we get 'any which is a bit surprising + ;(is (= (env/with-compiler-env test-cenv + ; (:tag (analyze test-env '(isa? ::foo :bar)))) + ; 'boolean)) + ;; has changed, why does this return #{clj any} ? + ;(is (= (env/with-compiler-env test-cenv + ; (:tag (analyze test-env '(assoc nil :foo :bar)))) + ; 'clj)) + ) + +(deftest lib-inference-extern-call + (testing "Test return type inference for core fns whose + internal implementation uses standard JS APIs" + (is (= 'boolean + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(array? (array))))))) + (is (= 'array + (env/with-compiler-env test-cenv + (:tag (analyze test-env '(make-array js/String. 10)))))))) + +(deftest test-always-true-if + (is (= (env/with-compiler-env test-cenv + (:tag (analyze test-env '(if 1 2 "foo")))) + 'number))) + +;; will only work if the previous test works +(deftest test-count + (is (= (cljs.env/with-compiler-env test-cenv + (:tag (analyze test-env '(count [])))) + 'number)) + ) + +(deftest test-numeric + (is (= (ana/no-warn + (cljs.env/with-compiler-env test-cenv + (:tag (analyze test-env '(dec x))))) + 'number)) + ;; we relaxed int, making it too strict just creates + ;; problems for legit coercion, Clojure is vague so we are vague :) + ;; (is (= (ana/no-warn + ;; (cljs.env/with-compiler-env test-cenv + ;; (:tag (analyze test-env '(int x))))) + ;; 'number)) + (is (= (ana/no-warn + (cljs.env/with-compiler-env test-cenv + (:tag (analyze test-env '(unchecked-int x))))) + 'number)) + (is (= (ana/no-warn + (cljs.env/with-compiler-env test-cenv + (:tag (analyze test-env '(mod x y))))) + 'number)) + (is (= (ana/no-warn + (cljs.env/with-compiler-env test-cenv + (:tag (analyze test-env '(quot x y))))) + 'number)) + (is (= (ana/no-warn + (cljs.env/with-compiler-env test-cenv + (:tag (analyze test-env '(rem x y))))) + 'number)) + (is (= (ana/no-warn + (cljs.env/with-compiler-env test-cenv + (:tag (analyze test-env '(bit-count n))))) + 'number)) + ) + +(deftest test-ctor-infer + (is (= 'cljs.core/Foo + (:tag + (env/with-compiler-env test-cenv + (ana/no-warn + (analyze test-env + '(let [g (Foo.)] + g)))))))) + +(deftest test-goog-import-ctor-infer + (is (= 'goog.history/Html5History + (:tag + (env/with-compiler-env (env/default-compiler-env) + (ana/analyze-form-seq + '[(ns test.foo + (:import [goog.history Html5History])) + (Html5History.)] + {} true)))))) + +(deftest test-goog-infer + (is (= 'boolean + (:tag (env/with-compiler-env (env/default-compiler-env) + (ana/analyze-form-seq + '[(ns test.foo + (:require [goog.string :as gstring])) + (gstring/contains "foobar" "foo")] + {} true))))) + (is (= 'boolean + (:tag + (env/with-compiler-env (env/default-compiler-env) + (ana/analyze-form-seq + '[(ns test.foo + (:require [goog.object :as gobject])) + (gobject/containsKey (js-object) "foo")] + {} true)))))) diff --git a/src/test/clojure/cljs/util_tests.clj b/src/test/clojure/cljs/util_tests.clj new file mode 100644 index 0000000000..f5bf0bac11 --- /dev/null +++ b/src/test/clojure/cljs/util_tests.clj @@ -0,0 +1,65 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns cljs.util-tests + (:require [cljs.util :as util] + [clojure.java.io :as io]) + (:use clojure.test)) + +(deftest test-levenshtein-distance + (testing "levenshtein-distance" + (is (= 0 (util/levenshtein-distance "abc" "abc"))) + (is (= 1 (util/levenshtein-distance "abc" "abcd"))) + (is (= 1 (util/levenshtein-distance "abcd" "abc"))) + (is (= 3 (util/levenshtein-distance "kitten" "sitting"))))) + +(deftest test-suggestion + (testing "suggestion" + (is (= ":optimization" (util/suggestion 3 ":optimization" [":optimization" ":static-fns"]))))) + +(deftest test-unknown-opts + (testing "unknown-opts" + (is (= [[:bogus nil] + [:optimisations :optimizations]] + (sort (util/unknown-opts #{:optimisations :bogus} #{:optimizations :static-fns})))))) + +(deftest test-relative-name + (if util/windows? + (let [initial (System/getProperty "user.dir")] + (System/setProperty "user.dir" "C:\\Users\\anmonteiro\\Downloads\\clojurescript-master") + (is (= (util/relative-name (io/file "C:\\Users\\anmonteiro\\Downloads\\clojurescript-master\\out\\index.js")) "out\\index.js")) + (is (= (util/relative-name (io/as-url (io/file "C:\\Users\\anmonteiro\\Downloads\\clojurescript-master\\node_modules\\lodash\\array.js"))) "node_modules\\lodash\\array.js")) + ;; Check case-sensitivity: + (System/setProperty "user.dir" "c:\\users\\anmonteiro\\Downloads\\clojurescript-master") + (is (= (util/relative-name (io/file "C:\\Users\\anmonteiro\\Downloads\\clojurescript-master\\out\\index.js")) "out\\index.js")) + (is (= (util/relative-name (io/as-url (io/file "C:\\Users\\anmonteiro\\Downloads\\clojurescript-master\\node_modules\\lodash\\array.js"))) "node_modules\\lodash\\array.js")) + ;; Check pass-through: + (is (= (util/relative-name (io/file "C:\\Temp\\clojurescript\\out\\index.js")) "C:\\Temp\\clojurescript\\out\\index.js")) + (System/setProperty "user.dir" initial)) + ;; Non-windows + (let [initial (System/getProperty "user.dir")] + (System/setProperty "user.dir" "/Users/user/clojurescript") + (is (= (util/relative-name (io/file "/Users/user/clojurescript/out/index.js")) "out/index.js")) + (is (= (util/relative-name (io/as-url (io/file "/Users/user/clojurescript/out/index.js"))) "out/index.js")) + ;; Check pass-through: + (is (= (util/relative-name (io/file "/tmp/clojurescript/out/index.js")) "/tmp/clojurescript/out/index.js")) + (System/setProperty "user.dir" initial)))) + +(deftest test-path + (is (= (.getAbsolutePath (io/file "src/main/clojure/cljs/closure.clj")) + (util/path (io/as-url (io/file "src/main/clojure/cljs/closure.clj")))))) + +(deftest test-bytes-to-hex-str + (is (= "09616263" (#'util/bytes-to-hex-str (.getBytes "\u0009abc"))))) + +(deftest test-content-sha + (is (= "40BD001563085FC35165329EA1FF5C5ECBDBBEEF" (util/content-sha "123"))) + (is (= "40BD0" (util/content-sha "123" 5)))) + +(deftest test-cljs-3008 + (is (= :compilation (:clojure.error/phase (ex-data (util/compilation-error (Exception.))))))) diff --git a/src/test/externs/test.js b/src/test/externs/test.js new file mode 100644 index 0000000000..fe206c1a04 --- /dev/null +++ b/src/test/externs/test.js @@ -0,0 +1,23 @@ +/** + * @constructor + */ +var Foo = function() {}; +Foo.prototype.wozMethod = function() { +}; +/** + * @return {Foo} + */ +var baz = function() {}; +/** + * @constructor + */ +Foo.Bar = function() {}; +/** + * @return {Foo.Boo} + */ +Foo.Bar.prototype.baz = function() {}; +/** + * @constructor + */ +Foo.Boo = function() {}; +Foo.Boo.prototype.woz = function() {}; \ No newline at end of file diff --git a/src/test/node/test.js b/src/test/node/test.js new file mode 100644 index 0000000000..a4ad1c40bd --- /dev/null +++ b/src/test/node/test.js @@ -0,0 +1 @@ +var objectAssign = require("object-assign"); \ No newline at end of file diff --git a/src/test/self/bootstrap_test/core.cljs b/src/test/self/bootstrap_test/core.cljs new file mode 100644 index 0000000000..43b917c4d5 --- /dev/null +++ b/src/test/self/bootstrap_test/core.cljs @@ -0,0 +1,12 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns bootstrap-test.core) + +(defn foo [a b] + (+ a b)) \ No newline at end of file diff --git a/src/test/self/bootstrap_test/helper.clj b/src/test/self/bootstrap_test/helper.clj new file mode 100644 index 0000000000..7eff8b74c6 --- /dev/null +++ b/src/test/self/bootstrap_test/helper.clj @@ -0,0 +1,12 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns bootstrap-test.helper) + +(defn bar [a b] + `(* ~a ~b)) diff --git a/src/test/self/bootstrap_test/macros.clj b/src/test/self/bootstrap_test/macros.clj new file mode 100644 index 0000000000..07c3611bea --- /dev/null +++ b/src/test/self/bootstrap_test/macros.clj @@ -0,0 +1,13 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns bootstrap-test.macros + (:require [bootstrap-test.helper :refer [bar]])) + +(defmacro foo [a b] + (bar a b)) \ No newline at end of file diff --git a/src/test/self/self_host/test.cljs b/src/test/self/self_host/test.cljs new file mode 100644 index 0000000000..345404b4fc --- /dev/null +++ b/src/test/self/self_host/test.cljs @@ -0,0 +1,1626 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns self-host.test + (:require [cljs.test :as test + :refer-macros [run-tests deftest testing is async]] + [cljs.js :as cljs] + [cljs.analyzer :as ana] + [clojure.string :as string] + [cljs.stacktrace :as st] + [cljs.nodejs :as nodejs])) + +(set! (.-user js/cljs) #js {}) + +(nodejs/enable-util-print!) + +(defn latch [m f] + (let [r (atom 0)] + (add-watch r :latch + (fn [_ _ o n] + (when (== n m) (f)))) + r)) + +(defn inc! [r] + (swap! r inc)) + +(def vm (js/require "vm")) +(def fs (js/require "fs")) +(def st (cljs/empty-state)) + +(defn node-eval [{:keys [name source]}] + (if-not js/COMPILED + (.runInThisContext vm source (str (munge name) ".js")) + (js/eval source))) + +(def libs + {'bootstrap-test.core :cljs + 'bootstrap-test.macros :clj + 'bootstrap-test.helper :clj}) + +(defn node-load [{:keys [name macros]} cb] + (if (contains? libs name) + (let [path (str "src/test/self/" (cljs/ns->relpath name) + "." (cljs.core/name (get libs name)))] + (.readFile fs path "utf-8" + (fn [err src] + (cb (if-not err + {:lang :clj :source src} + (.error js/console err)))))) + (cb nil))) + +(defn elide-env [env ast opts] + (dissoc ast :env)) + +(defn var-ast + "Given an already derefed compiler state plus the symbols of a + namespace and a var (e.g. 'clojure.string and 'trim) , return the var + AST representation or nil if not found, probably because not required + yet. + + The 1-arity function does the splitting in case of a fully qualified + symbol (e.g. 'clojure.string/trim)." + ([st sym] + (var-ast st (symbol (namespace sym)) (symbol (name sym)))) + ([st ns-sym sym] + (get-in st [:cljs.analyzer/namespaces ns-sym :defs sym]))) + +(defn file->lang + "Converts a file path to a :lang keyword by inspecting the file + extension." + [file-path] + (if (string/ends-with? file-path ".js") + :js + :clj)) + +(defn str-evals-to + "Checks that a string evaluates to an expected value." + ([st l expected s] + (str-evals-to st l expected nil)) + ([st l expected s opts] + (cljs/eval-str st + s + nil + (merge + {:context :expr + :eval node-eval} + opts) + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= expected value)) + (inc! l))))) + +;; NOTE: can't set passes because callbacks happen _inside_ binding +;; do so will effect other tests + +(deftest test-require-updates-*loading* + (async done + (let [l (latch 4 done)] + (cljs/require + {} + 'load1.core + :reload-all + {:load (fn [_ cb] (cb {:lang :clj + :source "(ns load1.core)"})) + :eval (constantly nil)} + (fn [{:keys [error value]}] + (is (nil? error)) + (is value) + (is (= #{'load1.core} @cljs/*loaded*)) + (inc! l))) + (cljs/require + {} + 'load2.core + :reload-all + {:macros-ns true + :load (fn [_ cb] (cb {:lang :clj + :source "(ns load2.core)"})) + :eval (constantly nil)} + (fn [{:keys [error value]}] + (is (nil? error)) + (is value) + (is (= #{'load2.core$macros} @cljs/*loaded*)) + (inc! l))) + (cljs/require + {} + 'load3.core + :reload-all + {:load (fn [_ cb] (cb {:lang :js + :source ""})) + :eval (constantly nil)} + (fn [{:keys [error value]}] + (is (nil? error)) + (is value) + (is (= #{'load3.core} @cljs/*loaded*)) + (inc! l))) + (cljs/require + {} + 'load4.core + :reload-all + {:macros-ns true + :load (fn [_ cb] (cb {:lang :js + :source ""})) + :eval (constantly nil)} + (fn [{:keys [error value]}] + (is (nil? error)) + (is value) + (is (= #{'load4.core$macros} @cljs/*loaded*)) + (inc! l)))))) + +(deftest test-analyze-str + (async done + (let [l (latch 3 done)] + (cljs/analyze-str st "(+ 1 1)" nil + {:context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= :js (:op value))) + (inc! l))) + (cljs/analyze-str st "(defprotocol IFoo)" nil + {:context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (inc! l))) + (cljs/analyze-str st "(fn [] (let [x 7 y] (prn y)))" nil + {:context :expr} + (fn [{:keys [error value]}] + (is (nil? value)) + (is (= "Could not analyze " (ex-message error))) + (inc! l)))))) + +(deftest test-compile-str + (async done + (let [l (latch 7 done)] + (cljs/compile-str st "(+ 1 1)" + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= "((1) + (1));\n" value)) + (inc! l))) + (cljs/compile-str st "(fn [])" nil + {:context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= "(function (){\nreturn null;\n})" value)) + (inc! l))) + (cljs/compile-str st "(if cljs.core.first 1 2)" nil + {:context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= "(cljs.core.truth_(cljs.core.first)?(1):(2))" value)) + (inc! l))) + (cljs/compile-str st "(.toString \"a\")" nil + {:context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= "\"a\".toString()" value)) + (inc! l))) + (cljs/compile-str st "(do (defn foo [a b] (+ a b)) (foo 1 2))" nil + {:context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (string/index-of value "cljs.user.foo.call(null,(1),(2))")) + (inc! l))) + (cljs/compile-str st "(do (defn foo [a b] (+ a b)) (foo 1 2))" nil + {:context :expr + :static-fns true} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (string/index-of value "cljs.user.foo((1),(2))")) + (inc! l))) + (cljs/compile-str st "(fn [] (let [x 7 y] (prn y)))" nil + {:context :expr} + (fn [{:keys [error value]}] + (is (nil? value)) + (is (= "Could not compile " (ex-message error))) + (inc! l)))))) + +(deftest test-eval-str + (async done + (let [l (latch 9 done)] + (cljs/eval-str st "(+ 1 1)" nil + {:eval node-eval} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (== 2 value)) + (inc! l))) + (cljs/eval-str st "(def x 1)" nil + {:eval node-eval + :context :expr + :def-emits-var true} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (var? value)) + (inc! l))) + (cljs/eval-str st "(fn [])" nil + {:eval node-eval + :context :expr + :def-emits-var true} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (fn? value)) + (inc! l))) + (cljs/eval-str st "((fn [a b] (+ a b)) 1 2)" nil + {:eval node-eval + :context :expr + :def-emits-var true} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (== 3 value)) + (inc! l))) + (cljs/eval-str st "(ns foo.bar)" nil + {:eval node-eval + :context :expr + :def-emits-var true} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (not (nil? js/foo.bar))) + (inc! l))) + (cljs/eval-str st "(defn foo [a b] (+ a b))" nil + {:eval node-eval + :context :expr + :def-emits-var true} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (== 3 (js/cljs.user.foo 1 2))) + (inc! l))) + (cljs/eval-str st "(do (defn foo [a b] (+ a b)) (foo 1 2))" nil + {:eval node-eval + :context :expr + :def-emits-var true + :static-fns true} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (== 3 value)) + (inc! l))) + (cljs/eval-str st "(def foo (let [x 1] (let [x (inc x)] x)))" nil + {:eval node-eval + :context :statement + :def-emits-var true} + (fn [{:keys [error value]}] + (is (nil? error)) + (inc! l))) + (cljs/eval-str st "(with-out-str (doseq [x [1 2]] (println x)))" nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (= "1\n2\n" value)) + (inc! l)))))) + +(deftest test-disable-analyze-deps + (async done + (let [l (latch 4 done)] + (cljs/analyze-str st + "(ns analyze-deps-as.core (:require [analyze-me.core :refer [abc]]))" + nil + {:context :expr + :eval cljs.js/js-eval + :analyze-deps false + :load (fn [_ cb] + (cb {:lang :clj + :source "(ns analyze-me.core)"}))} + (fn [{:keys [error]}] + (is (nil? error)) + (inc! l))) + (cljs/eval st + '(ns analyze-deps-e.core (:require [analyze-me.core :refer [abc]])) + {:context :expr + :eval cljs.js/js-eval + :analyze-deps false + :load (fn [_ cb] + (cb {:lang :clj + :source "(ns analyze-me.core)"}))} + (fn [{:keys [error]}] + (is (nil? error)) + (inc! l))) + (cljs/compile-str st + "(ns analyze-deps-c.core (:require [analyze-me.core :refer [abc]]))" + nil + {:context :expr + :eval cljs.js/js-eval + :analyze-deps false + :load (fn [_ cb] + (cb {:lang :clj + :source "(ns analyze-me.core)"}))} + (fn [{:keys [error]}] + (is (nil? error)) + (inc! l))) + (cljs/eval-str st + "(ns analyze-deps-es.core (:require [analyze-me.core :refer [abc]]))" + nil + {:context :expr + :eval cljs.js/js-eval + :analyze-deps false + :load (fn [_ cb] + (cb {:lang :clj + :source "(ns analyze-me.core)"}))} + (fn [{:keys [error]}] + (is (nil? error)) + (inc! l)))))) + +(deftest test-disable-load-macros + (async done + (let [l (latch 4 done)] + (cljs/analyze-str st + "(ns load-macros-as.core (:require-macros [load-me.core]))" + nil + {:context :expr + :eval cljs.js/js-eval + :load-macros false + :load (fn [_ _] + (throw (ex-info "unexpected" {})))} + (fn [{:keys [error]}] + (is (nil? error)) + (inc! l))) + (cljs/eval st + '(ns load-macros-e.core (:require-macros [load-me.core])) + {:context :expr + :eval cljs.js/js-eval + :load-macros false + :load (fn [_ _] + (throw (ex-info "unexpected" {})))} + (fn [{:keys [error]}] + (is (nil? error)) + (inc! l))) + (cljs/compile-str st + "(ns load-macros-c.core (:require-macros [load-me.core]))" + nil + {:context :expr + :eval cljs.js/js-eval + :load-macros false + :load (fn [_ _] + (throw (ex-info "unexpected" {})))} + (fn [{:keys [error]}] + (is (nil? error)) + (inc! l))) + (cljs/eval-str st + "(ns load-macros-es.core (:require-macros [load-me.core]))" + nil + {:context :expr + :eval cljs.js/js-eval + :load-macros false + :load (fn [_ _] + (throw (ex-info "unexpected" {})))} + (fn [{:keys [error]}] + (is (nil? error)) + (inc! l)))))) + +(deftest test-load-and-invoke-macros + (async done + (let [l (latch 12 done)] + ;; Normal require macros + (let [st (cljs/empty-state)] + (cljs/eval-str st + "(ns cljs.user (:require-macros foo.core))" + nil + {:eval node-eval + :load (fn [_ cb] (cb {:lang :clj :source "(ns foo.core) (defmacro add [a b] `(+ ~a ~b))"}))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(foo.core/add 1 2)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 3 value)) + (inc! l)))))) + (let [st (cljs/empty-state)] + ;; Refer macro symbol + (cljs/eval-str st + "(ns cljs.user (:require-macros [foo.core :refer [add]]))" + nil + {:eval node-eval + :load (fn [_ cb] (cb {:lang :clj :source "(ns foo.core) (defmacro add [a b] `(+ ~a ~b))"}))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(add 1 3)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 4 value)) + (inc! l)))))) + (let [st (cljs/empty-state)] + ;; Alias the macro namespace + (cljs/eval-str st + "(ns cljs.user (:require-macros [foo.core :as foo]))" + nil + {:eval node-eval + :load (fn [_ cb] (cb {:lang :clj :source "(ns foo.core) (defmacro add [a b] `(+ ~a ~b))"}))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(foo/add 1 5)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 6 value)) + (inc! l)))))) + (let [st (cljs/empty-state)] + ;; Use instead of require + (cljs/eval-str st + "(ns cljs.user (:use-macros [foo.core :only [add]]))" + nil + {:eval node-eval + :load (fn [_ cb] (cb {:lang :clj :source "(ns foo.core) (defmacro add [a b] `(+ ~a ~b))"}))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(add 1 8)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 9 value)) + (inc! l)))))) + (let [st (cljs/empty-state)] + ;; Employ inline macro specification sugar (include) + (cljs/eval-str st + "(ns cljs.user (:require [foo.core :include-macros true]))" + nil + {:eval node-eval + :load (fn [{:keys [macros]} cb] + (if macros + (cb {:lang :clj :source "(ns foo.core) (defmacro add [a b] `(+ ~a ~b))"}) + (cb {:lang :clj :source "(ns foo.core)"})))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(foo.core/add 1 13)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 14 value)) + (inc! l)))))) + (let [st (cljs/empty-state)] + ;; Employ inline macro specification sugar (include with alias) + (cljs/eval-str st + "(ns cljs.user (:require [foo.core :as foo :include-macros true]))" + nil + {:eval node-eval + :load (fn [{:keys [macros]} cb] + (if macros + (cb {:lang :clj :source "(ns foo.core) (defmacro add [a b] `(+ ~a ~b))"}) + (cb {:lang :clj :source "(ns foo.core)"})))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(foo/add 1 21)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 22 value)) + (inc! l)))))) + (let [st (cljs/empty-state)] + ;; Employ inline macro specification sugar (refer) + (cljs/eval-str st + "(ns cljs.user (:require [foo.core :refer-macros [add]]))" + nil + {:eval node-eval + :load (fn [{:keys [macros]} cb] + (if macros + (cb {:lang :clj :source "(ns foo.core) (defmacro add [a b] `(+ ~a ~b))"}) + (cb {:lang :clj :source "(ns foo.core)"})))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(add 1 34)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 35 value)) + (inc! l)))))) + (let [st (cljs/empty-state)] + ;; Employ inline macro specification sugar (refer with alias) + (cljs/eval-str st + "(ns cljs.user (:require [foo.core :as foo :refer-macros [add]]))" + nil + {:eval node-eval + :load (fn [{:keys [macros]} cb] + (if macros + (cb {:lang :clj :source "(ns foo.core) (defmacro add [a b] `(+ ~a ~b))"}) + (cb {:lang :clj :source "(ns foo.core)"})))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(+ (add 2 3) (foo/add 5 8))" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 18 value)) + (inc! l)))))) + (let [st (cljs/empty-state)] + ;; Rely on implicit macro loading (ns loads its own macros) + (cljs/eval-str st + "(ns cljs.user (:require foo.core))" + nil + {:eval node-eval + :load (fn [{:keys [macros]} cb] + (if macros + (cb {:lang :clj :source "(ns foo.core) (defmacro add [a b] `(+ ~a ~b))"}) + (cb {:lang :clj :source "(ns foo.core (:require-macros foo.core))"})))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(foo.core/add 100 200)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 300 value)) + (inc! l)))))) + (let [st (cljs/empty-state)] + ;; Rely on implicit macro inference (ns loads its own macros) + (cljs/eval-str st + "(ns cljs.user (:require [foo.core :refer [add]]))" + nil + {:eval node-eval + :load (fn [{:keys [macros]} cb] + (if macros + (cb {:lang :clj :source "(ns foo.core) (defmacro add [a b] `(+ ~a ~b))"}) + (cb {:lang :clj :source "(ns foo.core (:require-macros foo.core))"})))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(add 110 210)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 320 value)) + (inc! l)))))) + (let [st (cljs/empty-state)] + ;; Rely on implicit macro inference for renames (ns loads its own macros) + (cljs/eval-str st + "(ns cljs.user (:require [foo.core :refer [add] :rename {add plus}]))" + nil + {:eval node-eval + :load (fn [{:keys [macros]} cb] + (if macros + (cb {:lang :clj :source "(ns foo.core) (defmacro add [a b] `(+ ~a ~b))"}) + (cb {:lang :clj :source "(ns foo.core (:require-macros foo.core))"})))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(plus 110 210)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 320 value)) + (inc! l)))))) + (let [st (cljs/empty-state)] + ;; Rely on implicit macro loading (ns loads its own macros), with an alias + ;; CLJS-1657 + (cljs/eval-str st + "(ns cljs.user (:require [foo.core :as foo]))" + nil + {:eval node-eval + :load (fn [{:keys [macros]} cb] + (if macros + (cb {:lang :clj :source "(ns foo.core) (defmacro add [a b] `(+ ~a ~b))"}) + (cb {:lang :clj :source "(ns foo.core (:require-macros foo.core))"})))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(foo/add 300 500)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 800 value)) + (inc! l))))))))) + +(deftest test-eval-str-with-require-macros + (async done + (let [l (latch 2 done)] + (cljs/eval-str st + "(ns cljs.user (:require-macros [cljs.user.macros]))" + nil + {:eval node-eval + :load (fn [_ cb] (cb {:lang :clj :source "(ns cljs.user.macros)"}))} + (fn [{:keys [value error]}] + (is (nil? error)) + (inc! l))) + (cljs/eval-str st + "(ns cljs.user (:require-macros [cljs.user.macros :as cljs-user-macros]))" + nil + {:eval node-eval + :load (fn [_ cb] (cb {:lang :clj :source "(ns cljs.user.macros)"}))} + (fn [{:keys [error value]}] + (is (nil? error)) + (inc! l)))))) + +(deftest test-CLJS-1330 + (async done + (cljs/eval-str st + "(.toString 1)" + nil + {:eval node-eval} + (fn [{:keys [error value]}] + (is (= "1" value)) + (done))))) + +(deftest test-CLJS-1551 + (async done + (let [l (latch 3 done)] + (cljs/eval-str st + "(if-let [x true y true] 3)" + nil + {:eval node-eval} + (fn [{:keys [error value]}] + (is (nil? value)) + (is (= "if-let requires exactly 2 forms in binding vector" (ex-message (ex-cause (ex-cause error))))) + (inc! l))) + (cljs/eval-str st + "(if-let [x true] 1 2 3)" + nil + {:eval node-eval} + (fn [{:keys [error value]}] + (is (nil? value)) + (is (= "if-let requires 1 or 2 forms after binding vector" (ex-message (ex-cause (ex-cause error))))) + (inc! l))) + (cljs/eval-str st + "(if-let '(x true) 1)" + nil + {:eval node-eval} + (fn [{:keys [error value]}] + (is (nil? value)) + (is (= "if-let requires a vector for its binding" (ex-message (ex-cause (ex-cause error))))) + (inc! l)))))) + +(deftest test-CLJS-1573 + (async done + (let [l (latch 4 done)] + (cljs/compile-str st + "\"90°\"" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= "\"90\\u00b0\"" value)) + (inc! l))) + (cljs/compile-str st + "\"Ϊ\"" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= "\"\\u03aa\"" value)) + (inc! l))) + (cljs/compile-str st + "\"ሴ\"" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= "\"\\u1234\"" value)) + (inc! l))) + (cljs/eval-str st + "\"90°\"" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= "90°" value)) + (inc! l)))))) + +(deftest test-CLJS-1577 + (async done + (let [l (latch 3 done)] + (cljs/analyze-str st + "`.x" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= :quote (:op value))) + (is (= ''.x (:form value))) + (is (= '.x (-> value :expr :form))) + (inc! l))) + (cljs/compile-str st + "`.x" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (string/starts-with? value "new cljs.core.Symbol(null,\".x\",\".x\",")) + (inc! l))) + (cljs/eval-str st + "`.x" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= '.x value)) + (inc! l)))))) + +(deftest test-CLJS-1584 + (async done + (cljs/eval-str st + "(condp = 1 1 2)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 2 value)) + (done))))) + +(deftest test-CLJS-1585 + (async done + (cljs/eval-str st + "(ns alias-load.core (:require [aliased.core :as alias]))" + nil + {:ns 'cljs.user + :context :expr + :eval cljs.js/js-eval + :load (fn [_ cb] + (cb {:lang :clj :source "(ns aliased.core)"}))} + (fn [{:keys [error value]}] + (is (nil? error)) + (cljs.js/eval-str st + "::alias/bar" + nil + {:ns 'alias-load.core + :context :expr + :eval cljs.js/js-eval} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= :aliased.core/bar value)) + (done))))))) + +(deftest test-CLJS-1589 + (async done + (cljs/eval-str st + "(case 1 nil nil :x)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= :x value)) + (done))))) + +(deftest test-CLJS-1612 + (async done + (let [st (cljs/empty-state) + l (latch 10 done)] + (cljs/eval st '(ns foo.core + (:require [bar.core :as bar])) + {:load (fn [{:keys [macros]} cb] + (if macros + (cb {:lang :clj :source "(ns bar.core) (defmacro add [a b] `(+ ~a ~b))"}) + (cb {:lang :clj :source "(ns bar.core (:refer-macros bar.core)) (defn sub [a b] (- a b))"})))} + (fn [_] (inc! l))) + (testing "various syntax quote patterns" + (str-evals-to st l 'foo.core/foo "`foo" {:ns 'foo.core}) + (str-evals-to st l 'bar.core/sub "`bar/sub" {:ns 'foo.core}) + (str-evals-to st l 'bar.core/add "`bar/add" {:ns 'foo.core}) + (str-evals-to st l 'bar.core/undeclared "`bar/undeclared" {:ns 'foo.core})) + (testing "core macros under syntax quote" + (str-evals-to st l 13 + "(do (defmulti bar (fn [x y] [x y])) 13)" {:ns 'foo.core}) + (str-evals-to st l 17 + "(do (deftype FnLikeB [a] IFn (-invoke [_] a)) 17)" {:ns 'foo.core}) + (str-evals-to st l [10 4] + "(let [{:keys [a b] :or {b 4}} {:a 10}] [a b])" {:ns 'foo.core}) + (str-evals-to st l [[nil]] + "(js->clj (make-array nil 1 1))" {:ns 'foo.core}) + (str-evals-to st l [1 1 1 1 1] + "(let [an-array (int-array 5 0)] (js->clj (amap an-array idx ret (+ 1 (aget an-array idx)))))" {:ns 'foo.core}))))) + +(deftest test-eval-str-with-require + (async done + (let [l (latch 3 done)] + (cljs/eval-str st + "(ns foo.bar (:require [bootstrap-test.core]))\n(bootstrap-test.core/foo 3 4)" + nil + {:eval node-eval + :load node-load} + (fn [{:keys [value error]}] + (is (nil? error)) + (is (== 7 value)) + (inc! l))) + (cljs/eval-str st + "(ns foo.bar (:require-macros [bootstrap-test.macros :refer [foo]]))\n(foo 4 4)" + nil + {:eval node-eval + :load node-load} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (== 16 value)) + (inc! l))) + (cljs/eval-str st + "(ns foo.bar)\n(first [1 2 3])" + nil + {:eval node-eval + :load node-load} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (== 1 value)) + (inc! l)))))) + +#_(deftest test-ns-merge + (async done + (cljs/eval-str st + "(ns foo.bar (:require [bootstrap-test.core :refer [foo]])) + (ns foo.bar) + (foo 1 1)" + nil + {:eval node-eval + :load node-load} + (fn [{:keys [value error]}] + (is (nil? error)) + (done))))) + +(deftest test-cljs-1651 + (let [st (cljs/empty-state)] + (async done + (cljs/eval-str st + "(defn double [x] (* 2 x))" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "[(double 3) (apply double [3])]" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [value error]}] + (is (= value [6 6])) + (done)))))))) + +(deftest test-cljs-1854 + (let [st (cljs/empty-state)] + (async done + (cljs/eval st + '(require 'foo.core1854) + {:eval node-eval + :context :expr + :load (fn [_ cb] (cb {:lang :clj :source "(ns foo.core1854) (def ^:const x 1)"}))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval st + 'foo.core1854/x + {:eval node-eval + :context :expr} + (fn [{:keys [value error]}] + (is (nil? error)) + (is (= value 1)))) + (cljs/eval st + '(require 'foo.core1854 :reload) + {:eval node-eval + :context :expr + :load (fn [_ cb] (cb {:lang :clj :source "(ns foo.core1854) (def ^:const x 2)"}))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval st + 'foo.core1854/x + {:eval node-eval + :context :expr} + (fn [{:keys [value error]}] + (is (nil? error)) + (is (= value 2)))) + (cljs/eval st + '(require 'bar.core1854 :reload-all) + {:eval node-eval + :context :expr + :load (fn [{:keys [name]} cb] + (case name + bar.core1854 (cb {:lang :clj :source "(ns bar.core1854 (:require [foo.core1854]))"}) + foo.core1854 (cb {:lang :clj :source "(ns foo.core1854) (def ^:const x 3)"})))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval st + 'foo.core1854/x + {:eval node-eval + :context :expr} + (fn [{:keys [value error]}] + (is (nil? error)) + (is (= value 3)) + (done)))))))))))) + +(deftest test-cljs-1874 + (async done + (let [st (cljs/empty-state) + l (latch 1 done)] + (cljs/eval st '(ns foo.core + (:require-macros [bar.core])) + {:load (fn [_ cb] + (cb {:lang :clj + :source "(ns bar.core) (defmacro add [a b] `(+ ~a ~b))"}))} + (fn [_] + (is (false? (:fn-var (var-ast @st 'bar.core$macros/add)))) + (inc! l)))))) + +(deftest test-cljs-1949 + (async done + (let [st (cljs/empty-state) + l (latch 1 done)] + (cljs/eval-str + st + "(.catch (js/Promise. #(%2 \"x\")) #(println %))" + nil + {:context :expr + :eval node-eval} + (fn [{:keys [error] :as m}] + (is (nil? error)) + (inc! l)))))) + +(deftest test-cljs-2024 + (async done + (let [st (cljs/empty-state) + l (latch 1 done)] + (cljs/eval-str + st + "(find-ns-obj 'a.x)" + nil + {:context :expr + :eval node-eval} + (fn [{:keys [error] :as m}] + (is (nil? error)) + (inc! l)))))) + +(deftest test-cljs-2122 + (async done + (let [st (cljs/empty-state) + l (latch 2 done)] + (cljs/eval-str + st + "1" + nil + {:context :expr + :eval node-eval} + (fn [{:keys [error] :as m}] + (is (nil? error)) + (is (every? symbol? (keys (get-in @st [:cljs.analyzer/namespaces])))) + (inc! l))) + (cljs/eval-str + st + "1" + "A string name" + {:context :expr + :eval node-eval} + (fn [{:keys [error] :as m}] + (is (nil? error)) + (is (every? symbol? (keys (get-in @st [:cljs.analyzer/namespaces])))) + (inc! l)))))) + +(deftest test-string-requires-cljs-2232 + (async done + (let [st (cljs/empty-state) + l (latch 4 done)] + (cljs/compile-str + (atom @st) + "(ns foo.core (:require [path]))" + nil + {:context :expr + :target :nodejs + :eval node-eval} + (fn [{:keys [error value] :as m}] + (is (nil? error)) + (is (some? (re-find #"foo\.core\.node\$module\$path = require\('path'\);" value))) + (inc! l))) + (cljs/eval-str + (atom @st) + "(ns foo.core (:require [path])) (path/basename \"/foo/bar\")" + nil + {:context :expr + :target :nodejs + :eval node-eval} + (fn [{:keys [error value] :as m}] + (is (nil? error)) + (is (= value "bar")) + (inc! l))) + (cljs/analyze-str + (atom @st) + "(ns foo.core (:require [path]))" + nil + {:context :expr + :target :nodejs + :load (fn [_ cb] + (cb {:lang :js + :source ""}))} + (fn [{:keys [error value] :as m}] + (is (nil? error)) + (is (= (:deps value) '[path])) + (inc! l))) + (let [st (cljs/empty-state)] + (cljs/eval + st + '(ns foo.core (:require [path])) + {:context :expr + :target :nodejs + :eval node-eval} + (fn [{:keys [error value] :as m}] + (is (nil? error)) + (cljs/eval + st + '(path/basename "/foo/bar") + {:context :expr + :ns 'foo.core + :target :nodejs + :eval node-eval} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= value "bar")) + (inc! l))))))))) + +(deftest test-global-exports-cljs-2243 + (async done + (let [calculator-load (fn [_ cb] + (cb {:lang :js + :source "global.Calculator = { + add: function (a, b) { + return a + b; + }, + subtract: function (a, b) { + return a - b; + } +};"})) + st (cljs/empty-state) + l (latch 4 done)] + (swap! st assoc :js-dependency-index {"calculator" {:global-exports '{calculator Calculator}}}) + (cljs/compile-str + (atom @st) + "(ns foo.core (:require [calculator]))" + nil + {:context :expr + :load calculator-load + :eval node-eval} + (fn [{:keys [error value] :as m}] + (is (nil? error)) + (is (some? (re-find #"foo\.core\.global\$module\$calculator = goog.global\[\"Calculator\"\];" value))) + (inc! l))) + (cljs/eval-str + (atom @st) + "(ns foo.core (:require [calculator])) (calculator/add 1 2)" + nil + {:context :expr + :load calculator-load + :eval node-eval} + (fn [{:keys [error value] :as m}] + (is (nil? error)) + (is (= value 3)) + (inc! l))) + (cljs/analyze-str + (atom @st) + "(ns foo.core (:require [calculator]))" + nil + {:context :expr + :load calculator-load} + (fn [{:keys [error value] :as m}] + (is (nil? error)) + (is (= (:deps value) '[calculator])) + (inc! l))) + (let [st (atom @st)] + (cljs/eval + st + '(ns foo.core (:require [calculator])) + {:context :expr + :load calculator-load + :eval node-eval} + (fn [{:keys [error value] :as m}] + (is (nil? error)) + (cljs/eval + st + '(calculator/add 1 2) + {:context :expr + :ns 'foo.core + :eval node-eval} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= value 3)) + (inc! l))))))))) + +(deftest test-cljs-2287 + (async done + (let [st (cljs/empty-state) + l (latch 2 done)] + (cljs/eval-str + (atom @st) + "(ns foo.core (:require [path]))" + nil + {:context :expr + :target :nodejs + :def-emits-var true + :eval identity} + (fn [{{:keys [source]} :value}] + (is (some? (re-find #"foo\.core\.node\$module\$path = require\('path'\);\snull;" source))) + (inc! l))) + (let [calculator-load (fn [_ cb] + (cb {:lang :js + :source "global.Calculator = { + add: function (a, b) { + return a + b; + }, + subtract: function (a, b) { + return a - b; + } +};"}))] + (swap! st assoc :js-dependency-index {"calculator" {:global-exports '{calculator Calculator}}}) + (cljs/eval-str + (atom @st) + "(ns foo.core (:require [calculator])) (calculator/add 1 2)" + nil + {:context :expr + :def-emits-var true + :load calculator-load + :eval identity} + (fn [{{:keys [source]} :value}] + (is (some? (re-find #"foo\.core\.global\$module\$calculator = goog.global\[\"Calculator\"\];\snull;" source))) + (inc! l))))))) + +(deftest test-cljs-2814 + (is (= "global$module$react" (ana/munge-global-export 'react))) + (is (= "global$module$_CIRCA_material_ui$core$styles" (ana/munge-global-export "@material-ui/core/styles"))) + (is (= "node$module$_CIRCA_material_ui$core$styles" (ana/munge-node-lib "@material-ui/core/styles")))) + +(deftest test-cljs-2815 + (async done + (let [st (cljs/empty-state) + l (latch 1 done)] + (let [x-load (fn [_ cb] + (cb {:lang :js + :source "global.X = {};"}))] + (swap! st assoc :js-dependency-index {"@material-ui/core/styles" {:global-exports {"@material-ui/core/styles" "X"}} + "@material-ui/core/styles/a" {:global-exports {"@material-ui/core/styles/a" "X.a"}}}) + (cljs/eval-str + (atom @st) +"(ns foo.core + (:require [\"@material-ui/core/styles\" :as mui-styles] + [\"@material-ui/core/styles/a\" :as mui-styles-a])) + +(mui-styles/createMuiTheme) +(mui-styles-a/foo)" + nil + {:context :expr + :def-emits-var true + :load x-load + :eval identity} + (fn [{{:keys [source]} :value}] + (testing "global exports using string key" + (is (some? (re-find #"foo\.core\.global\$module\$_CIRCA_material_ui\$core\$styles = goog.global\[\"X\"\];\s" source)))) + (testing "global exports points to a sub property" + (is (some? (re-find #"foo\.core\.global\$module\$_CIRCA_material_ui\$core\$styles\$a = goog.global\[\"X\"\]\[\"a\"\];\s" source)))) + (inc! l))))))) + +(deftest test-cljs-2261 + (async done + (let [st (cljs/empty-state) + l (latch 2 done)] + (cljs/eval st '(ns bar.core2261a + (:require [foo.core2261a :refer-macros [cake]])) + {:ns 'cljs.user + :eval node-eval + :context :expr + :load (fn [{:keys [macros]} cb] + (if macros + (cb {:lang :clj + :source "(ns foo.core2261a) (defmacro cake [] `(->X))"}) + (cb {:lang :clj + :source "(ns foo.core2261a) (defrecord X [])"})))} + (fn [{:keys [error]}] + (is (nil? error)) + (cljs/eval-str st "(pr-str (cake))" nil + {:ns 'bar.core2261a + :eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= "#foo.core2261a.X{}" value)) + (inc! l))))) + (cljs/eval st '(ns bar.core2261b + (:require [foo.core2261b :refer-macros [cake]])) + {:ns 'cljs.user + :eval node-eval + :context :expr + :load (fn [{:keys [macros]} cb] + (if macros + (cb {:lang :clj + :source "(ns foo.core2261b) (defmacro cake [] `(X.))"}) + (cb {:lang :clj + :source "(ns foo.core2261b) (defrecord X [])"})))} + (fn [{:keys [error]}] + (is (nil? error)) + (cljs/eval-str st "(pr-str (cake))" nil + {:ns 'bar.core2261b + :eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= "#foo.core2261b.X{}" value)) + (inc! l)))))))) + +(deftest test-cljs-2266 + (async done + (let [st (cljs/empty-state) + l (latch 1 done)] + (cljs.js/eval-str st "(require 'clojure.x)" nil + {:eval node-eval + :load (fn [{:keys [name macros]} cb] + (cb (when (and (= name 'cljs.x) + (not macros)) + {:lang :clj + :source "(ns cljs.x)"})))} + (fn [{:keys [error]}] + (is (nil? error)) + (inc! l)))))) + +(deftest test-cljs-2303 + (async done + (let [st (cljs/empty-state) + load (fn [{:keys [name macros]} cb] + (cb (when (and (= name 'cljs.x) + (not macros)) + {:lang :clj + :source "(ns cljs.x)"}))) + l (latch 1 done)] + (cljs.js/eval-str st "(require 'clojure.x)" nil + {:eval node-eval + :load load} + (fn [{:keys [error]}] + (is (nil? error)) + (cljs.js/eval-str st "(require 'clojure.x)" nil + {:eval node-eval + :load load} + (fn [{:keys [error]}] + (is (nil? error)) + (inc! l)))))))) + +(deftest test-cljs-2354 + (async done + (let [st (cljs/empty-state) + load (fn [{:keys [name macros]} cb] + (cb (when (and (= name 'cljs.x) + (not macros)) + {:lang :clj + :source "(ns cljs.x)"}))) + l (latch 1 done)] + (cljs.js/compile-str st "(require 'clojure.x)" nil + {:load load} + (fn [{:keys [error value] :as m}] + (is (nil? error)) + (is (re-find #"goog\.require\('cljs.x'\)" value)) + (inc! l)))))) + +(deftest test-cljs-2356 + (async done + (let [st (cljs/empty-state) + load (fn [{:keys [name macros]} cb] + (cb (cond + (= name 'circular.a) + {:lang :clj + :source "(ns circular.a (:require circular.b))"} + + (= name 'circular.b) + {:lang :clj + :source "(ns circular.b (:require circular.a))"}))) + l (latch 2 done)] + (binding [ana/*cljs-dep-set* (with-meta #{} {:dep-path []})] + (cljs.js/compile-str st "(ns circular.a (:require circular.b))" nil + {:load load} + (fn [{:keys [error value] :as m}] + (is (some? error)) + (is (= "Circular dependency detected circular.a -> circular.b -> circular.a" + (.-message error))) + (inc! l)))) + (binding [ana/*cljs-dep-set* (with-meta #{} {:dep-path []})] + (cljs.js/eval-str st "(ns circular.a (:require circular.b))" nil + {:load load + :eval node-eval} + (fn [{:keys [error value] :as m}] + (is (some? error)) + (is (= "Circular dependency detected circular.a -> circular.b -> circular.a" + (.-message error))) + (inc! l))))))) + +(deftest test-self-host-self-require + (async done + (let [st (cljs/empty-state) + l (latch 1 done) + load (fn [{:keys [name macros]} cb] + (cb {:lang :clj + :source "(ns foo.core)"}))] + (binding [ana/*cljs-dep-set* (with-meta #{} {:dep-path []})] + (cljs.js/eval-str st "(ns foo.core)" nil + {:eval node-eval} + (fn [{:keys [error value] :as m}] + (is (nil? error)) + (cljs.js/eval-str st "(require 'foo.core :reload)" nil + {:load load + :eval node-eval + :def-emits-var true + :ns 'foo.core} + (fn [{:keys [error value] :as m}] + (is (nil? error)) + (inc! l))))))))) + +(deftest test-cljs-2367 + (async done + (let [st (cljs/empty-state) + l (latch 2 done)] + (cljs.js/eval st + '(require (quote foo-2367.core)) + {:context :expr + :def-emits-var true + :eval node-eval + :load (fn [_ cb] + (cb {:lang :clj + :source "(ns foo-2367.core) (def b (def a 3))"}))} + (fn [{:keys [error value]}] + (is (nil? error)) + (cljs.js/eval st + 'foo-2367.core/b + {:context :expr + :eval node-eval} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 3 value)) + (inc! l))))) + (cljs.js/eval st + '(require-macros (quote bar-2367.core)) + {:context :expr + :def-emits-var true + :eval node-eval + :load (fn [_ cb] + (cb {:lang :clj + :source "(ns bar-2367.core) (def b (def a 4)) (defmacro c [] b)"}))} + (fn [{:keys [error value]}] + (is (nil? error)) + (cljs.js/eval st + '(bar-2367.core/c) + {:context :expr + :eval node-eval} + (fn [{:keys [error value]}] + (is (nil? error)) + (is (= 4 value)) + (inc! l)))))))) + +(deftest test-mapping-stacktrace + (async done + (let [l (latch 1 done)] + (testing "it should correctly map from canonical representation (smoke test)." + (let [st (cljs/empty-state)] + (cljs/eval-str st + "(ns cljs.user (:require foo.bar :reload))" + 'cljs.user + {:source-map true + :ns 'cljs.user + :target :nodejs + :eval node-eval + :load (fn [{:keys [name]} cb] + (cb (when (= name 'foo.bar) + {:lang :clj + :source "(ns foo.bar)\n(defn broken-first [] (ffirst 0))" + :file "foo/bar.cljs"})))} + (fn [{:keys [ns value error file]}] + (let [sms (:source-maps @st)] + (is (= [{:function "broken-first" + :file "foo/bar.cljs" + :line 2 + :column 7}] + (st/mapped-stacktrace + [{:file "foo/bar.js" + :function "broken-first" + :line 2 + :column 0}] + sms))) + (inc! l))))))))) + +(deftest test-mapping-stacktrace-with-underscore + (async done + (let [l (latch 1 done)] + (testing "it should correctly map when file names contain underscore" + (let [st (cljs/empty-state)] + (cljs/eval-str st + "(ns cljs.user (:require foo.with-underscore :reload))" + 'cljs.user + {:source-map true + :ns 'cljs.user + :target :nodejs + :eval node-eval + :load (fn [{:keys [name]} cb] + (cb (when (= name 'foo.with-underscore) + {:lang :clj + :source "(ns foo.with-underscore)\n(defn broken-first [] (ffirst 0))" + :file "foo/with_underscore.cljs"})))} + (fn [{:keys [ns value error file]}] + (let [sms (:source-maps @st)] + (is (= [{:function "broken-first" + :file "foo/with_underscore.cljs" + :line 2 + :column 7}] + (st/mapped-stacktrace + [{:file "foo/with_underscore.js" + :function "broken-first" + :line 2 + :column 0}] + sms))) + (inc! l))))))))) + +(deftest test-append-source-map-with-nil-name + (async done + (let [ + l (latch 1 done)] + (testing "it should correctly use cljs-{js/Date as number} when name to cljs.js/eval-str is nil" + (let [st (cljs/empty-state)] + (cljs/eval-str st + "(ns cljs.user (:require foo.bar :reload))" + nil + {:source-map true + :ns 'cljs.user + :target :nodejs + :eval node-eval + :load (fn [{:keys [name]} cb] + (cb (when (= name 'foo.bar) + {:lang :clj + :source "(ns foo.bar)" + :file "foo/bar.cljs"})))} + (fn [{:keys [ns value error file]}] + (let [cljs-timestamp? #(let [[c t] (string/split % "-")] + (and (= "cljs" c) (not (js/isNaN (js/parseInt t))))) + sms (:source-maps @st)] + (is (some cljs-timestamp? (keys sms))) + (inc! l))))))))) + +(deftest test-cljs-2991 + (async done + (let [l (latch 1 done)] + (let [st (cljs/empty-state)] + (cljs/eval-str st + "(js-obj)" + nil + {:ns 'cljs.user + :target :nodejs + :eval node-eval} + (fn [{:keys [value]}] + (is (object? value)) + (is (empty? (js-keys value))) + (inc! l))))))) + +(deftest test-cljs-3129 + (async done + (let [l (latch 1 done)] + (let [st (cljs/empty-state)] + (cljs/eval-str st + "(ns cljs.user (:require-macros foo-3129-1.core))" + nil + {:eval node-eval + :load (fn [_ cb] (cb {:lang :clj :source "(ns foo-3129-1.core) (defmacro add [a b] `(+ ~a ~b))"}))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(foo-3129-1.core/add 1)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? value)) + (is (= "Wrong number of args (1) passed to foo-3129-1.core$macros/add" + (ex-message (ex-cause (ex-cause error))))) + (inc! l)))))) + (let [st (cljs/empty-state)] + (cljs/eval-str st + "(ns cljs.user (:require-macros foo-3129-2.core))" + nil + {:eval node-eval + :load (fn [_ cb] (cb {:lang :clj :source "(ns foo-3129-2.core) (defmacro add [a b] `(+ ~a ~b))"}))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(foo-3129-2.core/add 1 2 3)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? value)) + (is (= "Wrong number of args (3) passed to foo-3129-2.core$macros/add" + (ex-message (ex-cause (ex-cause error))))) + (inc! l)))))) + (let [st (cljs/empty-state)] + (cljs/eval-str st + "(ns cljs.user (:require-macros foo-3129-3.core))" + nil + {:eval node-eval + :load (fn [_ cb] (cb {:lang :clj :source "(ns foo-3129-3.core) (defmacro when [test & body])"}))} + (fn [{:keys [value error]}] + (is (nil? error)) + (cljs/eval-str st + "(foo-3129-3.core/when)" + nil + {:eval node-eval + :context :expr} + (fn [{:keys [error value]}] + (is (nil? value)) + (is (= "Wrong number of args (0) passed to foo-3129-3.core$macros/when" + (ex-message (ex-cause (ex-cause error))))) + (inc! l))))))))) + +(deftest test-cljs-3287 + (async done + (let [st (cljs/empty-state) + l (latch 2 done)] + (cljs/eval-str st + "(throw (js/Error. \"eval error\"))" + nil + {:ns 'cljs.user + :target :nodejs + :eval node-eval} + (fn [{:keys [error]}] + (is (some? error)) + (inc! l))) + (cljs/eval st + '(throw (js/Error. "eval error")) + {:ns 'cljs.user + :target :nodejs + :eval node-eval} + (fn [{:keys [error]}] + (is (some? error)) + (inc! l)))))) + +(deftest test-cljs-3288 + (async done + (let [st (cljs/empty-state) + l (latch 2 done) + load (fn [_ cb] (js/setTimeout #(cb {:lang :js :source ""}) 0))] + (cljs/eval st + '(require 'bootstrap-test.js-source) + {:ns 'cljs.user + :target :nodejs + :eval node-eval + :load load} + (fn [{:as res :keys [error]}] + (is (nil? error)) + (inc! l))) + (cljs/eval-str st + "(require 'bootstrap-test.js-source)" + nil + {:ns 'cljs.user + :target :nodejs + :eval node-eval + :load load} + (fn [{:as res :keys [error]}] + (is (nil? error)) + (inc! l)))))) + +(defn -main [& args] + (run-tests)) + +(set! *main-cli-fn* -main) + +(comment + ) diff --git a/src/test/self/self_parity/auxiliary.cljs b/src/test/self/self_parity/auxiliary.cljs new file mode 100644 index 0000000000..6d8707c8f1 --- /dev/null +++ b/src/test/self/self_parity/auxiliary.cljs @@ -0,0 +1,147 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns ^{:doc "This auxiliary namespace only exists to cause + the libs listed here to be dumped into the compiler output + directory where they can be loaded on demand when running + the compiler tests in bootstrap mode."} + self-parity.auxiliary + (:require + goog.Disposable + goog.Promise + goog.Throttle + goog.Timer + goog.Uri + goog.color + goog.color.Hsl + goog.color.Hsv + goog.color.Rgb + goog.color.alpha + goog.color.names + goog.crypt + goog.crypt.Aes + goog.crypt.Arc4 + goog.crypt.BlobHasher + goog.crypt.Cbc + goog.crypt.Hash + goog.crypt.Hmac + goog.crypt.Md5 + goog.crypt.Sha1 + goog.crypt.Sha2 + goog.crypt.Sha224 + goog.crypt.Sha256 + goog.crypt.Sha2_64bit + goog.crypt.Sha512 + goog.crypt.Sha512_256 + goog.crypt.base64 + goog.crypt.baseN + goog.crypt.hash32 + goog.crypt.hashTester + goog.crypt.pbkdf2 + goog.date.Date + goog.date.DateLike + goog.date.DateRange + goog.date.DateTime + goog.date.Interval + goog.date.UtcDateTime + goog.date.duration + goog.date.month + goog.date.relative.TimeDeltaFormatter + goog.date.relative.Unit + goog.date.weekDay + goog.format + goog.format.EmailAddress + goog.format.HtmlPrettyPrinter + goog.format.InternationalizedEmailAddress + goog.format.JsonPrettyPrinter + goog.i18n.BidiFormatter + goog.i18n.CharListDecompressor + goog.i18n.CharPickerData + goog.i18n.DateTimeFormat + goog.i18n.DateTimeParse + goog.i18n.GraphemeBreak + goog.i18n.MessageFormat + goog.i18n.NumberFormat + goog.i18n.TimeZone + goog.i18n.bidi + goog.i18n.bidi.Dir + goog.i18n.bidi.Format + goog.i18n.collation + goog.i18n.currency + goog.i18n.mime + goog.i18n.ordinalRules + goog.i18n.pluralRules + goog.i18n.uChar + goog.i18n.uChar.LocalNameFetcher + goog.i18n.uChar.RemoteNameFetcher + goog.i18n.uCharNames + goog.iter + goog.iter.Iterable + goog.iter.Iterator + goog.json + goog.json.NativeJsonProcessor + goog.json.Replacer + goog.json.Reviver + goog.json.Serializer + goog.json.hybrid + goog.locale + goog.locale.TimeZoneFingerprint + goog.locale.defaultLocaleNameConstants + goog.locale.timeZoneDetection + goog.math + goog.math.AffineTransform + goog.math.Bezier + goog.math.Box + goog.math.Coordinate + goog.math.Coordinate3 + goog.math.ExponentialBackoff + goog.math.Integer + goog.math.Line + goog.math.Long + goog.math.Matrix + goog.math.Path + goog.math.Path.Segment + goog.math.Range + goog.math.RangeSet + goog.math.Rect + goog.math.Size + goog.math.Vec2 + goog.math.Vec3 + goog.math.interpolator.Linear1 + goog.math.interpolator.Pchip1 + goog.math.interpolator.Spline1 + goog.math.paths + goog.math.tdma + goog.spell.SpellCheck + goog.string + goog.string.Const + goog.string.StringBuffer + goog.string.Unicode + goog.string.format + goog.string.newlines + goog.string.newlines.Line + goog.structs + goog.structs.AvlTree + goog.structs.CircularBuffer + goog.structs.Heap + goog.structs.InversionMap + goog.structs.LinkedMap + goog.structs.Map + goog.structs.Node + goog.structs.Pool + goog.structs.PriorityPool + goog.structs.PriorityQueue + goog.structs.QuadTree + goog.structs.QuadTree.Node + goog.structs.QuadTree.Point + goog.structs.Queue + goog.structs.Set + goog.structs.SimplePool + goog.structs.StringSet + goog.structs.TreeNode + goog.structs.Trie)) diff --git a/src/test/self/self_parity/setup.clj b/src/test/self/self_parity/setup.clj new file mode 100644 index 0000000000..b29c1ed102 --- /dev/null +++ b/src/test/self/self_parity/setup.clj @@ -0,0 +1,47 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns self-parity.setup + ^{:doc "Sets up the filesystem, priming the output directory + with needed source files so that the self-hosted compiler + being executed within Node has various dependency sources + available (without the benefit of being able to load resources + from a classpath)."} + (:require + [clojure.java.io :as io])) + +(def out-path (io/file "builds" "out-self-parity")) + +(defn copy-source + [source-resource-name] + (let [target-file (io/file out-path source-resource-name)] + (io/make-parents target-file) + (io/copy (io/input-stream (io/resource source-resource-name)) target-file))) + +(def test-check-source-resource-names + ["clojure/test/check.cljc" + "clojure/test/check/random.clj" + "clojure/test/check/random.cljs" + "clojure/test/check/rose_tree.cljc" + "clojure/test/check/clojure_test.cljc" + "clojure/test/check/clojure_test/assertions.cljc" + "clojure/test/check/clojure_test/assertions/cljs.cljc" + "clojure/test/check/results.cljc" + "clojure/test/check/impl.cljc" + "clojure/test/check/properties.cljc" + "clojure/test/check/random/longs.cljs" + "clojure/test/check/random/doubles.cljs" + "clojure/test/check/random/longs/bit_count_impl.cljs" + "clojure/test/check/generators.cljc"]) + +(def source-resource-names + (into ["clojure/template.clj"] + test-check-source-resource-names)) + +(defn -main [] + (run! copy-source source-resource-names)) diff --git a/src/test/self/self_parity/test.cljs b/src/test/self/self_parity/test.cljs new file mode 100644 index 0000000000..9b2a7b1e19 --- /dev/null +++ b/src/test/self/self_parity/test.cljs @@ -0,0 +1,381 @@ +;; Copyright (c) Rich Hickey. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this distribution. +;; By using this software in any fashion, you are agreeing to be bound by +;; the terms of this license. +;; You must not remove this notice, or any other, from this software. + +(ns + ^{:doc "Builds and runs the ClojureScript compiler test suite + in self-host mode, ensuring parity of bootstrapped ClojureScript + with JVM based ClojureScript. + + This involves dynamically loading the test suite files at runtime, + excercising that they can be compiled by the bootstrapped + ClojureScript compiler, and also running the resulting tests."} + self-parity.test + (:require [clojure.string :as string] + [cljs.compiler :as comp] + [cljs.nodejs :as nodejs] + [cljs.js :as cljs] + [cljs.tools.reader :as reader] + [cljs.stacktrace :as st] + [goog.object :as gobj] + [self-parity.auxiliary])) + +(def out-dir "builds/out-self-parity") + +(def src-paths [out-dir + "src/main/cljs" + "src/main/clojure" + "src/test/cljs"]) + +(defn require* + [name reload] + (let [ret (js/CLOSURE_IMPORT_SCRIPT + (if goog/debugLoader_ + (.getPathFromDeps_ goog/debugLoader_ name) + (gobj/get (.. js/goog -dependencies_ -nameToPath) name)))] + ;; handle requires from Closure Library goog.modules + (if (.isInModuleLoader_ js/goog) + (.getInternal_ (.. js/goog -module) name) + ret))) + +(defn init-runtime + "Initializes the runtime so that we can use the cljs.user + namespace and so that Google Closure is set up to work + properly with :optimizations :none." + [] + (set! (.-user js/cljs) #js {}) + ;; monkey-patch isProvided_ to avoid useless warnings + (js* "goog.isProvided_ = function(x) { return false; };") + ;; monkey-patch goog.require, skip all the loaded checks + (set! (.-require js/goog) require*) + ;; setup printing + (nodejs/enable-util-print!) + ;; redef goog.require to track loaded libs + (set! *loaded-libs* #{"cljs.core"}) + (set! (.-require js/goog) + (fn [name reload] + (when (or (not (contains? *loaded-libs* name)) reload) + (set! *loaded-libs* (conj (or *loaded-libs* #{}) name)) + (require* name reload))))) + +;; Node file reading fns + +(def fs (nodejs/require "fs")) + +(defn node-read-file + "Accepts a filename to read and a callback. Upon success, invokes + callback with the source. Otherwise invokes the callback with nil." + [filename cb] + (.readFile fs filename "utf-8" + (fn [err source] + (cb (when-not err + source))))) + +(defn node-read-file-sync + "Accepts a filename to read. Upon success, returns the source. + Otherwise returns nil." + [filename] + (.readFileSync fs filename "utf-8")) + +;; Facilities for loading Closure deps + +(defn closure-index + "Builds an index of Closure files. Similar to + cljs.js-deps/goog-dependencies*" + [] + (let [paths-to-provides + (map (fn [[_ path provides]] + [path (map second + (re-seq #"'(.*?)'" provides))]) + (re-seq #"\ngoog\.addDependency\('(.*)', \[(.*?)\].*" + (node-read-file-sync (str out-dir "/goog/deps.js"))))] + (into {} + (for [[path provides] paths-to-provides + provide provides] + [(symbol provide) (str out-dir "/goog/" (second (re-find #"(.*)\.js$" path)))])))) + +(def closure-index-mem (memoize closure-index)) + +(defn load-goog + "Loads a Google Closure implementation source file." + [name cb] + (if-let [goog-path (get (closure-index-mem) name)] + (if-let [source (node-read-file-sync (str goog-path ".js"))] + (cb {:source source + :lang :js}) + (cb nil)) + (cb nil))) + +;; Facilities for loading files + +(defn- filename->lang + "Converts a filename to a lang keyword by inspecting the file + extension." + [filename] + (if (string/ends-with? filename ".js") + :js + :clj)) + +(defn replace-extension + "Replaces the extension on a file." + [filename new-extension] + (string/replace filename #".clj[sc]?$" new-extension)) + +(defn parse-edn + "Parses edn source to Clojure data." + [edn-source] + (reader/read-string edn-source)) + +(defn- read-some + "Reads the first filename in a sequence of supplied filenames, + using a supplied read-file-fn, calling back upon first successful + read, otherwise calling back with nil. Before calling back, first + attempts to read AOT artifacts (JavaScript and cache edn)." + [[filename & more-filenames] macros read-file-fn cb] + (if filename + (read-file-fn + filename + (fn [source] + (if source + (let [source-cb-value {:lang (filename->lang filename) + :file filename + :source source}] + (if (and (not macros) + (or (string/ends-with? filename ".cljs") + (string/ends-with? filename ".cljc"))) + (read-file-fn + (replace-extension filename ".js") + (fn [javascript-source] + (if javascript-source + (read-file-fn + (str filename ".cache.edn") + (fn [cache-edn] + (if cache-edn + (cb {:lang :js + :source javascript-source + :cache (parse-edn cache-edn)}) + (cb source-cb-value)))) + (cb source-cb-value)))) + (cb source-cb-value))) + (read-some more-filenames macros read-file-fn cb)))) + (cb nil))) + +(defn filenames-to-try + "Produces a sequence of filenames to try reading, in the + order they should be tried." + [src-paths macros path] + (let [extensions (if macros + [".clj" ".cljc"] + [".cljs" ".cljc" ".js"])] + (for [extension extensions + src-path src-paths] + (str src-path "/" path extension)))) + +(defn skip-load? + "Indicates namespaces that we either don't need to load, + shouldn't load, or cannot load (owing to unresolved + technical issues)." + [name macros] + ((if macros + #{'cljs.core + 'cljs.repl} + #{'goog.object + 'goog.string + 'goog.string.StringBuffer + 'goog.array + 'cljs.core + 'cljs.env + 'cljs.tagged-literals + 'cljs.tools.reader + 'clojure.walk}) name)) + +(defn make-load-fn + "Makes a load function that will read from a sequence of src-paths + using a supplied read-file-fn. It returns a cljs.js-compatible + *load-fn*. + Read-file-fn is a 2-arity function (fn [filename source-cb] ...) where + source-cb is itself a function (fn [source] ...) that needs to be called + with the source of the library (as string)." + [src-paths read-file-fn] + (fn [{:keys [name macros path]} cb] + (if-not (skip-load? name macros) + (if (re-matches #"^goog/.*" path) + (load-goog name cb) + (read-some (filenames-to-try src-paths macros path) macros read-file-fn cb)) + (cb {:source "" + :lang :js})))) + +;; Facilities for evaluating JavaScript + +(def vm (nodejs/require "vm")) + +(defn node-eval + "Evaluates JavaScript in node." + [{:keys [name source]}] + (if-not js/COMPILED + (.runInThisContext vm source (str (munge name) ".js")) + (js/eval source))) + +;; Facilities for driving cljs.js + +(def st (cljs/empty-state)) + +(def load-fn (make-load-fn src-paths node-read-file)) + +(defn eval-form + "Evaluates a supplied form in a given namespace, + calling back with the evaluation result." + [st ns form cb] + (cljs/eval st + form + {:ns ns + :context :expr + :load load-fn + :eval node-eval + :source-map true + :verbose false} + cb)) + +;; Error handler + +(defn- handle-error + [error sms] + (loop [error error] + (let [message (if (instance? ExceptionInfo error) + (ex-message error) + (.-message error)) + parsed-stacktrace (st/parse-stacktrace {} + (.-stack error) + {:ua-product :nodejs} + {})] + (println message) + (print (st/mapped-stacktrace-str parsed-stacktrace sms)) + (when-some [cause (.-cause error)] + (print "caused by: ") + (recur cause))))) + +;; Test suite runner + +(defn run-tests + "Runs the tests." + [] + ;; Ideally we'd just load test_runner.cljs, but a few namespace tests + ;; don't yet run in bootstrapped ClojureScript. These are commented + ;; out below and can be uncommented as fixed. + (eval-form st 'cljs.user + '(ns parity.core + (:require [cljs.test :refer-macros [run-tests]] + [cljs.eval-test] + [cljs.primitives-test] + [cljs.destructuring-test] + [cljs.new-new-test] + [cljs.printing-test] + [cljs.seqs-test] + [cljs.collections-test] + [cljs.hashing-test] + [cljs.core-test :as core-test] + [cljs.reader-test] + [cljs.binding-test] + [cljs.parse-test] + #_[cljs.ns-test] + [clojure.set-test] + [clojure.string-test] + [clojure.data-test] + [clojure.datafy-test] + [clojure.edn] + [clojure.math-test] + [clojure.walk-test] + [cljs.macro-test] + [cljs.letfn-test] + [foo.ns-shadow-test] + [cljs.top-level] + [cljs.reducers-test] + [cljs.keyword-test] + [cljs.import-test] + [cljs.ns-test.foo] + [cljs.pprint] + [cljs.pprint-test] + [cljs.spec-test] + [cljs.specials-test] + [cljs.spec.test-test] + [cljs.clojure-alias-test] + [cljs.hash-map-test] + [cljs.map-entry-test] + [cljs.set-equiv-test] + [cljs.syntax-quote-test] + [cljs.other-functions-test] + [cljs.predicates-test] + [cljs.test-test] + [static.core-test] + [cljs.recur-test] + [cljs.array-access-test] + [cljs.inference-test] + [cljs.walk-test] + [cljs.repl-test] + [cljs.extend-to-native-test])) + (fn [{:keys [value error]}] + (if error + (handle-error error (:source-maps @st)) + (eval-form st 'parity.core + '(run-tests + 'cljs.eval-test + 'cljs.primitives-test + 'cljs.destructuring-test + 'cljs.new-new-test + 'cljs.printing-test + 'cljs.seqs-test + 'cljs.collections-test + 'cljs.hashing-test + 'cljs.core-test + 'cljs.reader-test + 'cljs.parse-test + 'clojure.set-test + 'clojure.string-test + 'clojure.data-test + 'clojure.datafy-test + 'clojure.edn + 'clojure.math-test + 'clojure.walk-test + 'cljs.letfn-test + 'cljs.reducers-test + 'cljs.binding-test + 'cljs.macro-test + 'cljs.top-level + 'cljs.keyword-test + #_'cljs.ns-test + 'cljs.ns-test.foo + 'foo.ns-shadow-test + 'cljs.import-test + 'cljs.pprint + 'cljs.pprint-test + 'cljs.spec-test + 'cljs.specials-test + 'cljs.spec.test-test + 'cljs.clojure-alias-test + 'cljs.hash-map-test + 'cljs.map-entry-test + 'cljs.set-equiv-test + 'cljs.syntax-quote-test + 'cljs.other-functions-test + 'cljs.predicates-test + 'cljs.test-test + 'static.core-test + 'cljs.recur-test + 'cljs.array-access-test + 'cljs.inference-test + 'cljs.walk-test + 'cljs.repl-test + 'cljs.extend-to-native-test) + (fn [{:keys [value error]}] + (when error + (handle-error error (:source-maps @st))))))))) + +(defn -main [& args] + (init-runtime) + (run-tests)) + +(set! *main-cli-fn* -main) diff --git a/test/clj/cljs/analyzer_tests.clj b/test/clj/cljs/analyzer_tests.clj deleted file mode 100644 index f7fe775c58..0000000000 --- a/test/clj/cljs/analyzer_tests.clj +++ /dev/null @@ -1,228 +0,0 @@ -(ns cljs.analyzer-tests - (:require [clojure.java.io :as io] - [cljs.analyzer :as a] - [cljs.env :as e]) - (:use clojure.test)) - -;;****************************************************************************** -;; cljs-warnings tests -;;****************************************************************************** - -(def warning-forms - {:undeclared-var (let [v (gensym)] `(~v 1 2 3)) - :fn-arity '(do (defn x [a b] (+ a b)) - (x 1 2 3 4))}) - -(defn warn-count [form] - (let [counter (atom 0) - tracker (fn [warning-type env & [extra]] - (when (warning-type a/*cljs-warnings*) - (swap! counter inc)))] - (a/with-warning-handlers [tracker] - (a/analyze (a/empty-env) form)) - @counter)) - -(deftest no-warn - (is (every? zero? (map (fn [[name form]] (a/no-warn (warn-count form))) warning-forms)))) - -(deftest all-warn - (is (every? #(= 1 %) (map (fn [[name form]] (a/all-warn (warn-count form))) warning-forms)))) - -;; ============================================================================= -;; NS parsing - -(def ns-env (assoc-in (a/empty-env) [:ns [:name]] 'cljs.user)) - -(deftest spec-validation - (is (.startsWith - (try - (a/analyze ns-env '(ns foo.bar (:require {:foo :bar}))) - (catch Exception e - (.getMessage e))) - "Only [lib.ns & options] and lib.ns specs supported in :require / :require-macros")) - (is (.startsWith - (try - (a/analyze ns-env '(ns foo.bar (:require [:foo :bar]))) - (catch Exception e - (.getMessage e))) - "Library name must be specified as a symbol in :require / :require-macros")) - (is (.startsWith - (try - (a/analyze ns-env '(ns foo.bar (:require [baz.woz :as woz :refer [] :plop]))) - (catch Exception e - (.getMessage e))) - "Only :as alias and :refer (names) options supported in :require")) - (is (.startsWith - (try - (a/analyze ns-env '(ns foo.bar (:require [baz.woz :as woz :refer [] :plop true]))) - (catch Exception e - (.getMessage e))) - "Only :as and :refer options supported in :require / :require-macros")) - (is (.startsWith - (try - (a/analyze ns-env '(ns foo.bar (:require [baz.woz :as woz :refer [] :as boz :refer []]))) - (catch Exception e - (.getMessage e))) - "Each of :as and :refer options may only be specified once in :require / :require-macros")) - (is (.startsWith - (try - (a/analyze ns-env '(ns foo.bar (:refer-clojure :refer []))) - (catch Exception e - (.getMessage e))) - "Only [:refer-clojure :exclude (names)] form supported")) - (is (.startsWith - (try - (a/analyze ns-env '(ns foo.bar (:use [baz.woz :exclude []]))) - (catch Exception e - (.getMessage e))) - "Only [lib.ns :only (names)] specs supported in :use / :use-macros")) - (is (.startsWith - (try - (a/analyze ns-env '(ns foo.bar (:require [baz.woz :as []]))) - (catch Exception e - (.getMessage e))) - ":as must be followed by a symbol in :require / :require-macros")) - (is (.startsWith - (try - (a/analyze ns-env '(ns foo.bar (:require [baz.woz :as woz] [noz.goz :as woz]))) - (catch Exception e - (.getMessage e))) - ":as alias must be unique")) - (is (.startsWith - (try - (a/analyze ns-env '(ns foo.bar (:unless []))) - (catch Exception e - (.getMessage e))) - "Only :refer-clojure, :require, :require-macros, :use and :use-macros libspecs supported")) - (is (.startsWith - (try - (a/analyze ns-env '(ns foo.bar (:require baz.woz) (:require noz.goz))) - (catch Exception e - (.getMessage e))) - "Only one "))) - -;; ============================================================================= -;; Inference tests - -(def test-cenv (atom {})) -(def test-env (assoc-in (a/empty-env) [:ns :name] 'cljs.core)) - -(e/with-compiler-env test-cenv - (a/analyze-file (io/file "src/cljs/cljs/core.cljs"))) - -(deftest basic-inference - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '1))) - 'number)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '"foo"))) - 'string)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(make-array 10)))) - 'array)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(js-obj)))) - 'object)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '[]))) - 'cljs.core/IVector)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '{}))) - 'cljs.core/IMap)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '#{}))) - 'cljs.core/ISet)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env ()))) - 'cljs.core/IList)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(fn [x] x)))) - 'function))) - -(deftest if-inference - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(if x "foo" 1)))) - '#{number string}))) - -(deftest fn-inference - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env - '(let [x (fn ([a] 1) ([a b] "foo") ([a b & r] ()))] - (x :one))))) - 'number)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env - '(let [x (fn ([a] 1) ([a b] "foo") ([a b & r] ()))] - (x :one :two))))) - 'string)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env - '(let [x (fn ([a] 1) ([a b] "foo") ([a b & r] ()))] - (x :one :two :three))))) - 'cljs.core/IList))) - -(deftest lib-inference - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(+ 1 2)))) - 'number)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(alength (array))))) - 'number)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(aclone (array))))) - 'array)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(count [1 2 3])))) - 'number)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(into-array [1 2 3])))) - 'array)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(js-obj)))) - 'object)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(-conj [] 1)))) - 'clj)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(conj [] 1)))) - 'clj)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(assoc nil :foo :bar)))) - 'clj)) - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(dissoc {:foo :bar} :foo)))) - '#{clj clj-nil}))) - -(deftest test-always-true-if - (is (= (e/with-compiler-env test-cenv - (:tag (a/analyze test-env '(if 1 2 "foo")))) - 'number))) - -;; will only work if the previous test works -(deftest test-count - (is (= (cljs.env/with-compiler-env test-cenv - (:tag (a/analyze test-env '(count [])))) - 'number))) - -(deftest test-numeric - (is (= (cljs.env/with-compiler-env test-cenv - (:tag (a/analyze test-env '(dec x)))) - 'number)) - (is (= (cljs.env/with-compiler-env test-cenv - (:tag (a/analyze test-env '(int x)))) - 'number)) - (is (= (cljs.env/with-compiler-env test-cenv - (:tag (a/analyze test-env '(unchecked-int x)))) - 'number)) - (is (= (cljs.env/with-compiler-env test-cenv - (:tag (a/analyze test-env '(mod x y)))) - 'number)) - (is (= (cljs.env/with-compiler-env test-cenv - (:tag (a/analyze test-env '(quot x y)))) - 'number)) - (is (= (cljs.env/with-compiler-env test-cenv - (:tag (a/analyze test-env '(rem x y)))) - 'number)) - (is (= (cljs.env/with-compiler-env test-cenv - (:tag (a/analyze test-env '(bit-count n)))) - 'number))) diff --git a/test/clj/cljs/closure_tests.clj b/test/clj/cljs/closure_tests.clj deleted file mode 100644 index d28090860e..0000000000 --- a/test/clj/cljs/closure_tests.clj +++ /dev/null @@ -1,21 +0,0 @@ -(ns cljs.closure-tests - (:use cljs.closure) - (:use clojure.test)) - -(deftest test-make-preamble - (testing "no options" - (is (= "" (make-preamble {})))) - (testing "nodejs" - (testing "with default hashbang" - (is (= "#!/usr/bin/env node\n" (make-preamble {:target :nodejs})))) - (testing "with custom hashbang" - (is (= "#!/bin/env node\n" (make-preamble {:target :nodejs - :hashbang "/bin/env node"})))) - (testing "with preamble" - (is (= "#!/usr/bin/env node\nvar preamble1 = require(\"preamble1\");\n" - (make-preamble {:target :nodejs - :preamble ["cljs/preamble1.js"]}))))) - (testing "preamble" - (is (= "var preamble1 = require(\"preamble1\");var preamble2 = require(\"preamble2\");\n" - (make-preamble {:preamble ["cljs/preamble1.js" - "cljs/preamble2.js"]}))))) diff --git a/test/clj/cljs/compiler_tests.clj b/test/clj/cljs/compiler_tests.clj deleted file mode 100644 index c92bab66b1..0000000000 --- a/test/clj/cljs/compiler_tests.clj +++ /dev/null @@ -1,2 +0,0 @@ -(ns cljs.compiler-tests - (:use clojure.test)) diff --git a/test/clj/cljs/repl_tests.clj b/test/clj/cljs/repl_tests.clj deleted file mode 100644 index 44f1394b68..0000000000 --- a/test/clj/cljs/repl_tests.clj +++ /dev/null @@ -1,24 +0,0 @@ -(ns cljs.repl-tests - (:require [clojure.java.io :as io] - [cljs.analyzer :as ana] - [cljs.env :as env] - [cljs.repl :as repl] - [cljs.repl.rhino :as rhino]) - (:use clojure.test)) - -#_(deftest file-info - (let [repl-env (rhino/repl-env) - compiler-env (env/default-compiler-env) - repl-env (assoc repl-env ::env/compiler compiler-env)] - (env/with-compiler-env compiler-env - (binding [ana/*cljs-ns* 'cljs.user] - (repl/-setup repl-env))) - (let [assoc-info (get-in @compiler-env [:cljs.analyzer/namespaces 'cljs.core :defs 'assoc]) - {:keys [file line]} assoc-info] - - (is assoc-info) - (is (number? line)) - (is file) - (and file - (is (io/resource file)))))) - diff --git a/test/cljs/baz.cljs b/test/cljs/baz.cljs deleted file mode 100644 index a5c1dde590..0000000000 --- a/test/cljs/baz.cljs +++ /dev/null @@ -1,3 +0,0 @@ -(ns baz) - -(defn f [x] x) diff --git a/test/cljs/cljs/binding_test.cljs b/test/cljs/cljs/binding_test.cljs deleted file mode 100644 index 34e63e3543..0000000000 --- a/test/cljs/cljs/binding_test.cljs +++ /dev/null @@ -1,12 +0,0 @@ -(ns cljs.binding-test - (:require [cljs.binding-test-other-ns :as o])) - -(defn test-binding [] - (binding [o/*foo* 2] - (assert (= o/*foo* 2))) - (assert (= o/*foo* 1))) - -(defn test-with-redefs [] - (with-redefs [o/bar 2] - (assert (= o/bar 2))) - (assert (= o/bar 10))) diff --git a/test/cljs/cljs/binding_test_other_ns.cljs b/test/cljs/cljs/binding_test_other_ns.cljs deleted file mode 100644 index 806582a8e4..0000000000 --- a/test/cljs/cljs/binding_test_other_ns.cljs +++ /dev/null @@ -1,5 +0,0 @@ -(ns cljs.binding-test-other-ns) - -(def ^:dynamic *foo* 1) - -(def bar 10) diff --git a/test/cljs/cljs/core_test.cljs b/test/cljs/cljs/core_test.cljs deleted file mode 100644 index 88f0dbfd11..0000000000 --- a/test/cljs/cljs/core_test.cljs +++ /dev/null @@ -1,2155 +0,0 @@ -(ns cljs.core-test - (:require [clojure.string :as s])) - -(defn test-stuff [] - ;; js primitives - (let [keys #(vec (js-keys %))] - (assert (= [] (keys (js-obj)) (keys (apply js-obj [])))) - (assert (= ["x"] (keys (js-obj "x" "y")) (keys (apply js-obj ["x" "y"]))))) - - ;; -equiv - (assert (= 1)) - (assert (= 1 1)) - (assert (= 1 1 1)) - (assert (= 1 1 1 1)) - (assert (not (= 1 2))) - (assert (not (= 1 2 1))) - (assert (not (= 1 1 2))) - (assert (not (= 1 1 2 1))) - (assert (not (= 1 1 1 2))) - - ;; arithmetic - (assert (= (+) 0)) - (assert (= (apply + []) 0)) - (assert (= (+ 1) 1)) - (assert (= (apply + [1]) 1)) - (assert (= (+ 1 1) 2)) - (assert (= (apply + [1 1]) 2)) - (assert (= (+ 1 2 3) 6)) - (assert (= (apply + [1 2 3]) 6)) - - (assert (= (- 1) -1)) - (assert (= (apply - [1]) -1)) - (assert (= (- 1 1) 0)) - (assert (= (apply - [1 1]) 0)) - (assert (= (- 3 2 1) 0)) - (assert (= (apply - [3 2 1]) 0)) - - (assert (= (*) 1)) - (assert (= (apply * []) 1)) - (assert (= (* 2) 2)) - (assert (= (apply * [2]) 2)) - (assert (= (* 2 3) 6)) - (assert (= (apply * [2 3]) 6)) - - (assert (= (/ 2) 0.5)) - (assert (= (apply / [2]) 0.5)) - (assert (= (/ 6 2) 3)) - (assert (= (apply / [6 2]) 3)) - (assert (= (/ 6 3 2) 1)) - (assert (= (apply / [6 3 2]) 1)) - - (assert (= (< 1) true)) - (assert (= (apply < [1]) true)) - (assert (= (< 1 2) true)) - (assert (= (apply < [1 2]) true)) - (assert (= (< 1 1) false)) - (assert (= (apply < [1 1]) false)) - (assert (= (< 2 1) false)) - (assert (= (apply < [2 1]) false)) - (assert (= (< 1 2 3) true)) - (assert (= (apply < [1 2 3]) true)) - (assert (= (< 1 1 3) false)) - (assert (= (apply < [1 1 3]) false)) - (assert (= (< 3 1 1) false)) - (assert (= (apply < [3 1 1]) false)) - - (assert (= (<= 1) true)) - (assert (= (apply <= [1]) true)) - (assert (= (<= 1 1) true)) - (assert (= (apply <= [1 1]) true)) - (assert (= (<= 1 2) true)) - (assert (= (apply <= [1 2]) true)) - (assert (= (<= 2 1) false)) - (assert (= (apply <= [2 1]) false)) - (assert (= (<= 1 2 3) true)) - (assert (= (apply <= [1 2 3]) true)) - (assert (= (<= 1 1 3) true)) - (assert (= (apply <= [1 1 3]) true)) - (assert (= (<= 3 1 1) false)) - (assert (= (apply <= [3 1 1]) false)) - - (assert (= (> 1) true)) - (assert (= (apply > [1]) true)) - (assert (= (> 2 1) true)) - (assert (= (apply > [2 1]) true)) - (assert (= (> 1 1) false)) - (assert (= (apply > [1 1]) false)) - (assert (= (> 1 2) false)) - (assert (= (apply > [1 2]) false)) - (assert (= (> 3 2 1) true)) - (assert (= (apply > [3 2 1]) true)) - (assert (= (> 3 1 1) false)) - (assert (= (apply > [3 1 1]) false)) - (assert (= (> 1 1 3) false)) - (assert (= (apply > [1 1 3]) false)) - - (assert (= (>= 1) true)) - (assert (= (apply >= [1]) true)) - (assert (= (>= 2 1) true)) - (assert (= (apply >= [2 1]) true)) - (assert (= (>= 1 1) true)) - (assert (= (apply >= [1 1]) true)) - (assert (= (>= 1 2) false)) - (assert (= (apply >= [1 2]) false)) - (assert (= (>= 3 2 1) true)) - (assert (= (apply >= [3 2 1]) true)) - (assert (= (>= 3 1 1) true)) - (assert (= (apply >= [3 1 1]) true)) - (assert (= (>= 3 1 2) false)) - (assert (= (apply >= [3 1 2]) false)) - (assert (= (>= 1 1 3) false)) - (assert (= (apply >= [1 1 3]) false)) - - (assert (= (dec 1) 0)) - (assert (= (apply dec [1]) 0)) - (assert (= (inc 0) 1)) - (assert (= (apply inc [0]) 1)) - - (assert (= (zero? 0) true)) - (assert (= (apply zero? [0]) true)) - (assert (= (zero? 1) false)) - (assert (= (apply zero? [1]) false)) - (assert (= (zero? -11) false)) - (assert (= (apply zero? [-11]) false)) - (assert (= (pos? 0) false)) - (assert (= (apply pos? [0]) false)) - (assert (= (pos? 1) true)) - (assert (= (apply pos? [1]) true)) - (assert (= (pos? -1) false)) - (assert (= (apply pos? [-1]) false)) - (assert (= (neg? -1) true)) - (assert (= (apply neg? [-1]) true)) - - (assert (= (max 1) 1)) - (assert (= (apply max [1]) 1)) - (assert (= (max 1 2) 2)) - (assert (= (apply max [1 2]) 2)) - (assert (= (max 2 1) 2)) - (assert (= (apply max [2 1]) 2)) - (assert (= (max 1 2 3) 3)) - (assert (= (apply max [1 2 3]) 3)) - (assert (= (max 1 3 2) 3)) - (assert (= (apply max [1 3 2]) 3)) - - (assert (= (min 1) 1)) - (assert (= (apply min [1]) 1)) - (assert (= (min 1 2) 1)) - (assert (= (apply min [1 2]) 1)) - (assert (= (min 2 1) 1)) - (assert (= (apply min [2 1]) 1)) - (assert (= (min 1 2 3) 1)) - (assert (= (apply min [1 2 3]) 1)) - (assert (= (min 2 1 3) 1)) - (assert (= (apply min [3 1 3]) 1)) - - (assert (= (mod 4 2) 0)) - (assert (= (apply mod [4 2]) 0)) - (assert (= (mod 3 2) 1)) - (assert (= (apply mod [3 2]) 1)) - (assert (= (mod -2 5) 3)) - - (assert (= [4 3 2 1 0] (loop [i 0 j ()] - (if (< i 5) - (recur (inc i) (conj j (fn [] i))) - (map #(%) j))))) - - (assert (= [[1 1] [1 2] [1 3] [2 1] [2 2] [2 3]] - (map #(%) (for [i [1 2] j [1 2 3]] (fn [] [i j]))))) - - (assert (integer? 0)) - (assert (integer? 42)) - (assert (integer? -42)) - (assert (not (integer? ""))) - (assert (not (integer? 1e308))) - (assert (not (integer? js/Infinity))) - (assert (not (integer? (- js/Infinity)))) - (assert (not (integer? js/NaN))) - - (assert (= 42 (int 42.5))) - (assert (integer? (int 42.5))) - - (assert (= 42 (long 42.5))) - (assert (integer? (long 42.5))) - - (assert (= -1 (int -1.5))) - (assert (= -9 (long -9.8))) - - (assert (= 2 (:b {:a 1 :b 2}))) - (assert (= 2 ('b '{:a 1 b 2}))) - (assert (= 2 ({:a 1 :b 2} :b))) - (assert (= 2 ({1 1 2 2} 2))) - (assert (= 2 (:a {:b 1} 2))) - (assert (= 2 (:a {} 2))) - (assert (= 2 ({:b 1} :a 2))) - (assert (= 2 ({} :a 2))) - (assert (= nil (:a {}))) - (assert (= nil (:a ""))) - (assert (= 2 (:a "" 2))) - (assert (= 2 (#{1 2 3} 2))) - (assert (zero? (hash (aget (js-obj) "foo")))) - - (assert (= 1 (apply :a '[{:a 1 a 2}]))) - (assert (= 1 (apply 'a '[{a 1 :b 2}]))) - (assert (= 1 (apply {:a 1} [:a]))) - (assert (= 2 (apply {:a 1} [:b 2]))) - - ; See - ; https://github.com/clojure/tools.reader#differences-from-lispreaderjava - ; about why these tests won't pass. Not clear if we should change the reader - ; or the test - ; (assert (= "baz" (name 'foo/bar/baz))) - ; (assert (= "foo/bar" (namespace 'foo/bar/baz))) - ; (assert (= "baz" (name :foo/bar/baz))) - ;(assert (= "foo/bar" (namespace :foo/bar/baz))) - (assert (nil? (namespace '/))) - (assert (= "/" (name '/))) - (assert (= "keyword" (name :keyword))) - ;;TODO: These next two tests need Clojure 1.5 - ;(assert (= "foo" (namespace 'foo//))) - ;(assert (= "/" (name 'foo//))) - - ; str - (assert (= ":hello" (str :hello))) - (assert (= "hello" (str 'hello))) - (assert (= "hello:world" (str "hello" :world))) - (assert (= ":helloworld" (str :hello 'world))) - - ; symbol - (assert (= 'a (symbol 'a))) - - ; keyword - (assert (= :a (keyword "a"))) - (assert (= :a (keyword 'a))) - (assert (= :a/b (keyword 'a 'b))) - (assert (= :a (keyword :a))) - - (assert (= {:a :b} (get {[1 2 3] {:a :b}, 4 5} [1 2 3]))) - (assert (= :a (nth [:a :b :c :d] 0))) - (assert (= :a (nth [:a :b :c :d] 0.1)) ) - (assert (not (= {:a :b :c nil} {:a :b :d nil}))) - (assert (= (list 3 2 1) [3 2 1])) - (assert (= [3 2 1] (seq (array 3 2 1)))) - (assert (= 9 (reduce + (next (seq (array 1 2 3 4)))))) - (assert (= () (rest nil))) - (assert (= nil (seq (array)))) - (assert (= nil (seq ""))) - (assert (= nil (seq []))) - (assert (= nil (seq {}))) - (assert (= () (rest ()))) - (assert (= () (rest [1]))) - (assert (= () (rest (array 1)))) - (assert (= {"x" "y"} (meta ^{"x" "y"} []))) - (assert (= {:a :b} (dissoc {:a :b :c :d} :c))) - (assert (= (hash-map :foo 5) - (assoc (cljs.core.ObjMap. nil (array) (js-obj)) :foo 5))) - - (assert (= "\"asdf\" \"asdf\"" (pr-str "asdf" "asdf"))) - (assert (= "[1 true {:a 2, :b #\"x\\\"y\"} #js [3 4]]" - (pr-str [1 true {:a 2 :b #"x\"y"} (array 3 4)]))) - - (assert (= "\"asdf\"\n" (prn-str "asdf"))) - (assert (= "[1 true {:a 2, :b 42} #js [3 4]]\n" - (prn-str [1 true {:a 2 :b 42} (array 3 4)]))) - - (assert (= "asdf" (print-str "asdf"))) - (assert (= "asdf\n" (println-str "asdf"))) - - (assert (= "" (pr-str))) - (assert (= "\n" (prn-str))) - (assert (= "12" (with-out-str (print 1) (print 2)))) - (assert (= "12" (with-out-str (*print-fn* 1) (*print-fn* 2)))) - - ;;this fails in v8 - why? - ;(assert (= "symbol\"'string" (pr-str (str 'symbol \" \' "string")))) - - (assert (not (= "one" "two"))) - (assert (= 3 (count "abc"))) - (assert (= 4 (count (array 1 2 3 4)))) - (assert (= "c" (nth "abc" 2))) - (assert (= "quux" (nth "abc" 3 "quux"))) - (assert (= 1 (nth (array 1 2 3 4) 0))) - (assert (= "val" (nth (array 1 2 3 4) 4 "val"))) - (assert (= "b" (get "abc" 1))) - (assert (= "harriet" (get "abcd" 4 "harriet"))) - (assert (= 4 (get (array 1 2 3 4) 3))) - (assert (= "zot" (get (array 1 2 3 4) 4 "zot"))) - (assert (= 10 (reduce + (array 1 2 3 4)))) - (assert (= 20 (reduce + 10 (array 1 2 3 4)))) - (assert (= "cabd" (let [jumble (fn [a b] (str (apply str (reverse (str a))) b))] - (reduce jumble "abcd")))) - (assert (= "cafrogbd" (let [jumble (fn [a b] (str (apply str (reverse (str a))) b))] - (reduce jumble "frog" "abcd")))) - (assert (= [0 0 1 0 1] - [(bit-and 1 0) - (bit-and 0 0) - (bit-and 1 1) - (bit-and 42 1) - (bit-and 41 1)])) - (assert (= [1 0 1 43 41] - [(bit-or 1 0) - (bit-or 0 0) - (bit-or 1 1) - (bit-or 42 1) - (bit-or 41 1)])) - (assert (= [1 0 0 42 40] - [(bit-and-not 1 0) - (bit-and-not 0 0) - (bit-and-not 1 1) - (bit-and-not 42 1) - (bit-and-not 41 1)])) - (assert (= [0 2 968 16649 0] - [(bit-clear 1 0) - (bit-clear 2 0) - (bit-clear 1000 5) - (bit-clear 16713 6) - (bit-clear 1024 10)])) - (assert (= [0 0 992 18761 0] - [(bit-flip 1 0) - (bit-flip 2 1) - (bit-flip 1000 3) - (bit-flip 16713 11) - (bit-flip 1024 10)])) - (assert (= [-2 -3 999 -16714 -1025] - [(bit-not 1) - (bit-not 2) - (bit-not -1000) - (bit-not 16713) - (bit-not 1024)])) - (assert (= [1 2 1000 18761 1024] - [(bit-set 1 0) - (bit-set 2 1) - (bit-set 1000 3) - (bit-set 16713 11) - (bit-set 1024 10)])) - (assert (= [true true true false true] - [(bit-test 1 0) - (bit-test 2 1) - (bit-test 1000 3) - (bit-test 16713 11) - (bit-test 1024 10)])) - (assert (= [true false true false false false] - [(true? true) - (true? false) - (false? false) - (false? true) - (true? js/undefined) - (false? js/undefined)])) - ;; apply - (assert (= 0 (apply + nil))) - (assert (= 0 (apply + (list)))) - (assert (= 1 (apply + (list 1)))) - (assert (= 3 (apply + 1 (list 2)))) - (assert (= 7 (apply + 1 2 (list 4)))) - (assert (= 15 (apply + 1 2 4 (list 8)))) - (assert (= 31 (apply + 1 2 4 8 (list 16)))) - (assert (= 63 (apply + 1 2 4 8 16 (list 32)))) - (assert (= 127 (apply + 1 2 4 8 16 (list 32 64)))) - (assert (= 4950 (apply + (take 100 (iterate inc 0))))) - (assert (= () (apply list []))) - (assert (= [1 2 3] (apply list [1 2 3]))) - (assert (= 6 (apply apply [+ [1 2 3]]))) - ;; apply with infinite sequence - (assert (= 3 (apply (fn [& args] - (+ (nth args 0) - (nth args 1) - (nth args 2))) - (iterate inc 0)))) - (assert (= [0 1 2 3 4] (take 5 (apply (fn [& m] m) (iterate inc 0))))) - (assert (= [1 2 3 4 5] (take 5 (apply (fn [x & m] m) (iterate inc 0))))) - (assert (= [2 3 4 5 6] (take 5 (apply (fn [x y & m] m) (iterate inc 0))))) - (assert (= [3 4 5 6 7] (take 5 (apply (fn [x y z & m] m) (iterate inc 0))))) - (assert (= [4 5 6 7 8] (take 5 (apply (fn [x y z a & m] m) (iterate inc 0))))) - (assert (= [5 6 7 8 9] (take 5 (apply (fn [x y z a b & m] m) (iterate inc 0))))) - ;; apply arity tests - (let [single-arity-non-variadic (fn [x y z] [z y x]) - multiple-arity-non-variadic (fn ([x] x) ([x y] [y x]) ([x y z] [z y x])) - single-arity-variadic-fixedargs (fn [x y & more] [more y x]) - single-arity-variadic-nofixedargs (fn [& more] more) - multiple-arity-variadic (fn ([x] x) ([x y] [y x]) ([x y & more] [more y x]))] - (assert (= [3 2 1] (apply single-arity-non-variadic [1 2 3]))) - (assert (= [3 2 1] (apply single-arity-non-variadic 1 [2 3]))) - (assert (= [3 2 1] (apply single-arity-non-variadic 1 2 [3]))) - (assert (= 42 (apply multiple-arity-non-variadic [42]))) - (assert (= [2 1] (apply multiple-arity-non-variadic [1 2]))) - (assert (= [2 1] (apply multiple-arity-non-variadic 1 [2]))) - (assert (= [3 2 1] (apply multiple-arity-non-variadic [1 2 3]))) - (assert (= [3 2 1] (apply multiple-arity-non-variadic 1 [2 3]))) - (assert (= [3 2 1] (apply multiple-arity-non-variadic 1 2 [3]))) - (assert (= [[3 4 5] 2 1] (apply single-arity-variadic-fixedargs [1 2 3 4 5]))) - (assert (= [[3 4 5] 2 1] (apply single-arity-variadic-fixedargs 1 [2 3 4 5]))) - (assert (= [[3 4 5] 2 1] (apply single-arity-variadic-fixedargs 1 2 [3 4 5]))) - (assert (= [[3 4 5] 2 1] (apply single-arity-variadic-fixedargs 1 2 3 [4 5]))) - (assert (= [[3 4 5] 2 1] (apply single-arity-variadic-fixedargs 1 2 3 4 [5]))) - (assert (= [3 4 5] (take 3 (first (apply single-arity-variadic-fixedargs (iterate inc 1)))))) - (assert (= [2 1] (rest (apply single-arity-variadic-fixedargs (iterate inc 1))))) - (assert (= [1 2 3 4 5] (apply single-arity-variadic-nofixedargs [1 2 3 4 5]))) - (assert (= [1 2 3 4 5] (apply single-arity-variadic-nofixedargs 1 [2 3 4 5]))) - (assert (= [1 2 3 4 5] (apply single-arity-variadic-nofixedargs 1 2 [3 4 5]))) - (assert (= [1 2 3 4 5] (apply single-arity-variadic-nofixedargs 1 2 3 [4 5]))) - (assert (= [1 2 3 4 5] (apply single-arity-variadic-nofixedargs 1 2 3 4 [5]))) - (assert (= [1 2 3 4 5] (take 5 (apply single-arity-variadic-nofixedargs (iterate inc 1))))) - (assert (= 42 (apply multiple-arity-variadic [42]))) - (assert (= [2 1] (apply multiple-arity-variadic [1 2]))) - (assert (= [2 1] (apply multiple-arity-variadic 1 [2]))) - (assert (= [[3 4 5] 2 1] (apply multiple-arity-variadic [1 2 3 4 5]))) - (assert (= [[3 4 5] 2 1] (apply multiple-arity-variadic 1 [2 3 4 5]))) - (assert (= [[3 4 5] 2 1] (apply multiple-arity-variadic 1 2 [3 4 5]))) - (assert (= [[3 4 5] 2 1] (apply multiple-arity-variadic 1 2 3 [4 5]))) - (assert (= [[3 4 5] 2 1] (apply multiple-arity-variadic 1 2 3 4 [5]))) - (assert (= [3 4 5] (take 3 (first (apply multiple-arity-variadic (iterate inc 1)))))) - (assert (= [2 1] (rest (apply multiple-arity-variadic (iterate inc 1)))))) - - ;; CLJS-383 - (let [f1 (fn f1 ([] 0) ([a] 1) ([a b] 2) ([a b c & more] 3)) - f2 (fn f2 ([x] :foo) ([x y & more] (apply f1 y more)))] - (assert (= 1 (f2 1 2)))) - (let [f (fn ([]) ([a & more] more))] - (assert (nil? (f :foo)))) - (assert (nil? (array-seq (array 1) 1))) - - ;; Functions with metadata - (let [f (fn [x] (* x 2)) - m {:foo "bar"} - mf (with-meta f m)] - (assert (nil? (meta f))) - (assert (fn? mf)) - (assert (= 4 (mf 2))) - (assert (= 4 (apply mf [2]))) - (assert (= (meta mf) m))) - - (let [a (atom 0)] - (assert (= 0 (deref a))) - (assert (= 1 (swap! a inc))) - (assert (= false (compare-and-set! a 0 42))) - (assert (= true (compare-and-set! a 1 7))) - (assert (nil? (meta a))) - (assert (nil? (get-validator a)))) - (let [a (atom 0)] - (assert (= 1 (swap! a + 1))) - (assert (= 4 (swap! a + 1 2))) - (assert (= 10 (swap! a + 1 2 3))) - (assert (= 20 (swap! a + 1 2 3 4)))) - (let [a (atom [1] :validator coll? :meta {:a 1})] - (assert (= coll? (get-validator a))) - (assert (= {:a 1} (meta a))) - (alter-meta! a assoc :b 2) - (assert (= {:a 1 :b 2} (meta a)))) - (assert (nil? (empty nil))) - (let [e-lazy-seq (empty (with-meta (lazy-seq (cons :a nil)) {:b :c}))] - (assert (seq? e-lazy-seq)) - (assert (empty? e-lazy-seq)) - (assert (= {:b :c} (meta e-lazy-seq)))) - (let [e-list (empty '^{:b :c} (1 2 3))] - (assert (seq? e-list)) - (assert (empty? e-list))) - (let [e-elist (empty '^{:b :c} ())] - (assert (seq? e-elist)) - (assert (empty? e-elist)) - (assert (= :c (get (meta e-elist) :b)))) - (let [e-cons (empty (with-meta (cons :a nil) {:b :c}))] - (assert (seq? e-cons)) - (assert (empty? e-cons)) - (assert (= {:b :c} (meta e-cons)))) - (let [e-vec (empty ^{:b :c} [:a :d :g])] - (assert (vector? e-vec)) - (assert (empty? e-vec)) - (assert (= {:b :c} (meta e-vec)))) - (let [e-omap (empty ^{:b :c} {:a :d :g :h})] - (assert (map? e-omap)) - (assert (empty? e-omap)) - (assert (= {:b :c} (meta e-omap)))) - (let [e-hmap (empty ^{:b :c} {[1 2] :d :g :h})] - (assert (map? e-hmap)) - (assert (empty? e-hmap)) - (assert (= {:b :c} (meta e-hmap)))) - - (let [a (atom nil)] - (assert (= 1 (try 1))) - (assert (= 2 (try 1 (throw (js/Error.)) (catch js/Error e 2)))) - (assert (= 2 (try 1 (throw (js/Error.)) (catch js/Error e 1 2)))) - (assert (= 2 (try 1 (throw (js/Error.)) (catch js/Error e 2) (catch :default e 3)))) - (assert (= 3 (try 1 (throw true) (catch js/Error e 2) (catch :default e 3)))) - (assert (= 2 (try 1 (throw 2) (catch js/Error e 3) (catch :default e e)))) - (assert (= 1 (try 1 (finally (reset! a 42))))) - (assert (= 42 (deref a)))) - - (assert (= [3] (nthnext [1 2 3] 2))) - (let [v [1 2 3]] - (assert (= v (for [e v] e))) - (assert (= [[1 1] [2 4] [3 9]] (for [e v :let [m (* e e)]] [e m]))) - (assert (= [1 2] (for [e v :while (< e 3)] e))) - (assert (= [3] (for [e v :when (> e 2)] e))) - (assert (= [[1 1] [2 4]] (for [e v :while (< e 3) :let [m (* e e)]] [e m])))) - (assert (not= 1 2)) - (assert (not (not= 1 1))) - (assert (not (not-empty []))) - (assert (boolean (not-empty [1 2 3]))) - (assert (= "joel" (min-key count "joel" "tom servo" "crooooooooow"))) - (assert (= "crooooooooow" (max-key count "joel" "tom servo" "crooooooooow"))) - (assert (= (partition-all 4 [1 2 3 4 5 6 7 8 9]) - [[1 2 3 4] [5 6 7 8] [9]])) - (assert (= (partition-all 4 2 [1 2 3 4 5 6 7 8 9]) - [[1 2 3 4] [3 4 5 6] [5 6 7 8] [7 8 9] [9]])) - (assert (= [true true] (take-while true? [true true 2 3 4]))) - (assert (= [[true true] [false false false] [true true]] - (partition-by true? [true true false false false true true]))) - (assert (= [0 2 4 6 8 10] (take-nth 2 [0 1 2 3 4 5 6 7 8 9 10]))) - (let [a10 (partial + 10) - a20 (partial + 10 10) - a21 (partial + 10 10 1) - a22 (partial + 10 5 4 3) - a23 (partial + 10 5 4 3 1)] - (assert (= 110 (a10 100))) - (assert (= 120 (a20 100))) - (assert (= 121 (a21 100))) - (assert (= 122 (a22 100))) - (assert (= 123 (a23 100)))) - (let [n2 (comp first rest) - n3 (comp first rest rest) - n4 (comp first rest rest rest) - n5 (comp first rest rest rest rest) - n6 (comp first rest rest rest rest rest)] - (assert (= 2 (n2 [1 2 3 4 5 6 7]))) - (assert (= 3 (n3 [1 2 3 4 5 6 7]))) - (assert (= 4 (n4 [1 2 3 4 5 6 7]))) - (assert (= 5 (n5 [1 2 3 4 5 6 7]))) - (assert (= 6 (n6 [1 2 3 4 5 6 7])))) - (let [sf (some-fn number? keyword? symbol?)] - (assert (sf :foo 1)) - (assert (sf :foo)) - (assert (sf 'bar 1)) - (assert (not (sf [] ())))) - (let [ep (every-pred number? zero?)] - (assert (ep 0 0 0)) - (assert (not (ep 1 2 3 0)))) - (assert ((complement number?) :foo)) - (assert (= [1 [2 3] [1 2 3]] ((juxt first rest seq) [1 2 3]))) - (assert (= 5 (max 1 2 3 4 5))) - (assert (= 5 (max 5 4 3 2 1))) - (assert (= 5.5 (max 1 2 3 4 5 5.5))) - (assert (= 1 (min 5 4 3 2 1))) - (assert (= 1 (min 1 2 3 4 5))) - (assert (= 0.5 (min 5 4 3 0.5 2 1))) - (let [x (array 1 2 3)] - (set! (.-foo x) :hello) - (assert (= (.-foo x) :hello))) - - (assert (set [])) - (assert (= #{} (set []))) - (assert (= #{} (hash-set))) - (assert (identical? cljs.core.PersistentHashSet (type (hash-set)))) - - (assert (= #{"foo"} (set ["foo"]))) - (assert (= #{"foo"} (hash-set "foo"))) - (assert (= #{1 2 3} #{1 3 2})) - (assert (= #{#{1 2 3} [4 5 6] {7 8} 9 10} - #{10 9 [4 5 6] {7 8} #{1 2 3}})) - (assert (not (= #{nil [] {} 0 #{}} #{}))) - (assert (= (count #{nil [] {} 0 #{}}) 5)) - (assert (= (conj #{1} 1) #{1})) - (assert (= (conj #{1} 2) #{2 1})) - (assert (= #{} (-empty #{1 2 3 4}))) - (assert (= (reduce + #{1 2 3 4 5}) 15)) - (assert (= 4 (get #{1 2 3 4} 4))) - (assert (contains? #{1 2 3 4} 4)) - (assert (contains? #{[] nil 0 {} #{}} {})) - (assert (contains? #{[1 2 3]} [1 2 3])) - (assert (not (contains? (-disjoin #{1 2 3} 3) 3))) - (assert (neg? -1)) - (assert (not (neg? 1))) - (assert (neg? -1.765)) - (assert (not (neg? 0))) - (assert (= [true false true false true false true false] - (map integer? - [1 1.00001 0x7e7 [] (- 88 1001991881) :foo 0 "0"]))) - (assert (= [true false true false true false] - (map odd? [1 2 3 4 -1 0]))) - (assert (= [true false true false true true] - (map even? [2 3 4 5 -2 0]))) - (assert (contains? {:a 1 :b 2} :a)) - (assert (not (contains? {:a 1 :b 2} :z))) - (assert (contains? [5 6 7] 1)) - (assert (contains? [5 6 7] 2)) - (assert (not (contains? [5 6 7] 3))) - (assert (contains? (to-array [5 6 7]) 1)) - (assert (contains? (to-array [5 6 7]) 2)) - (assert (not (contains? (to-array [5 6 7]) 3))) - (assert (not (contains? nil 42))) - (assert (contains? "f" 0)) - (assert (not (contains? "f" 55))) - (assert (distinct? 1 2 3)) - (assert (not (distinct? 1 2 3 1))) - - ;; distinct - (assert (= (distinct ()) ())) - (assert (= (distinct '(1)) '(1))) - (assert (= (distinct '(1 2 3 1 1 1)) '(1 2 3))) - (assert (= (distinct [1 1 1 2]) '(1 2))) - (assert (= (distinct [1 2 1 2]) '(1 2))) - (assert (= (distinct "a") ["a"])) - (assert (= (distinct "abcabab") ["a" "b" "c"])) - (assert (= (distinct ["abc" "abc"]) ["abc"])) - (assert (= (distinct [nil nil]) [nil])) - (assert (= (distinct [0.0 0.0]) [0.0])) - (assert (= (distinct ['sym 'sym]) '[sym])) - (assert (= (distinct [:kw :kw]) [:kw])) - (assert (= (distinct [42 42]) [42])) - (assert (= (distinct [[] []]) [[]])) - (assert (= (distinct ['(1 2) '(1 2)]) '[(1 2)])) - (assert (= (distinct [() ()]) [()])) - (assert (= (distinct [[1 2] [1 2]]) [[1 2]])) - (assert (= (distinct [{:a 1 :b 2} {:a 1 :b 2}]) [{:a 1 :b 2}])) - (assert (= (distinct [{} {}]) [{}])) - (assert (= (distinct [#{1 2} #{1 2}]) [#{1 2}])) - (assert (= (distinct [#{} #{}]) [#{}])) - - ;;regexps - (assert (= (str (re-pattern "f(.)o")) (str (js* "/f(.)o/")))) - (assert (= (re-find (re-pattern "foo") "foo bar foo baz foo zot") "foo")) - (assert (= (re-find (re-pattern "f(.)o") "foo bar foo baz foo zot") ["foo" "o"])) - (assert (= (re-matches (re-pattern "foo") "foo") "foo")) - (assert (= (re-matches (re-pattern "foo") "foo bar foo baz foo zot") nil)) - (assert (= (re-matches (re-pattern "foo.*") "foo bar foo baz foo zot") "foo bar foo baz foo zot")) - (assert (= (re-seq (re-pattern "foo") "foo bar foo baz foo zot") (list "foo" "foo" "foo"))) - (assert (= (re-seq (re-pattern "f(.)o") "foo bar foo baz foo zot") (list ["foo" "o"] ["foo" "o"] ["foo" "o"]))) - (assert (= (re-matches (re-pattern "(?i)foo") "Foo") "Foo")) - ; new RegExp("").source => "(?:)" on webkit-family envs, "" elsewhere - (assert (#{"#\"\"" "#\"(?:)\""} (pr-str #""))) - - ;; destructuring - (assert (= [2 1] (let [[a b] [1 2]] [b a]))) - (assert (= #{1 2} (let [[a b] [1 2]] #{a b}))) - (assert (= [1 2] (let [{a :a b :b} {:a 1 :b 2}] [a b]))) - (assert (= [1 2] (let [{:keys [a b]} {:a 1 :b 2}] [a b]))) - (assert (= [1 2 [1 2]] (let [[a b :as v] [1 2]] [a b v]))) - (assert (= [1 42] (let [{:keys [a b] :or {b 42}} {:a 1}] [a b]))) - (assert (= [1 nil] (let [{:keys [a b] :or {c 42}} {:a 1}] [a b]))) - (assert (= [2 1] (let [[a b] '(1 2)] [b a]))) - (assert (= {1 2} (let [[a b] [1 2]] {a b}))) - (assert (= [2 1] (let [[a b] (seq [1 2])] [b a]))) - - ;; update-in - (assert (= {:foo {:bar {:baz 1}}} - (update-in {:foo {:bar {:baz 0}}} [:foo :bar :baz] inc))) - (assert (= {:foo 1 :bar 2 :baz 10} - (update-in {:foo 1 :bar 2 :baz 3} [:baz] + 7))) - (assert (= [{:foo 1, :bar 2} {:foo 1, :bar 3}] - (update-in [{:foo 1 :bar 2}, {:foo 1 :bar 2}] [1 :bar] inc))) - (assert (= [{:foo {:bar 2}} {:foo {:bar 3}}] - (update-in [{:foo {:bar 2}}, {:foo {:bar 2}}] [1 :foo :bar] inc))) - - ;; assoc-in - (assert (= {:foo {:bar {:baz 100}}} - (assoc-in {:foo {:bar {:baz 0}}} [:foo :bar :baz] 100))) - (assert (= {:foo 1 :bar 2 :baz 100} - (assoc-in {:foo 1 :bar 2 :baz 3} [:baz] 100))) - (assert (= [{:foo [{:bar 2} {:baz 3}]} {:foo [{:bar 2} {:baz 100}]}] - (assoc-in [{:foo [{:bar 2} {:baz 3}]}, {:foo [{:bar 2} {:baz 3}]}] - [1 :foo 1 :baz] 100))) - (assert (= [{:foo 1, :bar 2} {:foo 1, :bar 100}] - (assoc-in [{:foo 1 :bar 2}, {:foo 1 :bar 2}] [1 :bar] 100))) - - ;; get-in - (assert (= 1 (get-in {:foo 1 :bar 2} [:foo]))) - (assert (= 2 (get-in {:foo {:bar 2}} [:foo :bar]))) - (assert (= 1 (get-in [{:foo 1}, {:foo 2}] [0 :foo]))) - (assert (= 4 (get-in [{:foo 1 :bar [{:baz 1}, {:buzz 2}]}, {:foo 3 :bar [{:baz 3}, {:buzz 4}]}] - [1 :bar 1 :buzz]))) - - ;; arrays - (let [a (to-array [1 2 3])] - (assert (= [10 20 30] (seq (amap a i ret (* 10 (aget a i)))))) - (assert (= 6 (areduce a i ret 0 (+ ret (aget a i))))) - (assert (= (seq a) (seq (to-array [1 2 3])))) - (assert (= 42 (aset a 0 42))) - (assert (not= (seq a) (seq (to-array [1 2 3])))) - (assert (not= a (aclone a)))) - - (let [a (array (array 1 2 3) (array 4 5 6))] - (assert (= (aget a 0 1) 2)) - (assert (= (apply aget a [0 1]) 2)) - (assert (= (aget a 1 1) 5)) - (assert (= (apply aget a [1 1]) 5)) - (aset a 0 0 "foo") - (assert (= (aget a 0 0) "foo")) - (apply aset a [0 0 "bar"]) - (assert (= (aget a 0 0) "bar"))) - - ;; sort - (assert (= [1 2 3 4 5] (sort [5 3 1 4 2]))) - (assert (= [1 2 3 4 5] (sort < [5 3 1 4 2]))) - (assert (= [5 4 3 2 1] (sort > [5 3 1 4 2]))) - - ;; sort-by - (assert (= ["a" [ 1 2] "foo"] (sort-by count ["foo" "a" [1 2]]))) - (assert (= ["foo" [1 2] "a"] (sort-by count > ["foo" "a" [1 2]]))) - - ;; shuffle - (let [coll [1 2 3 4 5 6 7 8 9 10] - ; while it is technically possible for this test to fail with a false negative, - ; it's _extraordinarily_ unlikely. - shuffles (filter #(not= coll %) (take 100 (iterate shuffle coll)))] - (assert (not (empty? shuffles)))) - - ;; js->clj - (assert (= {"a" 1, "b" 2} (js->clj (js* "{\"a\":1,\"b\":2}")))) - (assert (= {"a" nil} (js->clj (js* "{\"a\":null}")))) - (assert (= {} (js->clj (js* "{}")))) - (assert (= {"a" true, "b" false} (js->clj (js* "{\"a\":true,\"b\":false}")))) - (assert (= {:a 1, :b 2} (js->clj (js* "{\"a\":1,\"b\":2}") :keywordize-keys true))) - (assert (= [[{:a 1, :b 2} {:a 1, :b 2}]] - (js->clj (js* "[[{\"a\":1,\"b\":2}, {\"a\":1,\"b\":2}]]") :keywordize-keys true))) - (assert (= [[{:a 1, :b 2} {:a 1, :b 2}]] - (js->clj [[{:a 1, :b 2} {:a 1, :b 2}]]))) - (assert (= (js->clj nil) nil)) - - ;; clj->js - (assert (= (clj->js 'a) "a")) - (assert (= (clj->js :a) "a")) - (assert (= (clj->js "a") "a")) - (assert (= (clj->js 1) 1)) - (assert (= (clj->js nil) (js* "null"))) - (assert (= (clj->js true) (js* "true"))) - (assert (goog/isArray (clj->js []))) - (assert (goog/isArray (clj->js #{}))) - (assert (goog/isArray (clj->js '()))) - (assert (goog/isObject (clj->js {}))) - (assert (= (aget (clj->js {:a 1}) "a") 1)) - (assert (= (-> (clj->js {:a {:b {{:k :ey} :d}}}) - (aget "a") - (aget "b") - (aget "{:k :ey}")) - "d")) - - ;; last - (assert (= nil (last nil))) - (assert (= 3 (last [1 2 3]))) - - ;; dotimes - (let [s (atom [])] - (dotimes [n 5] - (swap! s conj n)) - (assert (= [0 1 2 3 4] @s))) - - ;; doseq - (let [v [1 2 3 4 5] - s (atom ())] - (doseq [n v] (swap! s conj n)) - (assert (= @s (reverse v)))) - - ;; delay - (let [a (atom 0) - d (delay (swap! a inc))] - (assert (false? (realized? d))) - (assert (zero? @a)) ;; delay hasn't triggered yet - (assert (= 1 @d)) ;; trigger it - (assert (= 1 @a)) ;; make sure side effect has happened - (assert (true? (realized? d))) - (assert (= 1 @d)) ;; body doesn't happen again - (assert (= 1 @a)) ;; atom hasn't changed either - (assert (= (force d) @d)) - (assert (= 1 (force 1)))) ;; you can safely force non-delays - - ;; assoc - (assert (= {1 2 3 4} (assoc {} 1 2 3 4))) - (assert (= {1 2} (assoc {} 1 2))) - (assert (= [42 2] (assoc [1 2] 0 42))) - - ;; dissoc - (assert (= {} (dissoc {1 2 3 4} 1 3))) - (assert (= {1 2} (dissoc {1 2 3 4} 3))) - (assert (nil? (dissoc nil :foo))) - - ;; disj - (assert (= #{1 2 3} (disj #{1 2 3}))) - (assert (= #{1 2} (disj #{1 2 3} 3))) - (assert (= #{1} (disj #{1 2 3} 2 3))) - (assert (nil? (disj nil :foo))) - - ;; memoize - (let [f (memoize (fn [] (rand)))] - (f) - (assert (= (f) (f)))) - - ;; find - (assert (= (find {} :a) nil)) - (assert (= (find {:a 1} :a) [:a 1])) - (assert (= (find {:a 1} :b) nil)) - (assert (= (find {:a 1 :b 2} :a) [:a 1])) - (assert (= (find {:a 1 :b 2} :b) [:b 2])) - (assert (= (find {:a 1 :b 2} :c) nil)) - (assert (= (find {} nil) nil)) - (assert (= (find {:a 1} nil) nil)) - (assert (= (find {:a 1 :b 2} nil) nil)) - (assert (= (find [1 2 3] 0) [0 1])) - - ;; mod,quot,rem - (assert (= (quot 4 2) 2)) - (assert (= (quot 3 2) 1)) - (assert (= (quot 6 4) 1)) - (assert (= (quot 0 5) 0)) - (assert (= (quot 42 5) 8)) - (assert (= (quot 42 -5) -8)) - (assert (= (quot -42 -5) 8)) - (assert (= (quot 9 3) 3)) - (assert (= (quot 9 -3) -3)) - (assert (= (quot -9 3) -3)) - (assert (= (quot 2 -5) 0)) - (assert (= (quot -2 5) 0)) - (assert (= (quot 0 3) 0)) - (assert (= (quot 0 -3) 0)) - - (assert (= (mod 4 2) 0)) - (assert (= (mod 3 2) 1)) - (assert (= (mod 6 4) 2)) - (assert (= (mod 0 5) 0)) - (assert (= (mod 4.5 2.0) 0.5)) - (assert (= (mod 42 5) 2)) - (assert (= (mod 9 3) 0)) - (assert (= (mod 9 -3) 0)) - (assert (= (mod -9 3) 0)) - (assert (= (mod -9 -3) 0)) - (assert (= (mod 0 3) 0)) - (assert (= (mod 3216478362187432 432143214) 120355456)) - - (assert (= (rem 4 2) 0)) - (assert (= (rem 0 5) 0)) - (assert (= (rem 4.5 2.0) 0.5)) - (assert (= (rem 42 5) 2)) - (assert (= (rem 2 5) 2)) - (assert (= (rem 2 -5) 2)) - (assert (= (rem 0 3) 0)) - - ;; range - (assert (= (range 10) (list 0 1 2 3 4 5 6 7 8 9))) - (assert (= (range 10 20) (list 10 11 12 13 14 15 16 17 18 19))) - (assert (= (range 10 20 2) (list 10 12 14 16 18))) - (assert (= (take 20 (range)) (list 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19))) - - ;; group-by - (let [d (group-by second {:a 1 :b 2 :c 1 :d 4 :e 1 :f 2})] - (assert (= 3 (count (get d 1)))) - (assert (= 2 (count (get d 2)))) - (assert (= 1 (count (get d 4))))) - - (assert (= {1 2 3 4 5 6} (merge {1 2} {3 4} {5 6}))) - (assert (= {1 2 3 4} (merge {1 2} {3 4} nil))) - - ;; frequencies - (assert (= {:a 3 :b 2} (frequencies [:a :b :a :b :a]))) - - ;; reductions - (assert (= [1 3 6 10 15] (reductions + [1 2 3 4 5]))) - - ;; keep - (assert (= [1 3 5 7 9] (keep #(if (odd? %) %) [1 2 3 4 5 6 7 8 9 10]))) - (assert (= [2 4 6 8 10] (keep #(if (even? %) %) [1 2 3 4 5 6 7 8 9 10]))) - - ;; keep-indexed - (assert (= [1 3 5 7 9] (keep-indexed #(if (odd? %1) %2) [0 1 2 3 4 5 6 7 8 9 10]))) - (assert (= [2 4 5] (keep-indexed #(if (pos? %2) %1) [-9 0 29 -7 45 3 -8]))) - - ;; map-indexed - (assert (= [[0 :a] [1 :b] [2 :c]] (map-indexed #(vector % %2) [:a :b :c]))) - - ;; merge-with - (assert (= '{"Foo" ("foo" "FOO" "fOo"), "Bar" ("bar" "BAR" "BAr"), "Baz" ["baz"], "Qux" ["qux" "quux"]} - (merge-with concat - {"Foo" ["foo" "FOO"] - "Bar" ["bar" "BAR"] - "Baz" ["baz"]} - {"Foo" ["fOo"] - "Bar" ["BAr"] - "Qux" ["qux" "quux"]}))) - (assert (= {:a 111, :b 102, :c 13} - (merge-with + - {:a 1 :b 2 :c 3} - {:a 10 :c 10} - {:a 100 :b 100}))) - - (assert (= {:a 3, :b 102, :c 13} - (apply merge-with [+ - {:a 1 :b 100} - {:a 1 :b 2 :c 3} - {:a 1 :c 10}]))) - - (assert (= '[a c e] (replace '[a b c d e] [0 2 4]))) - (assert (= [:one :zero :two :zero] - (replace {0 :zero 1 :one 2 :two} '(1 0 2 0)))) - - ;; split-at - (assert (= [[1 2] [3 4 5]] (split-at 2 [1 2 3 4 5]))) - - ;; split-with - (assert (= [[1 2 3] [4 5]] (split-with (partial >= 3) [1 2 3 4 5]))) - - ;; trampoline - (assert (= 10000 (trampoline (fn f [n] (if (>= n 10000) n #(f (inc n)))) 0))) - - ;; vary-meta - (assert (= {:a 1} (meta (vary-meta [] assoc :a 1)))) - (assert (= {:a 1 :b 2} (meta (vary-meta (with-meta [] {:b 2}) assoc :a 1)))) - - ;; hierarchy tests - (derive ::rect ::shape) - (derive ::square ::rect) - - (assert (= #{:cljs.core-test/shape} (parents ::rect))) - (assert (= #{:cljs.core-test/rect :cljs.core-test/shape} (ancestors ::square))) - (assert (= #{:cljs.core-test/rect :cljs.core-test/square} (descendants ::shape))) - (assert (true? (isa? 42 42))) - (assert (true? (isa? ::square ::shape))) - - (derive cljs.core.ObjMap ::collection) - (derive cljs.core.PersistentHashSet ::collection) - (assert (true? (isa? cljs.core.ObjMap ::collection))) - (assert (true? (isa? cljs.core.PersistentHashSet ::collection))) - (assert (false? (isa? cljs.core.IndexedSeq ::collection))) - ;; ?? (isa? String Object) - (assert (true? (isa? [::square ::rect] [::shape ::shape]))) - ;; ?? (ancestors java.util.ArrayList) - - ;; ?? isa? based dispatch tests - - ;; prefer-method test - (defmulti bar (fn [x y] [x y])) - (defmethod bar [::rect ::shape] [x y] :rect-shape) - (defmethod bar [::shape ::rect] [x y] :shape-rect) - - ;;(bar ::rect ::rect) - ;; -> java.lang.IllegalArgumentException: - ;; Multiple methods match dispatch value: - ;; [:cljs.core-test/rect :cljs.core-test/rect] -> [:cljs.core-test/rect :cljs.core-test/shape] - ;; and [:cljs.core-test/shape :cljs.core-test/rect], - ;; and neither is preferred - - (assert (zero? (count (prefers bar)))) - (prefer-method bar [::rect ::shape] [::shape ::rect]) - (assert (= 1 (count (prefers bar)))) - (assert (= :rect-shape (bar ::rect ::rect))) - (assert (= :rect-shape (apply (-get-method bar [::rect ::shape]) [::rect ::shape]))) - - ;; nested data structures tests - (defmulti nested-dispatch (fn [m] (-> m :a :b))) - (defmethod nested-dispatch :c [m] :nested-a) - (defmethod nested-dispatch :default [m] :nested-default) - (assert (= :nested-a (nested-dispatch {:a {:b :c}}))) - - (defmulti nested-dispatch2 ffirst) - (defmethod nested-dispatch2 :a [m] :nested-a) - (defmethod nested-dispatch2 :default [m] :nested-default) - (assert (= :nested-a (nested-dispatch2 [[:a :b]]))) - - ;; general tests - (defmulti foo1 (fn [& args] (first args))) - (defmethod foo1 :a [& args] :a-return) - (defmethod foo1 :default [& args] :default-return) - (assert (= :a-return (foo1 :a))) - (assert (= :default-return (foo1 1))) - - (defmulti area :Shape) - (defn rect [wd ht] {:Shape :Rect :wd wd :ht ht}) - (defn circle [radius] {:Shape :Circle :radius radius}) - (defmethod area :Rect [r] - (* (:wd r) (:ht r))) - (defmethod area :Circle [c] - (* Math/PI (* (:radius c) (:radius c)))) - (defmethod area :default [x] :oops) - (def r (rect 4 13)) - (def c (circle 12)) - (assert (= 52 (area r))) - (assert (= :oops (area {}))) - - ;; remove method tests - (assert (= 2 (count (methods bar)))) - (remove-method bar [::rect ::shape]) - (assert (= 1 (count (methods bar)))) - (remove-all-methods bar) - (assert (zero? (count (methods bar)))) - - ;; test apply - (defmulti apply-multi-test (fn ([_] 0) ([_ _] 0) ([_ _ _] 0))) - (defmethod apply-multi-test 0 - ([x] :one) - ([x y] :two) - ([x y & r] [:three r])) - (assert (= [:three '(2)] (apply apply-multi-test [0 1 2]))) - - ;; custom hierarchy tests - (def my-map-hierarchy (atom (-> (make-hierarchy) - (derive (type (obj-map)) ::map) - (derive (type (array-map)) ::map) - (derive (type (hash-map)) ::map) - (derive (type (sorted-map)) ::map)))) - (defmulti my-map? type :hierarchy my-map-hierarchy) - (defmethod my-map? ::map [_] true) - (defmethod my-map? :default [_] false) - (doseq [m [(obj-map) (array-map) (hash-map) (sorted-map)]] - (assert (my-map? m))) - (doseq [not-m [[] 1 "asdf" :foo]] - (assert (not (my-map? not-m)))) - - ;; Range - (assert (= (range 0 10 3) (list 0 3 6 9))) - (assert (= (count (range 0 10 3)) 4)) - (assert (= (range 0 -10 -3) (list 0 -3 -6 -9))) - (assert (= (count (range 0 -10 -3)) 4)) - (assert (= (range -10 10 3) (list -10 -7 -4 -1 2 5 8))) - (assert (= (count (range -10 10 3)) 7)) - (assert (= (range 0 1 1) (list 0))) - (assert (= (range 0 -3 -1) (list 0 -1 -2))) - (assert (= (range 3 0 -1) (list 3 2 1))) - (assert (= (range 0 10 -1) (list))) - (assert (= (range 0 1 0) (list))) - (assert (= (range 10 0 1) (list))) - (assert (= (range 0 0 0) (list))) - (assert (= (count (range 0 10 -1)) 0)) - (assert (= (count (range 0 1 0)) 0)) - (assert (= (count (range 10 0 1)) 0)) - (assert (= (count (range 0 0 0)) 0)) - (assert (= (take 3 (range 1 0 0)) (list 1 1 1))) - (assert (= (take 3 (range 3 1 0)) (list 3 3 3))) - - ;; PersistentVector - (let [pv (vec (range 97))] - (assert (= (nth pv 96) 96)) - (assert (= (nth pv 97 nil) nil)) - (assert (= (pv 96) 96)) - (assert (nil? (rseq []))) - (assert (= (reverse pv) (rseq pv)))) - - - (let [pv (vec (range 33))] - (assert (= pv - (-> pv - pop - pop - (conj 31) - (conj 32))))) - - (let [stack1 (pop (vec (range 97))) - stack2 (pop stack1)] - (assert (= 95 (peek stack1))) - (assert (= 94 (peek stack2)))) - - ;; CLJS-513 - (let [sentinel (js-obj)] - (assert (identical? sentinel (try ([] 0) (catch js/Error _ sentinel))))) - - ;; subvec - (let [v1 (vec (range 10)) - v2 (vec (range 5)) - s (subvec v1 2 8)] - (assert (= s - (-> v1 - (subvec 2) - (subvec 0 6)) - (->> v1 - (drop 2) - (take 6)))) - (assert (= 6 (count s))) - (assert (= [2 3 4 5 6] (pop s))) - (assert (= 7 (peek s))) - (assert (= [2 3 4 5 6 7 1] - (assoc s 6 1) - (conj s 1))) - (assert (= 27 (reduce + s))) - (assert (= s (vec s))) ; pour into plain vector - (let [m {:x 1}] (assert (= m (meta (with-meta s m))))) - ;; go outside ranges - (assert (= :fail (try (subvec v2 0 6) (catch js/Error e :fail)))) - (assert (= :fail (try (subvec v2 6 10) (catch js/Error e :fail)))) - (assert (= :fail (try (subvec v2 6 10) (catch js/Error e :fail)))) - (assert (= :fail (try (subvec v2 3 6) (catch js/Error e :fail)))) - ;; no layered subvecs - (assert (identical? v1 (.-v (subvec s 1 4)))) - ;; CLJS-513 - (let [sentinel (js-obj) - s (subvec [0 1 2 3] 1 2)] - (assert (identical? sentinel (try (s -1) (catch js/Error _ sentinel)))) - (assert (identical? sentinel (try (s 1) (catch js/Error _ sentinel))))) - ;; CLJS-765 - (let [sv1 (subvec [0 1 2 3] 1 2) - sv2 (subvec [0 1 2 3] 1 1)] - (assert (= (rseq sv1) '(1))) - (assert (nil? (rseq sv2))))) - - ;; TransientVector - (let [v1 (vec (range 15 48)) - v2 (vec (range 40 57)) - v1 (persistent! (assoc! (conj! (pop! (transient v1)) :foo) 0 :quux)) - v2 (persistent! (assoc! (conj! (transient v2) :bar) 0 :quux)) - v (into v1 v2)] - (assert (= v (vec (concat [:quux] (range 16 47) [:foo] - [:quux] (range 41 57) [:bar]))))) - (loop [v (transient []) - xs (range 100)] - (if-let [x (first xs)] - (recur - (condp #(%1 (mod %2 3)) x - #{0 2} (conj! v x) - #{1} (assoc! v (count v) x)) - (next xs)) - (assert (= (vec (range 100)) (persistent! v))))) - - ;; PersistentHashMap & TransientHashMap - (loop [m1 cljs.core.PersistentHashMap.EMPTY - m2 (transient cljs.core.PersistentHashMap.EMPTY) - i 0] - (if (< i 100) - (recur (assoc m1 i i) (assoc! m2 i i) (inc i)) - (let [m2 (persistent! m2)] - (assert (= (count m1) 100)) - (assert (= (count m2) 100)) - (assert (= m1 m2)) - (loop [i 0] - (if (< i 100) - (do (assert (= (m1 i) i)) - (assert (= (m2 i) i)) - (assert (= (get m1 i) i)) - (assert (= (get m2 i) i)) - (assert (contains? m1 i)) - (assert (contains? m2 i)) - (recur (inc i))))) - (assert (= (map vector (range 100) (range 100)) (sort-by first (seq m1)))) - (assert (= (map vector (range 100) (range 100)) (sort-by first (seq m2)))) - (assert (not (contains? (dissoc m1 3) 3)))))) - (let [m (-> (->> (interleave (range 10) (range 10)) - (apply assoc cljs.core.PersistentHashMap.EMPTY)) - (dissoc 3 5 7))] - (assert (= (count m) 7)) - (assert (= m {0 0 1 1 2 2 4 4 6 6 8 8 9 9}))) - (let [m (-> (->> (interleave (range 10) (range 10)) - (apply assoc cljs.core.PersistentHashMap.EMPTY)) - (conj [:foo 1]))] - (assert (= (count m) 11)) - (assert (= m {0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 :foo 1}))) - (let [m (-> (->> (interleave (range 10) (range 10)) - (apply assoc cljs.core.PersistentHashMap.EMPTY) - transient) - (conj! [:foo 1]) - persistent!)] - (assert (= (count m) 11)) - (assert (= m {0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 :foo 1}))) - (let [tm (->> (interleave (range 10) (range 10)) - (apply assoc cljs.core.PersistentHashMap.EMPTY) - transient)] - (loop [tm tm ks [3 5 7]] - (if-let [k (first ks)] - (recur (dissoc! tm k) (next ks)) - (let [m (persistent! tm)] - (assert (= (count m) 7)) - (assert (= m {0 0 1 1 2 2 4 4 6 6 8 8 9 9})))))) - (let [tm (-> (->> (interleave (range 10) (range 10)) - (apply assoc cljs.core.PersistentHashMap.EMPTY)) - (dissoc 3 5 7) - transient)] - (doseq [k [0 1 2 4 6 8 9]] - (assert (= k (get tm k)))) - (let [m (persistent! tm)] - (assert (= 2 (try (dissoc! tm 1) 1 (catch js/Error e 2)))) - (assert (= 2 (try (assoc! tm 10 10) 1 (catch js/Error e 2)))) - (assert (= 2 (try (persistent! tm) 1 (catch js/Error e 2)))) - (assert (= 2 (try (count tm) 1 (catch js/Error e 2)))) - (assert (= m {0 0 1 1 2 2 4 4 6 6 8 8 9 9})))) - (deftype FixedHash [h v] - IHash - (-hash [this] h) - IEquiv - (-equiv [this other] - (and (instance? FixedHash other) (= v (.-v other))))) - (def fixed-hash-foo (FixedHash. 0 :foo)) - (def fixed-hash-bar (FixedHash. 0 :bar)) - (let [m (assoc cljs.core.PersistentHashMap.EMPTY - fixed-hash-foo 1 - fixed-hash-bar 2)] - (assert (= (get m fixed-hash-foo) 1)) - (assert (= (get m fixed-hash-bar) 2)) - (assert (= (count m) 2)) - (let [m (dissoc m fixed-hash-foo)] - (assert (= (get m fixed-hash-bar) 2)) - (assert (not (contains? m fixed-hash-foo))) - (assert (= (count m) 1)))) - (let [m (into cljs.core.PersistentHashMap.EMPTY ; make sure we're testing - (zipmap (range 100) (range 100))) ; the correct map type - m (assoc m fixed-hash-foo 1 fixed-hash-bar 2)] - (assert (= (count m) 102)) - (assert (= (get m fixed-hash-foo) 1)) - (assert (= (get m fixed-hash-bar) 2)) - (let [m (dissoc m 3 5 7 fixed-hash-foo)] - (assert (= (get m fixed-hash-bar) 2)) - (assert (not (contains? m fixed-hash-foo))) - (assert (= (count m) 98)))) - (let [m (into cljs.core.PersistentHashMap.EMPTY ; make sure we're testing - (zipmap (range 100) (range 100))) ; the correct map type - m (transient m) - m (assoc! m fixed-hash-foo 1) - m (assoc! m fixed-hash-bar 2) - m (persistent! m)] - (assert (= (count m) 102)) - (assert (= (get m fixed-hash-foo) 1)) - (assert (= (get m fixed-hash-bar) 2)) - (let [m (dissoc m 3 5 7 fixed-hash-foo)] - (assert (= (get m fixed-hash-bar) 2)) - (assert (not (contains? m fixed-hash-foo))) - (assert (= (count m) 98)))) - - ;; PersistentArrayMap & TransientArrayMap - (def array-map-conversion-threshold - cljs.core.PersistentArrayMap.HASHMAP_THRESHOLD) - (loop [m1 cljs.core.PersistentArrayMap.EMPTY - m2 (transient cljs.core.PersistentArrayMap.EMPTY) - i 0] - (if (< i array-map-conversion-threshold) - (recur (assoc m1 i i) (assoc! m2 i i) (inc i)) - (let [m2 (persistent! m2)] - (assert (= (count m1) array-map-conversion-threshold)) - (assert (= (count m2) array-map-conversion-threshold)) - (assert (= m1 m2)) - (loop [i 0] - (if (< i array-map-conversion-threshold) - (do (assert (= (m1 i) i)) - (assert (= (m2 i) i)) - (assert (= (get m1 i) i)) - (assert (= (get m2 i) i)) - (assert (contains? m1 i)) - (assert (contains? m2 i)) - (recur (inc i))))) - (assert (= (map vector - (range array-map-conversion-threshold) - (range array-map-conversion-threshold)) - (sort-by first (seq m1)))) - (assert (= (map vector - (range array-map-conversion-threshold) - (range array-map-conversion-threshold)) - (sort-by first (seq m2)))) - (assert (not (contains? (dissoc m1 3) 3)))))) - (let [m (-> (->> (interleave (range 10) (range 10)) - (apply assoc cljs.core.PersistentArrayMap.EMPTY)) - (dissoc 3 5 7))] - (assert (= (count m) 7)) - (assert (= m {0 0 1 1 2 2 4 4 6 6 8 8 9 9}))) - (let [m (-> (->> (interleave (range 10) (range 10)) - (apply assoc cljs.core.PersistentArrayMap.EMPTY)) - (conj [:foo 1]))] - (assert (= (count m) 11)) - (assert (= m {0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 :foo 1}))) - (let [m (-> (->> (interleave (range 10) (range 10)) - (apply assoc cljs.core.PersistentArrayMap.EMPTY) - transient) - (conj! [:foo 1]) - persistent!)] - (assert (= (count m) 11)) - (assert (= m {0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 :foo 1}))) - (let [tm (->> (interleave (range 10) (range 10)) - (apply assoc cljs.core.PersistentArrayMap.EMPTY) - transient)] - (loop [tm tm ks [3 5 7]] - (if-let [k (first ks)] - (recur (dissoc! tm k) (next ks)) - (let [m (persistent! tm)] - (assert (= (count m) 7)) - (assert (= m {0 0 1 1 2 2 4 4 6 6 8 8 9 9})))))) - (let [tm (-> (->> (interleave (range 10) (range 10)) - (apply assoc cljs.core.PersistentArrayMap.EMPTY)) - (dissoc 3 5 7) - transient)] - (doseq [k [0 1 2 4 6 8 9]] - (assert (= k (get tm k)))) - (let [m (persistent! tm)] - (assert (= 2 (try (dissoc! tm 1) 1 (catch js/Error e 2)))) - (assert (= 2 (try (assoc! tm 10 10) 1 (catch js/Error e 2)))) - (assert (= 2 (try (persistent! tm) 1 (catch js/Error e 2)))) - (assert (= 2 (try (count tm) 1 (catch js/Error e 2)))) - (assert (= m {0 0 1 1 2 2 4 4 6 6 8 8 9 9})))) - (let [m (apply assoc cljs.core.PersistentArrayMap.EMPTY - (interleave (range (* 2 array-map-conversion-threshold)) - (range (* 2 array-map-conversion-threshold))))] - (assert (= (count m) (* 2 array-map-conversion-threshold))) - (assert (= (m array-map-conversion-threshold) array-map-conversion-threshold)) - (assert (= m (into cljs.core.PersistentHashMap.EMPTY - (map #(vector % %) - (range (* 2 array-map-conversion-threshold))))))) - - ;; literal maps - (loop [m1 {} m2 {} i 0] - (if (< i 100) - (recur (assoc m1 i i) (assoc m2 (str "foo" i) i) (inc i)) - (do (assert (= m1 (into cljs.core.PersistentHashMap.EMPTY - (map vector (range 100) (range 100))))) - (assert (= m2 (into cljs.core.PersistentHashMap.EMPTY - (map vector - (map (partial str "foo") (range 100)) - (range 100))))) - (assert (= (count m1) 100)) - (assert (= (count m2) 100))))) - - ;; CLJS-461: automatic map conversions - (loop [i 0 m (with-meta {} {:foo :bar}) result []] - (if (<= i (+ cljs.core.ObjMap.HASHMAP_THRESHOLD 2)) - (recur (inc i) (assoc m (str i) i) (conj result (meta m))) - (let [n (inc (+ cljs.core.ObjMap.HASHMAP_THRESHOLD 2)) - expected (repeat n {:foo :bar})] - (assert (= result expected))))) - (loop [i 0 m (with-meta {-1 :quux} {:foo :bar}) result []] - (if (<= i (+ cljs.core.PersistentArrayMap.HASHMAP_THRESHOLD 2)) - (recur (inc i) (assoc m i i) (conj result (meta m))) - (let [n (inc (+ cljs.core.PersistentArrayMap.HASHMAP_THRESHOLD 2)) - expected (repeat n {:foo :bar})] - (assert (= result expected))))) - - ;; TransientHashSet - (loop [s (transient #{}) - i 0] - (if (< i 100) - (recur (conj! s i) (inc i)) - (loop [s s i 0] - (if (< i 100) - (if (zero? (mod i 3)) - (recur (disj! s i) (inc i)) - (recur s (inc i))) - (let [s (persistent! s)] - (assert (= s (loop [s #{} xs (remove #(zero? (mod % 3)) (range 100))] - (if-let [x (first xs)] - (recur (conj s x) (next xs)) - s)))) - (assert (= s (set (remove #(zero? (mod % 3)) (range 100)))))))))) - - ;; PersistentTreeMap - (let [m1 (sorted-map) - c2 (comp - compare) - m2 (sorted-map-by c2)] - (assert (identical? cljs.core.PersistentTreeMap (type m1))) - (assert (identical? cljs.core.PersistentTreeMap (type m2))) - (assert (identical? compare (.-comp m1))) - (assert (zero? (count m1))) - (assert (zero? (count m2))) - (assert (nil? (rseq m1))) - (let [m1 (assoc m1 :foo 1 :bar 2 :quux 3) - m2 (assoc m2 :foo 1 :bar 2 :quux 3)] - (assert (= (count m1) 3)) - (assert (= (count m2) 3)) - (assert (= (seq m1) (list [:bar 2] [:foo 1] [:quux 3]))) - (assert (= (seq m2) (list [:quux 3] [:foo 1] [:bar 2]))) - (assert (= (seq m1) (rseq m2))) - (assert (= (seq m2) (rseq m1))) - (assert (= (conj m1 [:wibble 4]) {:foo 1 :bar 2 :quux 3 :wibble 4})) - (assert (= (count (conj m1 [:wibble 4])) 4)) - (assert (= (conj m2 [:wibble 4]) {:foo 1 :bar 2 :quux 3 :wibble 4})) - (assert (= (count (conj m2 [:wibble 4])) 4)) - (assert (= (map key (assoc m1 nil 4)) (list nil :bar :foo :quux))) - (assert (= (map key (assoc m2 nil 4)) (list :quux :foo :bar nil))))) - (let [m (->> [[0 10] [20 30] [10 20] [50 60] [30 40] [40 50]] - (mapcat (partial apply range)) - (mapcat #(list % %)) - (apply sorted-map)) - s1 (map #(vector % %) (range 60)) - s2 (map #(vector % %) (range 59 -1 -1))] - (assert (= (count m) 60)) - (assert (= (seq m) s1)) - (assert (= (rseq m) s2))) - (let [m (sorted-map :foo 1 :bar 2 :quux 3)] - (assert (= (dissoc m :foo) (hash-map :bar 2 :quux 3))) - (assert (= (count (dissoc m :foo)) 2)) - (assert (= (hash m) (hash (hash-map :foo 1 :bar 2 :quux 3)))) - (assert (= (subseq m < :foo) (list [:bar 2]))) - (assert (= (subseq m <= :foo) (list [:bar 2] [:foo 1]))) - (assert (= (subseq m > :foo) (list [:quux 3]))) - (assert (= (subseq m >= :foo) (list [:foo 1] [:quux 3]))) - (assert (= (map #(reduce (fn [_ x] x) %) m) (list 2 1 3))) - (assert (= (map #(reduce (fn [x _] x) 7 %) m) (list 7 7 7)))) - - ;; PersistentTreeSet - (let [s1 (sorted-set) - c2 (comp - compare) - s2 (sorted-set-by c2) - c3 #(compare (quot %1 2) (quot %2 2)) - s3 (sorted-set-by c3) - s4 (sorted-set-by <)] - (assert (identical? cljs.core.PersistentTreeSet (type s1))) - (assert (identical? cljs.core.PersistentTreeSet (type s2))) - (assert (identical? compare (-comparator s1))) - (assert (zero? (count s1))) - (assert (zero? (count s2))) - (assert (nil? (rseq s1))) - (let [s1 (conj s1 1 2 3) - s2 (conj s2 1 2 3) - s3 (conj s3 1 2 3 7 8 9) - s4 (conj s4 1 2 3)] - (assert (= (hash s1) (hash s2))) - (assert (= (hash s1) (hash #{1 2 3}))) - (assert (= (seq s1) (list 1 2 3))) - (assert (= (rseq s1) (list 3 2 1))) - (assert (= (seq s2) (list 3 2 1))) - (assert (= (rseq s2) (list 1 2 3))) - (assert (= (count s1) 3)) - (assert (= (count s2) 3)) - (assert (= (count s3) 4)) - (assert (= (get s3 0) 1)) - (assert (= (subseq s3 > 5) (list 7 8))) - (assert (= (subseq s3 > 6) (list 8))) - (assert (= (subseq s3 >= 6) (list 7 8))) - (assert (= (subseq s3 >= 12) nil)) - (assert (= (subseq s3 < 0) (list))) - (assert (= (subseq s3 < 5) (list 1 2))) - (assert (= (subseq s3 < 6) (list 1 2))) - (assert (= (subseq s3 <= 6) (list 1 2 7))) - (assert (= (subseq s3 >= 2 <= 6) (list 2 7))) - (assert (= (subseq s4 >= 2 < 3) (list 2))) - (let [s1 (disj s1 2) - s2 (disj s2 2)] - (assert (= (seq s1) (list 1 3))) - (assert (= (rseq s1) (list 3 1))) - (assert (= (seq s2) (list 3 1))) - (assert (= (rseq s2) (list 1 3))) - (assert (= (count s1) 2)) - (assert (= (count s2) 2))))) - - ;; defrecord - (defrecord Person [firstname lastname]) - (def fred (Person. "Fred" "Mertz")) - (assert (= (:firstname fred) "Fred")) - (def fred-too (Person. "Fred" "Mertz")) - (assert (= fred fred-too)) - (assert (false? (= fred nil))) - (assert (false? (= nil fred))) - - ;; invalid tests, cannot set meta and extmap directly - David - (def ethel (with-meta (assoc (Person. "Ethel" "Mertz") :husband :fred) - {:married true})) - (assert (= (meta ethel) {:married true})) - (def ethel-too (with-meta (assoc (Person. "Ethel" "Mertz") :husband :fred) - {:married true})) - (assert (= ethel ethel-too)) - - (assert (= (map->Person {:firstname "Fred" :lastname "Mertz"}) fred)) - (assert (= (->Person "Fred" "Mertz") fred)) - - (assert (= (count fred) 2)) - (assert (= (count ethel) 3)) - - (defrecord A []) - (assert (= {:foo 'bar} (meta (with-meta (A.) {:foo 'bar})))) - (assert (= 'bar (:foo (assoc (A.) :foo 'bar)))) - - (defrecord C [a b c]) - (def letters (C. "a" "b" "c")) - (assert (= (set (keys letters)) #{:a :b :c})) - (def more-letters (assoc letters :d "d" :e "e" :f "f")) - (assert (= (set (keys more-letters)) #{:a :b :c :d :e :f})) - (assert (= (set (keys (dissoc more-letters :d))) #{:a :b :c :e :f})) - (assert (= (set (keys (dissoc more-letters :d :e))) #{:a :b :c :f})) - (assert (= (set (keys (dissoc more-letters :d :e :f))) #{:a :b :c})) - - ;; ObjMap - (let [ks (map (partial str "foo") (range 500)) - m (apply obj-map (interleave ks (range 500)))] - (assert (instance? cljs.core.ObjMap m)) - (assert (= 500 (count m))) - (assert (= 123 (m "foo123")))) - - ;; comparator - (assert (= [1 1 2 2 3 5] (seq (.sort (to-array [2 3 1 5 2 1]) (comparator <))))) - (assert (= [5 3 2 2 1 1] (seq (.sort (to-array [2 3 1 5 2 1]) (comparator >))))) - - ;; dot - (let [s "abc"] - (assert (= 3 (.-length s))) - (assert (= 3 (. s -length))) - (assert (= 3 (. (str 138) -length))) - (assert (= 3 (. "abc" -length))) - (assert (= "bc" (.substring s 1))) - (assert (= "bc" (.substring "abc" 1))) - (assert (= "bc" ((memfn substring start) s 1))) - (assert (= "bc" (. s substring 1))) - (assert (= "bc" (. s (substring 1)))) - (assert (= "bc" (. s (substring 1 3)))) - (assert (= "bc" (.substring s 1 3))) - (assert (= "ABC" (. s (toUpperCase)))) - (assert (= "ABC" (. "abc" (toUpperCase)))) - (assert (= "ABC" ((memfn toUpperCase) s))) - (assert (= "BC" (. (. s (toUpperCase)) substring 1))) - (assert (= 2 (.-length (. (. s (toUpperCase)) substring 1))))) - - (assert (= (conj fred {:wife :ethel :friend :ricky}) - (map->Person {:firstname "Fred" :lastname "Mertz" :wife :ethel :friend :ricky}))) - (assert (= (conj fred {:lastname "Flintstone"}) - (map->Person {:firstname "Fred" :lastname "Flintstone"}))) - (assert (= (assoc fred :lastname "Flintstone") - (map->Person {:firstname "Fred" :lastname "Flintstone"}))) - (assert (= (assoc fred :wife :ethel) - (map->Person {:firstname "Fred" :lastname "Mertz" :wife :ethel}))) - (assert (= (dissoc ethel :husband) - (map->Person {:firstname "Ethel" :lastname "Mertz"}))) - - (defrecord A [x]) - (defrecord B [x]) - (assert (not= (A. nil) (B. nil))) - - (defprotocol IFoo (foo [this])) - (assert (= (meta (with-meta (reify IFoo (foo [this] :foo)) {:foo :bar})) - {:foo :bar})) - - (defmulti foo2 identity) - (defmethod foo2 0 [x] x) - (assert (= foo2 (ffirst {foo2 1}))) - - (defprotocol IMutate - (mutate [this])) - - (deftype Mutate [^:mutable a] - IMutate - (mutate [_] - (set! a 'foo))) - - ;; IFn - (deftype FnLike [] - IFn - (-invoke [_] :a) - (-invoke [_ a] :b) - (-invoke [_ a b] :c)) - - (assert (= :a ((FnLike.)))) - (assert (= :b ((FnLike.) 1))) - (assert (= :c ((FnLike.) 1 2))) - - (assert (= [:b :b :b] (map (FnLike.) [0 0 0]))) - - (deftype FnLikeB [a] - IFn - (-invoke [_] a)) - - (assert (= 1 ((FnLikeB. 1)))) - - ;; hashing bug in many JS runtimes CLJ-118 - (let [g #{(conj #{:2} :alt)} - h #{#{:2 :alt}}] - (assert (= g h))) - (assert (= (hash {:a 1 :b 2}) - (hash {:b 2 :a 1}))) - (assert (= (hash (hash-map :a 1 :b 2)) - (hash (hash-map :b 2 :a 1)))) - (assert (= (hash {:start 133 :end 134}) - (hash (apply hash-map [:start 133 :end 134])))) - - (defprotocol IHasFirst - (-get-first [this])) - - (defprotocol IFindsFirst - (-find-first [this other])) - - (deftype First [xs] - ISeqable - (-seq [this] (seq xs)) - IIndexed - (-nth [this i] (nth xs i)) - (-nth [this i not-found] (nth xs i not-found)) - IFn - (-invoke [[x]] x) - (-invoke [this x] this) - Object - (toString [[x]] (str x)) - IHasFirst - (-get-first [[x]] x) - IFindsFirst - (-find-first [_ [x]] x)) - - (let [fv (First. [1 2 3]) - fs (First. "asdf")] - (assert (= (fv) 1)) - (assert (= (fs) \a)) - (assert (= (str fs) \a)) - (assert (= (-get-first fv) 1)) - (assert (= (-get-first fs) \a)) - (assert (= (-find-first fv [1]) 1)) - (assert (identical? (fv 1) fv))) - - (deftype DestructuringWithLocals [a] - IFindsFirst - (-find-first [_ [x y]] - [x y a])) - - (let [t (DestructuringWithLocals. 1)] - (assert (= [2 3 1] (-find-first t [2 3])))) - - (let [x 1] - (assert (= (case x 1 :one) :one))) - (let [x 1] - (assert (= (case x 2 :two :default) :default))) - (let [x 1] - (assert (= (try - (case x 3 :three) - (catch js/Error e - :fail)) - :fail))) - (let [x 1] - (assert (= (case x - (1 2 3) :ok - :fail) - :ok))) - - (let [x [:a :b]] - (assert (= (case x - [:a :b] :ok) - :ok))) - - (let [a 'a] - (assert (= (case a - nil nil - & :amp - :none) - :none))) - - (let [a '&] - (assert (= (case a - nil nil - & :amp - :none) - :amp))) - - (let [foo 'a] - (assert (= (case foo - (a b c) :sym - :none) - :sym)) - (assert (= (case foo - (b c d) :sym - :none) - :none))) - - ;; IComparable - (assert (= 0 (compare false false))) - (assert (= -1 (compare false true))) - (assert (= 1 (compare true false))) - - (assert (= -1 (compare 0 1))) - (assert (= -1 (compare -1 1))) - (assert (= 0 (compare 1 1))) - (assert (= 1 (compare 1 0))) - (assert (= 1 (compare 1 -1))) - - (assert (= 0 (compare "cljs" "cljs"))) - (assert (= 0 (compare :cljs :cljs))) - (assert (= 0 (compare 'cljs 'cljs))) - (assert (= -1 (compare "a" "b"))) - (assert (= -1 (compare :a :b))) - (assert (= -1 (compare 'a 'b))) - ;; cases involving ns - (assert (= -1 (compare :b/a :c/a))) - (assert (= -1 (compare :c :a/b))) - (assert (= 1 (compare :a/b :c))) - (assert (= -1 (compare 'b/a 'c/a))) - (assert (= -1 (compare 'c 'a/b))) - (assert (= 1 (compare 'a/b 'c))) - - ;; This is different from clj. clj gives -2 next 3 tests - (assert (= -1 (compare "a" "c"))) - (assert (= -1 (compare :a :c))) - (assert (= -1 (compare 'a 'c))) - - (assert (= -1 (compare [1 2] [1 1 1]))) - (assert (= -1 (compare [1 2] [1 2 1]))) - (assert (= -1 (compare [1 1] [1 2]))) - (assert (= 0 (compare [1 2] [1 2]))) - (assert (= 1 (compare [1 2] [1 1]))) - (assert (= 1 (compare [1 1 1] [1 2]))) - (assert (= 1 (compare [1 1 2] [1 1 1]))) - - (assert (= -1 (compare (subvec [1 2 3] 1) (subvec [1 2 4] 1)))) - (assert (= 0 (compare (subvec [1 2 3] 1) (subvec [1 2 3] 1)))) - (assert (= 1 (compare (subvec [1 2 4] 1) (subvec [1 2 3] 1)))) - - ;; RSeq - - (assert (= '(3 2 1) (reverse (seq (array 1 2 3))))) - (assert (= '(3 2 1) (reverse [1 2 3]))) - (assert (= '(4 3 2 1) (cons 4 (reverse [1 2 3])))) - (assert (= 6 (reduce + (reverse [1 2 3])))) - (assert (= '(4 3 2) (map inc (reverse [1 2 3])))) - (assert (= '(4 2) (filter even? (reverse [1 2 3 4])))) - - ;; Chunked Sequences - - (let [r (range 64) - v (into [] r)] - (assert (= (hash (seq v)) (hash (seq v)))) - (assert (= 6 (reduce + (array-chunk (array 1 2 3))))) - (assert (instance? ChunkedSeq (seq v))) - (assert (= r (seq v))) - (assert (= (map inc r) (map inc v))) - (assert (= (filter even? r) (filter even? v))) - (assert (= (filter odd? r) (filter odd? v))) - (assert (= (concat r r r) (concat v v v))) - (assert (satisfies? IReduce (seq v))) - (assert (== 2010 (reduce + (nnext (nnext (seq v)))))) - (assert (== 2020 (reduce + 10 (nnext (nnext (seq v))))))) - - ;; INext - - (assert (= nil (next nil))) - (assert (= nil (next (seq (array 1))))) - (assert (= '(2 3) (next (seq (array 1 2 3))))) - (assert (= nil (next (reverse (seq (array 1)))))) - (assert (= '(2 1) (next (reverse (seq (array 1 2 3)))))) - (assert (= nil (next (cons 1 nil)))) - (assert (= '(2 3) (next (cons 1 (cons 2 (cons 3 nil)))))) - (assert (= nil (next (lazy-seq (cons 1 nil))))) - (assert (= '(2 3) (next (lazy-seq - (cons 1 - (lazy-seq - (cons 2 - (lazy-seq (cons 3 nil))))))))) - (assert (= nil (next (list 1)))) - (assert (= '(2 3) (next (list 1 2 3)))) - (assert (= nil (next [1]))) - (assert (= '(2 3) (next [1 2 3]))) - (assert (= nil (next (range 1 2)))) - (assert (= '(2 3) (next (range 1 4)))) - - ;; UUID - - (assert (= (UUID. "550e8400-e29b-41d4-a716-446655440000") - (UUID. "550e8400-e29b-41d4-a716-446655440000"))) - - (assert (not (identical? (UUID. "550e8400-e29b-41d4-a716-446655440000") - (UUID. "550e8400-e29b-41d4-a716-446655440000")))) - - (assert (= 42 (get {(UUID. "550e8400-e29b-41d4-a716-446655440000") 42} - (UUID. "550e8400-e29b-41d4-a716-446655440000") - :not-at-all-found))) - - (assert (= :not-at-all-found - (get {(UUID. "550e8400-e29b-41d4-a716-446655440000") 42} - (UUID. "666e8400-e29b-41d4-a716-446655440000") - :not-at-all-found))) - - ;; Reader literals - (assert (= #queue [1] (into cljs.core.PersistentQueue.EMPTY [1]))) - (assert (not= #queue [1 2] (into cljs.core.PersistentQueue.EMPTY [1]))) - - (assert (= #inst "2010-11-12T18:14:15.666-00:00" - #inst "2010-11-12T13:14:15.666-05:00")) - - (assert (= #uuid "550e8400-e29b-41d4-a716-446655440000" - #uuid "550e8400-e29b-41d4-a716-446655440000")) - - (assert (= 42 - (get {#uuid "550e8400-e29b-41d4-a716-446655440000" 42} - #uuid "550e8400-e29b-41d4-a716-446655440000"))) - - ;; pr-str - - (assert (= (pr-str 1) "1")) - (assert (= (pr-str -1) "-1")) - (assert (= (pr-str -1.5) "-1.5")) - (assert (= (pr-str [3 4]) "[3 4]")) - (assert (= (pr-str "foo") "\"foo\"")) - (assert (= (pr-str :hello) ":hello")) - (assert (= (pr-str 'goodbye) "goodbye")) - (assert (= (pr-str #{1 2 3}) "#{1 2 3}")) - (assert (= (pr-str '(7 8 9)) "(7 8 9)")) - (assert (= (pr-str '(deref foo)) "(deref foo)")) - (assert (= (pr-str '(quote bar)) "(quote bar)")) - (assert (= (pr-str 'foo/bar) "foo/bar")) - (assert (= (pr-str \a) "\"a\"")) - (assert (= (pr-str :foo/bar) ":foo/bar")) - (assert (= (pr-str nil) "nil")) - (assert (= (pr-str true) "true")) - (assert (= (pr-str false) "false")) - (assert (= (pr-str "string") "\"string\"")) - (assert (= (pr-str ["üñîçó∂£" :ทดสอบ/你好 'こんにちは]) "[\"üñîçó∂£\" :ทดสอบ/你好 こんにちは]")) - (assert (= (pr-str "escape chars \t \r \n \\ \" \b \f") "\"escape chars \\t \\r \\n \\\\ \\\" \\b \\f\"")) - - ;;; pr-str records - - (defrecord PrintMe [a b]) - (assert (= (pr-str (PrintMe. 1 2)) "#cljs.core-test.PrintMe{:a 1, :b 2}")) - - ;;; pr-str inst - - (assert (= (pr-str (js/Date. "2010-11-12T13:14:15.666-05:00")) - "#inst \"2010-11-12T18:14:15.666-00:00\"")) - - (doseq [month (range 1 13) day (range 1 29) hour (range 1 23)] - (let [pad (fn [n] - (if (< n 10) - (str "0" n) - n)) - inst (str "2010-" (pad month) "-" (pad day) "T" (pad hour) ":14:15.666-00:00")] - (assert (= (pr-str (js/Date. inst)) (str "#inst \"" inst "\""))))) - - ;;; pr-str uuid - - (let [uuid-str "550e8400-e29b-41d4-a716-446655440000" - uuid (UUID. uuid-str)] - (assert (= (pr-str uuid) (str "#uuid \"" uuid-str "\"")))) - - ;; CLJS-405 - - (defprotocol IBar (-bar [this x])) - - (defn baz [f] - (reify - IBar - (-bar [_ x] - (f x)))) - - (assert (= 2 (-bar (baz inc) 1))) - - ;; CLJS-401 / CLJS-411 - - (let [x "original"] - (defn original-closure-stmt [] x)) - - (let [x "overwritten"] - (assert (= "original" (original-closure-stmt)))) - - (assert (= "original" (let [x "original" - oce (fn [] x) - x "overwritten"] - (oce)))) - - - (letfn [(x [] "original") - (y [] (x))] - (let [x (fn [] "overwritten")] - (assert (= "original" (y))))) - - ;; CLJS-459: reduce-kv visit order - (assert (= (reduce-kv conj [] (sorted-map :foo 1 :bar 2)) - [:bar 2 :foo 1])) - - ;; Test builtin implementations of IKVReduce - (letfn [(kvr-test [data expect] - (assert (= :reduced (reduce-kv (fn [_ _ _] (reduced :reduced)) - [] data))) - (assert (= expect (reduce-kv (fn [r k v] (-> r (conj k) (conj v))) - [] data))))] - (kvr-test (obj-map :k0 :v0 :k1 :v1) [:k0 :v0 :k1 :v1]) - (kvr-test (hash-map :k0 :v0 :k1 :v1) [:k0 :v0 :k1 :v1]) - (kvr-test (array-map :k0 :v0 :k1 :v1) [:k0 :v0 :k1 :v1]) - (kvr-test [:v0 :v1] [0 :v0 1 :v1])) - (assert (= {:init :val} (reduce-kv assoc {:init :val} nil))) - - ;; data conveying exception - (assert (= {:foo 1} - (try (throw (ex-info "asdf" {:foo 1})) - (catch ExceptionInfo e - (ex-data e))))) - (assert (instance? js/Error (ex-info "asdf" {:foo 1}))) - (assert (not (instance? cljs.core.ExceptionInfo (js/Error.)))) - - ;; CLJS-435 - - (assert (= (assoc {} 154618822656 1 261993005056 1) - {154618822656 1 261993005056 1})) - - ;; CLJS-458 - - (assert (= (get-in {:a {:b 1}} [:a :b :c] :nothing-there) - :nothing-there)) - - ;; CLJS-464 - - (assert (nil? (get-in {:foo {:bar 2}} [:foo :bar :baz]))) - - ;; symbol metadata - - (assert (= (meta (with-meta 'foo {:tag 'int})) {:tag 'int})) - - ;; CLJS-467 - - (assert (= (reduce-kv + 0 (apply hash-map (range 1000))) - (reduce + (range 1000)))) - - ;; CLJS-477 - - (assert (= [js/undefined 1 2] ((fn [& more] more) js/undefined 1 2))) - (assert (= [js/undefined 4 5] ((fn [a b & more] more) 1 2 js/undefined 4 5))) - - ;; CLJS-493 - - (assert (nil? (get 42 :anything))) - (assert (= (get 42 :anything :not-found) :not-found)) - (assert (nil? (first (map get [42] [:anything])))) - (assert (= (first (map get [42] [:anything] [:not-found])) :not-found)) - - ;; CLJS-481 - - (let [fs (atom [])] - (doseq [x (range 4) - :let [y (inc x) - f (fn [] y)]] - (swap! fs conj f)) - (assert (= (map #(%) @fs) '(1 2 3 4)))) - - ;; CLJS-495 - (assert (false? (exists? js/jQuery))) - (def exists?-test-val 'foo) - (assert (exists? exists?-test-val)) - - ;; CLJS-496 - (assert (= (char 65) \A)) - (assert (= (char \A) \A)) - - ;; compile time run symbol hash codes - - (assert (= (hash 'foo) (hash (symbol "foo")))) - (assert (= (hash 'foo/bar) (hash (symbol "foo" "bar")))) - - (assert (= (lazy-cat [1] [2] [3]) '(1 2 3))) - - ;; r1798 core fn protocol regression - (extend-type object - ISeqable - (-seq [coll] - (map #(vector % (aget coll %)) (js-keys coll))) - - ILookup - (-lookup - ([coll k] - (-lookup coll k nil)) - ([coll k not-found] - (if-let [v (aget coll k)] - v - not-found)))) - - (assert (= (seq (js-obj "foo" 1 "bar" 2)) '(["foo" 1] ["bar" 2]))) - (assert (= (get (js-obj "foo" 1) "foo") 1)) - (assert (= (get (js-obj "foo" 1) "bar" ::not-found) ::not-found)) - (assert (= (reduce (fn [s [k v]] (+ s v)) 0 (js-obj "foo" 1 "bar" 2)) 3)) - - ;; CLJS-515 - (deftype PositionalFactoryTest [x]) - - (assert (== 1 (.-x (->PositionalFactoryTest 1)))) - - ;; CLJS-518 - (assert (nil? (:test "test"))) - - ;; CLJS-541 - (letfn [(f! [x] (print \f) x) - (g! [x] (print \g) x)] - (assert (= "ffgfg" - (with-out-str - (instance? Symbol (f! 'foo)) - (max (f! 5) (g! 10)) - (min (f! 5) (g! 10)))))) - - ;; CLJS-582 - (assert (= #{1 2} (set [1 2 2]))) - (assert (= #{1 2} (hash-set 1 2 2))) - (assert (= #{1 2} (apply hash-set [1 2 2]))) - - ;; CLJS-585 - (assert (= (last (map identity (into [] (range 32)))) 31)) - (assert (= (into #{} (range 32)) - (set (map identity (into [] (range 32)))))) - - ;; CLJS-580 - (def foo580) - (def foo580 {:a (fn []) :b (fn [] (foo580 :a))}) - (assert (nil? (((:b foo580))))) - - ;; CLJS-587 - (assert (== (first (filter #(== % 9999) (range))) 9999)) - - ;; LazySeq regressions - - ;; CLJS-604 - (assert (= () (concat nil []))) - (assert (= () (concat [] []))) - - ;; CLJS-600 - (assert (= "foobar" (apply str (concat "foo" "bar")))) - - ;; CLJS-608 - (assert (= '("") (re-seq #"\s*" ""))) - - ;; CLJS-638 - - (deftype KeywordTest [] - ILookup - (-lookup [o k] :nothing) - (-lookup [o k not-found] not-found)) - - (assert (= (:a (KeywordTest.)) :nothing)) - - ;; CLJS-648 (CLJ-1285) - (let [a (reify IHash (-hash [_] 42)) - b (reify IHash (-hash [_] 42)) - s (set (range 128))] - (assert (= (-> (conj s a b) transient (disj! a) persistent! (conj a)) - (-> (conj s a b) transient (disj! a) persistent! (conj a))))) - - ;; CLJS-660 - - (assert (= (-> 'a.b keyword ((juxt namespace name))) [nil "a.b"])) - (assert (= (-> 'a.b/c keyword ((juxt namespace name))) ["a.b" "c"])) - (assert (= (-> "a.b" keyword ((juxt namespace name))) [nil "a.b"])) - (assert (= (-> "a.b/c" keyword ((juxt namespace name))) ["a.b" "c"])) - - ;; CLJS-663 - - (assert (= (keyword 123) nil)) - (assert (= (keyword (js/Date.)) nil)) - - ;; CLJS-647 - (let [keys #(vec (js-keys %)) - z "x"] - (assert (= ["x"] - (keys (js-obj "x" "y")) - (keys (js-obj (identity "x") "y")) - (keys (js-obj z "y"))))) - - ;; CLJS-583 - - (def some-x 1) - (def some-y 1) - - (assert (= (count #{some-x some-y}) 1)) - - ;; CLJS-584 - - (assert (= (count {some-x :foo some-y :bar}) 1)) - - ;; CLJS-717 - - (assert (array? #js [1 2 3])) - (assert (= (alength #js [1 2 3]) 3)) - (assert (= (seq #js [1 2 3]) (seq [1 2 3]))) - (assert (= (set (js-keys #js {:foo "bar" :baz "woz"})) #{"foo" "baz"})) - (assert (= (aget #js {:foo "bar"} "foo") "bar")) - (assert (= (aget #js {"foo" "bar"} "foo") "bar")) - (assert (array? (aget #js {"foo" #js [1 2 3]} "foo"))) - (assert (= (seq (aget #js {"foo" #js [1 2 3]} "foo")) '(1 2 3))) - - ;; CLJS-725 - - (assert (= (apply vector (drop-while (partial = 1) [1 2 3])) [2 3])) - (assert (= (apply list (drop-while (partial = 1) [1 2 3])) '(2 3))) - (assert (= (set (drop 1 #js [1 2 3])) #{2 3})) - - ;; CLJS-724 - - (assert (nil? (first (rest (rest (rest (range 3))))))) - - ;; CLJS-730 - - (assert (true? (object? #js {}))) - (assert (false? (object? nil))) - - (assert - (== (count (hash-set [1 4] [2 4] [3 4] [0 3] [1 3] [2 3] [3 3] - [0 2] [1 2] [2 2] [3 2] [4 2] [0 1] [1 1] - [2 1] [3 1] [1 0] [2 0] [3 0])) - (count (list [1 4] [2 4] [3 4] [0 3] [1 3] [2 3] [3 3] - [0 2] [1 2] [2 2] [3 2] [4 2] [0 1] [1 1] - [2 1] [3 1] [1 0] [2 0] [3 0])))) - - (defprotocol IWoz - (-woz [this])) - - (def noz []) - - ;; CLJS-414 - - (assert (= (specify noz IWoz (-woz [_] :boz)) noz)) - (assert (not (identical? (specify noz IWoz (-woz [_] :boz)) noz))) - (assert (= (-woz (specify noz IWoz (-woz [this] this))) noz)) - (assert (= (-woz (specify noz IWoz (-woz [_] :boz))) :boz)) - - ;; CLJS-734 - - (assert (= (-> (transient []) (conj! 1 2) persistent!) [1 2])) - (assert (= (-> (transient #{1 2 3}) (disj! 1 2) persistent!) #{3})) - (assert (= (-> (transient {}) (assoc! :a 1 :b 2) persistent!) {:a 1 :b 2})) - (assert (= (-> (transient {:a 1 :b 2 :c 3}) (dissoc! :a :b) persistent!) {:c 3})) - - ;; CLJS-767 - - (doseq [n [nil "-1" "" "0" "1" false true (js-obj)]] - (assert (= :fail (try (assoc [1 2] n 4) - (catch js/Error e :fail)))) - (assert (= :fail (try (assoc (subvec [1 2 3] 2) n 4) - (catch js/Error e :fail)))) - (assert (= :fail (try (assoc (range 1 3) n 4) - (catch js/Error e :fail))))) - - ;; CLJS-768 - - (doseq [n [nil "-1" "" "0" "1" false true (js-obj)]] - (assert (= :fail (try (assoc! (transient [1 2]) n 4) - (catch js/Error e :fail))))) - - ;; Namespaced destructuring - - (let [{:keys [:a :b]} {:a 1 :b 2}] - (assert (= 1 a)) - (assert (= 2 b))) - - (let [{:keys [:a/b :c/d]} {:a/b 1 :c/d 2}] - (assert (= 1 b)) - (assert (= 2 d))) - - (let [{:keys [a/b c/d]} {:a/b 1 :c/d 2}] - (assert (= 1 b)) - (assert (= 2 d))) - - (let [{:syms [a/b c/d]} {'a/b 1 'c/d 2}] - (assert (= 1 b)) - (assert (= 2 d))) - - (let [{:keys [::s/x ::s/y]} {:clojure.string/x 1 :clojure.string/y 2}] - (assert (= x 1)) - (assert (= y 2))) - - ;; CLJS-739 - - (defn cljs-739 [arr names] - (let [name (first names)] - (if name - (recur (conj arr (fn [] (println name))) - (rest names)) - arr))) - - (assert (= (with-out-str (doseq [fn (cljs-739 [] [:a :b :c :d])] (fn))) - ":a\n:b\n:c\n:d\n")) - - ;; CLJS-728 - - (doseq [n [nil "-1" "" "0" "1" false true (js-obj)]] - (assert (nil? (get [1 2] n))) - (assert (= :fail (try (nth [1 2] n) (catch js/Error e :fail)))) - (assert (= 4 (get [1 2] n 4))) - (assert (= :fail (try (nth [1 2] n 4) (catch js/Error e :fail)))) - - (assert (nil? (get (subvec [1 2] 1) n))) - (assert (= :fail (try (nth (subvec [1 2] 1) n) (catch js/Error e :fail)))) - (assert (= 4 (get (subvec [1 2] 1) n 4))) - (assert (= :fail (try (nth (subvec [1 2] 1) n 4) (catch js/Error e :fail)))) - - (assert (nil? (get (transient [1 2]) n))) - (assert (= :fail (try (nth (transient [1 2]) n) (catch js/Error e :fail)))) - (assert (= 4 (get (transient [1 2]) n 4))) - (assert (= :fail (try (nth (transient [1 2]) n 4) (catch js/Error e :fail)))) - - (assert (nil? (get (range 1 3) n))) - (assert (= :fail (try (nth (range 1 3) n) (catch js/Error e :fail)))) - (assert (= 4 (get (range 1 3) n 4))) - (assert (= :fail (try (nth (range 1 3) n 4) (catch js/Error e :fail))))) - - - :ok - ) diff --git a/test/cljs/cljs/import_test.cljs b/test/cljs/cljs/import_test.cljs deleted file mode 100644 index 9edcbcd5cb..0000000000 --- a/test/cljs/cljs/import_test.cljs +++ /dev/null @@ -1,12 +0,0 @@ -(ns cljs.import-test - (:import goog.math.Long - [goog.math Vec2 Vec3] - [goog.math Integer])) - -(defn test-import [] - (assert (fn? Long)) - (assert (.equals (Long. 4 6) (.add (Long. 1 2) (Long. 3 4)))) - (assert (= "12" (str (Long.fromInt 12)))) - (assert (not (nil? (Vec2. 1 2)))) - (assert (not (nil? (Vec3. 1 2 3)))) - (assert (.equals (Integer.fromString "10") (goog.math.Integer.fromString "10")))) diff --git a/test/cljs/cljs/keyword_other.cljs b/test/cljs/cljs/keyword_other.cljs deleted file mode 100644 index a7e8021b7d..0000000000 --- a/test/cljs/cljs/keyword_other.cljs +++ /dev/null @@ -1,4 +0,0 @@ -(ns cljs.keyword-other) - -(defn foo [a b] - (+ a b)) diff --git a/test/cljs/cljs/keyword_test.cljs b/test/cljs/cljs/keyword_test.cljs deleted file mode 100644 index 58c827150a..0000000000 --- a/test/cljs/cljs/keyword_test.cljs +++ /dev/null @@ -1,8 +0,0 @@ -(ns cljs.keyword-test - (:require [cljs.keyword-other :as other]) - (:require-macros [clojure.core :as cc])) - -(defn test-keyword [] - (assert (= ::bar :cljs.keyword-test/bar)) - (assert (= ::other/foo :cljs.keyword-other/foo)) - (assert (= ::cc/foo :clojure.core/foo))) diff --git a/test/cljs/cljs/letfn_test.cljs b/test/cljs/cljs/letfn_test.cljs deleted file mode 100644 index cc9eb6db16..0000000000 --- a/test/cljs/cljs/letfn_test.cljs +++ /dev/null @@ -1,19 +0,0 @@ -(ns cljs.letfn-test) - -(defn test-letfn [] - (letfn [(ev? [x] - (if (zero? x) - true - (od? (dec x)))) - (od? [x] - (if (zero? x) - false - (ev? (dec x))))] - (assert (ev? 0)) - (assert (ev? 10)) - (assert (not (ev? 1))) - (assert (not (ev? 11))) - (assert (not (od? 0))) - (assert (not (od? 10))) - (assert (od? 1)) - (assert (od? 11)))) diff --git a/test/cljs/cljs/macro_test.cljs b/test/cljs/cljs/macro_test.cljs deleted file mode 100644 index 591ac39140..0000000000 --- a/test/cljs/cljs/macro_test.cljs +++ /dev/null @@ -1,6 +0,0 @@ -(ns cljs.macro-test - (:refer-clojure :exclude [==]) - (:use-macros [cljs.macro-test.macros :only [==]])) - -(defn test-macros [] - (assert (= (== 1 1) 2))) \ No newline at end of file diff --git a/test/cljs/cljs/macro_test/macros.clj b/test/cljs/cljs/macro_test/macros.clj deleted file mode 100644 index 2c495190f9..0000000000 --- a/test/cljs/cljs/macro_test/macros.clj +++ /dev/null @@ -1,5 +0,0 @@ -(ns cljs.macro-test.macros - (:refer-clojure :exclude [==])) - -(defmacro == [a b] - `(+ ~a ~b)) \ No newline at end of file diff --git a/test/cljs/cljs/ns_test.cljs b/test/cljs/cljs/ns_test.cljs deleted file mode 100644 index 9982ea06f8..0000000000 --- a/test/cljs/cljs/ns_test.cljs +++ /dev/null @@ -1,19 +0,0 @@ -(ns cljs.ns-test - (:refer-clojure :exclude [+ for]) - (:require-macros [clojure.core :as lang]) - (:require [cljs.ns-test.foo :refer [baz]] - [clojure.set :as s]) - (:use [cljs.ns-test.bar :only [quux]])) - -(def + -) - -(defn test-ns [] - (assert (= 4 (clojure.core/+ 2 1 1))) - (assert (= 0 (cljs.ns-test/+ 2 1 1))) - (assert (= 0 (+ 2 1 1))) - (assert (= 123 (baz))) - (assert (= 123 (quux))) - - (assert (= (range 5) (lang/for [x (range 5)] x))) - (assert (= #{1 2 3} (s/union #{1} #{2 3}))) - :ok) diff --git a/test/cljs/cljs/ns_test/bar.cljs b/test/cljs/cljs/ns_test/bar.cljs deleted file mode 100644 index b70cdd1e01..0000000000 --- a/test/cljs/cljs/ns_test/bar.cljs +++ /dev/null @@ -1,3 +0,0 @@ -(ns cljs.ns-test.bar) - -(defn quux [] 123) diff --git a/test/cljs/cljs/ns_test/foo.cljs b/test/cljs/cljs/ns_test/foo.cljs deleted file mode 100644 index 4ab8990176..0000000000 --- a/test/cljs/cljs/ns_test/foo.cljs +++ /dev/null @@ -1,9 +0,0 @@ -(ns cljs.ns-test.foo) - -(defn baz [] 123) - -(def kw ::foo) -(def qkw '::foo) - -(assert (= (str kw) ":cljs.ns-test.foo/foo")) -(assert (= (str qkw) ":cljs.ns-test.foo/foo")) diff --git a/test/cljs/cljs/reader_test.cljs b/test/cljs/cljs/reader_test.cljs deleted file mode 100644 index 17e8f268f7..0000000000 --- a/test/cljs/cljs/reader_test.cljs +++ /dev/null @@ -1,169 +0,0 @@ -(ns cljs.reader-test - (:require [cljs.reader :as reader] - [goog.object :as o])) - -(deftype T [a b]) -(defrecord R [a b]) - -(defn test-reader - [] - (assert (= 1 (reader/read-string "1"))) - (assert (= 2 (reader/read-string "#_nope 2"))) - (assert (= -1 (reader/read-string "-1"))) - (assert (= -1.5 (reader/read-string "-1.5"))) - (assert (= [3 4] (reader/read-string "[3 4]"))) - (assert (= "foo" (reader/read-string "\"foo\""))) - (assert (= :hello (reader/read-string ":hello"))) - (assert (= 'goodbye (reader/read-string "goodbye"))) - (assert (= '% (reader/read-string "%"))) - (assert (= #{1 2 3} (reader/read-string "#{1 2 3}"))) - (assert (= '(7 8 9) (reader/read-string "(7 8 9)"))) - (assert (= '(deref foo) (reader/read-string "@foo"))) - (assert (= '(quote bar) (reader/read-string "'bar"))) - (assert (= 'foo/bar (reader/read-string "foo/bar"))) - (assert (= \a (reader/read-string "\\a"))) - (assert (= {:tag 'String} (meta (reader/read-string "^String {:a 1}")))) - (assert (= [:a 'b #{'c {:d [:e :f :g]}}] - (reader/read-string "[:a b #{c {:d [:e :f :g]}}]"))) - (assert (= :foo/bar (reader/read-string ":foo/bar"))) - (assert (= nil (reader/read-string "nil"))) - (assert (= true (reader/read-string "true"))) - (assert (= false (reader/read-string "false"))) - (assert (= "string" (reader/read-string "\"string\""))) - (assert (= "escape chars \t \r \n \\ \" \b \f" (reader/read-string "\"escape chars \\t \\r \\n \\\\ \\\" \\b \\f\""))) - - ;; queue literals - (assert (= cljs.core.PersistentQueue.EMPTY - (reader/read-string "#queue []"))) - - (assert (= (-> cljs.core.PersistentQueue.EMPTY (conj 1)) - (reader/read-string "#queue [1]"))) - - (assert (= (into cljs.core.PersistentQueue.EMPTY [1 2]) - (reader/read-string "#queue [1 2]"))) - - ;; comments - (assert (= :threw (try - (reader/read-string ";foo") - :failed-to-throw - (catch js/Error e :threw)))) - (assert (= 3 (try - (reader/read-string ";foo\n3") - (catch js/Error e :threw)))) - (assert (= 3 (try - (reader/read-string ";foo\n3\n5") - (catch js/Error e :threw)))) - - ;; inst - (let [est-inst (reader/read-string "#inst \"2010-11-12T13:14:15.666-05:00\"") - utc-inst (reader/read-string "#inst \"2010-11-12T18:14:15.666-00:00\"") - pad (fn [n] - (if (< n 10) - (str "0" n) - n))] - - (assert (= (.valueOf (js/Date. "2010-11-12T13:14:15.666-05:00")) - (.valueOf est-inst))) - - (assert (= (.valueOf est-inst) - (.valueOf (reader/read-string (pr-str est-inst))))) - - (assert (= (.valueOf est-inst) - (.valueOf utc-inst))) - - (doseq [month (range 1 13) day (range 1 29) hour (range 1 23)] - (let [s (str "#inst \"2010-" (pad month) "-" (pad day) "T" (pad hour) ":14:15.666-06:00\"")] - (assert (= (-> s reader/read-string .valueOf) - (-> s reader/read-string pr-str reader/read-string .valueOf)))))) - - (let [insts [(reader/read-string "#inst \"2012\"") - (reader/read-string "#inst \"2012-01\"") - (reader/read-string "#inst \"2012-01-01\"") - (reader/read-string "#inst \"2012-01-01T00\"") - (reader/read-string "#inst \"2012-01-01T00:00:00.000\"") - (reader/read-string "#inst \"2012-01-01T00:00:00.000123456\"") - (reader/read-string "#inst \"2012-01-01T00:00:00.000123456789+00:00\"")]] - (assert (apply = (map #(.valueOf %) insts)))) - - ;; uuid literals - (let [u (reader/read-string "#uuid \"550e8400-e29b-41d4-a716-446655440000\"")] - (assert (= u (reader/read-string "#uuid \"550e8400-e29b-41d4-a716-446655440000\""))) - - (assert (not (identical? u (reader/read-string "#uuid \"550e8400-e29b-41d4-a716-446655440000\"")))) - - (assert (= u (-> u pr-str reader/read-string)))) - - ;; new tag parsers - - (reader/register-tag-parser! 'foo identity) - - (assert (= [1 2] (reader/read-string "#foo [1 2]"))) - - ;; tag elements with prefix component - (reader/register-tag-parser! 'foo.bar/baz identity) - (assert (= [1 2] (reader/read-string "#foo.bar/baz [1 2]"))) - - ;; default tag parser - (reader/register-default-tag-parser! (fn [tag val] val)) - (assert (= [1 2] (reader/read-string "#a.b/c [1 2]"))) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - ;; Unicode Tests - - ; sample unicode strings, symbols, keywords - (doseq [unicode - ["اختبار" ; arabic - "ทดสอบ" ; thai - "こんにちは" ; japanese hiragana - "你好" ; chinese traditional - "אַ גוט יאָר" ; yiddish - "cześć" ; polish - "привет" ; russian - - ;; RTL languages skipped below because tricky to insert - ;; ' and : at the "start" - - 'ทดสอบ - 'こんにちは - '你好 - 'cześć - 'привет - - :ทดสอบ - :こんにちは - :你好 - :cześć - :привет - - ;compound data - {:привет :ru "你好" :cn} - ]] - (let [input (pr-str unicode) - read (reader/read-string input)] - (assert (= unicode read) - (str "Failed to read-string \"" unicode "\" from: " input)))) - - ; unicode error cases - (doseq [unicode-error - ["\"abc \\ua\"" ; truncated - "\"abc \\x0z ...etc\"" ; incorrect code - "\"abc \\u0g00 ..etc\"" ; incorrect code - ]] - (let [r (try - (reader/read-string unicode-error) - :failed-to-throw - (catch js/Error e :ok))] - (assert (= r :ok) (str "Failed to throw reader error for: " unicode-error)))) - - ;; CLJS-717 - - (assert (array? (reader/read-string "#js [1 2 3]"))) - (assert (= (alength (reader/read-string "#js [1 2 3]")) 3)) - (assert (= (seq (reader/read-string "#js [1 2 3]")) (seq [1 2 3]))) - (assert (= (set (js-keys (reader/read-string "#js {:foo \"bar\" :baz \"woz\"}"))) #{"foo" "baz"})) - (assert (= (aget (reader/read-string "#js {:foo \"bar\"}") "foo") "bar")) - (assert (= (aget (reader/read-string "#js {\"foo\" \"bar\"}") "foo") "bar")) - (assert (array? (aget (reader/read-string "#js {\"foo\" #js [1 2 3]}") "foo"))) - (assert (= (seq (aget (reader/read-string "#js {\"foo\" #js [1 2 3]}") "foo")) '(1 2 3))) - - :ok) diff --git a/test/cljs/cljs/reducers_test.cljs b/test/cljs/cljs/reducers_test.cljs deleted file mode 100644 index 2866531588..0000000000 --- a/test/cljs/cljs/reducers_test.cljs +++ /dev/null @@ -1,30 +0,0 @@ -(ns cljs.reducers-test - (:require - [clojure.core.reducers :as r])) - -(defn test-builtin-impls [] - (assert (= 0 (r/fold + nil))) - (assert (= [1 2 3 4] (seq (r/reduce r/append! (r/cat) [1 2 3 4])))) - (assert (= 10 (r/reduce + (array 1 2 3 4)))) - (assert (= 11 (r/reduce + 1 (array 1 2 3 4)))) - (assert (= 10 (r/reduce + (list 1 2 3 4)))) - (assert (= 11 (r/reduce + 1 (list 1 2 3 4)))) - (assert (= (r/fold + + [1 2 3]) - (r/fold + [1 2 3]) - (r/reduce + [1 2 3]) - 6)) - (assert (= (r/fold + + (vec (range 2048))) - (r/reduce + (vec (range 2048))))) - (letfn [(f [[ks vs] k v] - [(conj ks k) (conj vs v)]) - (g ([] [#{} #{}]) - ([[ks1 vs1] [ks2 vs2]] - [(into ks1 ks2) (into vs1 vs2)]))] - (assert (= (r/reduce f (g) {:a 1 :b 2 :c 3}) - (r/fold g f {:a 1 :b 2 :c 3}) - [#{:a :b :c} #{1 2 3}])) - (let [m (into {} (for [x (range 2048)] [x (- x)]))] - (assert (= (r/reduce f (g) m) (r/fold g f m)))))) - -(defn test-all [] - (test-builtin-impls)) diff --git a/test/cljs/cljs/top_level.cljs b/test/cljs/cljs/top_level.cljs deleted file mode 100644 index 45f0e14331..0000000000 --- a/test/cljs/cljs/top_level.cljs +++ /dev/null @@ -1,13 +0,0 @@ -(ns cljs.top-level) - -(let [foo 1] - (defn bar [] - foo)) - -(let [foo 2] - (defn baz [] - foo)) - -(defn test [] - (assert (= (bar) 1)) - (assert (= (baz) 2))) \ No newline at end of file diff --git a/test/cljs/clojure/data_test.cljs b/test/cljs/clojure/data_test.cljs deleted file mode 100644 index 009b1ccbd5..0000000000 --- a/test/cljs/clojure/data_test.cljs +++ /dev/null @@ -1,22 +0,0 @@ -(ns clojure.data-test - (:require [clojure.data :refer [diff]])) - -(defn test-data [] - (assert (= [nil nil nil] (diff nil nil))) - (assert (= [1 2 nil] (diff 1 2))) - (assert (= [nil nil [1 2 3]] (diff [1 2 3] '(1 2 3)))) - (assert (= [1 [:a :b] nil] (diff 1 [:a :b]))) - (assert (= [{:a 1} :b nil] (diff {:a 1} :b))) - (assert (= [:team #{:p1 :p2} nil] (diff :team #{:p1 :p2}))) - (assert (= [{0 :a} [:a] nil] (diff {0 :a} [:a]))) - (assert (= [nil [nil 2] [1]] (diff [1] [1 2]))) - (assert (= [nil nil [1 2]] (diff [1 2] (into-array [1 2])))) - (assert (= [#{:a} #{:b} #{:c :d}] (diff #{:a :c :d} #{:b :c :d}))) - (assert (= [nil nil {:a 1}] (diff {:a 1} {:a 1}))) - (assert (= [{:a #{2}} {:a #{4}} {:a #{3}}] (diff {:a #{2 3}} {:a #{3 4}}))) - (assert (= [nil nil [1 2]] (diff [1 2] (into-array [1 2])))) - (assert (= [nil nil [1 2]] (diff (into-array [1 2]) [1 2]))) - (assert (= [{:a {:c [1]}} {:a {:c [0]}} {:a {:c [nil 2] :b 1}}] - (diff {:a {:b 1 :c [1 2]}} {:a {:b 1 :c [0 2]}}))) - (assert (= [{:a nil} {:a false} {:b nil :c false}] - (diff {:a nil :b nil :c false} {:a false :b nil :c false})))) diff --git a/test/cljs/clojure/string_test.cljs b/test/cljs/clojure/string_test.cljs deleted file mode 100644 index 5582a12a9d..0000000000 --- a/test/cljs/clojure/string_test.cljs +++ /dev/null @@ -1,100 +0,0 @@ -(ns clojure.string-test - (:require [clojure.string :as s])) - -(defn test-string - [] - ;; reverse - (assert (= "" (s/reverse ""))) - (assert (= "tab" (s/reverse "bat"))) - ;; replace - (assert (= "faabar" (s/replace "foobar" \o \a))) - (assert (= "barbarbar" (s/replace "foobarfoo" "foo" "bar"))) - (assert (= "FOObarFOO" (s/replace "foobarfoo" #"foo" s/upper-case))) - (assert (= "barbar)foo" (s/replace "foo(bar)foo" "foo(" "bar"))) - ;; join - (assert (= "" (s/join nil))) - (assert (= "" (s/join []))) - (assert (= "1" (s/join [1]))) - (assert (= "12" (s/join [1 2]))) - (assert (= "1,2,3" (s/join \, [1 2 3]))) - (assert (= "" (s/join \, []))) - (assert (= "1 and-a 2 and-a 3" (s/join " and-a " [1 2 3]))) - ;; capitalize - (assert (= "FOOBAR" (s/upper-case "Foobar"))) - (assert (= "foobar" (s/lower-case "FooBar"))) - (assert (= "Foobar" (s/capitalize "foobar"))) - (assert (= "Foobar" (s/capitalize "FOOBAR"))) - ;; split - (assert (= ["a" "b"] (s/split "a-b" #"-"))) - (assert (= ["a" "b" "c"] (s/split "a-b-c" #"-" -1))) - (assert (= ["a" "b" "c"] (s/split "a-b-c" #"-" 0))) - (assert (= ["a-b-c"] (s/split "a-b-c" #"-" 1))) - (assert (= ["a" "b-c"] (s/split "a-b-c" #"-" 2))) - (assert (= ["a" "b" "c"] (s/split "a-b-c" #"-" 3))) - (assert (= ["a" "b" "c"] (s/split "a-b-c" #"-" 4))) - (assert (vector? (s/split "abc" #"-"))) - (assert (= ["a-b-c"] (s/split "a-b-c" #"x" 2))) - (assert (= ["" "a" "b" "c" ""] (s/split "abc" (re-pattern "") 5))) - (assert (= ["a"] (s/split "ab" #"b"))) - (assert (= [] (s/split "ab" #"ab"))) - ;; split-lines - (let [result (s/split-lines "one\ntwo\r\nthree")] - (assert (= ["one" "two" "three"] result)) - (assert (vector? result))) - (assert (= (list "foo") (s/split-lines "foo"))) - ;; blank - (assert (s/blank? nil)) - (assert (s/blank? "")) - (assert (s/blank? " ")) - (assert (s/blank? " \t \n \r ")) - (assert (not (s/blank? " foo "))) - ;; escape - (assert (= "<foo&bar>" - (s/escape "" {\& "&" \< "<" \> ">"}))) - (assert (= " \\\"foo\\\" " - (s/escape " \"foo\" " {\" "\\\""}))) - (assert (= "faabor" - (s/escape "foobar" {\a \o, \o \a}))) - ;; replace-first - (assert (= "barbarfoo" (s/replace-first "foobarfoo" "foo" "bar"))) - (assert (= "barbarfoo" (s/replace-first "foobarfoo" #"foo" "bar"))) - (assert (= "z.ology" (s/replace-first "zoology" \o \.))) - (assert (= "FOObarfoo" (s/replace-first "foobarfoo" #"foo" s/upper-case))) - ;; trim - (assert (= "foo " (s/triml " foo "))) - (assert (= "" (s/triml " "))) - (assert (= " foo" (s/trimr " foo "))) - (assert (= "" (s/trimr " "))) - (assert (= "foo" (s/trim " foo \r\n"))) - ;; trim-newline - (assert (= "foo" (s/trim-newline "foo\n"))) - (assert (= "foo" (s/trim-newline "foo\r\n"))) - (assert (= "foo" (s/trim-newline "foo"))) - (assert (= "foo\r " (s/trim-newline "foo\r "))) - (assert (= "" (s/trim-newline ""))) - :ok) - -(comment - -(deftest char-sequence-handling - (are [result f args] (let [[^CharSequence s & more] args] - (= result (apply f (StringBuffer. s) more))) - "paz" s/reverse ["zap"] - "foo:bar" s/replace ["foo-bar" \- \:] - "ABC" s/replace ["abc" #"\w" s/upper-case] - "faa" s/replace ["foo" #"o" (StringBuffer. "a")] - "baz::quux" s/replace-first ["baz--quux" #"--" "::"] - "baz::quux" s/replace-first ["baz--quux" (StringBuffer. "--") (StringBuffer. "::")] - "zim-zam" s/replace-first ["zim zam" #" " (StringBuffer. "-")] - "Pow" s/capitalize ["POW"] - "BOOM" s/upper-case ["boom"] - "whimper" s/lower-case ["whimPER"] - ["foo" "bar"] s/split ["foo-bar" #"-"] - "calvino" s/trim [" calvino "] - "calvino " s/triml [" calvino "] - " calvino" s/trimr [" calvino "] - "the end" s/trim-newline ["the end\r\n\r\r\n"] - true s/blank? [" "] - ["a" "b"] s/split-lines ["a\nb"] - "fa la la" s/escape ["fo lo lo" {\o \a}])) -) diff --git a/test/cljs/foo/ns_shadow_test.cljs b/test/cljs/foo/ns_shadow_test.cljs deleted file mode 100644 index 17be09d888..0000000000 --- a/test/cljs/foo/ns_shadow_test.cljs +++ /dev/null @@ -1,20 +0,0 @@ -(ns foo.ns-shadow-test - (:require baz)) - -(defn bar [] 1) - -(defn quux [foo] - (+ (foo.ns-shadow-test/bar) foo)) - -(defn id [x] x) - -(defn foo [] (id 42)) - -(defn baz - ([] (baz 2)) - ([x] (quux 2))) - -(defn test-shadow [] - (assert (= (quux 2) 3)) - (assert (= (foo) 42)) - (assert (= (baz) 3))) diff --git a/test/cljs/test_runner.cljs b/test/cljs/test_runner.cljs deleted file mode 100644 index 3c2b43d8e5..0000000000 --- a/test/cljs/test_runner.cljs +++ /dev/null @@ -1,35 +0,0 @@ -(ns test-runner - (:require [cljs.core-test :as core-test] - [cljs.reader-test :as reader-test] - [cljs.binding-test :as binding-test] - [cljs.ns-test :as ns-test] - [clojure.string-test :as string-test] - [clojure.data-test :as data-test] - [cljs.macro-test :as macro-test] - [cljs.letfn-test :as letfn-test] - [foo.ns-shadow-test :as ns-shadow-test] - [cljs.top-level :as top-level] - [cljs.reducers-test :as reducers-test] - [cljs.keyword-test :as keyword-test] - [cljs.import-test :as import-test])) - -(set-print-fn! js/print) - -(core-test/test-stuff) -(reader-test/test-reader) -(string-test/test-string) -(data-test/test-data) -(binding-test/test-binding) -(binding-test/test-with-redefs) -(ns-test/test-ns) -(macro-test/test-macros) -(letfn-test/test-letfn) -(ns-shadow-test/test-shadow) -(top-level/test) -(reducers-test/test-all) -(keyword-test/test-keyword) -(import-test/test-import) - -(println "Tests completed without exception") - -