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,565 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Async sub-evaluator for (async ...) blocks.
|
|
3
|
+
*
|
|
4
|
+
* ## Architecture invariant
|
|
5
|
+
*
|
|
6
|
+
* The SYNC evaluator (evaluate.ts / special-forms.ts / apply.ts) is the
|
|
7
|
+
* canonical path. This file is a thin async wrapper — it handles only the
|
|
8
|
+
* forms that can contain sub-expressions that must be awaited (CljPending
|
|
9
|
+
* values unwrapped via @). Everything else delegates to asyncCtx.syncCtx.
|
|
10
|
+
*
|
|
11
|
+
* Design rule: never add `await` to the sync evaluator. Even trivial forms
|
|
12
|
+
* like (+ 1 2) must remain zero-overhead synchronous. The async path pays
|
|
13
|
+
* the Promise overhead only for code inside (async ...) blocks.
|
|
14
|
+
*
|
|
15
|
+
* ## What needs an async handler vs. what delegates to sync
|
|
16
|
+
*
|
|
17
|
+
* Forms with their own async handler (can contain @ sub-expressions):
|
|
18
|
+
* - `if`, `do`, `let/let*`, `loop`, `recur`, `try`, `set!`
|
|
19
|
+
*
|
|
20
|
+
* Forms that are safe to delegate to syncCtx.evaluate:
|
|
21
|
+
* - `quote`, `var`, `fn/fn*`, `ns` — no sub-expression evaluation at the
|
|
22
|
+
* creation site; fn bodies are evaluated async only when the fn is called.
|
|
23
|
+
* - `defmacro`, `defmulti`, `defmethod`, `letfn`, `delay`, `lazy-seq`,
|
|
24
|
+
* `quasiquote` — create thunks or install definitions; content is
|
|
25
|
+
* evaluated lazily or later.
|
|
26
|
+
* - `binding` — V1 limitation: async-computed binding values are not
|
|
27
|
+
* supported. Bind the var before the async block and use set! if needed.
|
|
28
|
+
* - `.`, `js/new` — JS interop is sync; args are NOT awaited before the
|
|
29
|
+
* call (V1 limitation: deref @ pending values explicitly before the form).
|
|
30
|
+
* - `async` — nested async blocks create a new CljPending via the sync path.
|
|
31
|
+
* - `def` — throws with a helpful message; define vars outside async blocks.
|
|
32
|
+
*
|
|
33
|
+
* ## Revert instructions
|
|
34
|
+
*
|
|
35
|
+
* To remove the async feature: delete this file, remove the `async` case and
|
|
36
|
+
* its import in special-forms.ts, remove CljPending from types.ts, remove
|
|
37
|
+
* cljPending from factories.ts, remove the pending case from printer.ts,
|
|
38
|
+
* and delete async-fns.ts from stdlib.
|
|
39
|
+
*
|
|
40
|
+
* Design session: .regibyte/sessions/87-async-pending-design-and-plan.md
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import { is } from '../assertions'
|
|
44
|
+
import { extend } from '../env'
|
|
45
|
+
import { CljThrownSignal, EvaluationError } from '../errors'
|
|
46
|
+
import { cljNil } from '../factories'
|
|
47
|
+
import type { CljList, CljValue, Env, EvaluationContext } from '../types'
|
|
48
|
+
import { bindParams, RecurSignal, resolveArity } from './arity'
|
|
49
|
+
import { destructureBindings } from './destructure'
|
|
50
|
+
import {
|
|
51
|
+
matchesDiscriminator,
|
|
52
|
+
parseTryStructure,
|
|
53
|
+
validateBindingVector,
|
|
54
|
+
} from './form-parsers'
|
|
55
|
+
|
|
56
|
+
// ---- AsyncEvalCtx ----
|
|
57
|
+
// A parallel evaluation context where all dispatch methods are async.
|
|
58
|
+
// Carries the original syncCtx for delegation of sync-only forms.
|
|
59
|
+
|
|
60
|
+
type AsyncEvalCtx = {
|
|
61
|
+
evaluate: (expr: CljValue, env: Env) => Promise<CljValue>
|
|
62
|
+
evaluateForms: (forms: CljValue[], env: Env) => Promise<CljValue>
|
|
63
|
+
applyCallable: (
|
|
64
|
+
fn: CljValue,
|
|
65
|
+
args: CljValue[],
|
|
66
|
+
callEnv: Env
|
|
67
|
+
) => Promise<CljValue>
|
|
68
|
+
syncCtx: EvaluationContext
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function createAsyncEvalCtx(syncCtx: EvaluationContext): AsyncEvalCtx {
|
|
72
|
+
const asyncCtx: AsyncEvalCtx = {
|
|
73
|
+
syncCtx,
|
|
74
|
+
evaluate: (expr, env) => evaluateFormAsync(expr, env, asyncCtx),
|
|
75
|
+
evaluateForms: (forms, env) => evaluateFormsAsync(forms, env, asyncCtx),
|
|
76
|
+
applyCallable: (fn, args, callEnv) =>
|
|
77
|
+
applyCallableAsync(fn, args, callEnv, asyncCtx),
|
|
78
|
+
}
|
|
79
|
+
return asyncCtx
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---- Main async dispatch ----
|
|
83
|
+
|
|
84
|
+
async function evaluateFormAsync(
|
|
85
|
+
expr: CljValue,
|
|
86
|
+
env: Env,
|
|
87
|
+
asyncCtx: AsyncEvalCtx
|
|
88
|
+
): Promise<CljValue> {
|
|
89
|
+
// Self-evaluating forms and symbols: delegate directly to sync evaluator.
|
|
90
|
+
// No async needed — these don't contain sub-expressions that could be pending.
|
|
91
|
+
switch (expr.kind) {
|
|
92
|
+
case 'number':
|
|
93
|
+
case 'string':
|
|
94
|
+
case 'boolean':
|
|
95
|
+
case 'keyword':
|
|
96
|
+
case 'nil':
|
|
97
|
+
case 'symbol':
|
|
98
|
+
case 'function':
|
|
99
|
+
case 'native-function':
|
|
100
|
+
case 'macro':
|
|
101
|
+
case 'multi-method':
|
|
102
|
+
case 'atom':
|
|
103
|
+
case 'reduced':
|
|
104
|
+
case 'volatile':
|
|
105
|
+
case 'regex':
|
|
106
|
+
case 'var':
|
|
107
|
+
case 'delay':
|
|
108
|
+
case 'lazy-seq':
|
|
109
|
+
case 'cons':
|
|
110
|
+
case 'namespace':
|
|
111
|
+
case 'pending':
|
|
112
|
+
return asyncCtx.syncCtx.evaluate(expr, env)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (expr.kind === 'vector') {
|
|
116
|
+
const elements: CljValue[] = []
|
|
117
|
+
for (const el of expr.value) {
|
|
118
|
+
elements.push(await evaluateFormAsync(el, env, asyncCtx))
|
|
119
|
+
}
|
|
120
|
+
return { kind: 'vector', value: elements }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (expr.kind === 'map') {
|
|
124
|
+
const entries: [CljValue, CljValue][] = []
|
|
125
|
+
for (const [k, v] of expr.entries) {
|
|
126
|
+
const ek = await evaluateFormAsync(k, env, asyncCtx)
|
|
127
|
+
const ev = await evaluateFormAsync(v, env, asyncCtx)
|
|
128
|
+
entries.push([ek, ev])
|
|
129
|
+
}
|
|
130
|
+
return { kind: 'map', entries }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (expr.kind === 'set') {
|
|
134
|
+
const elements: CljValue[] = []
|
|
135
|
+
for (const el of expr.values) {
|
|
136
|
+
elements.push(await evaluateFormAsync(el, env, asyncCtx))
|
|
137
|
+
}
|
|
138
|
+
return { kind: 'set', values: elements }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (expr.kind === 'list') {
|
|
142
|
+
return evaluateListAsync(expr, env, asyncCtx)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Unreachable — all CljValue kinds are handled above
|
|
146
|
+
return asyncCtx.syncCtx.evaluate(expr, env)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function evaluateFormsAsync(
|
|
150
|
+
forms: CljValue[],
|
|
151
|
+
env: Env,
|
|
152
|
+
asyncCtx: AsyncEvalCtx
|
|
153
|
+
): Promise<CljValue> {
|
|
154
|
+
let result: CljValue = cljNil()
|
|
155
|
+
for (const form of forms) {
|
|
156
|
+
const expanded = asyncCtx.syncCtx.expandAll(form, env)
|
|
157
|
+
result = await evaluateFormAsync(expanded, env, asyncCtx)
|
|
158
|
+
}
|
|
159
|
+
return result
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---- List evaluation ----
|
|
163
|
+
|
|
164
|
+
// Must mirror specialFormKeywords in special-forms.ts.
|
|
165
|
+
// If a new special form is added to the sync dispatcher and omitted here,
|
|
166
|
+
// (async ...) blocks will silently treat it as a function call at runtime.
|
|
167
|
+
// Add new forms here and delegate to syncCtx if no async-aware handling needed.
|
|
168
|
+
const ASYNC_SPECIAL_FORMS = new Set([
|
|
169
|
+
'quote',
|
|
170
|
+
'def',
|
|
171
|
+
'if',
|
|
172
|
+
'do',
|
|
173
|
+
'let',
|
|
174
|
+
'let*',
|
|
175
|
+
'fn',
|
|
176
|
+
'fn*',
|
|
177
|
+
'loop',
|
|
178
|
+
'recur',
|
|
179
|
+
'binding',
|
|
180
|
+
'set!',
|
|
181
|
+
'try',
|
|
182
|
+
'var',
|
|
183
|
+
'defmacro',
|
|
184
|
+
'defmulti',
|
|
185
|
+
'defmethod',
|
|
186
|
+
'letfn',
|
|
187
|
+
'quasiquote',
|
|
188
|
+
'delay',
|
|
189
|
+
'lazy-seq',
|
|
190
|
+
'ns',
|
|
191
|
+
'async',
|
|
192
|
+
// JS interop — delegate to sync; args inside (async ...) are not awaited
|
|
193
|
+
// before the interop call (V1 limitation: use @ explicitly before the form).
|
|
194
|
+
'.',
|
|
195
|
+
'js/new',
|
|
196
|
+
])
|
|
197
|
+
|
|
198
|
+
async function evaluateListAsync(
|
|
199
|
+
list: { kind: 'list'; value: CljValue[] },
|
|
200
|
+
env: Env,
|
|
201
|
+
asyncCtx: AsyncEvalCtx
|
|
202
|
+
): Promise<CljValue> {
|
|
203
|
+
if (list.value.length === 0) return list
|
|
204
|
+
|
|
205
|
+
const first = list.value[0]
|
|
206
|
+
|
|
207
|
+
// Special forms: dispatch to async-aware handlers for the ones that need it,
|
|
208
|
+
// delegate to sync ctx for safe ones (quote, var, fn, ns).
|
|
209
|
+
if (first.kind === 'symbol' && ASYNC_SPECIAL_FORMS.has(first.name)) {
|
|
210
|
+
return evaluateSpecialFormAsync(first.name, list, env, asyncCtx)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Evaluate the head (function position)
|
|
214
|
+
const fn = await evaluateFormAsync(first, env, asyncCtx)
|
|
215
|
+
|
|
216
|
+
// Deref interception: @x expands to (deref x).
|
|
217
|
+
// If the dereffed value is CljPending, await it here — this is the heart of async @.
|
|
218
|
+
if (is.aFunction(fn) && fn.name === 'deref' && list.value.length === 2) {
|
|
219
|
+
const val = await evaluateFormAsync(list.value[1], env, asyncCtx)
|
|
220
|
+
if (val.kind === 'pending') {
|
|
221
|
+
return val.promise // await the pending value
|
|
222
|
+
}
|
|
223
|
+
// Not pending: normal sync deref
|
|
224
|
+
return asyncCtx.syncCtx.applyCallable(fn, [val], env)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Evaluate args sequentially (left-to-right, preserving side-effect order)
|
|
228
|
+
const args: CljValue[] = []
|
|
229
|
+
for (const arg of list.value.slice(1)) {
|
|
230
|
+
args.push(await evaluateFormAsync(arg, env, asyncCtx))
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return applyCallableAsync(fn, args, env, asyncCtx)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ---- Special form async handlers ----
|
|
237
|
+
|
|
238
|
+
async function evaluateSpecialFormAsync(
|
|
239
|
+
name: string,
|
|
240
|
+
list: { kind: 'list'; value: CljValue[] },
|
|
241
|
+
env: Env,
|
|
242
|
+
asyncCtx: AsyncEvalCtx
|
|
243
|
+
): Promise<CljValue> {
|
|
244
|
+
switch (name) {
|
|
245
|
+
// Safe to delegate to sync: no sub-evaluation of async expressions
|
|
246
|
+
case 'quote':
|
|
247
|
+
case 'var':
|
|
248
|
+
case 'ns':
|
|
249
|
+
// fn/fn*: function CREATION is sync — the body is evaluated async only when called
|
|
250
|
+
case 'fn':
|
|
251
|
+
case 'fn*':
|
|
252
|
+
return asyncCtx.syncCtx.evaluate(list, env)
|
|
253
|
+
|
|
254
|
+
// recur: evaluate args async, then throw RecurSignal
|
|
255
|
+
case 'recur': {
|
|
256
|
+
const args: CljValue[] = []
|
|
257
|
+
for (const arg of list.value.slice(1)) {
|
|
258
|
+
args.push(await evaluateFormAsync(arg, env, asyncCtx))
|
|
259
|
+
}
|
|
260
|
+
throw new RecurSignal(args)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// do: sequential evaluation
|
|
264
|
+
case 'do':
|
|
265
|
+
return evaluateFormsAsync(list.value.slice(1), env, asyncCtx)
|
|
266
|
+
|
|
267
|
+
// def: V1 does not support def inside (async ...) — unusual use case
|
|
268
|
+
case 'def':
|
|
269
|
+
throw new EvaluationError(
|
|
270
|
+
'def inside (async ...) is not supported. Define vars outside the async block.',
|
|
271
|
+
{ list, env }
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
// if: evaluate condition, then selected branch
|
|
275
|
+
case 'if': {
|
|
276
|
+
const condition = await evaluateFormAsync(list.value[1], env, asyncCtx)
|
|
277
|
+
const isTruthy =
|
|
278
|
+
condition.kind !== 'nil' &&
|
|
279
|
+
!(condition.kind === 'boolean' && !condition.value)
|
|
280
|
+
if (isTruthy) {
|
|
281
|
+
return evaluateFormAsync(list.value[2], env, asyncCtx)
|
|
282
|
+
}
|
|
283
|
+
return list.value[3] !== undefined
|
|
284
|
+
? evaluateFormAsync(list.value[3], env, asyncCtx)
|
|
285
|
+
: cljNil()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// let/let*: sequential bindings (value eval is async, pattern binding is sync)
|
|
289
|
+
case 'let':
|
|
290
|
+
case 'let*':
|
|
291
|
+
return evaluateLetAsync(list, env, asyncCtx)
|
|
292
|
+
|
|
293
|
+
// loop: like let but supports recur
|
|
294
|
+
case 'loop':
|
|
295
|
+
return evaluateLoopAsync(list, env, asyncCtx)
|
|
296
|
+
|
|
297
|
+
// binding: evaluate binding values async, then body
|
|
298
|
+
case 'binding':
|
|
299
|
+
return evaluateBindingAsync(list, env, asyncCtx)
|
|
300
|
+
|
|
301
|
+
// try: evaluate body async, handle catch/finally async
|
|
302
|
+
case 'try':
|
|
303
|
+
return evaluateTryAsync(list, env, asyncCtx)
|
|
304
|
+
|
|
305
|
+
// set!: evaluate new value async, then call sync set! logic
|
|
306
|
+
case 'set!': {
|
|
307
|
+
// Re-delegate to sync ctx with the value already evaluated.
|
|
308
|
+
// The sync set! handler will re-evaluate list.value[2] as a form —
|
|
309
|
+
// that won't work with an already-evaluated value. So we call the sync
|
|
310
|
+
// evaluator on a reconstructed list with the value quoted.
|
|
311
|
+
const newVal = await evaluateFormAsync(list.value[2], env, asyncCtx)
|
|
312
|
+
const quotedVal: CljValue = {
|
|
313
|
+
kind: 'list',
|
|
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
|
+
}
|
|
320
|
+
return asyncCtx.syncCtx.evaluate(newList, env)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// quasiquote: delegate to sync — expansion is structural, no async sub-eval
|
|
324
|
+
case 'quasiquote':
|
|
325
|
+
return asyncCtx.syncCtx.evaluate(list, env)
|
|
326
|
+
|
|
327
|
+
// defmacro, defmulti, defmethod, letfn, delay, lazy-seq, async:
|
|
328
|
+
// delegate to sync evaluator (they don't have async sub-expressions in their
|
|
329
|
+
// definition forms, or they create thunks that are evaluated sync later)
|
|
330
|
+
default:
|
|
331
|
+
return asyncCtx.syncCtx.evaluate(list, env)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async function evaluateLetAsync(
|
|
336
|
+
list: { kind: 'list'; value: CljValue[] },
|
|
337
|
+
env: Env,
|
|
338
|
+
asyncCtx: AsyncEvalCtx
|
|
339
|
+
): Promise<CljValue> {
|
|
340
|
+
const bindings = list.value[1]
|
|
341
|
+
validateBindingVector(bindings, 'let', env)
|
|
342
|
+
|
|
343
|
+
let currentEnv = env
|
|
344
|
+
const pairs = bindings.value
|
|
345
|
+
for (let i = 0; i < pairs.length; i += 2) {
|
|
346
|
+
const pattern = pairs[i]
|
|
347
|
+
const valueForm = pairs[i + 1]
|
|
348
|
+
// Value evaluation is async; pattern binding is purely structural (sync).
|
|
349
|
+
const value = await evaluateFormAsync(valueForm, currentEnv, asyncCtx)
|
|
350
|
+
const boundPairs = destructureBindings(
|
|
351
|
+
pattern,
|
|
352
|
+
value,
|
|
353
|
+
asyncCtx.syncCtx,
|
|
354
|
+
currentEnv
|
|
355
|
+
)
|
|
356
|
+
currentEnv = extend(
|
|
357
|
+
boundPairs.map(([n]) => n),
|
|
358
|
+
boundPairs.map(([, v]) => v),
|
|
359
|
+
currentEnv
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
return evaluateFormsAsync(list.value.slice(2), currentEnv, asyncCtx)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function evaluateLoopAsync(
|
|
366
|
+
list: { kind: 'list'; value: CljValue[] },
|
|
367
|
+
env: Env,
|
|
368
|
+
asyncCtx: AsyncEvalCtx
|
|
369
|
+
): Promise<CljValue> {
|
|
370
|
+
const loopBindings = list.value[1]
|
|
371
|
+
validateBindingVector(loopBindings, 'loop', env)
|
|
372
|
+
|
|
373
|
+
const loopBody = list.value.slice(2)
|
|
374
|
+
|
|
375
|
+
// Collect patterns and evaluate initial values async
|
|
376
|
+
const patterns: CljValue[] = []
|
|
377
|
+
let currentValues: CljValue[] = []
|
|
378
|
+
let initEnv = env
|
|
379
|
+
for (let i = 0; i < loopBindings.value.length; i += 2) {
|
|
380
|
+
const pattern = loopBindings.value[i]
|
|
381
|
+
const value = await evaluateFormAsync(
|
|
382
|
+
loopBindings.value[i + 1],
|
|
383
|
+
initEnv,
|
|
384
|
+
asyncCtx
|
|
385
|
+
)
|
|
386
|
+
patterns.push(pattern)
|
|
387
|
+
currentValues.push(value)
|
|
388
|
+
const boundPairs = destructureBindings(
|
|
389
|
+
pattern,
|
|
390
|
+
value,
|
|
391
|
+
asyncCtx.syncCtx,
|
|
392
|
+
initEnv
|
|
393
|
+
)
|
|
394
|
+
initEnv = extend(
|
|
395
|
+
boundPairs.map(([n]) => n),
|
|
396
|
+
boundPairs.map(([, v]) => v),
|
|
397
|
+
initEnv
|
|
398
|
+
)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
while (true) {
|
|
402
|
+
let loopEnv = env
|
|
403
|
+
for (let i = 0; i < patterns.length; i++) {
|
|
404
|
+
const boundPairs = destructureBindings(
|
|
405
|
+
patterns[i],
|
|
406
|
+
currentValues[i],
|
|
407
|
+
asyncCtx.syncCtx,
|
|
408
|
+
loopEnv
|
|
409
|
+
)
|
|
410
|
+
loopEnv = extend(
|
|
411
|
+
boundPairs.map(([n]) => n),
|
|
412
|
+
boundPairs.map(([, v]) => v),
|
|
413
|
+
loopEnv
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
try {
|
|
417
|
+
return await evaluateFormsAsync(loopBody, loopEnv, asyncCtx)
|
|
418
|
+
} catch (e) {
|
|
419
|
+
if (e instanceof RecurSignal) {
|
|
420
|
+
if (e.args.length !== patterns.length) {
|
|
421
|
+
throw new EvaluationError(
|
|
422
|
+
`recur expects ${patterns.length} arguments but got ${e.args.length}`,
|
|
423
|
+
{ list, env }
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
currentValues = e.args
|
|
427
|
+
continue
|
|
428
|
+
}
|
|
429
|
+
throw e
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function evaluateBindingAsync(
|
|
435
|
+
list: { kind: 'list'; value: CljValue[] },
|
|
436
|
+
env: Env,
|
|
437
|
+
asyncCtx: AsyncEvalCtx
|
|
438
|
+
): Promise<CljValue> {
|
|
439
|
+
// Delegate to sync evaluator's binding form, but evaluate the binding values async.
|
|
440
|
+
// The sync binding handler re-evaluates the binding forms — we need to pre-evaluate
|
|
441
|
+
// them and pass them quoted. For V1, delegate to sync (binding values are usually
|
|
442
|
+
// simple expressions; async binding values are an edge case).
|
|
443
|
+
// This means dynamic bindings with async-computed values don't work in V1.
|
|
444
|
+
// They can be assigned with set! after the binding is established.
|
|
445
|
+
return asyncCtx.syncCtx.evaluate(list, env)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function evaluateTryAsync(
|
|
449
|
+
list: { kind: 'list'; value: CljValue[] },
|
|
450
|
+
env: Env,
|
|
451
|
+
asyncCtx: AsyncEvalCtx
|
|
452
|
+
): Promise<CljValue> {
|
|
453
|
+
// parseTryStructure validates catch/finally structure (binding symbol, ordering).
|
|
454
|
+
// matchesDiscriminator uses asyncCtx.syncCtx — discriminator evaluation is always
|
|
455
|
+
// synchronous (keyword checks, predicate calls on already-resolved values).
|
|
456
|
+
const { bodyForms, catchClauses, finallyForms } = parseTryStructure(
|
|
457
|
+
list as CljList,
|
|
458
|
+
env
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
let result: CljValue = cljNil()
|
|
462
|
+
let pendingThrow: unknown = null
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
result = await evaluateFormsAsync(bodyForms, env, asyncCtx)
|
|
466
|
+
} catch (e) {
|
|
467
|
+
if (e instanceof RecurSignal) throw e
|
|
468
|
+
|
|
469
|
+
let thrownValue: CljValue
|
|
470
|
+
if (e instanceof CljThrownSignal) {
|
|
471
|
+
thrownValue = e.value
|
|
472
|
+
} else if (e instanceof EvaluationError) {
|
|
473
|
+
thrownValue = {
|
|
474
|
+
kind: 'map',
|
|
475
|
+
entries: [
|
|
476
|
+
[
|
|
477
|
+
{ kind: 'keyword', name: ':type' },
|
|
478
|
+
{ kind: 'keyword', name: ':error/runtime' },
|
|
479
|
+
],
|
|
480
|
+
[
|
|
481
|
+
{ kind: 'keyword', name: ':message' },
|
|
482
|
+
{ kind: 'string', value: (e as Error).message },
|
|
483
|
+
],
|
|
484
|
+
],
|
|
485
|
+
}
|
|
486
|
+
} else {
|
|
487
|
+
throw e
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
let handled = false
|
|
491
|
+
for (const clause of catchClauses) {
|
|
492
|
+
if (
|
|
493
|
+
matchesDiscriminator(
|
|
494
|
+
clause.discriminator,
|
|
495
|
+
thrownValue,
|
|
496
|
+
env,
|
|
497
|
+
asyncCtx.syncCtx
|
|
498
|
+
)
|
|
499
|
+
) {
|
|
500
|
+
const catchEnv = extend([clause.binding], [thrownValue], env)
|
|
501
|
+
result = await evaluateFormsAsync(clause.body, catchEnv, asyncCtx)
|
|
502
|
+
handled = true
|
|
503
|
+
break
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (!handled) {
|
|
508
|
+
pendingThrow = e
|
|
509
|
+
}
|
|
510
|
+
} finally {
|
|
511
|
+
if (finallyForms) {
|
|
512
|
+
await evaluateFormsAsync(finallyForms, env, asyncCtx)
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (pendingThrow !== null) throw pendingThrow
|
|
517
|
+
return result
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ---- applyCallable (async) ----
|
|
521
|
+
|
|
522
|
+
async function applyCallableAsync(
|
|
523
|
+
fn: CljValue,
|
|
524
|
+
args: CljValue[],
|
|
525
|
+
callEnv: Env,
|
|
526
|
+
asyncCtx: AsyncEvalCtx
|
|
527
|
+
): Promise<CljValue> {
|
|
528
|
+
if (fn.kind === 'native-function') {
|
|
529
|
+
// Native functions are sync — call as-is.
|
|
530
|
+
// We do NOT auto-await CljPending results here: the caller is responsible
|
|
531
|
+
// for awaiting (via @ deref interception or then/catch). This preserves
|
|
532
|
+
// the ability to pass pending values around as first-class values.
|
|
533
|
+
if (fn.fnWithContext) {
|
|
534
|
+
return fn.fnWithContext(asyncCtx.syncCtx, callEnv, ...args)
|
|
535
|
+
}
|
|
536
|
+
return fn.fn(...args)
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (fn.kind === 'function') {
|
|
540
|
+
const arity = resolveArity(fn.arities, args.length)
|
|
541
|
+
let currentArgs = args
|
|
542
|
+
while (true) {
|
|
543
|
+
const localEnv = bindParams(
|
|
544
|
+
arity.params,
|
|
545
|
+
arity.restParam,
|
|
546
|
+
currentArgs,
|
|
547
|
+
fn.env,
|
|
548
|
+
asyncCtx.syncCtx, // bindParams uses syncCtx only for structural destructuring
|
|
549
|
+
callEnv
|
|
550
|
+
)
|
|
551
|
+
try {
|
|
552
|
+
return await evaluateFormsAsync(arity.body, localEnv, asyncCtx)
|
|
553
|
+
} catch (e) {
|
|
554
|
+
if (e instanceof RecurSignal) {
|
|
555
|
+
currentArgs = e.args
|
|
556
|
+
continue
|
|
557
|
+
}
|
|
558
|
+
throw e
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// keyword, map, and other callables: delegate to sync
|
|
564
|
+
return asyncCtx.syncCtx.applyCallable(fn, args, callEnv)
|
|
565
|
+
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { v } from '../factories'
|
|
2
|
+
import { is } from '../assertions'
|
|
3
|
+
import type {
|
|
4
|
+
CljMap,
|
|
5
|
+
CljSet,
|
|
6
|
+
CljValue,
|
|
7
|
+
CljVector,
|
|
8
|
+
EvaluationContext,
|
|
9
|
+
} from '../types'
|
|
3
10
|
import type { Env } from '../types'
|
|
4
11
|
|
|
5
12
|
export function evaluateVector(
|
|
@@ -8,8 +15,24 @@ export function evaluateVector(
|
|
|
8
15
|
ctx: EvaluationContext
|
|
9
16
|
): CljValue {
|
|
10
17
|
const evaluated = vector.value.map((v) => ctx.evaluate(v, env))
|
|
11
|
-
if (vector.meta)
|
|
12
|
-
|
|
18
|
+
if (vector.meta)
|
|
19
|
+
return { kind: 'vector' as const, value: evaluated, meta: vector.meta }
|
|
20
|
+
return v.vector(evaluated)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function evaluateSet(
|
|
24
|
+
set: CljSet,
|
|
25
|
+
env: Env,
|
|
26
|
+
ctx: EvaluationContext
|
|
27
|
+
): CljValue {
|
|
28
|
+
const evaluated: CljValue[] = []
|
|
29
|
+
for (const v of set.values) {
|
|
30
|
+
const ev = ctx.evaluate(v, env)
|
|
31
|
+
if (!evaluated.some((existing) => is.equal(existing, ev))) {
|
|
32
|
+
evaluated.push(ev)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return v.set(evaluated)
|
|
13
36
|
}
|
|
14
37
|
|
|
15
38
|
export function evaluateMap(
|
|
@@ -24,5 +47,5 @@ export function evaluateMap(
|
|
|
24
47
|
entries.push([evaluatedKey, evaluatedValue])
|
|
25
48
|
}
|
|
26
49
|
if (map.meta) return { kind: 'map' as const, entries, meta: map.meta }
|
|
27
|
-
return
|
|
50
|
+
return v.map(entries)
|
|
28
51
|
}
|