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
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// Vector-specific operations: vector, vec, subvec, peek, pop
|
|
2
|
+
//
|
|
3
|
+
// These functions are exclusively concerned with vectors (or lists treated as
|
|
4
|
+
// a stack for peek/pop). Pure vector construction and stack operations.
|
|
5
|
+
|
|
6
|
+
import { is } from '../assertions'
|
|
7
|
+
import { EvaluationError } from '../errors'
|
|
8
|
+
import { v } from '../factories'
|
|
9
|
+
import { printString } from '../printer'
|
|
10
|
+
import { toSeq } from '../transformations'
|
|
11
|
+
import { type CljValue } from '../types'
|
|
12
|
+
|
|
13
|
+
export const vectorFunctions: Record<string, CljValue> = {
|
|
14
|
+
vector: v
|
|
15
|
+
.nativeFn('vector', function vectorImpl(...args: CljValue[]) {
|
|
16
|
+
if (args.length === 0) {
|
|
17
|
+
return v.vector([])
|
|
18
|
+
}
|
|
19
|
+
return v.vector(args)
|
|
20
|
+
})
|
|
21
|
+
.doc('Returns a new vector containing the given values.', [['&', 'args']]),
|
|
22
|
+
|
|
23
|
+
vec: v
|
|
24
|
+
.nativeFn('vec', function vecImpl(coll: CljValue) {
|
|
25
|
+
if (coll === undefined || coll.kind === 'nil') return v.vector([])
|
|
26
|
+
if (is.vector(coll)) return coll
|
|
27
|
+
if (!is.seqable(coll)) {
|
|
28
|
+
throw EvaluationError.atArg(
|
|
29
|
+
`vec expects a collection or string, got ${printString(coll)}`,
|
|
30
|
+
{ coll },
|
|
31
|
+
0
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
return v.vector(toSeq(coll))
|
|
35
|
+
})
|
|
36
|
+
.doc('Creates a new vector containing the contents of coll.', [['coll']]),
|
|
37
|
+
|
|
38
|
+
subvec: v
|
|
39
|
+
.nativeFn(
|
|
40
|
+
'subvec',
|
|
41
|
+
function subvecImpl(vector: CljValue, start: CljValue, end?: CljValue) {
|
|
42
|
+
if (vector === undefined || !is.vector(vector)) {
|
|
43
|
+
throw EvaluationError.atArg(
|
|
44
|
+
`subvec expects a vector, got ${printString(vector)}`,
|
|
45
|
+
{ v: vector },
|
|
46
|
+
0
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
if (start === undefined || start.kind !== 'number') {
|
|
50
|
+
throw EvaluationError.atArg(
|
|
51
|
+
`subvec expects a number start index`,
|
|
52
|
+
{ start },
|
|
53
|
+
1
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
const s = start.value
|
|
57
|
+
const e =
|
|
58
|
+
end !== undefined && end.kind === 'number'
|
|
59
|
+
? end.value
|
|
60
|
+
: vector.value.length
|
|
61
|
+
if (s < 0 || e > vector.value.length || s > e) {
|
|
62
|
+
throw new EvaluationError(
|
|
63
|
+
`subvec index out of bounds: start=${s}, end=${e}, length=${vector.value.length}`,
|
|
64
|
+
{ v: vector, start, end }
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
return v.vector(vector.value.slice(s, e))
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
.doc(
|
|
71
|
+
'Returns a persistent vector of the items in vector from start (inclusive) to end (exclusive).',
|
|
72
|
+
[
|
|
73
|
+
['v', 'start'],
|
|
74
|
+
['v', 'start', 'end'],
|
|
75
|
+
]
|
|
76
|
+
),
|
|
77
|
+
|
|
78
|
+
peek: v
|
|
79
|
+
.nativeFn('peek', function peekImpl(coll: CljValue) {
|
|
80
|
+
if (coll === undefined || coll.kind === 'nil') return v.nil()
|
|
81
|
+
if (is.vector(coll)) {
|
|
82
|
+
return coll.value.length === 0
|
|
83
|
+
? v.nil()
|
|
84
|
+
: coll.value[coll.value.length - 1]
|
|
85
|
+
}
|
|
86
|
+
if (is.list(coll)) {
|
|
87
|
+
return coll.value.length === 0 ? v.nil() : coll.value[0]
|
|
88
|
+
}
|
|
89
|
+
throw EvaluationError.atArg(
|
|
90
|
+
`peek expects a list or vector, got ${printString(coll)}`,
|
|
91
|
+
{ coll },
|
|
92
|
+
0
|
|
93
|
+
)
|
|
94
|
+
})
|
|
95
|
+
.doc('For a list, same as first. For a vector, same as last.', [['coll']]),
|
|
96
|
+
|
|
97
|
+
pop: v
|
|
98
|
+
.nativeFn('pop', function popImpl(coll: CljValue) {
|
|
99
|
+
if (coll === undefined || coll.kind === 'nil') {
|
|
100
|
+
throw EvaluationError.atArg("Can't pop empty list", { coll }, 0)
|
|
101
|
+
}
|
|
102
|
+
if (is.vector(coll)) {
|
|
103
|
+
if (coll.value.length === 0)
|
|
104
|
+
throw new EvaluationError("Can't pop empty vector", { coll })
|
|
105
|
+
return v.vector(coll.value.slice(0, -1))
|
|
106
|
+
}
|
|
107
|
+
if (is.list(coll)) {
|
|
108
|
+
if (coll.value.length === 0)
|
|
109
|
+
throw new EvaluationError("Can't pop empty list", { coll })
|
|
110
|
+
return v.list(coll.value.slice(1))
|
|
111
|
+
}
|
|
112
|
+
throw EvaluationError.atArg(
|
|
113
|
+
`pop expects a list or vector, got ${printString(coll)}`,
|
|
114
|
+
{ coll },
|
|
115
|
+
0
|
|
116
|
+
)
|
|
117
|
+
})
|
|
118
|
+
.doc(
|
|
119
|
+
'For a list, returns a new list without the first item. For a vector, returns a new vector without the last item.',
|
|
120
|
+
[['coll']]
|
|
121
|
+
),
|
|
122
|
+
}
|
package/src/core/tokenizer.ts
CHANGED
|
@@ -33,6 +33,7 @@ const isNumber = (char: string) => {
|
|
|
33
33
|
const isDot = (char: string) => char === '.'
|
|
34
34
|
const isKeywordStart = (char: string) => char === ':'
|
|
35
35
|
const isHash = (char: string) => char === '#'
|
|
36
|
+
const isCaret = (char: string) => char === '^'
|
|
36
37
|
|
|
37
38
|
const isDelimiter = (char: string) =>
|
|
38
39
|
isLParen(char) ||
|
|
@@ -43,7 +44,8 @@ const isDelimiter = (char: string) =>
|
|
|
43
44
|
isRBrace(char) ||
|
|
44
45
|
isBacktick(char) ||
|
|
45
46
|
isSingleQuote(char) ||
|
|
46
|
-
isAt(char)
|
|
47
|
+
isAt(char) ||
|
|
48
|
+
isCaret(char)
|
|
47
49
|
|
|
48
50
|
const parseWhitespace = (ctx: TokenizationContext): Token => {
|
|
49
51
|
const scanner = ctx.scanner
|
|
@@ -217,6 +219,13 @@ const parseDerefToken = (ctx: TokenizationContext): Token => {
|
|
|
217
219
|
return { kind: 'Deref', start, end: scanner.position() }
|
|
218
220
|
}
|
|
219
221
|
|
|
222
|
+
const parseMetaToken = (ctx: TokenizationContext): Token => {
|
|
223
|
+
const scanner = ctx.scanner
|
|
224
|
+
const start = scanner.position()
|
|
225
|
+
scanner.advance() // consume '^'
|
|
226
|
+
return { kind: 'Meta', start, end: scanner.position() }
|
|
227
|
+
}
|
|
228
|
+
|
|
220
229
|
const parseRegexLiteral = (ctx: TokenizationContext, start: ReturnType<typeof ctx.scanner.position>): Token => {
|
|
221
230
|
const scanner = ctx.scanner
|
|
222
231
|
scanner.advance() // consume opening '"'
|
|
@@ -285,8 +294,8 @@ function parseDispatch(ctx: TokenizationContext): Token {
|
|
|
285
294
|
return { kind: tokenKeywords.VarQuote, start, end: scanner.position() }
|
|
286
295
|
}
|
|
287
296
|
if (next === '{') {
|
|
288
|
-
//
|
|
289
|
-
|
|
297
|
+
scanner.advance() // consume '{'
|
|
298
|
+
return { kind: tokenKeywords.SetStart, start, end: scanner.position() }
|
|
290
299
|
}
|
|
291
300
|
throw new TokenizerError(
|
|
292
301
|
`Unknown dispatch character: #${next ?? 'EOF'}`,
|
|
@@ -364,6 +373,7 @@ const tokenParseEntries: TokenParseEntry[] = [
|
|
|
364
373
|
],
|
|
365
374
|
[isTilde, parseTilde],
|
|
366
375
|
[isAt, parseDerefToken],
|
|
376
|
+
[isCaret, parseMetaToken],
|
|
367
377
|
[isHash, parseDispatch],
|
|
368
378
|
]
|
|
369
379
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { isList, isMap, isVector } from './assertions'
|
|
1
|
+
import { isCons, isLazySeq, isList, isMap, isNil, isSet, isVector } from './assertions'
|
|
2
2
|
import { EvaluationError } from './errors'
|
|
3
3
|
import { cljString, cljVector } from './factories'
|
|
4
|
-
import { printString } from './printer'
|
|
5
|
-
import { type CljValue, valueKeywords } from './types'
|
|
4
|
+
import { printString, getPrintContext } from './printer'
|
|
5
|
+
import { type CljCons, type CljDelay, type CljLazySeq, type CljValue, valueKeywords } from './types'
|
|
6
6
|
|
|
7
7
|
export function valueToString(value: CljValue): string {
|
|
8
8
|
switch (value.kind) {
|
|
@@ -16,12 +16,30 @@ export function valueToString(value: CljValue): string {
|
|
|
16
16
|
return value.name
|
|
17
17
|
case valueKeywords.symbol:
|
|
18
18
|
return value.name
|
|
19
|
-
case valueKeywords.list:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
case valueKeywords.list: {
|
|
20
|
+
const { printLength } = getPrintContext()
|
|
21
|
+
const items = printLength !== null ? value.value.slice(0, printLength) : value.value
|
|
22
|
+
const suffix = printLength !== null && value.value.length > printLength ? ' ...' : ''
|
|
23
|
+
return `(${items.map(valueToString).join(' ')}${suffix})`
|
|
24
|
+
}
|
|
25
|
+
case valueKeywords.vector: {
|
|
26
|
+
const { printLength } = getPrintContext()
|
|
27
|
+
const items = printLength !== null ? value.value.slice(0, printLength) : value.value
|
|
28
|
+
const suffix = printLength !== null && value.value.length > printLength ? ' ...' : ''
|
|
29
|
+
return `[${items.map(valueToString).join(' ')}${suffix}]`
|
|
30
|
+
}
|
|
31
|
+
case valueKeywords.map: {
|
|
32
|
+
const { printLength } = getPrintContext()
|
|
33
|
+
const entries = printLength !== null ? value.entries.slice(0, printLength) : value.entries
|
|
34
|
+
const suffix = printLength !== null && value.entries.length > printLength ? ' ...' : ''
|
|
35
|
+
return `{${entries.map(([key, v]) => `${valueToString(key)} ${valueToString(v)}`).join(' ')}${suffix}}`
|
|
36
|
+
}
|
|
37
|
+
case valueKeywords.set: {
|
|
38
|
+
const { printLength } = getPrintContext()
|
|
39
|
+
const items = printLength !== null ? value.values.slice(0, printLength) : value.values
|
|
40
|
+
const suffix = printLength !== null && value.values.length > printLength ? ' ...' : ''
|
|
41
|
+
return `#{${items.map(valueToString).join(' ')}${suffix}}`
|
|
42
|
+
}
|
|
25
43
|
case valueKeywords.function: {
|
|
26
44
|
if (value.arities.length === 1) {
|
|
27
45
|
const a = value.arities[0]
|
|
@@ -48,6 +66,26 @@ export function valueToString(value: CljValue): string {
|
|
|
48
66
|
const prefix = value.flags ? `(?${value.flags})` : ''
|
|
49
67
|
return `${prefix}${value.pattern}`
|
|
50
68
|
}
|
|
69
|
+
case valueKeywords.delay:
|
|
70
|
+
return value.realized ? `#<Delay @${valueToString(value.value!)}>` : '#<Delay pending>'
|
|
71
|
+
case valueKeywords.lazySeq: {
|
|
72
|
+
const realized = realizeLazySeq(value)
|
|
73
|
+
if (isNil(realized)) return '()'
|
|
74
|
+
return valueToString(realized)
|
|
75
|
+
}
|
|
76
|
+
case valueKeywords.cons: {
|
|
77
|
+
const items = consToArray(value)
|
|
78
|
+
const { printLength } = getPrintContext()
|
|
79
|
+
const visible = printLength !== null ? items.slice(0, printLength) : items
|
|
80
|
+
const suffix = printLength !== null && items.length > printLength ? ' ...' : ''
|
|
81
|
+
return `(${visible.map(valueToString).join(' ')}${suffix})`
|
|
82
|
+
}
|
|
83
|
+
case valueKeywords.namespace:
|
|
84
|
+
return `#namespace[${value.name}]`
|
|
85
|
+
case 'pending':
|
|
86
|
+
if (value.resolved && value.resolvedValue !== undefined)
|
|
87
|
+
return `#<Pending @${valueToString(value.resolvedValue)}>`
|
|
88
|
+
return '#<Pending>'
|
|
51
89
|
default:
|
|
52
90
|
throw new EvaluationError(`unhandled value type: ${value.kind}`, {
|
|
53
91
|
value,
|
|
@@ -55,6 +93,35 @@ export function valueToString(value: CljValue): string {
|
|
|
55
93
|
}
|
|
56
94
|
}
|
|
57
95
|
|
|
96
|
+
/** Realize a delay: evaluate thunk once, cache result. */
|
|
97
|
+
export function realizeDelay(d: CljDelay): CljValue {
|
|
98
|
+
if (d.realized) return d.value!
|
|
99
|
+
d.value = d.thunk()
|
|
100
|
+
d.realized = true
|
|
101
|
+
return d.value!
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Realize a lazy-seq: evaluate thunk once, cache result. Trampolines through chained lazy-seqs. */
|
|
105
|
+
export function realizeLazySeq(ls: CljLazySeq): CljValue {
|
|
106
|
+
let current: CljValue = ls
|
|
107
|
+
while (current.kind === 'lazy-seq') {
|
|
108
|
+
const lazy = current as CljLazySeq
|
|
109
|
+
if (lazy.realized) {
|
|
110
|
+
current = lazy.value!
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
if (lazy.thunk) {
|
|
114
|
+
lazy.value = lazy.thunk()
|
|
115
|
+
lazy.thunk = null
|
|
116
|
+
lazy.realized = true
|
|
117
|
+
current = lazy.value!
|
|
118
|
+
} else {
|
|
119
|
+
return { kind: 'nil', value: null }
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return current
|
|
123
|
+
}
|
|
124
|
+
|
|
58
125
|
export const toSeq = (collection: CljValue): CljValue[] => {
|
|
59
126
|
if (isList(collection)) {
|
|
60
127
|
return collection.value
|
|
@@ -65,11 +132,52 @@ export const toSeq = (collection: CljValue): CljValue[] => {
|
|
|
65
132
|
if (isMap(collection)) {
|
|
66
133
|
return collection.entries.map(([k, v]) => cljVector([k, v]))
|
|
67
134
|
}
|
|
135
|
+
if (isSet(collection)) {
|
|
136
|
+
return collection.values
|
|
137
|
+
}
|
|
68
138
|
if (collection.kind === 'string') {
|
|
69
139
|
return [...collection.value].map(cljString)
|
|
70
140
|
}
|
|
141
|
+
if (isLazySeq(collection)) {
|
|
142
|
+
const realized = realizeLazySeq(collection)
|
|
143
|
+
if (isNil(realized)) return []
|
|
144
|
+
return toSeq(realized)
|
|
145
|
+
}
|
|
146
|
+
if (isCons(collection)) {
|
|
147
|
+
return consToArray(collection)
|
|
148
|
+
}
|
|
71
149
|
throw new EvaluationError(
|
|
72
150
|
`toSeq expects a collection or string, got ${printString(collection)}`,
|
|
73
151
|
{ collection }
|
|
74
152
|
)
|
|
75
153
|
}
|
|
154
|
+
|
|
155
|
+
/** Walk a cons/lazy-seq chain into a flat array (trampoline, no recursion). */
|
|
156
|
+
export function consToArray(c: CljCons): CljValue[] {
|
|
157
|
+
const result: CljValue[] = [c.head]
|
|
158
|
+
let tail: CljValue = c.tail
|
|
159
|
+
while (true) {
|
|
160
|
+
if (isNil(tail)) break
|
|
161
|
+
if (isCons(tail)) {
|
|
162
|
+
result.push(tail.head)
|
|
163
|
+
tail = tail.tail
|
|
164
|
+
continue
|
|
165
|
+
}
|
|
166
|
+
if (isLazySeq(tail)) {
|
|
167
|
+
tail = realizeLazySeq(tail)
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
if (isList(tail)) {
|
|
171
|
+
result.push(...tail.value)
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
if (isVector(tail)) {
|
|
175
|
+
result.push(...tail.value)
|
|
176
|
+
break
|
|
177
|
+
}
|
|
178
|
+
// Other seqable types — fall through to toSeq
|
|
179
|
+
result.push(...toSeq(tail))
|
|
180
|
+
break
|
|
181
|
+
}
|
|
182
|
+
return result
|
|
183
|
+
}
|
package/src/core/types.ts
CHANGED
|
@@ -17,6 +17,12 @@ export const valueKeywords = {
|
|
|
17
17
|
volatile: 'volatile',
|
|
18
18
|
regex: 'regex',
|
|
19
19
|
var: 'var',
|
|
20
|
+
set: 'set',
|
|
21
|
+
delay: 'delay',
|
|
22
|
+
lazySeq: 'lazy-seq',
|
|
23
|
+
cons: 'cons',
|
|
24
|
+
namespace: 'namespace',
|
|
25
|
+
jsValue: 'js-value',
|
|
20
26
|
} as const
|
|
21
27
|
export type ValueKeywords = (typeof valueKeywords)[keyof typeof valueKeywords]
|
|
22
28
|
|
|
@@ -25,11 +31,12 @@ export type CljString = { kind: 'string'; value: string }
|
|
|
25
31
|
export type CljBoolean = { kind: 'boolean'; value: boolean }
|
|
26
32
|
export type CljKeyword = { kind: 'keyword'; name: string }
|
|
27
33
|
export type CljNil = { kind: 'nil'; value: null }
|
|
28
|
-
export type CljSymbol = { kind: 'symbol'; name: string }
|
|
29
|
-
export type CljList = { kind: 'list'; value: CljValue[] }
|
|
30
|
-
export type CljVector = { kind: 'vector'; value: CljValue[] }
|
|
31
|
-
export type CljMap = { kind: 'map'; entries: [CljValue, CljValue][] }
|
|
34
|
+
export type CljSymbol = { kind: 'symbol'; name: string; meta?: CljMap }
|
|
35
|
+
export type CljList = { kind: 'list'; value: CljValue[]; meta?: CljMap }
|
|
36
|
+
export type CljVector = { kind: 'vector'; value: CljValue[]; meta?: CljMap }
|
|
37
|
+
export type CljMap = { kind: 'map'; entries: [CljValue, CljValue][]; meta?: CljMap }
|
|
32
38
|
export type CljNamespace = {
|
|
39
|
+
kind: 'namespace'
|
|
33
40
|
name: string
|
|
34
41
|
vars: Map<string, CljVar> // user defs from (def ...)
|
|
35
42
|
aliases: Map<string, CljNamespace> // :as namespace aliases
|
|
@@ -40,7 +47,6 @@ export type Env = {
|
|
|
40
47
|
bindings: Map<string, CljValue> // native fns, macros, multimethods, local values
|
|
41
48
|
outer: Env | null
|
|
42
49
|
ns?: CljNamespace // set on namespace-root envs only
|
|
43
|
-
resolveNs?: (name: string) => Env | null // set on coreEnv only
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
export type DestructurePattern = CljSymbol | CljVector | CljMap
|
|
@@ -55,6 +61,7 @@ export type CljFunction = {
|
|
|
55
61
|
kind: 'function'
|
|
56
62
|
arities: Arity[]
|
|
57
63
|
env: Env
|
|
64
|
+
name?: string // set for named fn: (fn my-name [x] x)
|
|
58
65
|
meta?: CljMap
|
|
59
66
|
}
|
|
60
67
|
|
|
@@ -62,18 +69,50 @@ export type CljMacro = {
|
|
|
62
69
|
kind: 'macro'
|
|
63
70
|
arities: Arity[]
|
|
64
71
|
env: Env
|
|
72
|
+
name?: string // set for named defmacro
|
|
65
73
|
}
|
|
66
74
|
|
|
67
|
-
export type CljAtom = {
|
|
75
|
+
export type CljAtom = {
|
|
76
|
+
kind: 'atom'
|
|
77
|
+
value: CljValue
|
|
78
|
+
meta?: CljMap
|
|
79
|
+
watches?: Map<string, { key: CljValue; fn: CljValue; ctx: EvaluationContext; callEnv: Env }>
|
|
80
|
+
validator?: CljValue
|
|
81
|
+
}
|
|
68
82
|
export type CljReduced = { kind: 'reduced'; value: CljValue }
|
|
69
83
|
export type CljVolatile = { kind: 'volatile'; value: CljValue }
|
|
70
84
|
export type CljRegex = { kind: 'regex'; pattern: string; flags: string }
|
|
71
85
|
|
|
86
|
+
export type CljSet = { kind: 'set'; values: CljValue[] }
|
|
87
|
+
|
|
88
|
+
export type CljDelay = {
|
|
89
|
+
kind: 'delay'
|
|
90
|
+
thunk: () => CljValue
|
|
91
|
+
realized: boolean
|
|
92
|
+
value?: CljValue
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type CljLazySeq = {
|
|
96
|
+
kind: 'lazy-seq'
|
|
97
|
+
thunk: (() => CljValue) | null
|
|
98
|
+
realized: boolean
|
|
99
|
+
value?: CljValue // nil, list, cons, or another lazy-seq after realization
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export type CljCons = {
|
|
103
|
+
kind: 'cons'
|
|
104
|
+
head: CljValue
|
|
105
|
+
tail: CljValue // can be list, vector, lazy-seq, cons, or nil
|
|
106
|
+
meta?: CljMap
|
|
107
|
+
}
|
|
108
|
+
|
|
72
109
|
export type CljVar = {
|
|
73
110
|
kind: 'var'
|
|
74
111
|
ns: string
|
|
75
112
|
name: string
|
|
76
113
|
value: CljValue
|
|
114
|
+
dynamic?: boolean // set when def is annotated with ^:dynamic
|
|
115
|
+
bindingStack?: CljValue[] // active dynamic bindings (push/pop by `binding`)
|
|
77
116
|
meta?: CljMap
|
|
78
117
|
}
|
|
79
118
|
|
|
@@ -85,6 +124,17 @@ export type CljMultiMethod = {
|
|
|
85
124
|
defaultMethod?: CljFunction | CljNativeFunction
|
|
86
125
|
}
|
|
87
126
|
|
|
127
|
+
/**
|
|
128
|
+
* IO channels for a session. stdout is the primary output channel (println,
|
|
129
|
+
* print, pr, prn, pprint, newline). stderr is available for error output.
|
|
130
|
+
* Both are set by the session on context creation and read at call time by
|
|
131
|
+
* IO native functions — no closure capture, no reinstallation on restore.
|
|
132
|
+
*/
|
|
133
|
+
export type IOContext = {
|
|
134
|
+
stdout: (text: string) => void
|
|
135
|
+
stderr: (text: string) => void
|
|
136
|
+
}
|
|
137
|
+
|
|
88
138
|
export type EvaluationContext = {
|
|
89
139
|
evaluate: (expr: CljValue, env: Env) => CljValue
|
|
90
140
|
evaluateForms: (forms: CljValue[], env: Env) => CljValue
|
|
@@ -97,6 +147,39 @@ export type EvaluationContext = {
|
|
|
97
147
|
applyCallable: (fn: CljValue, args: CljValue[], callEnv: Env) => CljValue
|
|
98
148
|
applyMacro: (macro: CljMacro, rawArgs: CljValue[]) => CljValue
|
|
99
149
|
expandAll: (form: CljValue, env: Env) => CljValue
|
|
150
|
+
/**
|
|
151
|
+
* Resolves a namespace name (or alias) to its CljNamespace record.
|
|
152
|
+
* Wired by the session/runtime after context creation; defaults to no-op null.
|
|
153
|
+
*/
|
|
154
|
+
resolveNs: (name: string) => CljNamespace | null
|
|
155
|
+
/**
|
|
156
|
+
* IO channels — set by the session in buildSessionFacade.
|
|
157
|
+
* IO native functions (println, print, pr, prn, pprint, newline) read
|
|
158
|
+
* ctx.io.stdout at call time instead of closing over an emit callback.
|
|
159
|
+
* This means snapshot clones automatically use the correct output without
|
|
160
|
+
* any reinstallation of IO vars.
|
|
161
|
+
*/
|
|
162
|
+
io: IOContext
|
|
163
|
+
/**
|
|
164
|
+
* Mutable per-call fields set by session.evaluate / loadFile before
|
|
165
|
+
* executing forms. Used by evaluateDef to stamp :line/:column/:file onto
|
|
166
|
+
* vars. This codebase is synchronous, so mutation is safe.
|
|
167
|
+
*/
|
|
168
|
+
currentSource?: string
|
|
169
|
+
currentFile?: string
|
|
170
|
+
currentLineOffset?: number
|
|
171
|
+
currentColOffset?: number
|
|
172
|
+
/**
|
|
173
|
+
* Optional module loader for string `:require` specs.
|
|
174
|
+
* Called by processNsRequiresAsync when it encounters ["specifier" :as Alias].
|
|
175
|
+
* Wired from SessionOptions.importModule in buildSessionFacade.
|
|
176
|
+
*/
|
|
177
|
+
importModule?: (specifier: string) => unknown | Promise<unknown>
|
|
178
|
+
/**
|
|
179
|
+
* Switches the session's current namespace. Wired by buildSessionFacade.
|
|
180
|
+
* Called by `in-ns` at runtime. Without this hook, `in-ns` is a no-op.
|
|
181
|
+
*/
|
|
182
|
+
setCurrentNs?: (name: string) => void
|
|
100
183
|
}
|
|
101
184
|
|
|
102
185
|
export type CljNativeFunction = {
|
|
@@ -112,6 +195,18 @@ export type CljNativeFunction = {
|
|
|
112
195
|
meta?: CljMap
|
|
113
196
|
}
|
|
114
197
|
|
|
198
|
+
export type CljJsValue = { kind: 'js-value'; value: unknown }
|
|
199
|
+
|
|
200
|
+
// --- ASYNC (experimental, see evaluator/async-evaluator.ts) ---
|
|
201
|
+
export type CljPending = {
|
|
202
|
+
kind: 'pending'
|
|
203
|
+
promise: Promise<CljValue>
|
|
204
|
+
/** Set to true once the promise settles (fulfilled only). */
|
|
205
|
+
resolved?: boolean
|
|
206
|
+
resolvedValue?: CljValue
|
|
207
|
+
}
|
|
208
|
+
// --- END ASYNC ---
|
|
209
|
+
|
|
115
210
|
export type CljValue =
|
|
116
211
|
| CljNumber
|
|
117
212
|
| CljString
|
|
@@ -131,6 +226,13 @@ export type CljValue =
|
|
|
131
226
|
| CljVolatile
|
|
132
227
|
| CljRegex
|
|
133
228
|
| CljVar
|
|
229
|
+
| CljSet
|
|
230
|
+
| CljDelay
|
|
231
|
+
| CljLazySeq
|
|
232
|
+
| CljCons
|
|
233
|
+
| CljNamespace
|
|
234
|
+
| CljPending
|
|
235
|
+
| CljJsValue
|
|
134
236
|
|
|
135
237
|
/** Tokens */
|
|
136
238
|
export const tokenKeywords = {
|
|
@@ -154,6 +256,8 @@ export const tokenKeywords = {
|
|
|
154
256
|
Deref: 'Deref',
|
|
155
257
|
Regex: 'Regex',
|
|
156
258
|
VarQuote: 'VarQuote',
|
|
259
|
+
Meta: 'Meta',
|
|
260
|
+
SetStart: 'SetStart',
|
|
157
261
|
} as const
|
|
158
262
|
export const tokenSymbols = {
|
|
159
263
|
Quote: 'quote',
|
|
@@ -254,6 +358,12 @@ export type TokenRegex = {
|
|
|
254
358
|
export type TokenVarQuote = {
|
|
255
359
|
kind: 'VarQuote'
|
|
256
360
|
}
|
|
361
|
+
export type TokenMeta = {
|
|
362
|
+
kind: 'Meta'
|
|
363
|
+
}
|
|
364
|
+
export type TokenSetStart = {
|
|
365
|
+
kind: 'SetStart'
|
|
366
|
+
}
|
|
257
367
|
export type Token = (
|
|
258
368
|
| TokenLParen
|
|
259
369
|
| TokenRParen
|
|
@@ -275,4 +385,6 @@ export type Token = (
|
|
|
275
385
|
| TokenDeref
|
|
276
386
|
| TokenRegex
|
|
277
387
|
| TokenVarQuote
|
|
388
|
+
| TokenMeta
|
|
389
|
+
| TokenSetStart
|
|
278
390
|
) & { start: Cursor; end: Cursor }
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { resolve } from 'node:path'
|
|
3
|
+
import {
|
|
4
|
+
cljNativeFunction,
|
|
5
|
+
cljNil,
|
|
6
|
+
cljString,
|
|
7
|
+
valueToString,
|
|
8
|
+
type Session,
|
|
9
|
+
type CljValue,
|
|
10
|
+
} from '../core'
|
|
11
|
+
import type { RuntimeModule } from '../core/module'
|
|
12
|
+
import { inferSourceRoot } from '../bin/nrepl-utils'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Returns a RuntimeModule that installs Node.js host functions into clojure.core.
|
|
16
|
+
* Closes over the session for functions (like `load`) that need to drive evaluation.
|
|
17
|
+
*/
|
|
18
|
+
export function makeNodeHostModule(session: Session): RuntimeModule {
|
|
19
|
+
return {
|
|
20
|
+
id: 'conjure/host-node',
|
|
21
|
+
dependsOn: ['clojure.core'],
|
|
22
|
+
declareNs: [
|
|
23
|
+
{
|
|
24
|
+
name: 'clojure.core',
|
|
25
|
+
vars(_ctx) {
|
|
26
|
+
return new Map([
|
|
27
|
+
[
|
|
28
|
+
'slurp',
|
|
29
|
+
{
|
|
30
|
+
value: cljNativeFunction('slurp', (pathVal: CljValue) => {
|
|
31
|
+
const filePath = resolve(valueToString(pathVal))
|
|
32
|
+
if (!existsSync(filePath)) {
|
|
33
|
+
throw new Error(`slurp: file not found: ${filePath}`)
|
|
34
|
+
}
|
|
35
|
+
return cljString(readFileSync(filePath, 'utf8'))
|
|
36
|
+
}),
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
[
|
|
40
|
+
'spit',
|
|
41
|
+
{
|
|
42
|
+
value: cljNativeFunction(
|
|
43
|
+
'spit',
|
|
44
|
+
(pathVal: CljValue, content: CljValue) => {
|
|
45
|
+
const filePath = resolve(valueToString(pathVal))
|
|
46
|
+
writeFileSync(filePath, valueToString(content), 'utf8')
|
|
47
|
+
return cljNil()
|
|
48
|
+
}
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
'load',
|
|
54
|
+
{
|
|
55
|
+
value: cljNativeFunction('load', (pathVal: CljValue) => {
|
|
56
|
+
const filePath = resolve(valueToString(pathVal))
|
|
57
|
+
if (!existsSync(filePath)) {
|
|
58
|
+
throw new Error(`load: file not found: ${filePath}`)
|
|
59
|
+
}
|
|
60
|
+
const source = readFileSync(filePath, 'utf8')
|
|
61
|
+
const inferred = inferSourceRoot(filePath, source)
|
|
62
|
+
if (inferred) session.addSourceRoot(inferred)
|
|
63
|
+
const loadedNs = session.loadFile(source)
|
|
64
|
+
session.setNs(loadedNs)
|
|
65
|
+
return cljNil()
|
|
66
|
+
}),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
])
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
}
|
|
74
|
+
}
|