conjure-js 0.0.13 → 0.0.14

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 (58) hide show
  1. package/dist-cli/conjure-js.mjs +2328 -2089
  2. package/dist-vite-plugin/index.mjs +2327 -2088
  3. package/package.json +1 -1
  4. package/src/bin/version.ts +1 -1
  5. package/src/core/assertions.ts +10 -3
  6. package/src/core/bootstrap.ts +7 -23
  7. package/src/core/compiler/binding.ts +164 -0
  8. package/src/core/compiler/callable.ts +41 -0
  9. package/src/core/compiler/compile-env.ts +40 -0
  10. package/src/core/compiler/control-flow.ts +79 -0
  11. package/src/core/compiler/index.ts +121 -0
  12. package/src/core/env.ts +4 -4
  13. package/src/core/errors.ts +1 -0
  14. package/src/core/evaluator/apply.ts +7 -3
  15. package/src/core/evaluator/arity.ts +16 -6
  16. package/src/core/evaluator/async-evaluator.ts +68 -89
  17. package/src/core/evaluator/collections.ts +9 -4
  18. package/src/core/evaluator/destructure.ts +45 -55
  19. package/src/core/evaluator/dispatch.ts +21 -24
  20. package/src/core/evaluator/evaluate.ts +14 -2
  21. package/src/core/evaluator/expand.ts +5 -7
  22. package/src/core/evaluator/js-interop.ts +46 -33
  23. package/src/core/evaluator/quasiquote.ts +7 -11
  24. package/src/core/evaluator/recur-check.ts +1 -1
  25. package/src/core/evaluator/special-forms.ts +18 -38
  26. package/src/core/index.ts +1 -1
  27. package/src/core/keywords.ts +105 -0
  28. package/src/core/modules/core/index.ts +131 -0
  29. package/src/core/{stdlib → modules/core/stdlib}/arithmetic.ts +6 -6
  30. package/src/core/{stdlib → modules/core/stdlib}/async-fns.ts +6 -6
  31. package/src/core/{stdlib → modules/core/stdlib}/atoms.ts +7 -7
  32. package/src/core/{stdlib → modules/core/stdlib}/errors.ts +4 -4
  33. package/src/core/{stdlib → modules/core/stdlib}/hof.ts +6 -6
  34. package/src/core/{stdlib → modules/core/stdlib}/lazy.ts +4 -4
  35. package/src/core/{stdlib → modules/core/stdlib}/maps-sets.ts +6 -6
  36. package/src/core/{stdlib → modules/core/stdlib}/meta.ts +5 -5
  37. package/src/core/{stdlib → modules/core/stdlib}/predicates.ts +7 -7
  38. package/src/core/modules/core/stdlib/print.ts +108 -0
  39. package/src/core/{stdlib → modules/core/stdlib}/regex.ts +5 -5
  40. package/src/core/{stdlib → modules/core/stdlib}/seq.ts +7 -7
  41. package/src/core/{stdlib → modules/core/stdlib}/strings.ts +6 -6
  42. package/src/core/{stdlib → modules/core/stdlib}/transducers.ts +6 -6
  43. package/src/core/{stdlib → modules/core/stdlib}/utils.ts +10 -10
  44. package/src/core/{stdlib → modules/core/stdlib}/vars.ts +4 -4
  45. package/src/core/{stdlib → modules/core/stdlib}/vectors.ts +6 -6
  46. package/src/core/modules/js/index.ts +402 -0
  47. package/src/core/ns-forms.ts +25 -17
  48. package/src/core/positions.ts +22 -2
  49. package/src/core/printer.ts +162 -53
  50. package/src/core/reader.ts +25 -22
  51. package/src/core/registry.ts +10 -10
  52. package/src/core/runtime.ts +23 -23
  53. package/src/core/session.ts +17 -7
  54. package/src/core/tokenizer.ts +14 -4
  55. package/src/core/transformations.ts +48 -29
  56. package/src/core/types.ts +57 -81
  57. package/src/core/core-module.ts +0 -303
  58. package/src/core/stdlib/js-namespace.ts +0 -344
