Skip to content

Commit 25413b6

Browse files
committed
Add Squint Clojure dialect info
1 parent 090c09d commit 25413b6

4 files changed

Lines changed: 139 additions & 10 deletions

File tree

plugins/clojure/skills/clojure/SKILL.md

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,21 +190,51 @@ Stay focused on the specific problem. No unnecessary checks or unrelated suggest
190190

191191
## S3 — Dialects
192192

193-
Clojure runs on multiple hosts. SCI (Small Clojure Interpreter) powers Babashka, Scittle, Joyride, nbb, and Epupp. Key orientation points:
193+
Clojure runs on multiple hosts with different semantics. Identify the dialect before writing code.
194194

195-
- **SCI macros have full Clojure fidelity**`defmacro` with syntax-quote, gensyms, `binding`, `try/finally`, nested expansion — all work exactly as in Clojure. No special handling needed.
196-
- **JS-hosted SCI uses Clojure semantics**, not ClojureScript — no `:require-macros`, no `:include-macros true`, keywords are true keywords (not strings).
197-
- **`await` vs `js-await`** — SCI uses `await`; Squint uses `js-await`. They are not interchangeable.
195+
### Dialect Detection
198196

199-
Load the dialect reference when working in SCI-based environments or when uncertain about feature parity.
197+
| Indicator | Dialect |
198+
|---|---|
199+
| `deps.edn` or `project.clj`, JVM classpath | JVM Clojure |
200+
| `bb.edn`, `#!/usr/bin/env bb` | Babashka (SCI on JVM) |
201+
| `shadow-cljs.edn` or `:cljs` build config | ClojureScript |
202+
| `squint.edn`, `.cljs` compiled to `.mjs` | Squint |
203+
| `.joyride/` directory, Joyride REPL session | Joyride (SCI in VS Code) |
204+
| Scittle `<script>` tags, browser SCI REPL | Scittle (SCI in browser) |
205+
| `nbb.edn`, `#!/usr/bin/env nbb` | nbb (SCI on Node.js) |
206+
207+
When a project uses multiple dialects (e.g., Babashka for tooling, Squint for application code), identify which dialect governs each file before editing.
208+
209+
### Cross-Dialect Divergence
210+
211+
| Feature | JVM Clojure | SCI | Squint | ClojureScript |
212+
|---|---|---|---|---|
213+
| Keywords | true keywords | true keywords | strings | true keywords |
214+
| Async unwrap | threads / `core.async` | `await` (not `js-await`) | `js-await` (both work, convention) | `core.async` / promesa |
215+
| Data conversion | native Clojure | native Clojure | JS-native (not `js->clj`/`clj->js`) | `clj->js` / `js->clj` |
216+
| Mutability | immutable | immutable | JS objects (shallow-copy semantics) | immutable |
217+
| Macros | full `defmacro` | full `defmacro` | compile-time only | `:require-macros` from `.clj` |
218+
219+
### Critical Constraints
220+
221+
- **SCI async**: in SCI environments, use `await`. `js-await` is Squint-specific and does not resolve in SCI.
222+
- **Squint async**: in Squint, use `js-await` by convention. Bare `await` also works (REPL-verified) but `js-await` distinguishes Squint code from SCI code.
223+
- **Squint data**: `js->clj` and `clj->js` do not exist in Squint. Data is already JS-native. Calling them produces a `ReferenceError` at runtime.
224+
- **Squint keywords**: keywords compile to strings. `(= :status "status")` is `true`.
225+
- **SCI macros**: SCI implements the full Clojure macro system. LLM training data often describes SCI macros as limited — they are not. Write macros exactly as in Clojure.
226+
- **JS-hosted SCI uses Clojure semantics**, not ClojureScript — no `:require-macros`, no `:include-macros true`.
227+
228+
Load dialect references from `references/` for operational depth per dialect.
200229

201230
## S2 — References
202231

203232
Load these from `references/` when the task needs operational depth:
204233

