conjure-js 0.0.13 → 0.0.14

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 (58) hide show
  1. package/dist-cli/conjure-js.mjs +2328 -2089
  2. package/dist-vite-plugin/index.mjs +2327 -2088
  3. package/package.json +1 -1
  4. package/src/bin/version.ts +1 -1
  5. package/src/core/assertions.ts +10 -3
  6. package/src/core/bootstrap.ts +7 -23
  7. package/src/core/compiler/binding.ts +164 -0
  8. package/src/core/compiler/callable.ts +41 -0
  9. package/src/core/compiler/compile-env.ts +40 -0
  10. package/src/core/compiler/control-flow.ts +79 -0
  11. package/src/core/compiler/index.ts +121 -0
  12. package/src/core/env.ts +4 -4
  13. package/src/core/errors.ts +1 -0
  14. package/src/core/evaluator/apply.ts +7 -3
  15. package/src/core/evaluator/arity.ts +16 -6
  16. package/src/core/evaluator/async-evaluator.ts +68 -89
  17. package/src/core/evaluator/collections.ts +9 -4
  18. package/src/core/evaluator/destructure.ts +45 -55
  19. package/src/core/evaluator/dispatch.ts +21 -24
  20. package/src/core/evaluator/evaluate.ts +14 -2
  21. package/src/core/evaluator/expand.ts +5 -7
  22. package/src/core/evaluator/js-interop.ts +46 -33
  23. package/src/core/evaluator/quasiquote.ts +7 -11
  24. package/src/core/evaluator/recur-check.ts +1 -1
  25. package/src/core/evaluator/special-forms.ts +18 -38
  26. package/src/core/index.ts +1 -1
  27. package/src/core/keywords.ts +105 -0
  28. package/src/core/modules/core/index.ts +131 -0
  29. package/src/core/{stdlib → modules/core/stdlib}/arithmetic.ts +6 -6
  30. package/src/core/{stdlib → modules/core/stdlib}/async-fns.ts +6 -6
  31. package/src/core/{stdlib → modules/core/stdlib}/atoms.ts +7 -7
  32. package/src/core/{stdlib → modules/core/stdlib}/errors.ts +4 -4
  33. package/src/core/{stdlib → modules/core/stdlib}/hof.ts +6 -6
  34. package/src/core/{stdlib → modules/core/stdlib}/lazy.ts +4 -4
  35. package/src/core/{stdlib → modules/core/stdlib}/maps-sets.ts +6 -6
  36. package/src/core/{stdlib → modules/core/stdlib}/meta.ts +5 -5
  37. package/src/core/{stdlib → modules/core/stdlib}/predicates.ts +7 -7
  38. package/src/core/modules/core/stdlib/print.ts +108 -0
  39. package/src/core/{stdlib → modules/core/stdlib}/regex.ts +5 -5
  40. package/src/core/{stdlib → modules/core/stdlib}/seq.ts +7 -7
  41. package/src/core/{stdlib → modules/core/stdlib}/strings.ts +6 -6
  42. package/src/core/{stdlib → modules/core/stdlib}/transducers.ts +6 -6
  43. package/src/core/{stdlib → modules/core/stdlib}/utils.ts +10 -10
  44. package/src/core/{stdlib → modules/core/stdlib}/vars.ts +4 -4
  45. package/src/core/{stdlib → modules/core/stdlib}/vectors.ts +6 -6
  46. package/src/core/modules/js/index.ts +402 -0
  47. package/src/core/ns-forms.ts +25 -17
  48. package/src/core/positions.ts +22 -2
  49. package/src/core/printer.ts +162 -53
  50. package/src/core/reader.ts +25 -22
  51. package/src/core/registry.ts +10 -10
  52. package/src/core/runtime.ts +23 -23
  53. package/src/core/session.ts +17 -7
  54. package/src/core/tokenizer.ts +14 -4
  55. package/src/core/transformations.ts +48 -29
  56. package/src/core/types.ts +57 -81
  57. package/src/core/core-module.ts +0 -303
  58. package/src/core/stdlib/js-namespace.ts +0 -344
@@ -1,17 +1,17 @@
1
1
  import { builtInNamespaceSources } from '../clojure/generated/builtin-namespace-registry'
