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.
- package/conjure +0 -0
- package/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/assets/editor.worker-CdQrwHl8.js +26 -0
- package/dist/assets/main-A7ZMId9A.css +1 -0
- package/dist/assets/main-CmI-7epE.js +3137 -0
- package/dist/index.html +195 -0
- package/dist/vite.svg +1 -0
- package/package.json +68 -0
- package/src/bin/__fixtures__/smoke/app/lib.clj +4 -0
- package/src/bin/__fixtures__/smoke/app/main.clj +4 -0
- package/src/bin/__fixtures__/smoke/repl-smoke.ts +12 -0
- package/src/bin/bencode.ts +205 -0
- package/src/bin/cli.ts +250 -0
- package/src/bin/nrepl-utils.ts +59 -0
- package/src/bin/nrepl.ts +393 -0
- package/src/bin/version.ts +4 -0
- package/src/clojure/core.clj +620 -0
- package/src/clojure/core.clj.d.ts +189 -0
- package/src/clojure/demo/math.clj +16 -0
- package/src/clojure/demo/math.clj.d.ts +4 -0
- package/src/clojure/demo.clj +42 -0
- package/src/clojure/demo.clj.d.ts +0 -0
- package/src/clojure/generated/builtin-namespace-registry.ts +14 -0
- package/src/clojure/generated/clojure-core-source.ts +623 -0
- package/src/clojure/generated/clojure-string-source.ts +196 -0
- package/src/clojure/string.clj +192 -0
- package/src/clojure/string.clj.d.ts +25 -0
- package/src/core/assertions.ts +134 -0
- package/src/core/conversions.ts +108 -0
- package/src/core/core-env.ts +58 -0
- package/src/core/env.ts +78 -0
- package/src/core/errors.ts +39 -0
- package/src/core/evaluator/apply.ts +114 -0
- package/src/core/evaluator/arity.ts +174 -0
- package/src/core/evaluator/collections.ts +25 -0
- package/src/core/evaluator/destructure.ts +247 -0
- package/src/core/evaluator/dispatch.ts +73 -0
- package/src/core/evaluator/evaluate.ts +100 -0
- package/src/core/evaluator/expand.ts +79 -0
- package/src/core/evaluator/index.ts +72 -0
- package/src/core/evaluator/quasiquote.ts +87 -0
- package/src/core/evaluator/recur-check.ts +109 -0
- package/src/core/evaluator/special-forms.ts +517 -0
- package/src/core/factories.ts +155 -0
- package/src/core/gensym.ts +9 -0
- package/src/core/index.ts +76 -0
- package/src/core/positions.ts +38 -0
- package/src/core/printer.ts +86 -0
- package/src/core/reader.ts +559 -0
- package/src/core/scanners.ts +93 -0
- package/src/core/session.ts +610 -0
- package/src/core/stdlib/arithmetic.ts +361 -0
- package/src/core/stdlib/atoms.ts +88 -0
- package/src/core/stdlib/collections.ts +784 -0
- package/src/core/stdlib/errors.ts +81 -0
- package/src/core/stdlib/hof.ts +307 -0
- package/src/core/stdlib/meta.ts +48 -0
- package/src/core/stdlib/predicates.ts +240 -0
- package/src/core/stdlib/regex.ts +238 -0
- package/src/core/stdlib/strings.ts +311 -0
- package/src/core/stdlib/transducers.ts +256 -0
- package/src/core/stdlib/utils.ts +287 -0
- package/src/core/tokenizer.ts +437 -0
- package/src/core/transformations.ts +75 -0
- package/src/core/types.ts +258 -0
- package/src/main.ts +1 -0
- package/src/monaco-esm.d.ts +7 -0
- package/src/playground/clojure-tokens.ts +67 -0
- package/src/playground/editor.worker.ts +5 -0
- package/src/playground/find-form.ts +138 -0
- package/src/playground/playground.ts +342 -0
- package/src/playground/samples/00-welcome.clj +385 -0
- package/src/playground/samples/01-collections.clj +191 -0
- package/src/playground/samples/02-higher-order-functions.clj +215 -0
- package/src/playground/samples/03-destructuring.clj +194 -0
- package/src/playground/samples/04-strings-and-regex.clj +202 -0
- package/src/playground/samples/05-error-handling.clj +212 -0
- package/src/repl/repl.ts +116 -0
- package/tsconfig.build.json +10 -0
- 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
|
+
}
|