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.
- package/dist-cli/conjure-js.mjs +9360 -5298
- package/dist-vite-plugin/index.mjs +9463 -5185
- package/package.json +3 -1
- package/src/bin/cli.ts +2 -2
- package/src/bin/nrepl-symbol.ts +150 -0
- package/src/bin/nrepl.ts +289 -167
- package/src/bin/version.ts +1 -1
- package/src/clojure/core.clj +757 -29
- package/src/clojure/core.clj.d.ts +75 -131
- package/src/clojure/generated/builtin-namespace-registry.ts +4 -0
- package/src/clojure/generated/clojure-core-source.ts +758 -29
- package/src/clojure/generated/clojure-set-source.ts +136 -0
- package/src/clojure/generated/clojure-walk-source.ts +72 -0
- package/src/clojure/set.clj +132 -0
- package/src/clojure/set.clj.d.ts +20 -0
- package/src/clojure/string.clj.d.ts +14 -0
- package/src/clojure/walk.clj +68 -0
- package/src/clojure/walk.clj.d.ts +7 -0
- package/src/core/assertions.ts +114 -6
- package/src/core/bootstrap.ts +337 -0
- package/src/core/conversions.ts +48 -31
- package/src/core/core-module.ts +303 -0
- package/src/core/env.ts +20 -6
- package/src/core/evaluator/apply.ts +40 -25
- package/src/core/evaluator/arity.ts +8 -8
- package/src/core/evaluator/async-evaluator.ts +565 -0
- package/src/core/evaluator/collections.ts +28 -5
- package/src/core/evaluator/destructure.ts +180 -69
- package/src/core/evaluator/dispatch.ts +12 -14
- package/src/core/evaluator/evaluate.ts +22 -20
- package/src/core/evaluator/expand.ts +45 -15
- package/src/core/evaluator/form-parsers.ts +178 -0
- package/src/core/evaluator/index.ts +7 -9
- package/src/core/evaluator/js-interop.ts +189 -0
- package/src/core/evaluator/quasiquote.ts +14 -8
- package/src/core/evaluator/recur-check.ts +6 -6
- package/src/core/evaluator/special-forms.ts +234 -191
- package/src/core/factories.ts +182 -3
- package/src/core/index.ts +54 -4
- package/src/core/module.ts +136 -0
- package/src/core/ns-forms.ts +107 -0
- package/src/core/printer.ts +371 -11
- package/src/core/reader.ts +84 -33
- package/src/core/registry.ts +209 -0
- package/src/core/runtime.ts +376 -0
- package/src/core/session.ts +253 -487
- package/src/core/stdlib/arithmetic.ts +528 -194
- package/src/core/stdlib/async-fns.ts +132 -0
- package/src/core/stdlib/atoms.ts +291 -56
- package/src/core/stdlib/errors.ts +54 -50
- package/src/core/stdlib/hof.ts +82 -166
- package/src/core/stdlib/js-namespace.ts +344 -0
- package/src/core/stdlib/lazy.ts +34 -0
- package/src/core/stdlib/maps-sets.ts +322 -0
- package/src/core/stdlib/meta.ts +61 -30
- package/src/core/stdlib/predicates.ts +325 -187
- package/src/core/stdlib/regex.ts +126 -98
- package/src/core/stdlib/seq.ts +564 -0
- package/src/core/stdlib/strings.ts +164 -135
- package/src/core/stdlib/transducers.ts +95 -100
- package/src/core/stdlib/utils.ts +292 -130
- package/src/core/stdlib/vars.ts +27 -27
- package/src/core/stdlib/vectors.ts +122 -0
- package/src/core/tokenizer.ts +2 -2
- package/src/core/transformations.ts +117 -9
- package/src/core/types.ts +98 -2
- package/src/host/node-host-module.ts +74 -0
- package/src/{vite-plugin-clj/nrepl-relay.ts → nrepl/relay.ts} +72 -11
- package/src/vite-plugin-clj/codegen.ts +87 -95
- package/src/vite-plugin-clj/index.ts +178 -23
- package/src/vite-plugin-clj/namespace-utils.ts +39 -0
- package/src/vite-plugin-clj/static-analysis.ts +211 -0
- package/src/clojure/demo.clj +0 -72
- package/src/clojure/demo.clj.d.ts +0 -0
- package/src/core/core-env.ts +0 -61
- package/src/core/stdlib/collections.ts +0 -739
- package/src/host/node.ts +0 -55
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
// Sequence abstraction: list, seq, first, rest, cons, conj, count, empty?, empty,
|
|
2
|
+
// nth, get, contains?, last, reverse, repeat*, range*
|
|
3
|
+
//
|
|
4
|
+
// These are the "core sequence protocol" operations — they apply uniformly across
|
|
5
|
+
// all collection types. conj lives here because it implements the sequence
|
|
6
|
+
// construction protocol (prepend for lists, append for vectors, kv-pair for maps,
|
|
7
|
+
// element dedup for sets).
|
|
8
|
+
|
|
9
|
+
import { is } from '../assertions'
|
|
10
|
+
import { EvaluationError } from '../errors'
|
|
11
|
+
import { v } from '../factories'
|
|
12
|
+
import { printString } from '../printer'
|
|
13
|
+
import { realizeLazySeq, toSeq } from '../transformations'
|
|
14
|
+
import {
|
|
15
|
+
valueKeywords,
|
|
16
|
+
type CljList,
|
|
17
|
+
type CljMap,
|
|
18
|
+
type CljNumber,
|
|
19
|
+
type CljSet,
|
|
20
|
+
type CljString,
|
|
21
|
+
type CljValue,
|
|
22
|
+
type CljVector,
|
|
23
|
+
} from '../types'
|
|
24
|
+
|
|
25
|
+
export const seqFunctions: Record<string, CljValue> = {
|
|
26
|
+
list: v
|
|
27
|
+
.nativeFn('list', function listImpl(...args: CljValue[]) {
|
|
28
|
+
if (args.length === 0) {
|
|
29
|
+
return v.list([])
|
|
30
|
+
}
|
|
31
|
+
return v.list(args)
|
|
32
|
+
})
|
|
33
|
+
.doc('Returns a new list containing the given values.', [['&', 'args']]),
|
|
34
|
+
|
|
35
|
+
seq: v
|
|
36
|
+
.nativeFn('seq', function seqImpl(coll: CljValue): CljValue {
|
|
37
|
+
if (coll.kind === 'nil') return v.nil()
|
|
38
|
+
if (is.lazySeq(coll)) {
|
|
39
|
+
const realized = realizeLazySeq(coll)
|
|
40
|
+
if (is.nil(realized)) return v.nil()
|
|
41
|
+
return seqImpl(realized)
|
|
42
|
+
}
|
|
43
|
+
if (is.cons(coll)) return coll
|
|
44
|
+
if (!is.seqable(coll)) {
|
|
45
|
+
throw EvaluationError.atArg(
|
|
46
|
+
`seq expects a collection, string, or nil, got ${printString(coll)}`,
|
|
47
|
+
{ collection: coll },
|
|
48
|
+
0
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
const items = toSeq(coll)
|
|
52
|
+
return items.length === 0 ? v.nil() : v.list(items)
|
|
53
|
+
})
|
|
54
|
+
.doc(
|
|
55
|
+
'Returns a sequence of the given collection or string. Strings yield a sequence of single-character strings.',
|
|
56
|
+
[['coll']]
|
|
57
|
+
),
|
|
58
|
+
|
|
59
|
+
first: v
|
|
60
|
+
.nativeFn('first', function firstImpl(collection: CljValue): CljValue {
|
|
61
|
+
if (collection.kind === 'nil') return v.nil()
|
|
62
|
+
if (is.lazySeq(collection)) {
|
|
63
|
+
const realized = realizeLazySeq(collection)
|
|
64
|
+
if (is.nil(realized)) return v.nil()
|
|
65
|
+
return firstImpl(realized)
|
|
66
|
+
}
|
|
67
|
+
if (is.cons(collection)) return collection.head
|
|
68
|
+
if (!is.seqable(collection)) {
|
|
69
|
+
throw EvaluationError.atArg(
|
|
70
|
+
'first expects a collection or string',
|
|
71
|
+
{ collection },
|
|
72
|
+
0
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
const entries = toSeq(collection)
|
|
76
|
+
return entries.length === 0 ? v.nil() : entries[0]
|
|
77
|
+
})
|
|
78
|
+
.doc('Returns the first element of the given collection or string.', [
|
|
79
|
+
['coll'],
|
|
80
|
+
]),
|
|
81
|
+
|
|
82
|
+
rest: v
|
|
83
|
+
.nativeFn('rest', function restImpl(collection: CljValue): CljValue {
|
|
84
|
+
if (collection.kind === 'nil') return v.list([])
|
|
85
|
+
if (is.lazySeq(collection)) {
|
|
86
|
+
const realized = realizeLazySeq(collection)
|
|
87
|
+
if (is.nil(realized)) return v.list([])
|
|
88
|
+
return restImpl(realized)
|
|
89
|
+
}
|
|
90
|
+
if (is.cons(collection)) return collection.tail
|
|
91
|
+
if (!is.seqable(collection)) {
|
|
92
|
+
throw EvaluationError.atArg(
|
|
93
|
+
'rest expects a collection or string',
|
|
94
|
+
{ collection },
|
|
95
|
+
0
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
if (is.list(collection)) {
|
|
99
|
+
if (collection.value.length === 0) {
|
|
100
|
+
return collection // return the empty list
|
|
101
|
+
}
|
|
102
|
+
return v.list(collection.value.slice(1))
|
|
103
|
+
}
|
|
104
|
+
if (is.vector(collection)) {
|
|
105
|
+
return v.vector(collection.value.slice(1))
|
|
106
|
+
}
|
|
107
|
+
if (is.map(collection)) {
|
|
108
|
+
if (collection.entries.length === 0) {
|
|
109
|
+
return collection // return the empty map
|
|
110
|
+
}
|
|
111
|
+
return v.map(collection.entries.slice(1))
|
|
112
|
+
}
|
|
113
|
+
if (collection.kind === 'string') {
|
|
114
|
+
const chars = toSeq(collection)
|
|
115
|
+
return v.list(chars.slice(1))
|
|
116
|
+
}
|
|
117
|
+
throw EvaluationError.atArg(
|
|
118
|
+
`rest expects a collection or string, got ${printString(collection)}`,
|
|
119
|
+
{ collection },
|
|
120
|
+
0
|
|
121
|
+
)
|
|
122
|
+
})
|
|
123
|
+
.doc(
|
|
124
|
+
'Returns a sequence of the given collection or string excluding the first element.',
|
|
125
|
+
[['coll']]
|
|
126
|
+
),
|
|
127
|
+
|
|
128
|
+
// conj dispatches across all collection types — it belongs here as the primary
|
|
129
|
+
// sequence construction operation (cons-cell prepend for lists, append for
|
|
130
|
+
// vectors, kv-pair insert for maps, deduplicating add for sets).
|
|
131
|
+
conj: v
|
|
132
|
+
.nativeFn(
|
|
133
|
+
'conj',
|
|
134
|
+
function conjImpl(collection: CljValue, ...args: CljValue[]) {
|
|
135
|
+
if (!collection) {
|
|
136
|
+
throw new EvaluationError(
|
|
137
|
+
'conj expects a collection as first argument',
|
|
138
|
+
{ collection }
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
if (args.length === 0) {
|
|
142
|
+
return collection
|
|
143
|
+
}
|
|
144
|
+
if (!is.collection(collection)) {
|
|
145
|
+
throw EvaluationError.atArg(
|
|
146
|
+
`conj expects a collection, got ${printString(collection)}`,
|
|
147
|
+
{ collection },
|
|
148
|
+
0
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
if (is.list(collection)) {
|
|
152
|
+
const newItems = [] as CljValue[]
|
|
153
|
+
for (let i = args.length - 1; i >= 0; i--) {
|
|
154
|
+
newItems.push(args[i])
|
|
155
|
+
}
|
|
156
|
+
return v.list([...newItems, ...collection.value])
|
|
157
|
+
}
|
|
158
|
+
if (is.vector(collection)) {
|
|
159
|
+
return v.vector([...collection.value, ...args])
|
|
160
|
+
}
|
|
161
|
+
if (is.map(collection)) {
|
|
162
|
+
// each argument should be a vector key-pair
|
|
163
|
+
const newEntries: [CljValue, CljValue][] = [...collection.entries]
|
|
164
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
165
|
+
const pair = args[i] as CljVector
|
|
166
|
+
// pair args start at index 1 in the call (collection is index 0)
|
|
167
|
+
const pairArgIndex = i + 1
|
|
168
|
+
|
|
169
|
+
if (pair.kind !== 'vector') {
|
|
170
|
+
throw EvaluationError.atArg(
|
|
171
|
+
`conj on maps expects each argument to be a vector key-pair for maps, got ${printString(pair)}`,
|
|
172
|
+
{ pair },
|
|
173
|
+
pairArgIndex
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
if (pair.value.length !== 2) {
|
|
177
|
+
throw EvaluationError.atArg(
|
|
178
|
+
`conj on maps expects each argument to be a vector key-pair for maps, got ${printString(pair)}`,
|
|
179
|
+
{ pair },
|
|
180
|
+
pairArgIndex
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
const key = pair.value[0]
|
|
184
|
+
const keyIdx = newEntries.findIndex(function findKeyEntry(entry) {
|
|
185
|
+
return is.equal(entry[0], key)
|
|
186
|
+
})
|
|
187
|
+
if (keyIdx === -1) {
|
|
188
|
+
newEntries.push([key, pair.value[1]])
|
|
189
|
+
} else {
|
|
190
|
+
newEntries[keyIdx] = [key, pair.value[1]]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return v.map([...newEntries])
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (is.set(collection)) {
|
|
197
|
+
const newValues = [...collection.values]
|
|
198
|
+
for (const v of args) {
|
|
199
|
+
if (!newValues.some((existing) => is.equal(existing, v))) {
|
|
200
|
+
newValues.push(v)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return v.set(newValues)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
throw new EvaluationError(
|
|
207
|
+
`unhandled collection type, got ${printString(collection)}`,
|
|
208
|
+
{ collection }
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
.doc(
|
|
213
|
+
'Appends args to the given collection. Lists append in reverse order to the head, vectors append to the tail, sets add unique elements.',
|
|
214
|
+
[['collection', '&', 'args']]
|
|
215
|
+
),
|
|
216
|
+
|
|
217
|
+
cons: v
|
|
218
|
+
.nativeFn('cons', function consImpl(x: CljValue, xs: CljValue) {
|
|
219
|
+
// When tail is lazy-seq or cons, create a cons cell to preserve laziness
|
|
220
|
+
if (is.lazySeq(xs) || is.cons(xs)) {
|
|
221
|
+
return v.cons(x, xs)
|
|
222
|
+
}
|
|
223
|
+
if (is.nil(xs)) {
|
|
224
|
+
return v.list([x])
|
|
225
|
+
}
|
|
226
|
+
if (!is.collection(xs)) {
|
|
227
|
+
throw EvaluationError.atArg(
|
|
228
|
+
`cons expects a collection as second argument, got ${printString(xs)}`,
|
|
229
|
+
{ xs },
|
|
230
|
+
1
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
if (is.map(xs) || is.set(xs)) {
|
|
234
|
+
throw EvaluationError.atArg(
|
|
235
|
+
'cons on maps and sets is not supported, use vectors instead',
|
|
236
|
+
{ xs },
|
|
237
|
+
1
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const wrap = is.list(xs) ? v.list : v.vector
|
|
242
|
+
const newItems = [x, ...xs.value]
|
|
243
|
+
|
|
244
|
+
return wrap(newItems)
|
|
245
|
+
})
|
|
246
|
+
.doc('Returns a new collection with x prepended to the head of xs.', [
|
|
247
|
+
['x', 'xs'],
|
|
248
|
+
]),
|
|
249
|
+
|
|
250
|
+
get: v
|
|
251
|
+
.nativeFn(
|
|
252
|
+
'get',
|
|
253
|
+
function getImpl(target: CljValue, key: CljValue, notFound?: CljValue) {
|
|
254
|
+
const defaultValue = notFound ?? v.nil()
|
|
255
|
+
|
|
256
|
+
switch (target.kind) {
|
|
257
|
+
case valueKeywords.map: {
|
|
258
|
+
const entries = target.entries
|
|
259
|
+
for (const [k, v] of entries) {
|
|
260
|
+
if (is.equal(k, key)) {
|
|
261
|
+
return v
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return defaultValue
|
|
265
|
+
}
|
|
266
|
+
case valueKeywords.vector: {
|
|
267
|
+
const values = target.value
|
|
268
|
+
if (key.kind !== 'number') {
|
|
269
|
+
throw new EvaluationError(
|
|
270
|
+
'get on vectors expects a 0-based index as parameter',
|
|
271
|
+
{ key }
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
if (key.value < 0 || key.value >= values.length) {
|
|
275
|
+
return defaultValue
|
|
276
|
+
}
|
|
277
|
+
return values[key.value]
|
|
278
|
+
}
|
|
279
|
+
default:
|
|
280
|
+
return defaultValue
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
)
|
|
284
|
+
.doc(
|
|
285
|
+
'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.',
|
|
286
|
+
[
|
|
287
|
+
['target', 'key'],
|
|
288
|
+
['target', 'key', 'not-found'],
|
|
289
|
+
]
|
|
290
|
+
),
|
|
291
|
+
|
|
292
|
+
nth: v
|
|
293
|
+
.nativeFn(
|
|
294
|
+
'nth',
|
|
295
|
+
function nthImpl(coll: CljValue, n: CljValue, notFound?: CljValue) {
|
|
296
|
+
if (coll === undefined || (!is.list(coll) && !is.vector(coll))) {
|
|
297
|
+
throw new EvaluationError(
|
|
298
|
+
`nth expects a list or vector${coll !== undefined ? `, got ${printString(coll)}` : ''}`,
|
|
299
|
+
{ coll }
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
if (n === undefined || n.kind !== 'number') {
|
|
303
|
+
throw new EvaluationError(
|
|
304
|
+
`nth expects a number index${n !== undefined ? `, got ${printString(n)}` : ''}`,
|
|
305
|
+
{ n }
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
const index = (n as CljNumber).value
|
|
309
|
+
const items = coll.value
|
|
310
|
+
if (index < 0 || index >= items.length) {
|
|
311
|
+
if (notFound !== undefined) return notFound
|
|
312
|
+
const err = new EvaluationError(
|
|
313
|
+
`nth index ${index} is out of bounds for collection of length ${items.length}`,
|
|
314
|
+
{ coll, n }
|
|
315
|
+
)
|
|
316
|
+
err.data = { argIndex: 1 }
|
|
317
|
+
throw err
|
|
318
|
+
}
|
|
319
|
+
return items[index]
|
|
320
|
+
}
|
|
321
|
+
)
|
|
322
|
+
.doc(
|
|
323
|
+
'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.',
|
|
324
|
+
[['coll', 'n', 'not-found']]
|
|
325
|
+
),
|
|
326
|
+
|
|
327
|
+
last: v
|
|
328
|
+
.nativeFn('last', function lastImpl(coll: CljValue) {
|
|
329
|
+
if (coll === undefined || (!is.list(coll) && !is.vector(coll))) {
|
|
330
|
+
throw new EvaluationError(
|
|
331
|
+
`last expects a list or vector${coll !== undefined ? `, got ${printString(coll)}` : ''}`,
|
|
332
|
+
{ coll }
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
const items = coll.value
|
|
336
|
+
return items.length === 0 ? v.nil() : items[items.length - 1]
|
|
337
|
+
})
|
|
338
|
+
.doc('Returns the last element of the given collection.', [['coll']]),
|
|
339
|
+
|
|
340
|
+
reverse: v
|
|
341
|
+
.nativeFn('reverse', function reverseImpl(coll: CljValue) {
|
|
342
|
+
if (coll === undefined || (!is.list(coll) && !is.vector(coll))) {
|
|
343
|
+
throw EvaluationError.atArg(
|
|
344
|
+
`reverse expects a list or vector${coll !== undefined ? `, got ${printString(coll)}` : ''}`,
|
|
345
|
+
{ coll },
|
|
346
|
+
0
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
return v.list([...coll.value].reverse())
|
|
350
|
+
})
|
|
351
|
+
.doc(
|
|
352
|
+
'Returns a new sequence with the elements of the given collection in reverse order.',
|
|
353
|
+
[['coll']]
|
|
354
|
+
),
|
|
355
|
+
|
|
356
|
+
'empty?': v
|
|
357
|
+
.nativeFn('empty?', function emptyPredImpl(coll: CljValue) {
|
|
358
|
+
if (coll === undefined) {
|
|
359
|
+
throw EvaluationError.atArg('empty? expects one argument', {}, 0)
|
|
360
|
+
}
|
|
361
|
+
// nil and empty string count as empty, matching Clojure semantics
|
|
362
|
+
if (coll.kind === 'nil') return v.boolean(true)
|
|
363
|
+
if (!is.seqable(coll)) {
|
|
364
|
+
throw EvaluationError.atArg(
|
|
365
|
+
`empty? expects a collection, string, or nil, got ${printString(coll)}`,
|
|
366
|
+
{ coll },
|
|
367
|
+
0
|
|
368
|
+
)
|
|
369
|
+
}
|
|
370
|
+
return v.boolean(toSeq(coll).length === 0)
|
|
371
|
+
})
|
|
372
|
+
.doc(
|
|
373
|
+
'Returns true if coll has no items. Accepts collections, strings, and nil.',
|
|
374
|
+
[['coll']]
|
|
375
|
+
),
|
|
376
|
+
|
|
377
|
+
'contains?': v
|
|
378
|
+
.nativeFn(
|
|
379
|
+
'contains?',
|
|
380
|
+
function containsPredImpl(coll: CljValue, key: CljValue) {
|
|
381
|
+
if (coll === undefined) {
|
|
382
|
+
throw EvaluationError.atArg(
|
|
383
|
+
'contains? expects a collection as first argument',
|
|
384
|
+
{},
|
|
385
|
+
0
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
if (key === undefined) {
|
|
389
|
+
throw EvaluationError.atArg(
|
|
390
|
+
'contains? expects a key as second argument',
|
|
391
|
+
{},
|
|
392
|
+
1
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
if (coll.kind === 'nil') return v.boolean(false)
|
|
396
|
+
if (is.map(coll)) {
|
|
397
|
+
return v.boolean(
|
|
398
|
+
coll.entries.some(function checkKeyMatch([k]) {
|
|
399
|
+
return is.equal(k, key)
|
|
400
|
+
})
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
if (is.vector(coll)) {
|
|
404
|
+
if (key.kind !== 'number') return v.boolean(false)
|
|
405
|
+
return v.boolean(key.value >= 0 && key.value < coll.value.length)
|
|
406
|
+
}
|
|
407
|
+
if (is.set(coll)) {
|
|
408
|
+
return v.boolean(coll.values.some((v) => is.equal(v, key)))
|
|
409
|
+
}
|
|
410
|
+
throw EvaluationError.atArg(
|
|
411
|
+
`contains? expects a map, set, vector, or nil, got ${printString(coll)}`,
|
|
412
|
+
{ coll },
|
|
413
|
+
0
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
)
|
|
417
|
+
.doc(
|
|
418
|
+
'Returns true if key is present in coll. For maps checks key existence (including keys with nil values). For vectors checks index bounds.',
|
|
419
|
+
[['coll', 'key']]
|
|
420
|
+
),
|
|
421
|
+
|
|
422
|
+
'repeat*': v
|
|
423
|
+
.nativeFn('repeat*', function repeatImpl(n: CljValue, x: CljValue) {
|
|
424
|
+
if (n === undefined || n.kind !== 'number') {
|
|
425
|
+
throw EvaluationError.atArg(
|
|
426
|
+
`repeat expects a number as first argument${n !== undefined ? `, got ${printString(n)}` : ''}`,
|
|
427
|
+
{ n },
|
|
428
|
+
0
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
return v.list(Array(n.value).fill(x))
|
|
432
|
+
})
|
|
433
|
+
.doc('Returns a finite sequence of n copies of x (native helper).', [
|
|
434
|
+
['n', 'x'],
|
|
435
|
+
]),
|
|
436
|
+
|
|
437
|
+
// ── Range ────────────────────────────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
'range*': v
|
|
440
|
+
.nativeFn('range*', function rangeImpl(...args: CljValue[]) {
|
|
441
|
+
if (args.length === 0 || args.length > 3) {
|
|
442
|
+
throw new EvaluationError(
|
|
443
|
+
'range expects 1, 2, or 3 arguments: (range n), (range start end), or (range start end step)',
|
|
444
|
+
{ args }
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
const badIdx = args.findIndex(function checkIsNumber(a) {
|
|
448
|
+
return a.kind !== 'number'
|
|
449
|
+
})
|
|
450
|
+
if (badIdx !== -1) {
|
|
451
|
+
throw EvaluationError.atArg(
|
|
452
|
+
'range expects number arguments',
|
|
453
|
+
{ args },
|
|
454
|
+
badIdx
|
|
455
|
+
)
|
|
456
|
+
}
|
|
457
|
+
let start: number
|
|
458
|
+
let end: number
|
|
459
|
+
let step: number
|
|
460
|
+
if (args.length === 1) {
|
|
461
|
+
start = 0
|
|
462
|
+
end = (args[0] as CljNumber).value
|
|
463
|
+
step = 1
|
|
464
|
+
} else if (args.length === 2) {
|
|
465
|
+
start = (args[0] as CljNumber).value
|
|
466
|
+
end = (args[1] as CljNumber).value
|
|
467
|
+
step = 1
|
|
468
|
+
} else {
|
|
469
|
+
start = (args[0] as CljNumber).value
|
|
470
|
+
end = (args[1] as CljNumber).value
|
|
471
|
+
step = (args[2] as CljNumber).value
|
|
472
|
+
}
|
|
473
|
+
if (step === 0) {
|
|
474
|
+
// step is always the last arg: index args.length - 1
|
|
475
|
+
throw EvaluationError.atArg(
|
|
476
|
+
'range step cannot be zero',
|
|
477
|
+
{ args },
|
|
478
|
+
args.length - 1
|
|
479
|
+
)
|
|
480
|
+
}
|
|
481
|
+
const result: CljValue[] = []
|
|
482
|
+
if (step > 0) {
|
|
483
|
+
for (let i = start; i < end; i += step) {
|
|
484
|
+
result.push(v.number(i))
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
for (let i = start; i > end; i += step) {
|
|
488
|
+
result.push(v.number(i))
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return v.list(result)
|
|
492
|
+
})
|
|
493
|
+
.doc('Returns a finite sequence of numbers (native helper).', [
|
|
494
|
+
['n'],
|
|
495
|
+
['start', 'end'],
|
|
496
|
+
['start', 'end', 'step'],
|
|
497
|
+
]),
|
|
498
|
+
|
|
499
|
+
count: v
|
|
500
|
+
.nativeFn('count', function countImpl(countable: CljValue) {
|
|
501
|
+
if (countable.kind === 'nil') return v.number(0)
|
|
502
|
+
if (is.lazySeq(countable) || is.cons(countable)) {
|
|
503
|
+
return v.number(toSeq(countable).length)
|
|
504
|
+
}
|
|
505
|
+
if (
|
|
506
|
+
!(
|
|
507
|
+
[
|
|
508
|
+
valueKeywords.list,
|
|
509
|
+
valueKeywords.vector,
|
|
510
|
+
valueKeywords.map,
|
|
511
|
+
valueKeywords.set,
|
|
512
|
+
valueKeywords.string,
|
|
513
|
+
] as string[]
|
|
514
|
+
).includes(countable.kind)
|
|
515
|
+
) {
|
|
516
|
+
throw EvaluationError.atArg(
|
|
517
|
+
`count expects a countable value, got ${printString(countable)}`,
|
|
518
|
+
{ countable },
|
|
519
|
+
0
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
switch (countable.kind) {
|
|
524
|
+
case valueKeywords.list:
|
|
525
|
+
return v.number((countable as CljList).value.length)
|
|
526
|
+
case valueKeywords.vector:
|
|
527
|
+
return v.number((countable as CljVector).value.length)
|
|
528
|
+
case valueKeywords.map:
|
|
529
|
+
return v.number((countable as CljMap).entries.length)
|
|
530
|
+
case valueKeywords.set:
|
|
531
|
+
return v.number((countable as CljSet).values.length)
|
|
532
|
+
case valueKeywords.string:
|
|
533
|
+
return v.number((countable as CljString).value.length)
|
|
534
|
+
default:
|
|
535
|
+
throw new EvaluationError(
|
|
536
|
+
`count expects a countable value, got ${printString(countable)}`,
|
|
537
|
+
{ countable }
|
|
538
|
+
)
|
|
539
|
+
}
|
|
540
|
+
})
|
|
541
|
+
.doc('Returns the number of elements in the given countable value.', [
|
|
542
|
+
['countable'],
|
|
543
|
+
]),
|
|
544
|
+
|
|
545
|
+
empty: v
|
|
546
|
+
.nativeFn('empty', function emptyImpl(coll: CljValue) {
|
|
547
|
+
if (coll === undefined || coll.kind === 'nil') return v.nil()
|
|
548
|
+
switch (coll.kind) {
|
|
549
|
+
case 'list':
|
|
550
|
+
return v.list([])
|
|
551
|
+
case 'vector':
|
|
552
|
+
return v.vector([])
|
|
553
|
+
case 'map':
|
|
554
|
+
return v.map([])
|
|
555
|
+
case 'set':
|
|
556
|
+
return v.set([])
|
|
557
|
+
default:
|
|
558
|
+
return v.nil()
|
|
559
|
+
}
|
|
560
|
+
})
|
|
561
|
+
.doc('Returns an empty collection of the same category as coll, or nil.', [
|
|
562
|
+
['coll'],
|
|
563
|
+
]),
|
|
564
|
+
}
|