2
+ import { cljToJs as _cljToJs } from './conversions'
3
+ import { internVar, makeEnv } from './env'
2
4
  import { CljThrownSignal, EvaluationError, ReaderError } from './errors'
3
5
  import { createEvaluationContext, RecurSignal } from './evaluator'
4
- import { internVar, makeEnv } from './env'
5
- import { v } from './factories'
6
6
  import { jsToClj } from './evaluator/js-interop'
7
+ import { v } from './factories'
7
8
  import type { RuntimeModule } from './module'
8
- import { cljToJs as _cljToJs } from './conversions'
9
+ import { extractAliasMapFromTokens, extractNsNameFromTokens } from './ns-forms'
9
10
  import { formatErrorContext } from './positions'
10
11
  import { printString } from './printer'
11
12
  import { readForms } from './reader'
12
13
  import type { Runtime, RuntimeSnapshot } from './runtime'
13
14
  import { createRuntime, restoreRuntime } from './runtime'
14
- import { extractAliasMapFromTokens, extractNsNameFromTokens } from './ns-forms'
15
15
  import { tokenize } from './tokenizer'
16
16
  import type { CljNamespace, CljValue, Env } from './types'
17
17
 
@@ -57,7 +57,11 @@ export type Session = {
57
57
  getNs: (namespace: string) => CljNamespace | null
58
58
  loadFile: (source: string, nsName?: string, filePath?: string) => string
59
59
  /** Async variant of loadFile — handles string requires ((:require ["pkg" :as X])). */
60
- loadFileAsync: (source: string, nsName?: string, filePath?: string) => Promise<string>
60
+ loadFileAsync: (
61
+ source: string,
62
+ nsName?: string,
63
+ filePath?: string
64
+ ) => Promise<string>
61
65
  evaluate: (
62
66
  source: string,
63
67
  opts?: { lineOffset?: number; colOffset?: number; file?: string }
@@ -142,7 +146,11 @@ function buildSessionFacade(
142
146
  return runtime.loadFile(source, nsName, filePath, ctx)
143
147
  },
144
148
 
145
- async loadFileAsync(source: string, nsName?: string, filePath?: string): Promise<string> {
149
+ async loadFileAsync(
150
+ source: string,
151
+ nsName?: string,
152
+ filePath?: string
153
+ ): Promise<string> {
146
154
  // If there is no ns declaration in the source, pre-set the namespace from
147
155
  // the hint so the forms evaluate in the right context.
148
156
  if (nsName) {
@@ -301,7 +309,9 @@ function buildSessionFacade(
301
309
  },
302
310
 
303
311
  cljToJs(value: CljValue): unknown {
304
- return _cljToJs(value, { applyFunction: (fn, args) => ctx.applyCallable(fn, args, makeEnv()) })
312
+ return _cljToJs(value, {
313
+ applyFunction: (fn, args) => ctx.applyCallable(fn, args, makeEnv()),
314
+ })
305
315
  },
306
316
 
307
317
  evaluateForms(forms: CljValue[]): CljValue {
@@ -1,6 +1,7 @@
1
1
  import { TokenizerError } from './errors'
2
2
  import { makeCharScanner, type CharScanner } from './scanners'
3
- import { tokenKeywords, tokenSymbols, type Token } from './types'
3
+ import { tokenKeywords, tokenSymbols } from './keywords'
4
+ import type { Token } from './types'
4
5
 
5
6
  export type TokensResult = {
6
7
  tokens: Token[]
@@ -169,9 +170,15 @@ const parseNumber = (ctx: TokenizationContext): Token => {
169
170
  value += scanner.advance()! // consume '.'
170
171
  value += scanner.consumeWhile(isNumber)
171
172
  }
172
- if (!scanner.isAtEnd() && (scanner.peek() === 'e' || scanner.peek() === 'E')) {
173
+ if (
174
+ !scanner.isAtEnd() &&
175
+ (scanner.peek() === 'e' || scanner.peek() === 'E')
176
+ ) {
173
177
  value += scanner.advance()! // consume 'e' or 'E'
174
- if (!scanner.isAtEnd() && (scanner.peek() === '+' || scanner.peek() === '-')) {
178
+ if (
179
+ !scanner.isAtEnd() &&
180
+ (scanner.peek() === '+' || scanner.peek() === '-')
181
+ ) {
175
182
  value += scanner.advance()! // consume optional sign
176
183
  }
177
184
  const exponentDigits = scanner.consumeWhile(isNumber)
@@ -226,7 +233,10 @@ const parseMetaToken = (ctx: TokenizationContext): Token => {
226
233
  return { kind: 'Meta', start, end: scanner.position() }
227
234
  }
228
235
 
229
- const parseRegexLiteral = (ctx: TokenizationContext, start: ReturnType<typeof ctx.scanner.position>): Token => {
236
+ const parseRegexLiteral = (
237
+ ctx: TokenizationContext,
238
+ start: ReturnType<typeof ctx.scanner.position>
239
+ ): Token => {
230
240
  const scanner = ctx.scanner
231
241
  scanner.advance() // consume opening '"'
232
242
  const buffer: string[] = []
@@ -1,8 +1,14 @@
1
- import { isCons, isLazySeq, isList, isMap, isNil, isSet, isVector } from './assertions'
1
+ import { is } from './assertions'
2
2
  import { EvaluationError } from './errors'
3
- import { cljString, cljVector } from './factories'
4
- import { printString, getPrintContext } from './printer'
5
- import { type CljCons, type CljDelay, type CljLazySeq, type CljValue, valueKeywords } from './types'
3
+ import { v } from './factories'
4
+ import { valueKeywords } from './keywords'
5
+ import { getPrintContext, printString } from './printer'
6
+ import {
7
+ type CljCons,
8
+ type CljDelay,
9
+ type CljLazySeq,
10
+ type CljValue,
11
+ } from './types'
6
12
 
7
13
  export function valueToString(value: CljValue): string {
8
14
  switch (value.kind) {
@@ -18,26 +24,36 @@ export function valueToString(value: CljValue): string {
18
24
  return value.name
19
25
  case valueKeywords.list: {
20
26
  const { printLength } = getPrintContext()
21
- const items = printLength !== null ? value.value.slice(0, printLength) : value.value
22
- const suffix = printLength !== null && value.value.length > printLength ? ' ...' : ''
27
+ const items =
28
+ printLength !== null ? value.value.slice(0, printLength) : value.value
29
+ const suffix =
30
+ printLength !== null && value.value.length > printLength ? ' ...' : ''
23
31
  return `(${items.map(valueToString).join(' ')}${suffix})`
24
32
  }
25
33
  case valueKeywords.vector: {
26
34
  const { printLength } = getPrintContext()
27
- const items = printLength !== null ? value.value.slice(0, printLength) : value.value
28
- const suffix = printLength !== null && value.value.length > printLength ? ' ...' : ''
35
+ const items =
36
+ printLength !== null ? value.value.slice(0, printLength) : value.value
37
+ const suffix =
38
+ printLength !== null && value.value.length > printLength ? ' ...' : ''
29
39
  return `[${items.map(valueToString).join(' ')}${suffix}]`
30
40
  }
31
41
  case valueKeywords.map: {
32
42
  const { printLength } = getPrintContext()
33
- const entries = printLength !== null ? value.entries.slice(0, printLength) : value.entries
34
- const suffix = printLength !== null && value.entries.length > printLength ? ' ...' : ''
43
+ const entries =
44
+ printLength !== null
45
+ ? value.entries.slice(0, printLength)
46
+ : value.entries
47
+ const suffix =
48
+ printLength !== null && value.entries.length > printLength ? ' ...' : ''
35
49
  return `{${entries.map(([key, v]) => `${valueToString(key)} ${valueToString(v)}`).join(' ')}${suffix}}`
36
50
  }
37
51
  case valueKeywords.set: {
38
52
  const { printLength } = getPrintContext()
39
- const items = printLength !== null ? value.values.slice(0, printLength) : value.values
40
- const suffix = printLength !== null && value.values.length > printLength ? ' ...' : ''
53
+ const items =
54
+ printLength !== null ? value.values.slice(0, printLength) : value.values
55
+ const suffix =
56
+ printLength !== null && value.values.length > printLength ? ' ...' : ''
41
57
  return `#{${items.map(valueToString).join(' ')}${suffix}}`
42
58
  }
43
59
  case valueKeywords.function: {
@@ -67,17 +83,20 @@ export function valueToString(value: CljValue): string {
67
83
  return `${prefix}${value.pattern}`
68
84
  }
69
85
  case valueKeywords.delay:
70
- return value.realized ? `#<Delay @${valueToString(value.value!)}>` : '#<Delay pending>'
86
+ return value.realized
87
+ ? `#<Delay @${valueToString(value.value!)}>`
88
+ : '#<Delay pending>'
71
89
  case valueKeywords.lazySeq: {
72
90
  const realized = realizeLazySeq(value)
73
- if (isNil(realized)) return '()'
91
+ if (is.nil(realized)) return '()'
74
92
  return valueToString(realized)
75
93
  }
76
94
  case valueKeywords.cons: {
77
95
  const items = consToArray(value)
78
96
  const { printLength } = getPrintContext()
79
97
  const visible = printLength !== null ? items.slice(0, printLength) : items
80
- const suffix = printLength !== null && items.length > printLength ? ' ...' : ''
98
+ const suffix =
99
+ printLength !== null && items.length > printLength ? ' ...' : ''
81
100
  return `(${visible.map(valueToString).join(' ')}${suffix})`
82
101
  }
83
102
  case valueKeywords.namespace:
@@ -123,27 +142,27 @@ export function realizeLazySeq(ls: CljLazySeq): CljValue {
123
142
  }
124
143
 
125
144
  export const toSeq = (collection: CljValue): CljValue[] => {
126
- if (isList(collection)) {
145
+ if (is.list(collection)) {
127
146
  return collection.value
128
147
  }
129
- if (isVector(collection)) {
148
+ if (is.vector(collection)) {
130
149
  return collection.value
131
150
  }
132
- if (isMap(collection)) {
133
- return collection.entries.map(([k, v]) => cljVector([k, v]))
151
+ if (is.map(collection)) {
152
+ return collection.entries.map(([key, value]) => v.vector([key, value]))
134
153
  }
135
- if (isSet(collection)) {
154
+ if (is.set(collection)) {
136
155
  return collection.values
137
156
  }
138
157
  if (collection.kind === 'string') {
139
- return [...collection.value].map(cljString)
158
+ return [...collection.value].map(v.string)
140
159
  }
141
- if (isLazySeq(collection)) {
160
+ if (is.lazySeq(collection)) {
142
161
  const realized = realizeLazySeq(collection)
143
- if (isNil(realized)) return []
162
+ if (is.nil(realized)) return []
144
163
  return toSeq(realized)
145
164
  }
146
- if (isCons(collection)) {
165
+ if (is.cons(collection)) {
147
166
  return consToArray(collection)
148
167
  }
149
168
  throw new EvaluationError(
@@ -157,21 +176,21 @@ export function consToArray(c: CljCons): CljValue[] {
157
176
  const result: CljValue[] = [c.head]
158
177
  let tail: CljValue = c.tail
159
178
  while (true) {
160
- if (isNil(tail)) break
161
- if (isCons(tail)) {
179
+ if (is.nil(tail)) break
180
+ if (is.cons(tail)) {
162
181
  result.push(tail.head)
163
182
  tail = tail.tail
164
183
  continue
165
184
  }
166
- if (isLazySeq(tail)) {
185
+ if (is.lazySeq(tail)) {
167
186
  tail = realizeLazySeq(tail)
168
187
  continue
169
188
  }
170
- if (isList(tail)) {
189
+ if (is.list(tail)) {
171
190
  result.push(...tail.value)
172
191
  break
173
192
  }
174
- if (isVector(tail)) {
193
+ if (is.vector(tail)) {
175
194
  result.push(...tail.value)
176
195
  break
177
196
  }
package/src/core/types.ts CHANGED
@@ -1,31 +1,3 @@
1
- export const valueKeywords = {
2
- number: 'number',
3
- string: 'string',
4
- boolean: 'boolean',
5
- keyword: 'keyword',
6
- nil: 'nil',
7
- symbol: 'symbol',
8
- list: 'list',
9
- vector: 'vector',
10
- map: 'map',
11
- function: 'function',
12
- nativeFunction: 'native-function',
13
- macro: 'macro',
14
- multiMethod: 'multi-method',
15
- atom: 'atom',
16
- reduced: 'reduced',
17
- volatile: 'volatile',
18
- regex: 'regex',
19
- var: 'var',
20
- set: 'set',
21
- delay: 'delay',
22
- lazySeq: 'lazy-seq',
23
- cons: 'cons',
24
- namespace: 'namespace',
25
- jsValue: 'js-value',
26
- } as const
27
- export type ValueKeywords = (typeof valueKeywords)[keyof typeof valueKeywords]
28
-
29
1
  export type CljNumber = { kind: 'number'; value: number }
30
2
  export type CljString = { kind: 'string'; value: string }
31
3
  export type CljBoolean = { kind: 'boolean'; value: boolean }
@@ -34,19 +6,23 @@ export type CljNil = { kind: 'nil'; value: null }
34
6
  export type CljSymbol = { kind: 'symbol'; name: string; meta?: CljMap }
35
7
  export type CljList = { kind: 'list'; value: CljValue[]; meta?: CljMap }
36
8
  export type CljVector = { kind: 'vector'; value: CljValue[]; meta?: CljMap }
37
- export type CljMap = { kind: 'map'; entries: [CljValue, CljValue][]; meta?: CljMap }
9
+ export type CljMap = {
10
+ kind: 'map'
11
+ entries: [CljValue, CljValue][]
12
+ meta?: CljMap
13
+ }
38
14
  export type CljNamespace = {
39
15
  kind: 'namespace'
40
16
  name: string
41
- vars: Map<string, CljVar> // user defs from (def ...)
42
- aliases: Map<string, CljNamespace> // :as namespace aliases
43
- readerAliases: Map<string, string> // :as-alias reader aliases
17
+ vars: Map<string, CljVar> // user defs from (def ...)
18
+ aliases: Map<string, CljNamespace> // :as namespace aliases
19
+ readerAliases: Map<string, string> // :as-alias reader aliases
44
20
  }
45
21
 
46
22
  export type Env = {
47
- bindings: Map<string, CljValue> // native fns, macros, multimethods, local values
23
+ bindings: Map<string, CljValue> // native fns, macros, multimethods, local values
48
24
  outer: Env | null
49
- ns?: CljNamespace // set on namespace-root envs only
25
+ ns?: CljNamespace // set on namespace-root envs only
50
26
  }
51
27
 
52
28
  export type DestructurePattern = CljSymbol | CljVector | CljMap
@@ -55,13 +31,14 @@ export type Arity = {
55
31
  params: DestructurePattern[]
56
32
  restParam: DestructurePattern | null
57
33
  body: CljValue[]
34
+ compiledBody?: CompiledExpr
58
35
  }
59
36
 
60
37
  export type CljFunction = {
61
38
  kind: 'function'
62
39
  arities: Arity[]
63
40
  env: Env
64
- name?: string // set for named fn: (fn my-name [x] x)
41
+ name?: string // set for named fn: (fn my-name [x] x)
65
42
  meta?: CljMap
66
43
  }
67
44
 
@@ -69,14 +46,17 @@ export type CljMacro = {
69
46
  kind: 'macro'
70
47
  arities: Arity[]
71
48
  env: Env
72
- name?: string // set for named defmacro
49
+ name?: string // set for named defmacro
73
50
  }
74
51
 
75
52
  export type CljAtom = {
76
53
  kind: 'atom'
77
54
  value: CljValue
78
55
  meta?: CljMap
79
- watches?: Map<string, { key: CljValue; fn: CljValue; ctx: EvaluationContext; callEnv: Env }>
56
+ watches?: Map<
57
+ string,
58
+ { key: CljValue; fn: CljValue; ctx: EvaluationContext; callEnv: Env }
59
+ >
80
60
  validator?: CljValue
81
61
  }
82
62
  export type CljReduced = { kind: 'reduced'; value: CljValue }
@@ -96,13 +76,13 @@ export type CljLazySeq = {
96
76
  kind: 'lazy-seq'
97
77
  thunk: (() => CljValue) | null
98
78
  realized: boolean
99
- value?: CljValue // nil, list, cons, or another lazy-seq after realization
79
+ value?: CljValue // nil, list, cons, or another lazy-seq after realization
100
80
  }
101
81
 
102
82
  export type CljCons = {
103
83
  kind: 'cons'
104
84
  head: CljValue
105
- tail: CljValue // can be list, vector, lazy-seq, cons, or nil
85
+ tail: CljValue // can be list, vector, lazy-seq, cons, or nil
106
86
  meta?: CljMap
107
87
  }
108
88
 
@@ -111,8 +91,8 @@ export type CljVar = {
111
91
  ns: string
112
92
  name: string
113
93
  value: CljValue
114
- dynamic?: boolean // set when def is annotated with ^:dynamic
115
- bindingStack?: CljValue[] // active dynamic bindings (push/pop by `binding`)
94
+ dynamic?: boolean // set when def is annotated with ^:dynamic
95
+ bindingStack?: CljValue[] // active dynamic bindings (push/pop by `binding`)
116
96
  meta?: CljMap
117
97
  }
118
98
 
@@ -234,46 +214,6 @@ export type CljValue =
234
214
  | CljPending
235
215
  | CljJsValue
236
216
 
237
- /** Tokens */
238
- export const tokenKeywords = {
239
- LParen: 'LParen',
240
- RParen: 'RParen',
241
- LBracket: 'LBracket',
242
- RBracket: 'RBracket',
243
- LBrace: 'LBrace',
244
- RBrace: 'RBrace',
245
- String: 'String',
246
- Number: 'Number',
247
- Keyword: 'Keyword',
248
- Quote: 'Quote',
249
- Quasiquote: 'Quasiquote',
250
- Unquote: 'Unquote',
251
- UnquoteSplicing: 'UnquoteSplicing',
252
- Comment: 'Comment',
253
- Whitespace: 'Whitespace',
254
- Symbol: 'Symbol',
255
- AnonFnStart: 'AnonFnStart',
256
- Deref: 'Deref',
257
- Regex: 'Regex',
258
- VarQuote: 'VarQuote',
259
- Meta: 'Meta',
260
- SetStart: 'SetStart',
261
- } as const
262
- export const tokenSymbols = {
263
- Quote: 'quote',
264
- Quasiquote: 'quasiquote',
265
- Unquote: 'unquote',
266
- UnquoteSplicing: 'unquote-splicing',
267
- LParen: '(',
268
- RParen: ')',
269
- LBracket: '[',
270
- RBracket: ']',
271
- LBrace: '{',
272
- RBrace: '}',
273
- } as const
274
- export type TokenSymbols = (typeof tokenSymbols)[keyof typeof tokenSymbols]
275
- export type TokenKinds = keyof typeof tokenKeywords
276
-
277
217
  export type Cursor = {
278
218
  line: number
279
219
  col: number
@@ -388,3 +328,39 @@ export type Token = (
388
328
  | TokenMeta
389
329
  | TokenSetStart
390
330
  ) & { start: Cursor; end: Cursor }
331
+
332
+ /** Compiler */
333
+
334
+ /**
335
+ * A compiled expression takes runtime env + ctx, returns a CljValue.
336
+ * Signature includes ctx even though we don't use it yet
337
+ * Phases 2+ (if, fn*, apply) will need it. We keep the shape fixed now.
338
+ */
339
+ export type CompiledExpr = (env: Env, ctx: EvaluationContext) => CljValue
340
+
341
+ /**
342
+ * A compile function takes a node and an optional compile-time env. It returns a compiled expression
343
+ * when it can't compile a node, it returns null, falling back to the interpreter
344
+ * we have this definition here so that recursive compiler functions such as compileIf,
345
+ * and compileDo can reference the "root dispatcher" when compiling sub-forms.
346
+ * It's a clean way to prevent cyclical dependencies. Only recursive compiler fns need this.
347
+ */
348
+ export type CompileFn = (
349
+ node: CljValue,
350
+ env: CompileEnv | null
351
+ ) => CompiledExpr | null
352
+
353
+ /**
354
+ * A mutable box. Allocated at compile time.
355
+ * Read by compiled symbols that reference the slot binding.
356
+ */
357
+ export type SlotRef = { value: CljValue | null }
358
+
359
+ export type CompileEnv = {
360
+ bindings: Map<string, SlotRef>
361
+ outer: CompileEnv | null
362
+ loop?: {
363
+ slots: SlotRef[]
364
+ recurTarget: { args: CljValue[] | null }
365
+ }
366
+ }