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