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,28 +1,81 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { is } from '../assertions'
|
|
2
2
|
import { EvaluationError } from '../errors'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
cljKeyword,
|
|
5
|
+
cljList,
|
|
6
|
+
cljNil,
|
|
7
|
+
cljString,
|
|
8
|
+
cljSymbol,
|
|
9
|
+
v,
|
|
10
|
+
} from '../factories'
|
|
11
|
+
import { realizeLazySeq, consToArray } from '../transformations'
|
|
4
12
|
import type { CljMap, CljValue, Env, EvaluationContext } from '../types'
|
|
5
13
|
|
|
6
14
|
function toSeqSafe(value: CljValue): CljValue[] {
|
|
7
|
-
if (
|
|
8
|
-
if (
|
|
9
|
-
if (
|
|
15
|
+
if (is.nil(value)) return []
|
|
16
|
+
if (is.list(value)) return value.value
|
|
17
|
+
if (is.vector(value)) return value.value
|
|
18
|
+
if (is.lazySeq(value)) {
|
|
19
|
+
const realized = realizeLazySeq(value)
|
|
20
|
+
return toSeqSafe(realized)
|
|
21
|
+
}
|
|
22
|
+
if (is.cons(value)) return consToArray(value)
|
|
10
23
|
throw new EvaluationError(
|
|
11
24
|
`Cannot destructure ${value.kind} as a sequential collection`,
|
|
12
25
|
{ value }
|
|
13
26
|
)
|
|
14
27
|
}
|
|
15
28
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)
|
|
20
|
-
|
|
29
|
+
/** Return the first element of a seq-like value without full realization. */
|
|
30
|
+
function seqFirst(value: CljValue): CljValue {
|
|
31
|
+
if (is.nil(value)) return cljNil()
|
|
32
|
+
if (is.lazySeq(value)) {
|
|
33
|
+
const realized = realizeLazySeq(value)
|
|
34
|
+
return is.nil(realized) ? v.nil() : seqFirst(realized)
|
|
35
|
+
}
|
|
36
|
+
if (is.cons(value)) return value.head
|
|
37
|
+
if (is.list(value) || is.vector(value))
|
|
38
|
+
return value.value.length > 0 ? value.value[0] : v.nil()
|
|
39
|
+
return v.nil()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Return the tail of a seq-like value without full realization. */
|
|
43
|
+
function seqRest(value: CljValue): CljValue {
|
|
44
|
+
if (is.nil(value)) return v.list([])
|
|
45
|
+
if (is.lazySeq(value)) {
|
|
46
|
+
const realized = realizeLazySeq(value)
|
|
47
|
+
return is.nil(realized) ? v.list([]) : seqRest(realized)
|
|
48
|
+
}
|
|
49
|
+
if (is.cons(value)) return value.tail
|
|
50
|
+
if (is.list(value)) return v.list(value.value.slice(1))
|
|
51
|
+
if (is.vector(value)) return v.list(value.value.slice(1))
|
|
52
|
+
return v.list([])
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Check if a seq-like value is empty without full realization. */
|
|
56
|
+
function seqIsEmpty(value: CljValue): boolean {
|
|
57
|
+
if (is.nil(value)) return true
|
|
58
|
+
if (is.lazySeq(value)) {
|
|
59
|
+
const realized = realizeLazySeq(value)
|
|
60
|
+
return seqIsEmpty(realized)
|
|
61
|
+
}
|
|
62
|
+
if (is.cons(value)) return false
|
|
63
|
+
if (is.list(value) || is.vector(value)) return value.value.length === 0
|
|
64
|
+
return true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Check if a value is lazy (lazy-seq or cons with lazy tail). */
|
|
68
|
+
function isLazy(value: CljValue): boolean {
|
|
69
|
+
return is.lazySeq(value) || is.cons(value)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function findMapEntry(map: CljMap, key: CljValue): CljValue | undefined {
|
|
73
|
+
const entry = map.entries.find(([k]) => is.equal(k, key))
|
|
21
74
|
return entry ? entry[1] : undefined
|
|
22
75
|
}
|
|
23
76
|
|
|
24
77
|
function mapContainsKey(map: CljMap, key: CljValue): boolean {
|
|
25
|
-
return map.entries.some(([k]) =>
|
|
78
|
+
return map.entries.some(([k]) => is.equal(k, key))
|
|
26
79
|
}
|
|
27
80
|
|
|
28
81
|
function destructureVector(
|
|
@@ -35,10 +88,12 @@ function destructureVector(
|
|
|
35
88
|
const elems = [...pattern]
|
|
36
89
|
|
|
37
90
|
// :as alias — must appear as second-to-last with a symbol after it
|
|
38
|
-
const asIdx = elems.findIndex(
|
|
91
|
+
const asIdx = elems.findIndex(
|
|
92
|
+
(e) => is.keyword(e) && e.kind === 'keyword' && e.name === ':as'
|
|
93
|
+
)
|
|
39
94
|
if (asIdx !== -1) {
|
|
40
95
|
const asSym = elems[asIdx + 1]
|
|
41
|
-
if (!asSym || !
|
|
96
|
+
if (!asSym || !is.symbol(asSym)) {
|
|
42
97
|
throw new EvaluationError(':as must be followed by a symbol', { pattern })
|
|
43
98
|
}
|
|
44
99
|
pairs.push([asSym.name, value])
|
|
@@ -46,13 +101,15 @@ function destructureVector(
|
|
|
46
101
|
}
|
|
47
102
|
|
|
48
103
|
// & rest pattern
|
|
49
|
-
const ampIdx = elems.findIndex((e) =>
|
|
104
|
+
const ampIdx = elems.findIndex((e) => is.symbol(e) && e.name === '&')
|
|
50
105
|
let restPattern: CljValue | null = null
|
|
51
106
|
let positionalCount: number
|
|
52
107
|
if (ampIdx !== -1) {
|
|
53
108
|
restPattern = elems[ampIdx + 1]
|
|
54
109
|
if (!restPattern) {
|
|
55
|
-
throw new EvaluationError('& must be followed by a binding pattern', {
|
|
110
|
+
throw new EvaluationError('& must be followed by a binding pattern', {
|
|
111
|
+
pattern,
|
|
112
|
+
})
|
|
56
113
|
}
|
|
57
114
|
positionalCount = ampIdx
|
|
58
115
|
elems.splice(ampIdx)
|
|
@@ -60,28 +117,60 @@ function destructureVector(
|
|
|
60
117
|
positionalCount = elems.length
|
|
61
118
|
}
|
|
62
119
|
|
|
63
|
-
|
|
120
|
+
// For lazy seqs, walk with first/rest to avoid full realization.
|
|
121
|
+
// For eager collections, use flat array for efficiency.
|
|
122
|
+
if (isLazy(value)) {
|
|
123
|
+
let current: CljValue = value
|
|
124
|
+
for (let i = 0; i < positionalCount; i++) {
|
|
125
|
+
pairs.push(...destructureBindings(elems[i], seqFirst(current), ctx, env))
|
|
126
|
+
current = seqRest(current)
|
|
127
|
+
}
|
|
128
|
+
if (restPattern !== null) {
|
|
129
|
+
// For kwargs-style map destructuring on rest, we must realize
|
|
130
|
+
if (is.map(restPattern) && !seqIsEmpty(current)) {
|
|
131
|
+
const restArgs = toSeqSafe(current)
|
|
132
|
+
const entries: [CljValue, CljValue][] = []
|
|
133
|
+
for (let i = 0; i < restArgs.length; i += 2) {
|
|
134
|
+
entries.push([restArgs[i], restArgs[i + 1] ?? cljNil()])
|
|
135
|
+
}
|
|
136
|
+
pairs.push(
|
|
137
|
+
...destructureBindings(
|
|
138
|
+
restPattern,
|
|
139
|
+
{ kind: 'map', entries },
|
|
140
|
+
ctx,
|
|
141
|
+
env
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
} else {
|
|
145
|
+
// Keep the rest as-is (still lazy) — wrap in list only if it's nil/empty
|
|
146
|
+
const restValue = seqIsEmpty(current) ? cljNil() : current
|
|
147
|
+
pairs.push(...destructureBindings(restPattern, restValue, ctx, env))
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
const seq = toSeqSafe(value)
|
|
64
152
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
153
|
+
// positional bindings
|
|
154
|
+
for (let i = 0; i < positionalCount; i++) {
|
|
155
|
+
pairs.push(...destructureBindings(elems[i], seq[i] ?? cljNil(), ctx, env))
|
|
156
|
+
}
|
|
69
157
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
158
|
+
// rest binding
|
|
159
|
+
if (restPattern !== null) {
|
|
160
|
+
const restArgs = seq.slice(positionalCount)
|
|
161
|
+
let restValue: CljValue
|
|
162
|
+
if (is.map(restPattern) && restArgs.length > 0) {
|
|
163
|
+
// kwargs-style: coerce flat key-value pairs into a map
|
|
164
|
+
const entries: [CljValue, CljValue][] = []
|
|
165
|
+
for (let i = 0; i < restArgs.length; i += 2) {
|
|
166
|
+
entries.push([restArgs[i], restArgs[i + 1] ?? cljNil()])
|
|
167
|
+
}
|
|
168
|
+
restValue = { kind: 'map', entries }
|
|
169
|
+
} else {
|
|
170
|
+
restValue = restArgs.length > 0 ? cljList(restArgs) : cljNil()
|
|
79
171
|
}
|
|
80
|
-
restValue
|
|
81
|
-
} else {
|
|
82
|
-
restValue = restArgs.length > 0 ? cljList(restArgs) : cljNil()
|
|
172
|
+
pairs.push(...destructureBindings(restPattern, restValue, ctx, env))
|
|
83
173
|
}
|
|
84
|
-
pairs.push(...destructureBindings(restPattern, restValue, ctx, env))
|
|
85
174
|
}
|
|
86
175
|
|
|
87
176
|
return pairs
|
|
@@ -96,35 +185,41 @@ function destructureMap(
|
|
|
96
185
|
const pairs: [string, CljValue][] = []
|
|
97
186
|
|
|
98
187
|
const orMapVal = findMapEntry(pattern, cljKeyword(':or'))
|
|
99
|
-
const orMap = orMapVal &&
|
|
188
|
+
const orMap = orMapVal && is.map(orMapVal) ? orMapVal : null
|
|
100
189
|
const asVal = findMapEntry(pattern, cljKeyword(':as'))
|
|
101
190
|
|
|
102
|
-
if (!
|
|
103
|
-
throw new EvaluationError(
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
)
|
|
191
|
+
if (!is.map(value) && value.kind !== 'nil') {
|
|
192
|
+
throw new EvaluationError(`Cannot destructure ${value.kind} as a map`, {
|
|
193
|
+
value,
|
|
194
|
+
pattern,
|
|
195
|
+
})
|
|
107
196
|
}
|
|
108
197
|
|
|
109
|
-
const targetMap: CljMap =
|
|
110
|
-
? { kind: 'map', entries: [] }
|
|
111
|
-
: value as CljMap
|
|
198
|
+
const targetMap: CljMap =
|
|
199
|
+
value.kind === 'nil' ? { kind: 'map', entries: [] } : (value as CljMap)
|
|
112
200
|
|
|
113
201
|
for (const [k, v] of pattern.entries) {
|
|
114
|
-
if (
|
|
115
|
-
if (
|
|
202
|
+
if (is.keyword(k) && k.name === ':or') continue
|
|
203
|
+
if (is.keyword(k) && k.name === ':as') continue
|
|
116
204
|
|
|
117
205
|
// :keys shorthand — lookup by keyword (supports qualified: ns/foo → :ns/foo, binds to foo)
|
|
118
|
-
if (
|
|
119
|
-
if (!
|
|
120
|
-
throw new EvaluationError(
|
|
206
|
+
if (is.keyword(k) && k.name === ':keys') {
|
|
207
|
+
if (!is.vector(v)) {
|
|
208
|
+
throw new EvaluationError(
|
|
209
|
+
':keys must be followed by a vector of symbols',
|
|
210
|
+
{ pattern }
|
|
211
|
+
)
|
|
121
212
|
}
|
|
122
213
|
for (const sym of v.value) {
|
|
123
|
-
if (!
|
|
124
|
-
throw new EvaluationError(':keys vector must contain symbols', {
|
|
214
|
+
if (!is.symbol(sym)) {
|
|
215
|
+
throw new EvaluationError(':keys vector must contain symbols', {
|
|
216
|
+
pattern,
|
|
217
|
+
sym,
|
|
218
|
+
})
|
|
125
219
|
}
|
|
126
220
|
const slashIdx = sym.name.indexOf('/')
|
|
127
|
-
const localName =
|
|
221
|
+
const localName =
|
|
222
|
+
slashIdx !== -1 ? sym.name.slice(slashIdx + 1) : sym.name
|
|
128
223
|
const lookupKey = cljKeyword(':' + sym.name)
|
|
129
224
|
const present = mapContainsKey(targetMap, lookupKey)
|
|
130
225
|
const entry = present ? findMapEntry(targetMap, lookupKey)! : undefined
|
|
@@ -134,7 +229,8 @@ function destructureMap(
|
|
|
134
229
|
result = entry!
|
|
135
230
|
} else if (orMap) {
|
|
136
231
|
const orDefault = findMapEntry(orMap, cljSymbol(localName))
|
|
137
|
-
result =
|
|
232
|
+
result =
|
|
233
|
+
orDefault !== undefined ? ctx.evaluate(orDefault, env) : cljNil()
|
|
138
234
|
} else {
|
|
139
235
|
result = cljNil()
|
|
140
236
|
}
|
|
@@ -144,13 +240,19 @@ function destructureMap(
|
|
|
144
240
|
}
|
|
145
241
|
|
|
146
242
|
// :strs shorthand — lookup by string
|
|
147
|
-
if (
|
|
148
|
-
if (!
|
|
149
|
-
throw new EvaluationError(
|
|
243
|
+
if (is.keyword(k) && k.name === ':strs') {
|
|
244
|
+
if (!is.vector(v)) {
|
|
245
|
+
throw new EvaluationError(
|
|
246
|
+
':strs must be followed by a vector of symbols',
|
|
247
|
+
{ pattern }
|
|
248
|
+
)
|
|
150
249
|
}
|
|
151
250
|
for (const sym of v.value) {
|
|
152
|
-
if (!
|
|
153
|
-
throw new EvaluationError(':strs vector must contain symbols', {
|
|
251
|
+
if (!is.symbol(sym)) {
|
|
252
|
+
throw new EvaluationError(':strs vector must contain symbols', {
|
|
253
|
+
pattern,
|
|
254
|
+
sym,
|
|
255
|
+
})
|
|
154
256
|
}
|
|
155
257
|
const lookupKey = cljString(sym.name)
|
|
156
258
|
const present = mapContainsKey(targetMap, lookupKey)
|
|
@@ -161,7 +263,8 @@ function destructureMap(
|
|
|
161
263
|
result = entry!
|
|
162
264
|
} else if (orMap) {
|
|
163
265
|
const orDefault = findMapEntry(orMap, cljSymbol(sym.name))
|
|
164
|
-
result =
|
|
266
|
+
result =
|
|
267
|
+
orDefault !== undefined ? ctx.evaluate(orDefault, env) : cljNil()
|
|
165
268
|
} else {
|
|
166
269
|
result = cljNil()
|
|
167
270
|
}
|
|
@@ -171,13 +274,19 @@ function destructureMap(
|
|
|
171
274
|
}
|
|
172
275
|
|
|
173
276
|
// :syms shorthand — lookup by symbol
|
|
174
|
-
if (
|
|
175
|
-
if (!
|
|
176
|
-
throw new EvaluationError(
|
|
277
|
+
if (is.keyword(k) && k.name === ':syms') {
|
|
278
|
+
if (!is.vector(v)) {
|
|
279
|
+
throw new EvaluationError(
|
|
280
|
+
':syms must be followed by a vector of symbols',
|
|
281
|
+
{ pattern }
|
|
282
|
+
)
|
|
177
283
|
}
|
|
178
284
|
for (const sym of v.value) {
|
|
179
|
-
if (!
|
|
180
|
-
throw new EvaluationError(':syms vector must contain symbols', {
|
|
285
|
+
if (!is.symbol(sym)) {
|
|
286
|
+
throw new EvaluationError(':syms vector must contain symbols', {
|
|
287
|
+
pattern,
|
|
288
|
+
sym,
|
|
289
|
+
})
|
|
181
290
|
}
|
|
182
291
|
const lookupKey = cljSymbol(sym.name)
|
|
183
292
|
const present = mapContainsKey(targetMap, lookupKey)
|
|
@@ -188,7 +297,8 @@ function destructureMap(
|
|
|
188
297
|
result = entry!
|
|
189
298
|
} else if (orMap) {
|
|
190
299
|
const orDefault = findMapEntry(orMap, cljSymbol(sym.name))
|
|
191
|
-
result =
|
|
300
|
+
result =
|
|
301
|
+
orDefault !== undefined ? ctx.evaluate(orDefault, env) : cljNil()
|
|
192
302
|
} else {
|
|
193
303
|
result = cljNil()
|
|
194
304
|
}
|
|
@@ -205,9 +315,10 @@ function destructureMap(
|
|
|
205
315
|
let boundVal: CljValue
|
|
206
316
|
if (present) {
|
|
207
317
|
boundVal = entry!
|
|
208
|
-
} else if (orMap &&
|
|
318
|
+
} else if (orMap && is.symbol(k)) {
|
|
209
319
|
const orDefault = findMapEntry(orMap, cljSymbol(k.name))
|
|
210
|
-
boundVal =
|
|
320
|
+
boundVal =
|
|
321
|
+
orDefault !== undefined ? ctx.evaluate(orDefault, env) : cljNil()
|
|
211
322
|
} else {
|
|
212
323
|
boundVal = cljNil()
|
|
213
324
|
}
|
|
@@ -215,7 +326,7 @@ function destructureMap(
|
|
|
215
326
|
}
|
|
216
327
|
|
|
217
328
|
// :as alias
|
|
218
|
-
if (asVal &&
|
|
329
|
+
if (asVal && is.symbol(asVal)) {
|
|
219
330
|
pairs.push([asVal.name, value])
|
|
220
331
|
}
|
|
221
332
|
|
|
@@ -228,15 +339,15 @@ export function destructureBindings(
|
|
|
228
339
|
ctx: EvaluationContext,
|
|
229
340
|
env: Env
|
|
230
341
|
): [string, CljValue][] {
|
|
231
|
-
if (
|
|
342
|
+
if (is.symbol(pattern)) {
|
|
232
343
|
return [[pattern.name, value]]
|
|
233
344
|
}
|
|
234
345
|
|
|
235
|
-
if (
|
|
346
|
+
if (is.vector(pattern)) {
|
|
236
347
|
return destructureVector(pattern.value, value, ctx, env)
|
|
237
348
|
}
|
|
238
349
|
|
|
239
|
-
if (
|
|
350
|
+
if (is.map(pattern)) {
|
|
240
351
|
return destructureMap(pattern, value, ctx, env)
|
|
241
352
|
}
|
|
242
353
|
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
isCallable,
|
|
3
|
-
isEqual,
|
|
4
|
-
isMultiMethod,
|
|
5
|
-
isSpecialForm,
|
|
6
|
-
isSymbol,
|
|
7
|
-
} from '../assertions'
|
|
1
|
+
import { is } from '../assertions'
|
|
8
2
|
import { EvaluationError } from '../errors'
|
|
9
3
|
import { printString } from '../printer'
|
|
10
4
|
import { getPos } from '../positions'
|
|
@@ -26,7 +20,7 @@ function dispatchMultiMethod(
|
|
|
26
20
|
): CljValue {
|
|
27
21
|
const dispatchVal = ctx.applyFunction(mm.dispatchFn, args, env)
|
|
28
22
|
const method = mm.methods.find(({ dispatchVal: dv }) =>
|
|
29
|
-
|
|
23
|
+
is.equal(dv, dispatchVal)
|
|
30
24
|
)
|
|
31
25
|
if (method) return ctx.applyFunction(method.fn, args, env)
|
|
32
26
|
if (mm.defaultMethod) return ctx.applyFunction(mm.defaultMethod, args, env)
|
|
@@ -49,23 +43,23 @@ export function evaluateList(
|
|
|
49
43
|
ctx: EvaluationContext
|
|
50
44
|
): CljValue {
|
|
51
45
|
if (list.value.length === 0) {
|
|
52
|
-
|
|
46
|
+
return list
|
|
53
47
|
}
|
|
54
48
|
const first = list.value[0]
|
|
55
49
|
|
|
56
|
-
if (
|
|
50
|
+
if (is.specialForm(first)) {
|
|
57
51
|
return evaluateSpecialForm(first.name, list, env, ctx)
|
|
58
52
|
}
|
|
59
53
|
|
|
60
54
|
const evaledFirst = ctx.evaluate(first, env)
|
|
61
55
|
|
|
62
|
-
if (
|
|
56
|
+
if (is.multiMethod(evaledFirst)) {
|
|
63
57
|
const args = list.value.slice(1).map((v) => ctx.evaluate(v, env))
|
|
64
58
|
return dispatchMultiMethod(evaledFirst, args, ctx, env)
|
|
65
59
|
}
|
|
66
60
|
|
|
67
|
-
if (!
|
|
68
|
-
const name =
|
|
61
|
+
if (!is.callable(evaledFirst)) {
|
|
62
|
+
const name = is.symbol(first) ? first.name : printString(first)
|
|
69
63
|
throw new EvaluationError(`${name} is not callable`, { list, env })
|
|
70
64
|
}
|
|
71
65
|
|
|
@@ -73,7 +67,11 @@ export function evaluateList(
|
|
|
73
67
|
try {
|
|
74
68
|
return ctx.applyCallable(evaledFirst, args, env)
|
|
75
69
|
} catch (e) {
|
|
76
|
-
if (
|
|
70
|
+
if (
|
|
71
|
+
e instanceof EvaluationError &&
|
|
72
|
+
e.data?.argIndex !== undefined &&
|
|
73
|
+
!e.pos
|
|
74
|
+
) {
|
|
77
75
|
const argForm = list.value[(e.data.argIndex as number) + 1]
|
|
78
76
|
if (argForm) {
|
|
79
77
|
const pos = getPos(argForm)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { derefValue, getNamespaceEnv, lookup } from '../env'
|
|
2
2
|
import { EvaluationError } from '../errors'
|
|
3
|
-
import {
|
|
3
|
+
import { v } from '../factories'
|
|
4
4
|
import { getPos } from '../positions'
|
|
5
5
|
import { valueKeywords } from '../types'
|
|
6
6
|
|
|
7
7
|
import type { CljValue, Env, EvaluationContext } from '../types'
|
|
8
|
-
import { evaluateMap, evaluateVector } from './collections'
|
|
8
|
+
import { evaluateMap, evaluateSet, evaluateVector } from './collections'
|
|
9
9
|
import { evaluateList } from './dispatch'
|
|
10
10
|
|
|
11
11
|
export type EvaluationMeasurement = {
|
|
@@ -34,6 +34,10 @@ export function evaluateWithContext(
|
|
|
34
34
|
case valueKeywords.multiMethod:
|
|
35
35
|
case valueKeywords.boolean:
|
|
36
36
|
case valueKeywords.regex:
|
|
37
|
+
case valueKeywords.delay:
|
|
38
|
+
case valueKeywords.lazySeq:
|
|
39
|
+
case valueKeywords.cons:
|
|
40
|
+
case valueKeywords.namespace:
|
|
37
41
|
return expr
|
|
38
42
|
case valueKeywords.symbol: {
|
|
39
43
|
const slashIdx = expr.name.indexOf('/')
|
|
@@ -41,27 +45,23 @@ export function evaluateWithContext(
|
|
|
41
45
|
const alias = expr.name.slice(0, slashIdx)
|
|
42
46
|
const sym = expr.name.slice(slashIdx + 1)
|
|
43
47
|
const nsEnv = getNamespaceEnv(env)
|
|
44
|
-
//
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (v === undefined) {
|
|
49
|
-
throw new EvaluationError(`Symbol ${expr.name} not found`, {
|
|
50
|
-
symbol: expr.name,
|
|
51
|
-
env,
|
|
52
|
-
})
|
|
53
|
-
}
|
|
54
|
-
return v.value
|
|
55
|
-
}
|
|
56
|
-
// Fall back to full namespace Env chain (handles clojure.core/sym etc.)
|
|
57
|
-
const targetEnv = getRootEnv(env).resolveNs?.(alias) ?? null
|
|
58
|
-
if (!targetEnv) {
|
|
48
|
+
// Resolve alias: local :as alias first, then full namespace name
|
|
49
|
+
const targetNs =
|
|
50
|
+
nsEnv.ns?.aliases.get(alias) ?? ctx.resolveNs(alias) ?? null
|
|
51
|
+
if (!targetNs) {
|
|
59
52
|
throw new EvaluationError(`No such namespace or alias: ${alias}`, {
|
|
60
53
|
symbol: expr.name,
|
|
61
54
|
env,
|
|
62
55
|
})
|
|
63
56
|
}
|
|
64
|
-
|
|
57
|
+
const v = targetNs.vars.get(sym)
|
|
58
|
+
if (v === undefined) {
|
|
59
|
+
throw new EvaluationError(`Symbol ${expr.name} not found`, {
|
|
60
|
+
symbol: expr.name,
|
|
61
|
+
env,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
return derefValue(v)
|
|
65
65
|
}
|
|
66
66
|
return lookup(expr.name, env)
|
|
67
67
|
}
|
|
@@ -69,6 +69,8 @@ export function evaluateWithContext(
|
|
|
69
69
|
return evaluateVector(expr, env, ctx)
|
|
70
70
|
case valueKeywords.map:
|
|
71
71
|
return evaluateMap(expr, env, ctx)
|
|
72
|
+
case valueKeywords.set:
|
|
73
|
+
return evaluateSet(expr, env, ctx)
|
|
72
74
|
case valueKeywords.list:
|
|
73
75
|
return evaluateList(expr, env, ctx)
|
|
74
76
|
default:
|
|
@@ -88,7 +90,7 @@ export function evaluateFormsWithContext(
|
|
|
88
90
|
env: Env,
|
|
89
91
|
ctx: EvaluationContext
|
|
90
92
|
): CljValue {
|
|
91
|
-
let result: CljValue =
|
|
93
|
+
let result: CljValue = v.nil()
|
|
92
94
|
for (const form of forms) {
|
|
93
95
|
result = ctx.evaluate(form, env)
|
|
94
96
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { tryLookup } from '../env'
|
|
1
|
+
import { is } from '../assertions'
|
|
2
|
+
import { derefValue, getNamespaceEnv, tryLookup } from '../env'
|
|
3
3
|
import { cljList, cljMap, cljVector } from '../factories'
|
|
4
4
|
import type { CljValue, Env, EvaluationContext } from '../types'
|
|
5
5
|
|
|
@@ -25,13 +25,17 @@ export function macroExpandAllWithContext(
|
|
|
25
25
|
ctx: EvaluationContext
|
|
26
26
|
): CljValue {
|
|
27
27
|
// Vectors: expand each element (covers [x (some-macro ...)] in let bindings etc.)
|
|
28
|
-
if (
|
|
29
|
-
const expanded = form.value.map((sub) =>
|
|
30
|
-
|
|
28
|
+
if (is.vector(form)) {
|
|
29
|
+
const expanded = form.value.map((sub) =>
|
|
30
|
+
macroExpandAllWithContext(sub, env, ctx)
|
|
31
|
+
)
|
|
32
|
+
return expanded.every((e, i) => e === form.value[i])
|
|
33
|
+
? form
|
|
34
|
+
: cljVector(expanded)
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
// Maps: expand each key and value
|
|
34
|
-
if (
|
|
38
|
+
if (is.map(form)) {
|
|
35
39
|
const expanded = form.entries.map(
|
|
36
40
|
([k, v]) =>
|
|
37
41
|
[
|
|
@@ -39,13 +43,15 @@ export function macroExpandAllWithContext(
|
|
|
39
43
|
macroExpandAllWithContext(v, env, ctx),
|
|
40
44
|
] as [CljValue, CljValue]
|
|
41
45
|
)
|
|
42
|
-
return expanded.every(
|
|
46
|
+
return expanded.every(
|
|
47
|
+
([k, v], i) => k === form.entries[i][0] && v === form.entries[i][1]
|
|
48
|
+
)
|
|
43
49
|
? form
|
|
44
50
|
: cljMap(expanded)
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
// Atoms (number, string, boolean, keyword, nil, symbol, regex, functions, etc.)
|
|
48
|
-
if (!
|
|
54
|
+
if (!is.list(form)) return form
|
|
49
55
|
|
|
50
56
|
// Empty list
|
|
51
57
|
if (form.value.length === 0) return form
|
|
@@ -53,9 +59,13 @@ export function macroExpandAllWithContext(
|
|
|
53
59
|
const first = form.value[0]
|
|
54
60
|
|
|
55
61
|
// Non-symbol head (e.g. anonymous fn call `((fn [x] x) 5)`): expand all sub-forms
|
|
56
|
-
if (!
|
|
57
|
-
const expanded = form.value.map((sub) =>
|
|
58
|
-
|
|
62
|
+
if (!is.symbol(first)) {
|
|
63
|
+
const expanded = form.value.map((sub) =>
|
|
64
|
+
macroExpandAllWithContext(sub, env, ctx)
|
|
65
|
+
)
|
|
66
|
+
return expanded.every((e, i) => e === form.value[i])
|
|
67
|
+
? form
|
|
68
|
+
: cljList(expanded)
|
|
59
69
|
}
|
|
60
70
|
|
|
61
71
|
const name = first.name
|
|
@@ -66,14 +76,34 @@ export function macroExpandAllWithContext(
|
|
|
66
76
|
// Check whether the head resolves to a macro in the current env.
|
|
67
77
|
// tryLookup returns undefined for unknown symbols (forward refs, fn params, etc.)
|
|
68
78
|
// avoiding the try/catch exception path entirely.
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
// For qualified symbols like clojure.core/when-let or aliased m/my-macro,
|
|
80
|
+
// resolve via local :as alias first, then full namespace name.
|
|
81
|
+
let macroOrUnknown: CljValue | undefined
|
|
82
|
+
const slashIdx = name.indexOf('/')
|
|
83
|
+
if (slashIdx > 0 && slashIdx < name.length - 1) {
|
|
84
|
+
const nsPrefix = name.slice(0, slashIdx)
|
|
85
|
+
const localName = name.slice(slashIdx + 1)
|
|
86
|
+
const nsEnv = getNamespaceEnv(env)
|
|
87
|
+
const targetNs =
|
|
88
|
+
nsEnv.ns?.aliases.get(nsPrefix) ?? ctx.resolveNs(nsPrefix) ?? null
|
|
89
|
+
if (targetNs) {
|
|
90
|
+
const v = targetNs.vars.get(localName)
|
|
91
|
+
macroOrUnknown = v !== undefined ? derefValue(v) : undefined
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
macroOrUnknown = tryLookup(name, env)
|
|
95
|
+
}
|
|
96
|
+
if (macroOrUnknown !== undefined && is.macro(macroOrUnknown)) {
|
|
71
97
|
const expanded = ctx.applyMacro(macroOrUnknown, form.value.slice(1))
|
|
72
98
|
// Keep expanding until no more macros at the top level
|
|
73
99
|
return macroExpandAllWithContext(expanded, env, ctx)
|
|
74
100
|
}
|
|
75
101
|
|
|
76
102
|
// Special forms and function calls: expand all sub-forms
|
|
77
|
-
const expanded = form.value.map((sub) =>
|
|
78
|
-
|
|
103
|
+
const expanded = form.value.map((sub) =>
|
|
104
|
+
macroExpandAllWithContext(sub, env, ctx)
|
|
105
|
+
)
|
|
106
|
+
return expanded.every((e, i) => e === form.value[i])
|
|
107
|
+
? form
|
|
108
|
+
: cljList(expanded)
|
|
79
109
|
}
|