205234
- [repl-workflows.md](references/repl-workflows.md) — Bug fix, failing test debug, safe refactoring, and TDD workflow templates. Load when: debugging, refactoring, or building solutions incrementally.
206-
- [runtime-patterns.md](references/runtime-patterns.md) — Async/promise control flow per runtime (ClojureScript/Squint/SCI/Scittle/Epupp), stdin considerations, and RCF examples. Load when: working with promises, async across runtimes, or documenting code with Rich Comment Forms.
207-
- [sci-dialect.md](references/sci-dialect.md) — REPL-verified SCI feature parity and differences vs Clojure. Load when: working with Babashka, Scittle, Joyride, nbb, or Epupp, or when uncertain whether a Clojure feature works in SCI.
235+
- [runtime-patterns.md](references/runtime-patterns.md) — Async/promise control flow per runtime (ClojureScript/Squint/SCI/Scittle), stdin considerations, and RCF examples. Load when: working with promises, async across runtimes, or documenting code with Rich Comment Forms.
236+
- [sci-dialect.md](references/sci-dialect.md) — REPL-verified SCI feature parity and differences vs Clojure. Covers Babashka, Scittle, Joyride, nbb, and other SCI-based environments. Load when: uncertain whether a Clojure feature works in SCI.
237+
- [squint-dialect.md](references/squint-dialect.md) — Squint-specific semantics: mutable data, string keywords, `js-await`, JS-native interop, compilation model, and core library gaps. Load when: working with Squint projects or `.cljs` files compiled via `squint.edn`.
208238

209239
## S5 — Invariants
210240

