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,215 @@
1
+ (ns user.hof
2
+ (:require [clojure.string :as str]))
3
+
4
+ ;; Deep Dive: Higher-Order Functions & Transducers
5
+ ;;
6
+ ;; Press ⌘+Enter on any form to evaluate it.
7
+
8
+
9
+ ;; map
10
+
11
+ (comment
12
+ ;; Basic: apply f to every element
13
+ (map inc [1 2 3 4 5]) ;; => (2 3 4 5 6)
14
+ (map str [:a :b :c]) ;; => ("a" "b" "c")
15
+ (map count ["hi" "hello" "hey"]) ;; => (2 5 3)
16
+
17
+ (map (fn [x] (* x x)) (range 1 6)) ;; => (1 4 9 16 25)
18
+ (map #(* % %) (range 1 6)) ;; same, shorter syntax
19
+
20
+ ;; Multi-collection: zips and stops at the shortest
21
+ (map + [1 2 3] [10 20 30]) ;; => (11 22 33)
22
+ (map vector [:a :b :c] [1 2 3]) ;; => ([:a 1] [:b 2] [:c 3])
23
+ (map + [1 2 3] [10 20 30] [100 200 300]) ;; => (111 222 333)
24
+
25
+ ;; map-indexed: f receives [index value]
26
+ (map-indexed vector [:a :b :c]) ;; => ([0 :a] [1 :b] [2 :c])
27
+ (map-indexed (fn [i v] (str i ": " v))
28
+ ["alice" "bob" "carol"]) ;; => ("0: alice" "1: bob" "2: carol")
29
+ )
30
+
31
+
32
+ ;; filter / remove
33
+
34
+ (comment
35
+ (filter even? [1 2 3 4 5 6]) ;; => (2 4 6)
36
+ (filter string? [1 "a" :b "c" 2]) ;; => ("a" "c")
37
+ (filter :active [{:name "a" :active true}
38
+ {:name "b" :active false}
39
+ {:name "c" :active true}])
40
+ ;; => ({:name "a" :active true} {:name "c" :active true})
41
+
42
+ (remove even? [1 2 3 4 5 6]) ;; => (1 3 5)
43
+ (remove nil? [1 nil 2 nil 3]) ;; => (1 2 3)
44
+
45
+ (filter #(> (count %) 3) ["hi" "hello" "hey" "howdy"])
46
+ ;; => ("hello" "howdy")
47
+ )
48
+
49
+
50
+ ;; reduce
51
+ ;;
52
+ ;; The Swiss army knife — it can implement almost everything else.
53
+
54
+ (comment
55
+ ;; Two-arity: uses first two elements to start
56
+ (reduce + [1 2 3 4 5]) ;; => 15
57
+ (reduce * [1 2 3 4 5]) ;; => 120
58
+ (reduce str ["a" "b" "c"]) ;; => "abc"
59
+
60
+ ;; Three-arity: explicit initial accumulator
61
+ (reduce + 100 [1 2 3]) ;; => 106
62
+ (reduce conj [] '(1 2 3)) ;; => [1 2 3]
63
+ (reduce (fn [m [k v]] (assoc m k v))
64
+ {}
65
+ [[:a 1] [:b 2] [:c 3]]) ;; => {:a 1 :b 2 :c 3}
66
+
67
+ ;; Building a frequency map from scratch
68
+ (reduce (fn [acc x]
69
+ (update acc x (fnil inc 0)))
70
+ {}
71
+ [:a :b :a :c :b :a]) ;; => {:a 3 :b 2 :c 1}
72
+
73
+ ;; Early termination with `reduced` — wraps a value to signal "stop now"
74
+ (reduce (fn [acc x]
75
+ (if (nil? x)
76
+ (reduced acc)
77
+ (conj acc x)))
78
+ []
79
+ [1 2 3 nil 4 5]) ;; => [1 2 3] (stopped at nil)
80
+
81
+ (reduce (fn [_ x]
82
+ (when (> x 100) (reduced x)))
83
+ nil
84
+ (range 1000)) ;; => 101
85
+ )
86
+
87
+
88
+ ;; apply, partial, comp
89
+
90
+ (comment
91
+ ;; apply — call f with a collection as its argument list
92
+ (apply + [1 2 3 4]) ;; => 10
93
+ (apply str ["a" "b" "c"]) ;; => "abc"
94
+ (apply max [3 1 4 1 5 9 2 6]) ;; => 9
95
+
96
+ ;; Leading fixed args before the collection
97
+ (apply str "prefix-" ["a" "b"]) ;; => "prefix-ab"
98
+
99
+ ;; partial — fix some leading arguments
100
+ (def add10 (partial + 10))
101
+ (add10 5) ;; => 15
102
+ (map add10 [1 2 3]) ;; => (11 12 13)
103
+
104
+ (def greet (partial str "Hello, "))
105
+ (greet "World!") ;; => "Hello, World!"
106
+
107
+ ;; comp — compose right-to-left
108
+ (def clean (comp str/trim str/lower-case))
109
+ (clean " HELLO ") ;; => "hello"
110
+
111
+ ((comp inc inc inc) 0) ;; => 3
112
+ ((comp str/upper-case str/trim) " hello ") ;; => "HELLO"
113
+
114
+ ;; identity — returns its argument unchanged
115
+ (filter identity [1 nil 2 false 3]) ;; => (1 2 3)
116
+
117
+ ;; constantly — returns a function that always returns the same value
118
+ ((constantly 42) 1 2 3) ;; => 42
119
+ (map (constantly :x) [1 2 3]) ;; => (:x :x :x)
120
+ )
121
+
122
+
123
+ ;; complement, juxt, some, every?
124
+
125
+ (comment
126
+ ;; complement — logical NOT of a predicate
127
+ (def not-even? (complement even?))
128
+ (filter not-even? [1 2 3 4 5]) ;; => (1 3 5)
129
+
130
+ ;; juxt — call multiple functions on the same value, collect results
131
+ ((juxt :name :role) {:name "Alice" :role :admin}) ;; => ["Alice" :admin]
132
+ (map (juxt identity #(* % %)) [1 2 3 4 5])
133
+ ;; => ([1 1] [2 4] [3 9] [4 16] [5 25])
134
+
135
+ ;; some — return first truthy result of (f x), or nil
136
+ (some even? [1 3 5 6 7]) ;; => true
137
+ (some even? [1 3 5 7]) ;; => nil
138
+ (some #(when (> % 3) %) [1 2 3 4 5]) ;; => 4
139
+
140
+ ;; every? — true if (f x) is truthy for all elements
141
+ (every? even? [2 4 6]) ;; => true
142
+ (every? even? [2 4 5]) ;; => false
143
+
144
+ (not-any? odd? [2 4 6]) ;; => true
145
+ (not-every? odd? [1 2 3]) ;; => true
146
+ )
147
+
148
+
149
+ ;; sort, sort-by, group-by, frequencies
150
+
151
+ (def people
152
+ [{:name "Carol" :age 32 :dept :eng}
153
+ {:name "Alice" :age 28 :dept :design}
154
+ {:name "Bob" :age 35 :dept :eng}
155
+ {:name "Dave" :age 28 :dept :design}])
156
+
157
+ (comment
158
+ (sort [3 1 4 1 5 9 2 6]) ;; => (1 1 2 3 4 5 6 9)
159
+ (sort > [3 1 4 1 5 9 2 6]) ;; => (9 6 5 4 3 2 1 1)
160
+ (sort ["banana" "apple" "cherry"]) ;; => ("apple" "banana" "cherry")
161
+
162
+ (sort-by :age people) ;; youngest first
163
+ (sort-by :name people) ;; alphabetical
164
+
165
+ (group-by :dept people) ;; => {:eng [...] :design [...]}
166
+ (group-by :age people) ;; groups by age
167
+
168
+ (frequencies [:a :b :a :c :b :a]) ;; => {:a 3 :b 2 :c 1}
169
+ (distinct [1 2 3 1 2 4]) ;; => (1 2 3 4)
170
+ )
171
+
172
+
173
+ ;; Transducers
174
+ ;;
175
+ ;; Composable transformation pipelines decoupled from the source and sink.
176
+ ;; A 1-arg call to map/filter/etc returns a transducer instead of a result.
177
+ ;; Transducer `comp` applies LEFT-to-RIGHT (unlike function comp).
178
+
179
+ (comment
180
+ ;; `into` with a transducer
181
+ (into [] (map inc) [1 2 3 4 5]) ;; => [2 3 4 5 6]
182
+ (into [] (filter even?) [1 2 3 4 5 6]) ;; => [2 4 6]
183
+
184
+ ;; Chain with comp — one pass, no intermediate sequences
185
+ (into []
186
+ (comp (filter odd?)
187
+ (map #(* % %)))
188
+ [1 2 3 4 5 6 7])
189
+ ;; => [1 9 25 49] (squares of odd numbers)
190
+
191
+ ;; `transduce` — apply a transducer with reduce semantics
192
+ (transduce (comp (filter odd?)
193
+ (map #(* % %)))
194
+ +
195
+ [1 2 3 4 5 6 7])
196
+ ;; => 84 (sum of squares of odds)
197
+
198
+ ;; `sequence` — lazy sequence from a transducer
199
+ (sequence (comp (filter even?)
200
+ (map #(/ % 2)))
201
+ (range 1 11))
202
+ ;; => (1 2 3 4 5)
203
+
204
+ ;; partition-all — group into chunks
205
+ (into [] (partition-all 3) (range 10))
206
+ ;; => [[0 1 2] [3 4 5] [6 7 8] [9]]
207
+
208
+ ;; dedupe — remove consecutive duplicates
209
+ (into [] (dedupe) [1 1 2 3 3 3 4 1])
210
+ ;; => [1 2 3 4 1]
211
+
212
+ ;; take as a transducer — stops early, never touches the rest
213
+ (into [] (take 3) (range 1000))
214
+ ;; => [0 1 2]
215
+ )
@@ -0,0 +1,194 @@
1
+ (ns user.destructuring)
2
+
3
+ ;; Deep Dive: Destructuring
4
+ ;;
5
+ ;; Bind names to parts of a data structure in one step.
6
+ ;; Works in `let`, `fn` params, `defn` params, `loop`, and `defmacro`.
7
+ ;;
8
+ ;; Press ⌘+Enter on any form to evaluate it.
9
+
10
+
11
+ ;; Vector (Sequential) Destructuring
12
+ ;;
13
+ ;; Bind names to positions, left to right.
14
+
15
+ (comment
16
+ (let [[a b c] [10 20 30]]
17
+ (+ a b c)) ;; => 60
18
+
19
+ ;; Skip positions with _
20
+ (let [[_ second _ fourth] [1 2 3 4]]
21
+ [second fourth]) ;; => [2 4]
22
+
23
+ ;; Fewer bindings than elements — extras are ignored
24
+ (let [[a b] [1 2 3 4 5]]
25
+ [a b]) ;; => [1 2]
26
+
27
+ ;; & rest — bind remaining elements as a sequence
28
+ (let [[first-item & the-rest] [1 2 3 4 5]]
29
+ {:first first-item
30
+ :rest the-rest}) ;; => {:first 1 :rest (2 3 4 5)}
31
+
32
+ ;; :as — bind the whole collection in addition to parts
33
+ (let [[x y :as all] [1 2 3]]
34
+ {:x x :y y :all all}) ;; => {:x 1 :y 2 :all [1 2 3]}
35
+
36
+ ;; Nested vectors
37
+ (let [[a [b c] d] [1 [2 3] 4]]
38
+ [a b c d]) ;; => [1 2 3 4]
39
+ )
40
+
41
+
42
+ ;; Map Destructuring
43
+ ;;
44
+ ;; Bind names to values by key.
45
+
46
+ (comment
47
+ ;; Basic: bind local name to the value at a key
48
+ (let [{n :name a :age} {:name "Alice" :age 30 :role :admin}]
49
+ (str n " is " a)) ;; => "Alice is 30"
50
+
51
+ ;; :keys — shorthand when local name == keyword name
52
+ (let [{:keys [name age role]} {:name "Alice" :age 30 :role :admin}]
53
+ [name age role]) ;; => ["Alice" 30 :admin]
54
+
55
+ ;; :strs — like :keys but for string keys
56
+ (let [{:strs [name age]} {"name" "Bob" "age" 25}]
57
+ [name age]) ;; => ["Bob" 25]
58
+
59
+ ;; :as — bind the whole map too
60
+ (let [{:keys [name] :as person} {:name "Carol" :age 32}]
61
+ {:greeting (str "Hello " name)
62
+ :full person})
63
+
64
+ ;; :or — default values when key is absent (NOT when value is nil)
65
+ (let [{:keys [name role] :or {role :guest}} {:name "Dave"}]
66
+ [name role]) ;; => ["Dave" :guest] (:role was absent)
67
+
68
+ ;; :or does NOT apply when the key IS present but value is nil
69
+ (let [{:keys [role] :or {role :guest}} {:role nil}]
70
+ role) ;; => nil (key exists, :or doesn't fire)
71
+ )
72
+
73
+
74
+ ;; Destructuring in Function Params
75
+
76
+ ;; Vector destructuring in fn params
77
+ (defn sum-pair [[a b]]
78
+ (+ a b))
79
+
80
+ ;; Map destructuring in fn params
81
+ (defn greet-user [{:keys [name role] :or {role :guest}}]
82
+ (str "Hello " name " (" (clojure.core/name role) ")"))
83
+
84
+ ;; Multi-arg with map destructuring
85
+ (defn move [{:keys [x y]} {:keys [dx dy]}]
86
+ {:x (+ x dx) :y (+ y dy)})
87
+
88
+ (comment
89
+ (sum-pair [3 7]) ;; => 10
90
+ (greet-user {:name "Alice" :role :admin}) ;; => "Hello Alice (admin)"
91
+ (greet-user {:name "Bob"}) ;; => "Hello Bob (guest)"
92
+ (move {:x 0 :y 0} {:dx 3 :dy 5}) ;; => {:x 3 :y 5}
93
+ )
94
+
95
+
96
+ ;; Nested Destructuring
97
+
98
+ (comment
99
+ ;; Map inside vector
100
+ (let [[{:keys [name]} {:keys [score]}]
101
+ [{:name "Alice"} {:score 95}]]
102
+ (str name ": " score)) ;; => "Alice: 95"
103
+
104
+ ;; Vector inside map
105
+ (let [{:keys [name]
106
+ [first-score] :scores}
107
+ {:name "Bob" :scores [87 90 95]}]
108
+ (str name " first: " first-score)) ;; => "Bob first: 87"
109
+
110
+ ;; Deeply nested — a realistic API response shape
111
+ (def response
112
+ {:status 200
113
+ :body {:user {:id 42
114
+ :name "Alice"
115
+ :tags ["admin" "beta"]}}})
116
+
117
+ (let [{:keys [status]
118
+ {:keys [user]} :body} response
119
+ {:keys [id name]
120
+ [first-tag] :tags} user]
121
+ {:status status :id id :name name :first-tag first-tag})
122
+ ;; => {:status 200 :id 42 :name "Alice" :first-tag "admin"}
123
+ )
124
+
125
+
126
+ ;; Destructuring in loop/recur
127
+
128
+ (comment
129
+ (loop [[x & xs] [1 2 3 4 5]
130
+ acc 0]
131
+ (if x
132
+ (recur xs (+ acc x))
133
+ acc)) ;; => 15
134
+
135
+ (loop [{:keys [n acc]} {:n 5 :acc 1}]
136
+ (if (zero? n)
137
+ acc
138
+ (recur {:n (dec n) :acc (* acc n)})))
139
+ ;; => 120 (5!)
140
+ )
141
+
142
+
143
+ ;; Kwargs Destructuring (& {:keys})
144
+ ;;
145
+ ;; `& rest` where rest is treated as a flat key/value sequence.
146
+
147
+ (defn configure [& {:keys [host port timeout]
148
+ :or {host "localhost"
149
+ port 8080
150
+ timeout 5000}}]
151
+ {:host host :port port :timeout timeout})
152
+
153
+ (comment
154
+ (configure) ;; all defaults
155
+ (configure :port 3000)
156
+ (configure :host "prod.example.com" :port 443 :timeout 30000)
157
+ )
158
+
159
+
160
+ ;; Qualified :keys
161
+ ;;
162
+ ;; When map keys are namespaced keywords, the local name is the unqualified part.
163
+
164
+ (comment
165
+ (let [{:keys [user/name user/role]}
166
+ {:user/name "Alice" :user/role :admin}]
167
+ [name role]) ;; => ["Alice" :admin]
168
+ )
169
+
170
+
171
+ ;; Practical Patterns
172
+
173
+ (defn summarize [{:keys [name scores]}]
174
+ {:name name
175
+ :average (/ (reduce + scores) (count scores))
176
+ :best (apply max scores)})
177
+
178
+ (def students
179
+ [{:name "Alice" :scores [88 92 95]}
180
+ {:name "Bob" :scores [75 80 78]}
181
+ {:name "Carol" :scores [95 98 100]}])
182
+
183
+ (comment
184
+ (map summarize students)
185
+
186
+ (->> students
187
+ (map summarize)
188
+ (sort-by :average >)
189
+ (map :name)) ;; => ("Carol" "Alice" "Bob")
190
+
191
+ (let [{:keys [scores]} (first students)
192
+ [best & _] (sort > scores)]
193
+ best) ;; => 95
194
+ )
@@ -0,0 +1,202 @@
1
+ (ns user.strings-regex
2
+ (:require [clojure.string :as str]))
3
+
4
+ ;; Deep Dive: Strings & Regex
5
+ ;;
6
+ ;; Press ⌘+Enter on any form to evaluate it.
7
+
8
+
9
+ ;; Building & Inspecting Strings
10
+
11
+ (comment
12
+ ;; str — concatenate anything into a string
13
+ (str "hello" " " "world") ;; => "hello world"
14
+ (str :keyword) ;; => ":keyword"
15
+ (str 42) ;; => "42"
16
+ (str nil) ;; => "" (nil becomes empty string)
17
+ (str true false) ;; => "truefalse"
18
+
19
+ (subs "hello world" 6) ;; => "world"
20
+ (subs "hello world" 0 5) ;; => "hello"
21
+
22
+ (count "hello") ;; => 5
23
+ (count "") ;; => 0
24
+
25
+ (string? "hello") ;; => true
26
+ (string? :not-a-string) ;; => false
27
+ )
28
+
29
+
30
+ ;; clojure.string (required as str)
31
+
32
+ (comment
33
+ ;; Case
34
+ (str/upper-case "hello") ;; => "HELLO"
35
+ (str/lower-case "WORLD") ;; => "world"
36
+ (str/capitalize "hello world") ;; => "Hello world"
37
+
38
+ ;; Trimming whitespace
39
+ (str/trim " hello ") ;; => "hello"
40
+ (str/triml " hello ") ;; => "hello " (left only)
41
+ (str/trimr " hello ") ;; => " hello" (right only)
42
+ (str/trim-newline "hello\n") ;; => "hello"
43
+
44
+ ;; Joining
45
+ (str/join ", " ["one" "two" "three"]) ;; => "one, two, three"
46
+ (str/join ["a" "b" "c"]) ;; => "abc"
47
+
48
+ ;; Splitting
49
+ (str/split "a,b,c,d" #",") ;; => ["a" "b" "c" "d"]
50
+ (str/split "hello world" #"\s+") ;; => ["hello" "world"]
51
+ (str/split-lines "one\ntwo\nthree") ;; => ["one" "two" "three"]
52
+
53
+ ;; Search predicates
54
+ (str/includes? "hello world" "world") ;; => true
55
+ (str/starts-with? "hello world" "hello") ;; => true
56
+ (str/ends-with? "hello world" "world") ;; => true
57
+ (str/blank? " ") ;; => true
58
+ (str/blank? " x ") ;; => false
59
+
60
+ (str/index-of "hello world" "world") ;; => 6
61
+ (str/last-index-of "abcabc" "b") ;; => 4
62
+
63
+ (str/reverse "hello") ;; => "olleh"
64
+ )
65
+
66
+
67
+ ;; Replace
68
+
69
+ (comment
70
+ ;; Literal match
71
+ (str/replace "hello world" "world" "Clojure") ;; => "hello Clojure"
72
+
73
+ ;; Regex — all matches
74
+ (str/replace "hello world" #"[aeiou]" "*") ;; => "h*ll* w*rld"
75
+
76
+ ;; Regex + function — receives match vector, returns replacement string
77
+ (str/replace "hello world"
78
+ #"\b\w"
79
+ (fn [[match]] (str/upper-case match)))
80
+ ;; => "Hello World"
81
+
82
+ ;; replace-first — only the first occurrence
83
+ (str/replace-first "aabbaabb" "b" "X") ;; => "aaXbaabb"
84
+ (str/replace-first "hello" #"[aeiou]" "*") ;; => "h*llo"
85
+
86
+ ;; escape — apply a substitution map to every character
87
+ (str/escape "hello & <world>" {\& "&amp;" \< "&lt;" \> "&gt;"})
88
+ ;; => "hello &amp; &lt;world&gt;"
89
+ )
90
+
91
+
92
+ ;; Strings as Sequences
93
+ ;;
94
+ ;; Strings are seqable — all sequence functions work on them.
95
+
96
+ (comment
97
+ (seq "hello") ;; => ("h" "e" "l" "l" "o")
98
+
99
+ (first "hello") ;; => "h"
100
+ (rest "hello") ;; => ("e" "l" "l" "o")
101
+ (last "hello") ;; => "o"
102
+
103
+ (count "hello") ;; => 5
104
+
105
+ (map str/upper-case (seq "hello")) ;; => ("H" "E" "L" "L" "O")
106
+
107
+ ;; Set literals not supported yet — use an explicit membership check:
108
+ (filter (fn [c] (some #(= c %) ["a" "e" "i" "o" "u"])) (seq "hello world"))
109
+ ;; => ("e" "o" "o") (vowels only)
110
+
111
+ ;; Rebuild a string after seq manipulation
112
+ (apply str (filter (fn [c] (some #(= c %) ["a" "e" "i" "o" "u"])) (seq "hello world")))
113
+ ;; => "eoo"
114
+
115
+ (count "café") ;; => 4 (not byte count)
116
+ (seq "café") ;; => ("c" "a" "f" "é")
117
+ )
118
+
119
+
120
+ ;; Regex Literals
121
+ ;;
122
+ ;; Patterns follow JavaScript regex rules.
123
+
124
+ (comment
125
+ #"[0-9]+" ;; => #"[0-9]+"
126
+
127
+ ;; re-find — first match (string if no groups, vector if groups)
128
+ (re-find #"\d+" "abc123def456") ;; => "123"
129
+ (re-find #"(\w+)@(\w+)" "me@example.com")
130
+ ;; => ["me@example.com" "me" "example"] (full match + groups)
131
+
132
+ ;; re-matches — match against the ENTIRE string
133
+ (re-matches #"\d+" "123") ;; => "123"
134
+ (re-matches #"\d+" "123abc") ;; => nil (not entire string)
135
+ (re-matches #"(\d{4})-(\d{2})-(\d{2})" "2024-03-15")
136
+ ;; => ["2024-03-15" "2024" "03" "15"]
137
+
138
+ ;; re-seq — all matches as a lazy sequence
139
+ (re-seq #"\d+" "abc123def456ghi789") ;; => ("123" "456" "789")
140
+ (re-seq #"\b\w{4}\b" "the quick brown fox")
141
+ ;; => ("quick" "brown") (4-letter words)
142
+
143
+ ;; re-pattern — create a regex from a string (useful when dynamic)
144
+ (re-find (re-pattern "hello") "say hello!") ;; => "hello"
145
+ )
146
+
147
+
148
+ ;; Inline Regex Flags
149
+ ;;
150
+ ;; (?i) case-insensitive
151
+ ;; (?m) multiline (^ and $ match line boundaries)
152
+ ;; (?s) dotAll (. matches newlines too)
153
+
154
+ (comment
155
+ (re-find #"(?i)hello" "say HELLO!") ;; => "HELLO"
156
+ (re-matches #"(?i)[a-z]+" "HeLLo") ;; => "HeLLo"
157
+
158
+ (re-seq #"(?m)^\w+" "one\ntwo\nthree") ;; => ("one" "two" "three")
159
+
160
+ (re-seq #"(?im)^hello" "Hello\nHELLO\nhello")
161
+ ;; => ("Hello" "HELLO" "hello")
162
+ )
163
+
164
+
165
+ ;; Practical Patterns
166
+
167
+ (comment
168
+ ;; Parse a CSV row
169
+ (defn parse-csv [line]
170
+ (str/split line #","))
171
+
172
+ (parse-csv "alice,30,admin") ;; => ["alice" "30" "admin"]
173
+
174
+ ;; Extract structured data with groups
175
+ (defn parse-date [s]
176
+ (let [[_ y m d] (re-matches #"(\d{4})-(\d{2})-(\d{2})" s)]
177
+ {:year y :month m :day d}))
178
+
179
+ (parse-date "2024-03-15")
180
+ ;; => {:year "2024" :month "03" :day "15"}
181
+
182
+ ;; Slugify — URL-safe string
183
+ (defn slugify [s]
184
+ (-> s
185
+ str/trim
186
+ str/lower-case
187
+ (str/replace #"[^a-z0-9\s-]" "")
188
+ (str/replace #"\s+" "-")))
189
+
190
+ (slugify " Hello, World! It's Clojure ")
191
+ ;; => "hello-world-its-clojure"
192
+
193
+ ;; Template substitution — replace {{key}} placeholders
194
+ (defn render [template data]
195
+ (str/replace template
196
+ #"\{\{(\w+)\}\}"
197
+ (fn [[_ key]] (get data key ""))))
198
+
199
+ (render "Hello, {{name}}! You have {{count}} messages."
200
+ {"name" "Alice" "count" "3"})
201
+ ;; => "Hello, Alice! You have 3 messages."
202
+ )