├── .github └── workflows │ ├── cherry.yml │ ├── squint-filter-repo.yml │ └── squint.yml ├── .gitignore ├── add_repo.sh ├── bb.edn ├── bb └── tasks.clj ├── cherry ├── .clj-kondo │ └── config.edn ├── .github │ └── workflows │ │ └── ci.yml ├── .gitignore ├── .nojekyll ├── CHANGELOG.md ├── README.md ├── bb.edn ├── bb │ ├── integration_tests.clj │ └── tasks.clj ├── cljs.core.js ├── compile.clj ├── corpus │ ├── async.cljs │ ├── case.cljs │ ├── core_vars.cljs │ ├── destructuring.cljs │ ├── doseq.cljs │ ├── exports.cljs │ ├── fns.cljs │ ├── for.cljs │ ├── import_test_cljs.cljs │ ├── import_test_js.js │ ├── js_objects.cljs │ ├── js_star.cljs │ ├── let.cljs │ ├── macro_usage.cljs │ ├── macros.cljs │ ├── newline.cljs │ ├── no_core_vars.cljs │ └── ns_form.cljs ├── deps.edn ├── epl-v10.html ├── examples │ ├── deno-fresh │ │ ├── README.md │ │ ├── deno.json │ │ ├── dev.ts │ │ ├── fresh.gen.ts │ │ ├── import_map.json │ │ ├── islands │ │ │ ├── Counter.tsx │ │ │ └── cherry.cljs │ │ ├── main.ts │ │ ├── package.json │ │ ├── routes │ │ │ ├── api │ │ │ │ └── joke.ts │ │ │ └── index.tsx │ │ └── static │ │ │ ├── favicon.ico │ │ │ └── logo.svg │ ├── deno │ │ ├── README.md │ │ ├── example.cljs │ │ ├── import_map.json │ │ └── package.json │ ├── jsx │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── README.md │ │ ├── bb.edn │ │ ├── bunfig.toml │ │ ├── components │ │ │ ├── Title.tsx │ │ │ └── subtitle.tsx │ │ ├── next-env.d.ts │ │ ├── next.config.js │ │ ├── package.json │ │ ├── pages │ │ │ ├── _app.tsx │ │ │ ├── component.cljs │ │ │ └── index.tsx │ │ ├── public │ │ │ ├── favicon.ico │ │ │ └── vercel.svg │ │ ├── styles │ │ │ ├── Home.module.css │ │ │ └── globals.css │ │ ├── tasks.bb │ │ └── tsconfig.json │ ├── react │ │ ├── index.cljs │ │ ├── index.html │ │ ├── index.mjs │ │ ├── macros.cljs │ │ └── package.json │ ├── vite │ │ ├── .gitignore │ │ ├── README.md │ │ ├── bb.edn │ │ ├── cherry.cljs │ │ ├── counter.js │ │ ├── index.html │ │ ├── javascript.svg │ │ ├── main.js │ │ ├── package.json │ │ ├── public │ │ │ └── vite.svg │ │ ├── style.css │ │ └── tasks.bb │ ├── vitest │ │ ├── README.md │ │ ├── bb.edn │ │ ├── package.json │ │ ├── tasks.bb │ │ └── test │ │ │ └── foo.test.cljs │ └── wordle │ │ ├── index.html │ │ ├── package.json │ │ ├── wordle.cljs │ │ └── wordle.mjs ├── index.html ├── index.js ├── node_cli.js ├── notes │ └── repl.md ├── package.json ├── project.clj ├── resources │ └── cherry │ │ ├── cljs.core.edn │ │ ├── js_reserved.edn │ │ └── resource.clj ├── scratch.cljs ├── scratch_macros.cljs ├── script │ └── changelog.clj ├── shadow-cljs.edn ├── src │ ├── cherry │ │ ├── compiler.cljc │ │ ├── compiler │ │ │ └── node.cljs │ │ ├── internal.cljc │ │ ├── internal │ │ │ ├── cli.cljs │ │ │ ├── deftype.cljc │ │ │ ├── destructure.cljc │ │ │ ├── fn.cljc │ │ │ ├── loop.cljc │ │ │ ├── macros.cljc │ │ │ └── protocols.cljc │ │ └── vendor │ │ │ └── cljs │ │ │ ├── analyzer.cljc │ │ │ └── compiler.cljc │ └── com │ │ └── reasonr │ │ └── string.cljc ├── test-resources │ └── test_project │ │ ├── equality_test.cljs │ │ ├── macro_test.cljs │ │ ├── package.json │ │ └── src │ │ └── macros.cljc └── test │ ├── cherry │ ├── compiler_test.cljs │ ├── jsx_test.cljs │ └── test_utils.cljs │ ├── node_test.cljs │ └── test_scriptjure.clj ├── compiler-common ├── deps.edn └── src │ └── squint │ └── compiler_common.cljc └── squint ├── .clj-kondo └── config.edn ├── .dir-locals.el ├── .github ├── pull_request_template.md └── workflows │ └── ci.yml ├── .gitignore ├── .nojekyll ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── README.md ├── bb.edn ├── bb ├── node_repl_tests.clj └── tasks.clj ├── cljs.core.js ├── compile.clj ├── core.js ├── deps.edn ├── epl-v10.html ├── examples ├── bun │ └── http.cljs ├── ink │ ├── example.cljs │ └── package.json ├── quickjs │ ├── README.md │ └── main.cljs ├── solidjs │ ├── .gitignore │ ├── README.md │ ├── bb.edn │ ├── index.html │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── App.cljs │ │ ├── App.jsx │ │ ├── App.module.css │ │ ├── assets │ │ │ └── favicon.ico │ │ ├── index.css │ │ ├── index.jsx │ │ └── logo.svg │ └── vite.config.js ├── windowjs │ ├── README.md │ └── hello.cljs └── wordle │ ├── index.html │ ├── package.json │ ├── wordle.cljs │ └── wordle.mjs ├── index.html ├── index.js ├── jsx.js ├── node-api.js ├── node_cli.js ├── notes └── repl.md ├── package.json ├── resources └── squint │ ├── cljs.core.edn │ ├── core.edn │ ├── js_reserved.edn │ └── resource.clj ├── scratch_macros.cljc ├── shadow-cljs.edn ├── src └── squint │ ├── compiler.cljc │ ├── compiler │ └── node.cljs │ ├── internal │ ├── cli.cljs │ ├── deftype.cljc │ ├── destructure.cljc │ ├── fn.cljc │ ├── loop.cljc │ ├── macros.cljc │ └── protocols.cljc │ └── repl │ └── node.cljs ├── string.js └── test └── squint ├── clerk.clj ├── compiler_test.clj ├── compiler_test.cljs ├── eval_macro.clj ├── jsx_test.cljs ├── string_test.cljs └── test_utils.cljs /.github/workflows/cherry.yml: -------------------------------------------------------------------------------- 1 | name: Cherry 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, macOS-latest] # windows-latest 10 | 11 | runs-on: ${{ matrix.os }} 12 | 13 | steps: 14 | - name: "Checkout code" 15 | uses: "actions/checkout@v2" 16 | with: 17 | submodules: true 18 | 19 | - name: Prepare java 20 | uses: actions/setup-java@v2 21 | with: 22 | distribution: "adopt" 23 | java-version: 11 24 | 25 | - name: "Restore Cache" 26 | uses: "actions/cache@v1" 27 | with: 28 | path: "~/.m2/repository" 29 | key: "${{ runner.os }}-deps-${{ hashFiles('deps.edn') }}" 30 | restore-keys: "${{ runner.os }}-deps-" 31 | 32 | - name: Setup Clojure 33 | uses: DeLaGuardo/setup-clojure@9.3 34 | with: 35 | cli: 1.10.3.1040 36 | bb: latest 37 | 38 | - name: Run tests 39 | run: | 40 | cd cherry 41 | bb test 42 | bb integration-tests 43 | -------------------------------------------------------------------------------- /.github/workflows/squint-filter-repo.yml: -------------------------------------------------------------------------------- 1 | name: Squint Filter Repo 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest] 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | steps: 17 | - name: "Checkout code" 18 | uses: "actions/checkout@v2" 19 | with: 20 | submodules: true 21 | persist-credentials: false 22 | 23 | - name: Prepare java 24 | uses: actions/setup-java@v2 25 | with: 26 | distribution: "adopt" 27 | java-version: 11 28 | 29 | - name: "Restore Cache" 30 | uses: "actions/cache@v1" 31 | with: 32 | path: "~/.m2/repository" 33 | key: "${{ runner.os }}-deps-${{ hashFiles('deps.edn') }}" 34 | restore-keys: "${{ runner.os }}-deps-" 35 | 36 | - name: Setup python 37 | uses: actions/setup-python@v2 38 | with: 39 | python-version: 3.x 40 | 41 | - name: Download filter-repo 42 | run: | 43 | curl -sLO https://raw.githubusercontent.com/newren/git-filter-repo/main/git-filter-repo 44 | chmod +x git-filter-repo 45 | mv git-filter-repo /usr/local/bin 46 | printf '#!/bin/sh\n\nexec python "$@"\n' > python3 47 | chmod +x python3 48 | mv python3 /usr/local/bin 49 | 50 | - name: Setup Clojure 51 | uses: DeLaGuardo/setup-clojure@9.3 52 | with: 53 | bb: latest 54 | 55 | - name: Filter repo 56 | env: 57 | # Note: token must have access to squint-cljs with read/write for Contents, Actions, Issues and Pull Requests 58 | GITHUB_TOKEN: ${{ secrets.SQUINT_GITHUB_TOKEN }} 59 | run: | 60 | original_sha=$(git rev-parse HEAD) 61 | git filter-repo --subdirectory-filter=squint --tag-rename squint: 62 | git config user.name github-actions[bot] 63 | git config user.email github-actions[bot]@users.noreply.github.com 64 | short_sha=$(git rev-parse --short HEAD) 65 | git checkout -b "bump-common-$short_sha" 66 | git remote add origin https://$GITHUB_TOKEN@github.com/squint-cljs/squint.git 67 | git fetch origin 68 | bb bump-common --sha "$original_sha" 69 | git add deps.edn 70 | GIT_EDITOR=true git commit --amend 71 | git rebase origin/main --strategy-option theirs 72 | echo "Pushing" 73 | git push -u https://borkdude:$GITHUB_TOKEN@github.com/squint-cljs/squint.git HEAD 74 | branch=$(git branch --show) 75 | bb -x tasks/pull-request --github-token "$GITHUB_TOKEN" --branch "$branch" 76 | -------------------------------------------------------------------------------- /.github/workflows/squint.yml: -------------------------------------------------------------------------------- 1 | name: Squint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, macOS-latest, windows-latest] 10 | 11 | runs-on: ${{ matrix.os }} 12 | 13 | steps: 14 | - name: "Checkout code" 15 | uses: "actions/checkout@v2" 16 | with: 17 | submodules: true 18 | 19 | - name: Prepare java 20 | uses: actions/setup-java@v2 21 | with: 22 | distribution: "adopt" 23 | java-version: 11 24 | 25 | - name: "Restore Cache" 26 | uses: "actions/cache@v3" 27 | with: 28 | path: "~/.m2/repository" 29 | key: "${{ runner.os }}-deps-${{ hashFiles('deps.edn') }}" 30 | restore-keys: "${{ runner.os }}-deps-" 31 | 32 | - name: Setup Clojure 33 | uses: DeLaGuardo/setup-clojure@9.3 34 | with: 35 | cli: 1.10.3.1040 36 | bb: latest 37 | 38 | - name: Run tests 39 | run: | 40 | cd squint 41 | npm install 42 | bb test:node 43 | bb test:bb 44 | bb test:clj 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache 2 | .cache 3 | -------------------------------------------------------------------------------- /add_repo.sh: -------------------------------------------------------------------------------- 1 | # https://github.com/nextjournal/nextjournal/pull/5906 2 | # https://stackoverflow.com/a/43345686 3 | # usage: 4 | # git-add-repo https://github.com/example/example dir/to/save 5 | # NOTES 6 | # - installed gnu-sed (brew install gnu-sed) 7 | # - original script used lowercase `path` variable which clobbered PATH, 8 | # changed to `workingDir` 9 | function git-add-repo 10 | { 11 | repo="$1" 12 | dir="$(echo "$2" | sed 's/\/$//')" 13 | workingDir="$(pwd)" 14 | 15 | tmp="$(mktemp -d)" 16 | remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')" 17 | 18 | git clone "$repo" "$tmp" 19 | cd "$tmp" 20 | 21 | git filter-branch --index-filter ' 22 | git ls-files -s | 23 | sed "s,\t,&'"$dir"'/," | 24 | GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info && 25 | mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE" 26 | ' HEAD 27 | 28 | cd "$workingDir" 29 | git remote add -f "$remote" "file://$tmp/.git" 30 | git pull "$remote/main" 31 | git merge --allow-unrelated-histories -m "Merge repo $repo into main" --edit "$remote/main" 32 | git remote remove "$remote" 33 | rm -rf "$tmp" 34 | } 35 | -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | {:paths ["bb"] 2 | :tasks 3 | {squint:filter-repo 4 | {:doc "Rewrites squint mirror..." 5 | :requires ([clojure.string :as str]) 6 | :task (do 7 | (let [sha (str/trim (:out (shell {:out :string} "git rev-parse HEAD"))) 8 | shell (fn [& args] 9 | (apply println args) 10 | (apply shell args))] 11 | (shell "git filter-repo --subdirectory-filter=squint") 12 | (shell "git checkout -b" (str "bump-common-" (subs sha 0 7))) 13 | (let [prefix (if-let [token (System/getenv "SQUINT_GITHUB_TOKEN")] 14 | (str "borkdude:" token "@") 15 | "")] 16 | (shell (format "git remote add origin https://%sgithub.com/squint-cljs/squint.git" 17 | prefix))) 18 | (shell "git fetch origin") 19 | (shell "bb bump-common --sha" sha) 20 | (shell "git add deps.edn") 21 | (shell "git commit -m 'Bump compiler-common'") 22 | (shell "git rebase origin/main --strategy-option theirs")))}}} 23 | -------------------------------------------------------------------------------- /bb/tasks.clj: -------------------------------------------------------------------------------- 1 | (ns tasks) 2 | -------------------------------------------------------------------------------- /cherry/.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /cherry/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, macOS-latest] # windows-latest 10 | 11 | runs-on: ${{ matrix.os }} 12 | 13 | steps: 14 | - name: "Checkout code" 15 | uses: "actions/checkout@v2" 16 | with: 17 | submodules: true 18 | 19 | - name: Prepare java 20 | uses: actions/setup-java@v2 21 | with: 22 | distribution: "adopt" 23 | java-version: 11 24 | 25 | - name: "Restore Cache" 26 | uses: "actions/cache@v1" 27 | with: 28 | path: "~/.m2/repository" 29 | key: "${{ runner.os }}-deps-${{ hashFiles('deps.edn') }}" 30 | restore-keys: "${{ runner.os }}-deps-" 31 | 32 | - name: Setup Clojure 33 | uses: DeLaGuardo/setup-clojure@9.3 34 | with: 35 | cli: 1.10.3.1040 36 | bb: latest 37 | 38 | - name: Run tests 39 | run: | 40 | bb test 41 | bb integration-tests 42 | -------------------------------------------------------------------------------- /cherry/.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache 2 | .cache 3 | package-lock.json 4 | target 5 | node_modules 6 | .shadow-cljs 7 | lib 8 | report.html 9 | test/scratch.js 10 | dist 11 | .work 12 | test/scratch.cjs 13 | *.mjs 14 | test/*.mjs 15 | example.mjs 16 | /examples/vite/vite-project/cherry.mjs 17 | .lein-repl-history 18 | compiler-common 19 | -------------------------------------------------------------------------------- /cherry/.nojekyll: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /cherry/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | [Cherry](https://github.com/squint-cljs/cherry): Experimental ClojureScript to ES6 module compiler 2 | 3 | ## 0.0.0-alpha.60 4 | 5 | - [#78](https://github.com/squint-cljs/cherry/issues/78): fix macro call with more than 20 arguments 6 | - [#79](https://github.com/squint-cljs/cherry/issues/79): fix issue with advanced compilation and `_EQ_` symbol 7 | 8 | ## 0.0.0-alpha.59 9 | 10 | - [#78](https://github.com/squint-cljs/cherry/issues/78): fix macro call with more than 20 arguments 11 | - [#79](https://github.com/squint-cljs/cherry/issues/79): fix (workaround) issue with advanced compilation and `_EQ_` symbol 12 | 13 | ## 0.0.0-alpha.58 14 | 15 | - [#71](https://github.com/squint-cljs/cherry/issues/71): support alias with dashes 16 | - [#77](https://github.com/squint-cljs/cherry/issues/77): support async/await in variadic function 17 | s 18 | - [#67](https://github.com/squint-cljs/cherry/issues/67): support namespaced components in JSX 19 | 20 | ## 0.0.0-alpha.57 21 | 22 | - Fix rendering of number attributes in JSX 23 | -------------------------------------------------------------------------------- /cherry/README.md: -------------------------------------------------------------------------------- 1 | ## Cherry :cherries: 2 | 3 | Experimental ClojureScript to ES6 module compiler. 4 | 5 | Reducing friction between ClojureScript and JS tooling. 6 | 7 | > :warning: This project is an experiment and not recommended to be used in 8 | > production. It currently has many bugs and will undergo many breaking changes. 9 | 10 | Also check out [Squint](https://github.com/squint-cljs/squint) which 11 | is a CLJS _syntax_ to JS compiler. 12 | 13 | ## Quickstart 14 | 15 | Although it's early days and far from complete, you're welcome to try out cherry and submit issues. 16 | 17 | ``` shell 18 | $ mkdir cherry-test && cd cherry-test 19 | $ npm init -y 20 | $ npm install cherry-cljs@latest 21 | ``` 22 | 23 | Create a `.cljs` file, e.g. `example.cljs`: 24 | 25 | ``` clojure 26 | (ns example 27 | (:require ["fs" :as fs] 28 | ["url" :refer [fileURLToPath]])) 29 | 30 | (prn (fs/existsSync (fileURLToPath js/import.meta.url))) 31 | 32 | (defn foo [{:keys [a b c]}] 33 | (+ a b c)) 34 | 35 | (js/console.log (foo {:a 1 :b 2 :c 3})) 36 | ``` 37 | 38 | Then compile and run (`run` does both): 39 | 40 | ``` 41 | $ npx cherry run example.cljs 42 | true 43 | 6 44 | ``` 45 | 46 | Run `npx cherry --help` to see all command line options. 47 | 48 | ## Examples 49 | 50 | A few examples of currenly working projects compiled by cherry: 51 | 52 | - [playground](https://squint-cljs.github.io/cherry/) 53 | - [wordle](https://squint-cljs.github.io/cherry/examples/wordle/index.html) 54 | - [react](https://squint-cljs.github.io/cherry/examples/react/index.html) 55 | - [vite](examples/vite) 56 | - [cherry-action-example](https://github.com/borkdude/cherry-action-example) 57 | 58 | See the [examples](examples) directory for more. 59 | 60 | ## Project goals 61 | 62 | Goals of cherry: 63 | 64 | - Compile `.cljs` files on the fly into ES6-compatible `.mjs` files. 65 | - Compiler will be available on NPM and can be used from JS tooling, but isn't 66 | part of the compiled output unless explicitly used. 67 | - Compiled JS files are fairly readable and have source map support for 68 | debugging 69 | - Compiled JS files are linked to one shared NPM module `"cherry-cljs"` which 70 | contains `cljs.core.js`, `cljs.string`, etc. such that libraries written in 71 | cherry can be compiled and hosted on NPM, while all sharing the same 72 | standard library and data structures. See [this 73 | tweet](https://twitter.com/borkdude/status/1549830159326404616) on how that 74 | looks. 75 | - Output linked to older versions of cherry will work with newer 76 | versions of cherry: i.e. 'binary' compatibility. 77 | - Light-weight and fast: heavy lifting such as optimizations are expected to be 78 | done by JS tooling 79 | - No dependency on Google Closure: this project will use it for bootstrapping 80 | itself (by using the CLJS compiler), but users of this project won't use it for compilation 81 | - Macro support 82 | - REPL support 83 | - Async/await support. See [this tweet](https://twitter.com/borkdude/status/1549843802604638209) for a demo. 84 | - Native support for JS object destructuring: `[^:js {:keys [a b]} #js {:a 1 :b 2}]` 85 | - Native support for JSX via `#jsx` reader tag. See [example](https://github.com/squint-cljs/cherry/blob/main/examples/jsx/pages/component.cljs). 86 | 87 | Cherry may introduce new constructs such as `js/await` which won't be compatible 88 | with current CLJS. Also it might not support all features that CLJS offers. As 89 | such, using existing libraries from the CLJS ecosystem or compiling Cherry CLJS 90 | code with the CLJS compiler may become challenging. However, some results of 91 | this experiment may end up as improvements in the CLJS compiler if they turn out 92 | to be of value. 93 | 94 | See [slides](https://www.dropbox.com/s/955jgzy6hgpx67r/dcd2022-cljs-reimagined.pdf?dl=0) of a presentation given at Dutch Clojure Days 2022 about cherry and squint. 95 | 96 | Depending on interest both from people working on this and the broader 97 | community, the above goals may or may not be pursued. If you are interested in 98 | maturing cherry, please submit 99 | [issues](https://github.com/squint-cljs/cherry/issues) for bug reports or share 100 | your thoughts on [Github 101 | Discussions](https://github.com/squint-cljs/cherry/discussions). 102 | 103 | Cherry started out as a fork of 104 | [Scriptjure](https://github.com/arohner/scriptjure). Currently it's being 105 | reworked to meet the above goals. 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | ## Development 116 | 117 | ``` shell 118 | $ git clone git@github.com:squint-cljs/cherry.git 119 | $ cd cherry 120 | $ bb dev 121 | ``` 122 | 123 | For making local changes to [compiler-common](https://github.com/squint-cljs/compiler-common), also clone that repo _inside_ the cherry repository. 124 | 125 | License 126 | ======= 127 | Cherry is licensed under the EPL, the same as Clojure core and [Scriptjure](https://github.com/arohner/scriptjure). See epl-v10.html in the root directory for more information. 128 | -------------------------------------------------------------------------------- /cherry/bb.edn: -------------------------------------------------------------------------------- 1 | {:min-bb-version "0.9.161" 2 | :deps {borkdude/rewrite-edn {:mvn/version "0.3.4"}} 3 | :paths ["src" "resources" "bb"] 4 | :tasks {:requires ([tasks :as t]) 5 | build (t/build-cherry-npm-package) 6 | publish (t/publish) 7 | dev (t/watch-cherry) 8 | test {:doc "Run tests" 9 | :task (t/test-cherry)} 10 | bump-common tasks/bump-compiler-common 11 | integration-tests {:doc "Run integration tests" 12 | :task integration-tests/run-tests}}} 13 | -------------------------------------------------------------------------------- /cherry/bb/integration_tests.clj: -------------------------------------------------------------------------------- 1 | (ns integration-tests 2 | (:require [babashka.process :refer [sh shell]] 3 | [clojure.string :as str] 4 | [clojure.test :as t :refer [deftest is]])) 5 | 6 | (deftest macro-test 7 | (let [out (:out (sh "npx cherry run macro_test.cljs" {:err :inherit 8 | :dir "test-resources/test_project"}))] 9 | (is (str/includes? out "22")))) 10 | 11 | (deftest equality-test 12 | (let [out (:out (sh "npx cherry run equality_test.cljs" {:err :inherit 13 | :dir "test-resources/test_project"}))] 14 | (is (str/includes? out "[false true]")))) 15 | 16 | (defn run-tests [] 17 | (shell {:dir "test-resources/test_project"} "npm install") 18 | (let [{:keys [fail error]} (t/run-tests 'integration-tests)] 19 | (when (and fail error (pos? (+ fail error))) 20 | (throw (ex-info "Tests failed" {:babashka/exit 1}))))) 21 | -------------------------------------------------------------------------------- /cherry/bb/tasks.clj: -------------------------------------------------------------------------------- 1 | (ns tasks 2 | (:require 3 | [babashka.fs :as fs] 4 | [babashka.process :refer [shell]] 5 | [clojure.edn :as edn] 6 | [clojure.java.io :as io] 7 | [clojure.string :as str])) 8 | 9 | (defn munge* [s reserved] 10 | (let [s (str (munge s))] 11 | (if (contains? reserved s) 12 | (str s "$") 13 | s))) 14 | 15 | (defn shadow-extra-config 16 | [] 17 | (let [core-config (edn/read-string (slurp (io/resource "cherry/cljs.core.edn"))) 18 | reserved (edn/read-string (slurp (io/resource "cherry/js_reserved.edn"))) 19 | vars (:vars core-config) 20 | ks (map #(symbol (munge* % reserved)) vars) 21 | vs (map #(symbol "cljs.core" (str %)) vars) 22 | core-map (zipmap ks vs) 23 | core-map (assoc core-map 'goog_typeOf 'goog/typeOf)] 24 | {:modules 25 | {:cljs_core {:exports core-map}}})) 26 | 27 | (def test-config 28 | '{:compiler-options {:load-tests true} 29 | :modules {:cherry_tests {:init-fn cherry.compiler-test/init 30 | :depends-on #{:compiler}}}}) 31 | 32 | (defn shadow-extra-test-config [] 33 | (merge-with 34 | merge 35 | (shadow-extra-config) 36 | test-config)) 37 | 38 | (defn build-cherry-npm-package [] 39 | (fs/create-dirs ".work") 40 | (fs/delete-tree "lib") 41 | (fs/delete-tree ".shadow-cljs") 42 | (spit ".work/config-merge.edn" (shadow-extra-config)) 43 | (shell "npx shadow-cljs --config-merge .work/config-merge.edn release cherry")) 44 | 45 | (defn publish [] 46 | (build-cherry-npm-package) 47 | (run! fs/delete (fs/glob "lib" "*.map")) 48 | (shell "npm publish")) 49 | 50 | (defn watch-cherry [] 51 | (fs/delete-tree ".shadow-cljs/builds/clava/dev/ana/cherry") 52 | (fs/create-dirs ".work") 53 | (spit ".work/config-merge.edn" (shadow-extra-test-config)) 54 | (shell "npx shadow-cljs --aliases :dev --config-merge .work/config-merge.edn watch cherry")) 55 | 56 | (defn test-cherry [] 57 | (fs/create-dirs ".work") 58 | (spit ".work/config-merge.edn" (shadow-extra-test-config)) 59 | (shell "npm install") 60 | (shell "npx shadow-cljs --config-merge .work/config-merge.edn release cherry") 61 | (shell "node lib/cherry_tests.js")) 62 | 63 | (defn bump-compiler-common [] 64 | (let [{:keys [out]} 65 | (shell {:out :string 66 | :dir "../squint/compiler-common"} "git rev-parse HEAD") 67 | sha (str/trim out) 68 | deps (slurp "deps.edn") 69 | nodes ((requiring-resolve 'borkdude.rewrite-edn/parse-string) deps) 70 | nodes ((requiring-resolve 'borkdude.rewrite-edn/assoc-in) nodes [:deps 'io.github.squint-cljs/compiler-common :git/sha] sha) 71 | deps (str nodes)] 72 | (spit "deps.edn" deps))) 73 | -------------------------------------------------------------------------------- /cherry/cljs.core.js: -------------------------------------------------------------------------------- 1 | export * from './lib/cljs_core.js'; 2 | -------------------------------------------------------------------------------- /cherry/compile.clj: -------------------------------------------------------------------------------- 1 | (require '[cherry.compiler :refer [compile-string*]]) 2 | 3 | (let [{:keys [imports exports body]} 4 | (compile-string* (slurp *in*))] 5 | (str imports exports body)) 6 | -------------------------------------------------------------------------------- /cherry/corpus/async.cljs: -------------------------------------------------------------------------------- 1 | (ns async) 2 | 3 | (defn ^:async status [] 4 | (let [resp (js/await (js/fetch "https://clojure.org")) 5 | status (js/await (.-status resp))] 6 | status)) 7 | 8 | (js/console.log "status:" (js/await (status))) 9 | -------------------------------------------------------------------------------- /cherry/corpus/case.cljs: -------------------------------------------------------------------------------- 1 | (ns case) 2 | 3 | (def x (case :foo :foo :bar)) 4 | (prn x) 5 | -------------------------------------------------------------------------------- /cherry/corpus/core_vars.cljs: -------------------------------------------------------------------------------- 1 | (ns core-vars) 2 | 3 | (def js-map (clj->js {:foo :bar})) 4 | 5 | (js/console.log js-map) 6 | 7 | (def clj-map {:foo/bar (+ 1 2 3)}) 8 | 9 | (js/console.log (get clj-map :foo/bar)) ;; => 6 10 | 11 | (js/console.log (str clj-map)) 12 | 13 | (def log js/console.log) 14 | 15 | (log (first [1 2 3])) 16 | 17 | (defn foo [x] 18 | (dissoc x :foo)) 19 | 20 | ;; (log (str (foo {:foo 1 :bar 2}))) 21 | 22 | ;; (prn (reverse (map (fn [x] (inc x)) [1 2 3]))) 23 | 24 | ;; (derive :foo/bar :foo/baz) 25 | 26 | ;; (prn (isa? :foo/bar :foo/baz)) 27 | -------------------------------------------------------------------------------- /cherry/corpus/destructuring.cljs: -------------------------------------------------------------------------------- 1 | (ns destructuring) 2 | 3 | (let [^js {:keys [a] :as m} (clj->js {:a 1}) 4 | ^js {:js/keys [b]} (js-obj "js/b" :js/b) 5 | [c d e & f] [2 3 4 5 6]] 6 | (prn m a b c d e f)) 7 | -------------------------------------------------------------------------------- /cherry/corpus/doseq.cljs: -------------------------------------------------------------------------------- 1 | (ns doseq) 2 | 3 | (doseq [x [1 2 3] 4 | y [:hello :bye]] 5 | (prn x y)) 6 | -------------------------------------------------------------------------------- /cherry/corpus/exports.cljs: -------------------------------------------------------------------------------- 1 | (ns exports) 2 | 3 | (def foo (fn [] "Hello")) 4 | 5 | (defn default [] 6 | "Default") 7 | -------------------------------------------------------------------------------- /cherry/corpus/fns.cljs: -------------------------------------------------------------------------------- 1 | (ns fns) 2 | 3 | (def results (atom [])) 4 | 5 | (defn add! [x] 6 | (swap! results conj x)) 7 | 8 | (def foo (fn foo [{:keys [a b]}] 9 | (+ a b))) 10 | 11 | (add! (foo {:a 1 :b 2})) 12 | 13 | (defn bar [{:keys [a b]}] 14 | (+ a b)) 15 | 16 | (add! (bar {:a 1 :b 2})) 17 | 18 | (defn baz [^js {:keys [a b]}] 19 | (+ a b)) 20 | 21 | (add! (baz #js {:a 1 :b 2})) 22 | 23 | (defn quux [x] 24 | (if (pos? x) 25 | (recur (dec x)) 26 | x)) 27 | 28 | (add! (quux 5)) 29 | 30 | (defn mfoo 31 | ([x] x) 32 | ([_x y] y)) 33 | 34 | (add! (mfoo 1)) 35 | (add! (mfoo 1 2)) 36 | -------------------------------------------------------------------------------- /cherry/corpus/for.cljs: -------------------------------------------------------------------------------- 1 | (ns for) 2 | 3 | (prn (new cljs.core/LazySeq nil (fn [] [1 2 3]))) 4 | 5 | #_(prn 6 | (for [x [1 2 3] 7 | y [4 5 6]] 8 | [x y])) 9 | -------------------------------------------------------------------------------- /cherry/corpus/import_test_cljs.cljs: -------------------------------------------------------------------------------- 1 | (ns import-test-cljs 2 | (:require ["./exports.mjs" :refer [foo]])) 3 | 4 | (prn (foo)) 5 | 6 | -------------------------------------------------------------------------------- /cherry/corpus/import_test_js.js: -------------------------------------------------------------------------------- 1 | import the_default, { foo } from './exports.mjs'; 2 | 3 | console.log(foo()); 4 | console.log(the_default()); 5 | -------------------------------------------------------------------------------- /cherry/corpus/js_objects.cljs: -------------------------------------------------------------------------------- 1 | (ns js-objects) 2 | 3 | (let [^js {:keys [a b]} #js {:a 1 :b (+ 1 2 3)}] 4 | (js/console.log (+ a b))) 5 | 6 | (let [^js {:keys [a b]} #js {"a" 1 "b" (+ 1 2 3)}] 7 | (js/console.log (+ a b))) 8 | -------------------------------------------------------------------------------- /cherry/corpus/js_star.cljs: -------------------------------------------------------------------------------- 1 | (ns js-star) 2 | 3 | (js* "console.log(1,2,~{})" 3) 4 | 5 | (def x (js* "1 + ~{}" (+ 1 2 3))) 6 | 7 | (js/console.log x) 8 | -------------------------------------------------------------------------------- /cherry/corpus/let.cljs: -------------------------------------------------------------------------------- 1 | (ns foo) 2 | 3 | (def log js/console.log) 4 | 5 | (log "hello") 6 | (log (+ 1 2 3)) 7 | 8 | (let [y (let [x (do (log "in do") 9 | 12)] 10 | (log "x + 1 =" (inc x)) 11 | (+ x 13)) 12 | ;; shadowing 13 | inc "inc"] 14 | (log "y =" y inc)) 15 | -------------------------------------------------------------------------------- /cherry/corpus/macro_usage.cljs: -------------------------------------------------------------------------------- 1 | (ns macro-usage 2 | (:require-macros ["./macros.mjs" :refer [do-twice]])) 3 | 4 | (do-twice (prn :hello)) 5 | -------------------------------------------------------------------------------- /cherry/corpus/macros.cljs: -------------------------------------------------------------------------------- 1 | (ns macros) 2 | 3 | (defmacro do-twice [x] 4 | `(try (do ~x ~x) 5 | (finally (prn :done!)))) 6 | -------------------------------------------------------------------------------- /cherry/corpus/newline.cljs: -------------------------------------------------------------------------------- 1 | (ns newline) 2 | 3 | (def foo "Foo\nBar") 4 | 5 | (js/console.log foo) 6 | -------------------------------------------------------------------------------- /cherry/corpus/no_core_vars.cljs: -------------------------------------------------------------------------------- 1 | (ns no-core-vars) 2 | 3 | ;; The expectation is that when bundling this, it will only be a couple of bytes. 4 | 5 | (defn foo [] 6 | "hello") 7 | 8 | (js/console.log (foo)) 9 | -------------------------------------------------------------------------------- /cherry/corpus/ns_form.cljs: -------------------------------------------------------------------------------- 1 | (ns ns-form 2 | (:require 3 | ["fs" :as fs] 4 | ["process" :refer [version]])) 5 | 6 | (js/console.log version) 7 | 8 | (prn (fs/readFileSync "corpus/ns_form.cljs" "utf-8")) 9 | -------------------------------------------------------------------------------- /cherry/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {borkdude/edamame {:mvn/version "1.0.0"} 3 | babashka/process {:mvn/version "0.1.7"} 4 | org.babashka/cli {:mvn/version "0.3.32"} 5 | org.babashka/sci {:mvn/version "0.3.32"} 6 | io.github.squint-cljs/compiler-common {:local/root "../compiler-common"}} 7 | :aliases 8 | {:dev {} 9 | :cljs {:extra-paths ["test"] 10 | :extra-deps {thheller/shadow-cljs {:mvn/version "2.20.15"}}} 11 | :test ;; added by neil 12 | {:extra-paths ["test"] 13 | :extra-deps {io.github.cognitect-labs/test-runner 14 | {:git/tag "v0.5.0" :git/sha "b3fd0d2"} 15 | babashka/fs {:mvn/version "0.1.6"}} 16 | :main-opts ["-m" "cognitect.test-runner"] 17 | :exec-fn cognitect.test-runner.api/test}} 18 | } 19 | -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/README.md: -------------------------------------------------------------------------------- 1 | # fresh project 2 | 3 | ### Usage 4 | 5 | Start the project: 6 | 7 | ``` 8 | deno task start 9 | ``` 10 | 11 | This will watch the project directory and restart as necessary. 12 | -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": { 3 | "start": "deno run -A --watch=static/,routes/ dev.ts" 4 | }, 5 | "importMap": "./import_map.json" 6 | } 7 | -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/dev.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S deno run -A --watch=static/,routes/ 2 | 3 | import dev from "$fresh/dev.ts"; 4 | 5 | await dev(import.meta.url, "./main.ts"); 6 | -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/fresh.gen.ts: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT. This file is generated by fresh. 2 | // This file SHOULD be checked into source version control. 3 | // This file is automatically updated during development when running `dev.ts`. 4 | 5 | import * as $0 from "./routes/api/joke.ts"; 6 | import * as $1 from "./routes/index.tsx"; 7 | import * as $$0 from "./islands/Counter.tsx"; 8 | 9 | const manifest = { 10 | routes: { 11 | "./routes/api/joke.ts": $0, 12 | "./routes/index.tsx": $1, 13 | }, 14 | islands: { 15 | "./islands/Counter.tsx": $$0, 16 | }, 17 | baseUrl: import.meta.url, 18 | }; 19 | 20 | export default manifest; 21 | -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/import_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "$fresh/": "https://deno.land/x/fresh@1.0.1/", 4 | "fresh/": "https://deno.land/x/fresh@1.0.1/", 5 | "preact": "https://esm.sh/preact@10.8.2", 6 | "preact/": "https://esm.sh/preact@10.8.2/", 7 | "preact-render-to-string": "https://esm.sh/preact-render-to-string@5.2.0?deps=preact@10.8.2", 8 | "cherry-cljs/cljs.core.js": "https://cdn.jsdelivr.net/npm/cherry-cljs@0.0.0-alpha.29/cljs.core.js" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/islands/Counter.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import { h } from 'preact' 3 | import { useState } from 'preact/hooks' 4 | import { IS_BROWSER } from '$fresh/runtime.ts' 5 | import { incCounter, decCounter } from './cherry.mjs' 6 | 7 | export default function Counter(props: CounterProps) { 8 | const [count, setCount] = useState(props.start); 9 | return ( 10 |
11 |