@@ -43,7 +43,8 @@
43
43
  import { is } from '../assertions'
44
44
  import { extend } from '../env'
45
45
  import { CljThrownSignal, EvaluationError } from '../errors'
46
- import { cljNil } from '../factories'
46
+ import { cljNil, v } from '../factories'
47
+ import { specialFormKeywords, valueKeywords } from '../keywords'
47
48
  import type { CljList, CljValue, Env, EvaluationContext } from '../types'
48
49
  import { bindParams, RecurSignal, resolveArity } from './arity'
49
50
  import { destructureBindings } from './destructure'
@@ -89,56 +90,56 @@ async function evaluateFormAsync(
89
90
  // Self-evaluating forms and symbols: delegate directly to sync evaluator.
90
91
  // No async needed — these don't contain sub-expressions that could be pending.
91
92
  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':
93
+ case valueKeywords.number:
94
+ case valueKeywords.string:
95
+ case valueKeywords.boolean:
96
+ case valueKeywords.keyword:
97
+ case valueKeywords.nil:
98
+ case valueKeywords.symbol:
99
+ case valueKeywords.function:
100
+ case valueKeywords.nativeFunction:
101
+ case valueKeywords.macro:
102
+ case valueKeywords.multiMethod:
103
+ case valueKeywords.atom:
104
+ case valueKeywords.reduced:
105
+ case valueKeywords.volatile:
106
+ case valueKeywords.regex:
107
+ case valueKeywords.var:
108
+ case valueKeywords.delay:
109
+ case valueKeywords.lazySeq:
110
+ case valueKeywords.cons:
111
+ case valueKeywords.namespace:
112
+ case valueKeywords.pending:
112
113
  return asyncCtx.syncCtx.evaluate(expr, env)
113
114
  }
114
115
 
