conjure-js 0.0.10 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +259 -0
- package/dist-cli/conjure-js.mjs +1 -1
- package/package.json +3 -2
- package/src/bin/version.ts +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# Conjure
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/conjure-js)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
A Clojure interpreter written in TypeScript. Runs on Bun as a standalone CLI, embeds in any JS/TS project as a library, and exposes a full nREPL server compatible with Calva, Cursive, and CIDER.
|
|
7
|
+
|
|
8
|
+
**[Try it in the browser →](https://regibyte.github.io/conjure-js/)**
|
|
9
|
+
|
|
10
|
+
***
|
|
11
|
+
|
|
12
|
+
## What it is
|
|
13
|
+
|
|
14
|
+
Conjure is an **interpreter**. Source code is read, macro-expanded, and evaluated at runtime. There is no compilation step and no bytecode — the evaluator walks the AST directly.
|
|
15
|
+
|
|
16
|
+
It is designed to be embedded. The core session API is a plain TypeScript object: create a session, inject host functions, evaluate strings. The CLI and nREPL server are thin wrappers around the same session.
|
|
17
|
+
|
|
18
|
+
It is **not** a compiler. There is no Clojure → JavaScript code generation today. That is a long-horizon goal described in the roadmap.
|
|
19
|
+
|
|
20
|
+
***
|
|
21
|
+
|
|
22
|
+
## Features
|
|
23
|
+
|
|
24
|
+
### Language
|
|
25
|
+
|
|
26
|
+
* Immutable collections (no structural sharing): vectors, maps, sets, lists
|
|
27
|
+
* Namespaces with `ns`, `require`, `refer`, `alias`
|
|
28
|
+
* Multi-arity and variadic functions
|
|
29
|
+
* Sequential and associative destructuring, including nested patterns, `:keys`, `:syms`, `:strs`, qualified keys, and `& {:keys [...]}` kwargs
|
|
30
|
+
* Macros: `defmacro`, quasiquote/unquote/splicing, `macroexpand`, `macroexpand-all`
|
|
31
|
+
* Atoms for controlled mutable state
|
|
32
|
+
* `loop`/`recur` with tail-call optimization
|
|
33
|
+
* Transducers: `transduce`, `into` with xf
|
|
34
|
+
* Threading macros: `->`, `->>`
|
|
35
|
+
* Anonymous function shorthand: `#(+ % 1)`
|
|
36
|
+
|
|
37
|
+
### Standard Library
|
|
38
|
+
|
|
39
|
+
`clojure.core` and `clojure.string` are implemented in Clojure itself and loaded at session startup. This means the standard library is readable, forkable, and patchable without touching TypeScript.
|
|
40
|
+
|
|
41
|
+
### Error Handling
|
|
42
|
+
|
|
43
|
+
`try/catch/finally` where any value can be thrown and catch clauses use discriminators to decide what to handle. There is no class hierarchy. The discriminator in each `catch` clause is one of:
|
|
44
|
+
|
|
45
|
+
* **`:default`** — catches everything
|
|
46
|
+
* **`:error/runtime`** — catches interpreter-level errors (type errors, arity errors, etc.)
|
|
47
|
+
* **A keyword** — catches when the thrown value is a map whose `:type` key equals that keyword
|
|
48
|
+
* **A predicate function** — catches when `(pred thrown-value)` is truthy
|
|
49
|
+
|
|
50
|
+
```clojure
|
|
51
|
+
;; keyword discriminator — matches (:type thrown-value)
|
|
52
|
+
(try
|
|
53
|
+
(throw {:type :error/not-found :id 99})
|
|
54
|
+
(catch :error/not-found e (:id e))) ;; => 99
|
|
55
|
+
|
|
56
|
+
;; predicate discriminator — matches any map
|
|
57
|
+
(try
|
|
58
|
+
(throw {:type :error/not-found :id 99})
|
|
59
|
+
(catch map? e "got a map")) ;; => "got a map"
|
|
60
|
+
|
|
61
|
+
;; catch everything
|
|
62
|
+
(try
|
|
63
|
+
(/ 1 0)
|
|
64
|
+
(catch :default e (ex-message e)))
|
|
65
|
+
|
|
66
|
+
;; catch interpreter errors
|
|
67
|
+
(try
|
|
68
|
+
(+ 1 "not-a-number")
|
|
69
|
+
(catch :error/runtime e (ex-message e)))
|
|
70
|
+
|
|
71
|
+
;; ex-info produces a plain map {:message "..." :data {...}}
|
|
72
|
+
;; to catch it by keyword, put :type in the data map and throw the ex-info result
|
|
73
|
+
(defn validate! [x]
|
|
74
|
+
(when (neg? x)
|
|
75
|
+
(throw (assoc (ex-info "Negative value" {:value x}) :type :error/validation))))
|
|
76
|
+
|
|
77
|
+
(try
|
|
78
|
+
(validate! -1)
|
|
79
|
+
(catch :error/validation e
|
|
80
|
+
{:msg (ex-message e) :data (ex-data e)}))
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### nREPL Server
|
|
84
|
+
|
|
85
|
+
Full TCP nREPL server with bencode transport. Supports `eval`, `load-file`, `complete`, `clone`, `close`, `describe`, and `interrupt`. Namespace switching after `load-file` is handled automatically.
|
|
86
|
+
|
|
87
|
+
Writes `.nrepl-port` on startup for auto-connect.
|
|
88
|
+
|
|
89
|
+
### Host I/O
|
|
90
|
+
|
|
91
|
+
`slurp`, `spit`, and `load` are available in both the CLI and nREPL sessions.
|
|
92
|
+
|
|
93
|
+
***
|
|
94
|
+
|
|
95
|
+
## Key Differences from JVM Clojure
|
|
96
|
+
|
|
97
|
+
Conjure is semantically close to Clojure but runs on a JavaScript host. The following are not implemented and are not planned for the interpreter phase:
|
|
98
|
+
|
|
99
|
+
| JVM Clojure | Conjure |
|
|
100
|
+
|---|---|
|
|
101
|
+
| Java interop (`.method`, `new Foo`, `java.lang.*`) | Not available |
|
|
102
|
+
| `deftype`, `defrecord`, `defprotocol` | Not available |
|
|
103
|
+
| `gen-class` | Not available |
|
|
104
|
+
| `future`, `promise`, `agent`, `ref`, STM | Not available — use `atom` |
|
|
105
|
+
| `Long`, `BigDecimal`, ratio literals (`1/3`) | Numbers are JS floats |
|
|
106
|
+
| Class-based `catch` (`catch Exception e`) | Predicate-based catch only |
|
|
107
|
+
| `import`, Java class hierarchy | Not available |
|
|
108
|
+
|
|
109
|
+
The core data model, namespace system, macro system, and standard library semantics match Clojure closely. Code that avoids Java interop and JVM-specific types will generally run without modification.
|
|
110
|
+
|
|
111
|
+
***
|
|
112
|
+
|
|
113
|
+
## Installation
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npm install -g conjure-js
|
|
117
|
+
# or
|
|
118
|
+
bun install -g conjure-js
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The main host environment is Bun's runtime
|
|
122
|
+
You may need to install [Bun](https://bun.sh) for certain features.
|
|
123
|
+
|
|
124
|
+
***
|
|
125
|
+
|
|
126
|
+
## Getting Started
|
|
127
|
+
|
|
128
|
+
### Interactive REPL
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
conjure-js repl
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
Conjure 0.0.1
|
|
136
|
+
Type (exit) to exit the REPL.
|
|
137
|
+
user=> (map #(* % %) [1 2 3 4 5])
|
|
138
|
+
(1 4 9 16 25)
|
|
139
|
+
user=> (defn greet [name] (str "Hello, " name "!"))
|
|
140
|
+
#'user/greet
|
|
141
|
+
user=> (greet "World")
|
|
142
|
+
"Hello, World!"
|
|
143
|
+
user=> (exit)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Run a File
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
conjure-js run my-script.clj
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### nREPL Server
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
conjure-js nrepl-server
|
|
156
|
+
# Conjure nREPL server 0.0.1 started on port 7888
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Options:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
conjure-js nrepl-server --port 7889 --host 0.0.0.0
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
#### Connecting with Calva (VS Code)
|
|
166
|
+
|
|
167
|
+
Add to `.vscode/settings.json` in your project:
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"calva.replConnectSequences": [
|
|
172
|
+
{
|
|
173
|
+
"name": "Conjure nREPL",
|
|
174
|
+
"projectType": "generic",
|
|
175
|
+
"nReplPortFile": [".nrepl-port"]
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Then: **Calva: Connect to Running REPL Server in Project** → select `Conjure nREPL`.
|
|
182
|
+
|
|
183
|
+
#### Connecting with CIDER (Emacs)
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
M-x cider-connect RET
|
|
187
|
+
Host: localhost RET
|
|
188
|
+
Port: 7888 RET
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
#### Connecting with Cursive (IntelliJ)
|
|
192
|
+
|
|
193
|
+
Run → Edit Configurations → `+` → Clojure REPL → Remote → host `localhost`, port `7888`.
|
|
194
|
+
|
|
195
|
+
### Embed as a Library
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { createSession, printString } from 'conjure-js/src/core'
|
|
199
|
+
|
|
200
|
+
const session = createSession({
|
|
201
|
+
output: (text) => console.log(text),
|
|
202
|
+
sourceRoots: ['src/clojure'],
|
|
203
|
+
readFile: (path) => fs.readFileSync(path, 'utf8'),
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
const result = session.evaluate('(map inc [1 2 3])')
|
|
207
|
+
console.log(printString(result)) // (2 3 4)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
***
|
|
211
|
+
|
|
212
|
+
## Source Root Discovery
|
|
213
|
+
|
|
214
|
+
When running `conjure-js nrepl-server` or `conjure-js run`, Conjure looks for source roots by reading the `conjure.sourceRoots` field in `package.json`:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"conjure": {
|
|
219
|
+
"sourceRoots": ["src/clojure"]
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
If no config is found, it falls back to the current working directory. Source roots control how `require` resolves namespace files.
|
|
225
|
+
|
|
226
|
+
***
|
|
227
|
+
|
|
228
|
+
## Roadmap
|
|
229
|
+
|
|
230
|
+
### REPL UX
|
|
231
|
+
|
|
232
|
+
Multiline input with bracket-depth tracking (continuation prompt `...=>`), ANSI color output, and persistent history.
|
|
233
|
+
|
|
234
|
+
### nREPL Completeness
|
|
235
|
+
|
|
236
|
+
Full bencode op coverage: symbol info, docstring lookup, source location, cross-namespace navigation. The goal is feature parity with what Calva and CIDER expect from a production nREPL server.
|
|
237
|
+
|
|
238
|
+
### Browser nREPL Bridge
|
|
239
|
+
|
|
240
|
+
The Vite plugin (`vite-plugin-conjure`) will spawn a WebSocket nREPL endpoint alongside the dev server. A small runtime injected into the browser page connects to the WebSocket and acts as the nREPL evaluation target. Any compatible nREPL client — Calva, Cursive, CIDER — will be able to evaluate Clojure code that runs live in the browser, with full access to the DOM and the running application state.
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// vite.config.ts
|
|
244
|
+
cljPlugin({ sourceRoots: ['src'], nreplPort: 7889 })
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### JS Interop
|
|
248
|
+
|
|
249
|
+
A minimal, explicit host interface for calling JavaScript from Clojure: `js/box`, `js/get`, `js/call`, `js/invoke`. Values cross the boundary explicitly — no implicit coercion.
|
|
250
|
+
|
|
251
|
+
### Compiler (Long Horizon)
|
|
252
|
+
|
|
253
|
+
Clojure → JavaScript/TypeScript code generation, built on the existing macro expansion layer. The long-term goal is a self-hosting compiler: the compiler written in Conjure and compiled with itself.
|
|
254
|
+
|
|
255
|
+
***
|
|
256
|
+
|
|
257
|
+
## License
|
|
258
|
+
|
|
259
|
+
MIT
|
package/dist-cli/conjure-js.mjs
CHANGED
|
@@ -5431,7 +5431,7 @@ class BDecoderStream extends stream.Transform {
|
|
|
5431
5431
|
}
|
|
5432
5432
|
|
|
5433
5433
|
// src/bin/version.ts
|
|
5434
|
-
var VERSION = "0.0.
|
|
5434
|
+
var VERSION = "0.0.11";
|
|
5435
5435
|
|
|
5436
5436
|
// src/host/node.ts
|
|
5437
5437
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conjure-js",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.11",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"!src/**/*.test.ts",
|
|
28
28
|
"!src/demo-entry.ts",
|
|
29
29
|
"tsconfig.json",
|
|
30
|
-
"tsconfig.build.json"
|
|
30
|
+
"tsconfig.build.json",
|
|
31
|
+
"README.md"
|
|
31
32
|
],
|
|
32
33
|
"keywords": [
|
|
33
34
|
"clojure",
|
package/src/bin/version.ts
CHANGED