conjure-js 0.0.1

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/conjure +0 -0
  2. package/dist/assets/codicon-ngg6Pgfi.ttf +0 -0
  3. package/dist/assets/editor.worker-CdQrwHl8.js +26 -0
  4. package/dist/assets/main-A7ZMId9A.css +1 -0
  5. package/dist/assets/main-CmI-7epE.js +3137 -0
  6. package/dist/index.html +195 -0
  7. package/dist/vite.svg +1 -0
  8. package/package.json +68 -0
  9. package/src/bin/__fixtures__/smoke/app/lib.clj +4 -0
  10. package/src/bin/__fixtures__/smoke/app/main.clj +4 -0
  11. package/src/bin/__fixtures__/smoke/repl-smoke.ts +12 -0
  12. package/src/bin/bencode.ts +205 -0
  13. package/src/bin/cli.ts +250 -0
  14. package/src/bin/nrepl-utils.ts +59 -0
  15. package/src/bin/nrepl.ts +393 -0
  16. package/src/bin/version.ts +4 -0
  17. package/src/clojure/core.clj +620 -0
  18. package/src/clojure/core.clj.d.ts +189 -0
  19. package/src/clojure/demo/math.clj +16 -0
  20. package/src/clojure/demo/math.clj.d.ts +4 -0
  21. package/src/clojure/demo.clj +42 -0
  22. package/src/clojure/demo.clj.d.ts +0 -0
  23. package/src/clojure/generated/builtin-namespace-registry.ts +14 -0
  24. package/src/clojure/generated/clojure-core-source.ts +623 -0
  25. package/src/clojure/generated/clojure-string-source.ts +196 -0
  26. package/src/clojure/string.clj +192 -0
  27. package/src/clojure/string.clj.d.ts +25 -0
  28. package/src/core/assertions.ts +134 -0
  29. package/src/core/conversions.ts +108 -0
  30. package/src/core/core-env.ts +58 -0
  31. package/src/core/env.ts +78 -0
  32. package/src/core/errors.ts +39 -0
  33. package/src/core/evaluator/apply.ts +114 -0
  34. package/src/core/evaluator/arity.ts +174 -0
  35. package/src/core/evaluator/collections.ts +25 -0
  36. package/src/core/evaluator/destructure.ts +247 -0
  37. package/src/core/evaluator/dispatch.ts +73 -0
  38. package/src/core/evaluator/evaluate.ts +100 -0
  39. package/src/core/evaluator/expand.ts +79 -0
  40. package/src/core/evaluator/index.ts +72 -0
  41. package/src/core/evaluator/quasiquote.ts +87 -0
  42. package/src/core/evaluator/recur-check.ts +109 -0
  43. package/src/core/evaluator/special-forms.ts +517 -0
  44. package/src/core/factories.ts +155 -0
  45. package/src/core/gensym.ts +9 -0
  46. package/src/core/index.ts +76 -0
  47. package/src/core/positions.ts +38 -0
  48. package/src/core/printer.ts +86 -0
  49. package/src/core/reader.ts +559 -0
  50. package/src/core/scanners.ts +93 -0
  51. package/src/core/session.ts +610 -0
  52. package/src/core/stdlib/arithmetic.ts +361 -0
  53. package/src/core/stdlib/atoms.ts +88 -0
  54. package/src/core/stdlib/collections.ts +784 -0
  55. package/src/core/stdlib/errors.ts +81 -0
  56. package/src/core/stdlib/hof.ts +307 -0
  57. package/src/core/stdlib/meta.ts +48 -0
  58. package/src/core/stdlib/predicates.ts +240 -0
  59. package/src/core/stdlib/regex.ts +238 -0
  60. package/src/core/stdlib/strings.ts +311 -0
  61. package/src/core/stdlib/transducers.ts +256 -0
  62. package/src/core/stdlib/utils.ts +287 -0
  63. package/src/core/tokenizer.ts +437 -0
  64. package/src/core/transformations.ts +75 -0
  65. package/src/core/types.ts +258 -0
  66. package/src/main.ts +1 -0
  67. package/src/monaco-esm.d.ts +7 -0
  68. package/src/playground/clojure-tokens.ts +67 -0
  69. package/src/playground/editor.worker.ts +5 -0
  70. package/src/playground/find-form.ts +138 -0
  71. package/src/playground/playground.ts +342 -0
  72. package/src/playground/samples/00-welcome.clj +385 -0
  73. package/src/playground/samples/01-collections.clj +191 -0
  74. package/src/playground/samples/02-higher-order-functions.clj +215 -0
  75. package/src/playground/samples/03-destructuring.clj +194 -0
  76. package/src/playground/samples/04-strings-and-regex.clj +202 -0
  77. package/src/playground/samples/05-error-handling.clj +212 -0
  78. package/src/repl/repl.ts +116 -0
  79. package/tsconfig.build.json +10 -0
  80. package/tsconfig.json +31 -0
