Fix conformance gaps: exception types, byte/getBytes, host classes

Shake-out from the conformance-library sweep. Host-side fixes (runtime .ss,
no re-mint) plus one analyzer change (re-minted):

- Exception fidelity: ex-info and host-constructed throwables (RuntimeException.
  etc.) now carry their JVM class, so (class e), instance? across the exception
  hierarchy, .getMessage, and clojure.test thrown?/thrown-with-msg? all work.
- .getBytes returns a seqable/countable byte-array and honors UTF-16/UTF-32;
  String. decodes them. ->bytevector accepts byte-arrays (Base64).
- Universal .getClass / .toString / .indexOf / .lastIndexOf on any value/seq.
- record? uses the host jrec? predicate (the old (get x :jolt/deftype) crashed
  on a sorted-map by invoking its comparator).
- extend-protocol to abstract host types (clojure.lang.Fn/IFn/APersistentVector,
  java.net.URI) dispatches.
- New host classes: clojure.lang.PersistentQueue, java.util.ArrayList,
  java.net.URI, java.io.File / java.util.UUID ctors, Double/Float ctors+statics,
  regex instance? Pattern, System/setProperty.
- *assert* / *print-readably* are real settable/bindable vars.
- (symbol "ns/name") splits the namespace at the last slash.
- letfn fn params desugar destructuring (analyzer; re-minted).

unit.edn gains exinfo/hostobj/queue/hostctor/destructure regression rows.
This commit is contained in:
Yogthos 2026-06-22 17:52:38 -04:00
parent 185b4fd3ca
commit d83175b8c2
14 changed files with 570 additions and 39 deletions

View file

@ -84,6 +84,30 @@
(let loop ((p (reverse parts)))
(if (and (pair? p) (string=? (car p) "")) (loop (cdr p)) (reverse p))))
;; Encode a string to bytes (a bytevector) under a named charset. UTF-8 default;
;; ISO-8859-1/latin1/ascii are one byte per char; UTF-16/UTF-32 via Chez's codecs
;; (plain "UTF-16" emits a big-endian BOM then BE, matching the JVM). Shared by
;; .getBytes and decode-bytevector (String.).
(define (charset-encode-bv s csname)
(let ((cs (ascii-string-down (if (string? csname) csname (jolt-str-render-one csname)))))
(cond
((or (string=? cs "utf-8") (string=? cs "utf8")) (string->utf8 s))
((member cs '("iso-8859-1" "latin1" "iso8859-1" "us-ascii" "ascii"))
(let* ((n (string-length s)) (bv (make-bytevector n)))
(do ((i 0 (+ i 1))) ((= i n) bv)
(bytevector-u8-set! bv i (bitwise-and (char->integer (string-ref s i)) #xff)))))
((string=? cs "utf-16be") (string->utf16 s (endianness big)))
((string=? cs "utf-16le") (string->utf16 s (endianness little)))
((or (string=? cs "utf-16") (string=? cs "utf16") (string=? cs "unicode"))
(let ((be (string->utf16 s (endianness big))))
(let* ((n (bytevector-length be)) (bv (make-bytevector (+ n 2))))
(bytevector-u8-set! bv 0 #xfe) (bytevector-u8-set! bv 1 #xff)
(bytevector-copy! be 0 bv 2 n) bv)))
((or (string=? cs "utf-32be") (string=? cs "utf-32") (string=? cs "utf32"))
(string->utf32 s (endianness big)))
((string=? cs "utf-32le") (string->utf32 s (endianness little)))
(else (string->utf8 s)))))
(define (jolt-string-method method s rest)
(define (arg n) (list-ref rest n))
(cond
@ -118,16 +142,12 @@
((string=? method "compareTo")
(let ((o (arg 0))) (cond ((string<? s o) -1.0) ((string>? s o) 1.0) (else 0.0))))
((string=? method "getBytes")
;; (.getBytes s) / (.getBytes s charset). UTF-8 default; ISO-8859-1/latin1/
;; ascii encode one byte per char (clj-http-lite's body-encoding path).
(if (null? rest)
(string->utf8 s)
(let ((cn (ascii-string-down (if (string? (arg 0)) (arg 0) (jolt-str-render-one (arg 0))))))
(if (member cn '("iso-8859-1" "latin1" "iso8859-1" "us-ascii" "ascii"))
(let* ((n (string-length s)) (bv (make-bytevector n)))
(do ((i 0 (+ i 1))) ((= i n) bv)
(bytevector-u8-set! bv i (bitwise-and (char->integer (string-ref s i)) #xff))))
(string->utf8 s)))))
;; (.getBytes s) / (.getBytes s charset) -> a jolt byte-array (seqable /
;; countable / alength-able, like (byte-array …)); the JVM returns byte[].
(na-byte-array
(charset-encode-bv s (if (null? rest)
"utf-8"
(if (string? (arg 0)) (arg 0) (jolt-str-render-one (arg 0)))))))
((string=? method "matches") (if (irregex-match (str-irx (arg 0)) s) #t #f))
((string=? method "replaceAll") (irregex-replace/all (str-irx (arg 0)) s (arg 1)))
((string=? method "replaceFirst") (irregex-replace (str-irx (arg 0)) s (arg 1)))
@ -141,6 +161,11 @@
;; String.intern: jolt strings aren't pooled, but value equality holds, so the
;; canonical representation is the string itself.
((string=? method "intern") s)
;; A class token is its canonical-name string, so Class methods land here:
;; (.getName (.getClass x)) / (.getSimpleName …) over the name string.
((or (string=? method "getName") (string=? method "getCanonicalName")) s)
((string=? method "getSimpleName")
(let ((i (str-last-index-of s "."))) (if (>= i 0) (substring s (+ i 1) (string-length s)) s)))
(else (error #f (string-append "No method " method " for value")))))
;; --- clojure.core str-* primitives (the substrate clojure.string.clj calls) ---