conjure-js 0.0.12 → 0.0.13

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.
Files changed (77) hide show
  1. package/dist-cli/conjure-js.mjs +9360 -5298
  2. package/dist-vite-plugin/index.mjs +9463 -5185
  3. package/package.json +3 -1
  4. package/src/bin/cli.ts +2 -2
  5. package/src/bin/nrepl-symbol.ts +150 -0
  6. package/src/bin/nrepl.ts +289 -167
  7. package/src/bin/version.ts +1 -1
  8. package/src/clojure/core.clj +757 -29
  9. package/src/clojure/core.clj.d.ts +75 -131
  10. package/src/clojure/generated/builtin-namespace-registry.ts +4 -0
  11. package/src/clojure/generated/clojure-core-source.ts +758 -29
  12. package/src/clojure/generated/clojure-set-source.ts +136 -0
  13. package/src/clojure/generated/clojure-walk-source.ts +72 -0
  14. package/src/clojure/set.clj +132 -0
  15. package/src/clojure/set.clj.d.ts +20 -0
  16. package/src/clojure/string.clj.d.ts +14 -0
  17. package/src/clojure/walk.clj +68 -0
  18. package/src/clojure/walk.clj.d.ts +7 -0
  19. package/src/core/assertions.ts +114 -6
  20. package/src/core/bootstrap.ts +337 -0
  21. package/src/core/conversions.ts +48 -31
  22. package/src/core/core-module.ts +303 -0
  23. package/src/core/env.ts +20 -6
  24. package/src/core/evaluator/apply.ts +40 -25
  25. package/src/core/evaluator/arity.ts +8 -8
  26. package/src/core/evaluator/async-evaluator.ts +565 -0
  27. package/src/core/evaluator/collections.ts +28 -5
  28. package/src/core/evaluator/destructure.ts +180 -69
  29. package/src/core/evaluator/dispatch.ts +12 -14
  30. package/src/core/evaluator/evaluate.ts +22 -20
  31. package/src/core/evaluator/expand.ts +45 -15
  32. package/src/core/evaluator/form-parsers.ts +178 -0
  33. package/src/core/evaluator/index.ts +7 -9
  34. package/src/core/evaluator/js-interop.ts +189 -0
  35. package/src/core/evaluator/quasiquote.ts +14 -8
  36. package/src/core/evaluator/recur-check.ts +6 -6
  37. package/src/core/evaluator/special-forms.ts +234 -191
  38. package/src/core/factories.ts +182 -3
  39. package/src/core/index.ts +54 -4
  40. package/src/core/module.ts +136 -0
  41. package/src/core/ns-forms.ts +107 -0
  42. package/src/core/printer.ts +371 -11
  43. package/src/core/reader.ts +84 -33
  44. package/src/core/registry.ts +209 -0
  45. package/src/core/runtime.ts +376 -0
  46. package/src/core/session.ts +253 -487
  47. package/src/core/stdlib/arithmetic.ts +528 -194
  48. package/src/core/stdlib/async-fns.ts +132 -0
  49. package/src/core/stdlib/atoms.ts +291 -56
  50. package/src/core/stdlib/errors.ts +54 -50
  51. package/src/core/stdlib/hof.ts +82 -166
  52. package/src/core/stdlib/js-namespace.ts +344 -0
  53. package/src/core/stdlib/lazy.ts +34 -0
  54. package/src/core/stdlib/maps-sets.ts +322 -0
  55. package/src/core/stdlib/meta.ts +61 -30
  56. package/src/core/stdlib/predicates.ts +325 -187
  57. package/src/core/stdlib/regex.ts +126 -98
  58. package/src/core/stdlib/seq.ts +564 -0
  59. package/src/core/stdlib/strings.ts +164 -135
  60. package/src/core/stdlib/transducers.ts +95 -100
  61. package/src/core/stdlib/utils.ts +292 -130
  62. package/src/core/stdlib/vars.ts +27 -27
  63. package/src/core/stdlib/vectors.ts +122 -0
  64. package/src/core/tokenizer.ts +2 -2
  65. package/src/core/transformations.ts +117 -9
  66. package/src/core/types.ts +98 -2
  67. package/src/host/node-host-module.ts +74 -0
  68. package/src/{vite-plugin-clj/nrepl-relay.ts → nrepl/relay.ts} +72 -11
  69. package/src/vite-plugin-clj/codegen.ts +87 -95
  70. package/src/vite-plugin-clj/index.ts +178 -23
  71. package/src/vite-plugin-clj/namespace-utils.ts +39 -0
  72. package/src/vite-plugin-clj/static-analysis.ts +211 -0
  73. package/src/clojure/demo.clj +0 -72
  74. package/src/clojure/demo.clj.d.ts +0 -0
  75. package/src/core/core-env.ts +0 -61
  76. package/src/core/stdlib/collections.ts +0 -739
  77. package/src/host/node.ts +0 -55
