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,209 @@
|
|
|
1
|
+
import { isKeyword, isSymbol, isVector } from './assertions'
|
|
2
|
+
import { makeEnv, makeNamespace } from './env'
|
|
3
|
+
import { EvaluationError } from './errors'
|
|
4
|
+
import type { CljValue, Env } from './types'
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Core types
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
export type NamespaceRegistry = Map<string, Env>
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Clone helpers — used by snapshot / restoreRuntime
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
function cloneBindings(bindings: Map<string, CljValue>): Map<string, CljValue> {
|
|
17
|
+
const out = new Map<string, CljValue>()
|
|
18
|
+
for (const [k, v] of bindings) {
|
|
19
|
+
out.set(k, v.kind === 'var' ? { ...v } : v)
|
|
20
|
+
}
|
|
21
|
+
return out
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function cloneEnv(env: Env, memo: Map<Env, Env>): Env {
|
|
25
|
+
if (memo.has(env)) return memo.get(env)!
|
|
26
|
+
const cloned: Env = {
|
|
27
|
+
bindings: cloneBindings(env.bindings),
|
|
28
|
+
outer: null,
|
|
29
|
+
}
|
|
30
|
+
if (env.ns) {
|
|
31
|
+
cloned.ns = {
|
|
32
|
+
kind: 'namespace',
|
|
33
|
+
name: env.ns.name,
|
|
34
|
+
vars: new Map([...env.ns.vars].map(([k, v]) => [k, { ...v }])),
|
|
35
|
+
aliases: new Map(), // wired in cloneRegistry pass 2
|
|
36
|
+
readerAliases: new Map(env.ns.readerAliases),
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
memo.set(env, cloned)
|
|
40
|
+
if (env.outer) cloned.outer = cloneEnv(env.outer, memo)
|
|
41
|
+
return cloned
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function cloneRegistry(registry: NamespaceRegistry): NamespaceRegistry {
|
|
45
|
+
const memo = new Map<Env, Env>()
|
|
46
|
+
const next = new Map<string, Env>()
|
|
47
|
+
// Pass 1: clone all envs (ns.aliases left empty)
|
|
48
|
+
for (const [name, env] of registry) {
|
|
49
|
+
next.set(name, cloneEnv(env, memo))
|
|
50
|
+
}
|
|
51
|
+
// Pass 2: wire ns.aliases to the cloned CljNamespace objects
|
|
52
|
+
for (const [name, env] of registry) {
|
|
53
|
+
const clonedEnv = next.get(name)!
|
|
54
|
+
if (env.ns && clonedEnv.ns) {
|
|
55
|
+
for (const [alias, origNs] of env.ns.aliases) {
|
|
56
|
+
const targetCloned = next.get(origNs.name)
|
|
57
|
+
if (targetCloned?.ns) clonedEnv.ns.aliases.set(alias, targetCloned.ns)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return next
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// ensureNamespaceInRegistry — creates namespace env if it doesn't exist yet
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
export function ensureNamespaceInRegistry(
|
|
69
|
+
registry: NamespaceRegistry,
|
|
70
|
+
coreEnv: Env,
|
|
71
|
+
name: string
|
|
72
|
+
): Env {
|
|
73
|
+
if (!registry.has(name)) {
|
|
74
|
+
const nsEnv = makeEnv(coreEnv)
|
|
75
|
+
nsEnv.ns = makeNamespace(name)
|
|
76
|
+
registry.set(name, nsEnv)
|
|
77
|
+
}
|
|
78
|
+
return registry.get(name)!
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// processRequireSpec — processes a single [ns.name :as alias :refer [...]] spec.
|
|
83
|
+
// resolveNs is called when the target namespace isn't yet loaded.
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
export function processRequireSpec(
|
|
87
|
+
spec: CljValue,
|
|
88
|
+
currentEnv: Env,
|
|
89
|
+
registry: NamespaceRegistry,
|
|
90
|
+
resolveNs?: (nsName: string) => boolean
|
|
91
|
+
): void {
|
|
92
|
+
if (!isVector(spec)) {
|
|
93
|
+
throw new EvaluationError(
|
|
94
|
+
'require spec must be a vector, e.g. [my.ns :as alias]',
|
|
95
|
+
{ spec }
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const elements = spec.value
|
|
100
|
+
if (elements.length === 0 || !isSymbol(elements[0])) {
|
|
101
|
+
throw new EvaluationError(
|
|
102
|
+
'First element of require spec must be a namespace symbol',
|
|
103
|
+
{ spec }
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const nsName = elements[0].name
|
|
108
|
+
|
|
109
|
+
const hasAsAlias = elements.some(
|
|
110
|
+
(el) => isKeyword(el) && el.name === ':as-alias'
|
|
111
|
+
)
|
|
112
|
+
if (hasAsAlias) {
|
|
113
|
+
let i = 1
|
|
114
|
+
while (i < elements.length) {
|
|
115
|
+
const kw = elements[i]
|
|
116
|
+
if (!isKeyword(kw)) {
|
|
117
|
+
throw new EvaluationError(
|
|
118
|
+
`Expected keyword in require spec, got ${kw.kind}`,
|
|
119
|
+
{ spec, position: i }
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
if (kw.name === ':as-alias') {
|
|
123
|
+
i++
|
|
124
|
+
const alias = elements[i]
|
|
125
|
+
if (!alias || !isSymbol(alias)) {
|
|
126
|
+
throw new EvaluationError(':as-alias expects a symbol alias', {
|
|
127
|
+
spec,
|
|
128
|
+
position: i,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
currentEnv.ns!.readerAliases.set(alias.name, nsName)
|
|
132
|
+
i++
|
|
133
|
+
} else {
|
|
134
|
+
throw new EvaluationError(
|
|
135
|
+
`:as-alias specs only support :as-alias, got ${kw.name}`,
|
|
136
|
+
{ spec }
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let targetEnv = registry.get(nsName)
|
|
144
|
+
if (!targetEnv && resolveNs) {
|
|
145
|
+
resolveNs(nsName)
|
|
146
|
+
targetEnv = registry.get(nsName)
|
|
147
|
+
}
|
|
148
|
+
if (!targetEnv) {
|
|
149
|
+
throw new EvaluationError(
|
|
150
|
+
`Namespace ${nsName} not found. Only already-loaded namespaces can be required.`,
|
|
151
|
+
{ nsName }
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let i = 1
|
|
156
|
+
while (i < elements.length) {
|
|
157
|
+
const kw = elements[i]
|
|
158
|
+
if (!isKeyword(kw)) {
|
|
159
|
+
throw new EvaluationError(
|
|
160
|
+
`Expected keyword in require spec, got ${kw.kind}`,
|
|
161
|
+
{ spec, position: i }
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (kw.name === ':as') {
|
|
166
|
+
i++
|
|
167
|
+
const alias = elements[i]
|
|
168
|
+
if (!alias || !isSymbol(alias)) {
|
|
169
|
+
throw new EvaluationError(':as expects a symbol alias', {
|
|
170
|
+
spec,
|
|
171
|
+
position: i,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
currentEnv.ns!.aliases.set(alias.name, targetEnv.ns!)
|
|
175
|
+
i++
|
|
176
|
+
} else if (kw.name === ':refer') {
|
|
177
|
+
i++
|
|
178
|
+
const symsVec = elements[i]
|
|
179
|
+
if (!symsVec || !isVector(symsVec)) {
|
|
180
|
+
throw new EvaluationError(':refer expects a vector of symbols', {
|
|
181
|
+
spec,
|
|
182
|
+
position: i,
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
for (const sym of symsVec.value) {
|
|
186
|
+
if (!isSymbol(sym)) {
|
|
187
|
+
throw new EvaluationError(':refer vector must contain only symbols', {
|
|
188
|
+
spec,
|
|
189
|
+
sym,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
const v = targetEnv.ns!.vars.get(sym.name)
|
|
193
|
+
if (v === undefined) {
|
|
194
|
+
throw new EvaluationError(
|
|
195
|
+
`Symbol ${sym.name} not found in namespace ${nsName}`,
|
|
196
|
+
{ nsName, symbol: sym.name }
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
currentEnv.ns!.vars.set(sym.name, v)
|
|
200
|
+
}
|
|
201
|
+
i++
|
|
202
|
+
} else {
|
|
203
|
+
throw new EvaluationError(
|
|
204
|
+
`Unknown require option ${kw.name}. Supported: :as, :refer`,
|
|
205
|
+
{ spec, keyword: kw.name }
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { isKeyword, isString, isSymbol, isVector } from './assertions'
|
|
2
|
+
import {
|
|
3
|
+
resolveModuleOrder,
|
|
4
|
+
type RuntimeModule,
|
|
5
|
+
type ModuleContext,
|
|
6
|
+
} from './module'
|
|
7
|
+
import { internVar, makeEnv, makeNamespace } from './env'
|
|
8
|
+
import { EvaluationError } from './errors'
|
|
9
|
+
import { v } from './factories'
|
|
10
|
+
import { readForms } from './reader'
|
|
11
|
+
import { tokenize } from './tokenizer'
|
|
12
|
+
import type { CljNamespace, CljValue, Env, EvaluationContext } from './types'
|
|
13
|
+
import { builtInNamespaceSources } from '../clojure/generated/builtin-namespace-registry'
|
|
14
|
+
import { makeCoreModule } from './core-module'
|
|
15
|
+
import { makeJsModule } from './stdlib/js-namespace'
|
|
16
|
+
import {
|
|
17
|
+
cloneRegistry,
|
|
18
|
+
ensureNamespaceInRegistry,
|
|
19
|
+
processRequireSpec,
|
|
20
|
+
} from './registry'
|
|
21
|
+
import type { NamespaceRegistry } from './registry'
|
|
22
|
+
import {
|
|
23
|
+
extractAliasMapFromTokens,
|
|
24
|
+
extractNsNameFromTokens,
|
|
25
|
+
extractRequireClauses,
|
|
26
|
+
} from './ns-forms'
|
|
27
|
+
import { wireIdeStubs, wireNsCore } from './bootstrap'
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Core types
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
export type { NamespaceRegistry }
|
|
34
|
+
|
|
35
|
+
export type RuntimeSnapshot = {
|
|
36
|
+
registry: NamespaceRegistry
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type RuntimeOptions = {
|
|
40
|
+
sourceRoots?: string[]
|
|
41
|
+
readFile?: (filePath: string) => string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type Runtime = {
|
|
45
|
+
readonly registry: NamespaceRegistry
|
|
46
|
+
|
|
47
|
+
// Namespace management
|
|
48
|
+
ensureNamespace(name: string): Env
|
|
49
|
+
getNamespaceEnv(name: string): Env | null
|
|
50
|
+
getNs(name: string): CljNamespace | null
|
|
51
|
+
/** Updates the *ns* var root to reflect the named namespace. Called by session.setNs. */
|
|
52
|
+
syncNsVar(name: string): void
|
|
53
|
+
addSourceRoot(path: string): void
|
|
54
|
+
|
|
55
|
+
// Require mechanics — ctx is threaded through so lazy namespace loading works
|
|
56
|
+
processRequireSpec(spec: CljValue, fromEnv: Env, ctx: EvaluationContext): void
|
|
57
|
+
processNsRequires(
|
|
58
|
+
forms: CljValue[],
|
|
59
|
+
fromEnv: Env,
|
|
60
|
+
ctx: EvaluationContext
|
|
61
|
+
): void
|
|
62
|
+
/**
|
|
63
|
+
* Async variant of processNsRequires.
|
|
64
|
+
* Handles both symbol specs (sync) and string specs (async via ctx.importModule).
|
|
65
|
+
* Must be used when the ns form contains string (:require ["module" :as Alias]) entries.
|
|
66
|
+
*/
|
|
67
|
+
processNsRequiresAsync(
|
|
68
|
+
forms: CljValue[],
|
|
69
|
+
fromEnv: Env,
|
|
70
|
+
ctx: EvaluationContext
|
|
71
|
+
): Promise<void>
|
|
72
|
+
|
|
73
|
+
// File loading — ctx comes from the owning session
|
|
74
|
+
loadFile(
|
|
75
|
+
source: string,
|
|
76
|
+
nsName: string | undefined,
|
|
77
|
+
filePath: string | undefined,
|
|
78
|
+
ctx: EvaluationContext
|
|
79
|
+
): string
|
|
80
|
+
|
|
81
|
+
// Snapshot
|
|
82
|
+
snapshot(): RuntimeSnapshot
|
|
83
|
+
|
|
84
|
+
// Module installation — declarative capability extension
|
|
85
|
+
installModules(modules: RuntimeModule[]): void
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// buildRuntime — shared factory used by createRuntime and restoreRuntime.
|
|
90
|
+
// Orchestrates registry wiring and native fn installation via bootstrap.ts.
|
|
91
|
+
// Does NOT load clojure.core source — that's the session's bootstrap job.
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
function buildRuntime(
|
|
95
|
+
registry: NamespaceRegistry,
|
|
96
|
+
coreEnv: Env,
|
|
97
|
+
options: RuntimeOptions | undefined
|
|
98
|
+
): Runtime {
|
|
99
|
+
const sourceRoots = new Set<string>(options?.sourceRoots ?? [])
|
|
100
|
+
|
|
101
|
+
// varOwners tracks which module installed each var, keyed by "ns/varName".
|
|
102
|
+
// Prevents two modules from declaring the same var in the same namespace.
|
|
103
|
+
const varOwners = new Map<string, string>()
|
|
104
|
+
|
|
105
|
+
// currentNsRef mirrors the owning session's currentNs for require/resolve.
|
|
106
|
+
// Updated via runtime.syncNsVar() which session.setNs calls.
|
|
107
|
+
let currentNsRef = 'user'
|
|
108
|
+
|
|
109
|
+
// resolveNamespace: loads a namespace by name if not already in the registry.
|
|
110
|
+
// ctx is passed explicitly — this is the active EvaluationContext at the
|
|
111
|
+
// call site. For loadFile calls, it's the file's ctx. For runtime require
|
|
112
|
+
// native fn calls, it comes from the evaluator via cljNativeFunctionWithContext.
|
|
113
|
+
function resolveNamespace(nsName: string, ctx: EvaluationContext): boolean {
|
|
114
|
+
const builtInLoader = builtInNamespaceSources[nsName]
|
|
115
|
+
if (builtInLoader) {
|
|
116
|
+
runtime.loadFile(builtInLoader(), nsName, undefined, ctx)
|
|
117
|
+
return true
|
|
118
|
+
}
|
|
119
|
+
if (!options?.readFile || sourceRoots.size === 0) return false
|
|
120
|
+
for (const root of sourceRoots) {
|
|
121
|
+
const filePath = `${root.replace(/\/$/, '')}/${nsName.replace(/\./g, '/')}.clj`
|
|
122
|
+
try {
|
|
123
|
+
const source = options.readFile(filePath)
|
|
124
|
+
if (source) {
|
|
125
|
+
runtime.loadFile(source, undefined, undefined, ctx)
|
|
126
|
+
return true
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
continue
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return false
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
wireNsCore(registry, coreEnv, () => currentNsRef, resolveNamespace)
|
|
136
|
+
wireIdeStubs(registry, coreEnv)
|
|
137
|
+
|
|
138
|
+
const runtime: Runtime = {
|
|
139
|
+
get registry() {
|
|
140
|
+
return registry
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
ensureNamespace(name: string): Env {
|
|
144
|
+
return ensureNamespaceInRegistry(registry, coreEnv, name)
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
getNamespaceEnv(name: string): Env | null {
|
|
148
|
+
return registry.get(name) ?? null
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
getNs(name: string): CljNamespace | null {
|
|
152
|
+
return registry.get(name)?.ns ?? null
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
syncNsVar(name: string): void {
|
|
156
|
+
currentNsRef = name
|
|
157
|
+
const nsVarInner = coreEnv.ns?.vars.get('*ns*')
|
|
158
|
+
if (nsVarInner) {
|
|
159
|
+
const nsObj = registry.get(name)?.ns
|
|
160
|
+
if (nsObj) nsVarInner.value = nsObj
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
addSourceRoot(path: string): void {
|
|
165
|
+
sourceRoots.add(path)
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
processRequireSpec(
|
|
169
|
+
spec: CljValue,
|
|
170
|
+
fromEnv: Env,
|
|
171
|
+
ctx: EvaluationContext
|
|
172
|
+
): void {
|
|
173
|
+
processRequireSpec(spec, fromEnv, registry, (nsName) =>
|
|
174
|
+
resolveNamespace(nsName, ctx)
|
|
175
|
+
)
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
processNsRequires(
|
|
179
|
+
forms: CljValue[],
|
|
180
|
+
fromEnv: Env,
|
|
181
|
+
ctx: EvaluationContext
|
|
182
|
+
): void {
|
|
183
|
+
const requireClauses = extractRequireClauses(forms)
|
|
184
|
+
for (const specs of requireClauses) {
|
|
185
|
+
for (const spec of specs) {
|
|
186
|
+
if (
|
|
187
|
+
isVector(spec) &&
|
|
188
|
+
spec.value.length > 0 &&
|
|
189
|
+
isString(spec.value[0])
|
|
190
|
+
) {
|
|
191
|
+
const specifier = spec.value[0].value
|
|
192
|
+
throw new EvaluationError(
|
|
193
|
+
`String module require ["${specifier}" :as ...] is async — use evaluateAsync() instead of evaluate()`,
|
|
194
|
+
{ specifier }
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
processRequireSpec(spec, fromEnv, registry, (nsName) =>
|
|
198
|
+
resolveNamespace(nsName, ctx)
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
async processNsRequiresAsync(
|
|
205
|
+
forms: CljValue[],
|
|
206
|
+
fromEnv: Env,
|
|
207
|
+
ctx: EvaluationContext
|
|
208
|
+
): Promise<void> {
|
|
209
|
+
const requireClauses = extractRequireClauses(forms)
|
|
210
|
+
for (const specs of requireClauses) {
|
|
211
|
+
for (const spec of specs) {
|
|
212
|
+
if (
|
|
213
|
+
isVector(spec) &&
|
|
214
|
+
spec.value.length > 0 &&
|
|
215
|
+
isString(spec.value[0])
|
|
216
|
+
) {
|
|
217
|
+
// String module require — calls importModule and interns result as a var
|
|
218
|
+
const specifier = spec.value[0].value
|
|
219
|
+
if (!ctx.importModule) {
|
|
220
|
+
throw new EvaluationError(
|
|
221
|
+
`importModule is not configured; cannot require "${specifier}". Pass importModule to createSession().`,
|
|
222
|
+
{ specifier }
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
const elements = spec.value
|
|
226
|
+
let aliasName: string | null = null
|
|
227
|
+
for (let i = 1; i < elements.length; i++) {
|
|
228
|
+
if (
|
|
229
|
+
isKeyword(elements[i]) &&
|
|
230
|
+
(elements[i] as { name: string }).name === ':as'
|
|
231
|
+
) {
|
|
232
|
+
i++
|
|
233
|
+
const aliasSym = elements[i]
|
|
234
|
+
if (!aliasSym || !isSymbol(aliasSym)) {
|
|
235
|
+
throw new EvaluationError(':as expects a symbol alias', {
|
|
236
|
+
spec,
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
aliasName = aliasSym.name
|
|
240
|
+
break
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (aliasName === null) {
|
|
244
|
+
throw new EvaluationError(
|
|
245
|
+
`String require spec must have an :as alias: ["${specifier}" :as Alias]`,
|
|
246
|
+
{ spec }
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
const rawModule = await ctx.importModule(specifier)
|
|
250
|
+
internVar(aliasName, v.jsValue(rawModule), fromEnv)
|
|
251
|
+
} else {
|
|
252
|
+
// Symbol require spec — sync path
|
|
253
|
+
processRequireSpec(spec, fromEnv, registry, (nsName) =>
|
|
254
|
+
resolveNamespace(nsName, ctx)
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
loadFile(
|
|
262
|
+
source: string,
|
|
263
|
+
nsName: string | undefined,
|
|
264
|
+
filePath: string | undefined,
|
|
265
|
+
ctx: EvaluationContext
|
|
266
|
+
): string {
|
|
267
|
+
const tokens = tokenize(source)
|
|
268
|
+
const targetNs = extractNsNameFromTokens(tokens) ?? nsName ?? 'user'
|
|
269
|
+
const aliasMap = extractAliasMapFromTokens(tokens)
|
|
270
|
+
const forms = readForms(tokens, targetNs, aliasMap)
|
|
271
|
+
const env = this.ensureNamespace(targetNs)
|
|
272
|
+
ctx.currentSource = source
|
|
273
|
+
ctx.currentFile = filePath
|
|
274
|
+
ctx.currentLineOffset = 0
|
|
275
|
+
ctx.currentColOffset = 0
|
|
276
|
+
this.processNsRequires(forms, env, ctx)
|
|
277
|
+
try {
|
|
278
|
+
for (const form of forms) {
|
|
279
|
+
const expanded = ctx.expandAll(form, env)
|
|
280
|
+
ctx.evaluate(expanded, env)
|
|
281
|
+
}
|
|
282
|
+
} finally {
|
|
283
|
+
ctx.currentSource = undefined
|
|
284
|
+
ctx.currentFile = undefined
|
|
285
|
+
}
|
|
286
|
+
return targetNs
|
|
287
|
+
},
|
|
288
|
+
|
|
289
|
+
installModules(modules: RuntimeModule[]): void {
|
|
290
|
+
const ordered = resolveModuleOrder(modules, new Set(registry.keys()))
|
|
291
|
+
|
|
292
|
+
for (const mod of ordered) {
|
|
293
|
+
for (const decl of mod.declareNs) {
|
|
294
|
+
const nsEnv = ensureNamespaceInRegistry(registry, coreEnv, decl.name)
|
|
295
|
+
|
|
296
|
+
const ctx: ModuleContext = {
|
|
297
|
+
getVar(ns, name) {
|
|
298
|
+
const nsEnv = registry.get(ns)
|
|
299
|
+
const v = nsEnv?.ns?.vars.get(name)
|
|
300
|
+
return v ?? null
|
|
301
|
+
},
|
|
302
|
+
getNamespace(name) {
|
|
303
|
+
return registry.get(name)?.ns ?? null
|
|
304
|
+
},
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const varMap = decl.vars(ctx)
|
|
308
|
+
|
|
309
|
+
for (const [varName, decl] of varMap) {
|
|
310
|
+
const key = `${nsEnv.ns!.name}/${varName}`
|
|
311
|
+
const existing = varOwners.get(key)
|
|
312
|
+
if (existing !== undefined) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
`var '${varName}' in '${nsEnv.ns!.name}' already declared by module '${existing}'`
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
internVar(varName, decl.value, nsEnv, decl.meta)
|
|
318
|
+
if (decl.dynamic) {
|
|
319
|
+
const v = nsEnv.ns!.vars.get(varName)!
|
|
320
|
+
v.dynamic = true
|
|
321
|
+
}
|
|
322
|
+
varOwners.set(key, mod.id)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
snapshot(): RuntimeSnapshot {
|
|
329
|
+
return { registry: cloneRegistry(registry) }
|
|
330
|
+
},
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return runtime
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// createRuntime — builds a fresh runtime with bootstrapped core + user envs.
|
|
338
|
+
// Does NOT load clojure.core source — that's the session's bootstrap job.
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
|
|
341
|
+
export function createRuntime(options?: RuntimeOptions): Runtime {
|
|
342
|
+
const registry: NamespaceRegistry = new Map()
|
|
343
|
+
|
|
344
|
+
// Bootstrap: clojure.core env
|
|
345
|
+
const coreEnv = makeEnv()
|
|
346
|
+
coreEnv.ns = makeNamespace('clojure.core')
|
|
347
|
+
registry.set('clojure.core', coreEnv)
|
|
348
|
+
|
|
349
|
+
// Bootstrap: user env (outer = coreEnv for implicit core access)
|
|
350
|
+
const userEnv = makeEnv(coreEnv)
|
|
351
|
+
userEnv.ns = makeNamespace('user')
|
|
352
|
+
registry.set('user', userEnv)
|
|
353
|
+
|
|
354
|
+
const runtime = buildRuntime(registry, coreEnv, options)
|
|
355
|
+
runtime.installModules([makeCoreModule(), makeJsModule()])
|
|
356
|
+
return runtime
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ---------------------------------------------------------------------------
|
|
360
|
+
// restoreRuntime — re-wires a runtime from a snapshot.
|
|
361
|
+
// The cloned registry already contains fully-evaluated namespaces (including
|
|
362
|
+
// clojure.core), so no source bootstrap is needed. Only wiring is re-applied.
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
|
|
365
|
+
export function restoreRuntime(
|
|
366
|
+
snapshot: RuntimeSnapshot,
|
|
367
|
+
options?: RuntimeOptions
|
|
368
|
+
): Runtime {
|
|
369
|
+
const registry = cloneRegistry(snapshot.registry)
|
|
370
|
+
const coreEnv = registry.get('clojure.core')!
|
|
371
|
+
const runtime = buildRuntime(registry, coreEnv, options)
|
|
372
|
+
// No module reinstallation needed — IO functions (println, print, etc.) read
|
|
373
|
+
// ctx.io.stdout at call time, so the snapshot's native functions automatically
|
|
374
|
+
// use the session's output channel without any rewiring.
|
|
375
|
+
return runtime
|
|
376
|
+
}
|