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,565 @@
1
+ /**
2
+ * Async sub-evaluator for (async ...) blocks.
3
+ *
4
+ * ## Architecture invariant
5
+ *
6
+ * The SYNC evaluator (evaluate.ts / special-forms.ts / apply.ts) is the
7
+ * canonical path. This file is a thin async wrapper — it handles only the
8
+ * forms that can contain sub-expressions that must be awaited (CljPending
9
+ * values unwrapped via @). Everything else delegates to asyncCtx.syncCtx.
10
+ *
11
+ * Design rule: never add `await` to the sync evaluator. Even trivial forms
12
+ * like (+ 1 2) must remain zero-overhead synchronous. The async path pays
13
+ * the Promise overhead only for code inside (async ...) blocks.
14
+ *
15
+ * ## What needs an async handler vs. what delegates to sync
16
+ *
17
+ * Forms with their own async handler (can contain @ sub-expressions):
18
+ * - `if`, `do`, `let/let*`, `loop`, `recur`, `try`, `set!`
19
+ *
20
+ * Forms that are safe to delegate to syncCtx.evaluate:
21
+ * - `quote`, `var`, `fn/fn*`, `ns` — no sub-expression evaluation at the
22
+ * creation site; fn bodies are evaluated async only when the fn is called.
23
+ * - `defmacro`, `defmulti`, `defmethod`, `letfn`, `delay`, `lazy-seq`,
24
+ * `quasiquote` — create thunks or install definitions; content is
25
+ * evaluated lazily or later.
26
+ * - `binding` — V1 limitation: async-computed binding values are not
27
+ * supported. Bind the var before the async block and use set! if needed.
28
+ * - `.`, `js/new` — JS interop is sync; args are NOT awaited before the
29
+ * call (V1 limitation: deref @ pending values explicitly before the form).
30
+ * - `async` — nested async blocks create a new CljPending via the sync path.
31
+ * - `def` — throws with a helpful message; define vars outside async blocks.
32
+ *
33
+ * ## Revert instructions
34
+ *
35
+ * To remove the async feature: delete this file, remove the `async` case and
36
+ * its import in special-forms.ts, remove CljPending from types.ts, remove
37
+ * cljPending from factories.ts, remove the pending case from printer.ts,
38
+ * and delete async-fns.ts from stdlib.
39
+ *
40
+ * Design session: .regibyte/sessions/87-async-pending-design-and-plan.md
41
+ */
42
+
43
+ import { is } from '../assertions'
44
+ import { extend } from '../env'
45
+ import { CljThrownSignal, EvaluationError } from '../errors'
46
+ import { cljNil } from '../factories'
47
+ import type { CljList, CljValue, Env, EvaluationContext } from '../types'
48
+ import { bindParams, RecurSignal, resolveArity } from './arity'
49
+ import { destructureBindings } from './destructure'
50
+ import {
51
+ matchesDiscriminator,
52
+ parseTryStructure,
53
+ validateBindingVector,
54
+ } from './form-parsers'
55
+
56
+ // ---- AsyncEvalCtx ----
57
+ // A parallel evaluation context where all dispatch methods are async.
58
+ // Carries the original syncCtx for delegation of sync-only forms.
59
+
60
+ type AsyncEvalCtx = {
61
+ evaluate: (expr: CljValue, env: Env) => Promise<CljValue>
62
+ evaluateForms: (forms: CljValue[], env: Env) => Promise<CljValue>
63
+ applyCallable: (
64
+ fn: CljValue,
65
+ args: CljValue[],
66
+ callEnv: Env
67
+ ) => Promise<CljValue>
68
+ syncCtx: EvaluationContext
69
+ }
70
+
71
+ export function createAsyncEvalCtx(syncCtx: EvaluationContext): AsyncEvalCtx {
72
+ const asyncCtx: AsyncEvalCtx = {
73
+ syncCtx,
74
+ evaluate: (expr, env) => evaluateFormAsync(expr, env, asyncCtx),
75
+ evaluateForms: (forms, env) => evaluateFormsAsync(forms, env, asyncCtx),
76
+ applyCallable: (fn, args, callEnv) =>
77
+ applyCallableAsync(fn, args, callEnv, asyncCtx),
78
+ }
79
+ return asyncCtx
80
+ }
81
+
82
+ // ---- Main async dispatch ----
83
+
84
+ async function evaluateFormAsync(
85
+ expr: CljValue,
86
+ env: Env,
87
+ asyncCtx: AsyncEvalCtx
88
+ ): Promise<CljValue> {
89
+ // Self-evaluating forms and symbols: delegate directly to sync evaluator.
90
+ // No async needed — these don't contain sub-expressions that could be pending.
91
+ switch (expr.kind) {
92
+ case 'number':
93
+ case 'string':
94
+ case 'boolean':
95
+ case 'keyword':
96
+ case 'nil':
97
+ case 'symbol':
98
+ case 'function':
99
+ case 'native-function':
100
+ case 'macro':
101
+ case 'multi-method':
102
+ case 'atom':
103
+ case 'reduced':
104
+ case 'volatile':
105
+ case 'regex':
106
+ case 'var':
107
+ case 'delay':
108
+ case 'lazy-seq':
109
+ case 'cons':
110
+ case 'namespace':
111
+ case 'pending':
112
+ return asyncCtx.syncCtx.evaluate(expr, env)
113
+ }
114
+
115
+ if (expr.kind === 'vector') {
116
+ const elements: CljValue[] = []
117
+ for (const el of expr.value) {
118
+ elements.push(await evaluateFormAsync(el, env, asyncCtx))
119
+ }
120
+ return { kind: 'vector', value: elements }
121
+ }
122
+
123
+ if (expr.kind === 'map') {
124
+ const entries: [CljValue, CljValue][] = []
125
+ for (const [k, v] of expr.entries) {
126
+ const ek = await evaluateFormAsync(k, env, asyncCtx)
127
+ const ev = await evaluateFormAsync(v, env, asyncCtx)
128
+ entries.push([ek, ev])
129
+ }
130
+ return { kind: 'map', entries }
131
+ }
132
+
133
+ if (expr.kind === 'set') {
134
+ const elements: CljValue[] = []
135
+ for (const el of expr.values) {
136
+ elements.push(await evaluateFormAsync(el, env, asyncCtx))
137
+ }
138
+ return { kind: 'set', values: elements }
139
+ }
140
+
141
+ if (expr.kind === 'list') {
142
+ return evaluateListAsync(expr, env, asyncCtx)
143
+ }
144
+
145
+ // Unreachable — all CljValue kinds are handled above
146
+ return asyncCtx.syncCtx.evaluate(expr, env)
147
+ }
148
+
149
+ async function evaluateFormsAsync(
150
+ forms: CljValue[],
151
+ env: Env,
152
+ asyncCtx: AsyncEvalCtx
153
+ ): Promise<CljValue> {
154
+ let result: CljValue = cljNil()
155
+ for (const form of forms) {
156
+ const expanded = asyncCtx.syncCtx.expandAll(form, env)
157
+ result = await evaluateFormAsync(expanded, env, asyncCtx)
158
+ }
159
+ return result
160
+ }
161
+
162
+ // ---- List evaluation ----
163
+
164
+ // Must mirror specialFormKeywords in special-forms.ts.
165
+ // If a new special form is added to the sync dispatcher and omitted here,
166
+ // (async ...) blocks will silently treat it as a function call at runtime.
167
+ // Add new forms here and delegate to syncCtx if no async-aware handling needed.
168
+ const ASYNC_SPECIAL_FORMS = new Set([
169
+ 'quote',
170
+ 'def',
171
+ 'if',
172
+ 'do',
173
+ 'let',
174
+ 'let*',
175
+ 'fn',
176
+ 'fn*',
177
+ 'loop',
178
+ 'recur',
179
+ 'binding',
180
+ 'set!',
181
+ 'try',
182
+ 'var',
183
+ 'defmacro',
184
+ 'defmulti',
185
+ 'defmethod',
186
+ 'letfn',
187
+ 'quasiquote',
188
+ 'delay',
189
+ 'lazy-seq',
190
+ 'ns',
191
+ 'async',
192
+ // JS interop — delegate to sync; args inside (async ...) are not awaited
193
+ // before the interop call (V1 limitation: use @ explicitly before the form).
194
+ '.',
195
+ 'js/new',
196
+ ])
197
+
198
+ async function evaluateListAsync(
199
+ list: { kind: 'list'; value: CljValue[] },
200
+ env: Env,
201
+ asyncCtx: AsyncEvalCtx
202
+ ): Promise<CljValue> {
203
+ if (list.value.length === 0) return list
204
+
205
+ const first = list.value[0]
206
+
207
+ // Special forms: dispatch to async-aware handlers for the ones that need it,
208
+ // delegate to sync ctx for safe ones (quote, var, fn, ns).
209
+ if (first.kind === 'symbol' && ASYNC_SPECIAL_FORMS.has(first.name)) {
210
+ return evaluateSpecialFormAsync(first.name, list, env, asyncCtx)
211
+ }
212
+
213
+ // Evaluate the head (function position)
214
+ const fn = await evaluateFormAsync(first, env, asyncCtx)
215
+
216
+ // Deref interception: @x expands to (deref x).
217
+ // If the dereffed value is CljPending, await it here — this is the heart of async @.
218
+ if (is.aFunction(fn) && fn.name === 'deref' && list.value.length === 2) {
219
+ const val = await evaluateFormAsync(list.value[1], env, asyncCtx)
220
+ if (val.kind === 'pending') {
221
+ return val.promise // await the pending value
222
+ }
223
+ // Not pending: normal sync deref
224
+ return asyncCtx.syncCtx.applyCallable(fn, [val], env)
225
+ }
226
+
227
+ // Evaluate args sequentially (left-to-right, preserving side-effect order)
228
+ const args: CljValue[] = []
229
+ for (const arg of list.value.slice(1)) {
230
+ args.push(await evaluateFormAsync(arg, env, asyncCtx))
231
+ }
232
+
233
+ return applyCallableAsync(fn, args, env, asyncCtx)
234
+ }
235
+
236
+ // ---- Special form async handlers ----
237
+
238
+ async function evaluateSpecialFormAsync(
239
+ name: string,
240
+ list: { kind: 'list'; value: CljValue[] },
241
+ env: Env,
242
+ asyncCtx: AsyncEvalCtx
243
+ ): Promise<CljValue> {
244
+ switch (name) {
245
+ // Safe to delegate to sync: no sub-evaluation of async expressions
246
+ case 'quote':
247
+ case 'var':
248
+ case 'ns':
249
+ // fn/fn*: function CREATION is sync — the body is evaluated async only when called
250
+ case 'fn':
251
+ case 'fn*':
252
+ return asyncCtx.syncCtx.evaluate(list, env)
253
+
254
+ // recur: evaluate args async, then throw RecurSignal
255
+ case 'recur': {
256
+ const args: CljValue[] = []
257
+ for (const arg of list.value.slice(1)) {
258
+ args.push(await evaluateFormAsync(arg, env, asyncCtx))
259
+ }
260
+ throw new RecurSignal(args)
261
+ }
262
+
263
+ // do: sequential evaluation
264
+ case 'do':
265
+ return evaluateFormsAsync(list.value.slice(1), env, asyncCtx)
266
+
267
+ // def: V1 does not support def inside (async ...) — unusual use case
268
+ case 'def':
269
+ throw new EvaluationError(
270
+ 'def inside (async ...) is not supported. Define vars outside the async block.',
271
+ { list, env }
272
+ )
273
+
274
+ // if: evaluate condition, then selected branch
275
+ case 'if': {
276
+ const condition = await evaluateFormAsync(list.value[1], env, asyncCtx)
277
+ const isTruthy =
278
+ condition.kind !== 'nil' &&
279
+ !(condition.kind === 'boolean' && !condition.value)
280
+ if (isTruthy) {
281
+ return evaluateFormAsync(list.value[2], env, asyncCtx)
282
+ }
283
+ return list.value[3] !== undefined
284
+ ? evaluateFormAsync(list.value[3], env, asyncCtx)
285
+ : cljNil()
286
+ }
287
+
288
+ // let/let*: sequential bindings (value eval is async, pattern binding is sync)
289
+ case 'let':
290
+ case 'let*':
291
+ return evaluateLetAsync(list, env, asyncCtx)
292
+
293
+ // loop: like let but supports recur
294
+ case 'loop':
295
+ return evaluateLoopAsync(list, env, asyncCtx)
296
+
297
+ // binding: evaluate binding values async, then body
298
+ case 'binding':
299
+ return evaluateBindingAsync(list, env, asyncCtx)
300
+
301
+ // try: evaluate body async, handle catch/finally async
302
+ case 'try':
303
+ return evaluateTryAsync(list, env, asyncCtx)
304
+
305
+ // set!: evaluate new value async, then call sync set! logic
306
+ case 'set!': {
307
+ // Re-delegate to sync ctx with the value already evaluated.
308
+ // The sync set! handler will re-evaluate list.value[2] as a form —
309
+ // that won't work with an already-evaluated value. So we call the sync
310
+ // evaluator on a reconstructed list with the value quoted.
311
+ const newVal = await evaluateFormAsync(list.value[2], env, asyncCtx)
312
+ const quotedVal: CljValue = {
313
+ kind: 'list',
314
+ value: [{ kind: 'symbol', name: 'quote' }, newVal],
315
+ }
316
+ const newList: CljValue = {
317
+ kind: 'list',
318
+ value: [list.value[0], list.value[1], quotedVal],
319
+ }
320
+ return asyncCtx.syncCtx.evaluate(newList, env)
321
+ }
322
+
323
+ // quasiquote: delegate to sync — expansion is structural, no async sub-eval
324
+ case 'quasiquote':
325
+ return asyncCtx.syncCtx.evaluate(list, env)
326
+
327
+ // defmacro, defmulti, defmethod, letfn, delay, lazy-seq, async:
328
+ // delegate to sync evaluator (they don't have async sub-expressions in their
329
+ // definition forms, or they create thunks that are evaluated sync later)
330
+ default:
331
+ return asyncCtx.syncCtx.evaluate(list, env)
332
+ }
333
+ }
334
+
335
+ async function evaluateLetAsync(
336
+ list: { kind: 'list'; value: CljValue[] },
337
+ env: Env,
338
+ asyncCtx: AsyncEvalCtx
339
+ ): Promise<CljValue> {
340
+ const bindings = list.value[1]
341
+ validateBindingVector(bindings, 'let', env)
342
+
343
+ let currentEnv = env
344
+ const pairs = bindings.value
345
+ for (let i = 0; i < pairs.length; i += 2) {
346
+ const pattern = pairs[i]
347
+ const valueForm = pairs[i + 1]
348
+ // Value evaluation is async; pattern binding is purely structural (sync).
349
+ const value = await evaluateFormAsync(valueForm, currentEnv, asyncCtx)
350
+ const boundPairs = destructureBindings(
351
+ pattern,
352
+ value,
353
+ asyncCtx.syncCtx,
354
+ currentEnv
355
+ )
356
+ currentEnv = extend(
357
+ boundPairs.map(([n]) => n),
358
+ boundPairs.map(([, v]) => v),
359
+ currentEnv
360
+ )
361
+ }
362
+ return evaluateFormsAsync(list.value.slice(2), currentEnv, asyncCtx)
363
+ }
364
+
365
+ async function evaluateLoopAsync(
366
+ list: { kind: 'list'; value: CljValue[] },
367
+ env: Env,
368
+ asyncCtx: AsyncEvalCtx
369
+ ): Promise<CljValue> {
370
+ const loopBindings = list.value[1]
371
+ validateBindingVector(loopBindings, 'loop', env)
372
+
373
+ const loopBody = list.value.slice(2)
374
+
375
+ // Collect patterns and evaluate initial values async
376
+ const patterns: CljValue[] = []
377
+ let currentValues: CljValue[] = []
378
+ let initEnv = env
379
+ for (let i = 0; i < loopBindings.value.length; i += 2) {
380
+ const pattern = loopBindings.value[i]
381
+ const value = await evaluateFormAsync(
382
+ loopBindings.value[i + 1],
383
+ initEnv,
384
+ asyncCtx
385
+ )
386
+ patterns.push(pattern)
387
+ currentValues.push(value)
388
+ const boundPairs = destructureBindings(
389
+ pattern,
390
+ value,
391
+ asyncCtx.syncCtx,
392
+ initEnv
393
+ )
394
+ initEnv = extend(
395
+ boundPairs.map(([n]) => n),
396
+ boundPairs.map(([, v]) => v),
397
+ initEnv
398
+ )
399
+ }
400
+
401
+ while (true) {
402
+ let loopEnv = env
403
+ for (let i = 0; i < patterns.length; i++) {
404
+ const boundPairs = destructureBindings(
405
+ patterns[i],
406
+ currentValues[i],
407
+ asyncCtx.syncCtx,
408
+ loopEnv
409
+ )
410
+ loopEnv = extend(
411
+ boundPairs.map(([n]) => n),
412
+ boundPairs.map(([, v]) => v),
413
+ loopEnv
414
+ )
415
+ }
416
+ try {
417
+ return await evaluateFormsAsync(loopBody, loopEnv, asyncCtx)
418
+ } catch (e) {
419
+ if (e instanceof RecurSignal) {
420
+ if (e.args.length !== patterns.length) {
421
+ throw new EvaluationError(
422
+ `recur expects ${patterns.length} arguments but got ${e.args.length}`,
423
+ { list, env }
424
+ )
425
+ }
426
+ currentValues = e.args
427
+ continue
428
+ }
429
+ throw e
430
+ }
431
+ }
432
+ }
433
+
434
+ async function evaluateBindingAsync(
435
+ list: { kind: 'list'; value: CljValue[] },
436
+ env: Env,
437
+ asyncCtx: AsyncEvalCtx
438
+ ): Promise<CljValue> {
439
+ // Delegate to sync evaluator's binding form, but evaluate the binding values async.
440
+ // The sync binding handler re-evaluates the binding forms — we need to pre-evaluate
441
+ // them and pass them quoted. For V1, delegate to sync (binding values are usually
442
+ // simple expressions; async binding values are an edge case).
443
+ // This means dynamic bindings with async-computed values don't work in V1.
444
+ // They can be assigned with set! after the binding is established.
445
+ return asyncCtx.syncCtx.evaluate(list, env)
446
+ }
447
+
448
+ async function evaluateTryAsync(
449
+ list: { kind: 'list'; value: CljValue[] },
450
+ env: Env,
451
+ asyncCtx: AsyncEvalCtx
452
+ ): Promise<CljValue> {
453
+ // parseTryStructure validates catch/finally structure (binding symbol, ordering).
454
+ // matchesDiscriminator uses asyncCtx.syncCtx — discriminator evaluation is always
455
+ // synchronous (keyword checks, predicate calls on already-resolved values).
456
+ const { bodyForms, catchClauses, finallyForms } = parseTryStructure(
457
+ list as CljList,
458
+ env
459
+ )
460
+
461
+ let result: CljValue = cljNil()
462
+ let pendingThrow: unknown = null
463
+
464
+ try {
465
+ result = await evaluateFormsAsync(bodyForms, env, asyncCtx)
466
+ } catch (e) {
467
+ if (e instanceof RecurSignal) throw e
468
+
469
+ let thrownValue: CljValue
470
+ if (e instanceof CljThrownSignal) {
471
+ thrownValue = e.value
472
+ } else if (e instanceof EvaluationError) {
473
+ thrownValue = {
474
+ kind: 'map',
475
+ entries: [
476
+ [
477
+ { kind: 'keyword', name: ':type' },
478
+ { kind: 'keyword', name: ':error/runtime' },
479
+ ],
480
+ [
481
+ { kind: 'keyword', name: ':message' },
482
+ { kind: 'string', value: (e as Error).message },
483
+ ],
484
+ ],
485
+ }
486
+ } else {
487
+ throw e
488
+ }
489
+
490
+ let handled = false
491
+ for (const clause of catchClauses) {
492
+ if (
493
+ matchesDiscriminator(
494
+ clause.discriminator,
495
+ thrownValue,
496
+ env,
497
+ asyncCtx.syncCtx
498
+ )
499
+ ) {
500
+ const catchEnv = extend([clause.binding], [thrownValue], env)
501
+ result = await evaluateFormsAsync(clause.body, catchEnv, asyncCtx)
502
+ handled = true
503
+ break
504
+ }
505
+ }
506
+
507
+ if (!handled) {
508
+ pendingThrow = e
509
+ }
510
+ } finally {
511
+ if (finallyForms) {
512
+ await evaluateFormsAsync(finallyForms, env, asyncCtx)
513
+ }
514
+ }
515
+
516
+ if (pendingThrow !== null) throw pendingThrow
517
+ return result
518
+ }
519
+
520
+ // ---- applyCallable (async) ----
521
+
522
+ async function applyCallableAsync(
523
+ fn: CljValue,
524
+ args: CljValue[],
525
+ callEnv: Env,
526
+ asyncCtx: AsyncEvalCtx
527
+ ): Promise<CljValue> {
528
+ if (fn.kind === 'native-function') {
529
+ // Native functions are sync — call as-is.
530
+ // We do NOT auto-await CljPending results here: the caller is responsible
531
+ // for awaiting (via @ deref interception or then/catch). This preserves
532
+ // the ability to pass pending values around as first-class values.
533
+ if (fn.fnWithContext) {
534
+ return fn.fnWithContext(asyncCtx.syncCtx, callEnv, ...args)
535
+ }
536
+ return fn.fn(...args)
537
+ }
538
+
539
+ if (fn.kind === 'function') {
540
+ const arity = resolveArity(fn.arities, args.length)
541
+ let currentArgs = args
542
+ while (true) {
543
+ const localEnv = bindParams(
544
+ arity.params,
545
+ arity.restParam,
546
+ currentArgs,
547
+ fn.env,
548
+ asyncCtx.syncCtx, // bindParams uses syncCtx only for structural destructuring
549
+ callEnv
550
+ )
551
+ try {
552
+ return await evaluateFormsAsync(arity.body, localEnv, asyncCtx)
553
+ } catch (e) {
554
+ if (e instanceof RecurSignal) {
555
+ currentArgs = e.args
556
+ continue
557
+ }
558
+ throw e
559
+ }
560
+ }
561
+ }
562
+
563
+ // keyword, map, and other callables: delegate to sync
564
+ return asyncCtx.syncCtx.applyCallable(fn, args, callEnv)
565
+ }
@@ -1,5 +1,12 @@
1
- import { cljMap, cljVector } from '../factories'
2
- import type { CljMap, CljValue, CljVector, EvaluationContext } from '../types'
1
+ import { v } from '../factories'
2
+ import { is } from '../assertions'
3
+ import type {
4
+ CljMap,
5
+ CljSet,
6
+ CljValue,
7
+ CljVector,
8
+ EvaluationContext,
9
+ } from '../types'
3
10
  import type { Env } from '../types'