@@ -0,0 +1,337 @@
1
+ import { isNamespace, isSymbol } from './assertions'
2
+ import { internVar, makeNamespace, tryLookup } from './env'
3
+ import { EvaluationError } from './errors'
4
+ import { v } from './factories'
5
+ import type { CljNamespace, CljValue, Env, EvaluationContext } from './types'
6
+ import { ensureNamespaceInRegistry, processRequireSpec } from './registry'
7
+ import type { NamespaceRegistry } from './registry'
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // wireNsCore — wires *ns*, namespace introspection fns, require, and resolve
11
+ // into coreEnv. Called from buildRuntime with explicit parameters instead of
12
+ // relying on shared closure state.
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export function wireNsCore(
16
+ registry: NamespaceRegistry,
17
+ coreEnv: Env,
18
+ getCurrentNs: () => string,
19
+ resolveNamespace: (nsName: string, ctx: EvaluationContext) => boolean
20
+ ): void {
21
+ // *ns* var — holds the current namespace as a CljNamespace value
22
+ const initialNsObj = registry.get('user')?.ns ?? makeNamespace('user')
23
+ internVar('*ns*', initialNsObj, coreEnv)
24
+ const nsVar = coreEnv.ns?.vars.get('*ns*')
25
+ if (nsVar) nsVar.dynamic = true
26
+
27
+ // Helper: resolve a namespace symbol (or namespace object) to its CljNamespace
28
+ function resolveNsSym(sym: CljValue): CljNamespace | null {
29
+ if (sym === undefined) return null
30
+ if (isNamespace(sym)) return sym
31
+ if (!isSymbol(sym)) return null
32
+ return registry.get(sym.name)?.ns ?? null
33
+ }
34
+
35
+ // Namespace introspection
36
+ internVar(
37
+ 'ns-name',
38
+ v.nativeFn('ns-name', (x: CljValue) => {
39
+ if (x === undefined) return v.nil()
40
+ if (x.kind === 'namespace') return v.symbol(x.name)
41
+ if (x.kind === 'symbol') return x
42
+ if (x.kind === 'string') return v.symbol(x.value)
43
+ return v.nil()
44
+ }),
45
+ coreEnv
46
+ )
47
+
48
+ internVar(
49
+ 'all-ns',
50
+ v.nativeFn('all-ns', () =>
51
+ v.list([...registry.values()].map((env) => env.ns!).filter(Boolean))
52
+ ),
53
+ coreEnv
54
+ )
55
+
56
+ internVar(
57
+ 'find-ns',
58
+ v.nativeFn('find-ns', (sym: CljValue) => {
59
+ if (sym === undefined || !isSymbol(sym)) return v.nil()
60
+ return registry.get(sym.name)?.ns ?? v.nil()
61
+ }),
62
+ coreEnv
63
+ )
64
+
65
+ internVar(
66
+ 'in-ns',
67
+ v.nativeFnCtx('in-ns', (ctx, _callEnv, sym: CljValue) => {
68
+ if (!sym || !isSymbol(sym)) {
69
+ throw new EvaluationError('in-ns expects a symbol', { sym })
70
+ }
71
+ if (ctx.setCurrentNs) ctx.setCurrentNs(sym.name)
72
+ return registry.get(sym.name)?.ns ?? v.nil()
73
+ }),
74
+ coreEnv
75
+ )
76
+
77
+ internVar(
78
+ 'ns-aliases',
79
+ v.nativeFn('ns-aliases', (sym: CljValue) => {
80
+ const ns = resolveNsSym(sym)
81
+ if (!ns) return v.map([])
82
+ const entries: [CljValue, CljValue][] = []
83
+ ns.aliases.forEach((targetNs, alias) => {
84
+ entries.push([v.symbol(alias), targetNs])
85
+ })
86
+ return v.map(entries)
87
+ }),
88
+ coreEnv
89
+ )
90
+
91
+ internVar(
92
+ 'ns-interns',
93
+ v.nativeFn('ns-interns', (sym: CljValue) => {
94
+ const ns = resolveNsSym(sym)
95
+ if (!ns) return v.map([])
96
+ const entries: [CljValue, CljValue][] = []
97
+ ns.vars.forEach((theVar, name) => {
98
+ if (theVar.ns === ns.name) entries.push([v.symbol(name), theVar])
99
+ })
100
+ return v.map(entries)
101
+ }),
102
+ coreEnv
103
+ )
104
+
105
+ internVar(
106
+ 'ns-publics',
107
+ v.nativeFn('ns-publics', (sym: CljValue) => {
108
+ const ns = resolveNsSym(sym)
109
+ if (!ns) return v.map([])
110
+ const entries: [CljValue, CljValue][] = []
111
+ ns.vars.forEach((theVar, name) => {
112
+ if (theVar.ns !== ns.name) return
113
+ const isPrivate = (theVar.meta?.entries ?? []).some(
114
+ ([k, val]) =>
115
+ k.kind === 'keyword' && k.name === ':private' &&
116
+ val.kind === 'boolean' && val.value === true
117
+ )
118
+ if (!isPrivate) entries.push([v.symbol(name), theVar])
119
+ })
120
+ return v.map(entries)
121
+ }),
122
+ coreEnv
123
+ )
124
+
125
+ internVar(
126
+ 'ns-refers',
127
+ v.nativeFn('ns-refers', (sym: CljValue) => {
128
+ const ns = resolveNsSym(sym)
129
+ if (!ns) return v.map([])
130
+ const entries: [CljValue, CljValue][] = []
131
+ ns.vars.forEach((theVar, name) => {
132
+ if (theVar.ns !== ns.name) entries.push([v.symbol(name), theVar])
133
+ })
134
+ return v.map(entries)
135
+ }),
136
+ coreEnv
137
+ )
138
+
139
+ internVar(
140
+ 'ns-map',
141
+ v.nativeFn('ns-map', (sym: CljValue) => {
142
+ const ns = resolveNsSym(sym)
143
+ if (!ns) return v.map([])
144
+ const entries: [CljValue, CljValue][] = []
145
+ ns.vars.forEach((theVar, name) => {
146
+ entries.push([v.symbol(name), theVar])
147
+ })
148
+ return v.map(entries)
149
+ }),
150
+ coreEnv
151
+ )
152
+
153
+ internVar(
154
+ 'ns-imports',
155
+ v.nativeFn('ns-imports', (_sym: CljValue) => v.map([])),
156
+ coreEnv
157
+ )
158
+
159
+ internVar(
160
+ 'the-ns',
161
+ v.nativeFn('the-ns', (sym: CljValue) => {
162
+ if (sym === undefined) return v.nil()
163
+ if (isNamespace(sym)) return sym
164
+ if (!isSymbol(sym)) return v.nil()
165
+ return registry.get(sym.name)?.ns ?? v.nil()
166
+ }),
167
+ coreEnv
168
+ )
169
+
170
+ internVar(
171
+ 'instance?',
172
+ v.nativeFn('instance?', (_cls: CljValue, _obj: CljValue) =>
173
+ v.boolean(false)
174
+ ),
175
+ coreEnv
176
+ )
177
+
178
+ internVar(
179
+ 'class',
180
+ v.nativeFn('class', (x: CljValue) => {
181
+ if (x === undefined) return v.nil()
182
+ return v.string(`conjure.${x.kind}`)
183
+ }),
184
+ coreEnv
185
+ )
186
+
187
+ internVar(
188
+ 'class?',
189
+ v.nativeFn('class?', (_x: CljValue) => v.boolean(false)),
190
+ coreEnv
191
+ )
192
+
193
+ internVar(
194
+ 'special-symbol?',
195
+ v.nativeFn('special-symbol?', (sym: CljValue) => {
196
+ if (sym === undefined || !isSymbol(sym)) return v.boolean(false)
197
+ const specials = new Set([
198
+ 'def',
199
+ 'if',
200
+ 'do',
201
+ 'let',
202
+ 'quote',
203
+ 'var',
204
+ 'fn',
205
+ 'loop',
206
+ 'recur',
207
+ 'throw',
208
+ 'try',
209
+ 'catch',
210
+ 'finally',
211
+ 'ns',
212
+ 'defmacro',
213
+ 'binding',
214
+ 'monitor-enter',
215
+ 'monitor-exit',
216
+ 'new',
217
+ 'set!',
218
+ '.',
219
+ 'import',
220
+ ])
221
+ return v.boolean(specials.has(sym.name))
222
+ }),
223
+ coreEnv
224
+ )
225
+
226
+ internVar(
227
+ 'loaded-libs',
228
+ v.nativeFn('loaded-libs', () => v.set([...registry.keys()].map(v.symbol))),
229
+ coreEnv
230
+ )
231
+
232
+ // require — context-aware so it can thread ctx to resolveNamespace
233
+ internVar(
234
+ 'require',
235
+ v.nativeFnCtx('require', (ctx, _callEnv, ...args: CljValue[]) => {
236
+ const currentEnv = registry.get(getCurrentNs())!
237
+ for (const arg of args) {
238
+ processRequireSpec(arg, currentEnv, registry, (nsName) =>
239
+ resolveNamespace(nsName, ctx)
240
+ )
241
+ }
242
+ return v.nil()
243
+ }),
244
+ coreEnv
245
+ )
246
+
247
+ internVar(
248
+ 'resolve',
249
+ v.nativeFn('resolve', (sym: CljValue) => {
250
+ if (!isSymbol(sym)) return v.nil()
251
+ const slashIdx = sym.name.indexOf('/')
252
+ if (slashIdx > 0) {
253
+ const nsName = sym.name.slice(0, slashIdx)
254
+ const symName = sym.name.slice(slashIdx + 1)
255
+ const nsEnv = registry.get(nsName) ?? null
256
+ if (!nsEnv) return v.nil()
257
+ return tryLookup(symName, nsEnv) ?? v.nil()
258
+ }
259
+ const currentEnv = registry.get(getCurrentNs())!
260
+ return tryLookup(sym.name, currentEnv) ?? v.nil()
261
+ }),
262
+ coreEnv
263
+ )
264
+ }
265
+
266
+ // ---------------------------------------------------------------------------
267
+ // wireIdeStubs — wires clojure.reflect, cursive.repl.runtime, and Java class
268
+ // stubs into the registry. These are no-op shims for IDE compatibility.
269
+ // ---------------------------------------------------------------------------
270
+
271
+ export function wireIdeStubs(registry: NamespaceRegistry, coreEnv: Env): void {
272
+ // IDE stubs: clojure.reflect
273
+ const reflectEnv = ensureNamespaceInRegistry(
274
+ registry,
275
+ coreEnv,
276
+ 'clojure.reflect'
277
+ )
278
+ internVar(
279
+ 'parse-flags',
280
+ v.nativeFn('parse-flags', (_flags: CljValue, _kind: CljValue) => v.set([])),
281
+ reflectEnv
282
+ )
283
+ internVar(
284
+ 'reflect',
285
+ v.nativeFn('reflect', (_obj: CljValue) => v.map([])),
286
+ reflectEnv
287
+ )
288
+ internVar(
289
+ 'type-reflect',
290
+ v.nativeFn('type-reflect', (_typeobj: CljValue, ..._opts: CljValue[]) =>
291
+ v.map([])
292
+ ),
293
+ reflectEnv
294
+ )
295
+
296
+ // IDE stubs: cursive.repl.runtime
297
+ const cursiveEnv = ensureNamespaceInRegistry(
298
+ registry,
299
+ coreEnv,
300
+ 'cursive.repl.runtime'
301
+ )
302
+ internVar(
303
+ 'completions',
304
+ v.nativeFn('completions', (..._args: CljValue[]) => v.nil()),
305
+ cursiveEnv
306
+ )
307
+
308
+ // Java class stubs — Cursive references these as bare symbols for type checks
309
+ for (const javaClass of [
310
+ 'Class',
311
+ 'Object',
312
+ 'String',
313
+ 'Number',
314
+ 'Boolean',
315
+ 'Integer',
316
+ 'Long',
317
+ 'Double',
318
+ 'Float',
319
+ 'Byte',
320
+ 'Short',
321
+ 'Character',
322
+ 'Void',
323
+ 'Math',
324
+ 'System',
325
+ 'Runtime',
326
+ 'Thread',
327
+ 'Throwable',
328
+ 'Exception',
329
+ 'Error',
330
+ 'Iterable',
331
+ 'Comparable',
332
+ 'Runnable',
333
+ 'Cloneable',
334
+ ]) {
335
+ internVar(javaClass, v.keyword(`:java.lang/${javaClass}`), coreEnv)
336
+ }
337
+ }
@@ -1,15 +1,5 @@
1
1
  import { isCljValue } from './assertions'
