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.
Files changed (77) hide show
  1. package/dist-cli/conjure-js.mjs +9360 -5298
  2. package/dist-vite-plugin/index.mjs +9463 -5185
  3. package/package.json +3 -1
  4. package/src/bin/cli.ts +2 -2
  5. package/src/bin/nrepl-symbol.ts +150 -0
  6. package/src/bin/nrepl.ts +289 -167
  7. package/src/bin/version.ts +1 -1
  8. package/src/clojure/core.clj +757 -29
  9. package/src/clojure/core.clj.d.ts +75 -131
  10. package/src/clojure/generated/builtin-namespace-registry.ts +4 -0
  11. package/src/clojure/generated/clojure-core-source.ts +758 -29
  12. package/src/clojure/generated/clojure-set-source.ts +136 -0
  13. package/src/clojure/generated/clojure-walk-source.ts +72 -0
  14. package/src/clojure/set.clj +132 -0
  15. package/src/clojure/set.clj.d.ts +20 -0
  16. package/src/clojure/string.clj.d.ts +14 -0
  17. package/src/clojure/walk.clj +68 -0
  18. package/src/clojure/walk.clj.d.ts +7 -0
  19. package/src/core/assertions.ts +114 -6
  20. package/src/core/bootstrap.ts +337 -0
  21. package/src/core/conversions.ts +48 -31
  22. package/src/core/core-module.ts +303 -0
  23. package/src/core/env.ts +20 -6
  24. package/src/core/evaluator/apply.ts +40 -25
  25. package/src/core/evaluator/arity.ts +8 -8
  26. package/src/core/evaluator/async-evaluator.ts +565 -0
  27. package/src/core/evaluator/collections.ts +28 -5
  28. package/src/core/evaluator/destructure.ts +180 -69
  29. package/src/core/evaluator/dispatch.ts +12 -14
  30. package/src/core/evaluator/evaluate.ts +22 -20
  31. package/src/core/evaluator/expand.ts +45 -15
  32. package/src/core/evaluator/form-parsers.ts +178 -0
  33. package/src/core/evaluator/index.ts +7 -9
  34. package/src/core/evaluator/js-interop.ts +189 -0
  35. package/src/core/evaluator/quasiquote.ts +14 -8
  36. package/src/core/evaluator/recur-check.ts +6 -6
  37. package/src/core/evaluator/special-forms.ts +234 -191
  38. package/src/core/factories.ts +182 -3
  39. package/src/core/index.ts +54 -4
  40. package/src/core/module.ts +136 -0
  41. package/src/core/ns-forms.ts +107 -0
  42. package/src/core/printer.ts +371 -11
  43. package/src/core/reader.ts +84 -33
  44. package/src/core/registry.ts +209 -0
  45. package/src/core/runtime.ts +376 -0
  46. package/src/core/session.ts +253 -487
  47. package/src/core/stdlib/arithmetic.ts +528 -194
  48. package/src/core/stdlib/async-fns.ts +132 -0
  49. package/src/core/stdlib/atoms.ts +291 -56
  50. package/src/core/stdlib/errors.ts +54 -50
  51. package/src/core/stdlib/hof.ts +82 -166
  52. package/src/core/stdlib/js-namespace.ts +344 -0
  53. package/src/core/stdlib/lazy.ts +34 -0
  54. package/src/core/stdlib/maps-sets.ts +322 -0
  55. package/src/core/stdlib/meta.ts +61 -30
  56. package/src/core/stdlib/predicates.ts +325 -187
  57. package/src/core/stdlib/regex.ts +126 -98
  58. package/src/core/stdlib/seq.ts +564 -0
  59. package/src/core/stdlib/strings.ts +164 -135
  60. package/src/core/stdlib/transducers.ts +95 -100
  61. package/src/core/stdlib/utils.ts +292 -130
  62. package/src/core/stdlib/vars.ts +27 -27
  63. package/src/core/stdlib/vectors.ts +122 -0
  64. package/src/core/tokenizer.ts +2 -2
  65. package/src/core/transformations.ts +117 -9
  66. package/src/core/types.ts +98 -2
  67. package/src/host/node-host-module.ts +74 -0
  68. package/src/{vite-plugin-clj/nrepl-relay.ts → nrepl/relay.ts} +72 -11
  69. package/src/vite-plugin-clj/codegen.ts +87 -95
  70. package/src/vite-plugin-clj/index.ts +178 -23
  71. package/src/vite-plugin-clj/namespace-utils.ts +39 -0
  72. package/src/vite-plugin-clj/static-analysis.ts +211 -0
  73. package/src/clojure/demo.clj +0 -72
  74. package/src/clojure/demo.clj.d.ts +0 -0
  75. package/src/core/core-env.ts +0 -61
  76. package/src/core/stdlib/collections.ts +0 -739
  77. package/src/host/node.ts +0 -55
