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.
Files changed (58) hide show
  1. package/dist-cli/conjure-js.mjs +2328 -2089
  2. package/dist-vite-plugin/index.mjs +2327 -2088
  3. package/package.json +1 -1
  4. package/src/bin/version.ts +1 -1
  5. package/src/core/assertions.ts +10 -3
  6. package/src/core/bootstrap.ts +7 -23
  7. package/src/core/compiler/binding.ts +164 -0
  8. package/src/core/compiler/callable.ts +41 -0
  9. package/src/core/compiler/compile-env.ts +40 -0
  10. package/src/core/compiler/control-flow.ts +79 -0
  11. package/src/core/compiler/index.ts +121 -0
  12. package/src/core/env.ts +4 -4
  13. package/src/core/errors.ts +1 -0
  14. package/src/core/evaluator/apply.ts +7 -3
  15. package/src/core/evaluator/arity.ts +16 -6
  16. package/src/core/evaluator/async-evaluator.ts +68 -89
  17. package/src/core/evaluator/collections.ts +9 -4
  18. package/src/core/evaluator/destructure.ts +45 -55
  19. package/src/core/evaluator/dispatch.ts +21 -24
  20. package/src/core/evaluator/evaluate.ts +14 -2
  21. package/src/core/evaluator/expand.ts +5 -7
  22. package/src/core/evaluator/js-interop.ts +46 -33
  23. package/src/core/evaluator/quasiquote.ts +7 -11
  24. package/src/core/evaluator/recur-check.ts +1 -1
  25. package/src/core/evaluator/special-forms.ts +18 -38
  26. package/src/core/index.ts +1 -1
  27. package/src/core/keywords.ts +105 -0
  28. package/src/core/modules/core/index.ts +131 -0
  29. package/src/core/{stdlib → modules/core/stdlib}/arithmetic.ts +6 -6
  30. package/src/core/{stdlib → modules/core/stdlib}/async-fns.ts +6 -6
  31. package/src/core/{stdlib → modules/core/stdlib}/atoms.ts +7 -7
  32. package/src/core/{stdlib → modules/core/stdlib}/errors.ts +4 -4
  33. package/src/core/{stdlib → modules/core/stdlib}/hof.ts +6 -6
  34. package/src/core/{stdlib → modules/core/stdlib}/lazy.ts +4 -4
  35. package/src/core/{stdlib → modules/core/stdlib}/maps-sets.ts +6 -6
  36. package/src/core/{stdlib → modules/core/stdlib}/meta.ts +5 -5
  37. package/src/core/{stdlib → modules/core/stdlib}/predicates.ts +7 -7
  38. package/src/core/modules/core/stdlib/print.ts +108 -0
  39. package/src/core/{stdlib → modules/core/stdlib}/regex.ts +5 -5
  40. package/src/core/{stdlib → modules/core/stdlib}/seq.ts +7 -7
  41. package/src/core/{stdlib → modules/core/stdlib}/strings.ts +6 -6
  42. package/src/core/{stdlib → modules/core/stdlib}/transducers.ts +6 -6
  43. package/src/core/{stdlib → modules/core/stdlib}/utils.ts +10 -10
  44. package/src/core/{stdlib → modules/core/stdlib}/vars.ts +4 -4
  45. package/src/core/{stdlib → modules/core/stdlib}/vectors.ts +6 -6
  46. package/src/core/modules/js/index.ts +402 -0
  47. package/src/core/ns-forms.ts +25 -17
  48. package/src/core/positions.ts +22 -2
  49. package/src/core/printer.ts +162 -53
  50. package/src/core/reader.ts +25 -22
  51. package/src/core/registry.ts +10 -10
  52. package/src/core/runtime.ts +23 -23
  53. package/src/core/session.ts +17 -7
  54. package/src/core/tokenizer.ts +14 -4
  55. package/src/core/transformations.ts +48 -29
  56. package/src/core/types.ts +57 -81
  57. package/src/core/core-module.ts +0 -303
  58. package/src/core/stdlib/js-namespace.ts +0 -344