@@ -0,0 +1,196 @@
1
+ // Auto-generated from src/clojure/string.clj — do not edit directly.
2
+ // Re-generate with: npm run gen:core-source
3
+ export const clojure_stringSource = `\
4
+ (ns clojure.string)
5
+
6
+ ;; Runtime-injected native helpers. Declared here so clojure-lsp can resolve
7
+ ;; them; the interpreter treats bare (def name) as a no-op and leaves the
8
+ ;; native binding from coreEnv intact.
9
+ (def str-split*)
10
+ (def str-upper-case*)
11
+ (def str-lower-case*)
12
+ (def str-trim*)
13
+ (def str-triml*)
14
+ (def str-trimr*)
15
+ (def str-reverse*)
16
+ (def str-starts-with*)
17
+ (def str-ends-with*)
18
+ (def str-includes*)
19
+ (def str-index-of*)
20
+ (def str-last-index-of*)
21
+ (def str-replace*)
22
+ (def str-replace-first*)
23
+
24
+ ;; ---------------------------------------------------------------------------
25
+ ;; Joining / splitting
26
+ ;; ---------------------------------------------------------------------------
27
+
28
+ (defn join
29
+ "Returns a string of all elements in coll, as returned by (str), separated
30
+ by an optional separator."
31
+ ([coll] (join "" coll))
32
+ ([separator coll]
33
+ (if (nil? coll)
34
+ ""
35
+ (reduce
36
+ (fn [acc x]
37
+ (if (= acc "")
38
+ (str x)
39
+ (str acc separator x)))
40
+ ""
41
+ coll))))
42
+
43
+ (defn split
44
+ "Splits string on a regular expression. Optional limit is the maximum number
45
+ of parts returned. Trailing empty strings are not returned by default; pass
46
+ a limit of -1 to return all."
47
+ ([s sep] (str-split* s sep))
48
+ ([s sep limit] (str-split* s sep limit)))
49
+
50
+ (defn split-lines
51
+ "Splits s on \\\\n or \\\\r\\\\n. Trailing empty lines are not returned."
52
+ [s]
53
+ (split s #"\\r?\\n"))
54
+
55
+ ;; ---------------------------------------------------------------------------
56
+ ;; Case conversion
57
+ ;; ---------------------------------------------------------------------------
58
+
59
+ (defn upper-case
60
+ "Converts string to all upper-case."
61
+ [s]
62
+ (str-upper-case* s))
63
+
64
+ (defn lower-case
65
+ "Converts string to all lower-case."
66
+ [s]
67
+ (str-lower-case* s))
68
+
69
+ (defn capitalize
70
+ "Converts first character of the string to upper-case, all other
71
+ characters to lower-case."
72
+ [s]
73
+ (if (< (count s) 2)
74
+ (upper-case s)
75
+ (str (upper-case (subs s 0 1)) (lower-case (subs s 1)))))
76
+
77
+ ;; ---------------------------------------------------------------------------
78
+ ;; Trimming
79
+ ;; ---------------------------------------------------------------------------
80
+
81
+ (defn trim
82
+ "Removes whitespace from both ends of string."
83
+ [s]
84
+ (str-trim* s))
85
+
86
+ (defn triml
87
+ "Removes whitespace from the left side of string."
88
+ [s]
89
+ (str-triml* s))
90
+
91
+ (defn trimr
92
+ "Removes whitespace from the right side of string."
93
+ [s]
94
+ (str-trimr* s))
95
+
96
+ (defn trim-newline
97
+ "Removes all trailing newline \\\\n or return \\\\r characters from string.
98
+ Similar to Perl's chomp."
99
+ [s]
100
+ (replace s #"[\\r\\n]+$" ""))
101
+
102
+ ;; ---------------------------------------------------------------------------
103
+ ;; Predicates
104
+ ;; ---------------------------------------------------------------------------
105
+
106
+ (defn blank?
107
+ "True if s is nil, empty, or contains only whitespace."
108
+ [s]
109
+ (or (nil? s) (not (nil? (re-matches #"\\s*" s)))))
110
+
111
+ (defn starts-with?
112
+ "True if s starts with substr."
113
+ [s substr]
114
+ (str-starts-with* s substr))
115
+
116
+ (defn ends-with?
117
+ "True if s ends with substr."
118
+ [s substr]
119
+ (str-ends-with* s substr))
120
+
121
+ (defn includes?
122
+ "True if s includes substr."
123
+ [s substr]
124
+ (str-includes* s substr))
125
+
126
+ ;; ---------------------------------------------------------------------------
127
+ ;; Search
128
+ ;; ---------------------------------------------------------------------------
129
+
130
+ (defn index-of
131
+ "Return index of value (string) in s, optionally searching forward from
132
+ from-index. Return nil if value not found."
133
+ ([s value] (str-index-of* s value))
134
+ ([s value from-index] (str-index-of* s value from-index)))
135
+
136
+ (defn last-index-of
137
+ "Return last index of value (string) in s, optionally searching backward
138
+ from from-index. Return nil if value not found."
139
+ ([s value] (str-last-index-of* s value))
140
+ ([s value from-index] (str-last-index-of* s value from-index)))
141
+
142
+ ;; ---------------------------------------------------------------------------
143
+ ;; Replacement
144
+ ;; ---------------------------------------------------------------------------
145
+
146
+ (defn replace
147
+ "Replaces all instances of match with replacement in s.
148
+
149
+ match/replacement can be:
150
+ string / string — literal match, literal replacement
151
+ pattern / string — regex match; $1, $2, etc. substituted from groups
152
+ pattern / fn — regex match; fn called with match (string or vector
153
+ of [whole g1 g2 ...]), return value used as replacement.
154
+
155
+ See also replace-first."
156
+ [s match replacement]
157
+ (str-replace* s match replacement))
158
+
159
+ (defn replace-first
160
+ "Replaces the first instance of match with replacement in s.
161
+ Same match/replacement semantics as replace."
162
+ [s match replacement]
163
+ (str-replace-first* s match replacement))
164
+
165
+ (defn re-quote-replacement
166
+ "Given a replacement string that you wish to be a literal replacement for a
167
+ pattern match in replace or replace-first, escape any special replacement
168
+ characters ($ signs) so they are treated literally."
169
+ [s]
170
+ (replace s #"\\$" "$$$$"))
171
+
172
+ ;; ---------------------------------------------------------------------------
173
+ ;; Miscellaneous
174
+ ;; ---------------------------------------------------------------------------
175
+
176
+ (defn reverse
177
+ "Returns s with its characters reversed."
178
+ [s]
179
+ (str-reverse* s))
180
+
181
+ (defn escape
182
+ "Return a new string, using cmap to escape each character ch from s as
183
+ follows: if (cmap ch) is nil, append ch to the new string; otherwise append
184
+ (str (cmap ch)).
185
+
186
+ cmap may be a map or a function. Maps are callable directly (IFn semantics).
187
+
188
+ Note: Clojure uses char literal keys (e.g. {\\\\< \\"&lt;\\"}). This interpreter
189
+ has no char type, so map keys must be single-character strings instead
190
+ (e.g. {\\"<\\" \\"&lt;\\"})."
191
+ [s cmap]
192
+ (apply str (map (fn [c]
193
+ (let [r (cmap c)]
194
+ (if (nil? r) c (str r))))
195
+ (split s #""))))
196
+ `
@@ -0,0 +1,192 @@
1
+ (ns clojure.string)
2
+
3
+ ;; Runtime-injected native helpers. Declared here so clojure-lsp can resolve
4
+ ;; them; the interpreter treats bare (def name) as a no-op and leaves the
5
+ ;; native binding from coreEnv intact.
6
+ (def str-split*)
7
+ (def str-upper-case*)
8
+ (def str-lower-case*)
9
+ (def str-trim*)
10
+ (def str-triml*)
11
+ (def str-trimr*)
12
+ (def str-reverse*)
13
+ (def str-starts-with*)
14
+ (def str-ends-with*)
15
+ (def str-includes*)
16
+ (def str-index-of*)
17
+ (def str-last-index-of*)
18
+ (def str-replace*)
19
+ (def str-replace-first*)
20
+
21
+ ;; ---------------------------------------------------------------------------
22
+ ;; Joining / splitting
23
+ ;; ---------------------------------------------------------------------------
24
+
25
+ (defn join
26
+ "Returns a string of all elements in coll, as returned by (str), separated
27
+ by an optional separator."
28
+ ([coll] (join "" coll))
29
+ ([separator coll]
30
+ (if (nil? coll)
31
+ ""
32
+ (reduce
33
+ (fn [acc x]
34
+ (if (= acc "")
35
+ (str x)
36
+ (str acc separator x)))
37
+ ""
38
+ coll))))
39
+
40
+ (defn split
41
+ "Splits string on a regular expression. Optional limit is the maximum number
42
+ of parts returned. Trailing empty strings are not returned by default; pass
43
+ a limit of -1 to return all."
44
+ ([s sep] (str-split* s sep))
45
+ ([s sep limit] (str-split* s sep limit)))
46
+
47
+ (defn split-lines
48
+ "Splits s on \\n or \\r\\n. Trailing empty lines are not returned."
49
+ [s]
50
+ (split s #"\r?\n"))
51
+
52
+ ;; ---------------------------------------------------------------------------
53
+ ;; Case conversion
54
+ ;; ---------------------------------------------------------------------------
55
+
56
+ (defn upper-case
57
+ "Converts string to all upper-case."
58
+ [s]
59
+ (str-upper-case* s))
60
+
61
+ (defn lower-case
62
+ "Converts string to all lower-case."
63
+ [s]
64
+ (str-lower-case* s))
65
+
66
+ (defn capitalize
67
+ "Converts first character of the string to upper-case, all other
68
+ characters to lower-case."
69
+ [s]
70
+ (if (< (count s) 2)
71
+ (upper-case s)
72
+ (str (upper-case (subs s 0 1)) (lower-case (subs s 1)))))
73
+
74
+ ;; ---------------------------------------------------------------------------
75
+ ;; Trimming
76
+ ;; ---------------------------------------------------------------------------
77
+
78
+ (defn trim
79
+ "Removes whitespace from both ends of string."
80
+ [s]
81
+ (str-trim* s))
82
+
83
+ (defn triml
84
+ "Removes whitespace from the left side of string."
85
+ [s]
86
+ (str-triml* s))
87
+
88
+ (defn trimr
89
+ "Removes whitespace from the right side of string."
90
+ [s]
91
+ (str-trimr* s))
92
+
93
+ (defn trim-newline
94
+ "Removes all trailing newline \\n or return \\r characters from string.
95
+ Similar to Perl's chomp."
96
+ [s]
97
+ (replace s #"[\r\n]+$" ""))
98
+
99
+ ;; ---------------------------------------------------------------------------
100
+ ;; Predicates
101
+ ;; ---------------------------------------------------------------------------
102
+
103
+ (defn blank?
104
+ "True if s is nil, empty, or contains only whitespace."
105
+ [s]
106
+ (or (nil? s) (not (nil? (re-matches #"\s*" s)))))
107
+
108
+ (defn starts-with?
109
+ "True if s starts with substr."
110
+ [s substr]
111
+ (str-starts-with* s substr))
112
+
113
+ (defn ends-with?
114
+ "True if s ends with substr."
115
+ [s substr]
116
+ (str-ends-with* s substr))
117
+
118
+ (defn includes?
119
+ "True if s includes substr."
120
+ [s substr]
121
+ (str-includes* s substr))
122
+
123
+ ;; ---------------------------------------------------------------------------
124
+ ;; Search
125
+ ;; ---------------------------------------------------------------------------
126
+
127
+ (defn index-of
128
+ "Return index of value (string) in s, optionally searching forward from
129
+ from-index. Return nil if value not found."
130
+ ([s value] (str-index-of* s value))
131
+ ([s value from-index] (str-index-of* s value from-index)))
132
+
133
+ (defn last-index-of
134
+ "Return last index of value (string) in s, optionally searching backward
135
+ from from-index. Return nil if value not found."
136
+ ([s value] (str-last-index-of* s value))
137
+ ([s value from-index] (str-last-index-of* s value from-index)))
138
+
139
+ ;; ---------------------------------------------------------------------------
140
+ ;; Replacement
141
+ ;; ---------------------------------------------------------------------------
142
+
143
+ (defn replace
144
+ "Replaces all instances of match with replacement in s.
145
+
146
+ match/replacement can be:
147
+ string / string — literal match, literal replacement
148
+ pattern / string — regex match; $1, $2, etc. substituted from groups
149
+ pattern / fn — regex match; fn called with match (string or vector
150
+ of [whole g1 g2 ...]), return value used as replacement.
151
+
152
+ See also replace-first."
153
+ [s match replacement]
154
+ (str-replace* s match replacement))
155
+
156
+ (defn replace-first
157
+ "Replaces the first instance of match with replacement in s.
158
+ Same match/replacement semantics as replace."
159
+ [s match replacement]
160
+ (str-replace-first* s match replacement))
161
+
162
+ (defn re-quote-replacement
163
+ "Given a replacement string that you wish to be a literal replacement for a
164
+ pattern match in replace or replace-first, escape any special replacement
165
+ characters ($ signs) so they are treated literally."
166
+ [s]
167
+ (replace s #"\$" "$$$$"))
168
+
169
+ ;; ---------------------------------------------------------------------------
170
+ ;; Miscellaneous
171
+ ;; ---------------------------------------------------------------------------
172
+
173
+ (defn reverse
174
+ "Returns s with its characters reversed."
175
+ [s]
176
+ (str-reverse* s))
177
+
178
+ (defn escape
179
+ "Return a new string, using cmap to escape each character ch from s as
180
+ follows: if (cmap ch) is nil, append ch to the new string; otherwise append
181
+ (str (cmap ch)).
182
+
183
+ cmap may be a map or a function. Maps are callable directly (IFn semantics).
184
+
185
+ Note: Clojure uses char literal keys (e.g. {\\< \"&lt;\"}). This interpreter
186
+ has no char type, so map keys must be single-character strings instead
187
+ (e.g. {\"<\" \"&lt;\"})."
188
+ [s cmap]
189
+ (apply str (map (fn [c]
190
+ (let [r (cmap c)]
191
+ (if (nil? r) c (str r))))
192
+ (split s #""))))
@@ -0,0 +1,25 @@
1
+ export function join(coll: unknown): unknown;
2
+ export function join(separator: unknown, coll: unknown): unknown;
3
+ export function split(s: unknown, sep: unknown): unknown;
4
+ export function split(s: unknown, sep: unknown, limit: unknown): unknown;
5
+ export function split_lines(s: unknown): unknown;
6
+ export function upper_case(s: unknown): unknown;
7
+ export function lower_case(s: unknown): unknown;
8
+ export function capitalize(s: unknown): unknown;
9
+ export function trim(s: unknown): unknown;
10
+ export function triml(s: unknown): unknown;
11
+ export function trimr(s: unknown): unknown;
12
+ export function trim_newline(s: unknown): unknown;
13
+ export function blank_QMARK_(s: unknown): unknown;
14
+ export function starts_with_QMARK_(s: unknown, substr: unknown): unknown;
15
+ export function ends_with_QMARK_(s: unknown, substr: unknown): unknown;
16
+ export function includes_QMARK_(s: unknown, substr: unknown): unknown;
17
+ export function index_of(s: unknown, value: unknown): unknown;
18
+ export function index_of(s: unknown, value: unknown, from_index: unknown): unknown;
19
+ export function last_index_of(s: unknown, value: unknown): unknown;
20
+ export function last_index_of(s: unknown, value: unknown, from_index: unknown): unknown;
21
+ export function replace(s: unknown, match: unknown, replacement: unknown): unknown;
22
+ export function replace_first(s: unknown, match: unknown, replacement: unknown): unknown;
23
+ export function re_quote_replacement(s: unknown): unknown;
24
+ export function reverse(s: unknown): unknown;
25
+ export function escape(s: unknown, cmap: unknown): unknown;
@@ -0,0 +1,134 @@
1
+ import {
2
+ valueKeywords,
3
+ type CljAtom,
4
+ type CljBoolean,
5
+ type CljFunction,
6
+ type CljKeyword,
7
+ type CljList,
8
+ type CljMacro,
9
+ type CljMap,
10
+ type CljMultiMethod,
11
+ type CljNativeFunction,
12
+ type CljNumber,
13
+ type CljReduced,
14
+ type CljRegex,
15
+ type CljString,
16
+ type CljSymbol,
17
+ type CljValue,
18
+ type CljVector,
19
+ type CljVolatile,
20
+ } from './types.ts'
21
+ import { specialFormKeywords } from './evaluator/special-forms.ts'
22
+
23
+ export const isNil = (value: CljValue): boolean => value.kind === 'nil'
24
+ export const isFalsy = (value: CljValue): boolean => {
25
+ if (value.kind === 'nil') return true
26
+ if (value.kind === 'boolean') return !value.value
27
+ return false
28
+ }
29
+ export const isTruthy = (value: CljValue): boolean => {
30
+ return !isFalsy(value)
31
+ }
32
+ export const isSpecialForm = (
33
+ value: CljValue
34
+ ): value is CljSymbol & { name: keyof typeof specialFormKeywords } =>
35
+ value.kind === 'symbol' && value.name in specialFormKeywords
36
+ export const isSymbol = (value: CljValue): value is CljSymbol =>
37
+ value.kind === 'symbol'
38
+ export const isVector = (value: CljValue): value is CljVector =>
39
+ value.kind === 'vector'
40
+ export const isList = (value: CljValue): value is CljList =>
41
+ value.kind === 'list'
42
+ export const isFunction = (value: CljValue): value is CljFunction =>
43
+ value.kind === 'function'
44
+ export const isNativeFunction = (value: CljValue): value is CljNativeFunction =>
45
+ value.kind === 'native-function'
46
+ export const isMacro = (value: CljValue): value is CljMacro =>
47
+ value.kind === 'macro'
48
+ export const isMap = (value: CljValue): value is CljMap => value.kind === 'map'
49
+ export const isKeyword = (value: CljValue): value is CljKeyword =>
50
+ value.kind === 'keyword'
51
+ export const isAFunction = (
52
+ value: CljValue
53
+ ): value is CljFunction | CljNativeFunction =>
54
+ isFunction(value) || isNativeFunction(value)
55
+
56
+ /** True for any value that can be invoked like a function (IFn). */
57
+ export const isCallable = (value: CljValue): boolean =>
58
+ isAFunction(value) || isKeyword(value) || isMap(value)
59
+ export const isMultiMethod = (value: CljValue): value is CljMultiMethod =>
60
+ value.kind === 'multi-method'
61
+ export const isAtom = (value: CljValue): value is CljAtom =>
62
+ value.kind === 'atom'
63
+ export const isReduced = (value: CljValue): value is CljReduced =>
64
+ value.kind === 'reduced'
65
+ export const isVolatile = (value: CljValue): value is CljVolatile =>
66
+ value.kind === 'volatile'
67
+ export const isRegex = (value: CljValue): value is CljRegex =>
68
+ value.kind === 'regex'
69
+ export const isCollection = (
70
+ value: CljValue
71
+ ): value is CljList | CljVector | CljMap =>
72
+ isVector(value) || isMap(value) || isList(value)
73
+
74
+ export const isSeqable = (
75
+ value: CljValue
76
+ ): value is CljList | CljVector | CljMap | CljString =>
77
+ isCollection(value) || value.kind === 'string'
78
+
79
+ export const isCljValue = (value: any): value is CljValue => {
80
+ return (
81
+ typeof value === 'object' &&
82
+ value !== null &&
83
+ 'kind' in value &&
84
+ value.kind in valueKeywords
85
+ )
86
+ }
87
+
88
+ const equalityHandlers = {
89
+ [valueKeywords.number]: (a: CljNumber, b: CljNumber) => a.value === b.value,
90
+ [valueKeywords.string]: (a: CljString, b: CljString) => a.value === b.value,
91
+ [valueKeywords.boolean]: (a: CljBoolean, b: CljBoolean) =>
92
+ a.value === b.value,
93
+ [valueKeywords.nil]: () => true,
94
+ [valueKeywords.symbol]: (a: CljSymbol, b: CljSymbol) => a.name === b.name,
95
+ [valueKeywords.keyword]: (a: CljKeyword, b: CljKeyword) => a.name === b.name,
96
+ [valueKeywords.vector]: (a: CljVector, b: CljVector) => {
97
+ if (a.value.length !== b.value.length) return false
98
+ return a.value.every((value, index) => isEqual(value, b.value[index]))
99
+ },
100
+ [valueKeywords.map]: (a: CljMap, b: CljMap) => {
101
+ if (a.entries.length !== b.entries.length) return false
102
+ const uniqueKeys = new Set([
103
+ ...a.entries.map(([key]) => key),
104
+ ...b.entries.map(([key]) => key),
105
+ ])
106
+ for (const key of uniqueKeys) {
107
+ const aEntry = a.entries.find(([k]) => isEqual(k, key))
108
+ if (!aEntry) return false
109
+ const bEntry = b.entries.find(([k]) => isEqual(k, key))
110
+ if (!bEntry) return false
111
+ if (!isEqual(aEntry[1], bEntry[1])) return false
112
+ }
113
+ return true
114
+ },
115
+ [valueKeywords.list]: (a: CljList, b: CljList) => {
116
+ if (a.value.length !== b.value.length) return false
117
+ return a.value.every((value, index) => isEqual(value, b.value[index]))
118
+ },
119
+ [valueKeywords.atom]: (a: CljAtom, b: CljAtom) => a === b,
120
+ [valueKeywords.reduced]: (a: CljReduced, b: CljReduced) =>
121
+ isEqual(a.value, b.value),
122
+ [valueKeywords.volatile]: (a: CljVolatile, b: CljVolatile) => a === b,
123
+ // Regex uses reference equality matching Clojure Pattern semantics:
124
+ // (= #"foo" #"foo") => false — each literal is a distinct object
125
+ [valueKeywords.regex]: (a: CljRegex, b: CljRegex) => a === b,
126
+ }
127
+
128
+ export const isEqual = (a: CljValue, b: CljValue): boolean => {
129
+ if (a.kind !== b.kind) return false
130
+
131
+ const handler = equalityHandlers[a.kind as keyof typeof equalityHandlers]
132
+ if (!handler) return false
133
+ return handler(a as never, b as never)
134
+ }
@@ -0,0 +1,108 @@
1
+ import { isCljValue } from './assertions'
2
+ import { applyFunction } from './evaluator'
3
+ import {
4
+ cljBoolean,
5
+ cljKeyword,
6
+ cljMap,
7
+ cljNativeFunction,
8
+ cljNil,
9
+ cljNumber,
10
+ cljString,
11
+ cljVector,
12
+ } from './factories'
13
+ import type { CljValue } from './types'
14
+
15
+ export class ConversionError extends Error {
16
+ context: unknown
17
+ constructor(message: string, context?: unknown) {
18
+ super(message)
19
+ this.name = 'ConversionError'
20
+ this.context = context
21
+ }
22
+ }
23
+
24
+ const richKeyKinds = new Set(['list', 'vector', 'map'])
25
+
26
+ export function cljToJs(value: CljValue): unknown {
27
+ switch (value.kind) {
28
+ case 'number':
29
+ return value.value
30
+ case 'string':
31
+ return value.value
32
+ case 'boolean':
33
+ return value.value
34
+ case 'nil':
35
+ return null
36
+ case 'keyword':
37
+ return value.name.startsWith(':') ? value.name.slice(1) : value.name
38
+ case 'symbol':
39
+ return value.name
40
+ case 'list':
41
+ case 'vector':
42
+ return value.value.map(cljToJs)
43
+ case 'map': {
44
+ const obj: Record<string, unknown> = {}
45
+ for (const [k, v] of value.entries) {
46
+ if (richKeyKinds.has(k.kind)) {
47
+ throw new ConversionError(
48
+ `Rich key types (${k.kind}) are not supported in JS object conversion. Restructure your map to use string, keyword, or number keys.`,
49
+ { key: k, value: v }
50
+ )
51
+ }
52
+ const jsKey = String(cljToJs(k))
53
+ obj[jsKey] = cljToJs(v)
54
+ }
55
+ return obj
56
+ }
57
+ case 'function':
58
+ case 'native-function': {
59
+ const fn = value
60
+ return (...jsArgs: unknown[]) => {
61
+ const cljArgs = jsArgs.map(jsToClj)
62
+ const result = applyFunction(fn, cljArgs)
63
+ return cljToJs(result)
64
+ }
65
+ }
66
+ case 'macro':
67
+ throw new ConversionError(
68
+ 'Macros cannot be exported to JavaScript. Macros are compile-time constructs.',
69
+ { macro: value }
70
+ )
71
+ }
72
+ }
73
+
74
+ export function jsToClj(value: unknown): CljValue {
75
+ if (value === null || value === undefined) return cljNil()
76
+ if (isCljValue(value)) return value
77
+
78
+ switch (typeof value) {
79
+ case 'number':
80
+ return cljNumber(value)
81
+ case 'string':
82
+ return cljString(value)
83
+ case 'boolean':
84
+ return cljBoolean(value)
85
+ case 'function': {
86
+ const jsFn = value as (...args: unknown[]) => unknown
87
+ return cljNativeFunction('js-fn', (...cljArgs: CljValue[]) => {
88
+ const jsArgs = cljArgs.map(cljToJs)
89
+ const result = jsFn(...jsArgs)
90
+ return jsToClj(result)
91
+ })
92
+ }
93
+ case 'object': {
94
+ if (Array.isArray(value)) {
95
+ return cljVector(value.map(jsToClj))
96
+ }
97
+ const entries: [CljValue, CljValue][] = Object.entries(
98
+ value as Record<string, unknown>
99
+ ).map(([k, v]) => [cljKeyword(`:${k}`), jsToClj(v)])
100
+ return cljMap(entries)
101
+ }
102
+ default:
103
+ throw new ConversionError(
104
+ `Cannot convert JS value of type ${typeof value} to CljValue`,
105
+ { value }
106
+ )
107
+ }
108
+ }