conjure-js 0.0.11 → 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 +9336 -5028
- package/dist-vite-plugin/index.mjs +10455 -0
- package/package.json +9 -2
- package/src/bin/cli.ts +2 -2
- package/src/bin/nrepl-symbol.ts +150 -0
- package/src/bin/nrepl.ts +301 -157
- package/src/bin/version.ts +1 -1
- package/src/clojure/core.clj +764 -29
- package/src/clojure/core.clj.d.ts +76 -4
- package/src/clojure/demo/math.clj +5 -1
- package/src/clojure/generated/builtin-namespace-registry.ts +4 -0
- package/src/clojure/generated/clojure-core-source.ts +765 -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 +42 -7
- package/src/core/errors.ts +8 -0
- 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 +30 -4
- package/src/core/evaluator/destructure.ts +180 -69
- package/src/core/evaluator/dispatch.ts +24 -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 +380 -173
- package/src/core/factories.ts +182 -3
- package/src/core/index.ts +55 -5
- package/src/core/module.ts +136 -0
- package/src/core/ns-forms.ts +107 -0
- package/src/core/positions.ts +9 -2
- package/src/core/printer.ts +371 -11
- package/src/core/reader.ts +127 -29
- package/src/core/registry.ts +209 -0
- package/src/core/runtime.ts +376 -0
- package/src/core/session.ts +263 -478
- package/src/core/stdlib/arithmetic.ts +516 -215
- package/src/core/stdlib/async-fns.ts +132 -0
- package/src/core/stdlib/atoms.ts +286 -63
- package/src/core/stdlib/errors.ts +54 -50
- package/src/core/stdlib/hof.ts +74 -173
- 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 +109 -28
- package/src/core/stdlib/predicates.ts +322 -196
- 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 +283 -147
- package/src/core/stdlib/vars.ts +27 -27
- package/src/core/stdlib/vectors.ts +122 -0
- package/src/core/tokenizer.ts +13 -3
- package/src/core/transformations.ts +117 -9
- package/src/core/types.ts +118 -6
- package/src/host/node-host-module.ts +74 -0
- package/src/nrepl/relay.ts +432 -0
- package/src/vite-plugin-clj/codegen.ts +87 -95
- package/src/vite-plugin-clj/index.ts +242 -18
- 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 -63
- package/src/clojure/demo.clj.d.ts +0 -0
- package/src/core/core-env.ts +0 -60
- package/src/core/stdlib/collections.ts +0 -784
- package/src/host/node.ts +0 -55
package/src/core/session.ts
CHANGED
|
@@ -1,495 +1,185 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { loadCoreFunctions } from './core-env'
|
|
3
|
-
import { define, 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
|
-
loadFile: (source: string, nsName?: string) => string
|
|
30
|
-
|
|
58
|
+
loadFile: (source: string, nsName?: string, filePath?: string) => string
|
|
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
|
-
define(
|
|
346
|
-
'println',
|
|
347
|
-
cljNativeFunction('println', (...args: CljValue[]) => {
|
|
348
|
-
emitFn(args.map(valueToString).join(' '))
|
|
349
|
-
return cljNil()
|
|
350
|
-
}),
|
|
351
|
-
coreEnv
|
|
352
|
-
)
|
|
353
|
-
define(
|
|
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
|
|
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)),
|
|
394
110
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
const nsEnv = makeEnv(coreEnv)
|
|
399
|
-
nsEnv.ns = makeNamespace(name)
|
|
400
|
-
registry.set(name, nsEnv)
|
|
401
|
-
}
|
|
402
|
-
return registry.get(name)!
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function setNs(name: string) {
|
|
406
|
-
ensureNs(name)
|
|
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
|
-
define(
|
|
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
|
-
processNsRequires(forms, env)
|
|
465
|
-
for (const form of forms) {
|
|
466
|
-
const expanded = ctx.expandAll(form, env)
|
|
467
|
-
ctx.evaluate(expanded, env)
|
|
468
|
-
}
|
|
469
|
-
return targetNs
|
|
470
|
-
}
|
|
131
|
+
setNs(name: string) {
|
|
132
|
+
runtime.ensureNamespace(name)
|
|
133
|
+
currentNs = name
|
|
134
|
+
runtime.syncNsVar(name)
|
|
135
|
+
},
|
|
471
136
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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 })
|
|
475
157
|
return currentNs
|
|
476
158
|
},
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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 {
|
|
168
|
+
ctx.currentSource = source
|
|
169
|
+
ctx.currentFile = opts?.file
|
|
170
|
+
ctx.currentLineOffset = opts?.lineOffset ?? 0
|
|
171
|
+
ctx.currentColOffset = opts?.colOffset ?? 0
|
|
482
172
|
try {
|
|
483
173
|
const tokens = tokenize(source)
|
|
484
174
|
// If source opens with an ns declaration, switch to that namespace
|
|
485
175
|
// so requires resolve against the right env and currentNs is updated.
|
|
486
|
-
// This mirrors what loadFile does, making eval and load-file consistent.
|
|
487
176
|
const declaredNs = extractNsNameFromTokens(tokens)
|
|
488
177
|
if (declaredNs) {
|
|
489
|
-
|
|
178
|
+
runtime.ensureNamespace(declaredNs)
|
|
490
179
|
currentNs = declaredNs
|
|
180
|
+
runtime.syncNsVar(declaredNs)
|
|
491
181
|
}
|
|
492
|
-
const env =
|
|
182
|
+
const env = runtime.getNamespaceEnv(currentNs)!
|
|
493
183
|
// Seed alias map from tokens (new aliases declared in this source) and
|
|
494
184
|
// from ns.aliases/:as (prior require calls) and ns.readerAliases/:as-alias.
|
|
495
185
|
const aliasMap = extractAliasMapFromTokens(tokens)
|
|
@@ -500,8 +190,8 @@ function buildSessionApi(
|
|
|
500
190
|
aliasMap.set(alias, nsName)
|
|
501
191
|
})
|
|
502
192
|
const forms = readForms(tokens, currentNs, aliasMap)
|
|
503
|
-
processNsRequires(forms, env)
|
|
504
|
-
let result: CljValue =
|
|
193
|
+
runtime.processNsRequires(forms, env, ctx)
|
|
194
|
+
let result: CljValue = v.nil()
|
|
505
195
|
for (const form of forms) {
|
|
506
196
|
const expanded = ctx.expandAll(form, env)
|
|
507
197
|
result = ctx.evaluate(expanded, env)
|
|
@@ -523,15 +213,101 @@ function buildSessionApi(
|
|
|
523
213
|
(e instanceof EvaluationError || e instanceof ReaderError) &&
|
|
524
214
|
e.pos
|
|
525
215
|
) {
|
|
526
|
-
e.message += formatErrorContext(source, e.pos
|
|
216
|
+
e.message += formatErrorContext(source, e.pos, {
|
|
217
|
+
lineOffset: ctx.currentLineOffset,
|
|
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,
|
|
290
|
+
})
|
|
527
291
|
}
|
|
528
292
|
throw e
|
|
293
|
+
} finally {
|
|
294
|
+
ctx.currentSource = undefined
|
|
295
|
+
ctx.currentFile = undefined
|
|
529
296
|
}
|
|
530
297
|
},
|
|
531
|
-
|
|
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 {
|
|
532
308
|
try {
|
|
533
|
-
const env =
|
|
534
|
-
let result: CljValue =
|
|
309
|
+
const env = runtime.getNamespaceEnv(currentNs)!
|
|
310
|
+
let result: CljValue = v.nil()
|
|
535
311
|
for (const form of forms) {
|
|
536
312
|
const expanded = ctx.expandAll(form, env)
|
|
537
313
|
result = ctx.evaluate(expanded, env)
|
|
@@ -552,8 +328,9 @@ function buildSessionApi(
|
|
|
552
328
|
throw e
|
|
553
329
|
}
|
|
554
330
|
},
|
|
331
|
+
|
|
555
332
|
getCompletions(prefix: string, nsName?: string): string[] {
|
|
556
|
-
let env: Env | null = registry.get(nsName ?? currentNs) ?? null
|
|
333
|
+
let env: Env | null = runtime.registry.get(nsName ?? currentNs) ?? null
|
|
557
334
|
const seen = new Set<string>()
|
|
558
335
|
while (env) {
|
|
559
336
|
for (const key of env.bindings.keys()) seen.add(key)
|
|
@@ -565,7 +342,8 @@ function buildSessionApi(
|
|
|
565
342
|
return candidates.filter((k) => k.startsWith(prefix)).sort()
|
|
566
343
|
},
|
|
567
344
|
}
|
|
568
|
-
|
|
345
|
+
|
|
346
|
+
return session
|
|
569
347
|
}
|
|
570
348
|
|
|
571
349
|
// ---------------------------------------------------------------------------
|
|
@@ -573,25 +351,42 @@ function buildSessionApi(
|
|
|
573
351
|
// ---------------------------------------------------------------------------
|
|
574
352
|
|
|
575
353
|
export function createSession(options?: SessionOptions): Session {
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
registry.set('clojure.core', coreEnv)
|
|
582
|
-
|
|
583
|
-
const userEnv = makeEnv(coreEnv)
|
|
584
|
-
userEnv.ns = makeNamespace('user')
|
|
585
|
-
registry.set('user', userEnv)
|
|
354
|
+
const modules = options?.modules ?? []
|
|
355
|
+
const runtime = createRuntime({
|
|
356
|
+
sourceRoots: options?.sourceRoots,
|
|
357
|
+
readFile: options?.readFile,
|
|
358
|
+
})
|
|
586
359
|
|
|
587
|
-
const session =
|
|
360
|
+
const session = buildSessionFacade(runtime, 'user', options)
|
|
588
361
|
|
|
362
|
+
// Bootstrap: load clojure.core source (uses session's ctx via session.loadFile)
|
|
589
363
|
const coreLoader = builtInNamespaceSources['clojure.core']
|
|
590
364
|
if (!coreLoader) {
|
|
591
365
|
throw new Error('Missing built-in clojure.core source in registry')
|
|
592
366
|
}
|
|
593
367
|
session.loadFile(coreLoader(), 'clojure.core')
|
|
594
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
|
+
|
|
595
390
|
for (const source of options?.entries ?? []) {
|
|
596
391
|
session.loadFile(source)
|
|
597
392
|
}
|
|
@@ -600,24 +395,14 @@ export function createSession(options?: SessionOptions): Session {
|
|
|
600
395
|
}
|
|
601
396
|
|
|
602
397
|
/**
|
|
603
|
-
* A snapshot of a session's
|
|
398
|
+
* A snapshot of a session's runtime state + current namespace.
|
|
604
399
|
* Produced by snapshotSession; consumed by createSessionFromSnapshot.
|
|
605
400
|
* CljValue objects are shared (they are immutable); only the Env containers
|
|
606
401
|
* are deep-copied so each derived session gets independent namespace state.
|
|
607
402
|
*/
|
|
608
|
-
export type SessionSnapshot = {
|
|
609
|
-
registry: NamespaceRegistry
|
|
610
|
-
currentNs: string
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
/**
|
|
614
|
-
* Capture a deep clone of the session's env state.
|
|
615
|
-
* Typically called once after createSession() and before any user code is
|
|
616
|
-
* evaluated, to produce a pristine snapshot that can be cloned cheaply per test.
|
|
617
|
-
*/
|
|
618
403
|
export function snapshotSession(session: Session): SessionSnapshot {
|
|
619
404
|
return {
|
|
620
|
-
|
|
405
|
+
runtimeSnapshot: session.runtime.snapshot(),
|
|
621
406
|
currentNs: session.currentNs,
|
|
622
407
|
}
|
|
623
408
|
}
|
|
@@ -626,17 +411,17 @@ export function snapshotSession(session: Session): SessionSnapshot {
|
|
|
626
411
|
* Create a new session from a previously captured snapshot.
|
|
627
412
|
* Skips the core.clj bootstrap — the cloned registry already contains the
|
|
628
413
|
* fully-expanded core environment. Re-wires all registry-dependent closures
|
|
629
|
-
* (resolveNs, require) to the new registry instance.
|
|
414
|
+
* (resolveNs, require, IO fns) to the new registry instance.
|
|
630
415
|
*/
|
|
631
416
|
export function createSessionFromSnapshot(
|
|
632
417
|
snapshot: SessionSnapshot,
|
|
633
418
|
options?: SessionOptions
|
|
634
419
|
): Session {
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
)
|
|
420
|
+
const runtime = restoreRuntime(snapshot.runtimeSnapshot, {
|
|
421
|
+
sourceRoots: options?.sourceRoots,
|
|
422
|
+
readFile: options?.readFile,
|
|
423
|
+
})
|
|
424
|
+
const session = buildSessionFacade(runtime, snapshot.currentNs, options)
|
|
640
425
|
for (const source of options?.entries ?? []) {
|
|
641
426
|
session.loadFile(source)
|
|
642
427
|
}
|