conjure-js 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cli/conjure-js.mjs +9360 -5298
- package/dist-vite-plugin/index.mjs +9463 -5185
- package/package.json +3 -1
- package/src/bin/cli.ts +2 -2
- package/src/bin/nrepl-symbol.ts +150 -0
- package/src/bin/nrepl.ts +289 -167
- package/src/bin/version.ts +1 -1
- package/src/clojure/core.clj +757 -29
- package/src/clojure/core.clj.d.ts +75 -131
- package/src/clojure/generated/builtin-namespace-registry.ts +4 -0
- package/src/clojure/generated/clojure-core-source.ts +758 -29
- package/src/clojure/generated/clojure-set-source.ts +136 -0
- package/src/clojure/generated/clojure-walk-source.ts +72 -0
- package/src/clojure/set.clj +132 -0
- package/src/clojure/set.clj.d.ts +20 -0
- package/src/clojure/string.clj.d.ts +14 -0
- package/src/clojure/walk.clj +68 -0
- package/src/clojure/walk.clj.d.ts +7 -0
- package/src/core/assertions.ts +114 -6
- package/src/core/bootstrap.ts +337 -0
- package/src/core/conversions.ts +48 -31
- package/src/core/core-module.ts +303 -0
- package/src/core/env.ts +20 -6
- package/src/core/evaluator/apply.ts +40 -25
- package/src/core/evaluator/arity.ts +8 -8
- package/src/core/evaluator/async-evaluator.ts +565 -0
- package/src/core/evaluator/collections.ts +28 -5
- package/src/core/evaluator/destructure.ts +180 -69
- package/src/core/evaluator/dispatch.ts +12 -14
- package/src/core/evaluator/evaluate.ts +22 -20
- package/src/core/evaluator/expand.ts +45 -15
- package/src/core/evaluator/form-parsers.ts +178 -0
- package/src/core/evaluator/index.ts +7 -9
- package/src/core/evaluator/js-interop.ts +189 -0
- package/src/core/evaluator/quasiquote.ts +14 -8
- package/src/core/evaluator/recur-check.ts +6 -6
- package/src/core/evaluator/special-forms.ts +234 -191
- package/src/core/factories.ts +182 -3
- package/src/core/index.ts +54 -4
- package/src/core/module.ts +136 -0
- package/src/core/ns-forms.ts +107 -0
- package/src/core/printer.ts +371 -11
- package/src/core/reader.ts +84 -33
- package/src/core/registry.ts +209 -0
- package/src/core/runtime.ts +376 -0
- package/src/core/session.ts +253 -487
- package/src/core/stdlib/arithmetic.ts +528 -194
- package/src/core/stdlib/async-fns.ts +132 -0
- package/src/core/stdlib/atoms.ts +291 -56
- package/src/core/stdlib/errors.ts +54 -50
- package/src/core/stdlib/hof.ts +82 -166
- package/src/core/stdlib/js-namespace.ts +344 -0
- package/src/core/stdlib/lazy.ts +34 -0
- package/src/core/stdlib/maps-sets.ts +322 -0
- package/src/core/stdlib/meta.ts +61 -30
- package/src/core/stdlib/predicates.ts +325 -187
- package/src/core/stdlib/regex.ts +126 -98
- package/src/core/stdlib/seq.ts +564 -0
- package/src/core/stdlib/strings.ts +164 -135
- package/src/core/stdlib/transducers.ts +95 -100
- package/src/core/stdlib/utils.ts +292 -130
- package/src/core/stdlib/vars.ts +27 -27
- package/src/core/stdlib/vectors.ts +122 -0
- package/src/core/tokenizer.ts +2 -2
- package/src/core/transformations.ts +117 -9
- package/src/core/types.ts +98 -2
- package/src/host/node-host-module.ts +74 -0
- package/src/{vite-plugin-clj/nrepl-relay.ts → nrepl/relay.ts} +72 -11
- package/src/vite-plugin-clj/codegen.ts +87 -95
- package/src/vite-plugin-clj/index.ts +178 -23
- package/src/vite-plugin-clj/namespace-utils.ts +39 -0
- package/src/vite-plugin-clj/static-analysis.ts +211 -0
- package/src/clojure/demo.clj +0 -72
- package/src/clojure/demo.clj.d.ts +0 -0
- package/src/core/core-env.ts +0 -61
- package/src/core/stdlib/collections.ts +0 -739
- package/src/host/node.ts +0 -55
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { isNamespace, isSymbol } from './assertions'
|
|
2
|
+
import { internVar, makeNamespace, tryLookup } from './env'
|
|
3
|
+
import { EvaluationError } from './errors'
|
|
4
|
+
import { v } from './factories'
|
|
5
|
+
import type { CljNamespace, CljValue, Env, EvaluationContext } from './types'
|
|
6
|
+
import { ensureNamespaceInRegistry, processRequireSpec } from './registry'
|
|
7
|
+
import type { NamespaceRegistry } from './registry'
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// wireNsCore — wires *ns*, namespace introspection fns, require, and resolve
|
|
11
|
+
// into coreEnv. Called from buildRuntime with explicit parameters instead of
|
|
12
|
+
// relying on shared closure state.
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export function wireNsCore(
|
|
16
|
+
registry: NamespaceRegistry,
|
|
17
|
+
coreEnv: Env,
|
|
18
|
+
getCurrentNs: () => string,
|
|
19
|
+
resolveNamespace: (nsName: string, ctx: EvaluationContext) => boolean
|
|
20
|
+
): void {
|
|
21
|
+
// *ns* var — holds the current namespace as a CljNamespace value
|
|
22
|
+
const initialNsObj = registry.get('user')?.ns ?? makeNamespace('user')
|
|
23
|
+
internVar('*ns*', initialNsObj, coreEnv)
|
|
24
|
+
const nsVar = coreEnv.ns?.vars.get('*ns*')
|
|
25
|
+
if (nsVar) nsVar.dynamic = true
|
|
26
|
+
|
|
27
|
+
// Helper: resolve a namespace symbol (or namespace object) to its CljNamespace
|
|
28
|
+
function resolveNsSym(sym: CljValue): CljNamespace | null {
|
|
29
|
+
if (sym === undefined) return null
|
|
30
|
+
if (isNamespace(sym)) return sym
|
|
31
|
+
if (!isSymbol(sym)) return null
|
|
32
|
+
return registry.get(sym.name)?.ns ?? null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Namespace introspection
|
|
36
|
+
internVar(
|
|
37
|
+
'ns-name',
|
|
38
|
+
v.nativeFn('ns-name', (x: CljValue) => {
|
|
39
|
+
if (x === undefined) return v.nil()
|
|
40
|
+
if (x.kind === 'namespace') return v.symbol(x.name)
|
|
41
|
+
if (x.kind === 'symbol') return x
|
|
42
|
+
if (x.kind === 'string') return v.symbol(x.value)
|
|
43
|
+
return v.nil()
|
|
44
|
+
}),
|
|
45
|
+
coreEnv
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
internVar(
|
|
49
|
+
'all-ns',
|
|
50
|
+
v.nativeFn('all-ns', () =>
|
|
51
|
+
v.list([...registry.values()].map((env) => env.ns!).filter(Boolean))
|
|
52
|
+
),
|
|
53
|
+
coreEnv
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
internVar(
|
|
57
|
+
'find-ns',
|
|
58
|
+
v.nativeFn('find-ns', (sym: CljValue) => {
|
|
59
|
+
if (sym === undefined || !isSymbol(sym)) return v.nil()
|
|
60
|
+
return registry.get(sym.name)?.ns ?? v.nil()
|
|
61
|
+
}),
|
|
62
|
+
coreEnv
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
internVar(
|
|
66
|
+
'in-ns',
|
|
67
|
+
v.nativeFnCtx('in-ns', (ctx, _callEnv, sym: CljValue) => {
|
|
68
|
+
if (!sym || !isSymbol(sym)) {
|
|
69
|
+
throw new EvaluationError('in-ns expects a symbol', { sym })
|
|
70
|
+
}
|
|
71
|
+
if (ctx.setCurrentNs) ctx.setCurrentNs(sym.name)
|
|
72
|
+
return registry.get(sym.name)?.ns ?? v.nil()
|
|
73
|
+
}),
|
|
74
|
+
coreEnv
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
internVar(
|
|
78
|
+
'ns-aliases',
|
|
79
|
+
v.nativeFn('ns-aliases', (sym: CljValue) => {
|
|
80
|
+
const ns = resolveNsSym(sym)
|
|
81
|
+
if (!ns) return v.map([])
|
|
82
|
+
const entries: [CljValue, CljValue][] = []
|
|
83
|
+
ns.aliases.forEach((targetNs, alias) => {
|
|
84
|
+
entries.push([v.symbol(alias), targetNs])
|
|
85
|
+
})
|
|
86
|
+
return v.map(entries)
|
|
87
|
+
}),
|
|
88
|
+
coreEnv
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
internVar(
|
|
92
|
+
'ns-interns',
|
|
93
|
+
v.nativeFn('ns-interns', (sym: CljValue) => {
|
|
94
|
+
const ns = resolveNsSym(sym)
|
|
95
|
+
if (!ns) return v.map([])
|
|
96
|
+
const entries: [CljValue, CljValue][] = []
|
|
97
|
+
ns.vars.forEach((theVar, name) => {
|
|
98
|
+
if (theVar.ns === ns.name) entries.push([v.symbol(name), theVar])
|
|
99
|
+
})
|
|
100
|
+
return v.map(entries)
|
|
101
|
+
}),
|
|
102
|
+
coreEnv
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
internVar(
|
|
106
|
+
'ns-publics',
|
|
107
|
+
v.nativeFn('ns-publics', (sym: CljValue) => {
|
|
108
|
+
const ns = resolveNsSym(sym)
|
|
109
|
+
if (!ns) return v.map([])
|
|
110
|
+
const entries: [CljValue, CljValue][] = []
|
|
111
|
+
ns.vars.forEach((theVar, name) => {
|
|
112
|
+
if (theVar.ns !== ns.name) return
|
|
113
|
+
const isPrivate = (theVar.meta?.entries ?? []).some(
|
|
114
|
+
([k, val]) =>
|
|
115
|
+
k.kind === 'keyword' && k.name === ':private' &&
|
|
116
|
+
val.kind === 'boolean' && val.value === true
|
|
117
|
+
)
|
|
118
|
+
if (!isPrivate) entries.push([v.symbol(name), theVar])
|
|
119
|
+
})
|
|
120
|
+
return v.map(entries)
|
|
121
|
+
}),
|
|
122
|
+
coreEnv
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
internVar(
|
|
126
|
+
'ns-refers',
|
|
127
|
+
v.nativeFn('ns-refers', (sym: CljValue) => {
|
|
128
|
+
const ns = resolveNsSym(sym)
|
|
129
|
+
if (!ns) return v.map([])
|
|
130
|
+
const entries: [CljValue, CljValue][] = []
|
|
131
|
+
ns.vars.forEach((theVar, name) => {
|
|
132
|
+
if (theVar.ns !== ns.name) entries.push([v.symbol(name), theVar])
|
|
133
|
+
})
|
|
134
|
+
return v.map(entries)
|
|
135
|
+
}),
|
|
136
|
+
coreEnv
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
internVar(
|
|
140
|
+
'ns-map',
|
|
141
|
+
v.nativeFn('ns-map', (sym: CljValue) => {
|
|
142
|
+
const ns = resolveNsSym(sym)
|
|
143
|
+
if (!ns) return v.map([])
|
|
144
|
+
const entries: [CljValue, CljValue][] = []
|
|
145
|
+
ns.vars.forEach((theVar, name) => {
|
|
146
|
+
entries.push([v.symbol(name), theVar])
|
|
147
|
+
})
|
|
148
|
+
return v.map(entries)
|
|
149
|
+
}),
|
|
150
|
+
coreEnv
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
internVar(
|
|
154
|
+
'ns-imports',
|
|
155
|
+
v.nativeFn('ns-imports', (_sym: CljValue) => v.map([])),
|
|
156
|
+
coreEnv
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
internVar(
|
|
160
|
+
'the-ns',
|
|
161
|
+
v.nativeFn('the-ns', (sym: CljValue) => {
|
|
162
|
+
if (sym === undefined) return v.nil()
|
|
163
|
+
if (isNamespace(sym)) return sym
|
|
164
|
+
if (!isSymbol(sym)) return v.nil()
|
|
165
|
+
return registry.get(sym.name)?.ns ?? v.nil()
|
|
166
|
+
}),
|
|
167
|
+
coreEnv
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
internVar(
|
|
171
|
+
'instance?',
|
|
172
|
+
v.nativeFn('instance?', (_cls: CljValue, _obj: CljValue) =>
|
|
173
|
+
v.boolean(false)
|
|
174
|
+
),
|
|
175
|
+
coreEnv
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
internVar(
|
|
179
|
+
'class',
|
|
180
|
+
v.nativeFn('class', (x: CljValue) => {
|
|
181
|
+
if (x === undefined) return v.nil()
|
|
182
|
+
return v.string(`conjure.${x.kind}`)
|
|
183
|
+
}),
|
|
184
|
+
coreEnv
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
internVar(
|
|
188
|
+
'class?',
|
|
189
|
+
v.nativeFn('class?', (_x: CljValue) => v.boolean(false)),
|
|
190
|
+
coreEnv
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
internVar(
|
|
194
|
+
'special-symbol?',
|
|
195
|
+
v.nativeFn('special-symbol?', (sym: CljValue) => {
|
|
196
|
+
if (sym === undefined || !isSymbol(sym)) return v.boolean(false)
|
|
197
|
+
const specials = new Set([
|
|
198
|
+
'def',
|
|
199
|
+
'if',
|
|
200
|
+
'do',
|
|
201
|
+
'let',
|
|
202
|
+
'quote',
|
|
203
|
+
'var',
|
|
204
|
+
'fn',
|
|
205
|
+
'loop',
|
|
206
|
+
'recur',
|
|
207
|
+
'throw',
|
|
208
|
+
'try',
|
|
209
|
+
'catch',
|
|
210
|
+
'finally',
|
|
211
|
+
'ns',
|
|
212
|
+
'defmacro',
|
|
213
|
+
'binding',
|
|
214
|
+
'monitor-enter',
|
|
215
|
+
'monitor-exit',
|
|
216
|
+
'new',
|
|
217
|
+
'set!',
|
|
218
|
+
'.',
|
|
219
|
+
'import',
|
|
220
|
+
])
|
|
221
|
+
return v.boolean(specials.has(sym.name))
|
|
222
|
+
}),
|
|
223
|
+
coreEnv
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
internVar(
|
|
227
|
+
'loaded-libs',
|
|
228
|
+
v.nativeFn('loaded-libs', () => v.set([...registry.keys()].map(v.symbol))),
|
|
229
|
+
coreEnv
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
// require — context-aware so it can thread ctx to resolveNamespace
|
|
233
|
+
internVar(
|
|
234
|
+
'require',
|
|
235
|
+
v.nativeFnCtx('require', (ctx, _callEnv, ...args: CljValue[]) => {
|
|
236
|
+
const currentEnv = registry.get(getCurrentNs())!
|
|
237
|
+
for (const arg of args) {
|
|
238
|
+
processRequireSpec(arg, currentEnv, registry, (nsName) =>
|
|
239
|
+
resolveNamespace(nsName, ctx)
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
return v.nil()
|
|
243
|
+
}),
|
|
244
|
+
coreEnv
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
internVar(
|
|
248
|
+
'resolve',
|
|
249
|
+
v.nativeFn('resolve', (sym: CljValue) => {
|
|
250
|
+
if (!isSymbol(sym)) return v.nil()
|
|
251
|
+
const slashIdx = sym.name.indexOf('/')
|
|
252
|
+
if (slashIdx > 0) {
|
|
253
|
+
const nsName = sym.name.slice(0, slashIdx)
|
|
254
|
+
const symName = sym.name.slice(slashIdx + 1)
|
|
255
|
+
const nsEnv = registry.get(nsName) ?? null
|
|
256
|
+
if (!nsEnv) return v.nil()
|
|
257
|
+
return tryLookup(symName, nsEnv) ?? v.nil()
|
|
258
|
+
}
|
|
259
|
+
const currentEnv = registry.get(getCurrentNs())!
|
|
260
|
+
return tryLookup(sym.name, currentEnv) ?? v.nil()
|
|
261
|
+
}),
|
|
262
|
+
coreEnv
|
|
263
|
+
)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
// wireIdeStubs — wires clojure.reflect, cursive.repl.runtime, and Java class
|
|
268
|
+
// stubs into the registry. These are no-op shims for IDE compatibility.
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
export function wireIdeStubs(registry: NamespaceRegistry, coreEnv: Env): void {
|
|
272
|
+
// IDE stubs: clojure.reflect
|
|
273
|
+
const reflectEnv = ensureNamespaceInRegistry(
|
|
274
|
+
registry,
|
|
275
|
+
coreEnv,
|
|
276
|
+
'clojure.reflect'
|
|
277
|
+
)
|
|
278
|
+
internVar(
|
|
279
|
+
'parse-flags',
|
|
280
|
+
v.nativeFn('parse-flags', (_flags: CljValue, _kind: CljValue) => v.set([])),
|
|
281
|
+
reflectEnv
|
|
282
|
+
)
|
|
283
|
+
internVar(
|
|
284
|
+
'reflect',
|
|
285
|
+
v.nativeFn('reflect', (_obj: CljValue) => v.map([])),
|
|
286
|
+
reflectEnv
|
|
287
|
+
)
|
|
288
|
+
internVar(
|
|
289
|
+
'type-reflect',
|
|
290
|
+
v.nativeFn('type-reflect', (_typeobj: CljValue, ..._opts: CljValue[]) =>
|
|
291
|
+
v.map([])
|
|
292
|
+
),
|
|
293
|
+
reflectEnv
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
// IDE stubs: cursive.repl.runtime
|
|
297
|
+
const cursiveEnv = ensureNamespaceInRegistry(
|
|
298
|
+
registry,
|
|
299
|
+
coreEnv,
|
|
300
|
+
'cursive.repl.runtime'
|
|
301
|
+
)
|
|
302
|
+
internVar(
|
|
303
|
+
'completions',
|
|
304
|
+
v.nativeFn('completions', (..._args: CljValue[]) => v.nil()),
|
|
305
|
+
cursiveEnv
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
// Java class stubs — Cursive references these as bare symbols for type checks
|
|
309
|
+
for (const javaClass of [
|
|
310
|
+
'Class',
|
|
311
|
+
'Object',
|
|
312
|
+
'String',
|
|
313
|
+
'Number',
|
|
314
|
+
'Boolean',
|
|
315
|
+
'Integer',
|
|
316
|
+
'Long',
|
|
317
|
+
'Double',
|
|
318
|
+
'Float',
|
|
319
|
+
'Byte',
|
|
320
|
+
'Short',
|
|
321
|
+
'Character',
|
|
322
|
+
'Void',
|
|
323
|
+
'Math',
|
|
324
|
+
'System',
|
|
325
|
+
'Runtime',
|
|
326
|
+
'Thread',
|
|
327
|
+
'Throwable',
|
|
328
|
+
'Exception',
|
|
329
|
+
'Error',
|
|
330
|
+
'Iterable',
|
|
331
|
+
'Comparable',
|
|
332
|
+
'Runnable',
|
|
333
|
+
'Cloneable',
|
|
334
|
+
]) {
|
|
335
|
+
internVar(javaClass, v.keyword(`:java.lang/${javaClass}`), coreEnv)
|
|
336
|
+
}
|
|
337
|
+
}
|
package/src/core/conversions.ts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import { isCljValue } from './assertions'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
cljBoolean,
|
|
5
|
-
cljKeyword,
|
|
6
|
-
cljMap,
|
|
7
|
-
cljNativeFunction,
|
|
8
|
-
cljNil,
|
|
9
|
-
cljNumber,
|
|
10
|
-
cljString,
|
|
11
|
-
cljVector,
|
|
12
|
-
} from './factories'
|
|
2
|
+
import { v } from './factories'
|
|
13
3
|
import type { CljValue } from './types'
|
|
14
4
|
|
|
15
5
|
export class ConversionError extends Error {
|
|
@@ -21,9 +11,25 @@ export class ConversionError extends Error {
|
|
|
21
11
|
}
|
|
22
12
|
}
|
|
23
13
|
|
|
14
|
+
export type FunctionApplier = {
|
|
15
|
+
applyFunction: (fn: CljValue, args: CljValue[]) => CljValue
|
|
16
|
+
}
|
|
17
|
+
|
|
24
18
|
const richKeyKinds = new Set(['list', 'vector', 'map'])
|
|
25
19
|
|
|
26
|
-
|
|
20
|
+
// Used inside jsToClj's function wrapper when converting CLJ args back to JS.
|
|
21
|
+
// CLJ args passed to JS-wrapped functions are almost always data values, so
|
|
22
|
+
// no applier is needed. If a CLJ function is encountered here, we throw a clear
|
|
23
|
+
// error — use session.cljToJs() for function-bearing values.
|
|
24
|
+
const _throwingApplier: FunctionApplier = {
|
|
25
|
+
applyFunction: () => {
|
|
26
|
+
throw new ConversionError(
|
|
27
|
+
'Cannot convert a CLJ function to JS in this context — use session.cljToJs() instead.'
|
|
28
|
+
)
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function cljToJs(value: CljValue, applier: FunctionApplier): unknown {
|
|
27
33
|
switch (value.kind) {
|
|
28
34
|
case 'number':
|
|
29
35
|
return value.value
|
|
@@ -39,18 +45,18 @@ export function cljToJs(value: CljValue): unknown {
|
|
|
39
45
|
return value.name
|
|
40
46
|
case 'list':
|
|
41
47
|
case 'vector':
|
|
42
|
-
return value.value.map(cljToJs)
|
|
48
|
+
return value.value.map((item) => cljToJs(item, applier))
|
|
43
49
|
case 'map': {
|
|
44
50
|
const obj: Record<string, unknown> = {}
|
|
45
|
-
for (const [k,
|
|
51
|
+
for (const [k, val] of value.entries) {
|
|
46
52
|
if (richKeyKinds.has(k.kind)) {
|
|
47
53
|
throw new ConversionError(
|
|
48
54
|
`Rich key types (${k.kind}) are not supported in JS object conversion. Restructure your map to use string, keyword, or number keys.`,
|
|
49
|
-
{ key: k, value:
|
|
55
|
+
{ key: k, value: val }
|
|
50
56
|
)
|
|
51
57
|
}
|
|
52
|
-
const jsKey = String(cljToJs(k))
|
|
53
|
-
obj[jsKey] = cljToJs(
|
|
58
|
+
const jsKey = String(cljToJs(k, applier))
|
|
59
|
+
obj[jsKey] = cljToJs(val, applier)
|
|
54
60
|
}
|
|
55
61
|
return obj
|
|
56
62
|
}
|
|
@@ -58,9 +64,9 @@ export function cljToJs(value: CljValue): unknown {
|
|
|
58
64
|
case 'native-function': {
|
|
59
65
|
const fn = value
|
|
60
66
|
return (...jsArgs: unknown[]) => {
|
|
61
|
-
const cljArgs = jsArgs.map(jsToClj)
|
|
62
|
-
const result = applyFunction(fn, cljArgs)
|
|
63
|
-
return cljToJs(result)
|
|
67
|
+
const cljArgs = jsArgs.map((a) => jsToClj(a))
|
|
68
|
+
const result = applier.applyFunction(fn, cljArgs)
|
|
69
|
+
return cljToJs(result, applier)
|
|
64
70
|
}
|
|
65
71
|
}
|
|
66
72
|
case 'macro':
|
|
@@ -71,33 +77,44 @@ export function cljToJs(value: CljValue): unknown {
|
|
|
71
77
|
}
|
|
72
78
|
}
|
|
73
79
|
|
|
74
|
-
export
|
|
75
|
-
|
|
80
|
+
export interface JsToCljOpts {
|
|
81
|
+
/** When true, plain object keys become keywords. Default: true. */
|
|
82
|
+
keywordizeKeys?: boolean
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function jsToClj(value: unknown, opts: JsToCljOpts = {}): CljValue {
|
|
86
|
+
const { keywordizeKeys = true } = opts
|
|
87
|
+
|
|
88
|
+
if (value === null) return v.nil()
|
|
89
|
+
if (value === undefined) return v.jsValue(undefined)
|
|
76
90
|
if (isCljValue(value)) return value
|
|
77
91
|
|
|
78
92
|
switch (typeof value) {
|
|
79
93
|
case 'number':
|
|
80
|
-
return
|
|
94
|
+
return v.number(value)
|
|
81
95
|
case 'string':
|
|
82
|
-
return
|
|
96
|
+
return v.string(value)
|
|
83
97
|
case 'boolean':
|
|
84
|
-
return
|
|
98
|
+
return v.boolean(value)
|
|
85
99
|
case 'function': {
|
|
86
100
|
const jsFn = value as (...args: unknown[]) => unknown
|
|
87
|
-
return
|
|
88
|
-
const jsArgs = cljArgs.map(cljToJs)
|
|
101
|
+
return v.nativeFn('js-fn', (...cljArgs: CljValue[]) => {
|
|
102
|
+
const jsArgs = cljArgs.map((a) => cljToJs(a, _throwingApplier))
|
|
89
103
|
const result = jsFn(...jsArgs)
|
|
90
|
-
return jsToClj(result)
|
|
104
|
+
return jsToClj(result, opts)
|
|
91
105
|
})
|
|
92
106
|
}
|
|
93
107
|
case 'object': {
|
|
94
108
|
if (Array.isArray(value)) {
|
|
95
|
-
return
|
|
109
|
+
return v.vector(value.map((item) => jsToClj(item, opts)))
|
|
96
110
|
}
|
|
97
111
|
const entries: [CljValue, CljValue][] = Object.entries(
|
|
98
112
|
value as Record<string, unknown>
|
|
99
|
-
).map(([k,
|
|
100
|
-
|
|
113
|
+
).map(([k, val]) => [
|
|
114
|
+
keywordizeKeys ? v.keyword(`:${k}`) : v.string(k),
|
|
115
|
+
jsToClj(val, opts),
|
|
116
|
+
])
|
|
117
|
+
return v.map(entries)
|
|
101
118
|
}
|
|
102
119
|
default:
|
|
103
120
|
throw new ConversionError(
|