{count}

12 | 15 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/islands/cherry.cljs: -------------------------------------------------------------------------------- 1 | (ns cherry) 2 | 3 | (defn incCounter [x] 4 | (inc x)) 5 | 6 | (defn decCounter [x] 7 | (dec x)) 8 | -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/main.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | 7 | import { start } from "$fresh/server.ts"; 8 | import manifest from "./fresh.gen.ts"; 9 | await start(manifest); 10 | -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "cherry-cljs": "^0.0.0-alpha.36" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/routes/api/joke.ts: -------------------------------------------------------------------------------- 1 | import { HandlerContext } from "$fresh/server.ts"; 2 | 3 | // Jokes courtesy of https://punsandoneliners.com/randomness/programmer-jokes/ 4 | const JOKES = [ 5 | "Why do Java developers often wear glasses? They can't C#.", 6 | "A SQL query walks into a bar, goes up to two tables and says “can I join you?”", 7 | "Wasn't hard to crack Forrest Gump's password. 1forrest1.", 8 | "I love pressing the F5 key. It's refreshing.", 9 | "Called IT support and a chap from Australia came to fix my network connection. I asked “Do you come from a LAN down under?”", 10 | "There are 10 types of people in the world. Those who understand binary and those who don't.", 11 | "Why are assembly programmers often wet? They work below C level.", 12 | "My favourite computer based band is the Black IPs.", 13 | "What programme do you use to predict the music tastes of former US presidential candidates? An Al Gore Rhythm.", 14 | "An SEO expert walked into a bar, pub, inn, tavern, hostelry, public house.", 15 | ]; 16 | 17 | export const handler = (_req: Request, _ctx: HandlerContext): Response => { 18 | const randomIndex = Math.floor(Math.random() * JOKES.length); 19 | const body = JOKES[randomIndex]; 20 | return new Response(body); 21 | }; 22 | -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/routes/index.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx h */ 2 | import { h } from "preact"; 3 | import Counter from "../islands/Counter.tsx"; 4 | 5 | export default function Home() { 6 | return ( 7 |
8 | the fresh logo: a sliced lemon dripping with juice 13 |

14 | Welcome to `fresh`. Try update this message in the ./routes/index.tsx 15 | file, and refresh. 16 |

17 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/compiler-common/78772abb321208e4a7bbbcf85147d48d47f647d2/cherry/examples/deno-fresh/static/favicon.ico -------------------------------------------------------------------------------- /cherry/examples/deno-fresh/static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /cherry/examples/deno/README.md: -------------------------------------------------------------------------------- 1 | # Deno example 2 | 3 | - Run `npm install` to install cherry. 4 | - Run `npx cherry example.cljs` to compile. 5 | - Finally, to run the example with deno: 6 | 7 | ``` 8 | $ deno run --allow-net --import-map=import_map.json example.mjs 9 | ``` 10 | -------------------------------------------------------------------------------- /cherry/examples/deno/example.cljs: -------------------------------------------------------------------------------- 1 | (ns example 2 | (:require 3 | ["https://deno.land/std@0.146.0/http/server.ts" :as server])) 4 | 5 | (def port 8080) 6 | 7 | (defn handler [req] 8 | (let [agent (-> req (.-headers) (.get "user-agent")) 9 | body (str "Your user agent is: " (or agent 10 | "Unknown"))] 11 | (new js/Response body #js {:status 200}))) 12 | 13 | (server/serve handler #js {:port port}) 14 | -------------------------------------------------------------------------------- /cherry/examples/deno/import_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "cherry-cljs/cljs.core.js": "https://cdn.jsdelivr.net/npm/cherry-cljs@0.0.0-alpha.29/cljs.core.js" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /cherry/examples/deno/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "cherry-cljs": "^0.0.0-alpha.30" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /cherry/examples/jsx/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /cherry/examples/jsx/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | **/*.trace 37 | **/*.zip 38 | **/*.tar.gz 39 | **/*.tgz 40 | **/*.log 41 | package-lock.json 42 | **/*.bunpages/component.jsx 43 | node_modules.bun 44 | node_modules.server.bun 45 | pages/component.jsx 46 | bun.lockb 47 | yarn.lock 48 | -------------------------------------------------------------------------------- /cherry/examples/jsx/README.md: -------------------------------------------------------------------------------- 1 | # Next.js example with cherry JSX 2 | 3 | See `pages/component.cljs` for an example of cherry generating some JSX. 4 | 5 | Run `bb dev` to start developing. Make changes to `component.cljs` and see them 6 | hot-reloaded in the browser. 7 | -------------------------------------------------------------------------------- /cherry/examples/jsx/bb.edn: -------------------------------------------------------------------------------- 1 | {:pods {org.babashka/fswatcher {:version "0.0.3"}} 2 | :paths ["."] 3 | :tasks 4 | {:requires ([tasks :as t]) 5 | 6 | next-dev (shell "npx next dev") 7 | 8 | watch-cljs (t/watch-cljs {}) 9 | 10 | -dev {:depends [next-dev watch-cljs]} 11 | 12 | dev {:doc "Run next dev + watcher to re-build JSX" 13 | :task (run '-dev {:parallel true})}}} 14 | -------------------------------------------------------------------------------- /cherry/examples/jsx/bunfig.toml: -------------------------------------------------------------------------------- 1 | framework = "next" 2 | -------------------------------------------------------------------------------- /cherry/examples/jsx/components/Title.tsx: -------------------------------------------------------------------------------- 1 | import Hey from "./subtitle"; 2 | 3 | export default function Title() { 4 | return ( 5 |

6 | Hello 7 |

