├── .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 |
setCount(incCounter(count))} disabled={!IS_BROWSER}>
13 | -1
14 |
15 |
setCount(decCounter(count))} disabled={!IS_BROWSER}>
16 | +1
17 |
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 |
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 |
44 | Compile!
45 |
46 |
47 |
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 | You need to enable JavaScript to run this app.
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()))}
Click me
;
17 | }
18 | ;
19 | var App = function () {
20 | return
;
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 |
45 | Compile!
46 |
47 |
48 |
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 |
--------------------------------------------------------------------------------