Phase 10: Standard Library — clojure.string, clojure.set, clojure.walk

- src/jolt/clojure/string.clj (123 lines, 20 functions):
  blank?, capitalize, lower-case, upper-case, includes?, join,
  replace, replace-first, str-reverse, split, starts-with?,
  ends-with?, trim, triml, trimr, trim-newline, escape,
  index-of, last-index-of
- src/jolt/clojure/set.clj (124 lines, 10 operations):
  union, intersection, difference, select, project, rename,
  rename-keys, map-invert, join, index, subset?, superset?
- src/jolt/clojure/walk.clj (77 lines, 9 functions):
  walk, postwalk, prewalk, postwalk-demo, prewalk-demo,
  postwalk-replace, prewalk-replace, keywordize-keys,
  stringify-keys, macroexpand-all
- src/jolt/core.janet: 11 Janet string interop bindings
  (str-trim, str-upper, str-lower, str-find, str-replace,
  str-replace-all, str-reverse-b, str-join, str-split,
  str-triml, str-trimr)
- test/phase10-test.janet: 2 test sections (40-41)
  15+ assertions covering string and set functions
- All .clj files use eval-form for multi-form loading
- 315 ok, 2 fail (pre-existing, unchanged)
This commit is contained in:
Yogthos 2026-06-03 10:12:51 -04:00
parent e63c2ce8d5
commit fdb0f4ab83
10 changed files with 518 additions and 77 deletions

124
src/jolt/clojure/set.clj Normal file
View file