8 | ); 9 | } 10 | 11 | export enum TitleEnum { 12 | wow = 1, 13 | } 14 | -------------------------------------------------------------------------------- /cherry/examples/jsx/components/subtitle.tsx: -------------------------------------------------------------------------------- 1 | 2 | export default function Hey() { 3 | return
!!yep
; 4 | } 5 | -------------------------------------------------------------------------------- /cherry/examples/jsx/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /cherry/examples/jsx/next.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | reactStrictMode: true, 3 | typescript: { 4 | // !! WARN !! 5 | // Dangerously allow production builds to successfully complete even if 6 | // your project has type errors. 7 | // !! WARN !! 8 | ignoreBuildErrors: true, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /cherry/examples/jsx/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.57", 4 | "dependencies": { 5 | "cherry-cljs": "../..", 6 | "next": "12.2.3", 7 | "react": "^18", 8 | "react-dom": "^18", 9 | "react-is": "^17.0.2" 10 | }, 11 | "devDependencies": { 12 | "@types/node": "^18.6.5", 13 | "@types/react": "^18", 14 | "bun-framework-next": "^12", 15 | "react-refresh": "0.10.0", 16 | "typescript": "latest" 17 | }, 18 | "module": "index.js" 19 | } 20 | -------------------------------------------------------------------------------- /cherry/examples/jsx/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "../styles/globals.css"; 2 | 3 | function MyApp({ Component, pageProps }) { 4 | return ; 5 | } 6 | 7 | export default MyApp; 8 | -------------------------------------------------------------------------------- /cherry/examples/jsx/pages/component.cljs: -------------------------------------------------------------------------------- 1 | (ns pages.component 2 | (:require ["react" :refer [useState]])) 3 | 4 | (defn Counter [^:js {:keys [init]}] 5 | (let [[counter setCount] (useState init)] 6 | #jsx [:div 7 | "Count:" (.join (into-array (range counter)) " " ) 8 | [:div 9 | [:button 10 | {:onClick (fn [] 11 | (setCount (inc counter)))} 12 | "Click me"]]])) 13 | 14 | (defn MyComponent [] 15 | #jsx [Counter #js {:init 10}]) 16 | 17 | (def default MyComponent) 18 | -------------------------------------------------------------------------------- /cherry/examples/jsx/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import React from "react"; 3 | import styles from "../styles/Home.module.css"; 4 | import nextPackage from "next/package.json"; 5 | import { MyComponent } from "./component.jsx" 6 | 7 | export default function Home({}) { 8 | return ( 9 |
10 | 11 | Next.js 12 | 13 | 14 | 15 | 16 |
17 |

18 | Welcome to Next.js! v 19 | {nextPackage.version} 20 |

21 | 22 |

23 | Get started by editing{" "} 24 | pages/index.tsx 25 |

26 | 27 |
28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /cherry/examples/jsx/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/compiler-common/78772abb321208e4a7bbbcf85147d48d47f647d2/cherry/examples/jsx/public/favicon.ico -------------------------------------------------------------------------------- /cherry/examples/jsx/public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /cherry/examples/jsx/styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 99vh; 3 | padding: 0 0.5rem; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | height: 100vh; 9 | } 10 | 11 | .main { 12 | padding: 5rem 0; 13 | flex: 1; 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | } 19 | 20 | .footer { 21 | width: 100%; 22 | height: 100px; 23 | border-top: 1px solid #eaeaea; 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | } 28 | 29 | .footer a { 30 | display: flex; 31 | justify-content: center; 32 | align-items: center; 33 | flex-grow: 1; 34 | } 35 | 36 | .title a { 37 | color: #0070f3; 38 | text-decoration: none; 39 | } 40 | 41 | .title a:hover, 42 | .title a:focus, 43 | .title a:active { 44 | text-decoration: underline; 45 | } 46 | 47 | .title { 48 | margin: 0; 49 | line-height: 1.15; 50 | font-size: 4rem; 51 | } 52 | 53 | .title, 54 | .description { 55 | text-align: center; 56 | } 57 | 58 | .description { 59 | line-height: 1.5; 60 | font-size: 1.5rem; 61 | } 62 | 63 | .code { 64 | background: #fafafa; 65 | border-radius: 5px; 66 | padding: 0.75rem; 67 | font-size: 1.1rem; 68 | font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, 69 | Bitstream Vera Sans Mono, Courier New, monospace; 70 | } 71 | 72 | .grid { 73 | display: flex; 74 | align-items: center; 75 | justify-content: center; 76 | flex-wrap: wrap; 77 | max-width: 800px; 78 | margin-top: 3rem; 79 | } 80 | 81 | .card { 82 | margin: 1rem; 83 | padding: 1.5rem; 84 | text-align: left; 85 | color: inherit; 86 | text-decoration: none; 87 | border: 1px solid #eaeaea; 88 | border-radius: 10px; 89 | transition: color 0.15s ease, border-color 0.15s ease; 90 | width: 45%; 91 | } 92 | 93 | .card:hover, 94 | .card:focus, 95 | .card:active { 96 | color: #0070f3; 97 | border-color: #0070f3; 98 | } 99 | 100 | .card h2 { 101 | margin: 0 0 1rem 0; 102 | font-size: 1.5rem; 103 | } 104 | 105 | .card p { 106 | margin: 0; 107 | font-size: 1.25rem; 108 | line-height: 1.5; 109 | } 110 | 111 | .logo { 112 | height: 1em; 113 | margin-left: 0.5rem; 114 | } 115 | 116 | @media (max-width: 600px) { 117 | .grid { 118 | width: 100%; 119 | flex-direction: column; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /cherry/examples/jsx/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /cherry/examples/jsx/tasks.bb: -------------------------------------------------------------------------------- 1 | (ns tasks 2 | (:require 3 | [babashka.tasks :refer [shell]] 4 | [clojure.string :as str])) 5 | 6 | (defn watch-cljs [{:keys []}] 7 | (let [watch (requiring-resolve 'pod.babashka.fswatcher/watch)] 8 | (watch "pages" 9 | (fn [{:keys [type path]}] 10 | (when 11 | (and (#{:write :write|chmod} type) 12 | (str/ends-with? path ".cljs")) 13 | (shell {:continue true} "node node_modules/.bin/cherry" path)))) 14 | @(promise))) 15 | -------------------------------------------------------------------------------- /cherry/examples/jsx/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": false, 12 | "forceConsistentCasingInFileNames": true, 13 | "noEmit": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "jsx": "preserve", 20 | "baseUrl": ".", 21 | "paths": {}, 22 | "incremental": true 23 | }, 24 | "include": [ 25 | "next-env.d.ts", 26 | "**/*.ts", 27 | "**/*.tsx" 28 | ], 29 | "exclude": [ 30 | "node_modules" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /cherry/examples/react/index.cljs: -------------------------------------------------------------------------------- 1 | (ns index 2 | (:require 3 | ["https://cdn.skypack.dev/canvas-confetti$default" :as confetti] 4 | ["https://cdn.skypack.dev/react" :as react :refer [useEffect]] 5 | ["https://cdn.skypack.dev/react-dom" :as rdom]) 6 | (:require-macros ["./macros.mjs" :refer [$]])) 7 | 8 | (defn App [] 9 | (useEffect (fn [] (confetti)) #js []) 10 | (let [[count setCount] (react/useState 0)] 11 | ($ :div 12 | ($ :p "You clicked " count " times!") 13 | ($ :button {:onClick (fn [] 14 | (confetti) 15 | (setCount (inc count)))} 16 | "Click me")))) 17 | 18 | (rdom/render 19 | ($ App) 20 | (js/document.getElementById "app")) 21 | -------------------------------------------------------------------------------- /cherry/examples/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cherry React 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /cherry/examples/react/index.mjs: -------------------------------------------------------------------------------- 1 | import { vector, nth, count, clj__GT_js, keyword, arrayMap } from 'cherry-cljs/cljs.core.js' 2 | import confetti from 'https://cdn.skypack.dev/canvas-confetti'; 3 | import * as react from 'https://cdn.skypack.dev/react'; 4 | import { useEffect } from 'https://cdn.skypack.dev/react'; 5 | import * as rdom from 'https://cdn.skypack.dev/react-dom'; 6 | var App = function () { 7 | useEffect(function () { 8 | return confetti(); 9 | }, vector()); 10 | let vec__2831 = react.useState(0); 11 | let count32 = nth(vec__2831, 0, null); 12 | let setCount33 = nth(vec__2831, 1, null); 13 | return react.createElement("div", null, react.createElement("p", null, "You clicked ", count32, " times!"), react.createElement("button", clj__GT_js(arrayMap(keyword("onClick"), function () { 14 | confetti(); 15 | return setCount33((count32 + 1)); 16 | })), "Click me")); 17 | }; 18 | rdom.render(react.createElement(App, null, null), document.getElementById("app")); 19 | 20 | export { App } 21 | -------------------------------------------------------------------------------- /cherry/examples/react/macros.cljs: -------------------------------------------------------------------------------- 1 | (ns react.macros) 2 | 3 | (defmacro $ 4 | "Render element (keyword or symbol) with optional props" 5 | ([elt] 6 | `($ ~elt nil)) 7 | ([elt props & children] 8 | (let [elt (if (keyword? elt) (name elt) elt)] 9 | (if (map? props) 10 | `(react/createElement ~elt (clj->js ~props) ~@children) 11 | `(react/createElement ~elt nil ~props ~@children))))) 12 | -------------------------------------------------------------------------------- /cherry/examples/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "cherry-cljs": "../.." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /cherry/examples/vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /cherry/examples/vite/README.md: -------------------------------------------------------------------------------- 1 | # Vite 2 | 3 | This directory contains a demo that uses cherry with [Vite.js](https://vitejs.dev/). 4 | 5 | Run `npm install` and then run `bb dev` to try it out. Edit the `cherry.cljs` file to see changes. 6 | 7 | See it in action [here](https://twitter.com/borkdude/status/1552308825310494721). 8 | -------------------------------------------------------------------------------- /cherry/examples/vite/bb.edn: -------------------------------------------------------------------------------- 1 | {:pods {org.babashka/fswatcher {:version "0.0.3"}} 2 | :paths ["."] 3 | :tasks 4 | {:requires ([tasks :as t]) 5 | 6 | vite-dev (shell "npm run dev") 7 | 8 | watch-cljs (t/watch-cljs {}) 9 | 10 | -dev {:depends [vite-dev watch-cljs]} 11 | 12 | dev {:doc "Run vite dev + watcher to re-build cherry.cljs" 13 | :task (run '-dev {:parallel true})}}} 14 | -------------------------------------------------------------------------------- /cherry/examples/vite/cherry.cljs: -------------------------------------------------------------------------------- 1 | (ns cherry) 2 | 3 | (defn myCoolFn [x] 4 | (str 5 | "This is cool! " 6 | (+ 1 2 3 x))) 7 | -------------------------------------------------------------------------------- /cherry/examples/vite/counter.js: -------------------------------------------------------------------------------- 1 | import { myCoolFn } from './cherry.mjs' 2 | 3 | export function setupCounter(element) { 4 | let counter = 0 5 | const setCounter = (count) => { 6 | counter = count 7 | element.innerHTML = `Click! ${myCoolFn(counter)}` 8 | } 9 | element.addEventListener('click', () => setCounter(++counter)) 10 | setCounter(0) 11 | } 12 | -------------------------------------------------------------------------------- /cherry/examples/vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /cherry/examples/vite/javascript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cherry/examples/vite/main.js: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | import javascriptLogo from './javascript.svg' 3 | import { setupCounter } from './counter.js' 4 | 5 | document.querySelector('#app').innerHTML = ` 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |

Hello Vite!

14 |
15 | 16 |
17 |

18 | Click on the Vite logo to learn more 19 |

20 |
21 | ` 22 | 23 | setupCounter(document.querySelector('#counter')) 24 | -------------------------------------------------------------------------------- /cherry/examples/vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-project", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "devDependencies": { 12 | "vite": "^3.0.0" 13 | }, 14 | "dependencies": { 15 | "cherry-cljs": "^0.0.0-alpha.33" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cherry/examples/vite/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cherry/examples/vite/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | #app { 41 | max-width: 1280px; 42 | margin: 0 auto; 43 | padding: 2rem; 44 | text-align: center; 45 | } 46 | 47 | .logo { 48 | height: 6em; 49 | padding: 1.5em; 50 | will-change: filter; 51 | } 52 | .logo:hover { 53 | filter: drop-shadow(0 0 2em #646cffaa); 54 | } 55 | .logo.vanilla:hover { 56 | filter: drop-shadow(0 0 2em #f7df1eaa); 57 | } 58 | 59 | .card { 60 | padding: 2em; 61 | } 62 | 63 | .read-the-docs { 64 | color: #888; 65 | } 66 | 67 | button { 68 | border-radius: 8px; 69 | border: 1px solid transparent; 70 | padding: 0.6em 1.2em; 71 | font-size: 1em; 72 | font-weight: 500; 73 | font-family: inherit; 74 | background-color: #1a1a1a; 75 | cursor: pointer; 76 | transition: border-color 0.25s; 77 | } 78 | button:hover { 79 | border-color: #646cff; 80 | } 81 | button:focus, 82 | button:focus-visible { 83 | outline: 4px auto -webkit-focus-ring-color; 84 | } 85 | 86 | @media (prefers-color-scheme: light) { 87 | :root { 88 | color: #213547; 89 | background-color: #ffffff; 90 | } 91 | a:hover { 92 | color: #747bff; 93 | } 94 | button { 95 | background-color: #f9f9f9; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /cherry/examples/vite/tasks.bb: -------------------------------------------------------------------------------- 1 | (ns tasks 2 | (:require 3 | [babashka.tasks :refer [shell]] 4 | [clojure.string :as str])) 5 | 6 | (defn watch-cljs [{:keys []}] 7 | (let [watch (requiring-resolve 'pod.babashka.fswatcher/watch)] 8 | (watch "." 9 | (fn [{:keys [type path]}] 10 | (when 11 | (and (#{:write :write|chmod} type) 12 | (str/ends-with? path ".cljs")) 13 | (shell {:continue true} "node node_modules/.bin/cherry" path)))) 14 | @(promise))) 15 | -------------------------------------------------------------------------------- /cherry/examples/vitest/README.md: -------------------------------------------------------------------------------- 1 | # Vitest 2 | 3 | This example shows how you can use cherry and [vitest](https://vitest.dev/). 4 | 5 | Run `npm install` followed by `bb dev` and start editing `test/foo.test.cljs`. 6 | 7 | See it in action [here](https://twitter.com/borkdude/status/1552369264866230275). 8 | -------------------------------------------------------------------------------- /cherry/examples/vitest/bb.edn: -------------------------------------------------------------------------------- 1 | {:pods {org.babashka/fswatcher {:version "0.0.3"}} 2 | :paths ["."] 3 | :tasks 4 | {:requires ([tasks :as t]) 5 | 6 | vitetest (shell "npx vitest") 7 | 8 | watch-cljs (t/watch-cljs {}) 9 | 10 | -dev {:depends [vitetest watch-cljs]} 11 | 12 | dev {:doc "Run vitetest + watcher to re-build cherry.cljs" 13 | :task (run '-dev {:parallel true})}}} 14 | -------------------------------------------------------------------------------- /cherry/examples/vitest/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "vitest": "^0.19.1" 4 | }, 5 | "dependencies": { 6 | "cherry-cljs": "^0.0.0-alpha.33" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /cherry/examples/vitest/tasks.bb: -------------------------------------------------------------------------------- 1 | (ns tasks 2 | (:require 3 | [babashka.tasks :refer [shell]] 4 | [clojure.string :as str])) 5 | 6 | (defn watch-cljs [{:keys []}] 7 | (let [watch (requiring-resolve 'pod.babashka.fswatcher/watch)] 8 | (watch "." 9 | (fn [{:keys [type path]}] 10 | (when 11 | (and (#{:write :write|chmod} type) 12 | (str/ends-with? path ".cljs")) 13 | (shell "node node_modules/.bin/cherry" path))) 14 | {:recursive true}) 15 | @(promise))) 16 | -------------------------------------------------------------------------------- /cherry/examples/vitest/test/foo.test.cljs: -------------------------------------------------------------------------------- 1 | (ns foo.test 2 | (:require ["vitest" :refer [describe it expect]])) 3 | 4 | (defn ^:async foo-test [] 5 | (-> (expect "foo") 6 | (.toMatch "foo"))) 7 | 8 | (describe "suite" 9 | (fn [] 10 | (it "serial test" 11 | foo-test))) 12 | -------------------------------------------------------------------------------- /cherry/examples/wordle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wordle! 5 | 6 | 7 | 8 | 9 | 16 | 17 | 39 | 40 | 41 |

Wordle by @oxalorg. 42 | Play the game by typing letters and then hit return. The source code is taken from here and compiled using npx cherry wordle.cljs. 43 |

44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /cherry/examples/wordle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "cherry-cljs": "^0.0.0-alpha.29" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /cherry/examples/wordle/wordle.cljs: -------------------------------------------------------------------------------- 1 | (ns wordle) 2 | 3 | (def board-state (atom [])) 4 | (def counter (atom 0)) 5 | (def attempt (atom 0)) 6 | (def word-of-the-day (atom "hello")) 7 | 8 | (defn write-letter [cell letter] 9 | (set! (.-textContent cell) letter)) 10 | 11 | (defn make-cell [] 12 | (let [cell (js/document.createElement "div")] 13 | (set! (.-className cell) "cell") 14 | cell)) 15 | 16 | (defn make-board [n] 17 | (let [board (js/document.createElement "div")] 18 | (set! (.-className board) "board") 19 | ;; adding cells 20 | (doseq [_ (range n)] 21 | (let [cell (make-cell)] 22 | (swap! board-state conj cell) 23 | (.appendChild board cell))) 24 | board)) 25 | 26 | (defn get-letter [cell] 27 | (.-textContent cell)) 28 | 29 | (defn color-cell [idx cell] 30 | (let [color (fn [el color] 31 | (set! (-> el .-style .-backgroundColor) 32 | color)) 33 | letter (get-letter cell)] 34 | (cond 35 | (= letter (nth @word-of-the-day idx)) 36 | (color cell "green") 37 | 38 | (contains? (set @word-of-the-day) letter) 39 | (color cell "aqua") 40 | 41 | :else 42 | (color cell "#333333")))) 43 | 44 | (defn check-solution [cells] 45 | (doseq [[idx cell] (map-indexed vector cells)] 46 | (color-cell idx cell)) 47 | (= (mapv get-letter cells) 48 | (vec @word-of-the-day))) 49 | 50 | (defn user-input [key] 51 | (let [start (* 5 @attempt) 52 | end (* 5 (inc @attempt))] 53 | (cond 54 | (and (re-matches #"[a-z]" key) 55 | (< @counter end)) 56 | (do 57 | (write-letter (nth @board-state @counter) key) 58 | (swap! counter inc)) 59 | 60 | (and (= key "backspace") 61 | (> @counter start)) 62 | (do 63 | (swap! counter dec) 64 | (write-letter (nth @board-state @counter) "")) 65 | 66 | (and (= key "enter") 67 | (= @counter end)) 68 | (do 69 | (when (check-solution (subvec @board-state start end)) 70 | (js/alert "You won")) 71 | (swap! attempt inc)) 72 | 73 | ))) 74 | 75 | (defonce listener (atom nil)) 76 | 77 | (defn ^:dev/before-load unmount [] 78 | (js/document.removeEventListener "keydown" @listener) 79 | (let [app (js/document.getElementById "app")] 80 | (set! (.-innerHTML app) ""))) 81 | 82 | (defn mount [] 83 | (let [app (js/document.getElementById "app") 84 | board (make-board 30) 85 | input-listener 86 | (fn [e] 87 | (user-input (.toLowerCase(.-key e))))] 88 | (.appendChild app board) 89 | (reset! listener input-listener) 90 | (js/document.addEventListener 91 | "keydown" 92 | input-listener))) 93 | 94 | (mount) 95 | 96 | (comment 97 | (do 98 | (def sim ["a" "a" "a" "a" "a" "enter" 99 | "e" "h" "o" "l" "o" 100 | "backspace" "k" "enter" 101 | "h" "e" "l" "l" "o" "enter"]) 102 | (run! user-input sim))) 103 | 104 | ;; :thanks 👾 105 | -------------------------------------------------------------------------------- /cherry/examples/wordle/wordle.mjs: -------------------------------------------------------------------------------- 1 | import { seq, set, first, atom, dec, reset_BANG_, range, unchecked_inc, chunk_first, _EQ_, conj, truth_, key, vec, nth, contains_QMARK_, chunk_rest, chunked_seq_QMARK_, swap_BANG_, vector, mapv, _nth, subvec, inc, keyword, next, count, map_indexed, deref, re_matches } from 'cherry-cljs/cljs.core.js' 2 | var board_state = atom(vector()); 3 | var counter = atom(0); 4 | var attempt = atom(0); 5 | var word_of_the_day = atom("hello"); 6 | var write_letter = function (cell, letter) { 7 | return cell["textContent"] = letter; 8 | ; 9 | }; 10 | var make_cell = function () { 11 | let cell1 = document.createElement("div"); 12 | cell1["className"] = "cell"; 13 | return cell1; 14 | }; 15 | var make_board = function (n) { 16 | let board2 = document.createElement("div"); 17 | board2["className"] = "board"; 18 | let seq__37 = seq(range(n)); 19 | let chunk__48 = null; 20 | let count__59 = 0; 21 | let i__610 = 0; 22 | while(true){ 23 | if (truth_((i__610 < count__59))) { 24 | let _11 = _nth(chunk__48, i__610); 25 | let cell12 = make_cell(); 26 | swap_BANG_(board_state, conj, cell12); 27 | board2.appendChild(cell12); 28 | null; 29 | let G__13 = seq__37; 30 | let G__14 = chunk__48; 31 | let G__15 = count__59; 32 | let G__16 = unchecked_inc(i__610); 33 | seq__37 = G__13; 34 | chunk__48 = G__14; 35 | count__59 = G__15; 36 | i__610 = G__16; 37 | continue; 38 | } else { 39 | let temp__22288__auto__17 = seq(seq__37); 40 | if (truth_(temp__22288__auto__17)) { 41 | let seq__318 = temp__22288__auto__17; 42 | if (truth_(chunked_seq_QMARK_(seq__318))) { 43 | let c__22420__auto__19 = chunk_first(seq__318); 44 | let G__20 = chunk_rest(seq__318); 45 | let G__21 = c__22420__auto__19; 46 | let G__22 = count(c__22420__auto__19); 47 | let G__23 = 0; 48 | seq__37 = G__20; 49 | chunk__48 = G__21; 50 | count__59 = G__22; 51 | i__610 = G__23; 52 | continue; 53 | } else { 54 | let _24 = first(seq__318); 55 | let cell25 = make_cell(); 56 | swap_BANG_(board_state, conj, cell25); 57 | board2.appendChild(cell25); 58 | null; 59 | let G__26 = next(seq__318); 60 | let G__27 = null; 61 | let G__28 = 0; 62 | let G__29 = 0; 63 | seq__37 = G__26; 64 | chunk__48 = G__27; 65 | count__59 = G__28; 66 | i__610 = G__29; 67 | continue; 68 | }}};break; 69 | } 70 | ; 71 | return board2; 72 | }; 73 | var get_letter = function (cell) { 74 | return cell["textContent"]; 75 | }; 76 | var color_cell = function (idx, cell) { 77 | let color30 = function (el, color) { 78 | return el["style"]["backgroundColor"] = color; 79 | ; 80 | }; 81 | let letter31 = get_letter(cell); 82 | if (truth_(_EQ_(letter31, nth(deref(word_of_the_day), idx)))) { 83 | return color30(cell, "green");} else { 84 | if (truth_(contains_QMARK_(set(deref(word_of_the_day)), letter31))) { 85 | return color30(cell, "aqua");} else { 86 | if (truth_(keyword("else"))) { 87 | return color30(cell, "#333333");} else { 88 | return null;}}} 89 | }; 90 | var check_solution = function (cells) { 91 | let seq__3236 = seq(map_indexed(vector, cells)); 92 | let chunk__3337 = null; 93 | let count__3438 = 0; 94 | let i__3539 = 0; 95 | while(true){ 96 | if (truth_((i__3539 < count__3438))) { 97 | let vec__4043 = _nth(chunk__3337, i__3539); 98 | let idx44 = nth(vec__4043, 0, null); 99 | let cell45 = nth(vec__4043, 1, null); 100 | color_cell(idx44, cell45); 101 | null; 102 | let G__46 = seq__3236; 103 | let G__47 = chunk__3337; 104 | let G__48 = count__3438; 105 | let G__49 = unchecked_inc(i__3539); 106 | seq__3236 = G__46; 107 | chunk__3337 = G__47; 108 | count__3438 = G__48; 109 | i__3539 = G__49; 110 | continue; 111 | } else { 112 | let temp__22288__auto__50 = seq(seq__3236); 113 | if (truth_(temp__22288__auto__50)) { 114 | let seq__3251 = temp__22288__auto__50; 115 | if (truth_(chunked_seq_QMARK_(seq__3251))) { 116 | let c__22420__auto__52 = chunk_first(seq__3251); 117 | let G__53 = chunk_rest(seq__3251); 118 | let G__54 = c__22420__auto__52; 119 | let G__55 = count(c__22420__auto__52); 120 | let G__56 = 0; 121 | seq__3236 = G__53; 122 | chunk__3337 = G__54; 123 | count__3438 = G__55; 124 | i__3539 = G__56; 125 | continue; 126 | } else { 127 | let vec__5760 = first(seq__3251); 128 | let idx61 = nth(vec__5760, 0, null); 129 | let cell62 = nth(vec__5760, 1, null); 130 | color_cell(idx61, cell62); 131 | null; 132 | let G__63 = next(seq__3251); 133 | let G__64 = null; 134 | let G__65 = 0; 135 | let G__66 = 0; 136 | seq__3236 = G__63; 137 | chunk__3337 = G__64; 138 | count__3438 = G__65; 139 | i__3539 = G__66; 140 | continue; 141 | }}};break; 142 | } 143 | ; 144 | return _EQ_(mapv(get_letter, cells), vec(deref(word_of_the_day))); 145 | }; 146 | var user_input = function (key) { 147 | let start67 = (5 * deref(attempt)); 148 | let end68 = (5 * (deref(attempt) + 1)); 149 | if (truth_(re_matches(/[a-z]/, key)&&(deref(counter) < end68))) { 150 | write_letter(nth(deref(board_state), deref(counter)), key); 151 | return swap_BANG_(counter, inc);} else { 152 | if (truth_(_EQ_(key, "backspace")&&(deref(counter) > start67))) { 153 | swap_BANG_(counter, dec); 154 | return write_letter(nth(deref(board_state), deref(counter)), "");} else { 155 | if (truth_(_EQ_(key, "enter")&&_EQ_(deref(counter), end68))) { 156 | if (truth_(check_solution(subvec(deref(board_state), start67, end68)))) { 157 | alert("You won")}; 158 | return swap_BANG_(attempt, inc);} else { 159 | return null;}}} 160 | }; 161 | if (truth_((typeof listener !== 'undefined'))) { 162 | null} else { 163 | var listener = atom(null); 164 | }; 165 | var unmount = function () { 166 | document.removeEventListener("keydown", deref(listener)); 167 | let app69 = document.getElementById("app"); 168 | return app69["innerHTML"] = ""; 169 | ; 170 | }; 171 | var mount = function () { 172 | let app70 = document.getElementById("app"); 173 | let board71 = make_board(30); 174 | let input_listener72 = function (e) { 175 | return user_input(e["key"].toLowerCase()); 176 | }; 177 | app70.appendChild(board71); 178 | reset_BANG_(listener, input_listener72); 179 | return document.addEventListener("keydown", input_listener72); 180 | }; 181 | mount(); 182 | null; 183 | 184 | export { make_cell, counter, word_of_the_day, board_state, listener, user_input, check_solution, color_cell, attempt, unmount, mount, make_board, write_letter, get_letter } 185 | -------------------------------------------------------------------------------- /cherry/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cherry 5 | 6 | 7 | 8 | 9 | 17 | 31 | 32 | 33 |
34 | 41 |
42 |
43 | 46 |
47 |
48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /cherry/index.js: -------------------------------------------------------------------------------- 1 | export { compileString } from './lib/compiler.js' 2 | -------------------------------------------------------------------------------- /cherry/node_cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import './lib/cli.js' 4 | -------------------------------------------------------------------------------- /cherry/notes/repl.md: -------------------------------------------------------------------------------- 1 | # REPL 2 | 3 | To keep Clojure semantics for REPL, you don't escape compiling vars to globally 4 | defined vars. This will happen in `dev` mode. 5 | 6 | ``` clojure 7 | (ns foo) 8 | (def x 1) 9 | ``` 10 | 11 | => 12 | 13 | `cherry.root.foo.x = 1` 14 | 15 | But what about the ES6 module then? 16 | 17 | ``` javascript 18 | foo.mjs: 19 | 20 | export { x } 21 | ``` 22 | 23 | In a REPL, when you execute `(require '[foo])` it should not compile to `import * as foo from './foo.mjs` or so, but to: 24 | 25 | ``` clojure 26 | var foo = cherry.root.foo 27 | ``` 28 | 29 | perhaps? 30 | 31 | Or should we compile to: 32 | 33 | ``` clojure 34 | import * as foo from './foo.mjs?t=123' 35 | ``` 36 | 37 | and replace `t=123` with something else once we detect that `foo.cljs` is out of date? 38 | And we could reload the "current" module by doing the same? 39 | 40 | 41 | -------------------------------------------------------------------------------- /cherry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "name": "cherry-cljs", 4 | "sideEffects": false, 5 | "version": "0.0.0-alpha.60", 6 | "files": [ 7 | "cljs.core.js", 8 | "lib", 9 | "node_cli.js", 10 | "index.js" 11 | ], 12 | "bin": { 13 | "cherry": "node_cli.js" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.19.0", 17 | "@vercel/ncc": "^0.34.0", 18 | "chalk": "^5.0.1", 19 | "cherry-cljs": ".", 20 | "esbuild": "^0.14.49", 21 | "react": "^18.2.0", 22 | "shadow-cljs": "^2.20.1", 23 | "source-maps": "^1.0.12", 24 | "@babel/preset-react": "^7.18.6" 25 | }, 26 | "dependencies": { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cherry/project.clj: -------------------------------------------------------------------------------- 1 | (defproject scriptjure "0.1.24" 2 | :description "a clojure DSL for generating javascript" 3 | :url "http://github.com/arohner/scriptjure" 4 | :dependencies [[org.clojure/clojure "1.11.1"]] 5 | :dev-dependencies [[lein-clojars "0.6.0"]]) 6 | -------------------------------------------------------------------------------- /cherry/resources/cherry/js_reserved.edn: -------------------------------------------------------------------------------- 1 | #{"arguments" 2 | "abstract" 3 | "await" 4 | "boolean" 5 | "break" 6 | "byte" 7 | "case" 8 | "catch" 9 | "char" 10 | "class" 11 | "const" 12 | "continue" 13 | "debugger" 14 | "default" 15 | "delete" 16 | "do" 17 | "double" 18 | "else" 19 | "enum" 20 | "export" 21 | "extends" 22 | "final" 23 | "finally" 24 | "float" 25 | "for" 26 | "function" 27 | "goto" 28 | "if" 29 | "implements" 30 | "import" 31 | "in" 32 | "instanceof" 33 | "int" 34 | "interface" 35 | "let" 36 | "long" 37 | "native" 38 | "new" 39 | "package" 40 | "private" 41 | "protected" 42 | "public" 43 | "return" 44 | "short" 45 | "static" 46 | "super" 47 | "switch" 48 | "synchronized" 49 | "this" 50 | "throw" 51 | "throws" 52 | "transient" 53 | "try" 54 | "typeof" 55 | "var" 56 | "void" 57 | "volatile" 58 | "while" 59 | "with" 60 | "yield" 61 | "methods" 62 | "null" 63 | "constructor"} 64 | -------------------------------------------------------------------------------- /cherry/resources/cherry/resource.clj: -------------------------------------------------------------------------------- 1 | (ns cherry.resource 2 | (:require [clojure.edn :as edn] 3 | [clojure.java.io :as io])) 4 | 5 | (defmacro edn-resource [f] 6 | (list 'quote (edn/read-string (slurp (io/resource f))))) 7 | -------------------------------------------------------------------------------- /cherry/scratch.cljs: -------------------------------------------------------------------------------- 1 | (ns scratch (:require ["foo" :as foo])) 2 | (defn App [] #jsx [foo/c #js {:x 1}]) 3 | -------------------------------------------------------------------------------- /cherry/scratch_macros.cljs: -------------------------------------------------------------------------------- 1 | (ns scratch-macros) 2 | 3 | (defmacro do-twice [& body] 4 | `(do ~@body ~@body)) 5 | -------------------------------------------------------------------------------- /cherry/script/changelog.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns changelog 4 | (:require [clojure.string :as str])) 5 | 6 | (let [changelog (slurp "CHANGELOG.md") 7 | replaced (str/replace changelog 8 | #" #(\d+)" 9 | (fn [[_ issue after]] 10 | (format " [#%s](https://github.com/squint-cljs/cherry/issues/%s)%s" 11 | issue issue (str after)))) 12 | replaced (str/replace replaced 13 | #"@([a-zA-Z0-9-_]+)([, \.)])" 14 | (fn [[_ name after]] 15 | (format "[@%s](https://github.com/%s)%s" 16 | name name after)))] 17 | (spit "CHANGELOG.md" replaced)) 18 | -------------------------------------------------------------------------------- /cherry/shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:deps {:aliases [:cljs]} 2 | :builds 3 | {:cherry 4 | {:js-options {;; don't bundle any npm libs 5 | :js-provider :import} 6 | :compiler-options {:infer-externs :auto} 7 | :target :esm 8 | :runtime :node 9 | :output-dir "lib" 10 | :modules 11 | ;; note: this is overriden in bb build-cherry 12 | {:cljs_core {:exports {assoc cljs.core/assoc 13 | map cljs.core/map 14 | str cljs.core/str 15 | keyword cljs.core/keyword 16 | symbol cljs.core/symbol 17 | vector cljs.core/vector 18 | toJs cljs.core/clj->js 19 | toCljs cljs.core/js->clj 20 | dissoc cljs.core/dissoc 21 | conj cljs.core/conj 22 | get cljs.core/get 23 | arrayMap cljs.core/array-map 24 | hashMap cljs.core/hash-map 25 | first cljs.core/first 26 | rest cljs.core/rest 27 | next cljs.core/next 28 | nth cljs.core/nth 29 | seq cljs.core/seq 30 | goog_typeOf goog/typeOf}} 31 | :compiler {:depends-on #{:cljs_core} 32 | :exports 33 | {compileString cherry.compiler/compile-string}} 34 | :cli {:depends-on #{:compiler} 35 | :init-fn cherry.internal.cli/init}} 36 | :build-hooks [(shadow.cljs.build-report/hook 37 | {:output-to "report.html"})]}}} 38 | -------------------------------------------------------------------------------- /cherry/src/cherry/compiler/node.cljs: -------------------------------------------------------------------------------- 1 | (ns cherry.compiler.node 2 | (:require 3 | ["fs" :as fs] 4 | ["path" :as path] 5 | [cherry.compiler :as compiler] 6 | [clojure.string :as str] 7 | [edamame.core :as e] 8 | [sci.core :as sci])) 9 | 10 | (defn slurp [f] 11 | (fs/readFileSync f "utf-8")) 12 | 13 | (defn spit [f s] 14 | (fs/writeFileSync f s "utf-8")) 15 | 16 | (def classpath-dirs ["." "src"]) 17 | 18 | (defn resolve-file* [dir munged-macro-ns] 19 | (let [exts ["cljc" "cljs"]] 20 | (some (fn [ext] 21 | (let [full-path (path/resolve dir (str munged-macro-ns "." ext))] 22 | (when (fs/existsSync full-path) 23 | full-path))) 24 | exts))) 25 | 26 | (defn resolve-file [macro-ns] 27 | (let [path (-> macro-ns str (str/replace "-" "_"))] 28 | (some (fn [dir] 29 | (resolve-file* dir path)) 30 | classpath-dirs))) 31 | 32 | (def ctx (sci/init {:load-fn (fn [{:keys [namespace]}] 33 | (let [f (resolve-file namespace) 34 | fstr (slurp f)] 35 | {:source fstr}))})) 36 | 37 | (defn scan-macros [file] 38 | (let [s (slurp file) 39 | maybe-ns (e/parse-next (e/reader s) compiler/cherry-parse-opts)] 40 | (when (and (seq? maybe-ns) 41 | (= 'ns (first maybe-ns))) 42 | (let [[_ns _name & clauses] maybe-ns 43 | require-macros (some #(when (and (seq? %) 44 | (= :require-macros (first %))) 45 | (rest %)) 46 | clauses)] 47 | (when require-macros 48 | (reduce (fn [prev require-macros] 49 | (.then prev 50 | (fn [_] 51 | (let [[macro-ns & {:keys [refer]}] require-macros 52 | macros (js/Promise.resolve 53 | (do (sci/eval-form ctx (list 'require (list 'quote macro-ns))) 54 | (let [publics (sci/eval-form ctx 55 | `(ns-publics '~macro-ns)) 56 | ks (keys publics) 57 | vs (vals publics) 58 | vs (map deref vs) 59 | publics (zipmap ks vs) 60 | publics (if refer 61 | (select-keys publics refer) 62 | publics)] 63 | publics)))] 64 | (.then macros 65 | (fn [macros] 66 | (set! compiler/built-in-macros 67 | ;; hack 68 | (merge compiler/built-in-macros macros)))))))) 69 | (js/Promise.resolve nil) 70 | require-macros)))))) 71 | 72 | (defn compile-file [{:keys [in-file out-file]}] 73 | (-> (js/Promise.resolve (scan-macros in-file)) 74 | (.then #(compiler/compile-string* (slurp in-file))) 75 | (.then (fn [{:keys [javascript jsx]}] 76 | (let [out-file (or out-file 77 | (str/replace in-file #".clj(s|c)$" 78 | (if jsx 79 | ".jsx" 80 | ".mjs")))] 81 | (spit out-file javascript) 82 | {:out-file out-file}))))) 83 | -------------------------------------------------------------------------------- /cherry/src/cherry/internal.cljc: -------------------------------------------------------------------------------- 1 | (ns cherry.internal 2 | (:require [clojure.string :as str])) 3 | 4 | 5 | -------------------------------------------------------------------------------- /cherry/src/cherry/internal/cli.cljs: -------------------------------------------------------------------------------- 1 | (ns cherry.internal.cli 2 | (:require 3 | ["fs" :as fs] 4 | [babashka.cli :as cli] 5 | [cherry.compiler :as cc] 6 | [cherry.compiler.node :as compiler] 7 | [shadow.esm :as esm])) 8 | 9 | (defn info [& xs] 10 | (apply *print-err-fn* xs)) 11 | 12 | (defn compile-files 13 | [files] 14 | (reduce (fn [prev f] 15 | (-> (js/Promise.resolve prev) 16 | (.then 17 | #(do 18 | (info "[cherry] Compiling CLJS file:" f) 19 | (compiler/compile-file {:in-file f}))) 20 | (.then (fn [{:keys [out-file]}] 21 | (info "[cherry] Wrote JS file:" out-file) 22 | out-file)))) 23 | nil 24 | files)) 25 | 26 | (defn print-help [] 27 | (println "Cherry v0.0.0 28 | 29 | Usage: 30 | 31 | run Compile and run a file 32 | compile ... Compile file(s) 33 | help Print this help")) 34 | 35 | (defn fallback [{:keys [rest-cmds opts]}] 36 | (if-let [e (:e opts)] 37 | (let [res (cc/compile-string e) 38 | dir (fs/mkdtempSync ".tmp") 39 | f (str dir "/cherry.mjs")] 40 | (fs/writeFileSync f res "utf-8") 41 | (when (:show opts) 42 | (println res)) 43 | (-> (esm/dynamic-import (str (js/process.cwd) "/" f)) 44 | (.finally (fn [_] 45 | (fs/rmSync dir #js {:force true :recursive true}))))) 46 | (if (or (:help opts) 47 | (= "help" (first rest-cmds)) 48 | (empty? rest-cmds)) 49 | (print-help) 50 | (compile-files rest-cmds)))) 51 | 52 | (defn run [{:keys [opts]}] 53 | (let [{:keys [file]} opts] 54 | (info "[cherry] Running" file) 55 | (.then (compiler/compile-file {:in-file file}) 56 | (fn [{:keys [out-file]}] 57 | (esm/dynamic-import (str (js/process.cwd) "/" out-file)))))) 58 | 59 | #_(defn compile-form [{:keys [opts]}] 60 | (let [e (:e opts)] 61 | (println (t/compile! e)))) 62 | 63 | (def table 64 | [{:cmds ["run"] :fn run :cmds-opts [:file]} 65 | {:cmds ["compile"] :fn (fn [{:keys [rest-cmds]}] 66 | (compile-files rest-cmds))} 67 | {:cmds [] :fn fallback}]) 68 | 69 | (defn init [] 70 | (cli/dispatch table 71 | (.slice js/process.argv 2) 72 | {:aliases {:h :help}})) 73 | -------------------------------------------------------------------------------- /cherry/src/cherry/internal/deftype.cljc: -------------------------------------------------------------------------------- 1 | (ns cherry.internal.deftype 2 | (:require [clojure.core :as core] 3 | [cherry.internal.protocols :as p])) 4 | 5 | (def fast-path-protocols 6 | "protocol fqn -> [partition number, bit]" 7 | (zipmap (map #(symbol "cljs.core" (core/str %)) 8 | '[IFn ICounted IEmptyableCollection ICollection IIndexed ASeq ISeq INext 9 | ILookup IAssociative IMap IMapEntry ISet IStack IVector IDeref 10 | IDerefWithTimeout IMeta IWithMeta IReduce IKVReduce IEquiv IHash 11 | ISeqable ISequential IList IRecord IReversible ISorted IPrintWithWriter IWriter 12 | IPrintWithWriter IPending IWatchable IEditableCollection ITransientCollection 13 | ITransientAssociative ITransientMap ITransientVector ITransientSet 14 | IMultiFn IChunkedSeq IChunkedNext IComparable INamed ICloneable IAtom 15 | IReset ISwap IIterable]) 16 | (iterate (core/fn [[p b]] 17 | (if (core/== 2147483648 b) 18 | [(core/inc p) 1] 19 | [p #?(:clj (core/bit-shift-left b 1) 20 | :cljs (core/* 2 b))])) 21 | [0 1]))) 22 | 23 | (def fast-path-protocol-partitions-count 24 | "total number of partitions" 25 | (core/let [c (count fast-path-protocols) 26 | m (core/mod c 32)] 27 | (if (core/zero? m) 28 | (core/quot c 32) 29 | (core/inc (core/quot c 32))))) 30 | 31 | (core/defn- prepare-protocol-masks [env impls] 32 | (core/let [resolve identity #_(partial resolve-var env) 33 | impl-map (p/->impl-map impls) 34 | fpp-pbs (seq 35 | (keep fast-path-protocols 36 | (map resolve 37 | (keys impl-map))))] 38 | (if fpp-pbs 39 | (core/let [fpps (into #{} 40 | (filter (partial contains? fast-path-protocols) 41 | (map resolve (keys impl-map)))) 42 | parts (core/as-> (group-by first fpp-pbs) parts 43 | (into {} 44 | (map (juxt key (comp (partial map peek) val)) 45 | parts)) 46 | (into {} 47 | (map (juxt key (comp (partial reduce core/bit-or) val)) 48 | parts)))] 49 | [fpps (reduce (core/fn [ps p] (update-in ps [p] (core/fnil identity 0))) 50 | parts 51 | (range fast-path-protocol-partitions-count))])))) 52 | 53 | (core/defn- collect-protocols [impls env] 54 | (core/->> impls 55 | (filter core/symbol?) 56 | (map identity #_#(:name (cljs.analyzer/resolve-var (dissoc env :locals) %))) 57 | (into #{}))) 58 | 59 | (core/defn- annotate-specs [annots v [f sigs]] 60 | (conj v 61 | (vary-meta (cons f (map #(cons (second %) (nnext %)) sigs)) 62 | merge annots))) 63 | 64 | (core/defn dt->et 65 | ([type specs fields] 66 | (dt->et type specs fields false)) 67 | ([type specs fields inline] 68 | (core/let [annots {:cljs.analyzer/type type 69 | :cljs.analyzer/protocol-impl true 70 | :cljs.analyzer/protocol-inline inline}] 71 | (core/loop [ret [] specs specs] 72 | (if (seq specs) 73 | (core/let [p (first specs) 74 | ret (core/-> (conj ret p) 75 | (into (reduce (partial annotate-specs annots) [] 76 | (group-by first (take-while seq? (next specs)))))) 77 | specs (drop-while seq? (next specs))] 78 | (recur ret specs)) 79 | ret))))) 80 | 81 | (core/defn- build-positional-factory 82 | [rsym rname fields] 83 | (core/let [fn-name (with-meta (symbol (core/str '-> rsym)) 84 | (assoc (meta rsym) :factory :positional)) 85 | docstring (core/str "Positional factory function for " rname ".") 86 | field-values (if (core/-> rsym meta :internal-ctor) (conj fields nil nil nil) fields)] 87 | `(defn ~fn-name 88 | ~docstring 89 | [~@fields] 90 | (new ~rname ~@field-values)))) 91 | 92 | (core/defn core-deftype 93 | "(deftype name [fields*] options* specs*) 94 | Currently there are no options. 95 | Each spec consists of a protocol or interface name followed by zero 96 | or more method bodies: 97 | protocol-or-Object 98 | (methodName [args*] body)* 99 | The type will have the (by default, immutable) fields named by 100 | fields, which can have type hints. Protocols and methods 101 | are optional. The only methods that can be supplied are those 102 | declared in the protocols/interfaces. Note that method bodies are 103 | not closures, the local environment includes only the named fields, 104 | and those fields can be accessed directly. Fields can be qualified 105 | with the metadata :mutable true at which point (set! afield aval) will be 106 | supported in method bodies. Note well that mutable fields are extremely 107 | difficult to use correctly, and are present only to facilitate the building 108 | of higherlevel constructs, such as ClojureScript's reference types, in 109 | ClojureScript itself. They are for experts only - if the semantics and 110 | implications of :mutable are not immediately apparent to you, you should not 111 | be using them. 112 | Method definitions take the form: 113 | (methodname [args*] body) 114 | The argument and return types can be hinted on the arg and 115 | methodname symbols. If not supplied, they will be inferred, so type 116 | hints should be reserved for disambiguation. 117 | Methods should be supplied for all methods of the desired 118 | protocol(s). You can also define overrides for methods of Object. Note that 119 | a parameter must be supplied to correspond to the target object 120 | ('this' in JavaScript parlance). Note also that recur calls to the method 121 | head should *not* pass the target object, it will be supplied 122 | automatically and can not be substituted. 123 | In the method bodies, the (unqualified) name can be used to name the 124 | class (for calls to new, instance? etc). 125 | One constructor will be defined, taking the designated fields. Note 126 | that the field names __meta and __extmap are currently reserved and 127 | should not be used when defining your own types. 128 | Given (deftype TypeName ...), a factory function called ->TypeName 129 | will be defined, taking positional parameters for the fields" 130 | [&env _&form t fields & impls] 131 | #_(validate-fields "deftype" t fields) 132 | (core/let [env &env 133 | r t #_(:name (cljs.analyzer/resolve-var (dissoc env :locals) t)) 134 | [fpps pmasks] (prepare-protocol-masks env impls) 135 | protocols (collect-protocols impls env) 136 | t (vary-meta t assoc 137 | :protocols protocols 138 | :skip-protocol-flag fpps) ] 139 | `(do 140 | (deftype* ~t ~fields ~pmasks 141 | ~(if (seq impls) 142 | `(extend-type ~t ~@(dt->et t impls fields)))) 143 | (set! (.-getBasis ~t) (fn [] '[~@fields])) 144 | (set! (.-cljs$lang$type ~t) true) 145 | (set! (.-cljs$lang$ctorStr ~t) ~(core/str r)) 146 | (set! (.-cljs$lang$ctorPrWriter ~t) (fn [this# writer# opt#] (-write writer# ~(core/str r)))) 147 | 148 | ~(build-positional-factory t r fields) 149 | ~t))) 150 | -------------------------------------------------------------------------------- /cherry/src/cherry/internal/destructure.cljc: -------------------------------------------------------------------------------- 1 | ;; Adapted from CLJS core.cljc. Original copyright notice: 2 | 3 | ;; Copyright (c) Rich Hickey. All rights reserved. The use and distribution 4 | ;; terms for this software are covered by the Eclipse Public License 5 | ;; 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found in 6 | ;; the file epl-v10.html at the root of this distribution. By using this 7 | ;; software in any fashion, you are agreeing to be bound by the terms of this 8 | ;; license. You must not remove this notice, or any other, from this 9 | ;; software. 10 | 11 | (ns cherry.internal.destructure 12 | (:refer-clojure :exclude [destructure])) 13 | 14 | (defn destructure [bindings] 15 | (let [bents (partition 2 bindings) 16 | pb (fn pb [bvec b v] 17 | (let [pvec 18 | (fn [bvec b val] 19 | (let [gvec (gensym "vec__") 20 | gseq (gensym "seq__") 21 | gfirst (gensym "first__") 22 | has-rest (some #{'&} b)] 23 | (loop [ret (let [ret (conj bvec gvec val)] 24 | (if has-rest 25 | (conj ret gseq (list `seq gvec)) 26 | ret)) 27 | n 0 28 | bs b 29 | seen-rest? false] 30 | (if (seq bs) 31 | (let [firstb (first bs)] 32 | (cond 33 | (= firstb '&) (recur (pb ret (second bs) gseq) 34 | n 35 | (nnext bs) 36 | true) 37 | (= firstb :as) (pb ret (second bs) gvec) 38 | :else (if seen-rest? 39 | (throw #?(:clj (new Exception "Unsupported binding form, only :as can follow & parameter") 40 | :cljs (new js/Error "Unsupported binding form, only :as can follow & parameter"))) 41 | (recur (pb (if has-rest 42 | (conj ret 43 | gfirst `(first ~gseq) 44 | gseq `(next ~gseq)) 45 | ret) 46 | firstb 47 | (if has-rest 48 | gfirst 49 | (list `nth gvec n nil))) 50 | (inc n) 51 | (next bs) 52 | seen-rest?)))) 53 | ret)))) 54 | pmap 55 | (fn [bvec b v] 56 | (let [m (meta b) 57 | js-keys? (or (:js m) 58 | (= 'js (:tag m))) 59 | gmap (gensym "map__") 60 | defaults (:or b)] 61 | (loop [ret (-> bvec (conj gmap) (conj v) 62 | (conj gmap) (conj (list 'cljs.core/--destructure-map gmap)) 63 | ((fn [ret] 64 | (if (:as b) 65 | (conj ret (:as b) gmap) 66 | ret)))) 67 | bes (let [transforms 68 | (reduce 69 | (fn [transforms mk] 70 | (if (keyword? mk) 71 | (let [mkns (namespace mk) 72 | mkn (name mk)] 73 | (cond 74 | js-keys? (assoc transforms mk #(subs (str (keyword (or mkns (namespace %)) (name %))) 1)) 75 | (= mkn "keys") (assoc transforms mk #(keyword (or mkns (namespace %)) (name %))) 76 | (= mkn "syms") (assoc transforms mk #(list `quote (symbol (or mkns (namespace %)) (name %)))) 77 | (= mkn "strs") (assoc transforms mk str) 78 | :else transforms)) 79 | transforms)) 80 | {} 81 | (keys b))] 82 | (reduce 83 | (fn [bes entry] 84 | (reduce #(assoc %1 %2 ((val entry) %2)) 85 | (dissoc bes (key entry)) 86 | ((key entry) bes))) 87 | (dissoc b :as :or) 88 | transforms))] 89 | (if (seq bes) 90 | (let [bb (key (first bes)) 91 | bk (val (first bes)) 92 | local (if #?(:clj (instance? clojure.lang.Named bb) 93 | :cljs (cljs.core/implements? INamed bb)) 94 | (with-meta (symbol nil (name bb)) (meta bb)) 95 | bb) 96 | bv (if (contains? defaults local) 97 | (if js-keys? 98 | (list 'cljs.core/aget gmap bk (defaults local)) 99 | (list 'cljs.core/get gmap bk (defaults local))) 100 | (if js-keys? 101 | (list 'cljs.core/aget gmap bk) 102 | (list 'cljs.core/get gmap bk)))] 103 | (recur 104 | (if (or (keyword? bb) (symbol? bb)) ;(ident? bb) 105 | (-> ret (conj local bv)) 106 | (pb ret bb bv)) 107 | (next bes))) 108 | ret))))] 109 | (cond 110 | (symbol? b) (-> bvec (conj (if (namespace b) (symbol (name b)) b)) (conj v)) 111 | (keyword? b) (-> bvec (conj (symbol (name b))) (conj v)) 112 | (vector? b) (pvec bvec b v) 113 | (map? b) (pmap bvec b v) 114 | :else (throw 115 | #?(:clj (new Exception (str "Unsupported binding form: " b)) 116 | :cljs (new js/Error (str "Unsupported binding form: " b))))))) 117 | process-entry (fn [bvec b] (pb bvec (first b) (second b)))] 118 | (if (every? symbol? (map first bents)) 119 | bindings 120 | (if-let [kwbs (seq (filter #(keyword? (first %)) bents))] 121 | (throw 122 | #?(:clj (new Exception (str "Unsupported binding key: " (ffirst kwbs))) 123 | :cljs (new js/Error (str "Unsupported binding key: " (ffirst kwbs))))) 124 | (reduce process-entry [] bents))))) 125 | 126 | (defn core-let 127 | [bindings body] 128 | #_(assert-args let 129 | (vector? bindings) "a vector for its binding" 130 | (even? (count bindings)) "an even number of forms in binding vector") 131 | `(cljs.core/let* ~(destructure bindings) ~@body)) 132 | -------------------------------------------------------------------------------- /cherry/src/cherry/internal/loop.cljc: -------------------------------------------------------------------------------- 1 | ;; Adapted from CLJS core.cljc. Original copyright notice: 2 | 3 | ;; Copyright (c) Rich Hickey. All rights reserved. The use and distribution 4 | ;; terms for this software are covered by the Eclipse Public License 5 | ;; 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found in 6 | ;; the file epl-v10.html at the root of this distribution. By using this 7 | ;; software in any fashion, you are agreeing to be bound by the terms of this 8 | ;; license. You must not remove this notice, or any other, from this 9 | ;; software. 10 | 11 | (ns cherry.internal.loop 12 | (:refer-clojure :exclude [destructure]) 13 | (:require [cherry.internal.destructure :refer [destructure]])) 14 | 15 | (defn core-loop 16 | "Evaluates the exprs in a lexical context in which the symbols in 17 | the binding-forms are bound to their respective init-exprs or parts 18 | therein. Acts as a recur target." 19 | [_&form _&bindings bindings & body] 20 | #_(assert-args loop 21 | (vector? bindings) "a vector for its binding" 22 | (even? (count bindings)) "an even number of forms in binding vector") 23 | (let [db (destructure bindings)] 24 | (if (= db bindings) 25 | `(loop* ~bindings ~@body) 26 | (let [vs (take-nth 2 (drop 1 bindings)) 27 | bs (take-nth 2 bindings) 28 | gs (map (fn [b] (if (symbol? b) b (gensym))) bs) 29 | bfs (reduce (fn [ret [b v g]] 30 | (if (symbol? b) 31 | (conj ret g v) 32 | (conj ret g v b g))) 33 | [] (map vector bs vs gs))] 34 | `(let ~bfs 35 | (loop* ~(vec (interleave gs gs)) 36 | (let ~(vec (interleave bs gs)) 37 | ~@body))))))) 38 | -------------------------------------------------------------------------------- /cherry/src/com/reasonr/string.cljc: -------------------------------------------------------------------------------- 1 | ;;; string.clj -- functional string utilities for Clojure 2 | 3 | ;; by Stuart Sierra, http://stuartsierra.com/ 4 | ;; January 26, 2010 5 | 6 | ;; Copyright (c) Stuart Sierra, 2010. All rights reserved. The use 7 | ;; and distribution terms for this software are covered by the Eclipse 8 | ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 9 | ;; which can be found in the file epl-v10.html at the root of this 10 | ;; distribution. By using this software in any fashion, you are 11 | ;; agreeing to be bound by the terms of this license. You must not 12 | ;; remove this notice, or any other, from this software. 13 | 14 | 15 | ;; This file mostly contains methods that disappeared between clojure 1.2 and 1.3 16 | 17 | (ns com.reasonr.string 18 | (:refer-clojure :exclude (tail drop get))) 19 | 20 | (defn ^String tail 21 | "Returns the last n characters of s." 22 | [n ^String s] 23 | (if (< (count s) n) 24 | s 25 | (.substring s (- (count s) n)))) 26 | 27 | (defn ^String drop 28 | "Drops first n characters from s. Returns an empty string if n is 29 | greater than the length of s." 30 | [n ^String s] 31 | (if (< (count s) n) 32 | "" 33 | (.substring s n))) 34 | 35 | (defn ^String get 36 | "Gets the i'th character in string." 37 | {:deprecated "1.2"} 38 | [^String s i] 39 | (.charAt s i)) -------------------------------------------------------------------------------- /cherry/test-resources/test_project/equality_test.cljs: -------------------------------------------------------------------------------- 1 | (ns equality-test) 2 | 3 | (prn [(= 1 2) (= {:a 1} {:a 1})]) 4 | -------------------------------------------------------------------------------- /cherry/test-resources/test_project/macro_test.cljs: -------------------------------------------------------------------------------- 1 | (ns macro-test 2 | (:require-macros [macros :refer [do!]])) 3 | 4 | (prn (do! 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22)) 5 | -------------------------------------------------------------------------------- /cherry/test-resources/test_project/package.json: -------------------------------------------------------------------------------- 1 | {"dependencies": {"cherry-cljs": "../.."}} 2 | -------------------------------------------------------------------------------- /cherry/test-resources/test_project/src/macros.cljc: -------------------------------------------------------------------------------- 1 | (ns macros) 2 | 3 | (defmacro do! [& xs] 4 | (last xs)) 5 | -------------------------------------------------------------------------------- /cherry/test/cherry/jsx_test.cljs: -------------------------------------------------------------------------------- 1 | (ns cherry.jsx-test 2 | (:require 3 | ["@babel/core" :refer [transformSync]] 4 | ["react" :as React] 5 | [cherry.test-utils :refer [jss!]] 6 | [clojure.test :as t :refer [deftest]] 7 | [goog.object :as gobject])) 8 | 9 | (gobject/set js/global "React" React) 10 | 11 | (defn test-jsx [s] 12 | (let [expr (jss! s) 13 | code (:code (js->clj (transformSync expr #js {:presets #js ["@babel/preset-react"]}) :keywordize-keys true))] 14 | (js/eval code))) 15 | 16 | (deftest jsx-test 17 | (test-jsx "#jsx [:a {:href \"http://foo.com\"}]") 18 | (test-jsx "#jsx [:div #js {:dangerouslySetInnerHTML #js {:_html \"Hello\"}}]") 19 | (test-jsx "(defn App [] (let [x 1] #jsx [:div {:id x}]))") 20 | (test-jsx "(let [classes #js {:classes \"foo bar\"}] #jsx [:div {:className (:classes classes)}])") 21 | (test-jsx "(defn App [^:js {:keys [x]}] #jsx [:span x]) #jsx [App #js {:x 1}]") 22 | (test-jsx " 23 | (ns foo (:require [\"foo\" :as foo])) 24 | (defn App [] #jsx [foo/c #js {:x 1}]) ")) 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /cherry/test/cherry/test_utils.cljs: -------------------------------------------------------------------------------- 1 | (ns cherry.test-utils 2 | (:require 3 | ["cherry-cljs/cljs.core.js" :as cl] 4 | [cherry.compiler :as cherry] 5 | [squint.compiler-common :refer [*target*]] 6 | [clojure.test :as t])) 7 | 8 | (def old-fail (get-method t/report [:cljs.test/default :fail])) 9 | 10 | (defmethod t/report [:cljs.test/default :fail] [m] 11 | (set! js/process.exitCode 1) 12 | (old-fail m)) 13 | 14 | (defmethod t/report [:cljs.test/default :begin-test-var] [m] 15 | (let [var (:var m) 16 | name (:name (meta var))] 17 | (println "====" name))) 18 | 19 | (def old-error (get-method t/report [:cljs.test/default :fail])) 20 | 21 | (defmethod t/report [:cljs.test/default :error] [m] 22 | (set! js/process.exitCode 1) 23 | (old-error m)) 24 | 25 | (doseq [k (js/Object.keys cl)] 26 | (aset js/globalThis k (aget cl k))) 27 | 28 | (defn jss! [expr] 29 | (if (string? expr) 30 | (:body (cherry/compile-string* expr)) 31 | (binding [*target* :cherry] 32 | (cherry/transpile-form expr)))) 33 | 34 | (defn js! [expr] 35 | (let [js (jss! expr)] 36 | [(js/eval js) js])) 37 | 38 | (defn jsv! [expr] 39 | (first (js! expr))) 40 | -------------------------------------------------------------------------------- /cherry/test/node_test.cljs: -------------------------------------------------------------------------------- 1 | (ns node-test 2 | (:require 3 | ["node:assert/strict" :as assert] 4 | ["node:test" :refer [describe]])) 5 | 6 | (defn eq [x y] 7 | (assert/deepEqual (clj->js x) (clj->js y))) 8 | 9 | (defn ^:async foo [] 10 | (try (let [^:js {:keys [results]} (js/await (js/import "../corpus/fns.mjs"))] 11 | (eq [3 3 3 0 1 2] @results)) 12 | (catch :default e 13 | (assert/fail (ex-message e))))) 14 | 15 | (describe 16 | "fns works" (assert/doesNotReject foo)) 17 | -------------------------------------------------------------------------------- /compiler-common/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :deps {borkdude/edamame {:mvn/version "1.0.0"} 3 | babashka/process {:mvn/version "0.1.7"} 4 | org.babashka/cli {:mvn/version "0.4.37"} 5 | org.babashka/sci {:mvn/version "0.3.32"}}} 6 | -------------------------------------------------------------------------------- /squint/.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /squint/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((clojure-mode . ((cider-clojure-cli-global-options . "-A:nextjournal/clerk")))) 2 | -------------------------------------------------------------------------------- /squint/.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Pull requests should be targeted at 2 | [compiler-common](https://github.com/squint-cljs/compiler-common) and will be 3 | merged here automatically after being accepted in compiler-common. 4 | -------------------------------------------------------------------------------- /squint/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, macOS-latest, windows-latest] 10 | 11 | runs-on: ${{ matrix.os }} 12 | 13 | steps: 14 | - name: "Checkout code" 15 | uses: "actions/checkout@v2" 16 | with: 17 | submodules: true 18 | 19 | - name: Prepare java 20 | uses: actions/setup-java@v2 21 | with: 22 | distribution: "adopt" 23 | java-version: 11 24 | 25 | - name: "Restore Cache" 26 | uses: "actions/cache@v3" 27 | with: 28 | path: "~/.m2/repository" 29 | key: "${{ runner.os }}-deps-${{ hashFiles('deps.edn') }}" 30 | restore-keys: "${{ runner.os }}-deps-" 31 | 32 | - name: Setup Clojure 33 | uses: DeLaGuardo/setup-clojure@9.3 34 | with: 35 | cli: 1.10.3.1040 36 | bb: latest 37 | 38 | - name: Run tests 39 | run: | 40 | npm install 41 | bb test:node 42 | bb test:bb 43 | bb test:clj 44 | -------------------------------------------------------------------------------- /squint/.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache 2 | .cache 3 | package-lock.json 4 | target 5 | node_modules 6 | .shadow-cljs 7 | lib 8 | report.html 9 | test/scratch.js 10 | dist 11 | .work 12 | test/scratch.cjs 13 | *.mjs 14 | !/examples/wordle/wordle.mjs 15 | test/*.mjs 16 | example.mjs 17 | /examples/vite/vite-project/cherry.mjs 18 | .lein-repl-history 19 | scratch.cljs 20 | .vscode 21 | .nrepl-port 22 | examples/quickjs/hello 23 | .repl 24 | .clerk 25 | public 26 | .test 27 | scratch.js 28 | compiler-common 29 | -------------------------------------------------------------------------------- /squint/.nojekyll: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /squint/.prettierignore: -------------------------------------------------------------------------------- 1 | /.cpcache 2 | /.cache 3 | /target 4 | /node_modules 5 | /.shadow-cljs 6 | /lib 7 | /dist 8 | /.work 9 | *.mjs 10 | /.lein-repl-history 11 | /.vscode -------------------------------------------------------------------------------- /squint/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "quoteProps": "as-needed", 8 | "jsxSingleQuote": false, 9 | "trailingComma": "es5", 10 | "bracketSpacing": true, 11 | "bracketSameLine": false, 12 | "arrowParens": "always", 13 | "proseWrap": "preserve", 14 | "htmlWhitespaceSensitivity": "css", 15 | "endOfLine": "lf", 16 | "embeddedLanguageFormatting": "auto", 17 | "overrides": [ 18 | { 19 | "files": [ 20 | "*.css" 21 | ], 22 | "options": { 23 | "singleQuote": false 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /squint/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | [Squint](https://github.com/squint-cljs/squint): ClojureScript syntax to JavaScript compiler 4 | 5 | ## 0.0.7 6 | 7 | [#274](https://github.com/squint-cljs/squint/issues/274): fix logic precedence by wrapping in parens 8 | 9 | ## 0.0.6 10 | 11 | Add preliminary Node.js API in `node.js` 12 | 13 | ## 0.0.5 14 | 15 | - Support `{:& more :foo :bar}` syntax in JSX to spread the more map into the props, inspired by [helix](https://github.com/lilactown/helix) 16 | 17 | ## 0.0.4 18 | 19 | - Add `zero?` `pos?` and `neg?` core functions 20 | 21 | ## 0.0.3 22 | 23 | - Fix `boolean` core function export 24 | 25 | ## 0.0.2 26 | 27 | - Escape boolean in JSX attribute 28 | - Add `boolean` core function 29 | 30 | ## 0.0.1 31 | 32 | - Fix `+` symbol import by bumping shadow-cljs 33 | - Move `@babel/preset-react` dependency to dev dependency 34 | 35 | ## 0.0.0-alpha.52 36 | 37 | - Fix async/await + variadiac arguments 38 | - Fix namespaced component in JSX 39 | 40 | ## 0.0.0-alpha.51 41 | 42 | - Change default extension back to `.mjs`. Use `--extension js` to change this to `.js`. 43 | 44 | ## 0.0.0-alpha.50 45 | 46 | - Fix rendering of number attributes in JSX 47 | 48 | ## 0.0.0-alpha.49 49 | 50 | - Support `--extension` option to set extension for JS files 51 | - Change default extension from `.mjs` to `.js` 52 | -------------------------------------------------------------------------------- /squint/README.md: -------------------------------------------------------------------------------- 1 | ## Squint 2 | 3 | Squint is an experimental ClojureScript syntax to JavaScript compiler. 4 | 5 | Squint is not intended as a replacement for ClojureScript but as a tool to 6 | target JS for anything you would not use ClojureScript for, for whatever reason: 7 | performance, bundle size, ease of interop, etc. 8 | 9 | > :warning: This project should be considered experimental and may still undergo 10 | > breaking changes. It's fine to use it for non-critical projects but don't use 11 | > it in production yet. 12 | 13 | Squint was previously called ClavaScript and the name may appear in some places 14 | in this README. Please file an issue or PR if you spot one. 15 | 16 | ## Quickstart 17 | 18 | Although it's early days, you're welcome to try out `squint` and submit issues. 19 | 20 | ``` shell 21 | $ mkdir squint-test && cd squint-test 22 | $ npm init -y 23 | $ npm install squint-cljs@latest 24 | ``` 25 | 26 | Create a `.cljs` file, e.g. `example.cljs`: 27 | 28 | ``` clojure 29 | (ns example 30 | (:require ["fs" :as fs] 31 | ["url" :refer [fileURLToPath]])) 32 | 33 | (println (fs/existsSync (fileURLToPath js/import.meta.url))) 34 | 35 | (defn foo [{:keys [a b c]}] 36 | (+ a b c)) 37 | 38 | (println (foo {:a 1 :b 2 :c 3})) 39 | ``` 40 | 41 | Then compile and run (`run` does both): 42 | 43 | ``` 44 | $ npx squint run example.cljs 45 | true 46 | 6 47 | ``` 48 | 49 | Run `npx squint --help` to see all command line options. 50 | 51 | ## Why Squint 52 | 53 | Squint lets you write CLJS syntax but emits small JS output, while still having 54 | parts of the CLJS standard library available (ported to mutable data structures, 55 | so with caveats). This may work especially well for projects e.g. that you'd 56 | like to deploy on CloudFlare workers, node scripts, Github actions, etc. that 57 | need the extra performance, startup time and/or small bundle size. 58 | 59 | ## Differences with ClojureScript 60 | 61 | - Squint does not protect you in any way from the pitfalls of JS with regards to truthiness, mutability and equality 62 | - There is no CLJS standard library. The `"squint-cljs/core.js"` module has similar JS equivalents 63 | - Keywords are translated into strings 64 | - Maps, sequences and vectors are represented as mutable objects and arrays 65 | - Most functions return arrays and objects, not custom data structures 66 | - Supports async/await:`(def x (js/await y))`. Async functions must be marked 67 | with `^:async`: `(defn ^:async foo [])`. 68 | - `assoc!`, `dissoc!`, `conj!`, etc. perform in place mutation on objects 69 | - `assoc`, `dissoc`, `conj`, etc. return a new shallow copy of objects 70 | - `println` is a synonym for `console.log` 71 | - `pr-str` and `prn` coerce values to a string using `JSON.stringify` 72 | 73 | ### Seqs 74 | 75 | Squint does not implement Clojure seqs. Instead it uses the JavaScript 76 | [iteration 77 | protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) 78 | to work with collections. What this means in practice is the following: 79 | 80 | - `seq` takes a collection and returns an Iterable of that collection, or nil if it's empty 81 | - `iterable` takes a collection and returns an Iterable of that collection, even if it's empty 82 | - `seqable?` can be used to check if you can call either one 83 | 84 | Most collections are iterable already, so `seq` and `iterable` will simply 85 | return them; an exception are objects created via `{:a 1}`, where `seq` and 86 | `iterable` will return the result of `Object.entries`. 87 | 88 | `first`, `rest`, `map`, `reduce` et al. call `iterable` on the collection before 89 | processing, and functions that typically return seqs instead return an array of 90 | the results. 91 | 92 | #### Memory usage 93 | 94 | With respect to memory usage: 95 | 96 | - Lazy seqs in squint are built on generators. They do not cache their results, so every time they are consumed, they are re-calculated from scratch. 97 | - Lazy seq function results hold on to their input, so if the input contains resources that should be garbage collected, it is recommended to limit their scope and convert their results to arrays when leaving the scope: 98 | 99 | 100 | ``` clojure 101 | (js/global.gc) 102 | 103 | (println (js/process.memoryUsage)) 104 | 105 | (defn doit [] 106 | (let [x [(-> (new Array 10000000) 107 | (.fill 0)) :foo :bar] 108 | ;; Big array `x` is still being held on to by `y`: 109 | y (rest x)] 110 | (println (js/process.memoryUsage)) 111 | (vec y))) 112 | 113 | (println (doit)) 114 | 115 | (js/global.gc) 116 | ;; Note that big array is garbage collected now: 117 | (println (js/process.memoryUsage)) 118 | ``` 119 | 120 | Run the above program with `node --expose-gc ./node_cli mem.cljs` 121 | 122 | ## JSX 123 | 124 | You can produce JSX syntax using the `#jsx` tag: 125 | 126 | ``` clojure 127 | #jsx [:div "Hello"] 128 | ``` 129 | 130 | produces: 131 | 132 | ``` html 133 |
Hello
134 | ``` 135 | 136 | and outputs the `.jsx` extension automatically. 137 | 138 | You can use Clojure expressions within `#jsx` expressions: 139 | 140 | ``` clojure 141 | (let [x 1] #jsx [:div (inc x)]) 142 | ``` 143 | 144 | Note that when using a Clojure expression, you escape the JSX context so when you need to return more JSX, use the `#jsx` once again: 145 | 146 | ``` clojure 147 | (let [x 1] 148 | #jsx [:div 149 | (if (odd? x) 150 | #jsx [:span "Odd"] 151 | #jsx [:span "Even"])]) 152 | ``` 153 | 154 | To pass props, you can use `:&`: 155 | 156 | ``` clojure 157 | (let [props {:a 1}] 158 | #jsx [App {:& props}]) 159 | ``` 160 | 161 | See an example of an application using JSX [here](https://squint-cljs.github.io/demos/squint/solidjs/) ([source](https://github.com/squint-cljs/squint/blob/main/examples/solidjs/src/App.cljs)). 162 | 163 | ## Async/await 164 | 165 | squint supports `async/await`: 166 | 167 | ``` clojure 168 | (defn ^:async foo [] (js/Promise.resolve 10)) 169 | 170 | (def x (js/await (foo))) 171 | 172 | (println x) ;;=> 10 173 | ``` 174 | 175 | ## Roadmap 176 | 177 | In arbitrary order, these features are planned: 178 | 179 | - Macros 180 | - REPL 181 | - Protocols 182 | 183 | ## Presentations 184 | 185 | See [slides](https://www.dropbox.com/s/955jgzy6hgpx67r/dcd2022-cljs-reimagined.pdf?dl=0) of a presentation given at Dutch Clojure Days 2022 about cherry and squint. 186 | 187 | ## Development 188 | 189 | Development happens in [compiler-common](https://github.com/squint-cljs/compiler-common). 190 | 191 | ## Core team 192 | 193 | The core team consists of: 194 | 195 | - Michiel Borkent ([@borkdude](https://github.com/borkdude)) 196 | - Will Acton ([@lilactown](https://github.com/lilactown)) 197 | - Cora Sutton ([@corasaurus-hex](https://github.com/corasaurus-hex)) 198 | 199 | License 200 | ======= 201 | 202 | Squint is licensed under the EPL, the same as Clojure core and [Scriptjure](https://github.com/arohner/scriptjure). See epl-v10.html in the root directory for more information. 203 | -------------------------------------------------------------------------------- /squint/bb.edn: -------------------------------------------------------------------------------- 1 | {:min-bb-version "0.9.161" 2 | :deps {borkdude/rewrite-edn {:mvn/version "0.4.6"}} 3 | :paths ["src" "resources" "bb"] 4 | :tasks {:requires ([tasks :as t]) 5 | build (t/build-squint-npm-package) 6 | publish (t/publish) 7 | dev (t/watch-squint) 8 | test:node {:doc "Run tests in Node.js" 9 | :task (t/test-squint)} 10 | test:bb {:doc "Run tests in bb" 11 | :extra-deps {this/project {:local/root "."}} 12 | :extra-paths ["test"] 13 | :task (exec 'squint.compiler-test/run-tests)} 14 | test:clj {:doc "Run tests in Clojure" 15 | :task (clojure "-X:test")} 16 | bump-common (exec 'tasks/bump-compiler-common)} 17 | } 18 | -------------------------------------------------------------------------------- /squint/bb/node_repl_tests.clj: -------------------------------------------------------------------------------- 1 | (ns node-repl-tests 2 | (:require 3 | [babashka.fs :as fs] 4 | [babashka.process :as p :refer [process]] 5 | [clojure.string :as str] 6 | [clojure.test :as t :refer [deftest is]])) 7 | 8 | (defn repl-process 9 | [input dir opts] 10 | (process (into ["node" (str (fs/absolutize "node_cli.js")) "repl"] (:cmd opts)) 11 | (merge {:dir (or dir ".") 12 | :out :string 13 | :in input 14 | :err :inherit} 15 | opts))) 16 | 17 | (defn repl 18 | ([input] (repl input nil)) 19 | ([input dir] (repl input dir nil)) 20 | ([input dir opts] 21 | (-> (repl-process input dir opts) 22 | p/check))) 23 | 24 | (deftest repl-test 25 | (is (str/includes? (:out (repl "1")) "1\n")) 26 | (is (str/includes? (:out (repl "\"foo\"")) "foo\n")) 27 | (is (str/includes? (:out (repl ":foo")) "foo\n")) 28 | (is (str/includes? (:out (repl "[1 2 3]")) "[ 1, 2, 3 ]\n")) 29 | (is (str/includes? (:out (repl "(+ 1 2 3)")) "6\n")) 30 | (is (str/includes? (:out (repl "(ns foo (:require [\"fs\" :as fs])) (fs/existsSync \".\")")) "true")) 31 | (is (str/includes? (:out (repl "(defn foo [x] x) (foo 1)")) "1")) 32 | (is (str/includes? (:out (repl "\"foo\"")) "foo")) 33 | (is (str/includes? (:out (repl "(ns foo (:require [\"playwright$default\" :as pw])) (let [chrome pw/chromium] (when (some? chrome) :success))")) "success")) 34 | (is (str/includes? (:out (repl "(require '[\"playwright$default\" :as pw]) (let [chrome pw/chromium] (when (some? chrome) :success))")) "success"))) 35 | 36 | (defn run-tests [_] 37 | (let [{:keys [fail error]} 38 | (t/run-tests 'node-repl-tests)] 39 | (when (pos? (+ fail error)) 40 | (throw (ex-info "Tests failed" {:babashka/exit 1}))))) 41 | -------------------------------------------------------------------------------- /squint/bb/tasks.clj: -------------------------------------------------------------------------------- 1 | (ns tasks 2 | (:require 3 | [babashka.fs :as fs] 4 | [babashka.process :refer [shell]] 5 | [cheshire.core :as json] 6 | [clojure.edn :as edn] 7 | [clojure.java.io :as io] 8 | [node-repl-tests] 9 | [clojure.string :as str] 10 | [babashka.curl :as curl])) 11 | 12 | (defn munge* [s reserved] 13 | (let [s (str (munge s))] 14 | (if (contains? reserved s) 15 | (str s "$") 16 | s))) 17 | 18 | (defn shadow-extra-config 19 | [] 20 | (let [core-config (edn/read-string (slurp (io/resource "squint/cljs.core.edn"))) 21 | reserved (edn/read-string (slurp (io/resource "squint/js_reserved.edn"))) 22 | vars (:vars core-config) 23 | ks (map #(symbol (munge* % reserved)) vars) 24 | vs (map #(symbol "cljs.core" (str %)) vars) 25 | core-map (zipmap ks vs) 26 | core-map (assoc core-map 'goog_typeOf 'goog/typeOf)] 27 | {:modules 28 | {:cljs_core {:exports core-map}}})) 29 | 30 | (def test-config 31 | '{:compiler-options {:load-tests true} 32 | :modules {:squint_tests {:init-fn squint.compiler-test/init 33 | :depends-on #{:compiler}}}}) 34 | 35 | (defn shadow-extra-test-config [] 36 | (merge-with 37 | merge 38 | (shadow-extra-config) 39 | test-config)) 40 | 41 | (defn bump-core-vars [] 42 | (let [core-vars (:out (shell {:out :string} 43 | "node --input-type=module -e 'import * as squint from \"squint-cljs/core.js\";console.log(JSON.stringify(Object.keys(squint)))'")) 44 | parsed (apply sorted-set (map symbol (json/parse-string core-vars)))] 45 | (spit "resources/squint/core.edn" (with-out-str 46 | ((requiring-resolve 'clojure.pprint/pprint) 47 | parsed))))) 48 | 49 | (defn build-squint-npm-package [] 50 | (fs/create-dirs ".work") 51 | (fs/delete-tree "lib") 52 | (fs/delete-tree ".shadow-cljs") 53 | (bump-core-vars) 54 | (spit ".work/config-merge.edn" (shadow-extra-config)) 55 | (shell "npx shadow-cljs --config-merge .work/config-merge.edn release squint")) 56 | 57 | (defn publish [] 58 | (build-squint-npm-package) 59 | (run! fs/delete (fs/glob "lib" "*.map")) 60 | (shell "npm publish")) 61 | 62 | (defn watch-squint [] 63 | (fs/create-dirs ".work") 64 | (fs/delete-tree ".shadow-cljs/builds/squint/dev/ana/squint") 65 | (spit ".work/config-merge.edn" (shadow-extra-test-config)) 66 | (bump-core-vars) 67 | (shell "npx shadow-cljs --aliases :dev --config-merge .work/config-merge.edn watch squint")) 68 | 69 | (defn test-squint [] 70 | (fs/create-dirs ".work") 71 | (spit ".work/config-merge.edn" (shadow-extra-test-config)) 72 | (bump-core-vars) 73 | (shell "npx shadow-cljs --config-merge .work/config-merge.edn release squint") 74 | (shell "node lib/squint_tests.js") 75 | (node-repl-tests/run-tests {})) 76 | 77 | (defn bump-compiler-common [{:keys [sha]}] 78 | (let [rdissoc (requiring-resolve 'borkdude.rewrite-edn/dissoc) 79 | rupdate-in (requiring-resolve 'borkdude.rewrite-edn/update-in) 80 | deps (slurp "deps.edn") 81 | nodes ((requiring-resolve 'borkdude.rewrite-edn/parse-string) deps) 82 | nodes ((requiring-resolve 'borkdude.rewrite-edn/assoc-in) nodes [:deps 'io.github.squint-cljs/compiler-common :git/sha] sha) 83 | nodes ((requiring-resolve 'borkdude.rewrite-edn/assoc-in) nodes [:deps 'io.github.squint-cljs/compiler-common :deps/root] "compiler-common") 84 | nodes (rupdate-in nodes [:deps 'io.github.squint-cljs/compiler-common] 85 | rdissoc :local/root) 86 | deps (str nodes)] 87 | (spit "deps.edn" deps))) 88 | 89 | (defn pull-request [{:keys [github-token branch]}] 90 | (let [headers {"Accept" "application/vnd.github+json" 91 | "Authorization" (str "Bearer " github-token) 92 | "X-GitHub-Api-Version" "2022-11-28"} 93 | _resp (curl/post "https://api.github.com/repos/squint-cljs/squint/pulls" 94 | {:headers headers 95 | :body (json/generate-string {:title "Bump common" 96 | :body "Bump common" 97 | :head branch 98 | :base "main"})}) 99 | #_#_#_#_#_#_#_#_body (:body resp) 100 | body (json/parse-string body true) 101 | url (:issue_url body) 102 | comment-url (str url "/comments")] 103 | #_(curl/post comment-url 104 | {:headers headers 105 | :body (json/generate-string {:body "@borkdude: please merge!"})}))) 106 | -------------------------------------------------------------------------------- /squint/cljs.core.js: -------------------------------------------------------------------------------- 1 | export * from './lib/cljs_core.js'; 2 | -------------------------------------------------------------------------------- /squint/compile.clj: -------------------------------------------------------------------------------- 1 | (require '[cherry.compiler :refer [compile-string*]]) 2 | 3 | (let [{:keys [imports exports body]} 4 | (compile-string* (slurp *in*))] 5 | (str imports exports body)) 6 | -------------------------------------------------------------------------------- /squint/deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources" "compiler-common"] 2 | :deps {borkdude/edamame {:mvn/version "1.0.0"} 3 | babashka/process {:mvn/version "0.1.7"} 4 | org.babashka/cli {:mvn/version "0.4.37"} 5 | org.babashka/sci {:mvn/version "0.6.37"} 6 | io.github.squint-cljs/compiler-common {:local/root "../compiler-common"}} 7 | :aliases 8 | {:dev {} 9 | :cljs {:extra-paths ["test"] 10 | :extra-deps {thheller/shadow-cljs {:mvn/version "2.20.15"}}} 11 | :test ;; added by neil 12 | {:extra-paths ["test"] 13 | :extra-deps {io.github.cognitect-labs/test-runner 14 | {:git/tag "v0.5.0" :git/sha "b3fd0d2"} 15 | babashka/fs {:mvn/version "0.1.6"}} 16 | :main-opts ["-m" "cognitect.test-runner"] 17 | :exec-fn cognitect.test-runner.api/test} 18 | :nextjournal/clerk {:extra-deps {io.github.nextjournal/clerk {:mvn/version "0.10.562"}} 19 | :exec-fn nextjournal.clerk/build-static-app! 20 | :exec-args {:paths ["test/squint/clerk.clj"]}}} 21 | } 22 | -------------------------------------------------------------------------------- /squint/examples/bun/http.cljs: -------------------------------------------------------------------------------- 1 | (ns bun.http) 2 | 3 | (def default 4 | {:port 3000 5 | :fetch (fn [_req] 6 | (js/Response. "Welcome to Bun!"))}) 7 | -------------------------------------------------------------------------------- /squint/examples/ink/example.cljs: -------------------------------------------------------------------------------- 1 | (ns ink.example 2 | (:require 3 | ["ink" :refer [render Text]] 4 | ["react" :as react])) 5 | 6 | (defn Counter [] 7 | (let [[counter setCounter] (react/useState 0)] 8 | (react/useEffect 9 | (fn [] 10 | (let [timer (js/setInterval 11 | (fn [] 12 | (setCounter (inc counter))) 13 | 1000)] 14 | (fn [] 15 | (js/clearInterval timer))))) 16 | (react/createElement Text nil counter " tests passed"))) 17 | 18 | (render (react/createElement Counter)) 19 | -------------------------------------------------------------------------------- /squint/examples/ink/package.json: -------------------------------------------------------------------------------- 1 | {"dependencies":{"ink":"^3.2.0", 2 | "squint-cljs":"^0.0.0-alpha.52"}} 3 | -------------------------------------------------------------------------------- /squint/examples/quickjs/README.md: -------------------------------------------------------------------------------- 1 | # QuickJS 2 | 3 | [QuickJS](https://bellard.org/quickjs/) is a small and embeddable Javascript engine. You can use it to produce standalone executables from JS scripts. 4 | 5 | To install QuickJS on macOS, you can use [brew](https://formulae.brew.sh/formula/quickjs). 6 | 7 | Let's compile the `main.cljs` script using ClavaScript first: 8 | 9 | ``` 10 | $ npx clava main.cljs 11 | [clava] Compiling CLJS file: main.cljs 12 | [clava] Wrote JS file: main.mjs 13 | ``` 14 | 15 | Unfortunately, QuickJS doesn't load dependencies from NPM. Given the script `main.cljs`, we'll first have to bundle it, e.g. using esbuild: 16 | 17 | ``` 18 | $ npx esbuild main.mjs --bundle --minify --platform=node --outfile=main.min.mjs --format=esm 19 | 20 | main.min.mjs 842b 21 | 22 | ⚡ Done in 11ms 23 | ``` 24 | 25 | Now we are ready to run the script with QuickJS: 26 | 27 | ``` 28 | $ qjs main.min.mjs 29 | Hello world! 30 | [[1,4,7],[2,5,8],[3,6,9]] 31 | ``` 32 | 33 | Finally, to create a standalone executable: 34 | 35 | ``` 36 | $ qjsc -o hello main.min.mjs 37 | ``` 38 | 39 | Now we are ready to execute it: 40 | 41 | ``` 42 | $ ./hello 43 | Hello world! 44 | [[1,4,7],[2,5,8],[3,6,9]] 45 | ``` 46 | 47 | The executable is about 750kb. 48 | -------------------------------------------------------------------------------- /squint/examples/quickjs/main.cljs: -------------------------------------------------------------------------------- 1 | (ns main) 2 | 3 | (println "Hello world!") 4 | 5 | (prn (apply map vector [[1 2 3] 6 | [4 5 6] 7 | [7 8 9]])) 8 | 9 | -------------------------------------------------------------------------------- /squint/examples/solidjs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /squint/examples/solidjs/README.md: -------------------------------------------------------------------------------- 1 | ## Usage 2 | 3 | Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`. 4 | 5 | This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template. 6 | 7 | ```bash 8 | $ npm install # or pnpm install or yarn install 9 | ``` 10 | 11 | ### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs) 12 | 13 | ## Available Scripts 14 | 15 | In the project directory, you can run: 16 | 17 | ### `npm dev` or `npm start` 18 | 19 | Runs the app in the development mode.
20 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 21 | 22 | The page will reload if you make edits.
23 | 24 | ### `npm run build` 25 | 26 | Builds the app for production to the `dist` folder.
27 | It correctly bundles Solid in production mode and optimizes the build for the best performance. 28 | 29 | The build is minified and the filenames include the hashes.
30 | Your app is ready to be deployed! 31 | 32 | ## Deployment 33 | 34 | You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.) 35 | -------------------------------------------------------------------------------- /squint/examples/solidjs/bb.edn: -------------------------------------------------------------------------------- 1 | {:tasks 2 | {dev (shell "npx vite dev") 3 | build (do (shell "npx vite build --base=/demos/squint/solidjs/") 4 | (shell "bash -c 'cp -R dist/* ../../../squint-demos/squint/solidjs'"))}} 5 | -------------------------------------------------------------------------------- /squint/examples/solidjs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Solid App 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /squint/examples/solidjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-template-solid", 3 | "version": "0.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "vite", 7 | "dev": "vite", 8 | "build": "vite build", 9 | "serve": "vite preview" 10 | }, 11 | "license": "MIT", 12 | "devDependencies": { 13 | "vite": "^3.0.0", 14 | "vite-plugin-solid": "^2.3.0" 15 | }, 16 | "dependencies": { 17 | "solid-js": "^1.4.7" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /squint/examples/solidjs/src/App.cljs: -------------------------------------------------------------------------------- 1 | (ns App 2 | (:require ["./App.module.css$default" :as styles] 3 | ["./logo.svg$default" :as logo] 4 | ["solid-js" :refer [createSignal]] 5 | [squint.string :as str])) 6 | 7 | (defn Counter [{:keys [init]}] 8 | (let [[counter setCount] (createSignal init)] 9 | #jsx [:div 10 | "Count:" (str/join " " (range (counter))) 11 | [:div 12 | [:button 13 | {:onClick (fn [] 14 | (setCount (inc (counter))))} 15 | "Click me"]]])) 16 | 17 | (defn App [] 18 | #jsx [:div {:class styles.App} 19 | [:header {:class styles.header} 20 | [:img {:src logo 21 | :class styles.logo 22 | :alt "logo"}] 23 | [Counter {:init 5}]]]) 24 | 25 | (def default App) 26 | -------------------------------------------------------------------------------- /squint/examples/solidjs/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { } from './App.module.css' 2 | import { } from './logo.svg' 3 | import { join as str_join } from 'squint-cljs/string.js' 4 | import { get, nth, range } from 'squint-cljs/core.js' 5 | import styles from './App.module.css'; 6 | import logo from './logo.svg'; 7 | import { createSignal } from 'solid-js'; 8 | var Counter = function (p__1) { 9 | let map__23 = p__1; 10 | let init4 = get(map__23, "init"); 11 | let vec__58 = createSignal(init4); 12 | let counter9 = nth(vec__58, 0, null); 13 | let setCount10 = nth(vec__58, 1, null); 14 | return
Count: {str_join(" ", range(counter9()))}
; 17 | } 18 | ; 19 | var App = function () { 20 | return
logo
; 21 | } 22 | ; 23 | var default$ = App 24 | ; 25 | 26 | export { Counter, App } 27 | export default default$ 28 | -------------------------------------------------------------------------------- /squint/examples/solidjs/src/App.module.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .logo { 6 | animation: logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .link { 23 | color: #b318f0; 24 | } 25 | 26 | @keyframes logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /squint/examples/solidjs/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/compiler-common/78772abb321208e4a7bbbcf85147d48d47f647d2/squint/examples/solidjs/src/assets/favicon.ico -------------------------------------------------------------------------------- /squint/examples/solidjs/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /squint/examples/solidjs/src/index.jsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { render } from 'solid-js/web'; 3 | 4 | import './index.css'; 5 | import App from './App'; 6 | 7 | render(() => , document.getElementById('root')); 8 | -------------------------------------------------------------------------------- /squint/examples/solidjs/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /squint/examples/solidjs/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import solidPlugin from 'vite-plugin-solid'; 3 | 4 | export default defineConfig({ 5 | plugins: [solidPlugin()], 6 | server: { 7 | port: 3000, 8 | }, 9 | build: { 10 | target: 'esnext', 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /squint/examples/windowjs/README.md: -------------------------------------------------------------------------------- 1 | # WindowJS 2 | 3 | Window.js is an open-source Javascript runtime for desktop graphics programming. 4 | 5 | To run the example in this directory: 6 | 7 | ``` 8 | $ npx clava hello.cljs && ~/Downloads/windowjs hello.mjs 9 | ``` 10 | 11 | Replace `~/Downloads/windowsjs` to the location you installed `windowjs`. 12 | 13 | See the a [demo tweet](https://twitter.com/borkdude/status/1564560835145617408) here. 14 | -------------------------------------------------------------------------------- /squint/examples/windowjs/hello.cljs: -------------------------------------------------------------------------------- 1 | ;; translated from https://github.com/windowjs/windowjs/blob/main/examples/hello.js 2 | 3 | (set! js/window.title "Hello") 4 | 5 | (def canvas js/window.canvas) 6 | (def keep-drawing true) 7 | 8 | (js/window.addEventListener 9 | :click (fn [event] 10 | (js/console.log "Clicked on" event.x event.y))) 11 | 12 | (declare draw) 13 | 14 | (js/window.addEventListener 15 | :keydown (fn [event] 16 | (js/console.log "Key down:" event.key) 17 | (when (= event.key " ") 18 | (set! keep-drawing 19 | (not keep-drawing)) 20 | (when keep-drawing 21 | (js/requestAnimationFrame draw))))) 22 | 23 | (defn draw [] 24 | (set! canvas.fillStyle "#023047") 25 | (canvas.fillRect 0 0 canvas.width canvas.height) 26 | (set! canvas.fillStyle "#eb005a") 27 | (canvas.fillRect 100 100 200 200) 28 | (set! canvas.fillStyle "darkorange") 29 | (let [y (/ canvas.height 2) 30 | w canvas.width 31 | t (Math.cos (/ (js/performance.now) 300)) 32 | x (+ (/ w 2) (* (/ w 4) t))] 33 | (canvas.save) 34 | (canvas.translate x y) 35 | (canvas.rotate (/ (* t Math/PI) 2)) 36 | (canvas.fillRect -100 -100 200 200) 37 | (canvas.restore)) 38 | (when keep-drawing 39 | (js/requestAnimationFrame draw))) 40 | 41 | (js/requestAnimationFrame draw) 42 | -------------------------------------------------------------------------------- /squint/examples/wordle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wordle! 5 | 6 | 7 | 8 | 9 | 17 | 18 | 40 | 41 | 42 |

Wordle by @oxalorg. 43 | Play the game by typing letters and then hit return. The source code is taken from here and compiled using npx clava wordle.cljs. 44 |

45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /squint/examples/wordle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "cherry-cljs": "^0.0.0-alpha.29" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /squint/examples/wordle/wordle.cljs: -------------------------------------------------------------------------------- 1 | (ns wordle) 2 | 3 | (def board-state (atom [])) 4 | (def counter (atom 0)) 5 | (def attempt (atom 0)) 6 | (def word-of-the-day (atom "hello")) 7 | 8 | (defn write-letter [cell letter] 9 | (set! (.-textContent cell) letter)) 10 | 11 | (defn make-cell [] 12 | (let [cell (js/document.createElement "div")] 13 | (set! (.-className cell) "cell") 14 | cell)) 15 | 16 | (defn make-board [n] 17 | (let [board (js/document.createElement "div")] 18 | (set! (.-className board) "board") 19 | ;; adding cells 20 | (doseq [_ (range n)] 21 | (let [cell (make-cell)] 22 | (swap! board-state conj cell) 23 | (.appendChild board cell))) 24 | board)) 25 | 26 | (defn get-letter [cell] 27 | (.-textContent cell)) 28 | 29 | (defn color-cell [idx cell] 30 | (let [color (fn [el color] 31 | (set! (-> el .-style .-backgroundColor) 32 | color)) 33 | letter (get-letter cell)] 34 | (cond 35 | (= letter (nth @word-of-the-day idx)) 36 | (color cell "green") 37 | 38 | (contains? (set @word-of-the-day) letter) 39 | (color cell "aqua") 40 | 41 | :else 42 | (color cell "#333333")))) 43 | 44 | (defn check-solution [cells] 45 | (doseq [[idx cell] (map-indexed vector cells)] 46 | (color-cell idx cell)) 47 | (= (apply str (mapv get-letter cells)) 48 | (str @word-of-the-day))) 49 | 50 | (defn user-input [key] 51 | (let [start (* 5 @attempt) 52 | end (* 5 (inc @attempt))] 53 | (cond 54 | (and (re-matches #"[a-z]" key) 55 | (< @counter end)) 56 | (do 57 | (write-letter (nth @board-state @counter) key) 58 | (swap! counter inc)) 59 | 60 | (and (= key "backspace") 61 | (> @counter start)) 62 | (do 63 | (swap! counter dec) 64 | (write-letter (nth @board-state @counter) "")) 65 | 66 | (and (= key "enter") 67 | (= @counter end)) 68 | (do 69 | (when (check-solution (subvec @board-state start end)) 70 | (js/alert "You won")) 71 | (swap! attempt inc)) 72 | 73 | ))) 74 | 75 | (defonce listener (atom nil)) 76 | 77 | (defn ^:dev/before-load unmount [] 78 | (js/document.removeEventListener "keydown" @listener) 79 | (let [app (js/document.getElementById "app")] 80 | (set! (.-innerHTML app) ""))) 81 | 82 | (defn mount [] 83 | (let [app (js/document.getElementById "app") 84 | board (make-board 30) 85 | input-listener 86 | (fn [e] 87 | (user-input (.toLowerCase(.-key e))))] 88 | (.appendChild app board) 89 | (reset! listener input-listener) 90 | (js/document.addEventListener 91 | "keydown" 92 | input-listener))) 93 | 94 | (mount) 95 | 96 | (comment 97 | (do 98 | (def sim ["a" "a" "a" "a" "a" "enter" 99 | "e" "h" "o" "l" "o" 100 | "backspace" "k" "enter" 101 | "h" "e" "l" "l" "o" "enter"]) 102 | (run! user-input sim))) 103 | 104 | ;; :thanks 👾 105 | -------------------------------------------------------------------------------- /squint/examples/wordle/wordle.mjs: -------------------------------------------------------------------------------- 1 | import { atom, reset_BANG_, range, conj, vec, prn, nth, swap_BANG_, vector, mapv, subvec, inc, str, apply, map_indexed, deref, re_matches } from 'clavascript/core.js' 2 | var board_state = atom([]); 3 | var counter = atom(0); 4 | var attempt = atom(0); 5 | var word_of_the_day = atom("hello"); 6 | var write_letter = function (cell, letter) { 7 | return cell["textContent"] = letter; 8 | ; 9 | }; 10 | var make_cell = function () { 11 | let cell1 = document.createElement("div"); 12 | cell1["className"] = "cell"; 13 | return cell1; 14 | }; 15 | var make_board = function (n) { 16 | let board2 = document.createElement("div"); 17 | board2["className"] = "board"; 18 | let seq__37 = range(n); 19 | let chunk__48 = null; 20 | let count__59 = 0; 21 | let i__610 = 0; 22 | while(true){ 23 | if ((i__610 < count__59)) { 24 | let _11 = _nth(chunk__48, i__610); 25 | let cell12 = make_cell(); 26 | swap_BANG_(board_state, conj, cell12); 27 | board2.appendChild(cell12); 28 | null; 29 | let G__13 = seq__37; 30 | let G__14 = chunk__48; 31 | let G__15 = count__59; 32 | let G__16 = unchecked_inc(i__610); 33 | seq__37 = G__13; 34 | chunk__48 = G__14; 35 | count__59 = G__15; 36 | i__610 = G__16; 37 | continue; 38 | } else { 39 | let temp__29223__auto__17 = ((seq__37["length"] > 0)) ? (seq__37) : (null); 40 | if (temp__29223__auto__17) { 41 | let seq__318 = temp__29223__auto__17; 42 | if (false) { 43 | let c__29616__auto__19 = chunk_first(seq__318); 44 | let G__20 = chunk_rest(seq__318); 45 | let G__21 = c__29616__auto__19; 46 | let G__22 = count(c__29616__auto__19); 47 | let G__23 = 0; 48 | seq__37 = G__20; 49 | chunk__48 = G__21; 50 | count__59 = G__22; 51 | i__610 = G__23; 52 | continue; 53 | } else { 54 | let _24 = (seq__318[0]); 55 | let cell25 = make_cell(); 56 | swap_BANG_(board_state, conj, cell25); 57 | board2.appendChild(cell25); 58 | null; 59 | let G__26 = seq__318.slice(1); 60 | let G__27 = null; 61 | let G__28 = 0; 62 | let G__29 = 0; 63 | seq__37 = G__26; 64 | chunk__48 = G__27; 65 | count__59 = G__28; 66 | i__610 = G__29; 67 | continue; 68 | }}};break; 69 | } 70 | ; 71 | return board2; 72 | }; 73 | var get_letter = function (cell) { 74 | return cell["textContent"]; 75 | }; 76 | var color_cell = function (idx, cell) { 77 | let color30 = function (el, color) { 78 | return el["style"]["backgroundColor"] = color; 79 | ; 80 | }; 81 | let letter31 = get_letter(cell); 82 | if ((letter31 === nth(deref(word_of_the_day), idx))) { 83 | return color30(cell, "green");} else { 84 | if (contains_QMARK_(set(deref(word_of_the_day)), letter31)) { 85 | return color30(cell, "aqua");} else { 86 | if ("else") { 87 | return color30(cell, "#333333");} else { 88 | return null;}}} 89 | }; 90 | var check_solution = function (cells) { 91 | let seq__3236 = map_indexed(vector, cells); 92 | let chunk__3337 = null; 93 | let count__3438 = 0; 94 | let i__3539 = 0; 95 | while(true){ 96 | if ((i__3539 < count__3438)) { 97 | let vec__4043 = _nth(chunk__3337, i__3539); 98 | let idx44 = nth(vec__4043, 0, null); 99 | let cell45 = nth(vec__4043, 1, null); 100 | color_cell(idx44, cell45); 101 | null; 102 | let G__46 = seq__3236; 103 | let G__47 = chunk__3337; 104 | let G__48 = count__3438; 105 | let G__49 = unchecked_inc(i__3539); 106 | seq__3236 = G__46; 107 | chunk__3337 = G__47; 108 | count__3438 = G__48; 109 | i__3539 = G__49; 110 | continue; 111 | } else { 112 | let temp__29223__auto__50 = ((seq__3236["length"] > 0)) ? (seq__3236) : (null); 113 | if (temp__29223__auto__50) { 114 | let seq__3251 = temp__29223__auto__50; 115 | if (false) { 116 | let c__29616__auto__52 = chunk_first(seq__3251); 117 | let G__53 = chunk_rest(seq__3251); 118 | let G__54 = c__29616__auto__52; 119 | let G__55 = count(c__29616__auto__52); 120 | let G__56 = 0; 121 | seq__3236 = G__53; 122 | chunk__3337 = G__54; 123 | count__3438 = G__55; 124 | i__3539 = G__56; 125 | continue; 126 | } else { 127 | let vec__5760 = (seq__3251[0]); 128 | let idx61 = nth(vec__5760, 0, null); 129 | let cell62 = nth(vec__5760, 1, null); 130 | color_cell(idx61, cell62); 131 | null; 132 | let G__63 = seq__3251.slice(1); 133 | let G__64 = null; 134 | let G__65 = 0; 135 | let G__66 = 0; 136 | seq__3236 = G__63; 137 | chunk__3337 = G__64; 138 | count__3438 = G__65; 139 | i__3539 = G__66; 140 | continue; 141 | }}};break; 142 | } 143 | ; 144 | prn(str(mapv(get_letter, cells))); 145 | prn(str(vec(deref(word_of_the_day)))); 146 | return (apply(str, mapv(get_letter, cells)) === str(deref(word_of_the_day))); 147 | }; 148 | var user_input = function (key) { 149 | let start67 = (5 * deref(attempt)); 150 | let end68 = (5 * (deref(attempt) + 1)); 151 | if (re_matches(/[a-z]/, key)&&(deref(counter) < end68)) { 152 | write_letter(nth(deref(board_state), deref(counter)), key); 153 | return swap_BANG_(counter, inc);} else { 154 | if ((key === "backspace")&&(deref(counter) > start67)) { 155 | swap_BANG_(counter, dec); 156 | return write_letter(nth(deref(board_state), deref(counter)), "");} else { 157 | if ((key === "enter")&&(deref(counter) === end68)) { 158 | if (check_solution(subvec(deref(board_state), start67, end68))) { 159 | alert("You won")}; 160 | return swap_BANG_(attempt, inc);} else { 161 | return null;}}} 162 | }; 163 | if ((typeof listener !== 'undefined')) { 164 | null} else { 165 | var listener = atom(null); 166 | }; 167 | var unmount = function () { 168 | document.removeEventListener("keydown", deref(listener)); 169 | let app69 = document.getElementById("app"); 170 | return app69["innerHTML"] = ""; 171 | ; 172 | }; 173 | var mount = function () { 174 | let app70 = document.getElementById("app"); 175 | let board71 = make_board(30); 176 | let input_listener72 = function (e) { 177 | return user_input(e["key"].toLowerCase()); 178 | }; 179 | app70.appendChild(board71); 180 | reset_BANG_(listener, input_listener72); 181 | return document.addEventListener("keydown", input_listener72); 182 | }; 183 | mount(); 184 | null; 185 | 186 | export { make_cell, counter, word_of_the_day, board_state, listener, user_input, check_solution, color_cell, attempt, unmount, mount, make_board, write_letter, get_letter } 187 | -------------------------------------------------------------------------------- /squint/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Squint 5 | 6 | 7 | 8 | 9 | 18 | 31 | 32 | 33 |
34 |
35 |
36 | 42 |
43 |
44 | 47 |
48 |
49 |
50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /squint/index.js: -------------------------------------------------------------------------------- 1 | export { compileString } from './lib/compiler.js' 2 | -------------------------------------------------------------------------------- /squint/jsx.js: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | const code = readFileSync('test.jsx'); 3 | const transpiler = new Bun.Transpiler({ loader: "jsx" }); 4 | 5 | transpiler.transformSync(code); 6 | -------------------------------------------------------------------------------- /squint/node-api.js: -------------------------------------------------------------------------------- 1 | export * from './lib/compiler.node.js' 2 | -------------------------------------------------------------------------------- /squint/node_cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import './lib/cli.js' 4 | -------------------------------------------------------------------------------- /squint/notes/repl.md: -------------------------------------------------------------------------------- 1 | # REPL 2 | 3 | To keep Clojure semantics for REPL, you don't escape compiling vars to globally 4 | defined vars. This will happen in `dev` mode. 5 | 6 | ``` clojure 7 | (ns foo) 8 | (def x 1) 9 | ``` 10 | 11 | => 12 | 13 | `cherry.root.foo.x = 1` 14 | 15 | But what about the ES6 module then? 16 | 17 | ``` javascript 18 | foo.mjs: 19 | 20 | export { x } 21 | ``` 22 | 23 | In a REPL, when you execute `(require '[foo])` it should not compile to `import * as foo from './foo.mjs` or so, but to: 24 | 25 | ``` clojure 26 | var foo = cherry.root.foo 27 | ``` 28 | 29 | perhaps? 30 | 31 | Or should we compile to: 32 | 33 | ``` clojure 34 | import * as foo from './foo.mjs?t=123' 35 | ``` 36 | 37 | and replace `t=123` with something else once we detect that `foo.cljs` is out of date? 38 | And we could reload the "current" module by doing the same? 39 | 40 | 41 | -------------------------------------------------------------------------------- /squint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "squint-cljs", 3 | "type": "module", 4 | "sideEffects": false, 5 | "version": "0.0.7", 6 | "files": [ 7 | "core.js", 8 | "string.js", 9 | "lib", 10 | "node_cli.js", 11 | "index.js", 12 | "node-api.js" 13 | ], 14 | "bin": { 15 | "squint": "node_cli.js" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.18.13", 19 | "@babel/preset-react": "^7.18.6", 20 | "@vercel/ncc": "^0.34.0", 21 | "argparse": "^2.0.1", 22 | "chalk": "^5.0.1", 23 | "esbuild": "^0.14.49", 24 | "lodash": "^4.17.21", 25 | "lodash-es": "^4.17.21", 26 | "playwright": "^1.26.1", 27 | "prettier": "^2.7.1", 28 | "react": "^18.2.0", 29 | "shadow-cljs": "^2.19.8", 30 | "squint-cljs": "." 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /squint/resources/squint/core.edn: -------------------------------------------------------------------------------- 1 | #{Atom 2 | IIterable 3 | IIterable__iterator 4 | LazySeq 5 | PROTOCOL_SENTINEL 6 | _ 7 | _PLUS_ 8 | _iterator 9 | apply 10 | array_QMARK_ 11 | assoc 12 | assoc_BANG_ 13 | assoc_in 14 | assoc_in_BANG_ 15 | atom 16 | boolean$ 17 | butlast 18 | comp 19 | complement 20 | concat 21 | conj 22 | conj_BANG_ 23 | cons 24 | constantly 25 | contains_QMARK_ 26 | count 27 | cycle 28 | dec 29 | deref 30 | disj 31 | disj_BANG_ 32 | dissoc 33 | dissoc_BANG_ 34 | distinct 35 | drop 36 | drop_last 37 | drop_while 38 | empty 39 | empty_QMARK_ 40 | es6_iterator 41 | even_QMARK_ 42 | every_QMARK_ 43 | false_QMARK_ 44 | ffirst 45 | filter 46 | filterv 47 | first 48 | fnil 49 | frequencies 50 | get 51 | get_in 52 | group_by 53 | identical_QMARK_ 54 | identity 55 | inc 56 | interleave 57 | interpose 58 | into 59 | iterable 60 | keep 61 | last 62 | list 63 | list_QMARK_ 64 | map 65 | map_indexed 66 | mapcat 67 | mapv 68 | merge 69 | neg_QMARK_ 70 | nil_QMARK_ 71 | not 72 | not_any_QMARK_ 73 | not_every_QMARK_ 74 | nth 75 | odd_QMARK_ 76 | partial 77 | partition 78 | partition_all 79 | pos_QMARK_ 80 | pr_str 81 | println 82 | prn 83 | rand_int 84 | range 85 | re_matches 86 | reduce 87 | reduced 88 | reduced_QMARK_ 89 | remove 90 | repeat 91 | repeatedly 92 | replace 93 | reset_BANG_ 94 | rest 95 | reverse 96 | satisfies_QMARK_ 97 | second 98 | select_keys 99 | seq 100 | seqable_QMARK_ 101 | set 102 | shuffle 103 | some 104 | some_QMARK_ 105 | sort 106 | split_at 107 | split_with 108 | str 109 | subvec 110 | swap_BANG_ 111 | system_time 112 | take 113 | take_nth 114 | take_while 115 | true_QMARK_ 116 | update 117 | update_BANG_ 118 | update_in 119 | vec 120 | vector 121 | vector_QMARK_ 122 | zero_QMARK_} 123 | -------------------------------------------------------------------------------- /squint/resources/squint/js_reserved.edn: -------------------------------------------------------------------------------- 1 | #{"arguments" 2 | "abstract" 3 | "await" 4 | "boolean" 5 | "break" 6 | "byte" 7 | "case" 8 | "catch" 9 | "char" 10 | "class" 11 | "const" 12 | "continue" 13 | "debugger" 14 | "default" 15 | "delete" 16 | "do" 17 | "double" 18 | "else" 19 | "enum" 20 | "export" 21 | "extends" 22 | "final" 23 | "finally" 24 | "float" 25 | "for" 26 | "function" 27 | "goto" 28 | "if" 29 | "implements" 30 | "import" 31 | "in" 32 | "instanceof" 33 | "int" 34 | "interface" 35 | "let" 36 | "long" 37 | "native" 38 | "new" 39 | "package" 40 | "private" 41 | "protected" 42 | "public" 43 | "return" 44 | "short" 45 | "static" 46 | "super" 47 | "switch" 48 | "synchronized" 49 | "this" 50 | "throw" 51 | "throws" 52 | "transient" 53 | "try" 54 | "typeof" 55 | "var" 56 | "void" 57 | "volatile" 58 | "while" 59 | "with" 60 | "yield" 61 | "methods" 62 | "null" 63 | "constructor"} 64 | -------------------------------------------------------------------------------- /squint/resources/squint/resource.clj: -------------------------------------------------------------------------------- 1 | (ns squint.resource 2 | (:require [clojure.edn :as edn] 3 | [clojure.java.io :as io])) 4 | 5 | (defmacro edn-resource [f] 6 | (list 'quote (edn/read-string (slurp (io/resource f))))) 7 | -------------------------------------------------------------------------------- /squint/scratch_macros.cljc: -------------------------------------------------------------------------------- 1 | (ns scratch-macros) 2 | 3 | (defmacro do-twice [& body] 4 | `(do ~@body ~@body)) 5 | 6 | -------------------------------------------------------------------------------- /squint/shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:deps {:aliases [:cljs]} 2 | :builds 3 | {:squint 4 | {:js-options {;; don't bundle any npm libs 5 | :js-provider :import} 6 | :compiler-options {:infer-externs :auto} 7 | :target :esm 8 | :runtime :node 9 | :output-dir "lib" 10 | :modules 11 | ;; note: this is overriden in bb build-squint 12 | {:cljs_core {:exports {assoc cljs.core/assoc 13 | map cljs.core/map 14 | str cljs.core/str 15 | keyword cljs.core/keyword 16 | symbol cljs.core/symbol 17 | vector cljs.core/vector 18 | toJs cljs.core/clj->js 19 | toCljs cljs.core/js->clj 20 | dissoc cljs.core/dissoc 21 | conj cljs.core/conj 22 | get cljs.core/get 23 | arrayMap cljs.core/array-map 24 | hashMap cljs.core/hash-map 25 | first cljs.core/first 26 | rest cljs.core/rest 27 | next cljs.core/next 28 | nth cljs.core/nth 29 | seq cljs.core/seq 30 | goog_typeOf goog/typeOf}} 31 | :compiler {:depends-on #{:cljs_core} 32 | :exports 33 | {compileString squint.compiler/compile-string}} 34 | :compiler.node {:depends-on #{:compiler} 35 | :exports 36 | {compileFile squint.compiler.node/compile-file-js 37 | compileString squint.compiler.node/compile-string-js}} 38 | :cli {:depends-on #{:compiler :compiler.node} 39 | :init-fn squint.internal.cli/init}} 40 | :build-hooks [(shadow.cljs.build-report/hook 41 | {:output-to "report.html"})]}}} 42 | -------------------------------------------------------------------------------- /squint/src/squint/compiler/node.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.compiler.node 2 | (:require 3 | ["fs" :as fs] 4 | ["path" :as path] 5 | [squint.compiler :as compiler] 6 | [clojure.string :as str] 7 | [edamame.core :as e] 8 | [sci.core :as sci])) 9 | 10 | (defn slurp [f] 11 | (fs/readFileSync f "utf-8")) 12 | 13 | (defn spit [f s] 14 | (fs/writeFileSync f s "utf-8")) 15 | 16 | (def classpath-dirs ["." "src"]) 17 | 18 | (defn resolve-file* [dir munged-macro-ns] 19 | (let [exts ["cljc" "cljs"]] 20 | (some (fn [ext] 21 | (let [full-path (path/resolve dir (str munged-macro-ns "." ext))] 22 | (when (fs/existsSync full-path) 23 | full-path))) 24 | exts))) 25 | 26 | (defn resolve-file [macro-ns] 27 | (let [path (-> macro-ns str (str/replace "-" "_"))] 28 | (some (fn [dir] 29 | (resolve-file* dir path)) 30 | classpath-dirs))) 31 | 32 | (def ctx (sci/init {:load-fn (fn [{:keys [namespace]}] 33 | (let [f (resolve-file namespace) 34 | fstr (slurp f)] 35 | {:source fstr})) 36 | :classes {:allow :all 37 | 'js js/globalThis} 38 | })) 39 | 40 | (sci/alter-var-root sci/print-fn (constantly *print-fn*)) 41 | (sci/alter-var-root sci/print-err-fn (constantly *print-err-fn*)) 42 | 43 | (sci/enable-unrestricted-access!) 44 | 45 | (defn scan-macros [s] 46 | (let [maybe-ns (e/parse-next (e/reader s) compiler/squint-parse-opts)] 47 | (when (and (seq? maybe-ns) 48 | (= 'ns (first maybe-ns))) 49 | (let [[_ns _name & clauses] maybe-ns 50 | [require-macros reload] (some (fn [[clause reload]] 51 | (when (and (seq? clause) 52 | (= :require-macros (first clause))) 53 | [(rest clause) reload])) 54 | (partition-all 2 1 clauses))] 55 | (when require-macros 56 | (reduce (fn [prev require-macros] 57 | (.then prev 58 | (fn [_] 59 | (let [[macro-ns & {:keys [refer]}] require-macros 60 | macros (js/Promise.resolve 61 | (do (sci/eval-form ctx (cond-> (list 'require (list 'quote macro-ns)) 62 | reload (concat [:reload]))) 63 | (let [publics (sci/eval-form ctx 64 | `(ns-publics '~macro-ns)) 65 | ks (keys publics) 66 | vs (vals publics) 67 | vs (map deref vs) 68 | publics (zipmap ks vs) 69 | publics (if refer 70 | (select-keys publics refer) 71 | publics)] 72 | publics)))] 73 | (.then macros 74 | (fn [macros] 75 | (set! compiler/built-in-macros 76 | ;; hack 77 | (merge compiler/built-in-macros macros)))))))) 78 | (js/Promise.resolve nil) 79 | require-macros)))))) 80 | 81 | (defn compile-string [contents opts] 82 | (-> (js/Promise.resolve (scan-macros contents)) 83 | (.then #(compiler/compile-string* contents opts)))) 84 | 85 | (defn compile-file [{:keys [in-file in-str out-file extension] :as opts}] 86 | (let [contents (or in-str (slurp in-file))] 87 | (-> (compile-string contents opts) 88 | (.then (fn [{:keys [javascript jsx] :as opts}] 89 | (let [out-file (or out-file 90 | (str/replace in-file #".clj(s|c)$" 91 | (if jsx 92 | ".jsx" 93 | (or (when-let [ext extension] 94 | (str "." (str/replace ext #"^\." ""))) 95 | ".mjs"))))] 96 | (spit out-file javascript) 97 | (assoc opts :out-file out-file))))))) 98 | 99 | (defn ->clj [x] 100 | (js->clj x :keywordize-keys true)) 101 | 102 | (defn- jsify [f] 103 | (fn [& args] 104 | (let [args (mapv ->clj args) 105 | ret (apply f args)] 106 | (if (instance? js/Promise ret) 107 | (.then ret clj->js) 108 | (clj->js ret))))) 109 | 110 | #_{:clj-kondo/ignore [:unused-private-var]} 111 | (def ^:private compile-string-js 112 | (jsify compile-string)) 113 | 114 | #_{:clj-kondo/ignore [:unused-private-var]} 115 | (def ^:private compile-file-js 116 | (jsify compile-file)) 117 | -------------------------------------------------------------------------------- /squint/src/squint/internal/cli.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.internal.cli 2 | (:require 3 | ["fs" :as fs] 4 | ["path" :as path] 5 | [babashka.cli :as cli] 6 | [shadow.esm :as esm] 7 | [squint.compiler :as cc] 8 | [squint.compiler.node :as compiler] 9 | [squint.repl.node :as repl])) 10 | 11 | (defn compile-files 12 | [opts files] 13 | (if (:help opts) 14 | (do (println "Usage: squint compile ") 15 | (println) 16 | (println "Options: 17 | 18 | --elide-imports: do not include imports 19 | --elide-exports: do not include exports 20 | --extension: default extension for JS files")) 21 | (reduce (fn [prev f] 22 | (-> (js/Promise.resolve prev) 23 | (.then 24 | #(do 25 | (println "[squint] Compiling CLJS file:" f) 26 | (compiler/compile-file (assoc opts :in-file f)))) 27 | (.then (fn [{:keys [out-file]}] 28 | (println "[squint] Wrote JS file:" out-file) 29 | out-file)))) 30 | nil 31 | files))) 32 | 33 | (defn print-help [] 34 | (println "Squint v0.0.0 35 | 36 | Usage: squint 37 | 38 | Subcommands: 39 | 40 | -e Compile and run expression. 41 | run Compile and run a file 42 | compile ... Compile file(s) 43 | repl Start repl 44 | help Print this help 45 | 46 | Use squint --help to show more info.")) 47 | 48 | (defn fallback [{:keys [rest-cmds opts]}] 49 | (if-let [e (:e opts)] 50 | (if (:help opts) 51 | (println "Usage: squint -e 52 | 53 | Options: 54 | 55 | --no-run: do not run compiled expression 56 | --show: print compiled expression") 57 | (let [res (cc/compile-string e) 58 | dir (fs/mkdtempSync ".tmp") 59 | f (str dir "/squint.mjs")] 60 | (fs/writeFileSync f res "utf-8") 61 | (when (:show opts) 62 | (println res)) 63 | (when-not (:no-run opts) 64 | (let [path (if (path/isAbsolute f) f 65 | (str (js/process.cwd) "/" f))] 66 | (-> (esm/dynamic-import path) 67 | (.finally (fn [_] 68 | (fs/rmSync dir #js {:force true :recursive true})))))))) 69 | (if (or (:help opts) 70 | (= "help" (first rest-cmds)) 71 | (empty? rest-cmds)) 72 | (print-help) 73 | (compile-files opts rest-cmds)))) 74 | 75 | (defn run [{:keys [opts]}] 76 | (let [{:keys [file help]} opts] 77 | (if help 78 | nil 79 | (do (println "[squint] Running" file) 80 | (.then (compiler/compile-file (assoc opts :in-file file)) 81 | (fn [{:keys [out-file]}] 82 | (let [path (if (path/isAbsolute out-file) out-file 83 | (str (js/process.cwd) "/" out-file))] 84 | (esm/dynamic-import path)))))))) 85 | 86 | #_(defn compile-form [{:keys [opts]}] 87 | (let [e (:e opts)] 88 | (println (t/compile! e)))) 89 | 90 | (def table 91 | [{:cmds ["run"] :fn run :cmds-opts [:file]} 92 | {:cmds ["compile"] 93 | :coerce {:elide-exports :boolean 94 | :elide-imports :boolean} 95 | :fn (fn [{:keys [rest-cmds opts]}] 96 | (compile-files opts rest-cmds))} 97 | {:cmds ["repl"] :fn repl/repl} 98 | {:cmds [] :fn fallback}]) 99 | 100 | (defn init [] 101 | (cli/dispatch table 102 | (.slice js/process.argv 2) 103 | {:aliases {:h :help}})) 104 | -------------------------------------------------------------------------------- /squint/src/squint/internal/deftype.cljc: -------------------------------------------------------------------------------- 1 | (ns squint.internal.deftype 2 | (:require 3 | [clojure.core :as core] 4 | [squint.internal.protocols :as p])) 5 | 6 | (def fast-path-protocols 7 | "protocol fqn -> [partition number, bit]" 8 | (zipmap (map #(symbol "cljs.core" (core/str %)) 9 | '[IFn ICounted IEmptyableCollection ICollection IIndexed ASeq ISeq INext 10 | ILookup IAssociative IMap IMapEntry ISet IStack IVector IDeref 11 | IDerefWithTimeout IMeta IWithMeta IReduce IKVReduce IEquiv IHash 12 | ISeqable ISequential IList IRecord IReversible ISorted IPrintWithWriter IWriter 13 | IPrintWithWriter IPending IWatchable IEditableCollection ITransientCollection 14 | ITransientAssociative ITransientMap ITransientVector ITransientSet 15 | IMultiFn IChunkedSeq IChunkedNext IComparable INamed ICloneable IAtom 16 | IReset ISwap IIterable]) 17 | (iterate (core/fn [[p b]] 18 | (if (core/== 2147483648 b) 19 | [(core/inc p) 1] 20 | [p #?(:clj (core/bit-shift-left b 1) 21 | :cljs (core/* 2 b))])) 22 | [0 1]))) 23 | 24 | (def fast-path-protocol-partitions-count 25 | "total number of partitions" 26 | (core/let [c (count fast-path-protocols) 27 | m (core/mod c 32)] 28 | (if (core/zero? m) 29 | (core/quot c 32) 30 | (core/inc (core/quot c 32))))) 31 | 32 | (core/defn- prepare-protocol-masks [env impls] 33 | (core/let [resolve identity #_(partial resolve-var env) 34 | impl-map (p/->impl-map impls) 35 | fpp-pbs (seq 36 | (keep fast-path-protocols 37 | (map resolve 38 | (keys impl-map))))] 39 | (if fpp-pbs 40 | (core/let [fpps (into #{} 41 | (filter (partial contains? fast-path-protocols) 42 | (map resolve (keys impl-map)))) 43 | parts (core/as-> (group-by first fpp-pbs) parts 44 | (into {} 45 | (map (juxt key (comp (partial map peek) val)) 46 | parts)) 47 | (into {} 48 | (map (juxt key (comp (partial reduce core/bit-or) val)) 49 | parts)))] 50 | [fpps (reduce (core/fn [ps p] (update-in ps [p] (core/fnil identity 0))) 51 | parts 52 | (range fast-path-protocol-partitions-count))])))) 53 | 54 | (core/defn- collect-protocols [impls env] 55 | (core/->> impls 56 | (filter core/symbol?) 57 | (map identity #_#(:name (cljs.analyzer/resolve-var (dissoc env :locals) %))) 58 | (into #{}))) 59 | 60 | (core/defn- annotate-specs [annots v [f sigs]] 61 | (conj v 62 | (vary-meta (cons f (map #(cons (second %) (nnext %)) sigs)) 63 | merge annots))) 64 | 65 | (core/defn dt->et 66 | ([type specs fields] 67 | (dt->et type specs fields false)) 68 | ([type specs fields inline] 69 | (core/let [annots {:cljs.analyzer/type type 70 | :cljs.analyzer/protocol-impl true 71 | :cljs.analyzer/protocol-inline inline}] 72 | (core/loop [ret [] specs specs] 73 | (if (seq specs) 74 | (core/let [p (first specs) 75 | ret (core/-> (conj ret p) 76 | (into (reduce (partial annotate-specs annots) [] 77 | (group-by first (take-while seq? (next specs)))))) 78 | specs (drop-while seq? (next specs))] 79 | (recur ret specs)) 80 | ret))))) 81 | 82 | (core/defn- build-positional-factory 83 | [rsym rname fields] 84 | (core/let [fn-name (with-meta (symbol (core/str '-> rsym)) 85 | (assoc (meta rsym) :factory :positional)) 86 | docstring (core/str "Positional factory function for " rname ".") 87 | field-values (if (core/-> rsym meta :internal-ctor) (conj fields nil nil nil) fields)] 88 | `(defn ~fn-name 89 | ~docstring 90 | [~@fields] 91 | (new ~rname ~@field-values)))) 92 | 93 | (core/defn core-deftype 94 | "(deftype name [fields*] options* specs*) 95 | Currently there are no options. 96 | Each spec consists of a protocol or interface name followed by zero 97 | or more method bodies: 98 | protocol-or-Object 99 | (methodName [args*] body)* 100 | The type will have the (by default, immutable) fields named by 101 | fields, which can have type hints. Protocols and methods 102 | are optional. The only methods that can be supplied are those 103 | declared in the protocols/interfaces. Note that method bodies are 104 | not closures, the local environment includes only the named fields, 105 | and those fields can be accessed directly. Fields can be qualified 106 | with the metadata :mutable true at which point (set! afield aval) will be 107 | supported in method bodies. Note well that mutable fields are extremely 108 | difficult to use correctly, and are present only to facilitate the building 109 | of higherlevel constructs, such as ClojureScript's reference types, in 110 | ClojureScript itself. They are for experts only - if the semantics and 111 | implications of :mutable are not immediately apparent to you, you should not 112 | be using them. 113 | Method definitions take the form: 114 | (methodname [args*] body) 115 | The argument and return types can be hinted on the arg and 116 | methodname symbols. If not supplied, they will be inferred, so type 117 | hints should be reserved for disambiguation. 118 | Methods should be supplied for all methods of the desired 119 | protocol(s). You can also define overrides for methods of Object. Note that 120 | a parameter must be supplied to correspond to the target object 121 | ('this' in JavaScript parlance). Note also that recur calls to the method 122 | head should *not* pass the target object, it will be supplied 123 | automatically and can not be substituted. 124 | In the method bodies, the (unqualified) name can be used to name the 125 | class (for calls to new, instance? etc). 126 | One constructor will be defined, taking the designated fields. Note 127 | that the field names __meta and __extmap are currently reserved and 128 | should not be used when defining your own types. 129 | Given (deftype TypeName ...), a factory function called ->TypeName 130 | will be defined, taking positional parameters for the fields" 131 | [&env _&form t fields & impls] 132 | #_(validate-fields "deftype" t fields) 133 | (core/let [env &env 134 | r t #_(:name (cljs.analyzer/resolve-var (dissoc env :locals) t)) 135 | [fpps pmasks] (prepare-protocol-masks env impls) 136 | protocols (collect-protocols impls env) 137 | t (vary-meta t assoc 138 | :protocols protocols 139 | :skip-protocol-flag fpps)] 140 | `(do 141 | (deftype* ~t ~fields ~pmasks 142 | ~(when (seq impls) 143 | `(extend-type ~t ~@(dt->et t impls fields)))) 144 | ~(build-positional-factory t r fields) 145 | ~t))) 146 | -------------------------------------------------------------------------------- /squint/src/squint/internal/destructure.cljc: -------------------------------------------------------------------------------- 1 | ;; Adapted from CLJS core.cljc. Original copyright notice: 2 | 3 | ;; Copyright (c) Rich Hickey. All rights reserved. The use and distribution 4 | ;; terms for this software are covered by the Eclipse Public License 5 | ;; 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found in 6 | ;; the file epl-v10.html at the root of this distribution. By using this 7 | ;; software in any fashion, you are agreeing to be bound by the terms of this 8 | ;; license. You must not remove this notice, or any other, from this 9 | ;; software. 10 | 11 | (ns squint.internal.destructure 12 | (:refer-clojure :exclude [destructure])) 13 | 14 | (defn destructure [bindings] 15 | (let [bents (partition 2 bindings) 16 | pb (fn pb [bvec b v] 17 | (let [pvec 18 | (fn [bvec b val] 19 | (let [gvec (gensym "vec__") 20 | gseq (gensym "seq__") 21 | gfirst (gensym "first__") 22 | has-rest (some #{'&} b)] 23 | (loop [ret (let [ret (conj bvec gvec val)] 24 | (if has-rest 25 | (conj ret gseq (list `seq gvec)) 26 | ret)) 27 | n 0 28 | bs b 29 | seen-rest? false] 30 | (if (seq bs) 31 | (let [firstb (first bs)] 32 | (cond 33 | (= firstb '&) (recur (pb ret (second bs) gseq) 34 | n 35 | (nnext bs) 36 | true) 37 | (= firstb :as) (pb ret (second bs) gvec) 38 | :else (if seen-rest? 39 | (throw #?(:clj (new Exception "Unsupported binding form, only :as can follow & parameter") 40 | :cljs (new js/Error "Unsupported binding form, only :as can follow & parameter"))) 41 | (recur (pb (if has-rest 42 | (conj ret 43 | gfirst `(first ~gseq) 44 | gseq `(next ~gseq)) 45 | ret) 46 | firstb 47 | (if has-rest 48 | gfirst 49 | (list `nth gvec n nil))) 50 | (inc n) 51 | (next bs) 52 | seen-rest?)))) 53 | ret)))) 54 | pmap 55 | (fn [bvec b v] 56 | (let [m (meta b) 57 | js-keys? true #_(or (:js m) 58 | (= 'js (:tag m))) 59 | gmap (gensym "map__") 60 | defaults (:or b)] 61 | (loop [ret (-> bvec (conj gmap) (conj v) 62 | #_#_(conj gmap) (conj gmap) 63 | ((fn [ret] 64 | (if (:as b) 65 | (conj ret (:as b) gmap) 66 | ret)))) 67 | bes (let [transforms 68 | (reduce 69 | (fn [transforms mk] 70 | (if (keyword? mk) 71 | (let [mkns (namespace mk) 72 | mkn (name mk)] 73 | (cond 74 | js-keys? (assoc transforms mk #(subs (str (keyword (or mkns (namespace %)) (name %))) 1)) 75 | (= mkn "keys") (assoc transforms mk #(keyword (or mkns (namespace %)) (name %))) 76 | #_#_(= mkn "syms") (assoc transforms mk #(list `quote (symbol (or mkns (namespace %)) (name %)))) 77 | #_#_(= mkn "strs") (assoc transforms mk str) 78 | :else transforms)) 79 | transforms)) 80 | {} 81 | (keys b))] 82 | (reduce 83 | (fn [bes entry] 84 | (reduce #(assoc %1 %2 ((val entry) %2)) 85 | (dissoc bes (key entry)) 86 | ((key entry) bes))) 87 | (dissoc b :as :or) 88 | transforms))] 89 | (if (seq bes) 90 | (let [bb (key (first bes)) 91 | bk (val (first bes)) 92 | local (if #?(:clj (instance? clojure.lang.Named bb) 93 | :cljs (cljs.core/implements? INamed bb)) 94 | (with-meta (symbol nil (name bb)) (meta bb)) 95 | bb) 96 | bv (if (contains? defaults local) 97 | (list 'cljs.core/get gmap bk (defaults local)) 98 | (list 'cljs.core/get gmap bk))] 99 | (recur 100 | (if (or (keyword? bb) (symbol? bb)) ;(ident? bb) 101 | (-> ret (conj local bv)) 102 | (pb ret bb bv)) 103 | (next bes))) 104 | ret))))] 105 | (cond 106 | (symbol? b) (-> bvec (conj (if (namespace b) (symbol (name b)) b)) (conj v)) 107 | (keyword? b) (-> bvec (conj (symbol (name b))) (conj v)) 108 | (vector? b) (pvec bvec b v) 109 | (map? b) (pmap bvec b v) 110 | :else (throw 111 | #?(:clj (new Exception (str "Unsupported binding form: " b)) 112 | :cljs (new js/Error (str "Unsupported binding form: " b))))))) 113 | process-entry (fn [bvec b] (pb bvec (first b) (second b))) 114 | ret (if (every? symbol? (map first bents)) 115 | bindings 116 | (if-let [kwbs (seq (filter #(keyword? (first %)) bents))] 117 | (throw 118 | #?(:clj (new Exception (str "Unsupported binding key: " (ffirst kwbs))) 119 | :cljs (new js/Error (str "Unsupported binding key: " (ffirst kwbs))))) 120 | (reduce process-entry [] bents)))] 121 | ret)) 122 | 123 | (defn core-let 124 | [bindings body] 125 | #_(assert-args let 126 | (vector? bindings) "a vector for its binding" 127 | (even? (count bindings)) "an even number of forms in binding vector") 128 | `(cljs.core/let* ~(destructure bindings) ~@body)) 129 | -------------------------------------------------------------------------------- /squint/src/squint/internal/loop.cljc: -------------------------------------------------------------------------------- 1 | ;; Adapted from CLJS core.cljc. Original copyright notice: 2 | 3 | ;; Copyright (c) Rich Hickey. All rights reserved. The use and distribution 4 | ;; terms for this software are covered by the Eclipse Public License 5 | ;; 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found in 6 | ;; the file epl-v10.html at the root of this distribution. By using this 7 | ;; software in any fashion, you are agreeing to be bound by the terms of this 8 | ;; license. You must not remove this notice, or any other, from this 9 | ;; software. 10 | 11 | (ns squint.internal.loop 12 | (:refer-clojure :exclude [destructure]) 13 | (:require [squint.internal.destructure :refer [destructure]])) 14 | 15 | (defn core-loop 16 | "Evaluates the exprs in a lexical context in which the symbols in 17 | the binding-forms are bound to their respective init-exprs or parts 18 | therein. Acts as a recur target." 19 | [_&form _&bindings bindings & body] 20 | #_(assert-args loop 21 | (vector? bindings) "a vector for its binding" 22 | (even? (count bindings)) "an even number of forms in binding vector") 23 | (let [db (destructure bindings)] 24 | (if (= db bindings) 25 | `(loop* ~bindings ~@body) 26 | (let [vs (take-nth 2 (drop 1 bindings)) 27 | bs (take-nth 2 bindings) 28 | gs (map (fn [b] (if (symbol? b) b (gensym))) bs) 29 | bfs (reduce (fn [ret [b v g]] 30 | (if (symbol? b) 31 | (conj ret g v) 32 | (conj ret g v b g))) 33 | [] (map vector bs vs gs))] 34 | `(let ~bfs 35 | (loop* ~(vec (interleave gs gs)) 36 | (let ~(vec (interleave bs gs)) 37 | ~@body))))))) 38 | -------------------------------------------------------------------------------- /squint/src/squint/internal/protocols.cljc: -------------------------------------------------------------------------------- 1 | (ns squint.internal.protocols 2 | (:require [clojure.core :as core])) 3 | 4 | (core/defn- emit-protocol-method-arity 5 | [method-sym args] 6 | `(~args 7 | ((unchecked-get ~(first args) ; this 8 | ~method-sym) ~@args))) 9 | 10 | (core/defn- emit-protocol-method 11 | [p method] 12 | (let [mname (first method) 13 | method-sym (symbol (str p "_" mname)) 14 | [mdocs margs] (if (string? (second method)) 15 | [(second method) (drop 2 method)] 16 | [nil (rest method)]) 17 | this-sym (first margs)] 18 | `((def ~method-sym 19 | (js/Symbol ~(str p "_" mname))) 20 | (defn ~mname 21 | ~@(when mdocs [mdocs]) 22 | ~@(map #(emit-protocol-method-arity method-sym %) margs))))) 23 | 24 | (core/defn core-defprotocol 25 | [&env _&form p & doc+methods] 26 | (core/let [[doc-and-opts methods] [(core/take-while #(not (list? %)) 27 | doc+methods) 28 | (core/drop-while #(not (list? %)) 29 | doc+methods)] 30 | pmeta (if (string? (first doc-and-opts)) 31 | (into {:doc (first doc-and-opts)} 32 | (partition 2 (rest doc-and-opts))) 33 | (into {} (partition 2 doc-and-opts)))] 34 | `(do 35 | (def ~(with-meta p pmeta) (js/Symbol ~(str p))) 36 | ~@(mapcat #(emit-protocol-method p %) methods)))) 37 | 38 | (core/defn ->impl-map [impls] 39 | (core/loop [ret {} s impls] 40 | (if (seq s) 41 | (recur (assoc ret (first s) (take-while seq? (next s))) 42 | (drop-while seq? (next s))) 43 | ret))) 44 | 45 | (def ^:private js-type-sym->type 46 | '{object js/Object 47 | string js/String 48 | number js/Number 49 | array js/Array 50 | function js/Function 51 | boolean js/Boolean 52 | ;; TODO what to do here? 53 | default js/Object}) 54 | 55 | (core/defn- emit-type-method 56 | [psym type-sym method] 57 | (let [mname (first method) 58 | msym (symbol (str psym "_" mname)) 59 | margs (second method) 60 | mbody (drop 2 method)] 61 | `(let [f# (fn ~margs ~@mbody)] 62 | (unchecked-set 63 | (.-prototype ~type-sym) ~msym f#)))) 64 | 65 | (core/defn- emit-type-methods 66 | [type-sym [psym pmethods]] 67 | `((unchecked-set 68 | (.-prototype ~type-sym) 69 | ~psym true) 70 | ~@(map #(emit-type-method psym type-sym %) pmethods))) 71 | 72 | (core/defn core-extend-type 73 | [_&env _&form type-sym & impls] 74 | (core/let [type-sym (get js-type-sym->type type-sym type-sym) 75 | impl-map (->impl-map impls)] 76 | `(do 77 | ~@(mapcat #(emit-type-methods type-sym %) impl-map)))) 78 | -------------------------------------------------------------------------------- /squint/src/squint/repl/node.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.repl.node 2 | (:require 3 | ["fs" :as fs] 4 | ["net" :as net] 5 | ["path" :as path] 6 | ["process" :as process] 7 | ["readline" :as readline] 8 | ["squint-cljs/core.js" :as squint] 9 | ["url" :as url] 10 | ["vm" :as vm] 11 | [clojure.string :as str] 12 | [edamame.core :as e] 13 | [shadow.esm :as esm] 14 | [squint.compiler :as compiler] 15 | [squint.compiler-common :refer [*async* *cljs-ns* *repl*]])) 16 | 17 | (def pending-input (atom "")) 18 | 19 | (declare input-loop eval-next) 20 | 21 | (def in-progress (atom false)) 22 | 23 | (defn continue [rl socket] 24 | (reset! in-progress false) 25 | (.setPrompt ^js rl (str *cljs-ns* "=> ")) 26 | (.prompt rl) 27 | (when-not (str/blank? @pending-input) 28 | (eval-next socket rl))) 29 | 30 | (defn erase-processed [rdr] 31 | (let [line (e/get-line-number rdr) 32 | col (e/get-column-number rdr) 33 | lines (str/split-lines @pending-input) 34 | [line & lines] (drop (dec line) lines) 35 | edited (when line (subs line col))] 36 | (reset! pending-input (str/join "\n" (cons edited lines))))) 37 | 38 | (def tty (and js/process.stdout.isTTY 39 | js/process.stdin.setRawMode)) 40 | 41 | (def contextify-binding (js/process.binding "contextify")) 42 | 43 | (defn eval-expr [socket f] 44 | (let [ctx #js {:f f} 45 | _ (.createContext vm ctx)] 46 | (try 47 | (when (and tty (not socket)) 48 | (.setRawMode js/process.stdin false)) 49 | (-> (.runInContext vm "f()" ctx 50 | #js {:displayErrors true 51 | ;; :timeout 1000 52 | :breakOnSigint true 53 | :microtaskMode "afterEvaluate"}) 54 | (.then (fn [wrapper] 55 | (let [ctx #js {:f (if socket (fn [] wrapper) 56 | (fn [] 57 | (let [v (first wrapper)] 58 | (squint/prn v) 59 | wrapper)))} 60 | _ (.createContext vm ctx)] 61 | (.runInContext vm "f()" ctx 62 | #js {:displayErrors true 63 | ;; :timeout 1000 64 | :breakOnSigint true 65 | :microtaskMode "afterEvaluate"})))) 66 | (.finally (fn [] 67 | (when (and tty (not socket)) 68 | (.setRawMode js/process.stdin true))))) 69 | (catch :default e 70 | (when (and tty (not socket)) 71 | (.setRawMode js/process.stdin true)) 72 | (js/Promise.reject e))))) 73 | 74 | (def last-ns (atom *cljs-ns*)) 75 | 76 | (defn eval-js [js-str] 77 | (let [filename (str ".repl/" (gensym) ".mjs")] 78 | (when-not (fs/existsSync ".repl") 79 | (fs/mkdirSync ".repl")) 80 | (fs/writeFileSync filename js-str) 81 | (-> (esm/dynamic-import (-> (path/resolve (process/cwd) filename) 82 | url/pathToFileURL 83 | str)) 84 | (.finally (fn [] #_(prn filename) (fs/unlinkSync filename)))))) 85 | 86 | (defn compile [the-val rl socket] 87 | (let [{js-str :javascript 88 | cljs-ns :ns} (binding [*cljs-ns* @last-ns] 89 | (compiler/compile-string* (pr-str the-val)))] 90 | (reset! last-ns cljs-ns) 91 | (-> 92 | (eval-js js-str) 93 | (.then (fn [^js _val] 94 | (squint/println js/globalThis._repl) 95 | (continue rl socket))) 96 | (.catch (fn [err] 97 | (squint/println err) 98 | (continue rl socket)))))) 99 | 100 | (defn eval-next [socket rl] 101 | (when-not (or @in-progress (str/blank? @pending-input)) 102 | (reset! in-progress true) 103 | (let [rdr (e/reader @pending-input) 104 | the-val (try (e/parse-next rdr) 105 | (catch :default e 106 | (if (str/includes? (ex-message e) "EOF while reading") 107 | ::eof-while-reading 108 | (do (erase-processed rdr) 109 | (prn (str e)) 110 | ::continue))))] 111 | (cond (= ::continue the-val) 112 | (continue rl socket) 113 | (= ::eof-while-reading the-val) 114 | ;; more input expected 115 | (reset! in-progress false) 116 | :else 117 | (do (erase-processed rdr) 118 | (if-not (= :edamame.core/eof the-val) 119 | ;; (prn :pending @pending) 120 | (compile the-val rl socket) 121 | (reset! in-progress false))))))) 122 | 123 | (defn input-handler [socket rl input] 124 | (swap! pending-input str input "\n") 125 | (eval-next socket rl)) 126 | 127 | (defn on-line [^js rl socket] 128 | (.on rl "line" #(input-handler socket rl %))) 129 | 130 | (defn create-rl [] 131 | (.createInterface 132 | readline #js {:input js/process.stdin 133 | :output js/process.stdout})) 134 | 135 | (defn create-socket-rl [socket] 136 | (.createInterface 137 | readline #js {:input socket 138 | :output socket})) 139 | 140 | (defn input-loop [socket resolve] 141 | (let [rl (if socket 142 | (create-socket-rl socket) 143 | (create-rl))] 144 | (on-line rl socket) 145 | (.setPrompt rl (str *cljs-ns* "=> ")) 146 | (.on rl "close" resolve) 147 | (.prompt rl))) 148 | 149 | (defn on-connect [socket] 150 | (let [rl (create-socket-rl socket)] 151 | (on-line rl socket)) 152 | (.setNoDelay ^net/Socket socket true) 153 | (.on ^net/Socket socket "close" 154 | (fn [_had-error?] 155 | (println "Client closed connection.")))) 156 | 157 | (defn socket-repl 158 | ([] (socket-repl nil)) 159 | ([opts] 160 | (let [port (or (:port opts) 161 | 0) 162 | srv (net/createServer 163 | on-connect)] 164 | (.listen srv port "127.0.0.1" 165 | (fn [] 166 | (let [addr (-> srv (.address)) 167 | port (-> addr .-port) 168 | host (-> addr .-address)] 169 | (println (str "Socket REPL listening on port " 170 | port " on host " host)))))))) 171 | 172 | (defn repl 173 | ([] (repl nil)) 174 | ([_opts] 175 | (set! *cljs-ns* 'user) 176 | (set! *repl* true) 177 | (set! *async* true) 178 | (when tty (.setRawMode js/process.stdin true)) 179 | (.then (eval-js "globalThis.user = globalThis.user || {};") 180 | (fn [_] 181 | (js/Promise. (fn [resolve] 182 | (input-loop nil resolve))))))) 183 | -------------------------------------------------------------------------------- /squint/string.js: -------------------------------------------------------------------------------- 1 | import { iterable } from './core.js'; 2 | 3 | export function blank_QMARK_(s) { 4 | if (!s) return true; 5 | if (s.length === 0) return true; 6 | if (s.trimLeft().length === 0) return true; 7 | return false; 8 | } 9 | 10 | export function join(sep, coll) { 11 | if (coll === undefined) { 12 | coll = sep; 13 | sep = ''; 14 | } 15 | if (coll instanceof Array) { 16 | return coll.join(sep); 17 | } 18 | let ret = ''; 19 | let addSep = false; 20 | for (const o of iterable(coll)) { 21 | if (addSep) ret += sep; 22 | ret += o; 23 | addSep = true; 24 | } 25 | return ret; 26 | } 27 | 28 | export function trim(s) { 29 | return s.trim(); 30 | } 31 | 32 | export function split(s, re) { 33 | return s.split(re); 34 | } 35 | -------------------------------------------------------------------------------- /squint/test/squint/clerk.clj: -------------------------------------------------------------------------------- 1 | (ns squint.clerk 2 | (:require 3 | [nextjournal.clerk :as clerk] 4 | [squint.compiler :as sq] 5 | [clojure.string :as str])) 6 | 7 | ^::clerk/no-cache 8 | (clerk/clear-cache!) 9 | 10 | ^{::clerk/visibility {:code :hide :result :hide}} 11 | (comment 12 | (clerk/serve! nil) 13 | (clerk/clear-cache!) 14 | ) 15 | 16 | ;; The `squint-viewer` shows the compiled JS and evaluates the result in the browser. 17 | ;; Code is folded for brevity. 18 | ;; TODO: multiviewer 19 | ;; See Clerk book https://github.clerk.garden/nextjournal/book-of-clerk/commit/d74362039690a4505f15a61112cab7da0615e2b8/ 20 | ^{::clerk/visibility {:code :fold :result :hide}} 21 | (def squint-viewer 22 | {:transform-fn clerk/mark-presented 23 | :render-fn 24 | '(let [result (reagent/atom "foo") 25 | import (js/eval "(x) => import(x)")] 26 | (fn [{:keys [repl unrepl js]}] 27 | (v/html 28 | [:div 29 | [:h2 "Compiled JS:"] 30 | [:details 31 | [:summary "Production version"] 32 | [:pre unrepl]] 33 | [:details 34 | [:summary "REPL version"] 35 | [:pre repl]] 36 | (let [js (clojure.string/replace repl "'squint-cljs/core.js'" "'https://cdn.jsdelivr.net/npm/squint-cljs@0.0.0-alpha.46/core.js'") 37 | js (clojure.string/replace js "'squint-cljs/string.js'" "'https://cdn.jsdelivr.net/npm/squint-cljs@0.0.0-alpha.46/string.js'") 38 | js (str "globalThis.user = globalThis.user || {};\n" js) 39 | encoded (js/encodeURIComponent js) 40 | data-uri (str "data:text/javascript;charset=utf-8;eval=" "," encoded) 41 | eval-fn (fn [] 42 | (-> (import data-uri) 43 | (.then (fn [v] 44 | (reset! result js/globalThis._repl))) 45 | (.catch (fn [err] 46 | (reset! result (.-message err)) 47 | (js/console.log err)))))] 48 | (eval-fn) 49 | [:div 50 | [:div 51 | [:h2 "Evaluated result:"] 52 | [:div (v/inspect @result)]] 53 | [:div.m-1 54 | [:button.bg-gray-500.hover:bg-blue-700.text-white.font-bold.py-2.px-4.rounded 55 | {:on-click eval-fn} "Eval"]]])])))}) 56 | 57 | ;; The `compile-string` function compiles a string, in both REPL and non-REPL 58 | ;; mode. The REPL version is evaluated by `squint-viewer` and the result is 59 | ;; rendered using clerk. Code is folded for brevity. 60 | ^{::clerk/visibility {:code :fold :result :hide}} 61 | (defn compile! [s] 62 | (let [s (if (string? s) 63 | s 64 | (pr-str s))] 65 | {:type ::squint-result 66 | :repl (binding [sq/*repl* true] 67 | (sq/compile-string s)) 68 | :unrepl (sq/compile-string s) 69 | :js s})) 70 | 71 | (clerk/add-viewers! 72 | [{:pred #(= (:type %) ::squint-result) :transform-fn (clerk/update-val #(clerk/with-viewer squint-viewer %))}]) 73 | 74 | (compile! '(+ 1 2 3)) 75 | 76 | (compile! '[1 2 3]) 77 | 78 | (compile! 79 | '(do 80 | (defn foo [x] x) 81 | (foo 1337))) 82 | 83 | (compile! 84 | '(do 85 | ;; due to a bug in CIDER, there's a string in front of the ns form 86 | ""(ns squint.clerk 87 | (:require ["https://cdn.skypack.dev/canvas-confetti$default" :as confetti])) 88 | 89 | (confetti))) 90 | 91 | (compile! 92 | '(do 93 | ""(ns squint.clerk 94 | (:require ["https://cdn.jsdelivr.net/npm/squint-cljs@0.0.0-alpha.46/index.js" :as compiler] 95 | [clojure.string :as string])) 96 | ;; TODO, you actually need to compile using REPL here too 97 | (def js-str (str (compiler/compileString "(assoc! {:a 33} :a 2)"))) 98 | (let [_ (prn :js-str js-str) 99 | js-str (.replaceAll js-str "squint-cljs/core.js" "https://cdn.jsdelivr.net/npm/squint-cljs@0.0.0-alpha.46/core.js") 100 | _ (prn :foo1 js-str) 101 | js-str (.replaceAll js-str "'squint-cljs/string.js'" "'https://cdn.jsdelivr.net/npm/squint-cljs@0.0.0-alpha.46/string.js'") 102 | _ (prn :foo2 js-str) 103 | encoded (js/encodeURIComponent js-str) 104 | data-uri (+ "data:text/javascript;charset=utf-8;eval=" #_(js/Date.now) "," encoded)] 105 | (-> (js/import data-uri) 106 | (.then #(prn :dude js/globalThis._repl)) 107 | (.then (constantly js/globalThis._repl)))))) 108 | -------------------------------------------------------------------------------- /squint/test/squint/compiler_test.clj: -------------------------------------------------------------------------------- 1 | (ns squint.compiler-test 2 | (:require 3 | [babashka.fs :as fs] 4 | [babashka.process :refer [sh] :as p] 5 | [clojure.string :as str] 6 | [clojure.test :refer [deftest is] :as t] 7 | [squint.compiler :as sq])) 8 | 9 | (defn to-js [code {:keys [requires]}] 10 | (sq/compile-string 11 | (str "(ns module" 12 | "(:require " (str/join "\n" requires) "))" 13 | code))) 14 | 15 | (defn test-expr [code] 16 | (let [js (to-js code []) 17 | tmp-dir (fs/file ".test") 18 | _ (fs/create-dirs tmp-dir) 19 | tmp-file (fs/file tmp-dir "expr.js") 20 | _ (spit tmp-file js) 21 | {:keys [out]} (p/check (sh ["node" (str tmp-file)]))] 22 | (str/trim out))) 23 | 24 | (deftest compiler-test 25 | (is (str/includes? (test-expr "(prn (+ 1 2 3))") 26 | "6")) 27 | (is (str/includes? (test-expr "(ns foo (:require [\"fs\" :as fs])) (prn (fs/existsSync \".\"))") 28 | "true"))) 29 | 30 | (def our-ns *ns*) 31 | (defn run-tests [_] 32 | (let [{:keys [fail error]} 33 | (t/run-tests our-ns)] 34 | (when (pos? (+ fail error)) 35 | (throw (ex-info "Tests failed" {:babashka/exit 1}))))) 36 | 37 | (comment 38 | (test-expr "(prn (+ 1 2 3))") 39 | ) 40 | -------------------------------------------------------------------------------- /squint/test/squint/eval_macro.clj: -------------------------------------------------------------------------------- 1 | (ns squint.eval-macro) 2 | 3 | (defmacro evalll [expected body] 4 | `(cljs.test/async ~'done 5 | (let [prog# (~'compile! ~body) 6 | filename# (str (gensym "test") ".mjs")] 7 | (fs/writeFileSync filename# prog#) 8 | (-> (~'dyn-import (-> (path/resolve (js/process.cwd) filename#) 9 | url/pathToFileURL)) 10 | (.then 11 | (fn [~'mod] 12 | (do (cljs.test/is 13 | ~(if (not (seq? expected)) 14 | `(= ~expected (.-result ~'mod)) 15 | `(~expected (.-result ~'mod))))))) 16 | (.finally 17 | #(do (fs/unlinkSync filename#) 18 | (~'done))))))) 19 | -------------------------------------------------------------------------------- /squint/test/squint/jsx_test.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.jsx-test 2 | (:require 3 | ["@babel/core" :refer [transformSync]] 4 | ["react" :as React] 5 | [clojure.test :as t :refer [deftest]] 6 | [goog.object :as gobject] 7 | [squint.test-utils :refer [jss!]] 8 | #_:clj-kondo/ignore 9 | ["fs" :as fs] 10 | #_:clj-kondo/ignore 11 | ["path" :as path])) 12 | 13 | (gobject/set js/global "React" React) 14 | 15 | (defn test-jsx [s] 16 | (let [expr (jss! s) 17 | code (:code (js->clj (transformSync expr #js {:presets #js ["@babel/preset-react"]}) :keywordize-keys true))] 18 | (js/eval code))) 19 | 20 | (def testing (constantly nil)) 21 | 22 | (deftest jsx-test 23 | (test-jsx "#jsx [:a {:href \"http://foo.com\"}]") 24 | (test-jsx "#jsx [:div {:dangerouslySetInnerHTML {:_html \"Hello\"}}]") 25 | (test-jsx "(defn App [] (let [x 1] #jsx [:div {:id x}]))") 26 | (test-jsx "(let [classes {:classes \"foo bar\"}] #jsx [:div {:className (:classes classes)}])") 27 | (test-jsx "(defn App [{:keys [x]}] #jsx [:span x]) #jsx [App {:x 1}]") 28 | (test-jsx " 29 | (ns foo (:require [\"foo\" :as foo])) 30 | (defn App [] #jsx [foo/c #js {:x 1}]) ") 31 | (test-jsx "(defn TextField [{:keys [multiline]}]) #jsx [TextField {:multiline true}]") 32 | (testing "spread" 33 | (test-jsx "(defn TextField [{:keys [multiline]}]) (let [m {:a 1}] #jsx [TextField {:foo 1 :& m}])") 34 | (test-jsx "(defn TextField [{:keys [multiline]}]) (let [m {:a 1}] #jsx [TextField {:& m :foo :bar}])"))) 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /squint/test/squint/string_test.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.string-test 2 | (:require 3 | [clojure.test :as t :refer [deftest]] 4 | [squint.compiler :as compiler] 5 | [squint.test-utils :refer [eq]] 6 | #_:clj-kondo/ignore 7 | ["fs" :as fs] 8 | #_:clj-kondo/ignore 9 | ["path" :as path] 10 | ;; required at least by the `squint.eval-macro/evalll` macro 11 | ["url" :as url]) 12 | (:require-macros [squint.eval-macro :refer [evalll]])) 13 | 14 | (defn compile! [str-or-expr] 15 | (let [s (if (string? str-or-expr) 16 | str-or-expr 17 | (pr-str str-or-expr))] 18 | (compiler/compile-string s))) 19 | 20 | (def dyn-import (js/eval "(x) => import(x)")) 21 | 22 | (deftest blank?-test 23 | (evalll true 24 | '(do (ns foo (:require [squint.string :as str])) 25 | (def result (str/blank? ""))))) 26 | 27 | (deftest join-test 28 | (evalll "0--1--2--3--4--5--6--7--8--9" 29 | '(do (ns foo (:require [squint.string :as str])) 30 | (def result (str/join "--" (range 10)))))) 31 | 32 | (deftest string-conflict-test 33 | (evalll (fn [res] 34 | (eq ["foo","bar"] res)) 35 | '(do (ns foo (:require [squint.string :as str])) 36 | (defn split [x] (str x)) (def result (str/split "foo,bar" ","))))) 37 | 38 | (deftest split-test-string 39 | (evalll (fn [res] 40 | (eq ["foo","bar","baz"] res)) 41 | '(do (ns foo (:require [squint.string :as str])) 42 | (def result (str/split "foo--bar--baz" "--"))))) 43 | 44 | (deftest split-test-regex 45 | (evalll (fn [res] 46 | (eq ["foo","bar","baz"] res)) 47 | '(do (ns foo (:require [squint.string :as str])) 48 | (def result (str/split "fooxbarybaz" #"[xy]"))))) 49 | -------------------------------------------------------------------------------- /squint/test/squint/test_utils.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.test-utils 2 | (:require 3 | ["lodash$default" :as ld] 4 | ["squint-cljs/core.js" :as cl] 5 | ["squint-cljs/string.js" :as clstr] 6 | [clojure.test :as t] 7 | [squint.compiler :as squint])) 8 | 9 | (doseq [k (js/Object.keys cl)] 10 | (aset js/globalThis k (aget cl k))) 11 | 12 | (let [mut #js {}] 13 | (aset js/globalThis "squint.string" mut) 14 | (doseq [k (js/Object.keys clstr)] 15 | (aset mut k (aget clstr k)))) 16 | 17 | (defn eq [a b] 18 | (ld/isEqual (clj->js a) (clj->js b))) 19 | 20 | (def old-fail (get-method t/report [:cljs.test/default :fail])) 21 | 22 | (defmethod t/report [:cljs.test/default :fail] [m] 23 | (set! js/process.exitCode 1) 24 | (old-fail m)) 25 | 26 | (def old-error (get-method t/report [:cljs.test/default :fail])) 27 | 28 | (defmethod t/report [:cljs.test/default :error] [m] 29 | (set! js/process.exitCode 1) 30 | (old-error m)) 31 | 32 | (defn jss! [expr] 33 | (if (string? expr) 34 | (:body (squint/compile-string* expr)) 35 | (squint/transpile-form expr))) 36 | 37 | (defn js! [expr] 38 | (let [js (jss! expr)] 39 | [(js/eval js) js])) 40 | 41 | (defn jsv! [expr] 42 | (first (js! expr))) 43 | 44 | 45 | --------------------------------------------------------------------------------