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
package/src/core/session.ts
CHANGED
|
@@ -1,508 +1,185 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { loadCoreFunctions } from './core-env'
|
|
3
|
-
import { define, internVar, lookup, lookupVar, makeEnv, makeNamespace, tryLookup } from './env'
|
|
4
|
-
import { valueToString } from './transformations'
|
|
5
|
-
import { createEvaluationContext, RecurSignal } from './evaluator'
|
|
1
|
+
import { builtInNamespaceSources } from '../clojure/generated/builtin-namespace-registry'
|
|
6
2
|
import { CljThrownSignal, EvaluationError, ReaderError } from './errors'
|
|
7
|
-
import {
|
|
3
|
+
import { createEvaluationContext, RecurSignal } from './evaluator'
|
|
4
|
+
import { internVar, makeEnv } from './env'
|
|
5
|
+
import { v } from './factories'
|
|
6
|
+
import { jsToClj } from './evaluator/js-interop'
|
|
7
|
+
import type { RuntimeModule } from './module'
|
|
8
|
+
import { cljToJs as _cljToJs } from './conversions'
|
|
8
9
|
import { formatErrorContext } from './positions'
|
|
9
10
|
import { printString } from './printer'
|
|
10
11
|
import { readForms } from './reader'
|
|
12
|
+
import type { Runtime, RuntimeSnapshot } from './runtime'
|
|
13
|
+
import { createRuntime, restoreRuntime } from './runtime'
|
|
14
|
+
import { extractAliasMapFromTokens, extractNsNameFromTokens } from './ns-forms'
|
|
11
15
|
import { tokenize } from './tokenizer'
|
|
12
|
-
import type { CljNamespace, CljValue, Env
|
|
13
|
-
import { builtInNamespaceSources } from '../clojure/generated/builtin-namespace-registry'
|
|
16
|
+
import type { CljNamespace, CljValue, Env } from './types'
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Public types
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
16
21
|
|
|
17
|
-
type SessionOptions = {
|
|
22
|
+
export type SessionOptions = {
|
|
23
|
+
/** Primary output channel — wired to ctx.io.stdout (println, print, pr, prn, pprint, newline). */
|
|
18
24
|
output?: (text: string) => void
|
|
25
|
+
/** Secondary error channel — wired to ctx.io.stderr. */
|
|
26
|
+
stderr?: (text: string) => void
|
|
19
27
|
entries?: string[]
|
|
20
28
|
sourceRoots?: string[]
|
|
21
29
|
readFile?: (filePath: string) => string
|
|
30
|
+
modules?: RuntimeModule[]
|
|
31
|
+
/**
|
|
32
|
+
* Ambient JS globals injected into the `js` namespace as CljJsValue vars.
|
|
33
|
+
* Each key becomes accessible as `js/<key>` in Clojure code without any require.
|
|
34
|
+
* Example: `{ Math, console, fetch }` → `js/Math`, `js/console`, `js/fetch`.
|
|
35
|
+
*/
|
|
36
|
+
hostBindings?: Record<string, unknown>
|
|
37
|
+
/**
|
|
38
|
+
* Called when (:require ["specifier" :as Alias]) is encountered.
|
|
39
|
+
* Must return (or resolve to) the module object, which is boxed as CljJsValue
|
|
40
|
+
* and bound to Alias in the current namespace.
|
|
41
|
+
* Only usable via evaluateAsync() — string requires are inherently async.
|
|
42
|
+
* Examples:
|
|
43
|
+
* Node/Bun: importModule: (s) => import(s)
|
|
44
|
+
* Vite: importModule: (s) => import(s) // Vite resolves statically at build time
|
|
45
|
+
* Tests: importModule: (s) => fakeModules[s]
|
|
46
|
+
*/
|
|
47
|
+
importModule?: (specifier: string) => unknown | Promise<unknown>
|
|
22
48
|
}
|
|
23
49
|
|
|
24
50
|
export type Session = {
|
|
25
|
-
|
|
51
|
+
/** The underlying runtime — exposed for snapshot access and advanced embedding. */
|
|
52
|
+
readonly runtime: Runtime
|
|
53
|
+
/** Passthrough to runtime.registry. Used by nREPL and tooling for namespace lookup. */
|
|
54
|
+
readonly registry: Runtime['registry']
|
|
26
55
|
readonly currentNs: string
|
|
27
56
|
setNs: (namespace: string) => void
|
|
28
57
|
getNs: (namespace: string) => CljNamespace | null
|
|
29
58
|
loadFile: (source: string, nsName?: string, filePath?: string) => string
|
|
30
|
-
|
|
59
|
+
/** Async variant of loadFile — handles string requires ((:require ["pkg" :as X])). */
|
|
60
|
+
loadFileAsync: (source: string, nsName?: string, filePath?: string) => Promise<string>
|
|
61
|
+
evaluate: (
|
|
62
|
+
source: string,
|
|
63
|
+
opts?: { lineOffset?: number; colOffset?: number; file?: string }
|
|
64
|
+
) => CljValue
|
|
65
|
+
evaluateAsync: (
|
|
66
|
+
source: string,
|
|
67
|
+
opts?: { lineOffset?: number; colOffset?: number; file?: string }
|
|
68
|
+
) => Promise<CljValue>
|
|
31
69
|
evaluateForms: (forms: CljValue[]) => CljValue
|
|
70
|
+
/**
|
|
71
|
+
* Call a CljFunction or CljNativeFunction using this session's evaluation context.
|
|
72
|
+
* Unlike the bare `applyFunction` export from `core/index`, this resolves namespaces
|
|
73
|
+
* through the session's runtime registry — required for any CLJ code that references
|
|
74
|
+
* qualified symbols like `js/Math` or `:require`-d aliases.
|
|
75
|
+
*/
|
|
76
|
+
applyFunction: (fn: CljValue, args: CljValue[]) => CljValue
|
|
77
|
+
/**
|
|
78
|
+
* Convert a CljValue to a plain JS value using this session's evaluation context.
|
|
79
|
+
* CLJ functions are wrapped as JS callbacks that invoke via session.applyFunction,
|
|
80
|
+
* ensuring namespace resolution works for js/Math and other runtime namespaces.
|
|
81
|
+
*/
|
|
82
|
+
cljToJs: (value: CljValue) => unknown
|
|
32
83
|
addSourceRoot: (path: string) => void
|
|
33
84
|
getCompletions: (prefix: string, nsName?: string) => string[]
|
|
34
85
|
}
|
|
35
86
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
function extractNsNameFromTokens(tokens: Token[]): string | null {
|
|
39
|
-
const meaningful = tokens.filter((t) => t.kind !== 'Comment')
|
|
40
|
-
if (meaningful.length < 3) return null
|
|
41
|
-
if (meaningful[0].kind !== 'LParen') return null
|
|
42
|
-
if (meaningful[1].kind !== 'Symbol' || meaningful[1].value !== 'ns')
|
|
43
|
-
return null
|
|
44
|
-
if (meaningful[2].kind !== 'Symbol') return null
|
|
45
|
-
return meaningful[2].value
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Lightweight token scan to extract :as alias pairs from the ns form's :require clauses.
|
|
49
|
-
// Returns Map { 'alias' -> 'full.ns.name' } for all [some.ns :as alias] specs found.
|
|
50
|
-
// This runs before readForms so the reader can expand ::alias/foo at read time.
|
|
51
|
-
function extractAliasMapFromTokens(tokens: Token[]): Map<string, string> {
|
|
52
|
-
const aliases = new Map<string, string>()
|
|
53
|
-
const meaningful = tokens.filter(
|
|
54
|
-
(t) => t.kind !== 'Comment' && t.kind !== 'Whitespace'
|
|
55
|
-
)
|
|
56
|
-
// Must start with (ns ...)
|
|
57
|
-
if (meaningful.length < 3) return aliases
|
|
58
|
-
if (meaningful[0].kind !== 'LParen') return aliases
|
|
59
|
-
if (meaningful[1].kind !== 'Symbol' || meaningful[1].value !== 'ns')
|
|
60
|
-
return aliases
|
|
61
|
-
|
|
62
|
-
// Walk the top-level ns form tracking paren depth.
|
|
63
|
-
// For each [ vector ] we encounter, check for ns-sym :as alias-sym.
|
|
64
|
-
let i = 3 // skip ( ns <name>
|
|
65
|
-
let depth = 1
|
|
66
|
-
while (i < meaningful.length && depth > 0) {
|
|
67
|
-
const tok = meaningful[i]
|
|
68
|
-
if (tok.kind === 'LParen') {
|
|
69
|
-
depth++
|
|
70
|
-
i++
|
|
71
|
-
continue
|
|
72
|
-
}
|
|
73
|
-
if (tok.kind === 'RParen') {
|
|
74
|
-
depth--
|
|
75
|
-
i++
|
|
76
|
-
continue
|
|
77
|
-
}
|
|
78
|
-
if (tok.kind === 'LBracket') {
|
|
79
|
-
// Scan through this vector for: first Symbol (ns name) + :as + Symbol (alias)
|
|
80
|
-
let j = i + 1
|
|
81
|
-
let nsSym: string | null = null
|
|
82
|
-
while (j < meaningful.length && meaningful[j].kind !== 'RBracket') {
|
|
83
|
-
const t = meaningful[j]
|
|
84
|
-
if (t.kind === 'Symbol' && nsSym === null) {
|
|
85
|
-
nsSym = t.value
|
|
86
|
-
}
|
|
87
|
-
if (
|
|
88
|
-
t.kind === 'Keyword' &&
|
|
89
|
-
(t.value === ':as' || t.value === ':as-alias')
|
|
90
|
-
) {
|
|
91
|
-
j++
|
|
92
|
-
if (
|
|
93
|
-
j < meaningful.length &&
|
|
94
|
-
meaningful[j].kind === 'Symbol' &&
|
|
95
|
-
nsSym
|
|
96
|
-
) {
|
|
97
|
-
aliases.set((meaningful[j] as TokenSymbol).value, nsSym)
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
j++
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
i++
|
|
104
|
-
}
|
|
105
|
-
return aliases
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function findNsForm(forms: CljValue[]) {
|
|
109
|
-
const nsForm = forms.find(
|
|
110
|
-
(f) => isList(f) && isSymbol(f.value[0]) && f.value[0].name === 'ns'
|
|
111
|
-
)
|
|
112
|
-
if (!nsForm || !isList(nsForm)) return null
|
|
113
|
-
return nsForm
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function extractRequireClauses(forms: CljValue[]): CljValue[][] {
|
|
117
|
-
const nsForm = findNsForm(forms)
|
|
118
|
-
if (!nsForm) return []
|
|
119
|
-
const clauses: CljValue[][] = []
|
|
120
|
-
for (let i = 2; i < nsForm.value.length; i++) {
|
|
121
|
-
const clause = nsForm.value[i]
|
|
122
|
-
if (
|
|
123
|
-
isList(clause) &&
|
|
124
|
-
isKeyword(clause.value[0]) &&
|
|
125
|
-
clause.value[0].name === ':require'
|
|
126
|
-
) {
|
|
127
|
-
clauses.push(clause.value.slice(1))
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return clauses
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function processRequireSpec(
|
|
134
|
-
spec: CljValue,
|
|
135
|
-
currentEnv: Env,
|
|
136
|
-
registry: NamespaceRegistry,
|
|
137
|
-
resolveNamespace?: (nsName: string) => boolean
|
|
138
|
-
): void {
|
|
139
|
-
if (!isVector(spec)) {
|
|
140
|
-
throw new EvaluationError(
|
|
141
|
-
'require spec must be a vector, e.g. [my.ns :as alias]',
|
|
142
|
-
{ spec }
|
|
143
|
-
)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const elements = spec.value
|
|
147
|
-
if (elements.length === 0 || !isSymbol(elements[0])) {
|
|
148
|
-
throw new EvaluationError(
|
|
149
|
-
'First element of require spec must be a namespace symbol',
|
|
150
|
-
{ spec }
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const nsName = elements[0].name
|
|
155
|
-
|
|
156
|
-
// :as-alias creates a reader alias without loading the namespace.
|
|
157
|
-
// The namespace need not exist — the alias is only used for ::alias/foo expansion.
|
|
158
|
-
const hasAsAlias = elements.some(
|
|
159
|
-
(el) => isKeyword(el) && el.name === ':as-alias'
|
|
160
|
-
)
|
|
161
|
-
if (hasAsAlias) {
|
|
162
|
-
let i = 1
|
|
163
|
-
while (i < elements.length) {
|
|
164
|
-
const kw = elements[i]
|
|
165
|
-
if (!isKeyword(kw)) {
|
|
166
|
-
throw new EvaluationError(
|
|
167
|
-
`Expected keyword in require spec, got ${kw.kind}`,
|
|
168
|
-
{ spec, position: i }
|
|
169
|
-
)
|
|
170
|
-
}
|
|
171
|
-
if (kw.name === ':as-alias') {
|
|
172
|
-
i++
|
|
173
|
-
const alias = elements[i]
|
|
174
|
-
if (!alias || !isSymbol(alias)) {
|
|
175
|
-
throw new EvaluationError(':as-alias expects a symbol alias', {
|
|
176
|
-
spec,
|
|
177
|
-
position: i,
|
|
178
|
-
})
|
|
179
|
-
}
|
|
180
|
-
currentEnv.ns!.readerAliases.set(alias.name, nsName)
|
|
181
|
-
i++
|
|
182
|
-
} else {
|
|
183
|
-
throw new EvaluationError(
|
|
184
|
-
`:as-alias specs only support :as-alias, got ${kw.name}`,
|
|
185
|
-
{ spec }
|
|
186
|
-
)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
let targetEnv = registry.get(nsName)
|
|
193
|
-
if (!targetEnv && resolveNamespace) {
|
|
194
|
-
resolveNamespace(nsName)
|
|
195
|
-
targetEnv = registry.get(nsName)
|
|
196
|
-
}
|
|
197
|
-
if (!targetEnv) {
|
|
198
|
-
throw new EvaluationError(
|
|
199
|
-
`Namespace ${nsName} not found. Only already-loaded namespaces can be required.`,
|
|
200
|
-
{ nsName }
|
|
201
|
-
)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
let i = 1
|
|
205
|
-
while (i < elements.length) {
|
|
206
|
-
const kw = elements[i]
|
|
207
|
-
if (!isKeyword(kw)) {
|
|
208
|
-
throw new EvaluationError(
|
|
209
|
-
`Expected keyword in require spec, got ${kw.kind}`,
|
|
210
|
-
{ spec, position: i }
|
|
211
|
-
)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (kw.name === ':as') {
|
|
215
|
-
i++
|
|
216
|
-
const alias = elements[i]
|
|
217
|
-
if (!alias || !isSymbol(alias)) {
|
|
218
|
-
throw new EvaluationError(':as expects a symbol alias', {
|
|
219
|
-
spec,
|
|
220
|
-
position: i,
|
|
221
|
-
})
|
|
222
|
-
}
|
|
223
|
-
currentEnv.ns!.aliases.set(alias.name, targetEnv.ns!)
|
|
224
|
-
i++
|
|
225
|
-
} else if (kw.name === ':refer') {
|
|
226
|
-
i++
|
|
227
|
-
const symsVec = elements[i]
|
|
228
|
-
if (!symsVec || !isVector(symsVec)) {
|
|
229
|
-
throw new EvaluationError(':refer expects a vector of symbols', {
|
|
230
|
-
spec,
|
|
231
|
-
position: i,
|
|
232
|
-
})
|
|
233
|
-
}
|
|
234
|
-
for (const sym of symsVec.value) {
|
|
235
|
-
if (!isSymbol(sym)) {
|
|
236
|
-
throw new EvaluationError(':refer vector must contain only symbols', {
|
|
237
|
-
spec,
|
|
238
|
-
sym,
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
|
-
const v = lookupVar(sym.name, targetEnv)
|
|
242
|
-
if (v !== undefined) {
|
|
243
|
-
currentEnv.ns!.vars.set(sym.name, v)
|
|
244
|
-
} else {
|
|
245
|
-
let value: CljValue
|
|
246
|
-
try {
|
|
247
|
-
value = lookup(sym.name, targetEnv)
|
|
248
|
-
} catch {
|
|
249
|
-
throw new EvaluationError(
|
|
250
|
-
`Symbol ${sym.name} not found in namespace ${nsName}`,
|
|
251
|
-
{ nsName, symbol: sym.name }
|
|
252
|
-
)
|
|
253
|
-
}
|
|
254
|
-
define(sym.name, value, currentEnv)
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
i++
|
|
258
|
-
} else {
|
|
259
|
-
throw new EvaluationError(
|
|
260
|
-
`Unknown require option ${kw.name}. Supported: :as, :refer`,
|
|
261
|
-
{ spec, keyword: kw.name }
|
|
262
|
-
)
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// ---------------------------------------------------------------------------
|
|
268
|
-
// Clone helpers — used by snapshotSession / createSessionFromSnapshot
|
|
269
|
-
// ---------------------------------------------------------------------------
|
|
270
|
-
|
|
271
|
-
function cloneBindings(bindings: Map<string, CljValue>): Map<string, CljValue> {
|
|
272
|
-
const out = new Map<string, CljValue>()
|
|
273
|
-
for (const [k, v] of bindings) {
|
|
274
|
-
out.set(k, v.kind === 'var' ? { ...v } : v)
|
|
275
|
-
}
|
|
276
|
-
return out
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function cloneEnv(env: Env, memo: Map<Env, Env>): Env {
|
|
280
|
-
if (memo.has(env)) return memo.get(env)!
|
|
281
|
-
const cloned: Env = {
|
|
282
|
-
bindings: cloneBindings(env.bindings),
|
|
283
|
-
outer: null,
|
|
284
|
-
}
|
|
285
|
-
if (env.ns) {
|
|
286
|
-
cloned.ns = {
|
|
287
|
-
name: env.ns.name,
|
|
288
|
-
vars: new Map([...env.ns.vars].map(([k, v]) => [k, { ...v }])),
|
|
289
|
-
aliases: new Map(), // wired in cloneRegistry pass 2
|
|
290
|
-
readerAliases: new Map(env.ns.readerAliases),
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
memo.set(env, cloned)
|
|
294
|
-
if (env.outer) cloned.outer = cloneEnv(env.outer, memo)
|
|
295
|
-
return cloned
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function cloneRegistry(registry: NamespaceRegistry): NamespaceRegistry {
|
|
299
|
-
const memo = new Map<Env, Env>()
|
|
300
|
-
const next = new Map<string, Env>()
|
|
301
|
-
// Pass 1: clone all envs (ns.aliases left empty)
|
|
302
|
-
for (const [name, env] of registry) next.set(name, cloneEnv(env, memo))
|
|
303
|
-
// Pass 2: wire ns.aliases to the cloned CljNamespace objects
|
|
304
|
-
for (const [name, env] of registry) {
|
|
305
|
-
const clonedEnv = next.get(name)!
|
|
306
|
-
if (env.ns && clonedEnv.ns) {
|
|
307
|
-
for (const [alias, origNs] of env.ns.aliases) {
|
|
308
|
-
const targetCloned = next.get(origNs.name)
|
|
309
|
-
if (targetCloned?.ns) clonedEnv.ns.aliases.set(alias, targetCloned.ns)
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return next
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// ---------------------------------------------------------------------------
|
|
317
|
-
// SessionState — the minimal data a session operates on
|
|
318
|
-
// ---------------------------------------------------------------------------
|
|
319
|
-
|
|
320
|
-
type SessionState = {
|
|
321
|
-
registry: NamespaceRegistry
|
|
87
|
+
export type SessionSnapshot = {
|
|
88
|
+
runtimeSnapshot: RuntimeSnapshot
|
|
322
89
|
currentNs: string
|
|
323
90
|
}
|
|
324
91
|
|
|
325
92
|
// ---------------------------------------------------------------------------
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
// closures + the public API. Always re-wires resolveNs and require since
|
|
329
|
-
// both must close over the specific registry instance they receive.
|
|
93
|
+
// buildSessionFacade — thin evaluation facade over a Runtime.
|
|
94
|
+
// All namespace mechanics are delegated to the runtime.
|
|
330
95
|
// ---------------------------------------------------------------------------
|
|
331
96
|
|
|
332
|
-
function
|
|
333
|
-
|
|
97
|
+
function buildSessionFacade(
|
|
98
|
+
runtime: Runtime,
|
|
99
|
+
initialNs: string,
|
|
334
100
|
options?: SessionOptions
|
|
335
101
|
): Session {
|
|
336
|
-
|
|
337
|
-
let currentNs = state.currentNs
|
|
338
|
-
|
|
339
|
-
const coreEnv = registry.get('clojure.core')!
|
|
340
|
-
coreEnv.resolveNs = (name: string) => registry.get(name) ?? null
|
|
341
|
-
|
|
342
|
-
// Always re-wire print/println so snapshot-derived sessions get the right
|
|
343
|
-
// emit target. Falls back to console.log when no output callback is provided.
|
|
344
|
-
const emitFn = options?.output ?? ((text: string) => console.log(text))
|
|
345
|
-
internVar(
|
|
346
|
-
'println',
|
|
347
|
-
cljNativeFunction('println', (...args: CljValue[]) => {
|
|
348
|
-
emitFn(args.map(valueToString).join(' '))
|
|
349
|
-
return cljNil()
|
|
350
|
-
}),
|
|
351
|
-
coreEnv
|
|
352
|
-
)
|
|
353
|
-
internVar(
|
|
354
|
-
'print',
|
|
355
|
-
cljNativeFunction('print', (...args: CljValue[]) => {
|
|
356
|
-
emitFn(args.map(valueToString).join(' '))
|
|
357
|
-
return cljNil()
|
|
358
|
-
}),
|
|
359
|
-
coreEnv
|
|
360
|
-
)
|
|
361
|
-
|
|
362
|
-
// Mutable source roots — seeded from options, growable via addSourceRoot.
|
|
363
|
-
const sourceRoots = new Set<string>(options?.sourceRoots ?? [])
|
|
364
|
-
|
|
365
|
-
function addSourceRoot(path: string) {
|
|
366
|
-
sourceRoots.add(path)
|
|
367
|
-
}
|
|
102
|
+
let currentNs = initialNs
|
|
368
103
|
|
|
369
104
|
// One shared evaluation context for the lifetime of this session.
|
|
370
105
|
const ctx = createEvaluationContext()
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
loadFile(builtInLoader(), nsName)
|
|
376
|
-
return true
|
|
377
|
-
}
|
|
378
|
-
if (!options?.readFile || sourceRoots.size === 0) {
|
|
379
|
-
return false
|
|
380
|
-
}
|
|
381
|
-
for (const root of sourceRoots) {
|
|
382
|
-
const filePath = `${root.replace(/\/$/, '')}/${nsName.replace(/\./g, '/')}.clj`
|
|
383
|
-
try {
|
|
384
|
-
const source = options.readFile(filePath)
|
|
385
|
-
if (source) {
|
|
386
|
-
loadFile(source)
|
|
387
|
-
return true
|
|
388
|
-
}
|
|
389
|
-
} catch {
|
|
390
|
-
continue
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return false
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function ensureNs(name: string): Env {
|
|
397
|
-
if (!registry.has(name)) {
|
|
398
|
-
const nsEnv = makeEnv(coreEnv)
|
|
399
|
-
nsEnv.ns = makeNamespace(name)
|
|
400
|
-
registry.set(name, nsEnv)
|
|
401
|
-
}
|
|
402
|
-
return registry.get(name)!
|
|
106
|
+
ctx.resolveNs = (name: string) => runtime.getNs(name)
|
|
107
|
+
ctx.io = {
|
|
108
|
+
stdout: options?.output ?? ((text) => console.log(text)),
|
|
109
|
+
stderr: options?.stderr ?? ((text) => console.error(text)),
|
|
403
110
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
111
|
+
ctx.importModule = options?.importModule
|
|
112
|
+
ctx.setCurrentNs = (name: string) => {
|
|
113
|
+
runtime.ensureNamespace(name)
|
|
407
114
|
currentNs = name
|
|
115
|
+
runtime.syncNsVar(name)
|
|
408
116
|
}
|
|
409
117
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
118
|
+
const session: Session = {
|
|
119
|
+
get runtime() {
|
|
120
|
+
return runtime
|
|
121
|
+
},
|
|
413
122
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
123
|
+
get registry() {
|
|
124
|
+
return runtime.registry
|
|
125
|
+
},
|
|
418
126
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const currentEnv = registry.get(currentNs)!
|
|
423
|
-
for (const arg of args) {
|
|
424
|
-
processRequireSpec(arg, currentEnv, registry, resolveNamespace)
|
|
425
|
-
}
|
|
426
|
-
return cljNil()
|
|
427
|
-
}),
|
|
428
|
-
coreEnv
|
|
429
|
-
)
|
|
430
|
-
|
|
431
|
-
internVar(
|
|
432
|
-
'resolve',
|
|
433
|
-
cljNativeFunction('resolve', (sym: CljValue) => {
|
|
434
|
-
if (!isSymbol(sym)) return cljNil()
|
|
435
|
-
const slashIdx = sym.name.indexOf('/')
|
|
436
|
-
if (slashIdx > 0) {
|
|
437
|
-
const nsName = sym.name.slice(0, slashIdx)
|
|
438
|
-
const symName = sym.name.slice(slashIdx + 1)
|
|
439
|
-
const nsEnv = registry.get(nsName) ?? null
|
|
440
|
-
if (!nsEnv) return cljNil()
|
|
441
|
-
return tryLookup(symName, nsEnv) ?? cljNil()
|
|
442
|
-
}
|
|
443
|
-
const currentEnv = registry.get(currentNs)!
|
|
444
|
-
return tryLookup(sym.name, currentEnv) ?? cljNil()
|
|
445
|
-
}),
|
|
446
|
-
coreEnv
|
|
447
|
-
)
|
|
448
|
-
|
|
449
|
-
function processNsRequires(forms: CljValue[], env: Env) {
|
|
450
|
-
const requireClauses = extractRequireClauses(forms)
|
|
451
|
-
for (const specs of requireClauses) {
|
|
452
|
-
for (const spec of specs) {
|
|
453
|
-
processRequireSpec(spec, env, registry, resolveNamespace)
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
}
|
|
127
|
+
get currentNs() {
|
|
128
|
+
return currentNs
|
|
129
|
+
},
|
|
457
130
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
const env = ensureNs(targetNs)
|
|
464
|
-
ctx.currentSource = source
|
|
465
|
-
ctx.currentFile = filePath
|
|
466
|
-
ctx.currentLineOffset = 0
|
|
467
|
-
ctx.currentColOffset = 0
|
|
468
|
-
processNsRequires(forms, env)
|
|
469
|
-
try {
|
|
470
|
-
for (const form of forms) {
|
|
471
|
-
const expanded = ctx.expandAll(form, env)
|
|
472
|
-
ctx.evaluate(expanded, env)
|
|
473
|
-
}
|
|
474
|
-
} finally {
|
|
475
|
-
ctx.currentSource = undefined
|
|
476
|
-
ctx.currentFile = undefined
|
|
477
|
-
}
|
|
478
|
-
return targetNs
|
|
479
|
-
}
|
|
131
|
+
setNs(name: string) {
|
|
132
|
+
runtime.ensureNamespace(name)
|
|
133
|
+
currentNs = name
|
|
134
|
+
runtime.syncNsVar(name)
|
|
135
|
+
},
|
|
480
136
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
137
|
+
getNs(name: string): CljNamespace | null {
|
|
138
|
+
return runtime.getNs(name)
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
loadFile(source: string, nsName?: string, filePath?: string): string {
|
|
142
|
+
return runtime.loadFile(source, nsName, filePath, ctx)
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
async loadFileAsync(source: string, nsName?: string, filePath?: string): Promise<string> {
|
|
146
|
+
// If there is no ns declaration in the source, pre-set the namespace from
|
|
147
|
+
// the hint so the forms evaluate in the right context.
|
|
148
|
+
if (nsName) {
|
|
149
|
+
const tokens = tokenize(source)
|
|
150
|
+
if (!extractNsNameFromTokens(tokens)) {
|
|
151
|
+
runtime.ensureNamespace(nsName)
|
|
152
|
+
currentNs = nsName
|
|
153
|
+
runtime.syncNsVar(nsName)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
await session.evaluateAsync(source, { file: filePath })
|
|
484
157
|
return currentNs
|
|
485
158
|
},
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
159
|
+
|
|
160
|
+
addSourceRoot(path: string): void {
|
|
161
|
+
runtime.addSourceRoot(path)
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
evaluate(
|
|
165
|
+
source: string,
|
|
166
|
+
opts?: { lineOffset?: number; colOffset?: number; file?: string }
|
|
167
|
+
): CljValue {
|
|
491
168
|
ctx.currentSource = source
|
|
492
169
|
ctx.currentFile = opts?.file
|
|
493
170
|
ctx.currentLineOffset = opts?.lineOffset ?? 0
|
|
494
|
-
ctx.currentColOffset
|
|
171
|
+
ctx.currentColOffset = opts?.colOffset ?? 0
|
|
495
172
|
try {
|
|
496
173
|
const tokens = tokenize(source)
|
|
497
174
|
// If source opens with an ns declaration, switch to that namespace
|
|
498
175
|
// so requires resolve against the right env and currentNs is updated.
|
|
499
|
-
// This mirrors what loadFile does, making eval and load-file consistent.
|
|
500
176
|
const declaredNs = extractNsNameFromTokens(tokens)
|
|
501
177
|
if (declaredNs) {
|
|
502
|
-
|
|
178
|
+
runtime.ensureNamespace(declaredNs)
|
|
503
179
|
currentNs = declaredNs
|
|
180
|
+
runtime.syncNsVar(declaredNs)
|
|
504
181
|
}
|
|
505
|
-
const env =
|
|
182
|
+
const env = runtime.getNamespaceEnv(currentNs)!
|
|
506
183
|
// Seed alias map from tokens (new aliases declared in this source) and
|
|
507
184
|
// from ns.aliases/:as (prior require calls) and ns.readerAliases/:as-alias.
|
|
508
185
|
const aliasMap = extractAliasMapFromTokens(tokens)
|
|
@@ -513,8 +190,8 @@ function buildSessionApi(
|
|
|
513
190
|
aliasMap.set(alias, nsName)
|
|
514
191
|
})
|
|
515
192
|
const forms = readForms(tokens, currentNs, aliasMap)
|
|
516
|
-
processNsRequires(forms, env)
|
|
517
|
-
let result: CljValue =
|
|
193
|
+
runtime.processNsRequires(forms, env, ctx)
|
|
194
|
+
let result: CljValue = v.nil()
|
|
518
195
|
for (const form of forms) {
|
|
519
196
|
const expanded = ctx.expandAll(form, env)
|
|
520
197
|
result = ctx.evaluate(expanded, env)
|
|
@@ -538,7 +215,78 @@ function buildSessionApi(
|
|
|
538
215
|
) {
|
|
539
216
|
e.message += formatErrorContext(source, e.pos, {
|
|
540
217
|
lineOffset: ctx.currentLineOffset,
|
|
541
|
-
colOffset:
|
|
218
|
+
colOffset: ctx.currentColOffset,
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
throw e
|
|
222
|
+
} finally {
|
|
223
|
+
ctx.currentSource = undefined
|
|
224
|
+
ctx.currentFile = undefined
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
async evaluateAsync(
|
|
229
|
+
source: string,
|
|
230
|
+
opts?: { lineOffset?: number; colOffset?: number; file?: string }
|
|
231
|
+
): Promise<CljValue> {
|
|
232
|
+
ctx.currentSource = source
|
|
233
|
+
ctx.currentFile = opts?.file
|
|
234
|
+
ctx.currentLineOffset = opts?.lineOffset ?? 0
|
|
235
|
+
ctx.currentColOffset = opts?.colOffset ?? 0
|
|
236
|
+
try {
|
|
237
|
+
const tokens = tokenize(source)
|
|
238
|
+
const declaredNs = extractNsNameFromTokens(tokens)
|
|
239
|
+
if (declaredNs) {
|
|
240
|
+
runtime.ensureNamespace(declaredNs)
|
|
241
|
+
currentNs = declaredNs
|
|
242
|
+
runtime.syncNsVar(declaredNs)
|
|
243
|
+
}
|
|
244
|
+
const env = runtime.getNamespaceEnv(currentNs)!
|
|
245
|
+
const aliasMap = extractAliasMapFromTokens(tokens)
|
|
246
|
+
env.ns?.aliases.forEach((ns, alias) => {
|
|
247
|
+
aliasMap.set(alias, ns.name)
|
|
248
|
+
})
|
|
249
|
+
env.ns?.readerAliases.forEach((nsName, alias) => {
|
|
250
|
+
aliasMap.set(alias, nsName)
|
|
251
|
+
})
|
|
252
|
+
const forms = readForms(tokens, currentNs, aliasMap)
|
|
253
|
+
await runtime.processNsRequiresAsync(forms, env, ctx)
|
|
254
|
+
let result: CljValue = v.nil()
|
|
255
|
+
for (const form of forms) {
|
|
256
|
+
const expanded = ctx.expandAll(form, env)
|
|
257
|
+
result = ctx.evaluate(expanded, env)
|
|
258
|
+
}
|
|
259
|
+
if (result.kind !== 'pending') return result
|
|
260
|
+
try {
|
|
261
|
+
return await result.promise
|
|
262
|
+
} catch (e) {
|
|
263
|
+
if (e instanceof CljThrownSignal) {
|
|
264
|
+
throw new EvaluationError(
|
|
265
|
+
`Unhandled throw: ${printString(e.value)}`,
|
|
266
|
+
{ thrownValue: e.value }
|
|
267
|
+
)
|
|
268
|
+
}
|
|
269
|
+
throw e
|
|
270
|
+
}
|
|
271
|
+
} catch (e) {
|
|
272
|
+
if (e instanceof CljThrownSignal) {
|
|
273
|
+
throw new EvaluationError(
|
|
274
|
+
`Unhandled throw: ${printString(e.value)}`,
|
|
275
|
+
{ thrownValue: e.value }
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
if (e instanceof RecurSignal) {
|
|
279
|
+
throw new EvaluationError('recur called outside of loop or fn', {
|
|
280
|
+
args: e.args,
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
if (
|
|
284
|
+
(e instanceof EvaluationError || e instanceof ReaderError) &&
|
|
285
|
+
e.pos
|
|
286
|
+
) {
|
|
287
|
+
e.message += formatErrorContext(source, e.pos, {
|
|
288
|
+
lineOffset: ctx.currentLineOffset,
|
|
289
|
+
colOffset: ctx.currentColOffset,
|
|
542
290
|
})
|
|
543
291
|
}
|
|
544
292
|
throw e
|
|
@@ -547,10 +295,19 @@ function buildSessionApi(
|
|
|
547
295
|
ctx.currentFile = undefined
|
|
548
296
|
}
|
|
549
297
|
},
|
|
550
|
-
|
|
298
|
+
|
|
299
|
+
applyFunction(fn: CljValue, args: CljValue[]): CljValue {
|
|
300
|
+
return ctx.applyCallable(fn, args, makeEnv())
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
cljToJs(value: CljValue): unknown {
|
|
304
|
+
return _cljToJs(value, { applyFunction: (fn, args) => ctx.applyCallable(fn, args, makeEnv()) })
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
evaluateForms(forms: CljValue[]): CljValue {
|
|
551
308
|
try {
|
|
552
|
-
const env =
|
|
553
|
-
let result: CljValue =
|
|
309
|
+
const env = runtime.getNamespaceEnv(currentNs)!
|
|
310
|
+
let result: CljValue = v.nil()
|
|
554
311
|
for (const form of forms) {
|
|
555
312
|
const expanded = ctx.expandAll(form, env)
|
|
556
313
|
result = ctx.evaluate(expanded, env)
|
|
@@ -571,8 +328,9 @@ function buildSessionApi(
|
|
|
571
328
|
throw e
|
|
572
329
|
}
|
|
573
330
|
},
|
|
331
|
+
|
|
574
332
|
getCompletions(prefix: string, nsName?: string): string[] {
|
|
575
|
-
let env: Env | null = registry.get(nsName ?? currentNs) ?? null
|
|
333
|
+
let env: Env | null = runtime.registry.get(nsName ?? currentNs) ?? null
|
|
576
334
|
const seen = new Set<string>()
|
|
577
335
|
while (env) {
|
|
578
336
|
for (const key of env.bindings.keys()) seen.add(key)
|
|
@@ -584,7 +342,8 @@ function buildSessionApi(
|
|
|
584
342
|
return candidates.filter((k) => k.startsWith(prefix)).sort()
|
|
585
343
|
},
|
|
586
344
|
}
|
|
587
|
-
|
|
345
|
+
|
|
346
|
+
return session
|
|
588
347
|
}
|
|
589
348
|
|
|
590
349
|
// ---------------------------------------------------------------------------
|
|
@@ -592,25 +351,42 @@ function buildSessionApi(
|
|
|
592
351
|
// ---------------------------------------------------------------------------
|
|
593
352
|
|
|
594
353
|
export function createSession(options?: SessionOptions): Session {
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
registry.set('clojure.core', coreEnv)
|
|
354
|
+
const modules = options?.modules ?? []
|
|
355
|
+
const runtime = createRuntime({
|
|
356
|
+
sourceRoots: options?.sourceRoots,
|
|
357
|
+
readFile: options?.readFile,
|
|
358
|
+
})
|
|
601
359
|
|
|
602
|
-
const
|
|
603
|
-
userEnv.ns = makeNamespace('user')
|
|
604
|
-
registry.set('user', userEnv)
|
|
605
|
-
|
|
606
|
-
const session = buildSessionApi({ registry, currentNs: 'user' }, options)
|
|
360
|
+
const session = buildSessionFacade(runtime, 'user', options)
|
|
607
361
|
|
|
362
|
+
// Bootstrap: load clojure.core source (uses session's ctx via session.loadFile)
|
|
608
363
|
const coreLoader = builtInNamespaceSources['clojure.core']
|
|
609
364
|
if (!coreLoader) {
|
|
610
365
|
throw new Error('Missing built-in clojure.core source in registry')
|
|
611
366
|
}
|
|
612
367
|
session.loadFile(coreLoader(), 'clojure.core')
|
|
613
368
|
|
|
369
|
+
if (modules.length > 0) {
|
|
370
|
+
session.runtime.installModules(modules)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Intern host bindings into the js namespace as CljJsValue vars.
|
|
374
|
+
// Guard: built-in utility names (js/get, js/set!, js/call, etc.) must not be
|
|
375
|
+
// clobbered — they are already installed by makeJsModule() above.
|
|
376
|
+
if (options?.hostBindings) {
|
|
377
|
+
const jsEnv = runtime.getNamespaceEnv('js')
|
|
378
|
+
if (jsEnv) {
|
|
379
|
+
for (const [name, rawValue] of Object.entries(options.hostBindings)) {
|
|
380
|
+
if (jsEnv.ns?.vars.has(name)) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
`createSession: hostBindings key '${name}' conflicts with built-in js/${name} — choose a different key`
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
internVar(name, jsToClj(rawValue), jsEnv)
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
614
390
|
for (const source of options?.entries ?? []) {
|
|
615
391
|
session.loadFile(source)
|
|
616
392
|
}
|
|
@@ -619,24 +395,14 @@ export function createSession(options?: SessionOptions): Session {
|
|
|
619
395
|
}
|
|
620
396
|
|
|
621
397
|
/**
|
|
622
|
-
* A snapshot of a session's
|
|
398
|
+
* A snapshot of a session's runtime state + current namespace.
|
|
623
399
|
* Produced by snapshotSession; consumed by createSessionFromSnapshot.
|
|
624
400
|
* CljValue objects are shared (they are immutable); only the Env containers
|
|
625
401
|
* are deep-copied so each derived session gets independent namespace state.
|
|
626
402
|
*/
|
|
627
|
-
export type SessionSnapshot = {
|
|
628
|
-
registry: NamespaceRegistry
|
|
629
|
-
currentNs: string
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
/**
|
|
633
|
-
* Capture a deep clone of the session's env state.
|
|
634
|
-
* Typically called once after createSession() and before any user code is
|
|
635
|
-
* evaluated, to produce a pristine snapshot that can be cloned cheaply per test.
|
|
636
|
-
*/
|
|
637
403
|
export function snapshotSession(session: Session): SessionSnapshot {
|
|
638
404
|
return {
|
|
639
|
-
|
|
405
|
+
runtimeSnapshot: session.runtime.snapshot(),
|
|
640
406
|
currentNs: session.currentNs,
|
|
641
407
|
}
|
|
642
408
|
}
|
|
@@ -645,17 +411,17 @@ export function snapshotSession(session: Session): SessionSnapshot {
|
|
|
645
411
|
* Create a new session from a previously captured snapshot.
|
|
646
412
|
* Skips the core.clj bootstrap — the cloned registry already contains the
|
|
647
413
|
* fully-expanded core environment. Re-wires all registry-dependent closures
|
|
648
|
-
* (resolveNs, require) to the new registry instance.
|
|
414
|
+
* (resolveNs, require, IO fns) to the new registry instance.
|
|
649
415
|
*/
|
|
650
416
|
export function createSessionFromSnapshot(
|
|
651
417
|
snapshot: SessionSnapshot,
|
|
652
418
|
options?: SessionOptions
|
|
653
419
|
): Session {
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
)
|
|
420
|
+
const runtime = restoreRuntime(snapshot.runtimeSnapshot, {
|
|
421
|
+
sourceRoots: options?.sourceRoots,
|
|
422
|
+
readFile: options?.readFile,
|
|
423
|
+
})
|
|
424
|
+
const session = buildSessionFacade(runtime, snapshot.currentNs, options)
|
|
659
425
|
for (const source of options?.entries ?? []) {
|
|
660
426
|
session.loadFile(source)
|
|
661
427
|
}
|