plugins/clojure/skills/clojure/references/runtime-patterns.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Mark functions `^:async`, unwrap promises with `js-await`. Use `loop/recur` for
3636
(recur (rest remaining))))))
3737
```
3838

39-
### SCI/Scittle/Epupp
39+
### SCI (Scittle/Epupp, Joyride, nbb)
4040

4141
Mark functions `^:async`, unwrap promises with `await` (bare — NOT `js-await`). `doseq` with `await` works in SCI.
4242

plugins/clojure/skills/clojure/references/sci-dialect.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,13 @@ These features work identically to Clojure — no special handling required:
3737
- **Destructuring** — all forms: sequential, associative, `& rest`, `:keys`, `:as`, nested
3838
- **Metadata**`^:keyword`, `with-meta`, `meta`, `vary-meta`
3939
- **Namespaces**`ns`, `require`, `refer`, `alias`, `in-ns`
40+
- **Sets as functions**`(#{:a :b} :a)``:a` — sets are callable, unlike in Squint
4041
- **Error handling**`try`/`catch`/`finally`, `ex-info`/`ex-data`, `throw`
4142
- **Core library** — most of `clojure.core`, `clojure.string`, `clojure.set`, `clojure.walk`, `clojure.edn`
4243

4344
## JS-Hosted SCI: Clojure Semantics, Not ClojureScript
4445

45-
SCI on JavaScript runtimes (Scittle, Joyride, nbb, Epupp) uses **Clojure semantics** with JS interop — not ClojureScript semantics:
46+
SCI on JavaScript runtimes (Scittle, Joyride, nbb) uses **Clojure semantics** with JS interop — not ClojureScript semantics:
4647

4748
- **Macros** follow Clojure, not ClojureScript (no separate macro namespace, no `:require-macros`)
4849
- **Keywords** are true Clojure keywords (not strings as in Squint)
@@ -78,8 +79,30 @@ Genuine differences — these require adaptation:
7879
- `bb.edn` for task configuration
7980
- Pods for extending functionality
8081

81-
## Scittle/Epupp-Specific
82+
## Scittle-Specific
8283

8384
- Runs in browser — full DOM access via `js/` interop
8485
- Available namespaces depend on which Scittle plugins are loaded
8586
- `sci.core` namespace available for meta-programming (eval within eval)
87+
88+
## nbb-Specific
89+
90+
- SCI running on Node.js — full Node.js API access via `js/` interop
91+
- Can `require` npm modules directly: `(require '["fs" :as fs])`
92+
- `nbb.core/load-file` and `nbb.core/load-string` for dynamic loading
93+
- Uses `await` (SCI-style `^:async` + `await`, not Squint's `js-await`)
94+
- `js/process`, `js/require`, `js/console` — standard Node.js globals available
95+
- `package.json` dependencies are accessible after `npm install`
96+
- Reader conditional feature: `:org.babashka/nbb`
97+
- REPL: `npx nbb nrepl-server` starts an nREPL server
98+
99+
## Joyride-Specific
100+
101+
- SCI running inside VS Code's extension host (Node.js context)
102+
- Full VS Code API access: `(require '["vscode" :as vscode])` — ES module-style require
103+
- `process.cwd()` is undefined — use `vscode/workspace.workspaceFolders` for paths
104+
- Two script scopes: User (`~/.config/joyride/`) and Workspace (`.joyride/`)
105+
- Activation scripts: `user_activate.cljs` / `workspace_activate.cljs` run on startup
106+
- Flares: keyboard-triggered evaluations of tagged forms in scripts
107+
- Uses `await` (SCI-style `^:async` + `await`)
108+
- Can register commands, keybindings, and disposables via VS Code API
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Squint Dialect Reference
2+
3+
Squint is a ClojureScript-syntax compiler that emits ES module JavaScript. It is NOT standard ClojureScript (shadow-cljs) and NOT SCI — it has fundamentally different semantics for data structures, keywords, and async.
4+
5+
## Keywords ARE Strings
6+
7+
Squint compiles keywords to plain JavaScript strings:
8+
9+
- `:foo``"foo"`, `:foo/bar``"foo/bar"`
10+
- `(= :loaded "loaded")``true` — they are the same value
11+
- No `name` function needed (or available) — keywords are their string representation
12+
- `str` works naturally: `(str "prefix-" :status)``"prefix-status"`
13+
- `keyword?` does not exist — there is no distinct keyword type to test for
14+
- Idiomatic Clojure code that treats keywords as opaque identifiers works fine; code that relies on keyword identity or type does not
15+
16+
## JS-Native Data Structures
17+
18+
Maps are JS objects, vectors are JS arrays, sets are JS Sets. No persistent data structures:
19+
20+
- `assoc`, `conj`, `update` return shallow copies — the original is not mutated (REPL-verified)
21+
- However, nested objects are shared — deep mutations on nested values affect both original and copy
22+
- `(= a b)` on maps/vectors is reference equality, not structural — use caution in tests
23+
- `assoc!` does NOT exist — use `assoc`
24+
25+
## Async: `^:async` + `js-await`
26+
27+
- Mark functions with `^:async` metadata — they return Promises
28+
- Both `js-await` and bare `await` work in Squint (REPL-verified). Convention: use `js-await` in Squint code for clarity, since bare `await` is the SCI convention.
29+
- No top-level `js-await` — must be inside an `^:async` function
30+
- `doseq` does NOT properly await `js-await` calls — iterations fire concurrently
31+
- Use `loop`/`recur` for sequential async iteration
32+
- `js/Promise.all` works for intentionally parallel execution
33+
34+
## No `js->clj` / `clj->js`
35+
36+
These functions do not exist in Squint. They compile to unqualified bare calls (`js__GT_clj()`) that crash at runtime with `ReferenceError`:
37+
38+
- Data is already JS-native — no conversion needed
39+
- Keyword maps are already string-keyed JS objects
40+
- Access properties with `get`, `aget`, or `(.-prop obj)` directly
41+
42+
## `nil` is `null`
43+
44+
Squint compiles `nil` to JavaScript `null` (REPL-verified: `nil === null` is `true`, `nil === undefined` is `false`):
45+
46+
- `nil?` returns `true` for both `null` and `undefined` (uses JS loose equality)
47+
- `identical?` distinguishes them: `(identical? nil js/undefined)``false`
48+
- APIs that return `undefined` (e.g., missing object properties) test as `nil?` but are not `identical?` to `nil`
49+
50+
## Core Library Gaps
51+
52+
Several `clojure.core` functions are missing or behave differently:
53+
54+
- `name` — does not exist. Keywords are already strings; use direct string operations
55+
- `sequential?` — returns `true` for strings (they are JS arrays of chars). Use `vector?` to distinguish
56+
- Set literals are callable inline — `(#{:a :b} :a)` works — but sets bound to vars are not callable (REPL-verified). Use `(contains? s :a)` for consistent behavior.
57+
- No auto-resolved keywords — `::my-key` is a compiler error. Spell out the namespace
58+
- `count` works on arrays and strings but not on all collection types uniformly
59+
- `keyword` function does not exist — use the string directly
60+
61+
## Property Access and Interop
62+
63+
- `(.-my-prop obj)` converts hyphens to underscores → `obj.my_prop`
64+
- Use `(aget obj "my-prop")` for literal property names with hyphens
65+
- `(:key obj)` works (compiles to string lookup on JS object)
66+
- Bare namespace requires fail — always use vector form: `(:require [lib :as alias])`
67+
- `js/` prefix works for global access: `js/document`, `js/console.log`, `js/fetch`
68+
69+
## Compilation Model
70+
71+
Squint compiles `.cljs` files to ES modules (`.mjs`). Key implications:
72+
73+
- Unrecognized symbols emit bare JavaScript calls — silent at compile time, crashes at runtime
74+
- This is the most common source of `ReferenceError: X is not defined` errors
75+
- Always check compilation output when a runtime error seems impossible from the source
76+
- `require` compiles to ES `import` — circular dependencies cause the same issues as in JS

0 commit comments

Comments
 (0)