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.
Files changed (80) hide show
  1. package/dist-cli/conjure-js.mjs +9336 -5028
  2. package/dist-vite-plugin/index.mjs +10455 -0
  3. package/package.json +9 -2
  4. package/src/bin/cli.ts +2 -2
  5. package/src/bin/nrepl-symbol.ts +150 -0
  6. package/src/bin/nrepl.ts +301 -157
  7. package/src/bin/version.ts +1 -1
  8. package/src/clojure/core.clj +764 -29
  9. package/src/clojure/core.clj.d.ts +76 -4
  10. package/src/clojure/demo/math.clj +5 -1
  11. package/src/clojure/generated/builtin-namespace-registry.ts +4 -0
  12. package/src/clojure/generated/clojure-core-source.ts +765 -29
  13. package/src/clojure/generated/clojure-set-source.ts +136 -0
  14. package/src/clojure/generated/clojure-walk-source.ts +72 -0
  15. package/src/clojure/set.clj +132 -0
  16. package/src/clojure/set.clj.d.ts +20 -0
  17. package/src/clojure/string.clj.d.ts +14 -0
  18. package/src/clojure/walk.clj +68 -0
  19. package/src/clojure/walk.clj.d.ts +7 -0
  20. package/src/core/assertions.ts +114 -6
  21. package/src/core/bootstrap.ts +337 -0
  22. package/src/core/conversions.ts +48 -31
  23. package/src/core/core-module.ts +303 -0
  24. package/src/core/env.ts +42 -7
  25. package/src/core/errors.ts +8 -0
  26. package/src/core/evaluator/apply.ts +40 -25
  27. package/src/core/evaluator/arity.ts +8 -8
  28. package/src/core/evaluator/async-evaluator.ts +565 -0
  29. package/src/core/evaluator/collections.ts +30 -4
  30. package/src/core/evaluator/destructure.ts +180 -69
  31. package/src/core/evaluator/dispatch.ts +24 -14
  32. package/src/core/evaluator/evaluate.ts +22 -20
  33. package/src/core/evaluator/expand.ts +45 -15
  34. package/src/core/evaluator/form-parsers.ts +178 -0
  35. package/src/core/evaluator/index.ts +7 -9
  36. package/src/core/evaluator/js-interop.ts +189 -0
  37. package/src/core/evaluator/quasiquote.ts +14 -8
  38. package/src/core/evaluator/recur-check.ts +6 -6
  39. package/src/core/evaluator/special-forms.ts +380 -173
  40. package/src/core/factories.ts +182 -3
  41. package/src/core/index.ts +55 -5
  42. package/src/core/module.ts +136 -0
  43. package/src/core/ns-forms.ts +107 -0
  44. package/src/core/positions.ts +9 -2
  45. package/src/core/printer.ts +371 -11
  46. package/src/core/reader.ts +127 -29
  47. package/src/core/registry.ts +209 -0
  48. package/src/core/runtime.ts +376 -0
  49. package/src/core/session.ts +263 -478
  50. package/src/core/stdlib/arithmetic.ts +516 -215
  51. package/src/core/stdlib/async-fns.ts +132 -0
  52. package/src/core/stdlib/atoms.ts +286 -63
  53. package/src/core/stdlib/errors.ts +54 -50
  54. package/src/core/stdlib/hof.ts +74 -173
  55. package/src/core/stdlib/js-namespace.ts +344 -0
  56. package/src/core/stdlib/lazy.ts +34 -0
  57. package/src/core/stdlib/maps-sets.ts +322 -0
  58. package/src/core/stdlib/meta.ts +109 -28
  59. package/src/core/stdlib/predicates.ts +322 -196
  60. package/src/core/stdlib/regex.ts +126 -98
  61. package/src/core/stdlib/seq.ts +564 -0
  62. package/src/core/stdlib/strings.ts +164 -135
  63. package/src/core/stdlib/transducers.ts +95 -100
  64. package/src/core/stdlib/utils.ts +283 -147
  65. package/src/core/stdlib/vars.ts +27 -27
  66. package/src/core/stdlib/vectors.ts +122 -0
  67. package/src/core/tokenizer.ts +13 -3
  68. package/src/core/transformations.ts +117 -9
  69. package/src/core/types.ts +118 -6
  70. package/src/host/node-host-module.ts +74 -0
  71. package/src/nrepl/relay.ts +432 -0
  72. package/src/vite-plugin-clj/codegen.ts +87 -95
  73. package/src/vite-plugin-clj/index.ts +242 -18
  74. package/src/vite-plugin-clj/namespace-utils.ts +39 -0
  75. package/src/vite-plugin-clj/static-analysis.ts +211 -0
  76. package/src/clojure/demo.clj +0 -63
  77. package/src/clojure/demo.clj.d.ts +0 -0
  78. package/src/core/core-env.ts +0 -60
  79. package/src/core/stdlib/collections.ts +0 -784
  80. 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
+ }
@@ -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
- // TODO: set literals — #{1 2 3}
289
- throw new TokenizerError('Set literals are not yet supported', start)
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
- return `(${value.value.map(valueToString).join(' ')})`
21
- case valueKeywords.vector:
22
- return `[${value.value.map(valueToString).join(' ')}]`
23
- case valueKeywords.map:
24
- return `{${value.entries.map(([key, value]) => `${valueToString(key)} ${valueToString(value)}`).join(' ')}}`
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 = { kind: 'atom'; value: CljValue }
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
+ }