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.
Files changed (77) hide show
  1. package/dist-cli/conjure-js.mjs +9360 -5298
  2. package/dist-vite-plugin/index.mjs +9463 -5185
  3. package/package.json +3 -1
  4. package/src/bin/cli.ts +2 -2
  5. package/src/bin/nrepl-symbol.ts +150 -0
  6. package/src/bin/nrepl.ts +289 -167
  7. package/src/bin/version.ts +1 -1
  8. package/src/clojure/core.clj +757 -29
  9. package/src/clojure/core.clj.d.ts +75 -131
  10. package/src/clojure/generated/builtin-namespace-registry.ts +4 -0
  11. package/src/clojure/generated/clojure-core-source.ts +758 -29
  12. package/src/clojure/generated/clojure-set-source.ts +136 -0
  13. package/src/clojure/generated/clojure-walk-source.ts +72 -0
  14. package/src/clojure/set.clj +132 -0
  15. package/src/clojure/set.clj.d.ts +20 -0
  16. package/src/clojure/string.clj.d.ts +14 -0
  17. package/src/clojure/walk.clj +68 -0
  18. package/src/clojure/walk.clj.d.ts +7 -0
  19. package/src/core/assertions.ts +114 -6
  20. package/src/core/bootstrap.ts +337 -0
  21. package/src/core/conversions.ts +48 -31
  22. package/src/core/core-module.ts +303 -0
  23. package/src/core/env.ts +20 -6
  24. package/src/core/evaluator/apply.ts +40 -25
  25. package/src/core/evaluator/arity.ts +8 -8
  26. package/src/core/evaluator/async-evaluator.ts +565 -0
  27. package/src/core/evaluator/collections.ts +28 -5
  28. package/src/core/evaluator/destructure.ts +180 -69
  29. package/src/core/evaluator/dispatch.ts +12 -14
  30. package/src/core/evaluator/evaluate.ts +22 -20
  31. package/src/core/evaluator/expand.ts +45 -15
  32. package/src/core/evaluator/form-parsers.ts +178 -0
  33. package/src/core/evaluator/index.ts +7 -9
  34. package/src/core/evaluator/js-interop.ts +189 -0
  35. package/src/core/evaluator/quasiquote.ts +14 -8
  36. package/src/core/evaluator/recur-check.ts +6 -6
  37. package/src/core/evaluator/special-forms.ts +234 -191
  38. package/src/core/factories.ts +182 -3
  39. package/src/core/index.ts +54 -4
  40. package/src/core/module.ts +136 -0
  41. package/src/core/ns-forms.ts +107 -0
  42. package/src/core/printer.ts +371 -11
  43. package/src/core/reader.ts +84 -33
  44. package/src/core/registry.ts +209 -0
  45. package/src/core/runtime.ts +376 -0
  46. package/src/core/session.ts +253 -487
  47. package/src/core/stdlib/arithmetic.ts +528 -194
  48. package/src/core/stdlib/async-fns.ts +132 -0
  49. package/src/core/stdlib/atoms.ts +291 -56
  50. package/src/core/stdlib/errors.ts +54 -50
  51. package/src/core/stdlib/hof.ts +82 -166
  52. package/src/core/stdlib/js-namespace.ts +344 -0
  53. package/src/core/stdlib/lazy.ts +34 -0
  54. package/src/core/stdlib/maps-sets.ts +322 -0
  55. package/src/core/stdlib/meta.ts +61 -30
  56. package/src/core/stdlib/predicates.ts +325 -187
  57. package/src/core/stdlib/regex.ts +126 -98
  58. package/src/core/stdlib/seq.ts +564 -0
  59. package/src/core/stdlib/strings.ts +164 -135
  60. package/src/core/stdlib/transducers.ts +95 -100
  61. package/src/core/stdlib/utils.ts +292 -130
  62. package/src/core/stdlib/vars.ts +27 -27
  63. package/src/core/stdlib/vectors.ts +122 -0
  64. package/src/core/tokenizer.ts +2 -2
  65. package/src/core/transformations.ts +117 -9
  66. package/src/core/types.ts +98 -2
  67. package/src/host/node-host-module.ts +74 -0
  68. package/src/{vite-plugin-clj/nrepl-relay.ts → nrepl/relay.ts} +72 -11
  69. package/src/vite-plugin-clj/codegen.ts +87 -95
  70. package/src/vite-plugin-clj/index.ts +178 -23
  71. package/src/vite-plugin-clj/namespace-utils.ts +39 -0
  72. package/src/vite-plugin-clj/static-analysis.ts +211 -0
  73. package/src/clojure/demo.clj +0 -72
  74. package/src/clojure/demo.clj.d.ts +0 -0
  75. package/src/core/core-env.ts +0 -61
  76. package/src/core/stdlib/collections.ts +0 -739
  77. package/src/host/node.ts +0 -55