@@ -0,0 +1,402 @@
1
+ // js namespace — ambient JS interop utilities.
2
+ // Installed automatically alongside clojure.core. No explicit require needed.
3
+ // Users inject host globals (js/Math, js/console, etc.) via createSession({ hostBindings }).
4
+ import { is } from '../../assertions'
5
+ import {
6
+ type FunctionApplier,
7
+ cljToJs as cljToJsDeep,
8
+ jsToClj as jsToCljDeep,
9
+ } from '../../conversions'
10
+ import { EvaluationError } from '../../errors'
11
+ import { cljToJs, jsToClj } from '../../evaluator/js-interop'
12
+ import { v } from '../../factories'
13
+ import { valueKeywords } from '../../keywords'
14
+ import type { RuntimeModule, VarDeclaration, VarMap } from '../../module'
15
+ import type { CljValue, Env, EvaluationContext } from '../../types'
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Helpers
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /**
22
+ * Convert a Clojure key value to a JS object key string.
23
+ * Strings, keywords, and numbers are allowed — JS coerces numbers anyway.
24
+ */
25
+ function resolveJsKey(key: CljValue, fnName: string): string {
26
+ if (is.string(key)) return key.value
27
+ if (is.keyword(key)) return key.name.slice(1) // strip leading ':'
28
+ if (is.number(key)) return String(key.value) // JS coerces obj[0] to obj["0"]
29
+ throw new EvaluationError(
30
+ `${fnName}: key must be a string, keyword, or number, got ${key.kind}`,
31
+ { key }
32
+ )
33
+ }
34
+
35
+ /**
36
+ * Extract the raw value from a target CljValue for use in js/get and js/set!.
37
+ * Mirrors extractRawTarget in js-interop.ts: CljJsValue, CljString, CljNumber,
38
+ * CljBoolean are all valid targets — JS auto-boxes primitives for property access.
39
+ * Nil and other Clojure types are rejected.
40
+ */
41
+ function extractJsTarget(val: CljValue, fnName: string): unknown {
42
+ switch (val.kind) {
43
+ case valueKeywords.jsValue:
44
+ return val.value
45
+ case valueKeywords.string:
46
+ case valueKeywords.number:
47
+ case valueKeywords.boolean:
48
+ return val.value
49
+ case valueKeywords.nil:
50
+ throw new EvaluationError(`${fnName}: cannot access properties on nil`, {
51
+ val,
52
+ })
53
+ default:
54
+ throw new EvaluationError(
55
+ `${fnName}: expected a js-value or primitive, got ${val.kind}`,
56
+ { val }
57
+ )
58
+ }
59
+ }
60
+
61
+ /**
62
+ * JS interop functions that are part of the core namespace.
63
+ * Thus they are available globally when the JS module is loaded.
64
+ * We declare them here to allow the user to use them without
65
+ * having to refer to them in (ns (:require))
66
+ */
67
+ const coreNativeFunctions: Record<string, CljValue> = {
68
+ // JS interop — deep conversion functions
69
+ 'clj->js': v.nativeFnCtx(
70
+ 'clj->js',
71
+ (ctx: EvaluationContext, callEnv: Env, val: CljValue) => {
72
+ if (is.jsValue(val)) return val
73
+ const applier: FunctionApplier = {
74
+ applyFunction: (fn, args) => ctx.applyCallable(fn, args, callEnv),
75
+ }
76
+ return v.jsValue(cljToJsDeep(val, applier))
77
+ }
78
+ ),
79
+ 'js->clj': v.nativeFn('js->clj', (val: CljValue, opts?: CljValue) => {
80
+ if (val.kind === 'nil') return val
81
+ if (!is.jsValue(val)) {
82
+ throw new EvaluationError(`js->clj expects a js-value, got ${val.kind}`, {
83
+ val,
84
+ })
85
+ }
86
+ const keywordizeKeys = (() => {
87
+ if (!opts || opts.kind !== 'map') return false
88
+ for (const [k, flag] of opts.entries) {
89
+ if (k.kind === 'keyword' && k.name === ':keywordize-keys') {
90
+ return flag.kind !== 'boolean' || flag.value !== false
91
+ }
92
+ }
93
+ return false
94
+ })()
95
+ return jsToCljDeep(val.value, { keywordizeKeys })
96
+ }),
97
+ }
98
+
99
+ const moduleNativeFunctions: Record<string, CljValue> = {
100
+ // (js/get obj key) / (js/get obj key not-found)
101
+ // Dynamic property access. Primitives (string, number, boolean) are valid
102
+ // targets — same auto-boxing JS applies. Optional not-found default is returned
103
+ // when the property is absent (undefined), allowing idiomatic nil defaults.
104
+ get: v.nativeFn(
105
+ 'js/get',
106
+ (obj: CljValue, key: CljValue, ...rest: CljValue[]) => {
107
+ const raw = extractJsTarget(obj, 'js/get') as Record<string, unknown>
108
+ const jsKey = resolveJsKey(key, 'js/get')
109
+ const result = raw[jsKey]
110
+ if (result === undefined && rest.length > 0) return rest[0]
111
+ return jsToClj(result)
112
+ }
113
+ ),
114
+ // (js/set! obj key val) — mutate a property; returns val
115
+ 'set!': v.nativeFnCtx(
116
+ 'js/set!',
117
+ (
118
+ ctx: EvaluationContext,
119
+ callEnv: Env,
120
+ obj: CljValue,
121
+ key: CljValue,
122
+ val: CljValue
123
+ ) => {
124
+ const raw = extractJsTarget(obj, 'js/set!') as Record<string, unknown>
125
+ const jsKey = resolveJsKey(key, 'js/set!')
126
+ raw[jsKey] = cljToJs(val, ctx, callEnv)
127
+ return val
128
+ }
129
+ ),
130
+ // (js/call fn & args) — call a JS function with no this binding
131
+ call: v.nativeFnCtx(
132
+ 'js/call',
133
+ (
134
+ ctx: EvaluationContext,
135
+ callEnv: Env,
136
+ fn: CljValue,
137
+ ...args: CljValue[]
138
+ ) => {
139
+ const rawFn = fn.kind === 'js-value' ? fn.value : undefined
140
+ if (typeof rawFn !== 'function') {
141
+ throw new EvaluationError(
142
+ `js/call: expected a js-value wrapping a function, got ${fn.kind}`,
143
+ { fn }
144
+ )
145
+ }
146
+ const jsArgs = args.map((a) => cljToJs(a, ctx, callEnv))
147
+ return jsToClj((rawFn as (...a: unknown[]) => unknown)(...jsArgs))
148
+ }
149
+ ),
150
+ // (js/typeof x) — typeof equivalent for CljValues.
151
+ // Clojure primitives have unambiguous JS typeof values; js-value delegates to
152
+ // the raw typeof. Functions and other Clojure types throw — they're not at the
153
+ // JS boundary.
154
+ typeof: v.nativeFn('js/typeof', (x: CljValue) => {
155
+ switch (x.kind) {
156
+ case 'nil':
157
+ return v.string('object') // typeof null === 'object'
158
+ case 'number':
159
+ return v.string('number')
160
+ case 'string':
161
+ return v.string('string')
162
+ case 'boolean':
163
+ return v.string('boolean')
164
+ case 'js-value':
165
+ return v.string(typeof x.value)
166
+ default:
167
+ throw new EvaluationError(
168
+ `js/typeof: cannot determine JS type of Clojure ${x.kind}`,
169
+ { x }
170
+ )
171
+ }
172
+ }),
173
+ // (js/instanceof? obj cls) — obj instanceof cls
174
+ 'instanceof?': v.nativeFn(
175
+ 'js/instanceof?',
176
+ (obj: CljValue, cls: CljValue) => {
177
+ if (obj.kind !== 'js-value') {
178
+ throw new EvaluationError(
179
+ `js/instanceof?: expected js-value, got ${obj.kind}`,
180
+ { obj }
181
+ )
182
+ }
183
+ if (cls.kind !== 'js-value') {
184
+ throw new EvaluationError(
185
+ `js/instanceof?: expected js-value constructor, got ${cls.kind}`,
186
+ { cls }
187
+ )
188
+ }
189
+ return v.boolean(
190
+ obj.value instanceof (cls.value as new (...a: unknown[]) => unknown)
191
+ )
192
+ }
193
+ ),
194
+ // (js/array? x) — Array.isArray on the raw value
195
+ 'array?': v.nativeFn('js/array?', (x: CljValue) => {
196
+ if (x.kind !== 'js-value') return v.boolean(false)
197
+ return v.boolean(Array.isArray(x.value))
198
+ }),
199
+ // (js/null? x) — true if x is nil (JS null comes in as CljNil)
200
+ 'null?': v.nativeFn('js/null?', (x: CljValue) => {
201
+ return v.boolean(x.kind === 'nil')
202
+ }),
203
+ // (js/undefined? x) — true if x is CljJsValue wrapping undefined
204
+ 'undefined?': v.nativeFn('js/undefined?', (x: CljValue) => {
205
+ return v.boolean(x.kind === 'js-value' && x.value === undefined)
206
+ }),
207
+ // (js/some? x) — true if x is neither null (nil) nor undefined
208
+ 'some?': v.nativeFn('js/some?', (x: CljValue) => {
209
+ if (x.kind === 'nil') return v.boolean(false)
210
+ if (x.kind === 'js-value' && x.value === undefined) return v.boolean(false)
211
+ return v.boolean(true)
212
+ }),
213
+ // (js/get-in obj path) / (js/get-in obj path not-found)
214
+ // Deep property access. path must be a CljVector of string/keyword/number keys.
215
+ 'get-in': v.nativeFn(
216
+ 'js/get-in',
217
+ (obj: CljValue, path: CljValue, ...rest: CljValue[]) => {
218
+ if (path.kind !== 'vector') {
219
+ throw new EvaluationError(
220
+ `js/get-in: path must be a vector, got ${path.kind}`,
221
+ { path }
222
+ )
223
+ }
224
+ // Validate root eagerly — nil root is always an error
225
+ if (obj.kind === 'nil') {
226
+ throw new EvaluationError(
227
+ 'js/get-in: cannot access properties on nil',
228
+ { obj }
229
+ )
230
+ }
231
+ const notFound = rest.length > 0 ? rest[0] : v.jsValue(undefined)
232
+ let current: CljValue = obj
233
+ for (const key of path.value) {
234
+ if (current.kind === 'nil') return notFound
235
+ if (current.kind === 'js-value' && current.value === undefined)
236
+ return notFound
237
+ const raw = extractJsTarget(current, 'js/get-in') as Record<
238
+ string,
239
+ unknown
240
+ >
241
+ const jsKey = resolveJsKey(key, 'js/get-in')
242
+ current = jsToClj((raw as Record<string, unknown>)[jsKey])
243
+ }
244
+ if (
245
+ current.kind === 'js-value' &&
246
+ current.value === undefined &&
247
+ rest.length > 0
248
+ ) {
249
+ return notFound
250
+ }
251
+ return current
252
+ }
253
+ ),
254
+ // (js/prop key) / (js/prop key not-found)
255
+ // Returns a single-arg function that reads the given property from an object.
256
+ // Use with map/filter: (map (js/prop "name") users)
257
+ prop: v.nativeFn('js/prop', (key: CljValue, ...rest: CljValue[]) => {
258
+ const notFound = rest.length > 0 ? rest[0] : v.nil()
259
+ return v.nativeFn('js/prop-accessor', (obj: CljValue) => {
260
+ const raw = extractJsTarget(obj, 'js/prop') as Record<string, unknown>
261
+ const jsKey = resolveJsKey(key, 'js/prop')
262
+ const result = raw[jsKey]
263
+ if (result === undefined) return notFound
264
+ return jsToClj(result)
265
+ })
266
+ }),
267
+ // (js/method key & partialArgs)
268
+ // Returns a function that calls the named method on an object, prepending any partial args.
269
+ // (map (js/method "trim") strings)
270
+ // (map (js/method "toFixed" 2) numbers)
271
+ method: v.nativeFn(
272
+ 'js/method',
273
+ (key: CljValue, ...partialArgs: CljValue[]) => {
274
+ return v.nativeFnCtx(
275
+ 'js/method-caller',
276
+ (
277
+ ctx: EvaluationContext,
278
+ callEnv: Env,
279
+ obj: CljValue,
280
+ ...callArgs: CljValue[]
281
+ ) => {
282
+ const rawObj = extractJsTarget(obj, 'js/method') as Record<
283
+ string,
284
+ unknown
285
+ >
286
+ const jsKey = resolveJsKey(key, 'js/method')
287
+ const method = rawObj[jsKey]
288
+ if (typeof method !== 'function') {
289
+ throw new EvaluationError(
290
+ `js/method: property '${jsKey}' is not callable`,
291
+ { jsKey }
292
+ )
293
+ }
294
+ const allArgs = [...partialArgs, ...callArgs].map((a) =>
295
+ cljToJs(a, ctx, callEnv)
296
+ )
297
+ return jsToClj(
298
+ (method as (...a: unknown[]) => unknown).apply(rawObj, allArgs)
299
+ )
300
+ }
301
+ )
302
+ }
303
+ ),
304
+ // (js/merge obj1 obj2 ...) — Object.assign into a fresh object
305
+ merge: v.nativeFnCtx(
306
+ 'js/merge',
307
+ (ctx: EvaluationContext, callEnv: Env, ...args: CljValue[]) => {
308
+ const result = Object.assign(
309
+ {},
310
+ ...args.map((a) => cljToJs(a, ctx, callEnv))
311
+ )
312
+ return v.jsValue(result)
313
+ }
314
+ ),
315
+ // (js/seq arr) — JS array → Clojure vector with elements converted via jsToClj
316
+ seq: v.nativeFn('js/seq', (arr: CljValue) => {
317
+ if (arr.kind !== 'js-value' || !Array.isArray(arr.value)) {
318
+ throw new EvaluationError(
319
+ `js/seq: expected a js-value wrapping an array, got ${arr.kind}`,
320
+ { arr }
321
+ )
322
+ }
323
+ return v.vector((arr.value as unknown[]).map(jsToClj))
324
+ }),
325
+ // (js/array & args) — variadic args → JS array as CljJsValue
326
+ array: v.nativeFnCtx(
327
+ 'js/array',
328
+ (ctx: EvaluationContext, callEnv: Env, ...args: CljValue[]) => {
329
+ return v.jsValue(args.map((a) => cljToJs(a, ctx, callEnv)))
330
+ }
331
+ ),
332
+ // (js/obj key val key val ...) — variadic key-val pairs → JS plain object as CljJsValue
333
+ obj: v.nativeFnCtx(
334
+ 'js/obj',
335
+ (ctx: EvaluationContext, callEnv: Env, ...args: CljValue[]) => {
336
+ if (args.length % 2 !== 0) {
337
+ throw new EvaluationError('js/obj: requires even number of arguments', {
338
+ count: args.length,
339
+ })
340
+ }
341
+ const result: Record<string, unknown> = {}
342
+ for (let i = 0; i < args.length; i += 2) {
343
+ const jsKey = resolveJsKey(args[i], 'js/obj')
344
+ result[jsKey] = cljToJs(args[i + 1], ctx, callEnv)
345
+ }
346
+ return v.jsValue(result)
347
+ }
348
+ ),
349
+ // (js/keys obj) — Object.keys equivalent → Clojure vector of strings
350
+ keys: v.nativeFn('js/keys', (obj: CljValue) => {
351
+ const raw = extractJsTarget(obj, 'js/keys') as Record<string, unknown>
352
+ return v.vector(Object.keys(raw).map(v.string))
353
+ }),
354
+ // (js/values obj) — Object.values equivalent → Clojure vector, elements via jsToClj
355
+ values: v.nativeFn('js/values', (obj: CljValue) => {
356
+ const raw = extractJsTarget(obj, 'js/values') as Record<string, unknown>
357
+ return v.vector(Object.values(raw).map(jsToClj))
358
+ }),
359
+ // (js/entries obj) — Object.entries equivalent → vector of [key value] pairs
360
+ entries: v.nativeFn('js/entries', (obj: CljValue) => {
361
+ const raw = extractJsTarget(obj, 'js/entries') as Record<string, unknown>
362
+ return v.vector(
363
+ Object.entries(raw).map(([k, val]) =>
364
+ v.vector([v.string(k), jsToClj(val)])
365
+ )
366
+ )
367
+ }),
368
+ }
369
+
370
+ // ---------------------------------------------------------------------------
371
+ // Module
372
+ // ---------------------------------------------------------------------------
373
+
374
+ export function makeJsModule(): RuntimeModule {
375
+ return {
376
+ id: 'conjure-js/js-namespace',
377
+ declareNs: [
378
+ {
379
+ name: 'clojure.core',
380
+ vars(_ctx): VarMap {
381
+ const map = new Map<string, VarDeclaration>()
382
+ for (const [name, fn] of Object.entries(coreNativeFunctions)) {
383
+ map.set(name, { value: fn })
384
+ }
385
+ return map
386
+ },
387
+ },
388
+ {
389
+ name: 'js',
390
+ vars(_ctx): VarMap {
391
+ const map = new Map<string, VarDeclaration>()
392
+
393
+ for (const [name, fn] of Object.entries(moduleNativeFunctions)) {
394
+ map.set(name, { value: fn })
395
+ }
396
+
397
+ return map
398
+ },
399
+ },
400
+ ],
401
+ }
402
+ }
@@ -1,4 +1,5 @@
1
- import { isKeyword, isList, isSymbol } from './assertions'
1
+ import { is } from './assertions'
2
+ import { tokenKeywords } from './keywords'
2
3
  import type { CljValue, Token, TokenSymbol } from './types'