4
11
 
5
12
  export function evaluateVector(
@@ -8,8 +15,24 @@ export function evaluateVector(
8
15
  ctx: EvaluationContext
9
16
  ): CljValue {
10
17
  const evaluated = vector.value.map((v) => ctx.evaluate(v, env))
11
- if (vector.meta) return { kind: 'vector' as const, value: evaluated, meta: vector.meta }
12
- return cljVector(evaluated)
18
+ if (vector.meta)
19
+ return { kind: 'vector' as const, value: evaluated, meta: vector.meta }
20
+ return v.vector(evaluated)
21
+ }
22
+
23
+ export function evaluateSet(
24
+ set: CljSet,
25
+ env: Env,
26
+ ctx: EvaluationContext
27
+ ): CljValue {
28
+ const evaluated: CljValue[] = []
29
+ for (const v of set.values) {
30
+ const ev = ctx.evaluate(v, env)
31
+ if (!evaluated.some((existing) => is.equal(existing, ev))) {
32
+ evaluated.push(ev)
33
+ }
34
+ }
35
+ return v.set(evaluated)
13
36
  }
14
37
 
15
38
  export function evaluateMap(
@@ -24,5 +47,5 @@ export function evaluateMap(
24
47
  entries.push([evaluatedKey, evaluatedValue])
25
48
  }
26
49
  if (map.meta) return { kind: 'map' as const, entries, meta: map.meta }
27
- return cljMap(entries)
50
+ return v.map(entries)
28
51
  }