Merge pull request #297 from jolt-lang/string/tostring-coercion

clojure.string toString coercion; some-fn/ifn? reference semantics; misc host gaps
This commit is contained in:
Dmitri Sotnikov 2026-07-02 15:48:52 +00:00 committed by GitHub
commit 95186a6782
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 1100 additions and 941 deletions

View file

@ -321,6 +321,39 @@ in the watch namespaces is their STM `ref` section — refs are out of scope,
--- ---
### clojure.string coercion, some-fn, ifn? — since 1.2/1.3
```
(clojure.string/upper-case s) … (some-fn p & ps) (ifn? x)
```
**Semantics**
- S1. The clojure.string case fns and searches (`upper-case`, `lower-case`,
`capitalize`, `starts-with?`, `ends-with?`, `includes?`, `index-of`,
`replace`) take any Object `s` through its `toString`, like the reference's
`^CharSequence`+`.toString` signatures: `(upper-case :kw)` is `":KW"`,
`(capitalize 1)` is `"1"`. nil throws (method call on null); a nil `substr`
throws.
- S2. `some-fn` follows the reference arities: at least one predicate
(`(some-fn)` is an arity error) and the returned fn chains with `or`, so a
no-match result is the last predicate's own falsy value (`false` stays
`false`).
- S3. `ifn?` covers fns, keywords, symbols, maps, sets, vectors, vars,
multimethods, promises (invoking a promise delivers it), and a
deftype/defrecord implementing `clojure.lang.IFn`'s `invoke`.
- S4. A `defmulti`/`defmethod` deferred inside a fn body interns/resolves in
the namespace it was WRITTEN in (the macros bake their expansion ns), not
whatever namespace is current when it runs.
**Conformance**
S1S4 → corpus `string / toString coercion`, `core / some-fn`, `core / ifn?`,
`multimethods / deferred definition`; clojure-test-suite string/some-fn/
ifn-qmark/boolean-qmark/reduce namespaces (all fully passing).
---
## Authoring notes ## Authoring notes
- Source examples from the ClojureDocs export (`clojuredocs-export.edn`, - Source examples from the ClojureDocs export (`clojuredocs-export.edn`,

View file

@ -301,6 +301,16 @@
(def-var! "clojure.core" "future-cancelled?" jolt-native-future-cancelled?) (def-var! "clojure.core" "future-cancelled?" jolt-native-future-cancelled?)
(def-var! "clojure.core" "promise" jolt-promise-new) (def-var! "clojure.core" "promise" jolt-promise-new)
(def-var! "clojure.core" "deliver" jolt-deliver) (def-var! "clojure.core" "deliver" jolt-deliver)
;; a promise is an IFn on the JVM: (p val) delivers. Registered as a cold
;; invoke arm; callable-host? feeds the ifn? overlay (multimethods included).
(register-invoke-arm! jolt-promise?
(lambda (p args)
(if (and (pair? args) (null? (cdr args)))
(jolt-deliver p (car args))
(jolt-throw (jolt-host-throwable "clojure.lang.ArityException"
"Wrong number of args passed to a promise")))))
(def-var! "jolt.host" "callable-host?"
(lambda (x) (if (or (jolt-multifn? x) (jolt-promise? x)) #t jolt-nil)))
(def-var! "clojure.core" "agent" jolt-agent-new) (def-var! "clojure.core" "agent" jolt-agent-new)
(def-var! "clojure.core" "agent?" jolt-agent?) (def-var! "clojure.core" "agent?" jolt-agent?)
(def-var! "clojure.core" "send" jolt-agent-send) (def-var! "clojure.core" "send" jolt-agent-send)

View file

@ -60,6 +60,12 @@
;; branch and is mis-read as a missing :iterator key (nil). Some libraries ;; branch and is mis-read as a missing :iterator key (nil). Some libraries
;; (e.g. malli's -vmap) iterate a map this way. ;; (e.g. malli's -vmap) iterate a map this way.
((string=? name "iterator") (list (make-jiterator (jolt-seq obj)))) ((string=? name "iterator") (list (make-jiterator (jolt-seq obj))))
;; (.reduce coll f) / (.reduce coll f init): clojure.lang.IReduce — every
;; persistent collection reduces itself on the JVM.
((string=? name "reduce")
(list (if (pair? (cdr args))
(jolt-reduce (car args) (cadr args) obj)
(jolt-reduce (car args) obj))))
(else #f))) (else #f)))
;; Universal object-methods: on a ;; Universal object-methods: on a

View file

@ -202,7 +202,8 @@
(bitwise-ior (bitwise-arithmetic-shift-left r 1) (bitwise-ior (bitwise-arithmetic-shift-left r 1)
(bitwise-and (bitwise-arithmetic-shift-right v i) 1))))))) (bitwise-and (bitwise-arithmetic-shift-right v i) 1)))))))
(register-class-statics! "Long" (register-class-statics! "Long"
(list (cons "MAX_VALUE" (->num 9223372036854775807)) (list (cons "TYPE" "long")
(cons "MAX_VALUE" (->num 9223372036854775807))
(cons "MIN_VALUE" (->num -9223372036854775808)) (cons "MIN_VALUE" (->num -9223372036854775808))
(cons "bitCount" (lambda (n) (->num (bitwise-bit-count (bitwise-and (jnum->exact n) long-mask64))))) (cons "bitCount" (lambda (n) (->num (bitwise-bit-count (bitwise-and (jnum->exact n) long-mask64)))))
(cons "numberOfLeadingZeros" (lambda (n) (->num (long-nlz n)))) (cons "numberOfLeadingZeros" (lambda (n) (->num (long-nlz n))))
@ -214,6 +215,8 @@
(define (int->u32 n) (if (< n 0) (+ n 4294967296) n)) (define (int->u32 n) (if (< n 0) (+ n 4294967296) n))
(register-class-statics! "Integer" (register-class-statics! "Integer"
(list (cons "MAX_VALUE" (->num 2147483647)) (cons "MIN_VALUE" (->num -2147483648)) (list (cons "MAX_VALUE" (->num 2147483647)) (cons "MIN_VALUE" (->num -2147483648))
;; the primitive class token (int.class); jolt models a class as its name
(cons "TYPE" "int")
(cons "valueOf" (lambda (x . r) (cons "valueOf" (lambda (x . r)
(if (number? x) (->num x) (if (number? x) (->num x)
(parse-int-or-throw x (if (null? r) 10 (jnum->exact (car r))) "valueOf")))) (parse-int-or-throw x (if (null? r) 10 (jnum->exact (car r))) "valueOf"))))
@ -227,12 +230,14 @@
;; Byte / Short bounds (their values are plain integers on jolt; the statics let ;; Byte / Short bounds (their values are plain integers on jolt; the statics let
;; libraries reference the JVM ranges — clojure.test.check generates over them). ;; libraries reference the JVM ranges — clojure.test.check generates over them).
(register-class-statics! "Byte" (register-class-statics! "Byte"
(list (cons "MAX_VALUE" (->num 127)) (cons "MIN_VALUE" (->num -128)) (list (cons "TYPE" "byte")
(cons "MAX_VALUE" (->num 127)) (cons "MIN_VALUE" (->num -128))
(cons "valueOf" (lambda (x . r) (->num (if (number? x) x (parse-int-or-throw x 10 "valueOf"))))) (cons "valueOf" (lambda (x . r) (->num (if (number? x) x (parse-int-or-throw x 10 "valueOf")))))
(cons "parseByte" (lambda (x . r) (parse-int-or-throw x (if (null? r) 10 (jnum->exact (car r))) "parseByte"))) (cons "parseByte" (lambda (x . r) (parse-int-or-throw x (if (null? r) 10 (jnum->exact (car r))) "parseByte")))
(cons "toString" (lambda (x . r) (number->string (jnum->exact x)))))) (cons "toString" (lambda (x . r) (number->string (jnum->exact x))))))
(register-class-statics! "Short" (register-class-statics! "Short"
(list (cons "MAX_VALUE" (->num 32767)) (cons "MIN_VALUE" (->num -32768)) (list (cons "TYPE" "short")
(cons "MAX_VALUE" (->num 32767)) (cons "MIN_VALUE" (->num -32768))
(cons "valueOf" (lambda (x . r) (->num (if (number? x) x (parse-int-or-throw x 10 "valueOf"))))) (cons "valueOf" (lambda (x . r) (->num (if (number? x) x (parse-int-or-throw x 10 "valueOf")))))
(cons "parseShort" (lambda (x . r) (parse-int-or-throw x (if (null? r) 10 (jnum->exact (car r))) "parseShort"))) (cons "parseShort" (lambda (x . r) (parse-int-or-throw x (if (null? r) 10 (jnum->exact (car r))) "parseShort")))
(cons "toString" (lambda (x . r) (number->string (jnum->exact x)))))) (cons "toString" (lambda (x . r) (number->string (jnum->exact x))))))
@ -247,13 +252,15 @@
(cons "ROOT" "und") (cons "US" "en-US") (cons "ENGLISH" "en"))) (cons "ROOT" "und") (cons "US" "en-US") (cons "ENGLISH" "en")))
(register-class-statics! "Boolean" (register-class-statics! "Boolean"
(list (cons "parseBoolean" (lambda (s) (string=? "true" (ascii-string-down (if (string? s) s (jolt-str-render-one s)))))) (list (cons "TYPE" "boolean")
(cons "parseBoolean" (lambda (s) (string=? "true" (ascii-string-down (if (string? s) s (jolt-str-render-one s))))))
(cons "TRUE" #t) (cons "FALSE" #f))) (cons "TRUE" #t) (cons "FALSE" #f)))
(register-class-ctor! "Double" ->double) (register-class-ctor! "Double" ->double)
(register-class-ctor! "Float" ->double) (register-class-ctor! "Float" ->double)
(register-class-statics! "Double" (register-class-statics! "Double"
(list (cons "parseDouble" parse-double-or-throw) (list (cons "TYPE" "double")
(cons "parseDouble" parse-double-or-throw)
(cons "valueOf" ->double) (cons "valueOf" ->double)
(cons "toString" (lambda (x) (jolt-str-render-one (->double x)))) (cons "toString" (lambda (x) (jolt-str-render-one (->double x))))
(cons "isNaN" (lambda (x) (and (flonum? x) (nan? x)))) (cons "isNaN" (lambda (x) (and (flonum? x) (nan? x))))
@ -261,11 +268,13 @@
(cons "MAX_VALUE" 1.7976931348623157e308) (cons "MIN_VALUE" 4.9e-324) (cons "MAX_VALUE" 1.7976931348623157e308) (cons "MIN_VALUE" 4.9e-324)
(cons "POSITIVE_INFINITY" +inf.0) (cons "NEGATIVE_INFINITY" -inf.0) (cons "NaN" +nan.0))) (cons "POSITIVE_INFINITY" +inf.0) (cons "NEGATIVE_INFINITY" -inf.0) (cons "NaN" +nan.0)))
(register-class-statics! "Float" (register-class-statics! "Float"
(list (cons "parseFloat" parse-double-or-throw) (cons "valueOf" ->double))) (list (cons "TYPE" "float")
(cons "parseFloat" parse-double-or-throw) (cons "valueOf" ->double)))
;; Character: ASCII predicates (the engine is byte/ASCII oriented). ;; Character: ASCII predicates (the engine is byte/ASCII oriented).
(register-class-statics! "Character" (register-class-statics! "Character"
(list (cons "isUpperCase" (lambda (c) (let ((n (char-code c))) (and (>= n 65) (<= n 90))))) (list (cons "TYPE" "char")
(cons "isUpperCase" (lambda (c) (let ((n (char-code c))) (and (>= n 65) (<= n 90)))))
(cons "isLowerCase" (lambda (c) (let ((n (char-code c))) (and (>= n 97) (<= n 122))))) (cons "isLowerCase" (lambda (c) (let ((n (char-code c))) (and (>= n 97) (<= n 122)))))
(cons "isDigit" (lambda (c) (let ((n (char-code c))) (and (>= n 48) (<= n 57))))) (cons "isDigit" (lambda (c) (let ((n (char-code c))) (and (>= n 48) (<= n 57)))))
;; JVM Character.isWhitespace: Unicode whitespace (so U+2028 line separator ;; JVM Character.isWhitespace: Unicode whitespace (so U+2028 line separator

View file

@ -624,6 +624,31 @@
;; (Long. n) / (Long. "n"): a Long is just jolt's integer; return it (parse a string). ;; (Long. n) / (Long. "n"): a Long is just jolt's integer; return it (parse a string).
(register-class-ctor! "Long" (lambda (x) (if (string? x) (parse-int-or-throw x 10 "Long") (->num (jnum->exact x))))) (register-class-ctor! "Long" (lambda (x) (if (string? x) (parse-int-or-throw x 10 "Long") (->num (jnum->exact x)))))
(register-class-ctor! "java.lang.Long" (lambda (x) (if (string? x) (parse-int-or-throw x 10 "Long") (->num (jnum->exact x))))) (register-class-ctor! "java.lang.Long" (lambda (x) (if (string? x) (parse-int-or-throw x 10 "Long") (->num (jnum->exact x)))))
;; (Integer. n) / (Integer. "n"): jolt's integer, range-checked like intCast.
(define (integer-ctor x)
(jolt-int-cast (if (string? x) (parse-int-or-throw x 10 "Integer") x)))
(register-class-ctor! "Integer" integer-ctor)
(register-class-ctor! "java.lang.Integer" integer-ctor)
;; (Double. x) / (Double. "x"): jolt's double.
(define (double-ctor x)
(if (string? x)
(let ((n (string->number x)))
(if n (exact->inexact n)
(jolt-throw (jolt-host-throwable "java.lang.NumberFormatException"
(string-append "For input string: \"" x "\"")))))
(jolt-double x)))
(register-class-ctor! "Double" double-ctor)
(register-class-ctor! "java.lang.Double" double-ctor)
;; (Boolean. "true") / (Boolean. b): true for the string "true" (case-insensitive,
;; anything else false) or the boolean itself — Boolean.valueOf semantics; the
;; box is jolt's plain boolean.
(define (boolean-ctor x)
(cond ((string? x) (string-ci=? x "true"))
((boolean? x) x)
(else #f)))
(register-class-ctor! "Boolean" boolean-ctor)
(register-class-ctor! "java.lang.Boolean" boolean-ctor)
;; --- java.net.URI ----------------------------------------------------------- ;; --- java.net.URI -----------------------------------------------------------
;; A minimal RFC-3986 split into scheme/authority/host/port/path/query/fragment, ;; A minimal RFC-3986 split into scheme/authority/host/port/path/query/fragment,

View file

@ -61,26 +61,36 @@
(define (jolt-defmulti-setup name-sym dispatch . opts) (define (jolt-defmulti-setup name-sym dispatch . opts)
(let-values (((dk h) (parse-mm-opts opts))) (let-values (((dk h) (parse-mm-opts opts)))
(let ((mf (make-jolt-multifn (symbol-t-name name-sym) dispatch (let* ((sns (symbol-t-ns name-sym))
(new-mm-table) dk h (new-mm-table)))) ;; the macro qualifies the name with its EXPANSION ns, so a defmulti
(def-var! (chez-current-ns) (symbol-t-name name-sym) mf) ;; deferred inside a fn (a deftest body) still defines in the ns it
;; was written in, not whatever ns is current when it finally runs.
(ns (if (string? sns) sns (chez-current-ns)))
(mf (make-jolt-multifn (symbol-t-name name-sym) dispatch
(new-mm-table) dk h (new-mm-table))))
(def-var! ns (symbol-t-name name-sym) mf)
mf))) mf)))
;; (defmethod-setup 'mm dispatch-val impl) — add a method. Auto-creates the multifn ;; (defmethod-setup 'mm dispatch-val impl) — add a method. Auto-creates the multifn
;; if absent (defmethod before defmulti — rare; identity dispatch as a fallback). ;; if absent (defmethod before defmulti — rare; identity dispatch as a fallback).
(define (jolt-defmethod-setup mm-sym dval impl) (define (jolt-defmethod-setup mm-sym dval impl . rest)
(let* ((nm (symbol-t-name mm-sym)) (let* ((nm (symbol-t-name mm-sym))
(sns (symbol-t-ns mm-sym)) (sns (symbol-t-ns mm-sym))
(qns (and sns (not (jolt-nil? sns)) (not (null? sns)) sns)) (qns (and sns (not (jolt-nil? sns)) (not (null? sns)) sns))
;; the macro passes its EXPANSION ns so a defmethod deferred inside a
;; fn resolves like the JVM (against the ns it was written in, not the
;; ns current when it runs); absent (old emitted code) fall back to the
;; runtime ns.
(here (if (and (pair? rest) (string? (car rest))) (car rest) (chez-current-ns)))
;; qualified (cf.mm/ext) resolves in its own ns (cross-ns defmethod); ;; qualified (cf.mm/ext) resolves in its own ns (cross-ns defmethod);
;; unqualified resolves in the current ns, else a :refer's home ns (so a ;; unqualified resolves in the writing ns, else a :refer's home ns (so a
;; defmethod on a referred multifn lands on the real one), else stays in ;; defmethod on a referred multifn lands on the real one), else stays in
;; the current ns (a shadow, as before). ;; the writing ns (a shadow, as before).
(mns (cond (mns (cond
(qns (or (chez-resolve-alias (chez-current-ns) qns) qns)) (qns (or (chez-resolve-alias here qns) qns))
((var-cell-lookup (chez-current-ns) nm) (chez-current-ns)) ((var-cell-lookup here nm) here)
((chez-resolve-refer (chez-current-ns) nm) => values) ((chez-resolve-refer here nm) => values)
(else (chez-current-ns)))) (else here)))
(cur (var-deref mns nm)) (cur (var-deref mns nm))
(mf (if (jolt-multifn? cur) cur (mf (if (jolt-multifn? cur) cur
;; auto-create: copy the dispatch fn + default from a same-named ;; auto-create: copy the dispatch fn + default from a same-named

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -436,6 +436,16 @@
;; can't statically resolve to a procedure (a keyword/coll/proc held in a local) ;; can't statically resolve to a procedure (a keyword/coll/proc held in a local)
;; routes here. Off the arithmetic/self-recursion hot path by construction. ;; routes here. Off the arithmetic/self-recursion hot path by construction.
;; ============================================================================ ;; ============================================================================
;; (pred . handler) arms making a host type invocable; handler gets (f args).
(define jolt-invoke-arms '())
(define (register-invoke-arm! pred handler)
(set! jolt-invoke-arms (cons (cons pred handler) jolt-invoke-arms)))
(define (jolt-invoke-arm-for f)
(let loop ((as jolt-invoke-arms))
(cond ((null? as) #f)
(((caar as) f) (cdar as))
(else (loop (cdr as))))))
(define (jolt-invoke f . args) (define (jolt-invoke f . args)
(cond (cond
((procedure? f) (apply f args)) ((procedure? f) (apply f args))
@ -449,6 +459,9 @@
=> (lambda (m) (apply jolt-invoke m f args))) => (lambda (m) (apply jolt-invoke m f args)))
((and (reified-methods f) (hashtable-ref (reified-methods f) "invoke" #f)) ((and (reified-methods f) (hashtable-ref (reified-methods f) "invoke" #f))
=> (lambda (m) (apply jolt-invoke m f args))) => (lambda (m) (apply jolt-invoke m f args)))
;; host types registered as callable (promise delivers, …): consulted only
;; after every built-in case missed, so the hot dispatch pays nothing.
((jolt-invoke-arm-for f) => (lambda (h) (h f args)))
;; calling a non-fn: a ClassCastException naming the operator's CLASS (like ;; calling a non-fn: a ClassCastException naming the operator's CLASS (like
;; the JVM's "class clojure.lang.LazySeq cannot be cast to ... IFn" — never ;; the JVM's "class clojure.lang.LazySeq cannot be cast to ... IFn" — never
;; the value, whose printed form may be unbounded: ((range)) must throw, not ;; the value, whose printed form may be unbounded: ((range)) must throw, not

View file

@ -155,8 +155,43 @@
(when-let [s (seq coll)] (when-let [s (seq coll)]
(or (pred (first s)) (recur pred (next s))))) (or (pred (first s)) (recur pred (next s)))))
(defn some-fn [& preds] ;; Reference arities: at least one predicate ((some-fn) is an arity error), and
(fn [& xs] (some (fn [p] (some p xs)) preds))) ;; the returned fn chains with or — a no-match result is the last predicate's
;; own falsy value (false stays false, not nil).
(defn some-fn
([p]
(fn sp1
([] nil)
([x] (p x))
([x y] (or (p x) (p y)))
([x y z] (or (p x) (p y) (p z)))
([x y z & args] (or (sp1 x y z)
(some p args)))))
([p1 p2]
(fn sp2
([] nil)
([x] (or (p1 x) (p2 x)))
([x y] (or (p1 x) (p1 y) (p2 x) (p2 y)))
([x y z] (or (p1 x) (p1 y) (p1 z) (p2 x) (p2 y) (p2 z)))
([x y z & args] (or (sp2 x y z)
(some (fn [q] (or (p1 q) (p2 q))) args)))))
([p1 p2 p3]
(fn sp3
([] nil)
([x] (or (p1 x) (p2 x) (p3 x)))
([x y] (or (p1 x) (p2 x) (p3 x) (p1 y) (p2 y) (p3 y)))
([x y z] (or (p1 x) (p2 x) (p3 x) (p1 y) (p2 y) (p3 y) (p1 z) (p2 z) (p3 z)))
([x y z & args] (or (sp3 x y z)
(some (fn [q] (or (p1 q) (p2 q) (p3 q))) args)))))
([p1 p2 p3 & ps]
(let [ps (cons p1 (cons p2 (cons p3 ps)))]
(fn spn
([] nil)
([x] (some (fn [p] (p x)) ps))
([x y] (or (spn x) (spn y)))
([x y z] (or (spn x) (spn y) (spn z)))
([x y z & args] (or (spn x y z)
(some (fn [p] (some p args)) ps)))))))
(defn not-any? [pred coll] (not (some pred coll))) (defn not-any? [pred coll] (not (some pred coll)))

View file

@ -183,10 +183,16 @@
([x y z & args] (f (apply g x y z args))))) ([x y z & args] (f (apply g x y z args)))))
([f g & fs] (reduce comp (comp f g) fs))) ([f g & fs] (reduce comp (comp f g) fs)))
;; Canonical IFn set: fns, keywords, symbols, maps (sorted incl.), ;; Canonical IFn set: fns, keywords, symbols, maps (sorted incl.), sets,
;; sets, vectors, and vars — NOT lists ((ifn? '(1 2)) is false in Clojure). ;; vectors, vars — NOT lists ((ifn? '(1 2)) is false in Clojure) — plus the
;; host callables (multimethods, promises) and a deftype/record implementing
;; clojure.lang.IFn's invoke.
(defn ifn? [x] (defn ifn? [x]
(or (fn? x) (keyword? x) (symbol? x) (map? x) (set? x) (vector? x) (var? x))) (if (or (fn? x) (keyword? x) (symbol? x) (map? x) (set? x) (vector? x) (var? x)
(jolt.host/callable-host? x)
(jolt.host/jrec-method? x "invoke"))
true
false))
;; Auto-promoting (') and unchecked arithmetic. Jolt numbers don't overflow, ;; Auto-promoting (') and unchecked arithmetic. Jolt numbers don't overflow,
;; so all of these are the checked ops; fixed arities mirror Clojure's ;; so all of these are the checked ops; fixed arities mirror Clojure's

View file

@ -28,11 +28,18 @@
(let [args (if (string? (first args)) (rest args) args) (let [args (if (string? (first args)) (rest args) args)
args (if (and (map? (first args)) (not (symbol? (first args)))) (rest args) args) args (if (and (map? (first args)) (not (symbol? (first args)))) (rest args) args)
dispatch (first args) dispatch (first args)
opts (rest args)] opts (rest args)
`(defmulti-setup (quote ~name) ~dispatch ~@opts))) ;; qualify with the EXPANSION ns: a defmulti deferred inside a fn (a
;; deftest body) must still define in the ns it was written in.
qname (symbol (str (clojure.core/ns-name clojure.core/*ns*))
(clojure.core/name name))]
`(defmulti-setup (quote ~qname) ~dispatch ~@opts)))
(defmacro defmethod [mm dispatch-val & fn-tail] (defmacro defmethod [mm dispatch-val & fn-tail]
`(defmethod-setup (quote ~mm) ~dispatch-val (fn ~@fn-tail))) ;; the expansion ns rides along so a deferred defmethod resolves its multifn
;; against the ns it was written in (aliases and refers included).
`(defmethod-setup (quote ~mm) ~dispatch-val (fn ~@fn-tail)
~(str (clojure.core/ns-name clojure.core/*ns*))))
;; Multimethod table ops: a multimethod's method table lives on its ;; Multimethod table ops: a multimethod's method table lives on its
;; VAR (the value is just the dispatch closure), so these pass the name quoted ;; VAR (the value is just the dispatch closure), so these pass the name quoted

View file

@ -6,28 +6,32 @@
(if (nil? s) true (if (nil? s) true
(= 0 (count (str-trim s))))) (= 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 (defn capitalize
[s] [s]
(if (< 1 (count s)) (let [s (to-str s)]
(str (str-upper (subs s 0 1)) (if (< 1 (count s))
(str-lower (subs s 1))) (str (str-upper (subs s 0 1))
(str-upper s))) (str-lower (subs s 1)))
(str-upper s))))
(defn lower-case (defn lower-case
[s] [s]
(str-lower s)) (str-lower (to-str s)))
(defn upper-case (defn upper-case
[s] [s]
(str-upper s)) (str-upper (to-str s)))
(defn includes? (defn includes?
[s substr] [s substr]
(not (nil? (str-find substr s)))) (not (nil? (str-find substr (to-str s)))))
(defn join (defn join
@ -36,11 +40,11 @@
(defn replace (defn replace
[s match replacement] [s match replacement]
(str-replace-all match replacement s)) (str-replace-all match replacement (to-str s)))
(defn replace-first (defn replace-first
[s match replacement] [s match replacement]
(str-replace match replacement s)) (str-replace match replacement (to-str s)))
(defn reverse (defn reverse
[s] [s]
@ -68,16 +72,18 @@
(vec (str-split #"\r?\n" s))) (vec (str-split #"\r?\n" s)))
(defn starts-with? (defn starts-with?
[s substr] [s substr]
(let [slen (count s) slen2 (count substr)] (when (nil? substr) (throw (new NullPointerException "substr")))
(let [s (to-str s)
slen (count s) slen2 (count substr)]
(and (>= slen slen2) (and (>= slen slen2)
(= (subs s 0 slen2) substr)))) (= (subs s 0 slen2) substr))))
(defn ends-with? (defn ends-with?
[s substr] [s substr]
(let [slen (count s) slen2 (count substr)] (when (nil? substr) (throw (new NullPointerException "substr")))
(let [s (to-str s)
slen (count s) slen2 (count substr)]
(and (>= slen slen2) (and (>= slen slen2)
(= (subs s (- slen slen2)) substr)))) (= (subs s (- slen slen2)) substr))))
@ -97,8 +103,8 @@
(str-trimr s)) (str-trimr s))
(defn escape (defn escape
[s cmap] [s cmap]
(when (nil? s) (throw (new NullPointerException "s")))
(apply str (apply str
(map (fn [ch] (map (fn [ch]
(if-let [rep (cmap ch)] rep (str ch))) (if-let [rep (cmap ch)] rep (str ch)))
@ -107,9 +113,9 @@
(defn index-of (defn index-of
"0-based index of the first occurrence of value in s, or nil." "0-based index of the first occurrence of value in s, or nil."
([s value] ([s value]
(str-find value s)) (str-find value (to-str s)))
([s value from] ([s value from]
(let [idx (str-find value (subs s from))] (let [idx (str-find value (subs (to-str s) from))]
(when idx (+ from idx))))) (when idx (+ from idx)))))
(defn last-index-of (defn last-index-of

View file

@ -3558,4 +3558,11 @@
{:suite "casts / checked narrow" :label "float range-checks against Float/MAX_VALUE" :expected "[:iae 1.5]" :actual "[(try (float 1e300) (catch IllegalArgumentException _ :iae)) (float 1.5)]"} {:suite "casts / checked narrow" :label "float range-checks against Float/MAX_VALUE" :expected "[:iae 1.5]" :actual "[(try (float 1e300) (catch IllegalArgumentException _ :iae)) (float 1.5)]"}
{:suite "casts / unchecked wrap" :label "unchecked casts wrap and sign-fold; doubles saturate" :expected "[-56 -25536 \\A 9223372036854775807 0 0]" :actual "[(unchecked-byte 200) (unchecked-short 40000) (unchecked-char 65) (unchecked-long 1e300) (unchecked-int 4294967296) (unchecked-long ##NaN)]"} {:suite "casts / unchecked wrap" :label "unchecked casts wrap and sign-fold; doubles saturate" :expected "[-56 -25536 \\A 9223372036854775807 0 0]" :actual "[(unchecked-byte 200) (unchecked-short 40000) (unchecked-char 65) (unchecked-long 1e300) (unchecked-int 4294967296) (unchecked-long ##NaN)]"}
{:suite "ns / requiring-resolve" :label "loads the namespace then resolves; unqualified throws" :expected "[#{1 2} :iae]" :actual "[((requiring-resolve (quote clojure.set/union)) #{1} #{2}) (try (requiring-resolve (quote unqualified)) (catch IllegalArgumentException _ :iae))]"} {:suite "ns / requiring-resolve" :label "loads the namespace then resolves; unqualified throws" :expected "[#{1 2} :iae]" :actual "[((requiring-resolve (quote clojure.set/union)) #{1} #{2}) (try (requiring-resolve (quote unqualified)) (catch IllegalArgumentException _ :iae))]"}
{:suite "string / toString coercion" :label "case fns and searches take any Object through toString" :expected "[\":ASDF/X\" \"asdf\" \"1\" true true]" :actual "[(clojure.string/upper-case :asdf/x) (clojure.string/lower-case (quote ASDF)) (clojure.string/capitalize 1) (clojure.string/starts-with? :ab \":a\") (clojure.string/ends-with? (quote ab) \"b\")]"}
{:suite "string / toString coercion" :label "nil s throws; nil substr throws" :expected "[:t :t :t]" :actual "[(try (clojure.string/upper-case nil) (catch Throwable _ :t)) (try (clojure.string/starts-with? \"\" nil) (catch Throwable _ :t)) (try (clojure.string/escape nil {}) (catch Throwable _ :t))]"}
{:suite "core / some-fn" :label "returns the last predicate value (false, not nil); zero preds is an arity error" :expected "[false 2 :ae]" :actual "[((some-fn even?) 1) ((some-fn :a :b) {:b 2}) (try (some-fn) (catch clojure.lang.ArityException _ :ae))]"}
{:suite "core / ifn?" :label "multimethods and promises are IFn; promises deliver when invoked" :expected "[true true 7]" :actual "[(do (defmulti ifnq-mm identity) (ifn? ifnq-mm)) (ifn? (promise)) (let [p (promise)] (p 7) (deref p))]"}
{:suite "multimethods / deferred definition" :label "defmulti/defmethod inside a fn intern in the ns they were written in" :expected ":hit" :actual "((fn [] (defmulti cdm-deferred first) (defmethod cdm-deferred :a [_] :hit) (cdm-deferred [:a 1])))"}
{:suite "host-interop / wrapper ctors and TYPE" :label "Boolean/Integer/Double ctors; primitive TYPE tokens" :expected "[false true 12 1.5 \"int\" \"double\"]" :actual "[(Boolean. \"yes\") (Boolean. \"TRUE\") (Integer. \"12\") (Double. \"1.5\") (str Integer/TYPE) (str Double/TYPE)]"}
{:suite "host-interop / IReduce" :label ".reduce drives a collection's own reduce" :expected "[7 6]" :actual "[(.reduce [1 2 3] + 1) (.reduce [1 2 3] +)]"}
] ]

View file

@ -5,7 +5,6 @@ clojure.core-test.abs 1 0
clojure.core-test.add-watch 0 1 clojure.core-test.add-watch 0 1
clojure.core-test.bigint 6 0 clojure.core-test.bigint 6 0
clojure.core-test.bit-set 1 0 clojure.core-test.bit-set 1 0
clojure.core-test.boolean-qmark 0 3
clojure.core-test.compare 1 0 clojure.core-test.compare 1 0
clojure.core-test.conj 1 0 clojure.core-test.conj 1 0
clojure.core-test.contains-qmark 3 0 clojure.core-test.contains-qmark 3 0
@ -19,7 +18,6 @@ clojure.core-test.eval 0 3
clojure.core-test.even-qmark 1 0 clojure.core-test.even-qmark 1 0
clojure.core-test.float 1 0 clojure.core-test.float 1 0
clojure.core-test.get 0 1 clojure.core-test.get 0 1
clojure.core-test.ifn-qmark 2 0
clojure.core-test.inc 1 0 clojure.core-test.inc 1 0
clojure.core-test.int-qmark 3 0 clojure.core-test.int-qmark 3 0
clojure.core-test.intern 2 0 clojure.core-test.intern 2 0
@ -44,14 +42,12 @@ clojure.core-test.quot 30 0
clojure.core-test.rand-nth 0 1 clojure.core-test.rand-nth 0 1
clojure.core-test.rational-qmark 3 0 clojure.core-test.rational-qmark 3 0
clojure.core-test.realized-qmark 3 0 clojure.core-test.realized-qmark 3 0
clojure.core-test.reduce 0 1
clojure.core-test.rem 21 0 clojure.core-test.rem 21 0
clojure.core-test.remove-watch 0 1 clojure.core-test.remove-watch 0 1
clojure.core-test.run-bang 1 0 clojure.core-test.run-bang 1 0
clojure.core-test.select-keys 2 0 clojure.core-test.select-keys 2 0
clojure.core-test.seqable-qmark 1 0 clojure.core-test.seqable-qmark 1 0
clojure.core-test.shuffle 1 0 clojure.core-test.shuffle 1 0
clojure.core-test.some-fn 3 0
clojure.core-test.sort-by 2 0 clojure.core-test.sort-by 2 0
clojure.core-test.special-symbol-qmark 4 0 clojure.core-test.special-symbol-qmark 4 0
clojure.core-test.star 13 0 clojure.core-test.star 13 0
@ -62,9 +58,3 @@ clojure.core-test.vals 0 3
clojure.core-test.vec 1 0 clojure.core-test.vec 1 0
clojure.core-test.when-let 1 0 clojure.core-test.when-let 1 0
clojure.edn-test.read-string 46 5 clojure.edn-test.read-string 46 5
clojure.string-test.capitalize 0 4
clojure.string-test.ends-with-qmark 1 4
clojure.string-test.escape 1 0
clojure.string-test.lower-case 0 4
clojure.string-test.starts-with-qmark 1 10
clojure.string-test.upper-case 0 4