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
@@ -1,739 +0,0 @@
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 EvaluationError.atArg(`seq expects a collection, string, or nil, got ${printString(coll)}`, { collection: coll }, 0)
85
- }
86
- const items = toSeq(coll)
87
- return items.length === 0 ? cljNil() : cljList(items)
88
- }),
89
- 'Returns a sequence of the given collection or string. Strings yield a sequence of single-character strings.',
90
- [['coll']]
91
- ),
92
- first: withDoc(
93
- cljNativeFunction('first', (collection: CljValue) => {
94
- if (collection.kind === 'nil') return cljNil()
95
- if (!isSeqable(collection)) {
96
- throw EvaluationError.atArg('first expects a collection or string', { collection }, 0)
97
- }
98
- const entries = toSeq(collection)
99
- return entries.length === 0 ? cljNil() : entries[0]
100
- }),
101
- 'Returns the first element of the given collection or string.',
102
- [['coll']]
103
- ),
104
- rest: withDoc(
105
- cljNativeFunction('rest', (collection: CljValue) => {
106
- if (collection.kind === 'nil') return cljList([])
107
- if (!isSeqable(collection)) {
108
- throw EvaluationError.atArg('rest expects a collection or string', { collection }, 0)
109
- }
110
- if (isList(collection)) {
111
- if (collection.value.length === 0) {
112
- return collection // return the empty list
113
- }
114
- return cljList(collection.value.slice(1))
115
- }
116
- if (isVector(collection)) {
117
- return cljVector(collection.value.slice(1))
118
- }
119
- if (isMap(collection)) {
120
- if (collection.entries.length === 0) {
121
- return collection // return the empty map
122
- }
123
- return cljMap(collection.entries.slice(1))
124
- }
125
- if (collection.kind === 'string') {
126
- const chars = toSeq(collection)
127
- return cljList(chars.slice(1))
128
- }
129
- throw EvaluationError.atArg(`rest expects a collection or string, got ${printString(collection)}`, { collection }, 0)
130
- }),
131
- 'Returns a sequence of the given collection or string excluding the first element.',
132
- [['coll']]
133
- ),
134
- conj: withDoc(
135
- cljNativeFunction('conj', (collection: CljValue, ...args: CljValue[]) => {
136
- if (!collection) {
137
- throw new EvaluationError(
138
- 'conj expects a collection as first argument',
139
- { collection }
140
- )
141
- }
142
- if (args.length === 0) {
143
- return collection
144
- }
145
- if (!isCollection(collection)) {
146
- throw EvaluationError.atArg(`conj expects a collection, got ${printString(collection)}`, { collection }, 0)
147
- }
148
- if (isList(collection)) {
149
- const newItems = [] as CljValue[]
150
- for (let i = args.length - 1; i >= 0; i--) {
151
- newItems.push(args[i])
152
- }
153
- return cljList([...newItems, ...collection.value])
154
- }
155
- if (isVector(collection)) {
156
- return cljVector([...collection.value, ...args])
157
- }
158
- if (isMap(collection)) {
159
- // each argument should be a vector key-pair
160
- const newEntries: [CljValue, CljValue][] = [...collection.entries]
161
- for (let i = 0; i < args.length; i += 1) {
162
- const pair = args[i] as CljVector
163
- // pair args start at index 1 in the call (collection is index 0)
164
- const pairArgIndex = i + 1
165
-
166
- if (pair.kind !== 'vector') {
167
- throw EvaluationError.atArg(
168
- `conj on maps expects each argument to be a vector key-pair for maps, got ${printString(pair)}`,
169
- { pair },
170
- pairArgIndex
171
- )
172
- }
173
- if (pair.value.length !== 2) {
174
- throw EvaluationError.atArg(
175
- `conj on maps expects each argument to be a vector key-pair for maps, got ${printString(pair)}`,
176
- { pair },
177
- pairArgIndex
178
- )
179
- }
180
- const key = pair.value[0]
181
- const keyIdx = newEntries.findIndex((entry) => isEqual(entry[0], key))
182
- if (keyIdx === -1) {
183
- newEntries.push([key, pair.value[1]])
184
- } else {
185
- newEntries[keyIdx] = [key, pair.value[1]]
186
- }
187
- }
188
- return cljMap([...newEntries])
189
- }
190
-
191
- throw new EvaluationError(
192
- `unhandled collection type, got ${printString(collection)}`,
193
- { collection }
194
- )
195
- }),
196
- 'Appends args to the given collection. Lists append in reverse order to the head, vectors append to the tail.',
197
- [['collection', '&', 'args']]
198
- ),
199
- cons: withDoc(
200
- cljNativeFunction('cons', (x: CljValue, xs: CljValue) => {
201
- if (!isCollection(xs)) {
202
- throw EvaluationError.atArg(`cons expects a collection as second argument, got ${printString(xs)}`, { xs }, 1)
203
- }
204
- if (isMap(xs)) {
205
- throw EvaluationError.atArg('cons on maps is not supported, use vectors instead', { xs }, 1)
206
- }
207
-
208
- const wrap = isList(xs) ? cljList : cljVector
209
- const newItems = [x, ...xs.value]
210
-
211
- return wrap(newItems)
212
- }),
213
- 'Returns a new collection with x prepended to the head of xs.',
214
- [['x', 'xs']]
215
- ),
216
- assoc: withDoc(
217
- cljNativeFunction('assoc', (collection: CljValue, ...args: CljValue[]) => {
218
- if (!collection) {
219
- throw new EvaluationError(
220
- 'assoc expects a collection as first argument',
221
- { collection }
222
- )
223
- }
224
- // nil is treated as an empty map, matching Clojure: (assoc nil :k v) => {:k v}
225
- if (isNil(collection)) {
226
- collection = cljMap([])
227
- }
228
- if (isList(collection)) {
229
- throw new EvaluationError(
230
- 'assoc on lists is not supported, use vectors instead',
231
- { collection }
232
- )
233
- }
234
- if (!isCollection(collection)) {
235
- throw EvaluationError.atArg(`assoc expects a collection, got ${printString(collection)}`, { collection }, 0)
236
- }
237
- if (args.length < 2) {
238
- throw new EvaluationError('assoc expects at least two arguments', {
239
- args,
240
- })
241
- }
242
- if (args.length % 2 !== 0) {
243
- throw new EvaluationError(
244
- 'assoc expects an even number of binding arguments',
245
- {
246
- args,
247
- }
248
- )
249
- }
250
- if (isVector(collection)) {
251
- const newValues = [...collection.value]
252
- for (let i = 0; i < args.length; i += 2) {
253
- const index = args[i]
254
- if (index.kind !== 'number') {
255
- throw EvaluationError.atArg(
256
- `assoc on vectors expects each key argument to be a index (number), got ${printString(index)}`,
257
- { index },
258
- i + 1
259
- )
260
- }
261
- if (index.value > newValues.length) {
262
- throw EvaluationError.atArg(
263
- `assoc index ${index.value} is out of bounds for vector of length ${newValues.length}`,
264
- { index, collection },
265
- i + 1
266
- )
267
- }
268
- newValues[(index as CljNumber).value] = args[i + 1]
269
- }
270
- return cljVector(newValues)
271
- }
272
- if (isMap(collection)) {
273
- const newEntries: [CljValue, CljValue][] = [...collection.entries]
274
- // need to find the entry with the same key and replace it, if it doesn't exist, add it
275
- for (let i = 0; i < args.length; i += 2) {
276
- const key = args[i]
277
- const value = args[i + 1]
278
- const entryIdx = newEntries.findIndex((entry) =>
279
- isEqual(entry[0], key)
280
- )
281
- if (entryIdx === -1) {
282
- newEntries.push([key, value])
283
- } else {
284
- newEntries[entryIdx] = [key, value]
285
- }
286
- }
287
- return cljMap(newEntries)
288
- }
289
- throw new EvaluationError(
290
- `unhandled collection type, got ${printString(collection)}`,
291
- { collection }
292
- )
293
- }),
294
- '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.',
295
- [['collection', '&', 'kvals']]
296
- ),
297
- dissoc: withDoc(
298
- cljNativeFunction('dissoc', (collection: CljValue, ...args: CljValue[]) => {
299
- if (!collection) {
300
- throw new EvaluationError(
301
- 'dissoc expects a collection as first argument',
302
- { collection }
303
- )
304
- }
305
- if (isList(collection)) {
306
- throw EvaluationError.atArg('dissoc on lists is not supported, use vectors instead', { collection }, 0)
307
- }
308
- if (!isCollection(collection)) {
309
- throw EvaluationError.atArg(`dissoc expects a collection, got ${printString(collection)}`, { collection }, 0)
310
- }
311
- if (isVector(collection)) {
312
- if (collection.value.length === 0) {
313
- return collection // return the empty vector
314
- }
315
- const newValues = [...collection.value]
316
- for (let i = 0; i < args.length; i += 1) {
317
- const index = args[i]
318
- if (index.kind !== 'number') {
319
- throw EvaluationError.atArg(
320
- `dissoc on vectors expects each key argument to be a index (number), got ${printString(index)}`,
321
- { index },
322
- i + 1
323
- )
324
- }
325
- if (index.value >= newValues.length) {
326
- throw EvaluationError.atArg(
327
- `dissoc index ${index.value} is out of bounds for vector of length ${newValues.length}`,
328
- { index, collection },
329
- i + 1
330
- )
331
- }
332
- newValues.splice(index.value, 1)
333
- }
334
- return cljVector(newValues)
335
- }
336
- if (isMap(collection)) {
337
- if (collection.entries.length === 0) {
338
- return collection // return the empty map
339
- }
340
- const newEntries: [CljValue, CljValue][] = [...collection.entries]
341
- for (let i = 0; i < args.length; i += 1) {
342
- const key = args[i]
343
- const entryIdx = newEntries.findIndex((entry) =>
344
- isEqual(entry[0], key)
345
- )
346
- if (entryIdx === -1) {
347
- return collection // not found, unchanged
348
- }
349
- newEntries.splice(entryIdx, 1)
350
- }
351
- return cljMap(newEntries)
352
- }
353
- throw new EvaluationError(
354
- `unhandled collection type, got ${printString(collection)}`,
355
- { collection }
356
- )
357
- }),
358
- '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.',
359
- [['collection', '&', 'keys']]
360
- ),
361
- get: withDoc(
362
- cljNativeFunction(
363
- 'get',
364
- (target: CljValue, key: CljValue, notFound?: CljValue) => {
365
- const defaultValue = notFound ?? cljNil()
366
-
367
- switch (target.kind) {
368
- case valueKeywords.map: {
369
- const entries = target.entries
370
- for (const [k, v] of entries) {
371
- if (isEqual(k, key)) {
372
- return v
373
- }
374
- }
375
- return defaultValue
376
- }
377
- case valueKeywords.vector: {
378
- const values = target.value
379
- if (key.kind !== 'number') {
380
- throw new EvaluationError(
381
- 'get on vectors expects a 0-based index as parameter',
382
- { key }
383
- )
384
- }
385
- if (key.value < 0 || key.value >= values.length) {
386
- return defaultValue
387
- }
388
- return values[key.value]
389
- }
390
- default:
391
- return defaultValue
392
- }
393
- }
394
- ),
395
- '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.',
396
- [
397
- ['target', 'key'],
398
- ['target', 'key', 'not-found'],
399
- ]
400
- ),
401
- nth: withDoc(
402
- cljNativeFunction(
403
- 'nth',
404
- (coll: CljValue, n: CljValue, notFound?: CljValue) => {
405
- if (coll === undefined || (!isList(coll) && !isVector(coll))) {
406
- throw new EvaluationError(
407
- `nth expects a list or vector${coll !== undefined ? `, got ${printString(coll)}` : ''}`,
408
- { coll }
409
- )
410
- }
411
- if (n === undefined || n.kind !== 'number') {
412
- throw new EvaluationError(
413
- `nth expects a number index${n !== undefined ? `, got ${printString(n)}` : ''}`,
414
- { n }
415
- )
416
- }
417
- const index = (n as CljNumber).value
418
- const items = coll.value
419
- if (index < 0 || index >= items.length) {
420
- if (notFound !== undefined) return notFound
421
- const err = new EvaluationError(
422
- `nth index ${index} is out of bounds for collection of length ${items.length}`,
423
- { coll, n }
424
- )
425
- err.data = { argIndex: 1 }
426
- throw err
427
- }
428
- return items[index]
429
- }
430
- ),
431
- '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.',
432
- [['coll', 'n', 'not-found']]
433
- ),
434
-
435
- // take: cljNativeFunction('take', (n: CljValue, coll: CljValue) => {
436
- // if (n === undefined || n.kind !== 'number') {
437
- // throw new EvaluationError(
438
- // `take expects a number as first argument${n !== undefined ? `, got ${printString(n)}` : ''}`,
439
- // { n }
440
- // )
441
- // }
442
- // if (coll === undefined || !isCollection(coll)) {
443
- // throw new EvaluationError(
444
- // `take expects a collection as second argument${coll !== undefined ? `, got ${printString(coll)}` : ''}`,
445
- // { coll }
446
- // )
447
- // }
448
- // const count = (n as CljNumber).value
449
- // if (count <= 0) return cljList([])
450
- // return cljList(toSeq(coll).slice(0, count))
451
- // }),
452
-
453
- // drop: cljNativeFunction('drop', (n: CljValue, coll: CljValue) => {
454
- // if (n === undefined || n.kind !== 'number') {
455
- // throw new EvaluationError(
456
- // `drop 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
- // `drop 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(toSeq(coll))
468
- // return cljList(toSeq(coll).slice(count))
469
- // }),
470
-
471
- // ── Collection building ──────────────────────────────────────────────────
472
-
473
- concat: withDoc(
474
- cljNativeFunction('concat', (...colls: CljValue[]) => {
475
- const result: CljValue[] = []
476
- for (const coll of colls) {
477
- if (!isSeqable(coll)) {
478
- throw new EvaluationError(
479
- `concat expects collections or strings, got ${printString(coll)}`,
480
- { coll }
481
- )
482
- }
483
- result.push(...toSeq(coll))
484
- }
485
- return cljList(result)
486
- }),
487
- 'Returns a new sequence that is the concatenation of the given sequences or strings.',
488
- [['&', 'colls']]
489
- ),
490
-
491
- // into: cljNativeFunction('into', (to: CljValue, from: CljValue) => {
492
- // if (to === undefined || !isCollection(to)) {
493
- // throw new EvaluationError(
494
- // `into expects a collection as first argument${to !== undefined ? `, got ${printString(to)}` : ''}`,
495
- // { to }
496
- // )
497
- // }
498
- // if (from === undefined || !isCollection(from)) {
499
- // throw new EvaluationError(
500
- // `into expects a collection as second argument${from !== undefined ? `, got ${printString(from)}` : ''}`,
501
- // { from }
502
- // )
503
- // }
504
- // // reduce conj semantics: destination type drives insertion order
505
- // let acc = to
506
- // for (const item of toSeq(from)) {
507
- // if (isList(acc)) {
508
- // acc = cljList([item, ...acc.value])
509
- // } else if (isVector(acc)) {
510
- // acc = cljVector([...acc.value, item])
511
- // } else if (isMap(acc)) {
512
- // const pair = item
513
- // if (pair.kind !== 'vector' || pair.value.length !== 2) {
514
- // throw new EvaluationError(
515
- // `into on a map expects each source element to be a [k v] vector, got ${printString(pair)}`,
516
- // { pair }
517
- // )
518
- // }
519
- // const [k, v] = pair.value
520
- // const newEntries: [CljValue, CljValue][] = [...acc.entries]
521
- // const idx = newEntries.findIndex((entry) => isEqual(entry[0], k))
522
- // if (idx === -1) {
523
- // newEntries.push([k, v])
524
- // } else {
525
- // newEntries[idx] = [k, v]
526
- // }
527
- // acc = cljMap(newEntries)
528
- // }
529
- // }
530
- // return acc
531
- // }),
532
-
533
- zipmap: withDoc(
534
- cljNativeFunction('zipmap', (ks: CljValue, vs: CljValue) => {
535
- if (ks === undefined || !isSeqable(ks)) {
536
- throw new EvaluationError(
537
- `zipmap expects a collection or string as first argument${ks !== undefined ? `, got ${printString(ks)}` : ''}`,
538
- { ks }
539
- )
540
- }
541
- if (vs === undefined || !isSeqable(vs)) {
542
- throw new EvaluationError(
543
- `zipmap expects a collection or string as second argument${vs !== undefined ? `, got ${printString(vs)}` : ''}`,
544
- { vs }
545
- )
546
- }
547
- const keys = toSeq(ks)
548
- const vals = toSeq(vs)
549
- const len = Math.min(keys.length, vals.length)
550
- const entries: [CljValue, CljValue][] = []
551
- for (let i = 0; i < len; i++) {
552
- entries.push([keys[i], vals[i]])
553
- }
554
- return cljMap(entries)
555
- }),
556
- 'Returns a new map with the keys and values of the given collections.',
557
- [['ks', 'vs']]
558
- ),
559
- last: withDoc(
560
- cljNativeFunction('last', (coll: CljValue) => {
561
- if (coll === undefined || (!isList(coll) && !isVector(coll))) {
562
- throw new EvaluationError(
563
- `last expects a list or vector${coll !== undefined ? `, got ${printString(coll)}` : ''}`,
564
- { coll }
565
- )
566
- }
567
- const items = coll.value
568
- return items.length === 0 ? cljNil() : items[items.length - 1]
569
- }),
570
- 'Returns the last element of the given collection.',
571
- [['coll']]
572
- ),
573
-
574
- reverse: withDoc(
575
- cljNativeFunction('reverse', (coll: CljValue) => {
576
- if (coll === undefined || (!isList(coll) && !isVector(coll))) {
577
- throw EvaluationError.atArg(`reverse expects a list or vector${coll !== undefined ? `, got ${printString(coll)}` : ''}`, { coll }, 0)
578
- }
579
- return cljList([...coll.value].reverse())
580
- }),
581
- 'Returns a new sequence with the elements of the given collection in reverse order.',
582
- [['coll']]
583
- ),
584
-
585
- 'empty?': withDoc(
586
- cljNativeFunction('empty?', (coll: CljValue) => {
587
- if (coll === undefined) {
588
- throw EvaluationError.atArg('empty? expects one argument', {}, 0)
589
- }
590
- // nil and empty string count as empty, matching Clojure semantics
591
- if (coll.kind === 'nil') return cljBoolean(true)
592
- if (!isSeqable(coll)) {
593
- throw EvaluationError.atArg(`empty? expects a collection, string, or nil, got ${printString(coll)}`, { coll }, 0)
594
- }
595
- return cljBoolean(toSeq(coll).length === 0)
596
- }),
597
- 'Returns true if coll has no items. Accepts collections, strings, and nil.',
598
- [['coll']]
599
- ),
600
-
601
- 'contains?': withDoc(
602
- cljNativeFunction('contains?', (coll: CljValue, key: CljValue) => {
603
- if (coll === undefined) {
604
- throw EvaluationError.atArg('contains? expects a collection as first argument', {}, 0)
605
- }
606
- if (key === undefined) {
607
- throw EvaluationError.atArg('contains? expects a key as second argument', {}, 1)
608
- }
609
- if (coll.kind === 'nil') return cljBoolean(false)
610
- if (isMap(coll)) {
611
- return cljBoolean(coll.entries.some(([k]) => isEqual(k, key)))
612
- }
613
- if (isVector(coll)) {
614
- if (key.kind !== 'number') return cljBoolean(false)
615
- return cljBoolean(key.value >= 0 && key.value < coll.value.length)
616
- }
617
- throw EvaluationError.atArg(`contains? expects a map, vector, or nil, got ${printString(coll)}`, { coll }, 0)
618
- }),
619
- 'Returns true if key is present in coll. For maps checks key existence (including keys with nil values). For vectors checks index bounds.',
620
- [['coll', 'key']]
621
- ),
622
-
623
- repeat: withDoc(
624
- cljNativeFunction('repeat', (n: CljValue, x: CljValue) => {
625
- if (n === undefined || n.kind !== 'number') {
626
- // In real clojure, repeat with a single argument creates an infinite seq
627
- // since we don't support infinite seqs, we throw an error for now
628
- throw EvaluationError.atArg(`repeat expects a number as first argument${n !== undefined ? `, got ${printString(n)}` : ''}`, { n }, 0)
629
- }
630
- return cljList(Array(n.value).fill(x))
631
- }),
632
- 'Returns a sequence of n copies of x.',
633
- [['n', 'x']]
634
- ),
635
-
636
- // ── Range ────────────────────────────────────────────────────────────────
637
-
638
- range: withDoc(
639
- cljNativeFunction('range', (...args: CljValue[]) => {
640
- if (args.length === 0 || args.length > 3) {
641
- throw new EvaluationError(
642
- 'range expects 1, 2, or 3 arguments: (range n), (range start end), or (range start end step)',
643
- { args }
644
- )
645
- }
646
- const badIdx = args.findIndex((a) => a.kind !== 'number')
647
- if (badIdx !== -1) {
648
- throw EvaluationError.atArg('range expects number arguments', { args }, badIdx)
649
- }
650
- let start: number
651
- let end: number
652
- let step: number
653
- if (args.length === 1) {
654
- start = 0
655
- end = (args[0] as CljNumber).value
656
- step = 1
657
- } else if (args.length === 2) {
658
- start = (args[0] as CljNumber).value
659
- end = (args[1] as CljNumber).value
660
- step = 1
661
- } else {
662
- start = (args[0] as CljNumber).value
663
- end = (args[1] as CljNumber).value
664
- step = (args[2] as CljNumber).value
665
- }
666
- if (step === 0) {
667
- // step is always the last arg: index args.length - 1
668
- throw EvaluationError.atArg('range step cannot be zero', { args }, args.length - 1)
669
- }
670
- const result: CljValue[] = []
671
- if (step > 0) {
672
- for (let i = start; i < end; i += step) {
673
- result.push(cljNumber(i))
674
- }
675
- } else {
676
- for (let i = start; i > end; i += step) {
677
- result.push(cljNumber(i))
678
- }
679
- }
680
- return cljList(result)
681
- }),
682
- '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.',
683
- [['n'], ['start', 'end'], ['start', 'end', 'step']]
684
- ),
685
- keys: withDoc(
686
- cljNativeFunction('keys', (m: CljValue) => {
687
- if (m === undefined || !isMap(m)) {
688
- throw EvaluationError.atArg(`keys expects a map${m !== undefined ? `, got ${printString(m)}` : ''}`, { m }, 0)
689
- }
690
- return cljVector(m.entries.map(([k]) => k))
691
- }),
692
- 'Returns a vector of the keys of the given map.',
693
- [['m']]
694
- ),
695
- vals: withDoc(
696
- cljNativeFunction('vals', (m: CljValue) => {
697
- if (m === undefined || !isMap(m)) {
698
- throw EvaluationError.atArg(`vals expects a map${m !== undefined ? `, got ${printString(m)}` : ''}`, { m }, 0)
699
- }
700
- return cljVector(m.entries.map(([, v]) => v))
701
- }),
702
- 'Returns a vector of the values of the given map.',
703
- [['m']]
704
- ),
705
- count: withDoc(
706
- cljNativeFunction('count', (countable: CljValue) => {
707
- if (
708
- !(
709
- [
710
- valueKeywords.list,
711
- valueKeywords.vector,
712
- valueKeywords.map,
713
- valueKeywords.string,
714
- ] as string[]
715
- ).includes(countable.kind)
716
- ) {
717
- throw EvaluationError.atArg(`count expects a countable value, got ${printString(countable)}`, { countable }, 0)
718
- }
719
-
720
- switch (countable.kind) {
721
- case valueKeywords.list:
722
- return cljNumber((countable as CljList).value.length)
723
- case valueKeywords.vector:
724
- return cljNumber((countable as CljVector).value.length)
725
- case valueKeywords.map:
726
- return cljNumber((countable as CljMap).entries.length)
727
- case valueKeywords.string:
728
- return cljNumber((countable as CljString).value.length)
729
- default:
730
- throw new EvaluationError(
731
- `count expects a countable value, got ${printString(countable)}`,
732
- { countable }
733
- )
734
- }
735
- }),
736
- 'Returns the number of elements in the given countable value.',
737
- [['countable']]
738
- ),
739
- }