115
- if (expr.kind === 'vector') {
116
+ if (is.vector(expr)) {
116
117
  const elements: CljValue[] = []
117
118
  for (const el of expr.value) {
118
119
  elements.push(await evaluateFormAsync(el, env, asyncCtx))
119
120
  }
120
- return { kind: 'vector', value: elements }
121
+ return v.vector(elements)
121
122
  }
122
123
 
123
- if (expr.kind === 'map') {
124
+ if (is.map(expr)) {
124
125
  const entries: [CljValue, CljValue][] = []
125
126
  for (const [k, v] of expr.entries) {
126
127
  const ek = await evaluateFormAsync(k, env, asyncCtx)
127
128
  const ev = await evaluateFormAsync(v, env, asyncCtx)
128
129
  entries.push([ek, ev])
129
130
  }
130
- return { kind: 'map', entries }
131
+ return v.map(entries)
131
132
  }
132
133
 
133
- if (expr.kind === 'set') {
134
+ if (is.set(expr)) {
134
135
  const elements: CljValue[] = []
135
136
  for (const el of expr.values) {
136
137
  elements.push(await evaluateFormAsync(el, env, asyncCtx))
137
138
  }
138
- return { kind: 'set', values: elements }
139
+ return v.set(elements)
139
140
  }
140
141
 
141
- if (expr.kind === 'list') {
142
+ if (is.list(expr)) {
142
143
  return evaluateListAsync(expr, env, asyncCtx)
143
144
  }
144
145
 
@@ -151,7 +152,7 @@ async function evaluateFormsAsync(
151
152
  env: Env,
152
153
  asyncCtx: AsyncEvalCtx
153
154
  ): Promise<CljValue> {
154
- let result: CljValue = cljNil()
155
+ let result: CljValue = v.nil()
155
156
  for (const form of forms) {
156
157
  const expanded = asyncCtx.syncCtx.expandAll(form, env)
157
158
  result = await evaluateFormAsync(expanded, env, asyncCtx)
@@ -196,28 +197,28 @@ const ASYNC_SPECIAL_FORMS = new Set([
196
197
  ])
197
198
 
198
199
  async function evaluateListAsync(
199
- list: { kind: 'list'; value: CljValue[] },
200
+ list: CljList,
200
201
  env: Env,
201
202
  asyncCtx: AsyncEvalCtx
202
203
  ): Promise<CljValue> {
203
204
  if (list.value.length === 0) return list
204
205
 
205
- const first = list.value[0]
206
+ const head = list.value[0]
206
207
 
207
208
  // Special forms: dispatch to async-aware handlers for the ones that need it,
208
209
  // 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)
210
+ if (is.symbol(head) && ASYNC_SPECIAL_FORMS.has(head.name)) {
211
+ return evaluateSpecialFormAsync(head.name, list, env, asyncCtx)
211
212
  }
212
213
 
213
214
  // Evaluate the head (function position)
214
- const fn = await evaluateFormAsync(first, env, asyncCtx)
215
+ const fn = await evaluateFormAsync(head, env, asyncCtx)
215
216
 
216
217
  // Deref interception: @x expands to (deref x).
217
218
  // If the dereffed value is CljPending, await it here — this is the heart of async @.
218
219
  if (is.aFunction(fn) && fn.name === 'deref' && list.value.length === 2) {
219
220
  const val = await evaluateFormAsync(list.value[1], env, asyncCtx)
220
- if (val.kind === 'pending') {
221
+ if (is.pending(val)) {
221
222
  return val.promise // await the pending value
222
223
  }
223
224
  // Not pending: normal sync deref
@@ -237,22 +238,21 @@ async function evaluateListAsync(
237
238
 
238
239
  async function evaluateSpecialFormAsync(
239
240
  name: string,
240
- list: { kind: 'list'; value: CljValue[] },
241
+ list: CljList,
241
242
  env: Env,
242
243
  asyncCtx: AsyncEvalCtx
243
244
  ): Promise<CljValue> {
244
245
  switch (name) {
245
246
  // Safe to delegate to sync: no sub-evaluation of async expressions
246
- case 'quote':
247
- case 'var':
248
- case 'ns':
247
+ case specialFormKeywords.quote:
248
+ case specialFormKeywords.var:
249
+ case specialFormKeywords.ns:
249
250
  // fn/fn*: function CREATION is sync — the body is evaluated async only when called
250
- case 'fn':
251
- case 'fn*':
251
+ case specialFormKeywords.fn:
252
252
  return asyncCtx.syncCtx.evaluate(list, env)
253
253
 
254
254
  // recur: evaluate args async, then throw RecurSignal
255
- case 'recur': {
255
+ case specialFormKeywords.recur: {
256
256
  const args: CljValue[] = []
257
257
  for (const arg of list.value.slice(1)) {
258
258
  args.push(await evaluateFormAsync(arg, env, asyncCtx))
@@ -261,70 +261,58 @@ async function evaluateSpecialFormAsync(
261
261
  }
262
262
 
263
263
  // do: sequential evaluation
264
- case 'do':
264
+ case specialFormKeywords.do:
265
265
  return evaluateFormsAsync(list.value.slice(1), env, asyncCtx)
266
266
 
267
267
  // def: V1 does not support def inside (async ...) — unusual use case
268
- case 'def':
268
+ case specialFormKeywords.def:
269
269
  throw new EvaluationError(
270
270
  'def inside (async ...) is not supported. Define vars outside the async block.',
271
271
  { list, env }
272
272
  )
273
273
 
274
274
  // if: evaluate condition, then selected branch
275
- case 'if': {
275
+ case specialFormKeywords.if: {
276
276
  const condition = await evaluateFormAsync(list.value[1], env, asyncCtx)
277
277
  const isTruthy =
278
- condition.kind !== 'nil' &&
279
- !(condition.kind === 'boolean' && !condition.value)
278
+ !is.nil(condition) && !(is.boolean(condition) && !condition.value)
280
279
  if (isTruthy) {
281
280
  return evaluateFormAsync(list.value[2], env, asyncCtx)
282
281
  }
283
282
  return list.value[3] !== undefined
284
283
  ? evaluateFormAsync(list.value[3], env, asyncCtx)
285
- : cljNil()
284
+ : v.nil()
286
285
  }
287
286
 
288
287
  // let/let*: sequential bindings (value eval is async, pattern binding is sync)
289
- case 'let':
290
- case 'let*':
288
+ case specialFormKeywords.let:
291
289
  return evaluateLetAsync(list, env, asyncCtx)
292
290
 
293
291
  // loop: like let but supports recur
294
- case 'loop':
292
+ case specialFormKeywords.loop:
295
293
  return evaluateLoopAsync(list, env, asyncCtx)
296
294
 
297
295
  // binding: evaluate binding values async, then body
298
- case 'binding':
296
+ case specialFormKeywords.binding:
299
297
  return evaluateBindingAsync(list, env, asyncCtx)
300
298
 
301
299
  // try: evaluate body async, handle catch/finally async
302
- case 'try':
300
+ case specialFormKeywords.try:
303
301
  return evaluateTryAsync(list, env, asyncCtx)
304
302
 
305
303
  // set!: evaluate new value async, then call sync set! logic
306
- case 'set!': {
304
+ case specialFormKeywords['set!']: {
307
305
  // Re-delegate to sync ctx with the value already evaluated.
308
306
  // The sync set! handler will re-evaluate list.value[2] as a form —
309
307
  // that won't work with an already-evaluated value. So we call the sync
310
308
  // evaluator on a reconstructed list with the value quoted.
311
309
  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
- }
310
+ const quoted = v.list([v.symbol(specialFormKeywords.quote), newVal])
311
+ const newList = v.list([list.value[0], list.value[1], quoted])
320
312
  return asyncCtx.syncCtx.evaluate(newList, env)
321
313
  }
322
314
 
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:
315
+ // defmacro, quasiquote, defmulti, defmethod, letfn, delay, lazy-seq, async:
328
316
  // delegate to sync evaluator (they don't have async sub-expressions in their
329
317
  // definition forms, or they create thunks that are evaluated sync later)
330
318
  default:
@@ -333,12 +321,12 @@ async function evaluateSpecialFormAsync(
333
321
  }
334
322
 
335
323
  async function evaluateLetAsync(
336
- list: { kind: 'list'; value: CljValue[] },
324
+ list: CljList,
337
325
  env: Env,
338
326
  asyncCtx: AsyncEvalCtx
339
327
  ): Promise<CljValue> {
340
328
  const bindings = list.value[1]
341
- validateBindingVector(bindings, 'let', env)
329
+ validateBindingVector(bindings, specialFormKeywords.let, env)
342
330
 
343
331
  let currentEnv = env
344
332
  const pairs = bindings.value
@@ -363,12 +351,12 @@ async function evaluateLetAsync(
363
351
  }
364
352
 
365
353
  async function evaluateLoopAsync(
366
- list: { kind: 'list'; value: CljValue[] },
354
+ list: CljList,
367
355
  env: Env,
368
356
  asyncCtx: AsyncEvalCtx
369
357
  ): Promise<CljValue> {
370
358
  const loopBindings = list.value[1]
371
- validateBindingVector(loopBindings, 'loop', env)
359
+ validateBindingVector(loopBindings, specialFormKeywords.loop, env)
372
360
 
373
361
  const loopBody = list.value.slice(2)
374
362
 
@@ -432,7 +420,7 @@ async function evaluateLoopAsync(
432
420
  }
433
421
 
434
422
  async function evaluateBindingAsync(
435
- list: { kind: 'list'; value: CljValue[] },
423
+ list: CljList,
436
424
  env: Env,
437
425
  asyncCtx: AsyncEvalCtx
438
426
  ): Promise<CljValue> {
@@ -446,19 +434,16 @@ async function evaluateBindingAsync(
446
434
  }
447
435
 
448
436
  async function evaluateTryAsync(
449
- list: { kind: 'list'; value: CljValue[] },
437
+ list: CljList,
450
438
  env: Env,
451
439
  asyncCtx: AsyncEvalCtx
452
440
  ): Promise<CljValue> {
453
441
  // parseTryStructure validates catch/finally structure (binding symbol, ordering).
454
442
  // matchesDiscriminator uses asyncCtx.syncCtx — discriminator evaluation is always
455
443
  // synchronous (keyword checks, predicate calls on already-resolved values).
456
- const { bodyForms, catchClauses, finallyForms } = parseTryStructure(
457
- list as CljList,
458
- env
459
- )
444
+ const { bodyForms, catchClauses, finallyForms } = parseTryStructure(list, env)
460
445
 
461
- let result: CljValue = cljNil()
446
+ let result: CljValue = v.nil()
462
447
  let pendingThrow: unknown = null
463
448
 
464
449
  try {
@@ -471,16 +456,10 @@ async function evaluateTryAsync(
471
456
  thrownValue = e.value
472
457
  } else if (e instanceof EvaluationError) {
473
458
  thrownValue = {
474
- kind: 'map',
459
+ kind: valueKeywords.map,
475
460
  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
- ],
461
+ [v.keyword(':type'), v.keyword(':error/runtime')],
462
+ [v.keyword(':message'), v.string((e as Error).message)],
484
463
  ],
485
464
  }
486
465
  } else {
@@ -525,7 +504,7 @@ async function applyCallableAsync(
525
504
  callEnv: Env,
526
505
  asyncCtx: AsyncEvalCtx
527
506
  ): Promise<CljValue> {
528
- if (fn.kind === 'native-function') {
507
+ if (is.nativeFunction(fn)) {
529
508
  // Native functions are sync — call as-is.
530
509
  // We do NOT auto-await CljPending results here: the caller is responsible
531
510
  // for awaiting (via @ deref interception or then/catch). This preserves
@@ -536,7 +515,7 @@ async function applyCallableAsync(
536
515
  return fn.fn(...args)
537
516
  }
538
517
 
539
- if (fn.kind === 'function') {
518
+ if (is.function(fn)) {
540
519
  const arity = resolveArity(fn.arities, args.length)
541
520
  let currentArgs = args
542
521
  while (true) {
@@ -1,13 +1,14 @@
1
- import { v } from '../factories'
2
1
  import { is } from '../assertions'
2
+ import { v } from '../factories'
3
+ import { valueKeywords } from '../keywords'
3
4
  import type {
4
5
  CljMap,
5
6
  CljSet,
6
7
  CljValue,
7
8
  CljVector,
9
+ Env,
8
10
  EvaluationContext,
9
11
  } from '../types'
10
- import type { Env } from '../types'
11
12
 
12
13
  export function evaluateVector(
13
14
  vector: CljVector,
@@ -16,7 +17,11 @@ export function evaluateVector(
16
17
  ): CljValue {
17
18
  const evaluated = vector.value.map((v) => ctx.evaluate(v, env))
18
19
  if (vector.meta)
19
- return { kind: 'vector' as const, value: evaluated, meta: vector.meta }
20
+ return {
21
+ kind: valueKeywords.vector,
22
+ value: evaluated,
23
+ meta: vector.meta,
24
+ }
20
25
  return v.vector(evaluated)
21
26
  }
22
27
 
@@ -46,6 +51,6 @@ export function evaluateMap(
46
51
  const evaluatedValue = ctx.evaluate(value, env)
47
52
  entries.push([evaluatedKey, evaluatedValue])
48
53
  }
49
- if (map.meta) return { kind: 'map' as const, entries, meta: map.meta }
54
+ if (map.meta) return { kind: valueKeywords.map, entries, meta: map.meta }
50
55
  return v.map(entries)
51
56
  }
@@ -1,14 +1,7 @@
1
1
  import { is } from '../assertions'
2
2
  import { EvaluationError } from '../errors'
3
- import {
4
- cljKeyword,
5
- cljList,
6
- cljNil,
7
- cljString,
8
- cljSymbol,
9
- v,
10
- } from '../factories'
11
- import { realizeLazySeq, consToArray } from '../transformations'
3
+ import { v } from '../factories'
4
+ import { consToArray, realizeLazySeq } from '../transformations'
12
5
  import type { CljMap, CljValue, Env, EvaluationContext } from '../types'
13
6
 
14
7
  function toSeqSafe(value: CljValue): CljValue[] {
@@ -28,7 +21,7 @@ function toSeqSafe(value: CljValue): CljValue[] {
28
21
 
29
22
  /** Return the first element of a seq-like value without full realization. */
30
23
  function seqFirst(value: CljValue): CljValue {
31
- if (is.nil(value)) return cljNil()
24
+ if (is.nil(value)) return v.nil()
32
25
  if (is.lazySeq(value)) {
33
26
  const realized = realizeLazySeq(value)
34
27
  return is.nil(realized) ? v.nil() : seqFirst(realized)
@@ -88,9 +81,7 @@ function destructureVector(
88
81
  const elems = [...pattern]
89
82
 
90
83
  // :as alias — must appear as second-to-last with a symbol after it
91
- const asIdx = elems.findIndex(
92
- (e) => is.keyword(e) && e.kind === 'keyword' && e.name === ':as'
93
- )
84
+ const asIdx = elems.findIndex((e) => is.keyword(e) && e.name === ':as')
94
85
  if (asIdx !== -1) {
95
86
  const asSym = elems[asIdx + 1]
96
87
  if (!asSym || !is.symbol(asSym)) {
@@ -131,7 +122,7 @@ function destructureVector(
131
122
  const restArgs = toSeqSafe(current)
132
123
  const entries: [CljValue, CljValue][] = []
133
124
  for (let i = 0; i < restArgs.length; i += 2) {
134
- entries.push([restArgs[i], restArgs[i + 1] ?? cljNil()])
125
+ entries.push([restArgs[i], restArgs[i + 1] ?? v.nil()])
135
126
  }
136
127
  pairs.push(
137
128
  ...destructureBindings(
@@ -143,7 +134,7 @@ function destructureVector(
143
134
  )
144
135
  } else {
145
136
  // Keep the rest as-is (still lazy) — wrap in list only if it's nil/empty
146
- const restValue = seqIsEmpty(current) ? cljNil() : current
137
+ const restValue = seqIsEmpty(current) ? v.nil() : current
147
138
  pairs.push(...destructureBindings(restPattern, restValue, ctx, env))
148
139
  }
149
140
  }
@@ -152,7 +143,7 @@ function destructureVector(
152
143
 
153
144
  // positional bindings
154
145
  for (let i = 0; i < positionalCount; i++) {
155
- pairs.push(...destructureBindings(elems[i], seq[i] ?? cljNil(), ctx, env))
146
+ pairs.push(...destructureBindings(elems[i], seq[i] ?? v.nil(), ctx, env))
156
147
  }
157
148
 
158
149
  // rest binding
@@ -163,11 +154,11 @@ function destructureVector(
163
154
  // kwargs-style: coerce flat key-value pairs into a map
164
155
  const entries: [CljValue, CljValue][] = []
165
156
  for (let i = 0; i < restArgs.length; i += 2) {
166
- entries.push([restArgs[i], restArgs[i + 1] ?? cljNil()])
157
+ entries.push([restArgs[i], restArgs[i + 1] ?? v.nil()])
167
158
  }
168
159
  restValue = { kind: 'map', entries }
169
160
  } else {
170
- restValue = restArgs.length > 0 ? cljList(restArgs) : cljNil()
161
+ restValue = restArgs.length > 0 ? v.list(restArgs) : v.nil()
171
162
  }
172
163
  pairs.push(...destructureBindings(restPattern, restValue, ctx, env))
173
164
  }
@@ -184,33 +175,32 @@ function destructureMap(
184
175
  ): [string, CljValue][] {
185
176
  const pairs: [string, CljValue][] = []
186
177
 
187
- const orMapVal = findMapEntry(pattern, cljKeyword(':or'))
178
+ const orMapVal = findMapEntry(pattern, v.keyword(':or'))
188
179
  const orMap = orMapVal && is.map(orMapVal) ? orMapVal : null
189
- const asVal = findMapEntry(pattern, cljKeyword(':as'))
190
-
191
- if (!is.map(value) && value.kind !== 'nil') {
180
+ const asVal = findMapEntry(pattern, v.keyword(':as'))
181
+ const isNil = is.nil(value)
182
+ if (!is.map(value) && !isNil) {
192
183
  throw new EvaluationError(`Cannot destructure ${value.kind} as a map`, {
193
184
  value,
194
185
  pattern,
195
186
  })
196
187
  }
197
188
 
198
- const targetMap: CljMap =
199
- value.kind === 'nil' ? { kind: 'map', entries: [] } : (value as CljMap)
189
+ const targetMap: CljMap = isNil ? v.map([]) : (value as CljMap)
200
190
 
201
- for (const [k, v] of pattern.entries) {
202
- if (is.keyword(k) && k.name === ':or') continue
203
- if (is.keyword(k) && k.name === ':as') continue
191
+ for (const [key, val] of pattern.entries) {
192
+ if (is.keyword(key) && key.name === ':or') continue
193
+ if (is.keyword(key) && key.name === ':as') continue
204
194
 
205
195
  // :keys shorthand — lookup by keyword (supports qualified: ns/foo → :ns/foo, binds to foo)
206
- if (is.keyword(k) && k.name === ':keys') {
207
- if (!is.vector(v)) {
196
+ if (is.keyword(key) && key.name === ':keys') {
197
+ if (!is.vector(val)) {
208
198
  throw new EvaluationError(
209
199
  ':keys must be followed by a vector of symbols',
210
200
  { pattern }
211
201
  )
212
202
  }
213
- for (const sym of v.value) {
203
+ for (const sym of val.value) {
214
204
  if (!is.symbol(sym)) {
215
205
  throw new EvaluationError(':keys vector must contain symbols', {
216
206
  pattern,
@@ -220,7 +210,7 @@ function destructureMap(
220
210
  const slashIdx = sym.name.indexOf('/')
221
211
  const localName =
222
212
  slashIdx !== -1 ? sym.name.slice(slashIdx + 1) : sym.name
223
- const lookupKey = cljKeyword(':' + sym.name)
213
+ const lookupKey = v.keyword(':' + sym.name)
224
214
  const present = mapContainsKey(targetMap, lookupKey)
225
215
  const entry = present ? findMapEntry(targetMap, lookupKey)! : undefined
226
216
 
@@ -228,11 +218,11 @@ function destructureMap(
228
218
  if (present) {
229
219
  result = entry!
230
220
  } else if (orMap) {
231
- const orDefault = findMapEntry(orMap, cljSymbol(localName))
221
+ const orDefault = findMapEntry(orMap, v.symbol(localName))
232
222
  result =
233
- orDefault !== undefined ? ctx.evaluate(orDefault, env) : cljNil()
223
+ orDefault !== undefined ? ctx.evaluate(orDefault, env) : v.nil()
234
224
  } else {
235
- result = cljNil()
225
+ result = v.nil()
236
226
  }
237
227
  pairs.push([localName, result])
238
228
  }
@@ -240,21 +230,21 @@ function destructureMap(
240
230
  }
241
231
 
242
232
  // :strs shorthand — lookup by string
243
- if (is.keyword(k) && k.name === ':strs') {
244
- if (!is.vector(v)) {
233
+ if (is.keyword(key) && key.name === ':strs') {
234
+ if (!is.vector(val)) {
245
235
  throw new EvaluationError(
246
236
  ':strs must be followed by a vector of symbols',
247
237
  { pattern }
248
238
  )
249
239
  }
250
- for (const sym of v.value) {
240
+ for (const sym of val.value) {
251
241
  if (!is.symbol(sym)) {
252
242
  throw new EvaluationError(':strs vector must contain symbols', {
253
243
  pattern,
254
244
  sym,
255
245
  })
256
246
  }
257
- const lookupKey = cljString(sym.name)
247
+ const lookupKey = v.string(sym.name)
258
248
  const present = mapContainsKey(targetMap, lookupKey)
259
249
  const entry = present ? findMapEntry(targetMap, lookupKey)! : undefined
260
250
 
@@ -262,11 +252,11 @@ function destructureMap(
262
252
  if (present) {
263
253
  result = entry!
264
254
  } else if (orMap) {
265
- const orDefault = findMapEntry(orMap, cljSymbol(sym.name))
255
+ const orDefault = findMapEntry(orMap, v.symbol(sym.name))
266
256
  result =
267
- orDefault !== undefined ? ctx.evaluate(orDefault, env) : cljNil()
257
+ orDefault !== undefined ? ctx.evaluate(orDefault, env) : v.nil()
268
258
  } else {
269
- result = cljNil()
259
+ result = v.nil()
270
260
  }
271
261
  pairs.push([sym.name, result])
272
262
  }
@@ -274,21 +264,21 @@ function destructureMap(
274
264
  }
275
265
 
276
266
  // :syms shorthand — lookup by symbol
277
- if (is.keyword(k) && k.name === ':syms') {
278
- if (!is.vector(v)) {
267
+ if (is.keyword(key) && key.name === ':syms') {
268
+ if (!is.vector(val)) {
279
269
  throw new EvaluationError(
280
270
  ':syms must be followed by a vector of symbols',
281
271
  { pattern }
282
272
  )
283
273
  }
284
- for (const sym of v.value) {
274
+ for (const sym of val.value) {
285
275
  if (!is.symbol(sym)) {
286
276
  throw new EvaluationError(':syms vector must contain symbols', {
287
277
  pattern,
288
278
  sym,
289
279
  })
290
280
  }
291
- const lookupKey = cljSymbol(sym.name)
281
+ const lookupKey = v.symbol(sym.name)
292
282
  const present = mapContainsKey(targetMap, lookupKey)
293
283
  const entry = present ? findMapEntry(targetMap, lookupKey)! : undefined
294
284
 
@@ -296,11 +286,11 @@ function destructureMap(
296
286
  if (present) {
297
287
  result = entry!
298
288
  } else if (orMap) {
299
- const orDefault = findMapEntry(orMap, cljSymbol(sym.name))
289
+ const orDefault = findMapEntry(orMap, v.symbol(sym.name))
300
290
  result =
301
- orDefault !== undefined ? ctx.evaluate(orDefault, env) : cljNil()
291
+ orDefault !== undefined ? ctx.evaluate(orDefault, env) : v.nil()
302
292
  } else {
303
- result = cljNil()
293
+ result = v.nil()
304
294
  }
305
295
  pairs.push([sym.name, result])
306
296
  }
@@ -309,20 +299,20 @@ function destructureMap(
309
299
 
310
300
  // Regular entry: {local-pattern :lookup-key}
311
301
  // The key of the map entry is the binding pattern, the value is the lookup key
312
- const entry = findMapEntry(targetMap, v)
313
- const present = mapContainsKey(targetMap, v)
302
+ const entry = findMapEntry(targetMap, val)
303
+ const present = mapContainsKey(targetMap, val)
314
304
 
315
305
  let boundVal: CljValue
316
306
  if (present) {
317
307
  boundVal = entry!
318
- } else if (orMap && is.symbol(k)) {
319
- const orDefault = findMapEntry(orMap, cljSymbol(k.name))
308
+ } else if (orMap && is.symbol(key)) {
309
+ const orDefault = findMapEntry(orMap, v.symbol(key.name))
320
310
  boundVal =
321
- orDefault !== undefined ? ctx.evaluate(orDefault, env) : cljNil()
311
+ orDefault !== undefined ? ctx.evaluate(orDefault, env) : v.nil()
322
312
  } else {
323
- boundVal = cljNil()
313
+ boundVal = v.nil()
324
314
  }
325
- pairs.push(...destructureBindings(k, boundVal, ctx, env))
315
+ pairs.push(...destructureBindings(key, boundVal, ctx, env))
326
316
  }
327
317
 
328
318
  // :as alias