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,303 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RuntimeModule,
|
|
3
|
+
VarDeclaration,
|
|
4
|
+
VarMap,
|
|
5
|
+
ModuleContext,
|
|
6
|
+
} from './module'
|
|
7
|
+
import { buildPrintContext, prettyPrintString, printString, withPrintContext } from './printer'
|
|
8
|
+
import { derefValue } from './env'
|
|
9
|
+
import { valueToString } from './transformations'
|
|
10
|
+
import type { CljMap, CljValue, Env, EvaluationContext } from './types'
|
|
11
|
+
import { arithmeticFunctions } from './stdlib/arithmetic'
|
|
12
|
+
import { atomFunctions } from './stdlib/atoms'
|
|
13
|
+
import { mapsSetsFunctions } from './stdlib/maps-sets'
|
|
14
|
+
import { seqFunctions } from './stdlib/seq'
|
|
15
|
+
import { vectorFunctions } from './stdlib/vectors'
|
|
16
|
+
import { errorFunctions } from './stdlib/errors'
|
|
17
|
+
import { hofFunctions } from './stdlib/hof'
|
|
18
|
+
import { metaFunctions } from './stdlib/meta'
|
|
19
|
+
import { predicateFunctions } from './stdlib/predicates'
|
|
20
|
+
import { regexFunctions } from './stdlib/regex'
|
|
21
|
+
import { stringFunctions } from './stdlib/strings'
|
|
22
|
+
import { transducerFunctions } from './stdlib/transducers'
|
|
23
|
+
import { utilFunctions } from './stdlib/utils'
|
|
24
|
+
import { lazyFunctions } from './stdlib/lazy'
|
|
25
|
+
import { varFunctions } from './stdlib/vars'
|
|
26
|
+
// --- ASYNC (experimental) ---
|
|
27
|
+
import { asyncFunctions } from './stdlib/async-fns'
|
|
28
|
+
import { v } from './factories'
|
|
29
|
+
import { is } from './assertions'
|
|
30
|
+
import { EvaluationError } from './errors'
|
|
31
|
+
import { cljToJs as cljToJsDeep, jsToClj as jsToCljDeep, type FunctionApplier } from './conversions'
|
|
32
|
+
// --- END ASYNC ---
|
|
33
|
+
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Native function registry — installed as the initial clojure.core binding
|
|
36
|
+
// set by createRuntime.
|
|
37
|
+
//
|
|
38
|
+
// INTENTIONAL BOOTSTRAP OVERRIDES:
|
|
39
|
+
// Several functions below are redefined by clojure.core.clj after bootstrap.
|
|
40
|
+
// They serve as scaffolding during the bootstrap phase only. The Clojure
|
|
41
|
+
// source versions (lazy / transducer-aware) become authoritative once loaded.
|
|
42
|
+
// Overridden by clojure.core.clj:
|
|
43
|
+
// concat → lazy recursive version (shadows eager native)
|
|
44
|
+
// map → lazy + transducer 2-arity
|
|
45
|
+
// filter → lazy + transducer 2-arity
|
|
46
|
+
// take → lazy + stateful transducer
|
|
47
|
+
// drop → lazy + stateful transducer
|
|
48
|
+
// take-while → lazy + stateless transducer
|
|
49
|
+
// drop-while → lazy + stateful transducer
|
|
50
|
+
// map-indexed → lazy + stateful transducer
|
|
51
|
+
// keep → lazy + transducer
|
|
52
|
+
// keep-indexed→ lazy + stateful transducer
|
|
53
|
+
// mapcat → lazy + transducer
|
|
54
|
+
// partition-by→ lazy + stateful transducer
|
|
55
|
+
// iterate → lazy infinite sequence
|
|
56
|
+
// repeatedly → lazy infinite sequence
|
|
57
|
+
// cycle → lazy infinite sequence
|
|
58
|
+
// repeat → lazy infinite (delegates to repeat* for finite arity)
|
|
59
|
+
// range → lazy infinite (delegates to range* for finite arity)
|
|
60
|
+
// into → 2-arity uses reduce+conj; 3-arity uses transduce
|
|
61
|
+
// sequence → materialise via into
|
|
62
|
+
// completing → 0-arity init + 1-arity completion wrapper
|
|
63
|
+
// newline → redefined as (defn newline [] (println ""))
|
|
64
|
+
// not → redefined as pure Clojure (if x false true)
|
|
65
|
+
// dorun → redefined in Clojure
|
|
66
|
+
// doall → redefined in Clojure
|
|
67
|
+
//
|
|
68
|
+
// Dynamic vars declared here (NOT overridden by clojure.core.clj):
|
|
69
|
+
// *out* → nil by default; bound by with-out-str to capture stdout
|
|
70
|
+
// *err* → nil by default; bound by with-err-str to capture stderr
|
|
71
|
+
// *print-length*, *print-level* → print control
|
|
72
|
+
//
|
|
73
|
+
// range* and repeat* are intentionally kept — clojure.core.clj calls them
|
|
74
|
+
// explicitly as private native helpers for finite-arity range/repeat.
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
const nativeFunctions = {
|
|
78
|
+
...arithmeticFunctions,
|
|
79
|
+
...atomFunctions,
|
|
80
|
+
...seqFunctions,
|
|
81
|
+
...vectorFunctions,
|
|
82
|
+
...mapsSetsFunctions,
|
|
83
|
+
...errorFunctions,
|
|
84
|
+
...predicateFunctions,
|
|
85
|
+
...hofFunctions,
|
|
86
|
+
...metaFunctions,
|
|
87
|
+
...transducerFunctions,
|
|
88
|
+
...regexFunctions,
|
|
89
|
+
...stringFunctions,
|
|
90
|
+
...utilFunctions,
|
|
91
|
+
...varFunctions,
|
|
92
|
+
...lazyFunctions,
|
|
93
|
+
// --- ASYNC (experimental) ---
|
|
94
|
+
...asyncFunctions,
|
|
95
|
+
// --- END ASYNC ---
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Emit text to the current output channel.
|
|
101
|
+
* If *out* is dynamically bound to a callable (e.g. inside with-out-str),
|
|
102
|
+
* invoke it. Otherwise fall back to ctx.io.stdout.
|
|
103
|
+
*
|
|
104
|
+
* NOTE: We look up *out* via ctx.resolveNs('clojure.core') rather than
|
|
105
|
+
* tryLookup(callEnv) because Clojure functions close over the original
|
|
106
|
+
* snapshot env. After a snapshot restore, those closure envs point to the
|
|
107
|
+
* old CljVar objects, not the session's freshly cloned ones. resolveNs goes
|
|
108
|
+
* through the runtime registry and always returns the session's own var.
|
|
109
|
+
*/
|
|
110
|
+
function emitToOut(ctx: EvaluationContext, callEnv: Env, text: string): void {
|
|
111
|
+
const outVar = ctx.resolveNs('clojure.core')?.vars.get('*out*')
|
|
112
|
+
const out = outVar ? derefValue(outVar) : undefined
|
|
113
|
+
if (out && (out.kind === 'function' || out.kind === 'native-function')) {
|
|
114
|
+
ctx.applyCallable(out, [v.string(text)], callEnv)
|
|
115
|
+
} else {
|
|
116
|
+
ctx.io.stdout(text)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Emit text to the current error channel.
|
|
122
|
+
* If *err* is dynamically bound to a callable (e.g. inside with-err-str),
|
|
123
|
+
* invoke it. Otherwise fall back to ctx.io.stderr.
|
|
124
|
+
* Same snapshot-env rationale as emitToOut.
|
|
125
|
+
*/
|
|
126
|
+
function emitToErr(ctx: EvaluationContext, callEnv: Env, text: string): void {
|
|
127
|
+
const errVar = ctx.resolveNs('clojure.core')?.vars.get('*err*')
|
|
128
|
+
const err = errVar ? derefValue(errVar) : undefined
|
|
129
|
+
if (err && (err.kind === 'function' || err.kind === 'native-function')) {
|
|
130
|
+
ctx.applyCallable(err, [v.string(text)], callEnv)
|
|
131
|
+
} else {
|
|
132
|
+
ctx.io.stderr(text)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Returns the full clojure.core RuntimeModule.
|
|
138
|
+
*
|
|
139
|
+
* IO functions (println, print, newline, pr, prn, pprint) read ctx.io.stdout
|
|
140
|
+
* at call time instead of closing over an emit callback. This means:
|
|
141
|
+
* - No output parameter needed here
|
|
142
|
+
* - Snapshot clones automatically use the correct output without reinstalling
|
|
143
|
+
* any IO vars (restoreRuntime no longer needs makeIOModule)
|
|
144
|
+
*/
|
|
145
|
+
export function makeCoreModule(): RuntimeModule {
|
|
146
|
+
return {
|
|
147
|
+
id: 'clojure/core',
|
|
148
|
+
declareNs: [
|
|
149
|
+
{
|
|
150
|
+
name: 'clojure.core',
|
|
151
|
+
vars(_ctx: ModuleContext): VarMap {
|
|
152
|
+
const map = new Map<string, VarDeclaration>()
|
|
153
|
+
|
|
154
|
+
// Pure stdlib functions (all have .meta via .doc())
|
|
155
|
+
for (const [name, fn] of Object.entries(nativeFunctions)) {
|
|
156
|
+
const meta = (fn as { meta?: CljMap }).meta
|
|
157
|
+
map.set(name, { value: fn, ...(meta ? { meta } : {}) })
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// IO functions — route through emitToOut/emitToErr so that
|
|
161
|
+
// *out*/*err* dynamic bindings (e.g. inside with-out-str) are honoured.
|
|
162
|
+
map.set('println', {
|
|
163
|
+
value: v.nativeFnCtx(
|
|
164
|
+
'println',
|
|
165
|
+
(ctx, callEnv, ...args: CljValue[]) => {
|
|
166
|
+
withPrintContext(buildPrintContext(ctx), () => {
|
|
167
|
+
emitToOut(
|
|
168
|
+
ctx,
|
|
169
|
+
callEnv,
|
|
170
|
+
args.map(valueToString).join(' ') + '\n'
|
|
171
|
+
)
|
|
172
|
+
})
|
|
173
|
+
return v.nil()
|
|
174
|
+
}
|
|
175
|
+
),
|
|
176
|
+
})
|
|
177
|
+
map.set('print', {
|
|
178
|
+
value: v.nativeFnCtx(
|
|
179
|
+
'print',
|
|
180
|
+
(ctx, callEnv, ...args: CljValue[]) => {
|
|
181
|
+
withPrintContext(buildPrintContext(ctx), () => {
|
|
182
|
+
emitToOut(ctx, callEnv, args.map(valueToString).join(' '))
|
|
183
|
+
})
|
|
184
|
+
return v.nil()
|
|
185
|
+
}
|
|
186
|
+
),
|
|
187
|
+
})
|
|
188
|
+
map.set('newline', {
|
|
189
|
+
value: v.nativeFnCtx('newline', (ctx, callEnv) => {
|
|
190
|
+
emitToOut(ctx, callEnv, '\n')
|
|
191
|
+
return v.nil()
|
|
192
|
+
}),
|
|
193
|
+
})
|
|
194
|
+
map.set('pr', {
|
|
195
|
+
value: v.nativeFnCtx('pr', (ctx, callEnv, ...args: CljValue[]) => {
|
|
196
|
+
withPrintContext(buildPrintContext(ctx), () => {
|
|
197
|
+
emitToOut(
|
|
198
|
+
ctx,
|
|
199
|
+
callEnv,
|
|
200
|
+
args.map((v) => printString(v)).join(' ')
|
|
201
|
+
)
|
|
202
|
+
})
|
|
203
|
+
return v.nil()
|
|
204
|
+
}),
|
|
205
|
+
})
|
|
206
|
+
map.set('prn', {
|
|
207
|
+
value: v.nativeFnCtx('prn', (ctx, callEnv, ...args: CljValue[]) => {
|
|
208
|
+
withPrintContext(buildPrintContext(ctx), () => {
|
|
209
|
+
emitToOut(
|
|
210
|
+
ctx,
|
|
211
|
+
callEnv,
|
|
212
|
+
args.map((v) => printString(v)).join(' ') + '\n'
|
|
213
|
+
)
|
|
214
|
+
})
|
|
215
|
+
return v.nil()
|
|
216
|
+
}),
|
|
217
|
+
})
|
|
218
|
+
map.set('pprint', {
|
|
219
|
+
value: v.nativeFnCtx(
|
|
220
|
+
'pprint',
|
|
221
|
+
(ctx, callEnv, form: CljValue, widthArg?: CljValue) => {
|
|
222
|
+
if (form === undefined) return v.nil()
|
|
223
|
+
const maxWidth =
|
|
224
|
+
widthArg?.kind === 'number' ? widthArg.value : 80
|
|
225
|
+
withPrintContext(buildPrintContext(ctx), () => {
|
|
226
|
+
emitToOut(
|
|
227
|
+
ctx,
|
|
228
|
+
callEnv,
|
|
229
|
+
prettyPrintString(form, maxWidth) + '\n'
|
|
230
|
+
)
|
|
231
|
+
})
|
|
232
|
+
return v.nil()
|
|
233
|
+
}
|
|
234
|
+
),
|
|
235
|
+
})
|
|
236
|
+
map.set('warn', {
|
|
237
|
+
value: v.nativeFnCtx(
|
|
238
|
+
'warn',
|
|
239
|
+
(ctx, callEnv, ...args: CljValue[]) => {
|
|
240
|
+
withPrintContext(buildPrintContext(ctx), () => {
|
|
241
|
+
emitToErr(
|
|
242
|
+
ctx,
|
|
243
|
+
callEnv,
|
|
244
|
+
args.map(valueToString).join(' ') + '\n'
|
|
245
|
+
)
|
|
246
|
+
})
|
|
247
|
+
return v.nil()
|
|
248
|
+
}
|
|
249
|
+
),
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
// Dynamic output-channel vars. IO functions check these first before
|
|
253
|
+
// falling back to ctx.io.stdout / ctx.io.stderr. Bound by with-out-str
|
|
254
|
+
// and with-err-str macros defined in clojure.core.
|
|
255
|
+
map.set('*out*', { value: v.nil(), dynamic: true })
|
|
256
|
+
map.set('*err*', { value: v.nil(), dynamic: true })
|
|
257
|
+
|
|
258
|
+
// Dynamic print-control vars
|
|
259
|
+
map.set('*print-length*', { value: v.nil(), dynamic: true })
|
|
260
|
+
map.set('*print-level*', { value: v.nil(), dynamic: true })
|
|
261
|
+
|
|
262
|
+
// Compatibility var for IDE tooling
|
|
263
|
+
map.set('*compiler-options*', { value: v.map([]) })
|
|
264
|
+
|
|
265
|
+
// JS interop — deep conversion functions
|
|
266
|
+
map.set('clj->js', {
|
|
267
|
+
value: v.nativeFnCtx('clj->js', (ctx: EvaluationContext, callEnv: Env, val: CljValue) => {
|
|
268
|
+
if (is.jsValue(val)) return val
|
|
269
|
+
const applier: FunctionApplier = {
|
|
270
|
+
applyFunction: (fn, args) => ctx.applyCallable(fn, args, callEnv),
|
|
271
|
+
}
|
|
272
|
+
return v.jsValue(cljToJsDeep(val, applier))
|
|
273
|
+
}),
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
map.set('js->clj', {
|
|
277
|
+
value: v.nativeFn('js->clj', (val: CljValue, opts?: CljValue) => {
|
|
278
|
+
if (val.kind === 'nil') return val
|
|
279
|
+
if (!is.jsValue(val)) {
|
|
280
|
+
throw new EvaluationError(
|
|
281
|
+
`js->clj expects a js-value, got ${val.kind}`,
|
|
282
|
+
{ val }
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
const keywordizeKeys = (() => {
|
|
286
|
+
if (!opts || opts.kind !== 'map') return false
|
|
287
|
+
for (const [k, flag] of opts.entries) {
|
|
288
|
+
if (k.kind === 'keyword' && k.name === ':keywordize-keys') {
|
|
289
|
+
return flag.kind !== 'boolean' || flag.value !== false
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return false
|
|
293
|
+
})()
|
|
294
|
+
return jsToCljDeep(val.value, { keywordizeKeys })
|
|
295
|
+
}),
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
return map
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
}
|
|
303
|
+
}
|
package/src/core/env.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EvaluationError } from './errors'
|
|
2
2
|
import type { CljNamespace, CljMap, CljValue, CljVar, Env } from './types'
|
|
3
|
-
import {
|
|
3
|
+
import { v } from './factories'
|
|
4
4
|
|
|
5
5
|
class EnvError extends Error {
|
|
6
6
|
context: any
|
|
@@ -20,7 +20,13 @@ export function derefValue(val: CljValue): CljValue {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export function makeNamespace(name: string): CljNamespace {
|
|
23
|
-
return {
|
|
23
|
+
return {
|
|
24
|
+
kind: 'namespace',
|
|
25
|
+
name,
|
|
26
|
+
vars: new Map(),
|
|
27
|
+
aliases: new Map(),
|
|
28
|
+
readerAliases: new Map(),
|
|
29
|
+
}
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
export function makeEnv(outer?: Env): Env {
|
|
@@ -34,8 +40,11 @@ export function lookup(name: string, env: Env): CljValue {
|
|
|
34
40
|
let current: Env | null = env
|
|
35
41
|
while (current) {
|
|
36
42
|
const raw = current.bindings.get(name)
|
|
37
|
-
|
|
43
|
+
// Local bindings are stored as plain values — do NOT auto-deref.
|
|
44
|
+
// A var stored in a local binding (e.g. from `(var foo)`) is a first-class value.
|
|
45
|
+
if (raw !== undefined) return raw
|
|
38
46
|
const v = current.ns?.vars.get(name)
|
|
47
|
+
// Namespace vars are always auto-deref'd: `foo` resolves to the var's current value.
|
|
39
48
|
if (v !== undefined) return derefValue(v)
|
|
40
49
|
current = current.outer
|
|
41
50
|
}
|
|
@@ -46,7 +55,7 @@ export function tryLookup(name: string, env: Env): CljValue | undefined {
|
|
|
46
55
|
let current: Env | null = env
|
|
47
56
|
while (current) {
|
|
48
57
|
const raw = current.bindings.get(name)
|
|
49
|
-
if (raw !== undefined) return
|
|
58
|
+
if (raw !== undefined) return raw
|
|
50
59
|
const v = current.ns?.vars.get(name)
|
|
51
60
|
if (v !== undefined) return derefValue(v)
|
|
52
61
|
current = current.outer
|
|
@@ -59,14 +68,19 @@ export function tryLookup(name: string, env: Env): CljValue | undefined {
|
|
|
59
68
|
* Re-def: mutates the existing var's value in place.
|
|
60
69
|
* New def: creates a new CljVar and stores it in ns.vars.
|
|
61
70
|
*/
|
|
62
|
-
export function internVar(
|
|
71
|
+
export function internVar(
|
|
72
|
+
name: string,
|
|
73
|
+
value: CljValue,
|
|
74
|
+
nsEnv: Env,
|
|
75
|
+
meta?: CljMap
|
|
76
|
+
) {
|
|
63
77
|
const ns = nsEnv.ns!
|
|
64
78
|
const existing = ns.vars.get(name)
|
|
65
79
|
if (existing) {
|
|
66
80
|
existing.value = value
|
|
67
81
|
if (meta) existing.meta = meta
|
|
68
82
|
} else {
|
|
69
|
-
ns.vars.set(name,
|
|
83
|
+
ns.vars.set(name, v.var(ns.name, name, value, meta))
|
|
70
84
|
}
|
|
71
85
|
}
|
|
72
86
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { is } from '../assertions'
|
|
2
2
|
import { EvaluationError } from '../errors'
|
|
3
3
|
import { cljNil } from '../factories'
|
|
4
4
|
import { printString } from '../printer'
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
EvaluationContext,
|
|
12
12
|
} from '../types'
|
|
13
13
|
import { bindParams, RecurSignal, resolveArity } from './arity'
|
|
14
|
+
import { cljToJs, jsToClj } from './js-interop'
|
|
14
15
|
|
|
15
16
|
export function applyFunctionWithContext(
|
|
16
17
|
fn: CljFunction | CljNativeFunction,
|
|
@@ -58,6 +59,23 @@ export function applyFunctionWithContext(
|
|
|
58
59
|
)
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
export function applyMacroWithContext(
|
|
63
|
+
macro: CljMacro,
|
|
64
|
+
rawArgs: CljValue[],
|
|
65
|
+
ctx: EvaluationContext
|
|
66
|
+
): CljValue {
|
|
67
|
+
const arity = resolveArity(macro.arities, rawArgs.length)
|
|
68
|
+
const localEnv = bindParams(
|
|
69
|
+
arity.params,
|
|
70
|
+
arity.restParam,
|
|
71
|
+
rawArgs,
|
|
72
|
+
macro.env,
|
|
73
|
+
ctx,
|
|
74
|
+
macro.env
|
|
75
|
+
)
|
|
76
|
+
return ctx.evaluateForms(arity.body, localEnv)
|
|
77
|
+
}
|
|
78
|
+
|
|
61
79
|
/**
|
|
62
80
|
* Invokes any IFn value — functions, native functions, keywords, and maps.
|
|
63
81
|
* Used by comp, partial, and any other HOF that needs to call an arbitrary
|
|
@@ -69,25 +87,39 @@ export function applyCallableWithContext(
|
|
|
69
87
|
ctx: EvaluationContext,
|
|
70
88
|
callEnv: Env
|
|
71
89
|
): CljValue {
|
|
72
|
-
if (
|
|
90
|
+
if (is.aFunction(fn)) {
|
|
73
91
|
return applyFunctionWithContext(fn, args, ctx, callEnv)
|
|
74
92
|
}
|
|
75
|
-
if (
|
|
93
|
+
if (is.jsValue(fn)) {
|
|
94
|
+
if (typeof fn.value !== 'function') {
|
|
95
|
+
throw new EvaluationError(
|
|
96
|
+
`js-value is not callable: ${typeof fn.value}`,
|
|
97
|
+
{ fn, args }
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
const jsArgs = args.map((a) => cljToJs(a, ctx, callEnv))
|
|
101
|
+
const rawResult = (fn.value as (...a: unknown[]) => unknown)(...jsArgs)
|
|
102
|
+
return jsToClj(rawResult)
|
|
103
|
+
}
|
|
104
|
+
if (is.keyword(fn)) {
|
|
76
105
|
const target = args[0]
|
|
77
106
|
const defaultVal = args.length > 1 ? args[1] : cljNil()
|
|
78
|
-
if (
|
|
79
|
-
const entry = target.entries.find(([k]) =>
|
|
107
|
+
if (is.map(target)) {
|
|
108
|
+
const entry = target.entries.find(([k]) => is.equal(k, fn))
|
|
80
109
|
return entry ? entry[1] : defaultVal
|
|
81
110
|
}
|
|
82
111
|
return defaultVal
|
|
83
112
|
}
|
|
84
|
-
if (
|
|
113
|
+
if (is.map(fn)) {
|
|
85
114
|
if (args.length === 0) {
|
|
86
|
-
throw new EvaluationError(
|
|
115
|
+
throw new EvaluationError(
|
|
116
|
+
'Map used as function requires at least one argument',
|
|
117
|
+
{ fn, args }
|
|
118
|
+
)
|
|
87
119
|
}
|
|
88
120
|
const key = args[0]
|
|
89
121
|
const defaultVal = args.length > 1 ? args[1] : cljNil()
|
|
90
|
-
const entry = fn.entries.find(([k]) =>
|
|
122
|
+
const entry = fn.entries.find(([k]) => is.equal(k, key))
|
|
91
123
|
return entry ? entry[1] : defaultVal
|
|
92
124
|
}
|
|
93
125
|
throw new EvaluationError(`${printString(fn)} is not a callable value`, {
|
|
@@ -95,20 +127,3 @@ export function applyCallableWithContext(
|
|
|
95
127
|
args,
|
|
96
128
|
})
|
|
97
129
|
}
|
|
98
|
-
|
|
99
|
-
export function applyMacroWithContext(
|
|
100
|
-
macro: CljMacro,
|
|
101
|
-
rawArgs: CljValue[],
|
|
102
|
-
ctx: EvaluationContext
|
|
103
|
-
): CljValue {
|
|
104
|
-
const arity = resolveArity(macro.arities, rawArgs.length)
|
|
105
|
-
const localEnv = bindParams(
|
|
106
|
-
arity.params,
|
|
107
|
-
arity.restParam,
|
|
108
|
-
rawArgs,
|
|
109
|
-
macro.env,
|
|
110
|
-
ctx,
|
|
111
|
-
macro.env
|
|
112
|
-
)
|
|
113
|
-
return ctx.evaluateForms(arity.body, localEnv)
|
|
114
|
-
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { is } from '../assertions'
|
|
2
2
|
import { extend } from '../env'
|
|
3
3
|
import { EvaluationError } from '../errors'
|
|
4
4
|
import { cljList, cljNil } from '../factories'
|
|
@@ -23,14 +23,14 @@ export function parseParamVector(
|
|
|
23
23
|
args: CljVector,
|
|
24
24
|
env: Env
|
|
25
25
|
): { params: DestructurePattern[]; restParam: DestructurePattern | null } {
|
|
26
|
-
const ampIdx = args.value.findIndex((a) =>
|
|
26
|
+
const ampIdx = args.value.findIndex((a) => is.symbol(a) && a.name === '&')
|
|
27
27
|
let params: DestructurePattern[] = []
|
|
28
28
|
let restParam: DestructurePattern | null = null
|
|
29
29
|
if (ampIdx === -1) {
|
|
30
30
|
params = args.value as DestructurePattern[]
|
|
31
31
|
} else {
|
|
32
32
|
const ampsCount = args.value.filter(
|
|
33
|
-
(a) =>
|
|
33
|
+
(a) => is.symbol(a) && a.name === '&'
|
|
34
34
|
).length
|
|
35
35
|
if (ampsCount > 1) {
|
|
36
36
|
throw new EvaluationError('& can only appear once', { args, env })
|
|
@@ -58,23 +58,23 @@ export function parseArities(forms: CljValue[], env: Env): Arity[] {
|
|
|
58
58
|
)
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
if (
|
|
61
|
+
if (is.vector(forms[0])) {
|
|
62
62
|
const paramVec = forms[0]
|
|
63
63
|
const { params, restParam } = parseParamVector(paramVec, env)
|
|
64
64
|
return [{ params, restParam, body: forms.slice(1) }]
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
if (
|
|
67
|
+
if (is.list(forms[0])) {
|
|
68
68
|
const arities: Arity[] = []
|
|
69
69
|
for (const form of forms) {
|
|
70
|
-
if (!
|
|
70
|
+
if (!is.list(form) || form.value.length === 0) {
|
|
71
71
|
throw new EvaluationError(
|
|
72
72
|
'Multi-arity clause must be a list starting with a parameter vector',
|
|
73
73
|
{ form, env }
|
|
74
74
|
)
|
|
75
75
|
}
|
|
76
76
|
const paramVec = form.value[0]
|
|
77
|
-
if (!
|
|
77
|
+
if (!is.vector(paramVec)) {
|
|
78
78
|
throw new EvaluationError(
|
|
79
79
|
'First element of arity clause must be a parameter vector',
|
|
80
80
|
{ paramVec, env }
|
|
@@ -134,7 +134,7 @@ export function bindParams(
|
|
|
134
134
|
if (restParam !== null) {
|
|
135
135
|
const restArgs = args.slice(params.length)
|
|
136
136
|
let restValue: CljValue
|
|
137
|
-
if (
|
|
137
|
+
if (is.map(restParam) && restArgs.length > 0) {
|
|
138
138
|
const entries: [CljValue, CljValue][] = []
|
|
139
139
|
for (let i = 0; i < restArgs.length; i += 2) {
|
|
140
140
|
entries.push([restArgs[i], restArgs[i + 1] ?? cljNil()])
|