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
|
@@ -1,29 +1,18 @@
|
|
|
1
|
+
import { is } from '../assertions'
|
|
1
2
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
isTruthy,
|
|
11
|
-
isVector,
|
|
12
|
-
} from '../assertions'
|
|
13
|
-
import { define, extend, getNamespaceEnv, getRootEnv, internVar, lookup, lookupVar } from '../env'
|
|
3
|
+
define,
|
|
4
|
+
extend,
|
|
5
|
+
getNamespaceEnv,
|
|
6
|
+
internVar,
|
|
7
|
+
lookup,
|
|
8
|
+
lookupVar,
|
|
9
|
+
makeEnv,
|
|
10
|
+
} from '../env'
|
|
14
11
|
import { CljThrownSignal, EvaluationError } from '../errors'
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
cljMultiArityMacro,
|
|
20
|
-
cljMultiMethod,
|
|
21
|
-
cljNativeFunction,
|
|
22
|
-
cljNil,
|
|
23
|
-
cljNumber,
|
|
24
|
-
cljString,
|
|
25
|
-
cljVar,
|
|
26
|
-
} from '../factories'
|
|
12
|
+
import { v } from '../factories'
|
|
13
|
+
// --- ASYNC (experimental) ---
|
|
14
|
+
import { createAsyncEvalCtx } from './async-evaluator'
|
|
15
|
+
// --- END ASYNC ---
|
|
27
16
|
import { getLineCol, getPos } from '../positions'
|
|
28
17
|
import type {
|
|
29
18
|
CljFunction,
|
|
@@ -38,13 +27,24 @@ import type {
|
|
|
38
27
|
} from '../types'
|
|
39
28
|
import { parseArities, RecurSignal } from './arity'
|
|
40
29
|
import { destructureBindings } from './destructure'
|
|
30
|
+
import { evaluateDot, evaluateNew } from './js-interop'
|
|
41
31
|
import { evaluateQuasiquote } from './quasiquote'
|
|
42
32
|
import { assertRecurInTailPosition } from './recur-check'
|
|
33
|
+
import {
|
|
34
|
+
matchesDiscriminator,
|
|
35
|
+
parseTryStructure,
|
|
36
|
+
validateBindingVector,
|
|
37
|
+
} from './form-parsers'
|
|
43
38
|
|
|
44
39
|
function hasDynamicMeta(meta: CljMap | undefined): boolean {
|
|
45
40
|
if (!meta) return false
|
|
46
41
|
for (const [k, v] of meta.entries) {
|
|
47
|
-
if (
|
|
42
|
+
if (
|
|
43
|
+
is.keyword(k) &&
|
|
44
|
+
k.name === ':dynamic' &&
|
|
45
|
+
is.boolean(v) &&
|
|
46
|
+
v.value === true
|
|
47
|
+
) {
|
|
48
48
|
return true
|
|
49
49
|
}
|
|
50
50
|
}
|
|
@@ -69,14 +69,24 @@ export const specialFormKeywords = {
|
|
|
69
69
|
var: 'var',
|
|
70
70
|
binding: 'binding',
|
|
71
71
|
'set!': 'set!',
|
|
72
|
+
letfn: 'letfn',
|
|
73
|
+
delay: 'delay',
|
|
74
|
+
'lazy-seq': 'lazy-seq',
|
|
75
|
+
// --- ASYNC (experimental) ---
|
|
76
|
+
async: 'async',
|
|
77
|
+
// --- END ASYNC ---
|
|
78
|
+
// --- JS INTEROP ---
|
|
79
|
+
'.': '.',
|
|
80
|
+
'js/new': 'js/new',
|
|
81
|
+
// --- END JS INTEROP ---
|
|
72
82
|
} as const
|
|
73
83
|
|
|
74
84
|
function keywordToDispatchFn(kw: CljKeyword): CljNativeFunction {
|
|
75
|
-
return
|
|
85
|
+
return v.nativeFn(`kw:${kw.name}`, (...args: CljValue[]) => {
|
|
76
86
|
const target = args[0]
|
|
77
|
-
if (!
|
|
78
|
-
const entry = target.entries.find(([k]) =>
|
|
79
|
-
return entry ? entry[1] :
|
|
87
|
+
if (!is.map(target)) return v.nil()
|
|
88
|
+
const entry = target.entries.find(([k]) => is.equal(k, kw))
|
|
89
|
+
return entry ? entry[1] : v.nil()
|
|
80
90
|
})
|
|
81
91
|
}
|
|
82
92
|
|
|
@@ -85,83 +95,12 @@ function evaluateTry(
|
|
|
85
95
|
env: Env,
|
|
86
96
|
ctx: EvaluationContext
|
|
87
97
|
): CljValue {
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
binding: string
|
|
93
|
-
body: CljValue[]
|
|
94
|
-
}> = []
|
|
95
|
-
let finallyForms: CljValue[] | null = null
|
|
96
|
-
|
|
97
|
-
for (let i = 0; i < forms.length; i++) {
|
|
98
|
-
const form = forms[i]
|
|
99
|
-
if (isList(form) && form.value.length > 0 && isSymbol(form.value[0])) {
|
|
100
|
-
const head = form.value[0].name
|
|
101
|
-
if (head === 'catch') {
|
|
102
|
-
if (form.value.length < 3) {
|
|
103
|
-
throw new EvaluationError(
|
|
104
|
-
'catch requires a discriminator and a binding symbol',
|
|
105
|
-
{ form, env }
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
const discriminator = form.value[1]
|
|
109
|
-
const bindingSym = form.value[2]
|
|
110
|
-
if (!isSymbol(bindingSym)) {
|
|
111
|
-
throw new EvaluationError('catch binding must be a symbol', {
|
|
112
|
-
form,
|
|
113
|
-
env,
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
catchClauses.push({
|
|
117
|
-
discriminator,
|
|
118
|
-
binding: bindingSym.name,
|
|
119
|
-
body: form.value.slice(3),
|
|
120
|
-
})
|
|
121
|
-
continue
|
|
122
|
-
}
|
|
123
|
-
if (head === 'finally') {
|
|
124
|
-
if (i !== forms.length - 1) {
|
|
125
|
-
throw new EvaluationError(
|
|
126
|
-
'finally clause must be the last in try expression',
|
|
127
|
-
{
|
|
128
|
-
form,
|
|
129
|
-
env,
|
|
130
|
-
}
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
finallyForms = form.value.slice(1)
|
|
134
|
-
continue
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
bodyForms.push(form)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function matchesDiscriminator(
|
|
141
|
-
discriminator: CljValue,
|
|
142
|
-
thrown: CljValue
|
|
143
|
-
): boolean {
|
|
144
|
-
const disc = ctx.evaluate(discriminator, env)
|
|
145
|
-
if (isKeyword(disc)) {
|
|
146
|
-
if (disc.name === ':default') return true
|
|
147
|
-
if (!isMap(thrown)) return false
|
|
148
|
-
const typeEntry = thrown.entries.find(
|
|
149
|
-
([k]) => isKeyword(k) && k.name === ':type'
|
|
150
|
-
)
|
|
151
|
-
if (!typeEntry) return false
|
|
152
|
-
return isEqual(typeEntry[1], disc)
|
|
153
|
-
}
|
|
154
|
-
if (isAFunction(disc)) {
|
|
155
|
-
const result = ctx.applyFunction(disc, [thrown], env)
|
|
156
|
-
return isTruthy(result)
|
|
157
|
-
}
|
|
158
|
-
throw new EvaluationError(
|
|
159
|
-
'catch discriminator must be a keyword or a predicate function',
|
|
160
|
-
{ discriminator: disc, env }
|
|
161
|
-
)
|
|
162
|
-
}
|
|
98
|
+
const { bodyForms, catchClauses, finallyForms } = parseTryStructure(
|
|
99
|
+
list,
|
|
100
|
+
env
|
|
101
|
+
)
|
|
163
102
|
|
|
164
|
-
let result: CljValue =
|
|
103
|
+
let result: CljValue = v.nil()
|
|
165
104
|
let pendingThrow: unknown = null
|
|
166
105
|
|
|
167
106
|
try {
|
|
@@ -173,9 +112,9 @@ function evaluateTry(
|
|
|
173
112
|
if (e instanceof CljThrownSignal) {
|
|
174
113
|
thrownValue = e.value
|
|
175
114
|
} else if (e instanceof EvaluationError) {
|
|
176
|
-
thrownValue =
|
|
177
|
-
[
|
|
178
|
-
[
|
|
115
|
+
thrownValue = v.map([
|
|
116
|
+
[v.keyword(':type'), v.keyword(':error/runtime')],
|
|
117
|
+
[v.keyword(':message'), v.string(e.message)],
|
|
179
118
|
])
|
|
180
119
|
} else {
|
|
181
120
|
throw e
|
|
@@ -183,7 +122,7 @@ function evaluateTry(
|
|
|
183
122
|
|
|
184
123
|
let handled = false
|
|
185
124
|
for (const clause of catchClauses) {
|
|
186
|
-
if (matchesDiscriminator(clause.discriminator, thrownValue)) {
|
|
125
|
+
if (matchesDiscriminator(clause.discriminator, thrownValue, env, ctx)) {
|
|
187
126
|
const catchEnv = extend([clause.binding], [thrownValue], env)
|
|
188
127
|
result = ctx.evaluateForms(clause.body, catchEnv)
|
|
189
128
|
handled = true
|
|
@@ -239,11 +178,14 @@ function buildVarMeta(
|
|
|
239
178
|
if (hasPosInfo) {
|
|
240
179
|
const { line, col } = getLineCol(ctx.currentSource!, pos!.start)
|
|
241
180
|
const lineOffset = ctx.currentLineOffset ?? 0
|
|
242
|
-
const colOffset
|
|
243
|
-
posEntries.push([
|
|
244
|
-
posEntries.push([
|
|
181
|
+
const colOffset = ctx.currentColOffset ?? 0
|
|
182
|
+
posEntries.push([v.keyword(':line'), v.number(line + lineOffset)])
|
|
183
|
+
posEntries.push([
|
|
184
|
+
v.keyword(':column'),
|
|
185
|
+
v.number(line === 1 ? col + colOffset : col),
|
|
186
|
+
])
|
|
245
187
|
if (ctx.currentFile) {
|
|
246
|
-
posEntries.push([
|
|
188
|
+
posEntries.push([v.keyword(':file'), v.string(ctx.currentFile)])
|
|
247
189
|
}
|
|
248
190
|
}
|
|
249
191
|
|
|
@@ -254,7 +196,7 @@ function buildVarMeta(
|
|
|
254
196
|
)
|
|
255
197
|
|
|
256
198
|
const allEntries = [...baseEntries, ...posEntries]
|
|
257
|
-
return allEntries.length > 0 ?
|
|
199
|
+
return allEntries.length > 0 ? v.map(allEntries) : undefined
|
|
258
200
|
}
|
|
259
201
|
|
|
260
202
|
function evaluateDef(
|
|
@@ -273,7 +215,7 @@ function evaluateDef(
|
|
|
273
215
|
// (def name) with no value is a bare declaration — a no-op in the evaluator.
|
|
274
216
|
// This lets .clj source files declare runtime-injected symbols so that
|
|
275
217
|
// clojure-lsp can resolve them, without clobbering the native binding.
|
|
276
|
-
if (list.value[2] === undefined) return
|
|
218
|
+
if (list.value[2] === undefined) return v.nil()
|
|
277
219
|
|
|
278
220
|
const nsEnv = getNamespaceEnv(env)
|
|
279
221
|
const cljNs = nsEnv.ns!
|
|
@@ -290,11 +232,11 @@ function evaluateDef(
|
|
|
290
232
|
if (hasDynamicMeta(varMeta)) existing.dynamic = true
|
|
291
233
|
}
|
|
292
234
|
} else {
|
|
293
|
-
const
|
|
294
|
-
if (hasDynamicMeta(varMeta))
|
|
295
|
-
cljNs.vars.set(name.name,
|
|
235
|
+
const newVar = v.var(cljNs.name, name.name, newValue, varMeta)
|
|
236
|
+
if (hasDynamicMeta(varMeta)) newVar.dynamic = true
|
|
237
|
+
cljNs.vars.set(name.name, newVar)
|
|
296
238
|
}
|
|
297
|
-
return
|
|
239
|
+
return v.nil()
|
|
298
240
|
}
|
|
299
241
|
|
|
300
242
|
const evaluateNs = (
|
|
@@ -302,16 +244,16 @@ const evaluateNs = (
|
|
|
302
244
|
_env: Env,
|
|
303
245
|
_ctx: EvaluationContext
|
|
304
246
|
): CljValue => {
|
|
305
|
-
return
|
|
247
|
+
return v.nil() // special form handled by the environment, no effects here
|
|
306
248
|
}
|
|
307
249
|
|
|
308
250
|
function evaluateIf(list: CljList, env: Env, ctx: EvaluationContext): CljValue {
|
|
309
251
|
const condition = ctx.evaluate(list.value[1], env)
|
|
310
|
-
if (!
|
|
252
|
+
if (!is.falsy(condition)) {
|
|
311
253
|
return ctx.evaluate(list.value[2], env)
|
|
312
254
|
}
|
|
313
255
|
if (!list.value[3]) {
|
|
314
|
-
return
|
|
256
|
+
return v.nil() // no-else case, return nil
|
|
315
257
|
}
|
|
316
258
|
return ctx.evaluate(list.value[3], env)
|
|
317
259
|
}
|
|
@@ -326,18 +268,7 @@ function evaluateLet(
|
|
|
326
268
|
ctx: EvaluationContext
|
|
327
269
|
): CljValue {
|
|
328
270
|
const bindings = list.value[1]
|
|
329
|
-
|
|
330
|
-
throw new EvaluationError('Bindings must be a vector', {
|
|
331
|
-
bindings,
|
|
332
|
-
env,
|
|
333
|
-
})
|
|
334
|
-
}
|
|
335
|
-
if (bindings.value.length % 2 !== 0) {
|
|
336
|
-
throw new EvaluationError(
|
|
337
|
-
'Bindings must be a balanced pair of keys and values',
|
|
338
|
-
{ bindings, env }
|
|
339
|
-
)
|
|
340
|
-
}
|
|
271
|
+
validateBindingVector(bindings, 'let', env)
|
|
341
272
|
const body = list.value.slice(2)
|
|
342
273
|
let localEnv = env
|
|
343
274
|
for (let i = 0; i < bindings.value.length; i += 2) {
|
|
@@ -359,30 +290,112 @@ function evaluateFn(
|
|
|
359
290
|
env: Env,
|
|
360
291
|
_ctx: EvaluationContext
|
|
361
292
|
): CljValue {
|
|
362
|
-
const
|
|
293
|
+
const rest = list.value.slice(1)
|
|
294
|
+
// (fn name [...] ...) — optional name symbol before the param vector/arities
|
|
295
|
+
let fnName: string | undefined
|
|
296
|
+
let arityForms = rest
|
|
297
|
+
if (rest[0]?.kind === 'symbol') {
|
|
298
|
+
fnName = rest[0].name
|
|
299
|
+
arityForms = rest.slice(1)
|
|
300
|
+
}
|
|
301
|
+
const arities = parseArities(arityForms, env)
|
|
363
302
|
for (const arity of arities) {
|
|
364
303
|
assertRecurInTailPosition(arity.body)
|
|
365
304
|
}
|
|
366
|
-
|
|
305
|
+
const fn = v.multiArityFunction(arities, env)
|
|
306
|
+
if (fnName) {
|
|
307
|
+
fn.name = fnName
|
|
308
|
+
// Bind the name in the fn's closure env so the body can call itself by name.
|
|
309
|
+
// We wrap the captured env in a new frame containing name → fn, then point
|
|
310
|
+
// fn.env at that frame so the binding is visible during application.
|
|
311
|
+
const selfEnv = makeEnv(env)
|
|
312
|
+
selfEnv.bindings.set(fnName, fn)
|
|
313
|
+
fn.env = selfEnv
|
|
314
|
+
}
|
|
315
|
+
return fn
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function evaluateLetfn(
|
|
319
|
+
list: CljList,
|
|
320
|
+
env: Env,
|
|
321
|
+
ctx: EvaluationContext
|
|
322
|
+
): CljValue {
|
|
323
|
+
// (letfn [(f1 [x] ...) (f2 [x] ...)] body...)
|
|
324
|
+
const fnSpecs = list.value[1]
|
|
325
|
+
if (!is.vector(fnSpecs)) {
|
|
326
|
+
throw new EvaluationError('letfn binding specs must be a vector', {
|
|
327
|
+
fnSpecs,
|
|
328
|
+
env,
|
|
329
|
+
})
|
|
330
|
+
}
|
|
331
|
+
const body = list.value.slice(2)
|
|
332
|
+
|
|
333
|
+
// Create a shared env frame for all the fns to close over
|
|
334
|
+
const sharedEnv = makeEnv(env)
|
|
335
|
+
|
|
336
|
+
// First pass: create all fn objects in the shared env
|
|
337
|
+
for (const spec of fnSpecs.value) {
|
|
338
|
+
if (!is.list(spec) || spec.value.length < 2 || !is.symbol(spec.value[0])) {
|
|
339
|
+
throw new EvaluationError(
|
|
340
|
+
'letfn specs must be (name [params] body...) forms',
|
|
341
|
+
{ spec }
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
const name = spec.value[0].name
|
|
345
|
+
const arityForms = spec.value.slice(1)
|
|
346
|
+
const arities = parseArities(arityForms, sharedEnv)
|
|
347
|
+
for (const arity of arities) {
|
|
348
|
+
assertRecurInTailPosition(arity.body)
|
|
349
|
+
}
|
|
350
|
+
const fn = v.multiArityFunction(arities, sharedEnv)
|
|
351
|
+
fn.name = name
|
|
352
|
+
sharedEnv.bindings.set(name, fn)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Second pass: point all fn envs to the shared env so they can see each other
|
|
356
|
+
for (const spec of fnSpecs.value) {
|
|
357
|
+
const name = (spec as CljList).value[0] as { name: string }
|
|
358
|
+
const fn = sharedEnv.bindings.get(name.name) as CljFunction
|
|
359
|
+
fn.env = sharedEnv
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return ctx.evaluateForms(body, sharedEnv)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function mergeDocIntoMeta(base: CljMap | undefined, docstring: string): CljMap {
|
|
366
|
+
const docEntry: [CljValue, CljValue] = [
|
|
367
|
+
v.keyword(':doc'),
|
|
368
|
+
v.string(docstring),
|
|
369
|
+
]
|
|
370
|
+
const existing = (base?.entries ?? []).filter(
|
|
371
|
+
([k]) => !(k.kind === 'keyword' && k.name === ':doc')
|
|
372
|
+
)
|
|
373
|
+
return { kind: 'map', entries: [...existing, docEntry] }
|
|
367
374
|
}
|
|
368
375
|
|
|
369
376
|
function evaluateDefmacro(
|
|
370
377
|
list: CljList,
|
|
371
378
|
env: Env,
|
|
372
|
-
|
|
379
|
+
ctx: EvaluationContext
|
|
373
380
|
): CljValue {
|
|
374
381
|
const name = list.value[1]
|
|
375
|
-
if (!
|
|
382
|
+
if (!is.symbol(name)) {
|
|
376
383
|
throw new EvaluationError('First element of defmacro must be a symbol', {
|
|
377
384
|
name,
|
|
378
385
|
list,
|
|
379
386
|
env,
|
|
380
387
|
})
|
|
381
388
|
}
|
|
382
|
-
const
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
389
|
+
const rest = list.value.slice(2)
|
|
390
|
+
const docstring = rest[0]?.kind === 'string' ? rest[0].value : undefined
|
|
391
|
+
const arityForms = docstring ? rest.slice(1) : rest
|
|
392
|
+
const arities = parseArities(arityForms, env)
|
|
393
|
+
const macro = v.multiArityMacro(arities, env)
|
|
394
|
+
macro.name = name.name
|
|
395
|
+
const varMeta = buildVarMeta(name.meta, ctx, name)
|
|
396
|
+
const finalMeta = docstring ? mergeDocIntoMeta(varMeta, docstring) : varMeta
|
|
397
|
+
internVar(name.name, macro, getNamespaceEnv(env), finalMeta)
|
|
398
|
+
return v.nil()
|
|
386
399
|
}
|
|
387
400
|
|
|
388
401
|
function evaluateLoop(
|
|
@@ -391,18 +404,7 @@ function evaluateLoop(
|
|
|
391
404
|
ctx: EvaluationContext
|
|
392
405
|
): CljValue {
|
|
393
406
|
const loopBindings = list.value[1]
|
|
394
|
-
|
|
395
|
-
throw new EvaluationError('loop bindings must be a vector', {
|
|
396
|
-
loopBindings,
|
|
397
|
-
env,
|
|
398
|
-
})
|
|
399
|
-
}
|
|
400
|
-
if (loopBindings.value.length % 2 !== 0) {
|
|
401
|
-
throw new EvaluationError(
|
|
402
|
-
'loop bindings must be a balanced pair of keys and values',
|
|
403
|
-
{ loopBindings, env }
|
|
404
|
-
)
|
|
405
|
-
}
|
|
407
|
+
validateBindingVector(loopBindings, 'loop', env)
|
|
406
408
|
const loopBody = list.value.slice(2)
|
|
407
409
|
assertRecurInTailPosition(loopBody)
|
|
408
410
|
|
|
@@ -474,7 +476,7 @@ function evaluateDefmulti(
|
|
|
474
476
|
ctx: EvaluationContext
|
|
475
477
|
): CljValue {
|
|
476
478
|
const mmName = list.value[1]
|
|
477
|
-
if (!
|
|
479
|
+
if (!is.symbol(mmName)) {
|
|
478
480
|
throw new EvaluationError('defmulti: first argument must be a symbol', {
|
|
479
481
|
list,
|
|
480
482
|
env,
|
|
@@ -482,11 +484,11 @@ function evaluateDefmulti(
|
|
|
482
484
|
}
|
|
483
485
|
const dispatchFnExpr = list.value[2]
|
|
484
486
|
let dispatchFn: CljFunction | CljNativeFunction
|
|
485
|
-
if (
|
|
487
|
+
if (is.keyword(dispatchFnExpr)) {
|
|
486
488
|
dispatchFn = keywordToDispatchFn(dispatchFnExpr)
|
|
487
489
|
} else {
|
|
488
490
|
const evaluated = ctx.evaluate(dispatchFnExpr, env)
|
|
489
|
-
if (!
|
|
491
|
+
if (!is.aFunction(evaluated)) {
|
|
490
492
|
throw new EvaluationError(
|
|
491
493
|
'defmulti: dispatch-fn must be a function or keyword',
|
|
492
494
|
{ list, env }
|
|
@@ -494,9 +496,9 @@ function evaluateDefmulti(
|
|
|
494
496
|
}
|
|
495
497
|
dispatchFn = evaluated
|
|
496
498
|
}
|
|
497
|
-
const mm =
|
|
499
|
+
const mm = v.multiMethod(mmName.name, dispatchFn, [])
|
|
498
500
|
internVar(mmName.name, mm, getNamespaceEnv(env))
|
|
499
|
-
return
|
|
501
|
+
return v.nil()
|
|
500
502
|
}
|
|
501
503
|
|
|
502
504
|
function evaluateDefmethod(
|
|
@@ -505,7 +507,7 @@ function evaluateDefmethod(
|
|
|
505
507
|
ctx: EvaluationContext
|
|
506
508
|
): CljValue {
|
|
507
509
|
const mmName = list.value[1]
|
|
508
|
-
if (!
|
|
510
|
+
if (!is.symbol(mmName)) {
|
|
509
511
|
throw new EvaluationError('defmethod: first argument must be a symbol', {
|
|
510
512
|
list,
|
|
511
513
|
env,
|
|
@@ -513,18 +515,18 @@ function evaluateDefmethod(
|
|
|
513
515
|
}
|
|
514
516
|
const dispatchVal = ctx.evaluate(list.value[2], env)
|
|
515
517
|
const existing = lookup(mmName.name, env)
|
|
516
|
-
if (!
|
|
518
|
+
if (!is.multiMethod(existing)) {
|
|
517
519
|
throw new EvaluationError(
|
|
518
520
|
`defmethod: ${mmName.name} is not a multimethod`,
|
|
519
521
|
{ list, env }
|
|
520
522
|
)
|
|
521
523
|
}
|
|
522
524
|
const arities = parseArities([list.value[3], ...list.value.slice(4)], env)
|
|
523
|
-
const methodFn =
|
|
524
|
-
const isDefault =
|
|
525
|
+
const methodFn = v.multiArityFunction(arities, env)
|
|
526
|
+
const isDefault = is.keyword(dispatchVal) && dispatchVal.name === ':default'
|
|
525
527
|
let updated: CljMultiMethod
|
|
526
528
|
if (isDefault) {
|
|
527
|
-
updated =
|
|
529
|
+
updated = v.multiMethod(
|
|
528
530
|
existing.name,
|
|
529
531
|
existing.dispatchFn,
|
|
530
532
|
existing.methods,
|
|
@@ -532,30 +534,30 @@ function evaluateDefmethod(
|
|
|
532
534
|
)
|
|
533
535
|
} else {
|
|
534
536
|
const filtered = existing.methods.filter(
|
|
535
|
-
(m) => !
|
|
537
|
+
(m) => !is.equal(m.dispatchVal, dispatchVal)
|
|
536
538
|
)
|
|
537
|
-
updated =
|
|
539
|
+
updated = v.multiMethod(existing.name, existing.dispatchFn, [
|
|
538
540
|
...filtered,
|
|
539
541
|
{ dispatchVal, fn: methodFn },
|
|
540
542
|
])
|
|
541
543
|
}
|
|
542
544
|
// Update the var's value in place if possible, otherwise fall back to define
|
|
543
|
-
const
|
|
544
|
-
if (
|
|
545
|
-
|
|
545
|
+
const eVar = lookupVar(mmName.name, env)
|
|
546
|
+
if (eVar) {
|
|
547
|
+
eVar.value = updated
|
|
546
548
|
} else {
|
|
547
549
|
define(mmName.name, updated, getNamespaceEnv(env))
|
|
548
550
|
}
|
|
549
|
-
return
|
|
551
|
+
return v.nil()
|
|
550
552
|
}
|
|
551
553
|
|
|
552
554
|
function evaluateVar(
|
|
553
555
|
list: CljList,
|
|
554
556
|
env: Env,
|
|
555
|
-
|
|
557
|
+
ctx: EvaluationContext
|
|
556
558
|
): CljValue {
|
|
557
559
|
const sym = list.value[1]
|
|
558
|
-
if (!
|
|
560
|
+
if (!is.symbol(sym)) {
|
|
559
561
|
throw new EvaluationError('var expects a symbol', { list })
|
|
560
562
|
}
|
|
561
563
|
|
|
@@ -564,19 +566,13 @@ function evaluateVar(
|
|
|
564
566
|
const alias = sym.name.slice(0, slashIdx)
|
|
565
567
|
const localName = sym.name.slice(slashIdx + 1)
|
|
566
568
|
const nsEnv = getNamespaceEnv(env)
|
|
567
|
-
//
|
|
568
|
-
const
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
if (!v) throw new EvaluationError(`Var ${sym.name} not found`, { sym })
|
|
572
|
-
return v
|
|
573
|
-
}
|
|
574
|
-
// Fall back to full namespace Env chain (handles clojure.core/sym etc.)
|
|
575
|
-
const targetEnv = getRootEnv(env).resolveNs?.(alias) ?? null
|
|
576
|
-
if (!targetEnv) {
|
|
569
|
+
// Resolve alias: local :as alias first, then full namespace name
|
|
570
|
+
const targetNs =
|
|
571
|
+
nsEnv.ns?.aliases.get(alias) ?? ctx.resolveNs(alias) ?? null
|
|
572
|
+
if (!targetNs) {
|
|
577
573
|
throw new EvaluationError(`No such namespace: ${alias}`, { sym })
|
|
578
574
|
}
|
|
579
|
-
const v =
|
|
575
|
+
const v = targetNs.vars.get(localName)
|
|
580
576
|
if (!v) throw new EvaluationError(`Var ${sym.name} not found`, { sym })
|
|
581
577
|
return v
|
|
582
578
|
}
|
|
@@ -597,7 +593,7 @@ function evaluateBinding(
|
|
|
597
593
|
ctx: EvaluationContext
|
|
598
594
|
): CljValue {
|
|
599
595
|
const bindings = list.value[1]
|
|
600
|
-
if (!
|
|
596
|
+
if (!is.vector(bindings)) {
|
|
601
597
|
throw new EvaluationError('binding requires a vector of bindings', {
|
|
602
598
|
list,
|
|
603
599
|
env,
|
|
@@ -614,11 +610,10 @@ function evaluateBinding(
|
|
|
614
610
|
|
|
615
611
|
for (let i = 0; i < bindings.value.length; i += 2) {
|
|
616
612
|
const sym = bindings.value[i]
|
|
617
|
-
if (!
|
|
618
|
-
throw new EvaluationError(
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
)
|
|
613
|
+
if (!is.symbol(sym)) {
|
|
614
|
+
throw new EvaluationError('binding left-hand side must be a symbol', {
|
|
615
|
+
sym,
|
|
616
|
+
})
|
|
622
617
|
}
|
|
623
618
|
const newVal = ctx.evaluate(bindings.value[i + 1], env)
|
|
624
619
|
const v = lookupVar(sym.name, env)
|
|
@@ -648,7 +643,11 @@ function evaluateBinding(
|
|
|
648
643
|
}
|
|
649
644
|
}
|
|
650
645
|
|
|
651
|
-
function evaluateSet(
|
|
646
|
+
function evaluateSet(
|
|
647
|
+
list: CljList,
|
|
648
|
+
env: Env,
|
|
649
|
+
ctx: EvaluationContext
|
|
650
|
+
): CljValue {
|
|
652
651
|
if (list.value.length !== 3) {
|
|
653
652
|
throw new EvaluationError(
|
|
654
653
|
`set! requires exactly 2 arguments, got ${list.value.length - 1}`,
|
|
@@ -656,7 +655,7 @@ function evaluateSet(list: CljList, env: Env, ctx: EvaluationContext): CljValue
|
|
|
656
655
|
)
|
|
657
656
|
}
|
|
658
657
|
const symForm = list.value[1]
|
|
659
|
-
if (!
|
|
658
|
+
if (!is.symbol(symForm)) {
|
|
660
659
|
throw new EvaluationError(
|
|
661
660
|
`set! first argument must be a symbol, got ${symForm.kind}`,
|
|
662
661
|
{ symForm, env }
|
|
@@ -686,6 +685,40 @@ function evaluateSet(list: CljList, env: Env, ctx: EvaluationContext): CljValue
|
|
|
686
685
|
return newVal
|
|
687
686
|
}
|
|
688
687
|
|
|
688
|
+
function evaluateDelay(
|
|
689
|
+
list: CljList,
|
|
690
|
+
env: Env,
|
|
691
|
+
ctx: EvaluationContext
|
|
692
|
+
): CljValue {
|
|
693
|
+
const body = list.value.slice(1)
|
|
694
|
+
return v.delay(() => ctx.evaluateForms(body, env))
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function evaluateLazySeqForm(
|
|
698
|
+
list: CljList,
|
|
699
|
+
env: Env,
|
|
700
|
+
ctx: EvaluationContext
|
|
701
|
+
): CljValue {
|
|
702
|
+
const body = list.value.slice(1)
|
|
703
|
+
return v.lazySeq(() => ctx.evaluateForms(body, env))
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// --- ASYNC BLOCK HANDLER (experimental) ---
|
|
707
|
+
// Gateway into the async sub-evaluator. See async-evaluator.ts.
|
|
708
|
+
// To revert: remove this function, the `async` case below, and the import above.
|
|
709
|
+
function evaluateAsyncBlock(
|
|
710
|
+
list: CljList,
|
|
711
|
+
env: Env,
|
|
712
|
+
ctx: EvaluationContext
|
|
713
|
+
): CljValue {
|
|
714
|
+
const body = list.value.slice(1)
|
|
715
|
+
if (body.length === 0) return v.pending(Promise.resolve(v.nil()))
|
|
716
|
+
const asyncCtx = createAsyncEvalCtx(ctx)
|
|
717
|
+
const promise = asyncCtx.evaluateForms(body, env)
|
|
718
|
+
return v.pending(promise)
|
|
719
|
+
}
|
|
720
|
+
// --- END ASYNC BLOCK HANDLER ---
|
|
721
|
+
|
|
689
722
|
type SpecialFormEvaluatorFn = (
|
|
690
723
|
list: CljList,
|
|
691
724
|
env: Env,
|
|
@@ -710,6 +743,16 @@ const specialFormEvaluatorEntries = {
|
|
|
710
743
|
var: evaluateVar,
|
|
711
744
|
binding: evaluateBinding,
|
|
712
745
|
'set!': evaluateSet,
|
|
746
|
+
letfn: evaluateLetfn,
|
|
747
|
+
delay: evaluateDelay,
|
|
748
|
+
'lazy-seq': evaluateLazySeqForm,
|
|
749
|
+
// --- ASYNC (experimental) ---
|
|
750
|
+
async: evaluateAsyncBlock,
|
|
751
|
+
// --- END ASYNC ---
|
|
752
|
+
// --- JS INTEROP ---
|
|
753
|
+
'.': evaluateDot,
|
|
754
|
+
'js/new': evaluateNew,
|
|
755
|
+
// --- END JS INTEROP ---
|
|
713
756
|
} as const satisfies Record<
|
|
714
757
|
keyof typeof specialFormKeywords,
|
|
715
758
|
SpecialFormEvaluatorFn
|