3
4
 
4
5
  // ---------------------------------------------------------------------------
@@ -10,7 +11,7 @@ import type { CljValue, Token, TokenSymbol } from './types'
10
11
  // Looks for the pattern: LParen Symbol("ns") Symbol(name) at the top of the
11
12
  // token stream. Returns the namespace name or null.
12
13
  export function extractNsNameFromTokens(tokens: Token[]): string | null {
13
- const meaningful = tokens.filter((t) => t.kind !== 'Comment')
14
+ const meaningful = tokens.filter((t) => t.kind !== tokenKeywords.Comment)
14
15
  if (meaningful.length < 3) return null
15
16
  if (meaningful[0].kind !== 'LParen') return null
16
17
  if (meaningful[1].kind !== 'Symbol' || meaningful[1].value !== 'ns')
@@ -27,43 +28,50 @@ export function extractAliasMapFromTokens(
27
28
  ): Map<string, string> {
28
29
  const aliases = new Map<string, string>()
29
30
  const meaningful = tokens.filter(
30
- (t) => t.kind !== 'Comment' && t.kind !== 'Whitespace'
31
+ (t) =>
32
+ t.kind !== tokenKeywords.Comment && t.kind !== tokenKeywords.Whitespace
31
33
  )
32
34
  if (meaningful.length < 3) return aliases
33
- if (meaningful[0].kind !== 'LParen') return aliases
34
- if (meaningful[1].kind !== 'Symbol' || meaningful[1].value !== 'ns')
35
+ if (meaningful[0].kind !== tokenKeywords.LParen) return aliases
36
+ if (
37
+ meaningful[1].kind !== tokenKeywords.Symbol ||
38
+ meaningful[1].value !== 'ns'
39
+ )
35
40
  return aliases
36
41
 
37
42
  let i = 3 // skip ( ns <name>
38
43
  let depth = 1
39
44
  while (i < meaningful.length && depth > 0) {
40
45
  const tok = meaningful[i]
41
- if (tok.kind === 'LParen') {
46
+ if (tok.kind === tokenKeywords.LParen) {
42
47
  depth++
43
48
  i++
44
49
  continue
45
50
  }
46
- if (tok.kind === 'RParen') {
51
+ if (tok.kind === tokenKeywords.RParen) {
47
52
  depth--
48
53
  i++
49
54
  continue
50
55
  }
51
- if (tok.kind === 'LBracket') {
56
+ if (tok.kind === tokenKeywords.LBracket) {
52
57
  let j = i + 1
53
58
  let nsSym: string | null = null
54
- while (j < meaningful.length && meaningful[j].kind !== 'RBracket') {
59
+ while (
60
+ j < meaningful.length &&
61
+ meaningful[j].kind !== tokenKeywords.RBracket
62
+ ) {
55
63
  const t = meaningful[j]
56
- if (t.kind === 'Symbol' && nsSym === null) {
64
+ if (t.kind === tokenKeywords.Symbol && nsSym === null) {
57
65
  nsSym = t.value
58
66
  }
59
67
  if (
60
- t.kind === 'Keyword' &&
68
+ t.kind === tokenKeywords.Keyword &&
61
69
  (t.value === ':as' || t.value === ':as-alias')
62
70
  ) {
63
71
  j++
64
72
  if (
65
73
  j < meaningful.length &&
66
- meaningful[j].kind === 'Symbol' &&
74
+ meaningful[j].kind === tokenKeywords.Symbol &&
67
75
  nsSym
68
76
  ) {
69
77
  aliases.set((meaningful[j] as TokenSymbol).value, nsSym)
@@ -80,12 +88,12 @@ export function extractAliasMapFromTokens(
80
88
  function findNsForm(forms: CljValue[]) {
81
89
  const nsForm = forms.find(
82
90
  (f) =>
83
- isList(f) &&
91
+ is.list(f) &&
84
92
  f.value.length > 0 &&
85
- isSymbol(f.value[0]) &&
93
+ is.symbol(f.value[0]) &&
86
94
  f.value[0].name === 'ns'
87
95
  )
88
- if (!nsForm || !isList(nsForm)) return null
96
+ if (!nsForm || !is.list(nsForm)) return null
89
97
  return nsForm
90
98
  }
91
99
 
@@ -96,8 +104,8 @@ export function extractRequireClauses(forms: CljValue[]): CljValue[][] {
96
104
  for (let i = 2; i < nsForm.value.length; i++) {
97
105
  const clause = nsForm.value[i]
98
106
  if (
99
- isList(clause) &&
100
- isKeyword(clause.value[0]) &&
107
+ is.list(clause) &&
108
+ is.keyword(clause.value[0]) &&
101
109
  clause.value[0].name === ':require'
102
110
  ) {
103
111
  clauses.push(clause.value.slice(1))
@@ -1,4 +1,5 @@
1
- import type { CljValue, Pos } from './types'
1
+ import { EvaluationError } from './errors'
2
+ import type { CljList, CljValue, Pos } from './types'
2
3
 
3
4
  export function setPos(val: CljValue, pos: Pos): void {
4
5
  Object.defineProperty(val, '_pos', {
@@ -37,9 +38,28 @@ export function formatErrorContext(
37
38
  ): string {
38
39
  const { line, col, lineText } = getLineCol(source, pos.start)
39
40
  const absLine = line + (opts?.lineOffset ?? 0)
40
- const absCol = line === 1 ? col + (opts?.colOffset ?? 0) : col
41
+ const absCol = line === 1 ? col + (opts?.colOffset ?? 0) : col
41
42
  const span = Math.max(1, pos.end - pos.start)
42
43
  // Caret uses raw col so it aligns with the displayed lineText snippet.
43
44
  const caret = ' '.repeat(col) + '^'.repeat(span)
44
45
  return `\n at line ${absLine}, col ${absCol + 1}:\n ${lineText}\n ${caret}`
45
46
  }
47
+
48
+ /**
49
+ * Mutable function to hydrate the inner position of an EvaluationError
50
+ * If the error has an argIndex in its data and no position,
51
+ * if it already has a position stamped, do nothing.
52
+ */
53
+ export function maybeHydrateErrorPos(error: unknown, list: CljList) {
54
+ if (
55
+ error instanceof EvaluationError &&
56
+ error.data?.argIndex !== undefined &&
57
+ !error.pos
58
+ ) {
59
+ const argForm = list.value[(error.data.argIndex as number) + 1]
60
+ if (argForm) {
61
+ const pos = getPos(argForm)
62
+ if (pos) error.pos = pos
63
+ }
64
+ }
65
+ }