@@ -1,59 +1,67 @@
1
1
  // Miscellaneous utilities: str, type, gensym, eval, macroexpand-1, macroexpand,
2
2
  // namespace, name, keyword
3
- import { isKeyword, isList, isMacro, isSymbol, isTruthy } from '../assertions'
3
+ import { is } from '../assertions'
4
4
  import { tryLookup } from '../env'
5
5
  import { EvaluationError } from '../errors'
6
- import {
7
- cljBoolean,
8
- cljKeyword,
9
- cljNativeFunction,
10
- cljNativeFunctionWithContext,
11
- cljNil,
12
- cljString,
13
- cljSymbol,
14
- withDoc,
15
- } from '../factories'
6
+ import { v } from '../factories'
16
7
  import { makeGensym } from '../gensym'
17
- import { joinLines, printString } from '../printer'
8
+ import { buildPrintContext, joinLines, prettyPrintString, printString, withPrintContext } from '../printer'
9
+ import { readForms } from '../reader'
10
+ import { tokenize } from '../tokenizer'
18
11
  import { valueToString } from '../transformations'
19
12
  import type { CljValue, Env, EvaluationContext } from '../types'
20
13
 
21
14
  export const utilFunctions: Record<string, CljValue> = {
22
- str: withDoc(
23
- cljNativeFunction('str', (...args: CljValue[]) => {
24
- return cljString(args.map(valueToString).join(''))
25
- }),
26
- 'Returns a concatenated string representation of the given values.',
27
- [['&', 'args']]
28
- ),
29
- subs: withDoc(
30
- cljNativeFunction(
15
+ str: v
16
+ .nativeFn('str', function strImpl(...args: CljValue[]) {
17
+ return v.string(
18
+ args.map((v) => (v.kind === 'nil' ? '' : valueToString(v))).join('')
19
+ )
20
+ })
21
+ .doc('Returns a concatenated string representation of the given values.', [
22
+ ['&', 'args'],
23
+ ]),
24
+ subs: v
25
+ .nativeFn(
31
26
  'subs',
32
- (s: CljValue, start: CljValue, end?: CljValue) => {
27
+ function subsImpl(s: CljValue, start: CljValue, end?: CljValue) {
33
28
  if (s === undefined || s.kind !== 'string') {
34
- throw EvaluationError.atArg(`subs expects a string as first argument${s !== undefined ? `, got ${printString(s)}` : ''}`, { s }, 0)
29
+ throw EvaluationError.atArg(
30
+ `subs expects a string as first argument${s !== undefined ? `, got ${printString(s)}` : ''}`,
31
+ { s },
32
+ 0
33
+ )
35
34
  }
36
35
  if (start === undefined || start.kind !== 'number') {
37
- throw EvaluationError.atArg(`subs expects a number as second argument${start !== undefined ? `, got ${printString(start)}` : ''}`, { start }, 1)
36
+ throw EvaluationError.atArg(
37
+ `subs expects a number as second argument${start !== undefined ? `, got ${printString(start)}` : ''}`,
38
+ { start },
39
+ 1
40
+ )
38
41
  }
39
42
  if (end !== undefined && end.kind !== 'number') {
40
- throw EvaluationError.atArg(`subs expects a number as optional third argument${end !== undefined ? `, got ${printString(end)}` : ''}`, { end }, 2)
43
+ throw EvaluationError.atArg(
44
+ `subs expects a number as optional third argument${end !== undefined ? `, got ${printString(end)}` : ''}`,
45
+ { end },
46
+ 2
47
+ )
41
48
  }
42
49
  const from = start.value
43
50
  const to = end?.value
44
- return cljString(
51
+ return v.string(
45
52
  to === undefined ? s.value.slice(from) : s.value.slice(from, to)
46
53
  )
47
54
  }
55
+ )
56
+ .doc(
57
+ 'Returns the substring of s beginning at start, and optionally ending before end.',
58
+ [
59
+ ['s', 'start'],
60
+ ['s', 'start', 'end'],
61
+ ]
48
62
  ),
49
- 'Returns the substring of s beginning at start, and optionally ending before end.',
50
- [
51
- ['s', 'start'],
52
- ['s', 'start', 'end'],
53
- ]
54
- ),
55
- type: withDoc(
56
- cljNativeFunction('type', (x: CljValue) => {
63
+ type: v
64
+ .nativeFn('type', function typeImpl(x: CljValue) {
57
65
  if (x === undefined) {
58
66
  throw new EvaluationError('type expects an argument', { x })
59
67
  }
@@ -76,30 +84,39 @@ export const utilFunctions: Record<string, CljValue> = {
76
84
  if (!name) {
77
85
  throw new EvaluationError(`type: unhandled kind ${x.kind}`, { x })
78
86
  }
79
- return cljKeyword(name)
80
- }),
81
- 'Returns a keyword representing the type of the given value.',
82
- [['x']]
83
- ),
84
- gensym: withDoc(
85
- cljNativeFunction('gensym', (...args: CljValue[]) => {
87
+ return v.keyword(name)
88
+ })
89
+ .doc('Returns a keyword representing the type of the given value.', [
90
+ ['x'],
91
+ ]),
92
+ gensym: v
93
+ .nativeFn('gensym', function gensymImpl(...args: CljValue[]) {
86
94
  if (args.length > 1) {
87
95
  throw new EvaluationError('gensym takes 0 or 1 arguments', { args })
88
96
  }
89
97
  const prefix = args[0]
90
98
  if (prefix !== undefined && prefix.kind !== 'string') {
91
- throw EvaluationError.atArg(`gensym prefix must be a string${prefix !== undefined ? `, got ${printString(prefix)}` : ''}`, { prefix }, 0)
99
+ throw EvaluationError.atArg(
100
+ `gensym prefix must be a string${prefix !== undefined ? `, got ${printString(prefix)}` : ''}`,
101
+ { prefix },
102
+ 0
103
+ )
92
104
  }
93
105
  const p = prefix?.kind === 'string' ? prefix.value : 'G'
94
- return cljSymbol(makeGensym(p))
95
- }),
96
- 'Returns a unique symbol with the given prefix. Defaults to "G" if no prefix is provided.',
97
- [[], ['prefix']]
98
- ),
99
- eval: withDoc(
100
- cljNativeFunctionWithContext(
106
+ return v.symbol(makeGensym(p))
107
+ })
108
+ .doc(
109
+ 'Returns a unique symbol with the given prefix. Defaults to "G" if no prefix is provided.',
110
+ [[], ['prefix']]
111
+ ),
112
+ eval: v
113
+ .nativeFnCtx(
101
114
  'eval',
102
- (ctx: EvaluationContext, callEnv: Env, form: CljValue | undefined) => {
115
+ function evalImpl(
116
+ ctx: EvaluationContext,
117
+ callEnv: Env,
118
+ form: CljValue | undefined
119
+ ) {
103
120
  if (form === undefined) {
104
121
  throw new EvaluationError('eval expects a form as argument', {
105
122
  form,
@@ -108,155 +125,300 @@ export const utilFunctions: Record<string, CljValue> = {
108
125
  const expanded = ctx.expandAll(form, callEnv)
109
126
  return ctx.evaluate(expanded, callEnv)
110
127
  }
128
+ )
129
+ .doc(
130
+ 'Evaluates the given form in the global environment and returns the result.',
131
+ [['form']]
111
132
  ),
112
- 'Evaluates the given form in the global environment and returns the result.',
113
- [['form']]
114
- ),
115
133
 
116
- 'macroexpand-1': withDoc(
117
- cljNativeFunctionWithContext(
134
+ 'macroexpand-1': v
135
+ .nativeFnCtx(
118
136
  'macroexpand-1',
119
- (ctx: EvaluationContext, callEnv: Env, form: CljValue) => {
120
- if (!isList(form) || form.value.length === 0) return form
137
+ function macroexpand1Impl(
138
+ ctx: EvaluationContext,
139
+ callEnv: Env,
140
+ form: CljValue
141
+ ) {
142
+ if (!is.list(form) || form.value.length === 0) return form
121
143
  const head = form.value[0]
122
- if (!isSymbol(head)) return form
144
+ if (!is.symbol(head)) return form
123
145
  const macroValue = tryLookup(head.name, callEnv)
124
146
  if (macroValue === undefined) return form
125
- if (!isMacro(macroValue)) return form
147
+ if (!is.macro(macroValue)) return form
126
148
  return ctx.applyMacro(macroValue, form.value.slice(1))
127
149
  }
150
+ )
151
+ .doc(
152
+ 'If the head of the form is a macro, expands it and returns the resulting forms. Otherwise, returns the form unchanged.',
153
+ [['form']]
128
154
  ),
129
- 'If the head of the form is a macro, expands it and returns the resulting forms. Otherwise, returns the form unchanged.',
130
- [['form']]
131
- ),
132
155
 
133
- macroexpand: withDoc(
134
- cljNativeFunctionWithContext(
156
+ macroexpand: v
157
+ .nativeFnCtx(
135
158
  'macroexpand',
136
- (ctx: EvaluationContext, callEnv: Env, form: CljValue) => {
159
+ function macroexpandImpl(
160
+ ctx: EvaluationContext,
161
+ callEnv: Env,
162
+ form: CljValue
163
+ ) {
137
164
  let current = form
138
165
  while (true) {
139
- if (!isList(current) || current.value.length === 0) return current
166
+ if (!is.list(current) || current.value.length === 0) return current
140
167
  const head = current.value[0]
141
- if (!isSymbol(head)) return current
168
+ if (!is.symbol(head)) return current
142
169
  const macroValue = tryLookup(head.name, callEnv)
143
170
  if (macroValue === undefined) return current
144
- if (!isMacro(macroValue)) return current
171
+ if (!is.macro(macroValue)) return current
145
172
  current = ctx.applyMacro(macroValue, current.value.slice(1))
146
173
  }
147
174
  }
175
+ )
176
+ .doc(
177
+ joinLines([
178
+ 'Expands all macros until the expansion is stable (head is no longer a macro)',
179
+ '',
180
+ 'Note neither macroexpand-1 nor macroexpand will expand macros in sub-forms',
181
+ ]),
182
+ [['form']]
148
183
  ),
149
- joinLines([
150
- 'Expands all macros until the expansion is stable (head is no longer a macro)',
151
- '',
152
- 'Note neither macroexpand-1 nor macroexpand will expand macros in sub-forms',
153
- ]),
154
- [['form']]
155
- ),
156
184
 
157
- 'macroexpand-all': withDoc(
158
- cljNativeFunctionWithContext(
185
+ 'macroexpand-all': v
186
+ .nativeFnCtx(
159
187
  'macroexpand-all',
160
- (ctx: EvaluationContext, callEnv: Env, form: CljValue) =>
161
- ctx.expandAll(form, callEnv)
188
+ function macroexpandAllImpl(
189
+ ctx: EvaluationContext,
190
+ callEnv: Env,
191
+ form: CljValue
192
+ ) {
193
+ return ctx.expandAll(form, callEnv)
194
+ }
195
+ )
196
+ .doc(
197
+ joinLines([
198
+ 'Fully expands all macros in a form recursively — including in sub-forms.',
199
+ '',
200
+ 'Unlike macroexpand, this descends into every sub-expression.',
201
+ 'Expansion stops at quote/quasiquote boundaries and fn/loop bodies.',
202
+ ]),
203
+ [['form']]
162
204
  ),
163
- joinLines([
164
- 'Fully expands all macros in a form recursively — including in sub-forms.',
165
- '',
166
- 'Unlike macroexpand, this descends into every sub-expression.',
167
- 'Expansion stops at quote/quasiquote boundaries and fn/loop bodies.',
168
- ]),
169
- [['form']]
170
- ),
171
205
 
172
206
  // Returns the namespace string of a qualified keyword or symbol, or nil.
173
207
  // (namespace :user/foo) => "user"
174
208
  // (namespace :foo) => nil
175
209
  // (namespace 'user/foo) => "user"
176
- namespace: withDoc(
177
- cljNativeFunction('namespace', (x: CljValue) => {
210
+ namespace: v
211
+ .nativeFn('namespace', function namespaceImpl(x: CljValue) {
178
212
  if (x === undefined) {
179
213
  throw EvaluationError.atArg('namespace expects an argument', { x }, 0)
180
214
  }
181
215
  let raw: string | undefined
182
- if (isKeyword(x)) {
216
+ if (is.keyword(x)) {
183
217
  // keyword name format: ":ns/local" or ":local"
184
218
  raw = x.name.slice(1) // strip leading ":"
185
- } else if (isSymbol(x)) {
219
+ } else if (is.symbol(x)) {
186
220
  raw = x.name
187
221
  } else {
188
- throw EvaluationError.atArg(`namespace expects a keyword or symbol, got ${printString(x)}`, { x }, 0)
222
+ throw EvaluationError.atArg(
223
+ `namespace expects a keyword or symbol, got ${printString(x)}`,
224
+ { x },
225
+ 0
226
+ )
189
227
  }
190
228
  const slashIdx = raw.indexOf('/')
191
- if (slashIdx <= 0) return cljNil()
192
- return cljString(raw.slice(0, slashIdx))
193
- }),
194
- 'Returns the namespace string of a qualified keyword or symbol, or nil if the argument is not qualified.',
195
- [['x']]
196
- ),
229
+ if (slashIdx <= 0) return v.nil()
230
+ return v.string(raw.slice(0, slashIdx))
231
+ })
232
+ .doc(
233
+ 'Returns the namespace string of a qualified keyword or symbol, or nil if the argument is not qualified.',
234
+ [['x']]
235
+ ),
197
236
 
198
237
  // Returns the local name of a keyword or symbol as a string.
199
238
  // (name :user/foo) => "foo"
200
239
  // (name :foo) => "foo"
201
240
  // (name 'user/foo) => "foo"
202
- name: withDoc(
203
- cljNativeFunction('name', (x: CljValue) => {
241
+ name: v
242
+ .nativeFn('name', function nameImpl(x: CljValue) {
204
243
  if (x === undefined) {
205
244
  throw EvaluationError.atArg('name expects an argument', { x }, 0)
206
245
  }
207
246
  let raw: string | undefined
208
- if (isKeyword(x)) {
247
+ if (is.keyword(x)) {
209
248
  raw = x.name.slice(1) // strip leading ":"
210
- } else if (isSymbol(x)) {
249
+ } else if (is.symbol(x)) {
211
250
  raw = x.name
212
251
  } else if (x.kind === 'string') {
213
252
  return x
214
253
  } else {
215
- throw EvaluationError.atArg(`name expects a keyword, symbol, or string, got ${printString(x)}`, { x }, 0)
254
+ throw EvaluationError.atArg(
255
+ `name expects a keyword, symbol, or string, got ${printString(x)}`,
256
+ { x },
257
+ 0
258
+ )
216
259
  }
217
260
  const slashIdx = raw.indexOf('/')
218
- return cljString(slashIdx >= 0 ? raw.slice(slashIdx + 1) : raw)
219
- }),
220
- 'Returns the local name of a qualified keyword or symbol, or the string value if the argument is a string.',
221
- [['x']]
222
- ),
261
+ return v.string(slashIdx >= 0 ? raw.slice(slashIdx + 1) : raw)
262
+ })
263
+ .doc(
264
+ 'Returns the local name of a qualified keyword or symbol, or the string value if the argument is a string.',
265
+ [['x']]
266
+ ),
223
267
 
224
268
  // Constructs a keyword.
225
269
  // (keyword "foo") => :foo
226
270
  // (keyword "user" "foo") => :user/foo
227
- keyword: withDoc(
228
- cljNativeFunction('keyword', (...args: CljValue[]) => {
271
+ keyword: v
272
+ .nativeFn('keyword', function keywordImpl(...args: CljValue[]) {
229
273
  if (args.length === 0 || args.length > 2) {
230
274
  throw new EvaluationError('keyword expects 1 or 2 string arguments', {
231
275
  args,
232
276
  })
233
277
  }
234
278
  if (args[0].kind !== 'string') {
235
- throw EvaluationError.atArg(`keyword expects a string, got ${printString(args[0])}`, { args }, 0)
279
+ throw EvaluationError.atArg(
280
+ `keyword expects a string, got ${printString(args[0])}`,
281
+ { args },
282
+ 0
283
+ )
236
284
  }
237
285
  if (args.length === 1) {
238
- return cljKeyword(`:${args[0].value}`)
286
+ return v.keyword(`:${args[0].value}`)
239
287
  }
240
288
  if (args[1].kind !== 'string') {
241
- throw EvaluationError.atArg(`keyword second argument must be a string, got ${printString(args[1])}`, { args }, 1)
289
+ throw EvaluationError.atArg(
290
+ `keyword second argument must be a string, got ${printString(args[1])}`,
291
+ { args },
292
+ 1
293
+ )
242
294
  }
243
- return cljKeyword(`:${args[0].value}/${args[1].value}`)
244
- }),
245
- joinLines([
246
- 'Constructs a keyword with the given name and namespace strings. Returns a keyword value.',
247
- '',
248
- 'Note: do not use : in the keyword strings, it will be added automatically.',
249
- 'e.g. (keyword "foo") => :foo',
295
+ return v.keyword(`:${args[0].value}/${args[1].value}`)
296
+ })
297
+ .doc(
298
+ joinLines([
299
+ 'Constructs a keyword with the given name and namespace strings. Returns a keyword value.',
300
+ '',
301
+ 'Note: do not use : in the keyword strings, it will be added automatically.',
302
+ 'e.g. (keyword "foo") => :foo',
303
+ ]),
304
+ [['name'], ['ns', 'name']]
305
+ ),
306
+
307
+ boolean: v
308
+ .nativeFn('boolean', function booleanImpl(x: CljValue) {
309
+ if (x === undefined) return v.boolean(false)
310
+ return v.boolean(is.truthy(x))
311
+ })
312
+ .doc('Coerces to boolean. Everything is true except false and nil.', [
313
+ ['x'],
250
314
  ]),
251
- [['name'], ['ns', 'name']]
252
- ),
253
315
 
254
- boolean: withDoc(
255
- cljNativeFunction('boolean', (x: CljValue) => {
256
- if (x === undefined) return cljBoolean(false)
257
- return cljBoolean(isTruthy(x))
258
- }),
259
- 'Coerces to boolean. Everything is true except false and nil.',
260
- [['x']]
261
- ),
316
+ 'clojure-version': v
317
+ .nativeFn('clojure-version', function clojureVersionImpl() {
318
+ return v.string('1.12.0')
319
+ })
320
+ .doc('Returns a string describing the current Clojure version.', [[]]),
321
+
322
+ 'pr-str': v
323
+ .nativeFnCtx('pr-str', function prStrImpl(ctx: EvaluationContext, _callEnv, ...args: CljValue[]) {
324
+ return withPrintContext(buildPrintContext(ctx), () =>
325
+ v.string(args.map(printString).join(' '))
326
+ )
327
+ })
328
+ .doc(
329
+ 'Returns a readable string representation of the given values (strings are quoted).',
330
+ [['&', 'args']]
331
+ ),
332
+
333
+ 'pretty-print-str': v
334
+ .nativeFnCtx(
335
+ 'pretty-print-str',
336
+ function prettyPrintStrImpl(ctx: EvaluationContext, _callEnv, ...args: CljValue[]) {
337
+ if (args.length === 0) return v.string('')
338
+ const form = args[0]
339
+ const widthArg = args[1]
340
+ const maxWidth =
341
+ widthArg !== undefined && widthArg.kind === 'number'
342
+ ? widthArg.value
343
+ : 80
344
+ return withPrintContext(buildPrintContext(ctx), () =>
345
+ v.string(prettyPrintString(form, maxWidth))
346
+ )
347
+ }
348
+ )
349
+ .doc('Returns a pretty-printed string representation of form.', [
350
+ ['form'],
351
+ ['form', 'max-width'],
352
+ ]),
353
+
354
+ 'read-string': v
355
+ .nativeFn('read-string', function readStringImpl(s: CljValue) {
356
+ if (s === undefined || s.kind !== 'string') {
357
+ throw EvaluationError.atArg(
358
+ `read-string expects a string${s !== undefined ? `, got ${printString(s)}` : ''}`,
359
+ { s },
360
+ 0
361
+ )
362
+ }
363
+ const tokens = tokenize(s.value)
364
+ const forms = readForms(tokens)
365
+ if (forms.length === 0) return v.nil()
366
+ return forms[0]
367
+ })
368
+ .doc(
369
+ 'Reads one object from the string s. Returns nil if string is empty.',
370
+ [['s']]
371
+ ),
372
+
373
+ 'prn-str': v
374
+ .nativeFnCtx('prn-str', function prnStrImpl(ctx: EvaluationContext, _callEnv, ...args: CljValue[]) {
375
+ return withPrintContext(buildPrintContext(ctx), () =>
376
+ v.string(args.map(printString).join(' ') + '\n')
377
+ )
378
+ })
379
+ .doc('pr-str to a string, followed by a newline.', [['&', 'args']]),
380
+
381
+ 'print-str': v
382
+ .nativeFnCtx('print-str', function printStrImpl(ctx: EvaluationContext, _callEnv, ...args: CljValue[]) {
383
+ return withPrintContext(buildPrintContext(ctx), () =>
384
+ v.string(args.map(valueToString).join(' '))
385
+ )
386
+ })
387
+ .doc('print to a string (human-readable, no quotes on strings).', [
388
+ ['&', 'args'],
389
+ ]),
390
+
391
+ 'println-str': v
392
+ .nativeFn('println-str', function printlnStrImpl(...args: CljValue[]) {
393
+ return v.string(args.map(valueToString).join(' ') + '\n')
394
+ })
395
+ .doc('println to a string.', [['&', 'args']]),
396
+
397
+ symbol: v
398
+ .nativeFn('symbol', function symbolImpl(...args: CljValue[]) {
399
+ if (args.length === 0 || args.length > 2) {
400
+ throw new EvaluationError('symbol expects 1 or 2 string arguments', {
401
+ args,
402
+ })
403
+ }
404
+ if (args.length === 1) {
405
+ if (is.symbol(args[0])) return args[0]
406
+ if (args[0].kind !== 'string') {
407
+ throw EvaluationError.atArg(
408
+ `symbol expects a string, got ${printString(args[0])}`,
409
+ { args },
410
+ 0
411
+ )
412
+ }
413
+ return v.symbol(args[0].value)
414
+ }
415
+ if (args[0].kind !== 'string' || args[1].kind !== 'string') {
416
+ throw new EvaluationError('symbol expects string arguments', { args })
417
+ }
418
+ return v.symbol(`${args[0].value}/${args[1].value}`)
419
+ })
420
+ .doc('Returns a Symbol with the given namespace and name.', [
421
+ ['name'],
422
+ ['ns', 'name'],
423
+ ]),
262
424
  }
@@ -1,42 +1,41 @@
1
- import { isAFunction, isVar } from '../assertions'
1
+ import { is } from '../assertions'
2
2
  import { EvaluationError } from '../errors'
3
- import {
4
- cljBoolean,
5
- cljNativeFunction,
6
- cljNativeFunctionWithContext,
7
- withDoc,
8
- } from '../factories'
3
+ import { v } from '../factories'
9
4
  import type { CljValue } from '../types'
10
5
 
11
6
  export const varFunctions: Record<string, CljValue> = {
12
- 'var?': withDoc(
13
- cljNativeFunction('var?', (x: CljValue) => cljBoolean(isVar(x))),
14
- 'Returns true if x is a Var.',
15
- [['x']]
16
- ),
7
+ 'var?': v
8
+ .nativeFn('var?', function isVarImpl(x: CljValue) {
9
+ return v.boolean(is.var(x))
10
+ })
11
+ .doc('Returns true if x is a Var.', [['x']]),
17
12
 
18
- 'var-get': withDoc(
19
- cljNativeFunction('var-get', (x: CljValue) => {
20
- if (!isVar(x)) {
13
+ 'var-get': v
14
+ .nativeFn('var-get', function varGetImpl(x: CljValue) {
15
+ if (!is.var(x)) {
21
16
  throw new EvaluationError(`var-get expects a Var, got ${x.kind}`, { x })
22
17
  }
23
18
  return x.value
24
- }),
25
- 'Returns the value in the Var object.',
26
- [['x']]
27
- ),
19
+ })
20
+ .doc('Returns the value in the Var object.', [['x']]),
28
21
 
29
- 'alter-var-root': withDoc(
30
- cljNativeFunctionWithContext(
22
+ 'alter-var-root': v
23
+ .nativeFnCtx(
31
24
  'alter-var-root',
32
- (ctx, callEnv, varVal: CljValue, f: CljValue, ...args: CljValue[]) => {
33
- if (!isVar(varVal)) {
25
+ function alterVarRootImpl(
26
+ ctx,
27
+ callEnv,
28
+ varVal: CljValue,
29
+ f: CljValue,
30
+ ...args: CljValue[]
31
+ ) {
32
+ if (!is.var(varVal)) {
34
33
  throw new EvaluationError(
35
34
  `alter-var-root expects a Var as its first argument, got ${varVal.kind}`,
36
35
  { varVal }
37
36
  )
38
37
  }
39
- if (!isAFunction(f)) {
38
+ if (!is.aFunction(f)) {
40
39
  throw new EvaluationError(
41
40
  `alter-var-root expects a function as its second argument, got ${f.kind}`,
42
41
  { f }
@@ -46,8 +45,9 @@ export const varFunctions: Record<string, CljValue> = {
46
45
  varVal.value = newVal
47
46
  return newVal
48
47
  }
48
+ )
49
+ .doc(
50
+ 'Atomically alters the root binding of var v by applying f to its current value plus any additional args.',
51
+ [['v', 'f', '&', 'args']]
49
52
  ),
50
- 'Atomically alters the root binding of var v by applying f to its current value plus any additional args.',
51
- [['v', 'f', '&', 'args']]
52
- ),
53
53
  }