2
- import { applyFunction } from './evaluator'
3
- import {
4
- cljBoolean,
5
- cljKeyword,
6
- cljMap,
7
- cljNativeFunction,
8
- cljNil,
9
- cljNumber,
10
- cljString,
11
- cljVector,
12
- } from './factories'
2
+ import { v } from './factories'
13
3
  import type { CljValue } from './types'
14
4
 
15
5
  export class ConversionError extends Error {
@@ -21,9 +11,25 @@ export class ConversionError extends Error {
21
11
  }
22
12
  }
23
13
 
14
+ export type FunctionApplier = {
15
+ applyFunction: (fn: CljValue, args: CljValue[]) => CljValue
16
+ }
17
+
24
18
  const richKeyKinds = new Set(['list', 'vector', 'map'])
25
19
 
26
- export function cljToJs(value: CljValue): unknown {
20
+ // Used inside jsToClj's function wrapper when converting CLJ args back to JS.
21
+ // CLJ args passed to JS-wrapped functions are almost always data values, so
22
+ // no applier is needed. If a CLJ function is encountered here, we throw a clear
23
+ // error — use session.cljToJs() for function-bearing values.
24
+ const _throwingApplier: FunctionApplier = {
25
+ applyFunction: () => {
26
+ throw new ConversionError(
27
+ 'Cannot convert a CLJ function to JS in this context — use session.cljToJs() instead.'
28
+ )
29
+ },
30
+ }
31
+
32
+ export function cljToJs(value: CljValue, applier: FunctionApplier): unknown {
27
33
  switch (value.kind) {
28
34
  case 'number':
29
35
  return value.value
@@ -39,18 +45,18 @@ export function cljToJs(value: CljValue): unknown {
39
45
  return value.name
40
46
  case 'list':
41
47
  case 'vector':
42
- return value.value.map(cljToJs)
48
+ return value.value.map((item) => cljToJs(item, applier))
43
49
  case 'map': {
44
50
  const obj: Record<string, unknown> = {}
45
- for (const [k, v] of value.entries) {
51
+ for (const [k, val] of value.entries) {
46
52
  if (richKeyKinds.has(k.kind)) {
47
53
  throw new ConversionError(
48
54
  `Rich key types (${k.kind}) are not supported in JS object conversion. Restructure your map to use string, keyword, or number keys.`,
49
- { key: k, value: v }
55
+ { key: k, value: val }
50
56
  )
51
57
  }
52
- const jsKey = String(cljToJs(k))
53
- obj[jsKey] = cljToJs(v)
58
+ const jsKey = String(cljToJs(k, applier))
59
+ obj[jsKey] = cljToJs(val, applier)
54
60
  }
55
61
  return obj
56
62
  }
@@ -58,9 +64,9 @@ export function cljToJs(value: CljValue): unknown {
58
64
  case 'native-function': {
59
65
  const fn = value
60
66
  return (...jsArgs: unknown[]) => {
61
- const cljArgs = jsArgs.map(jsToClj)
62
- const result = applyFunction(fn, cljArgs)
63
- return cljToJs(result)
67
+ const cljArgs = jsArgs.map((a) => jsToClj(a))
68
+ const result = applier.applyFunction(fn, cljArgs)
69
+ return cljToJs(result, applier)
64
70
  }
65
71
  }
66
72
  case 'macro':
@@ -71,33 +77,44 @@ export function cljToJs(value: CljValue): unknown {
71
77
  }
72
78
  }
73
79
 
74
- export function jsToClj(value: unknown): CljValue {
75
- if (value === null || value === undefined) return cljNil()
80
+ export interface JsToCljOpts {
81
+ /** When true, plain object keys become keywords. Default: true. */
82
+ keywordizeKeys?: boolean
83
+ }
84
+
85
+ export function jsToClj(value: unknown, opts: JsToCljOpts = {}): CljValue {
86
+ const { keywordizeKeys = true } = opts
87
+
88
+ if (value === null) return v.nil()
89
+ if (value === undefined) return v.jsValue(undefined)
76
90
  if (isCljValue(value)) return value
77
91
 
78
92
  switch (typeof value) {
79
93
  case 'number':
80
- return cljNumber(value)
94
+ return v.number(value)
81
95
  case 'string':
82
- return cljString(value)
96
+ return v.string(value)
83
97
  case 'boolean':
84
- return cljBoolean(value)
98
+ return v.boolean(value)
85
99
  case 'function': {
86
100
  const jsFn = value as (...args: unknown[]) => unknown
87
- return cljNativeFunction('js-fn', (...cljArgs: CljValue[]) => {
88
- const jsArgs = cljArgs.map(cljToJs)
101
+ return v.nativeFn('js-fn', (...cljArgs: CljValue[]) => {
102
+ const jsArgs = cljArgs.map((a) => cljToJs(a, _throwingApplier))
89
103
  const result = jsFn(...jsArgs)
90
- return jsToClj(result)
104
+ return jsToClj(result, opts)
91
105
  })
92
106
  }
93
107
  case 'object': {
94
108
  if (Array.isArray(value)) {
95
- return cljVector(value.map(jsToClj))
109
+ return v.vector(value.map((item) => jsToClj(item, opts)))
96
110
  }
97
111
  const entries: [CljValue, CljValue][] = Object.entries(
98
112
  value as Record<string, unknown>
99
- ).map(([k, v]) => [cljKeyword(`:${k}`), jsToClj(v)])
100
- return cljMap(entries)
113
+ ).map(([k, val]) => [
114
+ keywordizeKeys ? v.keyword(`:${k}`) : v.string(k),
115
+ jsToClj(val, opts),
116
+ ])
117
+ return v.map(entries)
101
118
  }
102
119
  default:
103
120
  throw new ConversionError(