conjure-js 0.0.1

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 (80) hide show
  1. package/conjure +0 -0
  2. package/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
  3. package/dist/assets/editor.worker-CdQrwHl8.js +26 -0
  4. package/dist/assets/main-A7ZMId9A.css +1 -0
  5. package/dist/assets/main-CmI-7epE.js +3137 -0
  6. package/dist/index.html +195 -0
  7. package/dist/vite.svg +1 -0
  8. package/package.json +68 -0
  9. package/src/bin/__fixtures__/smoke/app/lib.clj +4 -0
  10. package/src/bin/__fixtures__/smoke/app/main.clj +4 -0
  11. package/src/bin/__fixtures__/smoke/repl-smoke.ts +12 -0
  12. package/src/bin/bencode.ts +205 -0
  13. package/src/bin/cli.ts +250 -0
  14. package/src/bin/nrepl-utils.ts +59 -0
  15. package/src/bin/nrepl.ts +393 -0
  16. package/src/bin/version.ts +4 -0
  17. package/src/clojure/core.clj +620 -0
  18. package/src/clojure/core.clj.d.ts +189 -0
  19. package/src/clojure/demo/math.clj +16 -0
  20. package/src/clojure/demo/math.clj.d.ts +4 -0
  21. package/src/clojure/demo.clj +42 -0
  22. package/src/clojure/demo.clj.d.ts +0 -0
  23. package/src/clojure/generated/builtin-namespace-registry.ts +14 -0
  24. package/src/clojure/generated/clojure-core-source.ts +623 -0
  25. package/src/clojure/generated/clojure-string-source.ts +196 -0
  26. package/src/clojure/string.clj +192 -0
  27. package/src/clojure/string.clj.d.ts +25 -0
  28. package/src/core/assertions.ts +134 -0
  29. package/src/core/conversions.ts +108 -0
  30. package/src/core/core-env.ts +58 -0
  31. package/src/core/env.ts +78 -0
  32. package/src/core/errors.ts +39 -0
  33. package/src/core/evaluator/apply.ts +114 -0
  34. package/src/core/evaluator/arity.ts +174 -0
  35. package/src/core/evaluator/collections.ts +25 -0
  36. package/src/core/evaluator/destructure.ts +247 -0
  37. package/src/core/evaluator/dispatch.ts +73 -0
  38. package/src/core/evaluator/evaluate.ts +100 -0
  39. package/src/core/evaluator/expand.ts +79 -0
  40. package/src/core/evaluator/index.ts +72 -0
  41. package/src/core/evaluator/quasiquote.ts +87 -0
  42. package/src/core/evaluator/recur-check.ts +109 -0
  43. package/src/core/evaluator/special-forms.ts +517 -0
  44. package/src/core/factories.ts +155 -0
  45. package/src/core/gensym.ts +9 -0
  46. package/src/core/index.ts +76 -0
  47. package/src/core/positions.ts +38 -0
  48. package/src/core/printer.ts +86 -0
  49. package/src/core/reader.ts +559 -0
  50. package/src/core/scanners.ts +93 -0
  51. package/src/core/session.ts +610 -0
  52. package/src/core/stdlib/arithmetic.ts +361 -0
  53. package/src/core/stdlib/atoms.ts +88 -0
  54. package/src/core/stdlib/collections.ts +784 -0
  55. package/src/core/stdlib/errors.ts +81 -0
  56. package/src/core/stdlib/hof.ts +307 -0
  57. package/src/core/stdlib/meta.ts +48 -0
  58. package/src/core/stdlib/predicates.ts +240 -0
  59. package/src/core/stdlib/regex.ts +238 -0
  60. package/src/core/stdlib/strings.ts +311 -0
  61. package/src/core/stdlib/transducers.ts +256 -0
  62. package/src/core/stdlib/utils.ts +287 -0
  63. package/src/core/tokenizer.ts +437 -0
  64. package/src/core/transformations.ts +75 -0
  65. package/src/core/types.ts +258 -0
  66. package/src/main.ts +1 -0
  67. package/src/monaco-esm.d.ts +7 -0
  68. package/src/playground/clojure-tokens.ts +67 -0
  69. package/src/playground/editor.worker.ts +5 -0
  70. package/src/playground/find-form.ts +138 -0
  71. package/src/playground/playground.ts +342 -0
  72. package/src/playground/samples/00-welcome.clj +385 -0
  73. package/src/playground/samples/01-collections.clj +191 -0
  74. package/src/playground/samples/02-higher-order-functions.clj +215 -0
  75. package/src/playground/samples/03-destructuring.clj +194 -0
  76. package/src/playground/samples/04-strings-and-regex.clj +202 -0
  77. package/src/playground/samples/05-error-handling.clj +212 -0
  78. package/src/repl/repl.ts +116 -0
  79. package/tsconfig.build.json +10 -0
  80. package/tsconfig.json +31 -0