@@ -3,17 +3,24 @@ import type {
3
3
  Arity,
4
4
  CljAtom,
5
5
  CljBoolean,
6
+ CljCons,
7
+ CljDelay,
6
8
  CljFunction,
9
+ CljJsValue,
7
10
  CljKeyword,
11
+ CljLazySeq,
8
12
  CljList,
9
13
  CljMacro,
10
14
  CljMap,
11
15
  CljMultiMethod,
16
+ CljNamespace,
12
17
  CljNativeFunction,
13
18
  CljNil,
14
19
  CljNumber,
20
+ CljPending,
15
21
  CljReduced,
16
22
  CljRegex,
23
+ CljSet,
17
24
  CljString,
18
25
  CljSymbol,
19
26
  CljValue,
@@ -39,6 +46,7 @@ export const cljSymbol = <T extends string>(name: T) =>
39
46
  ({ kind: 'symbol', name }) as const satisfies CljSymbol
40
47
  export const cljList = <T extends CljValue[]>(value: T) =>
41
48
  ({ kind: 'list', value }) as const satisfies CljList
49
+ export const cljSet = (values: CljValue[]): CljSet => ({ kind: 'set', values })
42
50
  export const cljVector = <T extends CljValue[]>(value: T) =>
43
51
  ({ kind: 'vector', value }) as const satisfies CljVector
44
52
  export const cljMap = <T extends [CljValue, CljValue][]>(entries: T) =>
@@ -71,7 +79,11 @@ export const cljNativeFunction = <
71
79
  ({ kind: 'native-function', name, fn }) as const satisfies CljNativeFunction
72
80
  export const cljNativeFunctionWithContext = <
73
81
  T extends string,
74
- U extends (ctx: EvaluationContext, callEnv: Env, ...args: CljValue[]) => CljValue,
82
+ U extends (
83
+ ctx: EvaluationContext,
84
+ callEnv: Env,
85
+ ...args: CljValue[]
86
+ ) => CljValue,
75
87
  >(
76
88
  name: T,
77
89
  fn: U
@@ -110,8 +122,12 @@ export const cljRegex = (pattern: string, flags: string = ''): CljRegex => ({
110
122
  flags,
111
123
  })
112
124
 
113
- export const cljVar = (ns: string, name: string, value: CljValue, meta?: CljMap): CljVar =>
114
- ({ kind: 'var', ns, name, value, meta })
125
+ export const cljVar = (
126
+ ns: string,
127
+ name: string,
128
+ value: CljValue,
129
+ meta?: CljMap
130
+ ): CljVar => ({ kind: 'var', ns, name, value, meta })
115
131
 
116
132
  export const cljAtom = (value: CljValue): CljAtom => ({ kind: 'atom', value })
117
133
  export const cljReduced = (value: CljValue): CljReduced => ({
@@ -122,6 +138,50 @@ export const cljVolatile = (value: CljValue): CljVolatile => ({
122
138
  kind: 'volatile',
123
139
  value,
124
140
  })
141
+ export const cljDelay = (thunk: () => CljValue): CljDelay => ({
142
+ kind: 'delay',
143
+ thunk,
144
+ realized: false,
145
+ })
146
+ export const cljLazySeq = (thunk: () => CljValue): CljLazySeq => ({
147
+ kind: 'lazy-seq',
148
+ thunk,
149
+ realized: false,
150
+ })
151
+ export const cljCons = (head: CljValue, tail: CljValue): CljCons => ({
152
+ kind: 'cons',
153
+ head,
154
+ tail,
155
+ })
156
+ export const cljNamespace = (name: string): CljNamespace => ({
157
+ kind: 'namespace',
158
+ name,
159
+ vars: new Map(),
160
+ aliases: new Map(),
161
+ readerAliases: new Map(),
162
+ })
163
+
164
+ export const cljJsValue = (value: unknown): CljJsValue => ({
165
+ kind: 'js-value',
166
+ value,
167
+ })
168
+
169
+ // --- ASYNC (experimental) ---
170
+ export const cljPending = (promise: Promise<CljValue>): CljPending => {
171
+ const pending: CljPending = { kind: 'pending', promise }
172
+ // Track fulfillment so the printer can show #<Pending @val> when already settled.
173
+ promise.then(
174
+ (v) => {
175
+ pending.resolved = true
176
+ pending.resolvedValue = v
177
+ },
178
+ () => {
179
+ /* rejection — no resolved state; printer shows #<Pending> */
180
+ }
181
+ )
182
+ return pending
183
+ }
184
+ // --- END ASYNC ---
125
185
 
126
186
  export const withDoc = <T extends CljNativeFunction | CljFunction>(
127
187
  fn: T,
@@ -142,6 +202,57 @@ export const withDoc = <T extends CljNativeFunction | CljFunction>(
142
202
  ]),
143
203
  })
144
204
 
205
+ // ---------------------------------------------------------------------------
206
+ // NativeFnBuilder — fluent construction API for native functions
207
+ //
208
+ // Satisfies CljNativeFunction structurally, so it can be stored in any
209
+ // registry or record that expects CljNativeFunction — no .build() call needed.
210
+ // ---------------------------------------------------------------------------
211
+
212
+ export type NativeFnBuilder = CljNativeFunction & {
213
+ /** Attach doc-string and optional arglists metadata. */
214
+ doc(text: string, arglists?: string[][]): NativeFnBuilder
215
+ }
216
+
217
+ function buildDocMeta(text: string, arglists?: string[][]): CljMap {
218
+ return cljMap([
219
+ [cljKeyword(':doc'), cljString(text)],
220
+ ...(arglists
221
+ ? ([
222
+ [
223
+ cljKeyword(':arglists'),
224
+ cljVector(arglists.map((args) => cljVector(args.map(cljSymbol)))),
225
+ ],
226
+ ] as [CljValue, CljValue][])
227
+ : []),
228
+ ])
229
+ }
230
+
231
+ function makeNativeFnBuilder(def: CljNativeFunction): NativeFnBuilder {
232
+ // Reconstruct a plain CljNativeFunction explicitly so that the spread
233
+ // inside .doc() never accidentally picks up builder methods from a previous
234
+ // clone round.
235
+ const plain: CljNativeFunction = {
236
+ kind: 'native-function',
237
+ name: def.name,
238
+ fn: def.fn,
239
+ ...(def.fnWithContext !== undefined
240
+ ? { fnWithContext: def.fnWithContext }
241
+ : {}),
242
+ ...(def.meta !== undefined ? { meta: def.meta } : {}),
243
+ }
244
+
245
+ return {
246
+ ...plain,
247
+ doc(text: string, arglists?: string[][]): NativeFnBuilder {
248
+ return makeNativeFnBuilder({
249
+ ...plain,
250
+ meta: buildDocMeta(text, arglists),
251
+ })
252
+ },
253
+ }
254
+ }
255
+
145
256
  export const cljMultiMethod = (
146
257
  name: string,
147
258
  dispatchFn: CljFunction | CljNativeFunction,
@@ -157,3 +268,71 @@ export const cljMultiMethod = (
157
268
  methods,
158
269
  defaultMethod,
159
270
  })
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // v — unified value factory namespace
274
+ //
275
+ // Mirrors the cljXxx standalone functions but collected under one object so
276
+ // stdlib files need only a single import. Primitive factories are thin
277
+ // aliases; nativeFn / nativeFnCtx return a NativeFnBuilder with .doc().
278
+ // ---------------------------------------------------------------------------
279
+
280
+ export const v = {
281
+ // primitives
282
+ number: cljNumber,
283
+ string: cljString,
284
+ boolean: cljBoolean,
285
+ keyword: cljKeyword,
286
+ nil: cljNil,
287
+ symbol: cljSymbol,
288
+ kw: cljKeyword,
289
+
290
+ // collections
291
+ list: cljList,
292
+ vector: cljVector,
293
+ map: cljMap,
294
+ set: cljSet,
295
+ cons: cljCons,
296
+
297
+ // callables
298
+ function: cljFunction,
299
+ multiArityFunction: cljMultiArityFunction,
300
+ macro: cljMacro,
301
+ multiArityMacro: cljMultiArityMacro,
302
+ multiMethod: cljMultiMethod,
303
+
304
+ // fluent native function builders
305
+ nativeFn(
306
+ name: string,
307
+ fn: (...args: CljValue[]) => CljValue
308
+ ): NativeFnBuilder {
309
+ return makeNativeFnBuilder({ kind: 'native-function', name, fn })
310
+ },
311
+ nativeFnCtx(
312
+ name: string,
313
+ fn: (ctx: EvaluationContext, callEnv: Env, ...args: CljValue[]) => CljValue
314
+ ): NativeFnBuilder {
315
+ return makeNativeFnBuilder({
316
+ kind: 'native-function',
317
+ name,
318
+ fn: () => {
319
+ throw new EvaluationError('Native function called without context', {
320
+ name,
321
+ })
322
+ },
323
+ fnWithContext: fn,
324
+ })
325
+ },
326
+
327
+ // other value types
328
+ var: cljVar,
329
+ atom: cljAtom,
330
+ regex: cljRegex,
331
+ reduced: cljReduced,
332
+ volatile: cljVolatile,
333
+ delay: cljDelay,
334
+ lazySeq: cljLazySeq,
335
+ namespace: cljNamespace,
336
+ pending: cljPending,
337
+ jsValue: cljJsValue,
338
+ }
package/src/core/index.ts CHANGED
@@ -1,12 +1,35 @@
1
1
  // Session API
2
- export { createSession, snapshotSession, createSessionFromSnapshot } from './session'
3
- export type { Session, SessionSnapshot } from './session'
2
+ export {
3
+ createSession,
4
+ snapshotSession,
5
+ createSessionFromSnapshot,
6
+ } from './session'
7
+ export type { Session, SessionSnapshot, SessionOptions } from './session'
8
+
9
+ // Runtime API (advanced embedding)
10
+ export { createRuntime, restoreRuntime } from './runtime'
11
+ export type { Runtime, RuntimeSnapshot, RuntimeOptions } from './runtime'
12
+
13
+ // Module system
14
+ export { resolveModuleOrder } from './module'
15
+ export { makeCoreModule } from './core-module'
16
+ export type {
17
+ RuntimeModule,
18
+ NamespaceDeclaration,
19
+ VarDeclaration,
20
+ VarMap,
21
+ ModuleContext,
22
+ } from './module'
4
23
 
5
24
  // Conversions
6
25
  export { cljToJs, jsToClj, ConversionError } from './conversions'
26
+ export type { FunctionApplier } from './conversions'
7
27
 
8
28
  // Evaluator
9
- export { applyFunction, applyMacro, evaluateWithMeasurements } from './evaluator'
29
+ export {
30
+ applyFunction,
31
+ evaluateWithMeasurements,
32
+ } from './evaluator'
10
33
 
11
34
  // Errors
12
35
  export { EvaluationError, ReaderError, TokenizerError } from './errors'
@@ -25,9 +48,14 @@ export {
25
48
  cljFunction,
26
49
  cljMultiArityFunction,
27
50
  cljNativeFunction,
51
+ cljNativeFunctionWithContext,
28
52
  cljMacro,
29
53
  cljMultiArityMacro,
30
54
  cljVar,
55
+ cljNamespace,
56
+ cljPending,
57
+ // fluent builder — use v.nativeFn / v.nativeFnCtx for module authoring
58
+ v,
31
59
  } from './factories'
32
60
 
33
61
  // Assertions
@@ -47,6 +75,7 @@ export {
47
75
  isCollection,
48
76
  isEqual,
49
77
  isVar,
78
+ isNamespace,
50
79
  } from './assertions'
51
80
 
52
81
  // Env
@@ -58,8 +87,26 @@ export { valueToString } from './transformations'
58
87
  // Printer
59
88
  export { printString } from './printer'
60
89
 
61
- // Tokenizer (public for tooling consumers)
90
+ // Tokenizer + Reader (public for tooling consumers and EDN parsing)
62
91
  export { tokenize } from './tokenizer'
92
+ export { readForms } from './reader'
93
+
94
+ // readString — parse a single EDN source string to a CljValue.
95
+ // Equivalent to Clojure's read-string. Useful for deserialising results
96
+ // returned by remote nodes over the mesh wire.
97
+ import { tokenize as _tokenize } from './tokenizer'
98
+ import { readForms as _readForms } from './reader'
99
+ import type { CljValue as _CljValue } from './types'
100
+
101
+ export function readString(source: string): _CljValue {
102
+ const tokens = _tokenize(source)
103
+ const forms = _readForms(tokens)
104
+ if (forms.length === 0) throw new Error('readString: empty input')
105
+ return forms[0]
106
+ }
107
+
108
+ // JS interop — import map type for user-defined session entrypoints
109
+ export type ImportMap = Record<string, unknown>
63
110
 
64
111
  // Types
65
112
  export type {
@@ -78,6 +125,9 @@ export type {
78
125
  CljMacro,
79
126
  CljVar,
80
127
  CljNamespace,
128
+ CljPending, // experimental
81
129
  Env,
82
130
  Arity,
131
+ IOContext,
132
+ EvaluationContext,
83
133
  } from './types'
@@ -0,0 +1,136 @@
1
+ import type { CljMap, CljNamespace, CljValue } from './types'
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // RuntimeModule — declarative unit of capability installation into a runtime.
5
+ // Modules are plain data. `vars(ctx)` is the only computation, called once per
6
+ // namespace declaration at install time in dependency order.
7
+ //
8
+ // Additive namespace model: multiple modules may contribute vars to the same
9
+ // namespace. The uniqueness invariant is at the (namespace, varName) level —
10
+ // two modules cannot declare the same var in the same namespace.
11
+ //
12
+ // `dependsOn` lists namespace names. A module that lists 'clojure.core' will
13
+ // be installed after ALL modules that contribute vars to 'clojure.core'.
14
+ // If no installed module provides a depended-on namespace, resolveModuleOrder
15
+ // throws immediately with a clear message.
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export type RuntimeModule = {
19
+ id: string
20
+ dependsOn?: string[] // namespace names that must be fully installed before this module
21
+ declareNs: NamespaceDeclaration[]
22
+ }
23
+
24
+ export type NamespaceDeclaration = {
25
+ name: string // the namespace this declaration contributes vars into
26
+ vars(ctx: ModuleContext): VarMap
27
+ }
28
+
29
+ export type VarDeclaration = {
30
+ value: CljValue
31
+ meta?: CljMap
32
+ dynamic?: boolean
33
+ macro?: boolean
34
+ }
35
+
36
+ export type VarMap = Map<string, VarDeclaration>
37
+
38
+ export type ModuleContext = {
39
+ // Read-only view of already-installed state at construction time
40
+ getVar(ns: string, name: string): CljValue | null
41
+ getNamespace(name: string): CljNamespace | null
42
+ }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // resolveModuleOrder — returns modules in dependency-safe install order.
46
+ //
47
+ // Algorithm (Kahn's topological sort):
48
+ // 1. Build nsProviders: namespace → [module IDs that contribute to it]
49
+ // 2. For each module M and each dep namespace in M.dependsOn:
50
+ // - If no provider exists → throw (missing dep)
51
+ // - For each provider P where P.id ≠ M.id → add edge P → M
52
+ // 3. Run Kahn's algorithm; throw on cycle
53
+ // ---------------------------------------------------------------------------
54
+
55
+ export function resolveModuleOrder(
56
+ modules: RuntimeModule[],
57
+ existingNamespaces?: Set<string>
58
+ ): RuntimeModule[] {
59
+ // Index modules by ID for quick lookup
60
+ const byId = new Map<string, RuntimeModule>()
61
+ for (const m of modules) {
62
+ if (byId.has(m.id)) {
63
+ throw new Error(`Duplicate module ID: '${m.id}'`)
64
+ }
65
+ byId.set(m.id, m)
66
+ }
67
+
68
+ // Build namespace → provider module IDs map
69
+ const nsProviders = new Map<string, string[]>()
70
+ for (const m of modules) {
71
+ for (const decl of m.declareNs) {
72
+ const providers = nsProviders.get(decl.name) ?? []
73
+ providers.push(m.id)
74
+ nsProviders.set(decl.name, providers)
75
+ }
76
+ }
77
+
78
+ // Build dependency edges: providerID → [dependentIDs]
79
+ // inDegree tracks how many unresolved dependencies each module has
80
+ const graph = new Map<string, string[]>()
81
+ const inDegree = new Map<string, number>()
82
+ for (const m of modules) {
83
+ graph.set(m.id, [])
84
+ inDegree.set(m.id, 0)
85
+ }
86
+
87
+ for (const m of modules) {
88
+ for (const depNs of m.dependsOn ?? []) {
89
+ // A dep is satisfied if some already-installed namespace provides it
90
+ if (existingNamespaces?.has(depNs)) continue
91
+
92
+ const providers = nsProviders.get(depNs)
93
+ if (!providers || providers.length === 0) {
94
+ throw new Error(
95
+ `No module provides namespace '${depNs}' (required by '${m.id}')`
96
+ )
97
+ }
98
+ for (const providerId of providers) {
99
+ // Self-exclusion: a module that both contributes to and depends on the
100
+ // same namespace does not create a self-loop.
101
+ if (providerId === m.id) continue
102
+ graph.get(providerId)!.push(m.id)
103
+ inDegree.set(m.id, inDegree.get(m.id)! + 1)
104
+ }
105
+ }
106
+ }
107
+
108
+ // Kahn's algorithm — start with nodes that have no unresolved deps
109
+ const queue: string[] = []
110
+ for (const [id, degree] of inDegree) {
111
+ if (degree === 0) queue.push(id)
112
+ }
113
+
114
+ const result: RuntimeModule[] = []
115
+ while (queue.length > 0) {
116
+ const id = queue.shift()!
117
+ result.push(byId.get(id)!)
118
+ for (const dependentId of graph.get(id)!) {
119
+ const newDegree = inDegree.get(dependentId)! - 1
120
+ inDegree.set(dependentId, newDegree)
121
+ if (newDegree === 0) queue.push(dependentId)
122
+ }
123
+ }
124
+
125
+ // If not all modules were processed, there is a cycle
126
+ if (result.length !== modules.length) {
127
+ const unprocessed = modules
128
+ .map((m) => m.id)
129
+ .filter((id) => !result.some((m) => m.id === id))
130
+ throw new Error(
131
+ `Circular dependency detected in module system. Modules in cycle: ${unprocessed.join(', ')}`
132
+ )
133
+ }
134
+
135
+ return result
136
+ }
@@ -0,0 +1,107 @@
1
+ import { isKeyword, isList, isSymbol } from './assertions'
2
+ import type { CljValue, Token, TokenSymbol } from './types'
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Token scan helpers — lightweight pre-parse scans for ns form metadata.
6
+ // These are semantic (module declaration analysis), not syntactic (parsing).
7
+ // Exported so session.evaluate and runtime.loadFile can reuse them.
8
+ // ---------------------------------------------------------------------------
9
+
10
+ // Looks for the pattern: LParen Symbol("ns") Symbol(name) at the top of the
11
+ // token stream. Returns the namespace name or null.
12
+ export function extractNsNameFromTokens(tokens: Token[]): string | null {
13
+ const meaningful = tokens.filter((t) => t.kind !== 'Comment')
14
+ if (meaningful.length < 3) return null
15
+ if (meaningful[0].kind !== 'LParen') return null
16
+ if (meaningful[1].kind !== 'Symbol' || meaningful[1].value !== 'ns')
17
+ return null
18
+ if (meaningful[2].kind !== 'Symbol') return null
19
+ return meaningful[2].value
20
+ }
21
+
22
+ // Returns Map { 'alias' -> 'full.ns.name' } for all [some.ns :as alias] and
23
+ // [some.ns :as-alias alias] specs found in the ns form's :require clauses.
24
+ // Runs before readForms so the reader can expand ::alias/foo at read time.
25
+ export function extractAliasMapFromTokens(
26
+ tokens: Token[]
27
+ ): Map<string, string> {
28
+ const aliases = new Map<string, string>()
29
+ const meaningful = tokens.filter(
30
+ (t) => t.kind !== 'Comment' && t.kind !== 'Whitespace'
31
+ )
32
+ 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
+ return aliases
36
+
37
+ let i = 3 // skip ( ns <name>
38
+ let depth = 1
39
+ while (i < meaningful.length && depth > 0) {
40
+ const tok = meaningful[i]
41
+ if (tok.kind === 'LParen') {
42
+ depth++
43
+ i++
44
+ continue
45
+ }
46
+ if (tok.kind === 'RParen') {
47
+ depth--
48
+ i++
49
+ continue
50
+ }
51
+ if (tok.kind === 'LBracket') {
52
+ let j = i + 1
53
+ let nsSym: string | null = null
54
+ while (j < meaningful.length && meaningful[j].kind !== 'RBracket') {
55
+ const t = meaningful[j]
56
+ if (t.kind === 'Symbol' && nsSym === null) {
57
+ nsSym = t.value
58
+ }
59
+ if (
60
+ t.kind === 'Keyword' &&
61
+ (t.value === ':as' || t.value === ':as-alias')
62
+ ) {
63
+ j++
64
+ if (
65
+ j < meaningful.length &&
66
+ meaningful[j].kind === 'Symbol' &&
67
+ nsSym
68
+ ) {
69
+ aliases.set((meaningful[j] as TokenSymbol).value, nsSym)
70
+ }
71
+ }
72
+ j++
73
+ }
74
+ }
75
+ i++
76
+ }
77
+ return aliases
78
+ }
79
+
80
+ function findNsForm(forms: CljValue[]) {
81
+ const nsForm = forms.find(
82
+ (f) =>
83
+ isList(f) &&
84
+ f.value.length > 0 &&
85
+ isSymbol(f.value[0]) &&
86
+ f.value[0].name === 'ns'
87
+ )
88
+ if (!nsForm || !isList(nsForm)) return null
89
+ return nsForm
90
+ }
91
+
92
+ export function extractRequireClauses(forms: CljValue[]): CljValue[][] {
93
+ const nsForm = findNsForm(forms)
94
+ if (!nsForm) return []
95
+ const clauses: CljValue[][] = []
96
+ for (let i = 2; i < nsForm.value.length; i++) {
97
+ const clause = nsForm.value[i]
98
+ if (
99
+ isList(clause) &&
100
+ isKeyword(clause.value[0]) &&
101
+ clause.value[0].name === ':require'
102
+ ) {
103
+ clauses.push(clause.value.slice(1))
104
+ }
105
+ }
106
+ return clauses
107
+ }