jolt/stdlib/clojure/string.clj
Yogthos e17bcfd0af clojure.string toString coercion; some-fn/ifn? reference semantics; misc host gaps
The clojure.string case fns and searches now take any Object s through its
toString like the reference's ^CharSequence signatures ((upper-case :kw) is
":KW", (capitalize 1) is "1"); nil throws, and a nil substr in
starts-with?/ends-with? throws. some-fn re-ported with the reference
arities: (some-fn) is an arity error and a no-match result is the last
predicate's own falsy value (false, not nil). ifn? covers multimethods,
promises (which are now invocable — calling one delivers, via a cold-path
invoke-arm registry that costs the hot dispatch nothing), and deftypes
implementing IFn's invoke.

One structural find on the way: defmulti/defmethod deferred inside a fn
body (the deftest pattern) interned/resolved in whatever namespace was
current when they RAN, not the one they were written in — the macros now
bake their expansion ns and the setups honor it.

Also: Boolean/Integer/Double wrapper ctors, primitive TYPE statics
(Integer/TYPE etc.), .reduce on collections (IReduce), and Long/TYPE.

cts baseline 5857 -> 5904 pass, 58 -> 28 errors, 57 baselined namespaces —
the string cluster, some-fn, ifn-qmark, boolean-qmark, and reduce
namespaces are all fully clean. 7 JVM-certified corpus rows; spec entry.
2026-07-02 11:38:37 -04:00

154 lines
3.9 KiB
Clojure

; 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)))))
;; The case fns and the searches take any Object s through its toString, like
;; the reference ((upper-case :kw) is ":KW", (capitalize 1) is "1"); nil throws
;; like calling a method on null.
(defn- to-str [s]
(if (nil? s)
(throw (new NullPointerException "s"))
(.toString s)))
(defn capitalize
[s]
(let [s (to-str s)]
(if (< 1 (count s))
(str (str-upper (subs s 0 1))
(str-lower (subs s 1)))
(str-upper s))))
(defn lower-case
[s]
(str-lower (to-str s)))
(defn upper-case
[s]
(str-upper (to-str s)))
(defn includes?
[s substr]
(not (nil? (str-find substr (to-str s)))))
(defn join
([coll] (str-join coll))
([separator coll] (str-join coll separator)))
(defn replace
[s match replacement]
(str-replace-all match replacement (to-str s)))
(defn replace-first
[s match replacement]
(str-replace match replacement (to-str s)))
(defn reverse
[s]
(str-reverse-b s))
(defn str-reverse
[s]
(str-reverse-b s))
(defn split
([s re] (split s re 0))
([s re limit]
;; Java Pattern.split semantics: limit > 0 caps the parts (trailing empties
;; kept); limit < 0 splits fully and keeps trailing empties; limit 0 (the
;; default) splits fully then drops trailing empty strings — but a no-match
;; result ([input], the only 1-element case) is returned as-is.
(let [parts (vec (str-split re s (if (pos? limit) limit nil)))]
(if (and (zero? limit) (> (count parts) 1))
(loop [v parts] (if (and (seq v) (= "" (peek v))) (recur (pop v)) v))
parts))))
(defn split-lines
"Split s on \\n or \\r\\n, returning a vector of lines."
[s]
(vec (str-split #"\r?\n" s)))
(defn starts-with?
[s substr]
(when (nil? substr) (throw (new NullPointerException "substr")))
(let [s (to-str s)
slen (count s) slen2 (count substr)]
(and (>= slen slen2)
(= (subs s 0 slen2) substr))))
(defn ends-with?
[s substr]
(when (nil? substr) (throw (new NullPointerException "substr")))
(let [s (to-str s)
slen (count s) slen2 (count substr)]
(and (>= slen slen2)
(= (subs s (- slen slen2)) substr))))
(defn trim
[s]
(str-trim s))
(defn triml
[s]
(str-triml s))
(defn trimr
[s]
(str-trimr s))
(defn escape
[s cmap]
(when (nil? s) (throw (new NullPointerException "s")))
(apply str
(map (fn [ch]
(if-let [rep (cmap ch)] rep (str ch)))
s)))
(defn index-of
"0-based index of the first occurrence of value in s, or nil."
([s value]
(str-find value (to-str s)))
([s value from]
(let [idx (str-find value (subs (to-str s) from))]
(when idx (+ from idx)))))
(defn last-index-of
([s value]
(let [r (str-reverse-b s) sval (str-reverse-b value)
idx (str-find sval r)]
(when idx (- (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 (- from (+ idx (count value)))))))
(defn re-quote-replacement
"Escape special characters (backslash and dollar) in a regex replacement
string so it is used literally rather than interpreted."
[replacement]
(apply str
(map (fn [ch]
(let [c (str ch)]
(if (or (= c "\\") (= c "$")) (str "\\" c) c)))
(seq replacement))))
;; Ported from clojure.string/trim-newline (CharSequence interop replaced with
;; portable count/subs). Removes all trailing \n or \r characters.
(defn trim-newline
"Removes all trailing newline \\n or return \\r characters from
string. Similar to Perl's chomp."
[s]
(loop [index (count s)]
(if (zero? index)
""
(let [c (subs s (dec index) index)]
(if (or (= c "\n") (= c "\r"))
(recur (dec index))
(subs s 0 index))))))