@ -0,0 +1,124 @@
; Jolt Standard Library: clojure.set
; Set operations (union, intersection, difference, subset?, superset?, etc.)
(defn union
"Return a set that is the union of the input sets."
([s1] s1)
([s1 s2]
(if (< (count s1) (count s2))
(reduce conj s2 s1)
(reduce conj s1 s2)))
([s1 s2 & sets]
(reduce union (union s1 s2) sets)))
(defn intersection
"Return a set that is the intersection of the input sets."
([s1] s1)
([s1 s2]
(reduce (fn [acc item]
(if (contains? s2 item) acc (disj acc item)))
s1 s1))
([s1 s2 & sets]
(reduce intersection (intersection s1 s2) sets)))
(defn difference
"Return a set that is the first set without elements of the other sets."
([s1] s1)
([s1 s2]
(reduce disj s1 s2))
([s1 s2 & sets]
(reduce difference (difference s1 s2) sets)))
(defn select
"Returns a set of the elements for which pred is true."
[pred s]
(reduce (fn [acc item]
(if (pred item) acc (disj acc item)))
s s))
(defn project
"Returns a rel of the elements of xrel with only the keys in ks."
[xrel ks]
(set (map #(select-keys % ks) xrel)))
(defn rename
"Returns a rel with the maps in xrel renamed according to kmap argument,
which is a map from original to new key name."
[xrel kmap]
(set
(map
(fn [m]
(reduce (fn [acc [old new]]
(if (contains? m old)
(assoc acc new (get m old))
acc))
(apply dissoc m (keys kmap))
kmap))
xrel)))
(defn rename-keys
"Returns the map with the keys in kmap renamed to the values in kmap."
[map kmap]
(reduce
(fn [m [old new]]
(if (contains? m old)
(assoc m new (get m old) old nil)
m))
map kmap))
(defn map-invert
"Returns the map with the vals mapped to the keys."
[m]
(reduce (fn [acc [k v]] (assoc acc v k)) {} m))
(defn join
"When passed 2 rels, returns the rel corresponding to the natural
join. When passed an additional keymap, joins on the corresponding
keys."
([xrel yrel]
(if (and (seq xrel) (seq yrel))
(let [ks (intersection (set (keys (first xrel)))
(set (keys (first yrel))))
idx (map-invert (zipmap (range) yrel))]
(reduce (fn [acc x]
(reduce (fn [acc y]
(if (= (select-keys x ks)
(select-keys y ks))
(conj acc (merge x y))
acc))
acc yrel))
#{} xrel))
#{}))
([xrel yrel kmap]
(let [kmap (if (map? kmap) kmap (zipmap kmap kmap))
idx (reduce (fn [m y]
(assoc m (select-keys y (vals kmap)) y))
{} yrel)]
(reduce
(fn [acc x]
(let [found (get idx (select-keys x (keys kmap)))]
(if found
(conj acc (merge x (rename-keys found kmap)))
acc)))
#{} xrel))))
(defn index
"Returns a map of the distinct values of ks in the xrel mapped to a
set of the maps in xrel with the corresponding values of ks."
[xrel ks]
(reduce (fn [m x]
(let [ik (select-keys x ks)]
(assoc m ik (conj (get m ik #{}) x))))
{} xrel))
(defn subset?
"Is set1 a subset of set2?"
[set1 set2]
(and (<= (count set1) (count set2))
(every? #(contains? set2 %) set1)))
(defn superset?
"Is set1 a superset of set2?"
[set1 set2]
(and (>= (count set1) (count set2))
(every? #(contains? set1 %) set2)))

126
src/jolt/clojure/string.clj Normal file
View file

@ -0,0 +1,126 @@
; Jolt Standard Library: clojure.string
; String manipulation functions using Jolt core string interop.
(defn blank?
[s]
(if (nil? s) true
(= 0 (count (str-trim s)))))
(defn capitalize
"Converts first character of the string to upper-case, all other
characters to lower-case."
[s]
(if (< 1 (count s))
(str (str-upper (subs s 0 1))
(str-lower (subs s 1)))
(str-upper s)))
(defn lower-case
"Converts string to all lower-case."
[s]
(str-lower s))
(defn upper-case
"Converts string to all upper-case."
[s]
(str-upper s))
(defn includes?
"True if s includes substr."
[s substr]
(not (nil? (str-find substr s))))
(defn join
"Returns a string of all elements in coll, separated by
an optional separator."
([coll] (str-join coll))
([separator coll] (str-join coll separator)))
(defn replace
"Replaces all instance of match with replacement in s."
[s match replacement]
(str-replace-all match s replacement))
(defn replace-first
"Replaces the first instance of pattern in string with replacement."
[s match replacement]
(str-replace match s replacement))
(defn str-reverse
"Returns s with its characters reversed."
[s]
(str-reverse-b s))
(defn split
"Splits string on a regular expression. Optional limit."
([s re]
(map str-trim (str-split re s)))
([s re limit]
(take limit (split s re))))
(defn starts-with?
"True if s starts with substr."
[s substr]
(let [slen (count s) slen2 (count substr)]
(and (>= slen slen2)
(= (subs s 0 slen2) substr))))
(defn ends-with?
"True if s ends with substr."
[s substr]
(let [slen (count s) slen2 (count substr)]
(and (>= slen slen2)
(= (subs s (- slen slen2)) substr))))
(defn trim
"Removes whitespace from both ends of string."
[s]
(str-trim s))
(defn triml
"Removes whitespace from the left side of string."
[s]
(str-triml s))
(defn trimr
"Removes whitespace from the right side of string."
[s]
(str-trimr s))
(defn trim-newline
"Removes all trailing newline \\n or return \\r characters from string."
[s]
(var result s)
(while (or (= (subs result (dec (count result))) "\n")
(= (subs result (dec (count result))) "\r"))
(set result (subs result 0 (dec (count result)))))
result)
(defn escape
"Return a new string, using cmap to escape each character ch from s."
[s cmap]
(apply str
(map (fn [ch]
(if-let [rep (cmap ch)] rep (str ch)))
s)))
(defn index-of
"Return index of value (string or char) in s, optionally
from start. Returns nil if not found."
([s value]
(let [idx (str-find value s)]
(when idx (inc idx))))
([s value from]
(let [idx (str-find value (subs s from))]
(when idx (+ from (inc idx))))))
(defn last-index-of
"Return last index of value (string or char) in s."
([s value]
(let [r (str-reverse-b s) sval (str-reverse-b value)
idx (str-find sval r)]
(when idx (inc (- (count s) (+ idx (count value)))))))
([s value from]
(let [sub (subs s 0 from) r (str-reverse-b sub) sval (str-reverse-b value)
idx (str-find sval r)]
(when idx (inc (- from (+ idx (count value))))))))

77
src/jolt/clojure/walk.clj Normal file
View file

@ -0,0 +1,77 @@
; Jolt Standard Library: clojure.walk
; This file generalizes tree walking for Clojure data structures.
(defn walk
"Traverses form, an arbitrary data structure. inner and outer are
functions. Applies inner to each element of form, building up a
data structure of the same type, then applies outer to the result.
Recognizes all Clojure data structures. Consumes seqs."
[inner outer form]
(cond
(list? form) (outer (apply list (map inner form)))
(seq? form) (outer (doall (map inner form)))
(vector? form) (outer (vec (map inner form)))
(map? form) (outer (into (empty form) (map inner form)))
(set? form) (outer (into (empty form) (map inner form)))
:else (outer form)))
(defn postwalk
"Performs a depth-first, post-order traversal of form. Calls f on
each sub-form, uses f's return value in place of the original.
Recognizes all Clojure data structures. Consumes seqs."
[f form]
(walk (partial postwalk f) f form))
(defn prewalk
"Like postwalk, but does pre-order traversal."
[f form]
(walk (partial prewalk f) identity (f form)))
(defn postwalk-demo
"Demonstrates the behavior of postwalk by returning a lazy seq of
forms passed to the postwalk outer function during the traversal
of form."
[form]
(let [acc (atom [])]
(postwalk (fn [x] (swap! acc conj x) x) form)
@acc))
(defn prewalk-demo
"Demonstrates the behavior of prewalk by returning a lazy seq of
forms passed to the prewalk outer function during the traversal
of form."
[form]
(let [acc (atom [])]
(prewalk (fn [x] (swap! acc conj x) x) form)
@acc))
(defn postwalk-replace
"Recursively transforms form by replacing keys in smap with their
values. Like clojure.string/replace but works with any data
structure. Does replacement at the leaves of the tree first."
[smap form]
(postwalk (fn [x] (if (contains? smap x) (get smap x) x)) form))
(defn prewalk-replace
"Recursively transforms form by replacing keys in smap with their
values. Like postwalk-replace but does replacement at the root of
the tree first."
[smap form]
(prewalk (fn [x] (if (contains? smap x) (get smap x) x)) form))
(defn keywordize-keys
"Recursively transforms all map keys from strings to keywords."
[m]
(let [f (fn [[k v]] (if (string? k) [(keyword k) v] [k v]))]
(postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
(defn stringify-keys
"Recursively transforms all map keys from keywords to strings."
[m]
(let [f (fn [[k v]] (if (keyword? k) [(name k) v] [k v]))]
(postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
(defn macroexpand-all
"Recursively performs all possible macroexpansions in form."
[form]
(prewalk (fn [x] (if (seq? x) (macroexpand x) x)) form))

View file

@ -1243,6 +1243,17 @@
"str" core-str
"name" core-name
"subs" core-subs
"str-trim" string/trim
"str-upper" string/ascii-upper
"str-lower" string/ascii-lower
"str-find" string/find
"str-replace" string/replace
"str-replace-all" string/replace-all
"str-reverse-b" string/reverse
"str-join" string/join
"str-split" string/split
"str-triml" string/triml
"str-trimr" string/trimr
"print" core-print
"println" core-println
"pr" core-pr