@@ -0,0 +1,784 @@
1
+ // Collections: list, vector, hash-map, first, rest, seq, cons, conj, count,
2
+ // nth, get, assoc, dissoc, keys, vals, take, drop, concat, into, zipmap,
3
+ // last, reverse, empty?, repeat, range
4
+
5
+ import {
6
+ isCollection,
7
+ isEqual,
8
+ isList,
9
+ isMap,
10
+ isNil,
11
+ isSeqable,
12
+ isVector,
13
+ } from '../assertions'
14
+ import { EvaluationError } from '../errors'
15
+ import {
16
+ cljBoolean,
17
+ cljList,
18
+ cljMap,
19
+ cljNativeFunction,
20
+ cljNil,
21
+ cljNumber,
22
+ cljVector,
23
+ withDoc,
24
+ } from '../factories'
25
+ import { printString } from '../printer'
26
+ import { toSeq } from '../transformations'
27
+ import {
28
+ valueKeywords,
29
+ type CljList,
30
+ type CljMap,
31
+ type CljNumber,
32
+ type CljString,
33
+ type CljValue,
34
+ type CljVector,
35
+ } from '../types'
36
+
37
+ export const collectionFunctions: Record<string, CljValue> = {
38
+ list: withDoc(
39
+ cljNativeFunction('list', (...args: CljValue[]) => {
40
+ if (args.length === 0) {
41
+ return cljList([])
42
+ }
43
+ return cljList(args)
44
+ }),
45
+ 'Returns a new list containing the given values.',
46
+ [['&', 'args']]
47
+ ),
48
+ vector: withDoc(
49
+ cljNativeFunction('vector', (...args: CljValue[]) => {
50
+ if (args.length === 0) {
51
+ return cljVector([])
52
+ }
53
+ return cljVector(args)
54
+ }),
55
+ 'Returns a new vector containing the given values.',
56
+ [['&', 'args']]
57
+ ),
58
+ 'hash-map': withDoc(
59
+ cljNativeFunction('hash-map', (...kvals: CljValue[]) => {
60
+ if (kvals.length === 0) {
61
+ return cljMap([])
62
+ }
63
+ if (kvals.length % 2 !== 0) {
64
+ throw new EvaluationError(
65
+ `hash-map expects an even number of arguments, got ${kvals.length}`,
66
+ { args: kvals }
67
+ )
68
+ }
69
+ const entries: [CljValue, CljValue][] = []
70
+ for (let i = 0; i < kvals.length; i += 2) {
71
+ const key = kvals[i]
72
+ const value = kvals[i + 1]
73
+ entries.push([key, value])
74
+ }
75
+ return cljMap(entries)
76
+ }),
77
+ 'Returns a new hash-map containing the given key-value pairs.',
78
+ [['&', 'kvals']]
79
+ ),
80
+ seq: withDoc(
81
+ cljNativeFunction('seq', (coll: CljValue) => {
82
+ if (coll.kind === 'nil') return cljNil()
83
+ if (!isSeqable(coll)) {
84
+ throw new EvaluationError(
85
+ `seq expects a collection, string, or nil, got ${printString(coll)}`,
86
+ { collection: coll }
87
+ )
88
+ }
89
+ const items = toSeq(coll)
90
+ return items.length === 0 ? cljNil() : cljList(items)
91
+ }),
92
+ 'Returns a sequence of the given collection or string. Strings yield a sequence of single-character strings.',
93
+ [['coll']]
94
+ ),
95
+ first: withDoc(
96
+ cljNativeFunction('first', (collection: CljValue) => {
97
+ if (collection.kind === 'nil') return cljNil()
98
+ if (!isSeqable(collection)) {
99
+ throw new EvaluationError('first expects a collection or string', {
100
+ collection,
101
+ })
102
+ }
103
+ const entries = toSeq(collection)
104
+ return entries.length === 0 ? cljNil() : entries[0]
105
+ }),
106
+ 'Returns the first element of the given collection or string.',
107
+ [['coll']]
108
+ ),
109
+ rest: withDoc(
110
+ cljNativeFunction('rest', (collection: CljValue) => {
111
+ if (collection.kind === 'nil') return cljList([])
112
+ if (!isSeqable(collection)) {
113
+ throw new EvaluationError('rest expects a collection or string', {
114
+ collection,
115
+ })
116
+ }
117
+ if (isList(collection)) {
118
+ if (collection.value.length === 0) {
119
+ return collection // return the empty list
120
+ }
121
+ return cljList(collection.value.slice(1))
122
+ }
123
+ if (isVector(collection)) {
124
+ return cljVector(collection.value.slice(1))
125
+ }
126
+ if (isMap(collection)) {
127
+ if (collection.entries.length === 0) {
128
+ return collection // return the empty map
129
+ }
130
+ return cljMap(collection.entries.slice(1))
131
+ }
132
+ if (collection.kind === 'string') {
133
+ const chars = toSeq(collection)
134
+ return cljList(chars.slice(1))
135
+ }
136
+ throw new EvaluationError(
137
+ `rest expects a collection or string, got ${printString(collection)}`,
138
+ { collection }
139
+ )
140
+ }),
141
+ 'Returns a sequence of the given collection or string excluding the first element.',
142
+ [['coll']]
143
+ ),
144
+ conj: withDoc(
145
+ cljNativeFunction('conj', (collection: CljValue, ...args: CljValue[]) => {
146
+ if (!collection) {
147
+ throw new EvaluationError(
148
+ 'conj expects a collection as first argument',
149
+ { collection }
150
+ )
151
+ }
152
+ if (args.length === 0) {
153
+ return collection
154
+ }
155
+ if (!isCollection(collection)) {
156
+ throw new EvaluationError(
157
+ `conj expects a collection, got ${printString(collection)}`,
158
+ { collection }
159
+ )
160
+ }
161
+ if (isList(collection)) {
162
+ const newItems = [] as CljValue[]
163
+ for (let i = args.length - 1; i >= 0; i--) {
164
+ newItems.push(args[i])
165
+ }
166
+ return cljList([...newItems, ...collection.value])
167
+ }
168
+ if (isVector(collection)) {
169
+ return cljVector([...collection.value, ...args])
170
+ }
171
+ if (isMap(collection)) {
172
+ // each argument should be a vector key-pair
173
+ const newEntries: [CljValue, CljValue][] = [...collection.entries]
174
+ for (let i = 0; i < args.length; i += 1) {
175
+ const pair = args[i] as CljVector
176
+
177
+ if (pair.kind !== 'vector') {
178
+ throw new EvaluationError(
179
+ `conj on maps expects each argument to be a vector key-pair for maps, got ${printString(pair)}`,
180
+ { pair }
181
+ )
182
+ }
183
+ if (pair.value.length !== 2) {
184
+ throw new EvaluationError(
185
+ `conj on maps expects each argument to be a vector key-pair for maps, got ${printString(pair)}`,
186
+ { pair }
187
+ )
188
+ }
189
+ const key = pair.value[0]
190
+ const keyIdx = newEntries.findIndex((entry) => isEqual(entry[0], key))
191
+ if (keyIdx === -1) {
192
+ newEntries.push([key, pair.value[1]])
193
+ } else {
194
+ newEntries[keyIdx] = [key, pair.value[1]]
195
+ }
196
+ }
197
+ return cljMap([...newEntries])
198
+ }
199
+
200
+ throw new EvaluationError(
201
+ `unhandled collection type, got ${printString(collection)}`,
202
+ { collection }
203
+ )
204
+ }),
205
+ 'Appends args to the given collection. Lists append in reverse order to the head, vectors append to the tail.',
206
+ [['collection', '&', 'args']]
207
+ ),
208
+ cons: withDoc(
209
+ cljNativeFunction('cons', (x: CljValue, xs: CljValue) => {
210
+ if (!isCollection(xs)) {
211
+ throw new EvaluationError(
212
+ `cons expects a collection as second argument, got ${printString(xs)}`,
213
+ { xs }
214
+ )
215
+ }
216
+ if (isMap(xs)) {
217
+ throw new EvaluationError(
218
+ 'cons on maps is not supported, use vectors instead',
219
+ { xs }
220
+ )
221
+ }
222
+
223
+ const wrap = isList(xs) ? cljList : cljVector
224
+ const newItems = [x, ...xs.value]
225
+
226
+ return wrap(newItems)
227
+ }),
228
+ 'Returns a new collection with x prepended to the head of xs.',
229
+ [['x', 'xs']]
230
+ ),
231
+ assoc: withDoc(
232
+ cljNativeFunction('assoc', (collection: CljValue, ...args: CljValue[]) => {
233
+ if (!collection) {
234
+ throw new EvaluationError(
235
+ 'assoc expects a collection as first argument',
236
+ { collection }
237
+ )
238
+ }
239
+ // nil is treated as an empty map, matching Clojure: (assoc nil :k v) => {:k v}
240
+ if (isNil(collection)) {
241
+ collection = cljMap([])
242
+ }
243
+ if (isList(collection)) {
244
+ throw new EvaluationError(
245
+ 'assoc on lists is not supported, use vectors instead',
246
+ { collection }
247
+ )
248
+ }
249
+ if (!isCollection(collection)) {
250
+ throw new EvaluationError(
251
+ `assoc expects a collection, got ${printString(collection)}`,
252
+ { collection }
253
+ )
254
+ }
255
+ if (args.length < 2) {
256
+ throw new EvaluationError('assoc expects at least two arguments', {
257
+ args,
258
+ })
259
+ }
260
+ if (args.length % 2 !== 0) {
261
+ throw new EvaluationError(
262
+ 'assoc expects an even number of binding arguments',
263
+ {
264
+ args,
265
+ }
266
+ )
267
+ }
268
+ if (isVector(collection)) {
269
+ const newValues = [...collection.value]
270
+ for (let i = 0; i < args.length; i += 2) {
271
+ const index = args[i]
272
+ if (index.kind !== 'number') {
273
+ throw new EvaluationError(
274
+ `assoc on vectors expects each key argument to be a index (number), got ${printString(index)}`,
275
+ { index }
276
+ )
277
+ }
278
+ if (index.value > newValues.length) {
279
+ throw new EvaluationError(
280
+ `assoc index ${index.value} is out of bounds for vector of length ${newValues.length}`,
281
+ { index, collection }
282
+ )
283
+ }
284
+ newValues[(index as CljNumber).value] = args[i + 1]
285
+ }
286
+ return cljVector(newValues)
287
+ }
288
+ if (isMap(collection)) {
289
+ const newEntries: [CljValue, CljValue][] = [...collection.entries]
290
+ // need to find the entry with the same key and replace it, if it doesn't exist, add it
291
+ for (let i = 0; i < args.length; i += 2) {
292
+ const key = args[i]
293
+ const value = args[i + 1]
294
+ const entryIdx = newEntries.findIndex((entry) =>
295
+ isEqual(entry[0], key)
296
+ )
297
+ if (entryIdx === -1) {
298
+ newEntries.push([key, value])
299
+ } else {
300
+ newEntries[entryIdx] = [key, value]
301
+ }
302
+ }
303
+ return cljMap(newEntries)
304
+ }
305
+ throw new EvaluationError(
306
+ `unhandled collection type, got ${printString(collection)}`,
307
+ { collection }
308
+ )
309
+ }),
310
+ 'Associates the value val with the key k in collection. If collection is a map, returns a new map with the same mappings, otherwise returns a vector with the new value at index k.',
311
+ [['collection', '&', 'kvals']]
312
+ ),
313
+ dissoc: withDoc(
314
+ cljNativeFunction('dissoc', (collection: CljValue, ...args: CljValue[]) => {
315
+ if (!collection) {
316
+ throw new EvaluationError(
317
+ 'dissoc expects a collection as first argument',
318
+ { collection }
319
+ )
320
+ }
321
+ if (isList(collection)) {
322
+ throw new EvaluationError(
323
+ 'dissoc on lists is not supported, use vectors instead',
324
+ { collection }
325
+ )
326
+ }
327
+ if (!isCollection(collection)) {
328
+ throw new EvaluationError(
329
+ `dissoc expects a collection, got ${printString(collection)}`,
330
+ { collection }
331
+ )
332
+ }
333
+ if (isVector(collection)) {
334
+ if (collection.value.length === 0) {
335
+ return collection // return the empty vector
336
+ }
337
+ const newValues = [...collection.value]
338
+ for (let i = 0; i < args.length; i += 1) {
339
+ const index = args[i]
340
+ if (index.kind !== 'number') {
341
+ throw new EvaluationError(
342
+ `dissoc on vectors expects each key argument to be a index (number), got ${printString(index)}`,
343
+ { index }
344
+ )
345
+ }
346
+ if (index.value >= newValues.length) {
347
+ throw new EvaluationError(
348
+ `dissoc index ${index.value} is out of bounds for vector of length ${newValues.length}`,
349
+ { index, collection }
350
+ )
351
+ }
352
+ newValues.splice(index.value, 1)
353
+ }
354
+ return cljVector(newValues)
355
+ }
356
+ if (isMap(collection)) {
357
+ if (collection.entries.length === 0) {
358
+ return collection // return the empty map
359
+ }
360
+ const newEntries: [CljValue, CljValue][] = [...collection.entries]
361
+ for (let i = 0; i < args.length; i += 1) {
362
+ const key = args[i]
363
+ const entryIdx = newEntries.findIndex((entry) =>
364
+ isEqual(entry[0], key)
365
+ )
366
+ if (entryIdx === -1) {
367
+ return collection // not found, unchanged
368
+ }
369
+ newEntries.splice(entryIdx, 1)
370
+ }
371
+ return cljMap(newEntries)
372
+ }
373
+ throw new EvaluationError(
374
+ `unhandled collection type, got ${printString(collection)}`,
375
+ { collection }
376
+ )
377
+ }),
378
+ 'Dissociates the key k from collection. If collection is a map, returns a new map with the same mappings, otherwise returns a vector with the value at index k removed.',
379
+ [['collection', '&', 'keys']]
380
+ ),
381
+ get: withDoc(
382
+ cljNativeFunction(
383
+ 'get',
384
+ (target: CljValue, key: CljValue, notFound?: CljValue) => {
385
+ const defaultValue = notFound ?? cljNil()
386
+
387
+ switch (target.kind) {
388
+ case valueKeywords.map: {
389
+ const entries = target.entries
390
+ for (const [k, v] of entries) {
391
+ if (isEqual(k, key)) {
392
+ return v
393
+ }
394
+ }
395
+ return defaultValue
396
+ }
397
+ case valueKeywords.vector: {
398
+ const values = target.value
399
+ if (key.kind !== 'number') {
400
+ throw new EvaluationError(
401
+ 'get on vectors expects a 0-based index as parameter',
402
+ { key }
403
+ )
404
+ }
405
+ if (key.value < 0 || key.value >= values.length) {
406
+ return defaultValue
407
+ }
408
+ return values[key.value]
409
+ }
410
+ default:
411
+ return defaultValue
412
+ }
413
+ }
414
+ ),
415
+ 'Returns the value associated with key in target. If target is a map, returns the value associated with key, otherwise returns the value at index key in target. If not-found is provided, it is returned if the key is not found, otherwise nil is returned.',
416
+ [
417
+ ['target', 'key'],
418
+ ['target', 'key', 'not-found'],
419
+ ]
420
+ ),
421
+ nth: withDoc(
422
+ cljNativeFunction(
423
+ 'nth',
424
+ (coll: CljValue, n: CljValue, notFound?: CljValue) => {
425
+ if (coll === undefined || (!isList(coll) && !isVector(coll))) {
426
+ throw new EvaluationError(
427
+ `nth expects a list or vector${coll !== undefined ? `, got ${printString(coll)}` : ''}`,
428
+ { coll }
429
+ )
430
+ }
431
+ if (n === undefined || n.kind !== 'number') {
432
+ throw new EvaluationError(
433
+ `nth expects a number index${n !== undefined ? `, got ${printString(n)}` : ''}`,
434
+ { n }
435
+ )
436
+ }
437
+ const index = (n as CljNumber).value
438
+ const items = coll.value
439
+ if (index < 0 || index >= items.length) {
440
+ if (notFound !== undefined) return notFound
441
+ throw new EvaluationError(
442
+ `nth index ${index} is out of bounds for collection of length ${items.length}`,
443
+ { coll, n }
444
+ )
445
+ }
446
+ return items[index]
447
+ }
448
+ ),
449
+ 'Returns the nth element of the given collection. If not-found is provided, it is returned if the index is out of bounds, otherwise an error is thrown.',
450
+ [['coll', 'n', 'not-found']]
451
+ ),
452
+
453
+ // take: cljNativeFunction('take', (n: CljValue, coll: CljValue) => {
454
+ // if (n === undefined || n.kind !== 'number') {
455
+ // throw new EvaluationError(
456
+ // `take expects a number as first argument${n !== undefined ? `, got ${printString(n)}` : ''}`,
457
+ // { n }
458
+ // )
459
+ // }
460
+ // if (coll === undefined || !isCollection(coll)) {
461
+ // throw new EvaluationError(
462
+ // `take expects a collection as second argument${coll !== undefined ? `, got ${printString(coll)}` : ''}`,
463
+ // { coll }
464
+ // )
465
+ // }
466
+ // const count = (n as CljNumber).value
467
+ // if (count <= 0) return cljList([])
468
+ // return cljList(toSeq(coll).slice(0, count))
469
+ // }),
470
+
471
+ // drop: cljNativeFunction('drop', (n: CljValue, coll: CljValue) => {
472
+ // if (n === undefined || n.kind !== 'number') {
473
+ // throw new EvaluationError(
474
+ // `drop expects a number as first argument${n !== undefined ? `, got ${printString(n)}` : ''}`,
475
+ // { n }
476
+ // )
477
+ // }
478
+ // if (coll === undefined || !isCollection(coll)) {
479
+ // throw new EvaluationError(
480
+ // `drop expects a collection as second argument${coll !== undefined ? `, got ${printString(coll)}` : ''}`,
481
+ // { coll }
482
+ // )
483
+ // }
484
+ // const count = (n as CljNumber).value
485
+ // if (count <= 0) return cljList(toSeq(coll))
486
+ // return cljList(toSeq(coll).slice(count))
487
+ // }),
488
+
489
+ // ── Collection building ──────────────────────────────────────────────────
490
+
491
+ concat: withDoc(
492
+ cljNativeFunction('concat', (...colls: CljValue[]) => {
493
+ const result: CljValue[] = []
494
+ for (const coll of colls) {
495
+ if (!isSeqable(coll)) {
496
+ throw new EvaluationError(
497
+ `concat expects collections or strings, got ${printString(coll)}`,
498
+ { coll }
499
+ )
500
+ }
501
+ result.push(...toSeq(coll))
502
+ }
503
+ return cljList(result)
504
+ }),
505
+ 'Returns a new sequence that is the concatenation of the given sequences or strings.',
506
+ [['&', 'colls']]
507
+ ),
508
+
509
+ // into: cljNativeFunction('into', (to: CljValue, from: CljValue) => {
510
+ // if (to === undefined || !isCollection(to)) {
511
+ // throw new EvaluationError(
512
+ // `into expects a collection as first argument${to !== undefined ? `, got ${printString(to)}` : ''}`,
513
+ // { to }
514
+ // )
515
+ // }
516
+ // if (from === undefined || !isCollection(from)) {
517
+ // throw new EvaluationError(
518
+ // `into expects a collection as second argument${from !== undefined ? `, got ${printString(from)}` : ''}`,
519
+ // { from }
520
+ // )
521
+ // }
522
+ // // reduce conj semantics: destination type drives insertion order
523
+ // let acc = to
524
+ // for (const item of toSeq(from)) {
525
+ // if (isList(acc)) {
526
+ // acc = cljList([item, ...acc.value])
527
+ // } else if (isVector(acc)) {
528
+ // acc = cljVector([...acc.value, item])
529
+ // } else if (isMap(acc)) {
530
+ // const pair = item
531
+ // if (pair.kind !== 'vector' || pair.value.length !== 2) {
532
+ // throw new EvaluationError(
533
+ // `into on a map expects each source element to be a [k v] vector, got ${printString(pair)}`,
534
+ // { pair }
535
+ // )
536
+ // }
537
+ // const [k, v] = pair.value
538
+ // const newEntries: [CljValue, CljValue][] = [...acc.entries]
539
+ // const idx = newEntries.findIndex((entry) => isEqual(entry[0], k))
540
+ // if (idx === -1) {
541
+ // newEntries.push([k, v])
542
+ // } else {
543
+ // newEntries[idx] = [k, v]
544
+ // }
545
+ // acc = cljMap(newEntries)
546
+ // }
547
+ // }
548
+ // return acc
549
+ // }),
550
+
551
+ zipmap: withDoc(
552
+ cljNativeFunction('zipmap', (ks: CljValue, vs: CljValue) => {
553
+ if (ks === undefined || !isSeqable(ks)) {
554
+ throw new EvaluationError(
555
+ `zipmap expects a collection or string as first argument${ks !== undefined ? `, got ${printString(ks)}` : ''}`,
556
+ { ks }
557
+ )
558
+ }
559
+ if (vs === undefined || !isSeqable(vs)) {
560
+ throw new EvaluationError(
561
+ `zipmap expects a collection or string as second argument${vs !== undefined ? `, got ${printString(vs)}` : ''}`,
562
+ { vs }
563
+ )
564
+ }
565
+ const keys = toSeq(ks)
566
+ const vals = toSeq(vs)
567
+ const len = Math.min(keys.length, vals.length)
568
+ const entries: [CljValue, CljValue][] = []
569
+ for (let i = 0; i < len; i++) {
570
+ entries.push([keys[i], vals[i]])
571
+ }
572
+ return cljMap(entries)
573
+ }),
574
+ 'Returns a new map with the keys and values of the given collections.',
575
+ [['ks', 'vs']]
576
+ ),
577
+ last: withDoc(
578
+ cljNativeFunction('last', (coll: CljValue) => {
579
+ if (coll === undefined || (!isList(coll) && !isVector(coll))) {
580
+ throw new EvaluationError(
581
+ `last expects a list or vector${coll !== undefined ? `, got ${printString(coll)}` : ''}`,
582
+ { coll }
583
+ )
584
+ }
585
+ const items = coll.value
586
+ return items.length === 0 ? cljNil() : items[items.length - 1]
587
+ }),
588
+ 'Returns the last element of the given collection.',
589
+ [['coll']]
590
+ ),
591
+
592
+ reverse: withDoc(
593
+ cljNativeFunction('reverse', (coll: CljValue) => {
594
+ if (coll === undefined || (!isList(coll) && !isVector(coll))) {
595
+ throw new EvaluationError(
596
+ `reverse expects a list or vector${coll !== undefined ? `, got ${printString(coll)}` : ''}`,
597
+ { coll }
598
+ )
599
+ }
600
+ return cljList([...coll.value].reverse())
601
+ }),
602
+ 'Returns a new sequence with the elements of the given collection in reverse order.',
603
+ [['coll']]
604
+ ),
605
+
606
+ 'empty?': withDoc(
607
+ cljNativeFunction('empty?', (coll: CljValue) => {
608
+ if (coll === undefined) {
609
+ throw new EvaluationError('empty? expects one argument', {})
610
+ }
611
+ // nil and empty string count as empty, matching Clojure semantics
612
+ if (coll.kind === 'nil') return cljBoolean(true)
613
+ if (!isSeqable(coll)) {
614
+ throw new EvaluationError(
615
+ `empty? expects a collection, string, or nil, got ${printString(coll)}`,
616
+ { coll }
617
+ )
618
+ }
619
+ return cljBoolean(toSeq(coll).length === 0)
620
+ }),
621
+ 'Returns true if coll has no items. Accepts collections, strings, and nil.',
622
+ [['coll']]
623
+ ),
624
+
625
+ 'contains?': withDoc(
626
+ cljNativeFunction('contains?', (coll: CljValue, key: CljValue) => {
627
+ if (coll === undefined) {
628
+ throw new EvaluationError(
629
+ 'contains? expects a collection as first argument',
630
+ {}
631
+ )
632
+ }
633
+ if (key === undefined) {
634
+ throw new EvaluationError(
635
+ 'contains? expects a key as second argument',
636
+ {}
637
+ )
638
+ }
639
+ if (coll.kind === 'nil') return cljBoolean(false)
640
+ if (isMap(coll)) {
641
+ return cljBoolean(coll.entries.some(([k]) => isEqual(k, key)))
642
+ }
643
+ if (isVector(coll)) {
644
+ if (key.kind !== 'number') return cljBoolean(false)
645
+ return cljBoolean(key.value >= 0 && key.value < coll.value.length)
646
+ }
647
+ throw new EvaluationError(
648
+ `contains? expects a map, vector, or nil, got ${printString(coll)}`,
649
+ { coll }
650
+ )
651
+ }),
652
+ 'Returns true if key is present in coll. For maps checks key existence (including keys with nil values). For vectors checks index bounds.',
653
+ [['coll', 'key']]
654
+ ),
655
+
656
+ repeat: withDoc(
657
+ cljNativeFunction('repeat', (n: CljValue, x: CljValue) => {
658
+ if (n === undefined || n.kind !== 'number') {
659
+ // In real clojure, repeat with a single argument creates an infinite seq
660
+ // since we don't support infinite seqs, we throw an error for now
661
+ throw new EvaluationError(
662
+ `repeat expects a number as first argument${n !== undefined ? `, got ${printString(n)}` : ''}`,
663
+ { n }
664
+ )
665
+ }
666
+ return cljList(Array(n.value).fill(x))
667
+ }),
668
+ 'Returns a sequence of n copies of x.',
669
+ [['n', 'x']]
670
+ ),
671
+
672
+ // ── Range ────────────────────────────────────────────────────────────────
673
+
674
+ range: withDoc(
675
+ cljNativeFunction('range', (...args: CljValue[]) => {
676
+ if (args.length === 0 || args.length > 3) {
677
+ throw new EvaluationError(
678
+ 'range expects 1, 2, or 3 arguments: (range n), (range start end), or (range start end step)',
679
+ { args }
680
+ )
681
+ }
682
+ if (args.some((a) => a.kind !== 'number')) {
683
+ throw new EvaluationError('range expects number arguments', { args })
684
+ }
685
+ let start: number
686
+ let end: number
687
+ let step: number
688
+ if (args.length === 1) {
689
+ start = 0
690
+ end = (args[0] as CljNumber).value
691
+ step = 1
692
+ } else if (args.length === 2) {
693
+ start = (args[0] as CljNumber).value
694
+ end = (args[1] as CljNumber).value
695
+ step = 1
696
+ } else {
697
+ start = (args[0] as CljNumber).value
698
+ end = (args[1] as CljNumber).value
699
+ step = (args[2] as CljNumber).value
700
+ }
701
+ if (step === 0) {
702
+ throw new EvaluationError('range step cannot be zero', { args })
703
+ }
704
+ const result: CljValue[] = []
705
+ if (step > 0) {
706
+ for (let i = start; i < end; i += step) {
707
+ result.push(cljNumber(i))
708
+ }
709
+ } else {
710
+ for (let i = start; i > end; i += step) {
711
+ result.push(cljNumber(i))
712
+ }
713
+ }
714
+ return cljList(result)
715
+ }),
716
+ 'Returns a sequence of numbers from start (inclusive) to end (exclusive), incrementing by step. If step is positive, the sequence is generated from start to end, otherwise it is generated from end to start.',
717
+ [['n'], ['start', 'end'], ['start', 'end', 'step']]
718
+ ),
719
+ keys: withDoc(
720
+ cljNativeFunction('keys', (m: CljValue) => {
721
+ if (m === undefined || !isMap(m)) {
722
+ throw new EvaluationError(
723
+ `keys expects a map${m !== undefined ? `, got ${printString(m)}` : ''}`,
724
+ { m }
725
+ )
726
+ }
727
+ return cljVector(m.entries.map(([k]) => k))
728
+ }),
729
+ 'Returns a vector of the keys of the given map.',
730
+ [['m']]
731
+ ),
732
+ vals: withDoc(
733
+ cljNativeFunction('vals', (m: CljValue) => {
734
+ if (m === undefined || !isMap(m)) {
735
+ throw new EvaluationError(
736
+ `vals expects a map${m !== undefined ? `, got ${printString(m)}` : ''}`,
737
+ { m }
738
+ )
739
+ }
740
+ return cljVector(m.entries.map(([, v]) => v))
741
+ }),
742
+ 'Returns a vector of the values of the given map.',
743
+ [['m']]
744
+ ),
745
+ count: withDoc(
746
+ cljNativeFunction('count', (countable: CljValue) => {
747
+ if (
748
+ !(
749
+ [
750
+ valueKeywords.list,
751
+ valueKeywords.vector,
752
+ valueKeywords.map,
753
+ valueKeywords.string,
754
+ ] as string[]
755
+ ).includes(countable.kind)
756
+ ) {
757
+ throw new EvaluationError(
758
+ `count expects a countable value, got ${printString(countable)}`,
759
+ {
760
+ countable,
761
+ }
762
+ )
763
+ }
764
+
765
+ switch (countable.kind) {
766
+ case valueKeywords.list:
767
+ return cljNumber((countable as CljList).value.length)
768
+ case valueKeywords.vector:
769
+ return cljNumber((countable as CljVector).value.length)
770
+ case valueKeywords.map:
771
+ return cljNumber((countable as CljMap).entries.length)
772
+ case valueKeywords.string:
773
+ return cljNumber((countable as CljString).value.length)
774
+ default:
775
+ throw new EvaluationError(
776
+ `count expects a countable value, got ${printString(countable)}`,
777
+ { countable }
778
+ )
779
+ }
780
+ }),
781
+ 'Returns the number of elements in the given countable value.',
782
+ [['countable']]
783
+ ),
784
+ }